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" |
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 Loading... |
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 Loading... |
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 Loading... |
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 } |
OLD | NEW |