Skip to content

Commit 5b887a4

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 5b887a4

5 files changed

Lines changed: 550 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: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
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+
# Generate signals and send over send_socket
134+
self.assertEqual(
135+
vn.node_read(test_nodes["signal_generator"], send_smpls, 2, 1), 1
136+
)
137+
self.assertEqual(
138+
vn.node_write(test_nodes["send_socket"], send_smpls, 1), 1
139+
)
140+
141+
# read received signals and send them to recv_socket
142+
self.assertEqual(
143+
vn.node_read(test_nodes["intmdt_socket"], intmdt_smpls, 2, 100), 100
144+
)
145+
self.assertEqual(
146+
vn.node_write(test_nodes["intmdt_socket"], intmdt_smpls[0:50], 50), 50
147+
)
148+
self.assertEqual(
149+
vn.node_write(test_nodes["intmdt_socket"], intmdt_smpls[50:100], 50), 50
150+
)
151+
152+
# confirm rev_socket signals
153+
self.assertEqual(
154+
vn.node_read(test_nodes["recv_socket"], recv_smpls[0:50], 2, 50), 50
155+
)
156+
self.assertEqual(
157+
vn.node_read(test_nodes["recv_socket"], recv_smpls[50:100], 2, 50), 50
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+
# if another 100 samples have not been allocated, sending 200 at once is impossible with recv_smpls
173+
self.assertEqual(
174+
vn.node_write(test_nodes["recv_socket"], recv_smpls, 100), 100
175+
)
176+
# try writing as full slice
177+
self.assertEqual(
178+
vn.node_write(test_nodes["intmdt_socket"], recv_smpls[0:100], 100), 100
179+
)
180+
181+
# cleanup
182+
for node in test_nodes.values():
183+
vn.node_stop(node)
184+
vn.node_destroy(node)
185+
186+
except Exception as e:
187+
self.fail(f" err: {e}")
188+
189+
190+
test_node_config = {
191+
"test_node": {
192+
"type": "socket",
193+
"format": "villas.binary",
194+
"layer": "udp",
195+
"in": {
196+
"address": "*:12000",
197+
"signals": [{"name": "tap_position", "type": "integer", "init": 0}],
198+
},
199+
"out": {"address": "127.0.0.1:12001"},
200+
}
201+
}
202+
203+
send_recv_test = {
204+
"send_socket": {
205+
"type": "socket",
206+
"format": "protobuf",
207+
"layer": "udp",
208+
"in": {
209+
"address": "127.0.0.1:65532",
210+
"signals": [
211+
{"name": "voltage", "type": "float", "unit": "V"},
212+
{"name": "current", "type": "float", "unit": "A"},
213+
],
214+
},
215+
"out": {
216+
"address": "127.0.0.1:65533",
217+
"netem": {"enabled": False},
218+
"multicast": {"enabled": False},
219+
},
220+
},
221+
"intmdt_socket": {
222+
"type": "socket",
223+
"format": "protobuf",
224+
"layer": "udp",
225+
"in": {
226+
"address": "127.0.0.1:65533",
227+
"signals": [
228+
{"name": "voltage", "type": "float", "unit": "V"},
229+
{"name": "current", "type": "float", "unit": "A"},
230+
],
231+
},
232+
"out": {
233+
"address": "127.0.0.1:65534",
234+
"netem": {"enabled": False},
235+
"multicast": {"enabled": False},
236+
},
237+
},
238+
"recv_socket": {
239+
"type": "socket",
240+
"format": "protobuf",
241+
"layer": "udp",
242+
"in": {
243+
"address": "127.0.0.1:65534",
244+
"signals": [
245+
{"name": "voltage", "type": "float", "unit": "V"},
246+
{"name": "current", "type": "float", "unit": "A"},
247+
],
248+
},
249+
"out": {
250+
"address": "127.0.0.1:65535",
251+
"netem": {"enabled": False},
252+
"multicast": {"enabled": False},
253+
},
254+
},
255+
"signal_generator": {
256+
"type": "signal.v2",
257+
"limit": 100,
258+
"rate": 10,
259+
"in": {
260+
"signals": [
261+
{
262+
"amplitude": 2,
263+
"name": "voltage",
264+
"phase": 90,
265+
"signal": "sine",
266+
"type": "float",
267+
"unit": "V",
268+
},
269+
{
270+
"amplitude": 1,
271+
"name": "current",
272+
"phase": 0,
273+
"signal": "sine",
274+
"type": "float",
275+
"unit": "A",
276+
},
277+
],
278+
"hooks": [{"type": "print", "format": "villas.human"}],
279+
},
280+
},
281+
}
282+
283+
if __name__ == "__main__":
284+
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)