-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
test: add a python version of test-server #20706
base: main
Are you sure you want to change the base?
test: add a python version of test-server #20706
Conversation
2383815
to
913ab48
Compare
So, I think this PR is a bit misplaced. It seems like a good chunk of our qunit tests are aiming to test specific behaviours of The real reason I did this was to help me develop and test new features in the bridge and/or So: I think we should merge this soonish, but leave the existing C After that, I think a reasonable next step would be to try to use |
913ab48
to
692597d
Compare
692597d
to
8f954de
Compare
8f954de
to
551f3f7
Compare
551f3f7
to
5ea399d
Compare
5ea399d
to
8b13dbb
Compare
hmm. |
6b8b56e
to
740518c
Compare
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.
Amazing work, thanks! So now pytest-cov runs both scenarios.
The one thing that I'm missing is to document this in HACKING.md. Right now this still says "Run ./test-server". Can you please add that py-only alternative and how to invoke it?
@@ -0,0 +1,49 @@ | |||
import os |
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.
Could this file move to test/pytest/ ? Would be a bit less clutter-y.
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 still left open, but the problem with moving it to test/pytest/
is that this fixture would also run when running normal tests as it has autouse=True
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.
What are "normal" tests? So far this is only for the alternative python test server, so presumably it shouldn't even run when using the C test server? Or perhaps it should, and this should be an independent commit that reeingineers the private test server for both QUnit test scenarios?
['dbus-daemon', f'--config-file={dbus_config}', '--print-pid'], stdout=subprocess.PIPE | ||
) | ||
except FileNotFoundError: | ||
yield None # no dbus-daemon? Don't patch. |
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.
IMO that should hard-fail with the FileNotFoundError. It's actively dangerous if you run an unit test on your production machine (which is common practice), and you don't have dbus-daemon installed (dbus-broker has long been the default in Fedora -- my system doesn't have it installed either).
A test which wants to use this won't work correctly without dbus-daemon -- it should either skip itself or handle the exception.
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 added this because it's missing from toxbox...
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.
Should it instead warn then?
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 still think it ought to hard-fail. Accidentally talking to the real user bus instead of the private test one can have dramatic consequences, if/once we write unit tests with "this is my playground" in mind. I think that so far we don't -- all unit tests run against the normal existing user bus, right? I don't see any dbus server launch anywhere in main, in fact I don't even know how they would work in github workflows, the only dbus-run-session
that I see is in flatpak-test.yml (and there's nothing similar anywhere else).
It could perhaps also try dbus-broker?
Or perhaps just leave that out for the time being?
@@ -0,0 +1,492 @@ | |||
# This file is part of Cockpit. |
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 has a __main__()
, so needs a shebang. (Why didn't our battery of linters not flag that?) GH doesn't show if the file is executable.
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.
The intent is to run this like spy -m test.pytest.mockwebserver
which will hit the __name__ == '__main__'
case and doesn't require the #!
. If you run it directly then it won't be able to find its relative imports, much less the bits of cockpit it imports.
This could use docs :)
This is roughly a replacement for test-server. It's an outline for what a simple cockpit-ws in Python might look like (albeit without any of the necessary authentication code). It's already useful enough to run the majority of the existing QUnit, which means we can now run most unit tests without needing to build any C code.
740518c
to
f8b4a22
Compare
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 probably a bit too nitpicky from my side, especially seeing how long this is open.
What I do wonder is, do we want to kill mock_info("pybridge")
it seems useless as we no longer have a C bridge in main
.
@@ -0,0 +1,49 @@ | |||
import os |
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 still left open, but the problem with moving it to test/pytest/
is that this fixture would also run when running normal tests as it has autouse=True
['dbus-daemon', f'--config-file={dbus_config}', '--print-pid'], stdout=subprocess.PIPE | ||
) | ||
except FileNotFoundError: | ||
yield None # no dbus-daemon? Don't patch. |
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.
Should it instead warn then?
|
||
@systemd_ctypes.bus.Interface.Method('', 'i') | ||
def request_signal_emission(self, which_one: int) -> None: | ||
del which_one |
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.
what purpose does this have? There isn't anything consuming which_one
as far as I can see.
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.
And src/ws/mock-service.c also only returns a test_signal if which_one == 0, so technically the test harness behaviour changed
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.
del
of a function parameter is a convention for indicating to a linter that the variable is intentionally unused. it's like naming the argument with a _
at the start, except that it keeps the name of the parameter unmodified in case it gets passed as as kwarg (like which_one=...
).
# You should have received a copy of the GNU General Public License | ||
# along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
|
||
# https://github.com/astral-sh/ruff/issues/10980#issuecomment-2219615329 |
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.
Nothing happened here sadly.
@routes.get(r'/mock/info') | ||
async def mock_info(_request: web.Request) -> web.Response: | ||
return web.json_response({ | ||
'pybridge': True, |
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.
Dumb unrelated question, why do we still have this? We only have a pybridge
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.
Right, I sent #21583 to clean it up (that's the level of engineering I'm capable of today..)
async def mock_info(_request: web.Request) -> web.Response: | ||
return web.json_response({ | ||
'pybridge': True, | ||
'skip_slow_tests': 'COCKPIT_SKIP_SLOW_TESTS' in os.environ |
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 used in one place and also confusing:
if (await mock_info("skip_slow_tests")) {
assert.ok(true, "skipping on python bridge, not implemented");
return;
}
The comment seems wildly inaccurate, the test is a bit slow but it finished in 10 seconds so imo not that bad.
> info: # test: parallel stress test Array(0)
> warn: Test "parallel stress test" took longer than 3000ms, but no timeout was set. Set QUnit.config.testTimeout or call assert.timeout() to avoid a timeout in QUnit 3. ht
tps://qunitjs.com/api/config/testTimeout/
One small issue
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.
Fixed the timeout issue in https://github.com/cockpit-project/cockpit/compare/main...jelly:qunit-timeout-deprecated?expand=1
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 applies to running under either valgrind or qemu software emulated builders.
|
||
@routes.get('/') | ||
async def index(_request: web.Request) -> web.Response: | ||
cases = Path('qunit').rglob('test-*.html') |
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.
Hmm, am I silly? There is no qunit
path? So I am a bit confused what this lists.
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.
Like it's mostly under base1
?
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.
esbuild puts the built bundles of pkg/base1/test* into qunit//test.
|
||
|
||
async def main() -> None: | ||
parser = argparse.ArgumentParser(description="Serve a single git repository via HTTP") |
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.
🤔 That could use an update :-)
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.
Oops. Thanks for the catch.
'base1/test-external.html', | ||
'base1/test-http.html', | ||
'base1/test-stream.html', | ||
'shell/machines/test-machines.html', |
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.
So test-machines has cockpit.dbus(null, { bus: 'internal' })
I guess that's the system bus?
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.
No. That's our internal fake D-Bus inside of the bridge. We have some internal endpoints like configuration, machines, etc. that we publish via D-Bus for convenience, rather than making them their own channel types.
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.
There's still a lot left from my previous review, but I'd first like to understand where this is going.
It seems to me that what we don't want is a complete implementation of a web server which is just being used for unit tests, that feels like a dead end. As you said, much of this tests the actual cockpit ws functionality, so this would only make sense if this eventually evolves into the web server. But we don't have a design/architecture for that yet (see COCKPIT-1238 and in particular COCKPIT-1239), and also we can't use aiohttp for that. So much like test-server
, it would make sense if the Python version was an extension/derivative of the official web server which then implements all the mock paths.
So perhaps it'd be prundent to queue this until after COCKPIT-1239?
|
||
./test-server | ||
|
||
which will output a URL to connect to with a browser, such as | ||
<http://localhost:8765/qunit/base1/test-dbus.html>. Adjust the path for different | ||
tests and inspect the results there. | ||
tests and inspect the results there. Although the QUnit tests are mostly about testing the client side API, they also indirectly test the webserver, and test-server is written using many of the same components as cockpit-ws. |
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.
Overly long line
['dbus-daemon', f'--config-file={dbus_config}', '--print-pid'], stdout=subprocess.PIPE | ||
) | ||
except FileNotFoundError: | ||
yield None # no dbus-daemon? Don't patch. |
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 still think it ought to hard-fail. Accidentally talking to the real user bus instead of the private test one can have dramatic consequences, if/once we write unit tests with "this is my playground" in mind. I think that so far we don't -- all unit tests run against the normal existing user bus, right? I don't see any dbus server launch anywhere in main, in fact I don't even know how they would work in github workflows, the only dbus-run-session
that I see is in flatpak-test.yml (and there's nothing similar anywhere else).
It could perhaps also try dbus-broker?
Or perhaps just leave that out for the time being?
@@ -0,0 +1,49 @@ | |||
import os |
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.
What are "normal" tests? So far this is only for the alternative python test server, so presumably it shouldn't even run when using the C test server? Or perhaps it should, and this should be an independent commit that reeingineers the private test server for both QUnit test scenarios?
import aiohttp | ||
from aiohttp import WSCloseCode, web |
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.
As discussed in our 1-1 yesterday and in https://issues.redhat.com/browse/COCKPIT-1238: for the top-level web server we can't use aiohttp
as that doesn't support HTTP 2/3. The most prominent candidate would be fastapi or the underlying starlette. I wouldn't want our future cockpit-ws package to end up depending on and being written in two different frameworks.
It's probably easier to port it now while it's in its infancy than once we actually start rolling it out.
I'm not yet clear where this is going, though, I'll write something in the top-level review. This might be a follow-up.
|
||
@routes.get('/') | ||
async def index(_request: web.Request) -> web.Response: | ||
cases = Path('qunit').rglob('test-*.html') |
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.
esbuild puts the built bundles of pkg/base1/test* into qunit//test.
very very broken at present, but definitely enough to show promise...
Update: the rationale on this PR has changed a bit, so I'll write about the updated idea here. The main reason for that change is that in the course of getting various qunit tests working with the new server it became clear that part of what the tests are trying to exercise is cockpit-ws itself. That makes sense because although
test-server
is a separate binary, it uses quite a lot of the same internal implementation as the existingcockpit-ws
, so the code that the two of them share gets exercised by the tests.Moving to a situation where we have a version of test-server in Python which we use to run the tests, but still run the C-based
cockpit-ws
in production would reduce the meaningfulness of these tests.The core reason why I started this work remains the same though: after a bunch of recent hacking on
cockpit.js
(andpkg/lib/cockpit/
) I started to want a better developer experience for that. This is also why I developed the coverage stuff.Specifically: this work makes it possible to run qunit tests without needing to build
test-server
: justvendor/checkout
,./build.js
andpytest -k test-fsinfo.html
. The new server is significantly faster to run, as it runs the bridge code in-process (reducing startup times).There are a few missing features (like missing dbus mock endpoints and so on) vs. the C-based test-server. 5 test files are marked as xfail when running under the new server. There's probably some low-hanging fruit there, but I wanted to get this merged up before it rots too much.