Skip to content

Commit

Permalink
Improve the CI to automatically check the accuracy of Hades on each c…
Browse files Browse the repository at this point in the history
…ommit and pull request.
  • Loading branch information
Arignir committed Dec 18, 2023
1 parent 485775a commit 67a6864
Show file tree
Hide file tree
Showing 16 changed files with 307 additions and 10 deletions.
70 changes: 70 additions & 0 deletions .github/workflows/accuracy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: Accuracy

on: [push, pull_request, workflow_dispatch]

jobs:
linux:
runs-on: ubuntu-latest
steps:
- name: Fetch Source Code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install Dependencies
run: |
sudo apt-get update
sudo apt-get install -y meson ninja-build libsdl2-dev libglew-dev libgtk-3-dev libreadline-dev libedit-dev libcapstone-dev
- name: Build Hades w/ Debugger
run: |
meson build --werror -Dwith_debugger=true
cd build
ninja
- name: Download Test Roms
run: |
# Download BIOS
echo "$BIOS_DATA" | base64 -d | gpg --pinentry-mode loopback --passphrase "$BIOS_KEY" -d -o ./bios.bin
# Create ROMs directory
mkdir roms
cd roms
# Download AGS
declare ags_url="$(echo "$AGS_URL" | base64 -d | gpg --pinentry-mode loopback --passphrase "$AGS_KEY" -d)"
wget "$ags_url" -O ags.zip
unzip ags.zip
shred -u ags.zip
mv AGB_*.gba ags.gba
# Download the remaining testing ROMs
wget https://raw.githubusercontent.com/jsmolka/gba-tests/master/arm/arm.gba
wget https://raw.githubusercontent.com/jsmolka/gba-tests/master/thumb/thumb.gba
wget https://raw.githubusercontent.com/Arignir/Hades-Tests/master/roms/dma-start-delay.gba
wget https://raw.githubusercontent.com/Arignir/Hades-Tests/master/roms/openbus-bios.gba
wget https://raw.githubusercontent.com/Arignir/Hades-Tests/master/roms/timer-basic.gba
env:
BIOS_DATA: ${{ secrets.BIOS_DATA }}
BIOS_KEY: ${{ secrets.BIOS_KEY }}
AGS_URL: ${{ secrets.AGS_URL }}
AGS_KEY: ${{ secrets.AGS_KEY }}
- name: Check Accuracy
run: |
python3 ./accuracy/check.py --binary ./build/hades --roms ./roms/
- name: Collect Screenshots
uses: actions/upload-artifact@v3
if: always()
with:
name: tests-screenshots
path: './.tests_screenshots/'
if-no-files-found: error
- name: Cleanup
if: always()
run: |
if [[ -f ./bios.bin ]]; then
shred -u ./bios.bin
echo "BIOS deleted"
fi
if [[ -f ./roms/ags.gba ]]; then
shred -u ./roms/ags.gba
echo "AGS deleted"
fi
12 changes: 6 additions & 6 deletions .github/workflows/main.yml → .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ jobs:
run:
shell: msys2 {0}
steps:
- name: 'Sync source code'
- name: Fetch Source Code
uses: actions/checkout@v4
with:
submodules: 'recursive'
submodules: recursive
- name: Install Dependencies
uses: msys2/setup-msys2@v2
with:
Expand All @@ -35,10 +35,10 @@ jobs:
mac-os:
runs-on: macos-latest
steps:
- name: 'Sync source code'
- name: Fetch Source Code
uses: actions/checkout@v4
with:
submodules: 'recursive'
submodules: recursive
- name: Install Dependencies
run: |
brew install meson ninja sdl2 glew create-dmg
Expand Down Expand Up @@ -130,10 +130,10 @@ jobs:
linux:
runs-on: ubuntu-latest
steps:
- name: 'Sync source code'
- name: Fetch Source Code
uses: actions/checkout@v4
with:
submodules: 'recursive'
submodules: recursive
- name: Install Dependencies
run: |
sudo apt-get update
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
/screenshots
/build
/build.*
/build-*
/games
/demos
*.gba
*.bin
config.json
.hades-dbg.history
__pycache__

