initial import
[vuplus_webkit] / Tools / TestResultServer / model / jsonresults_unittest.py
1 # Copyright (C) 2010 Google Inc. All rights reserved.
2 #
3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions are
5 # met:
6 #
7 #     * Redistributions of source code must retain the above copyright
8 # notice, this list of conditions and the following disclaimer.
9 #     * Redistributions in binary form must reproduce the above
10 # copyright notice, this list of conditions and the following disclaimer
11 # in the documentation and/or other materials provided with the
12 # distribution.
13 #     * Neither the name of Google Inc. nor the names of its
14 # contributors may be used to endorse or promote products derived from
15 # this software without specific prior written permission.
16 #
17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29 try:
30     import jsonresults
31     from jsonresults import JsonResults
32 except ImportError:
33     print "ERROR: Add the TestResultServer, google_appengine and yaml/lib directories to your PYTHONPATH"
34     raise
35
36 import unittest
37
38
39 JSON_RESULTS_TEMPLATE = (
40     '{"Webkit":{'
41     '"allFixableCount":[[TESTDATA_COUNT]],'
42     '"buildNumbers":[[TESTDATA_BUILDNUMBERS]],'
43     '"chromeRevision":[[TESTDATA_CHROMEREVISION]],'
44     '"deferredCounts":[[TESTDATA_COUNTS]],'
45     '"fixableCount":[[TESTDATA_COUNT]],'
46     '"fixableCounts":[[TESTDATA_COUNTS]],'
47     '"secondsSinceEpoch":[[TESTDATA_TIMES]],'
48     '"tests":{[TESTDATA_TESTS]},'
49     '"webkitRevision":[[TESTDATA_WEBKITREVISION]],'
50     '"wontfixCounts":[[TESTDATA_COUNTS]]'
51     '},'
52     '"version":[VERSION]'
53     '}')
54
55 JSON_RESULTS_COUNTS_TEMPLATE = (
56     '{'
57     '"C":[TESTDATA],'
58     '"F":[TESTDATA],'
59     '"I":[TESTDATA],'
60     '"O":[TESTDATA],'
61     '"P":[TESTDATA],'
62     '"T":[TESTDATA],'
63     '"X":[TESTDATA],'
64     '"Z":[TESTDATA]}')
65
66 JSON_RESULTS_DIRECTORY_TEMPLATE = '"[TESTDATA_DIRECTORY]":{[TESTDATA_DATA]}'
67
68 JSON_RESULTS_TESTS_TEMPLATE = (
69     '"[TESTDATA_TEST_NAME]":{'
70     '"results":[[TESTDATA_TEST_RESULTS]],'
71     '"times":[[TESTDATA_TEST_TIMES]]}')
72
73 JSON_RESULTS_PREFIX = "ADD_RESULTS("
74 JSON_RESULTS_SUFFIX = ");"
75
76 JSON_RESULTS_TEST_LIST_TEMPLATE = (
77     '{"Webkit":{"tests":{[TESTDATA_TESTS]}}}')
78
79
80 class JsonResultsTest(unittest.TestCase):
81     def setUp(self):
82         self._builder = "Webkit"
83
84     def _make_test_json(self, test_data):
85         if not test_data:
86             return JSON_RESULTS_PREFIX + JSON_RESULTS_SUFFIX
87
88         builds = test_data["builds"]
89         tests = test_data["tests"]
90         if not builds or not tests:
91             return JSON_RESULTS_PREFIX + JSON_RESULTS_SUFFIX
92
93         json = JSON_RESULTS_TEMPLATE
94
95         counts = []
96         build_numbers = []
97         webkit_revision = []
98         chrome_revision = []
99         times = []
100         for build in builds:
101             counts.append(JSON_RESULTS_COUNTS_TEMPLATE.replace("[TESTDATA]", build))
102             build_numbers.append("1000%s" % build)
103             webkit_revision.append("2000%s" % build)
104             chrome_revision.append("3000%s" % build)
105             times.append("100000%s000" % build)
106
107         json = json.replace("[TESTDATA_COUNTS]", ",".join(counts))
108         json = json.replace("[TESTDATA_COUNT]", ",".join(builds))
109         json = json.replace("[TESTDATA_BUILDNUMBERS]", ",".join(build_numbers))
110         json = json.replace("[TESTDATA_WEBKITREVISION]", ",".join(webkit_revision))
111         json = json.replace("[TESTDATA_CHROMEREVISION]", ",".join(chrome_revision))
112         json = json.replace("[TESTDATA_TIMES]", ",".join(times))
113
114         if "version" in test_data:
115             json = json.replace("[VERSION]", str(test_data["version"]))
116         else:
117             json = json.replace("[VERSION]", "3")
118
119         json_tests = []
120         for (name, test) in sorted(tests.iteritems()):
121             json_tests.append(self._parse_tests_dict(name, test))
122
123         json = json.replace("[TESTDATA_TESTS]", ",".join(json_tests))
124
125         return JSON_RESULTS_PREFIX + json + JSON_RESULTS_SUFFIX
126
127     def _parse_tests_dict(self, name, test):
128         if "results" in test:
129             test_results = JSON_RESULTS_TESTS_TEMPLATE.replace("[TESTDATA_TEST_NAME]", name)
130             test_results = test_results.replace("[TESTDATA_TEST_RESULTS]", test["results"])
131             test_results = test_results.replace("[TESTDATA_TEST_TIMES]", test["times"])
132             return test_results
133
134         test_results = JSON_RESULTS_DIRECTORY_TEMPLATE.replace("[TESTDATA_DIRECTORY]", name)
135         testdata = []
136         for (child_name, child_test) in sorted(test.iteritems()):
137             testdata.append(self._parse_tests_dict(child_name, child_test))
138         test_results = test_results.replace("[TESTDATA_DATA]", ",".join(testdata))
139         return test_results
140
141     def _test_merge(self, aggregated_data, incremental_data, expected_data, max_builds=jsonresults.JSON_RESULTS_MAX_BUILDS):
142         aggregated_results = self._make_test_json(aggregated_data)
143         incremental_results = self._make_test_json(incremental_data)
144         merged_results = JsonResults.merge(self._builder, aggregated_results, incremental_results, max_builds, sort_keys=True)
145
146         if expected_data:
147             expected_results = self._make_test_json(expected_data)
148             self.assertEquals(merged_results, expected_results)
149         else:
150             self.assertFalse(merged_results)
151
152     def _test_get_test_list(self, input_data, expected_data):
153         input_results = self._make_test_json(input_data)
154
155         json_tests = []
156         for test in expected_data:
157             json_tests.append("\"" + test + "\":{}")
158
159         expected_results = (JSON_RESULTS_PREFIX +
160             JSON_RESULTS_TEST_LIST_TEMPLATE.replace("[TESTDATA_TESTS]", ",".join(json_tests)) +
161             JSON_RESULTS_SUFFIX)
162
163         actual_results = JsonResults.get_test_list(self._builder, input_results)
164         self.assertEquals(actual_results, expected_results)
165
166     def test_merge_null_incremental_results(self):
167         # Empty incremental results json.
168         # Nothing to merge.
169         self._test_merge(
170             # Aggregated results
171             {"builds": ["2", "1"],
172              "tests": {"001.html": {
173                            "results": "[200,\"F\"]",
174                            "times": "[200,0]"}}},
175             # Incremental results
176             None,
177             # Expect no merge happens.
178             None)
179
180     def test_merge_empty_incremental_results(self):
181         # No actual incremental test results (only prefix and suffix) to merge.
182         # Nothing to merge.
183         self._test_merge(
184             # Aggregated results
185             {"builds": ["2", "1"],
186              "tests": {"001.html": {
187                            "results": "[200,\"F\"]",
188                            "times": "[200,0]"}}},
189             # Incremental results
190             {"builds": [],
191              "tests": {}},
192             # Expected no merge happens.
193             None)
194
195     def test_merge_empty_aggregated_results(self):
196         # No existing aggregated results.
197         # Merged results == new incremental results.
198         self._test_merge(
199             # Aggregated results
200             None,
201             # Incremental results
202
203             {"builds": ["2", "1"],
204              "tests": {"001.html": {
205                            "results": "[200,\"F\"]",
206                            "times": "[200,0]"}}},
207             # Expected result
208             {"builds": ["2", "1"],
209              "tests": {"001.html": {
210                            "results": "[200,\"F\"]",
211                            "times": "[200,0]"}}})
212
213     def test_merge_incremental_single_test_single_run_same_result(self):
214         # Incremental results has the latest build and same test results for
215         # that run.
216         # Insert the incremental results at the first place and sum number
217         # of runs for "F" (200 + 1) to get merged results.
218         self._test_merge(
219             # Aggregated results
220             {"builds": ["2", "1"],
221              "tests": {"001.html": {
222                            "results": "[200,\"F\"]",
223                            "times": "[200,0]"}}},
224             # Incremental results
225             {"builds": ["3"],
226              "tests": {"001.html": {
227                            "results": "[1,\"F\"]",
228                            "times": "[1,0]"}}},
229             # Expected results
230             {"builds": ["3", "2", "1"],
231              "tests": {"001.html": {
232                            "results": "[201,\"F\"]",
233                            "times": "[201,0]"}}})
234
235     def test_merge_single_test_single_run_different_result(self):
236         # Incremental results has the latest build but different test results
237         # for that run.
238         # Insert the incremental results at the first place.
239         self._test_merge(
240             # Aggregated results
241             {"builds": ["2", "1"],
242              "tests": {"001.html": {
243                            "results": "[200,\"F\"]",
244                            "times": "[200,0]"}}},
245             # Incremental results
246             {"builds": ["3"],
247              "tests": {"001.html": {
248                            "results": "[1, \"I\"]",
249                            "times": "[1,1]"}}},
250             # Expected results
251             {"builds": ["3", "2", "1"],
252              "tests": {"001.html": {
253                            "results": "[1,\"I\"],[200,\"F\"]",
254                            "times": "[1,1],[200,0]"}}})
255
256     def test_merge_single_test_single_run_result_changed(self):
257         # Incremental results has the latest build but results which differ from
258         # the latest result (but are the same as an older result).
259         self._test_merge(
260             # Aggregated results
261             {"builds": ["2", "1"],
262              "tests": {"001.html": {
263                            "results": "[200,\"F\"],[10,\"I\"]",
264                            "times": "[200,0],[10,1]"}}},
265             # Incremental results
266             {"builds": ["3"],
267              "tests": {"001.html": {
268                            "results": "[1,\"I\"]",
269                            "times": "[1,1]"}}},
270             # Expected results
271             {"builds": ["3", "2", "1"],
272              "tests": {"001.html": {
273                            "results": "[1,\"I\"],[200,\"F\"],[10,\"I\"]",
274                            "times": "[1,1],[200,0],[10,1]"}}})
275
276     def test_merge_multiple_tests_single_run(self):
277         # All tests have incremental updates.
278         self._test_merge(
279             # Aggregated results
280             {"builds": ["2", "1"],
281              "tests": {"001.html": {
282                            "results": "[200,\"F\"]",
283                            "times": "[200,0]"},
284                        "002.html": {
285                            "results": "[100,\"I\"]",
286                            "times": "[100,1]"}}},
287             # Incremental results
288             {"builds": ["3"],
289              "tests": {"001.html": {
290                            "results": "[1,\"F\"]",
291                            "times": "[1,0]"},
292                        "002.html": {
293                            "results": "[1,\"I\"]",
294                            "times": "[1,1]"}}},
295             # Expected results
296             {"builds": ["3", "2", "1"],
297              "tests": {"001.html": {
298                            "results": "[201,\"F\"]",
299                            "times": "[201,0]"},
300                        "002.html": {
301                            "results": "[101,\"I\"]",
302                            "times": "[101,1]"}}})
303
304     def test_merge_multiple_tests_single_run_one_no_result(self):
305         self._test_merge(
306             # Aggregated results
307             {"builds": ["2", "1"],
308              "tests": {"001.html": {
309                            "results": "[200,\"F\"]",
310                            "times": "[200,0]"},
311                        "002.html": {
312                            "results": "[100,\"I\"]",
313                            "times": "[100,1]"}}},
314             # Incremental results
315             {"builds": ["3"],
316              "tests": {"002.html": {
317                            "results": "[1,\"I\"]",
318                            "times": "[1,1]"}}},
319             # Expected results
320             {"builds": ["3", "2", "1"],
321              "tests": {"001.html": {
322                            "results": "[1,\"N\"],[200,\"F\"]",
323                            "times": "[201,0]"},
324                        "002.html": {
325                            "results": "[101,\"I\"]",
326                            "times": "[101,1]"}}})
327
328     def test_merge_single_test_multiple_runs(self):
329         self._test_merge(
330             # Aggregated results
331             {"builds": ["2", "1"],
332              "tests": {"001.html": {
333                            "results": "[200,\"F\"]",
334                            "times": "[200,0]"}}},
335             # Incremental results
336             {"builds": ["4", "3"],
337              "tests": {"001.html": {
338                            "results": "[2, \"I\"]",
339                            "times": "[2,2]"}}},
340             # Expected results
341             {"builds": ["4", "3", "2", "1"],
342              "tests": {"001.html": {
343                            "results": "[2,\"I\"],[200,\"F\"]",
344                            "times": "[2,2],[200,0]"}}})
345
346     def test_merge_multiple_tests_multiple_runs(self):
347         self._test_merge(
348             # Aggregated results
349             {"builds": ["2", "1"],
350              "tests": {"001.html": {
351                            "results": "[200,\"F\"]",
352                            "times": "[200,0]"},
353                        "002.html": {
354                            "results": "[10,\"Z\"]",
355                            "times": "[10,0]"}}},
356             # Incremental results
357             {"builds": ["4", "3"],
358              "tests": {"001.html": {
359                            "results": "[2, \"I\"]",
360                            "times": "[2,2]"},
361                        "002.html": {
362                            "results": "[1,\"C\"]",
363                            "times": "[1,1]"}}},
364             # Expected results
365             {"builds": ["4", "3", "2", "1"],
366              "tests": {"001.html": {
367                            "results": "[2,\"I\"],[200,\"F\"]",
368                            "times": "[2,2],[200,0]"},
369                        "002.html": {
370                            "results": "[1,\"C\"],[10,\"Z\"]",
371                            "times": "[1,1],[10,0]"}}})
372
373     def test_merge_incremental_result_older_build(self):
374         # Test the build in incremental results is older than the most recent
375         # build in aggregated results.
376         # The incremental results should be dropped and no merge happens.
377         self._test_merge(
378             # Aggregated results
379             {"builds": ["3", "1"],
380              "tests": {"001.html": {
381                            "results": "[200,\"F\"]",
382                            "times": "[200,0]"}}},
383             # Incremental results
384             {"builds": ["2"],
385              "tests": {"001.html": {
386                            "results": "[1, \"F\"]",
387                            "times": "[1,0]"}}},
388             # Expected no merge happens.
389             None)
390
391     def test_merge_incremental_result_same_build(self):
392         # Test the build in incremental results is same as the build in
393         # aggregated results.
394         # The incremental results should be dropped and no merge happens.
395         self._test_merge(
396             # Aggregated results
397             {"builds": ["2", "1"],
398              "tests": {"001.html": {
399                            "results": "[200,\"F\"]",
400                            "times": "[200,0]"}}},
401             # Incremental results
402             {"builds": ["3", "2"],
403              "tests": {"001.html": {
404                            "results": "[2, \"F\"]",
405                            "times": "[2,0]"}}},
406             # Expected no merge happens.
407             None)
408
409     def test_merge_remove_test_with_no_data(self):
410         # Remove test where there is no data in all runs.
411         self._test_merge(
412             # Aggregated results
413             {"builds": ["2", "1"],
414              "tests": {"001.html": {
415                            "results": "[200,\"N\"]",
416                            "times": "[200,0]"},
417                        "002.html": {
418                            "results": "[10,\"F\"]",
419                            "times": "[10,0]"}}},
420             # Incremental results
421             {"builds": ["3"],
422              "tests": {"001.html": {
423                            "results": "[1,\"N\"]",
424                            "times": "[1,0]"},
425                        "002.html": {
426                            "results": "[1,\"P\"]",
427                            "times": "[1,0]"}}},
428             # Expected results
429             {"builds": ["3", "2", "1"],
430              "tests": {"002.html": {
431                            "results": "[1,\"P\"],[10,\"F\"]",
432                            "times": "[11,0]"}}})
433
434     def test_merge_remove_test_with_all_pass(self):
435         # Remove test where all run pass and max running time < 1 seconds
436         self._test_merge(
437             # Aggregated results
438             {"builds": ["2", "1"],
439              "tests": {"001.html": {
440                            "results": "[200,\"P\"]",
441                            "times": "[200,0]"},
442                        "002.html": {
443                            "results": "[10,\"F\"]",
444                            "times": "[10,0]"}}},
445             # Incremental results
446             {"builds": ["3"],
447              "tests": {"001.html": {
448                            "results": "[1,\"P\"]",
449                            "times": "[1,0]"},
450                        "002.html": {
451                            "results": "[1,\"P\"]",
452                            "times": "[1,0]"}}},
453             # Expected results
454             {"builds": ["3", "2", "1"],
455              "tests": {"002.html": {
456                            "results": "[1,\"P\"],[10,\"F\"]",
457                            "times": "[11,0]"}}})
458
459     def test_merge_keep_test_with_all_pass_but_slow_time(self):
460         # Do not remove test where all run pass but max running time >= 1 seconds
461         self._test_merge(
462             # Aggregated results
463             {"builds": ["2", "1"],
464              "tests": {"001.html": {
465                            "results": "[200,\"P\"]",
466                            "times": "[200,0]"},
467                        "002.html": {
468                            "results": "[10,\"F\"]",
469                            "times": "[10,0]"}}},
470             # Incremental results
471             {"builds": ["3"],
472              "tests": {"001.html": {
473                            "results": "[1,\"P\"]",
474                            "times": "[1,1]"},
475                        "002.html": {
476                            "results": "[1,\"P\"]",
477                            "times": "[1,0]"}}},
478             # Expected results
479             {"builds": ["3", "2", "1"],
480              "tests": {"001.html": {
481                            "results": "[201,\"P\"]",
482                            "times": "[1,1],[200,0]"},
483                        "002.html": {
484                            "results": "[1,\"P\"],[10,\"F\"]",
485                            "times": "[11,0]"}}})
486
487     def test_merge_prune_extra_results(self):
488         # Remove items from test results and times that exceed the max number
489         # of builds to track.
490         max_builds = str(jsonresults.JSON_RESULTS_MAX_BUILDS)
491         self._test_merge(
492             # Aggregated results
493             {"builds": ["2", "1"],
494              "tests": {"001.html": {
495                            "results": "[" + max_builds + ",\"F\"],[1,\"I\"]",
496                            "times": "[" + max_builds + ",0],[1,1]"}}},
497             # Incremental results
498             {"builds": ["3"],
499              "tests": {"001.html": {
500                            "results": "[1,\"T\"]",
501                            "times": "[1,1]"}}},
502             # Expected results
503             {"builds": ["3", "2", "1"],
504              "tests": {"001.html": {
505                            "results": "[1,\"T\"],[" + max_builds + ",\"F\"]",
506                            "times": "[1,1],[" + max_builds + ",0]"}}})
507
508     def test_merge_prune_extra_results_small(self):
509         # Remove items from test results and times that exceed the max number
510         # of builds to track, using smaller threshold.
511         max_builds = str(jsonresults.JSON_RESULTS_MAX_BUILDS_SMALL)
512         self._test_merge(
513             # Aggregated results
514             {"builds": ["2", "1"],
515              "tests": {"001.html": {
516                            "results": "[" + max_builds + ",\"F\"],[1,\"I\"]",
517                            "times": "[" + max_builds + ",0],[1,1]"}}},
518             # Incremental results
519             {"builds": ["3"],
520              "tests": {"001.html": {
521                            "results": "[1,\"T\"]",
522                            "times": "[1,1]"}}},
523             # Expected results
524             {"builds": ["3", "2", "1"],
525              "tests": {"001.html": {
526                            "results": "[1,\"T\"],[" + max_builds + ",\"F\"]",
527                            "times": "[1,1],[" + max_builds + ",0]"}}},
528             int(max_builds))
529
530     def test_merge_prune_extra_results_with_new_result_of_same_type(self):
531         # Test that merging in a new result of the same type as the last result
532         # causes old results to fall off.
533         max_builds = str(jsonresults.JSON_RESULTS_MAX_BUILDS_SMALL)
534         self._test_merge(
535             # Aggregated results
536             {"builds": ["2", "1"],
537              "tests": {"001.html": {
538                            "results": "[" + max_builds + ",\"F\"],[1,\"N\"]",
539                            "times": "[" + max_builds + ",0],[1,1]"}}},
540             # Incremental results
541             {"builds": ["3"],
542              "tests": {"001.html": {
543                            "results": "[1,\"F\"]",
544                            "times": "[1,0]"}}},
545             # Expected results
546             {"builds": ["3", "2", "1"],
547              "tests": {"001.html": {
548                            "results": "[" + max_builds + ",\"F\"]",
549                            "times": "[" + max_builds + ",0]"}}},
550             int(max_builds))
551
552     def test_merge_build_directory_hierarchy(self):
553         self._test_merge(
554             # Aggregated results
555             {"builds": ["2", "1"],
556              "tests": {"foo/001.html": {
557                            "results": "[50,\"F\"]",
558                            "times": "[50,0]"},
559                        "foo/002.html": {
560                            "results": "[100,\"I\"]",
561                            "times": "[100,0]"}}},
562             # Incremental results
563             {"builds": ["3"],
564              "tests": {"foo": {
565                            "001.html": {
566                                "results": "[1,\"F\"]",
567                                "times": "[1,0]"},
568                            "002.html": {
569                                "results": "[1,\"I\"]",
570                                "times": "[1,0]"}}},
571              "version": 4},
572             # Expected results
573             {"builds": ["3", "2", "1"],
574              "tests": {"foo/001.html": {
575                            "results": "[51,\"F\"]",
576                            "times": "[51,0]"},
577                        "foo/002.html": {
578                            "results": "[101,\"I\"]",
579                            "times": "[101,0]"}},
580              "version": 3})
581
582     # FIXME(aboxhall): Add some tests for xhtml/svg test results.
583
584     def test_get_test_name_list(self):
585         # Get test name list only. Don't include non-test-list data and
586         # of test result details.
587         self._test_get_test_list(
588             # Input results
589             {"builds": ["3", "2", "1"],
590              "tests": {"001.html": {
591                            "results": "[200,\"P\"]",
592                            "times": "[200,0]"},
593                        "002.html": {
594                            "results": "[10,\"F\"]",
595                            "times": "[10,0]"}}},
596             # Expected results
597             ["001.html", "002.html"])
598
599     def test_remove_gtest_modifiers(self):
600         self._test_merge(
601             # Aggregated results
602             {"builds": ["2", "1"],
603              "tests": {"foo.bar": {
604                            "results": "[50,\"F\"]",
605                            "times": "[50,0]"},
606                        "foo.bar2": {
607                            "results": "[100,\"I\"]",
608                            "times": "[100,0]"},
609                        "foo.FAILS_bar3": {
610                            "results": "[100,\"I\"]",
611                            "times": "[100,0]"},
612                        }},
613             # Incremental results
614             {"builds": ["3"],
615              "tests": {"foo.FLAKY_bar": {
616                            "results": "[1,\"F\"]",
617                            "times": "[1,0]"},
618                        "foo.DISABLED_bar2": {
619                            "results": "[1,\"I\"]",
620                            "times": "[1,0]"},
621                        "foo.bar3": {
622                            "results": "[1,\"I\"]",
623                            "times": "[1,0]"},
624                        "foo.FAILS_bar3": {
625                            "results": "[1,\"I\"]",
626                            "times": "[1,0]"},
627                        "foo.MAYBE_bar4": {
628                            "results": "[1,\"I\"]",
629                            "times": "[1,0]"}},
630              "version": 4},
631             # Expected results
632             {"builds": ["3", "2", "1"],
633              "tests": {"foo.bar": {
634                            "results": "[51,\"F\"]",
635                            "times": "[51,0]"},
636                        "foo.bar2": {
637                            "results": "[101,\"I\"]",
638                            "times": "[101,0]"},
639                        "foo.bar3": {
640                            "results": "[1,\"I\"]",
641                            "times": "[1,0]"},
642                        "foo.FAILS_bar3": {
643                               "results": "[1,\"N\"],[100,\"I\"]",
644                               "times": "[101,0]"},
645                        "foo.bar4": {
646                            "results": "[1,\"I\"]",
647                            "times": "[1,0]"}},
648              "version": 3})
649
650 if __name__ == '__main__':
651     unittest.main()