Chromium Code Reviews| 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 // TestList returns a TestList representation of ag. | |
| 106 func (ag *AggregateResult) TestList() TestList { | |
| 107 tl := TestList{ | |
| 108 Builder: ag.Builder, | |
| 109 Tests: ag.Tests, | |
| 110 } | |
| 111 tl.Tests.WalkLeaves(func(_ string, leaf *AggregateTestLeaf) { | |
|
Vadim Sh.
2016/08/16 18:36:28
is it ok that function that looks like a getter (T
| |
| 112 leaf.Results = nil | |
| 113 leaf.Runtimes = nil | |
| 114 }) | |
| 115 return tl | |
| 116 } | |
| 117 | |
| 89 // MarshalJSON marshal ag into JSON. | 118 // MarshalJSON marshal ag into JSON. |
| 90 func (ag *AggregateResult) MarshalJSON() ([]byte, error) { | 119 func (ag *AggregateResult) MarshalJSON() ([]byte, error) { |
| 91 v, err := json.Marshal(ag.Version) | 120 v, err := json.Marshal(ag.Version) |
| 92 if err != nil { | 121 if err != nil { |
| 93 return nil, err | 122 return nil, err |
| 94 } | 123 } |
| 95 vRaw := json.RawMessage(v) | 124 vRaw := json.RawMessage(v) |
| 96 | 125 |
| 97 info := *ag.BuilderInfo | 126 info := *ag.BuilderInfo |
| 98 | 127 |
| 99 // If FailuresByType exists, do not include FixableCounts | 128 // If FailuresByType exists, do not include FixableCounts |
| 100 // because it is deprecated. | 129 // because it is deprecated. |
| 101 if info.FailuresByType != nil { | 130 if info.FailuresByType != nil { |
| 102 info.FixableCounts = nil | 131 info.FixableCounts = nil |
| 103 } | 132 } |
| 104 | 133 |
| 105 b, err := json.Marshal(&info) | 134 b, err := json.Marshal(&info) |
| 106 if err != nil { | 135 if err != nil { |
| 107 return nil, err | 136 return nil, err |
| 108 } | 137 } |
| 109 infoRaw := json.RawMessage(b) | 138 infoRaw := json.RawMessage(b) |
| 110 | 139 |
| 111 return json.Marshal(map[string]*json.RawMessage{ | 140 return json.Marshal(map[string]*json.RawMessage{ |
| 112 "version": &vRaw, | 141 "version": &vRaw, |
| 113 ag.Builder: &infoRaw, | 142 ag.Builder: &infoRaw, |
| 114 }) | 143 }) |
| 115 } | 144 } |
| 116 | 145 |
| 146 // extractBuilderName gets the builder name from the supplied map. | |
| 147 // This depends on the fact that AggregateResults are expected to | |
| 148 // only have two top-level keys: (1) "version" (2) the builder name. | |
| 149 func extractBuilderName(m map[string]json.RawMessage) (string, error) { | |
| 150 for k := range m { | |
| 151 if k != "version" { | |
| 152 return k, nil | |
| 153 } | |
| 154 } | |
| 155 return "", errors.New("builder name not found") | |
| 156 } | |
| 157 | |
| 117 // UnmarshalJSON decodes JSON data into t. | 158 // UnmarshalJSON decodes JSON data into t. |
| 118 // | 159 // |
| 119 // The expected format is a modified version of the format described in the URL | 160 // 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 | 161 // below. The modifications account for the structure of results.json and |
| 121 // results_small.json files in the wild. | 162 // results_small.json files in the wild. |
| 122 // | 163 // |
| 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 | 164 // 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 { | 165 func (ag *AggregateResult) UnmarshalJSON(data []byte) error { |
| 125 var m map[string]json.RawMessage | 166 var m map[string]json.RawMessage |
| 126 if err := json.Unmarshal(data, &m); err != nil { | 167 if err := json.Unmarshal(data, &m); err != nil { |
| 127 return err | 168 return err |
| 128 } | 169 } |
| 129 | 170 |
| 130 // Version. | 171 // Version. |
| 131 | 172 |
| 132 n, err := parseVersion(m) | 173 n, err := parseVersion(m) |
| 133 if err != nil { | 174 if err != nil { |
| 134 return err | 175 return err |
| 135 } | 176 } |
| 136 ag.Version = n | 177 ag.Version = n |
| 137 | 178 |
| 179 // Builder name. | |
| 180 | |
| 181 builder, err := extractBuilderName(m) | |
| 182 if err != nil { | |
| 183 return err | |
| 184 } | |
| 185 ag.Builder = builder | |
| 186 | |
| 138 // BuilderInfo. | 187 // BuilderInfo. |
| 139 | 188 |
| 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 | 189 var info *BuilderInfo |
| 146 » if err := json.Unmarshal(raw, &info); err != nil { | 190 » if err := json.Unmarshal(m[builder], &info); err != nil { |
| 147 return err | 191 return err |
| 148 } | 192 } |
| 149 ag.BuilderInfo = info | 193 ag.BuilderInfo = info |
| 150 | 194 |
| 151 if err := ag.checkFields(); err != nil { | 195 if err := ag.checkFields(); err != nil { |
| 152 return err | 196 return err |
| 153 } | 197 } |
| 154 if err := info.computeFailuresByType(); err != nil { | 198 if err := info.computeFailuresByType(); err != nil { |
| 155 return err | 199 return err |
| 156 } | 200 } |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 170 } | 214 } |
| 171 return n, nil | 215 return n, nil |
| 172 } | 216 } |
| 173 | 217 |
| 174 type fieldError struct { | 218 type fieldError struct { |
| 175 Name string // Name of field. | 219 Name string // Name of field. |
| 176 Value interface{} // Invalid value in the field that caused error. | 220 Value interface{} // Invalid value in the field that caused error. |
| 177 } | 221 } |
| 178 | 222 |
| 179 func (f *fieldError) Error() string { | 223 func (f *fieldError) Error() string { |
| 180 » return fmt.Sprintf("model: field %q has invalid value: %v", f.Name, f.Va lue) | 224 » return fmt.Sprintf("model: field %q has invalid value: %v (%T)", f.Name, f.Value, f.Value) |
| 181 } | 225 } |
| 182 | 226 |
| 183 func (ag *AggregateResult) checkFields() error { | 227 func (ag *AggregateResult) checkFields() error { |
| 184 if ag.Version > ResultsVersion { | 228 if ag.Version > ResultsVersion { |
| 185 return &fieldError{"Version", ag.Version} | 229 return &fieldError{"Version", ag.Version} |
| 186 } | 230 } |
| 187 if ag.BuilderInfo == nil { | 231 if ag.BuilderInfo == nil { |
| 188 return &fieldError{"BuilderInfo", ag.BuilderInfo} | 232 return &fieldError{"BuilderInfo", ag.BuilderInfo} |
| 189 } | 233 } |
| 190 return nil | 234 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 | 294 // WalkLeaves is similar to Walk but only calls fn for |
| 251 // *AggregateTestLeaf. | 295 // *AggregateTestLeaf. |
| 252 func (at AggregateTest) WalkLeaves(fn func(key string, leaf *AggregateTestLeaf)) { | 296 func (at AggregateTest) WalkLeaves(fn func(key string, leaf *AggregateTestLeaf)) { |
| 253 at.Walk(func(key string, node Node) { | 297 at.Walk(func(key string, node Node) { |
| 254 if leaf, ok := node.(*AggregateTestLeaf); ok { | 298 if leaf, ok := node.(*AggregateTestLeaf); ok { |
| 255 fn(key, leaf) | 299 fn(key, leaf) |
| 256 } | 300 } |
| 257 }) | 301 }) |
| 258 } | 302 } |
| 259 | 303 |
| 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. | 304 // MarshalJSON marshals at into JSON. |
| 270 func (at *AggregateTest) MarshalJSON() ([]byte, error) { | 305 func (at *AggregateTest) MarshalJSON() ([]byte, error) { |
| 271 if at == nil { | 306 if at == nil { |
| 272 return json.Marshal(nil) | 307 return json.Marshal(nil) |
| 273 } | 308 } |
| 274 | 309 |
| 275 m := make(map[string]*json.RawMessage) | 310 m := make(map[string]*json.RawMessage) |
| 276 | 311 |
| 277 for k, v := range *at { | 312 for k, v := range *at { |
| 278 b, err := json.Marshal(&v) | 313 b, err := json.Marshal(&v) |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 327 } | 362 } |
| 328 if *at == nil { | 363 if *at == nil { |
| 329 *at = AggregateTest{} | 364 *at = AggregateTest{} |
| 330 } | 365 } |
| 331 (*at)[k] = child | 366 (*at)[k] = child |
| 332 } | 367 } |
| 333 | 368 |
| 334 return nil | 369 return nil |
| 335 } | 370 } |
| 336 | 371 |
| 337 // isAggregateTestLeaf returns true if the supplied map is likely represents a | 372 // isAggregateTestLeaf returns true if the supplied map is likely an |
| 338 // AggregateTestLeaf. | 373 // AggregateTestLeaf. |
| 339 func isAggregateTestLeaf(m map[string]interface{}) bool { | 374 func isAggregateTestLeaf(m map[string]interface{}) bool { |
| 340 for key, val := range m { | 375 for key, val := range m { |
| 341 if key == "results" { | 376 if key == "results" { |
| 342 if _, ok := val.([]interface{}); ok { | 377 if _, ok := val.([]interface{}); ok { |
| 343 return true | 378 return true |
| 344 } | 379 } |
| 345 } | 380 } |
| 346 } | 381 } |
| 347 return false | 382 return false |
| (...skipping 21 matching lines...) Expand all Loading... | |
| 369 func (leaf *AggregateTestLeaf) node() {} | 404 func (leaf *AggregateTestLeaf) node() {} |
| 370 | 405 |
| 371 // aggregateTestLeafAux is used to marshal and unmarshal AggregateTestLeaf. | 406 // aggregateTestLeafAux is used to marshal and unmarshal AggregateTestLeaf. |
| 372 type aggregateTestLeafAux struct { | 407 type aggregateTestLeafAux struct { |
| 373 Results []ResultSummary `json:"results,omitempty"` | 408 Results []ResultSummary `json:"results,omitempty"` |
| 374 Runtimes []RuntimeSummary `json:"times,omitempty"` | 409 Runtimes []RuntimeSummary `json:"times,omitempty"` |
| 375 Expected *string `json:"expected,omitempty"` | 410 Expected *string `json:"expected,omitempty"` |
| 376 Bugs []string `json:"bugs,omitempty"` | 411 Bugs []string `json:"bugs,omitempty"` |
| 377 } | 412 } |
| 378 | 413 |
| 379 // MarshalJSON marshal l into JSON. | 414 // MarshalJSON marshals leaf into JSON. |
| 380 func (leaf *AggregateTestLeaf) MarshalJSON() ([]byte, error) { | 415 func (leaf *AggregateTestLeaf) MarshalJSON() ([]byte, error) { |
| 381 aux := aggregateTestLeafAux{ | 416 aux := aggregateTestLeafAux{ |
| 382 Results: leaf.Results, | 417 Results: leaf.Results, |
| 383 Runtimes: leaf.Runtimes, | 418 Runtimes: leaf.Runtimes, |
| 384 Bugs: leaf.Bugs, | 419 Bugs: leaf.Bugs, |
| 385 } | 420 } |
| 386 if s := strings.Join(leaf.Expected, " "); len(s) > 0 { | 421 if s := strings.Join(leaf.Expected, " "); len(s) > 0 { |
| 387 aux.Expected = &s | 422 aux.Expected = &s |
| 388 } | 423 } |
| 389 return json.Marshal(&aux) | 424 return json.Marshal(&aux) |
| 390 } | 425 } |
| 391 | 426 |
| 392 // UnmarshalJSON unmarshal the supplied data into l. | 427 // UnmarshalJSON unmarshals the supplied data into leaf. |
| 393 func (leaf *AggregateTestLeaf) UnmarshalJSON(data []byte) error { | 428 func (leaf *AggregateTestLeaf) UnmarshalJSON(data []byte) error { |
| 394 var aux aggregateTestLeafAux | 429 var aux aggregateTestLeafAux |
| 395 if err := json.Unmarshal(data, &aux); err != nil { | 430 if err := json.Unmarshal(data, &aux); err != nil { |
| 396 return err | 431 return err |
| 397 } | 432 } |
| 398 | 433 |
| 399 leaf.Results = aux.Results | 434 leaf.Results = aux.Results |
| 400 leaf.Runtimes = aux.Runtimes | 435 leaf.Runtimes = aux.Runtimes |
| 401 if aux.Expected != nil { | 436 if aux.Expected != nil { |
| 402 leaf.Expected = strings.Split(*aux.Expected, " ") | 437 leaf.Expected = strings.Split(*aux.Expected, " ") |
| (...skipping 244 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 647 | 682 |
| 648 func isDebugBuilder(builder string) bool { | 683 func isDebugBuilder(builder string) bool { |
| 649 for _, s := range []string{"debug", "dbg"} { | 684 for _, s := range []string{"debug", "dbg"} { |
| 650 if strings.Contains(strings.ToLower(builder), s) { | 685 if strings.Contains(strings.ToLower(builder), s) { |
| 651 return true | 686 return true |
| 652 } | 687 } |
| 653 } | 688 } |
| 654 return false | 689 return false |
| 655 } | 690 } |
| 656 | 691 |
| 657 // Trim trims the leaves of Tests in ar to the specified size. | 692 // Trim trims ag's fields to the specified size. |
| 658 func (ag *AggregateResult) Trim(size int) error { | 693 func (ag *AggregateResult) Trim(size int) error { |
| 659 t := runtimeThresholdNormal | 694 t := runtimeThresholdNormal |
| 660 | 695 |
| 661 if isDebugBuilder(ag.Builder) { | 696 if isDebugBuilder(ag.Builder) { |
| 662 t = runtimeThresholdDebug | 697 t = runtimeThresholdDebug |
| 663 } | 698 } |
| 664 | 699 |
| 700 ag.SecondsEpoch = ag.SecondsEpoch[:min(size, len(ag.SecondsEpoch))] | |
| 701 ag.BlinkRevs = ag.BlinkRevs[:min(size, len(ag.BlinkRevs))] | |
| 702 ag.ChromeRevs = ag.ChromeRevs[:min(size, len(ag.ChromeRevs))] | |
| 703 ag.BuildNumbers = ag.BuildNumbers[:min(size, len(ag.BuildNumbers))] | |
| 704 | |
| 665 return ag.Tests.trim(size, t) | 705 return ag.Tests.trim(size, t) |
| 666 } | 706 } |
| 667 | 707 |
| 668 func (at AggregateTest) trim(size int, threshold float64) error { | 708 func (at AggregateTest) trim(size int, threshold float64) error { |
| 669 for k, v := range at { | 709 for k, v := range at { |
| 670 if leaf, ok := v.(*AggregateTestLeaf); ok { | 710 if leaf, ok := v.(*AggregateTestLeaf); ok { |
| 671 leaf.trim(size) | 711 leaf.trim(size) |
| 672 if leaf.shouldDelete(threshold) { | 712 if leaf.shouldDelete(threshold) { |
| 673 delete(at, k) | 713 delete(at, k) |
| 674 } | 714 } |
| (...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 736 } | 776 } |
| 737 } | 777 } |
| 738 for _, r := range leaf.Runtimes { | 778 for _, r := range leaf.Runtimes { |
| 739 if r.Runtime >= threshold { | 779 if r.Runtime >= threshold { |
| 740 return false | 780 return false |
| 741 } | 781 } |
| 742 } | 782 } |
| 743 | 783 |
| 744 return true | 784 return true |
| 745 } | 785 } |
| OLD | NEW |