xref: /aosp_15_r20/external/toolchain-utils/crosperf/generate_report_unittest.py (revision 760c253c1ed00ce9abd48f8546f08516e57485fe)
1*760c253cSXin Li#!/usr/bin/env python3
2*760c253cSXin Li# -*- coding: utf-8 -*-
3*760c253cSXin Li# Copyright 2016 The ChromiumOS Authors
4*760c253cSXin Li# Use of this source code is governed by a BSD-style license that can be
5*760c253cSXin Li# found in the LICENSE file.
6*760c253cSXin Li
7*760c253cSXin Li"""Test for generate_report.py."""
8*760c253cSXin Li
9*760c253cSXin Li
10*760c253cSXin Liimport copy
11*760c253cSXin Liimport json
12*760c253cSXin Liimport unittest
13*760c253cSXin Liimport unittest.mock as mock
14*760c253cSXin Li
15*760c253cSXin Liimport generate_report
16*760c253cSXin Liimport results_report
17*760c253cSXin Liimport test_flag
18*760c253cSXin Li
19*760c253cSXin Li
20*760c253cSXin Li# pylint: disable=deprecated-module
21*760c253cSXin Litry:
22*760c253cSXin Li    from StringIO import StringIO  # for Python 2
23*760c253cSXin Liexcept ImportError:
24*760c253cSXin Li    from io import StringIO  # for Python 3
25*760c253cSXin Li
26*760c253cSXin Li
27*760c253cSXin Liclass _ContextualStringIO(StringIO):
28*760c253cSXin Li    """StringIO that can be used in `with` statements."""
29*760c253cSXin Li
30*760c253cSXin Li    def __init__(self, *args):
31*760c253cSXin Li        StringIO.__init__(self, *args)
32*760c253cSXin Li
33*760c253cSXin Li    def __enter__(self):
34*760c253cSXin Li        return self
35*760c253cSXin Li
36*760c253cSXin Li    def __exit__(self, _type, _value, _traceback):
37*760c253cSXin Li        pass
38*760c253cSXin Li
39*760c253cSXin Li
40*760c253cSXin Liclass GenerateReportTests(unittest.TestCase):
41*760c253cSXin Li    """Tests for generate_report.py."""
42*760c253cSXin Li
43*760c253cSXin Li    def testCountBenchmarks(self):
44*760c253cSXin Li        runs = {
45*760c253cSXin Li            "foo": [[{}, {}, {}], [{}, {}, {}, {}]],
46*760c253cSXin Li            "bar": [],
47*760c253cSXin Li            "baz": [[], [{}], [{}, {}, {}]],
48*760c253cSXin Li        }
49*760c253cSXin Li        results = generate_report.CountBenchmarks(runs)
50*760c253cSXin Li        expected_results = [("foo", 4), ("bar", 0), ("baz", 3)]
51*760c253cSXin Li        self.assertCountEqual(expected_results, results)
52*760c253cSXin Li
53*760c253cSXin Li    def testCutResultsInPlace(self):
54*760c253cSXin Li        bench_data = {
55*760c253cSXin Li            "foo": [[{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 2.5, "c": 1}]],
56*760c253cSXin Li            "bar": [[{"d": 11, "e": 12, "f": 13}]],
57*760c253cSXin Li            "baz": [[{"g": 12, "h": 13}]],
58*760c253cSXin Li            "qux": [[{"i": 11}]],
59*760c253cSXin Li        }
60*760c253cSXin Li        original_bench_data = copy.deepcopy(bench_data)
61*760c253cSXin Li
62*760c253cSXin Li        max_keys = 2
63*760c253cSXin Li        results = generate_report.CutResultsInPlace(
64*760c253cSXin Li            bench_data, max_keys=max_keys, complain_on_update=False
65*760c253cSXin Li        )
66*760c253cSXin Li        # Cuts should be in-place.
67*760c253cSXin Li        self.assertIs(results, bench_data)
68*760c253cSXin Li        self.assertCountEqual(
69*760c253cSXin Li            list(original_bench_data.keys()), list(bench_data.keys())
70*760c253cSXin Li        )
71*760c253cSXin Li        for bench_name, original_runs in original_bench_data.items():
72*760c253cSXin Li            bench_runs = bench_data[bench_name]
73*760c253cSXin Li            self.assertEqual(len(original_runs), len(bench_runs))
74*760c253cSXin Li            # Order of these sub-lists shouldn't have changed.
75*760c253cSXin Li            for original_list, new_list in zip(original_runs, bench_runs):
76*760c253cSXin Li                self.assertEqual(len(original_list), len(new_list))
77*760c253cSXin Li                for original_keyvals, sub_keyvals in zip(
78*760c253cSXin Li                    original_list, new_list
79*760c253cSXin Li                ):
80*760c253cSXin Li                    # sub_keyvals must be a subset of original_keyvals
81*760c253cSXin Li                    self.assertDictContainsSubset(sub_keyvals, original_keyvals)
82*760c253cSXin Li
83*760c253cSXin Li    def testCutResultsInPlaceLeavesRetval(self):
84*760c253cSXin Li        bench_data = {
85*760c253cSXin Li            "foo": [[{"retval": 0, "a": 1}]],
86*760c253cSXin Li            "bar": [[{"retval": 1}]],
87*760c253cSXin Li            "baz": [[{"RETVAL": 1}]],
88*760c253cSXin Li        }
89*760c253cSXin Li        results = generate_report.CutResultsInPlace(
90*760c253cSXin Li            bench_data, max_keys=0, complain_on_update=False
91*760c253cSXin Li        )
92*760c253cSXin Li        # Just reach into results assuming we know it otherwise outputs things in
93*760c253cSXin Li        # the expected way. If it doesn't, testCutResultsInPlace should give an
94*760c253cSXin Li        # indication as to what, exactly, is broken.
95*760c253cSXin Li        self.assertEqual(list(results["foo"][0][0].items()), [("retval", 0)])
96*760c253cSXin Li        self.assertEqual(list(results["bar"][0][0].items()), [("retval", 1)])
97*760c253cSXin Li        self.assertEqual(list(results["baz"][0][0].items()), [])
98*760c253cSXin Li
99*760c253cSXin Li    def _RunMainWithInput(self, args, input_obj):
100*760c253cSXin Li        assert "-i" not in args
101*760c253cSXin Li        args += ["-i", "-"]
102*760c253cSXin Li        input_buf = _ContextualStringIO(json.dumps(input_obj))
103*760c253cSXin Li        with mock.patch(
104*760c253cSXin Li            "generate_report.PickInputFile", return_value=input_buf
105*760c253cSXin Li        ) as patched_pick:
106*760c253cSXin Li            result = generate_report.Main(args)
107*760c253cSXin Li            patched_pick.assert_called_once_with("-")
108*760c253cSXin Li            return result
109*760c253cSXin Li
110*760c253cSXin Li    @mock.patch("generate_report.RunActions")
111*760c253cSXin Li    def testMain(self, mock_run_actions):
112*760c253cSXin Li        # Email is left out because it's a bit more difficult to test, and it'll be
113*760c253cSXin Li        # mildly obvious if it's failing.
114*760c253cSXin Li        args = ["--json", "--html", "--text"]
115*760c253cSXin Li        return_code = self._RunMainWithInput(
116*760c253cSXin Li            args, {"platforms": [], "data": {}}
117*760c253cSXin Li        )
118*760c253cSXin Li        self.assertEqual(0, return_code)
119*760c253cSXin Li        self.assertEqual(mock_run_actions.call_count, 1)
120*760c253cSXin Li        ctors = [ctor for ctor, _ in mock_run_actions.call_args[0][0]]
121*760c253cSXin Li        self.assertEqual(
122*760c253cSXin Li            ctors,
123*760c253cSXin Li            [
124*760c253cSXin Li                results_report.JSONResultsReport,
125*760c253cSXin Li                results_report.TextResultsReport,
126*760c253cSXin Li                results_report.HTMLResultsReport,
127*760c253cSXin Li            ],
128*760c253cSXin Li        )
129*760c253cSXin Li
130*760c253cSXin Li    @mock.patch("generate_report.RunActions")
131*760c253cSXin Li    def testMainSelectsHTMLIfNoReportsGiven(self, mock_run_actions):
132*760c253cSXin Li        args = []
133*760c253cSXin Li        return_code = self._RunMainWithInput(
134*760c253cSXin Li            args, {"platforms": [], "data": {}}
135*760c253cSXin Li        )
136*760c253cSXin Li        self.assertEqual(0, return_code)
137*760c253cSXin Li        self.assertEqual(mock_run_actions.call_count, 1)
138*760c253cSXin Li        ctors = [ctor for ctor, _ in mock_run_actions.call_args[0][0]]
139*760c253cSXin Li        self.assertEqual(ctors, [results_report.HTMLResultsReport])
140*760c253cSXin Li
141*760c253cSXin Li    # We only mock print_exc so we don't have exception info printed to stdout.
142*760c253cSXin Li    @mock.patch("generate_report.WriteFile", side_effect=ValueError("Oh noo"))
143*760c253cSXin Li    @mock.patch("traceback.print_exc")
144*760c253cSXin Li    def testRunActionsRunsAllActionsRegardlessOfExceptions(
145*760c253cSXin Li        self, mock_print_exc, mock_write_file
146*760c253cSXin Li    ):
147*760c253cSXin Li        actions = [
148*760c253cSXin Li            (None, "json"),
149*760c253cSXin Li            (None, "html"),
150*760c253cSXin Li            (None, "text"),
151*760c253cSXin Li            (None, "email"),
152*760c253cSXin Li        ]
153*760c253cSXin Li        output_prefix = "-"
154*760c253cSXin Li        ok = generate_report.RunActions(
155*760c253cSXin Li            actions, {}, output_prefix, overwrite=False, verbose=False
156*760c253cSXin Li        )
157*760c253cSXin Li        self.assertFalse(ok)
158*760c253cSXin Li        self.assertEqual(mock_write_file.call_count, len(actions))
159*760c253cSXin Li        self.assertEqual(mock_print_exc.call_count, len(actions))
160*760c253cSXin Li
161*760c253cSXin Li    @mock.patch("generate_report.WriteFile")
162*760c253cSXin Li    def testRunActionsReturnsTrueIfAllActionsSucceed(self, mock_write_file):
163*760c253cSXin Li        actions = [(None, "json"), (None, "html"), (None, "text")]
164*760c253cSXin Li        output_prefix = "-"
165*760c253cSXin Li        ok = generate_report.RunActions(
166*760c253cSXin Li            actions, {}, output_prefix, overwrite=False, verbose=False
167*760c253cSXin Li        )
168*760c253cSXin Li        self.assertEqual(mock_write_file.call_count, len(actions))
169*760c253cSXin Li        self.assertTrue(ok)
170*760c253cSXin Li
171*760c253cSXin Li
172*760c253cSXin Liif __name__ == "__main__":
173*760c253cSXin Li    test_flag.SetTestMode(True)
174*760c253cSXin Li    unittest.main()
175