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