Commit Graph

52 Commits

Author SHA1 Message Date
Allison Karlitskaya d474bdca0a test/static-code: copy mypy setup from bots/
Move our list of static files out of test/static-code (which is shared
with other projects).  Add testlib to that list.

This reduces the cost of running mypy on a warm cache from 20s to ~0.3s,
while expanding checking to every single Python file in the source tree.

A overall `test/static-code` run (minus `node_modules`) decreases from
~24s to ~4s.  The majority of that time is now in the json validity
checking rule (since it runs a separate python invocation for each
file).
2024-04-23 00:13:10 +02:00
Allison Karlitskaya 60d54b8a31 tools: rename vulture(- → _)suppressions
We are about to ask mypy to run on the entire source tree, and it
doesn't like having `-` in module names.  Use a `_` instead.
2024-04-23 00:13:10 +02:00
Allison Karlitskaya 6298dd93c8 test/static-code: drop flake8
`ruff` in preview mode now implements the various E[123] rules that we
were depending on flake8 for.  Drop it.
2024-04-16 14:08:52 +02:00
Allison Karlitskaya 5b6c1ce8ae pyproject: enable 'preview' ruff rules
Now that we gate our ruff updates via the tasks container, we can afford
to be a bit more future-oriented here.

This brings in a nice raft of fix-ups, including my personal favourite:

  RUF003 Comment contains ambiguous `µ` (MICRO SIGN). Did you mean `μ` (GREEK SMALL LETTER MU)?

Sure... why not?

The majority of fixes here are from a single rule suggesting to rewrite

  a and b or c

as

  (a and b) or c

But in most (but not all) cases in our code we actually want to write
that as

  b if a else c

I've also added some type annotations to help me make the right choice
in places where it wasn't immediately clear.
2024-04-16 14:08:52 +02:00
Allison Karlitskaya dada0dcc54 pyproject.toml: stop linting in venvs
Lacking a proper requirements.txt file, we're at the mercy of whatever
got randomly uploaded to pypy last night, and we've been seeing a lot of
breaks lately.

We already run our ruff/mypy/etc. stuff as part of our unit tests
workflow, so let's stop running it in venv.  The idea is that we should
pass against the current version of the tasks container (which will
probably shift at some point to being our pinned version of the tasks
container).

While we're at it, drop Python 3.7 (which the world seems to have
collectively decided to stop caring about) and add Python 3.13 (which
has been in Fedora 39 for a while already).  Note: we do *not* drop
Python 3.6 — although it is no longer an officially supported release,
it's used in RHEL 7 and RHEL 8 and Fedora intends to keep packaging it.
2024-03-11 12:52:01 +01:00
Allison Karlitskaya 9b221db9b6 ruff: move linter settings to [lint] section
New ruff warns to stderr that the old way is deprecated.  That's enough
to convince test/static-code that an error is being reported.

Let's move this over.
2024-02-02 16:16:19 +01:00
Martin Pitt 9fa8581e41 mypy: Ignore external modules without annotations
These are imported in scripts in src/ and pkg/, which we will soon start
covering.
2023-11-02 09:45:34 +01:00
Jelle van der Waa 1907213c2f ruff: Enable UP032 f-string rule
Convert .format() to f-strings with ruff's auto-fixer.
2023-09-27 17:44:24 +02:00
Allison Karlitskaya 9fe6e1d471 ruff: enable PIE rules, apply some auto-fixups
The main thing here is catching unnecessary `pass`.

