def __init__(self, arguments):
        self._arguments = arguments
        self._settings = self._arguments.get_settings("test_runner")
        self._failed = False

        self._import_manager = Import_Manager()
        self._preimported_modules = [
            "core.Import_Manager", "settings", "settings.Settings",
            "settings.Arguments"
        ]

        self._loader = unittest.TestLoader()
        self._loader.testMethodPrefix = self._settings.get(
            "test_method_prefix")

        if self._settings.get("coverage"):
            # Only consider our own module so that we exclude system and site
            # packages, and exclude the test bench, test runner and tests
            # themselves from code coverage.
            path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
            include_path = "{}/*".format(path)
            excluded_patterns = ["test.py", "bench/*", "tests/*"]
            excluded_paths = [
                "{}/{}".format(path, pattern) for pattern in excluded_patterns
            ]
            self._statement_coverage = coverage.Coverage(include=include_path,
                                                         omit=excluded_paths)

            self._method_coverage = Method_Coverage(self._arguments,
                                                    self._import_manager)
        else:
            self._statement_coverage = None
            self._method_coverage = None
    def __init__(self, arguments):
        self._arguments = arguments
        self._settings = self._arguments.get_settings("test_runner")
        self._failed = False

        self._import_manager = Import_Manager()
        self._preimported_modules = [
            "core.Import_Manager", "settings", "settings.Settings",
            "settings.Arguments"
        ]

        self._loader = unittest.TestLoader()
        self._loader.testMethodPrefix = self._settings.get("test_method_prefix")

        if self._settings.get("coverage"):
            # Only consider our own module so that we exclude system and site 
            # packages, and exclude the test bench, test runner and tests 
            # themselves from code coverage.
            path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
            include_path = "{}/*".format(path)
            excluded_patterns = ["test.py", "bench/*", "tests/*"]
            excluded_paths = [
                "{}/{}".format(path, pattern) for pattern in excluded_patterns
            ]
            self._statement_coverage = coverage.Coverage(include=include_path,
                                                         omit=excluded_paths)

            self._method_coverage = Method_Coverage(self._arguments,
                                                    self._import_manager)
        else:
            self._statement_coverage = None
            self._method_coverage = None
