Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add unit test infrastructure #5

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
7 changes: 3 additions & 4 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ insert_final_newline = true
[Makefile]
indent_style = tab
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.py]
generated_code = true
83 changes: 83 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
name: test

on: [push]

jobs:
pytest:
runs-on: ubuntu-latest
# container:
# image: scottyhardy/docker-wine:latest
# env:
# # USER_NAME: runner
# # USER_UID: ${USER_UID:-1010}
# # USER_GID: ${USER_GID:-"${USER_UID}"}
# # USER_HOME: ${USER_HOME:-/home/"${USER_NAME}"}
# # USER_PASSWD: ${USER_PASSWD:-"$(openssl passwd -1 -salt "$(openssl rand -base64 6)" "${USER_NAME}")"}
# # USER_SUDO: ${USER_SUDO:-yes}
# RDP_SERVER: yes
# # RUN_AS_ROOT: ${RUN_AS_ROOT:-no}
# # FORCED_OWNERSHIP: ${FORCED_OWNERSHIP:-no}
# # TZ: ${TZ:-UTC}
# # USE_XVFB: ${USE_XVFB:-no}
# DUMMY_PULSEAUDIO: yes # ${DUMMY_PULSEAUDIO:-no}

steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- uses: robinraju/release-downloader@v1
with:
repository: "gbdk-2020/gbdk-2020"
tag: "4.3.0"
fileName: "gbdk-linux64.tar.gz"
extract: true
- uses: pyvista/setup-headless-display-action@v2
- name: Install wine
run: |
sudo dpkg --add-architecture i386
sudo mkdir -pm755 /etc/apt/keyrings
sudo wget -O /etc/apt/keyrings/winehq-archive.key https://dl.winehq.org/wine-builds/winehq.key
sudo wget -NP /etc/apt/sources.list.d/ https://dl.winehq.org/wine-builds/ubuntu/dists/jammy/winehq-jammy.sources
sudo apt update
sudo apt install -y --install-recommends winehq-stable winbind
wine --version
- name: Install wine-mono
run: |
wget https://github.com/madewokherd/wine-mono/releases/download/wine-mono-9.0.0/wine-mono-9.0.0-x86.msi
WINEDEBUG=fixme-all WINEDLLOVERRIDES=mscoree=d wine wine-mono-9.0.0-x86.msi
- name: pip install
shell: bash
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
working-directory: ./tests
- name: make roms
shell: bash
run: |
make
ls build
working-directory: ./tests
- name: test single rom (python)
shell: bash
run: |
python test.py
working-directory: ./tests
continue-on-error: true
- run: |
ls -alF
shell: bash
working-directory: ./tests/build
- name: test single rom (bash)
run: |
wine ../bgb/bgb.exe -autoexit -hf -stateonexit -screenonexit build/wav_test_load_and_play.bmp -rom build/wav_test_load_and_play.gb
working-directory: ./tests
- run: |
ls -alF
shell: bash
working-directory: ./tests/build
# run: |
# pytest -v -s
# working-directory: ./tests
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
.vscode/settings.json
.venv/
gbdk/
Source/obj
Source/out
mgb.gb
bgb
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "tests/gbdk_unit_test"]
path = tests/gbdk_unit_test
url = [email protected]:untoxa/gbdk_unit_test.git
10 changes: 10 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@
},
"dependsOn": "make-debug",
"problemMatcher": []
},
{
"label": "make-test-roms",
"type": "shell",
"command": "make",
"args": [],
"options": {
"cwd": "tests/"
},
"problemMatcher": []
}
]
}
4 changes: 4 additions & 0 deletions tests/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/build
/unused
__pycache__
.pytest_cache
39 changes: 39 additions & 0 deletions tests/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
MYDIR = .
BLDDIR = $(MYDIR)/build
FW = $(MYDIR)/framework
CC = ../gbdk/bin/lcc -tempdir=$(BLDDIR) -Wl-j -Wl-m -Wl-w -Wl-yt2 -Wl-yo4 -Wl-ya4

TESTS = $(wildcard *.c)
OBJS = $(TESTS:%.c=$(BLDDIR)/%.o)
TESTROMS = $(TESTS:%.c=$(BLDDIR)/%.gb)

BGBDIR = ../bgb
# GBDKDIR = ../gbdk

all: clean mkdirs $(BGBDIR) build-all

.PHONY: clean
clean:
rm -rf $(BLDDIR)

.PHONY: mkdirs
mkdirs:
mkdir -p $(BLDDIR)

$(BGBDIR):
wget https://bgb.bircd.org/bgb.zip
unzip -jo -qq bgb.zip -d ../bgb
rm bgb.zip

# gbdk-linux64.tar.gz:
# wget https://github.com/gbdk-2020/gbdk-2020/releases/download/4.3.0/gbdk-linux64.tar.gz

# $(GBDKDIR):
# tar -zxvf gbdk-linux64.tar.gz -C $(GBDKDIR)
# # rm gbdk-linux64.tar.gz

$(BLDDIR)/%.gb: $(MYDIR)/%.c mkdirs $(BGBDIR)
$(CC) -o $@ $<

.PHONY: build-all
build-all: $(TESTROMS)
152 changes: 152 additions & 0 deletions tests/bgb_get_snapshot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import subprocess
from array import array
import os
from pathlib import Path
from PIL import Image, ImageChops

from gbdk_unit_test.framework.BGB_toolkit import load_noi, read_bgb_snspshot

base_dir = os.path.dirname(os.path.realpath(__file__))
base_path = Path(base_dir)


def load_rom_snapshot(rom_relative):

make_and_run(rom_relative)

snapshot_file = base_path.joinpath(rom_relative).with_suffix('.sna')

