Skip to content

Commit 3e1fe25

Browse files
author
Kevin Vu te Laar
committed
integration tests: added and enable test discovery
integration tests are now: - split from unit tests - improved to cover more cases - automatically executed upon discovery Integration tests must have the filename prefix `test` to be discovered. Signed-off-by: Kevin Vu te Laar <vu.te@rwth-aachen.de>
1 parent e3d072e commit 3e1fe25

3 files changed

Lines changed: 291 additions & 1 deletion

File tree

tests/integration/CMakeLists.txt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,12 @@ add_custom_target(run-integration-tests
1818
villas-hook
1919
)
2020
21-
add_dependencies(run-tests run-integration-tests)
21+
add_custom_target(run-python-integration-tests
22+
COMMAND
23+
python3 -m unittest discover ${CMAKE_CURRENT_SOURCE_DIR}
24+
DEPENDS
25+
python-binding
26+
USES_TERMINAL
27+
)
28+
29+
add_dependencies(run-tests run-integration-tests run-python-integration-tests)

tests/integration/python/__init__.py

Whitespace-only changes.
Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
"""
2+
Author: Kevin Vu te Laar <vu.te@rwth-aachen.de>
3+
SPDX-FileCopyrightText: 2014-2025 Institute for Automation of Complex Power Systems, RWTH Aachen University
4+
SPDX-License-Identifier: Apache-2.0
5+
""" # noqa: E501
6+
7+
import json
8+
import re
9+
import unittest
10+
import uuid
11+
import villas_node as vn
12+
13+
14+
class SimpleWrapperTests(unittest.TestCase):
15+
def setUp(self):
16+
try:
17+
self.node_uuid = str(uuid.uuid4())
18+
self.config = json.dumps(test_node_config, indent=2)
19+
self.test_node = vn.node_new(self.node_uuid, self.config)
20+
except Exception as e:
21+
self.fail(f"new_node err: {e}")
22+
23+
def tearDown(self):
24+
try:
25+
vn.node_stop(self.test_node)
26+
vn.node_destroy(self.test_node)
27+
except Exception as e:
28+
self.fail(f"node cleanup error: {e}")
29+
30+
def test_activity_changes(self):
31+
try:
32+
vn.node_check(self.test_node)
33+
vn.node_prepare(self.test_node)
34+
# starting twice
35+
self.assertEqual(0, vn.node_start(self.test_node))
36+
37+
# check if the node is running
38+
self.assertTrue(vn.node_is_enabled(self.test_node))
39+
40+
# pausing twice
41+
self.assertEqual(0, vn.node_pause(self.test_node))
42+
self.assertEqual(-1, vn.node_pause(self.test_node))
43+
44+
# resuming
45+
self.assertEqual(0, vn.node_resume(self.test_node))
46+
47+
# stopping twice
48+
self.assertEqual(0, vn.node_stop(self.test_node))
49+
self.assertEqual(0, vn.node_stop(self.test_node))
50+
51+
# restarting
52+
vn.node_restart(self.test_node)
53+
54+
# check if everything still works after restarting
55+
vn.node_pause(self.test_node)
56+
vn.node_resume(self.test_node)
57+
vn.node_stop(self.test_node)
58+
vn.node_start(self.test_node)
59+
except Exception as e:
60+
self.fail(f" err: {e}")
61+
62+
def test_reverse_node(self):
63+
try:
64+
self.assertEqual(1, vn.node_input_signals_max_cnt(self.test_node))
65+
self.assertEqual(0, vn.node_output_signals_max_cnt(self.test_node))
66+
67+
self.assertEqual(0, vn.node_reverse(self.test_node))
68+
69+
# input and output hooks/details are not reversed
70+
# input and output are reversed, can be seen with wireshark and
71+
# function test_rw_socket_and_reverse() below
72+
self.assertEqual(1, vn.node_input_signals_max_cnt(self.test_node))
73+
self.assertEqual(0, vn.node_output_signals_max_cnt(self.test_node))
74+
# self.assertEqual(0, vn.node_input_signals_max_cnt(self.test_node))
75+
# self.assertEqual(1, vn.node_output_signals_max_cnt(self.test_node))
76+
except Exception as e:
77+
self.fail(f"Reversing node in and output failed: {e}")
78+
79+
# Test whether or not a node can be recreated with the string from node_to_json_str
80+
# node_to_json_str has a wrong config format causing the config string to create
81+
# a node without a name
82+
# uuid can not match
83+
def test_config_from_string(self):
84+
try:
85+
config_str = vn.node_to_json_str(self.test_node)
86+
config_obj = json.loads(config_str)
87+
88+
config_copy_str = json.dumps(config_obj, indent=2)
89+
90+
test_node = vn.node_new("", config_copy_str)
91+
92+
self.assertEqual(
93+
re.sub(
94+
r"^[^:]+: uuid=[0-9a-fA-F-]+, ", "", vn.node_name_full(test_node)
95+
),
96+
re.sub(
97+
r"^[^:]+: uuid=[0-9a-fA-F-]+, ",
98+
"",
99+
vn.node_name_full(self.test_node),
100+
),
101+
)
102+
except Exception as e:
103+
self.fail(f" err: {e}")
104+
105+
def test_rw_socket_and_reverse(self):
106+
try:
107+
data_str = json.dumps(send_recv_test, indent=2)
108+
data = json.loads(data_str)
109+
110+
test_nodes = {}
111+
for name, content in data.items():
112+
obj = {name: content}
113+
config = json.dumps(obj, indent=2)
114+
id = str(uuid.uuid4())
115+
116+
test_nodes[name] = vn.node_new(id, config)
117+
118+
for node in test_nodes.values():
119+
if vn.node_check(node):
120+
raise RuntimeError("Failed to verify node configuration")
121+
if vn.node_prepare(node):
122+
raise RuntimeError(
123+
f"Failed to verify {vn.node_name(node)} node configuration"
124+
)
125+
vn.node_start(node)
126+
127+
# Arrays to store samples
128+
send_smpls = vn.smps_array(1)
129+
intmdt_smpls = vn.smps_array(100)
130+
recv_smpls = vn.smps_array(100)
131+
132+
for i in range(100):
133+
# send_smpls holds a new sample each time, but the old one still has a reference in the socket buffer (below)
134+
# it is necessary to allocate a new sample each time to send_smpls
135+
send_smpls[0] = vn.sample_alloc(2)
136+
intmdt_smpls[i] = vn.sample_alloc(2)
137+
recv_smpls[i] = vn.sample_alloc(2)
138+
139+
# Generate signals and send over send_socket
140+
self.assertEqual(
141+
vn.node_read(test_nodes["signal_generator"], send_smpls, 1), 1
142+
)
143+
self.assertEqual(
144+
vn.node_write(test_nodes["send_socket"], send_smpls, 1), 1
145+
)
146+
147+
# read received signals and send them to recv_socket
148+
self.assertEqual(
149+
vn.node_read(test_nodes["intmdt_socket"], intmdt_smpls, 100), 100
150+
)
151+
self.assertEqual(
152+
vn.node_write(test_nodes["intmdt_socket"], intmdt_smpls, 100), 100
153+
)
154+
155+
# confirm rev_socket signals
156+
self.assertEqual(
157+
vn.node_read(test_nodes["recv_socket"], recv_smpls, 100), 100
158+
)
159+
160+
# reversing in and outputs
161+
# stopping the socket is necessary to clean up buffers
162+
# starting the node again will bind the reversed socket addresses
163+
# this can be confirmed when observing network traffic
164+
# node details do not represent this properly as of now
165+
for node in test_nodes.values():
166+
vn.node_reverse(node)
167+
vn.node_stop(node)
168+
169+
for node in test_nodes.values():
170+
vn.node_start(node)
171+
172+
self.assertEqual(
173+
vn.node_write(test_nodes["recv_socket"], recv_smpls, 100), 100
174+
)
175+
self.assertEqual(
176+
vn.node_write(test_nodes["intmdt_socket"], intmdt_smpls, 100), 100
177+
)
178+
179+
# cleanup
180+
for node in test_nodes.values():
181+
vn.node_stop(node)
182+
vn.node_destroy(node)
183+
184+
except Exception as e:
185+
self.fail(f" err: {e}")
186+
187+
188+
test_node_config = {
189+
"test_node": {
190+
"type": "socket",
191+
"format": "villas.binary",
192+
"layer": "udp",
193+
"in": {
194+
"address": "*:12000",
195+
"signals": [{"name": "tap_position", "type": "integer", "init": 0}],
196+
},
197+
"out": {"address": "127.0.0.1:12001"},
198+
}
199+
}
200+
201+
send_recv_test = {
202+
"send_socket": {
203+
"type": "socket",
204+
"format": "protobuf",
205+
"layer": "udp",
206+
"in": {
207+
"address": "127.0.0.1:65532",
208+
"signals": [
209+
{"name": "voltage", "type": "float", "unit": "V"},
210+
{"name": "current", "type": "float", "unit": "A"},
211+
],
212+
},
213+
"out": {
214+
"address": "127.0.0.1:65533",
215+
"netem": {"enabled": False},
216+
"multicast": {"enabled": False},
217+
},
218+
},
219+
"intmdt_socket": {
220+
"type": "socket",
221+
"format": "protobuf",
222+
"layer": "udp",
223+
"in": {
224+
"address": "127.0.0.1:65533",
225+
"signals": [
226+
{"name": "voltage", "type": "float", "unit": "V"},
227+
{"name": "current", "type": "float", "unit": "A"},
228+
],
229+
},
230+
"out": {
231+
"address": "127.0.0.1:65534",
232+
"netem": {"enabled": False},
233+
"multicast": {"enabled": False},
234+
},
235+
},
236+
"recv_socket": {
237+
"type": "socket",
238+
"format": "protobuf",
239+
"layer": "udp",
240+
"in": {
241+
"address": "127.0.0.1:65534",
242+
"signals": [
243+
{"name": "voltage", "type": "float", "unit": "V"},
244+
{"name": "current", "type": "float", "unit": "A"},
245+
],
246+
},
247+
"out": {
248+
"address": "127.0.0.1:65535",
249+
"netem": {"enabled": False},
250+
"multicast": {"enabled": False},
251+
},
252+
},
253+
"signal_generator": {
254+
"type": "signal.v2",
255+
"limit": 100,
256+
"rate": 10,
257+
"in": {
258+
"signals": [
259+
{
260+
"amplitude": 2,
261+
"name": "voltage",
262+
"phase": 90,
263+
"signal": "sine",
264+
"type": "float",
265+
"unit": "V",
266+
},
267+
{
268+
"amplitude": 1,
269+
"name": "current",
270+
"phase": 0,
271+
"signal": "sine",
272+
"type": "float",
273+
"unit": "A",
274+
},
275+
],
276+
"hooks": [{"type": "print", "format": "villas.human"}],
277+
},
278+
},
279+
}
280+
281+
if __name__ == "__main__":
282+
unittest.main()

0 commit comments

Comments
 (0)