blob: f6aff6ca3887454d176c9f8d7638f7e6e4b9f4d3 [file] [log] [blame]
Mike Frysingerd0b43852021-02-12 19:04:57 -05001# Copyright 2021 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Unittests for wrapper3"""
6
7import os
8from pathlib import Path
9import sys
10from typing import List, Union
11
12from chromite.lib import constants
13from chromite.lib import cros_build_lib
14from chromite.lib import cros_test_lib
15from chromite.lib import timeout_util
16from chromite.scripts import wrapper3
17
18
19assert sys.version_info >= (3, 6), 'This module requires Python 3.6+'
20
21
22WRAPPER = Path(__file__).resolve().parent / 'wrapper3.py'
23
24
25class FindTargetTests(cros_test_lib.TempDirTestCase):
26 """Tests for FindTarget()."""
27
28 def setUp(self):
29 # It is important that the code we test is not peturbed because the funky
30 # path logic relies heavily upon it to make decisions.
31 self.assertIsNone(wrapper3.CHROMITE_PATH)
32
33 # TODO)vapier): Switch tempdir to pathlib.
34 self.tempdir = Path(self.tempdir)
35
36 # Create a skeleton chromite layout.
37 # tmpdir/
38 # chromite/
39 # bin/<wrapper>
40 # scripts/
41 # api -> <real chromite>/api/
42 # lib -> <real chromite>/lib/
43 # utils -> <real chromite>/utils/
44 # __init__.py # Marker file for Python module import.
45 # PRESUBMIT.cfg # Marker file for our wrapper to find chromite.
46 self.chromite_dir = self.tempdir / 'chromite'
47 self.bindir = self.chromite_dir / 'bin'
48 self.bindir.mkdir(parents=True)
49 self.scripts_dir = self.chromite_dir / 'scripts'
50 self.scripts_dir.mkdir()
51 for subdir in ('api', 'lib', 'utils'):
52 (self.chromite_dir / subdir).symlink_to(
53 Path(constants.CHROMITE_DIR) / subdir)
54 for subfile in ('__init__.py', 'PRESUBMIT.cfg'):
55 (self.chromite_dir / subfile).touch()
56 self.wrapper = self.scripts_dir / WRAPPER.name
57 # Copy over the wrapper. We can't just symlink it because the code also
58 # walks & resolves symlinks on itself. Try hardlink at first, but if the
59 # tempdir is on a diff mount, fallback to a copy.
60 try:
61 if sys.version_info >= (3, 8):
62 self.wrapper.link_to(WRAPPER)
63 else:
64 os.link(WRAPPER, self.wrapper)
65 except OSError:
66 self.wrapper.write_bytes(WRAPPER.read_bytes())
67 self.wrapper.chmod(0o755)
68
69 @staticmethod
70 def insert_path(var: str, value: str):
71 """Insert |value| into the start of the environment |var|."""
72 if var in os.environ:
73 value += f':{os.environ[var]}'
74 os.environ[var] = value
75
76 def gen_script(self, path: Path, wrapper: Path = None):
77 """Create a script at |path|."""
78 path.parent.mkdir(parents=True, exist_ok=True)
79 path = path.with_suffix('.py')
80 path.write_text('def main(argv):\n print("hi", argv)\n')
81 if wrapper is None:
82 wrapper = path.with_suffix('')
83 wrapper.symlink_to(self.wrapper)
84
85 def run_script(self, argv: List[Union[Path, str]], **kwargs):
86 """Run |prog| and return the output."""
87 # Log the directory layout to help with debugging.
88 try:
89 cros_build_lib.run(['tree', '-p', str(self.tempdir)], encoding='utf-8',
90 print_cmd=False)
91 except cros_build_lib.RunCommandError:
92 pass
93
94 # Helper to include a small timeout in case of bugs.
95 with timeout_util.Timeout(30):
96 return cros_build_lib.run([str(x) for x in argv], capture_output=True,
97 encoding='utf-8', **kwargs)
98
99 def _run_tests(self, prog: Path, verify=None, **kwargs):
100 """Run |prog| in the different fun ways."""
101 if verify is None:
102 verify = lambda result: self.assertEqual('hi []\n', result.output)
103
104 # Execute absolute path.
105 result = self.run_script([prog], **kwargs)
106 verify(result)
107
108 # Execute ./ relative path.
109 result = self.run_script([f'./{prog.name}'], cwd=prog.parent, **kwargs)
110 verify(result)
111
112 # Execute ./path/ relative path.
113 result = self.run_script([f'./{prog.parent.name}/{prog.name}'],
114 cwd=prog.parent.parent, **kwargs)
115 verify(result)
116
117 # Run via $PATH.
118 self.insert_path('PATH', str(prog.parent))
119 result = self.run_script([prog.name], **kwargs)
120 verify(result)
121
122 def testExternal(self):
123 """Verify use from outside of chromite/ works with main() scripts."""
124 prog = self.tempdir / 'path' / 'prog'
125 self.gen_script(prog)
126 self._run_tests(prog)
127
128 def testChromiteBin(self):
129 """Verify chromite/bin/ works with module in chromite/scripts/."""
130 prog = self.bindir / 'prog'
131 self.gen_script(self.scripts_dir / prog.name, prog)
132 self._run_tests(prog)
133
134 def testChromiteScripts(self):
135 """Verify chromite/scripts/ works with main() scripts."""
136 prog = self.scripts_dir / 'prog'
137 self.gen_script(prog)
138 self._run_tests(prog)
139
140 def testChromiteCustomdir(self):
141 """Verify chromite/customdir/ works with main() scripts."""
142 prog = self.chromite_dir / 'customdir' / 'prog'
143 self.gen_script(prog)
144 self._run_tests(prog)
145
146 def testChromiteTopdir(self):
147 """Verify chromite/ works with main() scripts."""
148 prog = self.chromite_dir / 'prog'
149 self.gen_script(prog)
150 self._run_tests(prog)
151
152 def testUnittests(self):
153 """Allow direct execution of unittests."""
154 prog = self.chromite_dir / 'subdir' / 'prog_unittest'
155 prog.parent.mkdir(parents=True, exist_ok=True)
156 path = prog.with_suffix('.py')
157 path.write_text('import sys; print("hi", sys.argv[1:])\n')
158 prog.symlink_to(self.wrapper)
159 self._run_tests(prog)
160
161 def testTests(self):
162 """Allow direct execution of tests."""
163 prog = self.chromite_dir / 'subdir' / 'prog_unittest'
164 prog.parent.mkdir(parents=True, exist_ok=True)
165 prog.symlink_to(self.wrapper)
166 prog.with_suffix('.py').write_text(
167 'import sys; print("hi", sys.argv[1:])\n')
168 self._run_tests(prog)
169
170 def testWrapper(self):
171 """Fail quickly when running the wrapper directly."""
172 verify = lambda result: self.assertEqual(result.returncode, 100)
173 self._run_tests(self.wrapper, verify=verify, check=False)
174
175 def testMissingScript(self):
176 """Fail quickly if wrapped script is missing."""
177 verify = lambda result: self.assertNotEqual(result.returncode, 0)
178 prog = self.bindir / 'prog'
179 prog.symlink_to(self.wrapper)
180 self._run_tests(prog, verify=verify, check=False)
181
182 def testBrokenScript(self):
183 """Fail quickly if wrapped script is corrupt."""
184 verify = lambda result: self.assertNotEqual(result.returncode, 0)
185 prog = self.scripts_dir / 'prog'
186 prog.symlink_to(self.wrapper)
187 # Script has syntax errors and cannot be imported.
188 prog.with_suffix('.py').write_text('}')
189 self._run_tests(prog, verify=verify, check=False)