1# GObject-Introspection - a framework for introspecting GObject libraries 2# 3# This library is free software; you can redistribute it and/or 4# modify it under the terms of the GNU Lesser General Public 5# License as published by the Free Software Foundation; either 6# version 2 of the License, or (at your option) any later version. 7# 8# This library is distributed in the hope that it will be useful, 9# but WITHOUT ANY WARRANTY; without even the implied warranty of 10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11# Lesser General Public License for more details. 12# 13# You should have received a copy of the GNU Lesser General Public 14# License along with this library; if not, write to the 15# Free Software Foundation, Inc., 59 Temple Place - Suite 330, 16# Boston, MA 02111-1307, USA. 17 18import distutils 19import os 20import shlex 21import unittest 22from contextlib import contextmanager 23 24from giscanner.ccompiler import CCompiler, FLAGS_RETAINING_MACROS 25 26 27@contextmanager 28def Environ(new_environ): 29 """Context manager for os.environ.""" 30 old_environ = os.environ.copy() 31 os.environ.clear() 32 os.environ.update(new_environ) 33 try: 34 yield 35 finally: 36 # Restore previous environment 37 os.environ.clear() 38 os.environ.update(old_environ) 39 40 41@unittest.skipIf(os.name != 'posix', 'tests for unix compiler only') 42class UnixCCompilerTest(unittest.TestCase): 43 44 def assertListStartsWith(self, seq, prefix): 45 """Checks whether seq starts with specified prefix.""" 46 if not isinstance(seq, list): 47 raise self.fail('First argument is not a list: %r' % (seq,)) 48 if not isinstance(prefix, list): 49 raise self.fail('Second argument is not a list: %r' % (prefix,)) 50 self.assertSequenceEqual(seq[:len(prefix)], prefix, seq_type=list) 51 52 def assertIsSubsequence(self, list1, list2): 53 """Checks whether list1 is a subsequence of list2. Not necessarily a contiguous one.""" 54 if not isinstance(list1, list): 55 raise self.fail('First argument is not a list: %r' % (list1,)) 56 if not isinstance(list2, list): 57 raise self.fail('Second argument is not a list: %r' % (list2,)) 58 start = 0 59 for elem in list1: 60 try: 61 start = list2.index(elem, start) + 1 62 except ValueError: 63 self.fail('%r is not a subsequence of %r' % (list1, list2)) 64 65 def test_link_cmd(self): 66 with Environ(dict(CC="foobar")): 67 compiler = CCompiler() 68 self.assertEqual(compiler.linker_cmd[0], "foobar") 69 70 def test_link_args_override(self): 71 with Environ(dict(CC="foobar")): 72 compiler = CCompiler() 73 self.assertEqual(compiler.compiler.linker_so[0], "foobar") 74 75 def compile_args(self, environ={}, compiler_name='unix', 76 pkg_config_cflags=[], cpp_includes=[], 77 source='a.c', init_sections=[]): 78 """Returns a list of arguments that would be passed to the compiler executing given compilation step.""" 79 80 try: 81 from unittest.mock import patch 82 except ImportError as e: 83 raise unittest.SkipTest(e) 84 85 with patch.object(distutils.ccompiler.CCompiler, 'spawn') as spawn: 86 with Environ(environ): 87 cc = CCompiler(compiler_name=compiler_name) 88 # Avoid check if target is newer from source. 89 cc.compiler.force = True 90 # Don't actually do anything. 91 cc.compiler.dry_run = True 92 cc.compile(pkg_config_cflags, cpp_includes, [source], init_sections) 93 self.assertEqual(1, spawn.call_count) 94 args, kwargs = spawn.call_args 95 return args[0] 96 97 def preprocess_args(self, environ={}, compiler_name=None, 98 source='a.c', output=None, cpp_options=[]): 99 """Returns a list of arguments that would be passed to the preprocessor executing given preprocessing step.""" 100 101 try: 102 from unittest.mock import patch 103 except ImportError as e: 104 raise unittest.SkipTest(e) 105 106 with patch.object(distutils.ccompiler.CCompiler, 'spawn') as spawn: 107 with Environ(environ): 108 cc = CCompiler(compiler_name=compiler_name) 109 # Avoid check if target is newer from source. 110 cc.compiler.force = True 111 # Don't actually do anything. 112 cc.compiler.dry_run = True 113 cc.preprocess(source, output, cpp_options) 114 self.assertEqual(1, spawn.call_count) 115 args, kwargs = spawn.call_args 116 return args[0] 117 118 @unittest.skip("Currently a Python build time compiler is used as the default.") 119 def test_compile_default(self): 120 """Checks that cc is used as the default compiler.""" 121 args = self.compile_args() 122 self.assertListStartsWith(args, ['cc']) 123 124 def test_compile_cc(self): 125 """Checks that CC overrides used compiler.""" 126 args = self.compile_args(environ=dict(CC='supercc')) 127 self.assertListStartsWith(args, ['supercc']) 128 129 def test_preprocess_cc(self): 130 """Checks that CC overrides used preprocessor when CPP is unspecified.""" 131 args = self.preprocess_args(environ=dict(CC='clang')) 132 self.assertListStartsWith(args, ['clang']) 133 self.assertIn('-E', args) 134 135 def test_preprocess_cpp(self): 136 """Checks that CPP overrides used preprocessor regardless of CC.""" 137 args = self.preprocess_args(environ=dict(CC='my-compiler', CPP='my-preprocessor')) 138 self.assertListStartsWith(args, ['my-preprocessor']) 139 self.assertNotIn('-E', args) 140 141 @unittest.skip("Currently a Python build time preprocessor is used as the default") 142 def test_preprocess_default(self): 143 """Checks that cpp is used as the default preprocessor.""" 144 args = self.preprocess_args() 145 self.assertListStartsWith(args, ['cpp']) 146 147 def test_multiple_args_in_cc(self): 148 """Checks that shell word splitting rules are used to split CC.""" 149 args = self.compile_args(environ=dict(CC='build-log -m " hello there " gcc')) 150 self.assertListStartsWith(args, ['build-log', '-m', ' hello there ', 'gcc']) 151 152 def test_multiple_args_in_cpp(self): 153 """Checks that shell word splitting rules are used to split CPP.""" 154 args = self.preprocess_args(environ=dict(CPP='build-log -m " hello there" gcc -E')) 155 self.assertListStartsWith(args, ['build-log', '-m', ' hello there', 'gcc', '-E']) 156 157 def test_deprecation_warnings_are_disabled_during_compilation(self): 158 """Checks that deprecation warnings are disabled during compilation.""" 159 args = self.compile_args() 160 self.assertIn('-Wno-deprecated-declarations', args) 161 162 def test_preprocess_includes_cppflags(self): 163 """Checks that preprocessing step includes CPPFLAGS.""" 164 args = self.preprocess_args(environ=dict(CPPFLAGS='-fsecure -Ddebug')) 165 self.assertIsSubsequence(['-fsecure', '-Ddebug'], args) 166 167 def test_compile_includes_cppflags(self): 168 """Checks that compilation step includes both CFLAGS and CPPFLAGS, in that order.""" 169 args = self.compile_args(environ=dict(CFLAGS='-lfoo -Da="x y" -Weverything', 170 CPPFLAGS='-fsecure -Ddebug')) 171 self.assertIsSubsequence(['-lfoo', '-Da=x y', '-Weverything', '-fsecure', '-Ddebug'], 172 args) 173 174 def test_flags_retaining_macros_are_filtered_out(self): 175 """Checks that flags that would retain macros after preprocessing step are filtered out.""" 176 args = self.preprocess_args(cpp_options=list(FLAGS_RETAINING_MACROS)) 177 for flag in FLAGS_RETAINING_MACROS: 178 self.assertNotIn(flag, args) 179 180 @unittest.expectedFailure 181 def test_macros(self): 182 """"Checks that macros from CFLAGS are defined only once.""" 183 args = self.compile_args(environ=dict(CFLAGS='-DSECRET_MACRO')) 184 self.assertEqual(1, args.count('-DSECRET_MACRO')) 185 186 @unittest.expectedFailure 187 def test_flags_retaining_macros_are_filtered_out_from_cppflags(self): 188 """Checks that flags that would retain macros after preprocessing step are filtered out from CPPFLAGS.""" 189 cppflags = ' '.join(shlex.quote(flag) for flag in FLAGS_RETAINING_MACROS) 190 args = self.preprocess_args(environ=dict(CPPFLAGS=cppflags)) 191 for flag in FLAGS_RETAINING_MACROS: 192 self.assertNotIn(flag, args) 193 194 def test_preprocess_preserves_comments(self): 195 """Checks that preprocessing step includes flag that preserves comments.""" 196 args = self.preprocess_args() 197 self.assertIn('-C', args) 198 199 def test_perprocess_includes_cwd(self): 200 """Checks that preprocessing includes current working directory.""" 201 args = self.preprocess_args() 202 self.assertIn('-I.', args) 203 204 def test_preprocess_command(self): 205 """"Checks complete preprocessing command.""" 206 args = self.preprocess_args(environ=dict(CPP='gcc -E'), 207 source='/tmp/file.c') 208 self.assertEqual(['gcc', '-E', '-I.', '-C', '/tmp/file.c'], 209 args) 210 211 def test_compile_command(self): 212 """Checks complete compilation command.""" 213 args = self.compile_args(environ=dict(CC='clang'), 214 source='/tmp/file.c') 215 self.assertEqual(['clang', 216 '-c', '/tmp/file.c', 217 '-o', '/tmp/file.o', 218 '-Wno-deprecated-declarations'], 219 args) 220 221 222if __name__ == '__main__': 223 unittest.main() 224