Skip to content

Commit

Permalink
tests: migration from nose to pytest
Browse files Browse the repository at this point in the history
Python 3.11 and higher are no longer compatible with nose.

Signed-off-by: Vassili Tchersky <[email protected]>
  • Loading branch information
vassilit committed Mar 5, 2025
2 parents d328a41 + b839f32 commit 79a1e6b
Show file tree
Hide file tree
Showing 52 changed files with 246 additions and 499 deletions.
6 changes: 3 additions & 3 deletions docs/developers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -237,13 +237,13 @@ Notable targets

:test:

Build the tests (requires ``python`` and ``nosetest`` installed).
Optionally ``valgrind`` can be installed to run the tests through
Build the tests (requires ``python`` and ``pytest`` installed).
Optionally ``valgrind`` can be installed to run the tests through
valgrind:

.. code-block:: bash
$ USE_VALGRIND=1 nosetests # or nosetests-3.3, python3 needed.
$ USE_VALGRIND=1 pytest
:xgettext:

Expand Down
2 changes: 1 addition & 1 deletion docs/install.rst
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ Here's a list of readily prepared commands for known operating systems:

.. code-block:: bash
$ apt-get install pkg-config git scons python3-sphinx python3-nose gettext build-essential
$ apt-get install pkg-config git scons python3-sphinx python3-pytest gettext build-essential
# Optional dependencies for more features:
$ apt-get install libelf-dev libglib2.0-dev libblkid-dev libjson-glib-1.0 libjson-glib-dev
# Optional dependencies for the GUI:
Expand Down
23 changes: 10 additions & 13 deletions docs/testing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Testsuite
complete yet (and probably never will), but it's already a valuable boost of
confidence in ``rmlint's`` correctness.

The tests are based on ``nosetest`` and are written in ``python>=3.0``.
The tests are based on ``pytest`` and are written in ``python>=3.0``.
Every testcase just runs the (previously built) ``rmlint`` binary a
and parses its json output. So they are technically blackbox-tests.

Expand Down Expand Up @@ -34,17 +34,17 @@ variables which are:
- ``RM_TS_PRINT_CMD``: Print the command that is currently run.
- ``RM_TS_KEEP_TESTDIR``: If a test failed, keep the test files.

