mirror of https://github.com/zulip/zulip.git
Add option for re-running failed tests.
This adds the option '--rerun' to the `test-backend` infrastructure. It runs the tests that failed during the last 'test-backend' run. It works by stailing failed test info at var/last_test_failure.json Cleaned up by Umair Khan and Tim Abbott.
This commit is contained in:
parent
79ad174ad3
commit
d54dea819d
|
@ -46,7 +46,9 @@ tests.
|
||||||
Another thing to note is that our tests generally "fail fast," i.e. they
|
Another thing to note is that our tests generally "fail fast," i.e. they
|
||||||
stop at the first sign of trouble. This is generally a good thing for
|
stop at the first sign of trouble. This is generally a good thing for
|
||||||
iterative development, but you can override this behavior with the
|
iterative development, but you can override this behavior with the
|
||||||
`--nonfatal-errors` option.
|
`--nonfatal-errors` option. A useful option to combine with that is
|
||||||
|
the `--rerun` option, which will rerun just the tests that failed in
|
||||||
|
the last test run.
|
||||||
|
|
||||||
## How to write tests.
|
## How to write tests.
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,13 @@
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
from typing import List
|
||||||
import glob
|
import glob
|
||||||
import optparse
|
import optparse
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import ujson
|
||||||
|
|
||||||
# check for the venv
|
# check for the venv
|
||||||
from lib import sanity_check
|
from lib import sanity_check
|
||||||
|
@ -90,6 +92,23 @@ not_yet_fully_covered = {
|
||||||
|
|
||||||
enforce_fully_covered = sorted(target_fully_covered - not_yet_fully_covered)
|
enforce_fully_covered = sorted(target_fully_covered - not_yet_fully_covered)
|
||||||
|
|
||||||
|
FAILED_TEST_PATH = 'var/last_test_failure.json'
|
||||||
|
|
||||||
|
def get_failed_tests():
|
||||||
|
# type: () -> List[str]
|
||||||
|
try:
|
||||||
|
with open(FAILED_TEST_PATH, 'r') as f:
|
||||||
|
return ujson.load(f)
|
||||||
|
except IOError:
|
||||||
|
print("var/last_test_failure.json doesn't exist; running all tests.")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def write_failed_tests(failed_tests):
|
||||||
|
# type: (List[str]) -> None
|
||||||
|
if failed_tests:
|
||||||
|
with open(FAILED_TEST_PATH, 'w') as f:
|
||||||
|
ujson.dump(failed_tests, f)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
TOOLS_DIR = os.path.dirname(os.path.abspath(__file__))
|
TOOLS_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
os.chdir(os.path.dirname(TOOLS_DIR))
|
os.chdir(os.path.dirname(TOOLS_DIR))
|
||||||
|
@ -165,11 +184,27 @@ if __name__ == "__main__":
|
||||||
action="store_true",
|
action="store_true",
|
||||||
default=False,
|
default=False,
|
||||||
help="Run tests in reverse order.")
|
help="Run tests in reverse order.")
|
||||||
|
parser.add_option('--rerun', dest="rerun",
|
||||||
|
action = "store_true",
|
||||||
|
default=False,
|
||||||
|
help=("Run the tests which failed the last time "
|
||||||
|
"test-backend was run. Implies --nonfatal-errors."))
|
||||||
|
|
||||||
(options, args) = parser.parse_args()
|
(options, args) = parser.parse_args()
|
||||||
|
|
||||||
zerver_test_dir = 'zerver/tests/'
|
zerver_test_dir = 'zerver/tests/'
|
||||||
|
|
||||||
|
# While running --rerun, we read var/last_test_failure.json to get
|
||||||
|
# the list of tests that failed on the last run, and then pretend
|
||||||
|
# those tests were passed explicitly. --rerun implies
|
||||||
|
# --nonfatal-errors, so that we don't end up removing tests from
|
||||||
|
# the list that weren't run.
|
||||||
|
if options.rerun:
|
||||||
|
options.fatal_errors = False
|
||||||
|
failed_tests = get_failed_tests()
|
||||||
|
if failed_tests:
|
||||||
|
args = failed_tests
|
||||||
|
|
||||||
# to transform forward slashes '/' present in the argument into dots '.'
|
# to transform forward slashes '/' present in the argument into dots '.'
|
||||||
for suite in args:
|
for suite in args:
|
||||||
args[args.index(suite)] = suite.rstrip('/').replace("/", ".")
|
args[args.index(suite)] = suite.rstrip('/').replace("/", ".")
|
||||||
|
@ -267,7 +302,8 @@ if __name__ == "__main__":
|
||||||
test_runner = TestRunner(failfast=options.fatal_errors, verbosity=2,
|
test_runner = TestRunner(failfast=options.fatal_errors, verbosity=2,
|
||||||
parallel=parallel, reverse=options.reverse,
|
parallel=parallel, reverse=options.reverse,
|
||||||
keepdb=True)
|
keepdb=True)
|
||||||
failures = test_runner.run_tests(suites, full_suite=full_suite)
|
failures, failed_tests = test_runner.run_tests(suites, full_suite=full_suite)
|
||||||
|
write_failed_tests(failed_tests)
|
||||||
|
|
||||||
templates_not_rendered = test_runner.get_shallow_tested_templates()
|
templates_not_rendered = test_runner.get_shallow_tested_templates()
|
||||||
# We only check the templates if all the tests ran and passed
|
# We only check the templates if all the tests ran and passed
|
||||||
|
|
|
@ -167,6 +167,11 @@ class TextTestResult(runner.TextTestResult):
|
||||||
This class has unpythonic function names because base class follows
|
This class has unpythonic function names because base class follows
|
||||||
this style.
|
this style.
|
||||||
"""
|
"""
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
# type: (*Any, **Any) -> None
|
||||||
|
super(TextTestResult, self).__init__(*args, **kwargs)
|
||||||
|
self.failed_tests = [] # type: List[str]
|
||||||
|
|
||||||
def addInfo(self, test, msg):
|
def addInfo(self, test, msg):
|
||||||
# type: (TestCase, Text) -> None
|
# type: (TestCase, Text) -> None
|
||||||
self.stream.write(msg)
|
self.stream.write(msg)
|
||||||
|
@ -193,6 +198,8 @@ class TextTestResult(runner.TextTestResult):
|
||||||
def addFailure(self, *args, **kwargs):
|
def addFailure(self, *args, **kwargs):
|
||||||
# type: (*Any, **Any) -> None
|
# type: (*Any, **Any) -> None
|
||||||
TestResult.addFailure(self, *args, **kwargs)
|
TestResult.addFailure(self, *args, **kwargs)
|
||||||
|
test_name = full_test_name(args[0])
|
||||||
|
self.failed_tests.append(test_name)
|
||||||
|
|
||||||
def addSkip(self, test, reason):
|
def addSkip(self, test, reason):
|
||||||
# type: (TestCase, Text) -> None
|
# type: (TestCase, Text) -> None
|
||||||
|
@ -364,7 +371,7 @@ class Runner(DiscoverRunner):
|
||||||
|
|
||||||
def run_tests(self, test_labels, extra_tests=None,
|
def run_tests(self, test_labels, extra_tests=None,
|
||||||
full_suite=False, **kwargs):
|
full_suite=False, **kwargs):
|
||||||
# type: (List[str], Optional[List[TestCase]], bool, **Any) -> bool
|
# type: (List[str], Optional[List[TestCase]], bool, **Any) -> Tuple[bool, List[str]]
|
||||||
self.setup_test_environment()
|
self.setup_test_environment()
|
||||||
try:
|
try:
|
||||||
suite = self.build_suite(test_labels, extra_tests)
|
suite = self.build_suite(test_labels, extra_tests)
|
||||||
|
@ -387,7 +394,7 @@ class Runner(DiscoverRunner):
|
||||||
failed = self.suite_result(suite, result)
|
failed = self.suite_result(suite, result)
|
||||||
if not failed:
|
if not failed:
|
||||||
write_instrumentation_reports(full_suite=full_suite)
|
write_instrumentation_reports(full_suite=full_suite)
|
||||||
return failed
|
return failed, result.failed_tests
|
||||||
|
|
||||||
def get_test_names(suite):
|
def get_test_names(suite):
|
||||||
# type: (TestSuite) -> List[str]
|
# type: (TestSuite) -> List[str]
|
||||||
|
|
Loading…
Reference in New Issue