forked from ubuntu/ubuntu-make
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathruntests
executable file
·341 lines (286 loc) · 16 KB
/
runtests
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
#!/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)