Skip to content

Commit 9f0f994

Browse files
TalonsLeemseri
authored andcommitted
CP-24132: unit test for usb_scan.py (xapi-project#3324)
* CP-24132: unit test for usb_scan.py Refactor usb_scan.py and add unit tests Signed-off-by: Xin(Talons) Li <[email protected]>
1 parent 5e8c0f6 commit 9f0f994

File tree

2 files changed

+405
-11
lines changed

2 files changed

+405
-11
lines changed

scripts/test_usb_scan.py

Lines changed: 382 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,382 @@
1+
#!/usr/bin/env python
2+
#
3+
# unittest for usb_scan.py
4+
5+
from collections import Mapping, Container, Iterable
6+
import mock
7+
from nose.tools import nottest
8+
import os
9+
import shutil
10+
import sys
11+
import tempfile
12+
import unittest
13+
14+
sys.modules["xcp"] = mock.Mock()
15+
sys.modules["xcp.logger"] = mock.Mock()
16+
sys.modules["pyudev"] = mock.Mock()
17+
18+
19+
class MocDeviceAttrs(Mapping):
20+
def __init__(self, device):
21+
self.d = device.get_attr()
22+
23+
def __iter__(self):
24+
for name in self.d:
25+
yield name
26+
27+
def __len__(self):
28+
return len(self.d)
29+
30+
def __getitem__(self, name):
31+
return self.d.get(name)
32+
33+
34+
class MocDevice(Mapping):
35+
36+
def __init__(self, d):
37+
self.d = d
38+
39+
@property
40+
def sys_name(self):
41+
return self.d.get("name")
42+
43+
def get_prop(self):
44+
return self.d.get("props")
45+
46+
def get_attr(self):
47+
return self.d.get("attrs")
48+
49+
@property
50+
def attributes(self):
51+
return MocDeviceAttrs(self)
52+
53+
def __iter__(self):
54+
for name in self.get_prop():
55+
yield name
56+
57+
def __len__(self):
58+
return len(self.get_prop())
59+
60+
def __getitem__(self, name):
61+
return self.get_prop().get(name)
62+
63+
64+
class MocEnumerator(object):
65+
66+
def __init__(self, ds):
67+
self.ds = ds
68+
69+
def __iter__(self):
70+
for d in self.ds:
71+
yield MocDevice(d)
72+
73+
74+
class MocContext(object):
75+
76+
def __init__(self, devices, interfaces):
77+
self.devices = devices
78+
self.interfaces = interfaces
79+
80+
def list_devices(self, **kwargs):
81+
if "usb" == kwargs.pop("subsystem"):
82+
dev_type = kwargs.pop("DEVTYPE")
83+
if "usb_device" == dev_type:
84+
return MocEnumerator(self.devices)
85+
elif "usb_interface" == dev_type:
86+
return MocEnumerator(self.interfaces)
87+
return MocEnumerator([])
88+
89+
90+
def mock_setup(mod, devices, interfaces, path):
91+
mod.log.error = test_log
92+
mod.log.debug = test_log
93+
mod.Policy._PATH = path
94+
mod.pyudev.Context = mock.Mock(return_value=MocContext(
95+
devices, interfaces))
96+
97+
98+
@nottest
99+
def test_log(m):
100+
pass
101+
102+
103+
class TestUsbScan(unittest.TestCase):
104+
105+
def setUp(self):
106+
try:
107+
self.work_dir = tempfile.mkdtemp(prefix="test_usb_scan")
108+
except:
109+
raise
110+
111+
def tearDown(self):
112+
shutil.rmtree(self.work_dir, ignore_errors=True)
113+
114+
@nottest
115+
def test_usb_common(self, moc_devices, moc_interfaces, moc_results,
116+
path="./scripts/usb-policy.conf"):
117+
import usb_scan
118+
mock_setup(usb_scan, moc_devices, moc_interfaces, path)
119+
120+
devices, interfaces = usb_scan.get_usb_info()
121+
122+
pusbs = usb_scan.make_pusbs_list(devices, interfaces)
123+
124+
# pass pusbs in json to XAPI
125+
self.assertItemsEqual(pusbs, moc_results)
126+
127+
@nottest
128+
def test_usb_exit(self, devices, interfaces, results,
129+
path="./scripts/usb-policy.conf", msg=""):
130+
with self.assertRaises(SystemExit) as cm:
131+
self.test_usb_common(devices, interfaces, results, path)
132+
if msg:
133+
self.assertIn(msg, cm.exception.message)
134+
135+
def test_usb_dongle(self):
136+
devices = [
137+
{
138+
"name": "1-2",
139+
"props": {
140+
"ID_VENDOR_FROM_DATABASE": "Feitian Technologies, Inc."
141+
},
142+
"attrs": {
143+
"idVendor": "096e",
144+
"bNumInterfaces": " 1",
145+
"bConfigurationValue": "1",
146+
"bcdDevice": "010a",
147+
"version": " 1.10",
148+
"idProduct": "0302",
149+
"bDeviceClass": "00",
150+
}
151+
}
152+
]
153+
interfaces = [
154+
{
155+
"name": "1-2:1.0",
156+
"attrs": {
157+
"bInterfaceClass": "03",
158+
"bInterfaceSubClass": "00",
159+
"bInterfaceProtocol": "00",
160+
"bInterfaceNumber": "00",
161+
}
162+
}
163+
]
164+
results = [
165+
{
166+
"product-desc": "",
167+
"product-id": "0302",
168+
"description": "Feitian Technologies, Inc.",
169+
"vendor-desc": "Feitian Technologies, Inc.",
170+
"version": "1.10",
171+
"vendor-id": "096e",
172+
"path": "1-2",
173+
"serial": ""
174+
}
175+
]
176+
self.test_usb_common(devices, interfaces, results)
177+
178+
def test_usb_dongle_on_hub(self):
179+
devices = [
180+
{
181+
"name": "1-2.1",
182+
"props": {
183+
"ID_VENDOR_FROM_DATABASE": "Feitian Technologies, Inc."
184+
},
185+
"attrs": {
186+
"idVendor": "096e",
187+
"bNumInterfaces": " 1",
188+
"bConfigurationValue": "1",
189+
"bcdDevice": "010a",
190+
"version": " 1.10",
191+
"idProduct": "0302",
192+
"bDeviceClass": "00",
193+
}
194+
}
195+
]
196+
interfaces = [
197+
{
198+
"name": "1-2.1:1.0",
199+
"attrs": {
200+
"bInterfaceClass": "03",
201+
"bInterfaceSubClass": "00",
202+
"bInterfaceProtocol": "00",
203+
"bInterfaceNumber": "00",
204+
}
205+
}
206+
]
207+
results = [
208+
{
209+
"product-desc": "",
210+
"product-id": "0302",
211+
"description": "Feitian Technologies, Inc.",
212+
"vendor-desc": "Feitian Technologies, Inc.",
213+
"version": "1.10",
214+
"vendor-id": "096e",
215+
"path": "1-2.1",
216+
"serial": ""
217+
}
218+
]
219+
self.test_usb_common(devices, interfaces, results)
220+
221+
def test_usb_dongle_unbinded(self):
222+
devices = [
223+
{
224+
"name": "1-2",
225+
"props": {
226+
"ID_VENDOR_FROM_DATABASE": "Feitian Technologies, Inc."
227+
},
228+
"attrs": {
229+
"idVendor": "096e",
230+
"bNumInterfaces": "",
231+
"bConfigurationValue": "",
232+
"bcdDevice": "010a",
233+
"version": " 1.10",
234+
"idProduct": "0302",
235+
"bDeviceClass": "00",
236+
}
237+
}
238+
]
239+
interfaces = [
240+
]
241+
results = [
242+
]
243+
self.test_usb_common(devices, interfaces, results)
244+
245+
def test_usb_keyboard(self):
246+
devices = [
247+
{
248+
"name": "1-2",
249+
"props": {
250+
"ID_VENDOR_FROM_DATABASE": "Dell Computer Corp."
251+
},
252+
"attrs": {
253+
"idVendor": "413c",
254+
"bNumInterfaces": " 2",
255+
"bConfigurationValue": "1",
256+
"bcdDevice": "0110",
257+
"version": " 2.00",
258+
"idProduct": "2113",
259+
"bDeviceClass": "00",
260+
}
261+
}
262+
]
263+
interfaces = [
264+
{
265+
"name": "1-2:1.0",
266+
"attrs": {
267+
"bInterfaceClass": "03",
268+
"bInterfaceSubClass": "01",
269+
"bInterfaceProtocol": "01",
270+
"bInterfaceNumber": "00",
271+
}
272+
},
273+
{
274+
"name": "1-2:1.1",
275+
"attrs": {
276+
"bInterfaceClass": "03",
277+
"bInterfaceSubClass": "00",
278+
"bInterfaceProtocol": "00",
279+
"bInterfaceNumber": "01",
280+
}
281+
}
282+
]
283+
results = [
284+
]
285+
self.test_usb_common(devices, interfaces, results)
286+
287+
def test_usb_config_missing(self):
288+
self.test_usb_exit([], [], [], "not_exist.conf")
289+
290+
@nottest
291+
def test_usb_config_error_common(self, content, msg):
292+
path = os.path.join(self.work_dir, "usb-policy.conf")
293+
with open(path, "w") as f:
294+
f.write(content)
295+
self.test_usb_exit([], [], [], path, msg)
296+
297+
def test_usb_config_error_unexpected_chars_with_comment(self):
298+
content = """ss# unexpected words with comment
299+
ALLOW:vid=056a pid=0314 class=03 # Wacom Intuos tablet
300+
ALLOW: # Otherwise allow everything else
301+
"""
302+
self.test_usb_config_error_common(content,
303+
"Caught error need more than 1 "
304+
"value to unpack")
305+
306+
def test_usb_config_error_duplicated_key(self):
307+
content = """# duplicated key word
308+
ALLOW:vid=056a vid=0314 class=03 # Wacom Intuos tablet
309+
ALLOW: # Otherwise allow everything else
310+
"""
311+
self.test_usb_config_error_common(content, "duplicated tag")
312+
313+
def test_usb_config_error_invalid_key(self):
314+
content = """# invalid key word
315+
ALLOW:vid=056a psid=0314 class=03 # Wacom Intuos tablet
316+
ALLOW: # Otherwise allow everything else
317+
"""
318+
self.test_usb_config_error_common(content, "Malformed policy rule, "
319+
"unable to parse")
320+
321+
def test_usb_config_error_hex_length_4(self):
322+
content = """# hex length not 4
323+
ALLOW:vid=056a pid=031 class=03 # Wacom Intuos tablet
324+
ALLOW: # Otherwise allow everything else
325+
"""
326+
self.test_usb_config_error_common(content, "length error")
327+
328+
def test_usb_config_error_hex_length_2(self):
329+
content = """# hex length not 2
330+
DENY:vid=056a pid=0314 class=035 # Wacom Intuos tablet
331+
ALLOW: # Otherwise allow everything else
332+
"""
333+
self.test_usb_config_error_common(content, "length error")
334+
335+
def test_usb_config_error_action_key(self):
336+
content = """# wrong action key word
337+
ALLOWED:vid=056a pid=0314 class=03 # Wacom Intuos tablet
338+
ALLOW: # Otherwise allow everything else
339+
"""
340+
self.test_usb_config_error_common(content, "Malformed action")
341+
342+
def test_usb_config_error_unexpected_chars_end(self):
343+
content = """# unexpected words in the end
344+
ALLOW:vid=056a pid=0314 class=03 kk # Wacom Intuos tablet
345+
ALLOW: # Otherwise allow everything else
346+
"""
347+
self.test_usb_config_error_common(content, "Malformed policy rule, "
348+
"unable to parse")
349+
350+
def test_usb_config_error_unexpected_chars_beg(self):
351+
content = """# unexpected words at the beginning
352+
ii ALLOW:vid=056a pid=0314 class=03 # Wacom Intuos tablet
353+
ALLOW: # Otherwise allow everything else
354+
"""
355+
self.test_usb_config_error_common(content, "Malformed action")
356+
357+
def test_usb_config_error_unexpected_chars_mid(self):
358+
content = """# unexpected words in the middle
359+
ALLOW:vid=056a pid=0314 jj class=03 # Wacom Intuos tablet
360+
ALLOW: # Otherwise allow everything else
361+
"""
362+
self.test_usb_config_error_common(content, "Malformed policy rule, "
363+
"unable to parse")
364+
365+
def test_usb_config_error_unexpected_non_empty_line(self):
366+
content = """# unexpected non empty line
367+
ALLOW:vid=056a pid=0314 class=03 # Wacom Intuos tablet
368+
aa
369+
ALLOW: # Otherwise allow everything else
370+
"""
371+
self.test_usb_config_error_common(content,
372+
"Caught error need more than 1 "
373+
"value to unpack")
374+
375+
def test_usb_config_error_missing_colon(self):
376+
content = """# missing colon after action
377+
ALLOW:vid=056a pid=0314 class=03 # Wacom Intuos tablet
378+
ALLOW # Otherwise allow everything else
379+
"""
380+
self.test_usb_config_error_common(content,
381+
"Caught error need more than 1 "
382+
"value to unpack")

0 commit comments

Comments
 (0)