OLD | NEW |
1 package model | 1 package model |
2 | 2 |
3 import ( | 3 import ( |
4 "bytes" | 4 "bytes" |
5 "encoding/json" | 5 "encoding/json" |
6 "errors" | 6 "errors" |
7 "fmt" | 7 "fmt" |
8 "io" | 8 "io" |
9 "io/ioutil" | 9 "io/ioutil" |
10 "strconv" | 10 "strconv" |
(...skipping 24 matching lines...) Expand all Loading... |
35 if bytes.HasPrefix(b, CleanPrefix) && bytes.HasSuffix(b, CleanSuffix) { | 35 if bytes.HasPrefix(b, CleanPrefix) && bytes.HasSuffix(b, CleanSuffix) { |
36 result := bytes.TrimPrefix(b, CleanPrefix) | 36 result := bytes.TrimPrefix(b, CleanPrefix) |
37 result = bytes.TrimSuffix(result, CleanSuffix) | 37 result = bytes.TrimSuffix(result, CleanSuffix) |
38 return bytes.NewReader(result), nil | 38 return bytes.NewReader(result), nil |
39 } | 39 } |
40 | 40 |
41 return bytes.NewReader(b), nil | 41 return bytes.NewReader(b), nil |
42 } | 42 } |
43 | 43 |
44 // AggregateResult represents "results.json" and "results-small.json" files. | 44 // AggregateResult represents "results.json" and "results-small.json" files. |
45 // The Builder field must be set to the expected builder name before unmarshalin
g. | |
46 type AggregateResult struct { | 45 type AggregateResult struct { |
47 Version int | 46 Version int |
48 Builder string | 47 Builder string |
49 *BuilderInfo | 48 *BuilderInfo |
50 } | 49 } |
51 | 50 |
52 // BuilderInfo represents aggregate information for a builder. | 51 // BuilderInfo represents aggregate information for a builder. |
53 type BuilderInfo struct { | 52 type BuilderInfo struct { |
54 // SecondsEpoch is the start time of tests expressed in seconds from | 53 // SecondsEpoch is the start time of tests expressed in seconds from |
55 // the Unix epoch. | 54 // the Unix epoch. |
(...skipping 23 matching lines...) Expand all Loading... |
79 FailuresByType map[string][]int `json:"num_failures_by_type,omitempty"` | 78 FailuresByType map[string][]int `json:"num_failures_by_type,omitempty"` |
80 | 79 |
81 // FixableCounts represents test failures in a legacy format, | 80 // FixableCounts represents test failures in a legacy format, |
82 // and is usually nil. | 81 // and is usually nil. |
83 // | 82 // |
84 // It is included here because it may be needed to compute | 83 // It is included here because it may be needed to compute |
85 // FailuresByType when unmarshaling JSON. | 84 // FailuresByType when unmarshaling JSON. |
86 FixableCounts []map[string]int `json:"fixableCounts,omitempty"` | 85 FixableCounts []map[string]int `json:"fixableCounts,omitempty"` |
87 } | 86 } |
88 | 87 |
| 88 // TestList is a representation an AggregateResult in which |
| 89 // the Results and Runtimes fields of all the AggregateTestLeafs |
| 90 // are set to nil. |
| 91 type TestList struct { |
| 92 Builder string |
| 93 Tests AggregateTest |
| 94 } |
| 95 |
| 96 // MarshalJSON marshals tl into JSON. |
| 97 func (tl *TestList) MarshalJSON() ([]byte, error) { |
| 98 return json.Marshal(map[string]map[string]AggregateTest{ |
| 99 tl.Builder: { |
| 100 "tests": tl.Tests, |
| 101 }, |
| 102 }) |
| 103 } |
| 104 |
| 105 // ToTestList returns a TestList representation of ag. |
| 106 // The receiver's Tests field will be modified in the process. |
| 107 func (ag *AggregateResult) ToTestList() TestList { |
| 108 tl := TestList{ |
| 109 Builder: ag.Builder, |
| 110 Tests: ag.Tests, |
| 111 } |
| 112 tl.Tests.WalkLeaves(func(_ string, leaf *AggregateTestLeaf) { |
| 113 leaf.Results = nil |
| 114 leaf.Runtimes = nil |
| 115 }) |
| 116 return tl |
| 117 } |
| 118 |
89 // MarshalJSON marshal ag into JSON. | 119 // MarshalJSON marshal ag into JSON. |
90 func (ag *AggregateResult) MarshalJSON() ([]byte, error) { | 120 func (ag *AggregateResult) MarshalJSON() ([]byte, error) { |
91 v, err := json.Marshal(ag.Version) | 121 v, err := json.Marshal(ag.Version) |
92 if err != nil { | 122 if err != nil { |
93 return nil, err | 123 return nil, err |
94 } | 124 } |
95 vRaw := json.RawMessage(v) | 125 vRaw := json.RawMessage(v) |
96 | 126 |
97 info := *ag.BuilderInfo | 127 info := *ag.BuilderInfo |
98 | 128 |
99 // If FailuresByType exists, do not include FixableCounts | 129 // If FailuresByType exists, do not include FixableCounts |
100 // because it is deprecated. | 130 // because it is deprecated. |
101 if info.FailuresByType != nil { | 131 if info.FailuresByType != nil { |
102 info.FixableCounts = nil | 132 info.FixableCounts = nil |
103 } | 133 } |
104 | 134 |
105 b, err := json.Marshal(&info) | 135 b, err := json.Marshal(&info) |
106 if err != nil { | 136 if err != nil { |
107 return nil, err | 137 return nil, err |
108 } | 138 } |
109 infoRaw := json.RawMessage(b) | 139 infoRaw := json.RawMessage(b) |
110 | 140 |
111 return json.Marshal(map[string]*json.RawMessage{ | 141 return json.Marshal(map[string]*json.RawMessage{ |
112 "version": &vRaw, | 142 "version": &vRaw, |
113 ag.Builder: &infoRaw, | 143 ag.Builder: &infoRaw, |
114 }) | 144 }) |
115 } | 145 } |
116 | 146 |
| 147 // extractBuilderName gets the builder name from the supplied map. |
| 148 // This depends on the fact that AggregateResults are expected to |
| 149 // only have two top-level keys: (1) "version" (2) the builder name. |
| 150 func extractBuilderName(m map[string]json.RawMessage) (string, error) { |
| 151 for k := range m { |
| 152 if k != "version" { |
| 153 return k, nil |
| 154 } |
| 155 } |
| 156 return "", errors.New("builder name not found") |
| 157 } |
| 158 |
117 // UnmarshalJSON decodes JSON data into t. | 159 // UnmarshalJSON decodes JSON data into t. |
118 // | 160 // |
119 // The expected format is a modified version of the format described in the URL | 161 // The expected format is a modified version of the format described in the URL |
120 // below. The modifications account for the structure of results.json and | 162 // below. The modifications account for the structure of results.json and |
121 // results_small.json files in the wild. | 163 // results_small.json files in the wild. |
122 // | 164 // |
123 // https://chromium.googlesource.com/chromium/src/+/c7dd0560d9544a15908239bebc
177410899851ca/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/layout_pac
kage/bot_test_expectations.py#45 | 165 // https://chromium.googlesource.com/chromium/src/+/c7dd0560d9544a15908239bebc
177410899851ca/third_party/WebKit/Tools/Scripts/webkitpy/layout_tests/layout_pac
kage/bot_test_expectations.py#45 |
124 func (ag *AggregateResult) UnmarshalJSON(data []byte) error { | 166 func (ag *AggregateResult) UnmarshalJSON(data []byte) error { |
125 var m map[string]json.RawMessage | 167 var m map[string]json.RawMessage |
126 if err := json.Unmarshal(data, &m); err != nil { | 168 if err := json.Unmarshal(data, &m); err != nil { |
127 return err | 169 return err |
128 } | 170 } |
129 | 171 |
130 // Version. | 172 // Version. |
131 | 173 |
132 n, err := parseVersion(m) | 174 n, err := parseVersion(m) |
133 if err != nil { | 175 if err != nil { |
134 return err | 176 return err |
135 } | 177 } |
136 ag.Version = n | 178 ag.Version = n |
137 | 179 |
| 180 // Builder name. |
| 181 |
| 182 builder, err := extractBuilderName(m) |
| 183 if err != nil { |
| 184 return err |
| 185 } |
| 186 ag.Builder = builder |
| 187 |
138 // BuilderInfo. | 188 // BuilderInfo. |
139 | 189 |
140 raw, ok := m[ag.Builder] | |
141 if !ok { | |
142 return fmt.Errorf("model: missing builder %q", ag.Builder) | |
143 } | |
144 | |
145 var info *BuilderInfo | 190 var info *BuilderInfo |
146 » if err := json.Unmarshal(raw, &info); err != nil { | 191 » if err := json.Unmarshal(m[builder], &info); err != nil { |
147 return err | 192 return err |
148 } | 193 } |
149 ag.BuilderInfo = info | 194 ag.BuilderInfo = info |
150 | 195 |
151 if err := ag.checkFields(); err != nil { | 196 if err := ag.checkFields(); err != nil { |
152 return err | 197 return err |
153 } | 198 } |
154 if err := info.computeFailuresByType(); err != nil { | 199 if err := info.computeFailuresByType(); err != nil { |
155 return err | 200 return err |
156 } | 201 } |
(...skipping 13 matching lines...) Expand all Loading... |
170 } | 215 } |
171 return n, nil | 216 return n, nil |
172 } | 217 } |
173 | 218 |
174 type fieldError struct { | 219 type fieldError struct { |
175 Name string // Name of field. | 220 Name string // Name of field. |
176 Value interface{} // Invalid value in the field that caused error. | 221 Value interface{} // Invalid value in the field that caused error. |
177 } | 222 } |
178 | 223 |
179 func (f *fieldError) Error() string { | 224 func (f *fieldError) Error() string { |
180 » return fmt.Sprintf("model: field %q has invalid value: %v", f.Name, f.Va
lue) | 225 » return fmt.Sprintf("model: field %q has invalid value: %v (%T)", f.Name,
f.Value, f.Value) |
181 } | 226 } |
182 | 227 |
183 func (ag *AggregateResult) checkFields() error { | 228 func (ag *AggregateResult) checkFields() error { |
184 if ag.Version > ResultsVersion { | 229 if ag.Version > ResultsVersion { |
185 return &fieldError{"Version", ag.Version} | 230 return &fieldError{"Version", ag.Version} |
186 } | 231 } |
187 if ag.BuilderInfo == nil { | 232 if ag.BuilderInfo == nil { |
188 return &fieldError{"BuilderInfo", ag.BuilderInfo} | 233 return &fieldError{"BuilderInfo", ag.BuilderInfo} |
189 } | 234 } |
190 return nil | 235 return nil |
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
250 // WalkLeaves is similar to Walk but only calls fn for | 295 // WalkLeaves is similar to Walk but only calls fn for |
251 // *AggregateTestLeaf. | 296 // *AggregateTestLeaf. |
252 func (at AggregateTest) WalkLeaves(fn func(key string, leaf *AggregateTestLeaf))
{ | 297 func (at AggregateTest) WalkLeaves(fn func(key string, leaf *AggregateTestLeaf))
{ |
253 at.Walk(func(key string, node Node) { | 298 at.Walk(func(key string, node Node) { |
254 if leaf, ok := node.(*AggregateTestLeaf); ok { | 299 if leaf, ok := node.(*AggregateTestLeaf); ok { |
255 fn(key, leaf) | 300 fn(key, leaf) |
256 } | 301 } |
257 }) | 302 }) |
258 } | 303 } |
259 | 304 |
260 // ToTestList set the Results and Runtimes fields of all the | |
261 // AggregateTestLeaf under the receiver AggregateTest to nil. | |
262 func (at AggregateTest) ToTestList() { | |
263 at.WalkLeaves(func(_ string, leaf *AggregateTestLeaf) { | |
264 leaf.Results = nil | |
265 leaf.Runtimes = nil | |
266 }) | |
267 } | |
268 | |
269 // MarshalJSON marshals at into JSON. | 305 // MarshalJSON marshals at into JSON. |
270 func (at *AggregateTest) MarshalJSON() ([]byte, error) { | 306 func (at *AggregateTest) MarshalJSON() ([]byte, error) { |
271 if at == nil { | 307 if at == nil { |
272 return json.Marshal(nil) | 308 return json.Marshal(nil) |
273 } | 309 } |
274 | 310 |
275 m := make(map[string]*json.RawMessage) | 311 m := make(map[string]*json.RawMessage) |
276 | 312 |
277 for k, v := range *at { | 313 for k, v := range *at { |
278 b, err := json.Marshal(&v) | 314 b, err := json.Marshal(&v) |
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
327 } | 363 } |
328 if *at == nil { | 364 if *at == nil { |
329 *at = AggregateTest{} | 365 *at = AggregateTest{} |
330 } | 366 } |
331 (*at)[k] = child | 367 (*at)[k] = child |
332 } | 368 } |
333 | 369 |
334 return nil | 370 return nil |
335 } | 371 } |
336 | 372 |
337 // isAggregateTestLeaf returns true if the supplied map is likely represents a | 373 // isAggregateTestLeaf returns true if the supplied map is likely an |
338 // AggregateTestLeaf. | 374 // AggregateTestLeaf. |
339 func isAggregateTestLeaf(m map[string]interface{}) bool { | 375 func isAggregateTestLeaf(m map[string]interface{}) bool { |
340 for key, val := range m { | 376 for key, val := range m { |
341 if key == "results" { | 377 if key == "results" { |
342 if _, ok := val.([]interface{}); ok { | 378 if _, ok := val.([]interface{}); ok { |
343 return true | 379 return true |
344 } | 380 } |
345 } | 381 } |
346 } | 382 } |
347 return false | 383 return false |
(...skipping 21 matching lines...) Expand all Loading... |
369 func (leaf *AggregateTestLeaf) node() {} | 405 func (leaf *AggregateTestLeaf) node() {} |
370 | 406 |
371 // aggregateTestLeafAux is used to marshal and unmarshal AggregateTestLeaf. | 407 // aggregateTestLeafAux is used to marshal and unmarshal AggregateTestLeaf. |
372 type aggregateTestLeafAux struct { | 408 type aggregateTestLeafAux struct { |
373 Results []ResultSummary `json:"results,omitempty"` | 409 Results []ResultSummary `json:"results,omitempty"` |
374 Runtimes []RuntimeSummary `json:"times,omitempty"` | 410 Runtimes []RuntimeSummary `json:"times,omitempty"` |
375 Expected *string `json:"expected,omitempty"` | 411 Expected *string `json:"expected,omitempty"` |
376 Bugs []string `json:"bugs,omitempty"` | 412 Bugs []string `json:"bugs,omitempty"` |
377 } | 413 } |
378 | 414 |
379 // MarshalJSON marshal l into JSON. | 415 // MarshalJSON marshals leaf into JSON. |
380 func (leaf *AggregateTestLeaf) MarshalJSON() ([]byte, error) { | 416 func (leaf *AggregateTestLeaf) MarshalJSON() ([]byte, error) { |
381 aux := aggregateTestLeafAux{ | 417 aux := aggregateTestLeafAux{ |
382 Results: leaf.Results, | 418 Results: leaf.Results, |
383 Runtimes: leaf.Runtimes, | 419 Runtimes: leaf.Runtimes, |
384 Bugs: leaf.Bugs, | 420 Bugs: leaf.Bugs, |
385 } | 421 } |
386 if s := strings.Join(leaf.Expected, " "); len(s) > 0 { | 422 if s := strings.Join(leaf.Expected, " "); len(s) > 0 { |
387 aux.Expected = &s | 423 aux.Expected = &s |
388 } | 424 } |
389 return json.Marshal(&aux) | 425 return json.Marshal(&aux) |
390 } | 426 } |
391 | 427 |
392 // UnmarshalJSON unmarshal the supplied data into l. | 428 // UnmarshalJSON unmarshals the supplied data into leaf. |
393 func (leaf *AggregateTestLeaf) UnmarshalJSON(data []byte) error { | 429 func (leaf *AggregateTestLeaf) UnmarshalJSON(data []byte) error { |
394 var aux aggregateTestLeafAux | 430 var aux aggregateTestLeafAux |
395 if err := json.Unmarshal(data, &aux); err != nil { | 431 if err := json.Unmarshal(data, &aux); err != nil { |
396 return err | 432 return err |
397 } | 433 } |
398 | 434 |
399 leaf.Results = aux.Results | 435 leaf.Results = aux.Results |
400 leaf.Runtimes = aux.Runtimes | 436 leaf.Runtimes = aux.Runtimes |
401 if aux.Expected != nil { | 437 if aux.Expected != nil { |
402 leaf.Expected = strings.Split(*aux.Expected, " ") | 438 leaf.Expected = strings.Split(*aux.Expected, " ") |
(...skipping 244 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
647 | 683 |
648 func isDebugBuilder(builder string) bool { | 684 func isDebugBuilder(builder string) bool { |
649 for _, s := range []string{"debug", "dbg"} { | 685 for _, s := range []string{"debug", "dbg"} { |
650 if strings.Contains(strings.ToLower(builder), s) { | 686 if strings.Contains(strings.ToLower(builder), s) { |
651 return true | 687 return true |
652 } | 688 } |
653 } | 689 } |
654 return false | 690 return false |
655 } | 691 } |
656 | 692 |
657 // Trim trims the leaves of Tests in ar to the specified size. | 693 // Trim trims ag's fields to the specified size. |
658 func (ag *AggregateResult) Trim(size int) error { | 694 func (ag *AggregateResult) Trim(size int) error { |
659 t := runtimeThresholdNormal | 695 t := runtimeThresholdNormal |
660 | 696 |
661 if isDebugBuilder(ag.Builder) { | 697 if isDebugBuilder(ag.Builder) { |
662 t = runtimeThresholdDebug | 698 t = runtimeThresholdDebug |
663 } | 699 } |
664 | 700 |
| 701 ag.SecondsEpoch = ag.SecondsEpoch[:min(size, len(ag.SecondsEpoch))] |
| 702 ag.BlinkRevs = ag.BlinkRevs[:min(size, len(ag.BlinkRevs))] |
| 703 ag.ChromeRevs = ag.ChromeRevs[:min(size, len(ag.ChromeRevs))] |
| 704 ag.BuildNumbers = ag.BuildNumbers[:min(size, len(ag.BuildNumbers))] |
| 705 |
665 return ag.Tests.trim(size, t) | 706 return ag.Tests.trim(size, t) |
666 } | 707 } |
667 | 708 |
668 func (at AggregateTest) trim(size int, threshold float64) error { | 709 func (at AggregateTest) trim(size int, threshold float64) error { |
669 for k, v := range at { | 710 for k, v := range at { |
670 if leaf, ok := v.(*AggregateTestLeaf); ok { | 711 if leaf, ok := v.(*AggregateTestLeaf); ok { |
671 leaf.trim(size) | 712 leaf.trim(size) |
672 if leaf.shouldDelete(threshold) { | 713 if leaf.shouldDelete(threshold) { |
673 delete(at, k) | 714 delete(at, k) |
674 } | 715 } |
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
736 } | 777 } |
737 } | 778 } |
738 for _, r := range leaf.Runtimes { | 779 for _, r := range leaf.Runtimes { |
739 if r.Runtime >= threshold { | 780 if r.Runtime >= threshold { |
740 return false | 781 return false |
741 } | 782 } |
742 } | 783 } |
743 | 784 |
744 return true | 785 return true |
745 } | 786 } |
OLD | NEW |