# Copyright 2013 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Task unittest.

Part of the Chrome build flags optimization.
"""

__author__ = "yuhenglong@google.com (Yuheng Long)"

import random
import sys
import unittest

import task
from task import Task


# The number of flags be tested.
NUM_FLAGS = 20

# The random build result values used to test get set result method.
RANDOM_BUILD_RESULT = 100

# The random test result values used to test get set result method.
RANDOM_TESTRESULT = 100


class MockFlagSet(object):
    """This class emulates a set of flags.

    It returns the flags and hash value, when the FormattedForUse method and the
    __hash__ method is called, respectively. These values are initialized when the
    MockFlagSet instance is constructed.
    """

    def __init__(self, flags=0, hash_value=-1):
        self._flags = flags
        self._hash_value = hash_value

    def __eq__(self, other):
        assert isinstance(other, MockFlagSet)
        return self._flags == other.FormattedForUse()

    def FormattedForUse(self):
        return self._flags

    def __hash__(self):
        return self._hash_value

    def GetHash(self):
        return self._hash_value


class TaskTest(unittest.TestCase):
    """This class test the Task class."""

    def testEqual(self):
        """Test the equal method of the task.

        Two tasks are equal if and only if their encapsulated flag_sets are equal.
        """

        flags = range(NUM_FLAGS)

        # Two tasks having the same flag set should be equivalent.
        flag_sets = [MockFlagSet(flag) for flag in flags]
        for flag_set in flag_sets:
            assert Task(flag_set) == Task(flag_set)

        # Two tasks having different flag set should be different.
        for flag_set in flag_sets:
            test_task = Task(flag_set)
            other_flag_sets = [
                flags for flags in flag_sets if flags != flag_set
            ]
            for flag_set1 in other_flag_sets:
                assert test_task != Task(flag_set1)

    def testHash(self):
        """Test the hash method of the task.

        Two tasks are equal if and only if their encapsulated flag_sets are equal.
        """

        # Random identifier that is not relevant in this test.
        identifier = random.randint(-sys.maxint - 1, -1)

        flag_sets = [
            MockFlagSet(identifier, value) for value in range(NUM_FLAGS)
        ]
        for flag_set in flag_sets:
            # The hash of a task is the same as the hash of its flag set.
            hash_task = Task(flag_set)
            hash_value = hash(hash_task)
            assert hash_value == flag_set.GetHash()

            # The hash of a task does not change.
            assert hash_value == hash(hash_task)

    def testGetIdentifier(self):
        """Test the get identifier method of the task.

        The get identifier method should returns the flag set in the build stage.
        """

        flag_sets = [MockFlagSet(flag) for flag in range(NUM_FLAGS)]
        for flag_set in flag_sets:
            identifier_task = Task(flag_set)

            identifier = identifier_task.GetIdentifier(task.BUILD_STAGE)

            # The task formats the flag set into a string.
            assert identifier == str(flag_set.FormattedForUse())

    def testGetSetResult(self):
        """Test the get and set result methods of the task.

        The get result method should return the same results as were set.
        """

        flag_sets = [MockFlagSet(flag) for flag in range(NUM_FLAGS)]
        for flag_set in flag_sets:
            result_task = Task(flag_set)

            # The get result method should return the same results as were set, in
            # build stage. Currently, the build result is a 5-element tuple containing
            # the checksum of the result image, the performance cost of the build, the
            # compilation image, the length of the build, and the length of the text
            # section of the build.
            result = tuple(
                [random.randint(0, RANDOM_BUILD_RESULT) for _ in range(5)]
            )
            result_task.SetResult(task.BUILD_STAGE, result)
            assert result == result_task.GetResult(task.BUILD_STAGE)

            # The checksum is the identifier of the test stage.
            identifier = result_task.GetIdentifier(task.TEST_STAGE)
            # The first element of the result tuple is the checksum.
            assert identifier == result[0]

            # The get result method should return the same results as were set, in
            # test stage.
            random_test_result = random.randint(0, RANDOM_TESTRESULT)
            result_task.SetResult(task.TEST_STAGE, random_test_result)
            test_result = result_task.GetResult(task.TEST_STAGE)
            assert test_result == random_test_result

    def testDone(self):
        """Test the done methods of the task.

        The done method should return false is the task has not perform and return
        true after the task is finished.
        """

        flags = range(NUM_FLAGS)

        flag_sets = [MockFlagSet(flag) for flag in flags]
        for flag_set in flag_sets:
            work_task = Task(flag_set)

            # The task has not been compiled nor tested.
            assert not work_task.Done(task.TEST_STAGE)
            assert not work_task.Done(task.BUILD_STAGE)

            # After the task has been compiled, it should indicate finished in BUILD
            # stage.
            result = tuple(
                [random.randint(0, RANDOM_BUILD_RESULT) for _ in range(5)]
            )
            work_task.SetResult(task.BUILD_STAGE, result)
            assert not work_task.Done(task.TEST_STAGE)
            assert work_task.Done(task.BUILD_STAGE)

            # After the task has been tested, it should indicate finished in TEST
            # stage.
            work_task.SetResult(
                task.TEST_STAGE, random.randint(0, RANDOM_TESTRESULT)
            )
            assert work_task.Done(task.TEST_STAGE)
            assert work_task.Done(task.BUILD_STAGE)


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