66#
77# The SCons test suite consists of:
88#
9- # - unit tests - included in *Tests.py files from src / dir
9+ # - unit tests - included in *Tests.py files from SCons / dir
1010# - end-to-end tests - these are *.py files in test/ directory that
1111# require custom SCons framework from testing/
1212#
1313# This script adds SCons/ and testing/ directories to PYTHONPATH,
1414# performs test discovery and processes them according to options.
15- #
16- # With -p (--package) option, script tests specified package from
17- # build directory and sets PYTHONPATH to reference modules unpacked
18- # during build process for testing purposes (build/test-*).
1915
2016"""
2117Options:
2218 -a --all Run all tests.
2319 -b --baseline BASE Run test scripts against baseline BASE.
24- --builddir DIR Directory in which packages were built.
2520 -d --debug Run test scripts under the Python debugger.
2621 -D --devmode Run tests in Python's development mode (3.7+ only)
22+ --e2e-only Run only the end-to-end tests
2723 -e --external Run the script in external mode (for external Tools)
2824 -f --file FILE Only run tests listed in FILE.
2925 -j --jobs JOBS Run tests in JOBS parallel jobs.
3733 chars! You might run into some deadlocks else.
3834 -o --output FILE Save the output from a test run to the log file.
3935 -P PYTHON Use the specified Python interpreter.
40- -p --package PACKAGE Test against the specified PACKAGE:
41- deb Debian
42- local-tar-gz .tar.gz standalone package
43- local-zip .zip standalone package
44- rpm Red Hat
45- src-tar-gz .tar.gz source package
46- src-zip .zip source package
47- tar-gz .tar.gz distribution
48- zip .zip distribution
4936 --passed Summarize which tests passed.
5037 -q --quiet Don't print the test being executed.
5138 --quit-on-failure Quit on any test failure.
5441 and a percentage value, based on the total and
5542 current number of tests.
5643 -t --time Print test execution time.
44+ --unit-only Run only the unit tests
5745 -v VERSION Specify the SCons version.
5846 --verbose=LEVEL Set verbose level:
5947 1 = print executed commands,
8775cwd = os .getcwd ()
8876
8977baseline = None
90- builddir = os .path .join (cwd , 'build' )
9178external = 0
9279devmode = False
9380debug = ''
9481execute_tests = True
9582jobs = 1
9683list_only = False
9784printcommand = True
98- package = None
9985print_passed_summary = False
10086scons = None
10187scons_exec = False
10288testlistfile = None
10389version = ''
10490print_times = False
10591python = None
106- sp = []
10792print_progress = True
10893catch_output = False
10994suppress_output = False
11095allow_pipe_files = True
11196quit_on_failure = False
11297excludelistfile = None
98+ e2e_only = unit_only = False
11399
114100script = sys .argv [0 ].split ("/" )[- 1 ]
115101usagestr = """\
@@ -153,7 +139,6 @@ def _process_short_opts(self, rargs, values):
153139 "b:dDef:hj:klnP:p:qsv:Xx:t" ,
154140 [
155141 "baseline=" ,
156- "builddir=" ,
157142 "debug" ,
158143 "devmode" ,
159144 "external" ,
@@ -164,7 +149,6 @@ def _process_short_opts(self, rargs, values):
164149 "list" ,
165150 "no-exec" ,
166151 "nopipefiles" ,
167- "package=" ,
168152 "passed" ,
169153 "python=" ,
170154 "quiet" ,
@@ -175,16 +159,14 @@ def _process_short_opts(self, rargs, values):
175159 "exec=" ,
176160 "verbose=" ,
177161 "exclude-list=" ,
162+ "e2e-only" ,
163+ "unit-only" ,
178164 ],
179165)
180166
181167for o , a in opts :
182168 if o in ['-b' , '--baseline' ]:
183169 baseline = a
184- elif o in ['--builddir' ]:
185- builddir = a
186- if not os .path .isabs (builddir ):
187- builddir = os .path .normpath (os .path .join (cwd , builddir ))
188170 elif o in ['-d' , '--debug' ]:
189171 for d in sys .path :
190172 pdb = os .path .join (d , 'pdb.py' )
@@ -215,8 +197,6 @@ def _process_short_opts(self, rargs, values):
215197 execute_tests = False
216198 elif o in ['--nopipefiles' ]:
217199 allow_pipe_files = False
218- elif o in ['-p' , '--package' ]:
219- package = a
220200 elif o in ['--passed' ]:
221201 print_passed_summary = True
222202 elif o in ['-P' , '--python' ]:
@@ -241,9 +221,13 @@ def _process_short_opts(self, rargs, values):
241221 scons = a
242222 elif o in ['--exclude-list' ]:
243223 excludelistfile = a
224+ elif o in ['--e2e-only' ]:
225+ e2e_only = True
226+ elif o in ['--unit-only' ]:
227+ unit_only = True
244228
245229
246- class Unbuffered () :
230+ class Unbuffered :
247231 """ class to arrange for stdout/stderr to be unbuffered """
248232 def __init__ (self , file ):
249233 self .file = file
@@ -261,7 +245,7 @@ def __getattr__(self, attr):
261245
262246if options .output :
263247 logfile = open (options .output , 'w' )
264- class Tee () :
248+ class Tee :
265249 def __init__ (self , openfile , stream ):
266250 self .file = openfile
267251 self .stream = stream
@@ -296,13 +280,9 @@ def whereis(file):
296280 return f
297281 return None
298282
299- sp .append (builddir )
300- sp .append (cwd )
301283
302- #
303284_ws = re .compile (r'\s' )
304285
305-
306286def escape (s ):
307287 if _ws .search (s ):
308288 s = '"' + s + '"'
@@ -414,7 +394,7 @@ class PopenExecutor(RuntestBase):
414394 by calling subprocess.run (behind the covers uses Popen.
415395 Very similar to SystemExecutor, but uses command_str
416396 instead of command_args, and doesn't allow for not catching
417- the output.
397+ the output) .
418398 """
419399 # For an explanation of the following 'if ... else'
420400 # and the 'allow_pipe_files' option, please check out the
@@ -553,13 +533,38 @@ def footer(self, f):
553533
554534
555535# ---[ test discovery ]------------------------------------
536+ # This section figures which tests to run.
537+ #
538+ # The initial testlist is made by reading from the testlistfile,
539+ # if supplied, or by looking at the test arguments, if supplied,
540+ # or by looking for all test files if the "all" argument is supplied.
541+ # One of the three is required.
542+ #
543+ # Each test path, whichever of the three sources it comes from,
544+ # specifies either a test file or a directory to search for
545+ # SCons tests. SCons code layout assumes that any file under the 'SCons'
546+ # subdirectory that ends with 'Tests.py' is a unit test, and any Python
547+ # script (*.py) under the 'test' subdirectory is an end-to-end test.
548+ # We need to track these because they are invoked differently.
549+ # find_unit_tests and find_e2e_tests are used for this searching.
550+ #
551+ # Note that there are some tests under 'SCons' that *begin* with
552+ # 'test_', but they're packaging and installation tests, not
553+ # functional tests, so we don't execute them by default. (They can
554+ # still be executed by hand, though).
555+ #
556+ # Test exclusions, if specified, are then applied.
556557
557- tests = []
558- excludetests = []
559558unittests = []
560559endtests = []
561560
562561
562+ def scanlist (testlist ):
563+ """Process a testlist file"""
564+ tests = [t .strip () for t in testlist if not t .startswith ('#' )]
565+ return [t for t in tests if t ]
566+
567+
563568def find_unit_tests (directory ):
564569 """ Look for unit tests """
565570 result = []
@@ -583,7 +588,7 @@ def find_e2e_tests(directory):
583588 continue
584589 try :
585590 with open (os .path .join (dirpath , ".exclude_tests" )) as f :
586- excludes = [ e . split ( "#" , 1 )[ 0 ]. strip () for e in f . readlines ()]
591+ excludes = scanlist ( f )
587592 except EnvironmentError :
588593 excludes = []
589594 for fname in filenames :
@@ -592,27 +597,12 @@ def find_e2e_tests(directory):
592597 return sorted (result )
593598
594599
600+ # initial selection:
595601if testlistfile :
596602 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 ]
601- tests = [x for x in tests if x ]
603+ tests = scanlist (f )
602604else :
603605 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-
616606 if options .all :
617607 testpaths = ['SCons' , 'test' ]
618608 elif args :
@@ -622,47 +612,44 @@ def find_e2e_tests(directory):
622612 # Clean up path so it can match startswith's below
623613 # sys.stderr.write("Changed:%s->"%tp)
624614 # remove leading ./ or .\
625- if tp [ 0 ] == '.' and tp [1 ] in (os .sep , os .altsep ):
615+ if tp . startswith ( '.' ) and tp [1 ] in (os .sep , os .altsep ):
626616 tp = tp [2 :]
627617 # tp = os.path.normpath(tp)
628618 # sys.stderr.write('->%s<-'%tp)
629619 # sys.stderr.write("to:%s\n"%tp)
630620 for path in glob .glob (tp ):
631621 if os .path .isdir (path ):
632- if path .startswith ('SCons' ) or path .startswith ('testing' ):
633- for p in find_unit_tests (path ):
634- unittests .append (p )
622+ if path .startswith (('SCons' , 'testing' )):
623+ unittests .extend (find_unit_tests (path ))
635624 elif path .startswith ('test' ):
636- for p in find_e2e_tests (path ):
637- endtests .append (p )
625+ endtests .extend (find_e2e_tests (path ))
638626 else :
639627 if path .endswith ("Tests.py" ):
640628 unittests .append (path )
641- else :
629+ elif path . endswith ( ".py" ) :
642630 endtests .append (path )
631+ tests = unittests + endtests
643632
644- tests .extend (unittests )
645- tests .extend (endtests )
646- tests .sort ()
633+ # Remove exclusions:
634+ if e2e_only :
635+ tests = [t for t in tests if not t .endswith ("Tests.py" )]
636+ if unit_only :
637+ tests = [t for t in tests if t .endswith ("Tests.py" )]
638+ if excludelistfile :
639+ with open (excludelistfile , 'r' ) as f :
640+ excludetests = scanlist (f )
641+ tests = [t for t in tests if t not in excludetests ]
647642
648643if not tests :
649644 sys .stderr .write (usagestr + """
650- runtest.py: No tests were found.
651- Tests can be specified on the command line, read from file
652- with -f option, or discovered with -a to run all tests.
645+ runtest: no tests were found.
646+ Tests can be specified on the command line, read from a file with
647+ the -f/--file option, or discovered with -a/--all to run all tests.
653648""" )
654649 sys .exit (1 )
655650
656- if excludelistfile :
657- with open (excludelistfile , 'r' ) as f :
658- excludetests = f .readlines ()
659- excludetests = [x for x in excludetests if x [0 ] != '#' ]
660- excludetests = [x [:- 1 ] for x in excludetests ]
661- excludetests = [x .strip () for x in excludetests ]
662- excludetests = [x for x in excludetests if x ]
663651
664652# ---[ test processing ]-----------------------------------
665- tests = [t for t in tests if t not in excludetests ]
666653tests = [Test (t , n + 1 ) for n , t in enumerate (tests )]
667654
668655if list_only :
@@ -697,10 +684,9 @@ def log_result(t, io_lock=None):
697684 we need to lock access to the log to avoid interleaving. The same
698685 would apply if output was a file.
699686
700- :param t: a completed testcase
701- :type t: Test
702- :param io_lock:
703- :type io_lock: threading.Lock
687+ Args:
688+ t (Test): (completed) testcase instance
689+ io_lock (threading.lock): (optional) lock to use
704690 """
705691
706692 # there is no lock in single-job run, which includes
@@ -727,6 +713,18 @@ def log_result(t, io_lock=None):
727713
728714
729715def run_test (t , io_lock = None , run_async = True ):
716+ """ Run a testcase.
717+
718+ Builds the command line to give to execute().
719+ Also the best place to record some information that will be
720+ used in output, which in some conditions is printed here.
721+
722+ Args:
723+ t (Test): testcase instance
724+ io_lock (threading.Lock): (optional) lock to use
725+ run_async (bool): whether to run asynchronously
726+ """
727+
730728 t .headline = ""
731729 command_args = []
732730 if debug :
@@ -738,7 +736,7 @@ def run_test(t, io_lock=None, run_async=True):
738736 # For example --runner TestUnit.TAPTestRunner
739737 command_args .append ('--runner ' + options .runner )
740738 t .command_args = [escape (python )] + command_args
741- t .command_str = " " .join ([ escape ( python )] + command_args )
739+ t .command_str = " " .join (t . command_args )
742740 if printcommand :
743741 if print_progress :
744742 t .headline += "%d/%d (%.2f%s) %s\n " % (
@@ -753,7 +751,7 @@ def run_test(t, io_lock=None, run_async=True):
753751 if not suppress_output and not catch_output :
754752 # defer printing the headline until test is done
755753 sys .stdout .write (t .headline )
756- head , tail = os .path .split (t .abspath )
754+ head , _ = os .path .split (t .abspath )
757755 fixture_dirs = []
758756 if head :
759757 fixture_dirs .append (head )
@@ -775,7 +773,7 @@ class RunTest(threading.Thread):
775773 """
776774 def __init__ (self , queue = None , io_lock = None ,
777775 group = None , target = None , name = None , args = (), kwargs = None ):
778- super (RunTest , self ).__init__ (group = group , target = target , name = name )
776+ super ().__init__ (group = group , target = target , name = name )
779777 self .queue = queue
780778 self .io_lock = io_lock
781779
@@ -856,6 +854,7 @@ def run(self):
856854if fail :
857855 sys .exit (1 )
858856elif no_result :
857+ # if no fails, but skips were found
859858 sys .exit (2 )
860859else :
861860 sys .exit (0 )
0 commit comments