Skip to content

Commit

Permalink
Add initial unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
tstirrat committed Jul 25, 2024
1 parent ed61a57 commit 565d8de
Show file tree
Hide file tree
Showing 18 changed files with 396 additions and 4 deletions.
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
42 changes: 42 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Pytest

on: [push]

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.12"]

steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- uses: schmiddim/action-wget-unzip@v2
with:
url: "https://github.com/gbdk-2020/gbdk-2020/releases/download/4.3.0/gbdk-linux64.tar.gz"
destination: gbdk
- uses: schmiddim/action-wget-unzip@v2
with:
url: "https://bgb.bircd.org/bgbw64.zip"
destination: bgb
- name: Install dependencies
run: |
cd tests
python -m pip install --upgrade pip
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Lint with Ruff
run: |
cd tests
pip install ruff
ruff --format=github --target-version=py310 .
continue-on-error: true
- name: Test with pytest
run: |
cd tests
pytest -v -s
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
25 changes: 25 additions & 0 deletions tests/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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)


all: clean mkdirs build-all

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

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

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

.PHONY: build-all
build-all: $(TESTROMS)
141 changes: 141 additions & 0 deletions tests/bgb_get_snapshot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
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')

subprocess.run([
"/usr/bin/env",
"wine",
"../bgb/bgb64.exe",
"-set \"DebugSrcBrk=1\"",
"-hf",
"-stateonexit",
"-screenonexit",
screenshot_path,

rom_relative,
],
cwd=base_dir,
# stdout=subprocess.DEVNULL,
# stderr=subprocess.DEVNULL,
)

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


def make_rom(rom_relative):
result = subprocess.call(
[
"/usr/bin/env",
"make",
rom_relative
],
cwd=base_dir,
# stdout=subprocess.DEVNULL
)

if result != 0:
raise Exception("Tried to make rom, 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(500);

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
10 changes: 10 additions & 0 deletions tests/screens_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import pytest

from bgb_get_snapshot import CHECKSCREEN


def describe_screens():

@pytest.mark.parametrize('snapshot', ['build/splash_screen.gb'], indirect=True)
def it_matches_snapshot(snapshot):
assert CHECKSCREEN(snapshot, "splash_screen.png")
17 changes: 17 additions & 0 deletions tests/splash_screen.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#include <gbdk/emu_debug.h>
#include <gbdk/platform.h>

#include "../Source/screen/splash.c"
#include "../Source/screen/utils.c"

uint8_t j;

void main(void) {

displaySetup();

showSplashScreen();
delay(500);

EMU_BREAKPOINT;
}
Binary file added tests/splash_screen.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 30 additions & 0 deletions tests/wav_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import pytest
from array import array

from bgb_get_snapshot import get


def describe_wav_loading():

@pytest.mark.parametrize('snapshot', ['build/wav_test_load_and_play.gb'], indirect=True)
def it_loads_the_correct_waveform(snapshot):
_AUD3WAVERAM = get(snapshot, 'IO_REG', '__AUD3WAVERAM', 16)

expected = array('B')
expected.frombytes(bytearray([0x22, 0x55, 0x77, 0xAA, 0xBB, 0xDD, 0xEE, 0xFF,
0xEE, 0xDD, 0xBB, 0xAA, 0x77, 0x66, 0x44, 0x00]))
assert _AUD3WAVERAM == expected

@pytest.mark.parametrize('snapshot', ['build/wav_test_load_and_play.gb'], indirect=True)
def it_plays_a_note(snapshot):
rAUD3LOW = get(snapshot, 'IO_REG', '_NR33_REG')
rAUD3HIGH = get(snapshot, 'IO_REG', '_NR34_REG')

# noteIndex = 64 - 24 == 40
# freq[noteIndex] == 1849U == 0x0739

retrig = 0x80
assert rAUD3LOW == 0x39
assert rAUD3HIGH == 0x07 | retrig

# TODO: check frequency, env, vol etc
Loading

0 comments on commit 565d8de

Please sign in to comment.