Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build and run tests in parallel #24

Merged
merged 4 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 44 additions & 15 deletions dronecan_dsdlc.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import argparse
import em
import shutil
import time

try:
import dronecan.dsdl
Expand Down Expand Up @@ -63,6 +64,7 @@
parser.add_argument('--output', '-O', action='store')
parser.add_argument('--build', action='append')
parser.add_argument('--run-tests', action='store_true')
parser.add_argument('-j', '--jobs', type=int)
parser.add_argument('namespace_dir', nargs='+')
args = parser.parse_args()

Expand Down Expand Up @@ -143,6 +145,18 @@ def expand_message(msg_name):
f.write(output.encode("utf-8"))
return msg_name

def process_test(msg_name, jobs):
print(bcolors.HEADER + 'Starting Test for %s' % (msg_name,) + bcolors.ENDC)
if message_dict[msg_name].kind == message_dict[msg_name].KIND_SERVICE:
if len(message_dict[msg_name].request_fields):
compile_test_app(msg_name+'_request', build_dir)
run_test(message_dict[msg_name], 'request', build_dir)
compile_test_app(msg_name+'_response', build_dir, jobs)
run_test(message_dict[msg_name], 'response', build_dir)
else:
compile_test_app(msg_name, build_dir, jobs)
run_test(message_dict[msg_name], None, build_dir)

# callback for maintaining list of built messages
def append_builtlist(msg_name):
global builtlist
Expand All @@ -167,8 +181,9 @@ def append_builtlist(msg_name):
buildlist = new_buildlist

from multiprocessing import Pool
jobs = args.jobs if args.jobs is not None else os.cpu_count()

pool = Pool()
pool = Pool(processes=jobs)

results = []
if buildlist is not None:
Expand Down Expand Up @@ -215,17 +230,31 @@ def append_builtlist(msg_name):
shutil.copy(os.path.join(templates_dir, 'test_helpers.h'), build_dir+'/test/')

# start building test apps
for msg_name in sorted(builtlist):
#ignore message types that are only for includes
if message_dict[msg_name].default_dtid is None:
continue
print(bcolors.HEADER + 'Starting Test for %s' % (msg_name,) + bcolors.ENDC)
if message_dict[msg_name].kind == message_dict[msg_name].KIND_SERVICE:
if len(message_dict[msg_name].request_fields):
compile_test_app(msg_name+'_request', build_dir)
run_test(message_dict[msg_name], 'request', build_dir)
compile_test_app(msg_name+'_response', build_dir)
run_test(message_dict[msg_name], 'response', build_dir)
else:
compile_test_app(msg_name, build_dir)
run_test(message_dict[msg_name], None, build_dir)
pool = Pool(processes=jobs)
try:
one_run = False
results = []
msg_list = [msg_name for msg_name in sorted(builtlist, reverse=True) if message_dict[msg_name].default_dtid is not None]
if len(msg_list) > 0:
# need to run one test not in parallel so all the dependent code gets built
process_test(msg_list.pop(), jobs)
# don't keep too many jobs pending so we can wait for them to finish quickly on error
while len(results) > 0 or len(msg_list) > 0:
while len(results) < jobs*1.25 and len(msg_list) > 0:
results.append(pool.apply_async(process_test, (msg_list.pop(), 1)))
time.sleep(0.1)
pending = []
for result in results:
if result.ready():
result.get() # will raise exception if failed
else:
pending.append(result)
results = pending
except KeyboardInterrupt:
pool.terminate()
raise
finally:
pool.close()
pool.join()

print("All tests completed successfully")
34 changes: 18 additions & 16 deletions dronecan_dsdlc_tester.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,44 +16,46 @@

from dronecan_dsdlc_helpers import *

def compile_test_app(msg_name, build_dir):
def compile_test_app(msg_name, build_dir, num_cores=""):
print(bcolors.BOLD + 'Compiling Test App for %s' % msg_name + bcolors.ENDC)
subprocess.run('cd %s/test/; make -f test_%s.mk' % (build_dir, msg_name), shell=True, check=True)
subprocess.run('cd %s/test/; make -f test_%s.mk -j%s' % (build_dir, msg_name, str(num_cores)), shell=True, check=True)

def run_test(msg, msg_type, build_dir):
if msg_type:
msg_full_name = msg.full_name + '_' + msg_type
else:
msg_full_name = msg.full_name
print(bcolors.BOLD + 'Running Test App for %s' % msg_full_name + bcolors.ENDC)
msg_struct = dronecan.transport.CompoundValue(msg, _mode=msg_type)
print(msg_full_name, "Empty struct:", msg_struct)
p = pexpect.spawn('%s/test/%s' % (build_dir, msg_full_name))
payload = bytearray.fromhex(p.readline().decode('utf-8').strip())
print("Sample Payload:", payload.hex())
print(msg_full_name, "Sample payload:", payload.hex())
if len(payload) == 0:
return
msg_struct = dronecan.transport.CompoundValue(msg, _mode=msg_type)
print(msg, msg_struct)
msg_struct._unpack(dronecan.transport.bits_from_bytes(payload))
print(msg_struct)
print(dronecan.transport.bits_from_bytes(payload))
print(msg_struct._pack())
bits = dronecan.transport.bits_from_bytes(payload)
print(msg_full_name, "Bits:", bits)
msg_struct._unpack(bits)
print(msg_full_name, "Unpacked struct:", msg_struct)
packed_bits = msg_struct._pack()
print(msg_full_name, "Repacked bits:", packed_bits)
#pack the struct
repacked_payload = bytearray.hex(dronecan.transport.bytes_from_bits(msg_struct._pack()))
print("Packed Struct:", repacked_payload)
repacked_payload = bytearray.hex(dronecan.transport.bytes_from_bits(packed_bits))
print(msg_full_name, "Repacked payload:", repacked_payload)
msg_struct._unpack(dronecan.transport.bits_from_bytes(bytearray.fromhex(repacked_payload)))
print(msg_struct)
print(msg_struct._pack())
print(msg_full_name, "Unpacked struct again:", msg_struct)
print(msg_full_name, "Packed bits again:", msg_struct._pack())

if repacked_payload != payload.hex():
raise Exception("Repacked payload does not match original payload")
raise Exception(msg_full_name + " Repacked payload does not match original payload")
p.sendline(repacked_payload)
lines = p.readlines()
stripped_lines = [line.decode('utf-8').strip() for line in lines]
print(stripped_lines)
print(msg_full_name, "Output lines:", stripped_lines)
if 'Messages are equal' in stripped_lines:
return
else:
raise Exception("Test failed")
raise Exception(msg_full_name + " Test failed")

if __name__ == '__main__':
msg = dronecan.uavcan.equipment.ahrs.MagneticFieldStrength()
Expand Down
Loading