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