michael@0: # This Source Code Form is subject to the terms of the Mozilla Public michael@0: # License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: # file, You can obtain one at http://mozilla.org/MPL/2.0/. michael@0: michael@0: from __future__ import unicode_literals michael@0: michael@0: import imp michael@0: import json michael@0: import os michael@0: import shutil michael@0: import sys michael@0: import tempfile michael@0: import unittest michael@0: michael@0: import mozpack.path as mozpath michael@0: michael@0: from mozwebidlcodegen import ( michael@0: WebIDLCodegenManager, michael@0: WebIDLCodegenManagerState, michael@0: ) michael@0: michael@0: from mozfile import NamedTemporaryFile michael@0: michael@0: from mozunit import ( michael@0: MockedOpen, michael@0: main, michael@0: ) michael@0: michael@0: michael@0: OUR_DIR = mozpath.abspath(mozpath.dirname(__file__)) michael@0: TOPSRCDIR = mozpath.normpath(mozpath.join(OUR_DIR, '..', '..', '..', '..')) michael@0: michael@0: michael@0: class TestWebIDLCodegenManager(unittest.TestCase): michael@0: TEST_STEMS = { michael@0: 'Child', michael@0: 'Parent', michael@0: 'ExampleBinding', michael@0: 'TestEvent', michael@0: } michael@0: michael@0: @property michael@0: def _static_input_paths(self): michael@0: s = {mozpath.join(OUR_DIR, p) for p in os.listdir(OUR_DIR) michael@0: if p.endswith('.webidl')} michael@0: michael@0: return s michael@0: michael@0: @property michael@0: def _config_path(self): michael@0: config = mozpath.join(TOPSRCDIR, 'dom', 'bindings', 'Bindings.conf') michael@0: self.assertTrue(os.path.exists(config)) michael@0: michael@0: return config michael@0: michael@0: def _get_manager_args(self): michael@0: tmp = tempfile.mkdtemp() michael@0: self.addCleanup(shutil.rmtree, tmp) michael@0: michael@0: cache_dir = mozpath.join(tmp, 'cache') michael@0: os.mkdir(cache_dir) michael@0: michael@0: ip = self._static_input_paths michael@0: michael@0: inputs = ( michael@0: ip, michael@0: {mozpath.splitext(mozpath.basename(p))[0] for p in ip}, michael@0: set(), michael@0: set(), michael@0: ) michael@0: michael@0: return dict( michael@0: config_path=self._config_path, michael@0: inputs=inputs, michael@0: exported_header_dir=mozpath.join(tmp, 'exports'), michael@0: codegen_dir=mozpath.join(tmp, 'codegen'), michael@0: state_path=mozpath.join(tmp, 'state.json'), michael@0: make_deps_path=mozpath.join(tmp, 'codegen.pp'), michael@0: make_deps_target='codegen.pp', michael@0: cache_dir=cache_dir, michael@0: ) michael@0: michael@0: def _get_manager(self): michael@0: return WebIDLCodegenManager(**self._get_manager_args()) michael@0: michael@0: def test_unknown_state_version(self): michael@0: """Loading a state file with a too new version resets state.""" michael@0: args = self._get_manager_args() michael@0: michael@0: p = args['state_path'] michael@0: michael@0: with open(p, 'wb') as fh: michael@0: json.dump({ michael@0: 'version': WebIDLCodegenManagerState.VERSION + 1, michael@0: 'foobar': '1', michael@0: }, fh) michael@0: michael@0: manager = WebIDLCodegenManager(**args) michael@0: michael@0: self.assertEqual(manager._state['version'], michael@0: WebIDLCodegenManagerState.VERSION) michael@0: self.assertNotIn('foobar', manager._state) michael@0: michael@0: def test_generate_build_files(self): michael@0: """generate_build_files() does the right thing from empty.""" michael@0: manager = self._get_manager() michael@0: result = manager.generate_build_files() michael@0: self.assertEqual(len(result.inputs), 5) michael@0: michael@0: output = manager.expected_build_output_files() michael@0: self.assertEqual(result.created, output) michael@0: self.assertEqual(len(result.updated), 0) michael@0: self.assertEqual(len(result.unchanged), 0) michael@0: michael@0: for f in output: michael@0: self.assertTrue(os.path.isfile(f)) michael@0: michael@0: for f in manager.GLOBAL_DECLARE_FILES: michael@0: self.assertIn(mozpath.join(manager._exported_header_dir, f), output) michael@0: michael@0: for f in manager.GLOBAL_DEFINE_FILES: michael@0: self.assertIn(mozpath.join(manager._codegen_dir, f), output) michael@0: michael@0: for s in self.TEST_STEMS: michael@0: self.assertTrue(os.path.isfile(mozpath.join( michael@0: manager._exported_header_dir, '%sBinding.h' % s))) michael@0: self.assertTrue(os.path.isfile(mozpath.join( michael@0: manager._codegen_dir, '%sBinding.cpp' % s))) michael@0: michael@0: self.assertTrue(os.path.isfile(manager._state_path)) michael@0: michael@0: with open(manager._state_path, 'rb') as fh: michael@0: state = json.load(fh) michael@0: self.assertEqual(state['version'], 1) michael@0: self.assertIn('webidls', state) michael@0: michael@0: child = state['webidls']['Child.webidl'] michael@0: self.assertEqual(len(child['inputs']), 2) michael@0: self.assertEqual(len(child['outputs']), 2) michael@0: self.assertEqual(child['sha1'], 'c41527cad3bc161fa6e7909e48fa11f9eca0468b') michael@0: michael@0: def test_generate_build_files_load_state(self): michael@0: """State should be equivalent when instantiating a new instance.""" michael@0: args = self._get_manager_args() michael@0: m1 = WebIDLCodegenManager(**args) michael@0: self.assertEqual(len(m1._state['webidls']), 0) michael@0: m1.generate_build_files() michael@0: michael@0: m2 = WebIDLCodegenManager(**args) michael@0: self.assertGreater(len(m2._state['webidls']), 2) michael@0: self.assertEqual(m1._state, m2._state) michael@0: michael@0: def test_no_change_no_writes(self): michael@0: """If nothing changes, no files should be updated.""" michael@0: args = self._get_manager_args() michael@0: m1 = WebIDLCodegenManager(**args) michael@0: m1.generate_build_files() michael@0: michael@0: m2 = WebIDLCodegenManager(**args) michael@0: result = m2.generate_build_files() michael@0: michael@0: self.assertEqual(len(result.inputs), 0) michael@0: self.assertEqual(len(result.created), 0) michael@0: self.assertEqual(len(result.updated), 0) michael@0: michael@0: def test_output_file_regenerated(self): michael@0: """If an output file disappears, it is regenerated.""" michael@0: args = self._get_manager_args() michael@0: m1 = WebIDLCodegenManager(**args) michael@0: m1.generate_build_files() michael@0: michael@0: rm_count = 0 michael@0: for p in m1._state['webidls']['Child.webidl']['outputs']: michael@0: rm_count += 1 michael@0: os.unlink(p) michael@0: michael@0: for p in m1.GLOBAL_DECLARE_FILES: michael@0: rm_count += 1 michael@0: os.unlink(mozpath.join(m1._exported_header_dir, p)) michael@0: michael@0: m2 = WebIDLCodegenManager(**args) michael@0: result = m2.generate_build_files() michael@0: self.assertEqual(len(result.created), rm_count) michael@0: michael@0: def test_only_rebuild_self(self): michael@0: """If an input file changes, only rebuild that one file.""" michael@0: args = self._get_manager_args() michael@0: m1 = WebIDLCodegenManager(**args) michael@0: m1.generate_build_files() michael@0: michael@0: child_path = None michael@0: for p in m1._input_paths: michael@0: if p.endswith('Child.webidl'): michael@0: child_path = p michael@0: break michael@0: michael@0: self.assertIsNotNone(child_path) michael@0: child_content = open(child_path, 'rb').read() michael@0: michael@0: with MockedOpen({child_path: child_content + '\n/* */'}): michael@0: m2 = WebIDLCodegenManager(**args) michael@0: result = m2.generate_build_files() michael@0: self.assertEqual(result.inputs, set([child_path])) michael@0: self.assertEqual(len(result.updated), 0) michael@0: self.assertEqual(len(result.created), 0) michael@0: michael@0: def test_rebuild_dependencies(self): michael@0: """Ensure an input file used by others results in others rebuilding.""" michael@0: args = self._get_manager_args() michael@0: m1 = WebIDLCodegenManager(**args) michael@0: m1.generate_build_files() michael@0: michael@0: parent_path = None michael@0: child_path = None michael@0: for p in m1._input_paths: michael@0: if p.endswith('Parent.webidl'): michael@0: parent_path = p michael@0: elif p.endswith('Child.webidl'): michael@0: child_path = p michael@0: michael@0: self.assertIsNotNone(parent_path) michael@0: parent_content = open(parent_path, 'rb').read() michael@0: michael@0: with MockedOpen({parent_path: parent_content + '\n/* */'}): michael@0: m2 = WebIDLCodegenManager(**args) michael@0: result = m2.generate_build_files() michael@0: self.assertEqual(result.inputs, {child_path, parent_path}) michael@0: self.assertEqual(len(result.updated), 0) michael@0: self.assertEqual(len(result.created), 0) michael@0: michael@0: def test_python_change_regenerate_everything(self): michael@0: """If a Python file changes, we should attempt to rebuild everything.""" michael@0: michael@0: # We don't want to mutate files in the source directory because we want michael@0: # to be able to build from a read-only filesystem. So, we install a michael@0: # dummy module and rewrite the metadata to say it comes from the source michael@0: # directory. michael@0: # michael@0: # Hacking imp to accept a MockedFile doesn't appear possible. So for michael@0: # the first iteration we read from a temp file. The second iteration michael@0: # doesn't need to import, so we are fine with a mocked file. michael@0: fake_path = mozpath.join(OUR_DIR, 'fakemodule.py') michael@0: with NamedTemporaryFile('wt') as fh: michael@0: fh.write('# Original content') michael@0: fh.flush() michael@0: mod = imp.load_source('mozwebidlcodegen.fakemodule', fh.name) michael@0: mod.__file__ = fake_path michael@0: michael@0: args = self._get_manager_args() michael@0: m1 = WebIDLCodegenManager(**args) michael@0: with MockedOpen({fake_path: '# Original content'}): michael@0: old_exists = os.path.exists michael@0: try: michael@0: def exists(p): michael@0: if p == fake_path: michael@0: return True michael@0: return old_exists(p) michael@0: michael@0: os.path.exists = exists michael@0: michael@0: result = m1.generate_build_files() michael@0: l = len(result.inputs) michael@0: michael@0: with open(fake_path, 'wt') as fh: michael@0: fh.write('# Modified content') michael@0: michael@0: m2 = WebIDLCodegenManager(**args) michael@0: result = m2.generate_build_files() michael@0: self.assertEqual(len(result.inputs), l) michael@0: michael@0: result = m2.generate_build_files() michael@0: self.assertEqual(len(result.inputs), 0) michael@0: finally: michael@0: os.path.exists = old_exists michael@0: del sys.modules['mozwebidlcodegen.fakemodule'] michael@0: michael@0: def test_copy_input(self): michael@0: """Ensure a copied .webidl file is handled properly.""" michael@0: michael@0: # This test simulates changing the type of a WebIDL from static to michael@0: # preprocessed. In that scenario, the original file still exists but michael@0: # it should no longer be consulted during codegen. michael@0: michael@0: args = self._get_manager_args() michael@0: m1 = WebIDLCodegenManager(**args) michael@0: m1.generate_build_files() michael@0: michael@0: old_path = None michael@0: for p in args['inputs'][0]: michael@0: if p.endswith('Parent.webidl'): michael@0: old_path = p michael@0: break michael@0: self.assertIsNotNone(old_path) michael@0: michael@0: new_path = mozpath.join(args['cache_dir'], 'Parent.webidl') michael@0: shutil.copy2(old_path, new_path) michael@0: michael@0: args['inputs'][0].remove(old_path) michael@0: args['inputs'][0].add(new_path) michael@0: michael@0: m2 = WebIDLCodegenManager(**args) michael@0: result = m2.generate_build_files() michael@0: self.assertEqual(len(result.updated), 0) michael@0: michael@0: michael@0: if __name__ == '__main__': michael@0: main()