Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Run unit tests update #8105

Merged
merged 8 commits into from
Jan 11, 2021
Merged
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
293 changes: 164 additions & 129 deletions unit-tests/run-unit-tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,21 @@
# Copyright(c) 2020 Intel Corporation. All Rights Reserved.

import sys, os, subprocess, locale, re, platform, getopt
from abc import ABC
MMirbach marked this conversation as resolved.
Show resolved Hide resolved

def usage():
ourname = os.path.basename(sys.argv[0])
print( 'Syntax: ' + ourname + ' <dir-or-regex>' )
print( ' If given a directory, run all unit-tests in $dir (in Windows: .../build/Release; in Linux: .../build' )
print( ' Test logs are kept in <dir>/unit-tests/<test-name>.log' )
print( ' If given a regular expression, run all python tests in unit-tests directory that fit <regex>, to stdout' )
print( 'Syntax: ' + ourname + ' [options] [dir]' )
print( ' dir: the directory holding the executable tests to run (default to the build directory')
print( 'Options:' )
print( ' --debug Turn on debugging information' )
print( ' -v, --verbose Errors will dump the log to stdout' )
print( ' -q, --quiet Suppress output; rely on exit status (0=no failures)' )
print( ' -r, --regex run all tests that fit the following regular expression')
sys.exit(2)
def debug(*args):
pass


def stream_has_color( stream ):
if not hasattr(stream, "isatty"):
return False
Expand Down Expand Up @@ -56,7 +55,7 @@ def progress(*args):
def out(*args):
print( *args )
def progress(*args):
print( *args )
print( *args, end='\r' )
MMirbach marked this conversation as resolved.
Show resolved Hide resolved

n_errors = 0
def error(*args):
Expand All @@ -68,15 +67,49 @@ def error(*args):
def info(*args):
out( '-I-', *args)

def filesin( root ):
# Yield all files found in root, using relative names ('root/a' would be yielded as 'a')
for (path,subdirs,leafs) in os.walk( root ):
for leaf in leafs:
# We have to stick to Unix conventions because CMake on Windows is fubar...
yield os.path.relpath( path + '/' + leaf, root ).replace( '\\', '/' )

def find( dir, mask ):
pattern = re.compile( mask )
for leaf in filesin( dir ):
if pattern.search( leaf ):
debug(leaf)
yield leaf

# get os and directories for future use
# NOTE: WSL will read as 'Linux' but the build is Windows-based!
system = platform.system()
if system == 'Linux' and "microsoft" not in platform.uname()[3].lower():
linux = True
else:
linux = False

current_dir = os.path.dirname(os.path.abspath(__file__))
# this script is located in librealsense/unit-tests, so one directory up is the main repository
librealsense = os.path.dirname(current_dir)

# function for checking if a file is an executable

def is_executable(path_to_test):
if linux:
return os.access(path_to_test, os.X_OK)
else:
return path_to_test.endswith('.exe')

# Parse command-line:
try:
opts,args = getopt.getopt( sys.argv[1:], 'hvq',
longopts = [ 'help', 'verbose', 'debug', 'quiet' ])
opts,args = getopt.getopt( sys.argv[1:], 'hvqr',
MMirbach marked this conversation as resolved.
Show resolved Hide resolved
longopts = [ 'help', 'verbose', 'debug', 'quiet', 'regex' ])
MMirbach marked this conversation as resolved.
Show resolved Hide resolved
except getopt.GetoptError as err:
error( err ) # something like "option -a not recognized"
usage()
verbose = False
regex = None
for opt,arg in opts:
if opt in ('-h','--help'):
usage()
Expand All @@ -90,8 +123,34 @@ def debug(*args):
elif opt in ('-q','--quiet'):
def out(*args):
pass
if len(args) != 1:
elif opt[0] in ('-r', '--regex'):
regex = opt[1]
if len(args) > 1:
usage()
target = None
if len(args) == 1:
if os.path.isdir( args[0] ):
target = args[0]
else:
usage()
# Trying to assume target directory from inside build directory. Only works if there is only one location with tests
if not target:
build = librealsense + os.sep + 'build'
for executable in find(build, '(^|/)test-.*'):
if not is_executable(executable):
continue
dir_with_test = build + os.sep + os.path.dirname(executable)
if target and target != dir_with_test:
maloel marked this conversation as resolved.
Show resolved Hide resolved
error("Found executable tests in 2 directories:", target, "and", dir_with_test, ". Can't default to directory")
usage()
target = dir_with_test

if target:
logdir = target + '/unit-tests'
MMirbach marked this conversation as resolved.
Show resolved Hide resolved
else: # no test executables were found. We put the logs directly in build directory
logdir = librealsense + os.sep + 'build'
os.makedirs( logdir, exist_ok = True )
n_tests = 0

