oc_chat
Loading...
Searching...
No Matches
__main__.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# oc_chat/testing/__main__.py
4
5
6"""
7CLI entry for the §20 stand-in: ``python -m oc_chat.testing`` (Plan 184).
8
9Purpose
10-------
11Thin process shell over :func:`oc_chat.testing.runner.run_standin`:
12emit the report (JSON to stdout under ``--json``; captured pytest
13output plus a compact summary otherwise) and exit with the §4 code.
14
15Responsibilities
16----------------
17- ``main`` — run, emit, return the exit code.
18- The §12.4 minimal fatal envelope: when the report itself cannot be
19 serialised, emit the fixed-shape substitute and exit 3.
20
21Diagnostics
22-----------
23Domain: OC_TESTING
24Levels:
25 L2 — lifecycle events
26 L3 — semantic details
27 L4 — deep tracing
28
29Contracts
30---------
31- Under ``--json`` stdout carries the report document; human notes for
32 fatal configuration errors go to stderr (§4).
33- The process exit code always equals ``report["exit_code"]``.
34
35Dependencies
36------------
37- oc_diagnostics (structured debug logging via _dbg)
38- oc_chat.testing.runner (the orchestrator)
39"""
40from __future__ import annotations
41
42import json
43import sys
44
45from oc_diagnostics import _dbg
46
47from oc_chat.testing.runner import run_standin
48
49#: §12.4 minimal fatal envelope (fixed shape; serialisation cannot fail
50#: for the reasons the full report's did).
51_FATAL_ENVELOPE_PREFIX = (
52 # OCT-LINT: disable=no_hardcoded_secrets reason="The section-12.4 minimal fatal envelope is a spec-mandated fixed JSON literal; machine schema text, not a credential"
53 '{"schema": "oct-test-fatal/v1", "status": "orchestrator_error", '
54 '"exit_code": 3, "errors": [{"kind": "orchestrator", '
55 '"code": "REPORT_SERIALIZATION_FAILED", "severity": "fatal", '
56 '"message": '
57)
58_FATAL_ENVELOPE_SUFFIX = ', "source": "orchestrator"}]}'
59
60
61def main(argv: list[str] | None = None) -> int:
62 """Run the stand-in CLI; return the §4 exit code."""
63 func = "main"
64 args = list(sys.argv[1:]) if argv is None else list(argv)
65 result = run_standin(args)
66 report = result.report
67 _dbg("OC_TESTING", func,
68 f"status={report.get('status')} exit={result.exit_code}", 2)
69
70 if "--json" in args:
71 try:
72 document = json.dumps(report, indent=2)
73 except (TypeError, ValueError) as exc:
74 message = json.dumps(
75 f"report serialisation failed: {exc}"[:2000]
76 )
77 sys.stdout.write(
78 _FATAL_ENVELOPE_PREFIX + message + _FATAL_ENVELOPE_SUFFIX
79 + "\n"
80 )
81 return 3
82 sys.stdout.write(document + "\n")
83 if report.get("status") == "configuration_error":
84 sys.stderr.write(
85 "oc_chat.testing: configuration error (exit 4); see the "
86 "report's errors[] for the catalog code\n"
87 )
88 return result.exit_code
89
90 if result.captured_output:
91 sys.stdout.write(result.captured_output)
92 summary = report.get("summary", {})
93 sys.stdout.write(
94 f"oc_chat.testing stand-in: status={report.get('status')} "
95 f"exit={result.exit_code} collected={summary.get('collected', 0)} "
96 f"passed={summary.get('passed', 0)} "
97 f"failed={summary.get('failed', 0)} "
98 f"skipped={summary.get('skipped', 0)} "
99 f"timeout={summary.get('timeout', 0)} "
100 f"violations={len(report.get('contract_violations', []))}\n"
101 )
102 for entry in report.get("errors", []):
103 sys.stdout.write(
104 f" [{entry['severity']}] {entry['code']}: {entry['message']}\n"
105 )
106 return result.exit_code
107
108
109if __name__ == "__main__":
110 sys.exit(main())