#!/usr/bin/env python

import subprocess
import sys
from unittest import mock

import mozunit
import pytest
from mozpower import MozPower
from mozpower.mozpower import MissingProcessorInfoError, OsCpuComboMissingError


def test_mozpower_android_init_failure():
    """Tests that the MozPower object fails when the android
    flag is set. Remove this test once android is implemented.
    """
    with pytest.raises(NotImplementedError):
        MozPower(android=True)


def test_mozpower_oscpu_combo_missing_error():
    """Tests that the error OsCpuComboMissingError is raised
    when we can't find a OS, and CPU combination (and, therefore, cannot
    find a power measurer).
    """
    with mock.patch.object(
        MozPower, "_get_os", return_value="Not-An-OS"
    ) as _, mock.patch.object(
        MozPower, "_get_processor_info", return_value="Not-A-Processor"
    ) as _:
        with pytest.raises(OsCpuComboMissingError):
            MozPower()


def test_mozpower_processor_info_missing_error():
    """Tests that the error MissingProcessorInfoError is raised
    when failures occur during processor information parsing.
    """
    # The builtins module name differs between python 2 and 3
    builtins_name = "__builtin__"
    if sys.version_info[0] == 3:
        builtins_name = "builtins"

    def os_side_effect_true(*args, **kwargs):
        """Used as a passing side effect for os.path.exists calls."""
        return True

    def os_side_effect_false(*args, **kwargs):
        """Used as a failing side effect for os.path.exists calls."""
        return False

    def subprocess_side_effect_fail(*args, **kwargs):
        """Used to mock a failure in subprocess.check_output calls."""
        raise subprocess.CalledProcessError(1, "Testing failure")

    # Test failures in macos processor information parsing
    with mock.patch.object(MozPower, "_get_os", return_value="Darwin") as _:

        with mock.patch("os.path.exists") as os_mock:
            os_mock.side_effect = os_side_effect_false

            # Check that we fail properly if the processor
            # information file doesn't exist.
            with pytest.raises(MissingProcessorInfoError):
                MozPower()

            # Check that we fail properly when an error occurs
            # in the subprocess call.
            os_mock.side_effect = os_side_effect_true
            with mock.patch("subprocess.check_output") as subprocess_mock:
                subprocess_mock.side_effect = subprocess_side_effect_fail
                with pytest.raises(MissingProcessorInfoError):
                    MozPower()

    # Test failures in linux processor information parsing
    with mock.patch.object(MozPower, "_get_os", return_value="Linux") as _:

        with mock.patch("os.path.exists") as os_mock:
            os_mock.side_effect = os_side_effect_false

            # Check that we fail properly if the processor
            # information file doesn't exist.
            with pytest.raises(MissingProcessorInfoError):
                MozPower()

            # Check that we fail properly when the model cannot be found
            # with by searching for 'model name'.
            os_mock.side_effect = os_side_effect_true
            with mock.patch(
                "%s.open" % builtins_name, mock.mock_open(read_data="")
            ) as _:
                with pytest.raises(MissingProcessorInfoError):
                    MozPower()


def test_mozpower_oscpu_combo(mozpower_obj):
    """Tests that the correct class is instantiated for a given
    OS and CPU combination (MacIntelPower in this case).
    """
    assert mozpower_obj.measurer.__class__.__name__ == "MacIntelPower"
    assert (
        mozpower_obj.measurer._os == "darwin" and mozpower_obj.measurer._cpu == "intel"
    )


def test_mozpower_measuring(mozpower_obj):
    """Tests that measurers are properly called with each method."""
    with mock.patch(
        "mozpower.macintelpower.MacIntelPower.initialize_power_measurements"
    ) as _, mock.patch(
        "mozpower.macintelpower.MacIntelPower.finalize_power_measurements"
    ) as _, mock.patch(
        "mozpower.macintelpower.MacIntelPower.get_perfherder_data"
    ) as _:
        mozpower_obj.initialize_power_measurements()
        mozpower_obj.measurer.initialize_power_measurements.assert_called()

        mozpower_obj.finalize_power_measurements()
        mozpower_obj.measurer.finalize_power_measurements.assert_called()

        mozpower_obj.get_perfherder_data()
        mozpower_obj.measurer.get_perfherder_data.assert_called()