...also add a missing ruff.toml file to test/static-code.
2023-09-08 11:02:00 +02:00
Allison Karlitskaya 17cd42da90 pyproject: add an ignore for a bad error 2023-09-08 11:02:00 +02:00
Allison Karlitskaya f4be9066c9 python bridge: rewrite packages serving code
Rework the packages loading code.  The main changes:

 - we don't attempt to do checksums anymore.  This was taking a large
   amount of time when loading the bridge and we never implemented it
   properly anyway (since we weren't sending the `.checksum` fields in
   the manifests)

 - instead, we do scan the list of files in the package one time (at
   first use, not load) and build our map of URL paths to filenames at
   this point.  This allows us to retain our efficient lookup code, but
   requires us to do the actual file loading at the time of the request.
   Because we're no longer storing the contents of the files in memory,
   this is a substantial runtime memory usage reduction.

 - drop all of the extra code we had for walking the paths in a
   particular order.  We just do (r)globs now.

 - move all of the code for deciding which packages to load to a
   separate class.  We now load all of the manifests and evaluate them
   (requirements, conditionals, priorities) and only the packages that
   we actually intend to serve are then scanned.  The new structure also
   makes it easier for cockpit-beiboot to do its thing.

 - the packages loading code now uses the actual cockpit version number
   for requires comparisons instead of the previously hardcoded '300'.
   Add an extra check if our version number is `0` (ie: running out of
   git) and disable version checking in that case.

 - clean up the relationship between `cockpit.packages` and
   `cockpit.channels.packages`.  Previously, `cockpit.packages` would
   access properties on the channel object and call methods on it to
   serve the content back.  Now the channel requests the data from
   `cockpit.packages` (which merely returns the result).

 - enabled by the above: full typing throughout, and mypy is happy.  We
   have one tiny little thorn in that the packages channel is not
   strictly capable of knowing that the router to which it's connected
   has a `packages` attribute, but that's nothing that we weren't doing
   already.  Add a comment to draw attention to it.

 - to the extent possible, we try to keep the state of the packages
   channel away from the packages code proper.  This led to an overhaul
   of our `Content-Security-Policy` not to include the origin URLs in
   the policy output.  This is redundant anyway, since that's what
   "'self'" is for.  We do need to do one hack for websockets though,
   until we can convince ourselves about browser support for the
   standard.  This hack is lifted to the channel level.  Adjust tests
   accordingly.

 - with some small changes to our pyproject.toml, the two rewritten
   files (`packages.py` and `channels/packages.py`) are now also passing
   pylint, but we don't enable that yet, since everything else is
   broken.
2023-07-17 16:58:42 +02:00
Allison Karlitskaya c8c62a1cbf pyproject.toml: copy systemd_ctypes tox setup
Copy the same sort of tox setup that systemd_ctypes is using, with some
minor adjustments to testing dependencies and the like.

The biggest change here is that instead of running each linter tool as a
separate command, we run them all together via test/static-code.  The
main reason for that is that we have a lot of stand-alone executable
Python scripts (chmod +x, and not ending in .py) that we also want to
lint, and the linters don't know how to find those on their own.

As in systemd_ctypes, typing `tox p` now gives a fairly nice experience.

Note also: it's now possible to do a linter run with tools installed
from PyPI using something like:

    tox -e py311-lint

It's also possible to run pytest with older version of Python:

    pytest -e py36-pytest

It's also possible to run all of the venv-based environments like:

    tox p -m venv
2023-07-04 16:45:11 +02:00
Allison Karlitskaya d07c627d45 pytest: drop "auto" asyncio mode
This feature isn't in any version of pytest-asyncio available for Python
3.6, so drop it from our config for now and add explicit decorators in
the places where they were missing.
2023-07-04 16:45:11 +02:00
Allison Karlitskaya 94e8f638b5 python: fix up various mypy issues
We're actually really close to having working mypy including the unit
tests in test/pytest.

The biggest change here is that we stopped using the return value of
Peer.do_connect_transport() a while ago.  Properly change that to be
`None` everywhere.
2023-07-04 16:45:11 +02:00
Martin Pitt 6deceb97fc ruff: Ignore RUF012
ruff 0.0.274 starts to complain about this. We can fix this at some
point, but for now let's unbreak the container refresh.
2023-06-22 20:03:36 +02:00
Allison Karlitskaya c4f3eb3707 test: convert test_bridge.py to pytest 2023-06-17 17:16:30 +02:00
Allison Karlitskaya 5a497e86c7 test/verify: teach pytest how to load check-*
Add a conftest.py file to test/verify with a hook that allows us to load
our `check-*` files.  This is a little bit complicated because the `-`
in the name and the lack of `.py` means that they're not recognized as
normal python modules, so we need to do some hacks (similar to the ones
we do in test/common/run-tests).

