xref: /aosp_15_r20/external/toolchain-utils/crosperf/results_report_unittest.py (revision 760c253c1ed00ce9abd48f8546f08516e57485fe)
1*760c253cSXin Li#!/usr/bin/env python3
2*760c253cSXin Li# -*- coding: utf-8 -*-
3*760c253cSXin Li#
4*760c253cSXin Li# Copyright 2016 The ChromiumOS Authors
5*760c253cSXin Li# Use of this source code is governed by a BSD-style license that can be
6*760c253cSXin Li# found in the LICENSE file.
7*760c253cSXin Li
8*760c253cSXin Li"""Unittest for the results reporter."""
9*760c253cSXin Li
10*760c253cSXin Li
11*760c253cSXin Liimport collections
12*760c253cSXin Liimport io
13*760c253cSXin Liimport os
14*760c253cSXin Liimport unittest
15*760c253cSXin Liimport unittest.mock as mock
16*760c253cSXin Li
17*760c253cSXin Lifrom benchmark_run import MockBenchmarkRun
18*760c253cSXin Lifrom cros_utils import logger
19*760c253cSXin Lifrom experiment_factory import ExperimentFactory
20*760c253cSXin Lifrom experiment_file import ExperimentFile
21*760c253cSXin Lifrom machine_manager import MockCrosMachine
22*760c253cSXin Lifrom machine_manager import MockMachineManager
23*760c253cSXin Lifrom results_cache import MockResult
24*760c253cSXin Lifrom results_report import BenchmarkResults
25*760c253cSXin Lifrom results_report import HTMLResultsReport
26*760c253cSXin Lifrom results_report import JSONResultsReport
27*760c253cSXin Lifrom results_report import ParseChromeosImage
28*760c253cSXin Lifrom results_report import ParseStandardPerfReport
29*760c253cSXin Lifrom results_report import TextResultsReport
30*760c253cSXin Liimport test_flag
31*760c253cSXin Li
32*760c253cSXin Li
33*760c253cSXin Liclass FreeFunctionsTest(unittest.TestCase):
34*760c253cSXin Li    """Tests for any free functions in results_report."""
35*760c253cSXin Li
36*760c253cSXin Li    def testParseChromeosImage(self):
37*760c253cSXin Li        # N.B. the cases with blank versions aren't explicitly supported by
38*760c253cSXin Li        # ParseChromeosImage. I'm not sure if they need to be supported, but the
39*760c253cSXin Li        # goal of this was to capture existing functionality as much as possible.
40*760c253cSXin Li        base_case = (
41*760c253cSXin Li            "/my/chroot/src/build/images/x86-generic/R01-1.0.date-time"
42*760c253cSXin Li            "/chromiumos_test_image.bin"
43*760c253cSXin Li        )
44*760c253cSXin Li        self.assertEqual(ParseChromeosImage(base_case), ("R01-1.0", base_case))
45*760c253cSXin Li
46*760c253cSXin Li        dir_base_case = os.path.dirname(base_case)
47*760c253cSXin Li        self.assertEqual(ParseChromeosImage(dir_base_case), ("", dir_base_case))
48*760c253cSXin Li
49*760c253cSXin Li        buildbot_case = (
50*760c253cSXin Li            "/my/chroot/chroot/tmp/buildbot-build/R02-1.0.date-time"
51*760c253cSXin Li            "/chromiumos_test_image.bin"
52*760c253cSXin Li        )
53*760c253cSXin Li        buildbot_img = buildbot_case.split("/chroot/tmp")[1]
54*760c253cSXin Li
55*760c253cSXin Li        self.assertEqual(
56*760c253cSXin Li            ParseChromeosImage(buildbot_case), ("R02-1.0", buildbot_img)
57*760c253cSXin Li        )
58*760c253cSXin Li        self.assertEqual(
59*760c253cSXin Li            ParseChromeosImage(os.path.dirname(buildbot_case)),
60*760c253cSXin Li            ("", os.path.dirname(buildbot_img)),
61*760c253cSXin Li        )
62*760c253cSXin Li
63*760c253cSXin Li        # Ensure we do something reasonable when giving paths that don't quite
64*760c253cSXin Li        # match the expected pattern.
65*760c253cSXin Li        fun_case = "/chromiumos_test_image.bin"
66*760c253cSXin Li        self.assertEqual(ParseChromeosImage(fun_case), ("", fun_case))
67*760c253cSXin Li
68*760c253cSXin Li        fun_case2 = "chromiumos_test_image.bin"
69*760c253cSXin Li        self.assertEqual(ParseChromeosImage(fun_case2), ("", fun_case2))
70*760c253cSXin Li
71*760c253cSXin Li
72*760c253cSXin Li# There are many ways for this to be done better, but the linter complains
73*760c253cSXin Li# about all of them (that I can think of, at least).
74*760c253cSXin Li_fake_path_number = [0]
75*760c253cSXin Li
76*760c253cSXin Li
77*760c253cSXin Lidef FakePath(ext):
78*760c253cSXin Li    """Makes a unique path that shouldn't exist on the host system.
79*760c253cSXin Li
80*760c253cSXin Li    Each call returns a different path, so if said path finds its way into an
81*760c253cSXin Li    error message, it may be easier to track it to its source.
82*760c253cSXin Li    """
83*760c253cSXin Li    _fake_path_number[0] += 1
84*760c253cSXin Li    prefix = "/tmp/should/not/exist/%d/" % (_fake_path_number[0],)
85*760c253cSXin Li    return os.path.join(prefix, ext)
86*760c253cSXin Li
87*760c253cSXin Li
88*760c253cSXin Lidef MakeMockExperiment(compiler="gcc"):
89*760c253cSXin Li    """Mocks an experiment using the given compiler."""
90*760c253cSXin Li    mock_experiment_file = io.StringIO(
91*760c253cSXin Li        """
92*760c253cSXin Li      board: x86-alex
93*760c253cSXin Li      remote: 127.0.0.1
94*760c253cSXin Li      locks_dir: /tmp
95*760c253cSXin Li      perf_args: record -a -e cycles
96*760c253cSXin Li      benchmark: PageCycler {
97*760c253cSXin Li        iterations: 3
98*760c253cSXin Li      }
99*760c253cSXin Li
100*760c253cSXin Li      image1 {
101*760c253cSXin Li        chromeos_image: %s
102*760c253cSXin Li      }
103*760c253cSXin Li
104*760c253cSXin Li      image2 {
105*760c253cSXin Li        remote: 127.0.0.2
106*760c253cSXin Li        chromeos_image: %s
107*760c253cSXin Li      }
108*760c253cSXin Li      """
109*760c253cSXin Li        % (FakePath("cros_image1.bin"), FakePath("cros_image2.bin"))
110*760c253cSXin Li    )
111*760c253cSXin Li    efile = ExperimentFile(mock_experiment_file)
112*760c253cSXin Li    experiment = ExperimentFactory().GetExperiment(
113*760c253cSXin Li        efile, FakePath("working_directory"), FakePath("log_dir")
114*760c253cSXin Li    )
115*760c253cSXin Li    for label in experiment.labels:
116*760c253cSXin Li        label.compiler = compiler
117*760c253cSXin Li    return experiment
118*760c253cSXin Li
119*760c253cSXin Li
120*760c253cSXin Lidef _InjectSuccesses(experiment, how_many, keyvals, for_benchmark=0):
121*760c253cSXin Li    """Injects successful experiment runs (for each label) into the experiment."""
122*760c253cSXin Li    # Defensive copy of keyvals, so if it's modified, we'll know.
123*760c253cSXin Li    keyvals = dict(keyvals)
124*760c253cSXin Li    num_configs = len(experiment.benchmarks) * len(experiment.labels)
125*760c253cSXin Li    num_runs = len(experiment.benchmark_runs) // num_configs
126*760c253cSXin Li
127*760c253cSXin Li    # TODO(gbiv): Centralize the mocking of these, maybe? (It's also done in
128*760c253cSXin Li    # benchmark_run_unittest)
129*760c253cSXin Li    bench = experiment.benchmarks[for_benchmark]
130*760c253cSXin Li    cache_conditions = []
131*760c253cSXin Li    log_level = "average"
132*760c253cSXin Li    share_cache = ""
133*760c253cSXin Li    locks_dir = ""
134*760c253cSXin Li    log = logger.GetLogger()
135*760c253cSXin Li    machine_manager = MockMachineManager(
136*760c253cSXin Li        FakePath("chromeos_root"), 0, log_level, locks_dir
137*760c253cSXin Li    )
138*760c253cSXin Li    machine_manager.AddMachine("testing_machine")
139*760c253cSXin Li    machine = next(
140*760c253cSXin Li        m for m in machine_manager.GetMachines() if m.name == "testing_machine"
141*760c253cSXin Li    )
142*760c253cSXin Li
143*760c253cSXin Li    def MakeSuccessfulRun(n, label):
144*760c253cSXin Li        run = MockBenchmarkRun(
145*760c253cSXin Li            "mock_success%d" % (n,),
146*760c253cSXin Li            bench,
147*760c253cSXin Li            label,
148*760c253cSXin Li            1 + n + num_runs,
149*760c253cSXin Li            cache_conditions,
150*760c253cSXin Li            machine_manager,
151*760c253cSXin Li            log,
152*760c253cSXin Li            log_level,
153*760c253cSXin Li            share_cache,
154*760c253cSXin Li            {},
155*760c253cSXin Li        )
156*760c253cSXin Li        mock_result = MockResult(log, label, log_level, machine)
157*760c253cSXin Li        mock_result.keyvals = keyvals
158*760c253cSXin Li        run.result = mock_result
159*760c253cSXin Li        return run
160*760c253cSXin Li
161*760c253cSXin Li    for label in experiment.labels:
162*760c253cSXin Li        experiment.benchmark_runs.extend(
163*760c253cSXin Li            MakeSuccessfulRun(n, label) for n in range(how_many)
164*760c253cSXin Li        )
165*760c253cSXin Li    return experiment
166*760c253cSXin Li
167*760c253cSXin Li
168*760c253cSXin Liclass TextResultsReportTest(unittest.TestCase):
169*760c253cSXin Li    """Tests that the output of a text report contains the things we pass in.
170*760c253cSXin Li
171*760c253cSXin Li    At the moment, this doesn't care deeply about the format in which said
172*760c253cSXin Li    things are displayed. It just cares that they're present.
173*760c253cSXin Li    """
174*760c253cSXin Li
175*760c253cSXin Li    def _checkReport(self, mock_getcooldown, email):
176*760c253cSXin Li        num_success = 2
177*760c253cSXin Li        success_keyvals = {"retval": 0, "machine": "some bot", "a_float": 3.96}
178*760c253cSXin Li        experiment = _InjectSuccesses(
179*760c253cSXin Li            MakeMockExperiment(), num_success, success_keyvals
180*760c253cSXin Li        )
181*760c253cSXin Li        SECONDS_IN_MIN = 60
182*760c253cSXin Li        mock_getcooldown.return_value = {
183*760c253cSXin Li            experiment.remote[0]: 12 * SECONDS_IN_MIN,
184*760c253cSXin Li            experiment.remote[1]: 8 * SECONDS_IN_MIN,
185*760c253cSXin Li        }
186*760c253cSXin Li
187*760c253cSXin Li        text_report = TextResultsReport.FromExperiment(
188*760c253cSXin Li            experiment, email=email
189*760c253cSXin Li        ).GetReport()
190*760c253cSXin Li        self.assertIn(str(success_keyvals["a_float"]), text_report)
191*760c253cSXin Li        self.assertIn(success_keyvals["machine"], text_report)
192*760c253cSXin Li        self.assertIn(MockCrosMachine.CPUINFO_STRING, text_report)
193*760c253cSXin Li        self.assertIn("\nDuration\n", text_report)
194*760c253cSXin Li        self.assertIn("Total experiment time:\n", text_report)
195*760c253cSXin Li        self.assertIn("Cooldown wait time:\n", text_report)
196*760c253cSXin Li        self.assertIn(
197*760c253cSXin Li            "DUT %s: %d min" % (experiment.remote[0], 12), text_report
198*760c253cSXin Li        )
199*760c253cSXin Li        self.assertIn("DUT %s: %d min" % (experiment.remote[1], 8), text_report)
200*760c253cSXin Li        return text_report
201*760c253cSXin Li
202*760c253cSXin Li    @mock.patch.object(TextResultsReport, "GetTotalWaitCooldownTime")
203*760c253cSXin Li    def testOutput(self, mock_getcooldown):
204*760c253cSXin Li        email_report = self._checkReport(mock_getcooldown, email=True)
205*760c253cSXin Li        text_report = self._checkReport(mock_getcooldown, email=False)
206*760c253cSXin Li
207*760c253cSXin Li        # Ensure that the reports somehow different. Otherwise, having the
208*760c253cSXin Li        # distinction is useless.
209*760c253cSXin Li        self.assertNotEqual(email_report, text_report)
210*760c253cSXin Li
211*760c253cSXin Li    def test_get_totalwait_cooldowntime(self):
212*760c253cSXin Li        experiment = MakeMockExperiment()
213*760c253cSXin Li        cros_machines = experiment.machine_manager.GetMachines()
214*760c253cSXin Li        cros_machines[0].AddCooldownWaitTime(120)
215*760c253cSXin Li        cros_machines[1].AddCooldownWaitTime(240)
216*760c253cSXin Li        text_results = TextResultsReport.FromExperiment(experiment, email=False)
217*760c253cSXin Li        total = text_results.GetTotalWaitCooldownTime()
218*760c253cSXin Li        self.assertEqual(total[experiment.remote[0]], 120)
219*760c253cSXin Li        self.assertEqual(total[experiment.remote[1]], 240)
220*760c253cSXin Li
221*760c253cSXin Li
222*760c253cSXin Liclass HTMLResultsReportTest(unittest.TestCase):
223*760c253cSXin Li    """Tests that the output of a HTML report contains the things we pass in.
224*760c253cSXin Li
225*760c253cSXin Li    At the moment, this doesn't care deeply about the format in which said
226*760c253cSXin Li    things are displayed. It just cares that they're present.
227*760c253cSXin Li    """
228*760c253cSXin Li
229*760c253cSXin Li    _TestOutput = collections.namedtuple(
230*760c253cSXin Li        "TestOutput",
231*760c253cSXin Li        [
232*760c253cSXin Li            "summary_table",
233*760c253cSXin Li            "perf_html",
234*760c253cSXin Li            "chart_js",
235*760c253cSXin Li            "charts",
236*760c253cSXin Li            "full_table",
237*760c253cSXin Li            "experiment_file",
238*760c253cSXin Li        ],
239*760c253cSXin Li    )
240*760c253cSXin Li
241*760c253cSXin Li    @staticmethod
242*760c253cSXin Li    def _GetTestOutput(
243*760c253cSXin Li        perf_table,
244*760c253cSXin Li        chart_js,
245*760c253cSXin Li        summary_table,
246*760c253cSXin Li        print_table,
247*760c253cSXin Li        chart_divs,
248*760c253cSXin Li        full_table,
249*760c253cSXin Li        experiment_file,
250*760c253cSXin Li    ):
251*760c253cSXin Li        # N.B. Currently we don't check chart_js; it's just passed through because
252*760c253cSXin Li        # cros lint complains otherwise.
253*760c253cSXin Li        summary_table = print_table(summary_table, "HTML")
254*760c253cSXin Li        perf_html = print_table(perf_table, "HTML")
255*760c253cSXin Li        full_table = print_table(full_table, "HTML")
256*760c253cSXin Li        return HTMLResultsReportTest._TestOutput(
257*760c253cSXin Li            summary_table=summary_table,
258*760c253cSXin Li            perf_html=perf_html,
259*760c253cSXin Li            chart_js=chart_js,
260*760c253cSXin Li            charts=chart_divs,
261*760c253cSXin Li            full_table=full_table,
262*760c253cSXin Li            experiment_file=experiment_file,
263*760c253cSXin Li        )
264*760c253cSXin Li
265*760c253cSXin Li    def _GetOutput(self, experiment=None, benchmark_results=None):
266*760c253cSXin Li        with mock.patch("results_report_templates.GenerateHTMLPage") as standin:
267*760c253cSXin Li            if experiment is not None:
268*760c253cSXin Li                HTMLResultsReport.FromExperiment(experiment).GetReport()
269*760c253cSXin Li            else:
270*760c253cSXin Li                HTMLResultsReport(benchmark_results).GetReport()
271*760c253cSXin Li            mod_mock = standin
272*760c253cSXin Li        self.assertEqual(mod_mock.call_count, 1)
273*760c253cSXin Li        # call_args[0] is positional args, call_args[1] is kwargs.
274*760c253cSXin Li        self.assertEqual(mod_mock.call_args[0], tuple())
275*760c253cSXin Li        fmt_args = mod_mock.call_args[1]
276*760c253cSXin Li        return self._GetTestOutput(**fmt_args)
277*760c253cSXin Li
278*760c253cSXin Li    def testNoSuccessOutput(self):
279*760c253cSXin Li        output = self._GetOutput(MakeMockExperiment())
280*760c253cSXin Li        self.assertIn("no result", output.summary_table)
281*760c253cSXin Li        self.assertIn("no result", output.full_table)
282*760c253cSXin Li        self.assertEqual(output.charts, "")
283*760c253cSXin Li        self.assertNotEqual(output.experiment_file, "")
284*760c253cSXin Li
285*760c253cSXin Li    def testSuccessfulOutput(self):
286*760c253cSXin Li        num_success = 2
287*760c253cSXin Li        success_keyvals = {"retval": 0, "a_float": 3.96}
288*760c253cSXin Li        output = self._GetOutput(
289*760c253cSXin Li            _InjectSuccesses(MakeMockExperiment(), num_success, success_keyvals)
290*760c253cSXin Li        )
291*760c253cSXin Li
292*760c253cSXin Li        self.assertNotIn("no result", output.summary_table)
293*760c253cSXin Li        # self.assertIn(success_keyvals['machine'], output.summary_table)
294*760c253cSXin Li        self.assertIn("a_float", output.summary_table)
295*760c253cSXin Li        self.assertIn(str(success_keyvals["a_float"]), output.summary_table)
296*760c253cSXin Li        self.assertIn("a_float", output.full_table)
297*760c253cSXin Li        # The _ in a_float is filtered out when we're generating HTML.
298*760c253cSXin Li        self.assertIn("afloat", output.charts)
299*760c253cSXin Li        # And make sure we have our experiment file...
300*760c253cSXin Li        self.assertNotEqual(output.experiment_file, "")
301*760c253cSXin Li
302*760c253cSXin Li    def testBenchmarkResultFailure(self):
303*760c253cSXin Li        labels = ["label1"]
304*760c253cSXin Li        benchmark_names_and_iterations = [("bench1", 1)]
305*760c253cSXin Li        benchmark_keyvals = {"bench1": [[]]}
306*760c253cSXin Li        results = BenchmarkResults(
307*760c253cSXin Li            labels, benchmark_names_and_iterations, benchmark_keyvals
308*760c253cSXin Li        )
309*760c253cSXin Li        output = self._GetOutput(benchmark_results=results)
310*760c253cSXin Li        self.assertIn("no result", output.summary_table)
311*760c253cSXin Li        self.assertEqual(output.charts, "")
312*760c253cSXin Li        self.assertEqual(output.experiment_file, "")
313*760c253cSXin Li
314*760c253cSXin Li    def testBenchmarkResultSuccess(self):
315*760c253cSXin Li        labels = ["label1"]
316*760c253cSXin Li        benchmark_names_and_iterations = [("bench1", 1)]
317*760c253cSXin Li        benchmark_keyvals = {"bench1": [[{"retval": 1, "foo": 2.0}]]}
318*760c253cSXin Li        results = BenchmarkResults(
319*760c253cSXin Li            labels, benchmark_names_and_iterations, benchmark_keyvals
320*760c253cSXin Li        )
321*760c253cSXin Li        output = self._GetOutput(benchmark_results=results)
322*760c253cSXin Li        self.assertNotIn("no result", output.summary_table)
323*760c253cSXin Li        self.assertIn("bench1", output.summary_table)
324*760c253cSXin Li        self.assertIn("bench1", output.full_table)
325*760c253cSXin Li        self.assertNotEqual(output.charts, "")
326*760c253cSXin Li        self.assertEqual(output.experiment_file, "")
327*760c253cSXin Li
328*760c253cSXin Li
329*760c253cSXin Liclass JSONResultsReportTest(unittest.TestCase):
330*760c253cSXin Li    """Tests JSONResultsReport."""
331*760c253cSXin Li
332*760c253cSXin Li    REQUIRED_REPORT_KEYS = ("date", "time", "label", "test_name", "pass")
333*760c253cSXin Li    EXPERIMENT_REPORT_KEYS = (
334*760c253cSXin Li        "board",
335*760c253cSXin Li        "chromeos_image",
336*760c253cSXin Li        "chromeos_version",
337*760c253cSXin Li        "chrome_version",
338*760c253cSXin Li        "compiler",
339*760c253cSXin Li    )
340*760c253cSXin Li
341*760c253cSXin Li    @staticmethod
342*760c253cSXin Li    def _GetRequiredKeys(is_experiment):
343*760c253cSXin Li        required_keys = JSONResultsReportTest.REQUIRED_REPORT_KEYS
344*760c253cSXin Li        if is_experiment:
345*760c253cSXin Li            required_keys += JSONResultsReportTest.EXPERIMENT_REPORT_KEYS
346*760c253cSXin Li        return required_keys
347*760c253cSXin Li
348*760c253cSXin Li    def _CheckRequiredKeys(self, test_output, is_experiment):
349*760c253cSXin Li        required_keys = self._GetRequiredKeys(is_experiment)
350*760c253cSXin Li        for output in test_output:
351*760c253cSXin Li            for key in required_keys:
352*760c253cSXin Li                self.assertIn(key, output)
353*760c253cSXin Li
354*760c253cSXin Li    def testAllFailedJSONReportOutput(self):
355*760c253cSXin Li        experiment = MakeMockExperiment()
356*760c253cSXin Li        results = JSONResultsReport.FromExperiment(experiment).GetReportObject()
357*760c253cSXin Li        self._CheckRequiredKeys(results, is_experiment=True)
358*760c253cSXin Li        # Nothing succeeded; we don't send anything more than what's required.
359*760c253cSXin Li        required_keys = self._GetRequiredKeys(is_experiment=True)
360*760c253cSXin Li        for result in results:
361*760c253cSXin Li            self.assertCountEqual(result.keys(), required_keys)
362*760c253cSXin Li
363*760c253cSXin Li    def testJSONReportOutputWithSuccesses(self):
364*760c253cSXin Li        success_keyvals = {
365*760c253cSXin Li            "retval": 0,
366*760c253cSXin Li            "a_float": "2.3",
367*760c253cSXin Li            "many_floats": [["1.0", "2.0"], ["3.0"]],
368*760c253cSXin Li            "machine": "i'm a pirate",
369*760c253cSXin Li        }
370*760c253cSXin Li
371*760c253cSXin Li        # 2 is arbitrary.
372*760c253cSXin Li        num_success = 2
373*760c253cSXin Li        experiment = _InjectSuccesses(
374*760c253cSXin Li            MakeMockExperiment(), num_success, success_keyvals
375*760c253cSXin Li        )
376*760c253cSXin Li        results = JSONResultsReport.FromExperiment(experiment).GetReportObject()
377*760c253cSXin Li        self._CheckRequiredKeys(results, is_experiment=True)
378*760c253cSXin Li
379*760c253cSXin Li        num_passes = num_success * len(experiment.labels)
380*760c253cSXin Li        non_failures = [r for r in results if r["pass"]]
381*760c253cSXin Li        self.assertEqual(num_passes, len(non_failures))
382*760c253cSXin Li
383*760c253cSXin Li        # TODO(gbiv): ...Is the 3.0 *actually* meant to be dropped?
384*760c253cSXin Li        expected_detailed = {"a_float": 2.3, "many_floats": [1.0, 2.0]}
385*760c253cSXin Li        for pass_ in non_failures:
386*760c253cSXin Li            self.assertIn("detailed_results", pass_)
387*760c253cSXin Li            self.assertDictEqual(expected_detailed, pass_["detailed_results"])
388*760c253cSXin Li            self.assertIn("machine", pass_)
389*760c253cSXin Li            self.assertEqual(success_keyvals["machine"], pass_["machine"])
390*760c253cSXin Li
391*760c253cSXin Li    def testFailedJSONReportOutputWithoutExperiment(self):
392*760c253cSXin Li        labels = ["label1"]
393*760c253cSXin Li        # yapf:disable
394*760c253cSXin Li        benchmark_names_and_iterations = [
395*760c253cSXin Li            ("bench1", 1),
396*760c253cSXin Li            ("bench2", 2),
397*760c253cSXin Li            ("bench3", 1),
398*760c253cSXin Li            ("bench4", 0),
399*760c253cSXin Li        ]
400*760c253cSXin Li        # yapf:enable
401*760c253cSXin Li
402*760c253cSXin Li        benchmark_keyvals = {
403*760c253cSXin Li            "bench1": [[{"retval": 1, "foo": 2.0}]],
404*760c253cSXin Li            "bench2": [[{"retval": 1, "foo": 4.0}, {"retval": -1, "bar": 999}]],
405*760c253cSXin Li            # lack of retval is considered a failure.
406*760c253cSXin Li            "bench3": [[{}]],
407*760c253cSXin Li            "bench4": [[]],
408*760c253cSXin Li        }
409*760c253cSXin Li        bench_results = BenchmarkResults(
410*760c253cSXin Li            labels, benchmark_names_and_iterations, benchmark_keyvals
411*760c253cSXin Li        )
412*760c253cSXin Li        results = JSONResultsReport(bench_results).GetReportObject()
413*760c253cSXin Li        self._CheckRequiredKeys(results, is_experiment=False)
414*760c253cSXin Li        self.assertFalse(any(r["pass"] for r in results))
415*760c253cSXin Li
416*760c253cSXin Li    def testJSONGetReportObeysJSONSettings(self):
417*760c253cSXin Li        labels = ["label1"]
418*760c253cSXin Li        benchmark_names_and_iterations = [("bench1", 1)]
419*760c253cSXin Li        # These can be anything, really. So long as they're distinctive.
420*760c253cSXin Li        separators = (",\t\n\t", ":\t\n\t")
421*760c253cSXin Li        benchmark_keyvals = {"bench1": [[{"retval": 0, "foo": 2.0}]]}
422*760c253cSXin Li        bench_results = BenchmarkResults(
423*760c253cSXin Li            labels, benchmark_names_and_iterations, benchmark_keyvals
424*760c253cSXin Li        )
425*760c253cSXin Li        reporter = JSONResultsReport(
426*760c253cSXin Li            bench_results, json_args={"separators": separators}
427*760c253cSXin Li        )
428*760c253cSXin Li        result_str = reporter.GetReport()
429*760c253cSXin Li        self.assertIn(separators[0], result_str)
430*760c253cSXin Li        self.assertIn(separators[1], result_str)
431*760c253cSXin Li
432*760c253cSXin Li    def testSuccessfulJSONReportOutputWithoutExperiment(self):
433*760c253cSXin Li        labels = ["label1"]
434*760c253cSXin Li        benchmark_names_and_iterations = [("bench1", 1), ("bench2", 2)]
435*760c253cSXin Li        benchmark_keyvals = {
436*760c253cSXin Li            "bench1": [[{"retval": 0, "foo": 2.0}]],
437*760c253cSXin Li            "bench2": [[{"retval": 0, "foo": 4.0}, {"retval": 0, "bar": 999}]],
438*760c253cSXin Li        }
439*760c253cSXin Li        bench_results = BenchmarkResults(
440*760c253cSXin Li            labels, benchmark_names_and_iterations, benchmark_keyvals
441*760c253cSXin Li        )
442*760c253cSXin Li        results = JSONResultsReport(bench_results).GetReportObject()
443*760c253cSXin Li        self._CheckRequiredKeys(results, is_experiment=False)
444*760c253cSXin Li        self.assertTrue(all(r["pass"] for r in results))
445*760c253cSXin Li        # Enforce that the results have *some* deterministic order.
446*760c253cSXin Li        keyfn = lambda r: (
447*760c253cSXin Li            r["test_name"],
448*760c253cSXin Li            r["detailed_results"].get("foo", 5.0),
449*760c253cSXin Li        )
450*760c253cSXin Li        sorted_results = sorted(results, key=keyfn)
451*760c253cSXin Li        detailed_results = [r["detailed_results"] for r in sorted_results]
452*760c253cSXin Li        bench1, bench2_foo, bench2_bar = detailed_results
453*760c253cSXin Li        self.assertEqual(bench1["foo"], 2.0)
454*760c253cSXin Li        self.assertEqual(bench2_foo["foo"], 4.0)
455*760c253cSXin Li        self.assertEqual(bench2_bar["bar"], 999)
456*760c253cSXin Li        self.assertNotIn("bar", bench1)
457*760c253cSXin Li        self.assertNotIn("bar", bench2_foo)
458*760c253cSXin Li        self.assertNotIn("foo", bench2_bar)
459*760c253cSXin Li
460*760c253cSXin Li
461*760c253cSXin Liclass PerfReportParserTest(unittest.TestCase):
462*760c253cSXin Li    """Tests for the perf report parser in results_report."""
463*760c253cSXin Li
464*760c253cSXin Li    @staticmethod
465*760c253cSXin Li    def _ReadRealPerfReport():
466*760c253cSXin Li        my_dir = os.path.dirname(os.path.realpath(__file__))
467*760c253cSXin Li        with open(os.path.join(my_dir, "perf_files/perf.data.report.0")) as f:
468*760c253cSXin Li            return f.read()
469*760c253cSXin Li
470*760c253cSXin Li    def testParserParsesRealWorldPerfReport(self):
471*760c253cSXin Li        report = ParseStandardPerfReport(self._ReadRealPerfReport())
472*760c253cSXin Li        self.assertCountEqual(["cycles", "instructions"], list(report.keys()))
473*760c253cSXin Li
474*760c253cSXin Li        # Arbitrarily selected known percentages from the perf report.
475*760c253cSXin Li        known_cycles_percentages = {
476*760c253cSXin Li            "0xffffffffa4a1f1c9": 0.66,
477*760c253cSXin Li            "0x0000115bb7ba9b54": 0.47,
478*760c253cSXin Li            "0x0000000000082e08": 0.00,
479*760c253cSXin Li            "0xffffffffa4a13e63": 0.00,
480*760c253cSXin Li        }
481*760c253cSXin Li        report_cycles = report["cycles"]
482*760c253cSXin Li        self.assertEqual(len(report_cycles), 214)
483*760c253cSXin Li        for k, v in known_cycles_percentages.items():
484*760c253cSXin Li            self.assertIn(k, report_cycles)
485*760c253cSXin Li            self.assertEqual(v, report_cycles[k])
486*760c253cSXin Li
487*760c253cSXin Li        known_instrunctions_percentages = {
488*760c253cSXin Li            "0x0000115bb6c35d7a": 1.65,
489*760c253cSXin Li            "0x0000115bb7ba9b54": 0.67,
490*760c253cSXin Li            "0x0000000000024f56": 0.00,
491*760c253cSXin Li            "0xffffffffa4a0ee03": 0.00,
492*760c253cSXin Li        }
493*760c253cSXin Li        report_instructions = report["instructions"]
494*760c253cSXin Li        self.assertEqual(len(report_instructions), 492)
495*760c253cSXin Li        for k, v in known_instrunctions_percentages.items():
496*760c253cSXin Li            self.assertIn(k, report_instructions)
497*760c253cSXin Li            self.assertEqual(v, report_instructions[k])
498*760c253cSXin Li
499*760c253cSXin Li
500*760c253cSXin Liif __name__ == "__main__":
501*760c253cSXin Li    test_flag.SetTestMode(True)
502*760c253cSXin Li    unittest.main()
503