Skip to content

Commit d81dbb0

Browse files
committed
Add selection options to test runner
runtest.py can now take --unit-only and --e2e-only selection options to limit a test run to tests from one or other category. Signed-off-by: Mats Wichmann <[email protected]>
1 parent 29145ce commit d81dbb0

File tree

1 file changed

+56
-28
lines changed

1 file changed

+56
-28
lines changed

runtest.py

Lines changed: 56 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
--builddir DIR Directory in which packages were built.
2525
-d --debug Run test scripts under the Python debugger.
2626
-D --devmode Run tests in Python's development mode (3.7+ only)
27+
--e2e-only Run only the end-to-end tests
2728
-e --external Run the script in external mode (for external Tools)
2829
-f --file FILE Only run tests listed in FILE.
2930
-j --jobs JOBS Run tests in JOBS parallel jobs.
@@ -54,6 +55,7 @@
5455
and a percentage value, based on the total and
5556
current number of tests.
5657
-t --time Print test execution time.
58+
--unit-only Run only the unit tests
5759
-v VERSION Specify the SCons version.
5860
--verbose=LEVEL Set verbose level:
5961
1 = print executed commands,
@@ -110,6 +112,7 @@
110112
allow_pipe_files = True
111113
quit_on_failure = False
112114
excludelistfile = None
115+
e2e_only = unit_only = False
113116

114117
script = sys.argv[0].split("/")[-1]
115118
usagestr = """\
@@ -175,6 +178,8 @@ def _process_short_opts(self, rargs, values):
175178
"exec=",
176179
"verbose=",
177180
"exclude-list=",
181+
"e2e-only",
182+
"unit-only",
178183
],
179184
)
180185

@@ -241,6 +246,10 @@ def _process_short_opts(self, rargs, values):
241246
scons = a
242247
elif o in ['--exclude-list']:
243248
excludelistfile = a
249+
elif o in ['--e2e-only']:
250+
e2e_only = True
251+
elif o in ['--unit-only']:
252+
unit_only = True
244253

245254

246255
class Unbuffered():
@@ -553,6 +562,27 @@ def footer(self, f):
553562

554563

555564
# ---[ test discovery ]------------------------------------
565+
# This section figures which tests to run.
566+
#
567+
# The initial testlist is made by reading from the testlistfile,
568+
# if supplied, or by looking at the test arguments, if supplied,
569+
# or by looking for all test files if the "all" argument is supplied.
570+
# One of the three is required.
571+
#
572+
# Each test path, whichever of the three sources it comes from,
573+
# specifies either a test file or a directory to search for
574+
# SCons tests. SCons code layout assumes that any file under the 'src'
575+
# subdirectory that ends with 'Tests.py' is a unit test, and any Python
576+
# script (*.py) under the 'test' subdirectory is an end-to-end test.
577+
# We need to track these because they are invoked differently.
578+
# find_unit_tests and find_e2e_tests are used for this searching.
579+
#
580+
# Note that there are some tests under 'src' that *begin* with
581+
# 'test_', but they're packaging and installation tests, not
582+
# functional tests, so we don't execute them by default. (They can
583+
# still be executed by hand, though).
584+
#
585+
# Test exclusions, if specified, are then applied.
556586

557587
tests = []
558588
excludetests = []
@@ -594,25 +624,10 @@ def find_e2e_tests(directory):
594624

595625
if testlistfile:
596626
with open(testlistfile, 'r') as f:
597-
tests = f.readlines()
598-
tests = [x for x in tests if x[0] != '#']
599-
tests = [x[:-1] for x in tests]
600-
tests = [x.strip() for x in tests]
627+
tests = [x[:-1].strip() for x in f if not x.startswith('#')]
601628
tests = [x for x in tests if x]
602629
else:
603630
testpaths = []
604-
605-
# Each test path specifies a test file, or a directory to search for
606-
# SCons tests. SCons code layout assumes that any file under the 'SCons'
607-
# subdirectory that ends with 'Tests.py' is a unit test, and any Python
608-
# script (*.py) under the 'test' subdirectory an end-to-end test.
609-
# We need to track these because they are invoked differently.
610-
#
611-
# Note that there are some tests under 'SCons' that *begin* with
612-
# 'test_', but they're packaging and installation tests, not
613-
# functional tests, so we don't execute them by default. (They can
614-
# still be executed by hand, though).
615-
616631
if options.all:
617632
testpaths = ['SCons', 'test']
618633
elif args:
@@ -622,24 +637,25 @@ def find_e2e_tests(directory):
622637
# Clean up path so it can match startswith's below
623638
# sys.stderr.write("Changed:%s->"%tp)
624639
# remove leading ./ or .\
625-
if tp[0] == '.' and tp[1] in (os.sep, os.altsep):
640+
if tp.startswith('.') and tp[1] in (os.sep, os.altsep):
626641
tp = tp[2:]
627642
# tp = os.path.normpath(tp)
628643
# sys.stderr.write('->%s<-'%tp)
629644
# sys.stderr.write("to:%s\n"%tp)
630645
for path in glob.glob(tp):
631646
if os.path.isdir(path):
632-
if path.startswith('SCons') or path.startswith('testing'):
647+
if path.startswith(('SCons', 'testing')) and not e2e_only:
633648
for p in find_unit_tests(path):
634649
unittests.append(p)
635-
elif path.startswith('test'):
650+
elif path.startswith('test') and not unit_only:
636651
for p in find_e2e_tests(path):
637652
endtests.append(p)
638653
else:
639-
if path.endswith("Tests.py"):
654+
if path.endswith("Tests.py") and not e2e_only:
640655
unittests.append(path)
641656
else:
642-
endtests.append(path)
657+
if not unit_only:
658+
endtests.append(path)
643659

644660
tests.extend(unittests)
645661
tests.extend(endtests)
@@ -697,10 +713,9 @@ def log_result(t, io_lock=None):
697713
we need to lock access to the log to avoid interleaving. The same
698714
would apply if output was a file.
699715
700-
:param t: a completed testcase
701-
:type t: Test
702-
:param io_lock:
703-
:type io_lock: threading.Lock
716+
Args:
717+
t (Test): (completed) testcase instance
718+
io_lock (threading.lock): (optional) lock to use
704719
"""
705720

