Repository navigation
Description
Summary
When selection contains no source files, run() returns success before replacing .metrics/dry.edn or applying the requested output format. A previous report remains on disk, while both --edn and --format edn print a plain-text sentence instead of the documented EDN map.
Verified against master at 66ff6d21a42c04afcad89c78a80066176d1294b0 (dryer 0.1.0), using Python 3.14.8.
Minimal reproduction
Requires Git and Python >=3.11. No package installation, parser downloads, or native dependencies are needed. Run in a fresh disposable checkout:
work=$(mktemp -d)
git clone https://github.com/unclebob/dryer.git "$work/dryer"
cd "$work/dryer"
git checkout 66ff6d21a42c04afcad89c78a80066176d1294b0
python3 -B - <<'PY'
"""Run from a dryer checkout with Python >=3.11; standard library only."""
import ast
import contextlib
import io
from pathlib import Path
import sys
import tempfile
import types
sys.dont_write_bytecode = True
sys.path.insert(0, str(Path('src').resolve()))
# Load the real CLI without its parser-dependent scan import. Empty selection
# must return before either omitted scan function can be called.
path = Path('src/dryer/cli.py')
tree = ast.parse(path.read_text())
tree.body = [n for n in tree.body if not (isinstance(n, ast.ImportFrom) and n.module == 'dryer.scan')]
cli = types.ModuleType('dryer_cli_probe')
sys.modules[cli.__name__] = cli
exec(compile(tree, str(path), 'exec'), cli.__dict__)
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
metrics = root / '.metrics/dry.edn'
metrics.parent.mkdir()
old = '{:candidates [{:score 1.0 :language "python" :left {:file "old.py" :start-line 1 :end-line 4} :right {:file "old2.py" :start-line 1 :end-line 4} :left-nodes 20 :right-nodes 20}]}\n'
for flag in ('--edn', '--format'):
metrics.write_text(old)
output = io.StringIO()
args = ['--root', directory, flag] + (['edn'] if flag == '--format' else [])
with contextlib.redirect_stdout(output):
code = cli.run(args)
print(f'{args[2:]}: exit={code}, stdout={output.getvalue()!r}, stale_metrics={metrics.read_text() == old}')
assert code == 0
assert output.getvalue() == 'No source files to analyze.\n'
assert metrics.read_text() == old
PYThis source-level probe executes the original functions through Python AST loading, omitting only parser-dependent imports. It does not replace any exercised function or run the installed CLI/parser end to end. Its assertions verify the observed defect, not the expected fixed behavior.
Actual result
['--edn']: exit=0, stdout='No source files to analyze.\n', stale_metrics=True
['--format', 'edn']: exit=0, stdout='No source files to analyze.\n', stale_metrics=True
Expected result
On an empty selection, write a fresh empty report and honor EDN output, or fail explicitly with a documented no-analysis status that cannot be mistaken for a successfully written report. The help currently defines exit 0 as “the report was written.”
Cause
The early return in run() precedes metrics writing and EDN rendering. render_edn([]) already supports an empty result.
Suggested regression test
Seed .metrics/dry.edn with a valid nonempty previous report, select zero files, and invoke both EDN spellings. Assert the selected empty-result contract: for a successful run, stdout and metrics contain the fresh {:candidates []} map, not old findings. Also cover a selection filter that matches nothing. The existing empty-project test checks only text stdout, not stale metrics or EDN.
Activity