1 | #!/usr/bin/env python
|
---|
2 | #
|
---|
3 | # Run Django test suite in parallel
|
---|
4 | #
|
---|
5 | # Run it as: ./paralleltests.py --testdir=/path/to/django/tests --runners=<N> --settings=test_sqlite [test_labels ...]
|
---|
6 | #
|
---|
7 | # The optimal number of runners is probably the number of cores * 2
|
---|
8 | # If 'testdir' optional argument is not given, it's assumed that it is the
|
---|
9 | # current working directory.
|
---|
10 |
|
---|
11 |
|
---|
12 | from subprocess import Popen
|
---|
13 | import os
|
---|
14 | import sys
|
---|
15 | import time
|
---|
16 |
|
---|
17 | runtests = None
|
---|
18 |
|
---|
19 | MISSING_APPS = [
|
---|
20 | 'django.contrib.formtools',
|
---|
21 | 'django.contrib.webdesign',
|
---|
22 | 'django.contrib.sitemaps',
|
---|
23 | 'django.contrib.gis'
|
---|
24 | ]
|
---|
25 |
|
---|
26 |
|
---|
27 | def parse_options(argv):
|
---|
28 | global runtests
|
---|
29 |
|
---|
30 | opts = []
|
---|
31 | labels = []
|
---|
32 | n_runners = 1
|
---|
33 | testdir = '.'
|
---|
34 | for arg in argv:
|
---|
35 | if arg.startswith('--testdir='):
|
---|
36 | testdir = arg.split('=')[1]
|
---|
37 | elif arg.startswith('--runners='):
|
---|
38 | n_runners = int(arg.split('=')[1])
|
---|
39 | elif arg.startswith('-'):
|
---|
40 | opts.append(arg)
|
---|
41 | else:
|
---|
42 | labels.append(arg)
|
---|
43 |
|
---|
44 | testdir = os.path.abspath(testdir)
|
---|
45 | if not os.path.exists(os.path.join(testdir, 'runtests.py')):
|
---|
46 | raise ValueError("Directory '%s' doesn't seem to be django tests directory" % testdir)
|
---|
47 |
|
---|
48 | sys.path.insert(0, testdir)
|
---|
49 | os.chdir(testdir)
|
---|
50 |
|
---|
51 | import runtests as rt
|
---|
52 | runtests = rt
|
---|
53 |
|
---|
54 | return n_runners, labels, opts
|
---|
55 |
|
---|
56 |
|
---|
57 | def discover_tests():
|
---|
58 | test_labels = set([d for d in os.listdir(os.getcwd()) if
|
---|
59 | os.path.isdir(d) and d != '__pycache__' and
|
---|
60 | d not in runtests.SUBDIRS_TO_SKIP])
|
---|
61 | return list(test_labels | set(MISSING_APPS))
|
---|
62 |
|
---|
63 |
|
---|
64 | def split_labels(n_runners, labels):
|
---|
65 | for t in runtests.ALWAYS_INSTALLED_APPS:
|
---|
66 | labels.append(t)
|
---|
67 |
|
---|
68 | groups = []
|
---|
69 | for i in range(n_runners):
|
---|
70 | groups.append([])
|
---|
71 | for i, l in enumerate(labels):
|
---|
72 | groups[i % n_runners].append(l)
|
---|
73 |
|
---|
74 | for g in groups:
|
---|
75 | if 'model_inheritance_same_model_name' in g:
|
---|
76 | g.remove('model_inheritance_same_model_name')
|
---|
77 | if 'model_inheritance' in g:
|
---|
78 | g.append('model_inheritance_same_model_name')
|
---|
79 |
|
---|
80 | return groups
|
---|
81 |
|
---|
82 |
|
---|
83 | def run_tests(groups, extra_opts):
|
---|
84 | runners = []
|
---|
85 | for g in groups:
|
---|
86 | if len(g) == 0:
|
---|
87 | continue
|
---|
88 | cmd = ['./runtests.py'] + extra_opts + g
|
---|
89 | runners.append(Popen(cmd))
|
---|
90 | time.sleep(0.1)
|
---|
91 | return runners
|
---|
92 |
|
---|
93 |
|
---|
94 | def wait_for_tests(runners):
|
---|
95 | success = True
|
---|
96 |
|
---|
97 | while len(runners) > 0:
|
---|
98 | time.sleep(0.1)
|
---|
99 | for r in runners:
|
---|
100 | exitcode = r.poll()
|
---|
101 | if exitcode is not None:
|
---|
102 | runners.remove(r)
|
---|
103 | success = success and (exitcode == 0)
|
---|
104 | break
|
---|
105 | return success
|
---|
106 |
|
---|
107 |
|
---|
108 | def terminate_tests(runners):
|
---|
109 | for r in runners:
|
---|
110 | r.kill()
|
---|
111 |
|
---|
112 |
|
---|
113 | def run(n_runners, labels, extra_opts):
|
---|
114 | if not labels:
|
---|
115 | labels = discover_tests()
|
---|
116 | groups = split_labels(n_runners, labels)
|
---|
117 |
|
---|
118 | runners = run_tests(groups, extra_opts)
|
---|
119 | try:
|
---|
120 | success = wait_for_tests(runners)
|
---|
121 | except KeyboardInterrupt:
|
---|
122 | terminate_tests(runners)
|
---|
123 | success = False
|
---|
124 | return success
|
---|
125 |
|
---|
126 |
|
---|
127 | if __name__ == '__main__':
|
---|
128 | n_runners, labels, opts = parse_options(sys.argv[1:])
|
---|
129 | success = run(n_runners, labels, opts)
|
---|
130 | sys.exit(0 if success else -1)
|
---|