706721
# there is no lock in single-job run, which includes
@@ -727,6 +742,18 @@ def log_result(t, io_lock=None):
727742

728743

729744
def run_test(t, io_lock=None, run_async=True):
745+
""" Run a testcase.
746+
747+
Builds the command line to give to execute().
748+
Also the best place to record some information that will be
749+
used in output, which in some conditions is printed here.
750+
751+
Args:
752+
t (Test): testcase instance
753+
io_lock (threading.Lock): (optional) lock to use
754+
run_async (bool): whether to run asynchronously
755+
"""
756+
730757
t.headline = ""
731758
command_args = []
732759
if debug:
@@ -738,7 +765,7 @@ def run_test(t, io_lock=None, run_async=True):
738765
# For example --runner TestUnit.TAPTestRunner
739766
command_args.append('--runner ' + options.runner)
740767
t.command_args = [escape(python)] + command_args
741-
t.command_str = " ".join([escape(python)] + command_args)
768+
t.command_str = " ".join(t.command_args)
742769
if printcommand:
743770
if print_progress:
744771
t.headline += "%d/%d (%.2f%s) %s\n" % (
@@ -753,7 +780,7 @@ def run_test(t, io_lock=None, run_async=True):
753780
if not suppress_output and not catch_output:
754781
# defer printing the headline until test is done
755782
sys.stdout.write(t.headline)
756-
head, tail = os.path.split(t.abspath)
783+
head, _ = os.path.split(t.abspath)
757784
fixture_dirs = []
758785
if head:
759786
fixture_dirs.append(head)
@@ -775,7 +802,7 @@ class RunTest(threading.Thread):
775802
"""
776803
def __init__(self, queue=None, io_lock=None,
777804
group=None, target=None, name=None, args=(), kwargs=None):
778-
super(RunTest, self).__init__(group=group, target=target, name=name)
805+
super().__init__(group=group, target=target, name=name)
779806
self.queue = queue
780807
self.io_lock = io_lock
781808

@@ -856,6 +883,7 @@ def run(self):
856883
if fail:
857884
sys.exit(1)
858885
elif no_result:
886+
# if no fails, but skips were found
859887
sys.exit(2)
860888
else:
861889
sys.exit(0)

0 commit comments

Comments
 (0)