[gobject-introspection/ignore-py-compiler-flags] Test commands executed by unix C compiler.



commit 3126ad5bc833f9404eab339a86be216f223ac687
Author: Tomasz Miąsko <tomasz miasko gmail com>
Date:   Thu Dec 20 00:00:00 2018 +0000

    Test commands executed by unix C compiler.
    
    No functional changes intended.
    
    Tests check that:
    * Compiler is obtained from CC.
    * cc is used as the default compiler.
      Currently not true as a Python build time compiler is used as the default.
    * Preprocessor is obtained from CC when CPP is unspecified by adding -E.
    * Preprocessor is obtained from CPP.
    * cpp is used as the default preprocessor.
      Currently not true as Python build time preprocessor is used as the default.
    * Shell word splitting rules are used to split CC.
    * Shell word splitting rules are used to split CPP.
    * Deprecation warnings are disabled during compilation.
    * Preprocessing step includes CPPFLAGS.
    * Compilation step includes both CFLAGS and CPPFLAGS, in that order.
    * Macros from CFLAGS are defined only once.
      Currently not true as they are defined twice.
    * Flags that would retain macros after preprocessing step are filtered out.
      Currently only partially true as they aren't filtered out from CPPFLAGS.
    * Preprocessing step includes flag that preserves comments.
    * Preprocessing step includes current working directory.
    * Complete preprocessing command doesn't contain anything unexpected.
      Currently not true as Python build time CPPFLAGS are included as well.
    * Complete build command doesn't contain anything unexpected.
      Currently not true as Python build time CFLAGS and CPPFLAGS are included as well.

 giscanner/ccompiler.py          |   6 +-
 tests/scanner/Makefile.am       |   1 +
 tests/scanner/meson.build       |   1 +
 tests/scanner/test_ccompiler.py | 215 ++++++++++++++++++++++++++++++++++++++++
 4 files changed, 222 insertions(+), 1 deletion(-)
---
diff --git a/giscanner/ccompiler.py b/giscanner/ccompiler.py
index 5db04535..24dcbcbf 100644
--- a/giscanner/ccompiler.py
+++ b/giscanner/ccompiler.py
@@ -34,6 +34,10 @@ from distutils.sysconfig import customize_compiler
 from . import utils
 
 
+# Flags that retain macros in preprocessed output.
+FLAGS_RETAINING_MACROS = ['-g3', '-ggdb3', '-gstabs3', '-gcoff3', '-gxcoff3', '-gvms3']
+
+
 class CCompiler(object):
 
     compiler_cmd = ''
@@ -376,6 +380,6 @@ class CCompiler(object):
             else:
                 # We expect the preprocessor to remove macros. If debugging is turned
                 # up high enough that won't happen, so don't add those flags. Bug #720504
-                if option not in ['-g3', '-ggdb3', '-gstabs3', '-gcoff3', '-gxcoff3', '-gvms3']:
+                if option not in FLAGS_RETAINING_MACROS:
                     other_options.append(option)
         return (includes, macros, other_options)
diff --git a/tests/scanner/Makefile.am b/tests/scanner/Makefile.am
index d5124e73..e288fdee 100644
--- a/tests/scanner/Makefile.am
+++ b/tests/scanner/Makefile.am
@@ -229,6 +229,7 @@ CHECKDOCS =
 endif
 
 PYTESTS = \
+       test_ccompiler.py \
        test_shlibs.py \
        test_pkgconfig.py \
        test_sourcescanner.py \
diff --git a/tests/scanner/meson.build b/tests/scanner/meson.build
index 62809169..e8b11644 100644
--- a/tests/scanner/meson.build
+++ b/tests/scanner/meson.build
@@ -5,6 +5,7 @@ if test_env_common_path.length() > 0
 endif
 
 scanner_test_files = [
+  'test_ccompiler.py',
   'test_shlibs.py',
   'test_sourcescanner.py',
   'test_transformer.py',
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()


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]