generated from coulomb/repo-seed
local OpenCMIS TCK runtime
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -180,3 +180,4 @@ cython_debug/
|
|||||||
# Guide Board local generated outputs
|
# Guide Board local generated outputs
|
||||||
/runs/
|
/runs/
|
||||||
/reports/
|
/reports/
|
||||||
|
/.local/
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ PYTHONPATH=../guide-board/src python3 -m unittest discover -s tests
|
|||||||
## Docs
|
## Docs
|
||||||
|
|
||||||
- [docs/CMIS-PROFILES.md](docs/CMIS-PROFILES.md)
|
- [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/OPENCMIS-TCK-RUNNER.md](docs/OPENCMIS-TCK-RUNNER.md)
|
||||||
- [docs/SERVICE-AND-RETENTION.md](docs/SERVICE-AND-RETENTION.md)
|
- [docs/SERVICE-AND-RETENTION.md](docs/SERVICE-AND-RETENTION.md)
|
||||||
|
|
||||||
|
|||||||
261
adapters/opencmis_console_adapter.py
Normal file
261
adapters/opencmis_console_adapter.py
Normal file
@@ -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())
|
||||||
132
docs/LOCAL-TCK-RUNTIME.md
Normal file
132
docs/LOCAL-TCK-RUNTIME.md
Normal file
@@ -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=<browser-url>
|
||||||
|
org.apache.chemistry.opencmis.session.repository.id=<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.
|
||||||
@@ -25,7 +25,17 @@ or custom harness commands that do not use the local Java/Maven toolchain.
|
|||||||
|
|
||||||
## Command Configuration
|
## 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
|
```json
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -31,7 +31,25 @@
|
|||||||
"timeout_seconds": 300,
|
"timeout_seconds": 300,
|
||||||
"opencmis_tck": {
|
"opencmis_tck": {
|
||||||
"repository_id": "compat-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}"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -244,6 +244,7 @@ def _normalize_json_result(
|
|||||||
returncode: int,
|
returncode: int,
|
||||||
selected_group: str | None,
|
selected_group: str | None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
|
artifact_refs = _artifact_refs_from_payload(payload)
|
||||||
cases = _json_cases(payload)
|
cases = _json_cases(payload)
|
||||||
if cases:
|
if cases:
|
||||||
counts: dict[str, int] = {}
|
counts: dict[str, int] = {}
|
||||||
@@ -268,7 +269,7 @@ def _normalize_json_result(
|
|||||||
"result_counts": counts,
|
"result_counts": counts,
|
||||||
"cases": normalized_cases[:200],
|
"cases": normalized_cases[:200],
|
||||||
},
|
},
|
||||||
"artifact_refs": [],
|
"artifact_refs": artifact_refs,
|
||||||
}
|
}
|
||||||
|
|
||||||
result = _normalize_case_status(str(payload.get("result", "unknown")))
|
result = _normalize_case_status(str(payload.get("result", "unknown")))
|
||||||
@@ -282,7 +283,7 @@ def _normalize_json_result(
|
|||||||
"result_counts": {result: 1},
|
"result_counts": {result: 1},
|
||||||
"payload": payload,
|
"payload": payload,
|
||||||
},
|
},
|
||||||
"artifact_refs": [],
|
"artifact_refs": artifact_refs,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -327,6 +328,13 @@ def _json_cases(payload: dict[str, Any]) -> list[dict[str, Any]]:
|
|||||||
return []
|
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:
|
def _aggregate_result(counts: dict[str, int], returncode: int) -> str:
|
||||||
if counts.get("infrastructure_error"):
|
if counts.get("infrastructure_error"):
|
||||||
return "infrastructure_error"
|
return "infrastructure_error"
|
||||||
@@ -413,10 +421,12 @@ def _expand_arg(
|
|||||||
return (
|
return (
|
||||||
value.replace("{run_dir}", context["run_dir"])
|
value.replace("{run_dir}", context["run_dir"])
|
||||||
.replace("{artifact_dir}", str(artifact_dir))
|
.replace("{artifact_dir}", str(artifact_dir))
|
||||||
|
.replace("{extension_path}", context["extension_path"])
|
||||||
.replace("{browser_url}", _browser_url(context) or "")
|
.replace("{browser_url}", _browser_url(context) or "")
|
||||||
.replace("{repository_id}", _repository_id(context) or "")
|
.replace("{repository_id}", _repository_id(context) or "")
|
||||||
.replace("{check_group}", selected_group or "")
|
.replace("{check_group}", selected_group or "")
|
||||||
.replace("{target_id}", context["target_profile"]["id"])
|
.replace("{target_id}", context["target_profile"]["id"])
|
||||||
|
.replace("{timeout_seconds}", str(int(_timeout_seconds(context))))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
43
runtime/opencmis-tck/pom.xml
Normal file
43
runtime/opencmis-tck/pom.xml
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>guide-board.extensions</groupId>
|
||||||
|
<artifactId>open-cmis-tck-local-runtime</artifactId>
|
||||||
|
<version>0.1.0</version>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
|
<name>OpenCMIS TCK Local Runtime</name>
|
||||||
|
<description>Local Maven runtime for invoking Apache Chemistry OpenCMIS TCK ConsoleRunner.</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<opencmis.version>1.1.0</opencmis.version>
|
||||||
|
<slf4j.version>1.7.21</slf4j.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.chemistry.opencmis</groupId>
|
||||||
|
<artifactId>chemistry-opencmis-test-tck</artifactId>
|
||||||
|
<version>${opencmis.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-simple</artifactId>
|
||||||
|
<version>${slf4j.version}</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
|
<artifactId>exec-maven-plugin</artifactId>
|
||||||
|
<version>3.1.0</version>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
37
scripts/bootstrap_opencmis_tck.py
Normal file
37
scripts/bootstrap_opencmis_tck.py
Normal file
@@ -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())
|
||||||
181
src/open_cmis_tck/bootstrap.py
Normal file
181
src/open_cmis_tck/bootstrap.py
Normal file
@@ -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()
|
||||||
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import http.client
|
import http.client
|
||||||
import json
|
import json
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
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.planning import build_run_plan, validate_assessment_profile
|
||||||
from guide_board.retention import build_trend_summary
|
from guide_board.retention import build_trend_summary
|
||||||
from guide_board.service import ServiceHandle, start_service
|
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
|
from open_cmis_tck.profile import validate_cmis_profile_config
|
||||||
|
|
||||||
|
|
||||||
@@ -81,6 +83,64 @@ class OpenCmisTckExtensionTests(unittest.TestCase):
|
|||||||
broken_diagnostics["diagnostics"][0]["message"],
|
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:
|
def test_runs_cmis_preflight_against_local_endpoint(self) -> None:
|
||||||
server = HTTPServer(("127.0.0.1", 0), _CmisHandler)
|
server = HTTPServer(("127.0.0.1", 0), _CmisHandler)
|
||||||
thread = threading.Thread(target=server.serve_forever)
|
thread = threading.Thread(target=server.serve_forever)
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ move execution to a container/test host yet.
|
|||||||
|
|
||||||
```task
|
```task
|
||||||
id: OPEN-CMIS-TCK-WP-0002-T001
|
id: OPEN-CMIS-TCK-WP-0002-T001
|
||||||
status: todo
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "f3144edb-9807-4fd6-ae29-b8db18939bc4"
|
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
|
- Add a local dependency manifest or lock note that guide-board can snapshot in
|
||||||
assessment evidence.
|
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
|
## D2.2 - Local Environment Bootstrap Command
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: OPEN-CMIS-TCK-WP-0002-T002
|
id: OPEN-CMIS-TCK-WP-0002-T002
|
||||||
status: todo
|
status: in_progress
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "f993c1ef-8e6f-4ad1-8375-4487887deb8b"
|
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
|
- Successful bootstrap writes a local runtime summary that can be referenced by
|
||||||
later runs.
|
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
|
## D2.3 - OpenCMIS TCK Adapter Invocation
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: OPEN-CMIS-TCK-WP-0002-T003
|
id: OPEN-CMIS-TCK-WP-0002-T003
|
||||||
status: todo
|
status: in_progress
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "a446a80f-fc63-4ea8-9720-9294db57ade9"
|
state_hub_task_id: "a446a80f-fc63-4ea8-9720-9294db57ade9"
|
||||||
```
|
```
|
||||||
@@ -126,6 +145,17 @@ Acceptance:
|
|||||||
metadata are captured under the run directory.
|
metadata are captured under the run directory.
|
||||||
- The adapter can run a minimal repository/type group against a live target.
|
- 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
|
## D2.4 - Target Profiles And Credential References
|
||||||
|
|
||||||
```task
|
```task
|
||||||
|
|||||||
Reference in New Issue
Block a user