Repository navigation
Description
Summary
--threshold accepts non-finite and out-of-range floats. nan admits even a pair with zero similarity; inf, 1e309, and 1.1 suppress even exact matches. This silently changes results instead of reporting invalid configuration.
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
from pathlib import Path
import sys
import types
sys.dont_write_bytecode = True
sys.path.insert(0, str(Path('src').resolve()))
def load(name, file, omit):
path = Path(file)
tree = ast.parse(path.read_text())
tree.body = [n for n in tree.body if not (isinstance(n, ast.ImportFrom) and n.module == omit)]
module = types.ModuleType(name)
sys.modules[name] = module
exec(compile(tree, str(path), 'exec'), module.__dict__)
return module
# Only parser-dependent imports are omitted; argument parsing and pair
# comparison below execute the unchanged source functions.
cli = load('dryer_cli_probe', 'src/dryer/cli.py', 'dryer.scan')
scan = load('dryer_scan_probe', 'src/dryer/scan.py', 'dryer.extract')
from dryer.model import Entry
left = Entry('python', 'a.py', 1, 4, 20, frozenset({'a'}))
disjoint = Entry('python', 'b.py', 1, 4, 20, frozenset({'b'}))
identical = Entry('python', 'c.py', 1, 4, 20, frozenset({'a'}))
for value in ('nan', 'inf', 'Infinity', '1e309', '1.1', ' -0.1', ' -inf'):
options = cli.parse_args(['--threshold', value])
assert options.action == 'scan', value
unrelated = scan.find_duplicates([left, disjoint], options.threshold)
exact = scan.find_duplicates([left, identical], options.threshold)
print(f'{value!r}: action={options.action}, threshold={options.threshold}, disjoint_pairs={len(unrelated)}, exact_pairs={len(exact)}')
expected = (1, 1) if value in ('nan', ' -0.1', ' -inf') else (0, 0)
assert (len(unrelated), len(exact)) == expected
for value in ('-0.1', '-inf'):
options = cli.parse_args(['--threshold', value])
print(f'{value!r}: action={options.action}, exit={options.exit_code}')
assert options.exit_code == 1
for value in ('0', '0.82', '1'):
assert cli.parse_args(['--threshold', value]).action == 'scan'
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
'nan': action=scan, threshold=nan, disjoint_pairs=1, exact_pairs=1
'inf': action=scan, threshold=inf, disjoint_pairs=0, exact_pairs=0
'Infinity': action=scan, threshold=inf, disjoint_pairs=0, exact_pairs=0
'1e309': action=scan, threshold=inf, disjoint_pairs=0, exact_pairs=0
'1.1': action=scan, threshold=1.1, disjoint_pairs=0, exact_pairs=0
' -0.1': action=scan, threshold=-0.1, disjoint_pairs=1, exact_pairs=1
' -inf': action=scan, threshold=-inf, disjoint_pairs=1, exact_pairs=1
'-0.1': action=help, exit=1
'-inf': action=help, exit=1
Expected result
Reject non-finite thresholds and thresholds outside the inclusive interval [0, 1] as usage errors before selection or scanning. Preserve valid endpoints 0 and 1.
Cause
_number() only calls float(), and argument parsing assigns that result without bounds validation. find_duplicates() skips pairs only when score < threshold; this comparison is false for NaN.
Bare -0.1 and -inf are already rejected by _take() because they begin with -. However, quoted values with leading whitespace (" -0.1", " -inf") pass _take() and are accepted by float(). The reproduction distinguishes these cases rather than claiming all negative spellings are accepted.
Suggested regression test
Add parameterized parser tests rejecting nan, NaN, inf, Infinity, 1e309, 1.1, and whitespace-prefixed negative values, with a nonzero usage status before scanning. Assert 0, 0.82, and 1 still parse successfully. The existing threshold test covers only a nonnumeric string.
Activity