Skip to content

Commit 5eb4170

Browse files
authored
Fix a bug where Basilisp would error running a namespace from the CLI (#960)
This PR does provide a solution to the immediate problem described in #957 which prevents running namespaces from the CLI in many cases, however it does not address the larger Clojure compatibility issue outlined in the ticket.
1 parent 816d8f8 commit 5eb4170

File tree

3 files changed

+67
-26
lines changed

3 files changed

+67
-26
lines changed

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
### Added
99
* Added several missing functions to `basilisp.core` (#956)
1010

11+
### Fixed
12+
* Fixed an issue where attempting to run a namespace from the CLI could fail in certain cases (#957)
13+
1114
## [v0.1.0]
1215
### Added
1316
* Added `:end-line` and `:end-col` metadata to forms during compilation (#903)
@@ -31,7 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3134
* Fix a bug where Basilisp vectors were not callable (#932)
3235
* Fix a bug where `basilisp.lang.seq.LazySeq` instances were not thread-safe (#934)
3336
* Fix a bug where Seqs wrapping Python Iterable instances were not thread-safe (#936)
34-
* Fix several bugs where code was being executed from a string with interpolated variables, which could've allowed for code (#938)
37+
* Fix several bugs where code was being executed from a string with interpolated variables, which could've allowed for code injection (#938)
3538
* Fix a bug where record types and data readers whose fully qualified name started with a "b" could not be read (#947)
3639

3740
### Other

src/basilisp/cli.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,6 @@ def eval_file(filename: str, ctx: compiler.CompilerContext, ns: runtime.Namespac
6060
raise FileNotFoundError(f"Error: The file {filename} does not exist.")
6161

6262

63-
def eval_namespace(
64-
namespace: str, ctx: compiler.CompilerContext, ns: runtime.Namespace
65-
):
66-
"""Evaluate a file with the given name into a Python module AST node."""
67-
path = "/" + "/".join(namespace.split("."))
68-
return compiler.load(path, ctx, ns)
69-
70-
7163
def bootstrap_repl(ctx: compiler.CompilerContext, which_ns: str) -> types.ModuleType:
7264
"""Bootstrap the REPL with a few useful vars and returned the bootstrapped
7365
module so it's functions can be used by the REPL command."""
@@ -499,7 +491,7 @@ def run(
499491
parser.error(
500492
"argument --in-ns: not allowed with argument -n/--load-namespace"
501493
)
502-
in_ns = target
494+
in_ns = runtime.REPL_DEFAULT_NS
503495
else:
504496
in_ns = target if args.in_ns is not None else runtime.REPL_DEFAULT_NS
505497

@@ -540,7 +532,7 @@ def run(
540532
assert main_ns_var is not None
541533
main_ns_var.bind_root(sym.symbol(target))
542534

543-
eval_namespace(target, ctx, ns)
535+
importlib.import_module(munge(target))
544536
elif target == STDIN_FILE_NAME:
545537
eval_stream(io.TextIOWrapper(sys.stdin.buffer, encoding="utf-8"), ctx, ns)
546538
else:

tests/basilisp/cli_test.py

Lines changed: 61 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import pathlib
44
import platform
55
import re
6+
import secrets
67
import stat
78
import subprocess
89
import tempfile
@@ -286,41 +287,86 @@ def test_run_file_with_args(
286287
assert ret == result.lisp_out
287288

288289
@pytest.fixture
289-
def namespace_file(self, monkeypatch, tmp_path: pathlib.Path) -> pathlib.Path:
290-
parent = tmp_path / "package"
290+
def namespace_name(self) -> str:
291+
return f"package.core{secrets.token_hex(4)}"
292+
293+
@pytest.fixture
294+
def namespace_file(
295+
self, monkeypatch, tmp_path: pathlib.Path, namespace_name: str
296+
) -> pathlib.Path:
297+
parent_ns, child_ns = namespace_name.split(".", maxsplit=1)
298+
parent = tmp_path / parent_ns
291299
parent.mkdir()
292-
nsfile = parent / "core.lpy"
300+
nsfile = parent / f"{child_ns}.lpy"
293301
nsfile.touch()
294302
monkeypatch.syspath_prepend(str(tmp_path))
295303
yield nsfile
296304

297305
def test_cannot_run_namespace_with_in_ns_arg(
298-
self, run_cli, namespace_file: pathlib.Path
306+
self, run_cli, namespace_name: str, namespace_file: pathlib.Path
299307
):
300308
namespace_file.write_text("(println (+ 1 2))")
301309
with pytest.raises(SystemExit):
302-
run_cli(["run", "--in-ns", "otherpackage.core", "-n", "package.core"])
310+
run_cli(["run", "--in-ns", "otherpackage.core", "-n", namespace_name])
303311

304-
def test_run_namespace(self, run_cli, namespace_file: pathlib.Path):
305-
namespace_file.write_text("(println (+ 1 2))")
306-
result = run_cli(["run", "-n", "package.core"])
312+
def test_run_namespace(
313+
self, run_cli, namespace_name: str, namespace_file: pathlib.Path
314+
):
315+
namespace_file.write_text(f"(ns {namespace_name}) (println (+ 1 2))")
316+
result = run_cli(["run", "-n", namespace_name])
307317
assert f"3{os.linesep}" == result.lisp_out
308318

309-
def test_run_namespace_main_ns(self, run_cli, namespace_file: pathlib.Path):
319+
def test_run_namespace_main_ns(
320+
self, run_cli, namespace_name: str, namespace_file: pathlib.Path
321+
):
310322
namespace_file.write_text(
311-
"(ns package.core) (println (name *ns*)) (println *main-ns*)"
323+
f"(ns {namespace_name}) (println (name *ns*)) (println *main-ns*)"
324+
)
325+
result = run_cli(["run", "-n", namespace_name])
326+
assert (
327+
f"{namespace_name}{os.linesep}{namespace_name}{os.linesep}"
328+
== result.lisp_out
312329
)
313-
result = run_cli(["run", "-n", "package.core"])
314-
assert f"package.core{os.linesep}package.core{os.linesep}" == result.lisp_out
315330

316331
@pytest.mark.parametrize("args,ret", cli_args_params)
317332
def test_run_namespace_with_args(
318-
self, run_cli, namespace_file: pathlib.Path, args: List[str], ret: str
333+
self,
334+
run_cli,
335+
namespace_name: str,
336+
namespace_file: pathlib.Path,
337+
args: List[str],
338+
ret: str,
319339
):
320-
namespace_file.write_text(self.cli_args_code)
321-
result = run_cli(["run", "-n", "package.core", *args])
340+
namespace_file.write_text(f"(ns {namespace_name}) {self.cli_args_code}")
341+
result = run_cli(["run", "-n", namespace_name, *args])
322342
assert ret == result.lisp_out
323343

344+
def test_run_namespace_with_subnamespace(
345+
self, run_cli, monkeypatch, tmp_path: pathlib.Path
346+
):
347+
ns_name = f"parent{secrets.token_hex(4)}"
348+
child = "child"
349+
350+
parent_ns_dir = tmp_path / ns_name
351+
parent_ns_dir.mkdir()
352+
353+
parent_ns_file = tmp_path / f"{ns_name}.lpy"
354+
parent_ns_file.touch()
355+
parent_ns_file.write_text(
356+
f'(ns {ns_name} (:require [{ns_name}.{child}])) (python/print "loading:" *ns*)'
357+
)
358+
359+
child_ns_file = parent_ns_dir / f"{child}.lpy"
360+
child_ns_file.touch()
361+
child_ns_file.write_text(
362+
f'(ns {ns_name}.{child}) (python/print "loading:" *ns*)'
363+
)
364+
365+
monkeypatch.syspath_prepend(str(tmp_path))
366+
367+
result = run_cli(["run", "-n", ns_name])
368+
assert f"loading: {ns_name}.{child}\nloading: {ns_name}\n" == result.out
369+
324370
def test_run_stdin(self, run_cli):
325371
result = run_cli(["run", "-"], input="(println (+ 1 2))")
326372
assert f"3{os.linesep}" == result.lisp_out

0 commit comments

Comments
 (0)