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

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

Issue 2234353002: test-results: package model: Add full_result.go and tests (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: Improve coverage 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"
11 "strings" 11 "strings"
12 ) 12 )
13 13
14 // ResultsVersion is the latest version of the JSON format that "results.json" 14 // ResultsVersion is the latest version of the JSON format that "results.json"
15 // and "results-small.json" files are using. 15 // and "results-small.json" files are using.
16 const ResultsVersion = 4 16 const ResultsVersion = 4
17 17
18 var _ TestNode = (AggregateTest)(nil) 18 var (
19 var _ TestNode = (*AggregateTestLeaf)(nil) 19 » // CleanPrefix is the prefix that CleanJSON removes.
20 » CleanPrefix = []byte("ADD_RESULTS(")
21 » // CleanSuffix is the suffix that CleanJSON removes.
22 » CleanSuffix = []byte(");")
23 )
20 24
21 // CleanTestResultsJSON returns the result of removing a known prefix 25 // CleanJSON returns the result of removing CleanPrefix
22 // and suffix from the contents in r. If the prefix or suffix do not exist, 26 // and CleanSuffix from the contents in r. If either
23 // the returned io.Reader has the same contents as r. 27 // CleanPrefix or CleanSuffix does not exist, the returned
24 func CleanTestResultsJSON(r io.Reader) (io.Reader, error) { 28 // io.Reader has the same contents as r.
25 » pre := []byte("ADD_RESULTS(") 29 func CleanJSON(r io.Reader) (io.Reader, error) {
26 » suf := []byte(");")
27
28 b, err := ioutil.ReadAll(r) 30 b, err := ioutil.ReadAll(r)
29 if err != nil { 31 if err != nil {
30 return nil, err 32 return nil, err
31 } 33 }
32 34
33 » if bytes.HasPrefix(b, pre) && bytes.HasSuffix(b, suf) { 35 » if bytes.HasPrefix(b, CleanPrefix) && bytes.HasSuffix(b, CleanSuffix) {
34 » » result := bytes.TrimPrefix(b, pre) 36 » » result := bytes.TrimPrefix(b, CleanPrefix)
35 » » result = bytes.TrimSuffix(result, suf) 37 » » result = bytes.TrimSuffix(result, CleanSuffix)
36 return bytes.NewReader(result), nil 38 return bytes.NewReader(result), nil
37 } 39 }
38 40
39 return bytes.NewReader(b), nil 41 return bytes.NewReader(b), nil
40 } 42 }
41 43
42 // AggregateResult represents "results.json" and "results-small.json" files. 44 // AggregateResult represents "results.json" and "results-small.json" files.
43 // The Builder field must be set to the expected builder name before unmarshalin g. 45 // The Builder field must be set to the expected builder name before unmarshalin g.
44 type AggregateResult struct { 46 type AggregateResult struct {
45 Version int 47 Version int
46 Builder string 48 Builder string
47 *BuilderInfo 49 *BuilderInfo
48 } 50 }
49 51
50 // BuilderInfo represents aggregate information about a builder. 52 // BuilderInfo represents aggregate information for a builder.
51 type BuilderInfo struct { 53 type BuilderInfo struct {
52 // SecondsEpoch is the start time of tests expressed in seconds from 54 // SecondsEpoch is the start time of tests expressed in seconds from
53 // the Unix epoch. 55 // the Unix epoch.
54 SecondsEpoch []int64 `json:"secondsSinceEpoch"` 56 SecondsEpoch []int64 `json:"secondsSinceEpoch"`
55 57
56 » BlinkRevs []number `json:"blinkRevision"` 58 » // BlinkRevs is list of Blink revisions.
57 » BuildNumbers []number `json:"buildNumbers"` 59 » BlinkRevs []Number `json:"blinkRevision"`
60
61 » // BuildNumbers is list of build numbers.
62 » BuildNumbers []Number `json:"buildNumbers"`
58 63
59 // ChromeRevs is a list of Chrome/Chromium revisions. 64 // ChromeRevs is a list of Chrome/Chromium revisions.
60 // The elements are strings because they can either be revision 65 // The elements are strings because they can either be revision
61 // numbers or commit hashes. 66 // numbers or commit hashes.
62 ChromeRevs []string `json:"chromeRevision"` 67 ChromeRevs []string `json:"chromeRevision"`
63 68
64 // Tests is the test trie. The leaf nodes will be of type 69 // Tests is the test trie. The leaf nodes will be of type
65 // AggregateTestLeaf. 70 // AggregateTestLeaf.
66 Tests AggregateTest `json:"tests"` 71 Tests AggregateTest `json:"tests"`
67 72
68 // FailureMap is a map from long failure types to short failure 73 // FailureMap is a map from long failure types to short failure
69 // types. Usually, it resembles LongFailureTypes. 74 // types. Usually, it resembles LongFailureTypes.
70 FailureMap map[string]string `json:"failure_map"` 75 FailureMap map[string]string `json:"failure_map"`
71 76
72 // FailuresByType is a map from long failure type to 77 // FailuresByType is a map from long failure type to
73 // number of failures. 78 // number of failures.
74 FailuresByType map[string][]int `json:"num_failures_by_type,omitempty"` 79 FailuresByType map[string][]int `json:"num_failures_by_type,omitempty"`
75 80
76 // FixableCounts represents test failures in a legacy format, 81 // FixableCounts represents test failures in a legacy format,
77 // and is usually nil. 82 // and is usually nil.
78 // 83 //
79 // It is included here because it may be needed to compute 84 // It is included here because it may be needed to compute
80 // FailuresByType when unmarshaling JSON. 85 // FailuresByType when unmarshaling JSON.
81 FixableCounts []map[string]int `json:"fixableCounts,omitempty"` 86 FixableCounts []map[string]int `json:"fixableCounts,omitempty"`
82 } 87 }
83 88
89 // MarshalJSON marshal ag into JSON.
84 func (ag *AggregateResult) MarshalJSON() ([]byte, error) { 90 func (ag *AggregateResult) MarshalJSON() ([]byte, error) {
85 v, err := json.Marshal(ag.Version) 91 v, err := json.Marshal(ag.Version)
86 if err != nil { 92 if err != nil {
87 return nil, err 93 return nil, err
88 } 94 }
89 vRaw := json.RawMessage(v) 95 vRaw := json.RawMessage(v)
90 96
91 info := *ag.BuilderInfo 97 info := *ag.BuilderInfo
92 98
93 // If FailuresByType exists, do not include FixableCounts 99 // If FailuresByType exists, do not include FixableCounts
94 // because it is deprecated. 100 // because it is deprecated.
95 » if ag.FailuresByType != nil { 101 » if info.FailuresByType != nil {
96 info.FixableCounts = nil 102 info.FixableCounts = nil
97 } 103 }
98 104
99 b, err := json.Marshal(&info) 105 b, err := json.Marshal(&info)
100 if err != nil { 106 if err != nil {
101 return nil, err 107 return nil, err
102 } 108 }
103 infoRaw := json.RawMessage(b) 109 infoRaw := json.RawMessage(b)
104 110
105 return json.Marshal(map[string]*json.RawMessage{ 111 return json.Marshal(map[string]*json.RawMessage{
(...skipping 20 matching lines...) Expand all
126 n, err := parseVersion(m) 132 n, err := parseVersion(m)
127 if err != nil { 133 if err != nil {
128 return err 134 return err
129 } 135 }
130 ag.Version = n 136 ag.Version = n
131 137
132 // BuilderInfo. 138 // BuilderInfo.
133 139
134 raw, ok := m[ag.Builder] 140 raw, ok := m[ag.Builder]
135 if !ok { 141 if !ok {
136 » » return fmt.Errorf("model: missing builder: %s", ag.Builder) 142 » » return fmt.Errorf("model: missing builder %q", ag.Builder)
137 } 143 }
138 144
139 var info *BuilderInfo 145 var info *BuilderInfo
140 if err := json.Unmarshal(raw, &info); err != nil { 146 if err := json.Unmarshal(raw, &info); err != nil {
141 return err 147 return err
142 } 148 }
143 ag.BuilderInfo = info 149 ag.BuilderInfo = info
144 150
145 if err := ag.checkFields(); err != nil { 151 if err := ag.checkFields(); err != nil {
146 return err 152 return err
(...skipping 16 matching lines...) Expand all
163 return 0, fmt.Errorf("model: version %q must be int: %v", vStr, err) 169 return 0, fmt.Errorf("model: version %q must be int: %v", vStr, err)
164 } 170 }
165 return n, nil 171 return n, nil
166 } 172 }
167 173
168 type fieldError struct { 174 type fieldError struct {
169 Name string // Name of field. 175 Name string // Name of field.
170 Value interface{} // Invalid value in the field that caused error. 176 Value interface{} // Invalid value in the field that caused error.
171 } 177 }
172 178
173 func (f fieldError) Error() string { 179 func (f *fieldError) Error() string {
174 » return fmt.Sprintf("model: field %s has invalid value: %v", f.Name, f.Va lue) 180 » return fmt.Sprintf("model: field %q has invalid value: %v", f.Name, f.Va lue)
175 } 181 }
176 182
177 func (ag *AggregateResult) checkFields() error { 183 func (ag *AggregateResult) checkFields() error {
178 if ag.Version > ResultsVersion { 184 if ag.Version > ResultsVersion {
179 » » return fieldError{"Version", ag.Version} 185 » » return &fieldError{"Version", ag.Version}
180 } 186 }
181 if ag.BuilderInfo == nil { 187 if ag.BuilderInfo == nil {
182 » » return fieldError{"BuilderInfo", ag.BuilderInfo} 188 » » return &fieldError{"BuilderInfo", ag.BuilderInfo}
183 } 189 }
184 return nil 190 return nil
185 } 191 }
186 192
187 func (info *BuilderInfo) checkFields() error { 193 func (info *BuilderInfo) checkFields() error {
188 if info.SecondsEpoch == nil {
189 return fieldError{"SecondsEpoch", info.SecondsEpoch}
190 }
191 if info.BlinkRevs == nil {
192 return fieldError{"BlinkRevs", info.BlinkRevs}
193 }
194 if info.BuildNumbers == nil { 194 if info.BuildNumbers == nil {
195 » » return fieldError{"BuildNumbers", info.BuildNumbers} 195 » » return &fieldError{"BuildNumbers", info.BuildNumbers}
196 » }
197 » if info.ChromeRevs == nil {
198 » » return fieldError{"ChromeRevs", info.ChromeRevs}
199 » }
200 » if info.Tests == nil {
201 » » return fieldError{"Tests", info.Tests}
202 » }
203 » if info.FailureMap == nil {
204 » » return fieldError{"FailureMap", info.FailureMap}
205 » }
206 » if info.FailuresByType == nil && info.FixableCounts == nil {
207 » » return fieldError{"FailuresByType", info.FailuresByType}
208 } 196 }
209 return nil 197 return nil
210 } 198 }
211 199
212 // computeFailuresByType computes info.FailuresByType from info.FixableCounts. 200 // computeFailuresByType computes info.FailuresByType from info.FixableCounts.
213 // The function has no effect if info.FailuresByType is already non-nil. 201 // The function has no effect if info.FailuresByType is already non-nil.
214 func (info *BuilderInfo) computeFailuresByType() error { 202 func (info *BuilderInfo) computeFailuresByType() error {
215 if info.FailuresByType != nil { 203 if info.FailuresByType != nil {
216 // Already present. 204 // Already present.
217 return nil 205 return nil
218 } 206 }
219 207
220 if info.FixableCounts == nil { 208 if info.FixableCounts == nil {
221 return errors.New("model: nil FixableCounts") 209 return errors.New("model: nil FixableCounts")
222 } 210 }
223 211
224 res := make(map[string][]int) 212 res := make(map[string][]int)
225 for _, fc := range info.FixableCounts { 213 for _, fc := range info.FixableCounts {
226 for short, count := range fc { 214 for short, count := range fc {
227 long, ok := FailureLongNames[short] 215 long, ok := FailureLongNames[short]
228 if !ok { 216 if !ok {
229 » » » » return fmt.Errorf("model: unknown key: %s", shor t) 217 » » » » return fmt.Errorf("model: unknown key %q", short )
230 } 218 }
231 res[long] = append(res[long], count) 219 res[long] = append(res[long], count)
232 } 220 }
233 } 221 }
234 222
235 info.FailuresByType = res 223 info.FailuresByType = res
236 return nil 224 return nil
237 } 225 }
238 226
239 // AggregateTest represents Tests in a AggregateResult. 227 // AggregateTest represents Tests in a AggregateResult.
240 type AggregateTest map[string]TestNode 228 type AggregateTest map[string]Node
241 229
242 func (at AggregateTest) Walk(fn func(key string, node TestNode) error) { 230 var _ Node = (AggregateTest)(nil)
243 » for k, v := range at {
244 » » if leaf, ok := v.(*AggregateTestLeaf); ok {
245 » » » if err := fn(k, leaf); err != nil {
246 » » » » return
247 » » » }
248 » » » continue
249 » » }
250 231
251 » » if child, ok := v.(AggregateTest); ok { 232 func (at AggregateTest) node() {}
252 » » » if err := fn(k, child); err != nil { 233
253 » » » » return 234 // Walk performs a depth-first traversal of the Nodes reachable
254 » » » } 235 // from the receiver, calling fn each time. The Node in fn
255 » » » child.Walk(fn) 236 // is guaranteed to be either AggregateTest or *AggregateTestLeaf.
237 // The traversal order may vary across different runs.
238 func (at AggregateTest) Walk(fn func(key string, node Node)) {
239 » for key, node := range at {
240 » » switch val := node.(type) {
241 » » case *AggregateTestLeaf:
242 » » » fn(key, val)
243 » » case AggregateTest:
244 » » » fn(key, val)
245 » » » val.Walk(fn)
256 } 246 }
257 } 247 }
258 } 248 }
259 249
250 // WalkLeaves is similar to Walk but only calls fn for
251 // *AggregateTestLeaf.
260 func (at AggregateTest) WalkLeaves(fn func(key string, leaf *AggregateTestLeaf)) { 252 func (at AggregateTest) WalkLeaves(fn func(key string, leaf *AggregateTestLeaf)) {
261 » at.Walk(func(k string, node TestNode) error { 253 » at.Walk(func(key string, node Node) {
262 if leaf, ok := node.(*AggregateTestLeaf); ok { 254 if leaf, ok := node.(*AggregateTestLeaf); ok {
263 » » » fn(k, leaf) 255 » » » fn(key, leaf)
264 } 256 }
265 return nil
266 }) 257 })
267 } 258 }
268 259
269 func (at AggregateTest) Children() map[string]TestNode { return at } 260 // ToTestList set the Results and Runtimes fields of all the
270 func (at AggregateTest) testnode() {} 261 // AggregateTestLeaf under the receiver AggregateTest to nil.
271
272 func (at AggregateTest) ToTestList() { 262 func (at AggregateTest) ToTestList() {
273 at.WalkLeaves(func(_ string, leaf *AggregateTestLeaf) { 263 at.WalkLeaves(func(_ string, leaf *AggregateTestLeaf) {
274 leaf.Results = nil 264 leaf.Results = nil
275 leaf.Runtimes = nil 265 leaf.Runtimes = nil
276 }) 266 })
277 } 267 }
278 268
269 // MarshalJSON marshals at into JSON.
279 func (at *AggregateTest) MarshalJSON() ([]byte, error) { 270 func (at *AggregateTest) MarshalJSON() ([]byte, error) {
280 if at == nil { 271 if at == nil {
281 return json.Marshal(nil) 272 return json.Marshal(nil)
282 } 273 }
283 274
284 m := make(map[string]*json.RawMessage) 275 m := make(map[string]*json.RawMessage)
285 276
286 for k, v := range *at { 277 for k, v := range *at {
287 b, err := json.Marshal(&v) 278 b, err := json.Marshal(&v)
288 if err != nil { 279 if err != nil {
289 return nil, err 280 return nil, err
290 } 281 }
291 raw := json.RawMessage(b) 282 raw := json.RawMessage(b)
292 m[k] = &raw 283 m[k] = &raw
293 } 284 }
294 285
295 return json.Marshal(m) 286 return json.Marshal(m)
296 } 287 }
297 288
289 // UnmarshalJSON unmarshals the supplied data into at.
298 func (at *AggregateTest) UnmarshalJSON(data []byte) error { 290 func (at *AggregateTest) UnmarshalJSON(data []byte) error {
299 var m map[string]interface{} 291 var m map[string]interface{}
300 if err := json.Unmarshal(data, &m); err != nil { 292 if err := json.Unmarshal(data, &m); err != nil {
301 return err 293 return err
302 } 294 }
303 if at == nil { 295 if at == nil {
304 » » return errors.New("model: UnmarshalJSON on nil *AggregateTest") 296 » » return errors.New("model: UnmarshalJSON: nil *AggregateTest")
305 } 297 }
306 if *at == nil { 298 if *at == nil {
307 *at = AggregateTest{} 299 *at = AggregateTest{}
308 } 300 }
309 return at.constructTree(m) 301 return at.constructTree(m)
310 } 302 }
311 303
312 // constructTree constructs the tree of test nodes from the supplied map. 304 // constructTree constructs the tree of Nodes from the supplied map.
313 func (at *AggregateTest) constructTree(m map[string]interface{}) error { 305 func (at *AggregateTest) constructTree(m map[string]interface{}) error {
314 for k, v := range m { 306 for k, v := range m {
315 mm, ok := v.(map[string]interface{}) 307 mm, ok := v.(map[string]interface{})
316 if !ok { 308 if !ok {
317 continue 309 continue
318 } 310 }
319 311
320 if isAggregateTestLeaf(mm) { 312 if isAggregateTestLeaf(mm) {
321 l, err := makeAggregateTestLeaf(mm) 313 l, err := makeAggregateTestLeaf(mm)
322 if err != nil { 314 if err != nil {
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after
367 } 359 }
368 360
369 // AggregateTestLeaf is the summary of test results at the l of a tests trie. 361 // AggregateTestLeaf is the summary of test results at the l of a tests trie.
370 type AggregateTestLeaf struct { 362 type AggregateTestLeaf struct {
371 Results []ResultSummary 363 Results []ResultSummary
372 Runtimes []RuntimeSummary 364 Runtimes []RuntimeSummary
373 Expected []string 365 Expected []string
374 Bugs []string 366 Bugs []string
375 } 367 }
376 368
369 func (l *AggregateTestLeaf) node() {}
370
377 // aggregateTestLeafAux is used to marshal and unmarshal AggregateTestLeaf. 371 // aggregateTestLeafAux is used to marshal and unmarshal AggregateTestLeaf.
378 type aggregateTestLeafAux struct { 372 type aggregateTestLeafAux struct {
379 Results []ResultSummary `json:"results,omitempty"` 373 Results []ResultSummary `json:"results,omitempty"`
380 Runtimes []RuntimeSummary `json:"times,omitempty"` 374 Runtimes []RuntimeSummary `json:"times,omitempty"`
381 Expected *string `json:"expected,omitempty"` 375 Expected *string `json:"expected,omitempty"`
382 Bugs []string `json:"bugs,omitempty"` 376 Bugs []string `json:"bugs,omitempty"`
383 } 377 }
384 378
385 func (l *AggregateTestLeaf) Children() map[string]TestNode { return nil } 379 // MarshalJSON marshal l into JSON.
386 func (l *AggregateTestLeaf) testnode() {}
387
388 func (l *AggregateTestLeaf) MarshalJSON() ([]byte, error) { 380 func (l *AggregateTestLeaf) MarshalJSON() ([]byte, error) {
389 aux := aggregateTestLeafAux{ 381 aux := aggregateTestLeafAux{
390 Results: l.Results, 382 Results: l.Results,
391 Runtimes: l.Runtimes, 383 Runtimes: l.Runtimes,
392 Bugs: l.Bugs, 384 Bugs: l.Bugs,
393 } 385 }
394 if s := strings.Join(l.Expected, " "); len(s) > 0 { 386 if s := strings.Join(l.Expected, " "); len(s) > 0 {
395 aux.Expected = &s 387 aux.Expected = &s
396 } 388 }
397 return json.Marshal(&aux) 389 return json.Marshal(&aux)
398 } 390 }
399 391
392 // UnmarshalJSON unmarshal the supplied data into l.
400 func (l *AggregateTestLeaf) UnmarshalJSON(data []byte) error { 393 func (l *AggregateTestLeaf) UnmarshalJSON(data []byte) error {
401 var aux aggregateTestLeafAux 394 var aux aggregateTestLeafAux
402 if err := json.Unmarshal(data, &aux); err != nil { 395 if err := json.Unmarshal(data, &aux); err != nil {
403 return err 396 return err
404 } 397 }
405 398
406 l.Results = aux.Results 399 l.Results = aux.Results
407 l.Runtimes = aux.Runtimes 400 l.Runtimes = aux.Runtimes
408 if aux.Expected != nil { 401 if aux.Expected != nil {
409 l.Expected = strings.Split(*aux.Expected, " ") 402 l.Expected = strings.Split(*aux.Expected, " ")
410 } 403 }
411 l.Bugs = aux.Bugs 404 l.Bugs = aux.Bugs
412 405
413 return nil 406 return nil
414 } 407 }
415 408
416 // defaultFields sets default values for missing/invalid fields. 409 // defaultFields sets default values for missing/invalid fields.
417 func (l *AggregateTestLeaf) defaultFields() { 410 func (l *AggregateTestLeaf) defaultFields() {
418 if len(l.Results) == 0 { 411 if len(l.Results) == 0 {
419 l.Results = []ResultSummary{{1, "N"}} 412 l.Results = []ResultSummary{{1, "N"}}
420 } 413 }
421 if len(l.Runtimes) == 0 { 414 if len(l.Runtimes) == 0 {
422 l.Runtimes = []RuntimeSummary{{1, 0}} 415 l.Runtimes = []RuntimeSummary{{1, 0}}
423 } 416 }
424 } 417 }
425 418
419 // ResultSummary is the type of test failure and count of how many
420 // times the running time occured.
426 type ResultSummary struct { 421 type ResultSummary struct {
427 Count int 422 Count int
428 Type string 423 Type string
429 } 424 }
430 425
426 // MarshalJSON marshals rs into JSON.
431 func (rs *ResultSummary) MarshalJSON() ([]byte, error) { 427 func (rs *ResultSummary) MarshalJSON() ([]byte, error) {
432 return json.Marshal([]interface{}{ 428 return json.Marshal([]interface{}{
433 rs.Count, 429 rs.Count,
434 rs.Type, 430 rs.Type,
435 }) 431 })
436 } 432 }
437 433
434 // UnmarshalJSON unmarshals the provided data into rs.
438 func (rs *ResultSummary) UnmarshalJSON(data []byte) error { 435 func (rs *ResultSummary) UnmarshalJSON(data []byte) error {
439 var tmp []interface{} 436 var tmp []interface{}
440 if err := json.Unmarshal(data, &tmp); err != nil { 437 if err := json.Unmarshal(data, &tmp); err != nil {
441 return err 438 return err
442 } 439 }
443 if len(tmp) != 2 { 440 if len(tmp) != 2 {
444 » » return fmt.Errorf("ResultSummary: wrong length: %d, expect: %d", len(tmp), 2) 441 » » return fmt.Errorf("model: UnmarshalJSON: ResultSummary wrong len gth: %d, expect: %d", len(tmp), 2)
445 } 442 }
446 443
447 count, ok := tmp[0].(float64) 444 count, ok := tmp[0].(float64)
448 if !ok { 445 if !ok {
449 » » return fmt.Errorf("ResultSummary: wrong format: %v", tmp) 446 » » return fmt.Errorf("model: UnmarshalJSON: ResultSummary wrong typ e: %v", tmp)
450 } 447 }
451 rs.Count = int(count) 448 rs.Count = int(count)
452 449
453 rs.Type, ok = tmp[1].(string) 450 rs.Type, ok = tmp[1].(string)
454 if !ok { 451 if !ok {
455 » » return fmt.Errorf("ResultSummary: wrong format: %v", tmp) 452 » » return fmt.Errorf("model: UnmarshalJSON: ResultSummary wrong typ e: %v", tmp)
456 } 453 }
457 454
458 return nil 455 return nil
459 } 456 }
460 457
458 // RuntimeSummary is the running time of a test and count of how many
459 // times the running time occured.
461 type RuntimeSummary struct { 460 type RuntimeSummary struct {
462 Count int 461 Count int
463 Runtime float64 462 Runtime float64
464 } 463 }
465 464
465 // MarshalJSON marshals rs into JSON.
466 func (rs *RuntimeSummary) MarshalJSON() ([]byte, error) { 466 func (rs *RuntimeSummary) MarshalJSON() ([]byte, error) {
467 return json.Marshal([]float64{ 467 return json.Marshal([]float64{
468 float64(rs.Count), 468 float64(rs.Count),
469 rs.Runtime, 469 rs.Runtime,
470 }) 470 })
471 } 471 }
472 472
473 // UnmarshalJSON unmarshals the provided data into rs.
473 func (rs *RuntimeSummary) UnmarshalJSON(data []byte) error { 474 func (rs *RuntimeSummary) UnmarshalJSON(data []byte) error {
474 var tmp []float64 475 var tmp []float64
475 if err := json.Unmarshal(data, &tmp); err != nil { 476 if err := json.Unmarshal(data, &tmp); err != nil {
476 return err 477 return err
477 } 478 }
478 if len(tmp) != 2 { 479 if len(tmp) != 2 {
479 » » return fmt.Errorf("RuntimeSummary: wrong length: %d, expect: %d" , len(tmp), 2) 480 » » return fmt.Errorf("model: UnmarshalJSON: RuntimeSummary wrong le ngth: %d, expect: %d", len(tmp), 2)
480 } 481 }
481 482
482 rs.Count = int(tmp[0]) 483 rs.Count = int(tmp[0])
483 rs.Runtime = tmp[1] 484 rs.Runtime = tmp[1]
484 return nil 485 return nil
485 } 486 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698