From a707b1bbf5151d36d978e9903e28917de1be16c2 Mon Sep 17 00:00:00 2001 From: bayanxing Date: Fri, 18 Feb 2022 09:09:24 +0800 Subject: [PATCH] optimize output information Signed-off-by: bayanxing --- tools/idl-gen/_header_parser.py | 47 ++++-- tools/idl-gen/idl_generator.py | 37 ++-- tools/idl-gen/testcase/test_header_parser.py | 167 +++++++++++++------ tools/idl-gen/testcase/test_idl_generator.py | 29 +++- 4 files changed, 199 insertions(+), 81 deletions(-) diff --git a/tools/idl-gen/_header_parser.py b/tools/idl-gen/_header_parser.py index e8e3fba2..1ef1a3c1 100644 --- a/tools/idl-gen/_header_parser.py +++ b/tools/idl-gen/_header_parser.py @@ -94,7 +94,7 @@ class HeaderParser: def _checkout_function_pointer_param(self, params): if params == '': return [] - tt = re.match("(typedef )* *\\w+ \\( \\* \\) \\(( .* )\\)", params) + tt = re.match(r"(typedef )* *\w+ \( \* \) \(( .* )\)", params) if tt: params = tt[2].strip() + "," else: @@ -105,7 +105,7 @@ class HeaderParser: tt = re.match(" *(const )*(struct |enum |union )* *", params) if tt: # 去掉结构类型头 params = params[tt.regs[0][1]:] - tt = re.match("((unsigned )*[a-zA-Z0-9_:]+( *\\** *\\**)) *", params) + tt = re.match(r"((unsigned )*[a-zA-Z0-9_:]+( *\** *\**)) *", params) if tt: param_type = params[tt.regs[1][0]:tt.regs[1][1]] # 参数类型 params = params[tt.regs[0][1]:] @@ -150,11 +150,11 @@ class HeaderParser: for value in enm["values"]: v_value = value["value"] if not isinstance(v_value, int): - tt = re.match("0x|0X|\\(|-", v_value) + tt = re.match(r"0x|0X|\(|-", v_value) if tt: errmsg = "unexpected '%s'" % tt[0] v_value = v_value + " // " + errmsg - print("[Parser]: %s[line %d] " % (enm["filename"], enm["line_number"]), errmsg) + print("[HeaderParser]: %s[line %d] " % (enm["filename"], enm["line_number"]), errmsg) enum_dict["members"].append({"name": value["name"], "value": v_value}) self._header_dict["enum"].append(enum_dict) @@ -167,8 +167,11 @@ class HeaderParser: union_dict["name"] = stack["name"].split(" ")[-1] union_dict["type"] = "union" union_dict["members"] = [] + file_name = self._header_dict["path"] + "/" + self._header_dict["name"] for mb in stack["members"]: - union_dict["members"].append({"name": mb["name"], "type": mb["type"]}) + union_dict["members"].append({"file_name": file_name, + "line_number": mb["line_number"], + "name": mb["name"], "type": mb["type"]}) self._header_dict["union"].append(union_dict) def _extract_struct(self, stack): @@ -179,17 +182,26 @@ class HeaderParser: if stack["declaration_method"] in ["struct", "class"]: if len(stack["methods"]["public"]) == 0: # 不带函数 if not self._has_function_pointer(stack["properties"]["public"]): # 变量中没有函数指针 - # print(cs["name"], "转换到struct") struct_dict["name"] = stack["name"] struct_dict["type"] = "struct" struct_dict["members"] = [] + file_name = self._header_dict["path"] + "/" + self._header_dict["name"] for mb in stack["properties"]["public"]: if "enum_type" in mb: - struct_dict["members"].append({"name": mb["name"], "type": mb["enum_type"]["name"]}) + struct_dict["members"].append({"file_name": file_name, + "line_number": mb["line_number"], + "name": mb["name"], + "type": mb["enum_type"]["name"]}) elif mb["array"] == 1: - struct_dict["members"].append({"name": mb["name"], "type": mb["type"] + " *"}) + struct_dict["members"].append({"file_name": file_name, + "line_number": mb["line_number"], + "name": mb["name"], + "type": mb["type"] + " *"}) else: - struct_dict["members"].append({"name": mb["name"], "type": mb["type"]}) + struct_dict["members"].append({"file_name": file_name, + "line_number": mb["line_number"], + "name": mb["name"], + "type": mb["type"]}) self._header_dict["struct"].append(struct_dict) def _extract_interface(self, stack): @@ -200,10 +212,11 @@ class HeaderParser: if stack["declaration_method"] in ["struct", "class"]: # 带函数,或变量中包含函数指针 if len(stack["methods"]["public"]) > 0 or self._has_function_pointer(stack["properties"]["public"]): - # print(cs["name"], "转换到class") interface_dict["name"] = stack["name"] interface_dict["members"] = [] for mb in stack["methods"]["public"]: + if mb["name"] in (stack["name"], "DECLARE_INTERFACE_DESCRIPTOR"): + continue params = [] for param in mb["parameters"]: para_name = param["name"] @@ -211,14 +224,22 @@ class HeaderParser: para_name = "rand_name_%d" % self._rand_name_count self._rand_name_count += 1 params.append({"name": para_name, "type": param["type"]}) - interface_dict["members"].append({"name": mb["name"], "params": params}) + interface_dict["members"].append({"name": mb["name"], + "params": params, + "file_name": + self._header_dict["path"] + "/" + self._header_dict["name"], + "line_number": mb["line_number"]}) for mb in stack["properties"]["public"]: if mb["function_pointer"] > 0: interface_dict["members"].append({"name": mb["name"], - "params": self._checkout_function_pointer_param( - mb["type"])}) + "params": self._checkout_function_pointer_param(mb["type"]), + "file_name": + self._header_dict["path"] + "/" + self._header_dict[ + "name"], + "line_number": mb["line_number"]}) self._header_dict["interface"].append(interface_dict) + def _extract_typedef(self, typedefs): """ Extract typedef from global typedefs diff --git a/tools/idl-gen/idl_generator.py b/tools/idl-gen/idl_generator.py index e59aac39..42f7ca87 100644 --- a/tools/idl-gen/idl_generator.py +++ b/tools/idl-gen/idl_generator.py @@ -118,7 +118,7 @@ class IDLGenerator: def _generate_type(self, header): if self._has_user_define_type(header): self._install_package(header["path"]) - self._install_import(header) + self._install_import(header, "/Types.idl") self._install_enum(header["enum"]) self._install_stack(header["union"]) self._install_stack(header["struct"]) @@ -128,7 +128,7 @@ class IDLGenerator: def _generate_interface(self, header): for iface in header["interface"]: self._install_package(header["path"]) - self._install_import(header) + self._install_import(header, iface["name"]) self._install_interface(iface) self._write_file(header["path"], iface["name"] + ".idl") @@ -136,7 +136,7 @@ class IDLGenerator: def _install_package(self, file_path): self._idl += "package " + self._get_package(file_path) + ";\n\n" - def _install_import(self, header): + def _install_import(self, header, idl_file): original_idl = self._idl for file_name in header["import"]: if file_name in self._parse_results: @@ -151,6 +151,8 @@ class IDLGenerator: self._idl += "import " + self._get_package(include_file['path']) + ".%s;\n" % cb["name"] else: self._idl += "// can't import %s\n" % file_name + print("[IDLGenerator]: %s[line ?] can't find %s" + % (os.path.normpath(header["path"] + "/" + header["name"]), file_name)) for cb in header['callback']: self._idl += "import " + self._get_package(header['path']) + ".%s;\n" % cb["name"] @@ -169,7 +171,11 @@ class IDLGenerator: for stack in stacks: self._idl += stack["type"] + " %s {\n" % stack["name"] for member in stack["members"]: - self._idl += " %s %s;\n" % (self._swap_type_c2idl(member["type"]), member["name"]) + param_type = self._swap_type_c2idl(member["type"]) + if "unknown type" in param_type: + print("[IDLGenerator]: %s[line %d] %s" % ( + os.path.normpath(member["file_name"]), member["line_number"], param_type)) + self._idl += " %s %s;\n" % (param_type, member["name"]) self._idl += "};\n" def _install_interface(self, iface): @@ -180,15 +186,19 @@ class IDLGenerator: self._install_function(iface["name"], member) self._idl += "};\n" - def _install_function(self, iface_name, func): - self._idl += " %s(" % func["name"] - for i, param in enumerate(func["params"]): - tt = re.fullmatch("(enum)*(union)*(struct)* *%s *\\** * *\\**" % iface_name, param["type"]) + def _install_function(self, iface_name, member): + self._idl += " %s(" % member["name"] + for i, param in enumerate(member["params"]): + tt = re.fullmatch(r"(enum)*(union)*(struct)* *%s *\** * *\**" % iface_name, param["type"]) if tt: continue + param_type = self._swap_type_c2idl(param["type"]) + if "unknown type" in param_type: + print("[IDLGenerator]: %s[line %d] %s" % ( + os.path.normpath(member["file_name"]), member["line_number"], param_type)) self._idl += "%s %s %s," % ( '* *' in param["type"] and "[out]" or "[in]", - self._swap_type_c2idl(param["type"]), + param_type, param["name"]) if self._idl.endswith(','): self._idl = self._idl[:-1] + ");\n" @@ -214,7 +224,7 @@ class IDLGenerator: ] for cti in type_c2idl: for ct in cti[0]: - tt = re.match("(const )* *%s *(\\**) *" % ct, c_type) + tt = re.match(r"(const )* *%s *(\**) *" % ct, c_type) if tt: idl_type = cti[1] if c_type.count('*') == 1: @@ -226,7 +236,7 @@ class IDLGenerator: for type_name in self._key_list: if "_ENUM_POINTER" in c_type: c_type = c_type.replace("_ENUM_POINTER", " * ") - tt = re.fullmatch("(enum)*(union)*(struct)* *%s *\\** * *\\**" % type_name, c_type) + tt = re.fullmatch(r"(const )* *(enum)*(union)*(struct)* *%s *[*&]* * *[*&]*" % type_name, c_type) if tt: if len(self._key_list[type_name]) > 0: idl_type = self._key_list[type_name] + ' ' + type_name @@ -239,7 +249,7 @@ class IDLGenerator: def _convert_typedef(self, c_type): for type_name in self._typedef_list: - tt = re.match("(const )* *%s *(\\**) *" % type_name, c_type) + tt = re.match(r"(const )* *%s *\** *" % type_name, c_type) if tt: if self._typedef_list[type_name].count('*') == 1: idl_type = self._typedef_list[type_name].split(' ')[0] + '[]' @@ -280,14 +290,13 @@ class IDLGenerator: return idl_type idl_type = "/* unknown type: [%s] */" % c_type - print(">>>error:", idl_type) return idl_type def _write_file(self, file_path, file_name): file = os.path.join(self._make_output_dir(file_path), file_name).replace('\\', '/') with open(file, "w", encoding="utf8") as fp: fp.write(self._idl) - print("[Generate]: ", file) + print("Generate: --------------------- %s ---------------------\n" % os.path.normpath(file)) self._idl = "" @staticmethod diff --git a/tools/idl-gen/testcase/test_header_parser.py b/tools/idl-gen/testcase/test_header_parser.py index 7c2ff804..b91ac7c6 100644 --- a/tools/idl-gen/testcase/test_header_parser.py +++ b/tools/idl-gen/testcase/test_header_parser.py @@ -134,8 +134,10 @@ class HeaderParserTestCase(unittest.TestCase): self.assertSequenceEqual(parser._header_dict["union"], [{'name': 'SceneDesc', 'type': 'union', 'members': [ - {'name': 'id', 'type': 'uint32_t'}, - {'name': 'desc', 'type': 'const char *'} + {'file_name': '/', 'line_number': 3, + 'name': 'id', 'type': 'uint32_t'}, + {'file_name': '/', 'line_number': 4, + 'name': 'desc', 'type': 'const char *'} ]}]) def test_extract_union_without_name(self): @@ -156,14 +158,18 @@ class HeaderParserTestCase(unittest.TestCase): self.assertSequenceEqual(parser._header_dict["union"], [{'name': '', 'type': 'union', 'members': [ - {'name': 'id', 'type': 'uint32_t'}, - {'name': 'desc', 'type': 'const char *'} + {'file_name': '/', 'line_number': 3, + 'name': 'id', 'type': 'uint32_t'}, + {'file_name': '/', 'line_number': 4, + 'name': 'desc', 'type': 'const char *'} ]}, {'name': '', 'type': 'union', 'members': [ - {'name': 'id2', 'type': 'uint32_t'}, - {'name': 'desc2', 'type': 'const char *'} + {'file_name': '/', 'line_number': 7, + 'name': 'id2', 'type': 'uint32_t'}, + {'file_name': '/', 'line_number': 8, + 'name': 'desc2', 'type': 'const char *'} ]} ]) @@ -182,17 +188,21 @@ class HeaderParserTestCase(unittest.TestCase): self.assertSequenceEqual(parser._header_dict["union"], [{'name': 'SceneDesc', 'type': 'union', 'members': [ - {'name': 'id', 'type': 'uint32_t'}, - {'name': 'desc', 'type': 'const char *'} + {'file_name': '/', 'line_number': 4, + 'name': 'id', 'type': 'uint32_t'}, + {'file_name': '/', 'line_number': 5, + 'name': 'desc', 'type': 'const char *'} ]}]) parser._extract_struct(hjson["classes"]["AudioSceneDescriptor"]) self.assertSequenceEqual(parser._header_dict["struct"], [{'name': 'AudioSceneDescriptor', 'type': 'struct', 'members': [ - {'name': 'scene', 'type': 'SceneDesc'} + {'file_name': '/', 'line_number': 6, + 'name': 'scene', 'type': 'SceneDesc'} ]}]) def test_extract_struct(self): + self.maxDiff = None header_file = """ typedef struct { bool succeed; @@ -216,26 +226,40 @@ class HeaderParserTestCase(unittest.TestCase): parser = HeaderParser() hjson = json.loads(CppHeaderParser.CppHeader(header_file, "string").toJSON()) parser._extract_struct(hjson["classes"]["DevAbility"]) - self.assertSequenceEqual(parser._header_dict["struct"], [{'name': 'DevAbility', - 'type': 'struct', - 'members': [ - {'name': 'succeed', 'type': 'bool'}, - {'name': 'end', 'type': 'int8_t'}, - {'name': 'rate', 'type': 'float'}, - {'name': 'rate2', 'type': 'double'}, - {'name': 'desc', 'type': 'cstring'}, - {'name': 'location', 'type': 'std::string'}, - {'name': 'status', 'type': 'uint8_t'}, - {'name': 'busType', 'type': 'uint16_t'}, - {'name': 'num', 'type': 'int32_t'}, - {'name': 'count', 'type': 'uint32_t'}, - {'name': 'timestamp', 'type': 'uint64_t'}, - {'name': 'path', 'type': 'void'}, - {'name': 'chipInfo', 'type': 'char *'}, - {'name': 'status', 'type': 'RetStatus'}, - {'name': 'package', 'type': 'struct EvtPack'}, - {'name': 'pkgPtr', 'type': 'struct EvtPack *'} - ]}]) + self.assertSequenceEqual( + parser._header_dict["struct"], [{'name': 'DevAbility', + 'type': 'struct', + 'members': [ + {'file_name': '/', 'line_number': 3, 'name': 'succeed', + 'type': 'bool'}, + {'file_name': '/', 'line_number': 4, 'name': 'end', 'type': 'int8_t'}, + {'file_name': '/', 'line_number': 5, 'name': 'rate', 'type': 'float'}, + {'file_name': '/', 'line_number': 6, 'name': 'rate2', + 'type': 'double'}, + {'file_name': '/', 'line_number': 7, 'name': 'desc', + 'type': 'cstring'}, + {'file_name': '/', 'line_number': 8, 'name': 'location', + 'type': 'std::string'}, + {'file_name': '/', 'line_number': 9, 'name': 'status', + 'type': 'uint8_t'}, + {'file_name': '/', 'line_number': 10, 'name': 'busType', + 'type': 'uint16_t'}, + {'file_name': '/', 'line_number': 11, 'name': 'num', + 'type': 'int32_t'}, + {'file_name': '/', 'line_number': 12, 'name': 'count', + 'type': 'uint32_t'}, + {'file_name': '/', 'line_number': 13, 'name': 'timestamp', + 'type': 'uint64_t'}, + {'file_name': '/', 'line_number': 14, 'name': 'path', 'type': 'void'}, + {'file_name': '/', 'line_number': 15, 'name': 'chipInfo', + 'type': 'char *'}, + {'file_name': '/', 'line_number': 16, 'name': 'status', + 'type': 'RetStatus'}, + {'file_name': '/', 'line_number': 17, 'name': 'package', + 'type': 'struct EvtPack'}, + {'file_name': '/', 'line_number': 18, 'name': 'pkgPtr', + 'type': 'struct EvtPack *'} + ]}]) def test_extract_struct_with_enum_pointer(self): header_file = """ @@ -257,8 +281,10 @@ class HeaderParserTestCase(unittest.TestCase): self.assertSequenceEqual(parser._header_dict["struct"], [{'name': 'DevAbility', 'type': 'struct', 'members': [ - {'name': 'staPtr', 'type': 'Status_ENUM_POINTER'}, - {'name': 'testPtr', 'type': 'Test_ENUM_POINTER'} + {'file_name': '/', 'line_number': 3, + 'name': 'staPtr', 'type': 'Status_ENUM_POINTER'}, + {'file_name': '/', 'line_number': 4, + 'name': 'testPtr', 'type': 'Test_ENUM_POINTER'} ]}]) def test_extract_struct_with_macro(self): @@ -286,7 +312,8 @@ class HeaderParserTestCase(unittest.TestCase): self.assertSequenceEqual(parser._header_dict["struct"], [{'name': 'DevAbility', 'type': 'struct', 'members': [ - {'name': 'absCode', 'type': 'unsigned long *'} + {'file_name': '/', 'line_number': 8, + 'name': 'absCode', 'type': 'unsigned long *'} ]}]) def test_extract_struct_include_union_without_name(self): @@ -304,14 +331,17 @@ class HeaderParserTestCase(unittest.TestCase): self.assertSequenceEqual(parser._header_dict["union"], [{'name': '', 'type': 'union', 'members': [ - {'name': 'id', 'type': 'uint32_t'}, - {'name': 'desc', 'type': 'const char *'} + {'file_name': '/', 'line_number': 4, + 'name': 'id', 'type': 'uint32_t'}, + {'file_name': '/', 'line_number': 5, + 'name': 'desc', 'type': 'const char *'} ]}]) parser._extract_struct(hjson["classes"]["AudioSceneDescriptor"]) self.assertSequenceEqual(parser._header_dict["struct"], [{'name': 'AudioSceneDescriptor', 'type': 'struct', 'members': [ - {'name': '', 'type': ''} + {'file_name': '/', 'line_number': 6, + 'name': '', 'type': ''} ]}]) def test_extract_interface_with_method(self): @@ -328,13 +358,18 @@ class HeaderParserTestCase(unittest.TestCase): 'members': [ {'name': 'SetPowerStatus', 'params': [{'name': 'devIndex', - 'type': 'uint32_t'}]}, + 'type': 'uint32_t'}], + 'file_name': '/', + 'line_number': 3}, {'name': 'RunExtraCommand', 'params': [{'name': 'rand_name_0', 'type': 'uint32_t'}, {'name': 'rand_name_1', 'type': 'InputExtraCmd *'} - ]}]}]) + ], + 'file_name': '/', + 'line_number': 4 + }]}]) def test_extract_interface_with_function_point(self): header_file = """ @@ -352,28 +387,66 @@ class HeaderParserTestCase(unittest.TestCase): 'members': [ {'name': 'SetPowerStatus', 'params': [{'name': 'devIndex', - 'type': 'uint32_t'}]}, + 'type': 'uint32_t'}], + 'file_name': '/', + 'line_number': 3 + }, {'name': 'RunExtraCommand', 'params': [{'name': 'rand_name_0', 'type': 'uint32_t'}, {'name': 'rand_name_1', 'type': 'InputExtraCmd *'} - ]}, + ], + 'file_name': '/', + 'line_number': 4 + }, {'name': 'RunCapacitanceTest', 'params': [{'name': 'devIndex', 'type': 'uint32_t'}, {'name': 'testType', 'type': 'uint32_t'} - ]} + ], + 'file_name': '/', + 'line_number': 5 + } ]}]) - def test_extract_interface_callback(self): - # todo - pass - - def test_extract_interface_with_function_pointer_and_properties(self): - # todo - pass + def test_extract_interface_for_class(self): + header_file = """ + class ICameraDevice{ + public: + DECLARE_INTERFACE_DESCRIPTOR(u"HDI.Camera.V1_0.Device"); + virtual ~ICameraDevice() {} + virtual CamRetCode ICamera(); + virtual CamRetCode GetEnabledResults(std::vector &results) = 0; + virtual CamRetCode SetResultMode(const CallbackMode &mode) = 0; + }; + """ + parser = HeaderParser() + hjson = json.loads(CppHeaderParser.CppHeader(header_file, "string").toJSON()) + parser._extract_interface(hjson["classes"]["ICameraDevice"]) + self.assertSequenceEqual(parser._header_dict["interface"], [{'name': 'ICameraDevice', + 'members': [ + {'name': 'ICamera', + 'params': [], + 'file_name': '/', + 'line_number': 6 + }, + {'name': 'GetEnabledResults', + 'params': [{'name': 'results', + 'type': 'std::vector &'} + ], + 'file_name': '/', + 'line_number': 7 + }, + {'name': 'SetResultMode', + 'params': [{'name': 'mode', + 'type': 'const CallbackMode &'} + ], + 'file_name': '/', + 'line_number': 8 + } + ]}]) def test_extract_typedef(self): header_file = """ diff --git a/tools/idl-gen/testcase/test_idl_generator.py b/tools/idl-gen/testcase/test_idl_generator.py index 163861f4..59207ed8 100644 --- a/tools/idl-gen/testcase/test_idl_generator.py +++ b/tools/idl-gen/testcase/test_idl_generator.py @@ -48,7 +48,7 @@ class IDLGeneratorTestCase(unittest.TestCase): } } header = generator._parse_results['audio_test.h'] - generator._install_import(header) + generator._install_import(header, "test.h") self.assertEqual(generator._idl, "import include.audio.AudioRender;\n\n") def test_install_import_interfaces(self): @@ -77,7 +77,7 @@ class IDLGeneratorTestCase(unittest.TestCase): } } header = generator._parse_results['audio_test.h'] - generator._install_import(header) + generator._install_import(header, "test.h") self.assertEqual(generator._idl, "import include.audio.AudioRender;\n" "import include.audio.adapter.AudioAdapter;\n\n") @@ -103,7 +103,7 @@ class IDLGeneratorTestCase(unittest.TestCase): } } header = generator._parse_results['audio_test.h'] - generator._install_import(header) + generator._install_import(header, "test.h") self.assertEqual(generator._idl, "import include.AudioInterfaceOne;\nimport include.AudioInterfaceTwo;\n\n") def test_install_import_types(self): @@ -125,7 +125,7 @@ class IDLGeneratorTestCase(unittest.TestCase): } } header = generator._parse_results["audio_test.h"] - generator._install_import(header) + generator._install_import(header, "test.h") self.assertEqual(generator._idl, "import include.Types;\n\n") def test_install_import_merge_types(self): @@ -154,7 +154,7 @@ class IDLGeneratorTestCase(unittest.TestCase): } } header = generator._parse_results['audio_test.h'] - generator._install_import(header) + generator._install_import(header, "test.h") self.assertEqual(generator._idl, "import include.Types;\n\n") def test_install_import_types_and_interfaces(self): @@ -183,7 +183,7 @@ class IDLGeneratorTestCase(unittest.TestCase): } } header = generator._parse_results['audio_test.h'] - generator._install_import(header) + generator._install_import(header, "test.h") self.assertEqual(generator._idl, "import include.Types;\nimport include.audio.adapter.AudioAdapter;\n\n") def test_install_import_callback(self): @@ -201,7 +201,7 @@ class IDLGeneratorTestCase(unittest.TestCase): } } header = generator._parse_results['audio_test.h'] - generator._install_import(header) + generator._install_import(header, "test.h") self.assertEqual(generator._idl, "import include.audio.HotPlugCallback;\n" "import include.audio.VBlankCallback;\n\n") @@ -267,6 +267,21 @@ class IDLGeneratorTestCase(unittest.TestCase): " RunExtra([in] struct InputControllerDesc desc);\n" "};\n") + def test_install_interface_with_unknown_type(self): + header_file = """ + typedef struct { + int32_t (*RunExtra)(struct InputControllerDesc desc); + } InputController; + """ + parser = HeaderParser() + hjson = json.loads(CppHeaderParser.CppHeader(header_file, "string").toJSON()) + parser._extract_interface(hjson["classes"]["InputController"]) + generator = IDLGenerator() + generator._install_interface(parser._header_dict["interface"][0]) + self.assertEqual(generator._idl, "interface InputController {\n" + " RunExtra([in] /* unknown type: [InputControllerDesc] */ desc);\n" + "};\n") + def test_install_interface_callback(self): header_file = """ struct IFooCallback {