Skip to content
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
15 changes: 12 additions & 3 deletions .github/workflows/deploy-pages.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: Deploy Pages

on:
workflow_dispatch:
workflow_call:
inputs:
artifact_pattern:
Expand All @@ -15,11 +16,17 @@ jobs:
deploy:
runs-on: windows-latest
steps:
- uses: actions/checkout@v6

- uses: actions/setup-python@v6
with:
python-version: '3.x'

- name: Install build dependencies
run: python -m pip install -r requirements-core.txt

- name: Download emulator results
if: ${{ inputs.artifact_pattern != '' }}
uses: actions/download-artifact@v5
with:
pattern: ${{ inputs.artifact_pattern }}
Expand All @@ -37,10 +44,12 @@ jobs:
} else {
Write-Host "No emulator JSON artifacts found to publish."
}
python main.py --dump-emulators-json --dump-tests-json
Copy-Item emulators.json, tests.json -Destination pages -Force
python build.py --results-dir pages --emulators pages/emulators.json --tests pages/tests.json --output pages/index.html
Remove-Item pages\build.py -ErrorAction SilentlyContinue
Set-Location pages
python build.py
git add *.json
git add index.html
git add -A
if (git diff --cached --quiet) {
Write-Host "No gh-pages changes to commit."
exit 0
Expand Down
67 changes: 67 additions & 0 deletions build.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import argparse
import json
import os
from time import gmtime, strftime


def main():
parser = argparse.ArgumentParser()
parser.add_argument('--emulators', default='emulators.json')
parser.add_argument('--tests', default='tests.json')
parser.add_argument('--results-dir', default='.')
parser.add_argument('--output', default='index.html')
args = parser.parse_args()

emulators = json.load(open(args.emulators, 'rt', encoding='utf-8'))
tests = json.load(open(args.tests, 'rt', encoding='utf-8'))

for name in emulators:
result_file = os.path.join(args.results_dir, emulators[name]['file'])
if os.path.exists(result_file):
data = json.load(open(result_file, 'rt', encoding='utf-8'))
emulators[name].update(data)
emulators[name]['passed'] = len([result for result in data['tests'].values() if result['result'] != 'FAIL'])
else:
emulators[name].update({'passed': 0, 'tests': {}})

f = open(args.output, 'wt', encoding='utf-8')
f.write("""
<html><head><style>
table { border-collapse: collapse }
.emulator { position: sticky; top: 0px; }
.test { position: sticky; left: 0px; }
.tooltiptext { visibility: hidden; width: 200px; background-color: black; color: #fff; text-align: center; padding: 5px 0; border-radius: 6px; position: absolute; z-index: 1; left: 102%; }
tr:hover .tooltiptext { visibility: visible; }
td, th { border: #333 solid 1px; text-align: center; line-height: 1.5}
.PASS { background-color: #6e2 }
.FAIL { background-color: #e44 }
.UNKNOWN { background-color: #fd6 }
td {font-size:80%}
th{background:#eee}
th:first-child{text-align:right; padding-right:4px}
.screenshot { width: 160; height: 144; }
body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif}
</style></head><body><table>\n""")
f.write("<tr><th style=\"text-align:left\">Updated On<br>" + strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime()) + "</th>\n")
for name, emulator in sorted(emulators.items(), key=lambda n: -n[1]['passed']):
f.write(" <th class='emulator'><a href=\"%s\">%s</a> (%d/%d)</th>\n" % (emulator['url'], name, emulator['passed'], len(emulator['tests'])))
f.write("</tr>\n")
for test in tests:
name = test['name'].replace("/", "/&#8203;")
if test['url']:
name = "<a href=\"%s\">%s</a>" % (test['url'], name)
if test['description']:
name += "<span class=\"tooltiptext\">%s</span>" % (test['description'])
f.write("<tr><th class='test'>%s</th>\n" % (name))
for name, emulator in sorted(emulators.items(), key=lambda n: -n[1]['passed']):
result = emulator['tests'].get(test['name'])
if result:
f.write(" <td class='%s'>%s<br><img class='screenshot' src='data:image/png;base64,%s'></td>\n" % (result['result'], result['result'], result['screenshot']))
else:
f.write(" <td>No result</td>\n")
f.write("</tr>\n")
f.write("</table></body></html>")


