test_msvc.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. """
  2. Tests for msvc support module.
  3. """
  4. import os
  5. import contextlib
  6. import distutils.errors
  7. import mock
  8. import pytest
  9. from . import contexts
  10. # importing only setuptools should apply the patch
  11. __import__('setuptools')
  12. pytest.importorskip("distutils.msvc9compiler")
  13. def mock_reg(hkcu=None, hklm=None):
  14. """
  15. Return a mock for distutils.msvc9compiler.Reg, patched
  16. to mock out the functions that access the registry.
  17. """
  18. _winreg = getattr(distutils.msvc9compiler, '_winreg', None)
  19. winreg = getattr(distutils.msvc9compiler, 'winreg', _winreg)
  20. hives = {
  21. winreg.HKEY_CURRENT_USER: hkcu or {},
  22. winreg.HKEY_LOCAL_MACHINE: hklm or {},
  23. }
  24. @classmethod
  25. def read_keys(cls, base, key):
  26. """Return list of registry keys."""
  27. hive = hives.get(base, {})
  28. return [
  29. k.rpartition('\\')[2]
  30. for k in hive if k.startswith(key.lower())
  31. ]
  32. @classmethod
  33. def read_values(cls, base, key):
  34. """Return dict of registry keys and values."""
  35. hive = hives.get(base, {})
  36. return dict(
  37. (k.rpartition('\\')[2], hive[k])
  38. for k in hive if k.startswith(key.lower())
  39. )
  40. return mock.patch.multiple(
  41. distutils.msvc9compiler.Reg,
  42. read_keys=read_keys, read_values=read_values)
  43. class TestModulePatch:
  44. """
  45. Ensure that importing setuptools is sufficient to replace
  46. the standard find_vcvarsall function with a version that
  47. recognizes the "Visual C++ for Python" package.
  48. """
  49. key_32 = r'software\microsoft\devdiv\vcforpython\9.0\installdir'
  50. key_64 = key_32.replace(r'\microsoft', r'\wow6432node\microsoft')
  51. def test_patched(self):
  52. "Test the module is actually patched"
  53. mod_name = distutils.msvc9compiler.find_vcvarsall.__module__
  54. assert mod_name == "setuptools.msvc", "find_vcvarsall unpatched"
  55. def test_no_registry_entries_means_nothing_found(self):
  56. """
  57. No registry entries or environment variable should lead to an error
  58. directing the user to download vcpython27.
  59. """
  60. find_vcvarsall = distutils.msvc9compiler.find_vcvarsall
  61. query_vcvarsall = distutils.msvc9compiler.query_vcvarsall
  62. with contexts.environment(VS90COMNTOOLS=None):
  63. with mock_reg():
  64. assert find_vcvarsall(9.0) is None
  65. try:
  66. query_vcvarsall(9.0)
  67. except Exception as exc:
  68. expected = distutils.errors.DistutilsPlatformError
  69. assert isinstance(exc, expected)
  70. assert 'aka.ms/vcpython27' in str(exc)
  71. @pytest.fixture
  72. def user_preferred_setting(self):
  73. """
  74. Set up environment with different install dirs for user vs. system
  75. and yield the user_install_dir for the expected result.
  76. """
  77. with self.mock_install_dir() as user_install_dir:
  78. with self.mock_install_dir() as system_install_dir:
  79. reg = mock_reg(
  80. hkcu={
  81. self.key_32: user_install_dir,
  82. },
  83. hklm={
  84. self.key_32: system_install_dir,
  85. self.key_64: system_install_dir,
  86. },
  87. )
  88. with reg:
  89. yield user_install_dir
  90. def test_prefer_current_user(self, user_preferred_setting):
  91. """
  92. Ensure user's settings are preferred.
  93. """
  94. result = distutils.msvc9compiler.find_vcvarsall(9.0)
  95. expected = os.path.join(user_preferred_setting, 'vcvarsall.bat')
  96. assert expected == result
  97. @pytest.fixture
  98. def local_machine_setting(self):
  99. """
  100. Set up environment with only the system environment configured.
  101. """
  102. with self.mock_install_dir() as system_install_dir:
  103. reg = mock_reg(
  104. hklm={
  105. self.key_32: system_install_dir,
  106. },
  107. )
  108. with reg:
  109. yield system_install_dir
  110. def test_local_machine_recognized(self, local_machine_setting):
  111. """
  112. Ensure machine setting is honored if user settings are not present.
  113. """
  114. result = distutils.msvc9compiler.find_vcvarsall(9.0)
  115. expected = os.path.join(local_machine_setting, 'vcvarsall.bat')
  116. assert expected == result
  117. @pytest.fixture
  118. def x64_preferred_setting(self):
  119. """
  120. Set up environment with 64-bit and 32-bit system settings configured
  121. and yield the canonical location.
  122. """
  123. with self.mock_install_dir() as x32_dir:
  124. with self.mock_install_dir() as x64_dir:
  125. reg = mock_reg(
  126. hklm={
  127. # This *should* only exist on 32-bit machines
  128. self.key_32: x32_dir,
  129. # This *should* only exist on 64-bit machines
  130. self.key_64: x64_dir,
  131. },
  132. )
  133. with reg:
  134. yield x32_dir
  135. def test_ensure_64_bit_preferred(self, x64_preferred_setting):
  136. """
  137. Ensure 64-bit system key is preferred.
  138. """
  139. result = distutils.msvc9compiler.find_vcvarsall(9.0)
  140. expected = os.path.join(x64_preferred_setting, 'vcvarsall.bat')
  141. assert expected == result
  142. @staticmethod
  143. @contextlib.contextmanager
  144. def mock_install_dir():
  145. """
  146. Make a mock install dir in a unique location so that tests can
  147. distinguish which dir was detected in a given scenario.
  148. """
  149. with contexts.tempdir() as result:
  150. vcvarsall = os.path.join(result, 'vcvarsall.bat')
  151. with open(vcvarsall, 'w'):
  152. pass
  153. yield result