Empty file added accuracy/__init__.py
Empty file.
152 changes: 152 additions & 0 deletions accuracy/check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
#!/usr/bin/env python3

import os
import shutil
import filecmp
import textwrap
import argparse
import subprocess
import tempfile
from enum import Enum
from pathlib import Path


GREEN = '\033[32m'
YELLOW = '\033[33m'
RED = '\033[31m'
BOLD = '\033[1m'
RESET = '\033[0m'


class TestResult(Enum):
PASS = 0
SKIP = 1
FAIL = 2


class Test():
def __init__(self, name: str, rom: str, code: str, screenshot: str, skip: bool = False):
self.name = name

self.rom = rom
self.code = textwrap.dedent(code)
self.screenshot = screenshot
self.skip = skip

def run(self, hades_path: Path, rom_directory: Path, config_path: Path, tests_screenshots_directory: Path, verbose: bool):
module_path = Path(os.path.realpath(__file__)).parent

subprocess.run(
[hades_path, rom_directory / self.rom, '--without-gui', '--config', config_path],
input=self.code,
stdout=None if verbose else subprocess.DEVNULL,
stderr=None if verbose else subprocess.DEVNULL,
text=True,
encoding='utf-8',
check=True,
)

if not filecmp.cmp(tests_screenshots_directory / self.screenshot, module_path / 'expected' / self.screenshot, shallow=False):
raise RuntimeError("The screenshot taken during the test doesn't match the expected one.")


def main():
from suite import TESTS_SUITE

exit_code = 0

parser = argparse.ArgumentParser(
prog='Hades Accuracy Checker',
description='Tests the accuracy of Hades, a Gameboy Advance Emulator',
)

parser.add_argument(
'--binary',
nargs='?',
default='./hades',
help="Path to Hades' binary",
)

parser.add_argument(
'--roms',
nargs='?',
default='./roms',
help="Path to the test ROMS folder",
)

parser.add_argument(
'--verbose',
'-v',
action='store_true',
help="Show subcommands output",
)

args = parser.parse_args()

hades_binary = Path(os.getcwd()) / args.binary
rom_directory = Path(os.getcwd()) / args.roms

config = tempfile.NamedTemporaryFile()
config.write(textwrap.dedent('''
{
"file": {
"bios": "./bios.bin"
},
"emulation": {
"skip_bios": true,
"speed": 0,
"unbounded": false,
"backup_storage": {
"autodetect": true,
"type": 0
},
"rtc": {
"autodetect": true,
"enabled": true
}
}
}
''').encode('utf-8'))
config.flush()

tests_screenshots_directory = Path(os.getcwd()) / '.tests_screenshots'
if tests_screenshots_directory.exists():
shutil.rmtree(tests_screenshots_directory)
os.mkdir(tests_screenshots_directory)

print(f"┏━{'━' * 30}━┳━━━━━━┓")
print(f"┃ {'Name':30s} ┃ Res. ┃")
print(f"┣━{'━' * 30}━╋━━━━━━┫")

for test in TESTS_SUITE:
result = TestResult.FAIL

try:
if test.skip:
result = TestResult.SKIP
continue

test.run(hades_binary, rom_directory, config.name, tests_screenshots_directory, args.verbose)
result = TestResult.PASS
except Exception as e:
if args.verbose:
print(f"Error: {e}")
result = TestResult.FAIL
finally:
if result == TestResult.PASS:
pretty_result = f'{BOLD}{GREEN}PASS{RESET}'
elif result == TestResult.SKIP:
pretty_result = f'{BOLD}{YELLOW}SKIP{RESET}'
else:
pretty_result = f'{BOLD}{RED}FAIL{RESET}'
exit_code = 1

print(f"┃ {test.name:30s}{pretty_result} ┃")

print(f"┗━{'━' * 30}━┻━━━━━━┛")

exit(exit_code)


