scripts/python/recordreplay.py: fixed use when fgfs is run via wrapper script.
Various tweaks to motion tests.
This commit is contained in:
parent
4f28e2dfff
commit
29dc9ea93a
1 changed files with 89 additions and 26 deletions
|
@ -68,6 +68,9 @@ g_tapedir = './recordreplay.py.tapes'
|
||||||
|
|
||||||
|
|
||||||
def remove(path):
|
def remove(path):
|
||||||
|
'''
|
||||||
|
Removes file, ignoring any error.
|
||||||
|
'''
|
||||||
log(f'Removing: {path}')
|
log(f'Removing: {path}')
|
||||||
try:
|
try:
|
||||||
os.remove(path)
|
os.remove(path)
|
||||||
|
@ -76,6 +79,9 @@ def remove(path):
|
||||||
|
|
||||||
|
|
||||||
def readlink(path):
|
def readlink(path):
|
||||||
|
'''
|
||||||
|
Returns absolute path destination of link.
|
||||||
|
'''
|
||||||
ret = os.readlink(path)
|
ret = os.readlink(path)
|
||||||
if not os.path.isabs(ret):
|
if not os.path.isabs(ret):
|
||||||
ret = os.path.join(os.path.dirname(path), ret)
|
ret = os.path.join(os.path.dirname(path), ret)
|
||||||
|
@ -84,8 +90,10 @@ def readlink(path):
|
||||||
|
|
||||||
class Fg:
|
class Fg:
|
||||||
'''
|
'''
|
||||||
Runs flightgear. self.fg is a FlightGear.FlightGear instance, which uses
|
Runs flightgear, with support for setting/getting properties etc.
|
||||||
telnet to communicate with Flightgear.
|
|
||||||
|
self.fg is a FlightGear.FlightGear instance, which uses telnet to
|
||||||
|
communicate with Flightgear.
|
||||||
'''
|
'''
|
||||||
def __init__(self, aircraft, args, env=None, telnet_port=None):
|
def __init__(self, aircraft, args, env=None, telnet_port=None):
|
||||||
'''
|
'''
|
||||||
|
@ -131,7 +139,9 @@ class Fg:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log(f'*** preexec failed with e={e}')
|
log(f'*** preexec failed with e={e}')
|
||||||
raise
|
raise
|
||||||
self.child = subprocess.Popen(args2, env=environ,
|
self.child = subprocess.Popen(
|
||||||
|
args2,
|
||||||
|
env=environ,
|
||||||
preexec_fn=preexec,
|
preexec_fn=preexec,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -180,9 +190,24 @@ class Fg:
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
assert self.child
|
assert self.child
|
||||||
|
log(f'close(): stopping flightgear pid={self.child.pid}')
|
||||||
|
if 1:
|
||||||
|
# Kill any child processes so that things work if fgfs is being run
|
||||||
|
# by download_and_compile.sh's run_fgfs.sh script.
|
||||||
|
#
|
||||||
|
# This is Unix-only.
|
||||||
|
child_pids = subprocess.check_output(f'pgrep -P {self.child.pid}', shell=True)
|
||||||
|
child_pids = child_pids.decode('utf-8')
|
||||||
|
child_pids = child_pids.split()
|
||||||
|
for child_pid in child_pids:
|
||||||
|
#log(f'*** close() child_pid={child_pid}')
|
||||||
|
child_pid = int(child_pid)
|
||||||
|
#log(f'*** close() killing child_pid={child_pid}')
|
||||||
|
os.kill(child_pid, signal.SIGTERM)
|
||||||
self.child.terminate()
|
self.child.terminate()
|
||||||
self.child.wait()
|
self.child.wait()
|
||||||
self.child = None
|
self.child = None
|
||||||
|
#log(f'*** close() returning.')
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
if self.child:
|
if self.child:
|
||||||
|
@ -322,8 +347,22 @@ def test_record_replay(
|
||||||
|
|
||||||
|
|
||||||
def test_motion(fgfs, multiplayer=False):
|
def test_motion(fgfs, multiplayer=False):
|
||||||
|
'''
|
||||||
|
Records UFO moving with constant velocity with varying framerates, then
|
||||||
|
replays with varying framerates and checks that replayed UFO moves with
|
||||||
|
expected constant speed.
|
||||||
|
|
||||||
|
If <multiplayer> is true we also record MP UFO running in second Flightgear
|
||||||
|
instance and check that it too moves at constant speed when replaying.
|
||||||
|
'''
|
||||||
|
log('')
|
||||||
|
log('='*80)
|
||||||
|
log('== Record')
|
||||||
|
|
||||||
aircraft = 'ufo'
|
aircraft = 'ufo'
|
||||||
|
if multiplayer:
|
||||||
|
fg = Fg( aircraft, f'{fgfs} --prop:/sim/replay/log-raw-speed-multiplayer=cgdae-t')
|
||||||
|
else:
|
||||||
fg = Fg( aircraft, f'{fgfs}')
|
fg = Fg( aircraft, f'{fgfs}')
|
||||||
path = f'{g_tapedir}/{fg.aircraft}-continuous.fgtape'
|
path = f'{g_tapedir}/{fg.aircraft}-continuous.fgtape'
|
||||||
|
|
||||||
|
@ -331,18 +370,24 @@ def test_motion(fgfs, multiplayer=False):
|
||||||
|
|
||||||
fg.fg['/controls/engines/engine[0]/throttle'] = 0
|
fg.fg['/controls/engines/engine[0]/throttle'] = 0
|
||||||
|
|
||||||
|
# Throttle/speed for ufo is set in fgdata/Aircraft/ufo/ufo.nas.
|
||||||
|
#
|
||||||
|
speed_max = 2000 # default for ufo; current=7.
|
||||||
|
fixed_speed = 100
|
||||||
|
throttle = fixed_speed / speed_max
|
||||||
|
|
||||||
if multiplayer:
|
if multiplayer:
|
||||||
fg.fg['/sim/replay/record-multiplayer'] = True
|
fg.fg['/sim/replay/record-multiplayer'] = True
|
||||||
fg2 = Fg( aircraft, f'{fgfs} --callsign=cgdae-t --multiplay=in,4,,5033 --read-only', telnet_port=5501)
|
fg2 = Fg( aircraft, f'{fgfs} --callsign=cgdae-t --multiplay=in,4,,5033 --read-only', telnet_port=5501)
|
||||||
fg2.waitfor('/sim/fdm-initialized', 1, timeout=45)
|
fg2.waitfor('/sim/fdm-initialized', 1, timeout=45)
|
||||||
fg.fg['/controls/engines/engine[0]/throttle'] = 0.1
|
fg.fg['/controls/engines/engine[0]/throttle'] = throttle
|
||||||
fg2.fg['/controls/engines/engine[0]/throttle'] = 0.1
|
fg2.fg['/controls/engines/engine[0]/throttle'] = throttle
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
fgt = fg.fg['/controls/engines/engine[0]/throttle']
|
fgt = fg.fg['/controls/engines/engine[0]/throttle']
|
||||||
fg2t = fg2.fg['/controls/engines/engine[0]/throttle']
|
fg2t = fg2.fg['/controls/engines/engine[0]/throttle']
|
||||||
log(f'fgt={fgt} fg2t={fg2t}')
|
log(f'fgt={fgt} fg2t={fg2t}')
|
||||||
else:
|
else:
|
||||||
fg.fg['/controls/engines/engine[0]/throttle'] = 0.1
|
fg.fg['/controls/engines/engine[0]/throttle'] = throttle
|
||||||
|
|
||||||
# Run UFO with constant speed, varying the framerate so we check whether
|
# Run UFO with constant speed, varying the framerate so we check whether
|
||||||
# recorded speeds are affected.
|
# recorded speeds are affected.
|
||||||
|
@ -362,7 +407,7 @@ def test_motion(fgfs, multiplayer=False):
|
||||||
fg.fg['/sim/frame-rate-throttle-hz'] = 2
|
fg.fg['/sim/frame-rate-throttle-hz'] = 2
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
|
|
||||||
# Restore original frame rate.
|
# Change frame rate.
|
||||||
fg.fg['/sim/frame-rate-throttle-hz'] = 5
|
fg.fg['/sim/frame-rate-throttle-hz'] = 5
|
||||||
if multiplayer:
|
if multiplayer:
|
||||||
fg2.fg['/sim/frame-rate-throttle-hz'] = 2
|
fg2.fg['/sim/frame-rate-throttle-hz'] = 2
|
||||||
|
@ -380,10 +425,20 @@ def test_motion(fgfs, multiplayer=False):
|
||||||
log(f'*** path={path} path2={path2}')
|
log(f'*** path={path} path2={path2}')
|
||||||
g_cleanup.append(lambda: remove(path2))
|
g_cleanup.append(lambda: remove(path2))
|
||||||
|
|
||||||
|
log('')
|
||||||
|
log('='*80)
|
||||||
|
log('== Replay')
|
||||||
|
|
||||||
if multiplayer:
|
if multiplayer:
|
||||||
fg = Fg( aircraft, f'{fgfs} --load-tape={path} --prop:/sim/replay/log-raw-speed-multiplayer=cgdae-t')
|
fg = Fg( aircraft, f'{fgfs} --load-tape={path}'
|
||||||
|
f' --prop:/sim/replay/log-raw-speed-multiplayer=cgdae-t'
|
||||||
|
f' --prop:/sim/replay/log-raw-speed=true'
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
fg = Fg( aircraft, f'{fgfs} --load-tape={path} --prop:/sim/replay/log-raw-speed=true')
|
fg = Fg( aircraft,
|
||||||
|
f'{fgfs} --load-tape={path} --prop:/sim/replay/log-raw-speed=true',
|
||||||
|
#env='SG_LOG_DELTAS=flightgear/src/Aircraft/flightrecorder.cxx:replay=3',
|
||||||
|
)
|
||||||
fg.waitfor('/sim/fdm-initialized', 1, timeout=45)
|
fg.waitfor('/sim/fdm-initialized', 1, timeout=45)
|
||||||
fg.fg['/sim/frame-rate-throttle-hz'] = 10
|
fg.fg['/sim/frame-rate-throttle-hz'] = 10
|
||||||
fg.waitfor('/sim/replay/replay-state', 1)
|
fg.waitfor('/sim/replay/replay-state', 1)
|
||||||
|
@ -397,14 +452,17 @@ def test_motion(fgfs, multiplayer=False):
|
||||||
|
|
||||||
fg.waitfor('/sim/replay/replay-state-eof', 1)
|
fg.waitfor('/sim/replay/replay-state-eof', 1)
|
||||||
|
|
||||||
|
errors = []
|
||||||
def examine_values(infix=''):
|
def examine_values(infix=''):
|
||||||
|
'''
|
||||||
|
Looks at /sim/replay/log-raw-speed{infix}-values/value[], which will
|
||||||
|
contain measured speed of user/MP UFO. We check that the values are all
|
||||||
|
as expected - constant speed.
|
||||||
|
'''
|
||||||
log(f'== Looking at /sim/replay/log-raw-speed{infix}-values/value[]')
|
log(f'== Looking at /sim/replay/log-raw-speed{infix}-values/value[]')
|
||||||
items0 = fg.fg.ls( f'/sim/replay/log-raw-speed{infix}-values')
|
items0 = fg.fg.ls( f'/sim/replay/log-raw-speed{infix}-values')
|
||||||
log(f'len(items0)={len(items0)}')
|
log(f'{infix} len(items0)={len(items0)}')
|
||||||
if not items0:
|
assert items0, f'Failed to read items in /sim/replay/log-raw-speed{infix}-values/'
|
||||||
while 1:
|
|
||||||
log(f'*** hanging because failed to read contents of: /sim/replay/log-raw-speed{infix}-values')
|
|
||||||
time.sleep(5)
|
|
||||||
items = []
|
items = []
|
||||||
for item in items0:
|
for item in items0:
|
||||||
if item.name == 'value':
|
if item.name == 'value':
|
||||||
|
@ -414,19 +472,26 @@ def test_motion(fgfs, multiplayer=False):
|
||||||
for item in items[:-1]: # Ignore last item because replay at end interpolates.
|
for item in items[:-1]: # Ignore last item because replay at end interpolates.
|
||||||
speed = float(item.value)
|
speed = float(item.value)
|
||||||
prefix = ' '
|
prefix = ' '
|
||||||
if abs(speed - 200) > 0.5:
|
if abs(speed - fixed_speed) > 0.1:
|
||||||
num_errors += 1
|
num_errors += 1
|
||||||
prefix = '*'
|
prefix = '*'
|
||||||
log( f' {prefix} speed={speed} details: {item}')
|
log( f' {infix} {prefix} speed={speed:12.4} details: {item}')
|
||||||
assert num_errors == 0, 'Replay showed uneven speed.'
|
if num_errors != 0:
|
||||||
|
log( f'*** Replay showed uneven speed')
|
||||||
|
errors.append('1')
|
||||||
|
|
||||||
if multiplayer:
|
if multiplayer:
|
||||||
|
examine_values()
|
||||||
examine_values('-multiplayer')
|
examine_values('-multiplayer')
|
||||||
examine_values('-multiplayer-post')
|
examine_values('-multiplayer-post')
|
||||||
else:
|
else:
|
||||||
examine_values()
|
examine_values()
|
||||||
|
|
||||||
fg.close()
|
fg.close()
|
||||||
|
if errors:
|
||||||
|
raise Exception('Failure')
|
||||||
|
|
||||||
|
log('test_motion() passed')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
@ -480,6 +545,8 @@ if __name__ == '__main__':
|
||||||
else:
|
else:
|
||||||
raise Exception(f'Unrecognised arg: {arg!r}')
|
raise Exception(f'Unrecognised arg: {arg!r}')
|
||||||
|
|
||||||
|
g_tapedir = os.path.abspath(g_tapedir)
|
||||||
|
|
||||||
if do_test == 'motion':
|
if do_test == 'motion':
|
||||||
test_motion( fgfs)
|
test_motion( fgfs)
|
||||||
elif do_test == 'motion-mp':
|
elif do_test == 'motion-mp':
|
||||||
|
@ -545,11 +612,7 @@ if __name__ == '__main__':
|
||||||
for f in g_cleanup:
|
for f in g_cleanup:
|
||||||
try:
|
try:
|
||||||
f()
|
f()
|
||||||
except:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if 0:
|
log(f'{__file__}: Returning 0')
|
||||||
# This path can be used to check we cleanup properly after an error.
|
|
||||||
fg = Fg('./build-walk/fgfs.exe-run.sh --aircraft=harrier-gr3 --airport=egtk')
|
|
||||||
time.sleep(5)
|
|
||||||
assert 0
|
|
||||||
|
|
Loading…
Reference in a new issue