Skip to content

Commit 14f1567

Browse files
committed
fix(test): distinguish an unknown test name from an unknown file
Review feedback on #6061. A `path::test_name` selector that fails because the file is not a test file and one that fails because the file has no such test both reported "is not a known model or test file", which points at the wrong thing in the second case. The two are now told apart: if the path resolves to a known test file, the error names the test and the file it looked in. Otherwise the message is unchanged. Also adds Python model coverage for path selection, both in isolation and combined with --local, since selection is by file path and there was nothing pinning that .py behaves the same as .sql. Separately, renames a loop variable in _select_tests_by_test_path. It shadowed a str binding from the branch above with a Path, which mypy rejects; it was pre-existing on this branch rather than introduced here. Signed-off-by: Adegbite Ayoade <tripleaceme@gmail.com>
1 parent ab4a951 commit 14f1567

3 files changed

Lines changed: 123 additions & 5 deletions

File tree

‎sqlmesh/core/context.py‎

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3659,13 +3659,29 @@ def _select_tests_by_test_path(self, selector: str) -> t.Optional[t.List[ModelTe
36593659
)
36603660
return [metadata] if metadata is not None else None
36613661

3662-
for path in (Path(selector), Path(os.path.abspath(selector))):
3663-
matched = self._model_test_metadata_path_index.get(path)
3662+
for candidate in (Path(selector), Path(os.path.abspath(selector))):
3663+
matched = self._model_test_metadata_path_index.get(candidate)
36643664
if matched is not None:
36653665
return list(matched)
36663666

36673667
return None
36683668

3669+
def _unknown_test_selector_error(self, selector: str) -> str:
3670+
"""Explains why a selector matched nothing.
3671+
3672+
A `path::test_name` whose file is a known test file failed on the test name, not the
3673+
path, so the message says so rather than claiming the file is unknown.
3674+
"""
3675+
if "::" in selector:
3676+
path, _, _ = selector.rpartition("::")
3677+
if any(
3678+
candidate in self._model_test_metadata_path_index
3679+
for candidate in (Path(path), Path(os.path.abspath(path)))
3680+
):
3681+
return f"'{selector}' is not a known test in '{path}'."
3682+
3683+
return f"'{selector}' is not a known model or test file."
3684+
36693685
def select_tests(
36703686
self,
36713687
tests: t.Optional[t.List[str]] = None,
@@ -3704,7 +3720,7 @@ def select_tests(
37043720
matched = tests_by_model_path.get(os.path.abspath(test))
37053721
if matched is None:
37063722
if raise_on_unknown_paths:
3707-
raise SQLMeshError(f"'{test}' is not a known model or test file.")
3723+
raise SQLMeshError(self._unknown_test_selector_error(test))
37083724
continue
37093725
filtered_tests.extend(matched)
37103726

‎tests/cli/test_cli.py‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2820,3 +2820,54 @@ def test_test_local_with_model_paths(runner: CliRunner, tmp_path: Path, mocker)
28202820
assert result.exit_code != 0
28212821
assert "is not a known model or test file" in result.output
28222822
mock.assert_not_called()
2823+
2824+
2825+
def test_test_local_with_python_model_paths(runner: CliRunner, tmp_path: Path, mocker) -> None:
2826+
"""The `--local` + path-selector combination works for Python models too."""
2827+
create_example_project(tmp_path)
2828+
2829+
(tmp_path / "models" / "py_model.py").write_text(
2830+
"""
2831+
import pandas as pd # noqa: TID253
2832+
from sqlmesh import model, ExecutionContext
2833+
import typing as t
2834+
2835+
@model(
2836+
name="sqlmesh_example.py_model",
2837+
columns={"id": "int"},
2838+
)
2839+
def execute(context: ExecutionContext, **kwargs: t.Any) -> pd.DataFrame:
2840+
return pd.DataFrame([{"id": 1}])
2841+
""",
2842+
encoding="utf-8",
2843+
)
2844+
(tmp_path / "tests" / "test_py_model.yaml").write_text(
2845+
"""
2846+
test_py_model:
2847+
model: sqlmesh_example.py_model
2848+
outputs:
2849+
query:
2850+
rows:
2851+
- id: 1
2852+
""",
2853+
encoding="utf-8",
2854+
)
2855+
2856+
mock = _patch_state_access(mocker)
2857+
2858+
result = runner.invoke(
2859+
cli,
2860+
["--paths", str(tmp_path), "test", "--local", str(tmp_path / "models" / "py_model.py")],
2861+
)
2862+
2863+
assert result.exit_code == 0, f"Test failed: {result.output}\nException: {result.exception}"
2864+
assert "Successfully Ran 1 tests" in " ".join(result.output.split())
2865+
mock.assert_not_called()
2866+
2867+
# A Python file that is not a model is still an error rather than a silent no-op.
2868+
result = runner.invoke(
2869+
cli, ["--paths", str(tmp_path), "test", "--local", str(tmp_path / "models" / "nope.py")]
2870+
)
2871+
assert result.exit_code != 0
2872+
assert "is not a known model or test file" in result.output
2873+
mock.assert_not_called()

‎tests/core/test_test.py‎

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import datetime
4+
import re
45
import typing as t
56
import io
67
from pathlib import Path
@@ -2697,6 +2698,46 @@ def test_model_path_selects_its_tests(tmp_path: Path) -> None:
26972698
assert results.testsRun == 1
26982699

26992700

2701+
def test_python_model_path_selects_its_tests(tmp_path: Path) -> None:
2702+
"""Selection is by file path, so a Python model works the same way a SQL one does."""
2703+
init_example_project(tmp_path, engine_type="duckdb")
2704+
2705+
py_model = tmp_path / "models" / "py_model.py"
2706+
py_model.write_text(
2707+
"""
2708+
import pandas as pd # noqa: TID253
2709+
from sqlmesh import model, ExecutionContext
2710+
import typing as t
2711+
2712+
@model(
2713+
name="sqlmesh_example.py_model",
2714+
columns={"id": "int"},
2715+
)
2716+
def execute(context: ExecutionContext, **kwargs: t.Any) -> pd.DataFrame:
2717+
return pd.DataFrame([{"id": 1}])
2718+
"""
2719+
)
2720+
(tmp_path / "tests" / "test_py_model.yaml").write_text(
2721+
"""
2722+
test_py_model:
2723+
model: sqlmesh_example.py_model
2724+
outputs:
2725+
query:
2726+
rows:
2727+
- id: 1
2728+
"""
2729+
)
2730+
2731+
context = Context(paths=tmp_path)
2732+
2733+
results = context.test(tests=[str(py_model)])
2734+
assert results.testsRun == 1
2735+
assert len(results.successes) == 1
2736+
2737+
# The SQL model's own test is not pulled in by selecting the Python model.
2738+
assert context.test(tests=[str(tmp_path / "models" / "full_model.sql")]).testsRun == 1
2739+
2740+
27002741
def test_model_path_without_tests_selects_nothing(tmp_path: Path) -> None:
27012742
"""A known model that simply has no tests is not an error."""
27022743
init_example_project(tmp_path, engine_type="duckdb")
@@ -2763,15 +2804,25 @@ def test_unknown_path_errors_when_requested(tmp_path: Path) -> None:
27632804

27642805

27652806
def test_unknown_test_name_errors_when_requested(tmp_path: Path) -> None:
2766-
"""A known YAML file with an unknown `::test_name` is just as wrong as a bad path."""
2807+
"""A known YAML file with an unknown `::test_name` reports the test, not the file."""
27672808
init_example_project(tmp_path, engine_type="duckdb")
27682809
context = Context(paths=tmp_path)
27692810

27702811
test_path = tmp_path / "tests" / "test_full_model.yaml"
2771-
with pytest.raises(SQLMeshError, match="is not a known model or test file"):
2812+
with pytest.raises(SQLMeshError, match=f"is not a known test in '{re.escape(str(test_path))}'"):
27722813
context.select_tests(tests=[f"{test_path}::nope"], raise_on_unknown_paths=True)
27732814

27742815

2816+
def test_unknown_test_name_in_unknown_file_reports_the_file(tmp_path: Path) -> None:
2817+
"""A `::test_name` on a file that isn't a test file is a path problem, not a name one."""
2818+
init_example_project(tmp_path, engine_type="duckdb")
2819+
context = Context(paths=tmp_path)
2820+
2821+
missing = tmp_path / "tests" / "test_nope.yaml"
2822+
with pytest.raises(SQLMeshError, match="is not a known model or test file"):
2823+
context.select_tests(tests=[f"{missing}::nope"], raise_on_unknown_paths=True)
2824+
2825+
27752826
def test_select_model_still_filters_path_selection(tmp_path: Path) -> None:
27762827
"""`--select-model` keeps narrowing the selection rather than adding to it."""
27772828
init_example_project(tmp_path, engine_type="duckdb")

0 commit comments

Comments
 (0)