Skip to content

Commit 7a9dc29

Browse files
author
Kevin Vu te Laar
committed
binding tests: add binding wrapper tests
- CI/CD integration of binding wrapper tests. - Add unit and integration tests for binding wrapper. Signed-off-by: Kevin Vu te Laar <vu.te@rwth-aachen.de>
1 parent be2b770 commit 7a9dc29

5 files changed

Lines changed: 556 additions & 13 deletions

File tree

.gitlab-ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ test:unit:
173173
image: ${DOCKER_IMAGE_DEV}:${DOCKER_TAG}
174174
script:
175175
- cmake -S . -B build ${CMAKE_OPTS}
176-
- export PYTHONPATH=$PYTHONPATH:${PWD}/build/clients/python-binding
176+
- export PYTHONPATH=$PYTHONPATH:${PWD}/build/clients/python-binding/:${PWD}/python/villas/node/
177177
- cmake --build build ${CMAKE_BUILD_OPTS} --target run-unit-tests run-unit-tests-common run-python-binding-tests
178178
needs:
179179
- job: "build:source: [fedora]"
Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
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 binding as vn
12+
13+
14+
class BindingWrapperIntegrationTests(unittest.TestCase):
15+
def setUp(self):
16+
try:
17+
self.config = json.dumps(test_node_config, indent=2)
18+
self.node_uuid = str(uuid.uuid4())
19+
self.test_node = vn.node_new(self.config, self.node_uuid)
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(config, id)
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.SamplesArray(1)
129+
intmdt_smpls = vn.SamplesArray(100)
130+
recv_smpls = vn.SamplesArray(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, 2, 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, 2, 100), 100
150+
)
151+
self.assertEqual(
152+
vn.node_write(test_nodes["intmdt_socket"], intmdt_smpls[0:50], 50), 50
153+
)
154+
self.assertEqual(
155+
vn.node_write(test_nodes["intmdt_socket"], intmdt_smpls[50:100], 50), 50
156+
)
157+
158+
# confirm rev_socket signals
159+
self.assertEqual(
160+
vn.node_read(test_nodes["recv_socket"], recv_smpls[0:50], 2, 50), 50
161+
)
162+
self.assertEqual(
163+
vn.node_read(test_nodes["recv_socket"], recv_smpls[50:100], 2, 50), 50
164+
)
165+
166+
# reversing in and outputs
167+
# stopping the socket is necessary to clean up buffers
168+
# starting the node again will bind the reversed socket addresses
169+
# this can be confirmed when observing network traffic
170+
# node details do not represent this properly as of now
171+
for node in test_nodes.values():
172+
vn.node_reverse(node)
173+
vn.node_stop(node)
174+
175+
for node in test_nodes.values():
176+
vn.node_start(node)
177+
178+
# if another 100 samples have not been allocated, sending 200 at once is impossible with recv_smpls
179+
self.assertEqual(
180+
vn.node_write(test_nodes["recv_socket"], recv_smpls, 100), 100
181+
)
182+
# try writing as full slice
183+
self.assertEqual(
184+
vn.node_write(test_nodes["intmdt_socket"], recv_smpls[0:100], 100), 100
185+
)
186+
187+
# cleanup
188+
for node in test_nodes.values():
189+
vn.node_stop(node)
190+
vn.node_destroy(node)
191+
192+
except Exception as e:
193+
self.fail(f" err: {e}")
194+
195+
196+
test_node_config = {
197+
"test_node": {
198+
"type": "socket",
199+
"format": "villas.binary",
200+
"layer": "udp",
201+
"in": {
202+
"address": "*:12000",
203+
"signals": [{"name": "tap_position", "type": "integer", "init": 0}],
204+
},
205+
"out": {"address": "127.0.0.1:12001"},
206+
}
207+
}
208+
209+
send_recv_test = {
210+
"send_socket": {
211+
"type": "socket",
212+
"format": "protobuf",
213+
"layer": "udp",
214+
"in": {
215+
"address": "127.0.0.1:65532",
216+
"signals": [
217+
{"name": "voltage", "type": "float", "unit": "V"},
218+
{"name": "current", "type": "float", "unit": "A"},
219+
],
220+
},
221+
"out": {
222+
"address": "127.0.0.1:65533",
223+
"netem": {"enabled": False},
224+
"multicast": {"enabled": False},
225+
},
226+
},
227+
"intmdt_socket": {
228+
"type": "socket",
229+
"format": "protobuf",
230+
"layer": "udp",
231+
"in": {
232+
"address": "127.0.0.1:65533",
233+
"signals": [
234+
{"name": "voltage", "type": "float", "unit": "V"},
235+
{"name": "current", "type": "float", "unit": "A"},
236+
],
237+
},
238+
"out": {
239+
"address": "127.0.0.1:65534",
240+
"netem": {"enabled": False},
241+
"multicast": {"enabled": False},
242+
},
243+
},
244+
"recv_socket": {
245+
"type": "socket",
246+
"format": "protobuf",
247+
"layer": "udp",
248+
"in": {
249+
"address": "127.0.0.1:65534",
250+
"signals": [
251+
{"name": "voltage", "type": "float", "unit": "V"},
252+
{"name": "current", "type": "float", "unit": "A"},
253+
],
254+
},
255+
"out": {
256+
"address": "127.0.0.1:65535",
257+
"netem": {"enabled": False},
258+
"multicast": {"enabled": False},
259+
},
260+
},
261+
"signal_generator": {
262+
"type": "signal.v2",
263+
"limit": 100,
264+
"rate": 10,
265+
"in": {
266+
"signals": [
267+
{
268+
"amplitude": 2,
269+
"name": "voltage",
270+
"phase": 90,
271+
"signal": "sine",
272+
"type": "float",
273+
"unit": "V",
274+
},
275+
{
276+
"amplitude": 1,
277+
"name": "current",
278+
"phase": 0,
279+
"signal": "sine",
280+
"type": "float",
281+
"unit": "A",
282+
},
283+
],
284+
"hooks": [{"type": "print", "format": "villas.human"}],
285+
},
286+
},
287+
}
288+
289+
if __name__ == "__main__":
290+
unittest.main()

tests/integration/python/test_python_binding.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
class BindingIntegrationTests(unittest.TestCase):
1515
def setUp(self):
1616
try:
17-
self.node_uuid = str(uuid.uuid4())
1817
self.config = json.dumps(test_node_config, indent=2)
19-
self.test_node = vn.node_new(self.node_uuid, self.config)
18+
self.node_uuid = str(uuid.uuid4())
19+
self.test_node = vn.node_new(self.config, self.node_uuid)
2020
except Exception as e:
2121
self.fail(f"new_node err: {e}")
2222

@@ -87,7 +87,7 @@ def test_config_from_string(self):
8787

8888
config_copy_str = json.dumps(config_obj, indent=2)
8989

90-
test_node = vn.node_new("", config_copy_str)
90+
test_node = vn.node_new(config_copy_str, "")
9191

9292
self.assertEqual(
9393
re.sub(
@@ -113,7 +113,7 @@ def test_rw_socket_and_reverse(self):
113113
config = json.dumps(obj, indent=2)
114114
id = str(uuid.uuid4())
115115

116-
test_nodes[name] = vn.node_new(id, config)
116+
test_nodes[name] = vn.node_new(config, id)
117117

118118
for node in test_nodes.values():
119119
if vn.node_check(node):

0 commit comments

Comments
 (0)