OLD | NEW |
---|---|
(Empty) | |
1 package model | |
2 | |
3 import ( | |
4 "encoding/json" | |
5 "errors" | |
6 "strings" | |
7 ) | |
8 | |
9 // FullResult represents "full_results.json". | |
10 type FullResult struct { | |
11 Version int `json:"version"` | |
12 Builder string `json:"builder_name"` | |
13 BuildNumber Number `json:"build_number"` | |
14 SecondsEpoch int64 `json:"seconds_since_epoch"` | |
15 Tests FullTest `json:"tests"` | |
16 FailuresByType map[string]int `json:"num_failures_by_type"` | |
17 | |
18 // These fields are optional. | |
19 | |
20 ChromiumRev *string `json:"chromium_revision,omitempty"` | |
21 PathDelim *string `json:"path_delimiter,omitempty"` | |
22 Interrupted *bool `json:"interrupted,omitempty"` | |
23 BlinkRev *string `json:"blink_revision,omitempty"` | |
24 | |
25 // These fields are layout test specific. | |
26 | |
27 PrettyPatch *bool `json:"has_pretty_patch,omitempty"` | |
28 Wdiff *bool `json:"has_wdiff"` | |
29 LayoutTestsDir *string `json:"layout_tests_dir,omitempty"` | |
30 PixelTestsEnabled *bool `json:"pixel_tests_enabled,omitempty"` | |
31 | |
32 // These fields are deprecated. However, uploaders still produce them: | |
33 // https://chromium.googlesource.com/chromium/src/+/530b8ac05b53ddc56a 3787ad69bb1a843eee2f95/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/mo dels/test_run_results.py#172 | |
34 | |
35 Fixable *int `json:"fixable,omitempty"` | |
36 NumFlaky *int `json:"num_flaky,omitempty"` | |
37 NumPasses *int `json:"num_passes,omitempty"` | |
38 NumRegressions *int `json:"num_regressions,omitempty"` | |
39 Skips *int `json:"skips,omitempty"` | |
40 } | |
41 | |
42 // AggregateResult converts fr to an AggregateResult. The returned | |
43 // AggregateResult does not share references to objects referenced | |
44 // by fr. | |
45 func (fr *FullResult) AggregateResult() (AggregateResult, error) { | |
46 cRev := []string{} | |
47 if fr.ChromiumRev != nil { | |
48 cRev = append(cRev, *fr.ChromiumRev) | |
49 } | |
50 | |
51 failuresByType := make(map[string][]int) | |
52 for k, v := range fr.FailuresByType { | |
53 failuresByType[k] = []int{v} | |
54 } | |
55 | |
56 tests, err := fr.Tests.AggregateTest() | |
57 if err != nil { | |
58 return AggregateResult{}, err | |
59 } | |
60 | |
61 return AggregateResult{ | |
62 Version: ResultsVersion, | |
63 Builder: fr.Builder, | |
64 BuilderInfo: &BuilderInfo{ | |
65 SecondsEpoch: []int64{fr.SecondsEpoch}, | |
66 BuildNumbers: []Number{fr.BuildNumber}, | |
67 ChromeRevs: cRev, | |
68 Tests: tests, | |
69 FailureMap: FailureLongNames, | |
70 FailuresByType: failuresByType, | |
71 }, | |
72 }, nil | |
73 } | |
74 | |
75 // FullTest represents Tests in a FullResult. | |
76 type FullTest map[string]Node | |
77 | |
78 var _ Node = (FullTest)(nil) | |
79 | |
80 func (ft FullTest) node() {} | |
81 | |
82 // AggregateTest converts ft to an AggregateTest. The returned | |
83 // AggregateTest does not share references to objects referenced | |
84 // by ft. | |
85 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
| |
86 var aggr AggregateTest | |
87 | |
88 for k, v := range ft { | |
89 if l, ok := v.(*FullTestLeaf); ok { | |
90 aggrLeaf, err := l.AggregateTestLeaf() | |
91 if err != nil { | |
92 return nil, err | |
93 } | |
94 | |
95 if aggr == nil { | |
96 aggr = AggregateTest{} | |
97 } | |
98 aggr[k] = &aggrLeaf | |
99 continue | |
100 } | |
101 | |
102 child, ok := v.(FullTest) | |
103 if !ok { | |
104 return nil, errors.New("model: expected FullTest") | |
105 } | |
106 | |
107 next, err := child.AggregateTest() | |
108 if err != nil { | |
109 return nil, err | |
110 } | |
111 | |
112 if aggr == nil { | |
113 aggr = AggregateTest{} | |
114 } | |
115 aggr[k] = next | |
116 } | |
117 | |
118 return aggr, nil | |
119 } | |
120 | |
121 // MarshalJSON marshals ft into JSON. | |
122 func (ft *FullTest) MarshalJSON() ([]byte, error) { | |
123 if ft == nil { | |
124 return json.Marshal(ft) | |
125 } | |
126 | |
127 m := make(map[string]*json.RawMessage) | |
128 | |
129 for k, v := range *ft { | |
130 b, err := json.Marshal(&v) | |
131 if err != nil { | |
132 return nil, err | |
133 } | |
134 raw := json.RawMessage(b) | |
135 m[k] = &raw | |
136 } | |
137 | |
138 return json.Marshal(m) | |
139 } | |
140 | |
141 // UnmarshalJSON unmarshals the supplied data into ft. | |
142 func (ft *FullTest) UnmarshalJSON(data []byte) error { | |
143 var m map[string]interface{} | |
144 if err := json.Unmarshal(data, &m); err != nil { | |
145 return err | |
146 } | |
147 if ft == nil { | |
148 return errors.New("model: UnmarshalJSON: nil *FullTest") | |
149 } | |
150 if *ft == nil { | |
151 *ft = FullTest{} | |
152 } | |
153 return ft.constructTree(m) | |
154 } | |
155 | |
156 // constructTree constructs the tree of Nodes from the supplied map. | |
157 // constructTree returns an error if the receiver is nil. | |
158 func (ft *FullTest) constructTree(m map[string]interface{}) error { | |
159 for k, v := range m { | |
160 mm, ok := v.(map[string]interface{}) | |
161 if !ok { | |
162 continue | |
163 } | |
164 | |
165 if isFullTestLeaf(mm) { | |
166 l, err := makeFullTestLeaf(mm) | |
167 if err != nil { | |
168 return err | |
169 } | |
170 if *ft == nil { | |
171 *ft = FullTest{} | |
172 } | |
173 (*ft)[k] = l | |
174 continue | |
175 } | |
176 | |
177 var child FullTest | |
178 if err := child.constructTree(mm); err != nil { | |
179 return err | |
180 } | |
181 if *ft == nil { | |
182 *ft = FullTest{} | |
183 } | |
184 (*ft)[k] = child | |
185 } | |
186 | |
187 return nil | |
188 } | |
189 | |
190 // isFullTestLeaf returns true if the supplied map likely represents a | |
191 // FullTestLeaf. | |
192 func isFullTestLeaf(m map[string]interface{}) bool { | |
193 for _, val := range m { | |
194 if _, ok := val.(map[string]interface{}); ok { | |
195 return false | |
196 } | |
197 } | |
198 return true | |
199 } | |
200 | |
201 // makeFullTestLeaf returns a FullTestLeaf from the supplied map. | |
202 func makeFullTestLeaf(m map[string]interface{}) (*FullTestLeaf, error) { | |
203 l := &FullTestLeaf{} | |
204 b, err := json.Marshal(m) | |
205 if err != nil { | |
206 return nil, err | |
207 } | |
208 err = json.Unmarshal(b, &l) | |
209 return l, err | |
210 } | |
211 | |
212 // FullTestLeaf represents the results for a particular test name. | |
213 type FullTestLeaf struct { | |
214 Actual []string `json:"-"` | |
215 Expected []string `json:"-"` | |
216 | |
217 // These fields are optional. | |
218 | |
219 Runtime *float64 `json:"time,omitempty"` // In seconds. | |
220 Bugs []string `json:"bugs"` | |
221 Unexpected *bool `json:"is_unexpected,omitempty"` | |
222 | |
223 // These fields are layout test specific. | |
224 | |
225 RepaintOverlay *bool `json:"has_repaint_overlay,omitempty"` | |
226 MissingAudio *bool `json:"is_missing_audio,omitempty"` | |
227 MissingText *bool `json:"is_missing_text,omitempty"` | |
228 MissingVideo *bool `json:"is_missing_video,omitempty"` | |
229 UsedTestHarness *bool `json:"is_testharness_test,omitempty"` | |
230 ReferenceTestType *string `json:"reftest_type,omitempty"` | |
231 } | |
232 | |
233 var _ Node = (*FullTestLeaf)(nil) | |
234 | |
235 func (l *FullTestLeaf) node() {} | |
236 | |
237 // fullTestLeafAlias helps unmarshal and marshal FullTestLeaf. | |
238 type fullTestLeafAlias FullTestLeaf | |
239 | |
240 // testResultAux helps unmarshal and marshal FullTestLeaf. | |
241 type testResultAux struct { | |
242 Actual string `json:"actual"` | |
243 Expected string `json:"expected"` | |
244 *fullTestLeafAlias | |
245 } | |
246 | |
247 // AggregateTestLeaf converts l to AggregateTestLeaf. The returned | |
248 // AggregateTestLeaf does not share references to objects | |
249 // referenced by l. | |
250 // | |
251 // The returned error is always nil, but callers should check the | |
252 // error anyway because this behavior may change in the future. | |
253 func (l *FullTestLeaf) AggregateTestLeaf() (AggregateTestLeaf, error) { | |
254 var ret AggregateTestLeaf | |
255 | |
256 expected := strings.Join(l.Expected, " ") | |
257 actual := strings.Join(l.Actual, " ") | |
258 | |
259 if expected != "PASS" && expected != "NOTRUN" { | |
260 ret.Expected = make([]string, len(l.Expected)) | |
261 copy(ret.Expected, l.Expected) | |
262 } | |
263 | |
264 var shortFailures string | |
265 if (expected != "SKIP" && actual == "SKIP") || expected == "NOTRUN" { | |
266 shortFailures = "Y" | |
267 } else { | |
268 for _, f := range l.Actual { | |
269 val, ok := FailureShortNames[f] | |
270 if ok { | |
271 shortFailures += val | |
272 } else { | |
273 shortFailures += "U" | |
274 } | |
275 } | |
276 } | |
277 ret.Results = []ResultSummary{{1, shortFailures}} | |
278 | |
279 if len(l.Bugs) > 0 { | |
280 ret.Bugs = make([]string, len(l.Bugs)) | |
281 copy(ret.Bugs, l.Bugs) | |
282 } | |
283 | |
284 var time float64 | |
285 if l.Runtime != nil { | |
286 time = float64(round(*l.Runtime)) | |
287 } | |
288 ret.Runtimes = []RuntimeSummary{{1, time}} | |
289 | |
290 return ret, nil | |
291 } | |
292 | |
293 // MarshalJSON marshals l into JSON. | |
294 func (l *FullTestLeaf) MarshalJSON() ([]byte, error) { | |
295 aux := testResultAux{fullTestLeafAlias: (*fullTestLeafAlias)(l)} | |
296 aux.Actual = strings.Join(l.Actual, " ") | |
297 aux.Expected = strings.Join(l.Expected, " ") | |
298 return json.Marshal(&aux) | |
299 } | |
300 | |
301 // UnmarshalJSON unmarshals the supplied data into l. | |
302 func (l *FullTestLeaf) UnmarshalJSON(data []byte) error { | |
303 aux := testResultAux{fullTestLeafAlias: (*fullTestLeafAlias)(l)} | |
304 if err := json.Unmarshal(data, &aux); err != nil { | |
305 return err | |
306 } | |
307 l.Actual = strings.Split(aux.Actual, " ") | |
308 l.Expected = strings.Split(aux.Expected, " ") | |
309 return nil | |
310 } | |
311 | |
312 // Times represents "times_ms.json". | |
313 type Times map[string]float64 | |
OLD | NEW |