Skip to content

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

@berendt

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
No fields configured for issues without a type.

Projects

Status
In progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions