tests/run-tests: Use absolute paths where possible.

Replace some usages of paths relative to the current working directory
with absolute paths relative to the tests directory.

Fixes and resulting changes:
- default values of MICROPYTHON and MPYCROSS are absolute paths and
  always correct
- likewise, the correct full paths for tools and extmod directories
  are appended to sys.path
- printing/cleaning failures works properly since it expects the .exp
  and .out files in the tests directory which is also where they
  are written to now, plus no more need for changing directories

This fixes #5872 and allows running custom tests which use run-tests
without having to cd to the tests directory first, and the test output
still is in the tests/ directory instead of the current working directory.

Discovery of tests and all skip test logic based on paths relative to
the current working directory remains unchanged which essentially means
that for running most of MicroPython's own tests, run-tests must still
be ran from within it's directory, so document that.
This commit is contained in:
stijn 2020-04-08 08:01:58 +02:00 committed by Damien George
parent 91c5d168c0
commit 405893afc6
1 changed files with 31 additions and 23 deletions

View File

@ -5,21 +5,29 @@ import subprocess
import sys import sys
import platform import platform
import argparse import argparse
import inspect
import re import re
from glob import glob from glob import glob
# See stackoverflow.com/questions/2632199: __file__ nor sys.argv[0]
# are guaranteed to always work, this one should though.
BASEPATH = os.path.dirname(os.path.abspath(inspect.getsourcefile(lambda: None)))
def base_path(*p):
return os.path.abspath(os.path.join(BASEPATH, *p)).replace('\\', '/')
# Tests require at least CPython 3.3. If your default python3 executable # Tests require at least CPython 3.3. If your default python3 executable
# is of lower version, you can point MICROPY_CPYTHON3 environment var # is of lower version, you can point MICROPY_CPYTHON3 environment var
# to the correct executable. # to the correct executable.
if os.name == 'nt': if os.name == 'nt':
CPYTHON3 = os.getenv('MICROPY_CPYTHON3', 'python3.exe') CPYTHON3 = os.getenv('MICROPY_CPYTHON3', 'python3.exe')
MICROPYTHON = os.getenv('MICROPY_MICROPYTHON', '../ports/windows/micropython.exe') MICROPYTHON = os.getenv('MICROPY_MICROPYTHON', base_path('../ports/windows/micropython.exe'))
else: else:
CPYTHON3 = os.getenv('MICROPY_CPYTHON3', 'python3') CPYTHON3 = os.getenv('MICROPY_CPYTHON3', 'python3')
MICROPYTHON = os.getenv('MICROPY_MICROPYTHON', '../ports/unix/micropython') MICROPYTHON = os.getenv('MICROPY_MICROPYTHON', base_path('../ports/unix/micropython'))
# mpy-cross is only needed if --via-mpy command-line arg is passed # mpy-cross is only needed if --via-mpy command-line arg is passed
MPYCROSS = os.getenv('MICROPY_MPYCROSS', '../mpy-cross/mpy-cross') MPYCROSS = os.getenv('MICROPY_MPYCROSS', base_path('../mpy-cross/mpy-cross'))
# For diff'ing test output # For diff'ing test output
DIFF = os.getenv('MICROPY_DIFF', 'diff -u') DIFF = os.getenv('MICROPY_DIFF', 'diff -u')
@ -61,7 +69,7 @@ def run_micropython(pyb, args, test_file, is_special=False):
had_crash = False had_crash = False
if pyb is None: if pyb is None:
# run on PC # run on PC
if test_file.startswith(('cmdline/', 'feature_check/')) or test_file in special_tests: if test_file.startswith(('cmdline/', base_path('feature_check/'))) or test_file in special_tests:
# special handling for tests of the unix cmdline program # special handling for tests of the unix cmdline program
is_special = True is_special = True
@ -215,10 +223,10 @@ def run_feature_check(pyb, args, base_path, test_file):
if pyb is not None and test_file.startswith("repl_"): if pyb is not None and test_file.startswith("repl_"):
# REPL feature tests will not run via pyboard because they require prompt interactivity # REPL feature tests will not run via pyboard because they require prompt interactivity
return b"" return b""
return run_micropython(pyb, args, base_path + "/feature_check/" + test_file, is_special=True) return run_micropython(pyb, args, base_path("feature_check", test_file), is_special=True)
def run_tests(pyb, tests, args, base_path="."): def run_tests(pyb, tests, args):
test_count = 0 test_count = 0
testcase_count = 0 testcase_count = 0
passed_count = 0 passed_count = 0
@ -244,6 +252,10 @@ def run_tests(pyb, tests, args, base_path="."):
# If we're asked to --list-tests, we can't assume that there's a # If we're asked to --list-tests, we can't assume that there's a
# connection to target, so we can't run feature checks usefully. # connection to target, so we can't run feature checks usefully.
if not (args.list_tests or args.write_exp): if not (args.list_tests or args.write_exp):
# Even if we run completely different tests in a different directory,
# we need to access feature_checks from the same directory as the
# run-tests script itself so use base_path.
# Check if micropython.native is supported, and skip such tests if it's not # Check if micropython.native is supported, and skip such tests if it's not
output = run_feature_check(pyb, args, base_path, 'native_check.py') output = run_feature_check(pyb, args, base_path, 'native_check.py')
if output == b'CRASH': if output == b'CRASH':
@ -307,7 +319,7 @@ def run_tests(pyb, tests, args, base_path="."):
upy_float_precision = int(upy_float_precision) upy_float_precision = int(upy_float_precision)
has_complex = run_feature_check(pyb, args, base_path, 'complex.py') == b'complex\n' has_complex = run_feature_check(pyb, args, base_path, 'complex.py') == b'complex\n'
has_coverage = run_feature_check(pyb, args, base_path, 'coverage.py') == b'coverage\n' has_coverage = run_feature_check(pyb, args, base_path, 'coverage.py') == b'coverage\n'
cpy_byteorder = subprocess.check_output([CPYTHON3, base_path + '/feature_check/byteorder.py']) cpy_byteorder = subprocess.check_output([CPYTHON3, base_path('feature_check/byteorder.py')])
skip_endian = (upy_byteorder != cpy_byteorder) skip_endian = (upy_byteorder != cpy_byteorder)
# These tests don't test slice explicitly but rather use it to perform the test # These tests don't test slice explicitly but rather use it to perform the test
@ -509,8 +521,8 @@ def run_tests(pyb, tests, args, base_path="."):
testcase_count += len(output_expected.splitlines()) testcase_count += len(output_expected.splitlines())
filename_expected = test_basename + ".exp" filename_expected = base_path(test_basename + ".exp")
filename_mupy = test_basename + ".out" filename_mupy = base_path(test_basename + ".out")
if output_expected == output_mupy: if output_expected == output_mupy:
print("pass ", test_file) print("pass ", test_file)
@ -563,6 +575,10 @@ def main():
formatter_class=argparse.RawDescriptionHelpFormatter, formatter_class=argparse.RawDescriptionHelpFormatter,
description='''Run and manage tests for MicroPython. description='''Run and manage tests for MicroPython.
Tests are discovered by scanning test directories for .py files or using the
specified test files. If test files nor directories are specified, the script
expects to be ran in the tests directory (where this file is located) and the
builtin tests suitable for the target platform are ran.
When running tests, run-tests compares the MicroPython output of the test with the output When running tests, run-tests compares the MicroPython output of the test with the output
produced by running the test through CPython unless a <test>.exp file is found, in which produced by running the test through CPython unless a <test>.exp file is found, in which
case it is used as comparison. case it is used as comparison.
@ -598,10 +614,8 @@ the last matching regex is used:
args = cmd_parser.parse_args() args = cmd_parser.parse_args()
if args.print_failures: if args.print_failures:
os.chdir(os.path.abspath(os.path.dirname(__file__))) for exp in glob(base_path("*.exp")):
testbase = exp[:-4]
for exp in glob("*.exp"):
testbase = os.path.basename(exp)[:-4]
print() print()
print("FAILURE {0}".format(testbase)) print("FAILURE {0}".format(testbase))
os.system("{0} {1}.exp {1}.out".format(DIFF, testbase)) os.system("{0} {1}.exp {1}.out".format(DIFF, testbase))
@ -609,9 +623,7 @@ the last matching regex is used:
sys.exit(0) sys.exit(0)
if args.clean_failures: if args.clean_failures:
os.chdir(os.path.abspath(os.path.dirname(__file__))) for f in glob(base_path("*.exp")) + glob(base_path("*.out")):
for f in glob("*.exp") + glob("*.out"):
os.remove(f) os.remove(f)
sys.exit(0) sys.exit(0)
@ -622,7 +634,7 @@ the last matching regex is used:
pyb = None pyb = None
elif args.target in EXTERNAL_TARGETS: elif args.target in EXTERNAL_TARGETS:
global pyboard global pyboard
sys.path.append('../tools') sys.path.append(base_path('../tools'))
import pyboard import pyboard
pyb = pyboard.Pyboard(args.device, args.baudrate, args.user, args.password) pyb = pyboard.Pyboard(args.device, args.baudrate, args.user, args.password)
pyb.enter_raw_repl() pyb.enter_raw_repl()
@ -659,14 +671,10 @@ the last matching regex is used:
if not args.keep_path: if not args.keep_path:
# clear search path to make sure tests use only builtin modules and those in extmod # clear search path to make sure tests use only builtin modules and those in extmod
os.environ['MICROPYPATH'] = os.pathsep + '../extmod' os.environ['MICROPYPATH'] = os.pathsep + base_path('../extmod')
# Even if we run completely different tests in a different directory,
# we need to access feature_check's from the same directory as the
# run-tests script itself.
base_path = os.path.dirname(sys.argv[0]) or "."
try: try:
res = run_tests(pyb, tests, args, base_path) res = run_tests(pyb, tests, args)
finally: finally:
if pyb: if pyb:
pyb.close() pyb.close()