Permalink
Fetching contributors…
Cannot retrieve contributors at this time
executable file 342 lines (286 sloc) 16 KB
#!/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)