|
1 # This Source Code Form is subject to the terms of the Mozilla Public |
|
2 # License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
|
4 |
|
5 from __future__ import unicode_literals |
|
6 |
|
7 import imp |
|
8 import json |
|
9 import os |
|
10 import shutil |
|
11 import sys |
|
12 import tempfile |
|
13 import unittest |
|
14 |
|
15 import mozpack.path as mozpath |
|
16 |
|
17 from mozwebidlcodegen import ( |
|
18 WebIDLCodegenManager, |
|
19 WebIDLCodegenManagerState, |
|
20 ) |
|
21 |
|
22 from mozfile import NamedTemporaryFile |
|
23 |
|
24 from mozunit import ( |
|
25 MockedOpen, |
|
26 main, |
|
27 ) |
|
28 |
|
29 |
|
30 OUR_DIR = mozpath.abspath(mozpath.dirname(__file__)) |
|
31 TOPSRCDIR = mozpath.normpath(mozpath.join(OUR_DIR, '..', '..', '..', '..')) |
|
32 |
|
33 |
|
34 class TestWebIDLCodegenManager(unittest.TestCase): |
|
35 TEST_STEMS = { |
|
36 'Child', |
|
37 'Parent', |
|
38 'ExampleBinding', |
|
39 'TestEvent', |
|
40 } |
|
41 |
|
42 @property |
|
43 def _static_input_paths(self): |
|
44 s = {mozpath.join(OUR_DIR, p) for p in os.listdir(OUR_DIR) |
|
45 if p.endswith('.webidl')} |
|
46 |
|
47 return s |
|
48 |
|
49 @property |
|
50 def _config_path(self): |
|
51 config = mozpath.join(TOPSRCDIR, 'dom', 'bindings', 'Bindings.conf') |
|
52 self.assertTrue(os.path.exists(config)) |
|
53 |
|
54 return config |
|
55 |
|
56 def _get_manager_args(self): |
|
57 tmp = tempfile.mkdtemp() |
|
58 self.addCleanup(shutil.rmtree, tmp) |
|
59 |
|
60 cache_dir = mozpath.join(tmp, 'cache') |
|
61 os.mkdir(cache_dir) |
|
62 |
|
63 ip = self._static_input_paths |
|
64 |
|
65 inputs = ( |
|
66 ip, |
|
67 {mozpath.splitext(mozpath.basename(p))[0] for p in ip}, |
|
68 set(), |
|
69 set(), |
|
70 ) |
|
71 |
|
72 return dict( |
|
73 config_path=self._config_path, |
|
74 inputs=inputs, |
|
75 exported_header_dir=mozpath.join(tmp, 'exports'), |
|
76 codegen_dir=mozpath.join(tmp, 'codegen'), |
|
77 state_path=mozpath.join(tmp, 'state.json'), |
|
78 make_deps_path=mozpath.join(tmp, 'codegen.pp'), |
|
79 make_deps_target='codegen.pp', |
|
80 cache_dir=cache_dir, |
|
81 ) |
|
82 |
|
83 def _get_manager(self): |
|
84 return WebIDLCodegenManager(**self._get_manager_args()) |
|
85 |
|
86 def test_unknown_state_version(self): |
|
87 """Loading a state file with a too new version resets state.""" |
|
88 args = self._get_manager_args() |
|
89 |
|
90 p = args['state_path'] |
|
91 |
|
92 with open(p, 'wb') as fh: |
|
93 json.dump({ |
|
94 'version': WebIDLCodegenManagerState.VERSION + 1, |
|
95 'foobar': '1', |
|
96 }, fh) |
|
97 |
|
98 manager = WebIDLCodegenManager(**args) |
|
99 |
|
100 self.assertEqual(manager._state['version'], |
|
101 WebIDLCodegenManagerState.VERSION) |
|
102 self.assertNotIn('foobar', manager._state) |
|
103 |
|
104 def test_generate_build_files(self): |
|
105 """generate_build_files() does the right thing from empty.""" |
|
106 manager = self._get_manager() |
|
107 result = manager.generate_build_files() |
|
108 self.assertEqual(len(result.inputs), 5) |
|
109 |
|
110 output = manager.expected_build_output_files() |
|
111 self.assertEqual(result.created, output) |
|
112 self.assertEqual(len(result.updated), 0) |
|
113 self.assertEqual(len(result.unchanged), 0) |
|
114 |
|
115 for f in output: |
|
116 self.assertTrue(os.path.isfile(f)) |
|
117 |
|
118 for f in manager.GLOBAL_DECLARE_FILES: |
|
119 self.assertIn(mozpath.join(manager._exported_header_dir, f), output) |
|
120 |
|
121 for f in manager.GLOBAL_DEFINE_FILES: |
|
122 self.assertIn(mozpath.join(manager._codegen_dir, f), output) |
|
123 |
|
124 for s in self.TEST_STEMS: |
|
125 self.assertTrue(os.path.isfile(mozpath.join( |
|
126 manager._exported_header_dir, '%sBinding.h' % s))) |
|
127 self.assertTrue(os.path.isfile(mozpath.join( |
|
128 manager._codegen_dir, '%sBinding.cpp' % s))) |
|
129 |
|
130 self.assertTrue(os.path.isfile(manager._state_path)) |
|
131 |
|
132 with open(manager._state_path, 'rb') as fh: |
|
133 state = json.load(fh) |
|
134 self.assertEqual(state['version'], 1) |
|
135 self.assertIn('webidls', state) |
|
136 |
|
137 child = state['webidls']['Child.webidl'] |
|
138 self.assertEqual(len(child['inputs']), 2) |
|
139 self.assertEqual(len(child['outputs']), 2) |
|
140 self.assertEqual(child['sha1'], 'c41527cad3bc161fa6e7909e48fa11f9eca0468b') |
|
141 |
|
142 def test_generate_build_files_load_state(self): |
|
143 """State should be equivalent when instantiating a new instance.""" |
|
144 args = self._get_manager_args() |
|
145 m1 = WebIDLCodegenManager(**args) |
|
146 self.assertEqual(len(m1._state['webidls']), 0) |
|
147 m1.generate_build_files() |
|
148 |
|
149 m2 = WebIDLCodegenManager(**args) |
|
150 self.assertGreater(len(m2._state['webidls']), 2) |
|
151 self.assertEqual(m1._state, m2._state) |
|
152 |
|
153 def test_no_change_no_writes(self): |
|
154 """If nothing changes, no files should be updated.""" |
|
155 args = self._get_manager_args() |
|
156 m1 = WebIDLCodegenManager(**args) |
|
157 m1.generate_build_files() |
|
158 |
|
159 m2 = WebIDLCodegenManager(**args) |
|
160 result = m2.generate_build_files() |
|
161 |
|
162 self.assertEqual(len(result.inputs), 0) |
|
163 self.assertEqual(len(result.created), 0) |
|
164 self.assertEqual(len(result.updated), 0) |
|
165 |
|
166 def test_output_file_regenerated(self): |
|
167 """If an output file disappears, it is regenerated.""" |
|
168 args = self._get_manager_args() |
|
169 m1 = WebIDLCodegenManager(**args) |
|
170 m1.generate_build_files() |
|
171 |
|
172 rm_count = 0 |
|
173 for p in m1._state['webidls']['Child.webidl']['outputs']: |
|
174 rm_count += 1 |
|
175 os.unlink(p) |
|
176 |
|
177 for p in m1.GLOBAL_DECLARE_FILES: |
|
178 rm_count += 1 |
|
179 os.unlink(mozpath.join(m1._exported_header_dir, p)) |
|
180 |
|
181 m2 = WebIDLCodegenManager(**args) |
|
182 result = m2.generate_build_files() |
|
183 self.assertEqual(len(result.created), rm_count) |
|
184 |
|
185 def test_only_rebuild_self(self): |
|
186 """If an input file changes, only rebuild that one file.""" |
|
187 args = self._get_manager_args() |
|
188 m1 = WebIDLCodegenManager(**args) |
|
189 m1.generate_build_files() |
|
190 |
|
191 child_path = None |
|
192 for p in m1._input_paths: |
|
193 if p.endswith('Child.webidl'): |
|
194 child_path = p |
|
195 break |
|
196 |
|
197 self.assertIsNotNone(child_path) |
|
198 child_content = open(child_path, 'rb').read() |
|
199 |
|
200 with MockedOpen({child_path: child_content + '\n/* */'}): |
|
201 m2 = WebIDLCodegenManager(**args) |
|
202 result = m2.generate_build_files() |
|
203 self.assertEqual(result.inputs, set([child_path])) |
|
204 self.assertEqual(len(result.updated), 0) |
|
205 self.assertEqual(len(result.created), 0) |
|
206 |
|
207 def test_rebuild_dependencies(self): |
|
208 """Ensure an input file used by others results in others rebuilding.""" |
|
209 args = self._get_manager_args() |
|
210 m1 = WebIDLCodegenManager(**args) |
|
211 m1.generate_build_files() |
|
212 |
|
213 parent_path = None |
|
214 child_path = None |
|
215 for p in m1._input_paths: |
|
216 if p.endswith('Parent.webidl'): |
|
217 parent_path = p |
|
218 elif p.endswith('Child.webidl'): |
|
219 child_path = p |
|
220 |
|
221 self.assertIsNotNone(parent_path) |
|
222 parent_content = open(parent_path, 'rb').read() |
|
223 |
|
224 with MockedOpen({parent_path: parent_content + '\n/* */'}): |
|
225 m2 = WebIDLCodegenManager(**args) |
|
226 result = m2.generate_build_files() |
|
227 self.assertEqual(result.inputs, {child_path, parent_path}) |
|
228 self.assertEqual(len(result.updated), 0) |
|
229 self.assertEqual(len(result.created), 0) |
|
230 |
|
231 def test_python_change_regenerate_everything(self): |
|
232 """If a Python file changes, we should attempt to rebuild everything.""" |
|
233 |
|
234 # We don't want to mutate files in the source directory because we want |
|
235 # to be able to build from a read-only filesystem. So, we install a |
|
236 # dummy module and rewrite the metadata to say it comes from the source |
|
237 # directory. |
|
238 # |
|
239 # Hacking imp to accept a MockedFile doesn't appear possible. So for |
|
240 # the first iteration we read from a temp file. The second iteration |
|
241 # doesn't need to import, so we are fine with a mocked file. |
|
242 fake_path = mozpath.join(OUR_DIR, 'fakemodule.py') |
|
243 with NamedTemporaryFile('wt') as fh: |
|
244 fh.write('# Original content') |
|
245 fh.flush() |
|
246 mod = imp.load_source('mozwebidlcodegen.fakemodule', fh.name) |
|
247 mod.__file__ = fake_path |
|
248 |
|
249 args = self._get_manager_args() |
|
250 m1 = WebIDLCodegenManager(**args) |
|
251 with MockedOpen({fake_path: '# Original content'}): |
|
252 old_exists = os.path.exists |
|
253 try: |
|
254 def exists(p): |
|
255 if p == fake_path: |
|
256 return True |
|
257 return old_exists(p) |
|
258 |
|
259 os.path.exists = exists |
|
260 |
|
261 result = m1.generate_build_files() |
|
262 l = len(result.inputs) |
|
263 |
|
264 with open(fake_path, 'wt') as fh: |
|
265 fh.write('# Modified content') |
|
266 |
|
267 m2 = WebIDLCodegenManager(**args) |
|
268 result = m2.generate_build_files() |
|
269 self.assertEqual(len(result.inputs), l) |
|
270 |
|
271 result = m2.generate_build_files() |
|
272 self.assertEqual(len(result.inputs), 0) |
|
273 finally: |
|
274 os.path.exists = old_exists |
|
275 del sys.modules['mozwebidlcodegen.fakemodule'] |
|
276 |
|
277 def test_copy_input(self): |
|
278 """Ensure a copied .webidl file is handled properly.""" |
|
279 |
|
280 # This test simulates changing the type of a WebIDL from static to |
|
281 # preprocessed. In that scenario, the original file still exists but |
|
282 # it should no longer be consulted during codegen. |
|
283 |
|
284 args = self._get_manager_args() |
|
285 m1 = WebIDLCodegenManager(**args) |
|
286 m1.generate_build_files() |
|
287 |
|
288 old_path = None |
|
289 for p in args['inputs'][0]: |
|
290 if p.endswith('Parent.webidl'): |
|
291 old_path = p |
|
292 break |
|
293 self.assertIsNotNone(old_path) |
|
294 |
|
295 new_path = mozpath.join(args['cache_dir'], 'Parent.webidl') |
|
296 shutil.copy2(old_path, new_path) |
|
297 |
|
298 args['inputs'][0].remove(old_path) |
|
299 args['inputs'][0].add(new_path) |
|
300 |
|
301 m2 = WebIDLCodegenManager(**args) |
|
302 result = m2.generate_build_files() |
|
303 self.assertEqual(len(result.updated), 0) |
|
304 |
|
305 |
|
306 if __name__ == '__main__': |
|
307 main() |