1"""
2Test suite for _osx_support: shared OS X support functions.
3"""
4
5import os
6import platform
7import stat
8import sys
9import unittest
10
11from test.support import os_helper
12
13import _osx_support
14
15@unittest.skipUnless(sys.platform.startswith("darwin"), "requires OS X")
16class Test_OSXSupport(unittest.TestCase):
17
18    def setUp(self):
19        self.maxDiff = None
20        self.prog_name = 'bogus_program_xxxx'
21        self.temp_path_dir = os.path.abspath(os.getcwd())
22        self.env = self.enterContext(os_helper.EnvironmentVarGuard())
23        for cv in ('CFLAGS', 'LDFLAGS', 'CPPFLAGS',
24                            'BASECFLAGS', 'BLDSHARED', 'LDSHARED', 'CC',
25                            'CXX', 'PY_CFLAGS', 'PY_LDFLAGS', 'PY_CPPFLAGS',
26                            'PY_CORE_CFLAGS', 'PY_CORE_LDFLAGS'):
27            if cv in self.env:
28                self.env.unset(cv)
29
30    def add_expected_saved_initial_values(self, config_vars, expected_vars):
31        # Ensure that the initial values for all modified config vars
32        # are also saved with modified keys.
33        expected_vars.update(('_OSX_SUPPORT_INITIAL_'+ k,
34                config_vars[k]) for k in config_vars
35                    if config_vars[k] != expected_vars[k])
36
37    def test__find_executable(self):
38        if self.env['PATH']:
39            self.env['PATH'] = self.env['PATH'] + ':'
40        self.env['PATH'] = self.env['PATH'] + os.path.abspath(self.temp_path_dir)
41        os_helper.unlink(self.prog_name)
42        self.assertIsNone(_osx_support._find_executable(self.prog_name))
43        self.addCleanup(os_helper.unlink, self.prog_name)
44        with open(self.prog_name, 'w') as f:
45            f.write("#!/bin/sh\n/bin/echo OK\n")
46        os.chmod(self.prog_name, stat.S_IRWXU)
47        self.assertEqual(self.prog_name,
48                            _osx_support._find_executable(self.prog_name))
49
50    def test__read_output(self):
51        if self.env['PATH']:
52            self.env['PATH'] = self.env['PATH'] + ':'
53        self.env['PATH'] = self.env['PATH'] + os.path.abspath(self.temp_path_dir)
54        os_helper.unlink(self.prog_name)
55        self.addCleanup(os_helper.unlink, self.prog_name)
56        with open(self.prog_name, 'w') as f:
57            f.write("#!/bin/sh\n/bin/echo ExpectedOutput\n")
58        os.chmod(self.prog_name, stat.S_IRWXU)
59        self.assertEqual('ExpectedOutput',
60                            _osx_support._read_output(self.prog_name))
61
62    def test__find_build_tool(self):
63        out = _osx_support._find_build_tool('cc')
64        self.assertTrue(os.path.isfile(out),
65                            'cc not found - check xcode-select')
66
67    def test__get_system_version(self):
68        self.assertTrue(platform.mac_ver()[0].startswith(
69                                    _osx_support._get_system_version()))
70
71    def test__remove_original_values(self):
72        config_vars = {
73        'CC': 'gcc-test -pthreads',
74        }
75        expected_vars = {
76        'CC': 'clang -pthreads',
77        }
78        cv = 'CC'
79        newvalue = 'clang -pthreads'
80        _osx_support._save_modified_value(config_vars, cv, newvalue)
81        self.assertNotEqual(expected_vars, config_vars)
82        _osx_support._remove_original_values(config_vars)
83        self.assertEqual(expected_vars, config_vars)
84
85    def test__save_modified_value(self):
86        config_vars = {
87        'CC': 'gcc-test -pthreads',
88        }
89        expected_vars = {
90        'CC': 'clang -pthreads',
91        }
92        self.add_expected_saved_initial_values(config_vars, expected_vars)
93        cv = 'CC'
94        newvalue = 'clang -pthreads'
95        _osx_support._save_modified_value(config_vars, cv, newvalue)
96        self.assertEqual(expected_vars, config_vars)
97
98    def test__save_modified_value_unchanged(self):
99        config_vars = {
100        'CC': 'gcc-test -pthreads',
101        }
102        expected_vars = config_vars.copy()
103        cv = 'CC'
104        newvalue = 'gcc-test -pthreads'
105        _osx_support._save_modified_value(config_vars, cv, newvalue)
106        self.assertEqual(expected_vars, config_vars)
107
108    def test__supports_universal_builds(self):
109        import platform
110        mac_ver_tuple = tuple(int(i) for i in
111                            platform.mac_ver()[0].split('.')[0:2])
112        self.assertEqual(mac_ver_tuple >= (10, 4),
113                            _osx_support._supports_universal_builds())
114
115    def test__find_appropriate_compiler(self):
116        compilers = (
117                        ('gcc-test', 'i686-apple-darwin11-llvm-gcc-4.2'),
118                        ('clang', 'clang version 3.1'),
119                    )
120        config_vars = {
121        'CC': 'gcc-test -pthreads',
122        'CXX': 'cc++-test',
123        'CFLAGS': '-fno-strict-aliasing  -g -O3 -arch ppc -arch i386  ',
124        'LDFLAGS': '-arch ppc -arch i386   -g',
125        'CPPFLAGS': '-I. -isysroot /Developer/SDKs/MacOSX10.4u.sdk',
126        'BLDSHARED': 'gcc-test -bundle -arch ppc -arch i386 -g',
127        'LDSHARED': 'gcc-test -bundle -arch ppc -arch i386 '
128                        '-isysroot /Developer/SDKs/MacOSX10.4u.sdk -g',
129        }
130        expected_vars = {
131        'CC': 'clang -pthreads',
132        'CXX': 'clang++',
133        'CFLAGS': '-fno-strict-aliasing  -g -O3 -arch ppc -arch i386  ',
134        'LDFLAGS': '-arch ppc -arch i386   -g',
135        'CPPFLAGS': '-I. -isysroot /Developer/SDKs/MacOSX10.4u.sdk',
136        'BLDSHARED': 'clang -bundle -arch ppc -arch i386 -g',
137        'LDSHARED': 'clang -bundle -arch ppc -arch i386 '
138                        '-isysroot /Developer/SDKs/MacOSX10.4u.sdk -g',
139        }
140        self.add_expected_saved_initial_values(config_vars, expected_vars)
141
142        suffix = (':' + self.env['PATH']) if self.env['PATH'] else ''
143        self.env['PATH'] = os.path.abspath(self.temp_path_dir) + suffix
144        for c_name, c_output in compilers:
145            os_helper.unlink(c_name)
146            self.addCleanup(os_helper.unlink, c_name)
147            with open(c_name, 'w') as f:
148                f.write("#!/bin/sh\n/bin/echo " + c_output)
149            os.chmod(c_name, stat.S_IRWXU)
150        self.assertEqual(expected_vars,
151                            _osx_support._find_appropriate_compiler(
152                                    config_vars))
153
154    def test__remove_universal_flags(self):
155        config_vars = {
156        'CFLAGS': '-fno-strict-aliasing  -g -O3 -arch ppc -arch i386  ',
157        'LDFLAGS': '-arch ppc -arch i386   -g',
158        'CPPFLAGS': '-I. -isysroot /Developer/SDKs/MacOSX10.4u.sdk',
159        'BLDSHARED': 'gcc-4.0 -bundle  -arch ppc -arch i386 -g',
160        'LDSHARED': 'gcc-4.0 -bundle  -arch ppc -arch i386 '
161                        '-isysroot /Developer/SDKs/MacOSX10.4u.sdk -g',
162        }
163        expected_vars = {
164        'CFLAGS': '-fno-strict-aliasing  -g -O3    ',
165        'LDFLAGS': '    -g',
166        'CPPFLAGS': '-I.  ',
167        'BLDSHARED': 'gcc-4.0 -bundle    -g',
168        'LDSHARED': 'gcc-4.0 -bundle      -g',
169        }
170        self.add_expected_saved_initial_values(config_vars, expected_vars)
171
172        self.assertEqual(expected_vars,
173                            _osx_support._remove_universal_flags(
174                                    config_vars))
175
176    def test__remove_universal_flags_alternate(self):
177        # bpo-38360: also test the alternate single-argument form of -isysroot
178        config_vars = {
179        'CFLAGS': '-fno-strict-aliasing  -g -O3 -arch ppc -arch i386  ',
180        'LDFLAGS': '-arch ppc -arch i386   -g',
181        'CPPFLAGS': '-I. -isysroot/Developer/SDKs/MacOSX10.4u.sdk',
182        'BLDSHARED': 'gcc-4.0 -bundle  -arch ppc -arch i386 -g',
183        'LDSHARED': 'gcc-4.0 -bundle  -arch ppc -arch i386 '
184                        '-isysroot/Developer/SDKs/MacOSX10.4u.sdk -g',
185        }
186        expected_vars = {
187        'CFLAGS': '-fno-strict-aliasing  -g -O3    ',
188        'LDFLAGS': '    -g',
189        'CPPFLAGS': '-I.  ',
190        'BLDSHARED': 'gcc-4.0 -bundle    -g',
191        'LDSHARED': 'gcc-4.0 -bundle      -g',
192        }
193        self.add_expected_saved_initial_values(config_vars, expected_vars)
194
195        self.assertEqual(expected_vars,
196                            _osx_support._remove_universal_flags(
197                                    config_vars))
198
199    def test__remove_unsupported_archs(self):
200        config_vars = {
201        'CC': 'clang',
202        'CFLAGS': '-fno-strict-aliasing  -g -O3 -arch ppc -arch i386  ',
203        'LDFLAGS': '-arch ppc -arch i386   -g',
204        'CPPFLAGS': '-I. -isysroot /Developer/SDKs/MacOSX10.4u.sdk',
205        'BLDSHARED': 'gcc-4.0 -bundle  -arch ppc -arch i386 -g',
206        'LDSHARED': 'gcc-4.0 -bundle  -arch ppc -arch i386 '
207                        '-isysroot /Developer/SDKs/MacOSX10.4u.sdk -g',
208        }
209        expected_vars = {
210        'CC': 'clang',
211        'CFLAGS': '-fno-strict-aliasing  -g -O3  -arch i386  ',
212        'LDFLAGS': ' -arch i386   -g',
213        'CPPFLAGS': '-I. -isysroot /Developer/SDKs/MacOSX10.4u.sdk',
214        'BLDSHARED': 'gcc-4.0 -bundle   -arch i386 -g',
215        'LDSHARED': 'gcc-4.0 -bundle   -arch i386 '
216                        '-isysroot /Developer/SDKs/MacOSX10.4u.sdk -g',
217        }
218        self.add_expected_saved_initial_values(config_vars, expected_vars)
219
220        suffix = (':' + self.env['PATH']) if self.env['PATH'] else ''
221        self.env['PATH'] = os.path.abspath(self.temp_path_dir) + suffix
222        c_name = 'clang'
223        os_helper.unlink(c_name)
224        self.addCleanup(os_helper.unlink, c_name)
225        # exit status 255 means no PPC support in this compiler chain
226        with open(c_name, 'w') as f:
227            f.write("#!/bin/sh\nexit 255")
228        os.chmod(c_name, stat.S_IRWXU)
229        self.assertEqual(expected_vars,
230                            _osx_support._remove_unsupported_archs(
231                                    config_vars))
232
233    def test__override_all_archs(self):
234        self.env['ARCHFLAGS'] = '-arch x86_64'
235        config_vars = {
236        'CC': 'clang',
237        'CFLAGS': '-fno-strict-aliasing  -g -O3 -arch ppc -arch i386  ',
238        'LDFLAGS': '-arch ppc -arch i386   -g',
239        'CPPFLAGS': '-I. -isysroot /Developer/SDKs/MacOSX10.4u.sdk',
240        'BLDSHARED': 'gcc-4.0 -bundle  -arch ppc -arch i386 -g',
241        'LDSHARED': 'gcc-4.0 -bundle -arch ppc -arch i386 '
242                        '-isysroot /Developer/SDKs/MacOSX10.4u.sdk -g',
243        }
244        expected_vars = {
245        'CC': 'clang',
246        'CFLAGS': '-fno-strict-aliasing  -g -O3     -arch x86_64',
247        'LDFLAGS': '    -g -arch x86_64',
248        'CPPFLAGS': '-I. -isysroot /Developer/SDKs/MacOSX10.4u.sdk',
249        'BLDSHARED': 'gcc-4.0 -bundle    -g -arch x86_64',
250        'LDSHARED': 'gcc-4.0 -bundle   -isysroot '
251                        '/Developer/SDKs/MacOSX10.4u.sdk -g -arch x86_64',
252        }
253        self.add_expected_saved_initial_values(config_vars, expected_vars)
254
255        self.assertEqual(expected_vars,
256                            _osx_support._override_all_archs(
257                                    config_vars))
258
259    def test__check_for_unavailable_sdk(self):
260        config_vars = {
261        'CC': 'clang',
262        'CFLAGS': '-fno-strict-aliasing  -g -O3 -arch ppc -arch i386  '
263                        '-isysroot /Developer/SDKs/MacOSX10.1.sdk',
264        'LDFLAGS': '-arch ppc -arch i386   -g',
265        'CPPFLAGS': '-I. -isysroot /Developer/SDKs/MacOSX10.1.sdk',
266        'BLDSHARED': 'gcc-4.0 -bundle  -arch ppc -arch i386 -g',
267        'LDSHARED': 'gcc-4.0 -bundle  -arch ppc -arch i386 '
268                        '-isysroot /Developer/SDKs/MacOSX10.1.sdk -g',
269        }
270        expected_vars = {
271        'CC': 'clang',
272        'CFLAGS': '-fno-strict-aliasing  -g -O3 -arch ppc -arch i386  '
273                        ' ',
274        'LDFLAGS': '-arch ppc -arch i386   -g',
275        'CPPFLAGS': '-I.  ',
276        'BLDSHARED': 'gcc-4.0 -bundle  -arch ppc -arch i386 -g',
277        'LDSHARED': 'gcc-4.0 -bundle  -arch ppc -arch i386 '
278                        ' -g',
279        }
280        self.add_expected_saved_initial_values(config_vars, expected_vars)
281
282        self.assertEqual(expected_vars,
283                            _osx_support._check_for_unavailable_sdk(
284                                    config_vars))
285
286    def test__check_for_unavailable_sdk_alternate(self):
287        # bpo-38360: also test the alternate single-argument form of -isysroot
288        config_vars = {
289        'CC': 'clang',
290        'CFLAGS': '-fno-strict-aliasing  -g -O3 -arch ppc -arch i386  '
291                        '-isysroot/Developer/SDKs/MacOSX10.1.sdk',
292        'LDFLAGS': '-arch ppc -arch i386   -g',
293        'CPPFLAGS': '-I. -isysroot/Developer/SDKs/MacOSX10.1.sdk',
294        'BLDSHARED': 'gcc-4.0 -bundle  -arch ppc -arch i386 -g',
295        'LDSHARED': 'gcc-4.0 -bundle  -arch ppc -arch i386 '
296                        '-isysroot/Developer/SDKs/MacOSX10.1.sdk -g',
297        }
298        expected_vars = {
299        'CC': 'clang',
300        'CFLAGS': '-fno-strict-aliasing  -g -O3 -arch ppc -arch i386  '
301                        ' ',
302        'LDFLAGS': '-arch ppc -arch i386   -g',
303        'CPPFLAGS': '-I.  ',
304        'BLDSHARED': 'gcc-4.0 -bundle  -arch ppc -arch i386 -g',
305        'LDSHARED': 'gcc-4.0 -bundle  -arch ppc -arch i386 '
306                        ' -g',
307        }
308        self.add_expected_saved_initial_values(config_vars, expected_vars)
309
310        self.assertEqual(expected_vars,
311                            _osx_support._check_for_unavailable_sdk(
312                                    config_vars))
313
314    def test_get_platform_osx(self):
315        # Note, get_platform_osx is currently tested more extensively
316        # indirectly by test_sysconfig and test_distutils
317        config_vars = {
318        'CFLAGS': '-fno-strict-aliasing  -g -O3 -arch ppc -arch i386  '
319                        '-isysroot /Developer/SDKs/MacOSX10.1.sdk',
320        'MACOSX_DEPLOYMENT_TARGET': '10.6',
321        }
322        result = _osx_support.get_platform_osx(config_vars, ' ', ' ', ' ')
323        self.assertEqual(('macosx', '10.6', 'fat'), result)
324
325if __name__ == "__main__":
326    unittest.main()
327