summaryrefslogtreecommitdiff
path: root/tests/scanner/test_ccompiler.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/scanner/test_ccompiler.py')
-rw-r--r--tests/scanner/test_ccompiler.py215
1 files changed, 215 insertions, 0 deletions
diff --git a/tests/scanner/test_ccompiler.py b/tests/scanner/test_ccompiler.py
new file mode 100644
index 00000000..4a136d6a
--- /dev/null
+++ b/tests/scanner/test_ccompiler.py
@@ -0,0 +1,215 @@
+# GObject-Introspection - a framework for introspecting GObject libraries
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+import distutils
+import os
+import shlex
+import unittest
+from contextlib import contextmanager
+
+from giscanner.ccompiler import CCompiler, FLAGS_RETAINING_MACROS
+
+
+@contextmanager
+def Environ(new_environ):
+ """Context manager for os.environ."""
+ old_environ = os.environ.copy()
+ os.environ.clear()
+ os.environ.update(new_environ)
+ try:
+ yield
+ finally:
+ # Restore previous environment
+ os.environ.clear()
+ os.environ.update(old_environ)
+
+
+@unittest.skipIf(os.name != 'posix', 'tests for unix compiler only')
+class UnixCCompilerTest(unittest.TestCase):
+
+ def assertListStartsWith(self, seq, prefix):
+ """Checks whether seq starts with specified prefix."""
+ if not isinstance(seq, list):
+ raise self.fail('First argument is not a list: %r' % (seq,))
+ if not isinstance(prefix, list):
+ raise self.fail('Second argument is not a list: %r' % (prefix,))
+ self.assertSequenceEqual(seq[:len(prefix)], prefix, seq_type=list)
+
+ def assertIsSubsequence(self, list1, list2):
+ """Checks whether list1 is a subsequence of list2. Not necessarily a contiguous one."""
+ if not isinstance(list1, list):
+ raise self.fail('First argument is not a list: %r' % (list1,))
+ if not isinstance(list2, list):
+ raise self.fail('Second argument is not a list: %r' % (list2,))
+ start = 0
+ for elem in list1:
+ try:
+ start = list2.index(elem, start) + 1
+ except ValueError:
+ self.fail('%r is not a subsequence of %r' % (list1, list2))
+
+ def compile_args(self, environ={}, compiler_name='unix',
+ pkg_config_cflags=[], cpp_includes=[],
+ source='a.c', init_sections=[]):
+ """Returns a list of arguments that would be passed to the compiler executing given compilation step."""
+
+ try:
+ from unittest.mock import patch
+ except ImportError as e:
+ raise unittest.SkipTest(e)
+
+ with patch.object(distutils.ccompiler.CCompiler, 'spawn') as spawn:
+ with Environ(environ):
+ cc = CCompiler(compiler_name=compiler_name)
+ # Avoid check if target is newer from source.
+ cc.compiler.force = True
+ # Don't actually do anything.
+ cc.compiler.dry_run = True
+ cc.compile(pkg_config_cflags, cpp_includes, [source], init_sections)
+ spawn.assert_called_once()
+ args, kwargs = spawn.call_args
+ return args[0]
+
+ def preprocess_args(self, environ={}, compiler_name=None,
+ source='a.c', output=None, cpp_options=[]):
+ """Returns a list of arguments that would be passed to the preprocessor executing given preprocessing step."""
+
+ try:
+ from unittest.mock import patch
+ except ImportError as e:
+ raise unittest.SkipTest(e)
+
+ with patch.object(distutils.ccompiler.CCompiler, 'spawn') as spawn:
+ with Environ(environ):
+ cc = CCompiler(compiler_name=compiler_name)
+ # Avoid check if target is newer from source.
+ cc.compiler.force = True
+ # Don't actually do anything.
+ cc.compiler.dry_run = True
+ cc.preprocess(source, output, cpp_options)
+ spawn.assert_called_once()
+ args, kwargs = spawn.call_args
+ return args[0]
+
+ @unittest.skip("Currently a Python build time compiler is used as the default.")
+ def test_compile_default(self):
+ """Checks that cc is used as the default compiler."""
+ args = self.compile_args()
+ self.assertListStartsWith(args, ['cc'])
+
+ def test_compile_cc(self):
+ """Checks that CC overrides used compiler."""
+ args = self.compile_args(environ=dict(CC='supercc'))
+ self.assertListStartsWith(args, ['supercc'])
+
+ def test_preprocess_cc(self):
+ """Checks that CC overrides used preprocessor when CPP is unspecified."""
+ args = self.preprocess_args(environ=dict(CC='clang'))
+ self.assertListStartsWith(args, ['clang'])
+ self.assertIn('-E', args)
+
+ def test_preprocess_cpp(self):
+ """Checks that CPP overrides used preprocessor regardless of CC."""
+ args = self.preprocess_args(environ=dict(CC='my-compiler', CPP='my-preprocessor'))
+ self.assertListStartsWith(args, ['my-preprocessor'])
+ self.assertNotIn('-E', args)
+
+ @unittest.skip("Currently a Python build time preprocessor is used as the default")
+ def test_preprocess_default(self):
+ """Checks that cpp is used as the default preprocessor."""
+ args = self.preprocess_args()
+ self.assertListStartsWith(args, ['cpp'])
+
+ def test_multiple_args_in_cc(self):
+ """Checks that shell word splitting rules are used to split CC."""
+ args = self.compile_args(environ=dict(CC='build-log -m " hello there " gcc'))
+ self.assertListStartsWith(args, ['build-log', '-m', ' hello there ', 'gcc'])
+
+ def test_multiple_args_in_cpp(self):
+ """Checks that shell word splitting rules are used to split CPP."""
+ args = self.preprocess_args(environ=dict(CPP='build-log -m " hello there" gcc -E'))
+ self.assertListStartsWith(args, ['build-log', '-m', ' hello there', 'gcc', '-E'])
+
+ def test_deprecation_warnings_are_disabled_during_compilation(self):
+ """Checks that deprecation warnings are disabled during compilation."""
+ args = self.compile_args()
+ self.assertIn('-Wno-deprecated-declarations', args)
+
+ def test_preprocess_includes_cppflags(self):
+ """Checks that preprocessing step includes CPPFLAGS."""
+ args = self.preprocess_args(environ=dict(CPPFLAGS='-fsecure -Ddebug'))
+ self.assertIsSubsequence(['-fsecure', '-Ddebug'], args)
+
+ def test_compile_includes_cppflags(self):
+ """Checks that compilation step includes both CFLAGS and CPPFLAGS, in that order."""
+ args = self.compile_args(environ=dict(CFLAGS='-lfoo -Da="x y" -Weverything',
+ CPPFLAGS='-fsecure -Ddebug'))
+ self.assertIsSubsequence(['-lfoo', '-Da=x y', '-Weverything', '-fsecure', '-Ddebug'],
+ args)
+
+ def test_flags_retaining_macros_are_filtered_out(self):
+ """Checks that flags that would retain macros after preprocessing step are filtered out."""
+ args = self.preprocess_args(cpp_options=list(FLAGS_RETAINING_MACROS))
+ for flag in FLAGS_RETAINING_MACROS:
+ self.assertNotIn(flag, args)
+
+ @unittest.expectedFailure
+ def test_macros(self):
+ """"Checks that macros from CFLAGS are defined only once."""
+ args = self.compile_args(environ=dict(CFLAGS='-DSECRET_MACRO'))
+ self.assertEqual(1, args.count('-DSECRET_MACRO'))
+
+ @unittest.expectedFailure
+ def test_flags_retaining_macros_are_filtered_out_from_cppflags(self):
+ """Checks that flags that would retain macros after preprocessing step are filtered out from CPPFLAGS."""
+ cppflags = ' '.join(shlex.quote(flag) for flag in FLAGS_RETAINING_MACROS)
+ args = self.preprocess_args(environ=dict(CPPFLAGS=cppflags))
+ for flag in FLAGS_RETAINING_MACROS:
+ self.assertNotIn(flag, args)
+
+ def test_preprocess_preserves_comments(self):
+ """Checks that preprocessing step includes flag that preserves comments."""
+ args = self.preprocess_args()
+ self.assertIn('-C', args)
+
+ def test_perprocess_includes_cwd(self):
+ """Checks that preprocessing includes current working directory."""
+ args = self.preprocess_args()
+ self.assertIn('-I.', args)
+
+ @unittest.skip("Currently Python build time CPPFLAGS are included as well")
+ def test_preprocess_command(self):
+ """"Checks complete preprocessing command."""
+ args = self.preprocess_args(environ=dict(CPP='gcc -E'),
+ source='/tmp/file.c')
+ self.assertEqual(['gcc', '-E', '-I.', '-C', '/tmp/file.c'],
+ args)
+
+ @unittest.skip("Currently Python build time CFLAGS and CPPFLAGS are included as well")
+ def test_compile_command(self):
+ """Checks complete compilation command."""
+ args = self.compile_args(environ=dict(CC='clang'),
+ source='/tmp/file.c')
+ self.assertEqual(['clang',
+ '-c', '/tmp/file.c',
+ '-o', '/tmp/file.o',
+ '-Wno-deprecated-declarations'],
+ args)
+
+
+if __name__ == '__main__':
+ unittest.main()