Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(74)

Side by Side Diff: go/src/infra/appengine/test-results/model/aggregate_result.go

Issue 2252623002: test-results: package model: Add type TestList (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@cl-ing_upload
Patch Set: Fix renamed function call Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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
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
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
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
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
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
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698