-
Couldn't load subscription status.
- Fork 293
CA-390883: Add unit test for usb_reset.py (code fix done by CA-388318) #5401
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
CA-390883: Add unit test for usb_reset.py (code fix done by CA-388318) #5401
Conversation
|
|
||
| import sys | ||
|
|
||
| import pytest |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct me if I'm wrong, but I believe pytest is not available in xenserver 8. Does this mean these will not be run when building the xapi package?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could not find the invocation of a Python unittest in the xen-api build so far.
nosetests was used before, but it looks like it is no longer used.
Trying python -m unittest, I found this:
In scripts/test_usb_scan.py, the decorator @nottest is used to declare some functions as not being tests.
But Python unittest does not know about @nottest (from the previously used nosetest) and tries to execute these functions as if they were tests, which fails:
# (cd scripts;python2 -m unittest discover 2>&1 |grep ERROR)
# (cd scripts;python3 -m unittest discover 2>&1 |grep ERROR)
ERROR: test_usb_common (test_usb_scan.TestUsbScan)
TypeError: test_usb_common() takes at least 4 arguments (1 given)
ERROR: test_usb_config_error_common (test_usb_scan.TestUsbScan)
(likewise)
ERROR: test_usb_dongle (test_usb_scan.TestUsbScan)
ERROR: test_usb_dongle_on_hub (test_usb_scan.TestUsbScan)
ERROR: test_usb_dongle_unbinded (test_usb_scan.TestUsbScan)
ERROR: test_usb_exit (test_usb_scan.TestUsbScan)
ERROR: test_usb_keyboard (test_usb_scan.TestUsbScan)unittest only passes the self parameter, but not the other parameter, thus unittest fails.
I concluded that there is no way that the unit tests are executed in the xapi build if pytest (or the formerly used nosetests) is not available.
python3 -m unittest discover just does nothing:
----------------------------------------------------------------------
Ran 0 tests in 0.000s
OKThis is a big difference between unittest and pytest:
unittestpasses if it finds no testspytestexits with exit status 5 if it finds no tests.
This weakness, and the other reasons (you lose so much if not using pytest), should be enough to justify adding pytest to XS8 if we want proper unit testing in XS8.
9a84bd9 to
2a05087
Compare
A rootless container like using unshare --map-root-user --mount is a good way to test the correct calling convention for mount()/umount(): This structure has already been in use in the unit-tests of python-libs and status-report. Most of this the contepts used already in use in these projects it is likely good to describe them for for reviewers: - Update my previous container for py2.7 to use an updated setup-python for unshare() in unit tests (GitHub doesn't support nested namespaces) - Reuse my rootless container implementation which is already successfully used in python-libs and status-report unit tests since many months. - Reuse my universal import_file_as_module() implementation from status-report which allows to confirm the test correctness using Python 2.7 and use it as well for also confirming the Python3 fix. - Use a context manager test fixture to temporarily mock module imports: Tests mocking module imports permanently on the global level without a proper context manager to cleantup these mocks are bad because when other tests need to mock them differently, they would have to clean these mocks before importing or installing their own mocks: Clean up after work is finished, to not interfere with other tests. - Add a sufficient testcase to test usb_reset.py: mount() and umount() without mocking the system or library calls in any way. - Add these tests in a new sub-directory for unit tests for migrating all unit test files to it for separation of delivered scripts from unit tests that are not delivered. - Implemenent the new sub-directory as a Python module to allow for non-deprecated relative imports: Absolute imports within a module are deprecated and are warned on by static type checkers. - Add the pylint project dictionary to pass spellchecks for the new code. - Add the .sourcery.yaml for the Sourcery checker to not warn to use f-strings, which would prevent the test case from being validated using the stable and proven-in-use Python2-compatible usb_reset.py Signed-off-by: Bernhard Kaindl <bernhard.kaindl@cloud.com>
With the fix, enable the test to confirm correctness for Python3 too. Co-authored-by: Bernhard Kaindl <bernhard.kaindl@cloud.com> Signed-off-by: Ashwinh <ashwin.h@cloud.com>
a3be2b9 to
86363ec
Compare
|
From the past revert cases, adding UT is beneficial, but it's not a silver bullet. Let's all adhere to the same PR process as we discussed in toolstack channel.
Finally, please follow what @robhoes emphasized: |
86363ec to
cce3f45
Compare
| # Use imp only for python2.7, there is no alternative to it: | ||
| # pylint: disable-next=import-outside-toplevel, deprecated-module | ||
| import imp # pyright: ignore[reportMissingImports] | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not put the module into sys.modules
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given we are sure that we do have XenRT to cover this,
Doubting if it is necessary to add such complicated unittest, which requires to maintain anyway.
Also, do not forget to do rpm/spec build to make sure it is not broken.
| ) | ||
|
|
||
|
|
||
| def umount(target): # type:(str) -> None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this function is not called, did you intention to call it automatically when leave the session?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After reconsider,
Given we are pretty sure XenRT already cover this,
Here we involve so much code just to run mount/umount,
which involve unnecessary maintain effort.
How about just include the tiny fix for scripts/usb_reset.py, and leave others part to XenRT? @psafont
scripts/usb_reset.py
Outdated
|
|
||
| def umount(target): | ||
| if ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True | ||
| ).umount(target) < 0: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might consider encapsulating these two functions as the following pseudo code.
The Mounter class encapsulates the loading of libc and the management of mount/umount methods, that could enhance the code's reusability and won't need to loading libc repeatly and implementing the enter and exit methods, enabling the Mounter object to be used within a with statement.
import ctypes
import os
import logging
class Mounter:
def __init__(self):
self.libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True)
self.target = None
def mount(self, source, target, fs, flags=0):
if self.libc.mount(source.encode(), target.encode(), fs.encode(), flags, None) < 0:
error_message = "Failed to mount {} ({}) to {} with flags {}: {}".format(
source, fs, target, flags, os.strerror(ctypes.get_errno())
)
logging.error(error_message)
return error_message
else:
self.target = target
return "Mount successful"
def umount(self, target, flags=0):
if self.libc.umount2(target.encode(), flags) < 0:
error_message = "Failed to unmount {} with flags {}: {}".format(
target, flags, os.strerror(ctypes.get_errno())
)
logging.error(error_message)
return error_message
else:
return "Unmount successful"
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
if self.target is not None:
self.umount(self.target)
BTW, as libc manual mentioned https://www.gnu.org/software/libc/manual/html_node/Mount_002dUnmount_002dRemount.html
umount does the same thing as umount2 with flags set to zeroes. It is more widely available than umount2 but since it lacks the possibility to forcefully unmount a filesystem is deprecated when umount2 is also available.
Should we consider using umount2 instead?
The previous PR that broke |
|
(Although if it was up to me we'd just rewrite these python scripts in OCaml and avoid these problems altogether, but that would take a long time). Maybe we should have a new rule: whenever a python2->3 port breaks a python script we rewrite that script in OCaml. And no new Python scripts. |
This happens because we did not have feature branch, and new joiners may not familiar with such. This is introducing maintain effort to toolstack CoP anyway, if the CoP decide to take it, I have no objection. |
|
python->ocaml is right and better choice in long term, and I agree with it. |
|
It'd be nice to get this merged into the feature branch, can the testing for python 2 be dropped? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Requesting changes in the interface to make it obvious that the PR needs work
|
Closing due to inactivity, will speak with other engineers to merge this thing |
Info: This test uses
pytest, but before you say that tests (withoutpytest) run in XS8xapibuild, show where these tests run: As shown below,unittestlikely cannot work, andnosetestsis not installed inxapi.spec.Add a unit test that uses the equivalent of
/usr/bin/unshare --map-root-user --mount(which creates a temporary, root-less mount namespace) for testing the actual, not mocked mount() and unmount() implementations ofscripts/usb_reset.py.This serves just as an example that testing code that would otherwise require root privileges is perfectly possible and should be done to avoid committing any mistakes.
The actual fix for updating the
mount()andumount()functions ofscripts/usb_reset.pyfor Python3 can be pushed as a 2nd commit to the PR, at which, the test can be enabled for Python3 (no longer be skipped for Python3).Cc: @ashwin9390
Test usb_reset.mount/.umount to work in a rootless container