Chromium Code Reviews| Index: gm/display_json_results.py |
| =================================================================== |
| --- gm/display_json_results.py (revision 9180) |
| +++ gm/display_json_results.py (working copy) |
| @@ -3,10 +3,14 @@ |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| -"""Utility to confirm that a JSON summary written by GM contains no failures. |
| +"""Utility to display a summary of JSON-format GM results, and exit with |
| +a nonzero errorcode if there were non-ignored failures in the GM results. |
| Usage: |
| - python confirm_no_failures_in_json.py <filename> |
| + python display_json_results.py <filename> |
| + |
| +TODO(epoger): We may want to add flags so that the caller can choose which |
| +error types cause a nonzero return code. |
|
borenet
2013/05/17 19:48:33
Other options might be nice as well, for example t
epoger
2013/05/18 03:09:54
Good idea... added to the TODO. If it's OK with y
borenet
2013/05/20 12:09:55
Agreed.
|
| """ |
| __author__ = 'Elliot Poger' |
| @@ -20,28 +24,98 @@ |
| # gm_expectations.cpp ! |
| JSONKEY_ACTUALRESULTS = 'actual-results' |
| JSONKEY_ACTUALRESULTS_FAILED = 'failed' |
| +JSONKEY_ACTUALRESULTS_FAILUREIGNORED = 'failure-ignored' |
| +JSONKEY_ACTUALRESULTS_NOCOMPARISON = 'no-comparison' |
| +JSONKEY_ACTUALRESULTS_SUCCEEDED = 'succeeded' |
| -# This is the same indent level as used by jsoncpp, just for consistency. |
| -JSON_INDENTLEVEL = 3 |
| +class ResultAccumulator(object): |
| + """Object that accumulates results of a given type, and can generate a |
| + summary upon request.""" |
| -def Assert(filepath): |
| - """Raises an exception if the JSON summary at filepath contains any failed |
| - tests, or if we were unable to read the JSON summary.""" |
| - failed_tests = GetFailedTests(filepath) |
| - if failed_tests: |
| - raise Exception('JSON file %s contained these test failures...\n%s' % ( |
| - filepath, json.dumps(failed_tests, indent=JSON_INDENTLEVEL))) |
| + def __init__(self, name, do_list, do_fail): |
| + """name: name of the category this result type falls into |
| + do_list: whether to list all of the tests with this results type |
| + do_fail: whether to return with nonzero exit code if there are any |
| + results of this type |
| + """ |
| + self._name = name |
| + self._do_list = do_list |
| + self._do_fail = do_fail |
| + self._testnames = [] |
| + def AddResult(self, testname): |
| + """Adds a result of this particular type. |
| + testname: (string) name of the test""" |
| + self._testnames.append(testname) |
| -def GetFailedTests(filepath): |
| - """Returns the dictionary of failed tests from the JSON file at filepath.""" |
| + def ShouldSignalFailure(self): |
| + """Returns true if this result type is serious (self._do_fail is True) |
| + and there were any results of this type.""" |
| + if self._do_fail and self._testnames: |
| + return True |
| + else: |
| + return False |
| + |
| + def GetSummaryLine(self): |
| + """Returns a single-line string summary of all results added to this |
| + accumulator so far.""" |
| + summary = '' |
| + if self._do_fail: |
| + summary += '[*] ' |
| + else: |
| + summary += '[ ] ' |
| + summary += str(len(self._testnames)) |
| + summary += ' ' |
| + summary += self._name |
| + if self._do_list: |
| + summary += ': ' |
| + for testname in self._testnames: |
| + summary += testname |
| + summary += ' ' |
| + return summary |
| + |
| + |
| +# Map labels within the JSON file to the ResultAccumulator for each label. |
| +RESULTS_MAP = { |
|
borenet
2013/05/17 19:48:33
Is there any reason for this to be global? I'm al
epoger
2013/05/18 03:09:54
No need for it to be global... I moved its definit
|
| + JSONKEY_ACTUALRESULTS_FAILED: |
| + ResultAccumulator(name='ExpectationsMismatch', |
| + do_list=True, do_fail=True), |
| + JSONKEY_ACTUALRESULTS_FAILUREIGNORED: |
| + ResultAccumulator(name='IgnoredExpectationsMismatch', |
| + do_list=True, do_fail=False), |
| + JSONKEY_ACTUALRESULTS_NOCOMPARISON: |
| + ResultAccumulator(name='MissingExpectations', |
| + do_list=False, do_fail=False), |
| + JSONKEY_ACTUALRESULTS_SUCCEEDED: |
| + ResultAccumulator(name='Passed', |
| + do_list=False, do_fail=False), |
| +} |
| + |
| + |
| +def Display(filepath): |
| + """Displays a summary of the results in a JSON file, and returns True if |
| + those results indicate a significant failure. |
| + filepath: (string) path to JSON file""" |
| + failure = False |
| json_dict = json.load(open(filepath)) |
| actual_results = json_dict[JSONKEY_ACTUALRESULTS] |
| - return actual_results[JSONKEY_ACTUALRESULTS_FAILED] |
| + for label, accumulator in RESULTS_MAP.iteritems(): |
| + results = actual_results[label] |
| + if results: |
| + for result in results: |
| + accumulator.AddResult(result) |
| + print accumulator.GetSummaryLine() |
| + failure = failure or accumulator.ShouldSignalFailure() |
| + print '(results marked with [*] will cause nonzero return value)' |
| + return failure |
| if '__main__' == __name__: |
|
epoger
2013/05/17 17:56:45
Here are two sample runs:
epoger@wpgntat-ubiq141:
|
| if len(sys.argv) != 2: |
| raise Exception('usage: %s <input-json-filepath>' % sys.argv[0]) |
| - Assert(sys.argv[1]) |
| + failure = Display(sys.argv[1]) |
|
borenet
2013/05/17 19:48:33
You could also do:
sys.exit(Display(sys.argv[1]))
epoger
2013/05/18 03:09:54
If it's OK with you, I would really rather keep th
borenet
2013/05/20 12:09:55
Yep! Now the only thing that's slightly confusing
epoger
2013/05/21 16:02:51
Good point. I inverted the meaning of the returne
|
| + if failure: |
| + exit(1) |
| + else: |
| + exit(0) |