if __name__ == '__main__':
main()
169 changes: 133 additions & 36 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,27 +53,119 @@ def _new_instance(module_name, class_name):
return getattr(module, class_name)()


def load_emulators(filter_data):
emulator_factories = [
(lambda: _new_instance("emulators.bdm", "BDM"), ["Beaten Dying Moon", "bdm", "beaten"]),
(lambda: _new_instance("emulators.mgba", "MGBA"), ["mGBA", "mgba"]),
(lambda: _new_instance("emulators.kigb", "KiGB"), ["KiGB", "kigb"]),
(lambda: _new_instance("emulators.sameboy", "SameBoy"), ["SameBoy", "sameboy"]),
(lambda: _new_instance("emulators.bgb", "BGB"), ["bgb"]),
(lambda: _new_instance("emulators.vba", "VBA"), ["VisualBoyAdvance", "vba"]),
(lambda: _new_instance("emulators.vba", "VBAM"), ["VisualBoyAdvance-M", "vba-m", "vbam"]),
(lambda: _new_instance("emulators.nocash", "NoCash"), ["No$gmb", "nocash", "no$gmb"]),
(lambda: _new_instance("emulators.gambatte", "GambatteSpeedrun"), ["GambatteSpeedrun", "gambatte"]),
(lambda: _new_instance("emulators.emulicious", "Emulicious"), ["Emulicious", "emulicious"]),
(lambda: _new_instance("emulators.goomba", "Goomba"), ["Goomba", "goomba"]),
(lambda: _new_instance("emulators.binjgb", "Binjgb"), ["binjgb"]),
(lambda: _new_instance("emulators.pyboy", "PyBoy"), ["PyBoy", "pyboy"]),
(lambda: _new_instance("emulators.ares", "Ares"), ["ares"]),
(lambda: _new_instance("emulators.emmy", "Emmy"), ["Emmy", "emmy"]),
(lambda: _new_instance("emulators.gameroy", "GameRoy"), ["gameroy", "GameRoy"]),
EMULATOR_SPECS = [
{
'factory': lambda: _new_instance("emulators.bdm", "BDM"),
'keywords': ["Beaten Dying Moon", "bdm", "beaten"],
'name': "Beaten Dying Moon",
'url': "https://mattcurrie.com/bdm-demo/",
},
{
'factory': lambda: _new_instance("emulators.mgba", "MGBA"),
'keywords': ["mGBA", "mgba"],
'name': "mGBA",
'url': "https://mgba.io/",
},
{
'factory': lambda: _new_instance("emulators.kigb", "KiGB"),
'keywords': ["KiGB", "kigb"],
'name': "KiGB",
'url': "http://kigb.emuunlim.com/",
},
{
'factory': lambda: _new_instance("emulators.sameboy", "SameBoy"),
'keywords': ["SameBoy", "sameboy"],
'name': "SameBoy",
'url': "https://sameboy.github.io/",
},
{
'factory': lambda: _new_instance("emulators.bgb", "BGB"),
'keywords': ["bgb"],
'name': "bgb",
'url': "https://bgb.bircd.org/",
},
{
'factory': lambda: _new_instance("emulators.vba", "VBA"),
'keywords': ["VisualBoyAdvance", "vba"],
'name': "VisualBoyAdvance",
'url': "https://sourceforge.net/projects/vba",
},
{
'factory': lambda: _new_instance("emulators.vba", "VBAM"),
'keywords': ["VisualBoyAdvance-M", "vba-m", "vbam"],
'name': "VisualBoyAdvance-M",
'url': "https://vba-m.com/",
},
{
'factory': lambda: _new_instance("emulators.nocash", "NoCash"),
'keywords': ["No$gmb", "nocash", "no$gmb"],
'name': "No$gmb",
'url': "https://problemkaputt.de/gmb.htm",
},
{
'factory': lambda: _new_instance("emulators.gambatte", "GambatteSpeedrun"),
'keywords': ["GambatteSpeedrun", "gambatte"],
'name': "GambatteSpeedrun",
'url': "https://github.com/pokemon-speedrunning/gambatte-speedrun",
},
{
'factory': lambda: _new_instance("emulators.emulicious", "Emulicious"),
'keywords': ["Emulicious", "emulicious"],
'name': "Emulicious",
'url': "https://emulicious.net/",
},
{
'factory': lambda: _new_instance("emulators.goomba", "Goomba"),
'keywords': ["Goomba", "goomba"],
'name': "Goomba",
'url': "https://www.dwedit.org/gba/goombacolor.php",
},
{
'factory': lambda: _new_instance("emulators.binjgb", "Binjgb"),
'keywords': ["binjgb"],
'name': "binjgb",
'url': "https://github.com/binji/binjgb/releases",
},
{
'factory': lambda: _new_instance("emulators.pyboy", "PyBoy"),
'keywords': ["PyBoy", "pyboy"],
'name': "PyBoy",
'url': "https://github.com/Baekalfen/PyBoy",
},
{
'factory': lambda: _new_instance("emulators.ares", "Ares"),
'keywords': ["ares"],
'name': "ares",
'url': "https://ares-emu.net/",
},
{
'factory': lambda: _new_instance("emulators.emmy", "Emmy"),
'keywords': ["Emmy", "emmy"],
'name': "Emmy",
'url': "https://emmy.n1ark.com/",
},
{
'factory': lambda: _new_instance("emulators.gameroy", "GameRoy"),
'keywords': ["gameroy", "GameRoy"],
'name': "gameroy",
'url': "https://github.com/Rodrigodd/gameroy",
},
]


def get_emulator_specs(filter_data):
return [
spec for spec in EMULATOR_SPECS
if _matches_emulator_filter(filter_data, spec['keywords'])
]

return [factory() for factory, keywords in emulator_factories if _matches_emulator_filter(filter_data, keywords)]

def get_emulator_json_filename(name):
return "%s.json" % (name.replace(" ", "_").lower())


def load_emulators(filter_data):
return [spec['factory']() for spec in get_emulator_specs(filter_data)]


tests = testroms.acid.all + testroms.blargg.all + testroms.daid.all + testroms.ax6.all + testroms.mooneye.all + testroms.samesuite.all + testroms.hacktix.all + testroms.cpp.all + testroms.mealybug.all
Expand Down Expand Up @@ -124,26 +216,14 @@ def checkFilter(input, filter_data):
for test in tests
if checkFilter(test, args.test) and checkFilter(test.model, args.model)
]
emulators = load_emulators(args.emulator)

