From e0bf241e55d58dbd285cd7b28289c01204042b0a Mon Sep 17 00:00:00 2001 From: yannrichet Date: Sat, 13 Jun 2026 23:17:11 +0200 Subject: [PATCH] fix(cli): fzd and `fz design` passed args core fzd() rejects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fzd_main() and the `fz design` subcommand called core fzd() with `results_dir=` and the algorithm options spread as `**kwargs`, but fzd() accepts `analysis_dir` and `algorithm_options`. Every CLI invocation therefore failed immediately with: TypeError: fzd() got an unexpected keyword argument 'results_dir' Map both call sites to the correct parameters (results_dir -> analysis_dir, options dict -> algorithm_options=). The break shipped because fzd CLI execution had no test coverage — test_fzd.py exercised only the Python API. Add two regression tests (the standalone `fzd` entry point and the `fz design` subcommand) that run a real design with --results_dir and --options; verified they fail without the fix with the exact TypeError above. Co-Authored-By: Claude Fable 5 --- NEWS.md | 10 +++++++++ fz/cli.py | 8 +++---- tests/test_fzd.py | 56 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 4 deletions(-) diff --git a/NEWS.md b/NEWS.md index 4d88115..fed5fa7 100644 --- a/NEWS.md +++ b/NEWS.md @@ -24,6 +24,16 @@ - `fzd` docstring corrected: `analysis_dir` defaults to `"analysis"` (the CLI uses `results_fzd`). +### fzd CLI execution fix + +- The `fzd` command and the `fz design` subcommand were unusable: both passed + `results_dir=` and the algorithm options as `**kwargs` to core `fzd()`, which accepts + `analysis_dir` and `algorithm_options` instead — so every invocation failed immediately + with `TypeError: fzd() got an unexpected keyword argument 'results_dir'`. Both call + sites now map to the correct parameters. Added regression tests that exercise the `fzd` + CLI and the `fz design` subcommand (only the Python API was covered before, which is why + this shipped). + ### CLI hardening for scripting and AI agents - Log messages (`FZ_LOG_LEVEL`) and progress output now go to **stderr** instead of diff --git a/fz/cli.py b/fz/cli.py index 93c6910..f0e5602 100644 --- a/fz/cli.py +++ b/fz/cli.py @@ -672,9 +672,9 @@ def fzd_main(): model, args.output_expression, args.algorithm, - results_dir=args.results_dir, calculators=calculators, - **(algo_options if isinstance(algo_options, dict) else {}) + algorithm_options=(algo_options if isinstance(algo_options, dict) else {}), + analysis_dir=args.results_dir, ) # Print summary @@ -853,9 +853,9 @@ def main(): model, args.output_expression, args.algorithm, - results_dir=args.results_dir, calculators=calculators, - **algo_options + algorithm_options=algo_options, + analysis_dir=args.results_dir, ) # Print summary diff --git a/tests/test_fzd.py b/tests/test_fzd.py index 5d875bf..e30b338 100644 --- a/tests/test_fzd.py +++ b/tests/test_fzd.py @@ -2,7 +2,9 @@ Tests for fzd (iterative design of experiments with algorithms) """ +import json import os +import subprocess import sys import tempfile import shutil @@ -148,6 +150,60 @@ def test_fzd_randomsampling(self, simple_model): assert "result" in result["XY"].columns # output_expression as column name assert algo_path in result["algorithm"] # algorithm field contains the path + def test_fzd_cli_runs(self, simple_model, monkeypatch): + """Regression: the `fzd` CLI must actually run a design. + + fzd_main() called core fzd() with results_dir=/**options, but fzd() + accepts analysis_dir/algorithm_options — so every invocation raised + TypeError ('unexpected keyword argument results_dir'). No fzd CLI + execution test existed, so the break shipped. This covers entry point 1 + (the standalone `fzd` command, fz.cli:fzd_main). + """ + if shutil.which("bc") is None: + pytest.skip("bc command not available") + + input_dir, model = simple_model + algo_path = str(Path(__file__).parent.parent / "examples" / "algorithms" / "randomsampling.py") + analysis_dir = Path(input_dir).parent / "fzd_cli_out" + + from fz import cli + monkeypatch.setattr(sys, "argv", [ + "fzd", + "-i", str(input_dir), + "-m", json.dumps(model), + "-v", json.dumps({"x": "[0;1]", "y": "[0;1]"}), + "-e", "result", + "-a", algo_path, + "-o", json.dumps({"nvalues": 3, "seed": 42}), # exercises --options + "-r", str(analysis_dir), # exercises --results_dir + ]) + rc = cli.fzd_main() + assert rc == 0 + + def test_fz_design_cli_runs(self, simple_model): + """Regression: the `fz design` subcommand must run too (entry point 2). + + Same bug as test_fzd_cli_runs, separate call site in fz.cli:main. + """ + if shutil.which("bc") is None: + pytest.skip("bc command not available") + + input_dir, model = simple_model + algo_path = str(Path(__file__).parent.parent / "examples" / "algorithms" / "randomsampling.py") + analysis_dir = Path(input_dir).parent / "design_cli_out" + + result = subprocess.run([ + sys.executable, "-m", "fz.cli", "design", + "-i", str(input_dir), + "-m", json.dumps(model), + "-v", json.dumps({"x": "[0;1]", "y": "[0;1]"}), + "-e", "result", + "-a", algo_path, + "-o", json.dumps({"nvalues": 3, "seed": 42}), + "-r", str(analysis_dir), + ], capture_output=True, text=True) + assert result.returncode == 0, f"stdout:\n{result.stdout}\nstderr:\n{result.stderr}" + # Removed test_fzd_requires_pandas - pandas is now a required dependency def test_fzd_returns_dataframe(self, simple_model):