def test_mozpower_measuring_with_no_measurer(mozpower_obj):
    """Tests that no errors occur when the measurer is None, and the
    initialize, finalize, and get_perfherder_data functions are called.
    """
    with mock.patch(
        "mozpower.macintelpower.MacIntelPower.initialize_power_measurements"
    ) as _, mock.patch(
        "mozpower.macintelpower.MacIntelPower.finalize_power_measurements"
    ) as _, mock.patch(
        "mozpower.macintelpower.MacIntelPower.get_perfherder_data"
    ) as _:
        measurer = mozpower_obj.measurer
        mozpower_obj.measurer = None

        mozpower_obj.initialize_power_measurements()
        assert not measurer.initialize_power_measurements.called

        mozpower_obj.finalize_power_measurements()
        assert not measurer.finalize_power_measurements.called

        mozpower_obj.get_perfherder_data()
        assert not measurer.get_perfherder_data.called

        mozpower_obj.get_full_perfherder_data("mozpower")
        assert not measurer.get_perfherder_data.called


def test_mozpower_get_full_perfherder_data(mozpower_obj):
    """Tests that the full perfherder data blob is properly
    produced given a partial perfherder data blob with correct
    entries.
    """
    partial_perfherder = {
        "utilization": {
            "type": "power",
            "test": "mozpower",
            "unit": "%",
            "values": {"cpu": 50, "gpu": 0},
        },
        "power-usage": {
            "type": "power",
            "test": "mozpower",
            "unit": "mWh",
            "values": {"cpu": 2.0, "dram": 0.1, "gpu": 4.0},
        },
        "frequency-cpu": {
            "type": "power",
            "test": "mozpower",
            "unit": "MHz",
            "values": {
                "cpu-favg": 2.0,
                "cpu-fmax": 5.0,
                "cpu-fmin": 0.0,
            },
        },
        "frequency-gpu": {
            "type": "power",
            "test": "mozpower",
            "unit": "MHz",
            "values": {"gpu-favg": 3.0, "gpu-fmax": 6.0, "gpu-fmin": 0.0},
        },
    }
    utilization_vals = [0, 50]
    power_usage_vals = [2.0, 0.1, 4.0]
    frequency_cpu_vals = [2.0, 5.0, 0.0]
    frequency_gpu_vals = [3.0, 6.0, 0.0]

    with mock.patch("mozpower.macintelpower.MacIntelPower.get_perfherder_data") as gpd:
        gpd.return_value = partial_perfherder

        full_perfherder = mozpower_obj.get_full_perfherder_data("mozpower")
        assert full_perfherder["framework"]["name"] == "mozpower"
        assert len(full_perfherder["suites"]) == 4

        # Check that each of the two suites were created correctly.
        suites = full_perfherder["suites"]
        for suite in suites:
            assert "subtests" in suite

            assert suite["type"] == "power"
            assert suite["alertThreshold"] == 2.0
            assert suite["lowerIsBetter"]

            all_vals = []
            for subtest in suite["subtests"]:
                assert "value" in subtest

                # Check that the subtest names were created correctly
                if "utilization" in suite["name"]:
                    assert "utilization" in subtest["name"]
                elif "power-usage" in suite["name"]:
                    assert "power-usage" in subtest["name"]
                elif "frequency-cpu" in suite["name"]:
                    assert "frequency-cpu" in subtest["name"]
                elif "frequency-gpu" in suite["name"]:
                    assert "frequency-gpu" in subtest["name"]
                else:
                    assert False, "Unknown subtest name %s" % subtest["name"]

                all_vals.append(subtest["value"])

            if "utilization" in suite["name"]:
                assert len(all_vals) == 2
                assert suite["unit"] == "%"
                assert suite["name"] == "mozpower-utilization"
                assert not list(set(all_vals) - set(utilization_vals))
                assert suite["value"] == float(25)
            elif "power-usage" in suite["name"]:
                assert len(all_vals) == 3
                assert suite["unit"] == "mWh"
                assert suite["name"] == "mozpower-power-usage"
                assert not list(set(all_vals) - set(power_usage_vals))
                assert suite["value"] == float(6.1)
            elif "frequency-cpu" in suite["name"]:
                assert len(all_vals) == 3
                assert suite["unit"] == "MHz"
                assert suite["name"] == "mozpower-frequency-cpu"
                assert not list(set(all_vals) - set(frequency_cpu_vals))
                assert suite["value"] == float(2.0)
            elif "frequency-gpu" in suite["name"]:
                assert len(all_vals) == 3
                assert suite["unit"] == "MHz"
                assert suite["name"] == "mozpower-frequency-gpu"
                assert not list(set(all_vals) - set(frequency_gpu_vals))
                assert suite["value"] == float(3.0)
            else:
                assert False, "Unknown suite name %s" % suite["name"]


if __name__ == "__main__":
    mozunit.main()
