github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/misc/buildbot/builder/builder.go (about) 1 /* 2 Copyright 2012 The Camlistore Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // The buildbot is Camlistore's continuous builder. 18 // This builder program is started by the master. It then rebuilds 19 // Go 1, GoTip, Camlistore, and runs a battery of tests for Camlistore. 20 // It then sends a report to the master and terminates. 21 // It can also respond to progress requests from the master. 22 package main 23 24 import ( 25 "bufio" 26 "bytes" 27 "crypto/tls" 28 "encoding/json" 29 "errors" 30 "flag" 31 "fmt" 32 "io" 33 "io/ioutil" 34 "log" 35 "net/http" 36 "net/url" 37 "os" 38 "os/exec" 39 "os/signal" 40 "path/filepath" 41 "regexp" 42 "runtime" 43 "strings" 44 "sync" 45 "syscall" 46 "time" 47 48 "camlistore.org/pkg/osutil" 49 ) 50 51 const ( 52 interval = 60 * time.Second // polling frequency 53 warmup = 30 * time.Second // duration before we test if devcam server has started properly 54 ) 55 56 var ( 57 // TODO(mpl): use that one, same as in master. 58 altCamliRevURL = flag.String("camlirevurl", "", "alternative URL to query about the latest camlistore revision hash (e.g camlistore.org/latesthash), to alleviate hitting too often the Camlistore git repo.") 59 arch = flag.String("arch", "", "The arch we report the master(s). Defaults to runtime.GOARCH.") 60 fakeTests = flag.Bool("faketests", false, "Run fast fake tests instead of the real ones, for faster debugging.") 61 help = flag.Bool("h", false, "show this help") 62 host = flag.String("host", "0.0.0.0:8081", "listening hostname and port") 63 masterHosts = flag.String("masterhosts", "localhost:8080", "listening hostname and port of the master bots, i.e where to send the test suite reports. Comma separated list.") 64 ourOS = flag.String("os", "", "The OS we report the master(s). Defaults to runtime.GOOS.") 65 skipGo1Build = flag.Bool("skipgo1build", false, "skip initial go1 build, for debugging and quickly going to the next steps.") 66 verbose = flag.Bool("verbose", false, "print what's going on") 67 skipTLSCheck = flag.Bool("skiptlscheck", false, "accept any certificate presented by server when uploading results.") 68 taskLifespan = flag.Int("timeout", 600, "Lifespan (in seconds) for each task run by this builder, after which the task automatically terminates. 0 or negative means infinite.") 69 ) 70 71 var ( 72 testFile = []string{"AUTHORS", "CONTRIBUTORS"} 73 cacheDir string 74 camliHeadHash string 75 camliRoot string 76 camputCacheDir string 77 client = http.DefaultClient 78 dbg *debugger 79 defaultPATH string 80 go1Dir string 81 goTipDir string 82 goTipHash string 83 84 biSuitelk sync.Mutex 85 currentTestSuite *testSuite 86 currentBiSuite *biTestSuite 87 88 // Process of the camlistore server, so we can kill it when 89 // we get killed ourselves. 90 camliProc *os.Process 91 92 // For "If-Modified-Since" requests asking for progress. 93 // Updated every time a new test task/run is added to the test suite. 94 lastModified time.Time 95 ) 96 97 var devcamBin = filepath.Join("bin", "devcam") 98 var ( 99 hgCloneGo1Cmd = newTask("hg", "clone", "-u", "release", "https://code.google.com/p/go") 100 hgCloneGoTipCmd = newTask("hg", "clone", "-u", "tip", "https://code.google.com/p/go") 101 hgPullCmd = newTask("hg", "pull") 102 hgUpdateCmd = newTask("hg", "update", "-C", "default") 103 hgLogCmd = newTask("hg", "log", "-r", "tip", "--template", "{node}") 104 hgConfigCmd = newTask("hg", "--config", "extensions.purge=", "purge", "--all") 105 gitCloneCmd = newTask("git", "clone", "https://camlistore.googlesource.com/camlistore") 106 gitResetCmd = newTask("git", "reset", "--hard") 107 gitCleanCmd = newTask("git", "clean", "-Xdf") 108 gitPullCmd = newTask("git", "pull") 109 gitRevCmd = newTask("git", "rev-parse", "HEAD") 110 buildGoCmd = newTask("./make.bash") 111 buildCamliCmd = newTask("go", "run", "make.go", "-v") 112 runTestsCmd = newTask(devcamBin, "test") 113 runCamliCmd = newTask(devcamBin, "server", "--wipe", "--mysql") 114 camgetCmd = newTask(devcamBin, "get") 115 camputCmd = newTask(devcamBin, "put", "file", "--permanode", testFile[0]) 116 camputVivifyCmd = newTask(devcamBin, "put", "file", "--vivify", testFile[1]) 117 camputFilenodesCmd = newTask(devcamBin, "put", "file", "--filenodes", "pkg") 118 ) 119 120 func usage() { 121 fmt.Fprintf(os.Stderr, "\t builderBot \n") 122 flag.PrintDefaults() 123 os.Exit(2) 124 } 125 126 type debugger struct { 127 lg *log.Logger 128 } 129 130 func (dbg *debugger) Printf(format string, v ...interface{}) { 131 if dbg != nil && *verbose { 132 dbg.lg.Printf(format, v...) 133 } 134 } 135 136 func (dbg *debugger) Println(v ...interface{}) { 137 if v == nil { 138 return 139 } 140 if dbg != nil && *verbose { 141 dbg.lg.Println(v...) 142 } 143 } 144 145 type task struct { 146 Program string 147 Args []string 148 Start time.Time 149 Duration time.Duration 150 Err string 151 hidden bool 152 } 153 154 func newTask(program string, args ...string) *task { 155 return &task{Program: program, Args: args} 156 } 157 158 // because sometimes we do not want to modify the tsk template 159 // so we make a copy of it 160 func newTaskFrom(tsk *task) *task { 161 return newTask(tsk.Program, tsk.Args...) 162 } 163 164 func (t *task) String() string { 165 return fmt.Sprintf("%v %v", t.Program, t.Args) 166 } 167 168 func (t *task) Error() string { 169 return t.Err 170 } 171 172 func (t *task) run() (string, error) { 173 var err error 174 defer func() { 175 t.Duration = time.Now().Sub(t.Start) 176 if !t.hidden { 177 biSuitelk.Lock() 178 currentTestSuite.addRun(t) 179 biSuitelk.Unlock() 180 } 181 }() 182 dbg.Println(t.String()) 183 cmd := exec.Command(t.Program, t.Args...) 184 var stdout, stderr bytes.Buffer 185 cmd.Stdout = &stdout 186 cmd.Stderr = &stderr 187 t.Start = time.Now() 188 setTaskErr := func() { 189 var sout, serr string 190 if sout = stdout.String(); sout == "" { 191 sout = "(empty)" 192 } 193 if serr = stderr.String(); serr == "" { 194 serr = "(empty)" 195 } 196 t.Err = fmt.Sprintf("Stdout:\n%s\n\nStderr:\n%s", sout, serr) 197 if err != nil { 198 t.Err = fmt.Sprintf("%v\n\n%v", err, t.Err) 199 } 200 } 201 // TODO(mpl, wathiede): make it learn about task durations. 202 errc := make(chan error) 203 go func() { 204 errc <- cmd.Run() 205 }() 206 if *taskLifespan > 0 { 207 select { 208 case <-time.After(time.Duration(*taskLifespan) * time.Second): 209 setTaskErr() 210 t.Err = fmt.Sprintf("%v\n\nTask %q took too long. Giving up after %v seconds.\n", 211 t.Err, t.String(), *taskLifespan) 212 if cmd.Process != nil { 213 if err := cmd.Process.Signal(syscall.SIGTERM); err != nil { 214 dbg.Printf("Could not terminate process for task %q: %v", t.String(), err) 215 } 216 } 217 return "", t 218 case err = <-errc: 219 break 220 } 221 } else { 222 err = <-errc 223 } 224 if err != nil { 225 setTaskErr() 226 return "", t 227 } 228 return stdout.String(), nil 229 } 230 231 type testSuite struct { 232 Run []*task 233 CamliHash string 234 GoHash string 235 Err string 236 Start time.Time 237 IsTip bool 238 } 239 240 func (ts *testSuite) addRun(tsk *task) { 241 if ts == nil { 242 panic("Tried adding a run to a nil testSuite") 243 } 244 if ts.Start.IsZero() && len(ts.Run) == 0 { 245 ts.Start = tsk.Start 246 } 247 if tsk.Err != "" && ts.Err == "" { 248 ts.Err = tsk.Err 249 } 250 ts.Run = append(ts.Run, tsk) 251 lastModified = time.Now() 252 } 253 254 type biTestSuite struct { 255 Local bool 256 Go1 testSuite 257 GoTip testSuite 258 } 259 260 func main() { 261 flag.Usage = usage 262 flag.Parse() 263 if *help { 264 usage() 265 } 266 267 if *skipTLSCheck { 268 tr := &http.Transport{ 269 TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 270 } 271 client = &http.Client{Transport: tr} 272 } 273 274 go handleSignals() 275 http.HandleFunc("/progress", progressHandler) 276 go func() { 277 log.Printf("Now starting to listen on %v", *host) 278 if err := http.ListenAndServe(*host, nil); err != nil { 279 log.Fatalf("Could not start listening on %v: %v", *host, err) 280 } 281 }() 282 setup() 283 284 biSuitelk.Lock() 285 currentBiSuite = &biTestSuite{} 286 biSuitelk.Unlock() 287 for _, isTip := range [2]bool{false, true} { 288 currentTestSuite = &testSuite{ 289 Run: make([]*task, 0, 1), 290 IsTip: isTip, 291 Start: time.Now(), 292 } 293 // We prepare the Go tip tree as soon as in the Go 1 run, so 294 // we can set GoTipHash in the test suite. 295 if !isTip { 296 if err := prepGoTipTree(); err != nil { 297 endOfSuite(err) 298 // If we failed with that in the Go 1 run, we just restart 299 // from scratch instead of trying to cope with it in the Gotip run. 300 // Same for buildGoTip and prepCamliTree. 301 break 302 } 303 } 304 305 biSuitelk.Lock() 306 currentTestSuite.GoHash = goTipHash 307 biSuitelk.Unlock() 308 if isTip && !*fakeTests { 309 if err := buildGoTip(); err != nil { 310 endOfSuite(err) 311 break 312 } 313 } 314 if err := prepCamliTree(isTip); err != nil { 315 endOfSuite(err) 316 break 317 } 318 319 biSuitelk.Lock() 320 currentTestSuite.CamliHash = camliHeadHash 321 biSuitelk.Unlock() 322 restorePATH() 323 goDir := go1Dir 324 if isTip { 325 goDir = goTipDir 326 } 327 switchGo(goDir) 328 if *fakeTests { 329 if err := fakeRun(); err != nil { 330 endOfSuite(err) 331 continue 332 } 333 endOfSuite(nil) 334 if isTip { 335 break 336 } 337 continue 338 } 339 if err := buildCamli(); err != nil { 340 endOfSuite(err) 341 continue 342 } 343 if err := runTests(); err != nil { 344 endOfSuite(err) 345 continue 346 } 347 if err := runCamli(); err != nil { 348 endOfSuite(err) 349 continue 350 } 351 if err := hitCamliUi(); err != nil { 352 endOfSuite(err) 353 continue 354 } 355 doVivify := false 356 if err := camputOne(doVivify); err != nil { 357 endOfSuite(err) 358 continue 359 } 360 doVivify = true 361 if err := camputOne(doVivify); err != nil { 362 endOfSuite(err) 363 continue 364 } 365 if err := camputMany(); err != nil { 366 endOfSuite(err) 367 continue 368 } 369 endOfSuite(nil) 370 } 371 sanitizeRevs() 372 sendReport() 373 } 374 375 func sanitizeRevs() { 376 if currentBiSuite == nil { 377 return 378 } 379 if currentBiSuite.GoTip.Start.IsZero() { 380 return 381 } 382 if currentBiSuite.GoTip.CamliHash == "" && currentBiSuite.Go1.CamliHash == "" { 383 dbg.Printf("CamliHash not set in both Go1 and GoTip test suites") 384 return 385 } 386 if currentBiSuite.GoTip.CamliHash == "" && currentBiSuite.Go1.CamliHash == "" { 387 dbg.Printf("GoHash not set in both Go1 and GoTip test suites") 388 return 389 } 390 if currentBiSuite.GoTip.CamliHash != "" && currentBiSuite.Go1.CamliHash != "" && 391 currentBiSuite.GoTip.CamliHash != currentBiSuite.Go1.CamliHash { 392 panic("CamliHash in GoTip suite and in Go1 suite differ; should not happen.") 393 } 394 if currentBiSuite.GoTip.GoHash != "" && currentBiSuite.Go1.GoHash != "" && 395 currentBiSuite.GoTip.GoHash != currentBiSuite.Go1.GoHash { 396 panic("GoHash in GoTip suite and in Go1 suite differ; should not happen.") 397 } 398 if currentBiSuite.GoTip.GoHash == "" { 399 currentBiSuite.GoTip.GoHash = currentBiSuite.Go1.GoHash 400 } 401 if currentBiSuite.Go1.GoHash == "" { 402 currentBiSuite.Go1.GoHash = currentBiSuite.GoTip.GoHash 403 } 404 if currentBiSuite.GoTip.CamliHash == "" { 405 currentBiSuite.GoTip.CamliHash = currentBiSuite.Go1.CamliHash 406 } 407 if currentBiSuite.Go1.CamliHash == "" { 408 currentBiSuite.Go1.CamliHash = currentBiSuite.GoTip.CamliHash 409 } 410 } 411 412 func endOfSuite(err error) { 413 biSuitelk.Lock() 414 defer biSuitelk.Unlock() 415 if currentTestSuite.IsTip { 416 currentBiSuite.GoTip = *currentTestSuite 417 } else { 418 currentBiSuite.Go1 = *currentTestSuite 419 } 420 killCamli() 421 if err != nil { 422 log.Printf("%v", err) 423 } else { 424 dbg.Println("All good.") 425 } 426 } 427 428 func masterHostsReader(r io.Reader) ([]string, error) { 429 hosts := []string{} 430 scanner := bufio.NewScanner(r) 431 for scanner.Scan() { 432 l := scanner.Text() 433 u, err := url.Parse(l) 434 if err != nil { 435 return nil, err 436 } 437 if u.Host == "" { 438 return nil, fmt.Errorf("URL missing Host: %q", l) 439 } 440 hosts = append(hosts, u.String()) 441 } 442 if err := scanner.Err(); err != nil { 443 return nil, err 444 } 445 return hosts, nil 446 } 447 448 var masterHostsFile = filepath.Join(osutil.CamliConfigDir(), "builderbot-config") 449 450 func loadMasterHosts() error { 451 r, err := os.Open(masterHostsFile) 452 if err != nil { 453 return err 454 } 455 defer r.Close() 456 457 hosts, err := masterHostsReader(r) 458 if err != nil { 459 return err 460 } 461 if *masterHosts != "" { 462 *masterHosts += "," 463 } 464 log.Println("Additional host(s) to send our build reports:", hosts) 465 *masterHosts += strings.Join(hosts, ",") 466 return nil 467 } 468 469 func setup() { 470 var err error 471 defaultPATH = os.Getenv("PATH") 472 if defaultPATH == "" { 473 log.Fatal("PATH not set") 474 } 475 log.SetPrefix("BUILDER: ") 476 dbg = &debugger{log.New(os.Stderr, "BUILDER: ", log.LstdFlags)} 477 478 err = loadMasterHosts() 479 if err != nil { 480 if os.IsNotExist(err) { 481 log.Printf("%q missing. No additional remote master(s) will receive build report.", masterHostsFile) 482 } else { 483 log.Println("Error parsing master hosts file %q: %v", 484 masterHostsFile, err) 485 } 486 } 487 488 // the OS we run on 489 if *ourOS == "" { 490 *ourOS = runtime.GOOS 491 if *ourOS == "" { 492 // Can this happen? I don't think so, but just in case... 493 panic("runtime.GOOS was not set") 494 } 495 } 496 // the arch we run on 497 if *arch == "" { 498 *arch = runtime.GOARCH 499 if *arch == "" { 500 panic("runtime.GOARCH was not set") 501 } 502 } 503 504 // cacheDir 505 cacheDir = filepath.Join(os.TempDir(), "camlibot-cache") 506 if err := os.MkdirAll(cacheDir, 0755); err != nil { 507 log.Fatalf("Could not create cache dir %v: %v", cacheDir, err) 508 } 509 510 // get go1 and gotip source 511 if err := os.Chdir(cacheDir); err != nil { 512 log.Fatalf("Could not cd to %v: %v", cacheDir, err) 513 } 514 go1Dir, err = filepath.Abs("go1") 515 if err != nil { 516 log.Fatalf("Problem with Go 1 dir: %v", err) 517 } 518 goTipDir, err = filepath.Abs("gotip") 519 if err != nil { 520 log.Fatalf("Problem with Go tip dir: %v", err) 521 } 522 for _, goDir := range []string{go1Dir, goTipDir} { 523 // if go dirs exist, just reuse them 524 if _, err := os.Stat(goDir); err != nil { 525 if !os.IsNotExist(err) { 526 log.Fatalf("Could not stat %v: %v", goDir, err) 527 } 528 // go1/gotip dir not here, let's clone it. 529 hgCloneCmd := hgCloneGo1Cmd 530 if goDir == goTipDir { 531 hgCloneCmd = hgCloneGoTipCmd 532 } 533 tsk := newTask(hgCloneCmd.Program, hgCloneCmd.Args...) 534 tsk.hidden = true 535 if _, err := tsk.run(); err != nil { 536 log.Fatalf("Could not hg clone %v: %v", goDir, err) 537 } 538 if err := os.Rename("go", goDir); err != nil { 539 log.Fatalf("Could not rename go dir into %v: %v", goDir, err) 540 } 541 } 542 } 543 544 if !*skipGo1Build { 545 // build Go1 546 if err := buildGo1(); err != nil { 547 log.Fatal(err) 548 } 549 } 550 551 // get camlistore source 552 if err := os.Chdir(cacheDir); err != nil { 553 log.Fatal("Could not cd to %v: %v", cacheDir, err) 554 } 555 camliRoot, err = filepath.Abs("camlistore.org") 556 if err != nil { 557 log.Fatal(err) 558 } 559 // if camlistore dir already exists, reuse it 560 if _, err := os.Stat(camliRoot); err != nil { 561 if !os.IsNotExist(err) { 562 log.Fatalf("Could not stat %v: %v", camliRoot, err) 563 } 564 cloneCmd := newTask(gitCloneCmd.Program, append(gitCloneCmd.Args, camliRoot)...) 565 cloneCmd.hidden = true 566 if _, err := cloneCmd.run(); err != nil { 567 log.Fatalf("Could not git clone into %v: %v", camliRoot, err) 568 } 569 } 570 571 // recording camput cache dir, so we can clean it up fast everytime 572 homeDir := os.Getenv("HOME") 573 if homeDir == "" { 574 log.Fatal("HOME not set") 575 } 576 camputCacheDir = filepath.Join(homeDir, ".cache", "camlistore") 577 } 578 579 func buildGo1() error { 580 if err := os.Chdir(filepath.Join(go1Dir, "src")); err != nil { 581 log.Fatalf("Could not cd to %v: %v", go1Dir, err) 582 } 583 tsk := newTask(buildGoCmd.Program, buildGoCmd.Args...) 584 tsk.hidden = true 585 if _, err := tsk.run(); err != nil { 586 return err 587 } 588 return nil 589 } 590 591 func handleSignals() { 592 c := make(chan os.Signal) 593 sigs := []os.Signal{syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT} 594 signal.Notify(c, sigs...) 595 for { 596 sig := <-c 597 sysSig, ok := sig.(syscall.Signal) 598 if !ok { 599 log.Fatal("Not a unix signal") 600 } 601 switch sysSig { 602 case syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT: 603 log.Printf("Received %v signal, terminating.", sig) 604 killCamli() 605 os.Exit(0) 606 default: 607 panic("should not get other signals here") 608 } 609 } 610 } 611 612 var plausibleHashRx = regexp.MustCompile(`^[a-f0-9]{40}$`) 613 614 func prepGoTipTree() error { 615 if err := os.Chdir(goTipDir); err != nil { 616 return fmt.Errorf("Could not cd to %v: %v", goTipDir, err) 617 } 618 tasks := []*task{ 619 newTaskFrom(hgPullCmd), 620 newTaskFrom(hgUpdateCmd), 621 newTaskFrom(hgLogCmd), 622 newTaskFrom(hgConfigCmd), 623 } 624 hash := "" 625 for _, t := range tasks { 626 out, err := t.run() 627 if err != nil { 628 return fmt.Errorf("Could not prepare the Go tip tree with %v: %v", t.String(), err) 629 } 630 if t.String() == hgLogCmd.String() { 631 hash = strings.TrimRight(out, "\n") 632 } 633 } 634 if !plausibleHashRx.MatchString(hash) { 635 return fmt.Errorf("Go rev %q does not look like an hg hash.", hash) 636 } 637 goTipHash = hash 638 dbg.Println("current head in go tree: " + goTipHash) 639 return nil 640 } 641 642 func buildGoTip() error { 643 srcDir := filepath.Join(goTipDir, "src") 644 if err := os.Chdir(srcDir); err != nil { 645 return fmt.Errorf("Could not cd to %v: %v", srcDir, err) 646 } 647 if _, err := newTaskFrom(buildGoCmd).run(); err != nil { 648 return err 649 } 650 return nil 651 } 652 653 func prepCamliTree(isTip bool) error { 654 if err := os.Chdir(camliRoot); err != nil { 655 return fmt.Errorf("Could not cd to %v: %v", camliRoot, err) 656 } 657 rev := "HEAD" 658 if isTip { 659 if !plausibleHashRx.MatchString(camliHeadHash) { 660 // the run with Go 1 should have taken care of setting camliHeadHash 661 return errors.New("camliHeadHash hasn't been set properly in the Go 1 run") 662 } 663 // we reset to the rev that was noted at the previous run with Go 1 664 // because we want to do both runs at the same rev 665 rev = camliHeadHash 666 } 667 resetCmd := newTask(gitResetCmd.Program, append(gitResetCmd.Args, rev)...) 668 tasks := []*task{ 669 resetCmd, 670 newTaskFrom(gitCleanCmd), 671 } 672 for _, t := range tasks { 673 _, err := t.run() 674 if err != nil { 675 return fmt.Errorf("Could not prepare the Camli tree with %v: %v\n", t.String(), err) 676 } 677 } 678 if isTip { 679 // We only need to pull and get the camli head hash when in the Go 1 run 680 return nil 681 } 682 tasks = []*task{ 683 newTaskFrom(gitPullCmd), 684 newTaskFrom(gitRevCmd), 685 } 686 hash := "" 687 for _, t := range tasks { 688 out, err := t.run() 689 if err != nil { 690 return fmt.Errorf("Could not prepare the Camli tree with %v: %v\n", t.String(), err) 691 } 692 hash = strings.TrimRight(out, "\n") 693 } 694 if !plausibleHashRx.MatchString(hash) { 695 return fmt.Errorf("Camlistore rev %q does not look like a git hash.", hash) 696 } 697 camliHeadHash = hash 698 return nil 699 } 700 701 func restorePATH() { 702 err := os.Setenv("PATH", defaultPATH) 703 if err != nil { 704 log.Fatalf("Could not set PATH to %v: %v", defaultPATH, err) 705 } 706 } 707 708 func switchGo(goDir string) { 709 if runtime.GOOS == "plan9" { 710 panic("plan 9 not unsupported") 711 } 712 gobin := filepath.Join(goDir, "bin", "go") 713 if _, err := os.Stat(gobin); err != nil { 714 log.Fatalf("Could not stat 'go' bin at %q: %v", gobin, err) 715 } 716 p := filepath.Join(goDir, "bin") + string(filepath.ListSeparator) + defaultPATH 717 if err := os.Setenv("PATH", p); err != nil { 718 log.Fatalf("Could not set PATH to %v: %v", p, err) 719 } 720 if err := os.Setenv("GOROOT", goDir); err != nil { 721 log.Fatalf("Could not set GOROOT to %v: %v", goDir, err) 722 } 723 } 724 725 func cleanBuildGopaths() { 726 tmpDir := filepath.Join(camliRoot, "tmp") 727 if _, err := os.Stat(tmpDir); err != nil { 728 if !os.IsNotExist(err) { 729 log.Fatalf("Could not stat %v: %v", tmpDir, err) 730 } 731 // Does not exist, we only have to recreate it 732 // TODO(mpl): hmm maybe it should be an error that 733 // it does not exist, since it also contains the 734 // closure stuff? 735 if err := os.MkdirAll(tmpDir, 0755); err != nil { 736 log.Fatalf("Could not mkdir %v: %v", tmpDir, err) 737 } 738 return 739 } 740 f, err := os.Open(tmpDir) 741 if err != nil { 742 log.Fatalf("Could not open %v: %v", tmpDir, err) 743 } 744 defer f.Close() 745 names, err := f.Readdirnames(-1) 746 if err != nil { 747 log.Fatal("Could not read %v: %v", tmpDir, err) 748 } 749 for _, v := range names { 750 if strings.HasPrefix(v, "build-gopath") { 751 if err := os.RemoveAll(filepath.Join(tmpDir, v)); err != nil { 752 log.Fatalf("Could not remove %v: %v", v, err) 753 } 754 } 755 } 756 } 757 758 func fakeRun() error { 759 if _, err := newTask("sleep", "1").run(); err != nil { 760 return err 761 } 762 return nil 763 } 764 765 func buildCamli() error { 766 if err := os.Chdir(camliRoot); err != nil { 767 log.Fatalf("Could not cd to %v: %v", camliRoot, err) 768 } 769 // Clean up Camlistore's hermetic gopaths 770 cleanBuildGopaths() 771 772 if *verbose { 773 tsk := newTask("go", "version") 774 out, err := tsk.run() 775 tsk.hidden = true 776 if err != nil { 777 return fmt.Errorf("failed to run 'go version': %v", err) 778 } 779 out = strings.TrimRight(out, "\n") 780 dbg.Printf("Building Camlistore with: %v\n", out) 781 } 782 if _, err := newTaskFrom(buildCamliCmd).run(); err != nil { 783 return err 784 } 785 return nil 786 } 787 788 func runCamli() error { 789 if err := os.Chdir(camliRoot); err != nil { 790 log.Fatal(err) 791 } 792 793 t := newTaskFrom(runCamliCmd) 794 dbg.Println(t.String()) 795 cmd := exec.Command(t.Program, t.Args...) 796 var output []byte 797 errc := make(chan error, 1) 798 t.Start = time.Now() 799 go func() { 800 var err error 801 output, err = cmd.CombinedOutput() 802 if err != nil { 803 err = fmt.Errorf("%v: %v", err, string(output)) 804 } 805 errc <- err 806 }() 807 select { 808 case err := <-errc: 809 t.Err = fmt.Sprintf("%v terminated early:\n%v\n", t.String(), err) 810 biSuitelk.Lock() 811 currentTestSuite.addRun(t) 812 biSuitelk.Unlock() 813 log.Println(t.Err) 814 return t 815 case <-time.After(warmup): 816 biSuitelk.Lock() 817 currentTestSuite.addRun(t) 818 camliProc = cmd.Process 819 biSuitelk.Unlock() 820 dbg.Printf("%v running OK so far\n", t.String()) 821 } 822 return nil 823 } 824 825 func killCamli() { 826 if camliProc == nil { 827 return 828 } 829 dbg.Println("killing Camlistore server") 830 if err := camliProc.Kill(); err != nil { 831 log.Fatalf("Could not kill server with pid %v: %v", camliProc.Pid, err) 832 } 833 camliProc = nil 834 dbg.Println("") 835 } 836 837 func hitCamliUi() error { 838 if err := hitURL("http://localhost:3179/ui/"); err != nil { 839 return fmt.Errorf("could not reach camlistored UI page (dead server?): %v", err) 840 } 841 return nil 842 } 843 844 func hitURL(url string) (err error) { 845 tsk := newTask("http.Get", url) 846 defer func() { 847 if err != nil { 848 tsk.Err = fmt.Sprintf("%v", err) 849 } 850 biSuitelk.Lock() 851 currentTestSuite.addRun(tsk) 852 biSuitelk.Unlock() 853 }() 854 dbg.Println(tsk.String()) 855 tsk.Start = time.Now() 856 var resp *http.Response 857 resp, err = http.Get(url) 858 if err != nil { 859 return fmt.Errorf("%v: %v\n", tsk.String(), err) 860 } 861 defer resp.Body.Close() 862 if resp.StatusCode != 200 { 863 return fmt.Errorf("%v, got StatusCode: %d\n", tsk.String(), resp.StatusCode) 864 } 865 return nil 866 } 867 868 func camputOne(vivify bool) error { 869 if err := os.Chdir(camliRoot); err != nil { 870 log.Fatalf("Could not cd to %v: %v", camliRoot, err) 871 } 872 873 // clean up camput caches 874 if err := os.RemoveAll(camputCacheDir); err != nil { 875 log.Fatalf("Problem cleaning up camputCacheDir %v: %v", camputCacheDir, err) 876 } 877 878 // push the file to camli 879 tsk := newTaskFrom(camputCmd) 880 if vivify { 881 tsk = newTaskFrom(camputVivifyCmd) 882 } 883 out, err := tsk.run() 884 if err != nil { 885 return err 886 } 887 // TODO(mpl): parsing camput output is kinda weak. 888 firstSHA1 := regexp.MustCompile(`.*(sha1-[a-zA-Z0-9]+)\nsha1-[a-zA-Z0-9]+\nsha1-[a-zA-Z0-9]+\n.*`) 889 if vivify { 890 firstSHA1 = regexp.MustCompile(`.*(sha1-[a-zA-Z0-9]+)\n.*`) 891 } 892 m := firstSHA1.FindStringSubmatch(out) 893 if m == nil { 894 return fmt.Errorf("%v: unexpected camput output\n", tsk.String()) 895 } 896 blobref := m[1] 897 898 // get the file's json to find out the file's blobref 899 tsk = newTask(camgetCmd.Program, append(camgetCmd.Args, blobref)...) 900 out, err = tsk.run() 901 if err != nil { 902 return err 903 } 904 blobrefPattern := regexp.MustCompile(`"blobRef": "(sha1-[a-zA-Z0-9]+)",\n.*`) 905 m = blobrefPattern.FindStringSubmatch(out) 906 if m == nil { 907 return fmt.Errorf("%v: unexpected camget output\n", tsk.String()) 908 } 909 blobref = m[1] 910 911 // finally, get the file back 912 tsk = newTask(camgetCmd.Program, append(camgetCmd.Args, blobref)...) 913 out, err = tsk.run() 914 if err != nil { 915 return err 916 } 917 918 // and compare it with the original 919 wantFile := testFile[0] 920 if vivify { 921 wantFile = testFile[1] 922 } 923 fileContents, err := ioutil.ReadFile(wantFile) 924 if err != nil { 925 log.Fatalf("Could not read %v: %v", wantFile, err) 926 } 927 if string(fileContents) != out { 928 return fmt.Errorf("%v: contents fetched with camget differ from %v contents", tsk.String(), wantFile) 929 } 930 return nil 931 } 932 933 func camputMany() error { 934 err := os.Chdir(camliRoot) 935 if err != nil { 936 log.Fatalf("Could not cd to %v: %v", camliRoot, err) 937 } 938 939 // upload the full camli pkg tree 940 if _, err := newTaskFrom(camputFilenodesCmd).run(); err != nil { 941 return err 942 } 943 return nil 944 } 945 946 func runTests() error { 947 if err := os.Chdir(camliRoot); err != nil { 948 log.Fatal(err) 949 } 950 if _, err := newTaskFrom(runTestsCmd).run(); err != nil { 951 return err 952 } 953 return nil 954 } 955 956 const reportPrefix = "/report" 957 958 func postToURL(u string, r io.Reader) (*http.Response, error) { 959 // Default to plain HTTP. 960 if !(strings.HasPrefix(u, "http://") || strings.HasPrefix(u, "https://")) { 961 u = "http://" + u 962 } 963 uri, err := url.Parse(u) 964 if err != nil { 965 return nil, err 966 } 967 968 // If the URL explicitly specifies "/" or something else, we'll POST to 969 // that, otherwise default to build-time default. 970 if uri.Path == "" { 971 uri.Path = reportPrefix 972 } 973 974 // Save user/pass if specified in the URL. 975 user := uri.User 976 // But don't send user/pass in URL to server. 977 uri.User = nil 978 979 req, err := http.NewRequest("POST", uri.String(), r) 980 if err != nil { 981 return nil, err 982 } 983 req.Header.Set("Content-Type", "text/javascript") 984 // If user/pass set on original URL, set the auth header for the request. 985 if user != nil { 986 pass, ok := user.Password() 987 if !ok { 988 log.Println("Password not set for", user.Username(), "in", u) 989 } 990 req.SetBasicAuth(user.Username(), pass) 991 } 992 return client.Do(req) 993 } 994 995 func sendReport() { 996 biSuitelk.Lock() 997 // we make a copy so we can release the lock quickly enough 998 currentBiSuiteCpy := &biTestSuite{ 999 Go1: currentBiSuite.Go1, 1000 GoTip: currentBiSuite.GoTip, 1001 } 1002 biSuitelk.Unlock() 1003 masters := strings.Split(*masterHosts, ",") 1004 OSArch := *ourOS + "_" + *arch 1005 toReport := struct { 1006 OSArch string 1007 Ts *biTestSuite 1008 }{ 1009 OSArch: OSArch, 1010 Ts: currentBiSuiteCpy, 1011 } 1012 for _, v := range masters { 1013 // TODO(mpl): ipv6 too I suppose. just make a IsLocalhost func or whatever. 1014 // probably can borrow something from camli code for that. 1015 if strings.HasPrefix(v, "localhost") || strings.HasPrefix(v, "127.0.0.1") { 1016 toReport.Ts.Local = true 1017 } else { 1018 toReport.Ts.Local = false 1019 } 1020 report, err := json.MarshalIndent(toReport, "", " ") 1021 if err != nil { 1022 log.Printf("JSON serialization error: %v", err) 1023 return 1024 } 1025 r := bytes.NewReader(report) 1026 resp, err := postToURL(v, r) 1027 if err != nil { 1028 log.Printf("Could not send report: %v", err) 1029 continue 1030 } 1031 resp.Body.Close() 1032 } 1033 } 1034 1035 func progressHandler(w http.ResponseWriter, r *http.Request) { 1036 if r.Method != "GET" { 1037 log.Printf("Invalid method in progress handler: %v, want GET", r.Method) 1038 http.Error(w, "Invalid method", http.StatusMethodNotAllowed) 1039 return 1040 } 1041 if checkLastModified(w, r, lastModified) { 1042 return 1043 } 1044 biSuitelk.Lock() 1045 if currentBiSuite != nil { 1046 if currentTestSuite.IsTip { 1047 currentBiSuite.GoTip = *currentTestSuite 1048 } else { 1049 currentBiSuite.Go1 = *currentTestSuite 1050 } 1051 } 1052 sanitizeRevs() 1053 report, err := json.MarshalIndent(currentBiSuite, "", " ") 1054 if err != nil { 1055 biSuitelk.Unlock() 1056 log.Printf("JSON serialization error: %v", err) 1057 http.Error(w, "internal error", http.StatusInternalServerError) 1058 return 1059 } 1060 biSuitelk.Unlock() 1061 _, err = io.Copy(w, bytes.NewReader(report)) 1062 if err != nil { 1063 log.Printf("Could not send progress report: %v", err) 1064 } 1065 } 1066 1067 // modtime is the modification time of the resource to be served, or IsZero(). 1068 // return value is whether this request is now complete. 1069 func checkLastModified(w http.ResponseWriter, r *http.Request, modtime time.Time) bool { 1070 if modtime.IsZero() { 1071 return false 1072 } 1073 1074 // The Date-Modified header truncates sub-second precision, so 1075 // use mtime < t+1s instead of mtime <= t to check for unmodified. 1076 if t, err := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since")); err == nil && modtime.Before(t.Add(1*time.Second)) { 1077 h := w.Header() 1078 delete(h, "Content-Type") 1079 delete(h, "Content-Length") 1080 w.WriteHeader(http.StatusNotModified) 1081 return true 1082 } 1083 w.Header().Set("Last-Modified", modtime.UTC().Format(http.TimeFormat)) 1084 return false 1085 }