1
1
import subprocess
2
2
import unittest
3
+ from os import environ
4
+
5
+ import pyfakefs .fake_filesystem_unittest # type: ignore[import]
3
6
from mock import patch , Mock
4
7
5
8
from xcp .pci import PCI , PCIIds , PCIDevices
@@ -66,6 +69,22 @@ def tests_nodb(self):
66
69
PCIIds .read ()
67
70
exists_mock .assert_called_once_with ("/usr/share/hwdata/pci.ids" )
68
71
72
+ def test_videoclass_without_mock (self ):
73
+ """
74
+ Verifies that xcp.pci uses the open() and Popen() correctly across versions.
75
+ Tests PCIIds.read() and PCIDevices() without mock for verifying compatibility
76
+ with all Python versions.
77
+ (The old test using moc could not detect a missing step in the Py3 migration)
78
+ """
79
+ with pyfakefs .fake_filesystem_unittest .Patcher () as p :
80
+ assert p .fs
81
+ p .fs .add_real_file ("tests/data/pci.ids" , target_path = "/usr/share/hwdata/pci.ids" )
82
+ ids = PCIIds .read ()
83
+ saved_PATH = environ ["PATH" ]
84
+ environ ["PATH" ] = "tests/data" # Let PCIDevices() call Popen("tests/data/lspci")
85
+ self .assert_videoclass_devices (ids , PCIDevices ())
86
+ environ ["PATH" ] = saved_PATH
87
+
69
88
def test_videoclass_by_mock_calls (self ):
70
89
with patch ("xcp.pci.os.path.exists" ) as exists_mock , \
71
90
patch ("xcp.pci.open" ) as open_mock , \
@@ -81,12 +100,41 @@ def test_videoclass_by_mock_calls(self):
81
100
def mock_lspci_using_open_testfile (cls ):
82
101
"""Mock xcp.pci.PCIDevices.Popen() using open(tests/data/lspci-mn)"""
83
102
103
+ #
104
+ # Mocking xcp.pci.subprocess.Popen.stdout using open("tests/data/lspci-mn")
105
+ # does not model the actual behavior of subprocess.Popen().std* in Python3:
106
+ #
107
+ # From: https://docs.python.org/3.6/library/subprocess.html#popen-constructor:
108
+ # "If encoding or errors are specified, the file objects stdin, stdout and stderr
109
+ # are opened in text mode with the specified encoding and errors, as described
110
+ # above in Frequently Used Arguments.
111
+ # If universal_newlines is True, they are opened in text mode with default
112
+ # encoding. >>>__Otherwise, they are opened as binary streams.__<<<"
113
+ #
114
+ # By it's nature, mocking Popen() does not validate it's correct use in xcp.pci
115
+ # and therfore, it failed to detect the Python3 problem in `xcp/pci.py`
116
+ #
117
+ # The validation of xcp.pci.PCIDevices() using self.PopenWrapper(), replacing
118
+ # "lspci -mn" with "cat tests/data/lspci-mn" is able to detect such usage bugs.
119
+ #
120
+ # Correctly modeling subprocess.Popen() without really calling it is nontrivial,
121
+ # and would require something like the testfixtures library and even that would
122
+ # still be a mock and use the actual subprocess class. Mocking is better suited
123
+ # when the test knows how an API must be called, which isn't the case here.
124
+ #
84
125
with patch ("xcp.pci.subprocess.Popen" ) as popen_mock , \
85
126
open ("tests/data/lspci-mn" ) as fake_data :
86
127
popen_mock .return_value .stdout .__iter__ = Mock (return_value = iter (fake_data ))
87
128
devs = PCIDevices ()
88
- popen_mock .assert_called_once_with (['lspci' , '-mn' ], bufsize = 1 ,
89
- stdout = subprocess .PIPE )
129
+ #
130
+ # Updated to accept the use of universal_newlines=True as a kind of bridge
131
+ # from Python2 to Python3, because it is accepted by both versions and
132
+ # on both versions it makes Popen().stdout return a iterator on strings
133
+ # which is what xcp.pci.PCIDevices expects to get:
134
+ #
135
+ popen_mock .assert_called_once_with (
136
+ ["lspci" , "-mn" ], bufsize = 1 , stdout = subprocess .PIPE , universal_newlines = True
137
+ )
90
138
return devs
91
139
92
140
def assert_videoclass_devices (self , ids , devs ): # type: (PCIIds, PCIDevices) -> None
0 commit comments