428 lines
12 KiB
Python
428 lines
12 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""Tests for GX/LAIN text extraction in gx_lain/runtime.py
|
||
|
|
|
||
|
|
Tests verify:
|
||
|
|
- normalize_segments extracts text from decompressed payload via byte ranges
|
||
|
|
- _infer_lane assigns correct lanes (0-7) based on segment metadata
|
||
|
|
- _infer_semantic_role returns correct role strings
|
||
|
|
- map_lanes organizes segments into 8 lanes
|
||
|
|
- build_envelope constructs proper ExecutionEnvelope
|
||
|
|
- fuse_lanes merges lane results correctly
|
||
|
|
- compute_resonance computes per-lane resonance
|
||
|
|
- render_output_text formats text by cognitive_mode
|
||
|
|
- make_error constructs structured error dicts
|
||
|
|
"""
|
||
|
|
|
||
|
|
import sys
|
||
|
|
import json
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
sys.path.insert(0, str(Path.cwd()))
|
||
|
|
|
||
|
|
from gx_lain.runtime import (
|
||
|
|
normalize_segments,
|
||
|
|
_infer_lane,
|
||
|
|
_infer_semantic_role,
|
||
|
|
map_lanes,
|
||
|
|
build_envelope,
|
||
|
|
fuse_lanes,
|
||
|
|
compute_resonance,
|
||
|
|
render_output_text,
|
||
|
|
make_error,
|
||
|
|
)
|
||
|
|
from gx_compiler.compressor import GXCompressor
|
||
|
|
|
||
|
|
|
||
|
|
def test_normalize_segments_text_extraction():
|
||
|
|
"""Test that normalize_segments extracts correct text via byte ranges."""
|
||
|
|
source = "def hello():\n print('world')\n\nresult = 42"
|
||
|
|
compressed = GXCompressor.compress(source)
|
||
|
|
|
||
|
|
manifest = {
|
||
|
|
"source_file": "test.py",
|
||
|
|
"version": "1.0.0",
|
||
|
|
"segments": [
|
||
|
|
{"id": "seg_0", "start": 0, "end": 2, "start_byte": 0, "end_byte": 27},
|
||
|
|
{"id": "seg_1", "start": 2, "end": 3, "start_byte": 28, "end_byte": 41},
|
||
|
|
],
|
||
|
|
}
|
||
|
|
|
||
|
|
raw_segments = manifest["segments"]
|
||
|
|
result = normalize_segments(manifest, raw_segments, compressed)
|
||
|
|
|
||
|
|
if len(result) != 2:
|
||
|
|
print(f"FAIL: Expected 2 segments, got {len(result)}")
|
||
|
|
return False
|
||
|
|
|
||
|
|
seg0 = result[0]
|
||
|
|
seg1 = result[1]
|
||
|
|
|
||
|
|
if "hello" not in seg0["text"]:
|
||
|
|
print(f"FAIL: seg_0 should contain 'hello', got: {repr(seg0['text'])}")
|
||
|
|
return False
|
||
|
|
|
||
|
|
if "result" not in seg1["text"]:
|
||
|
|
print(f"FAIL: seg_1 should contain 'result', got: {repr(seg1['text'])}")
|
||
|
|
return False
|
||
|
|
|
||
|
|
print("PASS: Text extraction via byte ranges works correctly")
|
||
|
|
return True
|
||
|
|
|
||
|
|
|
||
|
|
def test_normalize_segments_fallback():
|
||
|
|
"""Test normalize_segments fallback when no text is extracted."""
|
||
|
|
manifest = {"source_file": "test.py", "version": "1.0.0", "segments": []}
|
||
|
|
compressed = GXCompressor.compress("test")
|
||
|
|
|
||
|
|
result = normalize_segments(manifest, [], compressed)
|
||
|
|
|
||
|
|
if result:
|
||
|
|
print(f"FAIL: Expected empty result for empty segments, got {len(result)}")
|
||
|
|
return False
|
||
|
|
|
||
|
|
print("PASS: Empty segments returns empty list")
|
||
|
|
return True
|
||
|
|
|
||
|
|
|
||
|
|
def test_normalize_segments_corrupt_payload():
|
||
|
|
"""Test normalize_segments handles corrupt payload gracefully."""
|
||
|
|
manifest = {
|
||
|
|
"source_file": "test.py",
|
||
|
|
"version": "1.0.0",
|
||
|
|
"segments": [
|
||
|
|
{"id": "seg_0", "start": 0, "end": 1, "start_byte": 0, "end_byte": 10}
|
||
|
|
],
|
||
|
|
}
|
||
|
|
|
||
|
|
result = normalize_segments(manifest, manifest["segments"], b"corrupt")
|
||
|
|
|
||
|
|
if len(result) != 1:
|
||
|
|
print(f"FAIL: Expected 1 segment even with corrupt payload, got {len(result)}")
|
||
|
|
return False
|
||
|
|
|
||
|
|
if result[0]["text"] and "segment" not in result[0]["text"]:
|
||
|
|
print(f"FAIL: Expected fallback text, got: {repr(result[0]['text'])}")
|
||
|
|
return False
|
||
|
|
|
||
|
|
print("PASS: Corrupt payload handled gracefully with fallback text")
|
||
|
|
return True
|
||
|
|
|
||
|
|
|
||
|
|
def test_infer_lane_structural():
|
||
|
|
"""Test lane inference for structural segments."""
|
||
|
|
assert _infer_lane({"id": "def_main"}) == 0, "def should be lane 0"
|
||
|
|
assert _infer_lane({"id": "class_Foo"}) == 0, "class should be lane 0"
|
||
|
|
assert _infer_lane({"id": "struct_header"}) == 0, "struct should be lane 0"
|
||
|
|
|
||
|
|
print("PASS: Structural segments correctly mapped to lane 0")
|
||
|
|
return True
|
||
|
|
|
||
|
|
|
||
|
|
def test_infer_lane_default():
|
||
|
|
"""Test lane inference returns default for unknown segments."""
|
||
|
|
lane = _infer_lane({"id": "some_random_segment"})
|
||
|
|
|
||
|
|
if lane != 1:
|
||
|
|
print(f"FAIL: Expected lane 1 (default), got {lane}")
|
||
|
|
return False
|
||
|
|
|
||
|
|
print("PASS: Unclassified segments default to lane 1")
|
||
|
|
return True
|
||
|
|
|
||
|
|
|
||
|
|
def test_infer_lane_all_types():
|
||
|
|
"""Test all lane types are correctly inferred."""
|
||
|
|
cases = [
|
||
|
|
({"id": "comment_block"}, 3),
|
||
|
|
({"id": "annotation_section"}, 3),
|
||
|
|
({"id": "hint_optimization"}, 4),
|
||
|
|
({"id": "meta_author"}, 6),
|
||
|
|
({"id": "epoch_v1"}, 7),
|
||
|
|
({"id": "version_2_0"}, 7),
|
||
|
|
]
|
||
|
|
|
||
|
|
for segment, expected in cases:
|
||
|
|
actual = _infer_lane(segment)
|
||
|
|
if actual != expected:
|
||
|
|
print(f"FAIL: {segment['id']} → lane {actual}, expected {expected}")
|
||
|
|
return False
|
||
|
|
|
||
|
|
print("PASS: All lane types correctly inferred")
|
||
|
|
return True
|
||
|
|
|
||
|
|
|
||
|
|
def test_infer_semantic_role():
|
||
|
|
"""Test semantic role inference for various segment types."""
|
||
|
|
cases = [
|
||
|
|
({"id": "def_calculate"}, "definition"),
|
||
|
|
({"id": "class_User"}, "definition"),
|
||
|
|
({"id": "constraint_validation"}, "constraint"),
|
||
|
|
({"id": "example_usage"}, "example"),
|
||
|
|
({"id": "meta_description"}, "meta"),
|
||
|
|
({"id": "comment_note"}, "meta"),
|
||
|
|
({"id": "unknown_block"}, "unknown"),
|
||
|
|
]
|
||
|
|
|
||
|
|
for segment, expected in cases:
|
||
|
|
actual = _infer_semantic_role(segment)
|
||
|
|
if actual != expected:
|
||
|
|
print(f"FAIL: {segment['id']} → {actual}, expected {expected}")
|
||
|
|
return False
|
||
|
|
|
||
|
|
print("PASS: All semantic roles correctly inferred")
|
||
|
|
return True
|
||
|
|
|
||
|
|
|
||
|
|
def test_map_lanes():
|
||
|
|
"""Test mapping segments into 8 lanes."""
|
||
|
|
segments = [
|
||
|
|
{"id": "s1", "symbolic_lane": 0, "text": "struct"},
|
||
|
|
{"id": "s2", "symbolic_lane": 1, "text": "flow"},
|
||
|
|
{"id": "s3", "symbolic_lane": 5, "text": "predict"},
|
||
|
|
{"id": "s4", "symbolic_lane": 7, "text": "epoch"},
|
||
|
|
]
|
||
|
|
|
||
|
|
lanes = map_lanes(segments)
|
||
|
|
|
||
|
|
if len(lanes) != 8:
|
||
|
|
print(f"FAIL: Expected 8 lanes, got {len(lanes)}")
|
||
|
|
return False
|
||
|
|
|
||
|
|
if len(lanes[0]) != 1 or len(lanes[1]) != 1:
|
||
|
|
print("FAIL: Segments not properly assigned")
|
||
|
|
return False
|
||
|
|
|
||
|
|
if lanes[0][0]["id"] != "s1":
|
||
|
|
print("FAIL: Lane 0 has wrong segment")
|
||
|
|
return False
|
||
|
|
|
||
|
|
print("PASS: Segments correctly mapped to 8 lanes")
|
||
|
|
return True
|
||
|
|
|
||
|
|
|
||
|
|
def test_map_lanes_clamp():
|
||
|
|
"""Test that lane values outside 0-7 are clamped."""
|
||
|
|
segments = [
|
||
|
|
{"id": "s1", "symbolic_lane": -1, "text": "neg"},
|
||
|
|
{"id": "s2", "symbolic_lane": 10, "text": "over"},
|
||
|
|
]
|
||
|
|
|
||
|
|
lanes = map_lanes(segments)
|
||
|
|
# -1 clamped to 0, 10 clamped to 7
|
||
|
|
if len(lanes[0]) != 1:
|
||
|
|
print("FAIL: Negative lane not clamped to 0")
|
||
|
|
return False
|
||
|
|
|
||
|
|
if len(lanes[7]) != 1:
|
||
|
|
print("FAIL: Over-max lane not clamped to 7")
|
||
|
|
return False
|
||
|
|
|
||
|
|
print("PASS: Lane clamping works for values outside 0-7")
|
||
|
|
return True
|
||
|
|
|
||
|
|
|
||
|
|
def test_build_envelope():
|
||
|
|
"""Test envelope construction."""
|
||
|
|
manifest = {"source_file": "test.py", "version": "1.0.0", "contributor": "tester"}
|
||
|
|
lanes = {0: [{"id": "s1", "text": "test"}]}
|
||
|
|
payload = b"test"
|
||
|
|
|
||
|
|
envelope = build_envelope(manifest, lanes, payload, context={"cognitive_mode": "analyze"})
|
||
|
|
|
||
|
|
if envelope["manifest"] != manifest:
|
||
|
|
print("FAIL: Manifest not preserved")
|
||
|
|
return False
|
||
|
|
|
||
|
|
if envelope["lanes"] != lanes:
|
||
|
|
print("FAIL: Lanes not preserved")
|
||
|
|
return False
|
||
|
|
|
||
|
|
if envelope["payload"] != payload:
|
||
|
|
print("FAIL: Payload not preserved")
|
||
|
|
return False
|
||
|
|
|
||
|
|
ctx = envelope["context"]
|
||
|
|
if ctx["cognitive_mode"] != "analyze":
|
||
|
|
print("FAIL: cognitive_mode not passed through")
|
||
|
|
return False
|
||
|
|
|
||
|
|
if ctx["contributor"] != "tester":
|
||
|
|
print("FAIL: contributor not from manifest")
|
||
|
|
return False
|
||
|
|
|
||
|
|
if "invocation_id" not in ctx:
|
||
|
|
print("FAIL: missing invocation_id")
|
||
|
|
return False
|
||
|
|
|
||
|
|
print("PASS: Envelope constructed with all fields")
|
||
|
|
return True
|
||
|
|
|
||
|
|
|
||
|
|
def test_fuse_lanes():
|
||
|
|
"""Test lane fusion combines results correctly."""
|
||
|
|
lane_results = {
|
||
|
|
0: {
|
||
|
|
"summary": "Structural analysis",
|
||
|
|
"key_points": ["Point A", "Point B"],
|
||
|
|
"constraints": ["Constraint 1"],
|
||
|
|
"open_questions": ["Question 1"],
|
||
|
|
},
|
||
|
|
1: {
|
||
|
|
"summary": "Flow analysis",
|
||
|
|
"key_points": ["Point C"],
|
||
|
|
"constraints": [],
|
||
|
|
"open_questions": [],
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
fused = fuse_lanes(lane_results)
|
||
|
|
|
||
|
|
if "Structural" not in fused["summary"] or "Flow" not in fused["summary"]:
|
||
|
|
print(f"FAIL: Summaries not merged: {fused['summary']}")
|
||
|
|
return False
|
||
|
|
|
||
|
|
if len(fused["key_points"]) != 3:
|
||
|
|
print(f"FAIL: Expected 3 key points, got {len(fused['key_points'])}")
|
||
|
|
return False
|
||
|
|
|
||
|
|
if "Constraint 1" not in fused["constraints"]:
|
||
|
|
print("FAIL: Constraint not merged")
|
||
|
|
return False
|
||
|
|
|
||
|
|
print("PASS: Lane results fused correctly")
|
||
|
|
return True
|
||
|
|
|
||
|
|
|
||
|
|
def test_compute_resonance():
|
||
|
|
"""Test resonance computation."""
|
||
|
|
lane_results = {
|
||
|
|
0: {"summary": "Has content"},
|
||
|
|
1: {"summary": ""},
|
||
|
|
2: {"summary": "More content"},
|
||
|
|
}
|
||
|
|
|
||
|
|
resonance = compute_resonance(lane_results, {})
|
||
|
|
|
||
|
|
if resonance["lane_0"] != 1.0:
|
||
|
|
print(f"FAIL: Lane 0 should have resonance 1.0, got {resonance['lane_0']}")
|
||
|
|
return False
|
||
|
|
|
||
|
|
if resonance["lane_1"] != 0.0:
|
||
|
|
print(f"FAIL: Lane 1 should have resonance 0.0, got {resonance['lane_1']}")
|
||
|
|
return False
|
||
|
|
|
||
|
|
if resonance["lane_2"] != 1.0:
|
||
|
|
print(f"FAIL: Lane 2 should have resonance 1.0, got {resonance['lane_2']}")
|
||
|
|
return False
|
||
|
|
|
||
|
|
print("PASS: Resonance correctly computed")
|
||
|
|
return True
|
||
|
|
|
||
|
|
|
||
|
|
def test_render_output_text():
|
||
|
|
"""Test output text rendering for different modes."""
|
||
|
|
fused = {
|
||
|
|
"summary": "Test summary",
|
||
|
|
"key_points": ["Key 1", "Key 2"],
|
||
|
|
"constraints": ["Const 1"],
|
||
|
|
"open_questions": ["Q1"],
|
||
|
|
}
|
||
|
|
|
||
|
|
text = render_output_text(fused, {"cognitive_mode": "analyze"})
|
||
|
|
|
||
|
|
if "[ANALYZE]" not in text:
|
||
|
|
print(f"FAIL: Missing mode header in: {text}")
|
||
|
|
return False
|
||
|
|
|
||
|
|
if "Test summary" not in text:
|
||
|
|
print(f"FAIL: Missing summary in: {text}")
|
||
|
|
return False
|
||
|
|
|
||
|
|
if "Key 1" not in text:
|
||
|
|
print(f"FAIL: Missing key points in: {text}")
|
||
|
|
return False
|
||
|
|
|
||
|
|
if "Const 1" not in text:
|
||
|
|
print(f"FAIL: Missing constraints in: {text}")
|
||
|
|
return False
|
||
|
|
|
||
|
|
if "Q1" not in text:
|
||
|
|
print(f"FAIL: Missing open questions in: {text}")
|
||
|
|
return False
|
||
|
|
|
||
|
|
print("PASS: Output text rendered correctly")
|
||
|
|
return True
|
||
|
|
|
||
|
|
|
||
|
|
def test_make_error():
|
||
|
|
"""Test structured error construction."""
|
||
|
|
err = make_error("DecodeError", "Something failed", segment_id="seg_0", lane=1, recoverable=True)
|
||
|
|
|
||
|
|
if err["type"] != "DecodeError":
|
||
|
|
print("FAIL: Error type mismatch")
|
||
|
|
return False
|
||
|
|
|
||
|
|
if err["message"] != "Something failed":
|
||
|
|
print("FAIL: Error message mismatch")
|
||
|
|
return False
|
||
|
|
|
||
|
|
if err["segment_id"] != "seg_0":
|
||
|
|
print("FAIL: Error segment_id mismatch")
|
||
|
|
return False
|
||
|
|
|
||
|
|
if err["lane"] != 1:
|
||
|
|
print("FAIL: Error lane mismatch")
|
||
|
|
return False
|
||
|
|
|
||
|
|
if err["recoverable"] is not True:
|
||
|
|
print("FAIL: Error recoverable mismatch")
|
||
|
|
return False
|
||
|
|
|
||
|
|
print("PASS: Structured error constructed correctly")
|
||
|
|
return True
|
||
|
|
|
||
|
|
|
||
|
|
def main_test():
|
||
|
|
print("[TEST SUITE] test_text_extraction.py")
|
||
|
|
print()
|
||
|
|
|
||
|
|
tests = [
|
||
|
|
("Text extraction via byte ranges", test_normalize_segments_text_extraction),
|
||
|
|
("Empty segments fallback", test_normalize_segments_fallback),
|
||
|
|
("Corrupt payload handling", test_normalize_segments_corrupt_payload),
|
||
|
|
("Structural lane inference", test_infer_lane_structural),
|
||
|
|
("Default lane inference", test_infer_lane_default),
|
||
|
|
("All lane types", test_infer_lane_all_types),
|
||
|
|
("Semantic role inference", test_infer_semantic_role),
|
||
|
|
("Map lanes", test_map_lanes),
|
||
|
|
("Lane clamping", test_map_lanes_clamp),
|
||
|
|
("Build envelope", test_build_envelope),
|
||
|
|
("Fuse lanes", test_fuse_lanes),
|
||
|
|
("Compute resonance", test_compute_resonance),
|
||
|
|
("Render output text", test_render_output_text),
|
||
|
|
("Make error", test_make_error),
|
||
|
|
]
|
||
|
|
|
||
|
|
passed = 0
|
||
|
|
failed = 0
|
||
|
|
|
||
|
|
for name, test_func in tests:
|
||
|
|
print(f"Running: {name}...", end=" ")
|
||
|
|
try:
|
||
|
|
if test_func():
|
||
|
|
passed += 1
|
||
|
|
else:
|
||
|
|
failed += 1
|
||
|
|
except Exception as e:
|
||
|
|
print(f"FAIL: {e}")
|
||
|
|
failed += 1
|
||
|
|
|
||
|
|
print()
|
||
|
|
print(f"Results: {passed} passed, {failed} failed")
|
||
|
|
|
||
|
|
return 0 if failed == 0 else 1
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
sys.exit(main_test())
|