if __name__ == '__main__':
main()
Binary file added accuracy/expected/ags_01.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added accuracy/expected/arm.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added accuracy/expected/dma_start_delay.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added accuracy/expected/openbus_bios.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added accuracy/expected/thumb.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added accuracy/expected/timer_basic.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
68 changes: 68 additions & 0 deletions accuracy/suite.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from typing import List
from check import Test

TESTS_SUITE: List[Test] = [

# Jsmolka's arm.gba & thumg.gba
# https://github.com/jsmolka/gba-tests
Test(
name="Jsmolka - Arm.gba",
rom='arm.gba',
code='''
frame 10
screenshot ./.tests_screenshots/arm.png
''',
screenshot='arm.png',
),
Test(
name="Jsmolka - Thumb.gba",
rom='thumb.gba',
code='''
frame 10
screenshot ./.tests_screenshots/thumb.png
''',
screenshot='thumb.png',
),

# Hades Tests
# https://github.com/Arignir/Hades-Tests
Test(
name="Hades Tests - DMA Start Delay",
rom='dma-start-delay.gba',
code='''
frame 20
screenshot ./.tests_screenshots/dma_start_delay.png
''',
screenshot='dma_start_delay.png',
skip=True,
),
Test(
name="Hades Tests - Openbus BIOS",
rom='openbus-bios.gba',
code='''
frame 20
screenshot ./.tests_screenshots/openbus_bios.png
''',
screenshot='openbus_bios.png',
),
Test(
name="Hades Tests - Timer Basic",
rom='timer-basic.gba',
code='''
frame 20
screenshot ./.tests_screenshots/timer_basic.png
''',
screenshot='timer_basic.png',
),

# AGS
Test(
name="AGS - Aging Tests",
rom='ags.gba',
code='''
frame 425
screenshot ./.tests_screenshots/ags_01.png
''',
screenshot='ags_01.png',
)
]
2 changes: 1 addition & 1 deletion source/app/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ app_config_load(

if (mjson_get_number(data, data_len, "$.emulation.speed", &d)) {
app->emulation.speed = (int)d;
app->emulation.speed = max(1, min(app->emulation.speed, 5));
app->emulation.speed = max(0, min(app->emulation.speed, 5));
}

if (mjson_get_bool(data, data_len, "$.emulation.backup_storage.autodetect", &b)) {
Expand Down
3 changes: 2 additions & 1 deletion source/app/dbg/lang/lexer.c
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,15 @@ debugger_lang_lexe(
while (input[i]) {
switch (input[i]) {
case '.':
case '_':
case 'a' ... 'z':
case 'A' ... 'Z': {
/* Lexe the whole identifier */
struct token *t;
size_t j;

j = 0;
while (isalnum(input[i + j]) || input[i + j] == '.' || input[i + j] == '/') {
while (isalnum(input[i + j]) || input[i + j] == '.' || input[i + j] == '_' || input[i + j] == '/') {
++j;
}

Expand Down
3 changes: 2 additions & 1 deletion source/app/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ main(
app.video.aspect_ratio = ASPECT_RATIO_RESIZE;
app.audio.mute = false;
app.audio.level = 1.0f;
app.audio.resample_frequency = 48000;
app.gfx.texture_filter = TEXTURE_FILTER_NEAREST;
app.ui.win.resize = true;
app.ui.win.resize_with_ratio = false;
Expand Down Expand Up @@ -158,7 +159,7 @@ main(
uint64_t sdl_counters[2];
float elapsed_ms;

sdl_counters[0] = SDL_GetPerformanceCounter();
sdl_counters[0] = 0; //SDL_GetPerformanceCounter();

app_emulator_process_all_notifs(&app);

Expand Down
2 changes: 1 addition & 1 deletion source/gba/channel.c
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ channel_next(
new_event = channel->events;
}

if ((uint8_t *)new_event < (uint8_t *)channel->events + channel->size) {
if (channel->events && (uint8_t *)new_event < (uint8_t *)channel->events + channel->size) {
return (new_event);
}
return (NULL);
Expand Down

0 comments on commit 67a6864

Please sign in to comment.