• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""Tests for distutils._msvccompiler."""
2import sys
3import unittest
4import os
5import threading
6
7from distutils.errors import DistutilsPlatformError
8from distutils.tests import support
9from test.support import run_unittest
10
11
12SKIP_MESSAGE = (None if sys.platform == "win32" else
13                "These tests are only for win32")
14
15@unittest.skipUnless(SKIP_MESSAGE is None, SKIP_MESSAGE)
16class msvccompilerTestCase(support.TempdirManager,
17                            unittest.TestCase):
18
19    def test_no_compiler(self):
20        import distutils._msvccompiler as _msvccompiler
21        # makes sure query_vcvarsall raises
22        # a DistutilsPlatformError if the compiler
23        # is not found
24        def _find_vcvarsall(plat_spec):
25            return None, None
26
27        old_find_vcvarsall = _msvccompiler._find_vcvarsall
28        _msvccompiler._find_vcvarsall = _find_vcvarsall
29        try:
30            self.assertRaises(DistutilsPlatformError,
31                              _msvccompiler._get_vc_env,
32                             'wont find this version')
33        finally:
34            _msvccompiler._find_vcvarsall = old_find_vcvarsall
35
36    def test_get_vc_env_unicode(self):
37        import distutils._msvccompiler as _msvccompiler
38
39        test_var = 'ṰḖṤṪ┅ṼẨṜ'
40        test_value = '₃⁴₅'
41
42        # Ensure we don't early exit from _get_vc_env
43        old_distutils_use_sdk = os.environ.pop('DISTUTILS_USE_SDK', None)
44        os.environ[test_var] = test_value
45        try:
46            env = _msvccompiler._get_vc_env('x86')
47            self.assertIn(test_var.lower(), env)
48            self.assertEqual(test_value, env[test_var.lower()])
49        finally:
50            os.environ.pop(test_var)
51            if old_distutils_use_sdk:
52                os.environ['DISTUTILS_USE_SDK'] = old_distutils_use_sdk
53
54    def test_get_vc2017(self):
55        import distutils._msvccompiler as _msvccompiler
56
57        # This function cannot be mocked, so pass it if we find VS 2017
58        # and mark it skipped if we do not.
59        version, path = _msvccompiler._find_vc2017()
60        if version:
61            self.assertGreaterEqual(version, 15)
62            self.assertTrue(os.path.isdir(path))
63        else:
64            raise unittest.SkipTest("VS 2017 is not installed")
65
66    def test_get_vc2015(self):
67        import distutils._msvccompiler as _msvccompiler
68
69        # This function cannot be mocked, so pass it if we find VS 2015
70        # and mark it skipped if we do not.
71        version, path = _msvccompiler._find_vc2015()
72        if version:
73            self.assertGreaterEqual(version, 14)
74            self.assertTrue(os.path.isdir(path))
75        else:
76            raise unittest.SkipTest("VS 2015 is not installed")
77
78
79class CheckThread(threading.Thread):
80    exc_info = None
81
82    def run(self):
83        try:
84            super().run()
85        except Exception:
86            self.exc_info = sys.exc_info()
87
88    def __bool__(self):
89        return not self.exc_info
90
91
92class TestSpawn(unittest.TestCase):
93    def test_concurrent_safe(self):
94        """
95        Concurrent calls to spawn should have consistent results.
96        """
97        import distutils._msvccompiler as _msvccompiler
98        compiler = _msvccompiler.MSVCCompiler()
99        compiler._paths = "expected"
100        inner_cmd = 'import os; assert os.environ["PATH"] == "expected"'
101        command = [sys.executable, '-c', inner_cmd]
102
103        threads = [
104            CheckThread(target=compiler.spawn, args=[command])
105            for n in range(100)
106        ]
107        for thread in threads:
108            thread.start()
109        for thread in threads:
110            thread.join()
111        assert all(threads)
112
113    def test_concurrent_safe_fallback(self):
114        """
115        If CCompiler.spawn has been monkey-patched without support
116        for an env, it should still execute.
117        """
118        import distutils._msvccompiler as _msvccompiler
119        from distutils import ccompiler
120        compiler = _msvccompiler.MSVCCompiler()
121        compiler._paths = "expected"
122
123        def CCompiler_spawn(self, cmd):
124            "A spawn without an env argument."
125            assert os.environ["PATH"] == "expected"
126
127        with unittest.mock.patch.object(
128                ccompiler.CCompiler, 'spawn', CCompiler_spawn):
129            compiler.spawn(["n/a"])
130
131        assert os.environ.get("PATH") != "expected"
132
133
134def test_suite():
135    return unittest.TestLoader().loadTestsFromTestCase(msvccompilerTestCase)
136
137if __name__ == "__main__":
138    run_unittest(test_suite())
139