nose_suite.py 6.53 KB
Newer Older
1 2 3 4 5
"""
Classes used for defining and running nose test suites
"""
import os
from pavelib.utils.test import utils as test_utils
6
from pavelib.utils.test.suites.suite import TestSuite
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
from pavelib.utils.envs import Env

__test__ = False  # do not collect


class NoseTestSuite(TestSuite):
    """
    A subclass of TestSuite with extra methods that are specific
    to nose tests
    """
    def __init__(self, *args, **kwargs):
        super(NoseTestSuite, self).__init__(*args, **kwargs)
        self.failed_only = kwargs.get('failed_only', False)
        self.fail_fast = kwargs.get('fail_fast', False)
        self.run_under_coverage = kwargs.get('with_coverage', True)
        self.report_dir = Env.REPORT_DIR / self.root
23 24 25 26 27 28 29 30 31

        # If set, put reports for run in "unique" directories.
        # The main purpose of this is to ensure that the reports can be 'slurped'
        # in the main jenkins flow job without overwriting the reports from other
        # build steps. For local development/testing, this shouldn't be needed.
        if os.environ.get("SHARD", None):
            shard_str = "shard_{}".format(os.environ.get("SHARD"))
            self.report_dir = self.report_dir / shard_str

32 33
        self.test_id_dir = Env.TEST_DIR / self.root
        self.test_ids = self.test_id_dir / 'noseids'
34 35
        self.extra_args = kwargs.get('extra_args', '')
        self.cov_args = kwargs.get('cov_args', '')
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60

    def __enter__(self):
        super(NoseTestSuite, self).__enter__()
        self.report_dir.makedirs_p()
        self.test_id_dir.makedirs_p()

    def __exit__(self, exc_type, exc_value, traceback):
        """
        Cleans mongo afer the tests run.
        """
        super(NoseTestSuite, self).__exit__(exc_type, exc_value, traceback)
        test_utils.clean_mongo()

    def _under_coverage_cmd(self, cmd):
        """
        If self.run_under_coverage is True, it returns the arg 'cmd'
        altered to be run under coverage. It returns the command
        unaltered otherwise.
        """
        if self.run_under_coverage:
            cmd0, cmd_rest = cmd.split(" ", 1)
            # We use "python -m coverage" so that the proper python
            # will run the importable coverage rather than the
            # coverage that OS path finds.

61 62 63
            if not cmd0.endswith('.py'):
                cmd0 = "`which {}`".format(cmd0)

64
            cmd = (
65
                "python -m coverage run {cov_args} --rcfile={rcfile} "
66
                "{cmd0} {cmd_rest}".format(
67
                    cov_args=self.cov_args,
68
                    rcfile=Env.PYTHON_COVERAGERC,
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
                    cmd0=cmd0,
                    cmd_rest=cmd_rest,
                )
            )

        return cmd

    @property
    def test_options_flags(self):
        """
        Takes the test options and returns the appropriate flags
        for the command.
        """
        opts = " "

        # Handle "--failed" as a special case: we want to re-run only
        # the tests that failed within our Django apps
        # This sets the --failed flag for the nosetests command, so this
        # functionality is the same as described in the nose documentation
        if self.failed_only:
            opts += "--failed"

        # This makes it so we use nose's fail-fast feature in two cases.
        # Case 1: --fail_fast is passed as an arg in the paver command
        # Case 2: The environment variable TESTS_FAIL_FAST is set as True
        env_fail_fast_set = (
            'TESTS_FAIL_FAST' in os.environ and os.environ['TEST_FAIL_FAST']
        )

        if self.fail_fast or env_fail_fast_set:
            opts += " --stop"

101 102 103
        if self.pdb:
            opts += " --pdb"

104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
        return opts


class SystemTestSuite(NoseTestSuite):
    """
    TestSuite for lms and cms nosetests
    """
    def __init__(self, *args, **kwargs):
        super(SystemTestSuite, self).__init__(*args, **kwargs)
        self.test_id = kwargs.get('test_id', self._default_test_id)
        self.fasttest = kwargs.get('fasttest', False)

    def __enter__(self):
        super(SystemTestSuite, self).__enter__()

    @property
    def cmd(self):
        cmd = (
122
            './manage.py {system} test --verbosity={verbosity} '
123
            '{test_id} {test_opts} --settings=test {extra} '
124
            '--with-xunit --xunit-file={xunit_report}'.format(
125
                system=self.root,
126
                verbosity=self.verbosity,
127 128
                test_id=self.test_id,
                test_opts=self.test_options_flags,
129
                extra=self.extra_args,
130
                xunit_report=self.report_dir / "nosetests.xml",
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
            )
        )

        return self._under_coverage_cmd(cmd)

    @property
    def _default_test_id(self):
        """
        If no test id is provided, we need to limit the test runner
        to the Djangoapps we want to test.  Otherwise, it will
        run tests on all installed packages. We do this by
        using a default test id.
        """
        # We need to use $DIR/*, rather than just $DIR so that
        # django-nose will import them early in the test process,
        # thereby making sure that we load any django models that are
        # only defined in test files.
148 149 150 151 152
        default_test_id = (
            "{system}/djangoapps/*"
            " common/djangoapps/*"
            " openedx/core/djangoapps/*"
            " openedx/tests/*"
153
            " openedx/core/lib/*"
154 155 156
        )

        if self.root in ('lms', 'cms'):
157
            default_test_id += " {system}/lib/*"
158 159

        if self.root == 'lms':
160
            default_test_id += " {system}/tests.py"
161
            default_test_id += " openedx/core/djangolib"
162

163
        if self.root == 'cms':
164
            default_test_id += " {system}/tests/*"
165

166
        return default_test_id.format(system=self.root)
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181


class LibTestSuite(NoseTestSuite):
    """
    TestSuite for edx-platform/common/lib nosetests
    """
    def __init__(self, *args, **kwargs):
        super(LibTestSuite, self).__init__(*args, **kwargs)
        self.test_id = kwargs.get('test_id', self.root)
        self.xunit_report = self.report_dir / "nosetests.xml"

    @property
    def cmd(self):
        cmd = (
            "nosetests --id-file={test_ids} {test_id} {test_opts} "
182
            "--with-xunit --xunit-file={xunit_report} {extra} "
183
            "--verbosity={verbosity}".format(
184 185 186 187
                test_ids=self.test_ids,
                test_id=self.test_id,
                test_opts=self.test_options_flags,
                xunit_report=self.xunit_report,
188
                verbosity=self.verbosity,
189
                extra=self.extra_args,
190 191 192 193
            )
        )

        return self._under_coverage_cmd(cmd)