| Index: go/src/infra/appengine/test-results/model/aggregate_result.go
|
| diff --git a/go/src/infra/appengine/test-results/model/aggregate_result.go b/go/src/infra/appengine/test-results/model/aggregate_result.go
|
| index 8888733a27e2414c8d405cf72456456d7ae2eaee..4eccfaaac90859bcd90f08e81e1efd23eb2bf82d 100644
|
| --- a/go/src/infra/appengine/test-results/model/aggregate_result.go
|
| +++ b/go/src/infra/appengine/test-results/model/aggregate_result.go
|
| @@ -15,24 +15,26 @@ import (
|
| // and "results-small.json" files are using.
|
| const ResultsVersion = 4
|
|
|
| -var _ TestNode = (AggregateTest)(nil)
|
| -var _ TestNode = (*AggregateTestLeaf)(nil)
|
| -
|
| -// CleanTestResultsJSON returns the result of removing a known prefix
|
| -// and suffix from the contents in r. If the prefix or suffix do not exist,
|
| -// the returned io.Reader has the same contents as r.
|
| -func CleanTestResultsJSON(r io.Reader) (io.Reader, error) {
|
| - pre := []byte("ADD_RESULTS(")
|
| - suf := []byte(");")
|
| +var (
|
| + // CleanPrefix is the prefix that CleanJSON removes.
|
| + CleanPrefix = []byte("ADD_RESULTS(")
|
| + // CleanSuffix is the suffix that CleanJSON removes.
|
| + CleanSuffix = []byte(");")
|
| +)
|
|
|
| +// CleanJSON returns the result of removing CleanPrefix
|
| +// and CleanSuffix from the contents in r. If either
|
| +// CleanPrefix or CleanSuffix does not exist, the returned
|
| +// io.Reader has the same contents as r.
|
| +func CleanJSON(r io.Reader) (io.Reader, error) {
|
| b, err := ioutil.ReadAll(r)
|
| if err != nil {
|
| return nil, err
|
| }
|
|
|
| - if bytes.HasPrefix(b, pre) && bytes.HasSuffix(b, suf) {
|
| - result := bytes.TrimPrefix(b, pre)
|
| - result = bytes.TrimSuffix(result, suf)
|
| + if bytes.HasPrefix(b, CleanPrefix) && bytes.HasSuffix(b, CleanSuffix) {
|
| + result := bytes.TrimPrefix(b, CleanPrefix)
|
| + result = bytes.TrimSuffix(result, CleanSuffix)
|
| return bytes.NewReader(result), nil
|
| }
|
|
|
| @@ -47,14 +49,17 @@ type AggregateResult struct {
|
| *BuilderInfo
|
| }
|
|
|
| -// BuilderInfo represents aggregate information about a builder.
|
| +// BuilderInfo represents aggregate information for a builder.
|
| type BuilderInfo struct {
|
| // SecondsEpoch is the start time of tests expressed in seconds from
|
| // the Unix epoch.
|
| SecondsEpoch []int64 `json:"secondsSinceEpoch"`
|
|
|
| - BlinkRevs []number `json:"blinkRevision"`
|
| - BuildNumbers []number `json:"buildNumbers"`
|
| + // BlinkRevs is list of Blink revisions.
|
| + BlinkRevs []Number `json:"blinkRevision"`
|
| +
|
| + // BuildNumbers is list of build numbers.
|
| + BuildNumbers []Number `json:"buildNumbers"`
|
|
|
| // ChromeRevs is a list of Chrome/Chromium revisions.
|
| // The elements are strings because they can either be revision
|
| @@ -81,6 +86,7 @@ type BuilderInfo struct {
|
| FixableCounts []map[string]int `json:"fixableCounts,omitempty"`
|
| }
|
|
|
| +// MarshalJSON marshal ag into JSON.
|
| func (ag *AggregateResult) MarshalJSON() ([]byte, error) {
|
| v, err := json.Marshal(ag.Version)
|
| if err != nil {
|
| @@ -92,7 +98,7 @@ func (ag *AggregateResult) MarshalJSON() ([]byte, error) {
|
|
|
| // If FailuresByType exists, do not include FixableCounts
|
| // because it is deprecated.
|
| - if ag.FailuresByType != nil {
|
| + if info.FailuresByType != nil {
|
| info.FixableCounts = nil
|
| }
|
|
|
| @@ -133,7 +139,7 @@ func (ag *AggregateResult) UnmarshalJSON(data []byte) error {
|
|
|
| raw, ok := m[ag.Builder]
|
| if !ok {
|
| - return fmt.Errorf("model: missing builder: %s", ag.Builder)
|
| + return fmt.Errorf("model: missing builder %q", ag.Builder)
|
| }
|
|
|
| var info *BuilderInfo
|
| @@ -170,41 +176,23 @@ type fieldError struct {
|
| Value interface{} // Invalid value in the field that caused error.
|
| }
|
|
|
| -func (f fieldError) Error() string {
|
| - return fmt.Sprintf("model: field %s has invalid value: %v", f.Name, f.Value)
|
| +func (f *fieldError) Error() string {
|
| + return fmt.Sprintf("model: field %q has invalid value: %v", f.Name, f.Value)
|
| }
|
|
|
| func (ag *AggregateResult) checkFields() error {
|
| if ag.Version > ResultsVersion {
|
| - return fieldError{"Version", ag.Version}
|
| + return &fieldError{"Version", ag.Version}
|
| }
|
| if ag.BuilderInfo == nil {
|
| - return fieldError{"BuilderInfo", ag.BuilderInfo}
|
| + return &fieldError{"BuilderInfo", ag.BuilderInfo}
|
| }
|
| return nil
|
| }
|
|
|
| func (info *BuilderInfo) checkFields() error {
|
| - if info.SecondsEpoch == nil {
|
| - return fieldError{"SecondsEpoch", info.SecondsEpoch}
|
| - }
|
| - if info.BlinkRevs == nil {
|
| - return fieldError{"BlinkRevs", info.BlinkRevs}
|
| - }
|
| if info.BuildNumbers == nil {
|
| - return fieldError{"BuildNumbers", info.BuildNumbers}
|
| - }
|
| - if info.ChromeRevs == nil {
|
| - return fieldError{"ChromeRevs", info.ChromeRevs}
|
| - }
|
| - if info.Tests == nil {
|
| - return fieldError{"Tests", info.Tests}
|
| - }
|
| - if info.FailureMap == nil {
|
| - return fieldError{"FailureMap", info.FailureMap}
|
| - }
|
| - if info.FailuresByType == nil && info.FixableCounts == nil {
|
| - return fieldError{"FailuresByType", info.FailuresByType}
|
| + return &fieldError{"BuildNumbers", info.BuildNumbers}
|
| }
|
| return nil
|
| }
|
| @@ -226,7 +214,7 @@ func (info *BuilderInfo) computeFailuresByType() error {
|
| for short, count := range fc {
|
| long, ok := FailureLongNames[short]
|
| if !ok {
|
| - return fmt.Errorf("model: unknown key: %s", short)
|
| + return fmt.Errorf("model: unknown key %q", short)
|
| }
|
| res[long] = append(res[long], count)
|
| }
|
| @@ -237,38 +225,40 @@ func (info *BuilderInfo) computeFailuresByType() error {
|
| }
|
|
|
| // AggregateTest represents Tests in a AggregateResult.
|
| -type AggregateTest map[string]TestNode
|
| -
|
| -func (at AggregateTest) Walk(fn func(key string, node TestNode) error) {
|
| - for k, v := range at {
|
| - if leaf, ok := v.(*AggregateTestLeaf); ok {
|
| - if err := fn(k, leaf); err != nil {
|
| - return
|
| - }
|
| - continue
|
| - }
|
| -
|
| - if child, ok := v.(AggregateTest); ok {
|
| - if err := fn(k, child); err != nil {
|
| - return
|
| - }
|
| - child.Walk(fn)
|
| +type AggregateTest map[string]Node
|
| +
|
| +var _ Node = (AggregateTest)(nil)
|
| +
|
| +func (at AggregateTest) node() {}
|
| +
|
| +// Walk performs a depth-first traversal of the Nodes reachable
|
| +// from the receiver, calling fn each time. The Node in fn
|
| +// is guaranteed to be either AggregateTest or *AggregateTestLeaf.
|
| +// The traversal order may vary across different runs.
|
| +func (at AggregateTest) Walk(fn func(key string, node Node)) {
|
| + for key, node := range at {
|
| + switch val := node.(type) {
|
| + case *AggregateTestLeaf:
|
| + fn(key, val)
|
| + case AggregateTest:
|
| + fn(key, val)
|
| + val.Walk(fn)
|
| }
|
| }
|
| }
|
|
|
| +// WalkLeaves is similar to Walk but only calls fn for
|
| +// *AggregateTestLeaf.
|
| func (at AggregateTest) WalkLeaves(fn func(key string, leaf *AggregateTestLeaf)) {
|
| - at.Walk(func(k string, node TestNode) error {
|
| + at.Walk(func(key string, node Node) {
|
| if leaf, ok := node.(*AggregateTestLeaf); ok {
|
| - fn(k, leaf)
|
| + fn(key, leaf)
|
| }
|
| - return nil
|
| })
|
| }
|
|
|
| -func (at AggregateTest) Children() map[string]TestNode { return at }
|
| -func (at AggregateTest) testnode() {}
|
| -
|
| +// ToTestList set the Results and Runtimes fields of all the
|
| +// AggregateTestLeaf under the receiver AggregateTest to nil.
|
| func (at AggregateTest) ToTestList() {
|
| at.WalkLeaves(func(_ string, leaf *AggregateTestLeaf) {
|
| leaf.Results = nil
|
| @@ -276,6 +266,7 @@ func (at AggregateTest) ToTestList() {
|
| })
|
| }
|
|
|
| +// MarshalJSON marshals at into JSON.
|
| func (at *AggregateTest) MarshalJSON() ([]byte, error) {
|
| if at == nil {
|
| return json.Marshal(nil)
|
| @@ -295,13 +286,14 @@ func (at *AggregateTest) MarshalJSON() ([]byte, error) {
|
| return json.Marshal(m)
|
| }
|
|
|
| +// UnmarshalJSON unmarshals the supplied data into at.
|
| func (at *AggregateTest) UnmarshalJSON(data []byte) error {
|
| var m map[string]interface{}
|
| if err := json.Unmarshal(data, &m); err != nil {
|
| return err
|
| }
|
| if at == nil {
|
| - return errors.New("model: UnmarshalJSON on nil *AggregateTest")
|
| + return errors.New("model: UnmarshalJSON: nil *AggregateTest")
|
| }
|
| if *at == nil {
|
| *at = AggregateTest{}
|
| @@ -309,7 +301,7 @@ func (at *AggregateTest) UnmarshalJSON(data []byte) error {
|
| return at.constructTree(m)
|
| }
|
|
|
| -// constructTree constructs the tree of test nodes from the supplied map.
|
| +// constructTree constructs the tree of Nodes from the supplied map.
|
| func (at *AggregateTest) constructTree(m map[string]interface{}) error {
|
| for k, v := range m {
|
| mm, ok := v.(map[string]interface{})
|
| @@ -374,6 +366,8 @@ type AggregateTestLeaf struct {
|
| Bugs []string
|
| }
|
|
|
| +func (l *AggregateTestLeaf) node() {}
|
| +
|
| // aggregateTestLeafAux is used to marshal and unmarshal AggregateTestLeaf.
|
| type aggregateTestLeafAux struct {
|
| Results []ResultSummary `json:"results,omitempty"`
|
| @@ -382,9 +376,7 @@ type aggregateTestLeafAux struct {
|
| Bugs []string `json:"bugs,omitempty"`
|
| }
|
|
|
| -func (l *AggregateTestLeaf) Children() map[string]TestNode { return nil }
|
| -func (l *AggregateTestLeaf) testnode() {}
|
| -
|
| +// MarshalJSON marshal l into JSON.
|
| func (l *AggregateTestLeaf) MarshalJSON() ([]byte, error) {
|
| aux := aggregateTestLeafAux{
|
| Results: l.Results,
|
| @@ -397,6 +389,7 @@ func (l *AggregateTestLeaf) MarshalJSON() ([]byte, error) {
|
| return json.Marshal(&aux)
|
| }
|
|
|
| +// UnmarshalJSON unmarshal the supplied data into l.
|
| func (l *AggregateTestLeaf) UnmarshalJSON(data []byte) error {
|
| var aux aggregateTestLeafAux
|
| if err := json.Unmarshal(data, &aux); err != nil {
|
| @@ -423,11 +416,14 @@ func (l *AggregateTestLeaf) defaultFields() {
|
| }
|
| }
|
|
|
| +// ResultSummary is the type of test failure and count of how many
|
| +// times the running time occured.
|
| type ResultSummary struct {
|
| Count int
|
| Type string
|
| }
|
|
|
| +// MarshalJSON marshals rs into JSON.
|
| func (rs *ResultSummary) MarshalJSON() ([]byte, error) {
|
| return json.Marshal([]interface{}{
|
| rs.Count,
|
| @@ -435,34 +431,38 @@ func (rs *ResultSummary) MarshalJSON() ([]byte, error) {
|
| })
|
| }
|
|
|
| +// UnmarshalJSON unmarshals the provided data into rs.
|
| func (rs *ResultSummary) UnmarshalJSON(data []byte) error {
|
| var tmp []interface{}
|
| if err := json.Unmarshal(data, &tmp); err != nil {
|
| return err
|
| }
|
| if len(tmp) != 2 {
|
| - return fmt.Errorf("ResultSummary: wrong length: %d, expect: %d", len(tmp), 2)
|
| + return fmt.Errorf("model: UnmarshalJSON: ResultSummary wrong length: %d, expect: %d", len(tmp), 2)
|
| }
|
|
|
| count, ok := tmp[0].(float64)
|
| if !ok {
|
| - return fmt.Errorf("ResultSummary: wrong format: %v", tmp)
|
| + return fmt.Errorf("model: UnmarshalJSON: ResultSummary wrong type: %v", tmp)
|
| }
|
| rs.Count = int(count)
|
|
|
| rs.Type, ok = tmp[1].(string)
|
| if !ok {
|
| - return fmt.Errorf("ResultSummary: wrong format: %v", tmp)
|
| + return fmt.Errorf("model: UnmarshalJSON: ResultSummary wrong type: %v", tmp)
|
| }
|
|
|
| return nil
|
| }
|
|
|
| +// RuntimeSummary is the running time of a test and count of how many
|
| +// times the running time occured.
|
| type RuntimeSummary struct {
|
| Count int
|
| Runtime float64
|
| }
|
|
|
| +// MarshalJSON marshals rs into JSON.
|
| func (rs *RuntimeSummary) MarshalJSON() ([]byte, error) {
|
| return json.Marshal([]float64{
|
| float64(rs.Count),
|
| @@ -470,13 +470,14 @@ func (rs *RuntimeSummary) MarshalJSON() ([]byte, error) {
|
| })
|
| }
|
|
|
| +// UnmarshalJSON unmarshals the provided data into rs.
|
| func (rs *RuntimeSummary) UnmarshalJSON(data []byte) error {
|
| var tmp []float64
|
| if err := json.Unmarshal(data, &tmp); err != nil {
|
| return err
|
| }
|
| if len(tmp) != 2 {
|
| - return fmt.Errorf("RuntimeSummary: wrong length: %d, expect: %d", len(tmp), 2)
|
| + return fmt.Errorf("model: UnmarshalJSON: RuntimeSummary wrong length: %d, expect: %d", len(tmp), 2)
|
| }
|
|
|
| rs.Count = int(tmp[0])
|
|
|