mirror of
https://github.com/Wiimpathy/HatariWii.git
synced 2024-11-26 11:34:20 +01:00
735 lines
28 KiB
Python
735 lines
28 KiB
Python
|
#!/usr/bin/env python
|
||
|
#
|
||
|
# Copyright (C) 2012-2015 by Eero Tamminen <oak at helsinkinet fi>
|
||
|
#
|
||
|
# This program is free software; you can redistribute it and/or modify
|
||
|
# it under the terms of the GNU General Public License as published by
|
||
|
# the Free Software Foundation; either version 2 of the License, or
|
||
|
# (at your option) any later version.
|
||
|
#
|
||
|
# This program is distributed in the hope that it will be useful,
|
||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
# GNU General Public License for more details.
|
||
|
"""
|
||
|
Tester boots the given TOS versions under Hatari with all the possible
|
||
|
combinations of the given machine HW configuration options, that are
|
||
|
supported by the tested TOS version.
|
||
|
|
||
|
Verification screenshot is taken at the end of each boot before
|
||
|
proceeding to testing of the next combination. Screenshot name
|
||
|
indicates the used combination, for example:
|
||
|
etos512k-falcon-rgb-gemdos-14M.png
|
||
|
etos512k-st-mono-floppy-1M.png
|
||
|
|
||
|
|
||
|
NOTE: If you want to test the latest, uninstalled version of Hatari,
|
||
|
you need to set PATH to point to your Hatari binary directory, like
|
||
|
this:
|
||
|
PATH=../../build/src:$PATH tos_tester.py <TOS images>
|
||
|
|
||
|
If hconsole isn't installed to one of the standard locations (under
|
||
|
/usr or /usr/local), or you don't run this from within Hatari sources,
|
||
|
you also need to specify hconsole.py location with:
|
||
|
export PYTHONPATH=/path/to/hconsole
|
||
|
"""
|
||
|
|
||
|
import getopt, os, signal, select, sys, time
|
||
|
|
||
|
def add_hconsole_paths():
|
||
|
"add most likely hconsole locations to module import path"
|
||
|
# prefer the devel version in Hatari sources, if it's found
|
||
|
subdirs = len(os.path.abspath(os.curdir).split(os.path.sep))-1
|
||
|
for level in range(subdirs):
|
||
|
f = level*(".." + os.path.sep) + "tools/hconsole/hconsole.py"
|
||
|
if os.path.isfile(f):
|
||
|
f = os.path.dirname(f)
|
||
|
sys.path.append(f)
|
||
|
print "Added local hconsole path: %s" % f
|
||
|
break
|
||
|
sys.path += ["/usr/local/share/hatari/hconsole",
|
||
|
"/usr/share/hatari/hconsole"]
|
||
|
|
||
|
add_hconsole_paths()
|
||
|
import hconsole
|
||
|
|
||
|
|
||
|
def warning(msg):
|
||
|
"output warning message"
|
||
|
sys.stderr.write("WARNING: %s\n" % msg)
|
||
|
|
||
|
|
||
|
# -----------------------------------------------
|
||
|
class TOS:
|
||
|
"class for TOS image information"
|
||
|
# objects have members:
|
||
|
# - path (string), given TOS image file path/name
|
||
|
# - name (string), filename with path and extension stripped
|
||
|
# - size (int), image file size, in kB
|
||
|
# - etos (bool), is EmuTOS?
|
||
|
# - version (int), TOS version
|
||
|
# - memwait (int), how many secs to wait before memcheck key press
|
||
|
# - fullwait (int), after which time safe to conclude boot to have failed
|
||
|
# - machines (tuple of strings), which Atari machines this TOS supports
|
||
|
|
||
|
def __init__(self, path):
|
||
|
self.path, self.size, self.name = self._add_file(path)
|
||
|
self.version, self.etos = self._add_version()
|
||
|
self.memwait, self.fullwait, self.machines = self._add_info()
|
||
|
|
||
|
|
||
|
def _add_file(self, img):
|
||
|
"get TOS file size and basename for 'img'"
|
||
|
if not os.path.isfile(img):
|
||
|
raise AssertionError("'%s' given as TOS image isn't a file" % img)
|
||
|
|
||
|
size = os.stat(img).st_size
|
||
|
tossizes = (196608, 262144, 524288)
|
||
|
if size not in tossizes:
|
||
|
raise AssertionError("image '%s' size not one of TOS sizes %s" % (img, repr(tossizes)))
|
||
|
|
||
|
name = os.path.basename(img)
|
||
|
name = name[:name.rfind('.')]
|
||
|
return (img, size/1024, name)
|
||
|
|
||
|
|
||
|
def _add_version(self):
|
||
|
"get TOS version and whether it's EmuTOS & supports GEMDOS HD"
|
||
|
f = open(self.path)
|
||
|
f.seek(0x2, 0)
|
||
|
version = (ord(f.read(1)) << 8) + ord(f.read(1))
|
||
|
# older TOS versions don't support autostarting
|
||
|
# programs from GEMDOS HD dir with *.INF files
|
||
|
f.seek(0x2C, 0)
|
||
|
etos = (f.read(4) == "ETOS")
|
||
|
return (version, etos)
|
||
|
|
||
|
|
||
|
def _add_info(self):
|
||
|
"add TOS version specific info of supported machines etc"
|
||
|
name, size, version = self.name, self.size, self.version
|
||
|
|
||
|
if self.etos:
|
||
|
# EmuTOS 512k, 256k and 192k versions have different machine support
|
||
|
if size == 512:
|
||
|
# startup screen on falcon 14MB is really slow
|
||
|
info = (5, 10, ("st", "ste", "tt", "falcon"))
|
||
|
elif size == 256:
|
||
|
info = (2, 8, ("st", "ste", "tt"))
|
||
|
elif size == 192:
|
||
|
info = (0, 6, ("st",))
|
||
|
else:
|
||
|
raise AssertionError("'%s' image size %dkB isn't valid for EmuTOS" % (name, size))
|
||
|
elif version <= 0x100:
|
||
|
# boots up really slow with 4MB
|
||
|
info = (0, 16, ("st",))
|
||
|
elif version <= 0x104:
|
||
|
info = (0, 6, ("st",))
|
||
|
elif version < 0x200:
|
||
|
info = (0, 6, ("ste",))
|
||
|
elif version < 0x300:
|
||
|
# are slower with VDI mode than others
|
||
|
info = (2, 8, ("st", "ste", "tt"))
|
||
|
elif version < 0x400:
|
||
|
# memcheck comes up fast, but boot takes time
|
||
|
info = (2, 8, ("tt",))
|
||
|
elif version < 0x500:
|
||
|
# memcheck takes long to come up with 14MB
|
||
|
info = (3, 8, ("falcon",))
|
||
|
else:
|
||
|
raise AssertionError("'%s' TOS version 0x%x isn't valid" % (name, version))
|
||
|
|
||
|
if self.etos:
|
||
|
print "%s is EmuTOS v%x %dkB" % (name, version, size)
|
||
|
else:
|
||
|
print "%s is normal TOS v%x" % (name, version)
|
||
|
# 0: whether / how long to wait to dismiss memory test
|
||
|
# 1: how long to wait until concluding test failed
|
||
|
# 2: list of machines supported by this TOS version
|
||
|
return info
|
||
|
|
||
|
def supports_gemdos_hd(self):
|
||
|
"whether TOS version supports Hatari's GEMDOS HD emulation"
|
||
|
return (self.version >= 0x0104)
|
||
|
|
||
|
def supports_hdinterface(self, hdinterface):
|
||
|
"whether TOS version supports monitor that is valid for given machine"
|
||
|
# EmuTOS doesn't require drivers to access DOS formatted disks
|
||
|
if self.etos:
|
||
|
# NOTE: IDE support is in EmuTOS since 0.9.0
|
||
|
if hdinterface == "ide" and self.size == 192:
|
||
|
return False
|
||
|
return True
|
||
|
# As ACSI (big endian) and IDE (little endian) images would require
|
||
|
# diffent binary drivers on them and it's not possible to generate
|
||
|
# such images automatically, testing ACSI & IDE images for normal
|
||
|
# TOS isn't support.
|
||
|
#
|
||
|
# (And even with a driver, only TOS 4.x supports IDE.)
|
||
|
return False
|
||
|
|
||
|
def supports_monitor(self, monitortype, machine):
|
||
|
"whether TOS version supports monitor that is valid for given machine"
|
||
|
# other monitor types valid for the machine are
|
||
|
# valid also for TOS that works on it
|
||
|
if monitortype.startswith("vdi"):
|
||
|
# sensible sized VDI modes don't work with TOS4
|
||
|
# (nor make sense with its Videl expander support)
|
||
|
if self.version >= 0x400:
|
||
|
return False
|
||
|
if self.etos:
|
||
|
# smallest EmuTOS image doesn't have any Falcon support
|
||
|
if machine == "falcon" and self.size == 192:
|
||
|
return False
|
||
|
# 2-plane modes don't work properly with real TOS
|
||
|
elif monitortype.endswith("2"):
|
||
|
return False
|
||
|
return True
|
||
|
|
||
|
def supports_32bit_addressing(self):
|
||
|
"whether TOS version supports 32-bit addressing"
|
||
|
if self.etos or self.version >= 0x300:
|
||
|
return True
|
||
|
return False
|
||
|
|
||
|
|
||
|
# -----------------------------------------------
|
||
|
def validate(args, full):
|
||
|
"return set of members not in the full set and given args"
|
||
|
return (set(args).difference(full), args)
|
||
|
|
||
|
class Config:
|
||
|
"Test configuration and validator class"
|
||
|
# full set of possible options
|
||
|
all_disks = ("floppy", "gemdos", "acsi", "ide")
|
||
|
all_graphics = ("mono", "rgb", "vga", "tv", "vdi1", "vdi2", "vdi4")
|
||
|
all_machines = ("st", "ste", "tt", "falcon")
|
||
|
all_memsizes = (0, 1, 2, 4, 6, 8, 10, 12, 14)
|
||
|
|
||
|
# defaults
|
||
|
fast = False
|
||
|
bools = []
|
||
|
disks = ("floppy", "gemdos")
|
||
|
graphics = ("mono", "rgb", "vga", "vdi1", "vdi4")
|
||
|
machines = ("st", "ste", "tt", "falcon")
|
||
|
memsizes = (0, 4, 14)
|
||
|
ttrams = (0, 32)
|
||
|
|
||
|
def __init__(self, argv):
|
||
|
longopts = ["bool=", "disks=", "fast", "graphics=", "help", "machines=", "memsizes=", "ttrams="]
|
||
|
try:
|
||
|
opts, paths = getopt.gnu_getopt(argv[1:], "b:d:fg:hm:s:t:", longopts)
|
||
|
except getopt.GetoptError as error:
|
||
|
self.usage(error)
|
||
|
self.handle_options(opts)
|
||
|
self.images = self.check_images(paths)
|
||
|
print "Test configuration:\n\t", self.disks, self.graphics, self.machines, self.memsizes, self.ttrams
|
||
|
|
||
|
|
||
|
def check_images(self, paths):
|
||
|
"validate given TOS images"
|
||
|
images = []
|
||
|
for img in paths:
|
||
|
try:
|
||
|
images.append(TOS(img))
|
||
|
except AssertionError as msg:
|
||
|
self.usage(msg)
|
||
|
if len(images) < 1:
|
||
|
self.usage("no TOS image files given")
|
||
|
return images
|
||
|
|
||
|
|
||
|
def handle_options(self, opts):
|
||
|
"parse command line options"
|
||
|
unknown = None
|
||
|
for opt, arg in opts:
|
||
|
args = arg.split(",")
|
||
|
if opt in ("-h", "--help"):
|
||
|
self.usage()
|
||
|
if opt in ("-f", "--fast"):
|
||
|
self.fast = True
|
||
|
elif opt in ("-b", "--bool"):
|
||
|
self.bools += args
|
||
|
elif opt in ("-d", "--disks"):
|
||
|
unknown, self.disks = validate(args, self.all_disks)
|
||
|
elif opt in ("-g", "--graphics"):
|
||
|
unknown, self.graphics = validate(args, self.all_graphics)
|
||
|
elif opt in ("-m", "--machines"):
|
||
|
unknown, self.machines = validate(args, self.all_machines)
|
||
|
elif opt in ("-s", "--memsizes"):
|
||
|
try:
|
||
|
args = [int(i) for i in args]
|
||
|
except ValueError:
|
||
|
self.usage("non-numeric memory sizes: %s" % arg)
|
||
|
unknown, self.memsizes = validate(args, self.all_memsizes)
|
||
|
elif opt in ("-t", "--ttrams"):
|
||
|
try:
|
||
|
args = [int(i) for i in args]
|
||
|
except ValueError:
|
||
|
self.usage("non-numeric TT-RAM sizes: %s" % arg)
|
||
|
for ram in args:
|
||
|
if ram < 0 or ram > 256:
|
||
|
self.usage("invalid TT-RAM (0-256) size: %d" % ram)
|
||
|
self.ttrams = args
|
||
|
if unknown:
|
||
|
self.usage("%s are invalid values for %s" % (list(unknown), opt))
|
||
|
|
||
|
|
||
|
def usage(self, msg=None):
|
||
|
"output program usage information"
|
||
|
name = os.path.basename(sys.argv[0])
|
||
|
print __doc__
|
||
|
print("""
|
||
|
Usage: %s [options] <TOS image files>
|
||
|
|
||
|
Options:
|
||
|
\t-h, --help\tthis help
|
||
|
\t-f, --fast\tdo tests with "--fastfdc yes --fast-forward yes"
|
||
|
\t-d, --disks\t%s
|
||
|
\t-g, --graphics\t%s
|
||
|
\t-m, --machines\t%s
|
||
|
\t-s, --memsizes\t%s
|
||
|
\t-t, --ttrams\t%s
|
||
|
\t-b, --bool\t(extra boolean Hatari options to test)
|
||
|
|
||
|
Multiple values for an option need to be comma separated. If some
|
||
|
option isn't given, default list of values will be used for that.
|
||
|
|
||
|
For example:
|
||
|
%s \\
|
||
|
\t--disks gemdos \\
|
||
|
\t--machines st,tt \\
|
||
|
\t--memsizes 0,4,14 \\
|
||
|
\t--ttrams 0,32 \\
|
||
|
\t--graphics mono,rgb \\
|
||
|
\t-bool --compatible,--rtc
|
||
|
""" % (name, self.all_disks, self.all_graphics, self.all_machines, self.all_memsizes, self.ttrams, name))
|
||
|
if msg:
|
||
|
print("ERROR: %s\n" % msg)
|
||
|
sys.exit(1)
|
||
|
|
||
|
|
||
|
def valid_disktype(self, machine, tos, disktype):
|
||
|
"return whether given disk type is valid for given machine / TOS version"
|
||
|
if disktype == "floppy":
|
||
|
return True
|
||
|
if disktype == "gemdos":
|
||
|
return tos.supports_gemdos_hd()
|
||
|
|
||
|
if machine in ("st", "ste"):
|
||
|
hdinterface = "acsi"
|
||
|
elif machine == "tt":
|
||
|
# TODO: according to todo.txt, Hatari ACSI emulation
|
||
|
# doesn't currently work for TT
|
||
|
hdinterface = "acsi"
|
||
|
elif machine == "falcon":
|
||
|
hdinterface = "ide"
|
||
|
else:
|
||
|
raise AssertionError("unknown machine %s" % machine)
|
||
|
|
||
|
if disktype in hdinterface:
|
||
|
return tos.supports_hdinterface(hdinterface)
|
||
|
return False
|
||
|
|
||
|
def valid_monitortype(self, machine, tos, monitortype):
|
||
|
"return whether given monitor type is valid for given machine / TOS version"
|
||
|
if machine in ("st", "ste"):
|
||
|
monitors = ("mono", "rgb", "tv", "vdi1", "vdi2", "vdi4")
|
||
|
elif machine == "tt":
|
||
|
monitors = ("mono", "vga", "vdi1", "vdi2", "vdi4")
|
||
|
elif machine == "falcon":
|
||
|
monitors = ("mono", "rgb", "vga", "vdi1", "vdi2", "vdi4")
|
||
|
else:
|
||
|
raise AssertionError("unknown machine %s" % machine)
|
||
|
if monitortype in monitors:
|
||
|
return tos.supports_monitor(monitortype, machine)
|
||
|
return False
|
||
|
|
||
|
def valid_memsize(self, machine, memsize):
|
||
|
"return whether given memory size is valid for given machine"
|
||
|
if machine in ("st", "ste"):
|
||
|
sizes = (0, 1, 2, 4)
|
||
|
elif machine in ("tt", "falcon"):
|
||
|
# 0 (512kB) isn't valid memory size for Falcon/TT
|
||
|
sizes = self.all_memsizes[1:]
|
||
|
else:
|
||
|
raise AssertionError("unknown machine %s" % machine)
|
||
|
if memsize in sizes:
|
||
|
return True
|
||
|
return False
|
||
|
|
||
|
def valid_ttram(self, machine, tos, ttram, winuae):
|
||
|
"return whether given TT-RAM size is valid for given machine"
|
||
|
if machine in ("st", "ste"):
|
||
|
if ttram == 0:
|
||
|
return True
|
||
|
elif machine in ("tt", "falcon"):
|
||
|
if ttram == 0:
|
||
|
return True
|
||
|
if not winuae:
|
||
|
warning("TT-RAM / 32-bit addressing is supported only by Hatari WinUAE CPU core version")
|
||
|
return False
|
||
|
if ttram < 0 or ttram > 256:
|
||
|
return False
|
||
|
return tos.supports_32bit_addressing()
|
||
|
else:
|
||
|
raise AssertionError("unknown machine %s" % machine)
|
||
|
return False
|
||
|
|
||
|
|
||
|
# -----------------------------------------------
|
||
|
def verify_file_match(srcfile, dstfile):
|
||
|
"return error string if given files are not identical"
|
||
|
if not os.path.exists(dstfile):
|
||
|
return "file '%s' missing" % dstfile
|
||
|
i = 0
|
||
|
f2 = open(srcfile)
|
||
|
for line in open(dstfile).readlines():
|
||
|
i += 1
|
||
|
if line != f2.readline():
|
||
|
return "file '%s' line %d doesn't match file '%s'" % (dstfile, i, srcfile)
|
||
|
|
||
|
def verify_file_empty(srcfile):
|
||
|
"return error string if given file isn't empty"
|
||
|
if not os.path.exists(srcfile):
|
||
|
return "file '%s' missing" % srcfile
|
||
|
lines = len(open(srcfile).readlines())
|
||
|
if lines > 0:
|
||
|
return "file '%s' isn't empty (%d lines)" % (srcfile, lines)
|
||
|
|
||
|
class Tester:
|
||
|
"test driver class"
|
||
|
output = "output" + os.path.sep
|
||
|
report = output + "report.txt"
|
||
|
# dummy Hatari config file to force suitable default options
|
||
|
dummycfg = "dummy.cfg"
|
||
|
defaults = [sys.argv[0], "--configfile", dummycfg]
|
||
|
testprg = "disk" + os.path.sep + "GEMDOS.PRG"
|
||
|
textinput = "disk" + os.path.sep + "TEXT"
|
||
|
textoutput= "disk" + os.path.sep + "TEST"
|
||
|
printout = output + "printer-out"
|
||
|
serialout = output + "serial-out"
|
||
|
fifofile = output + "midi-out"
|
||
|
bootauto = "bootauto.st.gz" # TOS old not to support GEMDOS HD either
|
||
|
bootdesk = "bootdesk.st.gz"
|
||
|
hdimage = "hd.img"
|
||
|
ideimage = "hd.img" # for now use the same image as for ACSI
|
||
|
results = None
|
||
|
|
||
|
def __init__(self):
|
||
|
"test setup initialization"
|
||
|
self.cleanup_all_files()
|
||
|
self.create_config()
|
||
|
self.create_files()
|
||
|
signal.signal(signal.SIGALRM, self.alarm_handler)
|
||
|
hatari = hconsole.Hatari(["--confirm-quit", "no"])
|
||
|
self.winuae = hatari.winuae
|
||
|
hatari.kill_hatari()
|
||
|
|
||
|
def alarm_handler(self, signum, dummy):
|
||
|
"output error if (timer) signal came before passing current test stage"
|
||
|
if signum == signal.SIGALRM:
|
||
|
print "ERROR: timeout triggered -> test FAILED"
|
||
|
else:
|
||
|
print "ERROR: unknown signal %d received" % signum
|
||
|
raise AssertionError
|
||
|
|
||
|
def create_config(self):
|
||
|
"create Hatari configuration file for testing"
|
||
|
# write specific configuration to:
|
||
|
# - avoid user's own config
|
||
|
# - get rid of the dialogs
|
||
|
# - don't warp mouse on resolution changes
|
||
|
# - limit Videl zooming to same sizes as ST screen zooming
|
||
|
# - get rid of statusbar and borders in TOS screenshots
|
||
|
# to make them smaller & more consistent
|
||
|
# - disable GEMDOS emu by default
|
||
|
# - use empty floppy disk image to avoid TOS error when no disks
|
||
|
# - set printer output file
|
||
|
# - disable serial in and set serial output file
|
||
|
# - disable MIDI in, use MIDI out as fifo file to signify test completion
|
||
|
dummy = open(self.dummycfg, "w")
|
||
|
dummy.write("[Log]\nnAlertDlgLogLevel = 0\nbConfirmQuit = FALSE\n\n")
|
||
|
dummy.write("[Screen]\nnMaxWidth = 832\nnMaxHeight = 576\nbCrop = TRUE\nbAllowOverscan = FALSE\nbMouseWarp = FALSE\n\n")
|
||
|
dummy.write("[HardDisk]\nbUseHardDiskDirectory = FALSE\n\n")
|
||
|
dummy.write("[Floppy]\nszDiskAFileName = blank-a.st.gz\n\n")
|
||
|
dummy.write("[Printer]\nbEnablePrinting = TRUE\nszPrintToFileName = %s\n\n" % self.printout)
|
||
|
dummy.write("[RS232]\nbEnableRS232 = TRUE\nszInFileName = \nszOutFileName = %s\n\n" % self.serialout)
|
||
|
dummy.write("[Midi]\nbEnableMidi = TRUE\nsMidiInFileName = \nsMidiOutFileName = %s\n\n" % self.fifofile)
|
||
|
dummy.close()
|
||
|
|
||
|
def cleanup_all_files(self):
|
||
|
"clean out any files left over from last run"
|
||
|
for path in (self.fifofile, "grab0001.png", "grab0001.bmp"):
|
||
|
if os.path.exists(path):
|
||
|
os.remove(path)
|
||
|
self.cleanup_test_files()
|
||
|
|
||
|
def create_files(self):
|
||
|
"create files needed during testing"
|
||
|
if not os.path.exists(self.output):
|
||
|
os.mkdir(self.output)
|
||
|
if not os.path.exists(self.fifofile):
|
||
|
os.mkfifo(self.fifofile)
|
||
|
|
||
|
def get_screenshot(self, instance, identity):
|
||
|
"save screenshot of test end result"
|
||
|
instance.run("screenshot")
|
||
|
if os.path.isfile("grab0001.png"):
|
||
|
os.rename("grab0001.png", self.output + identity + ".png")
|
||
|
elif os.path.isfile("grab0001.bmp"):
|
||
|
os.rename("grab0001.bmp", self.output + identity + ".bmp")
|
||
|
else:
|
||
|
warning("failed to locate screenshot grab0001.{png,bmp}")
|
||
|
|
||
|
def cleanup_test_files(self):
|
||
|
"remove unnecessary files at end of test"
|
||
|
for path in (self.serialout, self.printout):
|
||
|
if os.path.exists(path):
|
||
|
os.remove(path)
|
||
|
|
||
|
def verify_output(self, identity, tos, memory):
|
||
|
"do verification on all test output"
|
||
|
# both tos version and amount of memory affect what
|
||
|
# GEMDOS operations work properly...
|
||
|
ok = True
|
||
|
# check file truncate
|
||
|
error = verify_file_empty(self.textoutput)
|
||
|
if error:
|
||
|
print "ERROR: file wasn't truncated:\n\t%s" % error
|
||
|
os.rename(self.textoutput, "%s.%s" % (self.textoutput, identity))
|
||
|
ok = False
|
||
|
# check serial output
|
||
|
error = verify_file_match(self.textinput, self.serialout)
|
||
|
if error:
|
||
|
print "ERROR: serial output doesn't match input:\n\t%s" % error
|
||
|
os.rename(self.serialout, "%s.%s" % (self.serialout, identity))
|
||
|
ok = False
|
||
|
# check printer output
|
||
|
error = verify_file_match(self.textinput, self.printout)
|
||
|
if error:
|
||
|
if tos.etos or tos.version > 0x206 or (tos.version == 0x100 and memory > 1):
|
||
|
print "ERROR: printer output doesn't match input (EmuTOS, TOS v1.00 or >v2.06)\n\t%s" % error
|
||
|
os.rename(self.printout, "%s.%s" % (self.printout, identity))
|
||
|
ok = False
|
||
|
else:
|
||
|
if os.path.exists(self.printout):
|
||
|
error = verify_file_empty(self.printout)
|
||
|
if error:
|
||
|
print "WARNING: unexpected printer output (TOS v1.02 - TOS v2.06):\n\t%s" % error
|
||
|
os.rename(self.printout, "%s.%s" % (self.printout, identity))
|
||
|
self.cleanup_test_files()
|
||
|
return ok
|
||
|
|
||
|
|
||
|
def wait_fifo(self, fifo, timeout):
|
||
|
"wait_fifo(fifo) -> wait until fifo has input until given timeout"
|
||
|
print("Waiting %ss for fifo '%s' input..." % (timeout, self.fifofile))
|
||
|
sets = select.select([fifo], [], [], timeout)
|
||
|
if sets[0]:
|
||
|
print "...test program is READY, read what's in its fifo:",
|
||
|
try:
|
||
|
# read can block, make sure it's eventually interrupted
|
||
|
signal.alarm(timeout)
|
||
|
line = fifo.readline().strip()
|
||
|
signal.alarm(0)
|
||
|
print line
|
||
|
return (True, (line == "success"))
|
||
|
except IOError:
|
||
|
pass
|
||
|
print "ERROR: TIMEOUT without fifo input, BOOT FAILED"
|
||
|
return (False, False)
|
||
|
|
||
|
|
||
|
def open_fifo(self, timeout):
|
||
|
"open fifo for test program output"
|
||
|
try:
|
||
|
signal.alarm(timeout)
|
||
|
# open returns after Hatari has opened the other
|
||
|
# end of fifo, or when SIGALARM interrupts it
|
||
|
fifo = open(self.fifofile, "r")
|
||
|
# cancel signal
|
||
|
signal.alarm(0)
|
||
|
return fifo
|
||
|
except IOError:
|
||
|
print "ERROR: fifo open IOError!"
|
||
|
return None
|
||
|
|
||
|
def test(self, identity, testargs, tos, memory):
|
||
|
"run single boot test with given args and waits"
|
||
|
# Hatari command line options, don't exit if Hatari exits
|
||
|
instance = hconsole.Main(self.defaults + testargs, False)
|
||
|
fifo = self.open_fifo(tos.fullwait)
|
||
|
if not fifo:
|
||
|
print "ERROR: failed to get fifo to Hatari!"
|
||
|
self.get_screenshot(instance, identity)
|
||
|
instance.run("kill")
|
||
|
return (False, False, False, False)
|
||
|
else:
|
||
|
init_ok = True
|
||
|
|
||
|
if tos.memwait:
|
||
|
# pass memory test
|
||
|
time.sleep(tos.memwait)
|
||
|
instance.run("keypress %s" % hconsole.Scancode.Space)
|
||
|
|
||
|
# wait until test program has been run and output something to fifo
|
||
|
prog_ok, tests_ok = self.wait_fifo(fifo, tos.fullwait)
|
||
|
if tests_ok:
|
||
|
output_ok = self.verify_output(identity, tos, memory)
|
||
|
else:
|
||
|
print "TODO: collect info on failure, regs etc"
|
||
|
output_ok = False
|
||
|
|
||
|
# get screenshot after a small wait (to guarantee all
|
||
|
# test program output got to screen even with frameskip)
|
||
|
time.sleep(0.2)
|
||
|
self.get_screenshot(instance, identity)
|
||
|
# get rid of this Hatari instance
|
||
|
instance.run("kill")
|
||
|
return (init_ok, prog_ok, tests_ok, output_ok)
|
||
|
|
||
|
|
||
|
def prepare_test(self, config, tos, machine, monitor, disk, memory, ttram, extra):
|
||
|
"compose test ID and Hatari command line args, then call .test()"
|
||
|
identity = "%s-%s-%s-%s-%dM-%dM" % (tos.name, machine, monitor, disk, memory, ttram)
|
||
|
testargs = ["--tos", tos.path, "--machine", machine, "--memsize", str(memory)]
|
||
|
if self.winuae:
|
||
|
if ttram:
|
||
|
testargs += ["--addr24", "off", "--ttram", str(ttram)]
|
||
|
else:
|
||
|
testargs += ["--addr24", "on"]
|
||
|
|
||
|
if extra:
|
||
|
identity += "-%s%s" % (extra[0].replace("-", ""), extra[1])
|
||
|
testargs += extra
|
||
|
|
||
|
if monitor.startswith("vdi"):
|
||
|
planes = monitor[-1]
|
||
|
testargs += ["--vdi-planes", planes]
|
||
|
if planes == "1":
|
||
|
testargs += ["--vdi-width", "800", "--vdi-height", "600"]
|
||
|
elif planes == "2":
|
||
|
testargs += ["--vdi-width", "640", "--vdi-height", "480"]
|
||
|
else:
|
||
|
testargs += ["--vdi-width", "640", "--vdi-height", "400"]
|
||
|
else:
|
||
|
testargs += ["--monitor", monitor]
|
||
|
|
||
|
if config.fast:
|
||
|
testargs += ["--fastfdc", "yes", "--fast-forward", "yes"]
|
||
|
|
||
|
if disk == "gemdos":
|
||
|
# use Hatari autostart, must be last thing added to testargs!
|
||
|
testargs += [self.testprg]
|
||
|
elif disk == "floppy":
|
||
|
if tos.supports_gemdos_hd():
|
||
|
# GEMDOS HD supporting TOSes support also INF file autostart
|
||
|
testargs += ["--disk-a", self.bootdesk]
|
||
|
else:
|
||
|
testargs += ["--disk-a", self.bootauto]
|
||
|
elif disk == "acsi":
|
||
|
testargs += ["--acsi", self.hdimage]
|
||
|
elif disk == "ide":
|
||
|
testargs += ["--ide-master", self.ideimage]
|
||
|
else:
|
||
|
raise AssertionError("unknown disk type '%s'" % disk)
|
||
|
|
||
|
results = self.test(identity, testargs, tos, memory)
|
||
|
self.results[tos.name].append((identity, results))
|
||
|
|
||
|
def run(self, config):
|
||
|
"run all TOS boot test combinations"
|
||
|
self.results = {}
|
||
|
for tos in config.images:
|
||
|
self.results[tos.name] = []
|
||
|
print
|
||
|
print "***** TESTING: %s *****" % tos.name
|
||
|
print
|
||
|
count = 0
|
||
|
for machine in config.machines:
|
||
|
if machine not in tos.machines:
|
||
|
continue
|
||
|
for monitor in config.graphics:
|
||
|
if not config.valid_monitortype(machine, tos, monitor):
|
||
|
continue
|
||
|
for memory in config.memsizes:
|
||
|
if not config.valid_memsize(machine, memory):
|
||
|
continue
|
||
|
for ttram in config.ttrams:
|
||
|
if not config.valid_ttram(machine, tos, ttram, self.winuae):
|
||
|
continue
|
||
|
for disk in config.disks:
|
||
|
if not config.valid_disktype(machine, tos, disk):
|
||
|
continue
|
||
|
if config.bools:
|
||
|
for opt in config.bools:
|
||
|
for val in ('on', 'off'):
|
||
|
self.prepare_test(config, tos, machine, monitor, disk, memory, ttram, [opt, val])
|
||
|
count += 1
|
||
|
else:
|
||
|
self.prepare_test(config, tos, machine, monitor, disk, memory, ttram, None)
|
||
|
count += 1
|
||
|
if not count:
|
||
|
warning("no matching configuration for TOS '%s'" % tos.name)
|
||
|
self.cleanup_all_files()
|
||
|
|
||
|
def summary(self):
|
||
|
"summarize test results"
|
||
|
cases = [0, 0, 0, 0]
|
||
|
passed = [0, 0, 0, 0]
|
||
|
tosnames = self.results.keys()
|
||
|
tosnames.sort()
|
||
|
|
||
|
report = open(self.report, "w")
|
||
|
report.write("\nTest report:\n------------\n")
|
||
|
for tos in tosnames:
|
||
|
configs = self.results[tos]
|
||
|
if not configs:
|
||
|
report.write("\n+ WARNING: no configurations for '%s' TOS!\n" % tos)
|
||
|
continue
|
||
|
report.write("\n+ %s:\n" % tos)
|
||
|
for config, results in configs:
|
||
|
# convert True/False bools to FAIL/pass strings
|
||
|
values = [("FAIL","pass")[int(r)] for r in results]
|
||
|
report.write(" - %s: %s\n" % (config, values))
|
||
|
# update statistics
|
||
|
for idx in range(len(results)):
|
||
|
cases[idx] += 1
|
||
|
passed[idx] += results[idx]
|
||
|
|
||
|
report.write("\nSummary of FAIL/pass values:\n")
|
||
|
idx = 0
|
||
|
for line in ("Hatari init", "Test program running", "Test program test-cases", "Test program output"):
|
||
|
passes, total = passed[idx], cases[idx]
|
||
|
if passes < total:
|
||
|
if not passes:
|
||
|
result = "all %d FAILED" % total
|
||
|
else:
|
||
|
result = "%d/%d passed" % (passes, total)
|
||
|
else:
|
||
|
result = "all %d passed" % total
|
||
|
report.write("- %s: %s\n" % (line, result))
|
||
|
idx += 1
|
||
|
report.write("\n")
|
||
|
|
||
|
# print report out too
|
||
|
print "--- %s ---" % self.report
|
||
|
report = open(self.report, "r")
|
||
|
for line in report.readlines():
|
||
|
print line.strip()
|
||
|
|
||
|
|
||
|
# -----------------------------------------------
|
||
|
def main():
|
||
|
"tester main function"
|
||
|
info = "Hatari TOS bootup tester"
|
||
|
print "\n%s\n%s\n" % (info, "-"*len(info))
|
||
|
config = Config(sys.argv)
|
||
|
tester = Tester()
|
||
|
tester.run(config)
|
||
|
tester.summary()
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
main()
|