192 lines
7.6 KiB
Python
192 lines
7.6 KiB
Python
import hashlib
|
|
import importlib.util
|
|
import json
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
|
|
def load_validate_reference_artifacts():
|
|
module_path = Path(__file__).resolve().parent / "validate_reference_artifacts.py"
|
|
spec = importlib.util.spec_from_file_location("validate_reference_artifacts", module_path)
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
def write_json(path: Path, payload: dict):
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
|
|
|
|
|
|
def write_text(path: Path, text: str = "ok\n"):
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(text, encoding="utf-8")
|
|
|
|
|
|
def ready_metadata(source_hash: str) -> dict:
|
|
return {
|
|
"schema_version": "abaqus-user-subroutine-artifact-v1",
|
|
"feature_id": "umat",
|
|
"model_id": "single-element",
|
|
"artifact_status": "ready-for-comparison",
|
|
"abaqus": {"version": "2024", "precision": "double"},
|
|
"compiler": {"vendor": "Intel oneAPI", "name": "ifx", "version": "2024"},
|
|
"subroutine": {
|
|
"entry_points": ["UMAT"],
|
|
"source_files": [
|
|
{
|
|
"path": "src/fortran/abaqus/UMAT.for",
|
|
"language": "Fortran",
|
|
"sha256": source_hash,
|
|
}
|
|
],
|
|
},
|
|
"input_file": "model.inp",
|
|
"outputs": {
|
|
"tails": {
|
|
"msg": "job.msg.tail.txt",
|
|
"dat": "job.dat.tail.txt",
|
|
"log": "job.log.tail.txt",
|
|
"sta": "job.sta.tail.txt",
|
|
},
|
|
"csv": {"stresses": "extracted/stresses.csv"},
|
|
},
|
|
"extraction": {
|
|
"source_odb": "job.odb",
|
|
"tool": "Abaqus Python",
|
|
"extracted_at": "2026-06-10T00:00:00+09:00",
|
|
"csv_directory": "extracted",
|
|
"script": "extraction/extract_odb_to_csv.py",
|
|
},
|
|
}
|
|
|
|
|
|
def create_ready_bundle(root: Path) -> tuple[Path, dict]:
|
|
source = root / "src" / "fortran" / "abaqus" / "UMAT.for"
|
|
write_text(source, " subroutine umat()\n end\n")
|
|
source_hash = hashlib.sha256(source.read_bytes()).hexdigest()
|
|
|
|
model_dir = root / "references" / "umat" / "single-element"
|
|
for name in [
|
|
"model.inp",
|
|
"job.msg.tail.txt",
|
|
"job.dat.tail.txt",
|
|
"job.log.tail.txt",
|
|
"job.sta.tail.txt",
|
|
"extracted/stresses.csv",
|
|
"extraction/extract_odb_to_csv.py",
|
|
]:
|
|
write_text(model_dir / name)
|
|
|
|
return model_dir, ready_metadata(source_hash)
|
|
|
|
|
|
class ValidateReferenceArtifactsTests(unittest.TestCase):
|
|
def test_missing_references_directory_is_valid(self):
|
|
validator = load_validate_reference_artifacts()
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
self.assertEqual(validator.validate_root(Path(tmp)), [])
|
|
|
|
def test_draft_metadata_with_minimal_provenance_is_valid(self):
|
|
validator = load_validate_reference_artifacts()
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
root = Path(tmp)
|
|
write_json(
|
|
root / "references" / "umat" / "single-element" / "metadata.json",
|
|
{
|
|
"schema_version": "abaqus-user-subroutine-artifact-v1",
|
|
"feature_id": "umat",
|
|
"model_id": "single-element",
|
|
"artifact_status": "draft",
|
|
},
|
|
)
|
|
|
|
self.assertEqual(validator.validate_root(root), [])
|
|
|
|
def test_ready_for_comparison_requires_declared_files(self):
|
|
validator = load_validate_reference_artifacts()
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
root = Path(tmp)
|
|
write_json(
|
|
root / "references" / "umat" / "single-element" / "metadata.json",
|
|
{
|
|
"schema_version": "abaqus-user-subroutine-artifact-v1",
|
|
"feature_id": "umat",
|
|
"model_id": "single-element",
|
|
"artifact_status": "ready-for-comparison",
|
|
"abaqus": {"version": "2024", "precision": "double"},
|
|
"compiler": {"vendor": "Intel oneAPI", "name": "ifx", "version": "2024"},
|
|
"subroutine": {
|
|
"entry_points": ["UMAT"],
|
|
"source_files": [{"path": "src/fortran/abaqus/UMAT.for", "language": "Fortran", "sha256": "abc"}],
|
|
},
|
|
"input_file": "model.inp",
|
|
"outputs": {
|
|
"tails": {"msg": "job.msg.tail.txt", "dat": "job.dat.tail.txt", "log": "job.log.tail.txt"},
|
|
"csv": {"stresses": "extracted/stresses.csv"},
|
|
},
|
|
},
|
|
)
|
|
|
|
errors = validator.validate_root(root)
|
|
|
|
self.assertTrue(any("missing input_file" in error for error in errors))
|
|
self.assertTrue(any("missing output tail sta" in error for error in errors))
|
|
self.assertTrue(any("missing ready-for-comparison key extraction" in error for error in errors))
|
|
self.assertTrue(any("missing csv output" in error for error in errors))
|
|
self.assertTrue(any("missing source file" in error for error in errors))
|
|
|
|
def test_ready_for_comparison_accepts_external_bundle_without_abaqus_command(self):
|
|
validator = load_validate_reference_artifacts()
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
root = Path(tmp)
|
|
model_dir, payload = create_ready_bundle(root)
|
|
payload["extraction"]["odb_sha256_file"] = "result.odb.sha256"
|
|
write_text(model_dir / "result.odb.sha256", "0" * 64 + " job.odb\n")
|
|
write_json(model_dir / "metadata.json", payload)
|
|
|
|
self.assertEqual(validator.validate_root(root), [])
|
|
|
|
def test_ready_for_comparison_checks_source_sha256(self):
|
|
validator = load_validate_reference_artifacts()
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
root = Path(tmp)
|
|
model_dir, payload = create_ready_bundle(root)
|
|
payload["subroutine"]["source_files"][0]["sha256"] = "0" * 64
|
|
write_json(model_dir / "metadata.json", payload)
|
|
|
|
errors = validator.validate_root(root)
|
|
|
|
self.assertTrue(any("sha256 mismatch" in error for error in errors))
|
|
|
|
def test_ready_for_comparison_rejects_csv_outside_extracted_directory(self):
|
|
validator = load_validate_reference_artifacts()
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
root = Path(tmp)
|
|
model_dir, payload = create_ready_bundle(root)
|
|
payload["outputs"]["csv"] = {"stresses": "stresses.csv"}
|
|
write_text(model_dir / "stresses.csv")
|
|
write_json(model_dir / "metadata.json", payload)
|
|
|
|
errors = validator.validate_root(root)
|
|
|
|
self.assertTrue(any("csv output stresses must match extracted/*.csv" in error for error in errors))
|
|
|
|
def test_ready_for_comparison_checks_optional_odb_sha256_file(self):
|
|
validator = load_validate_reference_artifacts()
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
root = Path(tmp)
|
|
model_dir, payload = create_ready_bundle(root)
|
|
payload["extraction"]["odb_sha256_file"] = "result.odb.sha256"
|
|
write_text(model_dir / "result.odb.sha256", "not-a-sha\n")
|
|
write_json(model_dir / "metadata.json", payload)
|
|
|
|
errors = validator.validate_root(root)
|
|
|
|
self.assertTrue(any("invalid odb_sha256_file" in error for error in errors))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|