OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright 2015 The Chromium Authors. All rights reserved. | 2 # Copyright 2015 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """Tests generated CIPD packages. | 6 """Tests generated CIPD packages. |
7 | 7 |
8 Supposed to be invoked after build.py has run. Uses packages from out/*.cipd and | 8 Supposed to be invoked after build.py has run. Uses packages from out/*.cipd and |
9 tests from tests/*.py. | 9 tests from tests/*.py. |
10 | 10 |
11 Assumes cipd client is built in ../go/bin/cipd (true after build.py has run). | 11 Assumes cipd client is built in out/.cipd_client/cipd_* (true after build.py has |
| 12 run). |
12 """ | 13 """ |
13 | 14 |
14 import argparse | 15 import argparse |
15 import glob | |
16 import os | 16 import os |
17 import re | 17 import re |
18 import shutil | 18 import shutil |
19 import subprocess | 19 import subprocess |
20 import sys | 20 import sys |
21 import tempfile | 21 import tempfile |
22 | 22 |
23 | 23 |
24 # Root of infra.git repository. | 24 # Root of infra.git repository. |
25 ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | 25 ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
(...skipping 19 matching lines...) Expand all Loading... |
45 def get_docstring(test_script): | 45 def get_docstring(test_script): |
46 """Hacky way to grab a first line of a module docstring using regexps.""" | 46 """Hacky way to grab a first line of a module docstring using regexps.""" |
47 with open(test_script, 'rt') as f: | 47 with open(test_script, 'rt') as f: |
48 text = f.read() | 48 text = f.read() |
49 m = re.match(r'^.*"""(.*?)"""', text, re.DOTALL) | 49 m = re.match(r'^.*"""(.*?)"""', text, re.DOTALL) |
50 if not m: | 50 if not m: |
51 return None | 51 return None |
52 return m.group(1).strip().splitlines()[0] | 52 return m.group(1).strip().splitlines()[0] |
53 | 53 |
54 | 54 |
| 55 def find_cipd_client(out_dir): |
| 56 """Returns path to cipd client built by build.py. |
| 57 |
| 58 See build_cipd_client in build.py. It puts cipd client into |
| 59 '<out_dir>/.cipd_client/cipd_<digest>' and there's only one such file there. |
| 60 |
| 61 Prints error message and returns None if the file cannot be found. |
| 62 """ |
| 63 out_dir = os.path.join(out_dir, '.cipd_client') |
| 64 files = [f for f in os.listdir(out_dir) if f.startswith('cipd_')] |
| 65 if not files: |
| 66 print >> sys.stderr, 'Cannot find CIPD client in %s' % out_dir |
| 67 return None |
| 68 if len(files) != 1: |
| 69 print >> sys.stderr, ( |
| 70 'There should be only one cipd client binary in %s, found %s' % |
| 71 (out_dir, files)) |
| 72 return None |
| 73 cipd_client = os.path.join(out_dir, files[0]) |
| 74 if not os.access(cipd_client, os.X_OK): |
| 75 print >> sys.stderr, 'CIPD client at %s is not runnable' |
| 76 return None |
| 77 return cipd_client |
| 78 |
| 79 |
55 def run_test(cipd_client, package, work_dir, test_script): | 80 def run_test(cipd_client, package, work_dir, test_script): |
56 """Extracts a package to a dir and runs test_script with cwd == work_dir.""" | 81 """Extracts a package to a dir and runs test_script with cwd == work_dir.""" |
57 print_title('Deploying %s' % os.path.basename(package)) | 82 print_title('Deploying %s' % os.path.basename(package)) |
58 if not os.access(cipd_client, os.X_OK): | |
59 print >> sys.stderr, ( | |
60 'CIPD client at %s doesn\'t exist or not runnable. Run build.py to ' | |
61 'build it.' % cipd_client) | |
62 return 1 | |
63 cmd_line = ['cipd', 'pkg-deploy', '-root', work_dir, package] | 83 cmd_line = ['cipd', 'pkg-deploy', '-root', work_dir, package] |
64 print ' '.join(cmd_line) | 84 print ' '.join(cmd_line) |
65 if subprocess.call(args=cmd_line, executable=cipd_client): | 85 if subprocess.call(args=cmd_line, executable=cipd_client): |
66 raise TestException('Failed to install %s, see logs' % package) | 86 raise TestException('Failed to install %s, see logs' % package) |
67 | 87 |
68 print_title(get_docstring(test_script) or 'Running tests...') | 88 print_title(get_docstring(test_script) or 'Running tests...') |
69 cmd_line = ['python', test_script] | 89 cmd_line = ['python', test_script] |
70 print '%s in %s' % (' '.join(cmd_line), work_dir) | 90 print '%s in %s' % (' '.join(cmd_line), work_dir) |
71 env = os.environ.copy() | 91 env = os.environ.copy() |
72 env.pop('PYTHONPATH', None) | 92 env.pop('PYTHONPATH', None) |
73 ret = subprocess.call( | 93 ret = subprocess.call( |
74 args=cmd_line, executable=sys.executable, env=env, cwd=work_dir) | 94 args=cmd_line, executable=sys.executable, env=env, cwd=work_dir) |
75 if ret: | 95 if ret: |
76 raise TestException('Non zero exit code (%d)' % ret) | 96 raise TestException('Non zero exit code (%d)' % ret) |
77 | 97 |
78 | 98 |
79 def run( | 99 def run( |
80 cipd_client, | |
81 package_out_dir, | 100 package_out_dir, |
82 package_tests_dir, | 101 package_tests_dir, |
83 work_dir, | 102 work_dir, |
84 packages): | 103 packages): |
85 """Deployes build *.cipd package locally and runs tests against them. | 104 """Deployes build *.cipd package locally and runs tests against them. |
86 | 105 |
87 Used to verify the packaged code works when installed as CIPD package, it is | 106 Used to verify the packaged code works when installed as CIPD package, it is |
88 important for infra_python package that has non-trivial structure. | 107 important for infra_python package that has non-trivial structure. |
89 | 108 |
90 Args: | 109 Args: |
91 cipd_client: path to cipd client executable. | |
92 package_out_dir: where to search for built packages. | 110 package_out_dir: where to search for built packages. |
93 work_dir: where to install/update packages into. | 111 work_dir: where to install/update packages into. |
94 packages: names of *.cipd files in package_out_dir or [] for all. | 112 packages: names of *.cipd files in package_out_dir or [] for all. |
95 | 113 |
96 Returns: | 114 Returns: |
97 0 on success, 1 or error. | 115 0 on success, 1 or error. |
98 """ | 116 """ |
99 # Discover what to test. | 117 # Discover what to test. |
100 paths = [] | 118 paths = [] |
101 if not packages: | 119 if not packages: |
102 paths = glob.glob(os.path.join(package_out_dir, '*.cipd')) | 120 # Enumerate all known tests in tests/*.py and filter them based on |
| 121 # availability of corresponding *.cipd package in package_out_dir. It will |
| 122 # skip any cross-compiled packages, since they have additional '+<platform>' |
| 123 # suffix in the package file name. |
| 124 for test in os.listdir(package_tests_dir): |
| 125 if not test.endswith('.py'): |
| 126 continue |
| 127 pkg_file = os.path.join( |
| 128 package_out_dir, os.path.splitext(test)[0] + '.cipd') |
| 129 if os.path.exists(pkg_file): |
| 130 paths.append(pkg_file) |
103 else: | 131 else: |
104 for name in packages: | 132 for name in packages: |
105 abs_path = os.path.join(package_out_dir, name) | 133 abs_path = os.path.join(package_out_dir, name) |
106 if not os.path.isfile(abs_path): | 134 if not os.path.isfile(abs_path): |
107 raise TestException('No such package file: %s' % name) | 135 raise TestException('No such package file: %s' % name) |
108 paths.append(abs_path) | 136 paths.append(abs_path) |
109 paths = sorted(paths) | 137 paths = sorted(paths) |
110 if not paths: | 138 if not paths: |
111 print 'Nothing to test.' | 139 print 'Nothing to test.' |
112 return 0 | 140 return 0 |
113 | 141 |
114 # Run all tests sequentially. There're like 2 of them tops. | 142 cipd_client = find_cipd_client(package_out_dir) |
| 143 if not cipd_client: |
| 144 return 1 |
| 145 |
| 146 # Run all tests sequentially. Most of the are extra fast. |
115 nuke_temp = False | 147 nuke_temp = False |
116 if not work_dir: | 148 if not work_dir: |
117 work_dir = tempfile.mkdtemp(suffix='cipd_test') | 149 work_dir = tempfile.mkdtemp(suffix='cipd_test') |
118 nuke_temp = True | 150 nuke_temp = True |
119 work_dir = os.path.abspath(work_dir) | 151 work_dir = os.path.abspath(work_dir) |
120 try: | 152 try: |
121 fail = False | 153 fail = False |
122 for path in paths: | 154 for path in paths: |
123 name = os.path.splitext(os.path.basename(path))[0] | 155 name = os.path.splitext(os.path.basename(path))[0] |
124 test_script = os.path.join(package_tests_dir, '%s.py' % name) | 156 test_script = os.path.join(package_tests_dir, '%s.py' % name) |
(...skipping 17 matching lines...) Expand all Loading... |
142 finally: | 174 finally: |
143 if nuke_temp: | 175 if nuke_temp: |
144 try: | 176 try: |
145 shutil.rmtree(work_dir, ignore_errors=True) | 177 shutil.rmtree(work_dir, ignore_errors=True) |
146 except OSError as exc: | 178 except OSError as exc: |
147 print >> sys.stderr, 'Failed to delete %s: %s' % (work_dir, exc) | 179 print >> sys.stderr, 'Failed to delete %s: %s' % (work_dir, exc) |
148 | 180 |
149 | 181 |
150 def main( | 182 def main( |
151 args, | 183 args, |
152 go_workspace=os.path.join(ROOT, 'go'), | |
153 package_out_dir=os.path.join(ROOT, 'build', 'out'), | 184 package_out_dir=os.path.join(ROOT, 'build', 'out'), |
154 package_tests_dir=os.path.join(ROOT, 'build', 'tests')): | 185 package_tests_dir=os.path.join(ROOT, 'build', 'tests')): |
155 parser = argparse.ArgumentParser(description='Tests infra CIPD packages') | 186 parser = argparse.ArgumentParser(description='Tests infra CIPD packages') |
156 parser.add_argument( | 187 parser.add_argument( |
157 'packages', metavar='NAME', type=str, nargs='*', | 188 'packages', metavar='NAME', type=str, nargs='*', |
158 help='name of a build package file in build/out/* to deploy and test') | 189 help='name of a built package file in build/out/* to deploy and test') |
159 parser.add_argument( | 190 parser.add_argument( |
160 '--work-dir', metavar='DIR', dest='work_dir', | 191 '--work-dir', metavar='DIR', dest='work_dir', |
161 help='directory to deploy packages into (temporary dir by default)') | 192 help='directory to deploy packages into (temporary dir by default)') |
162 args = parser.parse_args(args) | 193 args = parser.parse_args(args) |
163 return run( | 194 return run( |
164 os.path.join(go_workspace, 'bin', 'cipd' + EXE_SUFFIX), | |
165 package_out_dir, | 195 package_out_dir, |
166 package_tests_dir, | 196 package_tests_dir, |
167 args.work_dir, | 197 args.work_dir, |
168 [n + '.cipd' if not n.endswith('.cipd') else n for n in args.packages]) | 198 [n + '.cipd' if not n.endswith('.cipd') else n for n in args.packages]) |
169 | 199 |
170 | 200 |
171 if __name__ == '__main__': | 201 if __name__ == '__main__': |
172 sys.exit(main(sys.argv[1:])) | 202 sys.exit(main(sys.argv[1:])) |
OLD | NEW |