369 lines
15 KiB
Python
369 lines
15 KiB
Python
|
#!/usr/bin/env python3
|
||
|
#
|
||
|
# Node/javascript mavlink test maker!
|
||
|
#
|
||
|
# This is written to run with python3 only, and the tests it produces are for node / javascript
|
||
|
# It is mavlink1 and mavlink2 and multi-type aware
|
||
|
#
|
||
|
# Copyright David 'Buzz' Bussenschutt July 2020
|
||
|
# Released under GNU GPL version 3 or later
|
||
|
#
|
||
|
# to run: cd test
|
||
|
# python3 make_tests.py > made_tests.js
|
||
|
# cd -
|
||
|
# mocha test --grep 'from C'
|
||
|
#
|
||
|
# reads the output from modified C test commands, for example, for ardupilot + mavlink2:
|
||
|
# "./testmav2.0_ardupilotmega | grep '^fd' command and builds a bunch of .js tests for this test data
|
||
|
#
|
||
|
# In order to identify types that are 'Long' or need Array wrapping, which need special handling we build a little lookup table
|
||
|
# from the autogenerated file:mavlink.tests.js" (which itself is made by mavgen_javascript.py )
|
||
|
#
|
||
|
# we label and number each of the tests in the output as well, so that its also easy to run individual tests with something like:
|
||
|
# mocha test --grep '1234'
|
||
|
# or we can run all of them with:
|
||
|
# mocha test --grep 'from C'
|
||
|
# or eg all the ardupilotmega tests against mavlink1:
|
||
|
# mocha test --grep 'using ardupilotmega/1.0'
|
||
|
|
||
|
import subprocess
|
||
|
import sys
|
||
|
|
||
|
# now tested and executes with this simple matrix of two mavtypes and two mav versions
|
||
|
cmddir = '../../../generator/C/test/posix/'
|
||
|
mavtypes = ['ardupilotmega','common']
|
||
|
versions = ['1.0','2.0']
|
||
|
cmds = []
|
||
|
|
||
|
#..so the C binding cmds executed/wrapped are: 'testmav1.0_ardupilotmega', 'testmav2.0_ardupilotmega', 'testmav1.0_common', 'testmav2.0_common'
|
||
|
|
||
|
#---------------------------------------------------------------------------------------
|
||
|
|
||
|
|
||
|
template1 = '''
|
||
|
it('id${ID} encode and decode ${NAME} from C using ${MAVTYPE}/${VERSION} ${SIGNED}', function() {
|
||
|
|
||
|
this.mav.seq = ${SEQ};
|
||
|
this.mav.srcSystem=${SRCSYS};
|
||
|
this.mav.srcComponent=${SRCCOMP};
|
||
|
'''
|
||
|
|
||
|
signing_extra_template = '''
|
||
|
|
||
|
//-------- START codeblock only for signed packets----------------
|
||
|
|
||
|
this.mav.seq = ${SEQ}-1;
|
||
|
|
||
|
// relevant to how we pass-in the Long object/s to jspack, we'll assume the calling user is smart enough to know that.
|
||
|
var wrap_long = function (someLong) {
|
||
|
return [someLong.getLowBitsUnsigned(), someLong.getHighBitsUnsigned()];
|
||
|
}
|
||
|
|
||
|
this.mav.signing.secret_key = new Buffer.from([ 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42 ]) ; // matches secret key in testmav.c
|
||
|
this.mav.signing.link_id = 0 ; // 1 byte // matches link_id in testmav.c
|
||
|
//this.mav.signing.timestamp = new Buffer.from([ 0,0,0,0,0,${TS}]); // 6 bytes // matches timestamp in testmav.c
|
||
|
this.mav.signing.timestamp = ${TS}; // at most 48 bits , fits in a native js number - matches timestamp in testmav.c
|
||
|
this.mav.signing.sign_outgoing = true; // todo false this
|
||
|
|
||
|
var epoch_offset = 1420070400;
|
||
|
var x= Long.fromString("${TS}", true);
|
||
|
var long_timestamp = wrap_long(x);
|
||
|
|
||
|
var target_system = 255; // the python impl in mavproxy uses 255 here , so we do, it could be this.sysid
|
||
|
var target_component = 0;
|
||
|
var secret_key = this.mav.signing.secret_key ;
|
||
|
|
||
|
MAVLink20Processor.prototype.send = function(mavmsg) {
|
||
|
buf = mavmsg.pack(this);
|
||
|
// no actual send here
|
||
|
this.seq = (this.seq + 1) % 256;
|
||
|
this.total_packets_sent +=1;
|
||
|
this.total_bytes_sent += buf.length;
|
||
|
}
|
||
|
|
||
|
var link_id =0;
|
||
|
var srcSystem=this.mav.srcSystem;
|
||
|
var srcComponent=this.mav.srcComponent;
|
||
|
stream_key = new Array(link_id,srcSystem,srcComponent).toString();
|
||
|
this.mav.signing.stream_timestamps[stream_key] = ${TS};
|
||
|
this.mav.signing.timestamp.should.eql(${TS}); //ts before setup
|
||
|
|
||
|
var setup_signing = new mavlink20.messages.setup_signing(target_system, target_component, secret_key, long_timestamp);
|
||
|
this.mav.send(setup_signing,this.sysid);
|
||
|
|
||
|
setup_signing.secret_key.should.eql(new Buffer.from([ 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42 ]) );
|
||
|
setup_signing.initial_timestamp.should.eql([${TS},0]);
|
||
|
//this.mav.signing.timestamp.should.eql(new Buffer.from([0,0,0,0,0,${TS}]));
|
||
|
this.mav.signing.timestamp.should.eql(${TS}+1); // ts after setup
|
||
|
this.mav.signing.link_id.should.eql(0);
|
||
|
this.mav.signing.sign_outgoing.should.eql(true);
|
||
|
|
||
|
//-------- END codeblock only for signed packets----------------
|
||
|
'''
|
||
|
|
||
|
template2 = '''
|
||
|
|
||
|
var test_${NAME} = this.tests.test_${NAME}()[0]; // get the assembled test object with test data already set, override just the min we need to do this test
|
||
|
|
||
|
//--- you can uncomment any of these to change the test, but you'll need to change the reference buffer to the right result too
|
||
|
//${FIELDS}
|
||
|
//---
|
||
|
|
||
|
// Create a buffer that matches what the Python version of MAVLink creates
|
||
|
var reference = new Buffer.from([${BUFFER}]);
|
||
|
|
||
|
this.mav.signing.timestamp = ${TS};// force ts to be correct, right before the pack() that matters
|
||
|
|
||
|
var p = test_${NAME}.pack(this.mav);
|
||
|
|
||
|
// console.log(p);
|
||
|
// p.forEach( x => { process.stdout.write( x.toString() ); process.stdout.write(" ") } ); process.stdout.write("\\n");
|
||
|
// p.forEach( x => { process.stdout.write( x.toString(16) ); process.stdout.write(" ") } ); process.stdout.write("\\n");
|
||
|
|
||
|
test_${NAME}._header.seq.should.eql(${SEQ});
|
||
|
test_${NAME}._header.srcSystem.should.eql(${SRCSYS});
|
||
|
test_${NAME}._header.srcComponent.should.eql(${SRCCOMP});
|
||
|
test_${NAME}._header.msgId.should.eql(test_${NAME}._id);
|
||
|
|
||
|
${SIGNED}test_${NAME}._header.incompat_flags.should.eql(1);
|
||
|
${UNSIGNED}test_${NAME}._header.incompat_flags.should.eql(0);
|
||
|
|
||
|
test_${NAME}._header.compat_flags.should.eql(0);
|
||
|
|
||
|
new Buffer.from(p).should.eql(reference);
|
||
|
});
|
||
|
'''
|
||
|
|
||
|
templatestart = '''
|
||
|
//
|
||
|
// (auto-generated by make_tests.py ), do not edit.
|
||
|
// generator by davidbuzz@gmail.com
|
||
|
//
|
||
|
// Copyright David 'Buzz' Bussenschutt July 2020
|
||
|
// Released under GNU GPL version 3 or later
|
||
|
//
|
||
|
// you can regenerate this file and its dependencies and run its tests, by executing the following: "cd pymavlink/generator/javascript ; npm test"
|
||
|
// or see make_tests.py which created this.
|
||
|
//
|
||
|
should = require('should');
|
||
|
var Long = require('long');
|
||
|
|
||
|
'''
|
||
|
|
||
|
templateheader = '''
|
||
|
//--------------------------------------------------------------------------------------------------------------------------------------------------------
|
||
|
|
||
|
describe('end-to-end node byte-level tests of ${MAVTYPE}/${VERSION} against C impl', function() {
|
||
|
|
||
|
beforeEach(function() {
|
||
|
|
||
|
var {mavlink${VERS}, MAVLink${VERS}Processor} = require('../implementations/mavlink_${MAVTYPE}_v${VERSION}/mavlink.js');// hardcoded here by make_tests.py generator
|
||
|
this.mav = new MAVLink${VERS}Processor(null, 42, 150); // hardcoded here by make_tests.py generator
|
||
|
this.tests = require('../implementations/mavlink_${MAVTYPE}_v${VERSION}/mavlink.tests.js');//// hardcoded here by make_tests.py generator
|
||
|
// be sure the test library is using the right version before we call into it
|
||
|
this.tests.set_mav(this.mav);
|
||
|
});'''
|
||
|
|
||
|
templatefooter = '''
|
||
|
|
||
|
});'''
|
||
|
|
||
|
#------------------------------------------------
|
||
|
|
||
|
def is_packet_and_field_in_long_list(pname,fname):
|
||
|
global llines
|
||
|
for l in llines:
|
||
|
if ( pname+'.'+fname in l ) :
|
||
|
return (True, l)
|
||
|
return (False, '')
|
||
|
|
||
|
testid = 1;
|
||
|
|
||
|
# for each of the 1.0,2.0 and common,ardupilotmega combos write tests
|
||
|
def do_make_output(mt,v,lines):
|
||
|
global testid
|
||
|
t = templateheader.replace('${MAVTYPE}',mt)
|
||
|
t = t.replace('${VERSION}',v)
|
||
|
t = t.replace('${VERS}',v.replace('.',''))
|
||
|
print(t)
|
||
|
|
||
|
last_line = ''
|
||
|
for line in lines:
|
||
|
if line.startswith('fd '): # mavlink2 start byte as human-readable hex, eg 'fd 08 7e 2a 0b e2 00 00 88 41 00 00 34 42 30 93 '
|
||
|
last_line = '2.0'
|
||
|
if v == 1: # if param 'v' requested mav1 data and we see mav2 data, ignore it
|
||
|
continue
|
||
|
hexdata = line.strip().replace(' ',', 0x')
|
||
|
hexdata = '0x'+hexdata
|
||
|
# ckeck if signing bit is set on this packet: 0xfd, 0x09, 0x01 <-- that 1
|
||
|
signchar = hexdata[15]
|
||
|
signint = int(signchar,16)
|
||
|
signbit = signint % 2; # lowest bit to true/false
|
||
|
|
||
|
if line.startswith('fe '): # mavlink1 start byte as human-readable hex, eg 'fe 08 7e 2a 0b e2 00 00 88 41 00 00 34 42 30 93 '
|
||
|
last_line = '1.0'
|
||
|
if v == 2: # if param 'v' requested mav2 data and we see mav1 data, ignore it
|
||
|
continue
|
||
|
hexdata = line.strip().replace(' ',', 0x')
|
||
|
hexdata = '0x'+hexdata
|
||
|
signbit = False
|
||
|
|
||
|
if line.startswith('sysid:'): # packet details human-readable eg 'sysid:42 compid:11 seq:126 RPM { rpm1: 17.000000 rpm2: 45.000000 }'
|
||
|
# skip parsing data if it's not this parser
|
||
|
if last_line != v:
|
||
|
continue
|
||
|
fields = line.split(' ')
|
||
|
sysid = fields[0].split(':')[1]
|
||
|
compid = fields[1].split(':')[1]
|
||
|
seq = fields[2].split(':')[1]
|
||
|
|
||
|
# lines without sign_ts, the { is earlier
|
||
|
if fields[4] == '{':
|
||
|
sign_ts = '0'
|
||
|
packetname = fields[3]
|
||
|
more = fields[4:]
|
||
|
else:
|
||
|
sign_ts = fields[3].split(':')[1]
|
||
|
packetname = fields[4]
|
||
|
more = fields[5:]
|
||
|
|
||
|
packetname = packetname.lower()
|
||
|
|
||
|
arraystarted = False
|
||
|
for i,x in enumerate(more):
|
||
|
|
||
|
if x == '[':
|
||
|
arraystarted = True
|
||
|
if x == ']':
|
||
|
arraystarted = False
|
||
|
more[i] = '], \n '
|
||
|
if not arraystarted and x == '':
|
||
|
more[i] = ',\n '
|
||
|
fixed = ''.join(more)
|
||
|
fixed = fixed.replace(',]',']') # drop unneeded comma from end of arrays
|
||
|
fixed = fixed.replace('{','');
|
||
|
fixed = fixed.replace('}','');
|
||
|
fixed = fixed.replace(':','=') # move : to =
|
||
|
import re
|
||
|
fixed = re.sub(r'(\'.*?\')', '\\1,\n ', fixed)
|
||
|
|
||
|
# now iterate over them as parsed lines safely
|
||
|
newlines = []
|
||
|
for fieldline in fixed.split('\n'):
|
||
|
if fieldline.strip() == '':
|
||
|
continue
|
||
|
# a little miniparser here to modify things after the = sign to insert our own test values, not the included ones, but leave
|
||
|
# value wrapping and other surrounding casting functions as-is.
|
||
|
( field, value) = fieldline.split('=');
|
||
|
if not value.startswith('['):
|
||
|
value = value.replace(',','')
|
||
|
field = field.replace(' ','')
|
||
|
else: # array
|
||
|
value = value.split('[')[1].split(']')[0]; # stuff inside the brackets, but not the brackets
|
||
|
(retval,match) = is_packet_and_field_in_long_list(packetname,field);
|
||
|
if retval == True:
|
||
|
parts = match.split('=');
|
||
|
after_equals = parts[1];
|
||
|
before_semicolon = after_equals.split(';')[0]
|
||
|
|
||
|
# determine old test value in the 'before_semicolon' line segment:
|
||
|
# a little miniparser here to modify things after the = sign to insert our own test values, not the included ones, but leave
|
||
|
# value wrapping and other surrounding casting functions as-is.
|
||
|
if not before_semicolon.replace(' ','').startswith('['):
|
||
|
if '"' in before_semicolon:
|
||
|
oldvalue = before_semicolon.split('"')[1]; # stuff inside the ", but not the "
|
||
|
elif 'Array' in before_semicolon:
|
||
|
oldvalue = before_semicolon.split('[')[1].split(']')[0]; # [1234]
|
||
|
else:
|
||
|
oldvalue = before_semicolon; # unwrapped numbers etc
|
||
|
else: # array
|
||
|
oldvalue = before_semicolon.split('[')[1].split(']')[0]; # stuff inside the brackets, but not the brackets
|
||
|
|
||
|
line_minus_data = before_semicolon.replace(oldvalue,value);
|
||
|
newlines.append(" test_"+packetname+"."+field+'='+line_minus_data+';')
|
||
|
else:
|
||
|
newlines.append(" test_"+packetname+"."+field+'='+value+';')
|
||
|
|
||
|
|
||
|
fixed = '\n//'.join(newlines)
|
||
|
|
||
|
t = template1
|
||
|
t = t.replace('${MAVTYPE}',mt)
|
||
|
t = t.replace('${VERSION}',v)
|
||
|
t = t.replace('${NAME}',packetname)
|
||
|
t = t.replace('${ID}',str(testid))
|
||
|
testid = testid+1
|
||
|
t = t.replace('${SEQ}',seq)
|
||
|
t = t.replace('${SRCSYS}',sysid)
|
||
|
t = t.replace('${SRCCOMP}',compid)
|
||
|
t = t.replace('${BUFFER}',hexdata)
|
||
|
if signbit:
|
||
|
t = t.replace('${SIGNED}','signed')
|
||
|
else :
|
||
|
t = t.replace('${SIGNED}','')
|
||
|
print(t)
|
||
|
|
||
|
if signbit:
|
||
|
t = signing_extra_template
|
||
|
t = t.replace('${SEQ}',seq)
|
||
|
t = t.replace('${TS}',sign_ts)
|
||
|
print(t)
|
||
|
|
||
|
t = template2
|
||
|
t = t.replace('${NAME}',packetname)
|
||
|
t = t.replace('${SEQ}',seq)
|
||
|
t = t.replace('${SRCSYS}',sysid)
|
||
|
t = t.replace('${SRCCOMP}',compid)
|
||
|
t = t.replace('${BUFFER}',hexdata)
|
||
|
t = t.replace('${FIELDS}',fixed)
|
||
|
t = t.replace('${TS}',sign_ts)
|
||
|
if signbit:
|
||
|
t = t.replace('${SIGNED}','/*signed*/ ')
|
||
|
t = t.replace('${UNSIGNED}','//unsigned ')
|
||
|
else :
|
||
|
t = t.replace('${SIGNED}','//signed ')
|
||
|
t = t.replace('${UNSIGNED}','/*unsigned*/ ')
|
||
|
print(t)
|
||
|
|
||
|
print('//------------------------------------------------------')
|
||
|
|
||
|
# append footer
|
||
|
t = templatefooter.replace('${MAVTYPE}',mt)
|
||
|
t = t.replace('${VERSION}',v)
|
||
|
t = t.replace('${VERS}',v.replace('.',''))
|
||
|
print(t)
|
||
|
|
||
|
|
||
|
#---------------------------------------------------------------------------------------
|
||
|
|
||
|
# example line from file:
|
||
|
# test_actuator_output_status.time_usec = Long.fromNumber(93372036854775807, true); // fieldtype: uint64_t isarray: False
|
||
|
llines = []
|
||
|
def make_long_lookup_table(mt,v):
|
||
|
global llines
|
||
|
_cmd = 'egrep "(wrap_long|new.*Array)" ../implementations/mavlink_'+mt+'_v'+v+'/mavlink.tests.js'; # relevant lines only
|
||
|
_result = subprocess.run(_cmd, stdout=subprocess.PIPE, shell=True)
|
||
|
_data = _result.stdout.decode('utf-8')
|
||
|
llines = _data.split("\n")
|
||
|
#return lines
|
||
|
|
||
|
#---------------------------------------------------------------------------------------
|
||
|
|
||
|
print(templatestart)
|
||
|
|
||
|
for mt in mavtypes:
|
||
|
for v in versions:
|
||
|
cmd = cmddir+'testmav'+v+'_'+mt
|
||
|
result = subprocess.run(cmd, stdout=subprocess.PIPE)
|
||
|
data = result.stdout.decode('utf-8')
|
||
|
lines = data.split("\n")
|
||
|
llines = []
|
||
|
make_long_lookup_table(mt, v);
|
||
|
do_make_output(mt,v,lines)
|
||
|
|
||
|
|
||
|
print("//output done")
|
||
|
|
||
|
sys.exit()
|
||
|
|