class Test_Run(object):
    """
    Test runner class.

    This class provides the means for running unit tests, code coverage, style
    checks and other steps that can be taken before, during and after the tests.
    """
    def __init__(self, arguments):
        self._arguments = arguments
        self._settings = self._arguments.get_settings("test_runner")
        self._failed = False

        self._import_manager = Import_Manager()
        self._preimported_modules = [
            "core.Import_Manager", "settings", "settings.Settings",
            "settings.Arguments"
        ]

        self._loader = unittest.TestLoader()
        self._loader.testMethodPrefix = self._settings.get(
            "test_method_prefix")

        if self._settings.get("coverage"):
            # Only consider our own module so that we exclude system and site
            # packages, and exclude the test bench, test runner and tests
            # themselves from code coverage.
            path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
            include_path = "{}/*".format(path)
            excluded_patterns = ["test.py", "bench/*", "tests/*"]
            excluded_paths = [
                "{}/{}".format(path, pattern) for pattern in excluded_patterns
            ]
            self._statement_coverage = coverage.Coverage(include=include_path,
                                                         omit=excluded_paths)

            self._method_coverage = Method_Coverage(self._arguments,
                                                    self._import_manager)
        else:
            self._statement_coverage = None
            self._method_coverage = None

    def is_passed(self):
        """
        Check whether all the test run parts succeeded.
        """

        return not self._failed

    def execute_unit_tests(self):
        """
        Execute the unit tests.
        """

        # Discard the module cache for the package modules imported in the test
        # runner. This ensures that they are reimported in the tests, which
        # makes the coverage consider start-up calls again.
        for module in self._preimported_modules:
            self._import_manager.unload(module, relative=True)

        if self._statement_coverage is not None:
            # Enable statement coverage around loading and running tests.
            self._statement_coverage.start()

        pattern = self._settings.get("pattern")
        verbosity = self._settings.get("verbosity")

        tests = self._loader.discover("tests",
                                      pattern=pattern,
                                      top_level_dir="..")

        factory = Test_Result_Factory(self._arguments)
        runner = unittest.runner.TextTestRunner(verbosity=verbosity,
                                                resultclass=factory)

        result = runner.run(tests)

        if self._statement_coverage is not None:
            self._statement_coverage.stop()

        # Reload the Import_Manager so that the Method_Coverage can safely use
        # it again to load modules.
        self._import_manager.reload_unloaded("core.Import_Manager")
        if self._method_coverage is not None:
            self._method_coverage.run(tests)

        if not result.wasSuccessful():
            self._failed = True

    def execute_statement_coverage_report(self):
        """
        Create a statement coverage report if coverage is enabled. We
        automatically disable coverage if we do not run all tests or a test has
        failed. The test run also fails if the statement coverage is not 100%.

        This method returns the report text if coverage is enabled, otherwise
        it returns `None`.
        """

        if self._statement_coverage is None:
            return None

        if self._failed or not self._settings.is_default("pattern"):
            return None

        report = StringIO()
        percentage = self._statement_coverage.report(file=report,
                                                     show_missing=True,
                                                     skip_covered=True)

        if percentage < 100:
            self._failed = True

        return report.getvalue()

    def execute_method_coverage_report(self):
        """
        Create a method coverage report if coverage is enabled. We automatically
        disable coverage if we do not run all tests or a test has failed.
        The test run also fails if the method coverage is not 100%.

        This method returns the report text if coverage is enabled, otherwise
        it returns `None`.
        """

        if self._method_coverage is None:
            return None

        if self._failed or not self._settings.is_default("pattern"):
            return None

        output, percentage = self._method_coverage.get_results()

        if percentage < 100:
            self._failed = True

        return output

    def _get_travis_environment(self, name):
        """
        Retrieve the string value of an environment variable with a name that
        starts with `TRAVIS_`, followed by the given `name`.

        If the environment variable is not found, an empty string is returned.
        """

        environment_variable = "TRAVIS_{}".format(name)
        if environment_variable not in os.environ:
            return ""

        return os.environ[environment_variable]

    def _get_commit_range(self):
        """
        Retrieve a Git commit range specification that includes the current
        branch, pull request or pushed commits.

        If no such range can be found, this returns an empty string.
        """

        # Check the commit range determined by Travis.
        commit_range = self._get_travis_environment("COMMIT_RANGE")
        if not commit_range:
            # For branches with only one commit, there may be no commit range.
            # Try to find the one commit.
            return self._get_travis_environment("COMMIT")

        # Determine the commit range of the current branch.
        range_parts = commit_range.split('.')
        first_commit = range_parts[0]
        latest_commit = range_parts[-1]
        pull_request = self._get_travis_environment("PULL_REQUEST")
        base_commit = ""

        if pull_request == "false":
            branch = self._get_travis_environment("BRANCH")
            default_branch = self._settings.get("default_branch")
            if branch != default_branch:
                # Retrieve the FETCH_HEAD of the default branch, since Travis
                # has a partial clone that does not contain all branch heads.
                check_call(["git", "fetch", "origin", default_branch])
                base_commit = "FETCH_HEAD"
        elif pull_request:
            base_commit = self._get_travis_environment("BRANCH")

        if base_commit:
            # Find commit hash of the earliest boundary point, which should be
            # the fork point of the current branch, i.e. where the commits
            # diverge from master ignoring merges from master. This could also
            # be found using `git merge-base --fork-point`, but this is not
            # supported on Git 1.8. There are more contrived solutions at
            # http://stackoverflow.com/q/1527234 but this single call works
            # good enough.
            commits = check_output([
                "git", "rev-list", "--boundary",
                "{}...{}".format(base_commit, latest_commit)
            ]).splitlines()

            fork_commits = [
                commit[1:] for commit in commits if commit.startswith('-')
            ]
            if fork_commits:
                first_commit = fork_commits[-1]

        return "{}..{}".format(first_commit, latest_commit)

    def get_changed_files(self):
        """
        Retrieve the files that were changed in a commit range.

        The Git commit range is retrieved from the environment variable
        `TRAVIS_COMMIT_RANGE`, which is set by Travis CI when the tests are run.
        The commit range is altered to contain all commits from the current
        branch stated in the `TRAVIS_BRANCH` environment variable, but only if
        the branch is not the default and the `TRAVIS_PULL_REQUEST` environment
        variable has the value "false" denoting that it is not a PR build.

        Only files that were changed and not deleted in those commits are
        included in the returned list. The commit range is also returned.
        """

        commit_range = self._get_commit_range()
        if not commit_range:
            # We can only determine the commit range on Travis.
            return [], ""

        # Retrieve all files that were changed in a commit. This excludes
        # deleted files which no longer exist at this point. Based on
        # a solution at http://stackoverflow.com/q/424071
        output = check_output([
            "git", "diff-tree", "--no-commit-id", "--name-only",
            "--diff-filter=ACMRTUXB", "-r", commit_range
        ])

        return output.splitlines(), commit_range

    def execute_pylint(self, files):
        """
        Execute pylint on a given list of files.

        Only Python files are included in the lint check.
        """

        # Filter list of file names to only include Python files.
        files = [filename for filename in files if filename.endswith('.py')]
        # There might not be any Python files left after filtering the list, so
        # do not run pylint if this is the case.
        if not files:
            return

        try:
            pylint.lint.Run(["--disable=duplicate-code", "--reports=n"] +
                            files)
        except SystemExit as e:
            if e.code != 0:
                self._failed = True

    def read_logs_directory(self):
        """
        Read all logs in the logs directory.

        Returns the concatenared file contents of all the logs.
        """

        files = glob.glob("logs/*.log")
        log_contents = ""
        for file in files:
            with open(file) as log_file:
                contents = log_file.read().rstrip("\n")
                if contents != "":
                    log_contents += contents + "\n"
                    self._failed = True

        return log_contents

    def clear_logs_directory(self):
        """
        Clear all log files the logs directory.
        """

        files = glob.glob("logs/*.log")
        for file in files:
            os.remove(file)
