Skip to content

Commit 827cd9d

Browse files
authored
Add an empty entry to sys.path for basilisp test CLI subcommand (#1075)
Relates to #1069
1 parent a4e2128 commit 827cd9d

File tree

4 files changed

+65
-16
lines changed

4 files changed

+65
-16
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
* Added functions to `basilisp.test` for using and combining test fixtures (#980)
1010
* Added the `importing-resolve` function for dynamically importing and resolving a Python name (#1065, #1070)
1111
* Added support for highlighting matching parens, brackets, and braces at the REPL (#1074)
12+
* Added the `-p`/`--include-path` argument to `basilisp test` CLI subcommand (#1075)
13+
* Added an empty entry to `sys.path` for `basilisp test` CLI subcommand (#1075)
1214

1315
### Fixed
1416
* Fix a bug where the reader was double counting the CRLF newline seq in metadata (#1063)
1517
* Conform to the `cider-nrepl` `info` ops spec by ensuring result's `:file` is URI, also added missing :column number (#1066)
1618
* Fix a bug with `basilisp.edn/write-string` where nested double quotes were not escaped properly (#1071)
19+
* Fix a bug where additional arguments to `basilisp test` CLI subcommand were not being passed correctly to Pytest (#1075)
1720

1821
## [v0.2.3]
1922
### Added

docs/gettingstarted.rst

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,25 +57,27 @@ Sub or child namespaces may be nested using folders.
5757
A namespace may be both a "leaf" and "branch" node in the source tree without any special configuration, as ``myproject.pkg`` is below.
5858
Basilisp source files should always have a ``.lpy`` extension.
5959

60-
::
60+
.. code-block:: text
6161
6262
.
6363
├── README.md
6464
├── poetry.lock
6565
├── pyproject.toml
6666
├── src
67-
   └── myproject
68-
   ├── core.lpy
69-
   ── pkg
70-
   │   └── subns.lpy
71-
   └── pkg.lpy
67+
└── myproject
68+
├── core.lpy
69+
── pkg
70+
└── subns.lpy
71+
└── pkg.lpy
7272
└── tests
73+
└── __init__.py
7374
└── myproject
7475
└── test_core.lpy
7576
7677
.. note::
7778

78-
Python ``__init__.py`` files are not required anywhere in Basilisp projects (including for nested namespaces), though you may need to use them if your project mixes Python and Basilisp sources.
79+
Python ``__init__.py`` files are not required anywhere in Basilisp project source directories (including for nested namespaces), though you may need to use them if your project mixes Python and Basilisp sources.
80+
A single ``__init__.py`` file may be required for running tests with Pytest -- see :ref:`testing_path` for more information.
7981

8082
Basilisp apps can use any of Python's myriad dependency management options, including `pip <https://pip.pypa.io/en/stable/>`_, `Pipenv <https://pipenv.pypa.io/en/latest/>`_, and `Poetry <https://python-poetry.org/>`_.
8183
Basilisp itself uses Poetry and that is the recommended dependency management tool for new Basilisp projects.

docs/testing.rst

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,34 @@ For asserting repeatedly against different inputs, you can use the :lpy:fn:`are`
3636
4 2 2
3737
0 -1 1)
3838
39+
.. _testing_path:
40+
41+
Testing and ``PYTHONPATH``
42+
--------------------------
43+
44+
Typical Clojure projects will have parallel ``src/`` and ``tests/`` folders in the project root.
45+
Project management tooling typically constructs the Java classpath to include both parallel trees for development and only ``src/`` for deployed software.
46+
Basilisp does not currently have such tooling (though it is planned!) and the recommended Python tooling is not configurable to allow for this distinction.
47+
48+
Due to this limitation, the easiest solution to facilitate test discovery with Pytest (Basilisp's default test runner) is to include a single, empty ``__init__.py`` file in the top-level ``tests`` directory:
49+
50+
.. code-block:: text
51+
52+
tests
53+
├── __init__.py
54+
└── myproject
55+
└── core_test.lpy
56+
57+
Test namespaces can then be created as if they are part of a giant ``tests`` package:
58+
59+
.. code-block::
60+
61+
(ns tests.myproject.core-test)
62+
63+
.. note::
64+
65+
The project maintainers acknowledge that this is not an ideal solution and would like to provide a more Clojure-like solution in the future.
66+
3967
.. _test_fixtures:
4068

4169
Fixtures

src/basilisp/cli.py

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import textwrap
88
import types
99
from pathlib import Path
10-
from typing import Any, Callable, Optional, Sequence, Type
10+
from typing import Any, Callable, List, Optional, Sequence, Type, Union
1111

1212
from basilisp import main as basilisp
1313
from basilisp.lang import compiler as compiler
@@ -379,7 +379,10 @@ def _add_runtime_arg_group(parser: argparse.ArgumentParser) -> None:
379379
)
380380

381381

382-
Handler = Callable[[argparse.ArgumentParser, argparse.Namespace], None]
382+
Handler = Union[
383+
Callable[[argparse.ArgumentParser, argparse.Namespace], None],
384+
Callable[[argparse.ArgumentParser, argparse.Namespace, List[str]], None],
385+
]
383386

384387

385388
def _subcommand(
@@ -388,6 +391,7 @@ def _subcommand(
388391
help: Optional[str] = None, # pylint: disable=redefined-builtin
389392
description: Optional[str] = None,
390393
handler: Handler,
394+
allows_extra: bool = False,
391395
) -> Callable[
392396
[Callable[[argparse.ArgumentParser], None]],
393397
Callable[["argparse._SubParsersAction"], None],
@@ -400,6 +404,7 @@ def _wrapped_subcommand(subparsers: "argparse._SubParsersAction"):
400404
subcommand, help=help, description=description
401405
)
402406
parser.set_defaults(handler=handler)
407+
parser.set_defaults(allows_extra=allows_extra)
403408
f(parser)
404409

405410
return _wrapped_subcommand
@@ -722,8 +727,11 @@ def _add_run_subcommand(parser: argparse.ArgumentParser) -> None:
722727

723728

724729
def test(
725-
parser: argparse.ArgumentParser, args: argparse.Namespace
730+
parser: argparse.ArgumentParser,
731+
args: argparse.Namespace,
732+
extra: List[str],
726733
) -> None: # pragma: no cover
734+
init_path(args)
727735
basilisp.init(_compiler_opts(args))
728736
try:
729737
import pytest
@@ -732,18 +740,20 @@ def test(
732740
"Cannot run tests without dependency PyTest. Please install PyTest and try again.",
733741
)
734742
else:
735-
pytest.main(args=list(args.args))
743+
pytest.main(args=list(extra))
736744

737745

738746
@_subcommand(
739747
"test",
740748
help="run tests in a Basilisp project",
741749
description="Run tests in a Basilisp project.",
742750
handler=test,
751+
allows_extra=True,
743752
)
744753
def _add_test_subcommand(parser: argparse.ArgumentParser) -> None:
745-
parser.add_argument("args", nargs=-1)
754+
parser.add_argument("args", nargs=-1, help="arguments passed on to Pytest")
746755
_add_compiler_arg_group(parser)
756+
_add_import_arg_group(parser)
747757
_add_runtime_arg_group(parser)
748758
_add_debug_arg_group(parser)
749759

@@ -765,7 +775,7 @@ def run_script():
765775
This is provided as a shim for platforms where shebang lines cannot contain more
766776
than one argument and thus `#!/usr/bin/env basilisp run` would be non-functional.
767777
768-
The current process is replaced as by `os.execlp`."""
778+
The current process is replaced as by `os.execvp`."""
769779
# os.exec* functions do not perform shell expansion, so we must do so manually.
770780
script_path = Path(sys.argv[1]).resolve()
771781
args = ["basilisp", "run", str(script_path)]
@@ -790,9 +800,15 @@ def invoke_cli(args: Optional[Sequence[str]] = None) -> None:
790800
_add_test_subcommand(subparsers)
791801
_add_version_subcommand(subparsers)
792802

793-
parsed_args = parser.parse_args(args=args)
794-
if hasattr(parsed_args, "handler"):
795-
parsed_args.handler(parser, parsed_args)
803+
parsed_args, extra = parser.parse_known_args(args=args)
804+
allows_extra = getattr(parsed_args, "allows_extra", False)
805+
if extra and not allows_extra:
806+
parser.error(f"unrecognized arguments: {' '.join(extra)}")
807+
elif hasattr(parsed_args, "handler"):
808+
if allows_extra:
809+
parsed_args.handler(parser, parsed_args, extra)
810+
else:
811+
parsed_args.handler(parser, parsed_args)
796812
else:
797813
parser.print_help()
798814

0 commit comments

Comments
 (0)