From 18a952aa0ce1d923c72664986b04f560119a75cd Mon Sep 17 00:00:00 2001 From: tegwick Date: Thu, 7 May 2026 23:43:21 +0200 Subject: [PATCH] local OpenCMIS TCK runtime --- .gitignore | 1 + README.md | 1 + adapters/opencmis_console_adapter.py | 261 ++++++++++++++++++ docs/LOCAL-TCK-RUNTIME.md | 132 +++++++++ docs/OPENCMIS-TCK-RUNNER.md | 12 +- .../assessments/cmis-browser-baseline.json | 20 +- runners/opencmis_tck.py | 14 +- runtime/opencmis-tck/pom.xml | 43 +++ scripts/bootstrap_opencmis_tck.py | 37 +++ src/open_cmis_tck/bootstrap.py | 181 ++++++++++++ tests/test_open_cmis_tck.py | 60 ++++ ...IS-TCK-WP-0002-live-test-infrastructure.md | 36 ++- 12 files changed, 791 insertions(+), 7 deletions(-) create mode 100644 adapters/opencmis_console_adapter.py create mode 100644 docs/LOCAL-TCK-RUNTIME.md create mode 100644 runtime/opencmis-tck/pom.xml create mode 100644 scripts/bootstrap_opencmis_tck.py create mode 100644 src/open_cmis_tck/bootstrap.py diff --git a/.gitignore b/.gitignore index e587187..d66726c 100644 --- a/.gitignore +++ b/.gitignore @@ -180,3 +180,4 @@ cython_debug/ # Guide Board local generated outputs /runs/ /reports/ +/.local/ diff --git a/README.md b/README.md index b484a69..62973a8 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,7 @@ PYTHONPATH=../guide-board/src python3 -m unittest discover -s tests ## Docs - [docs/CMIS-PROFILES.md](docs/CMIS-PROFILES.md) +- [docs/LOCAL-TCK-RUNTIME.md](docs/LOCAL-TCK-RUNTIME.md) - [docs/OPENCMIS-TCK-RUNNER.md](docs/OPENCMIS-TCK-RUNNER.md) - [docs/SERVICE-AND-RETENTION.md](docs/SERVICE-AND-RETENTION.md) diff --git a/adapters/opencmis_console_adapter.py b/adapters/opencmis_console_adapter.py new file mode 100644 index 0000000..3d718f9 --- /dev/null +++ b/adapters/opencmis_console_adapter.py @@ -0,0 +1,261 @@ +#!/usr/bin/env python3 +"""Adapter for Apache Chemistry OpenCMIS TCK ConsoleRunner.""" + +from __future__ import annotations + +import argparse +import json +import subprocess +from datetime import datetime, timezone +from pathlib import Path +from typing import Any + + +GROUP_CLASSES = { + "repository-type": [ + "org.apache.chemistry.opencmis.tck.tests.basics.BasicsTestGroup", + "org.apache.chemistry.opencmis.tck.tests.types.TypesTestGroup", + ], + "object-content": [ + "org.apache.chemistry.opencmis.tck.tests.crud.CRUDTestGroup", + ], + "navigation": [ + "org.apache.chemistry.opencmis.tck.tests.filing.FilingTestGroup", + ], + "query-acl-versioning": [ + "org.apache.chemistry.opencmis.tck.tests.query.QueryTestGroup", + "org.apache.chemistry.opencmis.tck.tests.control.ControlTestGroup", + "org.apache.chemistry.opencmis.tck.tests.versioning.VersioningTestGroup", + ], + "relationships": [ + "org.apache.chemistry.opencmis.tck.tests.crud.CRUDTestGroup", + ], + "change-log": [ + "org.apache.chemistry.opencmis.tck.tests.control.ControlTestGroup", + ], + "extension-gaps": [], +} + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--browser-url", required=True) + parser.add_argument("--repository-id", required=True) + parser.add_argument("--check-group", required=True) + parser.add_argument("--artifact-dir", type=Path, required=True) + parser.add_argument("--run-dir", type=Path) + parser.add_argument("--extension-path", type=Path, default=Path(__file__).resolve().parents[1]) + parser.add_argument("--user") + parser.add_argument("--password") + parser.add_argument("--maven-executable", default="mvn") + parser.add_argument("--timeout-seconds", type=int, default=300) + parser.add_argument("--dry-run", action="store_true") + args = parser.parse_args() + + result = run_console_adapter(args) + print(json.dumps(result, indent=2, sort_keys=True)) + return 0 if result["result"] in {"pass", "skipped"} else 1 + + +def run_console_adapter(args: argparse.Namespace) -> dict[str, Any]: + artifact_dir = args.artifact_dir.resolve() + artifact_dir.mkdir(parents=True, exist_ok=True) + session_path = artifact_dir / "session.properties" + groups_path = artifact_dir / "groups.txt" + invocation_path = artifact_dir / "console-runner-invocation.json" + console_stdout = artifact_dir / "console-runner-stdout.txt" + console_stderr = artifact_dir / "console-runner-stderr.txt" + + group_classes = GROUP_CLASSES.get(args.check_group) + if group_classes is None: + return _result( + "blocked", + [f"Unsupported OpenCMIS TCK check group: {args.check_group}."], + args, + [], + args.run_dir, + artifact_dir, + [], + ) + + _write_session_parameters(session_path, args) + _write_groups(groups_path, group_classes) + command = _maven_command(args, session_path, groups_path) + invocation = { + "created_at": _now(), + "command": command, + "check_group": args.check_group, + "group_classes": group_classes, + "session_parameters": str(session_path), + "groups_file": str(groups_path), + } + invocation_path.write_text(json.dumps(invocation, indent=2, sort_keys=True) + "\n", encoding="utf-8") + + artifact_refs = [ + str(session_path.relative_to(artifact_dir)), + str(groups_path.relative_to(artifact_dir)), + str(invocation_path.relative_to(artifact_dir)), + ] + if not group_classes: + return _result( + "skipped", + [f"No OpenCMIS TCK group classes are mapped for {args.check_group}; treated as a known-gap review group."], + args, + group_classes, + args.run_dir, + artifact_dir, + artifact_refs, + ) + if args.dry_run: + return _result( + "skipped", + ["Dry run completed; OpenCMIS TCK ConsoleRunner was not invoked."], + args, + group_classes, + args.run_dir, + artifact_dir, + artifact_refs, + ) + + completed = subprocess.run( + command, + capture_output=True, + text=True, + timeout=args.timeout_seconds, + check=False, + ) + console_stdout.write_text(completed.stdout, encoding="utf-8") + console_stderr.write_text(completed.stderr, encoding="utf-8") + artifact_refs.extend( + [ + str(console_stdout.relative_to(artifact_dir)), + str(console_stderr.relative_to(artifact_dir)), + ] + ) + + cases = _cases_from_console_output(completed.stdout) + if completed.returncode == 0 and not any(case["status"] == "fail" for case in cases): + status = "pass" + else: + status = "fail" + return _result( + status, + [f"OpenCMIS TCK ConsoleRunner exited with {completed.returncode} for {args.check_group}."], + args, + group_classes, + args.run_dir, + artifact_dir, + artifact_refs, + cases=cases, + returncode=completed.returncode, + ) + + +def _write_session_parameters(path: Path, args: argparse.Namespace) -> None: + values = { + "org.apache.chemistry.opencmis.binding.spi.type": "browser", + "org.apache.chemistry.opencmis.binding.browser.url": args.browser_url, + "org.apache.chemistry.opencmis.session.repository.id": args.repository_id, + "org.apache.chemistry.opencmis.binding.browser.succinct": "true", + "org.apache.chemistry.opencmis.binding.compression": "true", + "org.apache.chemistry.opencmis.binding.cookies": "true", + } + if args.user is not None: + values["org.apache.chemistry.opencmis.user"] = args.user + if args.password is not None: + values["org.apache.chemistry.opencmis.password"] = args.password + path.write_text( + "\n".join(f"{key}={value}" for key, value in values.items()) + "\n", + encoding="utf-8", + ) + + +def _write_groups(path: Path, group_classes: list[str]) -> None: + path.write_text("\n".join(group_classes) + ("\n" if group_classes else ""), encoding="utf-8") + + +def _maven_command(args: argparse.Namespace, session_path: Path, groups_path: Path) -> list[str]: + pom_path = args.extension_path / "runtime" / "opencmis-tck" / "pom.xml" + return [ + args.maven_executable, + "-q", + "-f", + str(pom_path), + "exec:java", + "-Dexec.mainClass=org.apache.chemistry.opencmis.tck.runner.ConsoleRunner", + f"-Dexec.args={session_path} {groups_path}", + ] + + +def _cases_from_console_output(output: str) -> list[dict[str, str]]: + cases = [] + for line in output.splitlines(): + stripped = line.strip() + upper = stripped.upper() + if not stripped: + continue + if "UNEXPECTED_EXCEPTION" in upper: + cases.append({"id": stripped[:120], "status": "infrastructure_error", "message": stripped}) + elif "FAILURE" in upper: + cases.append({"id": stripped[:120], "status": "fail", "message": stripped}) + elif "WARNING" in upper: + cases.append({"id": stripped[:120], "status": "warning", "message": stripped}) + elif "SKIPPED" in upper: + cases.append({"id": stripped[:120], "status": "skipped", "message": stripped}) + elif "OK" in upper: + cases.append({"id": stripped[:120], "status": "pass", "message": stripped}) + return cases + + +def _result( + status: str, + observations: list[str], + args: argparse.Namespace, + group_classes: list[str], + run_dir: Path | None, + artifact_dir: Path, + artifact_refs: list[str], + cases: list[dict[str, str]] | None = None, + returncode: int | None = None, +) -> dict[str, Any]: + cases = cases or [] + counts: dict[str, int] = {} + for case in cases: + counts[case["status"]] = counts.get(case["status"], 0) + 1 + if not counts: + counts[status] = 1 + return { + "result": status, + "observations": observations, + "tests": cases, + "facts": { + "adapter": "opencmis-console-runner", + "artifact_dir": str(artifact_dir), + "check_group": args.check_group, + "group_classes": group_classes, + "returncode": returncode, + "result_counts": counts, + }, + "artifact_refs": [ + _artifact_ref(artifact_dir / ref, run_dir, artifact_dir) + for ref in artifact_refs + ], + } + + +def _artifact_ref(path: Path, run_dir: Path | None, artifact_dir: Path) -> str: + resolved = path.resolve() + if run_dir is not None: + try: + return str(resolved.relative_to(run_dir.resolve())) + except ValueError: + pass + return str(resolved.relative_to(artifact_dir.resolve())) + + +def _now() -> str: + return datetime.now(timezone.utc).isoformat() + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/docs/LOCAL-TCK-RUNTIME.md b/docs/LOCAL-TCK-RUNTIME.md new file mode 100644 index 0000000..4c382eb --- /dev/null +++ b/docs/LOCAL-TCK-RUNTIME.md @@ -0,0 +1,132 @@ +# Local OpenCMIS TCK Runtime + +Status: draft +Created: 2026-05-07 + +## Purpose + +This document describes the local runtime needed to actually run Apache +Chemistry OpenCMIS TCK checks from the `open-cmis-tck` guide-board extension. + +The TCK is an open source Java toolkit. It is not vendored into this repository. +The extension provides a pinned Maven runtime descriptor and adapter commands +that resolve and run the TCK locally. + +## Runtime Pin + +The first local runtime uses: + +- Maven coordinate: + `org.apache.chemistry.opencmis:chemistry-opencmis-test-tck:1.1.0` +- License: Apache License 2.0 +- Runner class: + `org.apache.chemistry.opencmis.tck.runner.ConsoleRunner` +- Runner contract: session-parameters file plus group-list file + +The Apache Chemistry project is retired, so this extension pins the 1.1.0 Maven +artifact and records the retirement boundary in runtime summaries. + +Sources: + +- https://repo1.maven.org/maven2/org/apache/chemistry/opencmis/chemistry-opencmis-test-tck/1.1.0/ +- https://chemistry.apache.org/java/javadoc/org/apache/chemistry/opencmis/tck/runner/ConsoleRunner.html +- https://chemistry.apache.org/java/javadoc/org/apache/chemistry/opencmis/commons/SessionParameter.html + +## Bootstrap + +From this repository: + +```sh +PYTHONPATH=src python3 scripts/bootstrap_opencmis_tck.py +``` + +This checks Java, Maven, and the local runtime descriptor. It writes: + +```text +.local/opencmis-tck/runtime-summary.json +``` + +To resolve/cache Maven dependencies, run: + +```sh +PYTHONPATH=src python3 scripts/bootstrap_opencmis_tck.py --resolve +``` + +The `--resolve` step downloads Maven dependencies into the user's Maven cache. +It does not commit artifacts into this repository. + +## Current Local Prerequisites + +The local runner expects: + +- `java` on `PATH` +- `mvn` on `PATH` +- network access or an already-populated Maven cache for the first + `--resolve` + +On Ubuntu/WSL, a typical prerequisite install is: + +```sh +sudo apt-get update +sudo apt-get install -y openjdk-17-jdk maven +``` + +Use an already managed Java/Maven installation instead if this workstation has +one outside WSL. + +## Guide-Board Invocation + +The baseline assessment profile now points the OpenCMIS wrapper at: + +```text +adapters/opencmis_console_adapter.py +``` + +The adapter generates: + +- `session.properties` +- `groups.txt` +- `console-runner-invocation.json` +- `console-runner-stdout.txt` +- `console-runner-stderr.txt` + +and invokes Maven's `exec:java` goal against: + +```text +runtime/opencmis-tck/pom.xml +``` + +That Maven descriptor pulls the OpenCMIS TCK artifact and runs +`ConsoleRunner`. + +## Session Parameters + +For Browser Binding runs, the adapter writes OpenCMIS session parameters such +as: + +```properties +org.apache.chemistry.opencmis.binding.spi.type=browser +org.apache.chemistry.opencmis.binding.browser.url= +org.apache.chemistry.opencmis.session.repository.id= +org.apache.chemistry.opencmis.binding.browser.succinct=true +org.apache.chemistry.opencmis.binding.compression=true +org.apache.chemistry.opencmis.binding.cookies=true +``` + +User and password fields are written only when explicitly supplied. Secret +storage is not implemented in this step. + +## Check Group Mapping + +The first adapter maps guide-board check groups to OpenCMIS TCK group classes: + +- `repository-type`: `BasicsTestGroup`, `TypesTestGroup` +- `object-content`: `CRUDTestGroup` +- `navigation`: `FilingTestGroup` +- `query-acl-versioning`: `QueryTestGroup`, `ControlTestGroup`, + `VersioningTestGroup` +- `relationships`: `CRUDTestGroup` +- `change-log`: `ControlTestGroup` +- `extension-gaps`: no executable group yet; treated as known-gap review + +This mapping should be refined after the first live pilot output is reviewed. diff --git a/docs/OPENCMIS-TCK-RUNNER.md b/docs/OPENCMIS-TCK-RUNNER.md index c40d1ee..4444447 100644 --- a/docs/OPENCMIS-TCK-RUNNER.md +++ b/docs/OPENCMIS-TCK-RUNNER.md @@ -25,7 +25,17 @@ or custom harness commands that do not use the local Java/Maven toolchain. ## Command Configuration -Configure a TCK command as an argv list: +The baseline assessment profile is configured to call the local ConsoleRunner +adapter: + +```text +adapters/opencmis_console_adapter.py +``` + +See `docs/LOCAL-TCK-RUNTIME.md` for the bootstrap flow and Maven runtime +descriptor. + +You can also configure a custom TCK command as an argv list: ```json { diff --git a/profiles/assessments/cmis-browser-baseline.json b/profiles/assessments/cmis-browser-baseline.json index 756c049..992164b 100644 --- a/profiles/assessments/cmis-browser-baseline.json +++ b/profiles/assessments/cmis-browser-baseline.json @@ -31,7 +31,25 @@ "timeout_seconds": 300, "opencmis_tck": { "repository_id": "compat-tck", - "requires_java_maven": true + "requires_java_maven": true, + "command": [ + "python3", + "{extension_path}/adapters/opencmis_console_adapter.py", + "--browser-url", + "{browser_url}", + "--repository-id", + "{repository_id}", + "--check-group", + "{check_group}", + "--artifact-dir", + "{artifact_dir}", + "--run-dir", + "{run_dir}", + "--extension-path", + "{extension_path}", + "--timeout-seconds", + "{timeout_seconds}" + ] } } } diff --git a/runners/opencmis_tck.py b/runners/opencmis_tck.py index 6a9507d..3a3c8ef 100644 --- a/runners/opencmis_tck.py +++ b/runners/opencmis_tck.py @@ -244,6 +244,7 @@ def _normalize_json_result( returncode: int, selected_group: str | None, ) -> dict[str, Any]: + artifact_refs = _artifact_refs_from_payload(payload) cases = _json_cases(payload) if cases: counts: dict[str, int] = {} @@ -268,7 +269,7 @@ def _normalize_json_result( "result_counts": counts, "cases": normalized_cases[:200], }, - "artifact_refs": [], + "artifact_refs": artifact_refs, } result = _normalize_case_status(str(payload.get("result", "unknown"))) @@ -282,7 +283,7 @@ def _normalize_json_result( "result_counts": {result: 1}, "payload": payload, }, - "artifact_refs": [], + "artifact_refs": artifact_refs, } @@ -327,6 +328,13 @@ def _json_cases(payload: dict[str, Any]) -> list[dict[str, Any]]: return [] +def _artifact_refs_from_payload(payload: dict[str, Any]) -> list[str]: + refs = payload.get("artifact_refs", []) + if not isinstance(refs, list): + return [] + return [ref for ref in refs if isinstance(ref, str) and ref] + + def _aggregate_result(counts: dict[str, int], returncode: int) -> str: if counts.get("infrastructure_error"): return "infrastructure_error" @@ -413,10 +421,12 @@ def _expand_arg( return ( value.replace("{run_dir}", context["run_dir"]) .replace("{artifact_dir}", str(artifact_dir)) + .replace("{extension_path}", context["extension_path"]) .replace("{browser_url}", _browser_url(context) or "") .replace("{repository_id}", _repository_id(context) or "") .replace("{check_group}", selected_group or "") .replace("{target_id}", context["target_profile"]["id"]) + .replace("{timeout_seconds}", str(int(_timeout_seconds(context)))) ) diff --git a/runtime/opencmis-tck/pom.xml b/runtime/opencmis-tck/pom.xml new file mode 100644 index 0000000..64d7c8b --- /dev/null +++ b/runtime/opencmis-tck/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + guide-board.extensions + open-cmis-tck-local-runtime + 0.1.0 + pom + + OpenCMIS TCK Local Runtime + Local Maven runtime for invoking Apache Chemistry OpenCMIS TCK ConsoleRunner. + + + 1.1.0 + 1.7.21 + + + + + org.apache.chemistry.opencmis + chemistry-opencmis-test-tck + ${opencmis.version} + + + org.slf4j + slf4j-simple + ${slf4j.version} + runtime + + + + + + + org.codehaus.mojo + exec-maven-plugin + 3.1.0 + + + + diff --git a/scripts/bootstrap_opencmis_tck.py b/scripts/bootstrap_opencmis_tck.py new file mode 100644 index 0000000..d4046f7 --- /dev/null +++ b/scripts/bootstrap_opencmis_tck.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +"""Check and optionally resolve the local OpenCMIS TCK runtime.""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +from open_cmis_tck.bootstrap import check_runtime, default_summary_path + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument( + "--extension-root", + type=Path, + default=Path(__file__).resolve().parents[1], + ) + parser.add_argument("--output", type=Path) + parser.add_argument("--resolve", action="store_true") + parser.add_argument("--timeout-seconds", type=int, default=300) + args = parser.parse_args() + + output = args.output or default_summary_path(args.extension_root) + summary = check_runtime( + args.extension_root, + output, + resolve=args.resolve, + timeout_seconds=args.timeout_seconds, + ) + print(json.dumps(summary, indent=2, sort_keys=True)) + return 0 if summary["status"] == "ready" else 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/open_cmis_tck/bootstrap.py b/src/open_cmis_tck/bootstrap.py new file mode 100644 index 0000000..7205894 --- /dev/null +++ b/src/open_cmis_tck/bootstrap.py @@ -0,0 +1,181 @@ +"""Local OpenCMIS TCK runtime bootstrap helpers.""" + +from __future__ import annotations + +import json +import shutil +import subprocess +from datetime import datetime, timezone +from pathlib import Path +from typing import Any + + +TCK_COORDINATE = "org.apache.chemistry.opencmis:chemistry-opencmis-test-tck:1.1.0" +TCK_LICENSE = "Apache-2.0" + + +def check_runtime( + extension_root: Path, + output_path: Path | None = None, + resolve: bool = False, + timeout_seconds: int = 300, +) -> dict[str, Any]: + """Check local Java/Maven posture and optionally resolve TCK dependencies.""" + + root = extension_root.resolve() + pom_path = root / "runtime" / "opencmis-tck" / "pom.xml" + java = _probe(["java", "-version"]) + maven = _probe(["mvn", "-version"]) + missing = [ + name + for name, probe in {"java": java, "maven": maven}.items() + if not probe["available"] + ] + + dependency_resolution = { + "attempted": False, + "status": "not_requested", + "returncode": None, + "stdout": "", + "stderr": "", + } + if resolve and not missing: + dependency_resolution = _resolve_dependencies(pom_path, timeout_seconds) + elif resolve: + dependency_resolution["attempted"] = False + dependency_resolution["status"] = "blocked_missing_prerequisite" + + status = _status(missing, dependency_resolution) + summary = { + "id": "opencmis-tck-runtime", + "status": status, + "created_at": _now(), + "tck": { + "coordinate": TCK_COORDINATE, + "license": TCK_LICENSE, + "project_status": "Apache Chemistry retired; artifact remains available from Maven Central.", + "runner_class": "org.apache.chemistry.opencmis.tck.runner.ConsoleRunner", + "runner_contract": "ConsoleRunner accepts a session parameters file and a group list file.", + }, + "runtime": { + "java": java, + "maven": maven, + "pom_path": str(pom_path), + "dependency_resolution": dependency_resolution, + }, + "diagnostics": _diagnostics(missing, resolve, dependency_resolution), + } + + if output_path is not None: + output_path.parent.mkdir(parents=True, exist_ok=True) + output_path.write_text(json.dumps(summary, indent=2, sort_keys=True) + "\n", encoding="utf-8") + return summary + + +def default_summary_path(extension_root: Path) -> Path: + return extension_root / ".local" / "opencmis-tck" / "runtime-summary.json" + + +def _probe(command: list[str]) -> dict[str, Any]: + executable = shutil.which(command[0]) + if executable is None: + return { + "available": False, + "path": None, + "returncode": None, + "version_output": None, + } + try: + completed = subprocess.run( + command, + capture_output=True, + text=True, + timeout=20, + check=False, + ) + except (OSError, subprocess.TimeoutExpired) as exc: + return { + "available": False, + "path": executable, + "returncode": None, + "version_output": str(exc), + } + output = "\n".join( + part.strip() + for part in [completed.stdout, completed.stderr] + if part.strip() + ) + return { + "available": completed.returncode == 0, + "path": executable, + "returncode": completed.returncode, + "version_output": output[:4000], + } + + +def _resolve_dependencies(pom_path: Path, timeout_seconds: int) -> dict[str, Any]: + completed = subprocess.run( + [ + "mvn", + "-q", + "-f", + str(pom_path), + "dependency:resolve", + ], + capture_output=True, + text=True, + timeout=timeout_seconds, + check=False, + ) + return { + "attempted": True, + "status": "resolved" if completed.returncode == 0 else "failed", + "returncode": completed.returncode, + "stdout": completed.stdout[-4000:], + "stderr": completed.stderr[-4000:], + } + + +def _status(missing: list[str], dependency_resolution: dict[str, Any]) -> str: + if missing: + return "blocked" + if dependency_resolution["attempted"] and dependency_resolution["status"] != "resolved": + return "blocked" + return "ready" + + +def _diagnostics( + missing: list[str], + resolve: bool, + dependency_resolution: dict[str, Any], +) -> list[dict[str, str]]: + diagnostics = [] + for tool in missing: + diagnostics.append( + { + "severity": "error", + "field": f"runtime.{tool}", + "message": f"{tool} is not available on PATH; install it or point the local environment at an existing toolchain.", + } + ) + if resolve and dependency_resolution["status"] == "failed": + diagnostics.append( + { + "severity": "error", + "field": "runtime.dependency_resolution", + "message": "Maven could not resolve the OpenCMIS TCK runtime dependencies.", + } + ) + if not resolve and not missing: + diagnostics.append( + { + "severity": "info", + "field": "runtime.dependency_resolution", + "message": "Run the bootstrap command with --resolve to download/cache Maven dependencies.", + } + ) + return diagnostics + + +def _now() -> str: + return datetime.now(timezone.utc).isoformat() diff --git a/tests/test_open_cmis_tck.py b/tests/test_open_cmis_tck.py index 6ad8eb1..7ab8867 100644 --- a/tests/test_open_cmis_tck.py +++ b/tests/test_open_cmis_tck.py @@ -2,6 +2,7 @@ from __future__ import annotations import http.client import json +import subprocess import sys import threading import time @@ -15,6 +16,7 @@ from guide_board.execution import run_assessment from guide_board.planning import build_run_plan, validate_assessment_profile from guide_board.retention import build_trend_summary from guide_board.service import ServiceHandle, start_service +from open_cmis_tck.bootstrap import TCK_COORDINATE, check_runtime from open_cmis_tck.profile import validate_cmis_profile_config @@ -81,6 +83,64 @@ class OpenCmisTckExtensionTests(unittest.TestCase): broken_diagnostics["diagnostics"][0]["message"], ) + def test_bootstrap_reports_local_tck_runtime_posture(self) -> None: + with TemporaryDirectory() as temporary_directory: + output = Path(temporary_directory) / "runtime-summary.json" + + summary = check_runtime(ROOT, output, resolve=False) + + self.assertIn(summary["status"], {"ready", "blocked"}) + self.assertEqual(summary["tck"]["coordinate"], TCK_COORDINATE) + self.assertEqual( + summary["tck"]["runner_class"], + "org.apache.chemistry.opencmis.tck.runner.ConsoleRunner", + ) + self.assertTrue(output.exists()) + + def test_console_adapter_dry_run_writes_session_and_group_files(self) -> None: + with TemporaryDirectory() as temporary_directory: + temp_root = Path(temporary_directory) + run_dir = temp_root / "run" + artifact_dir = run_dir / "artifacts" / "open-cmis-tck" / "tck" / "repository-type" + completed = subprocess.run( + [ + sys.executable, + str(ROOT / "adapters" / "opencmis_console_adapter.py"), + "--browser-url", + "http://127.0.0.1:8000/cmis/browser", + "--repository-id", + "local-test-repository", + "--check-group", + "repository-type", + "--artifact-dir", + str(artifact_dir), + "--run-dir", + str(run_dir), + "--extension-path", + str(ROOT), + "--dry-run", + ], + capture_output=True, + text=True, + check=False, + ) + result = json.loads(completed.stdout) + + self.assertEqual(completed.returncode, 0) + self.assertEqual(result["result"], "skipped") + self.assertIn( + "artifacts/open-cmis-tck/tck/repository-type/session.properties", + result["artifact_refs"], + ) + self.assertIn( + "org.apache.chemistry.opencmis.binding.spi.type=browser", + (artifact_dir / "session.properties").read_text(encoding="utf-8"), + ) + self.assertIn( + "org.apache.chemistry.opencmis.tck.tests.basics.BasicsTestGroup", + (artifact_dir / "groups.txt").read_text(encoding="utf-8"), + ) + def test_runs_cmis_preflight_against_local_endpoint(self) -> None: server = HTTPServer(("127.0.0.1", 0), _CmisHandler) thread = threading.Thread(target=server.serve_forever) diff --git a/workplans/OPEN-CMIS-TCK-WP-0002-live-test-infrastructure.md b/workplans/OPEN-CMIS-TCK-WP-0002-live-test-infrastructure.md index 9dd5c3b..15dc80c 100644 --- a/workplans/OPEN-CMIS-TCK-WP-0002-live-test-infrastructure.md +++ b/workplans/OPEN-CMIS-TCK-WP-0002-live-test-infrastructure.md @@ -74,7 +74,7 @@ move execution to a container/test host yet. ```task id: OPEN-CMIS-TCK-WP-0002-T001 -status: todo +status: done priority: high state_hub_task_id: "f3144edb-9807-4fd6-ae29-b8db18939bc4" ``` @@ -89,11 +89,22 @@ Acceptance: - Add a local dependency manifest or lock note that guide-board can snapshot in assessment evidence. +Progress: + +- Resolved the first runtime pin to + `org.apache.chemistry.opencmis:chemistry-opencmis-test-tck:1.1.0`. +- Confirmed the local entry point is + `org.apache.chemistry.opencmis.tck.runner.ConsoleRunner`, which takes a + session-parameters file and group-list file. +- Added `runtime/opencmis-tck/pom.xml` as the local Maven runtime descriptor. +- Documented the retired Apache Chemistry boundary, Maven Central source, and + OpenCMIS session parameter keys in `docs/LOCAL-TCK-RUNTIME.md`. + ## D2.2 - Local Environment Bootstrap Command ```task id: OPEN-CMIS-TCK-WP-0002-T002 -status: todo +status: in_progress priority: high state_hub_task_id: "f993c1ef-8e6f-4ad1-8375-4487887deb8b" ``` @@ -107,11 +118,19 @@ Acceptance: - Successful bootstrap writes a local runtime summary that can be referenced by later runs. +Progress: + +- Added `scripts/bootstrap_opencmis_tck.py` and + `open_cmis_tck.bootstrap.check_runtime`. +- The bootstrap writes `.local/opencmis-tck/runtime-summary.json` and can + optionally resolve Maven dependencies with `--resolve`. +- Current WSL posture is blocked because `java` and `mvn` are not on `PATH`. + ## D2.3 - OpenCMIS TCK Adapter Invocation ```task id: OPEN-CMIS-TCK-WP-0002-T003 -status: todo +status: in_progress priority: high state_hub_task_id: "a446a80f-fc63-4ea8-9720-9294db57ade9" ``` @@ -126,6 +145,17 @@ Acceptance: metadata are captured under the run directory. - The adapter can run a minimal repository/type group against a live target. +Progress: + +- Added `adapters/opencmis_console_adapter.py`. +- The adapter writes OpenCMIS Browser Binding session properties, group lists, + invocation metadata, and ConsoleRunner stdout/stderr under the run artifact + directory. +- The baseline assessment profile now points the OpenCMIS wrapper at this local + adapter command. +- Live execution remains blocked until Java/Maven are installed and Maven can + resolve the TCK runtime. + ## D2.4 - Target Profiles And Credential References ```task