Coverage for tests / mixins.py: 100.000%

59 statements  

« prev     ^ index     » next       coverage.py v7.12.1a0.dev1, created at 2025-11-30 17:57 +0000

1# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 

2# For details: https://github.com/coveragepy/coveragepy/blob/main/NOTICE.txt 

3 

4""" 

5Test class mixins 

6 

7Some of these are transitional while working toward pure-pytest style. 

8""" 

9 

10from __future__ import annotations 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

11 

12import importlib 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

13import os 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

14import os.path 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

15import sys 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

16 

17from collections.abc import Iterable 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

18from typing import Any, Callable, cast 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

19 

20import pytest 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

21 

22from coverage.misc import SysModuleSaver 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

23from tests.helpers import change_dir, make_file, remove_tree 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

24 

25 

26class PytestBase: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

27 """A base class to connect to pytest in a test class hierarchy.""" 

28 

29 @pytest.fixture(autouse=True) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

30 def connect_to_pytest( 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

31 self, 

32 request: pytest.FixtureRequest, 

33 monkeypatch: pytest.MonkeyPatch, 

34 ) -> None: 

35 """Captures pytest facilities for use by other test helpers.""" 

36 # pylint: disable=attribute-defined-outside-init 

37 self._pytest_request = request 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

38 self._monkeypatch = monkeypatch 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

39 self.setUp() 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

40 

41 def setUp(self) -> None: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

42 """Per-test initialization. Override this as you wish.""" 

43 pass 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

44 

45 def addCleanup(self, fn: Callable[..., None], *args: Any) -> None: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

46 """Like unittest's addCleanup: code to call when the test is done.""" 

47 self._pytest_request.addfinalizer(lambda: fn(*args)) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

48 

49 def set_environ(self, name: str, value: str) -> None: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

50 """Set an environment variable `name` to be `value`.""" 

51 self._monkeypatch.setenv(name, value) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

52 

53 def del_environ(self, name: str) -> None: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

54 """Delete an environment variable, unless we set it.""" 

55 self._monkeypatch.delenv(name, raising=False) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

56 

57 

58class TempDirMixin: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

59 """Provides temp dir and data file helpers for tests.""" 

60 

61 # Our own setting: most of these tests run in their own temp directory. 

62 # Set this to False in your subclass if you don't want a temp directory 

63 # created. 

64 run_in_temp_dir = True 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

65 

66 @pytest.fixture(autouse=True) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

67 def _temp_dir(self, tmp_path_factory: pytest.TempPathFactory) -> Iterable[None]: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

68 """Create a temp dir for the tests, if they want it.""" 

69 if self.run_in_temp_dir: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

70 tmpdir = tmp_path_factory.mktemp("t") 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

71 self.temp_dir = str(tmpdir) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

72 with change_dir(self.temp_dir): 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

73 # Modules should be importable from this temp directory. We don't 

74 # use '' because we make lots of different temp directories and 

75 # nose's caching importer can get confused. The full path prevents 

76 # problems. 

77 sys.path.insert(0, os.getcwd()) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

78 yield 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

79 else: 

80 yield 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

81 

82 def make_file( 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%

83 self, 

84 filename: str, 

85 text: str = "", 

86 bytes: bytes = b"", 

87 newline: str | None = None, 

88 ) -> str: 

89 """Make a file. See `tests.helpers.make_file`""" 

90 # pylint: disable=redefined-builtin # bytes 

91 assert self.run_in_temp_dir, "Only use make_file when running in a temp dir" 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

92 return make_file(filename, text, bytes, newline) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

93 

94 

95class RestoreModulesMixin: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

96 """Auto-restore the imported modules at the end of each test.""" 

97 

98 @pytest.fixture(autouse=True) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

99 def _module_saving(self) -> Iterable[None]: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

100 """Remove modules we imported during the test.""" 

101 self._sys_module_saver = SysModuleSaver() 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

102 try: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

103 yield 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

104 finally: 

105 self._sys_module_saver.restore() 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

106 

107 def clean_local_file_imports(self) -> None: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

108 """Clean up the results of calls to `import_local_file`. 

109 

110 Use this if you need to `import_local_file` the same file twice in 

111 one test. 

112 

113 """ 

114 # So that we can re-import files, clean them out first. 

115 self._sys_module_saver.restore() 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

116 

117 # Also have to clean out the .pyc files, since the time stamp 

118 # resolution is only one second, a changed file might not be 

119 # picked up. 

120 remove_tree("__pycache__") 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

121 importlib.invalidate_caches() 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

122 

123 

124class StdStreamCapturingMixin: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

125 """ 

126 Adapter from the pytest capsys fixture to more convenient methods. 

127 

128 This doesn't also output to the real stdout, so we probably want to move 

129 to "real" capsys when we can use fixtures in test methods. 

130 

131 Once you've used one of these methods, the capturing is reset, so another 

132 invocation will only return the delta. 

133 

134 """ 

135 

136 @pytest.fixture(autouse=True) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

137 def _capcapsys(self, capsys: pytest.CaptureFixture[str]) -> None: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

138 """Grab the fixture so our methods can use it.""" 

139 self.capsys = capsys 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

140 

141 def stdouterr(self) -> tuple[str, str]: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

142 """Returns (out, err), two strings for stdout and stderr.""" 

143 return cast(tuple[str, str], self.capsys.readouterr()) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

144 

145 def stdout(self) -> str: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

146 """Returns a string, the captured stdout.""" 

147 return self.capsys.readouterr().out 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

148 

149 def stderr(self) -> str: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()

150 """Returns a string, the captured stderr.""" 

151 return self.capsys.readouterr().err 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%'()