if not snapshot_file.is_file():
raise Exception("Cannot load snapshot: " + snapshot_file)

noi_file = base_path.joinpath(rom_relative).with_suffix('.noi')
if not snapshot_file.is_file():
raise Exception("Cannot load symbols: " + noi_file)

screenshot = base_path.joinpath(rom_relative).with_suffix('.bmp')

snapshot = read_bgb_snspshot(snapshot_file)
symbols = load_noi(noi_file)
symbols = {value: key for key, value in symbols.items()}

snapshot['symbols'] = symbols
snapshot['screenshot'] = screenshot

return snapshot


def make_and_run(rom_relative):
make_rom(rom_relative)

rom_path_full = base_path.joinpath(rom_relative)
screenshot_path = rom_path_full.with_suffix('.bmp')

bgb_exe = ["wine", "../bgb/bgb64.exe"]

# windows
if os.name == 'nt':
bgb_exe = ["../bgb/bgb64.exe"]

cmd = [
*bgb_exe,
"-set \"DebugSrcBrk=1\"",
"-autoexit",
"-hf",
"-stateonexit",
# "-screenonexit", screenshot_path.absolute().as_posix(),
rom_relative,
]

print("executing bgb: ", cmd)
my_env = os.environ.copy()
my_env["WINEDEBUG"] = "fixme-all"

subprocess.call(cmd,
cwd=base_dir,
env=my_env,
# stdout=subprocess.DEVNULL,
# stderr=subprocess.DEVNULL,
)

print("bgb: finished")

if not rom_path_full.with_suffix(".sna").is_file():
raise Exception("Tried to run rom, failed " + rom_relative)


def make_rom(rom_relative):
print("calling make", rom_relative)
result = subprocess.call(
["make", rom_relative],
cwd=base_dir,
# stdout=subprocess.DEVNULL
)

print("make: finished", rom_relative)

if result != 0:
raise Exception("Rom make failed " + rom_relative)


# The following code is repurposed from unit_checker.py by untoxa (MIT License)

# WRAM = 49152
mem_map = {
'VRAM': 0x8000,
'WRAM': 0xC000,
'OAM': 0xFE00,
'IO_REG': 0xFF00,
'HRAM': 0xFF80,
}


def symbol_addr(snapshot, symbol, base):
if type(base) is str:
base = mem_map[base.upper()]
return snapshot['symbols'].get(symbol) - base


def get(snapshot, section, address, len=0):
if isinstance(address, str):
address = symbol_addr(snapshot, address, section)

if len > 1:
return snapshot[section][address:address + len]
else:
return snapshot[section][address]


def ASCIIZ(snapshot, section, address):
ofs = address
data = snapshot[section]
fin = ofs
while data[fin] != 0:
fin += 1
return str(data[ofs:fin], 'ascii') if fin - ofs > 0 else ''


def CHECKSCREEN(snapshot, file_name):
image_one = Image.open(base_path.joinpath(file_name)).convert('RGB')
image_two = Image.open(snapshot['screenshot']).convert('RGB')

diff = ImageChops.difference(image_one, image_two)

return (diff.getbbox() is None)


def find(input: dict | array, val: str | int, parent_key: str | None):
if isinstance(input, array):
for i, v in enumerate(input):
if v == val:
print("found", [parent_key, i])
return [parent_key, i]
elif isinstance(v, array):
find(v, val, i)
return

for k, v in input.items():
if v == val:
print("found", [parent_key, k])
return [parent_key, k]
elif isinstance(v, array) or isinstance(v, dict):
find(v, val, k)
8 changes: 8 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import pytest

from bgb_get_snapshot import load_rom_snapshot


@pytest.fixture
def snapshot(request):
return load_rom_snapshot(request.param)
1 change: 1 addition & 0 deletions tests/gbdk_unit_test
Submodule gbdk_unit_test added at 101355
35 changes: 35 additions & 0 deletions tests/pu1_plays_note.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#include <gbdk/emu_debug.h>
#include <gbdk/platform.h>

#include "../Source/io/midi.h"

#include "../Source/mGB.h"
#include "../Source/synth/common.c"
#include "../Source/synth/pulse.c"
#include "../Source/synth/wav.c"

bool systemIdle = true;

uint8_t statusByte;
uint8_t addressByte;
uint8_t valueByte;
uint8_t capturedAddress;

uint8_t result[2] = {0U, 0U};

void main(void) {
rAUDENA = AUDENA_ON;
rAUDVOL = AUDVOL_VOL_LEFT(7U) | AUDVOL_VOL_RIGHT(7U);

setOutputPan(PU1, 64U);

addressByte = 64U; // MIDI note
valueByte = 127U; // MIDI velocity

playNotePu1();
updatePu1();

delay(100);

EMU_BREAKPOINT;
}
18 changes: 18 additions & 0 deletions tests/pu1_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import pytest

from bgb_get_snapshot import get


def describe_pu1():

@pytest.mark.parametrize('snapshot', ['build/pu1_plays_note.gb'], indirect=True)
def it_plays_a_note(snapshot):
rAUD1LOW = get(snapshot, 'IO_REG', '_NR13_REG')
rAUD1HIGH = get(snapshot, 'IO_REG', '_NR14_REG')

# noteIndex = 64U - 36U == 28U
# f = freq[noteIndex] == 1650U == 0x0672

assert rAUD1LOW == 0x72
retrig = 0x80
assert rAUD1HIGH == 0x06 | retrig
4 changes: 4 additions & 0 deletions tests/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pillow ~= 10.4.0
pytest ~= 8.3.0
pytest-describe ~= 2.2.0
pytest-sugar ~= 1.0.0
Loading
Loading