1# Copyright 2019 Google LLC 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""Tests for glue.""" 16 17import pkgutil 18import unittest 19 20from compiler.front_end import glue 21from compiler.util import error 22from compiler.util import ir_data 23from compiler.util import ir_data_utils 24from compiler.util import parser_types 25from compiler.util import test_util 26 27_location = parser_types.make_location 28 29_ROOT_PACKAGE = "testdata.golden" 30_GOLDEN_PATH = "" 31 32_SPAN_SE_LOG_FILE_PATH = _GOLDEN_PATH + "span_se_log_file_status.emb" 33_SPAN_SE_LOG_FILE_EMB = pkgutil.get_data( 34 _ROOT_PACKAGE, _SPAN_SE_LOG_FILE_PATH).decode(encoding="UTF-8") 35_SPAN_SE_LOG_FILE_READER = test_util.dict_file_reader( 36 {_SPAN_SE_LOG_FILE_PATH: _SPAN_SE_LOG_FILE_EMB}) 37_SPAN_SE_LOG_FILE_IR = ir_data_utils.IrDataSerializer.from_json(ir_data.Module, 38 pkgutil.get_data( 39 _ROOT_PACKAGE, 40 _GOLDEN_PATH + "span_se_log_file_status.ir.txt" 41 ).decode(encoding="UTF-8")) 42_SPAN_SE_LOG_FILE_PARSE_TREE_TEXT = pkgutil.get_data( 43 _ROOT_PACKAGE, 44 _GOLDEN_PATH + "span_se_log_file_status.parse_tree.txt" 45).decode(encoding="UTF-8") 46_SPAN_SE_LOG_FILE_TOKENIZATION_TEXT = pkgutil.get_data( 47 _ROOT_PACKAGE, 48 _GOLDEN_PATH + "span_se_log_file_status.tokens.txt" 49).decode(encoding="UTF-8") 50 51 52class FrontEndGlueTest(unittest.TestCase): 53 """Tests for front_end.glue.""" 54 55 def test_parse_module(self): 56 # parse_module(file) should return the same thing as 57 # parse_module_text(text), assuming file can be read. 58 main_module, debug_info, errors = glue.parse_module( 59 _SPAN_SE_LOG_FILE_PATH, _SPAN_SE_LOG_FILE_READER) 60 main_module2, debug_info2, errors2 = glue.parse_module_text( 61 _SPAN_SE_LOG_FILE_EMB, _SPAN_SE_LOG_FILE_PATH) 62 self.assertEqual([], errors) 63 self.assertEqual([], errors2) 64 self.assertEqual(main_module, main_module2) 65 self.assertEqual(debug_info, debug_info2) 66 67 def test_parse_module_no_such_file(self): 68 file_name = "nonexistent.emb" 69 ir, debug_info, errors = glue.parse_emboss_file( 70 file_name, test_util.dict_file_reader({})) 71 self.assertEqual([[ 72 error.error("nonexistent.emb", _location((1, 1), (1, 1)), 73 "Unable to read file."), 74 error.note("nonexistent.emb", _location((1, 1), (1, 1)), 75 "File 'nonexistent.emb' not found."), 76 ]], errors) 77 self.assertFalse(file_name in debug_info.modules) 78 self.assertFalse(ir) 79 80 def test_parse_module_tokenization_error(self): 81 file_name = "tokens.emb" 82 ir, debug_info, errors = glue.parse_emboss_file( 83 file_name, test_util.dict_file_reader({file_name: "@"})) 84 self.assertTrue(debug_info.modules[file_name].source_code) 85 self.assertTrue(errors) 86 self.assertEqual("Unrecognized token", errors[0][0].message) 87 self.assertFalse(ir) 88 89 def test_parse_module_indentation_error(self): 90 file_name = "indent.emb" 91 ir, debug_info, errors = glue.parse_emboss_file( 92 file_name, test_util.dict_file_reader( 93 {file_name: "struct Foo:\n" 94 " 1 [+1] Int x\n" 95 " 2 [+1] Int y\n"})) 96 self.assertTrue(debug_info.modules[file_name].source_code) 97 self.assertTrue(errors) 98 self.assertEqual("Bad indentation", errors[0][0].message) 99 self.assertFalse(ir) 100 101 def test_parse_module_parse_error(self): 102 file_name = "parse.emb" 103 ir, debug_info, errors = glue.parse_emboss_file( 104 file_name, test_util.dict_file_reader( 105 {file_name: "struct foo:\n" 106 " 1 [+1] Int x\n" 107 " 3 [+1] Int y\n"})) 108 self.assertTrue(debug_info.modules[file_name].source_code) 109 self.assertEqual([[ 110 error.error(file_name, _location((1, 8), (1, 11)), 111 "A type name must be CamelCase.\n" 112 "Found 'foo' (SnakeWord), expected CamelWord.") 113 ]], errors) 114 self.assertFalse(ir) 115 116 def test_parse_error(self): 117 file_name = "parse.emb" 118 ir, debug_info, errors = glue.parse_emboss_file( 119 file_name, test_util.dict_file_reader( 120 {file_name: "struct foo:\n" 121 " 1 [+1] Int x\n" 122 " 2 [+1] Int y\n"})) 123 self.assertTrue(debug_info.modules[file_name].source_code) 124 self.assertEqual([[ 125 error.error(file_name, _location((1, 8), (1, 11)), 126 "A type name must be CamelCase.\n" 127 "Found 'foo' (SnakeWord), expected CamelWord.") 128 ]], errors) 129 self.assertFalse(ir) 130 131 def test_circular_dependency_error(self): 132 file_name = "cycle.emb" 133 ir, debug_info, errors = glue.parse_emboss_file( 134 file_name, test_util.dict_file_reader({ 135 file_name: "struct Foo:\n" 136 " 0 [+field1] UInt field1\n" 137 })) 138 self.assertTrue(debug_info.modules[file_name].source_code) 139 self.assertTrue(errors) 140 self.assertEqual("Dependency cycle\nfield1", errors[0][0].message) 141 self.assertFalse(ir) 142 143 def test_ir_from_parse_module(self): 144 log_file_path_ir = ir_data_utils.copy(_SPAN_SE_LOG_FILE_IR) 145 log_file_path_ir.source_file_name = _SPAN_SE_LOG_FILE_PATH 146 self.assertEqual(log_file_path_ir, glue.parse_module( 147 _SPAN_SE_LOG_FILE_PATH, _SPAN_SE_LOG_FILE_READER).ir) 148 149 def test_debug_info_from_parse_module(self): 150 debug_info = glue.parse_module(_SPAN_SE_LOG_FILE_PATH, 151 _SPAN_SE_LOG_FILE_READER).debug_info 152 self.maxDiff = 200000 # pylint:disable=invalid-name 153 self.assertEqual(_SPAN_SE_LOG_FILE_TOKENIZATION_TEXT.strip(), 154 debug_info.format_tokenization().strip()) 155 self.assertEqual(_SPAN_SE_LOG_FILE_PARSE_TREE_TEXT.strip(), 156 debug_info.format_parse_tree().strip()) 157 self.assertEqual(_SPAN_SE_LOG_FILE_IR, debug_info.ir) 158 self.assertEqual(ir_data_utils.IrDataSerializer(_SPAN_SE_LOG_FILE_IR).to_json(indent=2), 159 debug_info.format_module_ir()) 160 161 def test_parse_emboss_file(self): 162 # parse_emboss_file calls parse_module, wraps its results, and calls 163 # symbol_resolver.resolve_symbols() on the resulting IR. 164 ir, debug_info, errors = glue.parse_emboss_file(_SPAN_SE_LOG_FILE_PATH, 165 _SPAN_SE_LOG_FILE_READER) 166 module_ir, module_debug_info, module_errors = glue.parse_module( 167 _SPAN_SE_LOG_FILE_PATH, _SPAN_SE_LOG_FILE_READER) 168 self.assertEqual([], errors) 169 self.assertEqual([], module_errors) 170 self.assertTrue(test_util.proto_is_superset(ir.module[0], module_ir)) 171 self.assertEqual(module_debug_info, 172 debug_info.modules[_SPAN_SE_LOG_FILE_PATH]) 173 self.assertEqual(2, len(debug_info.modules)) 174 self.assertEqual(2, len(ir.module)) 175 self.assertEqual(_SPAN_SE_LOG_FILE_PATH, ir.module[0].source_file_name) 176 self.assertEqual("", ir.module[1].source_file_name) 177 178 def test_synthetic_error(self): 179 file_name = "missing_byte_order_attribute.emb" 180 ir, unused_debug_info, errors = glue.only_parse_emboss_file( 181 file_name, test_util.dict_file_reader({ 182 file_name: "struct Foo:\n" 183 " 0 [+8] UInt field\n" 184 })) 185 self.assertFalse(errors) 186 # Artificially mark the first field as is_synthetic. 187 first_field = ir.module[0].type[0].structure.field[0] 188 first_field.source_location.is_synthetic = True 189 ir, errors = glue.process_ir(ir, None) 190 self.assertTrue(errors) 191 self.assertEqual("Attribute 'byte_order' required on field which is byte " 192 "order dependent.", errors[0][0].message) 193 self.assertTrue(errors[0][0].location.is_synthetic) 194 self.assertFalse(ir) 195 196 def test_suppressed_synthetic_error(self): 197 file_name = "triplicate_symbol.emb" 198 ir, unused_debug_info, errors = glue.only_parse_emboss_file( 199 file_name, test_util.dict_file_reader({ 200 file_name: "struct Foo:\n" 201 " 0 [+1] UInt field\n" 202 " 1 [+1] UInt field\n" 203 " 2 [+1] UInt field\n" 204 })) 205 self.assertFalse(errors) 206 # Artificially mark the name of the second field as is_synthetic. 207 second_field = ir.module[0].type[0].structure.field[1] 208 second_field.name.source_location.is_synthetic = True 209 second_field.name.name.source_location.is_synthetic = True 210 ir, errors = glue.process_ir(ir, None) 211 self.assertEqual(1, len(errors)) 212 self.assertEqual("Duplicate name 'field'", errors[0][0].message) 213 self.assertFalse(errors[0][0].location.is_synthetic) 214 self.assertFalse(errors[0][1].location.is_synthetic) 215 self.assertFalse(ir) 216 217 218class DebugInfoTest(unittest.TestCase): 219 """Tests for DebugInfo and ModuleDebugInfo classes.""" 220 221 def test_debug_info_initialization(self): 222 debug_info = glue.DebugInfo() 223 self.assertEqual({}, debug_info.modules) 224 225 def test_debug_info_invalid_attribute_set(self): 226 debug_info = glue.DebugInfo() 227 with self.assertRaises(AttributeError): 228 debug_info.foo = "foo" 229 230 def test_debug_info_equality(self): 231 debug_info = glue.DebugInfo() 232 debug_info2 = glue.DebugInfo() 233 self.assertEqual(debug_info, debug_info2) 234 debug_info.modules["foo"] = glue.ModuleDebugInfo("foo") 235 self.assertNotEqual(debug_info, debug_info2) 236 debug_info2.modules["foo"] = glue.ModuleDebugInfo("foo") 237 self.assertEqual(debug_info, debug_info2) 238 239 def test_module_debug_info_initialization(self): 240 module_info = glue.ModuleDebugInfo("bar.emb") 241 self.assertEqual("bar.emb", module_info.file_name) 242 self.assertEqual(None, module_info.tokens) 243 self.assertEqual(None, module_info.parse_tree) 244 self.assertEqual(None, module_info.ir) 245 self.assertEqual(None, module_info.used_productions) 246 247 def test_module_debug_info_attribute_set(self): 248 module_info = glue.ModuleDebugInfo("bar.emb") 249 module_info.tokens = "a" 250 module_info.parse_tree = "b" 251 module_info.ir = "c" 252 module_info.used_productions = "d" 253 module_info.source_code = "e" 254 self.assertEqual("a", module_info.tokens) 255 self.assertEqual("b", module_info.parse_tree) 256 self.assertEqual("c", module_info.ir) 257 self.assertEqual("d", module_info.used_productions) 258 self.assertEqual("e", module_info.source_code) 259 260 def test_module_debug_info_bad_attribute_set(self): 261 module_info = glue.ModuleDebugInfo("bar.emb") 262 with self.assertRaises(AttributeError): 263 module_info.foo = "foo" 264 265 def test_module_debug_info_equality(self): 266 module_info = glue.ModuleDebugInfo("foo") 267 module_info2 = glue.ModuleDebugInfo("foo") 268 module_info_bar = glue.ModuleDebugInfo("bar") 269 self.assertEqual(module_info, module_info2) 270 module_info_bar = glue.ModuleDebugInfo("bar") 271 self.assertNotEqual(module_info, module_info_bar) 272 module_info.tokens = [] 273 self.assertNotEqual(module_info, module_info2) 274 module_info2.tokens = [] 275 self.assertEqual(module_info, module_info2) 276 module_info.parse_tree = [] 277 self.assertNotEqual(module_info, module_info2) 278 module_info2.parse_tree = [] 279 self.assertEqual(module_info, module_info2) 280 module_info.ir = [] 281 self.assertNotEqual(module_info, module_info2) 282 module_info2.ir = [] 283 self.assertEqual(module_info, module_info2) 284 module_info.used_productions = [] 285 self.assertNotEqual(module_info, module_info2) 286 module_info2.used_productions = [] 287 self.assertEqual(module_info, module_info2) 288 289 290class TestFormatProductionSet(unittest.TestCase): 291 """Tests for format_production_set.""" 292 293 def test_format_production_set(self): 294 production_texts = ["A -> B", "B -> C", "A -> C", "C -> A"] 295 productions = [parser_types.Production.parse(p) for p in production_texts] 296 self.assertEqual("\n".join(sorted(production_texts)), 297 glue.format_production_set(set(productions))) 298 299 300if __name__ == "__main__": 301 unittest.main() 302