-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
Add tests for BPD #3190
Add tests for BPD #3190
Conversation
On Travis PyGObject is complaining about GLib being too old. Based on this commit is seems PyGObject 3.31.2 was the version where this last changed (and before that commit, the GLib version on Travis would be OK for PyGObject). On Appveyor the problem is about the cairo header being missing on Windows. I could try and fix up the dependency configuration for the CIs to make this work (not sure what to do about cairo on Windows), but I think instead I'll change the test approach to use a dummy |
The tests failed on Windows because it's attempting to pickle the whole BPD server as part of spawning the server process, and that includes some un-picklable objects (locks for the db). I tried running the server in a I suppose I need to be setting up the library in the process that's spawned to run the server rather than trying to pass it in. That would probably mean losing access to some of the |
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 is awesome! Thank you for getting this started. It will be really really useful to have better test coverage of the server. I have two high-level suggestions:
- For mocking the player, it could be more convenient to use the mock module's patch functionality.
- Even just for convenience's sake, it would be great—although perhaps really hard—to avoid the need to launch a separate process (or thread) for the server. This could maybe be done by making the tests use Bluelet's async IO to communicate with the server, or by invoking commands "directly" instead of literally opening a socket for communication. That's probably too tricky to get right, so a good fallback would be to launch a subprocess that runs the server via the command line: that is, instead of "forking" the process with
multiprocessing
, usesubprocess
to launch abeet bpd
command that starts the server "from scratch" using the same database file.
Thanks for the review! I've had another go now using The difficulty is that whatever mocking of the player is done, it has to happen in the new process rather than the test process. Maybe I'm missing an easier way of doing that, but my hacky solution was the best way I could think of after trying a few things. I can't see a good way to use
The main inelegant bit is ensuring that the launched process has the correct python executable and paths. Looks like I didn't properly serialise the python path into the environment for Windows so I'll still have to sort that out. |
Aha; you're right—that is way more complicated than I made it seem at first. Sorry to lead you astray! This does seem to work—at least, most of the way—but I'm no longer convinced it's any easier than the "fork" approach with multiprocessing that you were proposing before. To get the correct Python executable, you can use sys.executable (but you probably already tried that). |
I think in the end I prefer the forking approach because I don't trust that I can launch a fresh process with exactly the same python environment otherwise. The detour wasn't wasted effort though since the server management is much more robust now. I stumbled into an unexpected behaviour with paths on the BPD virtual filesystem under Windows. When listing directory contents with the As far as I'm concerned now that this is running it's ready to go in. I'll save the addition of new tests and changes in the BPD server itself for future PRs. |
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.
Awesome! Yes, this does indeed seem like the way to go. Thank you for continuing to dig into this!
When I try running these tests with nose locally, each one prints out an exception from the server that indicates that the client has disconnected:
Traceback (most recent call last):
File "/Users/asampson/Documents/code/beets/beets/util/bluelet.py", line 343, in run
value = event.fire()
File "/Users/asampson/Documents/code/beets/beets/util/bluelet.py", line 508, in fire
return self.conn.sock.recv(self.bufsize)
ConnectionResetError: [Errno 54] Connection reset by peer
To avoid this extra noise when running tests, maybe it's possible to kill the server before disconnecting? Or else disconnect the client by sending the "close" command first?
I didn't see that connection reset message on Linux, but I see from the CI logs that there's a similar error on Windows. I thought the client and its socket were being garbage collected (and so closed) before the server was killed but I guess I was wrong. I've tried a different approach where the "test socket" is passed into the client rather than having the client open a fresh one. I'm then explicitly closing that socket before killing the server process. I've been mainly using pytest locally, but I'm seeing now that with nose there are warnings about I'm really not sure why the Python 2.7 tests started failing on the CI with an ascii encoding exception. I don't see how the latest commit changes any string handling, and the tests run fine on my Linux machine under Python 2.7.16 |
It seems that the issue is something to do with the encoding of the source files. I can't see any problem with the files those commits touched and in fact rolling back to the exact commit that was previously passing CI is now failing CI. I can reproduce the error locally by adding |
Sorry about those test failures! I tracked that down to a Tox bug that was introduced just a couple of days ago: tox-dev/tox#1234 I changed the Travis and Appveyor configurations to avoid the version with the bug, so future commits should not run into this problem. |
And you're right—it does look like nose just doesn't support |
A simple TCP client wraps the low level protocol to allow testing of the BPD server as a black box.
The current MPD spec is several versions ahead of BPD. These tests just compare the full specified list of commands against what BPD claims to support (without actually testing their implementations). It's handy as a measure of progress against the specification, but can perhaps be dropped later once all of the individual protocol methods have tests.
MPD now supports more fields ("tags") than what BPD advertises. Fixing this should be a simple task of extending the mapping of fields to tags in BPD's implementation.
The MPD protocol allows batching commands. There are two choices for the first message that announced a batch is starting, and we just go for the one that causes the server to respond with a marker message between each individual response, since that's easier. This might need to be tweaked in order to test the behaviour with the other batch indicator.
The BOD tests are currently forking a process with a server running, and this attempts to close stdin. Tests were failing due to DummyIn not implementing the close() method. Adding this simple no-op does the trick to allow forking and seems like a harmless addition.
Decode the bytes to strings: the MPD protocol specifies that the communications are all in UTF-8. Also parse the body into a dict since this is typically more convenient than having to do it manually in each test.
Ok thanks for following up about the tox issues. I rebased my branch and switched the It might seem that the test file is a bit helper-heavy, but it means that the actual tests are short and readable and provide useful errors when they fail. As I improve the test coverage the ratio of test code to helper code will improve. E.g. this test asserts that all of the responses were OK up to the one that is expected to fail, and takes care of starting the bpd server, adding the item to its playlist (and checking that response), and stopping the server: def test_cmd_playlistinfo(self):
with self.run_bpd() as client:
self._bpd_add(client, self.item1)
responses = client.send_commands(
('playlistinfo',),
('playlistinfo', '0'),
('playlistinfo', '200'))
self._assert_failed(responses, bpd.ERROR_ARG, pos=2) |
Awesome. This will be incredibly valuable to have, as you say, for improving BPD and increasing its protocol coverage. Thank you for all the work you've already put into this. ✨ |
I'd like to work on updating the MPD server version supported by BPD (#800) so as a first step I'm adding some tests that exercise BPD. I found an existing file that was testing parts of the BPD plugin, though the name is a bit unexpected (
test_player.py
rather than, say,test_bpd.py
) so perhaps this should be renamed.The test code adds a simple TCP client that can speak to an MPD server. It provides a small number of convenience functions for building requests and interpreting responses but it intended to be pretty dumb. This way we can treat BPD as a black box and test how it responds over the protocol rather than unit testing the functions in the plugin's module. The real BPD server is started in a separate thread, and we have to wait a small amount of time for it to spin up before attempting to connect to it.
Since starting the BPD server requires executing(The client now polls the server until the socket is bound so there's not much wasted delay, and the overall timeout is longer.)import gi
this means the tests have to grow a dependency on the relevant GObject modules. Since I start the BPD server in thesetUp
method, we incur the wait-to-connect delay for each test. I set this to 0.1s and it's sufficient on my laptop but it might need to be longer on other hardware.I've written tests based on the current MPD protocol specification, which is several versions ahead of where BPD is at. For this reason I've marked several of them as expected failures for now. The various
test_implements_*
tests are just checking that BPD claims to support all of the commands in each category of the full MPD list. They can perhaps be removed after we support all the commands, but I'm just using them as a progress meter for now to highlight what's missing from the implementation.My plan is to add more tests and update BPD in subsequent PRs. As well as new commands there are new behaviours like a new more powerful filter syntax and additional flags for old commands. For now I just want to get this stub for the test framework merged.