24
25
HERE = os.path.abspath(os.path.dirname(__file__))
26
27
# deploy.py isn't in our pythonpath when run as a script
27
sys.path.append(os.path.join(HERE, 'juju-deployer'))
28
sys.path.append(os.path.join(HERE, '..', 'juju-deployer'))
29
30
from ucitests import (
39
def rename_tests(suite, renamer):
40
"""Rename all tests in a suite.
42
:param suite: The tests to be renamed.
44
:param renamer: A callable returning a callable 'id' method for the
45
test. It should accept a callable that returns the current test id.
47
Note: Care should be taken to ensure that 'renamer' preserve unique ids.
49
# We modify the tests in place so it's ok to use iter_flat
50
for t in filters.iter_flat(suite):
51
# 'renamer' needs to be a two-level lambda or the wrong variable is
52
# captured. See tests for examples.
56
def prefix_test_ids(suite, prefix):
57
"""Change the test ids to embed a prefix for easier matching.
59
:param suite: The tests to be renamed.
61
:param prefix: A path from the branch root to the root python module (found
64
prefix = prefix.replace(os.path.sep, '.')
65
# The renamer receives the old_id callable and return a lambda that will be
66
# called as test.id().
67
renamer = lambda old_id: lambda: prefix + '.' + old_id()
68
rename_tests(suite, renamer)
38
71
class SysPath(object):
39
72
"""Add paths in front of sys.path temporarily."""
94
130
suite = loader.suiteClass()
95
131
for c in components:
96
suite.addTests(load_component_tests(loader, c))
97
suite = filters.include_regexps(include_regexps, suite)
98
suite = filters.exclude_regexps(exclude_regexps, suite)
132
component_suite = load_component_tests(loader, c)
133
prefix_test_ids(component_suite, c)
134
suite.addTests(component_suite)
135
suite = filters.include_regexps(include_regexps, suite)
136
suite = filters.exclude_regexps(exclude_regexps, suite)
140
def load_testing_tests(include_regexps, exclude_regexps=None):
141
loader = loaders.Loader()
142
suite = loader.suiteClass()
143
suite.addTests(loader.loadTestsFromTree('testing'))
144
suite = filters.include_regexps(include_regexps, suite)
145
suite = filters.exclude_regexps(exclude_regexps, suite)
149
def get_django_runner(component_base_dir):
103
150
"""Return a test runner class tailored to django needs.
110
157
Since the imports needs to be delayed, that DjangoTestRunner class has to
111
158
be defined inside a function so it can inherit from DjangoTestSuiteRunner.
113
from django.test import (
160
from django.test import simple
118
162
class DjangoTestRunner(simple.DjangoTestSuiteRunner):
140
184
return self._cached_test_suite
141
185
loader = loaders.Loader()
142
186
suite = load_component_tests(loader, self.component_base_dir)
187
prefix_test_ids(suite, component_base_dir)
143
188
# Filtered as required by the user
144
189
suite = filters.include_regexps(
145
190
self.options.include_regexps, suite)
174
219
test_labels, extra_tests=None, **kwargs)
176
221
def run_suite(self, suite, **kwargs):
222
suite = parallelize_suite_round_robin(suite,
223
self.options.concurrency)
177
224
suite.run(self.result)
178
225
return self.result
245
292
# values defined in the project settings.py
246
293
settings_path = '{}.settings'.format(component_name)
247
294
os.environ["DJANGO_SETTINGS_MODULE"] = settings_path
295
kls = get_django_runner(component_base_dir)
249
296
test_runner = kls(component_base_dir, requires_south,
250
297
out, result, options)
251
298
failures = test_runner.run_tests([])
266
313
suite = loader.suiteClass()
267
314
for c in components:
268
315
suite.addTests(load_component_tests(loader, c))
316
prefix_test_ids(suite, c)
269
317
suite = filters.include_regexps(include_regexps, suite)
270
318
suite = filters.exclude_regexps(exclude_regexps, suite)
281
329
:return: The test suite for all collected tests.
283
components = ['tests']
284
331
loader = loaders.Loader()
285
332
suite = loader.suiteClass()
287
suite.addTests(load_component_tests(loader, c))
333
suite.addTests(loader.loadTestsFromTree('tests'))
288
334
suite = filters.include_regexps(include_regexps, suite)
289
335
suite = filters.exclude_regexps(exclude_regexps, suite)
293
339
def load_charm_tests(include_regexps, exclude_regexps=None):
294
340
"""Load charm unittest from <charm>/unit_test."""
295
charms = ['charms/precise/restish']
342
'charms/precise/lander',
343
'charms/precise/rabbitmq-worker',
344
'charms/precise/wsgi-app',
345
'charms/precise/webui',
346
'charms/precise/key-secret-subordinate',
296
348
loader = loaders.Loader()
297
349
suite = loader.suiteClass()
299
351
with SysPath([c, os.path.join(c, 'hooks')]):
300
352
sub_loader = loader.SubLoader(root=c)
353
charm_suite = loader.suiteClass()
301
354
# Load only python tests from 'unit_tests' (that's specific
302
355
# for uci-engine charms).
303
suite.addTests(sub_loader.loadTestsFromTree('unit_tests'))
356
charm_suite.addTests(sub_loader.loadTestsFromTree('unit_tests'))
357
# Change the test ids to embed the leading path for easier matching
358
prefix_test_ids(charm_suite, c)
359
suite.addTests(charm_suite)
304
360
suite = filters.include_regexps(include_regexps, suite)
305
361
suite = filters.exclude_regexps(exclude_regexps, suite)
392
def parallelize_suite_round_robin(suite, concurrency):
393
"""Parallelize a suite.
395
:param suite: The input suite to transform.
397
:param concurrency: The level of concurrency.
399
:returns: A suite that will spread the execution of the input suite tests
400
across ``concurrency`` forked processes. The suite is evenly split
401
across the processes in a round-robin fashion. While this doesn't take
402
into account the duration of each test, it's doing a good job on
403
average as slower tests are most often grouped by class/module in the
406
# Nothing to parallelize if there is only one process
408
suite = testtools.ConcurrentTestSuite(
409
suite, runners.fork_for_tests(concurrency))
413
class RunTestsArgParser(runners.RunTestsArgParser):
416
super(RunTestsArgParser, self).__init__('run-tests')
418
'--no-skip', action='store_true',
420
help='Errors if at lest one test is skipped.')
336
423
def main(args, stdout, stderr):
337
424
# Interpret user wishes
338
parser = runners.RunTestsArgParser()
425
parser = RunTestsArgParser()
339
426
options = parser.parse_args(args)
340
427
if options.list_only:
341
428
# We won't run tests, we won't need a test result
348
435
result = subunit.TestProtocolClient(stdout)
349
436
# We'll run tests, setup the result accordingly
350
437
result.startTestRun()
351
# Load the tests, keeping only the ones required by the user
352
440
suite = load_regular_component_tests(options.include_regexps,
353
441
options.exclude_regexps)
354
if options.concurrency > 1:
355
suite = testtools.ConcurrentTestSuite(
356
suite, runners.fork_for_tests(options.concurrency))
442
suite = parallelize_suite_round_robin(suite, options.concurrency)
359
443
reg_rc = run_regular_tests(suite, stdout, result, options)
360
444
ret = reg_rc or ret
445
# The testing infrastructure itself
446
suite = load_testing_tests(options.include_regexps,
447
options.exclude_regexps)
448
suite = parallelize_suite_round_robin(suite, options.concurrency)
449
tst_rc = run_regular_tests(suite, stdout, result, options)
361
451
# orphan tests that don't have a proper home yet
362
452
suite = load_orphan_tests(options.include_regexps,
363
453
options.exclude_regexps)
454
suite = parallelize_suite_round_robin(suite, options.concurrency)
364
455
orphan_rc = run_regular_tests(suite, stdout, result, options)
365
456
ret = orphan_rc or ret
366
# Django-based components
367
with IsolatedImportAndLogging():
368
ppa_rc = run_django_tests('ppa-assigner', 'ppa_assigner', True,
369
stdout, result, options)
371
457
with IsolatedImportAndLogging():
372
458
ts_rc = run_django_tests('ticket_system', 'ticket_system', True,
373
459
stdout, result, options)
376
462
with IsolatedImportAndLogging(), SysPath(['ci-utils']):
377
463
# FIXME: We need some more setup here that tests themselves should do
378
464
# but they aren't ripe for that yet -- vila 2014-04-11
379
branches = os.path.join(HERE, 'branches')
465
branches = os.path.join(HERE, '..', 'branches')
380
466
deploy.build_sourcedeps(branches)
381
deploy.setup(for_tests=True, list_only=options.list_only)
383
charm_suite = load_charm_tests(options.include_regexps,
384
options.exclude_regexps)
386
charm_rc = run_regular_tests(charm_suite, stdout, result, options)
387
ret = charm_rc or ret
389
suite = load_integration_tests(options.include_regexps,
390
options.exclude_regexps)
391
itg_rc = run_regular_tests(suite, stdout, result, options)
468
deploy.setup(for_tests=True, list_only=options.list_only)
470
# The setup includes checking the environment and raising
471
# SystemExit if one var is missing. Since this is entangled with
472
# building the charms, we skip the lot.
476
charm_suite = load_charm_tests(
477
options.include_regexps, options.exclude_regexps)
478
charm_rc = run_regular_tests(
479
charm_suite, stdout, result, options)
480
ret = charm_rc or ret
482
suite = load_integration_tests(
483
options.include_regexps, options.exclude_regexps)
484
itg_rc = run_regular_tests(suite, stdout, result, options)
394
487
if not options.list_only:
395
488
# We did run tests, stop the run
396
489
result.stopTestRun()
397
490
# Fails if at least one fail
491
if not options.list_only and options.no_skip and result.skip_reasons:
492
# Some tests were skipped
401
if __name__ == '__main__':
402
sys.exit(main(sys.argv[1:], sys.stdout, sys.stderr))