def run( cmd, stdout = None ):
debug( 'Running:', cmd )
Expand All @@ -115,31 +174,6 @@ def run( cmd, stdout = None ):
if handle:
handle.close()

def filesin( root ):
# Yield all files found in root, using relative names ('root/a' would be yielded as 'a')
for (path,subdirs,leafs) in os.walk( root ):
for leaf in leafs:
# We have to stick to Unix conventions because CMake on Windows is fubar...
yield os.path.relpath( path + '/' + leaf, root ).replace( '\\', '/' )

def find( dir, mask ):
pattern = re.compile( mask )
for leaf in filesin( dir ):
if pattern.search( leaf ):
debug(leaf)
yield leaf

# NOTE: WSL will read as 'Linux' but the build is Windows-based!
system = platform.system()
if system == 'Linux' and "microsoft" not in platform.uname()[3].lower():
linux = True
else:
linux = False

current_dir = os.path.dirname(os.path.abspath(__file__))
# this script is located in librealsense/unit-tests, so one directory up is the main repository
librealsense = os.path.dirname(current_dir)

# Python scripts should be able to find the pyrealsense2 .pyd or else they won't work. We don't know
# if the user (Travis included) has pyrealsense2 installed but even if so, we want to use the one we compiled.
# we search the librealsense repository for the .pyd file (.so file in linux)
Expand All @@ -163,37 +197,6 @@ def find( dir, mask ):
os.environ["PYTHONPATH"] += os.pathsep + (current_dir + os.sep + "py")
# We can simply change `sys.path` but any child python scripts won't see it. We change the environment instead.

target = args[0]

# If a regular expression (not a directory) is passed in, find the test(s) and run
# them directly
if not os.path.isdir( target ):
if not pyrs:
error( "Python wrappers (pyrealsense2*." + pyrs + ") not found" )
usage()
n_tests = 0
for py_test in find(current_dir, target):
n_tests += 1
progress( py_test + ' ...' )
if linux:
cmd = ["python3"]
else:
cmd = ["py", "-3"]
if sys.flags.verbose:
cmd += ["-v"]
cmd += [current_dir + os.sep + py_test]
try:
run( cmd )
except subprocess.CalledProcessError as cpe:
error( cpe )
error( red + py_test + reset + ': exited with non-zero value! (' + str(cpe.returncode) + ')' )
if n_errors:
sys.exit(1)
if not n_tests:
error( "No tests found matching: " + target )
usage()
sys.exit(0)

def remove_newlines (lines):
for line in lines:
if line[-1] == '\n':
Expand Down Expand Up @@ -264,79 +267,111 @@ def check_log_for_fails(log, testname, exe):
return True
return False

logdir = target + '/unit-tests'
os.makedirs( logdir, exist_ok = True )
n_tests = 0

# In Linux, the build targets are located elsewhere than on Windows
# Go over all the tests from a "manifest" we take from the result of the last CMake
# run (rather than, for example, looking for test-* in the build-directory):
if linux:
manifestfile = target + '/CMakeFiles/TargetDirectories.txt'
else:
manifestfile = target + '/../CMakeFiles/TargetDirectories.txt'

for manifest_ctx in grep( r'(?<=unit-tests/build/)\S+(?=/CMakeFiles/test-\S+.dir$)', manifestfile ):

testdir = manifest_ctx['match'].group(0) # "log/internal/test-all"
testparent = os.path.dirname(testdir) # "log/internal"
if testparent:
testname = 'test-' + testparent.replace( '/', '-' ) + '-' + os.path.basename(testdir)[5:] # "test-log-internal-all"
else:
testname = testdir # no parent folder so we get "test-all"
if linux:
exe = target + '/unit-tests/build/' + testdir + '/' + testname
else:
exe = target + '/' + testname + '.exe'
log = logdir + '/' + testname + '.log'
# definition of classes for tests
class Test(ABC):
"""
Abstract class for a test. Holds the name of the test, the log file for it and the command line needed to run it
MMirbach marked this conversation as resolved.
Show resolved Hide resolved
"""
def __init__(self, testname, command, executable):
self.testname = testname
self.command = command
self.executable = executable
self.log = logdir + '/' + testname + '.log'

progress( testname, '>', log, '...' )
n_tests += 1
try:
run( [exe], stdout=log )
except FileNotFoundError:
error( red + testname + reset + ': executable not found! (' + exe + ')' )
continue
except subprocess.CalledProcessError as cpe:
if not check_log_for_fails(log, testname, exe):
# An unexpected error occurred
error( red + testname + reset + ': exited with non-zero value! (' + str(cpe.returncode) + ')' )

