Skip to content

Commit 8a722b2

Browse files
CI: Unit tests && fix bugs.
1 parent ab12d1f commit 8a722b2

17 files changed

Lines changed: 653 additions & 6 deletions

.github/ISSUE_TEMPLATE/bug.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ body:
2727
id: AmritaSense-version
2828
attributes:
2929
label: AmritaSense Version
30-
description: Check with `pip show amrita_core`
30+
description: Check with `pip show amrita_sense`
3131
placeholder: 0.1.0
3232
validations:
3333
required: true

.github/ISSUE_TEMPLATE/question.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ body:
2727
id: AmritaSense-version
2828
attributes:
2929
label: AmritaSense Version
30-
description: Check with `pip show amrita_core` command
30+
description: Check with `pip show amrita_sense` command
3131
placeholder: 0.1.0
3232
validations:
3333
required: false

.github/workflows/CI.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,7 @@ jobs:
4747
pylance-version: latest-release
4848

4949
- name: Run Unit Tests with JUnit XML output
50-
run: uv run pytest tests/ --cov=src/amrita_core --cov-report=term-missing
51-
--cov-report=xml --junitxml=test-results.xml -v
50+
run: uv run pytest tests/ --cov=src/amrita_sense --cov-report=term-missing --cov-report=xml --junitxml=test-results.xml -v
5251

5352
- name: Publish Test Report
5453
uses: dorny/test-reporter@v1

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,3 +361,6 @@ test-results.xml
361361
# Editor
362362
*.swp
363363

364+
# Pytest
365+
366+
test-results.xml