class Test_Run(object):
    """
    Test runner class.

    This class provides the means for running unit tests, code coverage, style
    checks and other steps that can be taken before, during and after the tests.
    """

    def __init__(self, arguments):
        self._arguments = arguments
        self._settings = self._arguments.get_settings("test_runner")
        self._failed = False

        self._import_manager = Import_Manager()
        self._preimported_modules = [
            "core.Import_Manager", "settings", "settings.Settings",
            "settings.Arguments"
        ]

        self._loader = unittest.TestLoader()
        self._loader.testMethodPrefix = self._settings.get("test_method_prefix")

        if self._settings.get("coverage"):
            # Only consider our own module so that we exclude system and site 
            # packages, and exclude the test bench, test runner and tests 
            # themselves from code coverage.
            path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
            include_path = "{}/*".format(path)
            excluded_patterns = ["test.py", "bench/*", "tests/*"]
            excluded_paths = [
                "{}/{}".format(path, pattern) for pattern in excluded_patterns
            ]
            self._statement_coverage = coverage.Coverage(include=include_path,
                                                         omit=excluded_paths)

            self._method_coverage = Method_Coverage(self._arguments,
                                                    self._import_manager)
        else:
            self._statement_coverage = None
            self._method_coverage = None

    def is_passed(self):
        """
        Check whether all the test run parts succeeded.
        """

        return not self._failed

    def execute_unit_tests(self):
        """
        Execute the unit tests.
        """

        # Discard the module cache for the package modules imported in the test 
        # runner. This ensures that they are reimported in the tests, which 
        # makes the coverage consider start-up calls again.
        for module in self._preimported_modules:
            self._import_manager.unload(module, relative=True)

        if self._statement_coverage is not None:
            # Enable statement coverage around loading and running tests.
            self._statement_coverage.start()

        pattern = self._settings.get("pattern")
        verbosity = self._settings.get("verbosity")

        tests = self._loader.discover("tests", pattern=pattern,
                                      top_level_dir="..")

        factory = Test_Result_Factory(self._arguments)
        runner = unittest.runner.TextTestRunner(verbosity=verbosity,
                                                resultclass=factory)

        result = runner.run(tests)

        if self._statement_coverage is not None:
            self._statement_coverage.stop()

        # Reload the Import_Manager so that the Method_Coverage can safely use 
        # it again to load modules.
        self._import_manager.reload_unloaded("core.Import_Manager")
        if self._method_coverage is not None:
            self._method_coverage.run(tests)

        if not result.wasSuccessful():
            self._failed = True

    def execute_statement_coverage_report(self):
        """
        Create a statement coverage report if coverage is enabled. We
        automatically disable coverage if we do not run all tests or a test has
        failed. The test run also fails if the statement coverage is not 100%.

        This method returns the report text if coverage is enabled, otherwise
        it returns `None`.
        """

        if self._statement_coverage is None:
            return None

        if self._failed or not self._settings.is_default("pattern"):
            return None

        report = StringIO()
        percentage = self._statement_coverage.report(file=report,
                                                     show_missing=True,
                                                     skip_covered=True)

        if percentage < 100:
            self._failed = True

        return report.getvalue()

    def execute_method_coverage_report(self):
        """
        Create a method coverage report if coverage is enabled. We automatically
        disable coverage if we do not run all tests or a test has failed.
        The test run also fails if the method coverage is not 100%.

        This method returns the report text if coverage is enabled, otherwise
        it returns `None`.
        """

        if self._method_coverage is None:
            return None

        if self._failed or not self._settings.is_default("pattern"):
            return None

        output, percentage = self._method_coverage.get_results()

        if percentage < 100:
            self._failed = True

        return output

    def _get_travis_environment(self, name):
        """
        Retrieve the string value of an environment variable with a name that
        starts with `TRAVIS_`, followed by the given `name`.

        If the environment variable is not found, an empty string is returned.
        """

        environment_variable = "TRAVIS_{}".format(name)
        if environment_variable not in os.environ:
            return ""

        return os.environ[environment_variable]

    def _get_commit_range(self):
        """
        Retrieve a Git commit range specification that includes the current
        branch, pull request or pushed commits.

        If no such range can be found, this returns an empty string.
        """

        # Check the commit range determined by Travis.
        commit_range = self._get_travis_environment("COMMIT_RANGE")
        if not commit_range:
            # For branches with only one commit, there may be no commit range. 
            # Try to find the one commit.
            return self._get_travis_environment("COMMIT")

        # Determine the commit range of the current branch.
        range_parts = commit_range.split('.')
        first_commit = range_parts[0]
        latest_commit = range_parts[-1]
        pull_request = self._get_travis_environment("PULL_REQUEST")
        base_commit = ""

        if pull_request == "false":
            branch = self._get_travis_environment("BRANCH")
            default_branch = self._settings.get("default_branch")
            if branch != default_branch:
                # Retrieve the FETCH_HEAD of the default branch, since Travis 
                # has a partial clone that does not contain all branch heads.
                check_call(["git", "fetch", "origin", default_branch])
                base_commit = "FETCH_HEAD"
        elif pull_request:
            base_commit = self._get_travis_environment("BRANCH")

        if base_commit:
            # Find commit hash of the earliest boundary point, which should be 
            # the fork point of the current branch, i.e. where the commits 
            # diverge from master ignoring merges from master. This could also 
            # be found using `git merge-base --fork-point`, but this is not 
            # supported on Git 1.8. There are more contrived solutions at 
            # http://stackoverflow.com/q/1527234 but this single call works 
            # good enough.
            commits = check_output([
                "git", "rev-list", "--boundary",
                "{}...{}".format(base_commit, latest_commit)
            ]).splitlines()

            fork_commits = [
                commit[1:] for commit in commits if commit.startswith('-')
            ]
            if fork_commits:
                first_commit = fork_commits[-1]

        return "{}..{}".format(first_commit, latest_commit)

    def get_changed_files(self):
        """
        Retrieve the files that were changed in a commit range.

        The Git commit range is retrieved from the environment variable
        `TRAVIS_COMMIT_RANGE`, which is set by Travis CI when the tests are run.
        The commit range is altered to contain all commits from the current
        branch stated in the `TRAVIS_BRANCH` environment variable, but only if
        the branch is not the default and the `TRAVIS_PULL_REQUEST` environment
        variable has the value "false" denoting that it is not a PR build.

        Only files that were changed and not deleted in those commits are
        included in the returned list. The commit range is also returned.
        """

        commit_range = self._get_commit_range()
        if not commit_range:
            # We can only determine the commit range on Travis.
            return [], ""

        # Retrieve all files that were changed in a commit. This excludes 
        # deleted files which no longer exist at this point. Based on 
        # a solution at http://stackoverflow.com/q/424071
        output = check_output([
            "git", "diff-tree", "--no-commit-id", "--name-only",
            "--diff-filter=ACMRTUXB", "-r", commit_range
        ])

        return output.splitlines(), commit_range

    def execute_pylint(self, files):
        """
        Execute pylint on a given list of files.

        Only Python files are included in the lint check.
        """

        # Filter list of file names to only include Python files.
        files = [filename for filename in files if filename.endswith('.py')]
        # There might not be any Python files left after filtering the list, so 
        # do not run pylint if this is the case.
        if not files:
            return

        try:
            pylint.lint.Run(["--disable=duplicate-code", "--reports=n"] + files)
        except SystemExit as e:
            if e.code != 0:
                self._failed = True

    def read_logs_directory(self):
        """
        Read all logs in the logs directory.

        Returns the concatenared file contents of all the logs.
        """

        files = glob.glob("logs/*.log")
        log_contents = ""
        for file in files:
            with open(file) as log_file:
                contents = log_file.read().rstrip("\n")
                if contents != "":
                    log_contents += contents + "\n"
                    self._failed = True

        return log_contents

    def clear_logs_directory(self):
        """
        Clear all log files the logs directory.
        """

        files = glob.glob("logs/*.log")
        for file in files:
            os.remove(file)