Add plugin registry for report exporters

Replace the hardcoded if/elif in Export.exec() with a dict registry.
Built-in formats (text, json, junit, html) are registered as lazy
loaders; missing optional deps (junit_xml, lxml) print a clear message
with a pip install hint instead of raising. Entry-points
(group "testium.exporters") are discovered at import time — installed
plugins are auto-detected with no extra config.

An unknown or unavailable format prints an info line and skips the
export; the test run is not interrupted.

Validation:
- New testium-fake-exporter package under test/validation/fake_exporter/
  installed automatically by scripts/build_env.sh on venv creation.
  It registers fake_format via entry-points and exports the tests
  table to CSV — a real, useful exporter that exercises the plugin
  contract end-to-end (entry-point discovery, dispatch, SQLite query).
- New dedicated items/report_plugin/ test exercises both the
  unknown-format skip path and the fake_format plugin path, with a
  py_func check (file_check.py) on the produced CSV. Runs once per
  validation suite.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-01 23:16:10 +02:00
parent a70b70db54
commit 8bd9b3e9d6
7 changed files with 152 additions and 20 deletions

View File

@@ -0,0 +1,42 @@
"""CSV report exporter — used as a real plugin by the testium validation suite.
Demonstrates the contract: take the SQLite connection, output path, optional
name/key filters, and produce the output. Has no dependency on testium
internals.
"""
import csv
class FakeExporter:
COLUMNS = [
'timestamp_start',
'test_id',
'parent_id',
'level',
'test_name',
'test_type',
'report_key',
'result',
'message',
'duration',
]
def __init__(self, name, con, path, pats, keys, no_header=False):
clauses = []
for p in pats:
clauses.append(f'test_name LIKE "{p}"')
for k in keys:
clauses.append(f'report_key LIKE "{k}"')
where = ('WHERE ' + ' OR '.join(clauses) + ' ') if clauses else ''
cols = ', '.join(self.COLUMNS)
rows = con.execute(
f'SELECT {cols} FROM tests {where}ORDER BY timestamp_start'
).fetchall()
with open(path, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
if not no_header:
writer.writerow(self.COLUMNS)
for row in rows:
writer.writerow(row)

View File

@@ -0,0 +1,14 @@
[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"
[project]
name = "testium-fake-exporter"
version = "0.1.0"
description = "Fake report exporter used by testium validation suite"
[project.entry-points."testium.exporters"]
fake_format = "fake_exporter:FakeExporter"
[tool.setuptools.packages.find]
where = ["."]

View File

@@ -0,0 +1,8 @@
import os
def file_contains(path, text):
if not os.path.isfile(path):
return False
with open(path, 'r') as f:
return text in f.read()

View File

@@ -0,0 +1 @@
no_param: Null

View File

@@ -0,0 +1,23 @@
- report:
name: Unknown exporter is skipped (must pass)
key: $(test)_PASS
export:
- definitely_not_a_format:
path: $(validation_report_path)$(psep)$(test)_unknown.txt
- report:
name: Plugin exporter from entry-points (fake_format CSV)
key: $(test)_PASS
export:
- fake_format:
path: $(validation_report_path)$(psep)$(test)_fake.csv
- py_func:
name: Check fake_format CSV content
file: $(test_path)$(psep)file_check.py
func_name: file_contains
key: $(test)_PASS
param:
- $(validation_report_path)$(psep)$(test)_fake.csv
- "Test preparation,Group"
expected_result: True