Skip to content

Unit tests for osism/main.py — OsismApp entry point #2361

Description

@berendt

Background

Follow-up to #2192 (foundation) and PR #2193 (pytest + Zuul infrastructure). Part of Tier 8 (#2199). osism/main.py (38 LOC) defines OsismApp, the cliff App subclass that wires the osism.commands entry-point namespace and replaces loguru's default sink with a custom stderr handler, plus main(), the osism console-script entry point (setup.cfg: osism = osism.main:main).

Scope

Add tests/unit/test_main.py covering OsismApp.__init__ and main() in osism/main.py. No coverage exists yet for this module.

Test targets

OsismApp.__init__()main.py:13

Patch:

  • osism.main.logger (the loguru logger as imported in the module) — so the test does not destroy the process-global loguru sink configuration

Logging setup

  • Construction calls logger.remove() exactly once (drops loguru's default handler)
  • logger.add() is called once with sys.stderr, level="INFO", colorize=True and the <green>{time:...}</green> format string (assert via call_args.kwargs)

cliff wiring

  • After construction: app.command_manager.namespace == "osism.commands" (real CommandManager — cliff registers entry points lazily without importing the command modules, so real construction is cheap)
  • app.deferred_help is True
  • app.parser.description == "OSISM manager interface"
  • Version is passed through from the package: osism.__version__ (pbr) may be None when package metadata is unavailable (osism/__init__.py:13 fallback) — patch osism.main.__version__ to None and assert construction still succeeds
  • app.run(["--version"]) raises SystemExit with code 0 (argparse version action fires before any command module is imported) — cheap smoke test that the option parser is wired

main()main.py:31

Patch osism.main.OsismApp.

  • main(["reconciler", "sync"]) constructs the app exactly once and calls app.run(["reconciler", "sync"]) with the argv unchanged
  • Return value pass-through: stub app.run returning 42main(...) returns 42; returning 0 → returns 0
  • Empty argv list is forwarded as-is (app.run([])) — only against the mocked app; never call the real app.run([]) in a unit test, cliff would enter interactive shell mode
  • Default-argument quirk: argv=sys.argv[1:] is evaluated once at import time, so the default is frozen — tests must always pass argv explicitly; add a short comment in the test file documenting this

Mocking hints

  • Patch osism.main.logger before instantiating OsismApp; this both isolates the assertion and prevents the test from removing loguru handlers other tests may rely on. Do not configure the real loguru logger in tests.
  • CommandManager("osism.commands") only registers entry points; commands are imported lazily on find_command, so plain OsismApp() does not pull in the heavy command modules (openstacksdk etc.).
  • For the --version smoke test use pytest.raises(SystemExit) and check excinfo.value.code == 0; capture stdout with capsys if you want to assert the program name appears.
  • mocker.patch("osism.main.OsismApp") returns the class mock; use mock_app_cls.return_value.run.return_value = 42 and assert mock_app_cls.return_value.run.assert_called_once_with([...]).

Definition of Done

  • tests/unit/test_main.py created
  • All listed cases covered
  • pytest --cov=osism.main shows ≥ 95 %
  • pipenv run pytest tests/unit/test_main.py passes locally
  • flake8, mypy, python-black remain green
  • Zuul job python-osism-unit-tests passes

Dependencies

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

Status
Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions