Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| #!/usr/bin/env python3 | |
| # -*- coding: utf-8 -*- | |
| # Copyright (C) 2014 Canonical | |
| # | |
| # Authors: | |
| # Didier Roche | |
| # | |
| # This program is free software; you can redistribute it and/or modify it under | |
| # the terms of the GNU General Public License as published by the Free Software | |
| # Foundation; version 3. | |
| # | |
| # This program is distributed in the hope that it will be useful, but WITHOUT | |
| # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | |
| # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more | |
| # details. | |
| # | |
| # You should have received a copy of the GNU General Public License along with | |
| # this program; if not, write to the Free Software Foundation, Inc., | |
| # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
| import argparse | |
| import configparser | |
| import logging | |
| import logging.config | |
| import nose | |
| import os | |
| import yaml | |
| import shutil | |
| import subprocess | |
| import sys | |
| import tempfile | |
| root_dir = os.path.abspath(os.path.dirname(__file__)) | |
| config_dir = os.path.join(root_dir, 'confs') | |
| DEBUG_CONFIG_FILE = os.path.join(config_dir, "debug.nose") | |
| COVERAGE_CONFIG_FILE = os.path.join(config_dir, "prod.nose") | |
| TESTS_DIR = os.path.join(root_dir, 'tests') | |
| DEBUG_LOG_CONFIG = os.path.join(config_dir, "debug.logcfg") | |
| TESTING_LOG_CONFIG = os.path.join(config_dir, "testing.logcfg") | |
| # subprocess need to output on stdout the logs to be monitored | |
| # the profile is the testing one + console output in warning mode | |
| TESTING_SUBPROCESS_LOG_CONFIG = os.path.join(config_dir, "testing.subprocess.logcfg") | |
| def transform_nose_config_to_cmd(filename): | |
| """Manually transform a nose config file to a cmd parameters | |
| This is needed in case the same parameter is repeated multiple times | |
| This return the cmd line array parameters.""" | |
| cmd_line = [] | |
| config = configparser.ConfigParser() | |
| config.read(filename) | |
| for key in config["nosetests"]: | |
| value = config["nosetests"][key] | |
| if value == '1': | |
| cmd_line.append('--' + key) | |
| # multiple values (what the config parameter for nose doesn't support) | |
| elif ',' in value: | |
| for val in value.split(','): | |
| cmd_line.append('--{}={}'.format(key, val)) | |
| else: | |
| cmd_line.append('--{}={}'.format(key, value)) | |
| return cmd_line | |
| def set_logging_profile(log_config_file): | |
| """Set logging profile for current process and subprocesses""" | |
| with open(log_config_file, 'rt') as f: | |
| logging_config = yaml.load(f.read()) | |
| logging.config.dictConfig(logging_config) | |
| os.environ["LOG_CFG"] = log_config_file | |
| if log_config_file == TESTING_LOG_CONFIG: | |
| os.environ["LOG_CFG"] = TESTING_SUBPROCESS_LOG_CONFIG | |
| def local_run(args): | |
| """Run directly the tests on the host""" | |
| # setup the environment in plain english so that we standardize the test bed (if some people have some .mo | |
| # Ubuntu Make files installed while having a locale set to it) to avoid getting translated strings not | |
| # matching our expectations. | |
| os.environ["LANGUAGE"] = "C" | |
| nose_args = [] | |
| # nosetests captured logs format | |
| nose_args.extend(["--logging-format", "%(asctime)s [%(name)s] %(levelname)s: %(message)s"]) | |
| ## handle config first | |
| specified_config = False | |
| if args.config: | |
| nose_args.extend(["--config", args.config]) | |
| specified_config = True | |
| elif args.debug: | |
| nose_args.extend(["--config", DEBUG_CONFIG_FILE]) | |
| set_logging_profile(DEBUG_LOG_CONFIG) | |
| specified_config = True | |
| elif args.coverage: | |
| nose_args.extend(transform_nose_config_to_cmd(COVERAGE_CONFIG_FILE)) | |
| set_logging_profile(TESTING_LOG_CONFIG) | |
| specified_config = True | |
| elif args.no_config: | |
| specified_config = True | |
| ## output xunit and json if requested | |
| if args.publish: | |
| nose_args.append("--with-xunit") | |
| # FIXME: disable nose-json for now, incompatible with coverage reporting when an exception is raised. | |
| # we are going to switch to nose2 anyway. | |
| #nose_args.append("--with-json") | |
| ## check if we want to run those tests with the system code | |
| if args.system: | |
| nose_args.append("-P") | |
| # let remove it from there as well | |
| sys.path.remove(root_dir) | |
| else: | |
| import tests | |
| tests.tools.set_local_umake() | |
| if not "all" in args.tests and len(args.tests) > 0: | |
| for test_type in args.tests: | |
| for named_test_type in ("small", "medium", "large", "pep8"): | |
| if test_type == named_test_type: | |
| if test_type == "pep8": | |
| nose_args.append(os.path.join(TESTS_DIR, "__init__.py")) | |
| else: | |
| nose_args.append(os.path.join(TESTS_DIR, named_test_type)) | |
| break | |
| # Not a named test_type, but a list of tests to run | |
| else: | |
| nose_args.append(test_type) | |
| # If no config is given, choose debug by default for partial run | |
| if not specified_config: | |
| nose_args.extend(["--config", DEBUG_CONFIG_FILE]) | |
| set_logging_profile(DEBUG_LOG_CONFIG) | |
| specified_config = True | |
| else: | |
| # If no config is given, run with coverage | |
| if not specified_config: | |
| nose_args.extend(transform_nose_config_to_cmd(COVERAGE_CONFIG_FILE)) | |
| set_logging_profile(TESTING_LOG_CONFIG) | |
| specified_config = True | |
| ## need a fake $0 argument | |
| nose_args.insert(0, "") | |
| nose.main(argv=nose_args) | |
| def vm_run(args): | |
| """Run the tests on a qemu vm""" | |
| artefacts_dir = tempfile.mkdtemp(prefix="umake-test-artefacts-") | |
| cmds = ['adt-run', '-s', | |
| # add ubuntu-make and ubuntu-make-builddeps ppas with their gpg key | |
| '--setup-commands', | |
| """apt-key adv --keyserver keyserver.ubuntu.com --recv-key 399B698EEA9EF163B6F9A0F62CC98497A1231595; | |
| echo "deb http://ppa.launchpad.net/ubuntu-desktop/ubuntu-make-builddeps/ubuntu trusty main" > /etc/apt/sources.list.d/autopkgtest-ubuntu-desktop-ubuntu-make-builddeps.list; | |
| echo "deb-src http://ppa.launchpad.net/ubuntu-desktop/ubuntu-make-builddeps/ubuntu trusty main" >> /etc/apt/sources.list.d/autopkgtest-ubuntu-desktop-ubuntu-make-builddeps.list; | |
| echo "deb http://ppa.launchpad.net/ubuntu-desktop/ubuntu-make/ubuntu trusty main" > /etc/apt/sources.list.d/autopkgtest-ubuntu-desktop-ubuntu-make.list; | |
| echo "deb-src http://ppa.launchpad.net/ubuntu-desktop/ubuntu-make/ubuntu trusty main" >> /etc/apt/sources.list.d/autopkgtest-ubuntu-desktop-ubuntu-make.list; apt-get update""", | |
| # artefacts saving | |
| "--output-dir", artefacts_dir, | |
| # env variables setting up tests | |
| '--env', "TESTS={}".format(' '.join(args.tests))] | |
| # default target to test is current tree | |
| target = ['--built-tree', '.'] | |
| # test package from archive or ppa (the most recent) | |
| if args.test_package: | |
| target = ["--apt-source", "ubuntu-make"] | |
| # git checkout testing | |
| if args.test_git: | |
| target = ['--no-built-binaries', "--git-source", args.test_git] | |
| # local source package testing | |
| if args.test_source: | |
| target = ["--source", args.test_source] | |
| cmds.extend(target) | |
| # add emulator options | |
| cmds.extend(['---', 'qemu', '-o', '/var/tmp', args.image]) | |
| try: | |
| return_code = subprocess.call(cmds) | |
| print("Artefacts are available at {}".format(artefacts_dir)) | |
| except FileNotFoundError: | |
| print("adt-run isn't installed.") | |
| shutil.rmtree(artefacts_dir) | |
| return_code = 1 | |
| sys.exit(return_code) | |
| def remote_run(args): | |
| """Start a run on the official ubuntu instances""" | |
| # Note that we wrap arguments containing spaces with double quoting as they are passed through ssh | |
| # ssh connection to snakefruit | |
| cmds = ["ssh", "snakefruit.canonical.com", "sudo", "-i", "-u", "ubuntu-archive", | |
| # run-autopkgtest command | |
| "run-autopkgtest", "-s", args.series, | |
| # ensure we always add the ppa to be in correct swift container archive | |
| "--ppa", "ubuntu-desktop/ubuntu-make-builddeps", "--ppa", "ubuntu-desktop/ubuntu-make", | |
| # env variables setting up tests (with double quoting | |
| '--env', "'TESTS={}'".format(' '.join(args.tests))] | |
| # add architectures if any: | |
| for arch in args.architecture: | |
| cmds.extend(["-a", arch]) | |
| target = [] | |
| if args.test_package: | |
| target = ["ubuntu-make"] | |
| # git checkout testing, put results under ubuntu-make-git tag | |
| if args.test_git: | |
| target = ["--test-git", "'{}'".format(args.test_git), "ubuntu-make-git"] | |
| cmds.extend(target) | |
| sys.exit(subprocess.call(cmds)) | |
| def add_tests_arg(parser): | |
| """add the generic tests arguments to the parser""" | |
| parser.add_argument("tests", nargs='*', help="Action to perform: all (or omitted) to run all tests. " | |
| "small/medium/large/pep8 or nose syntax: " | |
| "tests.small.test_frameworks_loader:TestFrameworkLoaderSaveConfig.foo") | |
| class _GlobalHelp(argparse._HelpAction): | |
| def __call__(self, parser, namespace, values, option_string=None): | |
| parser.print_help() | |
| # retrieve local subparser from parser | |
| subparsers_actions = [ | |
| action for action in parser._actions | |
| if isinstance(action, argparse._SubParsersAction)] | |
| print(""" | |
| ------------------------------------------- | |
| Default mode is 'local' and can be omitted. | |
| -------------------------------------------""") | |
| subparsers_actions[0].choices['local'].print_help() | |
| parser.exit() | |
| if __name__ == '__main__': | |
| # ensure we have the right chmod on the ssh key to connect to docker containers (git by default reverts to x44) | |
| os.chmod(os.path.join(root_dir, "docker", "umake_docker"), 0o600) | |
| parser = argparse.ArgumentParser(description="Run umake tests. Specified list of test run in debug mode. " | |
| "Running a partial suite of tests is using a normal mode. " | |
| "Running all tests is using coverage by default.", | |
| add_help=False) | |
| parser.add_argument('--help', action=_GlobalHelp, help='Show this help') | |
| command_group = parser.add_subparsers(help='What mode to run tests with') | |
| local_mode = command_group.add_parser('local', help='run tests locally (default). Enable easy debugging but large ' | |
| 'tests requires sudo and impact your sessions.') | |
| vm_mode = command_group.add_parser('vm', help='run tests with vm_mode running on a qemu image.') | |
| remote_mode = command_group.add_parser('remote', help='run on official autopkgtests ubuntu instances (only for ' | |
| 'ubuntu archive admins or release team members)') | |
| ## local options | |
| local_mode.set_defaults(run=local_run) | |
| add_tests_arg(local_mode) | |
| local_mode.add_argument('-s', "--system", action="store_true", help="Use system umake instead of local one") | |
| local_mode.add_argument("--publish", action="store_true", help="Publish xunit results format") | |
| config_group = local_mode.add_argument_group('Run configuration options', | |
| description="The default configuration is to use the debug profile " | |
| "when running some manually specific list of tests. No profile is " | |
| "selected when running some suites of tests and coverage " | |
| "profile when selecting all tests.")\ | |
| .add_mutually_exclusive_group() | |
| config_group.add_argument("-c", "--config", help="Force using a particular nose profile, disable autoselection") | |
| config_group.add_argument("--coverage", action="store_true", help="Force using coverage profile even when some " | |
| "tests or tessuite") | |
| config_group.add_argument("--debug", action="store_true", help="Force using debug profile even when running " | |
| "all tests") | |
| config_group.add_argument("--no-config", action="store_true", help="Disable any automated or manual config " | |
| "selection") | |
| ## vm options | |
| vm_mode.set_defaults(run=vm_run) | |
| vm_mode.add_argument('image', metavar="image_path", help="Image against which running the tests. This is what " | |
| "determines as well the release and arch against which " | |
| "we are testing on. You can build one cloud image via: " | |
| "'buildvm-ubuntu-cloud -r trusty' for instance.") | |
| add_tests_arg(vm_mode) | |
| target_group = vm_mode.add_argument_group('Ubuntu Make test target', | |
| description="This defines which Ubuntu Make you want to test. Default " | |
| "is the local tree (what's your pwd). You can change it " | |
| "for a git tree/branch or package from archive.")\ | |
| .add_mutually_exclusive_group() | |
| target_group.add_argument("--test-git", metavar='GITURL [branchname]', | |
| help="A single URL or URL branchname. The test will be git cloned from " | |
| "that URL and ran from the checkout. This will not build binary " | |
| "packages from the branch and run tests against those, the test " | |
| "dependencies will be taken from the archive, or Ubuntu Make PPA.") | |
| target_group.add_argument("--test-package", action="store_true", help="Test the ubuntu-make package (system run) " | |
| "from the archive or Ubuntu Make PPA.") | |
| target_group.add_argument("--test-source", metavar='DSC or some/pkg.dsc', | |
| help='Build DSC and use its tests and/or generated binary packages') | |
| ## remote options | |
| remote_mode.set_defaults(run=remote_run) | |
| remote_mode.add_argument("series", help='Distro series name.') | |
| add_tests_arg(remote_mode) | |
| remote_mode.add_argument('-a', '--architecture', action='append', default=[], | |
| help='Only run test(s) on given architecture name(s). ' | |
| 'Can be specified multiple times (default: all).') | |
| target_group = remote_mode.add_argument_group('Ubuntu Make test target', | |
| description="This defines which Ubuntu Make you want to test. You " | |
| "can change between a git tree/branch of package from " | |
| "archive.")\ | |
| .add_mutually_exclusive_group(required=True) | |
| target_group.add_argument("--test-package", action="store_true", help="Test the ubuntu-make package (system run) " | |
| "from the archive or Ubuntu Make PPA.") | |
| target_group.add_argument("--test-git", metavar='GITURL [branchname]', | |
| help="A single URL or URL branchname. The test will be git cloned from " | |
| "that URL and ran from the checkout. This will not build binary " | |
| "packages from the branch and run tests against those, the test " | |
| "dependencies will be taken from the archive, or Ubuntu Make PPA.") | |
| # set local as default and parse | |
| cmd = sys.argv[1:] | |
| if "--help" not in cmd: | |
| if cmd[0] not in ["local", "vm", "remote"]: | |
| cmd.insert(0, "local") | |
| args = parser.parse_args(cmd) | |
| args.run(args) |