github.com/yogeshkumararora/slsa-github-generator@v1.10.1-0.20240520161934-11278bd5afb4/internal/builders/go/main_test.go (about) 1 // Copyright 2023 SLSA Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package main 16 17 import ( 18 "bufio" 19 "errors" 20 "os" 21 "os/exec" 22 "path/filepath" 23 "regexp" 24 "testing" 25 26 "github.com/google/go-cmp/cmp" 27 "github.com/google/go-cmp/cmp/cmpopts" 28 29 "github.com/yogeshkumararora/slsa-github-generator/internal/builders/go/pkg" 30 "github.com/yogeshkumararora/slsa-github-generator/internal/utils" 31 ) 32 33 func checkWorkingDir(t *testing.T, wd, expected string) { 34 var expectedWd string 35 var err error 36 if expected != "" { 37 expectedWd, err = filepath.Abs(expected) 38 if err != nil { 39 t.Errorf("Abs: %v", err) 40 } 41 } else { 42 expectedWd, err = os.Getwd() 43 if err != nil { 44 t.Errorf("Getwd: %v", err) 45 } 46 } 47 48 if expectedWd != wd { 49 t.Errorf(cmp.Diff(wd, expectedWd)) 50 } 51 } 52 53 func errInvalidDirectoryFunc(t *testing.T, got error) { 54 want := pkg.ErrInvalidDirectory 55 if !errors.Is(got, want) { 56 t.Fatalf("unexpected error: %v", cmp.Diff(got, want, cmpopts.EquateErrors())) 57 } 58 } 59 60 func errUnsupportedVersionFunc(t *testing.T, got error) { 61 want := pkg.ErrUnsupportedVersion 62 if !errors.Is(got, want) { 63 t.Fatalf("unexpected error: %v", cmp.Diff(got, want, cmpopts.EquateErrors())) 64 } 65 } 66 67 func errInvalidEnvironmentVariableFunc(t *testing.T, got error) { 68 want := pkg.ErrInvalidEnvironmentVariable 69 if !errors.Is(got, want) { 70 t.Fatalf("unexpected error: %v", cmp.Diff(got, want, cmpopts.EquateErrors())) 71 } 72 } 73 74 func Test_runBuild(t *testing.T) { 75 tests := []struct { 76 subject string 77 name string 78 config string 79 evalEnvs string 80 workingDir string 81 err func(*testing.T, error) 82 commands []string 83 envs []string 84 }{ 85 { 86 name: "two ldflags", 87 subject: "binary-linux-amd64", 88 config: "./testdata/two-ldflags.yml", 89 evalEnvs: "VERSION_LDFLAGS:bla, ELSE:else", 90 commands: []string{ 91 "-trimpath", 92 "-tags=netgo", 93 "-ldflags=bla something-else", 94 "-o", 95 "binary-linux-amd64", 96 }, 97 envs: []string{ 98 "GOOS=linux", 99 "GOARCH=amd64", 100 "GO111MODULE=on", 101 "CGO_ENABLED=0", 102 }, 103 }, 104 { 105 name: "two ldflags empty env", 106 subject: "binary-linux-amd64", 107 config: "./testdata/two-ldflags-emptyenv.yml", 108 evalEnvs: "VERSION_LDFLAGS:bla, ELSE:else", 109 commands: []string{ 110 "-trimpath", 111 "-tags=netgo", 112 "-ldflags=bla something-else", 113 "-o", 114 "binary-linux-amd64", 115 }, 116 envs: []string{ 117 "GOOS=linux", 118 "GOARCH=amd64", 119 }, 120 }, 121 { 122 name: "two ldflags no env", 123 subject: "binary-linux-amd64", 124 config: "./testdata/two-ldflags-noenv.yml", 125 evalEnvs: "VERSION_LDFLAGS:bla, ELSE:else", 126 commands: []string{ 127 "-trimpath", 128 "-tags=netgo", 129 "-ldflags=bla something-else", 130 "-o", 131 "binary-linux-amd64", 132 }, 133 envs: []string{ 134 "GOOS=linux", 135 "GOARCH=amd64", 136 }, 137 }, 138 { 139 name: "two ldflags empty flags", 140 subject: "binary-linux-amd64", 141 config: "./testdata/two-ldflags-emptyflags.yml", 142 evalEnvs: "VERSION_LDFLAGS:bla, ELSE:else", 143 commands: []string{ 144 "-ldflags=bla something-else", 145 "-o", 146 "binary-linux-amd64", 147 }, 148 envs: []string{ 149 "GOOS=linux", 150 "GOARCH=amd64", 151 "GO111MODULE=on", 152 "CGO_ENABLED=0", 153 }, 154 }, 155 { 156 name: "two ldflags no flags", 157 subject: "binary-linux-amd64", 158 config: "./testdata/two-ldflags-noflags.yml", 159 evalEnvs: "VERSION_LDFLAGS:bla, ELSE:else", 160 commands: []string{ 161 "-ldflags=bla something-else", 162 "-o", 163 "binary-linux-amd64", 164 }, 165 envs: []string{ 166 "GOOS=linux", 167 "GOARCH=amd64", 168 "GO111MODULE=on", 169 "CGO_ENABLED=0", 170 }, 171 }, 172 { 173 name: "one ldflags", 174 subject: "binary-linux-amd64", 175 config: "./testdata/one-ldflags.yml", 176 evalEnvs: "VERSION_LDFLAGS:bla, ELSE:else", 177 commands: []string{ 178 "-trimpath", 179 "-tags=netgo", 180 "-ldflags=something-else", 181 "-o", 182 "binary-linux-amd64", 183 }, 184 envs: []string{ 185 "GOOS=linux", 186 "GOARCH=amd64", 187 "GO111MODULE=on", 188 "CGO_ENABLED=0", 189 }, 190 }, 191 { 192 name: "no ldflags", 193 subject: "binary-linux-amd64", 194 config: "./testdata/two-ldflags-noldflags.yml", 195 evalEnvs: "VERSION_LDFLAGS:bla, ELSE:else", 196 commands: []string{ 197 "-trimpath", 198 "-tags=netgo", 199 "-o", 200 "binary-linux-amd64", 201 }, 202 envs: []string{ 203 "GOOS=linux", 204 "GOARCH=amd64", 205 "GO111MODULE=on", 206 "CGO_ENABLED=0", 207 }, 208 }, 209 { 210 name: "empty ldflags", 211 subject: "binary-linux-amd64", 212 config: "./testdata/emptyldflags.yml", 213 evalEnvs: "VERSION_LDFLAGS:bla, ELSE:else", 214 commands: []string{ 215 "-trimpath", 216 "-tags=netgo", 217 "-o", 218 "binary-linux-amd64", 219 }, 220 envs: []string{ 221 "GOOS=linux", 222 "GOARCH=amd64", 223 "GO111MODULE=on", 224 "CGO_ENABLED=0", 225 }, 226 }, 227 { 228 name: "valid main", 229 subject: "binary-linux-amd64", 230 config: "./testdata/valid-main.yml", 231 evalEnvs: "VERSION_LDFLAGS:bla, ELSE:else", 232 commands: []string{ 233 "-trimpath", 234 "-tags=netgo", 235 "-ldflags=bla something-else", 236 "-o", 237 "binary-linux-amd64", 238 "./path/to/main.go", 239 }, 240 envs: []string{ 241 "GOOS=linux", 242 "GOARCH=amd64", 243 "GO111MODULE=on", 244 "CGO_ENABLED=0", 245 }, 246 }, 247 { 248 name: "valid working dir", 249 subject: "binary-linux-amd64", 250 config: "./testdata/valid-working-dir.yml", 251 evalEnvs: "VERSION_LDFLAGS:bla, ELSE:else", 252 commands: []string{ 253 "-trimpath", 254 "-tags=netgo", 255 "-ldflags=bla something-else", 256 "-o", 257 "binary-linux-amd64", 258 "main.go", 259 }, 260 envs: []string{ 261 "GOOS=linux", 262 "GOARCH=amd64", 263 "GO111MODULE=on", 264 "CGO_ENABLED=0", 265 }, 266 workingDir: "./valid/path/", 267 }, 268 { 269 name: "invalid main", 270 config: "./pkg/testdata/releaser-invalid-main.yml", 271 err: errInvalidDirectoryFunc, 272 }, 273 { 274 name: "missing version", 275 config: "./pkg/testdata/releaser-noversion.yml", 276 err: errUnsupportedVersionFunc, 277 }, 278 { 279 name: "invalid version", 280 config: "./pkg/testdata/releaser-invalid-version.yml", 281 err: errUnsupportedVersionFunc, 282 }, 283 { 284 name: "invalid envs", 285 config: "./pkg/testdata/releaser-invalid-envs.yml", 286 err: errInvalidEnvironmentVariableFunc, 287 }, 288 { 289 name: "invalid path", 290 config: "../pkg/testdata/releaser-invalid-main.yml", 291 err: errInvalidDirectoryFunc, 292 }, 293 { 294 name: "invalid dir path", 295 config: "../pkg/testdata/releaser-invalid-dir.yml", 296 err: errInvalidDirectoryFunc, 297 }, 298 } 299 300 for _, tt := range tests { 301 tt := tt // Re-initializing variable so it is not changed while executing the closure below 302 t.Run(tt.name, func(t *testing.T) { 303 // *** WARNING: do not enable t.Parallel(), because we're writing to environment variables ***. 304 file, err := os.CreateTemp("", "") 305 if err != nil { 306 t.Fatalf("unable to create a temp env file: %s", err) 307 } 308 defer os.Remove(file.Name()) 309 // http://craigwickesser.com/2015/01/capture-stdout-in-go/ 310 311 t.Setenv("GITHUB_OUTPUT", file.Name()) 312 313 err = runBuild(true, 314 tt.config, 315 tt.evalEnvs) 316 317 if tt.err != nil { 318 tt.err(t, err) 319 } 320 321 if err != nil { 322 return 323 } 324 325 if ret, err := file.Seek(0, 0); ret != 0 || err != nil { 326 t.Errorf("seek to zero") 327 } 328 cmd, env, subject, wd, err := extract(file) 329 if err != nil { 330 t.Errorf("extract: %v", err) 331 } 332 333 goc, err := exec.LookPath("go") 334 if err != nil { 335 t.Errorf("exec.LookPath: %v", err) 336 } 337 338 if !cmp.Equal(subject, tt.subject) { 339 t.Errorf(cmp.Diff(subject, tt.subject)) 340 } 341 342 commands := append([]string{goc, "build", "-mod=vendor"}, tt.commands...) 343 if !cmp.Equal(cmd, commands) { 344 t.Errorf(cmp.Diff(cmd, commands)) 345 } 346 347 checkWorkingDir(t, wd, tt.workingDir) 348 349 sorted := cmpopts.SortSlices(func(a, b string) bool { return a < b }) 350 if !cmp.Equal(env, tt.envs, sorted) { 351 t.Errorf(cmp.Diff(env, tt.envs)) 352 } 353 }) 354 } 355 } 356 357 func extract(file *os.File) ([]string, []string, string, string, error) { 358 rsubject := regexp.MustCompile(`^go-binary-name=(.*)$`) 359 rcmd := regexp.MustCompile(`^go-command=(.*)$`) 360 renv := regexp.MustCompile(`^go-env=(.*)$`) 361 rwd := regexp.MustCompile(`^go-working-dir=(.*)$`) 362 var subject string 363 var scmd string 364 var senv string 365 var wd string 366 367 scanner := bufio.NewScanner(file) 368 for scanner.Scan() { 369 n := rsubject.FindStringSubmatch(scanner.Text()) 370 if len(n) > 1 { 371 subject = n[1] 372 } 373 374 c := rcmd.FindStringSubmatch(scanner.Text()) 375 if len(c) > 1 { 376 scmd = c[1] 377 } 378 379 e := renv.FindStringSubmatch(scanner.Text()) 380 if len(e) > 1 { 381 senv = e[1] 382 } 383 384 w := rwd.FindStringSubmatch(scanner.Text()) 385 if len(w) > 1 { 386 wd = w[1] 387 } 388 389 if subject != "" && scmd != "" && senv != "" && wd != "" { 390 break 391 } 392 } 393 if err := scanner.Err(); err != nil { 394 return []string{}, []string{}, "", "", err 395 } 396 397 cmd, err := utils.UnmarshalList(scmd) 398 if err != nil { 399 return []string{}, []string{}, "", "", err 400 } 401 402 env, err := utils.UnmarshalList(senv) 403 if err != nil { 404 return []string{}, []string{}, "", "", err 405 } 406 407 return cmd, env, subject, wd, nil 408 }