Additionally slow tests can be omitted with by appending ``-a '!slow'`` to
the commandline. More information on this syntax can be found on the `nosetest
Additionally slow tests can be omitted with by appending ``-k 'not slow'`` to
the commandline. More information on this syntax can be found on the `pytest
documentation`_.

.. _`nosetest documentation`: http://nose.readthedocs.org/en/latest/plugins/attrib.html
.. _`pytest documentation`: https://docs.pytest.org/en/stable/example/markers.html

Before each release we call the testsuite (at least) like this:

.. code-block:: bash
$ sudo RM_TS_USE_VALGRIND=1 RM_TS_PRINT_CMD=1 RM_TS_PEDANTIC=1 nosetests-3.4 -s -a '!slow !known_issue'
$ sudo RM_TS_USE_VALGRIND=1 RM_TS_PRINT_CMD=1 RM_TS_PEDANTIC=1 pytest -s -a 'not slow and not known_issue'
The ``sudo`` here is there for executing some tests that need root access (like
the creating of bad user and group ids). Most tests will work without.
Expand All @@ -59,7 +59,7 @@ were executed (and how often) by the testsuite. Here's a short quickstart using
.. code-block:: bash
$ CFLAGS="-fprofile-arcs -ftest-coverage" LDFLAGS="-fprofile-arcs -ftest-coverage" scons -j4 DEBUG=1
$ sudo RM_TS_USE_VALGRIND=1 RM_TS_PRINT_CMD=1 RM_TS_PEDANTIC=1 nosetests-3.4 -s -a '!slow !known_issue'
$ sudo RM_TS_USE_VALGRIND=1 RM_TS_PRINT_CMD=1 RM_TS_PEDANTIC=1 pytest -s -a 'slow and not known_issue'
$ lcov --capture --directory . --output-file coverage.info
$ genhtml coverage.info --output-directory out
Expand All @@ -85,11 +85,9 @@ A template for a testcase looks like this:

.. code-block:: python
from nose import with_setup
from tests.utils import *
@with_setup(usual_setup_func, usual_teardown_func)
def test_basic():
def test_basic(usual_setup_usual_teardown):
create_file('xxx', 'a')
create_file('xxx', 'b')
Expand Down Expand Up @@ -117,11 +115,10 @@ Rules

.. code-block:: python
from nose.plugins.attrib import attr
import pytest
@attr('slow')
@with_setup(usual_setup_func, usual_teardown_func)
def test_debian_support():
@pytest.mark.slow
def test_debian_support(usual_setup_usual_teardown):
assert random.choice([True, False]):
* Unresolved issues can be marked with `known_issue` attribute to avoid failing automated travis testing
3 changes: 3 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[pytest]
markers =
slow: slow tests.
8 changes: 4 additions & 4 deletions tests/SConscript
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ def cmd_exists(cmd):


def run_tests(target=None, source=None, env=None):
names = ['nosetests-3.3', 'nosetests-3', 'python3-nosetests', 'nosetests3', 'nosetests']
names = ["pytest"]
exes = [exe for exe in names if cmd_exists(exe)]
if any(exes):
name = exes[0]
print('Found nosetests as "{}"'.format(name))
Exit(subprocess.call(name + ' -s -v -a !slow', shell=True))
print('Found pytest as "{}"'.format(name))
Exit(subprocess.call(name + ' -s -v -k "not slow"', shell=True))

print('Unable to find nosetests, tried these: ' + str(names))
print('Unable to find pytest, tried these: ' + str(names))
Exit(-1)


Expand Down
25 changes: 22 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,26 @@
import pytest
from tests.utils import cleanup_testdir, create_testdir
import tests.utils as utils

@pytest.fixture(autouse=True)
def with_cleanup_between_runs():
cleanup_testdir()
create_testdir()
utils.cleanup_testdir()
utils.create_testdir()


@pytest.fixture(params=["sh", "bash", "dash"])
def shell(request):
yield request.param


@pytest.fixture
def usual_setup_usual_teardown():
utils.usual_setup_func()
yield
utils.usual_teardown_func()


@pytest.fixture
def usual_setup_mount_bind_teardown():
utils.usual_setup_func()
yield
utils.mount_bind_teardown_func()
4 changes: 1 addition & 3 deletions tests/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
nose==1.3.7
pytest==8.3.4
parameterized==0.6.1
xattr==0.9.6
psutil==5.6.6
pytest==8.3.5
11 changes: 4 additions & 7 deletions tests/test_formatters/test_csv.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
#!/usr/bin/env python3
# encoding: utf-8
from nose import with_setup
import csv

from tests.utils import *

import csv

def csv_string_to_data(csv_dump):
data = list(csv.reader(csv_dump.splitlines()))
return data[1:]


@with_setup(usual_setup_func, usual_teardown_func)
def test_simple():
def test_simple(usual_setup_usual_teardown):
create_file('1234', 'a')
create_file('1234', 'b')
create_file('1234', 'stupid\'file,name')
Expand Down Expand Up @@ -40,8 +38,7 @@ def test_simple():


# regression test for GitHub issue #496
@with_setup(usual_setup_func, usual_teardown_func)
def test_no_checksum():
def test_no_checksum(usual_setup_usual_teardown):
# rmlint will not (normally) hash files with no same-sized siblings
create_file('x', 'a')
create_file('yy', 'b')
Expand Down
5 changes: 1 addition & 4 deletions tests/test_formatters/test_json.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
#!/usr/bin/env python3
# encoding: utf-8
from nose import with_setup
from tests.utils import *


@with_setup(usual_setup_func, usual_teardown_func)
def test_simple():
def test_simple(usual_setup_usual_teardown):
full_path_a = create_file('x', '\t\r\"\b\f\\')
full_path_b = create_file('x', '\"\t\n2134124')
head, *data, footer = run_rmlint('-S a')
Expand Down
5 changes: 1 addition & 4 deletions tests/test_formatters/test_others.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
#!/usr/bin/env python3
# encoding: utf-8
from nose import with_setup
from tests.utils import *


@with_setup(usual_setup_func, usual_teardown_func)
def test_just_call_it():
def test_just_call_it(usual_setup_usual_teardown):
create_file('1234', 'a')
create_file('1234', 'b')

Expand Down
13 changes: 4 additions & 9 deletions tests/test_formatters/test_py.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
#!/usr/bin/env python3
# encoding: utf-8
from nose import with_setup
from tests.utils import *

import subprocess

from parameterized import parameterized
import pytest


def _check_interpreter(interpreter):
Expand All @@ -16,16 +13,14 @@ def _check_interpreter(interpreter):
return False


@parameterized(["python2", "python3"])
@with_setup(usual_setup_func, usual_teardown_func)
def test_paranoia(interpreter):
@pytest.mark.parametrize("interpreter", ["python2", "python3"])
def test_paranoia(usual_setup_usual_teardown, interpreter):
if not _check_interpreter(interpreter):
print(
pytest.skip(
"Interpreter {} does not seem to be working, skipping test".format(
interpreter
)
)
return

create_file('xxx', 'a')
create_file('xxx', 'b')
Expand Down
51 changes: 14 additions & 37 deletions tests/test_formatters/test_sh.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
#!/usr/bin/env python3
# encoding: utf-8
from tests.utils import *

import shlex
import subprocess

from nose import with_setup
from parameterized import parameterized
from tests.utils import *
import pytest


def run_shell_script(shell, sh_path, *args):
Expand All @@ -22,9 +19,7 @@ def filter_part_of_directory(data):
return [e for e in data if e['type'] != 'part_of_directory']


@with_setup(usual_setup_func, usual_teardown_func)
@parameterized([("sh", ), ("bash", ), ("dash", )])
def test_basic(shell):
def test_basic(usual_setup_usual_teardown, shell):
create_file('xxx', 'a')
create_file('xxx', 'b')

Expand Down Expand Up @@ -72,9 +67,7 @@ def test_basic(shell):
assert '/a' in text


@parameterized([("sh", ), ("bash", ), ("dash", )])
@with_setup(usual_setup_func, usual_teardown_func)
def test_paranoia(shell):
def test_paranoia(usual_setup_usual_teardown, shell):
create_file('xxx', 'a')
create_file('xxx', 'b')
create_file('xxx', 'c')
Expand Down Expand Up @@ -125,8 +118,7 @@ def test_paranoia(shell):
assert footer['duplicates'] == 0


@with_setup(usual_setup_func, usual_teardown_func)
def test_anon_pipe():
def test_anon_pipe(usual_setup_usual_teardown):
create_file('xxx', 'long-dummy-file-1')
create_file('xxx', 'long-dummy-file-2')

Expand All @@ -141,9 +133,7 @@ def test_anon_pipe():
assert b'/long-dummy-file-2' in data


@parameterized([("sh", ), ("bash", ), ("dash", )])
@with_setup(usual_setup_func, usual_teardown_func)
def test_hardlink_duplicate_directories(shell):
def test_hardlink_duplicate_directories(usual_setup_usual_teardown, shell):
create_file('xxx', 'dir_a/x')
create_file('xxx', 'dir_b/x')

Expand Down Expand Up @@ -175,12 +165,8 @@ def _check_if_empty_dirs_deleted(shell, inverse_order, sh_path, data):
assert not os.path.exists(data[1]["path"])


@parameterized([
("sh", False), ("bash", False), ("dash", False),
("sh", True), ("bash", True), ("dash", True)
])
@with_setup(usual_setup_func, usual_teardown_func)
def test_remove_empty_dirs(shell, inverse_order):
@pytest.mark.parametrize("inverse_order", [False, True])
def test_remove_empty_dirs(usual_setup_usual_teardown, shell, inverse_order):
create_file('xxx', 'deep/a/b/c/d/e/1')
create_file('xxx', 'deep/x/2')

Expand Down Expand Up @@ -208,12 +194,8 @@ def test_remove_empty_dirs(shell, inverse_order):
_check_if_empty_dirs_deleted(shell, inverse_order, sh_path, data)


@parameterized([
("sh", False), ("bash", False), ("dash", False),
("sh", True), ("bash", True), ("dash", True)
])
@with_setup(usual_setup_func, usual_teardown_func)
def test_remove_empty_dirs_with_dupe_dirs(shell, inverse_order):
@pytest.mark.parametrize("inverse_order", [False, True])
def test_remove_empty_dirs_with_dupe_dirs(usual_setup_usual_teardown, shell, inverse_order):
create_file('xxx', 'deep/a/b/c/d/e/1')
create_file('xxx', 'deep/x/1')

Expand Down Expand Up @@ -241,9 +223,7 @@ def test_remove_empty_dirs_with_dupe_dirs(shell, inverse_order):

_check_if_empty_dirs_deleted(shell, inverse_order, sh_path, data)

@with_setup(usual_setup_func, usual_teardown_func)
@parameterized([("sh", ), ("bash", ), ("dash", )])
def test_cleanup_emptydirs(shell):
def test_cleanup_emptydirs(usual_setup_usual_teardown, shell):
create_file('xxx', 'dir1/a')

# create some ugly dir names
Expand Down Expand Up @@ -272,9 +252,7 @@ def test_cleanup_emptydirs(shell):



@with_setup(usual_setup_func, usual_teardown_func)
@parameterized([("sh", ), ("bash", ), ("dash", )])
def test_keep_parent_timestamps(shell):
def test_keep_parent_timestamps(usual_setup_usual_teardown, shell):
create_file('xxx', 'dir/a')
create_file('xxx', 'dir/b')

Expand All @@ -295,9 +273,8 @@ def test_keep_parent_timestamps(shell):


# regression test for GitHub issue #545
@parameterized.expand([('',), ('-D',)])
@with_setup(usual_setup_func, usual_teardown_func)
def test_skip_hardlinks(tm_opt):
@pytest.mark.parametrize("tm_opt", ('', '-D'))
def test_skip_hardlinks(usual_setup_usual_teardown, tm_opt):
dir_a = create_dirs('a')
create_file('xxx', 'a/1')
create_file('yyy', 'a/2')
Expand Down
Loading

0 comments on commit 79a1e6b

Please sign in to comment.