github.com/joshdk/godel@v0.0.0-20170529232908-862138a45aee/apps/distgo/cmd/build/build_test.go (about) 1 // Copyright 2016 Palantir Technologies, Inc. 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 build_test 16 17 import ( 18 "bytes" 19 "fmt" 20 "io/ioutil" 21 "os" 22 "os/exec" 23 "path" 24 "reflect" 25 "regexp" 26 "strings" 27 "testing" 28 29 "github.com/nmiyake/pkg/dirs" 30 "github.com/palantir/pkg/pkgpath" 31 "github.com/stretchr/testify/assert" 32 "github.com/stretchr/testify/require" 33 34 "github.com/palantir/godel/apps/distgo/cmd" 35 "github.com/palantir/godel/apps/distgo/cmd/build" 36 "github.com/palantir/godel/apps/distgo/params" 37 "github.com/palantir/godel/apps/distgo/pkg/binspec" 38 "github.com/palantir/godel/apps/distgo/pkg/git" 39 "github.com/palantir/godel/apps/distgo/pkg/git/gittest" 40 "github.com/palantir/godel/apps/distgo/pkg/osarch" 41 ) 42 43 const ( 44 testMain = `package main 45 46 import "fmt" 47 48 var testVersionVar = "defaultVersion" 49 50 func main() { 51 fmt.Println(testVersionVar) 52 } 53 ` 54 testCMain = `package main 55 56 import "C" 57 import "fmt" 58 59 func main() { 60 fmt.Println("C") 61 }` 62 testVersionValue = "1.0.1" 63 testBuildScript = `package main 64 65 import ( 66 "fmt" 67 "./dependency" // written by the build script 68 ) 69 70 func main() { 71 fmt.Println(dependency.V) 72 } 73 ` 74 longCompileMain = `package main 75 76 import ( 77 "encoding/json" 78 "net/http" 79 ) 80 81 func main() { 82 http.Get("") 83 json.Marshal("") 84 } 85 ` 86 ) 87 88 func TestBuildAll(t *testing.T) { 89 tmp, cleanup, err := dirs.TempDir("", "") 90 defer cleanup() 91 require.NoError(t, err) 92 93 for i, currCase := range []struct { 94 productName string 95 mainFileContent string 96 mainFilePath string 97 params params.Product 98 wantError bool 99 runExecutable bool 100 wantOutput string 101 }{ 102 { 103 productName: "randomProduct", 104 mainFileContent: testMain, 105 mainFilePath: "main.go", 106 params: params.Product{ 107 Build: params.Build{ 108 MainPkg: "./.", 109 VersionVar: "main.testVersionVar", 110 OSArchs: []osarch.OSArch{ 111 osarch.Current(), 112 }, 113 }, 114 }, 115 runExecutable: true, 116 wantOutput: testVersionValue + ".dirty", 117 }, 118 // building project that requires CGo succeeds if "CGO_ENABLED" environment variable is set to 1 119 { 120 productName: "CProduct", 121 mainFileContent: testCMain, 122 mainFilePath: "main.go", 123 params: params.Product{ 124 Build: params.Build{ 125 MainPkg: "./.", 126 Environment: map[string]string{ 127 "CGO_ENABLED": "1", 128 }, 129 OSArchs: []osarch.OSArch{ 130 osarch.Current(), 131 }, 132 }, 133 }, 134 runExecutable: true, 135 wantOutput: "C", 136 }, 137 // building project that requires CGo fails if "CGO_ENABLED" environment variable is set to 0 138 { 139 productName: "CProduct", 140 mainFileContent: testCMain, 141 mainFilePath: "main.go", 142 params: params.Product{ 143 Build: params.Build{ 144 MainPkg: "./.", 145 Environment: map[string]string{ 146 "CGO_ENABLED": "0", 147 }, 148 OSArchs: []osarch.OSArch{ 149 osarch.Current(), 150 }, 151 }, 152 }, 153 wantError: true, 154 }, 155 { 156 productName: "preBuildScript", 157 mainFileContent: testBuildScript, 158 mainFilePath: "main.go", 159 params: params.Product{ 160 Build: params.Build{ 161 Script: "" + 162 "mkdir dependency\n" + 163 "echo 'package dependency\n\nvar V = `success`\n' > dependency/lib.go\n", 164 MainPkg: "./.", 165 OSArchs: []osarch.OSArch{ 166 osarch.Current(), 167 }, 168 }, 169 }, 170 wantOutput: "success", 171 }, 172 { 173 productName: "customBuildScriptProduct", 174 mainFileContent: testMain, 175 mainFilePath: "main.go", 176 params: params.Product{ 177 Build: params.Build{ 178 MainPkg: "./.", 179 BuildArgsScript: `set -eu pipefail 180 VALUE="foo bar" 181 echo "-ldflags" 182 echo "-X \"main.testVersionVar=$VALUE\""`, 183 OSArchs: []osarch.OSArch{ 184 osarch.Current(), 185 }, 186 }, 187 }, 188 runExecutable: true, 189 wantOutput: "foo bar", 190 }, 191 { 192 productName: "foo", 193 mainFileContent: testMain, 194 mainFilePath: "foo/main.go", 195 params: params.Product{ 196 Build: params.Build{ 197 MainPkg: "./foo", 198 OSArchs: []osarch.OSArch{ 199 { 200 OS: "darwin", 201 Arch: "amd64", 202 }, 203 { 204 OS: "linux", 205 Arch: "amd64", 206 }, 207 { 208 OS: "windows", 209 Arch: "amd64", 210 }, 211 }, 212 }, 213 }, 214 wantOutput: "defaultVersion", 215 }, 216 } { 217 currTmpDir, err := ioutil.TempDir(tmp, "") 218 require.NoError(t, err) 219 220 gittest.InitGitDir(t, currTmpDir) 221 gittest.CreateGitTag(t, currTmpDir, testVersionValue) 222 223 mainFilePath := path.Join(currTmpDir, currCase.mainFilePath) 224 225 err = os.MkdirAll(path.Dir(mainFilePath), 0755) 226 require.NoError(t, err) 227 228 err = ioutil.WriteFile(mainFilePath, []byte(currCase.mainFileContent), 0644) 229 require.NoError(t, err) 230 231 binDir := path.Join(currTmpDir, "bin") 232 err = os.Mkdir(binDir, 0755) 233 require.NoError(t, err) 234 235 pkgPath, err := pkgpath.NewAbsPkgPath(path.Dir(mainFilePath)).Rel(currTmpDir) 236 require.NoError(t, err) 237 238 spec := binspec.New(currCase.params.Build.OSArchs, path.Base(pkgPath)) 239 err = spec.CreateDirectoryStructure(binDir, nil, false) 240 require.NoError(t, err) 241 242 gitProductInfo, err := git.NewProjectInfo(currTmpDir) 243 require.NoError(t, err) 244 245 buildSpec := params.NewProductBuildSpec( 246 currTmpDir, 247 currCase.productName, 248 gitProductInfo, 249 currCase.params, 250 params.Project{ 251 BuildOutputDir: "bin", 252 }, 253 ) 254 255 foundExecForCurrOsArch := false 256 257 err = build.Run([]params.ProductBuildSpec{buildSpec}, nil, build.Context{ 258 Parallel: false, 259 }, ioutil.Discard) 260 261 if currCase.wantError { 262 assert.Error(t, err, fmt.Sprintf("Case %d", i)) 263 } else { 264 assert.NoError(t, err, "Case %d", i) 265 266 artifactPaths := build.ArtifactPaths(buildSpec) 267 for _, currOSArch := range currCase.params.Build.OSArchs { 268 pathToCurrExecutable, ok := artifactPaths[currOSArch] 269 require.True(t, ok, "Case %d: could not find path for %s for %s", buildSpec.ProductName, currOSArch.String()) 270 fileInfo, err := os.Stat(pathToCurrExecutable) 271 require.NoError(t, err, "Case %d", i) 272 assert.False(t, fileInfo.IsDir()) 273 274 if reflect.DeepEqual(currOSArch, osarch.Current()) { 275 foundExecForCurrOsArch = true 276 output, err := exec.Command(pathToCurrExecutable).Output() 277 require.NoError(t, err) 278 assert.Equal(t, currCase.wantOutput, strings.TrimSpace(string(output)), "Case %d", i) 279 } 280 } 281 282 if currCase.runExecutable { 283 assert.True(t, foundExecForCurrOsArch, "Case %d: executable for current os/arch (%v) not found in %v", osarch.Current(), currCase.params.Build.OSArchs) 284 } 285 } 286 } 287 } 288 289 func TestBuildOnlyDistinctSpecs(t *testing.T) { 290 tmp, cleanup, err := dirs.TempDir("", "") 291 defer cleanup() 292 require.NoError(t, err) 293 294 mainFilePath := path.Join(tmp, "foo/main.go") 295 err = os.MkdirAll(path.Dir(mainFilePath), 0755) 296 require.NoError(t, err) 297 err = ioutil.WriteFile(mainFilePath, []byte(testMain), 0644) 298 require.NoError(t, err) 299 300 buildSpec := params.NewProductBuildSpec( 301 tmp, 302 "foo", 303 git.ProjectInfo{}, 304 params.Product{ 305 Build: params.Build{ 306 MainPkg: "./foo", 307 }, 308 }, 309 params.Project{ 310 BuildOutputDir: "bin", 311 }, 312 ) 313 314 buf := &bytes.Buffer{} 315 err = build.Run([]params.ProductBuildSpec{buildSpec, buildSpec}, nil, build.Context{ 316 Parallel: false, 317 }, buf) 318 require.NoError(t, err) 319 320 assert.Equal(t, 1, strings.Count(buf.String(), "Finished building foo")) 321 } 322 323 func TestBuildOnlySpecifiedOSArchs(t *testing.T) { 324 tmp, cleanup, err := dirs.TempDir("", "") 325 defer cleanup() 326 require.NoError(t, err) 327 328 mainFilePath := path.Join(tmp, "foo/main.go") 329 err = os.MkdirAll(path.Dir(mainFilePath), 0755) 330 require.NoError(t, err) 331 err = ioutil.WriteFile(mainFilePath, []byte(testMain), 0644) 332 require.NoError(t, err) 333 334 for i, currCase := range []struct { 335 specOSArchs []osarch.OSArch 336 osArchs []osarch.OSArch 337 want []string 338 notWant []string 339 }{ 340 // empty value for osArchs filter builds all 341 { 342 specOSArchs: []osarch.OSArch{{OS: "darwin", Arch: "amd64"}, {OS: "linux", Arch: "386"}}, 343 osArchs: nil, 344 want: []string{ 345 "Finished building foo for darwin-amd64", 346 "Finished building foo for linux-386", 347 }, 348 }, 349 // if non-empty filter is provided, only values matching filter are built 350 { 351 specOSArchs: []osarch.OSArch{{OS: "darwin", Arch: "amd64"}, {OS: "linux", Arch: "386"}}, 352 osArchs: []osarch.OSArch{{OS: "linux", Arch: "386"}}, 353 want: []string{ 354 "Finished building foo for linux-386", 355 }, 356 notWant: []string{ 357 "Finished building foo for darwin-amd64", 358 }, 359 }, 360 // if no OS/arch values match filter, nothing is built 361 { 362 specOSArchs: []osarch.OSArch{{OS: "darwin", Arch: "amd64"}, {OS: "linux", Arch: "386"}}, 363 osArchs: []osarch.OSArch{{OS: "windows", Arch: "386"}}, 364 want: []string{ 365 "$^", 366 }, 367 }, 368 } { 369 buildSpec := params.NewProductBuildSpec( 370 tmp, 371 "foo", 372 git.ProjectInfo{}, 373 params.Product{ 374 Build: params.Build{ 375 MainPkg: "./foo", 376 OSArchs: currCase.specOSArchs, 377 }, 378 }, 379 params.Project{ 380 BuildOutputDir: "bin", 381 }, 382 ) 383 384 buf := &bytes.Buffer{} 385 err = build.Run([]params.ProductBuildSpec{buildSpec}, cmd.OSArchFilter(currCase.osArchs), build.Context{ 386 Parallel: false, 387 }, buf) 388 require.NoError(t, err) 389 390 for _, want := range currCase.want { 391 assert.Regexp(t, regexp.MustCompile(want), buf.String(), "Case %d", i) 392 } 393 394 for _, notWant := range currCase.notWant { 395 assert.NotRegexp(t, regexp.MustCompile(notWant), buf.String(), "Case %d", i) 396 } 397 } 398 } 399 400 func TestBuildErrorMessage(t *testing.T) { 401 tmp, cleanup, err := dirs.TempDir(".", "") 402 defer cleanup() 403 require.NoError(t, err) 404 405 mainFilePath := path.Join(tmp, "foo/main.go") 406 err = os.MkdirAll(path.Dir(mainFilePath), 0755) 407 require.NoError(t, err) 408 err = ioutil.WriteFile(mainFilePath, []byte(`package main; asdfa`), 0644) 409 require.NoError(t, err) 410 411 buildSpec := params.NewProductBuildSpec( 412 tmp, 413 "foo", 414 git.ProjectInfo{}, 415 params.Product{ 416 Build: params.Build{ 417 MainPkg: "./foo", 418 }, 419 }, 420 params.Project{ 421 BuildOutputDir: "bin", 422 }, 423 ) 424 425 want := `(?s)^go install failed: build command \[.+go install ./foo\] run with additional environment variables \[GOOS=.+ GOARCH=.+\] failed with output:.+foo/main.go:1: syntax error: non-declaration statement outside function body$` 426 427 buf := &bytes.Buffer{} 428 err = build.Run([]params.ProductBuildSpec{buildSpec, buildSpec}, nil, build.Context{ 429 Install: true, 430 Parallel: false, 431 }, buf) 432 assert.Regexp(t, want, err.Error()) 433 } 434 435 func TestBuildInstallErrorMessage(t *testing.T) { 436 tmp, cleanup, err := dirs.TempDir(".", "") 437 defer cleanup() 438 require.NoError(t, err) 439 440 goRoot, err := dirs.GoRoot() 441 require.NoError(t, err) 442 _, err = os.Stat(goRoot) 443 require.NoError(t, err) 444 445 pkgDir := path.Join(goRoot, "pkg") 446 _, err = os.Stat(pkgDir) 447 require.NoError(t, err) 448 449 osArchPkgDir := path.Join(pkgDir, "dragonfly_amd64") 450 _, err = os.Stat(osArchPkgDir) 451 if os.IsNotExist(err) { 452 // if directory does not exist, attempt to create it (and clean up afterwards) 453 if err := os.Mkdir(osArchPkgDir, 0444); err == nil { 454 defer func() { 455 if err := os.RemoveAll(osArchPkgDir); err != nil { 456 fmt.Printf("Failed to remove directory %v: %v\n", osArchPkgDir, err) 457 } 458 }() 459 } 460 // if creation failed, assume that write permissions do not exist, which is sufficient for the test 461 } 462 463 mainFilePath := path.Join(tmp, "foo/main.go") 464 err = os.MkdirAll(path.Dir(mainFilePath), 0755) 465 require.NoError(t, err) 466 err = ioutil.WriteFile(mainFilePath, []byte(`package main`), 0644) 467 require.NoError(t, err) 468 469 buildSpec := params.NewProductBuildSpec( 470 tmp, 471 "foo", 472 git.ProjectInfo{}, 473 params.Product{ 474 Build: params.Build{ 475 MainPkg: "./foo", 476 OSArchs: []osarch.OSArch{ 477 { 478 OS: "dragonfly", 479 Arch: "amd64", 480 }, 481 }, 482 }, 483 }, 484 params.Project{ 485 BuildOutputDir: "bin", 486 }, 487 ) 488 489 goBinary := "go" 490 if output, err := exec.Command("command", "-v", "go").CombinedOutput(); err == nil { 491 goBinary = strings.TrimSpace(string(output)) 492 } 493 494 want := `(?s)go install failed: failed to install a Go standard library package due to insufficient permissions to create directory.\n` + 495 `This typically means that the standard library for the OS/architectecture combination have not been installed locally and the current user does not have write permissions to GOROOT/pkg.\n` + 496 fmt.Sprintf("Run \"sudo env GOOS=dragonfly GOARCH=amd64 %s install std\" to install the standard packages for this combination as root and then try again.\n", goBinary) + 497 `Full error: build command \[.+/go install ./foo\] run with additional environment variables \[GOOS=dragonfly GOARCH=amd64\] failed with output:\n` + 498 `go install .+: mkdir .+: permission denied$` 499 500 buf := &bytes.Buffer{} 501 err = build.Run([]params.ProductBuildSpec{buildSpec, buildSpec}, nil, build.Context{ 502 Install: true, 503 Parallel: false, 504 }, buf) 505 assert.Regexp(t, want, err.Error()) 506 } 507 508 func TestBuildAllParallel(t *testing.T) { 509 tmp, cleanup, err := dirs.TempDir("", "") 510 defer cleanup() 511 require.NoError(t, err) 512 513 for i, currCase := range []struct { 514 mainFiles map[string]string 515 specs []params.ProductBuildSpec 516 }{ 517 { 518 mainFiles: map[string]string{ 519 "foo/main.go": longCompileMain, 520 "bar/main.go": longCompileMain, 521 }, 522 specs: []params.ProductBuildSpec{ 523 { 524 ProductName: "foo", 525 Product: params.Product{ 526 Build: params.Build{ 527 MainPkg: "./foo", 528 OSArchs: []osarch.OSArch{ 529 { 530 OS: "darwin", 531 Arch: "amd64", 532 }, 533 { 534 OS: "linux", 535 Arch: "amd64", 536 }, 537 }, 538 OutputDir: "build", 539 }, 540 }, 541 VersionInfo: git.ProjectInfo{ 542 Version: "0.1.0", 543 }, 544 }, 545 { 546 ProductName: "bar", 547 Product: params.Product{ 548 Build: params.Build{ 549 MainPkg: "./bar", 550 OSArchs: []osarch.OSArch{ 551 { 552 OS: "darwin", 553 Arch: "amd64", 554 }, 555 { 556 OS: "linux", 557 Arch: "amd64", 558 }, 559 }, 560 OutputDir: "build", 561 }, 562 }, 563 VersionInfo: git.ProjectInfo{ 564 Version: "0.1.0", 565 }, 566 }, 567 }, 568 }, 569 } { 570 currTmpDir, err := ioutil.TempDir(tmp, "") 571 require.NoError(t, err) 572 573 for file, content := range currCase.mainFiles { 574 err := os.MkdirAll(path.Join(currTmpDir, path.Dir(file)), 0755) 575 require.NoError(t, err) 576 err = ioutil.WriteFile(path.Join(currTmpDir, file), []byte(content), 0644) 577 require.NoError(t, err) 578 } 579 580 for i := range currCase.specs { 581 currCase.specs[i].ProjectDir = currTmpDir 582 } 583 584 err = build.Run(currCase.specs, nil, build.Context{ 585 Parallel: true, 586 }, ioutil.Discard) 587 assert.NoError(t, err, "Case %d", i) 588 } 589 }