data/method/mavlink/pymavlink/generator/javascript/test/make_tests.py

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()