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