print("%d emulators" % (len(emulators)))
print("%d tests" % (len(tests)))
emulator_specs = get_emulator_specs(args.emulator)

if args.get_runtime:
for emulator in emulators:
emulator.setup()
for test in tests:
if not checkFilter(test, args.test):
continue
print("%s: %s: %g seconds" % (emulator, test, emulator.getRunTimeFor(test)))
emulator.undoSetup()
sys.exit()
if args.dump_emulators_json:
json.dump({
str(emulator): {
"file": emulator.getJsonFilename(),
"url": emulator.url,
} for emulator in emulators
spec['name']: {
'file': get_emulator_json_filename(spec['name']),
'url': spec['url'],
} for spec in emulator_specs
}, open("emulators.json", "wt"), indent=" ")
if args.dump_tests_json:
json.dump([
Expand All @@ -154,6 +234,23 @@ def checkFilter(input, filter_data):
} for test in tests
], open("tests.json", "wt"), indent=" ")
if args.dump_tests_json or args.dump_emulators_json:
print("%d emulators" % (len(emulator_specs)))
print("%d tests" % (len(tests)))
sys.exit()

emulators = load_emulators(args.emulator)

print("%d emulators" % (len(emulators)))
print("%d tests" % (len(tests)))

if args.get_runtime:
for emulator in emulators:
emulator.setup()
for test in tests:
if not checkFilter(test, args.test):
continue
print("%s: %s: %g seconds" % (emulator, test, emulator.getRunTimeFor(test)))
emulator.undoSetup()
sys.exit()

if args.get_startuptime:
Expand Down