This new script does not depend on ansible-test and provides much more robust job matrix testing.
It is also run on every job in the matrix now, to detect issues with jobs being re-run after matrix changes are made.
(cherry picked from commit d3da8e4a5b7c87ea6bb4f1345300ddb0a833a6b2)
1 | 1 |
deleted file mode 100755 |
... | ... |
@@ -1,107 +0,0 @@ |
1 |
-#!/usr/bin/env python |
|
2 |
-# PYTHON_ARGCOMPLETE_OK |
|
3 |
-"""Verify the current Shippable run has the required number of jobs.""" |
|
4 |
- |
|
5 |
-from __future__ import absolute_import, print_function |
|
6 |
- |
|
7 |
-# noinspection PyCompatibility |
|
8 |
-import argparse |
|
9 |
-import errno |
|
10 |
-import json |
|
11 |
-import os |
|
12 |
-import sys |
|
13 |
- |
|
14 |
-from lib.http import ( |
|
15 |
- HttpClient, |
|
16 |
-) |
|
17 |
- |
|
18 |
-from lib.util import ( |
|
19 |
- display, |
|
20 |
- ApplicationError, |
|
21 |
- ApplicationWarning, |
|
22 |
- MissingEnvironmentVariable, |
|
23 |
-) |
|
24 |
- |
|
25 |
- |
|
26 |
-try: |
|
27 |
- import argcomplete |
|
28 |
-except ImportError: |
|
29 |
- argcomplete = None |
|
30 |
- |
|
31 |
- |
|
32 |
-def main(): |
|
33 |
- """Main program function.""" |
|
34 |
- try: |
|
35 |
- args = parse_args() |
|
36 |
- display.verbosity = args.verbosity |
|
37 |
- display.color = args.color |
|
38 |
- |
|
39 |
- try: |
|
40 |
- run_id = os.environ['SHIPPABLE_BUILD_ID'] |
|
41 |
- except KeyError as ex: |
|
42 |
- raise MissingEnvironmentVariable(ex.args[0]) |
|
43 |
- |
|
44 |
- client = HttpClient(args) |
|
45 |
- response = client.get('https://api.shippable.com/jobs?runIds=%s' % run_id) |
|
46 |
- jobs = response.json() |
|
47 |
- |
|
48 |
- if not isinstance(jobs, list): |
|
49 |
- raise ApplicationError(json.dumps(jobs, indent=4, sort_keys=True)) |
|
50 |
- |
|
51 |
- if len(jobs) == 1: |
|
52 |
- raise ApplicationError('Shippable run %s has only one job. Did you use the "Rebuild with SSH" option?' % run_id) |
|
53 |
- except ApplicationWarning as ex: |
|
54 |
- display.warning(str(ex)) |
|
55 |
- exit(0) |
|
56 |
- except ApplicationError as ex: |
|
57 |
- display.error(str(ex)) |
|
58 |
- exit(1) |
|
59 |
- except KeyboardInterrupt: |
|
60 |
- exit(2) |
|
61 |
- except IOError as ex: |
|
62 |
- if ex.errno == errno.EPIPE: |
|
63 |
- exit(3) |
|
64 |
- raise |
|
65 |
- |
|
66 |
- |
|
67 |
-def parse_args(): |
|
68 |
- """Parse command line arguments.""" |
|
69 |
- parser = argparse.ArgumentParser() |
|
70 |
- |
|
71 |
- parser.add_argument('-e', '--explain', |
|
72 |
- action='store_true', |
|
73 |
- help='explain commands that would be executed') |
|
74 |
- |
|
75 |
- parser.add_argument('-v', '--verbose', |
|
76 |
- dest='verbosity', |
|
77 |
- action='count', |
|
78 |
- default=0, |
|
79 |
- help='display more output') |
|
80 |
- |
|
81 |
- parser.add_argument('--color', |
|
82 |
- metavar='COLOR', |
|
83 |
- nargs='?', |
|
84 |
- help='generate color output: %(choices)s', |
|
85 |
- choices=('yes', 'no', 'auto'), |
|
86 |
- const='yes', |
|
87 |
- default='auto') |
|
88 |
- |
|
89 |
- if argcomplete: |
|
90 |
- argcomplete.autocomplete(parser) |
|
91 |
- |
|
92 |
- args = parser.parse_args() |
|
93 |
- |
|
94 |
- if args.color == 'yes': |
|
95 |
- args.color = True |
|
96 |
- elif args.color == 'no': |
|
97 |
- args.color = False |
|
98 |
- elif 'SHIPPABLE' in os.environ: |
|
99 |
- args.color = True |
|
100 |
- else: |
|
101 |
- args.color = sys.stdout.isatty() |
|
102 |
- |
|
103 |
- return args |
|
104 |
- |
|
105 |
- |
|
106 |
-if __name__ == '__main__': |
|
107 |
- main() |
... | ... |
@@ -12,6 +12,7 @@ def main(): |
12 | 12 |
'lib/ansible/module_utils/urls.py', |
13 | 13 |
'test/units/module_utils/urls/test_Request.py', |
14 | 14 |
'test/units/module_utils/urls/test_fetch_url.py', |
15 |
+ 'test/utils/shippable/check_matrix.py', |
|
15 | 16 |
]) |
16 | 17 |
|
17 | 18 |
for path in sys.argv[1:] or sys.stdin.read().splitlines(): |
18 | 19 |
new file mode 100755 |
... | ... |
@@ -0,0 +1,107 @@ |
0 |
+#!/usr/bin/env python |
|
1 |
+"""Verify the currently executing Shippable test matrix matches the one defined in the "shippable.yml" file.""" |
|
2 |
+from __future__ import (absolute_import, division, print_function) |
|
3 |
+__metaclass__ = type |
|
4 |
+ |
|
5 |
+import datetime |
|
6 |
+import json |
|
7 |
+import os |
|
8 |
+import re |
|
9 |
+import sys |
|
10 |
+import time |
|
11 |
+ |
|
12 |
+try: |
|
13 |
+ from typing import NoReturn |
|
14 |
+except ImportError: |
|
15 |
+ NoReturn = None |
|
16 |
+ |
|
17 |
+try: |
|
18 |
+ # noinspection PyCompatibility |
|
19 |
+ from urllib2 import urlopen # pylint: disable=ansible-bad-import-from |
|
20 |
+except ImportError: |
|
21 |
+ # noinspection PyCompatibility |
|
22 |
+ from urllib.request import urlopen |
|
23 |
+ |
|
24 |
+ |
|
25 |
+def main(): # type: () -> None |
|
26 |
+ """Main entry point.""" |
|
27 |
+ with open('shippable.yml', 'rb') as yaml_file: |
|
28 |
+ yaml = yaml_file.read().decode('utf-8').splitlines() |
|
29 |
+ |
|
30 |
+ defined_matrix = [match.group(1) for match in [re.search(r'^ *- env: T=(.*)$', line) for line in yaml] if match and match.group(1) != 'none'] |
|
31 |
+ |
|
32 |
+ if not defined_matrix: |
|
33 |
+ fail('No matrix entries found in the "shippable.yml" file.', |
|
34 |
+ 'Did you modify the "shippable.yml" file?') |
|
35 |
+ |
|
36 |
+ run_id = os.environ['SHIPPABLE_BUILD_ID'] |
|
37 |
+ sleep = 1 |
|
38 |
+ jobs = [] |
|
39 |
+ |
|
40 |
+ for attempts_remaining in range(4, -1, -1): |
|
41 |
+ try: |
|
42 |
+ jobs = json.loads(urlopen('https://api.shippable.com/jobs?runIds=%s' % run_id).read()) |
|
43 |
+ |
|
44 |
+ if not isinstance(jobs, list): |
|
45 |
+ raise Exception('Shippable run %s data is not a list.' % run_id) |
|
46 |
+ |
|
47 |
+ break |
|
48 |
+ except Exception as ex: |
|
49 |
+ if not attempts_remaining: |
|
50 |
+ fail('Unable to retrieve Shippable run %s matrix.' % run_id, |
|
51 |
+ str(ex)) |
|
52 |
+ |
|
53 |
+ sys.stderr.write('Unable to retrieve Shippable run %s matrix: %s\n' % (run_id, ex)) |
|
54 |
+ sys.stderr.write('Trying again in %d seconds...\n' % sleep) |
|
55 |
+ time.sleep(sleep) |
|
56 |
+ sleep *= 2 |
|
57 |
+ |
|
58 |
+ if len(jobs) != len(defined_matrix): |
|
59 |
+ if len(jobs) == 1: |
|
60 |
+ hint = '\n\nMake sure you do not use the "Rebuild with SSH" option.' |
|
61 |
+ else: |
|
62 |
+ hint = '' |
|
63 |
+ |
|
64 |
+ fail('Shippable run %s has %d jobs instead of the expected %d jobs.' % (run_id, len(jobs), len(defined_matrix)), |
|
65 |
+ 'Try re-running the entire matrix.%s' % hint) |
|
66 |
+ |
|
67 |
+ actual_matrix = dict((job.get('jobNumber'), dict(tuple(line.split('=', 1)) for line in job.get('env', [])).get('T', '')) for job in jobs) |
|
68 |
+ errors = [(job_number, test, actual_matrix.get(job_number)) for job_number, test in enumerate(defined_matrix, 1) if actual_matrix.get(job_number) != test] |
|
69 |
+ |
|
70 |
+ if len(errors): |
|
71 |
+ error_summary = '\n'.join('Job %s expected "%s" but found "%s" instead.' % (job_number, expected, actual) for job_number, expected, actual in errors) |
|
72 |
+ |
|
73 |
+ fail('Shippable run %s has a job matrix mismatch.' % run_id, |
|
74 |
+ 'Try re-running the entire matrix.\n\n%s' % error_summary) |
|
75 |
+ |
|
76 |
+ |
|
77 |
+def fail(message, output): # type: (str, str) -> NoReturn |
|
78 |
+ # Include a leading newline to improve readability on Shippable "Tests" tab. |
|
79 |
+ # Without this, the first line becomes indented. |
|
80 |
+ output = '\n' + output.strip() |
|
81 |
+ |
|
82 |
+ timestamp = datetime.datetime.utcnow().replace(microsecond=0).isoformat() |
|
83 |
+ |
|
84 |
+ # hack to avoid requiring junit-xml, which isn't pre-installed on Shippable outside our test containers |
|
85 |
+ xml = ''' |
|
86 |
+<?xml version="1.0" encoding="utf-8"?> |
|
87 |
+<testsuites disabled="0" errors="1" failures="0" tests="1" time="0.0"> |
|
88 |
+\t<testsuite disabled="0" errors="1" failures="0" file="None" log="None" name="ansible-test" skipped="0" tests="1" time="0" timestamp="%s" url="None"> |
|
89 |
+\t\t<testcase classname="timeout" name="timeout"> |
|
90 |
+\t\t\t<error message="%s" type="error">%s</error> |
|
91 |
+\t\t</testcase> |
|
92 |
+\t</testsuite> |
|
93 |
+</testsuites> |
|
94 |
+''' % (timestamp, message, output) |
|
95 |
+ |
|
96 |
+ with open('test/results/junit/check-matrix.xml', 'w') as junit_fd: |
|
97 |
+ junit_fd.write(xml.lstrip()) |
|
98 |
+ |
|
99 |
+ sys.stderr.write(message + '\n') |
|
100 |
+ sys.stderr.write(output + '\n') |
|
101 |
+ |
|
102 |
+ sys.exit(1) |
|
103 |
+ |
|
104 |
+ |
|
105 |
+if __name__ == '__main__': |
|
106 |
+ main() |