Index: go/src/infra/appengine/test-results/model/full_result.go |
diff --git a/go/src/infra/appengine/test-results/model/full_result.go b/go/src/infra/appengine/test-results/model/full_result.go |
new file mode 100644 |
index 0000000000000000000000000000000000000000..0caebf7b5508369acd2c754f4691a730f201d27b |
--- /dev/null |
+++ b/go/src/infra/appengine/test-results/model/full_result.go |
@@ -0,0 +1,313 @@ |
+package model |
+ |
+import ( |
+ "encoding/json" |
+ "errors" |
+ "strings" |
+) |
+ |
+// FullResult represents "full_results.json". |
+type FullResult struct { |
+ Version int `json:"version"` |
+ Builder string `json:"builder_name"` |
+ BuildNumber Number `json:"build_number"` |
+ SecondsEpoch int64 `json:"seconds_since_epoch"` |
+ Tests FullTest `json:"tests"` |
+ FailuresByType map[string]int `json:"num_failures_by_type"` |
+ |
+ // These fields are optional. |
+ |
+ ChromiumRev *string `json:"chromium_revision,omitempty"` |
+ PathDelim *string `json:"path_delimiter,omitempty"` |
+ Interrupted *bool `json:"interrupted,omitempty"` |
+ BlinkRev *string `json:"blink_revision,omitempty"` |
+ |
+ // These fields are layout test specific. |
+ |
+ PrettyPatch *bool `json:"has_pretty_patch,omitempty"` |
+ Wdiff *bool `json:"has_wdiff"` |
+ LayoutTestsDir *string `json:"layout_tests_dir,omitempty"` |
+ PixelTestsEnabled *bool `json:"pixel_tests_enabled,omitempty"` |
+ |
+ // These fields are deprecated. However, uploaders still produce them: |
+ // https://chromium.googlesource.com/chromium/src/+/530b8ac05b53ddc56a3787ad69bb1a843eee2f95/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/models/test_run_results.py#172 |
+ |
+ Fixable *int `json:"fixable,omitempty"` |
+ NumFlaky *int `json:"num_flaky,omitempty"` |
+ NumPasses *int `json:"num_passes,omitempty"` |
+ NumRegressions *int `json:"num_regressions,omitempty"` |
+ Skips *int `json:"skips,omitempty"` |
+} |
+ |
+// AggregateResult converts fr to an AggregateResult. The returned |
+// AggregateResult does not share references to objects referenced |
+// by fr. |
+func (fr *FullResult) AggregateResult() (AggregateResult, error) { |
+ cRev := []string{} |
+ if fr.ChromiumRev != nil { |
+ cRev = append(cRev, *fr.ChromiumRev) |
+ } |
+ |
+ failuresByType := make(map[string][]int) |
+ for k, v := range fr.FailuresByType { |
+ failuresByType[k] = []int{v} |
+ } |
+ |
+ tests, err := fr.Tests.AggregateTest() |
+ if err != nil { |
+ return AggregateResult{}, err |
+ } |
+ |
+ return AggregateResult{ |
+ Version: ResultsVersion, |
+ Builder: fr.Builder, |
+ BuilderInfo: &BuilderInfo{ |
+ SecondsEpoch: []int64{fr.SecondsEpoch}, |
+ BuildNumbers: []Number{fr.BuildNumber}, |
+ ChromeRevs: cRev, |
+ Tests: tests, |
+ FailureMap: FailureLongNames, |
+ FailuresByType: failuresByType, |
+ }, |
+ }, nil |
+} |
+ |
+// FullTest represents Tests in a FullResult. |
+type FullTest map[string]Node |
+ |
+var _ Node = (FullTest)(nil) |
+ |
+func (ft FullTest) node() {} |
+ |
+// AggregateTest converts ft to an AggregateTest. The returned |
+// AggregateTest does not share references to objects referenced |
+// by ft. |
+func (ft FullTest) AggregateTest() (AggregateTest, error) { |
+ var aggr AggregateTest |
+ |
+ for k, v := range ft { |
+ if l, ok := v.(*FullTestLeaf); ok { |
+ aggrLeaf, err := l.AggregateTestLeaf() |
+ if err != nil { |
+ return nil, err |
+ } |
+ |
+ if aggr == nil { |
+ aggr = AggregateTest{} |
+ } |
+ aggr[k] = &aggrLeaf |
+ continue |
+ } |
+ |
+ child, ok := v.(FullTest) |
+ if !ok { |
+ return nil, errors.New("model: expected FullTest") |
+ } |
+ |
+ next, err := child.AggregateTest() |
+ if err != nil { |
+ return nil, err |
+ } |
+ |
+ if aggr == nil { |
+ aggr = AggregateTest{} |
+ } |
+ aggr[k] = next |
+ } |
+ |
+ return aggr, nil |
+} |
+ |
+// MarshalJSON marshals ft into JSON. |
+func (ft *FullTest) MarshalJSON() ([]byte, error) { |
+ if ft == nil { |
+ return json.Marshal(ft) |
+ } |
+ |
+ m := make(map[string]*json.RawMessage) |
+ |
+ for k, v := range *ft { |
+ b, err := json.Marshal(&v) |
+ if err != nil { |
+ return nil, err |
+ } |
+ raw := json.RawMessage(b) |
+ m[k] = &raw |
+ } |
+ |
+ return json.Marshal(m) |
+} |
+ |
+// UnmarshalJSON unmarshals the supplied data into ft. |
+func (ft *FullTest) UnmarshalJSON(data []byte) error { |
+ var m map[string]interface{} |
+ if err := json.Unmarshal(data, &m); err != nil { |
+ return err |
+ } |
+ if ft == nil { |
+ return errors.New("model: UnmarshalJSON: nil *FullTest") |
+ } |
+ if *ft == nil { |
+ *ft = FullTest{} |
+ } |
+ return ft.constructTree(m) |
+} |
+ |
+// constructTree constructs the tree of Nodes from the supplied map. |
+// constructTree returns an error if the receiver is nil. |
+func (ft *FullTest) constructTree(m map[string]interface{}) error { |
+ for k, v := range m { |
+ mm, ok := v.(map[string]interface{}) |
+ if !ok { |
+ continue |
+ } |
+ |
+ if isFullTestLeaf(mm) { |
+ l, err := makeFullTestLeaf(mm) |
+ if err != nil { |
+ return err |
+ } |
+ if *ft == nil { |
+ *ft = FullTest{} |
+ } |
+ (*ft)[k] = l |
+ continue |
+ } |
+ |
+ var child FullTest |
+ if err := child.constructTree(mm); err != nil { |
+ return err |
+ } |
+ if *ft == nil { |
+ *ft = FullTest{} |
+ } |
+ (*ft)[k] = child |
+ } |
+ |
+ return nil |
+} |
+ |
+// isFullTestLeaf returns true if the supplied map likely represents a |
+// FullTestLeaf. |
+func isFullTestLeaf(m map[string]interface{}) bool { |
+ for _, val := range m { |
+ if _, ok := val.(map[string]interface{}); ok { |
+ return false |
+ } |
+ } |
+ return true |
+} |
+ |
+// makeFullTestLeaf returns a FullTestLeaf from the supplied map. |
+func makeFullTestLeaf(m map[string]interface{}) (*FullTestLeaf, error) { |
+ l := &FullTestLeaf{} |
+ b, err := json.Marshal(m) |
+ if err != nil { |
+ return nil, err |
+ } |
+ err = json.Unmarshal(b, &l) |
+ return l, err |
+} |
+ |
+// FullTestLeaf represents the results for a particular test name. |
+type FullTestLeaf struct { |
+ Actual []string `json:"-"` |
+ Expected []string `json:"-"` |
+ |
+ // These fields are optional. |
+ |
+ Runtime *float64 `json:"time,omitempty"` // In seconds. |
+ Bugs []string `json:"bugs"` |
+ Unexpected *bool `json:"is_unexpected,omitempty"` |
+ |
+ // These fields are layout test specific. |
+ |
+ RepaintOverlay *bool `json:"has_repaint_overlay,omitempty"` |
+ MissingAudio *bool `json:"is_missing_audio,omitempty"` |
+ MissingText *bool `json:"is_missing_text,omitempty"` |
+ MissingVideo *bool `json:"is_missing_video,omitempty"` |
+ UsedTestHarness *bool `json:"is_testharness_test,omitempty"` |
+ ReferenceTestType *string `json:"reftest_type,omitempty"` |
+} |
+ |
+var _ Node = (*FullTestLeaf)(nil) |
+ |
+func (l *FullTestLeaf) node() {} |
+ |
+// fullTestLeafAlias helps unmarshal and marshal FullTestLeaf. |
+type fullTestLeafAlias FullTestLeaf |
+ |
+// testResultAux helps unmarshal and marshal FullTestLeaf. |
+type testResultAux struct { |
+ Actual string `json:"actual"` |
+ Expected string `json:"expected"` |
+ *fullTestLeafAlias |
+} |
+ |
+// AggregateTestLeaf converts l to AggregateTestLeaf. The returned |
+// AggregateTestLeaf does not share references to objects |
+// referenced by l. |
+// |
+// The returned error is always nil, but callers should check the |
+// error anyway because this behavior may change in the future. |
+func (l *FullTestLeaf) AggregateTestLeaf() (AggregateTestLeaf, error) { |
+ var ret AggregateTestLeaf |
+ |
+ expected := strings.Join(l.Expected, " ") |
+ actual := strings.Join(l.Actual, " ") |
+ |
+ if expected != "PASS" && expected != "NOTRUN" { |
+ ret.Expected = make([]string, len(l.Expected)) |
+ copy(ret.Expected, l.Expected) |
+ } |
+ |
+ var shortFailures string |
+ if (expected != "SKIP" && actual == "SKIP") || expected == "NOTRUN" { |
+ shortFailures = "Y" |
+ } else { |
+ for _, f := range l.Actual { |
+ val, ok := FailureShortNames[f] |
+ if ok { |
+ shortFailures += val |
+ } else { |
+ shortFailures += "U" |
+ } |
+ } |
+ } |
+ ret.Results = []ResultSummary{{1, shortFailures}} |
+ |
+ if len(l.Bugs) > 0 { |
+ ret.Bugs = make([]string, len(l.Bugs)) |
+ copy(ret.Bugs, l.Bugs) |
+ } |
+ |
+ var time float64 |
+ if l.Runtime != nil { |
+ time = *l.Runtime |
+ } |
+ ret.Runtimes = []RuntimeSummary{{1, time}} |
+ |
+ return ret, nil |
+} |
+ |
+// MarshalJSON marshals l into JSON. |
+func (l *FullTestLeaf) MarshalJSON() ([]byte, error) { |
+ aux := testResultAux{fullTestLeafAlias: (*fullTestLeafAlias)(l)} |
+ aux.Actual = strings.Join(l.Actual, " ") |
+ aux.Expected = strings.Join(l.Expected, " ") |
+ return json.Marshal(&aux) |
+} |
+ |
+// UnmarshalJSON unmarshals the supplied data into l. |
+func (l *FullTestLeaf) UnmarshalJSON(data []byte) error { |
+ aux := testResultAux{fullTestLeafAlias: (*fullTestLeafAlias)(l)} |
+ if err := json.Unmarshal(data, &aux); err != nil { |
+ return err |
+ } |
+ l.Actual = strings.Split(aux.Actual, " ") |
+ l.Expected = strings.Split(aux.Expected, " ") |
+ return nil |
+} |
+ |
+// Times represents "times_ms.json". |
+type Times map[string]float64 |