With this in place, it's now possible to run the integration test suite
via pytest with something like the following:

  $ test/common/pixel-tests pull
  $ test/image-prepare
  $ test/common/pywrap -m pytest test/verify -n4

Note: the -n option is for specifying the level of parallelism and
requires the python3-pytest-xdist plugin to be installed.

We adjust our vulture config a bit to teach it about pytest plugins.
2023-06-16 13:17:46 +02:00
Allison Karlitskaya ef9827931b python: change '''strings''' to """strings"""
beipack prefers if python files don't contain `'''` because it uses this
for its own escaping of the file content.  There's no ruff rule to
blanket-forbid the use of `'''` but all of the cases we have of `'''` in
src/cockpit happen to be docstrings, and there's a rule for that, so
let's turn it on.

This results in a lot of hints in docstrings in a lot of hits in other
files.  I've done a blanket

    sed -i s/\'\'\'/\"\"\"/g $(git grep -l "'''" src test tools)

and unsurprisingly, this hasn't caused any problems.
2023-06-14 12:03:32 +02:00
Allison Karlitskaya 63eee8e92d pyproject: add test/common to mypy path
This gives mypy a bit of help in finding our testing API.  Mostly, it
helps get rid of a lot of warnings like these in vim:

  # E: Cannot find implementation or library stub for module
2023-05-26 16:17:29 +02:00
Allison Karlitskaya 246668d75c ruff: remove custom isort config for test/common
Without the parent hacks we can just treat the bots and testlib stuff as
normal third party imports.

This causes one small problem: ruff defaults to auto-detect files in the
current directory as first-party imports, which affects a couple of
imports in test/common/run-tests.  Those are only detected first-party
if the files are present when we run ruff, which is not the case for
incremental changes checked by test/static-code when used as part of a
git hook.  This wasn't a problem before, because we hardcoded the
modules.  Disable ruff's automatic import checking (at the problem
toplevel) to avoid that problem in this case and potential future cases.
2023-05-26 16:17:29 +02:00
Allison Karlitskaya fafe29acf1 test/static-code: change approach to running ruff
Instead of running ruff on *.py, with a large number of exemptions (all
of test/verify, test/common, tools/, etc) we now run it on all of the
Python files that test/static-code can find.

Previous commits have done some amount of code cleanups on the
newly-exposed code, but we deal with most remaining issues by using
per-directory ruff.toml files which let us adjust the configuration in a
subdirectory.  We make sure to add those to test/static-code so that
they will be available for partial linting during hooks.
2023-05-26 09:08:06 +02:00
Allison Karlitskaya fd8ad77f94 python: add 'pygrep-hooks' to ruff config
This is mostly about detecting and forbidding blanket noqa and mypy
suppression comments.
2023-05-22 16:13:14 +02:00
Allison Karlitskaya fc19bc3797 python: forbid boolean positional arguments
Add "FBT" to our ruff config, which forbids use of booleans as
positional arguments in function definitions and calls.

Do some clean-ups for that.

One little bit of Python syntax:

  def func(x, *, y=None):
    ...

means that the following arguments can not be given positionally, and
must always by specified by name, like `y=value`.
2023-05-22 16:13:14 +02:00
Allison Karlitskaya ea98fb35a9 pyproject.toml: add a bunch of ruff rules
We can enable these without any code changes, so let's do it.
2023-05-22 16:13:14 +02:00
Allison Karlitskaya 616d627e44 python: let ruff rewrite some comprehensions
Add the "C4" rules to ruff and do some automated fix-ups based on those.
2023-05-22 16:13:14 +02:00
Allison Karlitskaya 4ed0fc090d python: add pytest checks to ruff config
...and do some suggested cleanups.
2023-05-22 16:13:14 +02:00
Allison Karlitskaya 8e23efd14e python: include clean-ups for isort
Theoretically, we want to separate things into five buckets (stdlib,
third-party, vendored, cockpit, local-folder) but we also want to take a
more or less stock isort behaviour, so we do this:

In our testcases, we want the "local folder" bucket to refer to the mock
utility classes in the pytest/ directory, while first-party refers to
cockpit.  Add __init__.py to test/ and test/pytest/ to ensure that we can
do relative imports (which we should have been doing already).

In src/ we make sure to always refer to our vendored packages by their
full name (`cockpit._vendor.*`) and to do relative imports from cockpit
itself.  That ends up putting our vendored packages in the first-party
bucket and cockpit into local-folder, which is approximately correct.

The only issue here is that if we directly include vendored packages
from our testcases, they end up in the same bucket with cockpit itself.
That's not a big problem.
2023-05-22 16:13:14 +02:00
Allison Karlitskaya 21c2ee5d61 python: enable ruff "G" (logging) checks 2023-05-22 16:13:14 +02:00
Allison Karlitskaya 78c0282e9c python: enable ruff "A" (builtins) checks, minus one
This enables checking for use of variable names which shadow builtins.
The check for class attributes is not reasonable (since it's almost
impossible to refer to class attributes as bare words), so disable it.
2023-05-22 16:13:14 +02:00
Allison Karlitskaya 76672a9d42 python: enable ruff bugbear checking
Enable the "B" category, add a single exception (which is hit from stuff
in tests/), and make some minor fixes.
2023-05-22 16:13:14 +02:00
Allison Karlitskaya 355c0aa59e python: isort everything via ruff
This commit is based on doing two things:

  - selecting "i" rules for ruff in our `pyproject.toml`

  - running `ruff check --fix .`
2023-05-17 19:54:15 +02:00
Allison Karlitskaya 6e8e03dd62 pyproject.toml: fix vulture suppressions path
We iterated a couple of times on the name and location of this directory
while working on the PR and forgot to update it in pyproject.toml.  Fix
it.
2023-05-12 12:04:51 +02:00
Allison Karlitskaya 10de075181 build: write a bare minimal PEP 517 build backend
Drop our dependency on setuptools and write our own PEP 517 build
backend.  This turns out to be relatively straightforward — only ~100
lines, and avoids the workarounds we had to do to prevent setuptools
from trying to write to the source tree.  It's also dramatically
faster.

While we're at it, we can now fix a long-standing nag: we now have a
proper place to make sure that `modules/checkout` gets called before
building an sdist or a wheel.  That means that you can run `tox` or `pip
install .` in a freshly checked-out tree and expect correct results.

We have to add a command-line interface to support wheel creation on
RHEL8, where pip is not yet PEP517-aware.

We don't implement any optional features of PEP 517 and we also don't
support editable installs.
2023-05-12 11:01:02 +02:00
Allison Karlitskaya 33250c51d0 test/static-code: fix our vulture setup
The way we've been using vulture is pretty ineffectual.  Cranking the
min-confidence all the way to 100 is skipping a lot of potentially
useful messages.  We've had to do that because vulture does
whole-program analysis and we run test/static-code from our post-common
hook only on the files that changed.

Let's move our vulture configuration to pyproject.toml, removing the
minimum confidence level while we're at it, so we get a lot more
suggestions.  We have to add some suppressions to deal with a lot of
those, but that's how the tool is intended to be used.

In order to avoid running vulture in the post-commit hook (where it only
has access to a subset of files) use an environment variable to signal
test/static-code that we're in this situation an skip vulture in that
case.

We effectively have two sets of configuration for vulture now:

 - what happens when you run `vulture`: this basically runs on `src/`
   and `test/pytest`, excluding the integration tests and testlib.  We
   do this because by default vulture only looks at *.py files, and our
   integration tests are executable scripts without extensions.  That
   would lead vulture to falsely conclude that most of testlib is
   unused.  This is also how vulture will run from ALE, if enabled.

 - when happens when you run `test-static/code`: in this case, we do
   more aggressively scanning for python files based on #!-lines.  In
   this case we scan all files (except for fmf_metadata) which means
   that the testlib appears (correctly) well-used.
2023-05-10 14:13:05 +02:00
Allison Karlitskaya 7be1ce37ae python: white-space changes, limit to 118 columns
Make a bunch of pure-whitespace changes to get us under the maximum line
length of 118.  Modify the value for ruff in pyproject.toml.

For some of these cases, particularly in some of the long function call
signatures in mocktransport, I let black do its thing.  It strongly
favours hanging indent for long argument lists and produced output like
this:

   def function(
       arg1: type,
       arg2: type,
       arg3: type,
   ) -> type:
       body

I'm not super thrilled about that, but it has precedence in PEP-8.
https://github.com/psf/black/issues/1178 has some (heated) discussion
about the exact indentation level, but nobody seems to prefer visual
indent to hanging indent in this case.

Let's let it be.
2023-05-10 14:13:05 +02:00
Allison Karlitskaya 8dd8121251 python: introduce ruff as a linter
Add some initial ruff configuration to pyproject.toml and install ruff
in the unit-tests container (via pypi, unfortunately).  Add a hook to
pytest to run ruff, if it's available.

The initial configuration is chosen to avoid having errors straight
away.  Fixes will follow.

Add ruff to test/static-code and make sure we send pyproject.toml into
the tmpdir when running from the post-commit (and pre-push) hooks.

ruff is still very much in development and it's missing a lot of
common-sense checks.  We'll keep flake8 as well, for the time being.
2023-05-10 14:13:05 +02:00
Allison Karlitskaya fb6c0d0879 pyproject.toml: require pytest-asyncio
We get a better error message if we declare our dependency here.
2023-05-10 14:13:05 +02:00
Allison Karlitskaya 217cfe5e0e python: migrate setuptools config to setup.cfg
The version of setuptools required to support pyproject.toml isn't
available in any current version of RHEL, so let's take a step back for
now and port our config to setup.cfg.

This lets us drop our setuptools dependency to 39.2.0, which is,
conveniently, the version available in RHEL8 as well as the minimum
version that supports dynamic version numbers (which we will soon use).

While we're at it, switch our package-finding logic to the
fully-automatic form, adding an __init__.py to cockpit.data to make sure
it gets included.
2023-05-03 09:50:03 +02:00
Allison Karlitskaya f9eb7c4ad0 python: add tox support
Include the test files in the sdist (and therefore SRPM).

This is how RPM expects to run our tests.

Use the setup from systemd_ctypes.  The default test environment (site)
will use the system environment and install no additional depends.
Manually specifying an environment (like `-e py311`) will create a
virtual environment and install the correct dependencies.

The rpm macros seem to use the py311 environment name by default both
for collecting build dependencies (to turn them into RPM package
depends) and for running the tests themselves, so this works well.

This also allows us to easily run the tests under old Python versions
(like `tox -e py37`).  Note, however, that we can't test Python 3.6
because we depend on a version of setuptools that's not available there.
2023-05-03 09:50:03 +02:00
Allison Karlitskaya 7e97d9775c pyproject.toml: enable pytest --strict-markers
As discussed in #18584, pytest really should not be ignoring unknown
marks.  Let's make sure that gets treated as an error.
2023-03-30 17:49:22 +02:00
Allison Karlitskaya 7d82d3c2b4 python: major peer initialization overhaul
Our PeerStateListener and SuperuserStartup (and DBusStartup and
ControlMessageStartup) objects had a strange and overlapping role which
was basically two things:

 - notification of completion of a task, with possible notification of
   failure, including reporting of exceptions

 - an authentication hook

The first thing is basically something like a Future, which is
equivalent to an asynchronous function call.  In some places, we were
even bridging this gap, such as via DBusStartup creating a future for
the (async) D-Bus call to wait on.

Let's push things away from these custom types and towards a more
unified approach of asynchronous function calls for startup of peers.
Failures are reported via exceptions and completion is reported by
returning.

For the authentication hook usecase, we can use the existing
ferny.InteractionResponder interface.  It also works as an async
method call, so add some async helpers to CockpitServerProtocol for
sending/receiving authorize challenge/responses asynchronously.

Drop our own custom askpass code and use ferny's.  Also drop all
handling of authorize control messages from Peer — this all happens via
the ferny InteractionAgent now.  Our "pseudo" test helper needs a
rewrite to deal with that.

We also drop our send-stderr control message solution that we copied
from the C bridge and replace it with a solution which passes the
original stderr over the redirected stderr from the ferny agent.

Add a bunch of unit tests to check that peers behave properly in various
situations.
2023-03-17 05:55:22 +01:00
Allison Karlitskaya 6fd6fcccfe python: add ferny as a submodule 2023-03-17 05:55:22 +01:00
Allison Karlitskaya f669809799 python: Change approach to vendoring systemd_ctypes
Instead of shipping our copy of systemd_ctypes as a top-level package
which might conflict with an independently-installed version, ship it as
a package called cockpit._vendor.systemd_ctypes.
2023-03-17 05:55:22 +01:00
Allison Karlitskaya fb0e224ba3 python: add cockpit-askpass in python 2023-02-09 17:56:55 +01:00
Martin Pitt 9b8be3baab python: Restrict mypy to src/cockpit
Disable checking src/systemd_ctypes, as that is imported from a separate
project.
2023-02-08 17:02:03 +01:00
Allison Karlitskaya 04a13a1395 python: send html error page on Packages errors
Like the C bridge send a proper formatted `fail.html` back to the
client. This does not handle anything any other then 404's.

Co-authored-by: Jelle van der Waa <jvanderwaa@redhat.com>
2023-01-26 20:18:15 +01:00
Allison Karlitskaya 1404e2712f test: build a wheel for the pybridge scenario
Since we dropped the Makefile rule for creating cockpit-bridge.pyz, the
pybridge scenario has been broken.

Add some minimal pyproject.toml metadata required to build a wheel of
the Python package.  Build this wheel on the outside, and inject it into
the VM via image-customize.

Note: we no longer bother with building the C parts of the package
anymore — we rely on the distro packages already present in the image.
2022-11-18 17:26:08 +01:00
Allison Karlitskaya 79f378dfe0 python: merge `make pycheck` into pytest
Add a new testcase in test/pytest/test_server.py which knows how to run
the QUnit tests, including collection of coverage information.

Make sure we build the `test-server` and the webpacks before running
`pytest` from the Makefile.  For running `pytest` directly, the html
tests will be skipped if the test-server is missing or if there are no
`dist/*/test-*.html` files.

Remove the `make pycheck` target, and drop the extra scenario from the
unit-tests workflow.

`pytest` now takes significantly longer to run.  The pytest-xdist
plugin can help with this, and it works in combination with our other
plugins, pytest-timeout and pytest-cov.

For manual invocation of pytest, use something like the following:

  pytest -n auto --cov --timeout 30

We run the tests non-parallel in the unit-tests container because
there's no real advantage to speeding this up in that case, and running
in parallel makes the log output more difficult to read.
2022-11-15 09:27:36 +01:00
Allison Karlitskaya 3ae05723ed python: drop tox section from pyproject.toml
This is a leftover from when we were considering using tox to drive the
pytest tests.  Without a build system (which was removed during the
review process), this won't work anyway.
2022-11-07 11:15:58 +01:00
Allison Karlitskaya f8c32f70fb python: coverage tweaks
Enable branch coverage.

Skip displaying files with 100% coverage in the report.

Add the '# pragma: no cover' pattern to our excludes list.  The docs
say:

    Note that when using the `exclude_lines` option in a configuration
    file, you are taking control of the entire list of regexes, so you
    need to re-specify the default “pragma: no cover” match if you still
    want it to apply.

Make a few other tweaks to the transports code to improve coverage,
turning some `if` statements into `assert`s, for example.  We also don't
try to observe BlockingIOError directly after polling as ready to read
or write — that's a difficult case to trigger.
2022-11-04 17:42:27 +01:00