-
Notifications
You must be signed in to change notification settings - Fork 51
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
Fix inheritance issues and separate the mocked dbus into an object #184
Conversation
For historical reasons, dbusmock is written for Python's unittest framework and most of what it does is done via @classmethod. We called it on the subclassed object though, so we got some weird behaviors. In particular, things like `a = foo.someval` would resolve to the parent class' `someval` but `foo.someval = a` would set that value on the subclassed object. This lead, indirectly, to our forked dbus-daemon not getting terminated. This needs to be be fixed in dbusmock (see [1]) but for now simply work around this by instantiating the DBusTestCase class directly. [1] martinpitt/python-dbusmock#184
ftr I'm also planning to move with spawn_server() as blah:
.... and have it auto-terminate instead of requiring callers to track the Popen objects. |
For historical reasons, dbusmock is written for Python's unittest framework and most of what it does is done via @classmethod. We called it on the subclassed object though, so we got some weird behaviors. In particular, things like `a = foo.someval` would resolve to the parent class' `someval` but `foo.someval = a` would set that value on the subclassed object. This lead, indirectly, to our forked dbus-daemon not getting terminated. This needs to be be fixed in dbusmock (see [1]) but for now simply work around this by instantiating the DBusTestCase class directly. [1] martinpitt/python-dbusmock#184
For historical reasons, dbusmock is written for Python's unittest framework and most of what it does is done via @classmethod. We called it on the subclassed object though, so we got some weird behaviors. In particular, things like `a = foo.someval` would resolve to the parent class' `someval` but `foo.someval = a` would set that value on the subclassed object. This lead, indirectly, to our forked dbus-daemon not getting terminated. This needs to be be fixed in dbusmock (see [1]) but for now simply work around this by instantiating the DBusTestCase class directly. [1] martinpitt/python-dbusmock#184
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.
Thanks @whot for your work here! I like this. It's still draft and there are several test failures, but I did a first round of review to get some feedback.
Sorry about the conflict -- I just pushed a branch to fix the CI regression on main, which happened because fedora:latest moved from F38 to F39.
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.
Thx for the review, much appreciated. I'll get this updated next week.
82606cf
to
5c4242c
Compare
Updated, with the main change aside from the various fixes being that there's now a |
We probably shouldn't set it to None before checking whether it's None, that seems less efficient than it could be.
On the run of a derived test class, cls._DBusTestCase__datadir calls up into the parent class for the datadir and removes it. The subsequent statement cls._DBusTestCase__datadir = None sets it on the derived class, not on DBusTestCase which remains at the original value. Doesn't matter too much since the next derived test class will repeat that process but we leave the parent datadir set despite having removed it which seems like a bad idea. See the comment in get_services_dir(), the datadir is always on the DBusTestCase class, so let's mirror that behavior here.
A slightly weird side-effect here: start_dbus was called on the DBusTestCase class - thus the session/system bus pid was set on that class. But tearDownClass() was called with the derived class from the test, so while the getters for variables resolved correctly, setattr(cls, f"{bus_type}_bus_pid", None) would set TestFoo.session_bus_pid = None and leave DBusTestCase.session_bus_pid at whatever value it had before. Fix this and add an assert to ensure we never start a session/system bus twice in the same test class.
5b08061
to
e54a23c
Compare
No real functional changes, this prepares the way for some extra asserts in the future and the behavior matches that of unittest-style classes which are always subclasses of DBusTestCase.
This is a potential break but it's unlikely to affect any old-style unittest classes since they would be subclasses by default. It may affect new-style pytest classes, if any exist, but OTOH it can catch confusing errors like setting a variable on the DBusTestCase class but then unsetting it on the derived class during a test.
This is what they are anyway, so let's do this and avoid the possibly accidental wrong class handling fixed a few commits ago.
This patch introduces the BusType class which handles the system/session buss differences including whatever is independent of who started the actual daemon such as getting a connection. It moves spawn_server and spawn_server_template to standalone functions since those do not have any dependency on anything else in DBusTestCase. It adds the PrivateDBus class which wraps a single instance of a DBus daemon, with a context manager so we can do things like: with PrivateDBus(BusType.SESSION) as bus: .... and have it self clean up automatically. Unlike the previous approach, we don't just fork in the background and kill via PID, we keep a reference to the process and kill it via Python's API. Finally, it changes the DBusTestCase class to use a single instance of those objects as its DBus server when created with start_session_bus() or start_system_bus(). Most of that class is now a wrapper around the PrivateDBus object (or the BusType object where appropriate). PrivateDBus also wraps some functions to make it look more like a DBusTestCase object to keep compatibility with the current pytest behavior. With all that, we can now return a PrivateDBus object from the pytest fixtures and have better insulation than before where the DBusTestCase class was responsible for singleton handling.
e54a23c
to
6b51c9e
Compare
Let's move spawn_server and spawn_server_template into an object with a context manager, greatly simplifying them where fixtures are being used. Note that both are renamed into: SpawnedMock.spawn_for_name() SpawedMock.spawn_with_template()
Some of these could've only been exposed by the recently added pytest fixtures - those have never been in a release so let's drop them.
deciseconds is a rather unusual unit. Let's switch the real API we now have to use seconds: float like time.sleep() and wrap our old API around that.
6b51c9e
to
c39fc5b
Compare
Ok, pipeline is green and I think this is ready. Or at least I've rebased and stared at it enough that I no longer see any bugs... |
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 looks great to me, thanks a lot for your work here!
@whot Do you want this in a release now, or do you have some more things you want to work on before that? |
I have nothing else in the pipe right now (beyond whatever bugs I just introduced in this MR ;) Thanks heaps for the quick merge! |
Ack, here is https://github.com/martinpitt/python-dbusmock/releases/tag/0.30.0 . Uploaded to Debian, and Fedora updates are in progress. |
Filing as draft for now because the last commit may break backwards compat, see below.
I noticed that my dbus-daemons didn't get terminated when using dbusmock in the xdg-desktop-portal which led to a bit of a rabbit hole. So I figured "let's fix this properly" 😄
For (understandable) historical reasons, the
DBusTestCase
class handled everything as@classmethod
. That causes a few issues though because thecls
argument is mostly the real class but in some cases it'sDBusTestCase
. This can lead to a getter resolving toDBusTestCase.somevar
but the setter resolving toMyTestClass(DBusTestCase).somevar
.The first few patches disentangle that to make sure we're always on the correct class instead of oscillating between
DBusTestCase
and the test class.The last (and most interesting) patch is to add a new
MockedDBus
object that wraps the actually started daemon.I've patched this so
DBusTestCase
wraps this new class but this may introduce some compatibility issues. Arguably we could leaveDBusTestCase
untouched and as legacy API and suggest usingMockedDBus
via pytest for the future. Since the pytests haven't been in a release yet we could also remove some of the wrappers from this new class and rely on callers to use the newBusType
/spawn_*
API directly.Ironically, the last patch somewhat undoes the previous patches in that everything is now on the
DBusTestCase
class, but that shouldn't matter much.It does pass the test suite here, so any incompatibilities introduced are not tested ;)