diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..ca27d87 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +** +!docker/ diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index 0163641..0000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1 +0,0 @@ -* @rust3ds/active diff --git a/.github/actions/citra/action.yml b/.github/actions/citra/action.yml new file mode 100644 index 0000000..18441f0 --- /dev/null +++ b/.github/actions/citra/action.yml @@ -0,0 +1,12 @@ +name: 'Run 3DS Executable' +description: 'Run a given 3DS executable with citra and GDB' +inputs: + executable: + description: > + The 3DS executable(s) to run. Globs and space separated lists allowed. + required: true +runs: + using: docker + image: ../../../Dockerfile + args: + - ${{ inputs.executable }} diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 4a1db0b..077222b 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -26,13 +26,17 @@ runs: components: clippy, rustfmt, rust-src toolchain: ${{ inputs.toolchain }} + - name: Set up Rust cache + uses: Swatinem/rust-cache@v2 + - name: Install build tools for host shell: bash run: sudo apt-get update && sudo apt-get install -y build-essential - name: Install cargo-3ds shell: bash - run: cargo install cargo-3ds + # TODO: replace with crates.io version once published + run: cargo install --git https://github.com/rust3ds/cargo-3ds --branch feature/verbose-flag - name: Set PATH to include devkitARM shell: bash diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 657c5f9..a8810fa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,10 +3,10 @@ name: CI on: push: branches: - - master + - main pull_request: branches: - - master + - main workflow_dispatch: env: @@ -65,3 +65,12 @@ jobs: - name: Build doc tests # need build-std=test until https://github.com/rust3ds/cargo-3ds/pull/42 run: cargo 3ds test --doc -Zbuild-std=std,test + + - name: Run lib + integration tests + uses: ./.github/actions/citra + with: + executable: ./target/armv6k-nintendo-3ds/debug/deps/*.elf + + # TODO: run doc tests. We might be able to do something with e.g. + # cargo's "runner" configuration, but it seems we also need a test + # runtime and stuff for that to work. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b0f5728 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,38 @@ +FROM buildpack-deps:latest as builder + +ARG CITRA_CHANNEL=nightly +ARG CITRA_RELEASE=1962 + +WORKDIR /tmp +COPY ./docker/download_citra.sh /usr/local/bin/download_citra +RUN apt-get update -y && apt-get install -y jq +RUN download_citra ${CITRA_CHANNEL} ${CITRA_RELEASE} + +RUN wget https://apt.devkitpro.org/install-devkitpro-pacman && \ + chmod +x ./install-devkitpro-pacman && \ + yes | /tmp/install-devkitpro-pacman +RUN dkp-pacman -S --noconfirm \ + devkitARM-gdb \ + libctru + +FROM ubuntu:latest + +RUN apt-get update -y && \ + apt-get install -y \ + libswscale5 \ + libsdl2-2.0-0 \ + libavformat58 \ + libavfilter7 \ + xvfb + +COPY --from=builder /opt/devkitpro /opt/devkitpro +ENV PATH=/opt/devkitpro/devkitARM/bin:${PATH} + +COPY --from=builder /tmp/citra.AppImage /usr/local/bin/citra +COPY ./docker/sdl2-config.ini /root/.config/citra-emu/ +COPY ./docker/test-runner.gdb /app/ +COPY ./docker/entrypoint.sh /app/ + +WORKDIR /app + +ENTRYPOINT [ "/app/entrypoint.sh" ] diff --git a/README.md b/README.md index d987acc..6141473 100644 --- a/README.md +++ b/README.md @@ -3,35 +3,14 @@ A set of tools for running automated Rust tests against Citra (3DS emulator). -## Usage +## Components -`./run.sh 3DSX_FILE` +* `test-runner`: a Rust crate for writing tests for 3DS homebrew +* `Dockerfile`: builds a container for running test executables with Citra. +* GitHub Actions: + * `.github/actions/setup`: action for setting up the Rust 3DS toolchain in + workflows + * `.github/actions/citra`: action for running test executables with Citra in + workflows -## Goals - -* Docker container for manually running tests against Citra -* GitHub Action for running automated tests -* Rust testing framework (custom runner) for use with the 3ds -* (maybe) Acceptance testing framework or glue for one? - -## Workflow / Notes - -1. Build a test executable (type tbd) -1. `citra-emu` container: bind-mount test executable and choose it -1. `driver` container perform input / output as needed for test, via VNC - * possible extension: `3dslink -s` to get actual stdout/stderr (return code?) - * acceptance testing of images, hopefully via screenshot - -## To do work - -* [ ] Reorganize docker build files vs runtime files a bit -* [ ] Make this repo useable as a github action -* [ ] Run itself as part of CI? I guess? -* [ ] Simpler user-run workflow: - * Ideally, a single command to spin everything up, build + load a 3dsx and run a vdo script. - * Maybe cargo args passed in as environment variable or something? -* [ ] Clearly defined dependencies + use cases: - * Should this be usable without Rust? - * Is docker the only real dependency? - * Does this need a separate binary, or can we just use native cargo test - capabilities? + diff --git a/docker/download_citra.sh b/docker/download_citra.sh index bacb06a..ec6699c 100755 --- a/docker/download_citra.sh +++ b/docker/download_citra.sh @@ -9,7 +9,7 @@ RELEASE_API="https://api.github.com/repos/citra-emu/citra-${CITRA_CHANNEL}/relea curl "${RELEASE_API}" | jq --raw-output '.assets[].browser_download_url' | - grep -E 'citra-linux-.*.tar.xz' | - xargs wget -O citra-linux.tar.xz + grep -E 'citra-linux-.*.tar.gz' | + xargs wget -O citra-linux.tar.gz -tar --strip-components 1 -xvf citra-linux.tar.xz +tar --strip-components 1 -xvf citra-linux.tar.gz diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100755 index 0000000..b514fdd --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# Clean up child processes on exit: https://stackoverflow.com/a/2173421/14436105 +trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT + +ls -lart $@ + +ERRS=0 +# shellcheck disable=SC2068 +for EXE in $@; do + VIDEO_OUT="$(dirname "$EXE")/$(basename "$EXE" .elf)_out.webm" + + # colored logs would be nice, but we can always just grab the plaintext log file + xvfb-run citra --appimage-extract-and-run --dump-video="$VIDEO_OUT" "$EXE" &>/dev/null & + + # Citra takes a little while to start up, so wait a little before we try to connect + sleep 3 + + arm-none-eabi-gdb --batch-silent --command /app/test-runner.gdb "$EXE" + STATUS=$? + if [ $STATUS -ne 0 ]; then + ERRS=$((ERRS + 1)) + fi +done + +exit $ERRS diff --git a/docker/sdl2-config.ini b/docker/sdl2-config.ini index 2abb7bb..3aeb006 100644 --- a/docker/sdl2-config.ini +++ b/docker/sdl2-config.ini @@ -1,342 +1,12 @@ - -[Controls] -# The input devices and parameters for each 3DS native input -# It should be in the format of "engine:[engine_name],[param1]:[value1],[param2]:[value2]..." -# Escape characters $0 (for ':'), $1 (for ',') and $2 (for '$') can be used in values - -# for button input, the following devices are available: -# - "keyboard" (default) for keyboard input. Required parameters: -# - "code": the code of the key to bind -# - "sdl" for joystick input using SDL. Required parameters: -# - "joystick": the index of the joystick to bind -# - "button"(optional): the index of the button to bind -# - "hat"(optional): the index of the hat to bind as direction buttons -# - "axis"(optional): the index of the axis to bind -# - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", "down", "left" or "right" -# - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is -# triggered if the axis value crosses -# - "direction"(only used for axis): "+" means the button is triggered when the axis value -# is greater than the threshold; "-" means the button is triggered when the axis value -# is smaller than the threshold -button_a= -button_b= -button_x= -button_y= -button_up= -button_down= -button_left= -button_right= -button_l= -button_r= -button_start= -button_select= -button_debug= -button_gpio14= -button_zl= -button_zr= -button_home= - -# for analog input, the following devices are available: -# - "analog_from_button" (default) for emulating analog input from direction buttons. Required parameters: -# - "up", "down", "left", "right": sub-devices for each direction. -# Should be in the format as a button input devices using escape characters, for example, "engine$0keyboard$1code$00" -# - "modifier": sub-devices as a modifier. -# - "modifier_scale": a float number representing the applied modifier scale to the analog input. -# Must be in range of 0.0-1.0. Defaults to 0.5 -# - "sdl" for joystick input using SDL. Required parameters: -# - "joystick": the index of the joystick to bind -# - "axis_x": the index of the axis to bind as x-axis (default to 0) -# - "axis_y": the index of the axis to bind as y-axis (default to 1) -circle_pad= -c_stick= - -# for motion input, the following devices are available: -# - "motion_emu" (default) for emulating motion input from mouse input. Required parameters: -# - "update_period": update period in milliseconds (default to 100) -# - "sensitivity": the coefficient converting mouse movement to tilting angle (default to 0.01) -# - "tilt_clamp": the max value of the tilt angle in degrees (default to 90) -# - "cemuhookudp" reads motion input from a udp server that uses cemuhook's udp protocol -motion_device= - -# for touch input, the following devices are available: -# - "emu_window" (default) for emulating touch input from mouse input to the emulation window. No parameters required -# - "cemuhookudp" reads touch input from a udp server that uses cemuhook's udp protocol -# - "min_x", "min_y", "max_x", "max_y": defines the udp device's touch screen coordinate system -touch_device= - -# Most desktop operating systems do not expose a way to poll the motion state of the controllers -# so as a way around it, cemuhook created a udp client/server protocol to broadcast the data directly -# from a controller device to the client program. Citra has a client that can connect and read -# from any cemuhook compatible motion program. - -# IPv4 address of the udp input server (Default "127.0.0.1") -udp_input_address= - -# Port of the udp input server. (Default 26760) -udp_input_port= - -# The pad to request data on. Should be between 0 (Pad 1) and 3 (Pad 4). (Default 0) -udp_pad_index= - -[Core] -# Whether to use the Just-In-Time (JIT) compiler for CPU emulation -# 0: Interpreter (slow), 1 (default): JIT (fast) -use_cpu_jit = - -# Change the Clock Frequency of the emulated 3DS CPU. -# Underclocking can increase the performance of the game at the risk of freezing. -# Overclocking may fix lag that happens on console, but also comes with the risk of freezing. -# Range is any positive integer (but we suspect 25 - 400 is a good idea) Default is 100 -cpu_clock_percentage = - -[Renderer] -# Whether to render using GLES or OpenGL -# 0 (default): OpenGL, 1: GLES -use_gles = - -# Whether to use software or hardware rendering. -# 0: Software, 1 (default): Hardware -use_hw_renderer = - -# Whether to use hardware shaders to emulate 3DS shaders -# 0: Software, 1 (default): Hardware -use_hw_shader = - -# Whether to use separable shaders to emulate 3DS shaders (macOS only) -# 0: Off (Default), 1 : On -separable_shader = - -# Whether to use accurate multiplication in hardware shaders -# 0: Off (Faster, but causes issues in some games) 1: On (Default. Slower, but correct) -shaders_accurate_mul = - -# Whether to use the Just-In-Time (JIT) compiler for shader emulation -# 0: Interpreter (slow), 1 (default): JIT (fast) -use_shader_jit = - -# Forces VSync on the display thread. Usually doesn't impact performance, but on some drivers it can -# so only turn this off if you notice a speed difference. -# 0: Off, 1 (default): On -use_vsync_new = - -# Reduce stuttering by storing and loading generated shaders to disk -# 0: Off, 1 (default. On) -use_disk_shader_cache = - -# Resolution scale factor -# 0: Auto (scales resolution to window size), 1: Native 3DS screen resolution, Otherwise a scale -# factor for the 3DS resolution -resolution_factor = - -# Texture filter name -texture_filter_name = - -# Limits the speed of the game to run no faster than this value as a percentage of target speed. -# Will not have an effect if unthrottled is enabled. -# 5 - 995: Speed limit as a percentage of target game speed. 0 for unthrottled. 100 (default) -frame_limit = - -# Overrides the frame limiter to use frame_limit_alternate instead of frame_limit. -# 0: Off (default), 1: On -use_frame_limit_alternate = - -# Alternate speed limit to be used instead of frame_limit if use_frame_limit_alternate is enabled -# 5 - 995: Speed limit as a percentage of target game speed. 0 for unthrottled. 200 (default) -frame_limit_alternate = - -# The clear color for the renderer. What shows up on the sides of the bottom screen. -# Must be in range of 0.0-1.0. Defaults to 0.0 for all. -bg_red = 0.5 -bg_blue = 0.5 -bg_green = 0.5 - -# Whether and how Stereoscopic 3D should be rendered -# 0 (default): Off, 1: Side by Side, 2: Anaglyph, 3: Interlaced, 4: Reverse Interlaced -render_3d = - -# Change 3D Intensity -# 0 - 100: Intensity. 0 (default) -factor_3d = - -# The name of the post processing shader to apply. -# Loaded from shaders if render_3d is off or side by side. -# Loaded from shaders/anaglyph if render_3d is anaglyph -pp_shader_name = - -# Whether to enable linear filtering or not -# This is required for some shaders to work correctly -# 0: Nearest, 1 (default): Linear -filter_mode = - -[Layout] -# Layout for the screen inside the render window. -# 0 (default): Default Top Bottom Screen, 1: Single Screen Only, 2: Large Screen Small Screen, 3: Side by Side -layout_option = - -# Toggle custom layout (using the settings below) on or off. -# 0 (default): Off, 1: On -custom_layout = - -# Screen placement when using Custom layout option -# 0x, 0y is the top left corner of the render window. -custom_top_left = -custom_top_top = -custom_top_right = -custom_top_bottom = -custom_bottom_left = -custom_bottom_top = -custom_bottom_right = -custom_bottom_bottom = - -# Swaps the prominent screen with the other screen. -# For example, if Single Screen is chosen, setting this to 1 will display the bottom screen instead of the top screen. -# 0 (default): Top Screen is prominent, 1: Bottom Screen is prominent -swap_screen = - -# Toggle upright orientation, for book style games. -# 0 (default): Off, 1: On -upright_screen = - -# Dumps textures as PNG to dump/textures/[Title ID]/. -# 0 (default): Off, 1: On -dump_textures = - -# Reads PNG files from load/textures/[Title ID]/ and replaces textures. -# 0 (default): Off, 1: On -custom_textures = - -# Loads all custom textures into memory before booting. -# 0 (default): Off, 1: On -preload_textures = - -[Audio] -# Whether or not to enable DSP LLE -# 0 (default): No, 1: Yes -enable_dsp_lle = - -# Whether or not to run DSP LLE on a different thread -# 0 (default): No, 1: Yes -enable_dsp_lle_thread = - - -# Which audio output engine to use. -# auto (default): Auto-select, null: No audio output, sdl2: SDL2 (if available) -output_engine = - -# Whether or not to enable the audio-stretching post-processing effect. -# This effect adjusts audio speed to match emulation speed and helps prevent audio stutter, -# at the cost of increasing audio latency. -# 0: No, 1 (default): Yes -enable_audio_stretching = - -# Which audio device to use. -# auto (default): Auto-select -output_device = - -# Output volume. -# 1.0 (default): 100%, 0.0; mute -volume = - -[Data Storage] -# Whether to create a virtual SD card. -# 1 (default): Yes, 0: No -use_virtual_sd = - -# The path of the virtual SD card directory. -# empty (default) will use the user_path -sdmc_directory = - -# The path of NAND directory. -# empty (default) will use the user_path -nand_directory = - -[System] -# The system model that Citra will try to emulate -# 0: Old 3DS, 1: New 3DS (default) -is_new_3ds = - -# The system region that Citra will use during emulation -# -1: Auto-select (default), 0: Japan, 1: USA, 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan -region_value = - -# The clock to use when citra starts -# 0: System clock (default), 1: fixed time -init_clock = - -# Time used when init_clock is set to fixed_time in the format %Y-%m-%d %H:%M:%S -# set to fixed time. Default 2000-01-01 00:00:01 -# Note: 3DS can only handle times later then Jan 1 2000 -init_time = - -[Camera] -# Which camera engine to use for the right outer camera -# blank (default): a dummy camera that always returns black image -camera_outer_right_name = - -# A config string for the right outer camera. Its meaning is defined by the camera engine -camera_outer_right_config = - -# The image flip to apply -# 0: None (default), 1: Horizontal, 2: Vertical, 3: Reverse -camera_outer_right_flip = - -# ... for the left outer camera -camera_outer_left_name = -camera_outer_left_config = -camera_outer_left_flip = - -# ... for the inner camera -camera_inner_name = -camera_inner_config = -camera_inner_flip = - [Miscellaneous] -# A filter which removes logs below a certain logging level. -# Examples: *:Debug Kernel.SVC:Trace Service.*:Critical log_filter = *:Info Debug.Emulated:Debug -# Kernel:Debug Service.APT:Trace [Debugging] -# Record frame time data, can be found in the log directory. Boolean value -record_frame_times = -# Port for listening to GDB connections. -use_gdbstub=false -gdbstub_port=24689 -# To LLE a service module add "LLE\=true" +use_gdbstub=true +gdbstub_port=4000 [WebService] -# Whether or not to enable telemetry -# 0: No, 1 (default): Yes -enable_telemetry = 1 -# URL for Web API -web_api_url = https://api.citra-emu.org -# Username and token for Citra Web Service -# See https://profile.citra-emu.org/ for more info -citra_username = -citra_token = +enable_telemetry = 0 [Video Dumping] -# Format of the video to output, default: webm -output_format = - -# Options passed to the muxer (optional) -# This is a param package, format: [key1]:[value1],[key2]:[value2],... -format_options = - -# Video encoder used, default: libvpx-vp9 -video_encoder = libvpx-vp9 - -# Options passed to the video codec (optional) -video_encoder_options = quality:realtime,speed:6,tile-columns:4,frame-parallel:1,threads:8,row-mt:1 - -# Video bitrate, default: 2500000 -video_bitrate = - -# Audio encoder used, default: libvorbis -audio_encoder = libvorbis - -# Options passed to the audio codec (optional) -audio_encoder_options = - -# Audio bitrate, default: 64000 -audio_bitrate = +output_format = webm diff --git a/docker/test-runner.gdb b/docker/test-runner.gdb index c3258a7..1b7e00c 100644 --- a/docker/test-runner.gdb +++ b/docker/test-runner.gdb @@ -2,15 +2,12 @@ # or should this be `_exit` ? break __ctru_exit commands - # TODO: needed? - continue # ARM calling convention will put the exit code in r0 when __ctru_exit is called. # Just tell GDB to exit with the same code, since it doesn't get passed back when # the program exits quit $r0 end -# TODO: parametrize or pass as command line arg instead -target extended-remote 192.168.0.167:4003 -# target extended-remote :4000 +target extended-remote :4000 continue +quit