# If we run python tests with no .pyd/.so file they will crash. Therefore we only run them if such a file was found
if pyrs:
# unit-test scripts are in the same directory as this script
for py_test in find(current_dir, '(^|/)test-.*\.py'):

testdir = py_test[:-3] # "log/internal/test-all" <- "log/internal/test-all.py"
testparent = os.path.dirname(testdir) # same as for cpp files
if testparent:
testname = 'test-' + testparent.replace( '/', '-' ) + '-' + os.path.basename(testdir)[5:]
else:
testname = testdir

log = logdir + '/' + testname + '.log'

progress( testname, '>', log, '...' )
def run_test(self):
MMirbach marked this conversation as resolved.
Show resolved Hide resolved
global n_tests
progress(self.testname, '>', self.log, '...')
n_tests += 1
test_path = current_dir + os.sep + py_test
if linux:
cmd = ["python3", test_path]
else:
cmd = ["py","-3", test_path]
try:
run( cmd, stdout=log )
run( self.command, stdout=self.log )
except FileNotFoundError:
error( red + testname + reset + ': file not found! (' + test_path + ')' )
continue
error(red + self.testname + reset + ': executable not found! (' + self.executable + ')')
except subprocess.CalledProcessError as cpe:
if not check_log_for_fails(log, testname, py_test):
if not check_log_for_fails(self.log, self.testname, self.executable):
# An unexpected error occurred
cat(log)
error(red + testname + reset + ': exited with non-zero value! (' + str(cpe.returncode) + ')')
error(red + self.testname + reset + ': exited with non-zero value! (' + str(cpe.returncode) + ')')

class PyTest(Test):
"""
Class for python tests
"""
def __init__(self, testname, path_to_test):
"""
:param testname: name of the test
:param path_to_test: the relative path from the current directory to the path
"""
global current_dir
MMirbach marked this conversation as resolved.
Show resolved Hide resolved

if linux:
cmd = ["python3", path_to_test]
else:
cmd = ["py", "-3", path_to_test]

Test.__init__(self, testname, cmd, current_dir + path_to_test)

MMirbach marked this conversation as resolved.
Show resolved Hide resolved
class CTest(Test):
MMirbach marked this conversation as resolved.
Show resolved Hide resolved
"""
Class for c/cpp tests
MMirbach marked this conversation as resolved.
Show resolved Hide resolved
"""
def __init__(self, testname, exe):
"""
:param testname: name of the test
:param exe: full path to executable
"""
Test.__init__(self, testname, exe, exe)

def get_tests():
global regex, target, pyrs, current_dir
if regex:
pattern = re.compile(regex)
if target:
# In Linux, the build targets are located elsewhere than on Windows
# Go over all the tests from a "manifest" we take from the result of the last CMake
# run (rather than, for example, looking for test-* in the build-directory):
if linux:
manifestfile = target + '/CMakeFiles/TargetDirectories.txt'
else:
manifestfile = target + '/../CMakeFiles/TargetDirectories.txt'
for manifest_ctx in grep(r'(?<=unit-tests/build/)\S+(?=/CMakeFiles/test-\S+.dir$)', manifestfile):
# We need to first create the test name so we can see if it fits the regex
testdir = manifest_ctx['match'].group(0) # "log/internal/test-all"
testparent = os.path.dirname(testdir) # "log/internal"
if testparent:
testname = 'test-' + testparent.replace('/', '-') + '-' + os.path.basename(testdir)[5:] # "test-log-internal-all"
else:
testname = testdir # no parent folder so we get "test-all"

if regex and not pattern.search( testname ):
continue

if linux:
exe = target + '/unit-tests/build/' + testdir + '/' + testname
else:
exe = target + '/' + testname + '.exe'

yield CTest(testname, exe)

# If we run python tests with no .pyd/.so file they will crash. Therefore we only run them if such a file was found
if pyrs:
# unit-test scripts are in the same directory as this script
for py_test in find(current_dir, '(^|/)test-.*\.py'):
testparent = os.path.dirname(py_test) # "log/internal" <- "log/internal/test-all.py"
if testparent:
testname = 'test-' + testparent.replace('/', '-') + '-' + os.path.basename(py_test)[5:-3] # remove .py
else:
testname = os.path.basename(py_test)[:-3]

if regex and not pattern.search( testname ):
continue

yield PyTest(testname, py_test)

for test in get_tests():
test.run_test()

progress()
if n_errors:
out( red + str(n_errors) + reset + ' of ' + str(n_tests) + ' test(s) failed!' + clear_eos )
sys.exit(1)
out( str(n_tests) + ' unit-test(s) completed successfully' + clear_eos )
sys.exit(0)
sys.exit(0)