diff --git a/src/itzi/itzi.py b/src/itzi/itzi.py index 6a2080b..51979ef 100644 --- a/src/itzi/itzi.py +++ b/src/itzi/itzi.py @@ -33,7 +33,7 @@ from datetime import datetime, timedelta from importlib.metadata import version from multiprocessing import Process -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable import numpy as np @@ -41,7 +41,7 @@ import itzi.itzi_error as itzi_error import itzi.messenger as msgr from itzi.const import VerbosityLevel -from itzi import parser +from itzi.parser import build_parser from itzi.profiler import profile_context from itzi.simulation_builder import SimulationBuilder from itzi.grass_session import GrassSessionManager @@ -52,16 +52,17 @@ from itzi.simulation import Simulation -def main(): - # default functions for subparsers - parser.run_parser.set_defaults(func=itzi_run) - parser.version_parser.set_defaults(func=itzi_version) - # get parsed arguments - args = parser.arg_parser.parse_args() - try: - args.func(args) - except AttributeError: - parser.arg_parser.print_usage() +def main(argv=None): + """argv: alternative CLI arguments, used for testing (default to sys.argv)""" + args = build_parser().parse_args(argv) + + command_mapper: dict[str, Callable] = { + "run": itzi_run, + "version": itzi_version, + } + + # args.command is the name of the subcommand + command_mapper[args.command](args) class SimulationRunner: diff --git a/src/itzi/parser.py b/src/itzi/parser.py index f24c1e8..cb63aa8 100644 --- a/src/itzi/parser.py +++ b/src/itzi/parser.py @@ -5,21 +5,24 @@ DESCR = "A dynamic, fully distributed hydraulic and hydrologic model." -arg_parser = argparse.ArgumentParser(description=DESCR) -subparsers = arg_parser.add_subparsers() - -# run a simulation -run_parser = subparsers.add_parser("run", help="run a simulation") -run_parser.add_argument( - "config_file", - nargs="+", - help=("an Itzï configuration file (if several given, run in batch mode)"), -) -run_parser.add_argument("-o", action="store_true", help="overwrite files if exist") -verbosity_parser = run_parser.add_mutually_exclusive_group() -verbosity_parser.add_argument("-v", action="count", help="increase verbosity") -verbosity_parser.add_argument("-q", action="count", help="decrease verbosity") - - -# display version -version_parser = subparsers.add_parser("version", help="display software version number") + +def build_parser() -> argparse.ArgumentParser: + arg_parser = argparse.ArgumentParser(description=DESCR) + subparsers = arg_parser.add_subparsers(dest="command", required=True) + + # run a simulation + run_parser = subparsers.add_parser("run", help="run a simulation") + run_parser.add_argument( + "config_file", + nargs="+", + help=("an Itzï configuration file (if several given, run in batch mode)"), + ) + run_parser.add_argument("-o", action="store_true", help="overwrite files if exist") + verbosity_parser = run_parser.add_mutually_exclusive_group() + verbosity_parser.add_argument("-v", action="count", help="increase verbosity") + verbosity_parser.add_argument("-q", action="count", help="decrease verbosity") + + # display version + subparsers.add_parser("version", help="display software version number") + + return arg_parser diff --git a/tests/cli/test_cli.py b/tests/cli/test_cli.py new file mode 100644 index 0000000..c421bb0 --- /dev/null +++ b/tests/cli/test_cli.py @@ -0,0 +1,52 @@ +"""Test the CLI""" + +import argparse +import os + +import pytest + +from itzi.const import VerbosityLevel +from itzi.itzi import main, itzi_run +from itzi.parser import build_parser + + +def test_run_parser_accepts_multiple_config_files(): + args = build_parser().parse_args(["run", "a.ini", "b.ini", "-o", "-vv"]) + assert args.config_file == ["a.ini", "b.ini"] + assert args.o is True + assert args.v == 2 + assert args.q is None + + +def test_run_parser_rejects_v_and_q_together(): + with pytest.raises(SystemExit): + build_parser().parse_args(["run", "a.ini", "-v", "-q"]) + + +def test_prints_version(monkeypatch, capsys): + monkeypatch.setattr("itzi.itzi.version", lambda _: "22.2") + assert main(["version"]) is None + assert capsys.readouterr().out.strip() == "22.2" + + +def test_itzi_run_sets_env_and_dispatches(monkeypatch): + calls = [] + messages = [] + + monkeypatch.setattr("itzi.itzi.itzi_run_one", calls.append) + monkeypatch.setattr("itzi.itzi.msgr.message", messages.append) + + args = argparse.Namespace( + config_file=["a.ini", "b.ini"], + o=True, + v=1, + q=None, + ) + + itzi_run(args) + + assert calls == ["a.ini", "b.ini"] + assert os.environ["GRASS_OVERWRITE"] == "1" + assert os.environ["ITZI_VERBOSE"] == str(VerbosityLevel.VERBOSE) + assert os.environ["GRASS_VERBOSE"] == "2" + assert any("Simulation(s) complete" in m for m in messages) diff --git a/tests/core/test_configreader.py b/tests/cli/test_configreader.py similarity index 100% rename from tests/core/test_configreader.py rename to tests/cli/test_configreader.py