@@ -12,18 +12,23 @@ package regression
1212import (
1313 "archive/tar"
1414 "compress/gzip"
15+ "database/sql"
1516 "encoding/json"
1617 "fmt"
1718 "io"
19+ "net"
1820 "net/http"
1921 "os"
2022 "os/exec"
2123 "path/filepath"
2224 "runtime"
2325 "sort"
26+ "strconv"
2427 "strings"
2528 "testing"
2629 "time"
30+
31+ _ "github.com/go-sql-driver/mysql"
2732)
2833
2934// baselineBin is the path to the pinned baseline bd binary.
@@ -32,24 +37,33 @@ var baselineBin string
3237// candidateBin is the path to the bd binary built from the current worktree.
3338var candidateBin string
3439
40+ // testDoltServerPort is the port of the isolated Dolt server started by TestMain.
41+ var testDoltServerPort int
42+
3543func TestMain (m * testing.M ) {
3644 if runtime .GOOS == "windows" {
3745 fmt .Fprintln (os .Stderr , "regression tests not yet supported on Windows (zip extraction needed)" )
3846 os .Exit (0 )
3947 }
4048
49+ // Start an isolated Dolt server so regression tests don't pollute
50+ // the production database on port 3307.
51+ cleanupServer := startTestDoltServer ()
52+
4153 tmpDir , err := os .MkdirTemp ("" , "bd-regression-bin-*" )
4254 if err != nil {
4355 fmt .Fprintf (os .Stderr , "creating temp dir: %v\n " , err )
56+ cleanupServer ()
4457 os .Exit (1 )
4558 }
46- defer os .RemoveAll (tmpDir )
4759
4860 // Build candidate from current worktree
4961 candidateBin = filepath .Join (tmpDir , "bd-candidate" )
5062 fmt .Fprintln (os .Stderr , "Building candidate binary..." )
5163 if err := buildCandidate (candidateBin ); err != nil {
5264 fmt .Fprintf (os .Stderr , "building candidate: %v\n " , err )
65+ os .RemoveAll (tmpDir )
66+ cleanupServer ()
5367 os .Exit (1 )
5468 }
5569
@@ -58,11 +72,16 @@ func TestMain(m *testing.M) {
5872 baselineBin , err = getBaseline ()
5973 if err != nil {
6074 fmt .Fprintf (os .Stderr , "getting baseline: %v\n " , err )
75+ os .RemoveAll (tmpDir )
76+ cleanupServer ()
6177 os .Exit (1 )
6278 }
6379
6480 fmt .Fprintf (os .Stderr , "Baseline: %s\n Candidate: %s\n \n " , baselineBin , candidateBin )
65- os .Exit (m .Run ())
81+ code := m .Run ()
82+ os .RemoveAll (tmpDir )
83+ cleanupServer ()
84+ os .Exit (code )
6685}
6786
6887// ---------------------------------------------------------------------------
@@ -242,6 +261,9 @@ func (w *workspace) runEnv() []string {
242261 "BEADS_NO_DAEMON=1" ,
243262 "GIT_CONFIG_NOSYSTEM=1" ,
244263 }
264+ if testDoltServerPort != 0 {
265+ env = append (env , "BEADS_DOLT_PORT=" + strconv .Itoa (testDoltServerPort ))
266+ }
245267 if v := os .Getenv ("TMPDIR" ); v != "" {
246268 env = append (env , "TMPDIR=" + v )
247269 }
@@ -695,3 +717,126 @@ func (w *workspace) tryCreate(args ...string) (string, error) {
695717 w .createdIDs = append (w .createdIDs , id )
696718 return id , nil
697719}
720+
721+ // ---------------------------------------------------------------------------
722+ // Test Dolt server (isolation from production)
723+ // ---------------------------------------------------------------------------
724+
725+ // startTestDoltServer starts a dedicated Dolt SQL server in a temp directory
726+ // on a dynamic port. This prevents regression tests from creating databases on
727+ // the production Dolt server (port 3307).
728+ // Returns a cleanup function that stops the server and removes the temp dir.
729+ func startTestDoltServer () func () {
730+ if _ , err := exec .LookPath ("dolt" ); err != nil {
731+ fmt .Fprintln (os .Stderr , "WARN: dolt not found in PATH; regression tests will be skipped" )
732+ return func () {}
733+ }
734+
735+ tmpDir , err := os .MkdirTemp ("" , "bd-regression-dolt-*" )
736+ if err != nil {
737+ fmt .Fprintf (os .Stderr , "WARN: failed to create test dolt dir: %v\n " , err )
738+ return func () {}
739+ }
740+
741+ dbDir := filepath .Join (tmpDir , "data" )
742+ if err := os .MkdirAll (dbDir , 0755 ); err != nil {
743+ fmt .Fprintf (os .Stderr , "WARN: failed to create test dolt data dir: %v\n " , err )
744+ _ = os .RemoveAll (tmpDir )
745+ return func () {}
746+ }
747+
748+ // Configure dolt user identity (required by dolt init).
749+ doltEnv := append (os .Environ (), "DOLT_ROOT_PATH=" + tmpDir )
750+ for _ , args := range [][]string {
751+ {"dolt" , "config" , "--global" , "--add" , "user.name" , "regression-test" },
752+ {"dolt" , "config" , "--global" , "--add" , "user.email" , "test@regression.test" },
753+ } {
754+ cfgCmd := exec .Command (args [0 ], args [1 :]... )
755+ cfgCmd .Env = doltEnv
756+ if out , err := cfgCmd .CombinedOutput (); err != nil {
757+ fmt .Fprintf (os .Stderr , "WARN: %s failed: %v\n %s\n " , args [1 ], err , out )
758+ _ = os .RemoveAll (tmpDir )
759+ return func () {}
760+ }
761+ }
762+
763+ initCmd := exec .Command ("dolt" , "init" )
764+ initCmd .Dir = dbDir
765+ initCmd .Env = doltEnv
766+ if out , err := initCmd .CombinedOutput (); err != nil {
767+ fmt .Fprintf (os .Stderr , "WARN: dolt init failed for test server: %v\n %s\n " , err , out )
768+ _ = os .RemoveAll (tmpDir )
769+ return func () {}
770+ }
771+
772+ port , err := findFreePort ()
773+ if err != nil {
774+ fmt .Fprintf (os .Stderr , "WARN: failed to find free port for test dolt server: %v\n " , err )
775+ _ = os .RemoveAll (tmpDir )
776+ return func () {}
777+ }
778+
779+ serverCmd := exec .Command ("dolt" , "sql-server" ,
780+ "-H" , "127.0.0.1" ,
781+ "-P" , fmt .Sprintf ("%d" , port ),
782+ "--no-auto-commit" ,
783+ )
784+ serverCmd .Dir = dbDir
785+ serverCmd .Env = doltEnv
786+ if os .Getenv ("BEADS_TEST_DOLT_VERBOSE" ) != "1" {
787+ serverCmd .Stderr = nil
788+ serverCmd .Stdout = nil
789+ }
790+ if err := serverCmd .Start (); err != nil {
791+ fmt .Fprintf (os .Stderr , "WARN: failed to start test dolt server: %v\n " , err )
792+ _ = os .RemoveAll (tmpDir )
793+ return func () {}
794+ }
795+
796+ if ! waitForServer (port , 10 * time .Second ) {
797+ fmt .Fprintf (os .Stderr , "WARN: test dolt server did not become ready on port %d\n " , port )
798+ _ = serverCmd .Process .Kill ()
799+ _ = serverCmd .Wait ()
800+ _ = os .RemoveAll (tmpDir )
801+ return func () {}
802+ }
803+
804+ testDoltServerPort = port
805+ fmt .Fprintf (os .Stderr , "Test Dolt server running on port %d\n " , port )
806+
807+ return func () {
808+ testDoltServerPort = 0
809+ _ = serverCmd .Process .Kill ()
810+ _ = serverCmd .Wait ()
811+ _ = os .RemoveAll (tmpDir )
812+ }
813+ }
814+
815+ // findFreePort finds an available TCP port by binding to :0.
816+ func findFreePort () (int , error ) {
817+ l , err := net .Listen ("tcp" , "127.0.0.1:0" )
818+ if err != nil {
819+ return 0 , err
820+ }
821+ port := l .Addr ().(* net.TCPAddr ).Port
822+ _ = l .Close ()
823+ return port , nil
824+ }
825+
826+ // waitForServer polls until the Dolt server accepts a MySQL connection.
827+ func waitForServer (port int , timeout time.Duration ) bool {
828+ deadline := time .Now ().Add (timeout )
829+ dsn := fmt .Sprintf ("root@tcp(127.0.0.1:%d)/?timeout=1s" , port )
830+ for time .Now ().Before (deadline ) {
831+ db , err := sql .Open ("mysql" , dsn )
832+ if err == nil {
833+ if err := db .Ping (); err == nil {
834+ _ = db .Close ()
835+ return true
836+ }
837+ _ = db .Close ()
838+ }
839+ time .Sleep (200 * time .Millisecond )
840+ }
841+ return false
842+ }
0 commit comments