import unittest import tempfile import os import sys import textwrap os.environ['GI_SCANNER_DISABLE_CACHE'] = '1' from giscanner import ast from giscanner.sourcescanner import SourceScanner from giscanner.transformer import Transformer from giscanner.message import MessageLogger def create_scanner_from_source_string(source): ss = SourceScanner() tmp_fd, tmp_name = tempfile.mkstemp(suffix='.h', text=True) try: with os.fdopen(tmp_fd, 'wt') as file: file.write(source) ss.parse_files([tmp_name]) finally: os.unlink(tmp_name) return ss def load_namespace_from_source_string(namespace, source): ss = create_scanner_from_source_string(source) xformer = Transformer(namespace) xformer.parse(ss.get_symbols()) class TestIdentifierFilter(unittest.TestCase): def test_underscore_t_identifier_filter(self): cmd = [sys.executable, '-c', textwrap.dedent(""" import sys, re for line in sys.stdin: line = re.sub(r"^test_t$", "TestContext", line) line = re.sub(r"_t$", "", line) line = re.sub(r"^test_", "Test_", line) line = re.sub(r"_([a-z])", lambda m: m.group(1).title(), line) sys.stdout.write(line)""")] namespace = ast.Namespace('Test', '1.0') xformer = Transformer(namespace, identifier_filter_cmd=cmd) self.assertEqual(xformer.strip_identifier('test_t'), 'Context') self.assertEqual(xformer.strip_identifier('test_foo_t'), 'Foo') self.assertEqual(xformer.strip_identifier('test_foot'), 'Foot') self.assertEqual(xformer.strip_identifier('test_foo_bart'), 'FooBart') self.assertEqual(xformer.strip_identifier('test_foo_tart'), 'FooTart') def test_invalid_command(self): cmd = ['this-is-not-a-real-command'] namespace = ast.Namespace('Test', '1.0') xformer = Transformer(namespace, identifier_filter_cmd=cmd) self.assertRaises(OSError, xformer.strip_identifier, 'test_t') def test_invalid_argument(self): cmd = [sys.executable, '--not-a-valid-argument'] namespace = ast.Namespace('Test', '1.0') xformer = Transformer(namespace, identifier_filter_cmd=cmd) self.assertRaises(ValueError, xformer.strip_identifier, 'test_t') class TestSymbolFilter(unittest.TestCase): def test_split_csymbol(self): cmd = [ sys.executable, '-c', 'import sys; sys.stdout.write("test_" + sys.stdin.read())'] namespace = ast.Namespace('Test', '1.0') xformer = Transformer(namespace, symbol_filter_cmd=cmd) self.assertEqual( xformer.split_csymbol('foo_bar_quux')[1], "foo_bar_quux") class TestStructTypedefs(unittest.TestCase): def setUp(self): # Hack to set logging singleton self.namespace = ast.Namespace('Test', '1.0') logger = MessageLogger.get(namespace=self.namespace) logger.enable_warnings(True) def test_anonymous_typedef(self): load_namespace_from_source_string(self.namespace, """ typedef struct { int value; } TestStruct; """) self.assertEqual(len(self.namespace.names), 1) node = self.namespace.get('Struct') self.assertTrue(node is not None) self.assertTrue(isinstance(node, ast.Record)) self.assertFalse(node.disguised) self.assertEqual(len(node.fields), 1) def test_typedef_before(self): load_namespace_from_source_string(self.namespace, """ typedef struct _TestStruct TestStruct; struct _TestStruct { int value; }; """) self.assertEqual(len(self.namespace.names), 1) node = self.namespace.get('Struct') self.assertTrue(node is not None) self.assertTrue(isinstance(node, ast.Record)) self.assertFalse(node.disguised) self.assertEqual(len(node.fields), 1) def test_typedef_after(self): load_namespace_from_source_string(self.namespace, """ struct _TestStruct { int value; }; typedef struct _TestStruct TestStruct; """) self.assertEqual(len(self.namespace.names), 1) node = self.namespace.get('Struct') self.assertTrue(node is not None) self.assertTrue(isinstance(node, ast.Record)) self.assertFalse(node.disguised) self.assertEqual(len(node.fields), 1) def test_tag_and_typedef(self): load_namespace_from_source_string(self.namespace, """ typedef struct _TestStruct { int value; } TestStruct; """) self.assertEqual(len(self.namespace.names), 1) node = self.namespace.get('Struct') self.assertTrue(node is not None) self.assertTrue(isinstance(node, ast.Record)) self.assertFalse(node.disguised) self.assertEqual(len(node.fields), 1) def test_struct_tag_only(self): load_namespace_from_source_string(self.namespace, """ struct TestStruct { int value; }; """) self.assertEqual(len(self.namespace.names), 1) node = self.namespace.get('Struct') self.assertTrue(node is not None) self.assertTrue(isinstance(node, ast.Record)) self.assertFalse(node.disguised) self.assertEqual(len(node.fields), 1) def test_struct_aliases(self): load_namespace_from_source_string(self.namespace, """ typedef struct { int value; } TestStruct; typedef TestStruct TestStructAlias1; typedef TestStruct TestStructAlias2; """) self.assertEqual(len(self.namespace.names), 3) node = self.namespace.get('Struct') self.assertTrue(node is not None) self.assertTrue(isinstance(node, ast.Record)) self.assertFalse(node.disguised) self.assertEqual(len(node.fields), 1) alias = self.namespace.get('StructAlias1') self.assertTrue(isinstance(alias, ast.Alias)) self.assertEqual(alias.target, node) self.assertEqual(alias.ctype, 'TestStructAlias1') alias = self.namespace.get('StructAlias2') self.assertTrue(isinstance(alias, ast.Alias)) self.assertEqual(alias.target, node) self.assertEqual(alias.ctype, 'TestStructAlias2') def test_struct_tag_aliases_before(self): # This test is similar to how GObject and GInitiallyUnowned are setup load_namespace_from_source_string(self.namespace, """ typedef struct _TestStruct TestStruct; typedef struct _TestStruct TestStructAlias; struct _TestStruct { int value; }; """) self.assertEqual(len(self.namespace.names), 2) node = self.namespace.get('Struct') self.assertTrue(node is not None) self.assertTrue(isinstance(node, ast.Record)) self.assertFalse(node.disguised) self.assertEqual(len(node.fields), 1) self.assertEqual(node.ctype, 'TestStruct') shared = self.namespace.get('StructAlias') self.assertTrue(shared is not None) self.assertTrue(isinstance(shared, ast.Record)) self.assertFalse(shared.disguised) self.assertEqual(len(shared.fields), 1) self.assertEqual(shared.ctype, 'TestStructAlias') def test_struct_tag_aliases_after(self): load_namespace_from_source_string(self.namespace, """ struct _TestStruct { int value; }; typedef struct _TestStruct TestStruct; typedef struct _TestStruct TestStructAlias; """) self.assertEqual(len(self.namespace.names), 2) node = self.namespace.get('Struct') self.assertTrue(node is not None) self.assertTrue(isinstance(node, ast.Record)) self.assertFalse(node.disguised) self.assertEqual(len(node.fields), 1) self.assertEqual(node.ctype, 'TestStruct') shared = self.namespace.get('StructAlias') self.assertTrue(shared is not None) self.assertTrue(isinstance(shared, ast.Record)) self.assertFalse(shared.disguised) self.assertEqual(len(shared.fields), 1) self.assertEqual(shared.ctype, 'TestStructAlias') def test_struct_pointer(self): load_namespace_from_source_string(self.namespace, """ typedef struct { int value; } TestStruct; typedef TestStruct* TestStructPtr; """) self.assertEqual(len(self.namespace.names), 2) node = self.namespace.get('Struct') self.assertTrue(node is not None) self.assertTrue(isinstance(node, ast.Record)) self.assertEqual(len(node.fields), 1) self.assertEqual(node.ctype, 'TestStruct') ptr = self.namespace.get('StructPtr') self.assertTrue(ptr is not None) self.assertTrue(isinstance(ptr, ast.Alias)) self.assertEqual(ptr.ctype, 'TestStructPtr') def test_struct_tag_pointer(self): load_namespace_from_source_string(self.namespace, """ typedef struct _TestStruct TestStruct; struct _TestStruct{ int value; }; typedef struct _TestStruct* TestStructPtr; """) self.assertEqual(len(self.namespace.names), 2) node = self.namespace.get('Struct') self.assertTrue(node is not None) self.assertTrue(isinstance(node, ast.Record)) self.assertFalse(node.disguised) self.assertEqual(node.ctype, 'TestStruct') self.assertEqual(len(node.fields), 1) ptr = self.namespace.get('StructPtr') self.assertTrue(ptr is not None) # This currently gives a disguised Record instead of an Alias self.assertTrue(isinstance(ptr, ast.Record)) self.assertTrue(ptr.disguised) self.assertEqual(len(ptr.fields), 0) self.assertEqual(ptr.ctype, 'TestStructPtr') class TestNestedStructs(unittest.TestCase): def setUp(self): # Hack to set logging singleton self.namespace = ast.Namespace('Test', '1.0') logger = MessageLogger.get(namespace=self.namespace) logger.enable_warnings(True) def test_nested_named(self): load_namespace_from_source_string(self.namespace, """ typedef struct { int value; } TestSimpleStruct; typedef struct { TestSimpleStruct nested_struct; } TestStructWithNested; """) self.assertEqual(len(self.namespace.names), 2) node = self.namespace.get('StructWithNested') self.assertEqual(len(node.fields), 1) simple = self.namespace.get('SimpleStruct') self.assertTrue(node is not None) field = node.fields[0] self.assertTrue(field is not None) self.assertTrue(isinstance(field, ast.Field)) self.assertEqual(field.type, simple) self.assertEqual(field.name, 'nested_struct') def test_nested_anonymous(self): load_namespace_from_source_string(self.namespace, """ typedef struct { struct { int value; }; } TestStructWithNestedAnon; """) self.assertEqual(len(self.namespace.names), 1) node = self.namespace.get('StructWithNestedAnon') self.assertEqual(len(node.fields), 1) field = node.fields[0] self.assertTrue(field is not None) self.assertTrue(isinstance(field, ast.Field)) self.assertEqual(field.name, None) anon = field.anonymous_node self.assertTrue(isinstance(anon, ast.Record)) self.assertEqual(len(anon.fields), 1) anon_field = anon.fields[0] self.assertTrue(anon_field is not None) self.assertTrue(isinstance(anon_field, ast.Field)) self.assertEqual(anon_field.name, 'value') def test_nested(self): load_namespace_from_source_string(self.namespace, """ typedef struct { struct { int value; } nested; } TestStructWithNested; """) self.assertEqual(len(self.namespace.names), 1) node = self.namespace.get('StructWithNested') self.assertEqual(len(node.fields), 1) field = node.fields[0] self.assertTrue(field is not None) self.assertTrue(isinstance(field, ast.Field)) self.assertEqual(field.name, 'nested') nested = field.anonymous_node self.assertTrue(isinstance(nested, ast.Record)) self.assertEqual(len(nested.fields), 1) self.assertEqual(nested.name, 'nested') nested_field = nested.fields[0] self.assertTrue(nested_field is not None) self.assertTrue(isinstance(nested_field, ast.Field)) self.assertEqual(nested_field.name, 'value') def test_struct_ptr(self): load_namespace_from_source_string(self.namespace, """ typedef struct { int value; } TestSimpleStruct; typedef struct { TestSimpleStruct *struct_ptr; } TestStructWithNestedPtr; """) self.assertEqual(len(self.namespace.names), 2) node = self.namespace.get('StructWithNestedPtr') self.assertEqual(len(node.fields), 1) simple = self.namespace.get('SimpleStruct') self.assertTrue(simple is not None) field = node.fields[0] self.assertTrue(field is not None) self.assertTrue(isinstance(field, ast.Field)) self.assertEqual(field.type.ctype, 'TestSimpleStruct*') self.assertEqual(field.name, 'struct_ptr') class TestUnions(unittest.TestCase): def setUp(self): # Hack to set logging singleton self.namespace = ast.Namespace('Test', '1.0') logger = MessageLogger.get(namespace=self.namespace) logger.enable_warnings(True) def test_union_with_struct(self): load_namespace_from_source_string(self.namespace, """ typedef struct { int value; } TestSimpleStruct; typedef union { TestSimpleStruct nested_struct; int value; } TestUnionWithNested; """) self.assertEqual(len(self.namespace.names), 2) node = self.namespace.get('UnionWithNested') self.assertEqual(len(node.fields), 2) simple = self.namespace.get('SimpleStruct') self.assertTrue(node is not None) field = node.fields[0] self.assertEqual(field.type, simple) self.assertEqual(field.name, 'nested_struct') field = node.fields[1] self.assertEqual(field.type.ctype, 'int') self.assertEqual(field.name, 'value') def test_nested(self): load_namespace_from_source_string(self.namespace, """ typedef struct { union { int ival; float fval; } nested; } TestNestedUnion; """) self.assertEqual(len(self.namespace.names), 1) node = self.namespace.get('NestedUnion') self.assertEqual(len(node.fields), 1) field = node.fields[0] self.assertEqual(field.name, 'nested') nested = field.anonymous_node self.assertTrue(isinstance(nested, ast.Union)) self.assertEqual(nested.name, 'nested') self.assertEqual(len(nested.fields), 2) self.assertEqual(nested.fields[0].name, 'ival') self.assertEqual(nested.fields[1].name, 'fval') def test_nested_anonymous(self): load_namespace_from_source_string(self.namespace, """ typedef struct { union { int ival; float fval; }; } TestStructWithNestedUnion; """) self.assertEqual(len(self.namespace.names), 1) node = self.namespace.get('StructWithNestedUnion') self.assertEqual(len(node.fields), 1) field = node.fields[0] self.assertEqual(field.name, None) nested = field.anonymous_node self.assertTrue(isinstance(nested, ast.Union)) self.assertEqual(nested.name, None) self.assertEqual(len(nested.fields), 2) self.assertEqual(nested.fields[0].name, 'ival') self.assertEqual(nested.fields[0].type.ctype, 'int') self.assertEqual(nested.fields[1].name, 'fval') self.assertEqual(nested.fields[1].type.ctype, 'float') class TestCallbacks(unittest.TestCase): def setUp(self): # Hack to set logging singleton self.namespace = ast.Namespace('Test', '1.0') logger = MessageLogger.get(namespace=self.namespace) logger.enable_warnings(True) def test_union_with_struct(self): load_namespace_from_source_string(self.namespace, """ typedef void (*TestCallback)(int value); """) self.assertEqual(len(self.namespace.names), 1) node = self.namespace.get('Callback') self.assertTrue(isinstance(node, ast.Callback)) class TestArrays(unittest.TestCase): def setUp(self): # Hack to set logging singleton self.namespace = ast.Namespace('Test', '1.0') logger = MessageLogger.get(namespace=self.namespace) logger.enable_warnings(True) def test_multidimensional_arrays(self): """Multidimensional arrays are flattend into a single dimension.""" load_namespace_from_source_string(self.namespace, """ typedef struct { int data[2][3][5][7][11]; } TestMultiDimArray; """) node = self.namespace.get('MultiDimArray') self.assertIsNotNone(node) field = node.fields[0] self.assertIsInstance(field, ast.Field) self.assertIsInstance(field.type, ast.Array) self.assertEqual(field.type.element_type, ast.TYPE_INT) self.assertEqual(field.type.size, 2 * 3 * 5 * 7 * 11) def test_flexible_array_member(self): """Flexible array members don't break transformer. They are generally unsupported, so nothing else is validated. """ load_namespace_from_source_string(self.namespace, """ typedef struct { int length; unsigned char data[]; } TestFlexibleArray; """) node = self.namespace.get('FlexibleArray') self.assertIsNotNone(node) if __name__ == '__main__': unittest.main()