run_tests: delete old wrapper tool
Now that we only have python3 to worry about, delete the old logic
and rename run_pytest to the canonical run_tests location. The
diff looks bad, but that's because we're deleting run_tests and
then renaming run_pytest on top of it.
BUG=chromium:997354
TEST=`./run_tests` passes
Change-Id: I7c43a3b671e0c5be531cef760259d18ee41167b9
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/chromite/+/2822082
Commit-Queue: Mike Frysinger <vapier@chromium.org>
Tested-by: Mike Frysinger <vapier@chromium.org>
Reviewed-by: Chris McDonald <cjmcdonald@chromium.org>
diff --git a/scripts/run_tests.py b/scripts/run_tests.py
new file mode 100644
index 0000000..b6b5e89
--- /dev/null
+++ b/scripts/run_tests.py
@@ -0,0 +1,148 @@
+# -*- coding: utf-8 -*-
+# Copyright 2020 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Chromite main test runner.
+
+Run the specified tests. If none are specified, we'll scan the
+tree looking for tests to run and then only run the semi-fast ones.
+"""
+
+from __future__ import print_function
+
+import os
+import sys
+
+import pytest # pylint: disable=import-error
+
+from chromite.lib import commandline
+from chromite.lib import constants
+from chromite.lib import cros_build_lib
+from chromite.lib import gs
+from chromite.lib import cros_logging as logging
+from chromite.lib import namespaces
+
+
+def main(argv):
+ parser = get_parser()
+ opts = parser.parse_args()
+ opts.Freeze()
+
+ pytest_args = opts.pytest_args
+
+ if opts.quick:
+ if not cros_build_lib.IsInsideChroot() and opts.chroot:
+ logging.warning('Tests start up faster when run from inside the chroot.')
+
+ if opts.chroot:
+ ensure_chroot_exists()
+ re_execute_inside_chroot(argv)
+ else:
+ pytest_args += ['--no-chroot']
+
+ # This is a cheesy hack to make sure gsutil is populated in the cache before
+ # we run tests. This is a partial workaround for crbug.com/468838.
+ gs.GSContext.GetDefaultGSUtilBin()
+
+ if opts.quick:
+ logging.info('Skipping test namespacing due to --quickstart.')
+ # Default to running in a single process under --quickstart. User args can
+ # still override this.
+ pytest_args = ['-n', '0'] + pytest_args
+ else:
+ # Namespacing is enabled by default because tests may break each other or
+ # interfere with parts of the running system if not isolated in a namespace.
+ # Disabling namespaces is not recommended for general use.
+ re_execute_with_namespace([sys.argv[0]] + argv)
+
+ # Check the environment. https://crbug.com/1015450
+ st = os.stat('/')
+ if st.st_mode & 0o7777 != 0o755:
+ cros_build_lib.Die(
+ f'The root directory has broken permissions: {st.st_mode:o}\n'
+ 'Fix with: sudo chmod 755 /')
+ if st.st_uid or st.st_gid:
+ cros_build_lib.Die(
+ f'The root directory has broken ownership: {st.st_uid}:{st.st_gid}'
+ ' (should be 0:0)\nFix with: sudo chown 0:0 /')
+
+ sys.exit(pytest.main(pytest_args))
+
+
+def re_execute_with_namespace(argv, network=False):
+ """Re-execute as root so we can unshare resources."""
+ if os.geteuid() != 0:
+ cmd = [
+ 'sudo',
+ 'HOME=%s' % os.environ['HOME'],
+ 'PATH=%s' % os.environ['PATH'],
+ '--',
+ ] + argv
+ os.execvp(cmd[0], cmd)
+ else:
+ namespaces.SimpleUnshare(net=not network, pid=True)
+ # We got our namespaces, so switch back to the user to run the tests.
+ gid = int(os.environ.pop('SUDO_GID'))
+ uid = int(os.environ.pop('SUDO_UID'))
+ user = os.environ.pop('SUDO_USER')
+ os.initgroups(user, gid)
+ os.setresgid(gid, gid, gid)
+ os.setresuid(uid, uid, uid)
+ os.environ['USER'] = user
+
+
+def re_execute_inside_chroot(argv):
+ """Re-execute the test wrapper inside the chroot."""
+ if cros_build_lib.IsInsideChroot():
+ return
+
+ target = os.path.join(constants.CHROMITE_DIR, 'scripts', 'run_tests')
+ relpath = os.path.relpath(target, '.')
+ # If we're in the scripts dir, make sure we always have a relative path,
+ # otherwise cros_sdk will search $PATH and fail.
+ if os.path.sep not in relpath:
+ relpath = os.path.join('.', relpath)
+ cmd = [
+ 'cros_sdk',
+ '--working-dir', '.',
+ '--',
+ relpath,
+ ]
+ os.execvp(cmd[0], cmd + argv)
+
+
+def ensure_chroot_exists():
+ """Ensure that a chroot exists for us to run tests in."""
+ chroot = os.path.join(constants.SOURCE_ROOT, constants.DEFAULT_CHROOT_DIR)
+ if not os.path.exists(chroot) and not cros_build_lib.IsInsideChroot():
+ cros_build_lib.run(['cros_sdk', '--create'])
+
+
+def get_parser():
+ """Build the parser for command line arguments."""
+ parser = commandline.ArgumentParser(
+ description=__doc__,
+ epilog='To see the help output for pytest:\n$ %(prog)s -- --help',
+ )
+ parser.add_argument(
+ '--quickstart',
+ dest='quick',
+ action='store_true',
+ help='Skip normal test sandboxing and namespacing for faster start up '
+ 'time.',
+ )
+ parser.add_argument(
+ '--no-chroot',
+ dest='chroot',
+ action='store_false',
+ help="Don't initialize or enter a chroot for the test invocation. May "
+ 'cause tests to unexpectedly fail!',
+ )
+ parser.add_argument(
+ 'pytest_args',
+ metavar='pytest arguments',
+ nargs='*',
+ help='Arguments to pass down to pytest (use -- to help separate)',
+ )
+ return parser