Пример #1
0
func TestCheckGCSBuilds(t *testing.T) {
	latestBuildNumberFoo := 42
	latestBuildNumberBar := 44
	latestBuildNumberBaz := 99
	tests := []struct {
		paths             map[string][]byte
		expectStable      bool
		expectedLastBuild int
		expectedStatus    map[string]BuildInfo
	}{
		{
			paths: map[string][]byte{
				"/foo/latest-build.txt": []byte(strconv.Itoa(latestBuildNumberFoo)),
				fmt.Sprintf("/foo/%v/finished.json", latestBuildNumberFoo): marshalOrDie(utils.FinishedFile{
					Result:    "SUCCESS",
					Timestamp: 1234,
				}, t),
				"/bar/latest-build.txt": []byte(strconv.Itoa(latestBuildNumberBar)),
				fmt.Sprintf("/bar/%v/finished.json", latestBuildNumberBar): marshalOrDie(utils.FinishedFile{
					Result:    "SUCCESS",
					Timestamp: 1234,
				}, t),
				"/baz/latest-build.txt": []byte(strconv.Itoa(latestBuildNumberBaz)),
				fmt.Sprintf("/baz/%v/finished.json", latestBuildNumberBaz): marshalOrDie(utils.FinishedFile{
					Result:    "UNSTABLE",
					Timestamp: 1234,
				}, t),
				"/": genMockGCSListResponse(),
			},
			expectStable: true,
			expectedStatus: map[string]BuildInfo{
				"foo": {Status: "Stable", ID: "42"},
				"bar": {Status: "Stable", ID: "44"},
				"baz": {Status: "[nonblocking] Not Stable", ID: "99"},
			},
		},
		{
			paths: map[string][]byte{
				"/foo/latest-build.txt": []byte(strconv.Itoa(latestBuildNumberFoo)),
				fmt.Sprintf("/foo/%v/finished.json", latestBuildNumberFoo): marshalOrDie(utils.FinishedFile{
					Result:    "SUCCESS",
					Timestamp: 1234,
				}, t),
				"/bar/latest-build.txt": []byte(strconv.Itoa(latestBuildNumberBar)),
				fmt.Sprintf("/bar/%v/finished.json", latestBuildNumberBar): marshalOrDie(utils.FinishedFile{
					Result:    "UNSTABLE",
					Timestamp: 1234,
				}, t),
				"/baz/latest-build.txt": []byte(strconv.Itoa(latestBuildNumberBaz)),
				fmt.Sprintf("/baz/%v/finished.json", latestBuildNumberBaz): marshalOrDie(utils.FinishedFile{
					Result:    "SUCCESS",
					Timestamp: 1234,
				}, t),
				"/": genMockGCSListResponse(),
			},
			expectStable: false,
			expectedStatus: map[string]BuildInfo{
				"foo": {Status: "Stable", ID: "42"},
				"bar": {Status: "Not Stable", ID: "44"},
				"baz": {Status: "[nonblocking] Stable", ID: "99"},
			},
		},
		{
			paths: map[string][]byte{
				"/foo/latest-build.txt": []byte(strconv.Itoa(latestBuildNumberFoo)),
				fmt.Sprintf("/foo/%v/finished.json", latestBuildNumberFoo): marshalOrDie(utils.FinishedFile{
					Result:    "SUCCESS",
					Timestamp: 1234,
				}, t),
				"/bar/latest-build.txt": []byte(strconv.Itoa(latestBuildNumberBar)),
				fmt.Sprintf("/bar/%v/finished.json", latestBuildNumberBar): marshalOrDie(utils.FinishedFile{
					Result:    "UNSTABLE",
					Timestamp: 1234,
				}, t),
				fmt.Sprintf("/bar/%v/artifacts/junit_01.xml", latestBuildNumberBar-1): getJUnit(5, 0),
				fmt.Sprintf("/bar/%v/artifacts/junit_02.xml", latestBuildNumberBar-1): getJUnit(5, 0),
				fmt.Sprintf("/bar/%v/artifacts/junit_03.xml", latestBuildNumberBar-1): getJUnit(5, 0),
				fmt.Sprintf("/bar/%v/artifacts/junit_01.xml", latestBuildNumberBar):   getJUnit(5, 0),
				fmt.Sprintf("/bar/%v/artifacts/junit_02.xml", latestBuildNumberBar):   getRealJUnitFailure(),
				fmt.Sprintf("/bar/%v/artifacts/junit_03.xml", latestBuildNumberBar):   getJUnit(5, 0),
				fmt.Sprintf("/bar/%v/finished.json", latestBuildNumberBar-1): marshalOrDie(utils.FinishedFile{
					Result:    "SUCCESS",
					Timestamp: 999,
				}, t),
				"/": genMockGCSListResponse(
					fmt.Sprintf("/bar/%v/artifacts/junit_01.xml", latestBuildNumberBar-1),
					fmt.Sprintf("/bar/%v/artifacts/junit_02.xml", latestBuildNumberBar-1),
					fmt.Sprintf("/bar/%v/artifacts/junit_03.xml", latestBuildNumberBar-1),
					fmt.Sprintf("/bar/%v/artifacts/junit_01.xml", latestBuildNumberBar),
					fmt.Sprintf("/bar/%v/artifacts/junit_02.xml", latestBuildNumberBar),
					fmt.Sprintf("/bar/%v/artifacts/junit_03.xml", latestBuildNumberBar),
				),
			},
			expectStable: true,
			expectedStatus: map[string]BuildInfo{
				"foo": {Status: "Stable", ID: "42"},
				"bar": {Status: "Ignorable flake", ID: "44"},
				"baz": {Status: "[nonblocking] Not Stable", ID: "-1"},
			},
		},
		{
			paths: map[string][]byte{
				"/foo/latest-build.txt": []byte(strconv.Itoa(latestBuildNumberFoo)),
				fmt.Sprintf("/foo/%v/finished.json", latestBuildNumberFoo): marshalOrDie(utils.FinishedFile{
					Result:    "SUCCESS",
					Timestamp: 1234,
				}, t),
				"/bar/latest-build.txt": []byte(strconv.Itoa(latestBuildNumberBar)),
				fmt.Sprintf("/bar/%v/finished.json", latestBuildNumberBar): marshalOrDie(utils.FinishedFile{
					Result:    "UNSTABLE",
					Timestamp: 1234,
				}, t),
				fmt.Sprintf("/bar/%v/artifacts/junit_01.xml", latestBuildNumberBar-1): getJUnit(5, 0),
				fmt.Sprintf("/bar/%v/artifacts/junit_02.xml", latestBuildNumberBar-1): getOtherRealJUnitFailure(),
				fmt.Sprintf("/bar/%v/artifacts/junit_03.xml", latestBuildNumberBar-1): getJUnit(5, 0),
				fmt.Sprintf("/bar/%v/artifacts/junit_01.xml", latestBuildNumberBar):   getJUnit(5, 0),
				fmt.Sprintf("/bar/%v/artifacts/junit_02.xml", latestBuildNumberBar):   getRealJUnitFailure(),
				fmt.Sprintf("/bar/%v/artifacts/junit_03.xml", latestBuildNumberBar):   getJUnit(5, 0),
				fmt.Sprintf("/bar/%v/finished.json", latestBuildNumberBar-1): marshalOrDie(utils.FinishedFile{
					Result:    "UNSTABLE",
					Timestamp: 999,
				}, t),
				"/": genMockGCSListResponse(
					fmt.Sprintf("/bar/%v/artifacts/junit_01.xml", latestBuildNumberBar-1),
					fmt.Sprintf("/bar/%v/artifacts/junit_02.xml", latestBuildNumberBar-1),
					fmt.Sprintf("/bar/%v/artifacts/junit_03.xml", latestBuildNumberBar-1),
					fmt.Sprintf("/bar/%v/artifacts/junit_01.xml", latestBuildNumberBar),
					fmt.Sprintf("/bar/%v/artifacts/junit_02.xml", latestBuildNumberBar),
					fmt.Sprintf("/bar/%v/artifacts/junit_03.xml", latestBuildNumberBar),
				),
			},
			expectStable: true,
			expectedStatus: map[string]BuildInfo{
				"foo": {Status: "Stable", ID: "42"},
				"bar": {Status: "Ignorable flake", ID: "44"},
				"baz": {Status: "[nonblocking] Not Stable", ID: "-1"},
			},
		},
		{
			paths: map[string][]byte{
				"/foo/latest-build.txt": []byte(strconv.Itoa(latestBuildNumberFoo)),
				fmt.Sprintf("/foo/%v/finished.json", latestBuildNumberFoo): marshalOrDie(utils.FinishedFile{
					Result:    "SUCCESS",
					Timestamp: 1234,
				}, t),
				"/bar/latest-build.txt": []byte(strconv.Itoa(latestBuildNumberBar)),
				fmt.Sprintf("/bar/%v/finished.json", latestBuildNumberBar): marshalOrDie(utils.FinishedFile{
					Result:    "UNSTABLE",
					Timestamp: 1234,
				}, t),
				fmt.Sprintf("/bar/%v/artifacts/junit_01.xml", latestBuildNumberBar-1): getJUnit(5, 0),
				fmt.Sprintf("/bar/%v/artifacts/junit_02.xml", latestBuildNumberBar-1): getRealJUnitFailure(),
				fmt.Sprintf("/bar/%v/artifacts/junit_03.xml", latestBuildNumberBar-1): getJUnit(5, 0),
				fmt.Sprintf("/bar/%v/artifacts/junit_01.xml", latestBuildNumberBar):   getJUnit(5, 0),
				fmt.Sprintf("/bar/%v/artifacts/junit_02.xml", latestBuildNumberBar):   getRealJUnitFailure(),
				fmt.Sprintf("/bar/%v/artifacts/junit_03.xml", latestBuildNumberBar):   getJUnit(5, 0),
				fmt.Sprintf("/bar/%v/finished.json", latestBuildNumberBar-1): marshalOrDie(utils.FinishedFile{
					Result:    "UNSTABLE",
					Timestamp: 999,
				}, t),
				"/": genMockGCSListResponse(
					fmt.Sprintf("/bar/%v/artifacts/junit_01.xml", latestBuildNumberBar-1),
					fmt.Sprintf("/bar/%v/artifacts/junit_02.xml", latestBuildNumberBar-1),
					fmt.Sprintf("/bar/%v/artifacts/junit_03.xml", latestBuildNumberBar-1),
					fmt.Sprintf("/bar/%v/artifacts/junit_01.xml", latestBuildNumberBar),
					fmt.Sprintf("/bar/%v/artifacts/junit_02.xml", latestBuildNumberBar),
					fmt.Sprintf("/bar/%v/artifacts/junit_03.xml", latestBuildNumberBar),
				),
			},
			expectStable: false,
			expectedStatus: map[string]BuildInfo{
				"foo": {Status: "Stable", ID: "42"},
				"bar": {Status: "Not Stable", ID: "44"},
				"baz": {Status: "[nonblocking] Not Stable", ID: "-1"},
			},
		},

		{
			paths: map[string][]byte{
				"/foo/latest-build.txt": []byte(strconv.Itoa(latestBuildNumberFoo)),
				fmt.Sprintf("/foo/%v/finished.json", latestBuildNumberFoo): marshalOrDie(utils.FinishedFile{
					Result:    "SUCCESS",
					Timestamp: 1234,
				}, t),
				"/bar/latest-build.txt": []byte(strconv.Itoa(latestBuildNumberBar)),
				fmt.Sprintf("/bar/%v/finished.json", latestBuildNumberBar): marshalOrDie(utils.FinishedFile{
					Result:    "FAILURE",
					Timestamp: 1234,
				}, t),
				"/": genMockGCSListResponse(),
			},
			expectStable: false,
			expectedStatus: map[string]BuildInfo{
				"foo": {Status: "Stable", ID: "42"},
				"bar": {Status: "Not Stable", ID: "44"},
				"baz": {Status: "[nonblocking] Not Stable", ID: "-1"},
			},
		},
		{
			paths: map[string][]byte{
				"/foo/latest-build.txt": []byte(strconv.Itoa(latestBuildNumberFoo)),
				fmt.Sprintf("/foo/%v/finished.json", latestBuildNumberFoo): marshalOrDie(utils.FinishedFile{
					Result:    "FAILURE",
					Timestamp: 1234,
				}, t),
				"/bar/latest-build.txt": []byte(strconv.Itoa(latestBuildNumberBar)),
				fmt.Sprintf("/bar/%v/finished.json", latestBuildNumberBar): marshalOrDie(utils.FinishedFile{
					Result:    "UNSTABLE",
					Timestamp: 1234,
				}, t),
				"/": genMockGCSListResponse(),
			},
			expectStable: false,
			expectedStatus: map[string]BuildInfo{
				"foo": {Status: "Not Stable", ID: "42"},
				"bar": {Status: "Not Stable", ID: "44"},
				"baz": {Status: "[nonblocking] Not Stable", ID: "-1"},
			},
		},
		{
			paths: map[string][]byte{
				"/foo/latest-build.txt": []byte(strconv.Itoa(latestBuildNumberFoo)),
				fmt.Sprintf("/foo/%v/finished.json", latestBuildNumberFoo): marshalOrDie(utils.FinishedFile{
					Result:    "UNSTABLE",
					Timestamp: 1234,
				}, t),
				"/bar/latest-build.txt": []byte(strconv.Itoa(latestBuildNumberBar)),
				fmt.Sprintf("/bar/%v/finished.json", latestBuildNumberBar): marshalOrDie(utils.FinishedFile{
					Result:    "SUCCESS",
					Timestamp: 1234,
				}, t),
				"/": genMockGCSListResponse(),
			},
			expectStable: false,
			expectedStatus: map[string]BuildInfo{
				"foo": {Status: "Not Stable", ID: "42"},
				"bar": {Status: "Stable", ID: "44"},
				"baz": {Status: "[nonblocking] Not Stable", ID: "-1"},
			},
		},
	}
	for index, test := range tests {
		server := httptest.NewServer(&testHandler{
			handler: func(res http.ResponseWriter, req *http.Request) {
				data, found := test.paths[req.URL.Path]
				if !found {
					res.WriteHeader(http.StatusNotFound)
					fmt.Fprintf(res, "Unknown path: %s", req.URL.Path)
					return
				}
				res.WriteHeader(http.StatusOK)
				res.Write(data)
			},
		})
		e2e := &RealE2ETester{
			BlockingJobNames: []string{
				"foo",
				"bar",
			},
			NonBlockingJobNames: []string{
				"baz",
			},
			BuildStatus:          map[string]BuildInfo{},
			GoogleGCSBucketUtils: utils.NewTestUtils(server.URL),
		}
		e2e.Init(nil)

		stable, _ := e2e.GCSBasedStable()
		if stable != test.expectStable {
			t.Errorf("%v: expected: %v, saw: %v", index, test.expectStable, stable)
		}
		if !reflect.DeepEqual(test.expectedStatus, e2e.BuildStatus) {
			t.Errorf("%v: expected: %v, saw: %v", index, test.expectedStatus, e2e.BuildStatus)
		}
	}
}
Пример #2
0
// internalInitialize will initialize the munger.
// if overrideUrl is specified, will create testUtils
func (sq *SubmitQueue) internalInitialize(config *github.Config, features *features.Features, overrideUrl string) error {
	sq.Lock()
	defer sq.Unlock()

	// Clean up all of our flags which we wish --flag="" to mean []string{}
	sq.BlockingJobNames = cleanStringSlice(sq.BlockingJobNames)
	sq.NonBlockingJobNames = cleanStringSlice(sq.NonBlockingJobNames)
	sq.PresubmitJobNames = cleanStringSlice(sq.PresubmitJobNames)
	sq.WeakStableJobNames = cleanStringSlice(sq.WeakStableJobNames)
	sq.RequiredStatusContexts = cleanStringSlice(sq.RequiredStatusContexts)
	sq.RequiredRetestContexts = cleanStringSlice(sq.RequiredRetestContexts)
	sq.DoNotMergeMilestones = cleanStringSlice(sq.DoNotMergeMilestones)
	sq.Metadata.RepoPullUrl = fmt.Sprintf("https://github.com/%s/%s/pulls/", config.Org, config.Project)
	sq.Metadata.ProjectName = strings.Title(config.Project)
	sq.githubConfig = config

	// TODO: This is not how injection for tests should work.
	if sq.FakeE2E {
		sq.e2e = &fake_e2e.FakeE2ETester{
			JobNames:           sq.BlockingJobNames,
			WeakStableJobNames: sq.WeakStableJobNames,
		}
	} else {
		var gcs *utils.Utils
		if overrideUrl != "" {
			gcs = utils.NewTestUtils("bucket", "logs", overrideUrl)
		} else {
			gcs = utils.NewWithPresubmitDetection(
				sq.features.GCSInfo.BucketName, sq.features.GCSInfo.LogDir,
				sq.features.GCSInfo.PullKey, sq.features.GCSInfo.PullLogDir,
			)
		}

		sq.e2e = (&e2e.RealE2ETester{
			BlockingJobNames:     sq.BlockingJobNames,
			NonBlockingJobNames:  sq.NonBlockingJobNames,
			WeakStableJobNames:   sq.WeakStableJobNames,
			BuildStatus:          map[string]e2e.BuildInfo{},
			GoogleGCSBucketUtils: gcs,
		}).Init(admin.Mux)
	}

	sq.lgtmTimeCache = mungerutil.NewLabelTimeCache(lgtmLabel)

	if len(config.Address) > 0 {
		if len(config.WWWRoot) > 0 {
			http.Handle("/", gziphandler.GzipHandler(http.FileServer(http.Dir(config.WWWRoot))))
		}
		http.Handle("/prs", gziphandler.GzipHandler(http.HandlerFunc(sq.servePRs)))
		http.Handle("/history", gziphandler.GzipHandler(http.HandlerFunc(sq.serveHistory)))
		http.Handle("/github-e2e-queue", gziphandler.GzipHandler(http.HandlerFunc(sq.serveGithubE2EStatus)))
		http.Handle("/google-internal-ci", gziphandler.GzipHandler(http.HandlerFunc(sq.serveGoogleInternalStatus)))
		http.Handle("/merge-info", gziphandler.GzipHandler(http.HandlerFunc(sq.serveMergeInfo)))
		http.Handle("/priority-info", gziphandler.GzipHandler(http.HandlerFunc(sq.servePriorityInfo)))
		http.Handle("/health", gziphandler.GzipHandler(http.HandlerFunc(sq.serveHealth)))
		http.Handle("/health.svg", gziphandler.GzipHandler(http.HandlerFunc(sq.serveHealthSVG)))
		http.Handle("/sq-stats", gziphandler.GzipHandler(http.HandlerFunc(sq.serveSQStats)))
		http.Handle("/flakes", gziphandler.GzipHandler(http.HandlerFunc(sq.serveFlakes)))
		http.Handle("/metadata", gziphandler.GzipHandler(http.HandlerFunc(sq.serveMetadata)))
		config.ServeDebugStats("/stats")
		go http.ListenAndServe(config.Address, nil)
	}

	admin.Mux.HandleFunc("/api/emergency/stop", sq.EmergencyStopHTTP)
	admin.Mux.HandleFunc("/api/emergency/resume", sq.EmergencyStopHTTP)
	admin.Mux.HandleFunc("/api/emergency/status", sq.EmergencyStopHTTP)

	if sq.githubE2EPollTime == 0 {
		sq.githubE2EPollTime = githubE2EPollTime
	}

	sq.healthHistory = make([]healthRecord, 0)

	go sq.handleGithubE2EAndMerge()
	go sq.updateGoogleE2ELoop()

	if sq.AdminPort != 0 {
		go http.ListenAndServe(fmt.Sprintf("0.0.0.0:%v", sq.AdminPort), admin.Mux)
	}
	return nil
}
Пример #3
0
func TestCheckGCSWeakBuilds(t *testing.T) {
	latestBuildNumberFoo := 42
	latestBuildNumberBar := 44
	tests := []struct {
		paths             map[string][]byte
		expectStable      bool
		expectedLastBuild int
		expectedStatus    map[string]BuildInfo
	}{
		// Simple case - both succeeds
		{
			paths: map[string][]byte{
				"/foo/latest-build.txt": []byte(strconv.Itoa(latestBuildNumberFoo)),
				fmt.Sprintf("/foo/%v/finished.json", latestBuildNumberFoo): marshalOrDie(utils.FinishedFile{
					Result:    "SUCCESS",
					Timestamp: 1234,
				}, t),
				"/bar/latest-build.txt": []byte(strconv.Itoa(latestBuildNumberBar)),
				fmt.Sprintf("/bar/%v/finished.json", latestBuildNumberBar): marshalOrDie(utils.FinishedFile{
					Result:    "SUCCESS",
					Timestamp: 1234,
				}, t),
			},
			expectStable: true,
			expectedStatus: map[string]BuildInfo{
				"foo": {Status: "Stable", ID: "42"},
				"bar": {Status: "Stable", ID: "44"},
			},
		},
		// If last build was successful we shouldn't be looking any further
		{
			paths: map[string][]byte{
				"/foo/latest-build.txt": []byte(strconv.Itoa(latestBuildNumberFoo)),
				fmt.Sprintf("/foo/%v/finished.json", latestBuildNumberFoo): marshalOrDie(utils.FinishedFile{
					Result:    "SUCCESS",
					Timestamp: 1234,
				}, t),
				fmt.Sprintf("/foo/%v/finished.json", latestBuildNumberFoo-1): marshalOrDie(utils.FinishedFile{
					Result:    "UNSTABLE",
					Timestamp: 1234,
				}, t),
				"/bar/latest-build.txt": []byte(strconv.Itoa(latestBuildNumberBar)),
				fmt.Sprintf("/bar/%v/finished.json", latestBuildNumberBar): marshalOrDie(utils.FinishedFile{
					Result:    "SUCCESS",
					Timestamp: 1234,
				}, t),
				fmt.Sprintf("/bar/%v/finished.json", latestBuildNumberBar-1): marshalOrDie(utils.FinishedFile{
					Result:    "FAILURE",
					Timestamp: 1234,
				}, t),
			},
			expectStable: true,
			expectedStatus: map[string]BuildInfo{
				"foo": {Status: "Stable", ID: "42"},
				"bar": {Status: "Stable", ID: "44"},
			},
		},
		// If the last build was unsuccessful but there's no failures in JUnit file we assume that it was
		// an infrastructure failure. Build should succeed if at least one of two builds were fully successful.
		{
			paths: map[string][]byte{
				"/foo/latest-build.txt": []byte(strconv.Itoa(latestBuildNumberFoo)),
				fmt.Sprintf("/foo/%v/finished.json", latestBuildNumberFoo): marshalOrDie(utils.FinishedFile{
					Result:    "UNSTABLE",
					Timestamp: 1234,
				}, t),
				fmt.Sprintf("/foo/%v/artifacts/junit_01.xml", latestBuildNumberFoo): getJUnit(5, 0),
				fmt.Sprintf("/foo/%v/finished.json", latestBuildNumberFoo-1): marshalOrDie(utils.FinishedFile{
					Result:    "UNSTABLE",
					Timestamp: 1233,
				}, t),
				fmt.Sprintf("/foo/%v/finished.json", latestBuildNumberFoo-2): marshalOrDie(utils.FinishedFile{
					Result:    "SUCCESS",
					Timestamp: 1232,
				}, t),
				"/bar/latest-build.txt": []byte(strconv.Itoa(latestBuildNumberBar)),
				fmt.Sprintf("/bar/%v/finished.json", latestBuildNumberBar): marshalOrDie(utils.FinishedFile{
					Result:    "SUCCESS",
					Timestamp: 1234,
				}, t),
			},
			expectStable: true,
			expectedStatus: map[string]BuildInfo{
				"foo": {Status: "Stable", ID: "42"},
				"bar": {Status: "Stable", ID: "44"},
			},
		},
		// If the last build was unsuccessful but there's no failures in JUnit file we assume that it was
		// an infrastructure failure. Build should fail more than both recent builds failed.
		{
			paths: map[string][]byte{
				"/foo/latest-build.txt": []byte(strconv.Itoa(latestBuildNumberFoo)),
				fmt.Sprintf("/foo/%v/finished.json", latestBuildNumberFoo): marshalOrDie(utils.FinishedFile{
					Result:    "UNSTABLE",
					Timestamp: 1234,
				}, t),
				fmt.Sprintf("/foo/%v/artifacts/junit_01.xml", latestBuildNumberFoo): getJUnit(5, 0),
				fmt.Sprintf("/foo/%v/finished.json", latestBuildNumberFoo-1): marshalOrDie(utils.FinishedFile{
					Result:    "UNSTABLE",
					Timestamp: 1233,
				}, t),
				fmt.Sprintf("/foo/%v/finished.json", latestBuildNumberFoo-2): marshalOrDie(utils.FinishedFile{
					Result:    "UNSTABLE",
					Timestamp: 1232,
				}, t),
				"/bar/latest-build.txt": []byte(strconv.Itoa(latestBuildNumberBar)),
				fmt.Sprintf("/bar/%v/finished.json", latestBuildNumberBar): marshalOrDie(utils.FinishedFile{
					Result:    "SUCCESS",
					Timestamp: 1234,
				}, t),
			},
			expectStable: false,
			expectedStatus: map[string]BuildInfo{
				"foo": {Status: "Not Stable", ID: "42"},
				"bar": {Status: "Stable", ID: "44"},
			},
		},
		// If the last build was unsuccessful and there's a failed test in a JUnit file we should fail.
		{
			paths: map[string][]byte{
				"/foo/latest-build.txt": []byte(strconv.Itoa(latestBuildNumberFoo)),
				fmt.Sprintf("/foo/%v/finished.json", latestBuildNumberFoo): marshalOrDie(utils.FinishedFile{
					Result:    "UNSTABLE",
					Timestamp: 1234,
				}, t),
				fmt.Sprintf("/foo/%v/artifacts/junit_01.xml", latestBuildNumberFoo): getJUnit(5, 0),
				fmt.Sprintf("/foo/%v/artifacts/junit_02.xml", latestBuildNumberFoo): getJUnit(5, 1),
				fmt.Sprintf("/foo/%v/artifacts/junit_03.xml", latestBuildNumberFoo): getJUnit(5, 0),
				"/bar/latest-build.txt":                                             []byte(strconv.Itoa(latestBuildNumberBar)),
				fmt.Sprintf("/bar/%v/finished.json", latestBuildNumberBar): marshalOrDie(utils.FinishedFile{
					Result:    "SUCCESS",
					Timestamp: 1234,
				}, t),
			},
			expectStable: false,
			expectedStatus: map[string]BuildInfo{
				"foo": {Status: "Not Stable", ID: "42"},
				"bar": {Status: "Stable", ID: "44"},
			},
		},
		// Result shouldn't depend on order.
		{
			paths: map[string][]byte{
				"/foo/latest-build.txt": []byte(strconv.Itoa(latestBuildNumberFoo)),
				fmt.Sprintf("/foo/%v/finished.json", latestBuildNumberFoo): marshalOrDie(utils.FinishedFile{
					Result:    "SUCCESS",
					Timestamp: 1234,
				}, t),
				"/bar/latest-build.txt": []byte(strconv.Itoa(latestBuildNumberBar)),
				fmt.Sprintf("/bar/%v/finished.json", latestBuildNumberBar): marshalOrDie(utils.FinishedFile{
					Result:    "FAILURE",
					Timestamp: 1234,
				}, t),
				fmt.Sprintf("/bar/%v/artifacts/junit_01.xml", latestBuildNumberBar): getJUnit(5, 0),
				fmt.Sprintf("/bar/%v/artifacts/junit_02.xml", latestBuildNumberBar): getJUnit(5, 1),
				fmt.Sprintf("/bar/%v/artifacts/junit_03.xml", latestBuildNumberBar): getJUnit(5, 1),
			},
			expectStable: false,
			expectedStatus: map[string]BuildInfo{
				"foo": {Status: "Stable", ID: "42"},
				"bar": {Status: "Not Stable", ID: "44"},
			},
		},
	}
	for _, test := range tests {
		server := httptest.NewServer(&testHandler{
			handler: func(res http.ResponseWriter, req *http.Request) {
				data, found := test.paths[req.URL.Path]
				if !found {
					res.WriteHeader(http.StatusNotFound)
					fmt.Fprintf(res, "Unknown path: %s", req.URL.Path)
					return
				}
				res.WriteHeader(http.StatusOK)
				res.Write(data)
			},
		})
		e2e := &RealE2ETester{
			WeakStableJobNames: []string{
				"foo",
				"bar",
			},
			BuildStatus:          map[string]BuildInfo{},
			GoogleGCSBucketUtils: utils.NewTestUtils(server.URL),
		}
		e2e.Init(nil)
		stable := e2e.GCSWeakStable()
		if stable != test.expectStable {
			t.Errorf("expected: %v, saw: %v", test.expectStable, stable)
		}
		if !reflect.DeepEqual(test.expectedStatus, e2e.BuildStatus) {
			t.Errorf("expected: %v, saw: %v", test.expectedStatus, e2e.BuildStatus)
		}
	}
}