@@ -35,21 +35,20 @@ func main() {
3535}
3636`
3737
38- const goMod = `module compilecheck
39-
40- go 1.19
41-
42- require go.mongodb.org/mongo-driver/v2 v2.1.0
43-
44- replace go.mongodb.org/mongo-driver/v2 => /mongo-go-driver
45- `
46-
4738// goVersions is the list of Go versions to test compilation against.
4839// To run tests for specific version(s), use the -run flag:
4940//
5041// go test -v -run '^TestCompileCheck/go:1.19$'
5142// go test -v -run '^TestCompileCheck/go:1\.(19|20)$'
52- var goVersions = []string {"1.19" , "1.20" , "1.21" , "1.22" , "1.23" , "1.24" , "1.25" }
43+ var goVersions = []string {
44+ "1.19" , // Minimum supported Go version for mongo-driver v2
45+ "1.20" ,
46+ "1.21" ,
47+ "1.22" ,
48+ "1.23" ,
49+ "1.24" ,
50+ "1.25" , // Test suite Go Version
51+ }
5352var architectures = []string {
5453 "386" ,
5554 "amd64" ,
@@ -65,21 +64,56 @@ var architectures = []string{
6564 "s390x" ,
6665}
6766
68- // goBuild constructs a build command that tries GOTOOLCHAIN=goX.Y.0 first, then falls back to goX.Y.
69- func goBuild (ver , workdir string , env , extraFlags []string ) string {
70- baseEnv := "PATH=/usr/local/go/bin:$PATH"
71- envStr := baseEnv
72- if len (env ) > 0 {
73- envStr = strings .Join (env , " " ) + " " + baseEnv
67+ // goExecConfig contains optional configuration for execGo.
68+ type goExecConfig struct {
69+ version string // Optional: Go version to use with GOTOOLCHAIN. If empty, uses default.
70+ env map [string ]string // Optional: Additional environment variables.
71+ }
72+
73+ // execContainer executes a shell command in the container and validates its output.
74+ func execContainer (t * testing.T , c testcontainers.Container , cmd string ) string {
75+ t .Helper ()
76+
77+ exit , out , err := c .Exec (context .Background (), []string {"bash" , "-lc" , cmd })
78+ require .NoError (t , err )
79+
80+ b , err := io .ReadAll (out )
81+ require .NoError (t , err )
82+ require .Equal (t , 0 , exit , "command failed: %s" , b )
83+
84+ s := string (b )
85+ // Strip leading non-printable bytes (some Docker/TTY combos emit these).
86+ for len (s ) > 0 && s [0 ] < 0x20 {
87+ s = s [1 :]
88+ }
89+ return s
90+ }
91+
92+ // execGo runs a Go command, trying GOTOOLCHAIN=goX.Y.0 first, then goX.Y.
93+ func execGo (t * testing.T , c testcontainers.Container , cfg * goExecConfig , args ... string ) string {
94+ t .Helper ()
95+
96+ if cfg == nil {
97+ cfg = & goExecConfig {}
7498 }
7599
76- flags := "-buildvcs=false"
77- if len (extraFlags ) > 0 {
78- flags += " " + strings .Join (extraFlags , " " )
100+ envParts := []string {"PATH=/usr/local/go/bin:$PATH" }
101+ for k , v := range cfg .env {
102+ envParts = append (envParts , fmt .Sprintf ("%s=%s" , k , v ))
103+ }
104+ envStr := strings .Join (envParts , " " )
105+ goArgs := strings .Join (args , " " )
106+
107+ var cmd string
108+ if cfg .version != "" {
109+ primaryCmd := fmt .Sprintf ("%s GOTOOLCHAIN=go%s.0 go %s 2>&1" , envStr , cfg .version , goArgs )
110+ fallbackCmd := fmt .Sprintf ("%s GOTOOLCHAIN=go%s go %s 2>&1" , envStr , cfg .version , goArgs )
111+ cmd = fmt .Sprintf ("%s || %s" , primaryCmd , fallbackCmd )
112+ } else {
113+ cmd = fmt .Sprintf ("%s go %s 2>&1" , envStr , goArgs )
79114 }
80115
81- return fmt .Sprintf ("cd %s && %s GOTOOLCHAIN=go%s.0 go build %s -o /dev/null main.go 2>&1 || %s GOTOOLCHAIN=go%s go build %s -o /dev/null main.go 2>&1" ,
82- workdir , envStr , ver , flags , envStr , ver , flags )
116+ return execContainer (t , c , cmd )
83117}
84118
85119func TestCompileCheck (t * testing.T ) {
@@ -95,6 +129,13 @@ func TestCompileCheck(t *testing.T) {
95129 Dockerfile : "Dockerfile" ,
96130 PrintBuildLog : true ,
97131 },
132+ Files : []testcontainers.ContainerFile {
133+ {
134+ Reader : strings .NewReader (mainGo ),
135+ ContainerFilePath : "/workspace/main.go" ,
136+ FileMode : 0o644 ,
137+ },
138+ },
98139 Entrypoint : []string {"tail" , "-f" , "/dev/null" },
99140 WorkingDir : "/workspace" ,
100141 }
@@ -108,115 +149,56 @@ func TestCompileCheck(t *testing.T) {
108149 require .NoError (t , container .Terminate (context .Background ()))
109150 })
110151
111- // Write main.go into the container.
112- exitCode , outputReader , err := container .Exec (context .Background (), []string {"sh" , "-c" , fmt .Sprintf ("cat > /workspace/main.go << 'GOFILE'\n %s\n GOFILE" , mainGo )})
113- require .NoError (t , err )
152+ // Initialize Go module and download dependencies using the test suite Go version.
153+ execGo (t , container , & goExecConfig {version : "1.25" }, "mod" , "init" , "compilecheck" )
154+ execGo (t , container , nil , "mod" , "edit" , "-replace=go.mongodb.org/mongo-driver/v2=/mongo-go-driver" )
155+ execGo (t , container , & goExecConfig {version : "1.25" }, "mod" , "tidy" )
114156
115- output , err := io .ReadAll (outputReader )
116- require .NoError (t , err )
117- require .Equal (t , 0 , exitCode , "failed to write main.go: %s" , output )
118-
119- // Write go.mod into the container.
120- exitCode , outputReader , err = container .Exec (context .Background (), []string {"sh" , "-c" , fmt .Sprintf ("cat > /workspace/go.mod << 'GOMOD'\n %s\n GOMOD" , goMod )})
121- require .NoError (t , err )
122-
123- output , err = io .ReadAll (outputReader )
124- require .NoError (t , err )
125- require .Equal (t , 0 , exitCode , "failed to write go.mod: %s" , output )
126-
127- exitCode , outputReader , err = container .Exec (context .Background (), []string {"sh" , "-c" , "cd /workspace && PATH=/usr/local/go/bin:$PATH go mod tidy 2>&1" })
128- require .NoError (t , err )
129-
130- output , err = io .ReadAll (outputReader )
131- require .NoError (t , err )
132- require .Equal (t , 0 , exitCode , "failed to tidy dependencies: %s" , output )
157+ // Set minimum Go version to what the driver claims (first version in our test list).
158+ execGo (t , container , nil , "mod" , "edit" , "-go=" + goVersions [0 ])
133159
134160 for _ , ver := range goVersions {
135161 ver := ver // capture
136162 t .Run ("go:" + ver , func (t * testing.T ) {
137163 t .Parallel ()
138164
139- t .Cleanup (func () {
140- t .Logf ("compilation checks passed for Go ver %s" , ver )
141- })
142-
143- // Each version gets its own workspace to avoid conflicts when running in parallel.
144- workspace := fmt .Sprintf ("/workspace-%s" , ver )
145-
146- setupCmd := fmt .Sprintf ("mkdir -p %[1]s && cp /workspace/main.go /workspace/go.mod /workspace/go.sum %[1]s/" , workspace )
147- exitCode , outputReader , err := container .Exec (context .Background (), []string {"sh" , "-c" , setupCmd })
148- require .NoError (t , err )
149-
150- output , err := io .ReadAll (outputReader )
151- require .NoError (t , err )
152- require .Equal (t , 0 , exitCode , "failed to setup workspace: %s" , output )
153-
154- cmd := fmt .Sprintf ("PATH=/usr/local/go/bin:$PATH GOTOOLCHAIN=go%[1]s.0+auto go version || PATH=/usr/local/go/bin:$PATH GOTOOLCHAIN=go%[1]s go version" , ver )
165+ versionCfg := & goExecConfig {version : ver }
155166
156- exit , out , err := container .Exec (context .Background (), []string {"bash" , "-lc" , cmd })
157- require .NoError (t , err )
167+ // Verify the Go version is available.
168+ versionOutput := execGo (t , container , versionCfg , "version" )
169+ require .Contains (t , versionOutput , "go" + ver , "unexpected go version: %s" , versionOutput )
158170
159- b , err := io .ReadAll (out )
160-
161- require .NoError (t , err )
162- require .Equal (t , 0 , exit , "go version failed: %s" , b )
163- require .Contains (t , string (b ), "go" + ver , "unexpected go version: %s" , b )
164-
165- // Standard build.
166- exitCode , outputReader , err = container .Exec (context .Background (), []string {
167- "sh" , "-c" , goBuild (ver , workspace , nil , nil ),
168- })
169- require .NoError (t , err )
170-
171- output , err = io .ReadAll (outputReader )
172- require .NoError (t , err )
173-
174- require .Equal (t , 0 , exitCode , "standard build failed: %s" , output )
171+ execGo (t , container , versionCfg , "build" , "-buildvcs=false" , "-o" , "/dev/null" , "main.go" )
175172
176173 // Dynamic linking build.
177- exitCode , outputReader , err = container .Exec (context .Background (), []string {
178- "sh" , "-c" , goBuild (ver , workspace , nil , []string {"-buildmode=plugin" }),
179- })
180- require .NoError (t , err )
181-
182- output , err = io .ReadAll (outputReader )
183- require .NoError (t , err )
184-
185- require .Equal (t , 0 , exitCode , "dynamic linking build failed: %s" , output )
174+ execGo (t , container , versionCfg , "build" , "-buildvcs=false" , "-buildmode=plugin" , "-o" , "/dev/null" , "main.go" )
186175
187176 // Build with build tags.
188- cgoEnv := []string {
189- "PKG_CONFIG_PATH=/root/install/libmongocrypt/lib/pkgconfig" ,
190- "CGO_CFLAGS='-I/root/install/libmongocrypt/include'" ,
191- "CGO_LDFLAGS='-L/root/install/libmongocrypt/lib -Wl,-rpath,/root/install/libmongocrypt/lib'" ,
192- }
193- exitCode , outputReader , err = container .Exec (context .Background (), []string {
194- "sh" , "-c" , goBuild (ver , workspace , cgoEnv , []string {"-tags=cse,gssapi,mongointernal" }),
195- })
196- require .NoError (t , err )
197-
198- output , err = io .ReadAll (outputReader )
199- require .NoError (t , err )
200-
201- require .Equal (t , 0 , exitCode , "build with build tags failed: %s" , output )
177+ execGo (t , container , & goExecConfig {
178+ version : ver ,
179+ env : map [string ]string {
180+ "PKG_CONFIG_PATH" : "/root/install/libmongocrypt/lib/pkgconfig" ,
181+ "CGO_CFLAGS" : "'-I/root/install/libmongocrypt/include'" ,
182+ "CGO_LDFLAGS" : "'-L/root/install/libmongocrypt/lib -Wl,-rpath,/root/install/libmongocrypt/lib'" ,
183+ },
184+ }, "build" , "-buildvcs=false" , "-tags=cse,gssapi,mongointernal" , "-o" , "/dev/null" , "main.go" )
202185
203186 // Build for each architecture.
204187 for _ , architecture := range architectures {
205188 architecture := architecture // capture
206189 t .Run ("arch:" + architecture , func (t * testing.T ) {
207190 t .Parallel ()
208191
209- archEnv := []string {"GOOS=linux" , "GOARCH=" + architecture }
210- exitCode , outputReader , err := container .Exec (
211- context .Background (),
212- []string {"sh" , "-c" , goBuild (ver , workspace , archEnv , nil )},
213- )
214- require .NoError (t , err )
215-
216- output , err := io .ReadAll (outputReader )
217- require .NoError (t , err )
192+ // Standard build.
193+ execGo (t , container , & goExecConfig {
194+ version : ver ,
195+ env : map [string ]string {
196+ "GOOS" : "linux" ,
197+ "GOARCH" : architecture ,
198+ },
199+ }, "build" , "-buildvcs=false" , "-o" , "/dev/null" , "main.go" )
218200
219- require . Equal ( t , 0 , exitCode , "build failed for architecture %s: %s" , architecture , output )
201+ t . Logf ( "compilation checks passed for go%s on %s" , ver , architecture )
220202 })
221203 }
222204 })
0 commit comments