#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright 2016 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Test for generate_report.py."""


import copy
import json
import unittest
import unittest.mock as mock

import generate_report
import results_report
import test_flag


# pylint: disable=deprecated-module
try:
    from StringIO import StringIO  # for Python 2
except ImportError:
    from io import StringIO  # for Python 3


class _ContextualStringIO(StringIO):
    """StringIO that can be used in `with` statements."""

    def __init__(self, *args):
        StringIO.__init__(self, *args)

    def __enter__(self):
        return self

    def __exit__(self, _type, _value, _traceback):
        pass


class GenerateReportTests(unittest.TestCase):
    """Tests for generate_report.py."""

    def testCountBenchmarks(self):
        runs = {
            "foo": [[{}, {}, {}], [{}, {}, {}, {}]],
            "bar": [],
            "baz": [[], [{}], [{}, {}, {}]],
        }
        results = generate_report.CountBenchmarks(runs)
        expected_results = [("foo", 4), ("bar", 0), ("baz", 3)]
        self.assertCountEqual(expected_results, results)

    def testCutResultsInPlace(self):
        bench_data = {
            "foo": [[{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 2.5, "c": 1}]],
            "bar": [[{"d": 11, "e": 12, "f": 13}]],
            "baz": [[{"g": 12, "h": 13}]],
            "qux": [[{"i": 11}]],
        }
        original_bench_data = copy.deepcopy(bench_data)

        max_keys = 2
        results = generate_report.CutResultsInPlace(
            bench_data, max_keys=max_keys, complain_on_update=False
        )
        # Cuts should be in-place.
        self.assertIs(results, bench_data)
        self.assertCountEqual(
            list(original_bench_data.keys()), list(bench_data.keys())
        )
        for bench_name, original_runs in original_bench_data.items():
            bench_runs = bench_data[bench_name]
            self.assertEqual(len(original_runs), len(bench_runs))
            # Order of these sub-lists shouldn't have changed.
            for original_list, new_list in zip(original_runs, bench_runs):
                self.assertEqual(len(original_list), len(new_list))
                for original_keyvals, sub_keyvals in zip(
                    original_list, new_list
                ):
                    # sub_keyvals must be a subset of original_keyvals
                    self.assertDictContainsSubset(sub_keyvals, original_keyvals)

    def testCutResultsInPlaceLeavesRetval(self):
        bench_data = {
            "foo": [[{"retval": 0, "a": 1}]],
            "bar": [[{"retval": 1}]],
            "baz": [[{"RETVAL": 1}]],
        }
        results = generate_report.CutResultsInPlace(
            bench_data, max_keys=0, complain_on_update=False
        )
        # Just reach into results assuming we know it otherwise outputs things in
        # the expected way. If it doesn't, testCutResultsInPlace should give an
        # indication as to what, exactly, is broken.
        self.assertEqual(list(results["foo"][0][0].items()), [("retval", 0)])
        self.assertEqual(list(results["bar"][0][0].items()), [("retval", 1)])
        self.assertEqual(list(results["baz"][0][0].items()), [])

    def _RunMainWithInput(self, args, input_obj):
        assert "-i" not in args
        args += ["-i", "-"]
        input_buf = _ContextualStringIO(json.dumps(input_obj))
        with mock.patch(
            "generate_report.PickInputFile", return_value=input_buf
        ) as patched_pick:
            result = generate_report.Main(args)
            patched_pick.assert_called_once_with("-")
            return result

    @mock.patch("generate_report.RunActions")
    def testMain(self, mock_run_actions):
        # Email is left out because it's a bit more difficult to test, and it'll be
        # mildly obvious if it's failing.
        args = ["--json", "--html", "--text"]
        return_code = self._RunMainWithInput(
            args, {"platforms": [], "data": {}}
        )
        self.assertEqual(0, return_code)
        self.assertEqual(mock_run_actions.call_count, 1)
        ctors = [ctor for ctor, _ in mock_run_actions.call_args[0][0]]
        self.assertEqual(
            ctors,
            [
                results_report.JSONResultsReport,
                results_report.TextResultsReport,
                results_report.HTMLResultsReport,
            ],
        )

    @mock.patch("generate_report.RunActions")
    def testMainSelectsHTMLIfNoReportsGiven(self, mock_run_actions):
        args = []
        return_code = self._RunMainWithInput(
            args, {"platforms": [], "data": {}}
        )
        self.assertEqual(0, return_code)
        self.assertEqual(mock_run_actions.call_count, 1)
        ctors = [ctor for ctor, _ in mock_run_actions.call_args[0][0]]
        self.assertEqual(ctors, [results_report.HTMLResultsReport])

    # We only mock print_exc so we don't have exception info printed to stdout.
    @mock.patch("generate_report.WriteFile", side_effect=ValueError("Oh noo"))
    @mock.patch("traceback.print_exc")
    def testRunActionsRunsAllActionsRegardlessOfExceptions(
        self, mock_print_exc, mock_write_file
    ):
        actions = [
            (None, "json"),
            (None, "html"),
            (None, "text"),
            (None, "email"),
        ]
        output_prefix = "-"
        ok = generate_report.RunActions(
            actions, {}, output_prefix, overwrite=False, verbose=False
        )
        self.assertFalse(ok)
        self.assertEqual(mock_write_file.call_count, len(actions))
        self.assertEqual(mock_print_exc.call_count, len(actions))

    @mock.patch("generate_report.WriteFile")
    def testRunActionsReturnsTrueIfAllActionsSucceed(self, mock_write_file):
        actions = [(None, "json"), (None, "html"), (None, "text")]
        output_prefix = "-"
        ok = generate_report.RunActions(
            actions, {}, output_prefix, overwrite=False, verbose=False
        )
        self.assertEqual(mock_write_file.call_count, len(actions))
        self.assertTrue(ok)


if __name__ == "__main__":
    test_flag.SetTestMode(True)
    unittest.main()
