github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/dashboard/app/build/test.go (about)

     1  // Copyright 2011 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // +build appengine
     6  
     7  package build
     8  
     9  // TODO(adg): test authentication
    10  // TODO(adg): refactor to use appengine/aetest instead
    11  
    12  import (
    13  	"bytes"
    14  	"encoding/json"
    15  	"errors"
    16  	"fmt"
    17  	"io"
    18  	"net/http"
    19  	"net/http/httptest"
    20  	"net/url"
    21  	"strings"
    22  	"time"
    23  
    24  	"appengine"
    25  	"appengine/datastore"
    26  )
    27  
    28  func init() {
    29  	handleFunc("/buildtest", testHandler)
    30  }
    31  
    32  var testEntityKinds = []string{
    33  	"Package",
    34  	"Commit",
    35  	"CommitRun",
    36  	"Result",
    37  	"PerfResult",
    38  	"PerfMetricRun",
    39  	"PerfConfig",
    40  	"PerfTodo",
    41  	"Log",
    42  }
    43  
    44  const testPkg = "golang.org/x/test"
    45  
    46  var testPackage = &Package{Name: "Test", Kind: "subrepo", Path: testPkg}
    47  
    48  var testPackages = []*Package{
    49  	{Name: "Go", Path: ""},
    50  	testPackage,
    51  }
    52  
    53  var tCommitTime = time.Now().Add(-time.Hour * 24 * 7)
    54  
    55  func tCommit(hash, parentHash, path string, bench bool) *Commit {
    56  	tCommitTime.Add(time.Hour) // each commit should have a different time
    57  	return &Commit{
    58  		PackagePath:       path,
    59  		Hash:              hash,
    60  		ParentHash:        parentHash,
    61  		Time:              tCommitTime,
    62  		User:              "adg",
    63  		Desc:              "change description " + hash,
    64  		NeedsBenchmarking: bench,
    65  	}
    66  }
    67  
    68  var testRequests = []struct {
    69  	path string
    70  	vals url.Values
    71  	req  interface{}
    72  	res  interface{}
    73  }{
    74  	// Packages
    75  	{"/packages", url.Values{"kind": {"subrepo"}}, nil, []*Package{testPackage}},
    76  
    77  	// Go repo
    78  	{"/commit", nil, tCommit("0001", "0000", "", true), nil},
    79  	{"/commit", nil, tCommit("0002", "0001", "", false), nil},
    80  	{"/commit", nil, tCommit("0003", "0002", "", true), nil},
    81  	{"/todo", url.Values{"kind": {"build-go-commit"}, "builder": {"linux-386"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0003"}}},
    82  	{"/todo", url.Values{"kind": {"build-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0003"}}},
    83  	{"/result", nil, &Result{Builder: "linux-386", Hash: "0001", OK: true}, nil},
    84  	{"/todo", url.Values{"kind": {"build-go-commit"}, "builder": {"linux-386"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0003"}}},
    85  	{"/result", nil, &Result{Builder: "linux-386", Hash: "0002", OK: true}, nil},
    86  	{"/todo", url.Values{"kind": {"build-go-commit"}, "builder": {"linux-386"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0003"}}},
    87  
    88  	// Other builders, to test the UI.
    89  	{"/result", nil, &Result{Builder: "linux-amd64", Hash: "0001", OK: true}, nil},
    90  	{"/result", nil, &Result{Builder: "linux-amd64-race", Hash: "0001", OK: true}, nil},
    91  	{"/result", nil, &Result{Builder: "netbsd-386", Hash: "0001", OK: true}, nil},
    92  	{"/result", nil, &Result{Builder: "plan9-386", Hash: "0001", OK: true}, nil},
    93  	{"/result", nil, &Result{Builder: "windows-386", Hash: "0001", OK: true}, nil},
    94  	{"/result", nil, &Result{Builder: "windows-amd64", Hash: "0001", OK: true}, nil},
    95  	{"/result", nil, &Result{Builder: "windows-amd64-race", Hash: "0001", OK: true}, nil},
    96  	{"/result", nil, &Result{Builder: "linux-amd64-temp", Hash: "0001", OK: true}, nil},
    97  
    98  	// multiple builders
    99  	{"/todo", url.Values{"kind": {"build-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0003"}}},
   100  	{"/result", nil, &Result{Builder: "linux-amd64", Hash: "0003", OK: true}, nil},
   101  	{"/todo", url.Values{"kind": {"build-go-commit"}, "builder": {"linux-386"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0003"}}},
   102  	{"/todo", url.Values{"kind": {"build-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0002"}}},
   103  
   104  	// branches
   105  	{"/commit", nil, tCommit("0004", "0003", "", false), nil},
   106  	{"/commit", nil, tCommit("0005", "0002", "", false), nil},
   107  	{"/todo", url.Values{"kind": {"build-go-commit"}, "builder": {"linux-386"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0005"}}},
   108  	{"/result", nil, &Result{Builder: "linux-386", Hash: "0005", OK: true}, nil},
   109  	{"/todo", url.Values{"kind": {"build-go-commit"}, "builder": {"linux-386"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0004"}}},
   110  	{"/result", nil, &Result{Builder: "linux-386", Hash: "0004", OK: false}, nil},
   111  	{"/todo", url.Values{"kind": {"build-go-commit"}, "builder": {"linux-386"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0003"}}},
   112  
   113  	// logs
   114  	{"/result", nil, &Result{Builder: "linux-386", Hash: "0003", OK: false, Log: "test"}, nil},
   115  	{"/log/a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", nil, nil, "test"},
   116  	{"/todo", url.Values{"kind": {"build-go-commit"}, "builder": {"linux-386"}}, nil, nil},
   117  
   118  	// repeat failure (shouldn't re-send mail)
   119  	{"/result", nil, &Result{Builder: "linux-386", Hash: "0003", OK: false, Log: "test"}, nil},
   120  
   121  	// non-Go repos
   122  	{"/commit", nil, tCommit("1001", "0000", testPkg, false), nil},
   123  	{"/commit", nil, tCommit("1002", "1001", testPkg, false), nil},
   124  	{"/commit", nil, tCommit("1003", "1002", testPkg, false), nil},
   125  	{"/todo", url.Values{"kind": {"build-package"}, "builder": {"linux-386"}, "packagePath": {testPkg}, "goHash": {"0001"}}, nil, &Todo{Kind: "build-package", Data: &Commit{Hash: "1003"}}},
   126  	{"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-386", Hash: "1003", GoHash: "0001", OK: true}, nil},
   127  	{"/todo", url.Values{"kind": {"build-package"}, "builder": {"linux-386"}, "packagePath": {testPkg}, "goHash": {"0001"}}, nil, &Todo{Kind: "build-package", Data: &Commit{Hash: "1002"}}},
   128  	{"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-386", Hash: "1002", GoHash: "0001", OK: true}, nil},
   129  	{"/todo", url.Values{"kind": {"build-package"}, "builder": {"linux-386"}, "packagePath": {testPkg}, "goHash": {"0001"}}, nil, &Todo{Kind: "build-package", Data: &Commit{Hash: "1001"}}},
   130  	{"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-386", Hash: "1001", GoHash: "0001", OK: true}, nil},
   131  	{"/todo", url.Values{"kind": {"build-package"}, "builder": {"linux-386"}, "packagePath": {testPkg}, "goHash": {"0001"}}, nil, nil},
   132  	{"/todo", url.Values{"kind": {"build-package"}, "builder": {"linux-386"}, "packagePath": {testPkg}, "goHash": {"0002"}}, nil, &Todo{Kind: "build-package", Data: &Commit{Hash: "1003"}}},
   133  
   134  	// re-build Go revision for stale subrepos
   135  	{"/todo", url.Values{"kind": {"build-go-commit"}, "builder": {"linux-386"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0005"}}},
   136  	{"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-386", Hash: "1001", GoHash: "0005", OK: false, Log: "boo"}, nil},
   137  	{"/todo", url.Values{"kind": {"build-go-commit"}, "builder": {"linux-386"}}, nil, nil},
   138  
   139  	// benchmarks
   140  	// build-go-commit must have precedence over benchmark-go-commit
   141  	{"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0005"}}},
   142  	// drain build-go-commit todo
   143  	{"/result", nil, &Result{Builder: "linux-amd64", Hash: "0005", OK: true}, nil},
   144  	{"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0004"}}},
   145  	{"/result", nil, &Result{Builder: "linux-amd64", Hash: "0004", OK: true}, nil},
   146  	{"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0002"}}},
   147  	{"/result", nil, &Result{Builder: "linux-amd64", Hash: "0002", OK: true}, nil},
   148  	// drain sub-repo todos
   149  	{"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-amd64", Hash: "1001", GoHash: "0005", OK: false}, nil},
   150  	{"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-amd64", Hash: "1002", GoHash: "0005", OK: false}, nil},
   151  	{"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-amd64", Hash: "1003", GoHash: "0005", OK: false}, nil},
   152  	// now we must get benchmark todo
   153  	{"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "benchmark-go-commit", Data: &Commit{Hash: "0003", PerfResults: []string{}}}},
   154  	{"/perf-result", nil, &PerfRequest{Builder: "linux-amd64", Benchmark: "http", Hash: "0003", OK: true}, nil},
   155  	{"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "benchmark-go-commit", Data: &Commit{Hash: "0003", PerfResults: []string{"http"}}}},
   156  	{"/perf-result", nil, &PerfRequest{Builder: "linux-amd64", Benchmark: "json", Hash: "0003", OK: true}, nil},
   157  	{"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "benchmark-go-commit", Data: &Commit{Hash: "0003", PerfResults: []string{"http", "json"}}}},
   158  	{"/perf-result", nil, &PerfRequest{Builder: "linux-amd64", Benchmark: "meta-done", Hash: "0003", OK: true}, nil},
   159  	{"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "benchmark-go-commit", Data: &Commit{Hash: "0001", PerfResults: []string{}}}},
   160  	{"/perf-result", nil, &PerfRequest{Builder: "linux-amd64", Benchmark: "http", Hash: "0001", OK: true}, nil},
   161  	{"/perf-result", nil, &PerfRequest{Builder: "linux-amd64", Benchmark: "meta-done", Hash: "0001", OK: true}, nil},
   162  	{"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-amd64"}}, nil, nil},
   163  	// create new commit, it must appear in todo
   164  	{"/commit", nil, tCommit("0006", "0005", "", true), nil},
   165  	// drain build-go-commit todo
   166  	{"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0006"}}},
   167  	{"/result", nil, &Result{Builder: "linux-amd64", Hash: "0006", OK: true}, nil},
   168  	{"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-amd64", Hash: "1003", GoHash: "0006", OK: false}, nil},
   169  	{"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-amd64", Hash: "1002", GoHash: "0006", OK: false}, nil},
   170  	{"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-amd64", Hash: "1001", GoHash: "0006", OK: false}, nil},
   171  	// now we must get benchmark todo
   172  	{"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "benchmark-go-commit", Data: &Commit{Hash: "0006", PerfResults: []string{}}}},
   173  	{"/perf-result", nil, &PerfRequest{Builder: "linux-amd64", Benchmark: "http", Hash: "0006", OK: true}, nil},
   174  	{"/perf-result", nil, &PerfRequest{Builder: "linux-amd64", Benchmark: "meta-done", Hash: "0006", OK: true}, nil},
   175  	{"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-amd64"}}, nil, nil},
   176  	// create new benchmark, all commits must re-appear in todo
   177  	{"/commit", nil, tCommit("0007", "0006", "", true), nil},
   178  	// drain build-go-commit todo
   179  	{"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0007"}}},
   180  	{"/result", nil, &Result{Builder: "linux-amd64", Hash: "0007", OK: true}, nil},
   181  	{"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-amd64", Hash: "1003", GoHash: "0007", OK: false}, nil},
   182  	{"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-amd64", Hash: "1002", GoHash: "0007", OK: false}, nil},
   183  	{"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-amd64", Hash: "1001", GoHash: "0007", OK: false}, nil},
   184  	// now we must get benchmark todo
   185  	{"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "benchmark-go-commit", Data: &Commit{Hash: "0007", PerfResults: []string{}}}},
   186  	{"/perf-result", nil, &PerfRequest{Builder: "linux-amd64", Benchmark: "bson", Hash: "0007", OK: true}, nil},
   187  	{"/perf-result", nil, &PerfRequest{Builder: "linux-amd64", Benchmark: "meta-done", Hash: "0007", OK: true}, nil},
   188  	{"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "benchmark-go-commit", Data: &Commit{Hash: "0007", PerfResults: []string{"bson"}}}},
   189  	{"/perf-result", nil, &PerfRequest{Builder: "linux-amd64", Benchmark: "meta-done", Hash: "0007", OK: true}, nil},
   190  	{"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "benchmark-go-commit", Data: &Commit{Hash: "0006", PerfResults: []string{"http"}}}},
   191  	{"/perf-result", nil, &PerfRequest{Builder: "linux-amd64", Benchmark: "meta-done", Hash: "0006", OK: true}, nil},
   192  	{"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "benchmark-go-commit", Data: &Commit{Hash: "0001", PerfResults: []string{"http"}}}},
   193  	{"/perf-result", nil, &PerfRequest{Builder: "linux-amd64", Benchmark: "meta-done", Hash: "0001", OK: true}, nil},
   194  	{"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-amd64"}}, nil, &Todo{Kind: "benchmark-go-commit", Data: &Commit{Hash: "0003", PerfResults: []string{"http", "json"}}}},
   195  	{"/perf-result", nil, &PerfRequest{Builder: "linux-amd64", Benchmark: "meta-done", Hash: "0003", OK: true}, nil},
   196  	{"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-amd64"}}, nil, nil},
   197  	// attach second builder
   198  	{"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-386"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0007"}}},
   199  	// drain build-go-commit todo
   200  	{"/result", nil, &Result{Builder: "linux-386", Hash: "0007", OK: true}, nil},
   201  	{"/result", nil, &Result{Builder: "linux-386", Hash: "0006", OK: true}, nil},
   202  	{"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-386", Hash: "1003", GoHash: "0007", OK: false}, nil},
   203  	{"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-386", Hash: "1002", GoHash: "0007", OK: false}, nil},
   204  	{"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-386", Hash: "1001", GoHash: "0007", OK: false}, nil},
   205  	// now we must get benchmark todo
   206  	{"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-386"}}, nil, &Todo{Kind: "benchmark-go-commit", Data: &Commit{Hash: "0007"}}},
   207  	{"/perf-result", nil, &PerfRequest{Builder: "linux-386", Benchmark: "meta-done", Hash: "0007", OK: true}, nil},
   208  	{"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-386"}}, nil, &Todo{Kind: "benchmark-go-commit", Data: &Commit{Hash: "0006"}}},
   209  	{"/perf-result", nil, &PerfRequest{Builder: "linux-386", Benchmark: "meta-done", Hash: "0006", OK: true}, nil},
   210  	{"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-386"}}, nil, &Todo{Kind: "benchmark-go-commit", Data: &Commit{Hash: "0001"}}},
   211  	{"/perf-result", nil, &PerfRequest{Builder: "linux-386", Benchmark: "meta-done", Hash: "0001", OK: true}, nil},
   212  	{"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-386"}}, nil, &Todo{Kind: "benchmark-go-commit", Data: &Commit{Hash: "0003"}}},
   213  	{"/perf-result", nil, &PerfRequest{Builder: "linux-386", Benchmark: "meta-done", Hash: "0003", OK: true}, nil},
   214  	{"/todo", url.Values{"kind": {"build-go-commit", "benchmark-go-commit"}, "builder": {"linux-386"}}, nil, nil},
   215  }
   216  
   217  func testHandler(w http.ResponseWriter, r *http.Request) {
   218  	if !appengine.IsDevAppServer() {
   219  		fmt.Fprint(w, "These tests must be run under the dev_appserver.")
   220  		return
   221  	}
   222  	c := appengine.NewContext(r)
   223  	if err := nukeEntities(c, testEntityKinds); err != nil {
   224  		logErr(w, r, err)
   225  		return
   226  	}
   227  	if r.FormValue("nukeonly") != "" {
   228  		fmt.Fprint(w, "OK")
   229  		return
   230  	}
   231  
   232  	for _, p := range testPackages {
   233  		if _, err := datastore.Put(c, p.Key(c), p); err != nil {
   234  			logErr(w, r, err)
   235  			return
   236  		}
   237  	}
   238  
   239  	origReq := *r
   240  	defer func() {
   241  		// HACK: We need to clobber the original request (see below)
   242  		// so make sure we fix it before exiting the handler.
   243  		*r = origReq
   244  	}()
   245  	for i, t := range testRequests {
   246  		c.Infof("running test %d %s vals='%q' req='%q' res='%q'", i, t.path, t.vals, t.req, t.res)
   247  		errorf := func(format string, args ...interface{}) {
   248  			fmt.Fprintf(w, "%d %s: ", i, t.path)
   249  			fmt.Fprintf(w, format, args...)
   250  			fmt.Fprintln(w)
   251  		}
   252  		var body io.ReadWriter
   253  		if t.req != nil {
   254  			body = new(bytes.Buffer)
   255  			json.NewEncoder(body).Encode(t.req)
   256  		}
   257  		url := "http://" + domain + t.path
   258  		if t.vals != nil {
   259  			url += "?" + t.vals.Encode() + "&version=2"
   260  		} else {
   261  			url += "?version=2"
   262  		}
   263  		req, err := http.NewRequest("POST", url, body)
   264  		if err != nil {
   265  			logErr(w, r, err)
   266  			return
   267  		}
   268  		if t.req != nil {
   269  			req.Method = "POST"
   270  		}
   271  		req.Header = origReq.Header
   272  		rec := httptest.NewRecorder()
   273  
   274  		// Make the request
   275  		*r = *req // HACK: App Engine uses the request pointer
   276  		// as a map key to resolve Contexts.
   277  		http.DefaultServeMux.ServeHTTP(rec, r)
   278  
   279  		if rec.Code != 0 && rec.Code != 200 {
   280  			errorf(rec.Body.String())
   281  			return
   282  		}
   283  		c.Infof("response='%v'", rec.Body.String())
   284  		resp := new(dashResponse)
   285  
   286  		// If we're expecting a *Todo value,
   287  		// prime the Response field with a Todo and a Commit inside it.
   288  		if t.path == "/todo" {
   289  			resp.Response = &Todo{Data: &Commit{}}
   290  		}
   291  
   292  		if strings.HasPrefix(t.path, "/log/") {
   293  			resp.Response = rec.Body.String()
   294  		} else {
   295  			err := json.NewDecoder(rec.Body).Decode(resp)
   296  			if err != nil {
   297  				errorf("decoding response: %v", err)
   298  				return
   299  			}
   300  		}
   301  		if e, ok := t.res.(string); ok {
   302  			g, ok := resp.Response.(string)
   303  			if !ok {
   304  				errorf("Response not string: %T", resp.Response)
   305  				return
   306  			}
   307  			if g != e {
   308  				errorf("response mismatch: got %q want %q", g, e)
   309  				return
   310  			}
   311  		}
   312  		if e, ok := t.res.(*Todo); ok {
   313  			g, ok := resp.Response.(*Todo)
   314  			if !ok {
   315  				errorf("Response not *Todo: %T", resp.Response)
   316  				return
   317  			}
   318  			if e.Data == nil && g.Data != nil {
   319  				errorf("Response.Data should be nil, got: %v", g.Data)
   320  				return
   321  			}
   322  			if g.Data == nil {
   323  				errorf("Response.Data is nil, want: %v", e.Data)
   324  				return
   325  			}
   326  			gd, ok := g.Data.(*Commit)
   327  			if !ok {
   328  				errorf("Response.Data not *Commit: %T", g.Data)
   329  				return
   330  			}
   331  			if g.Kind != e.Kind {
   332  				errorf("kind don't match: got %q, want %q", g.Kind, e.Kind)
   333  				return
   334  			}
   335  			ed := e.Data.(*Commit)
   336  			if ed.Hash != gd.Hash {
   337  				errorf("hashes don't match: got %q, want %q", gd.Hash, ed.Hash)
   338  				return
   339  			}
   340  			if len(gd.PerfResults) != len(ed.PerfResults) {
   341  				errorf("result data len don't match: got %v, want %v", len(gd.PerfResults), len(ed.PerfResults))
   342  				return
   343  			}
   344  			for i := range gd.PerfResults {
   345  				if gd.PerfResults[i] != ed.PerfResults[i] {
   346  					errorf("result data %v don't match: got %v, want %v", i, gd.PerfResults[i], ed.PerfResults[i])
   347  					return
   348  				}
   349  			}
   350  		}
   351  		if t.res == nil && resp.Response != nil {
   352  			errorf("response mismatch: got %q expected <nil>", resp.Response)
   353  			return
   354  		}
   355  	}
   356  	fmt.Fprint(w, "PASS\nYou should see only one mail notification (for 0003/linux-386) in the dev_appserver logs.")
   357  }
   358  
   359  func nukeEntities(c appengine.Context, kinds []string) error {
   360  	if !appengine.IsDevAppServer() {
   361  		return errors.New("can't nuke production data")
   362  	}
   363  	var keys []*datastore.Key
   364  	for _, kind := range kinds {
   365  		q := datastore.NewQuery(kind).KeysOnly()
   366  		for t := q.Run(c); ; {
   367  			k, err := t.Next(nil)
   368  			if err == datastore.Done {
   369  				break
   370  			}
   371  			if err != nil {
   372  				return err
   373  			}
   374  			keys = append(keys, k)
   375  		}
   376  	}
   377  	return datastore.DeleteMulti(c, keys)
   378  }