# Copyright 2019 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Tests for glue.""" import pkgutil import unittest from compiler.front_end import glue from compiler.util import error from compiler.util import ir_data from compiler.util import ir_data_utils from compiler.util import parser_types from compiler.util import test_util _location = parser_types.make_location _ROOT_PACKAGE = "testdata.golden" _GOLDEN_PATH = "" _SPAN_SE_LOG_FILE_PATH = _GOLDEN_PATH + "span_se_log_file_status.emb" _SPAN_SE_LOG_FILE_EMB = pkgutil.get_data( _ROOT_PACKAGE, _SPAN_SE_LOG_FILE_PATH).decode(encoding="UTF-8") _SPAN_SE_LOG_FILE_READER = test_util.dict_file_reader( {_SPAN_SE_LOG_FILE_PATH: _SPAN_SE_LOG_FILE_EMB}) _SPAN_SE_LOG_FILE_IR = ir_data_utils.IrDataSerializer.from_json(ir_data.Module, pkgutil.get_data( _ROOT_PACKAGE, _GOLDEN_PATH + "span_se_log_file_status.ir.txt" ).decode(encoding="UTF-8")) _SPAN_SE_LOG_FILE_PARSE_TREE_TEXT = pkgutil.get_data( _ROOT_PACKAGE, _GOLDEN_PATH + "span_se_log_file_status.parse_tree.txt" ).decode(encoding="UTF-8") _SPAN_SE_LOG_FILE_TOKENIZATION_TEXT = pkgutil.get_data( _ROOT_PACKAGE, _GOLDEN_PATH + "span_se_log_file_status.tokens.txt" ).decode(encoding="UTF-8") class FrontEndGlueTest(unittest.TestCase): """Tests for front_end.glue.""" def test_parse_module(self): # parse_module(file) should return the same thing as # parse_module_text(text), assuming file can be read. main_module, debug_info, errors = glue.parse_module( _SPAN_SE_LOG_FILE_PATH, _SPAN_SE_LOG_FILE_READER) main_module2, debug_info2, errors2 = glue.parse_module_text( _SPAN_SE_LOG_FILE_EMB, _SPAN_SE_LOG_FILE_PATH) self.assertEqual([], errors) self.assertEqual([], errors2) self.assertEqual(main_module, main_module2) self.assertEqual(debug_info, debug_info2) def test_parse_module_no_such_file(self): file_name = "nonexistent.emb" ir, debug_info, errors = glue.parse_emboss_file( file_name, test_util.dict_file_reader({})) self.assertEqual([[ error.error("nonexistent.emb", _location((1, 1), (1, 1)), "Unable to read file."), error.note("nonexistent.emb", _location((1, 1), (1, 1)), "File 'nonexistent.emb' not found."), ]], errors) self.assertFalse(file_name in debug_info.modules) self.assertFalse(ir) def test_parse_module_tokenization_error(self): file_name = "tokens.emb" ir, debug_info, errors = glue.parse_emboss_file( file_name, test_util.dict_file_reader({file_name: "@"})) self.assertTrue(debug_info.modules[file_name].source_code) self.assertTrue(errors) self.assertEqual("Unrecognized token", errors[0][0].message) self.assertFalse(ir) def test_parse_module_indentation_error(self): file_name = "indent.emb" ir, debug_info, errors = glue.parse_emboss_file( file_name, test_util.dict_file_reader( {file_name: "struct Foo:\n" " 1 [+1] Int x\n" " 2 [+1] Int y\n"})) self.assertTrue(debug_info.modules[file_name].source_code) self.assertTrue(errors) self.assertEqual("Bad indentation", errors[0][0].message) self.assertFalse(ir) def test_parse_module_parse_error(self): file_name = "parse.emb" ir, debug_info, errors = glue.parse_emboss_file( file_name, test_util.dict_file_reader( {file_name: "struct foo:\n" " 1 [+1] Int x\n" " 3 [+1] Int y\n"})) self.assertTrue(debug_info.modules[file_name].source_code) self.assertEqual([[ error.error(file_name, _location((1, 8), (1, 11)), "A type name must be CamelCase.\n" "Found 'foo' (SnakeWord), expected CamelWord.") ]], errors) self.assertFalse(ir) def test_parse_error(self): file_name = "parse.emb" ir, debug_info, errors = glue.parse_emboss_file( file_name, test_util.dict_file_reader( {file_name: "struct foo:\n" " 1 [+1] Int x\n" " 2 [+1] Int y\n"})) self.assertTrue(debug_info.modules[file_name].source_code) self.assertEqual([[ error.error(file_name, _location((1, 8), (1, 11)), "A type name must be CamelCase.\n" "Found 'foo' (SnakeWord), expected CamelWord.") ]], errors) self.assertFalse(ir) def test_circular_dependency_error(self): file_name = "cycle.emb" ir, debug_info, errors = glue.parse_emboss_file( file_name, test_util.dict_file_reader({ file_name: "struct Foo:\n" " 0 [+field1] UInt field1\n" })) self.assertTrue(debug_info.modules[file_name].source_code) self.assertTrue(errors) self.assertEqual("Dependency cycle\nfield1", errors[0][0].message) self.assertFalse(ir) def test_ir_from_parse_module(self): log_file_path_ir = ir_data_utils.copy(_SPAN_SE_LOG_FILE_IR) log_file_path_ir.source_file_name = _SPAN_SE_LOG_FILE_PATH self.assertEqual(log_file_path_ir, glue.parse_module( _SPAN_SE_LOG_FILE_PATH, _SPAN_SE_LOG_FILE_READER).ir) def test_debug_info_from_parse_module(self): debug_info = glue.parse_module(_SPAN_SE_LOG_FILE_PATH, _SPAN_SE_LOG_FILE_READER).debug_info self.maxDiff = 200000 # pylint:disable=invalid-name self.assertEqual(_SPAN_SE_LOG_FILE_TOKENIZATION_TEXT.strip(), debug_info.format_tokenization().strip()) self.assertEqual(_SPAN_SE_LOG_FILE_PARSE_TREE_TEXT.strip(), debug_info.format_parse_tree().strip()) self.assertEqual(_SPAN_SE_LOG_FILE_IR, debug_info.ir) self.assertEqual(ir_data_utils.IrDataSerializer(_SPAN_SE_LOG_FILE_IR).to_json(indent=2), debug_info.format_module_ir()) def test_parse_emboss_file(self): # parse_emboss_file calls parse_module, wraps its results, and calls # symbol_resolver.resolve_symbols() on the resulting IR. ir, debug_info, errors = glue.parse_emboss_file(_SPAN_SE_LOG_FILE_PATH, _SPAN_SE_LOG_FILE_READER) module_ir, module_debug_info, module_errors = glue.parse_module( _SPAN_SE_LOG_FILE_PATH, _SPAN_SE_LOG_FILE_READER) self.assertEqual([], errors) self.assertEqual([], module_errors) self.assertTrue(test_util.proto_is_superset(ir.module[0], module_ir)) self.assertEqual(module_debug_info, debug_info.modules[_SPAN_SE_LOG_FILE_PATH]) self.assertEqual(2, len(debug_info.modules)) self.assertEqual(2, len(ir.module)) self.assertEqual(_SPAN_SE_LOG_FILE_PATH, ir.module[0].source_file_name) self.assertEqual("", ir.module[1].source_file_name) def test_synthetic_error(self): file_name = "missing_byte_order_attribute.emb" ir, unused_debug_info, errors = glue.only_parse_emboss_file( file_name, test_util.dict_file_reader({ file_name: "struct Foo:\n" " 0 [+8] UInt field\n" })) self.assertFalse(errors) # Artificially mark the first field as is_synthetic. first_field = ir.module[0].type[0].structure.field[0] first_field.source_location.is_synthetic = True ir, errors = glue.process_ir(ir, None) self.assertTrue(errors) self.assertEqual("Attribute 'byte_order' required on field which is byte " "order dependent.", errors[0][0].message) self.assertTrue(errors[0][0].location.is_synthetic) self.assertFalse(ir) def test_suppressed_synthetic_error(self): file_name = "triplicate_symbol.emb" ir, unused_debug_info, errors = glue.only_parse_emboss_file( file_name, test_util.dict_file_reader({ file_name: "struct Foo:\n" " 0 [+1] UInt field\n" " 1 [+1] UInt field\n" " 2 [+1] UInt field\n" })) self.assertFalse(errors) # Artificially mark the name of the second field as is_synthetic. second_field = ir.module[0].type[0].structure.field[1] second_field.name.source_location.is_synthetic = True second_field.name.name.source_location.is_synthetic = True ir, errors = glue.process_ir(ir, None) self.assertEqual(1, len(errors)) self.assertEqual("Duplicate name 'field'", errors[0][0].message) self.assertFalse(errors[0][0].location.is_synthetic) self.assertFalse(errors[0][1].location.is_synthetic) self.assertFalse(ir) class DebugInfoTest(unittest.TestCase): """Tests for DebugInfo and ModuleDebugInfo classes.""" def test_debug_info_initialization(self): debug_info = glue.DebugInfo() self.assertEqual({}, debug_info.modules) def test_debug_info_invalid_attribute_set(self): debug_info = glue.DebugInfo() with self.assertRaises(AttributeError): debug_info.foo = "foo" def test_debug_info_equality(self): debug_info = glue.DebugInfo() debug_info2 = glue.DebugInfo() self.assertEqual(debug_info, debug_info2) debug_info.modules["foo"] = glue.ModuleDebugInfo("foo") self.assertNotEqual(debug_info, debug_info2) debug_info2.modules["foo"] = glue.ModuleDebugInfo("foo") self.assertEqual(debug_info, debug_info2) def test_module_debug_info_initialization(self): module_info = glue.ModuleDebugInfo("bar.emb") self.assertEqual("bar.emb", module_info.file_name) self.assertEqual(None, module_info.tokens) self.assertEqual(None, module_info.parse_tree) self.assertEqual(None, module_info.ir) self.assertEqual(None, module_info.used_productions) def test_module_debug_info_attribute_set(self): module_info = glue.ModuleDebugInfo("bar.emb") module_info.tokens = "a" module_info.parse_tree = "b" module_info.ir = "c" module_info.used_productions = "d" module_info.source_code = "e" self.assertEqual("a", module_info.tokens) self.assertEqual("b", module_info.parse_tree) self.assertEqual("c", module_info.ir) self.assertEqual("d", module_info.used_productions) self.assertEqual("e", module_info.source_code) def test_module_debug_info_bad_attribute_set(self): module_info = glue.ModuleDebugInfo("bar.emb") with self.assertRaises(AttributeError): module_info.foo = "foo" def test_module_debug_info_equality(self): module_info = glue.ModuleDebugInfo("foo") module_info2 = glue.ModuleDebugInfo("foo") module_info_bar = glue.ModuleDebugInfo("bar") self.assertEqual(module_info, module_info2) module_info_bar = glue.ModuleDebugInfo("bar") self.assertNotEqual(module_info, module_info_bar) module_info.tokens = [] self.assertNotEqual(module_info, module_info2) module_info2.tokens = [] self.assertEqual(module_info, module_info2) module_info.parse_tree = [] self.assertNotEqual(module_info, module_info2) module_info2.parse_tree = [] self.assertEqual(module_info, module_info2) module_info.ir = [] self.assertNotEqual(module_info, module_info2) module_info2.ir = [] self.assertEqual(module_info, module_info2) module_info.used_productions = [] self.assertNotEqual(module_info, module_info2) module_info2.used_productions = [] self.assertEqual(module_info, module_info2) class TestFormatProductionSet(unittest.TestCase): """Tests for format_production_set.""" def test_format_production_set(self): production_texts = ["A -> B", "B -> C", "A -> C", "C -> A"] productions = [parser_types.Production.parse(p) for p in production_texts] self.assertEqual("\n".join(sorted(production_texts)), glue.format_production_set(set(productions))) if __name__ == "__main__": unittest.main()