1# Copyright 2020 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import logging
6from collections import OrderedDict
7import os
8
9import result_sink_util
10
11LOGGER = logging.getLogger(__name__)
12
13
14class StdJson():
15
16  def __init__(self, **kwargs):
17    """Module for storing the results in standard JSON format.
18
19    https://chromium.googlesource.com/chromium/src/+/master/docs/testing/json_test_results_format.md
20    """
21
22    self.tests = OrderedDict()
23    self.result_sink = result_sink_util.ResultSinkClient()
24    self._shard_index = os.getenv('GTEST_SHARD_INDEX', 0)
25
26    if 'passed' in kwargs:
27      self.mark_all_passed(kwargs['passed'])
28    if 'failed' in kwargs:
29      self.mark_all_failed(kwargs['failed'])
30    if 'flaked' in kwargs:
31      self.mark_all_passed(kwargs['flaked'], flaky=True)
32
33  def _init_test(self, expected, actual, is_unexpected=False):
34    """Returns a dict of test result info used as values in self.tests dict."""
35    test = {
36        'expected': expected,
37        'actual': actual,
38        'shard': self._shard_index,
39    }
40    if is_unexpected:
41      test['is_unexpected'] = True
42
43    return test
44
45  def finalize(self):
46    """Teardown and finalizing tasks needed after all results are reported."""
47    LOGGER.info('Finalizing in standard json util.')
48    self.result_sink.close()
49
50  def mark_passed(self, test, flaky=False):
51    """Sets test as passed
52
53    Params:
54      test (str): a test in format "{TestCase}/{testMethod}"
55
56    If flaky=True, or if 'FAIL' already set in 'actual',
57    apply is_flaky=True for all test(s).
58    """
59    if not test:
60      LOGGER.warn('Empty or None test name passed to standard_json_util')
61      return
62
63    result_sink_test_result = result_sink_util.compose_test_result(
64        test, 'PASS', True)
65    self.result_sink.post(result_sink_test_result)
66
67    if test in self.tests:
68      self.tests[test]['actual'] = self.tests[test]['actual'] + " PASS"
69    else:
70      self.tests[test] = self._init_test('PASS', 'PASS')
71
72    if flaky or 'FAIL' in self.tests[test]['actual']:
73      self.tests[test]['is_flaky'] = True
74
75    self.tests[test].pop('is_unexpected', None)
76
77  def mark_all_passed(self, tests, flaky=False):
78    """Marks all tests as PASS"""
79    for test in tests:
80      self.mark_passed(test, flaky)
81
82  def mark_failed(self, test, test_log=None):
83    """Sets test(s) as failed.
84
85    Params:
86      test (str): a test in format "{TestCase}/{testMethod}"
87      test_log (str): log of the specific test
88    """
89    if not test:
90      LOGGER.warn('Empty or None test name passed to standard_json_util')
91      return
92
93    result_sink_test_result = result_sink_util.compose_test_result(
94        test, 'FAIL', False, test_log=test_log)
95    self.result_sink.post(result_sink_test_result)
96
97    if test in self.tests:
98      self.tests[test]['actual'] = self.tests[test]['actual'] + " FAIL"
99      self.tests[test]['is_unexpected'] = True
100    else:
101      self.tests[test] = self._init_test('PASS', 'FAIL', True)
102
103  def mark_all_failed(self, tests):
104    """Marks all tests as FAIL"""
105    for test in tests:
106      self.mark_failed(test)
107
108  def mark_skipped(self, test):
109    """Sets test(s) as expected SKIP.
110
111    Params:
112      test (str): a test in format "{TestCase}/{testMethod}"
113    """
114    if not test:
115      LOGGER.warn('Empty or None test name passed to standard_json_util')
116      return
117
118    result_sink_test_result = result_sink_util.compose_test_result(
119        test, 'SKIP', True, tags=[('disabled_test', 'true')])
120    self.result_sink.post(result_sink_test_result)
121
122    self.tests[test] = self._init_test('SKIP', 'SKIP')
123
124  def mark_all_skipped(self, tests):
125    for test in tests:
126      self.mark_skipped(test)
127
128  def mark_timeout(self, test):
129    """Sets test as TIMEOUT, which is used to indicate a test abort/timeout
130
131    Params:
132      test (str): a test in format "{TestCase}/{testMethod}"
133    """
134    if not test:
135      LOGGER.warn('Empty or None test name passed to standard_json_util')
136      return
137
138    # Timeout tests in iOS test runner are tests that's unexpectedly not run.
139    test_log = ('The test is compiled in test target but was unexpectedly not'
140                ' run or not finished.')
141    result_sink_test_result = result_sink_util.compose_test_result(
142        test,
143        'SKIP',
144        False,
145        test_log=test_log,
146        tags=[('disabled_test', 'false')])
147    self.result_sink.post(result_sink_test_result)
148
149    if test in self.tests:
150      self.tests[test]['actual'] = self.tests[test]['actual'] + " TIMEOUT"
151      self.tests[test]['is_unexpected'] = True
152    else:
153      self.tests[test] = self._init_test('PASS', 'TIMEOUT', True)
154