Skip to content

Commit

Permalink
Several bug fixes (#241)
Browse files Browse the repository at this point in the history
* Fix excludes, make it support directories instead of globs

* Make paths in file config relative to that files directory

* Add test to ensure that paths in config file are relative to config files directory

* Ensure defaults are set correctly, build relative paths correctly

* Resolve exclude and path args to match them correctly, update tests

* Attempt to fix Windows-specific test

* Another attempt to fix Window test
  • Loading branch information
darrenburns committed May 30, 2021
1 parent a1c0f33 commit c01a457
Show file tree
Hide file tree
Showing 24 changed files with 280 additions and 105 deletions.
17 changes: 11 additions & 6 deletions docs/source/guide/running_tests.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Running Tests via the CLI

To find and run tests in your project, you can run ``ward`` without any arguments.

This will recursively search through the current directory for modules with a name starting with ``test_`` or ending with ``_test``,
This will recursively search through the project for modules with a name starting with ``test_`` or ending with ``_test``,
and execute any tests contained in the modules it finds.

Test outcomes
Expand All @@ -28,9 +28,14 @@ run completes or is cancelled.
Specifying test paths with ``--path``
-------------------------------------

You can run tests in a specific directory or module using the ``--path`` option. For example, to run all tests inside a directory named ``tests``: ``ward --path tests``
You can run tests in a specific directory or module using the ``--path`` option. For example, to run all tests inside a directory named ``tests``: ``ward --path tests``.

To run tests in the current directory, you can just type ``ward``, which is functionally equivalent to ``ward --path .``.
To run all the tests in your project, you can just type ``ward`` from anywhere inside your project.

Ward considers your project to be the directory containing your ``pyproject.toml`` config file and all directories within. If you don't have a ``pyproject.toml`` file, then
Ward will look for a ``.git`` or ``.hg`` folder/file and consider that as your project root.

If Ward cannot find a project root, the running ``ward`` without a ``--path`` is equivalent to running ``ward --path .``.

You can directly specify a test module, for example: ``ward --path tests/api/test_get_user.py``.

Expand All @@ -41,16 +46,16 @@ Ward will run all tests it finds across all given paths. If one of the specified
Excluding modules or paths with ``--exclude``
---------------------------------------------

``ward --exclude glob1 --exclude glob2``
You can tell Ward to ignore specific modules or directories using the ``--exclude`` command line option. For example:

You can tell Ward to ignore specific modules or directories using the ``--exclude`` command line option. This option can be supplied multiple times, and supports glob patterns.
``ward --exclude path/to/dir1 --exclude path/to/dir2``

You can also exclude paths using ``pyproject.toml``:

.. code-block:: toml
[tool.ward]
exclude = ["glob1", "glob2"]
exclude = ["tests/resources", "tests/utilities.py"]
Selecting tagged tests with ``--tags``
--------------------------------------
Expand Down
4 changes: 2 additions & 2 deletions docs/source/guide/writing_tests.rst
Original file line number Diff line number Diff line change
Expand Up @@ -310,14 +310,14 @@ Here's an example of a test that is skipped on Windows:
@skip("Skipped on Windows", when=platform.system() == "Windows")
@test("_build_package_name constructs package name '{pkg}' from '{path}'")
@test("_build_package_data constructs package name '{pkg}' from '{path}'")
def _(
pkg=each("", "foo", "foo.bar"),
path=each("foo.py", "foo/bar.py", "foo/bar/baz.py"),
):
m = ModuleType(name="")
m.__file__ = path
assert _build_package_name(m) == pkg
assert _build_package_data(m) == pkg
.. image:: ../_static/conditional_skip.png
:align: center
Expand Down
26 changes: 13 additions & 13 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ build-backend = "poetry.masonry.api"

[tool.ward]
path = ["tests"]
exclude = ["tests/resources/"]

[tool.poetry]
name = "ward"
Expand Down
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ exclude =
.git,
__pycache__,
docs/source/conf.py
tests/resources/**/*.py
per-file-ignores =
# WPS421: it is possible to have prints in scripts
ward/collect.py: C901
Expand Down
12 changes: 12 additions & 0 deletions tests/resources/sample_test_dirs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# What is this directory?

This directory exists for testing purposes. It contains subdirectories that we can run Ward on.

Any tests found in these directories are explicitly excluded from the main Ward suite via the
`exclude` config option in `pyproject.toml`.

These directories may be used in the unit testing suite, or could be used as
part of the end-to-end testing process to ensure Ward runs as expected on unusual scenarios
(e.g. deeply nested directory structures, unusual import patterns, etc.)

It may contain multiple example test directories of different structures.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from ward import test


@test("another example test")
def _():
assert True
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from ward import test


@test("another example test")
def _():
assert True
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from ward import test


@test("one is equal to itself")
def _():
assert 1 == 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Ensure that if we import a test from another test module
# that we don't run the tests in that module two times!
from test_another_example import *


@test("two is equal to itself")
def _():
assert 2 == 2
2 changes: 2 additions & 0 deletions tests/resources/sample_test_dirs/deeply_nested/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[tool.ward]
path = ["my_tests"]
103 changes: 70 additions & 33 deletions tests/test_collect.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import platform
import sys
from dataclasses import dataclass
from modulefinder import ModuleFinder
from pathlib import Path
from pkgutil import ModuleInfo
from types import ModuleType
from unittest import mock

from cucumber_tag_expressions import parse

from tests.utilities import make_project
from ward import fixture, test
from ward._collect import (
_build_package_name,
PackageData,
_build_package_data,
_get_module_path,
_handled_within,
_is_excluded_module,
Expand Down Expand Up @@ -207,23 +208,53 @@ def _(mod=test_module):
@test("is_excluded_module({mod.name}) is True for {excludes}")
def _(
mod=test_module,
excludes=each(
"*", "*/**.py", str(PATH), "**/test_mod.py", "path/to/*", "path/*/*.py"
),
excludes=str(PATH),
):
assert _is_excluded_module(mod, [excludes])


@test("is_excluded_module({mod.name}) is False for {excludes}")
def _(mod=test_module, excludes=each("abc", str(PATH.parent))):
assert not _is_excluded_module(mod, [excludes])
def _(mod=test_module, excludes=each("abc", "/path/to", "/path")):
assert not _is_excluded_module(mod, exclusions=[excludes])


@test("remove_excluded_paths removes exclusions from list of paths")
def _():
paths = [Path("/a/b/c.py"), Path("/a/b/")]
excludes = ["**/*.py"]
assert _remove_excluded_paths(paths, excludes) == [paths[1]]
@fixture
def paths_to_py_files():
return [
Path("/a/b/c.py"),
Path("/a/b/d/e.py"),
Path("/a/b/d/f/g/h.py"),
]


for path_to_exclude in ["/a", "/a/", "/a/b", "/a/b/"]:

@test("remove_excluded_paths removes {exclude} from list of paths")
def _(exclude=path_to_exclude, paths=paths_to_py_files):
assert _remove_excluded_paths(paths, [exclude]) == []


@test(
"remove_excluded_paths removes correct files when exclusions relative to each other"
)
def _(paths=paths_to_py_files):
assert _remove_excluded_paths(paths, ["/a/b/d", "/a/b/d/", "/a/b/d/f"]) == [
Path("/a/b/c.py")
]


@test("remove_excluded_paths removes individually specified files")
def _(paths=paths_to_py_files):
assert _remove_excluded_paths(paths, ["/a/b/d/e.py", "/a/b/d/f/g/h.py"]) == [
Path("/a/b/c.py")
]


@test("remove_excluded_paths can remove mixture of files and dirs")
def _(paths=paths_to_py_files):
assert _remove_excluded_paths(paths, ["/a/b/d/e.py", "/a/b/d/f/g/"]) == [
Path("/a/b/c.py")
]


@fixture
Expand All @@ -249,31 +280,37 @@ def _(
assert not _handled_within(module_path, [root / search])


@test("test modules and mro chain are added to sys.modules")
@skip("Skipped on Windows", when=platform.system() == "Windows")
@test("_build_package_data constructs correct package data")
def _():
class Abc:
x: int

for base in reversed(Abc.__mro__):
assert base.__module__ in sys.modules
from ward._collect import Path as ImportedPath


@skip("Skipped on Windows", when=platform.system() == "Windows")
@test("_build_package_name constructs package name '{pkg}' from '{path}'")
def _(
pkg=each("", "foo", "foo.bar"), path=each("foo.py", "foo/bar.py", "foo/bar/baz.py")
):
m = ModuleType(name="")
m.__file__ = path
assert _build_package_name(m) == pkg
m.__file__ = "/foo/bar/baz/test_something.py"
patch_is_dir = mock.patch.object(ImportedPath, "is_dir", return_value=True)
# The intention of the side_effects below is to make `baz` and `bar` directories
# contain __init__.py files. It's not clean, but it does test the behaviour well.
patch_exists = mock.patch.object(
ImportedPath, "exists", side_effect=[True, True, False]
)
with patch_is_dir, patch_exists:
assert _build_package_data(m) == PackageData(
pkg_name="bar.baz", pkg_root=Path("/foo")
)


@skip("Skipped on Unix", when=platform.system() != "Windows")
@test("_build_package_name constructs package name '{pkg}' from '{path}'")
def _(
pkg=each("", "foo", "foo.bar"),
path=each("foo.py", "foo\\bar.py", "foo\\bar\\baz.py"),
):
@test("_build_package_data constructs package name '{pkg}' from '{path}'")
def _():
from ward._collect import Path as ImportedPath

m = ModuleType(name="")
m.__file__ = path
assert _build_package_name(m) == pkg
m.__file__ = "\\foo\\bar\\baz\\test_something.py"
patch_is_dir = mock.patch.object(ImportedPath, "is_dir", return_value=True)
patch_exists = mock.patch.object(
ImportedPath, "exists", side_effect=[True, True, False]
)
with patch_is_dir, patch_exists:
pkg_data = _build_package_data(m)
assert pkg_data.pkg_name == "bar.baz"
assert str(pkg_data.pkg_root).endswith("foo")

0 comments on commit c01a457

Please sign in to comment.