src/amrita_sense/types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ def offset_far(self, offset: list[int]) -> Self:
167167
"""
168168
with self._lock:
169169
offset = offset.copy()
170-
ptr: list[int] = self.base_addr
170+
ptr: list[int] = self.base_addr.copy()
171171
len_off: int = len(offset)
172172
len_ptr: int = len(ptr)
173173
len_diff: int = len_ptr - len_off

tests/test_alias.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from amrita_sense.instructions.alias import ALIAS, AliasNode
2+
from amrita_sense.node.wrapper import Node as NodeDecorator
3+
4+
5+
class TestAliasNode:
6+
"""Test cases for the AliasNode class."""
7+
8+
def test_alias_node_creation(self):
9+
"""Test creating an AliasNode with a wrapped node."""
10+
11+
@NodeDecorator()
12+
def original_node():
13+
return "original"
14+
15+
alias_node = AliasNode(original_node, "my_alias")
16+
17+
assert alias_node.node is original_node
18+
assert alias_node.alias == "my_alias"
19+
assert alias_node.address_able is True
20+
21+
def test_alias_node_execution(self):
22+
"""Test that AliasNode executes the wrapped node correctly."""
23+
24+
@NodeDecorator()
25+
def original_node():
26+
return "wrapped_result"
27+
28+
alias_node = AliasNode(original_node, "test_alias")
29+
result = alias_node()
30+
assert result == "wrapped_result"
31+
32+
def test_alias_node_with_arguments(self):
33+
"""Test AliasNode execution with arguments."""
34+
35+
@NodeDecorator()
36+
def original_node(x: int, y: str) -> str:
37+
return f"{x}:{y}"
38+
39+
alias_node = AliasNode(original_node, "arg_alias")
40+
result = alias_node(42, "test")
41+
assert result == "42:test"
42+
43+
44+
class TestALIAS:
45+
"""Test cases for the ALIAS instruction."""
46+
47+
def test_alias_instruction_creation(self):
48+
"""Test creating an ALIAS instruction."""
49+
50+
@NodeDecorator()
51+
def target_node():
52+
return "target"
53+
54+
alias_instruction = ALIAS(target_node, "my_alias_name")
55+
56+
assert isinstance(alias_instruction, AliasNode)
57+
assert alias_instruction.alias == "my_alias_name"
58+
assert alias_instruction.node is target_node

tests/test_core_nodes.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
from amrita_sense.node.core import BaseNode, Node, NodeCompose, NodeComposeRendered
2+
from amrita_sense.node.wrapper import Node as NodeDecorator
3+
4+
5+
class TestBaseNode:
6+
"""Test cases for the BaseNode class."""
7+
8+
def test_base_node_direct_instantiation(self):
9+
"""Test that BaseNode can be instantiated directly with required parameters."""
10+
import inspect
11+
12+
def dummy_func():
13+
return "dummy"
14+
15+
frame = inspect.currentframe()
16+
node = BaseNode()
17+
node._init(
18+
func=dummy_func,
19+
tag="test_node",
20+
wrap_to_async=True,
21+
address_able=False,
22+
frame=frame,
23+
)
24+
25+
assert node.func == dummy_func
26+
assert node.tag == "test_node"
27+
assert node.wrap_to_async is True
28+
assert node.address_able is False
29+
30+
31+
class TestNode:
32+
"""Test cases for the Node class."""
33+
34+
def test_node_creation_with_decorator(self):
35+
"""Test creating a Node using the @Node decorator."""
36+
37+
@NodeDecorator()
38+
def simple_function():
39+
return 42
40+
41+
assert isinstance(simple_function, Node)
42+
assert simple_function() == 42
43+
44+
def test_node_creation_with_parameters(self):
45+
"""Test creating a Node with custom parameters."""
46+
47+
@NodeDecorator(tag="custom_tag", wrap_to_async=False, address_able=True)
48+
def parameterized_function(x: int) -> int:
49+
return x * 2
50+
51+
node = parameterized_function
52+
assert node.tag == "custom_tag"
53+
assert node.wrap_to_async is False
54+
assert node.address_able is True
55+
assert node(5) == 10
56+
57+
def test_node_with_arguments(self):
58+
"""Test Node execution with arguments."""
59+
60+
@NodeDecorator()
61+
def function_with_args(a: int, b: str = "default") -> str:
62+
return f"{a}:{b}"
63+
64+
result = function_with_args(42, "test")
65+
assert result == "42:test"
66+
67+
result_default = function_with_args(42)
68+
assert result_default == "42:default"
69+
70+
71+
class TestNodeCompose:
72+
"""Test cases for the NodeCompose class."""
73+
74+
def test_node_compose_creation(self):
75+
"""Test creating a NodeCompose with multiple nodes."""
76+
77+
@NodeDecorator()
78+
def node1():
79+
return "node1"
80+
81+
@NodeDecorator()
82+
def node2():
83+
return "node2"
84+
85+
compose = NodeCompose(node1, node2)
86+
assert len(compose._graph) == 2
87+
assert compose._graph[0] is node1
88+
assert compose._graph[1] is node2
89+
90+
def test_node_compose_empty(self):
91+
"""Test creating an empty NodeCompose."""
92+
compose = NodeCompose()
93+
assert len(compose._graph) == 0
94+
95+
def test_node_compose_render(self):
96+
"""Test that NodeCompose can be rendered."""
97+
98+
@NodeDecorator()
99+
def test_node():
100+
return "test"
101+
102+
compose = NodeCompose(test_node)
103+
rendered = compose.render()
104+
assert isinstance(rendered, NodeComposeRendered)
105+
106+
107+
class TestNodeComposeRendered:
108+
"""Test cases for the NodeComposeRendered class."""
109+
110+
def test_node_compose_rendered_creation(self):
111+
"""Test creating a NodeComposeRendered instance via render()."""
112+
113+
@NodeDecorator()
114+
def test_node():
115+
return "test"
116+
117+
workflow = NodeCompose(test_node)
118+
rendered = workflow.render()
119+
120+
rendered.alias2vector_map

tests/test_deps_extra.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import pytest
2+
3+
from amrita_sense.runtime.deps import ADDR, FAR_OFFSET, NEAR_OFFSET, POINTER_DEPENDS
4+
from amrita_sense.types import PointerVector
5+
6+
7+
class _FakePointer:
8+
def __init__(self, pointer, alias_map):
9+
self._pointer = PointerVector(pointer)
10+
self._alias_map = alias_map
11+
12+
def find_addr_alias(self, name):
13+
return self._alias_map[name]
14+
15+
16+
def test_pointer_depends_returns_same():
17+
pc = object()
18+
assert POINTER_DEPENDS(pc) is pc
19+
20+
21+
def test_addr_returns_pointervector():
22+
pc = _FakePointer([1, 2], {"t": [1, 2]})
23+
addr = ADDR("t")(pc)
24+
assert isinstance(addr, PointerVector)
25+
assert addr == PointerVector([1, 2])
26+
27+
28+
def test_far_offset_computation():
29+
pc = _FakePointer([3, 4], {"t": [1, 1]})
30+
res = FAR_OFFSET("t")(pc)
31+
assert isinstance(res, PointerVector)
32+
assert res == PointerVector([2, 3])
33+
34+
35+
def test_near_offset_same_level():
36+
pc = _FakePointer([1, 5], {"t": [1, 2]})
37+
# When the target alias is at the same nesting level, the near offset
38+
# should return the last dimension difference without raising.
39+
assert NEAR_OFFSET("t")(pc) == 3
40+
41+
42+
def test_near_offset_different_level_raises():
43+
pc = _FakePointer([2, 5], {"t": [1, 2, 0]})
44+
# A far offset in the higher dimensions is considered invalid for NEAR_OFFSET.
45+
with pytest.raises(RuntimeError):
46+
NEAR_OFFSET("t")(pc)

tests/test_exceptions.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
from amrita_sense.exceptions import (
2+
BreakLoop,
3+
DependsException,
4+
DependsInjectFailed,
5+
DependsResolveFailed,
6+
InterruptNotice,
7+
NullPointerException,
8+
)
9+
10+
11+
class TestExceptions:
12+
"""Test cases for AmritaSense exception hierarchy."""
13+
14+
def test_interrupt_notice_inheritance(self):
15+
"""Test that InterruptNotice inherits from BaseException."""
16+
exc = InterruptNotice("test message")
17+
assert isinstance(exc, BaseException)
18+
assert not isinstance(exc, Exception)
19+
assert str(exc) == "InterruptNotice test message"
20+
21+
def test_interrupt_notice_no_message(self):
22+
"""Test InterruptNotice with no message."""
23+
exc = InterruptNotice()
24+
assert str(exc) == "InterruptNotice "
25+
26+
def test_null_pointer_exception(self):
27+
"""Test NullPointerException basic functionality."""
28+
exc = NullPointerException()
29+
assert isinstance(exc, Exception)
30+
31+
def test_break_loop_exception(self):
32+
"""Test BreakLoop exception."""
33+
exc = BreakLoop()
34+
assert isinstance(exc, Exception)
35+
36+
def test_depends_exception_hierarchy(self):
37+
"""Test DependsException inheritance hierarchy."""
38+
base_exc = DependsException()
39+
resolve_exc = DependsResolveFailed()
40+
inject_exc = DependsInjectFailed()
41+
42+
# All should inherit from their respective bases
43+
assert isinstance(resolve_exc, DependsResolveFailed)
44+
assert isinstance(inject_exc, DependsInjectFailed)
45+
assert isinstance(base_exc, DependsException)
46+
47+
# DependsException should inherit from Exception
48+
assert isinstance(base_exc, Exception)

tests/test_if_extra.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from amrita_sense.instructions.if_clause import IF
2+
from amrita_sense.instructions.workfl_ctrl import NOP
3+
from amrita_sense.node.wrapper import Node as NodeDecorator
4+
5+
6+
def _make_node(ret):
7+
@NodeDecorator()
8+
def fn():
9+
return ret
10+
11+
return fn
12+
13+
14+
def test_if_simple_extract():
15+
"""Simple IF clause should render a 3-chunk plus NOP composition."""
16+
cond = _make_node(True)
17+
action = _make_node("ok")
18+
19+
if_clause = IF(cond, action)
20+
extracted = if_clause.extract()
21+
22+
# Base IF chunk: ConditionJumpNode, condition, do, then NOP
23+
assert len(extracted._graph) == 4
24+
assert extracted._graph[-1] is NOP
25+
26+
27+
def test_if_with_elif_else_extract():
28+
"""IF with ELIF chains and ELSE should produce expanded chunks and final NOP."""
29+
cond1 = _make_node(False)
30+
do1 = _make_node(1)
31+
cond2 = _make_node(False)
32+
do2 = _make_node(2)
33+
else_do = _make_node(3)
34+
35+
composed = IF(cond1, do1).ELIF(cond2, do2).ELSE(else_do)
36+
extracted = composed.extract()
37+
38+
# There should be more than the base 4 elements and end with NOP
39+
assert len(extracted._graph) >= 7
40+
assert extracted._graph[-1] is NOP

0 commit comments

Comments
 (0)