| 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 |