Chromium Code Reviews| 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..3f5474d1bb44eb381294f6192e513e07d4c6600d |
| --- /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) { |
|
estaab
2016/08/12 15:17:20
won't this make a copy of ft since it's not a poin
nishanths
2016/08/12 16:45:58
FullTest's underlying type is a map, so it will be
|
| + 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 = float64(round(*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 |