From 7db1c3fe4a1d48b55215635547f46d8f7fe6a31c Mon Sep 17 00:00:00 2001 From: Johnny D Date: Sat, 19 Sep 2026 21:06:34 -0400 Subject: [PATCH] ci: preserve upstream checks and replace fork CI with one smoke test Retain all five inherited workflows byte-for-byte. Replace four fork-specific workflows and seven extra jobs with one dependency-free C++ session lifecycle smoke test, a two-minute Ubuntu job and superseded-run cancellation. Remove the redundant SDK, source-contract, native application and packaging test harnesses rather than hiding them behind a single runner. Leave production sources, dependency pins, CMake and platform build helpers unchanged. Update controller documentation to explain the narrower coverage and local source builds instead of automatic application artifacts. Validated the exact smoke command without dependencies; GCC, Clang and optimized NDEBUG builds pass. Eight disposable mutation controls fail as expected. YAML, Bash syntax, baseline blob checks and whitespace checks pass. Complete native applications and current-head GitHub Actions are not claimed validated. --- .github/workflows/native-switch2kit.yml | 142 ++----------- .github/workflows/switch2kit-desktop.yml | 41 ---- .github/workflows/switch2kit-linux.yml | 77 ------- .github/workflows/switch2kit-windows.yml | 68 ------- README.md | 19 +- docs/Switch2Kit.md | 46 +++-- tests/switch2kit/HostFileTests.cpp | 80 -------- tests/switch2kit/PolicyTests.cpp | 224 --------------------- tests/switch2kit/run.py | 67 ------ tests/switch2kit/smoke.cpp | 66 ++++++ tests/switch2kit/test_desktop_lifecycle.py | 185 ----------------- tests/switch2kit/test_host_file.py | 42 ---- tests/switch2kit/test_wiring.py | 80 -------- tests/switch2kit/windows-launch.ps1 | 12 -- 14 files changed, 111 insertions(+), 1038 deletions(-) delete mode 100644 .github/workflows/switch2kit-desktop.yml delete mode 100644 .github/workflows/switch2kit-linux.yml delete mode 100644 .github/workflows/switch2kit-windows.yml delete mode 100644 tests/switch2kit/HostFileTests.cpp delete mode 100644 tests/switch2kit/PolicyTests.cpp delete mode 100755 tests/switch2kit/run.py create mode 100644 tests/switch2kit/smoke.cpp delete mode 100644 tests/switch2kit/test_desktop_lifecycle.py delete mode 100644 tests/switch2kit/test_host_file.py delete mode 100644 tests/switch2kit/test_wiring.py delete mode 100644 tests/switch2kit/windows-launch.ps1 diff --git a/.github/workflows/native-switch2kit.yml b/.github/workflows/native-switch2kit.yml index c00447e411..85f505ebee 100644 --- a/.github/workflows/native-switch2kit.yml +++ b/.github/workflows/native-switch2kit.yml @@ -1,143 +1,25 @@ -name: Native Switch2Kit +name: Switch2Kit smoke on: pull_request: workflow_dispatch: permissions: contents: read concurrency: - group: native-switch2kit-${{ github.ref }} + group: switch2kit-smoke-${{ github.ref }} cancel-in-progress: true jobs: - policies: - runs-on: ubuntu-latest + smoke: + name: Switch2Kit session smoke + runs-on: ubuntu-24.04 + timeout-minutes: 2 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with: persist-credentials: false - - run: git submodule update --init dependencies/Switch2Kit - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - with: - repository: libsdl-org/SDL - ref: f87239e71e42da91ca317a12eefb82cfbf3393eb - path: test-sdl - persist-credentials: false - - name: Execute mapping, identity, setup rollback and session regressions - run: python3 tests/switch2kit/run.py --sdl test-sdl --sanitize - macos: - if: github.repository == 'jmonster/Cemu' - strategy: - fail-fast: false - matrix: - include: - - os: macos-15 - arch: arm64 - - os: macos-15-intel - arch: x86_64 - runs-on: ${{ matrix.os }} - timeout-minutes: 90 - env: - DEVELOPER_DIR: /Applications/Xcode_26.3.app/Contents/Developer - VCPKG_MAX_CONCURRENCY: 3 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - with: - submodules: recursive - persist-credentials: false - - name: Install native tools - run: brew install cmake ninja nasm automake libtool molten-vk - - name: Build the complete application - run: | - set -o pipefail - test "$(uname -m)" = '${{ matrix.arch }}' - bash scripts/build-switch2kit.sh 2>&1 | tee native-build.log - - name: Check production policies on the native architecture - run: python3 tests/switch2kit/run.py --sanitize - - name: Inspect embedded SDK, architecture and application signature - run: | - codesign --verify --deep --strict bin/Cemu_release.app - python3 dependencies/Switch2Kit/Integrations/Emulators/verify-bundle.py \ - cemu . build-switch2kit --architecture '${{ matrix.arch }}' - - name: Launch, quit and relaunch the exact ZIP with build dependencies denied + submodules: false + - name: Compile and run the single Cemu-owned smoke test + shell: bash run: | - xcrun swiftc -swift-version 6 -warnings-as-errors \ - dependencies/Switch2Kit/tests/emulator-launch/Observe.swift \ - -o "$RUNNER_TEMP/cemu-window-observer" - mkdir "$RUNNER_TEMP/cemu-app" - ditto -x -k build-switch2kit/integration-app.zip "$RUNNER_TEMP/cemu-app" - python3 dependencies/Switch2Kit/tests/emulator-launch/verify.py cemu \ - "$RUNNER_TEMP/cemu-app/Cemu_release.app" "$RUNNER_TEMP/cemu-window-observer" \ - "$PWD/launch-results" - - name: Check the disabled option retains the upstream deployment target - run: | - cmake -S . -B build-switch2kit -DENABLE_SWITCH2KIT=OFF \ - -DMACOS_BUNDLE=OFF -DCMAKE_OSX_DEPLOYMENT_TARGET=13.4 - python3 - <<'PY' - import json - from pathlib import Path - commands = json.loads(Path('build-switch2kit/compile_commands.json').read_text()) - assert not any('HAVE_SWITCH2KIT' in row['command'] for row in commands) - assert not any('Switch2KitSetup.cpp' in row['file'] for row in commands) - cache = Path('build-switch2kit/CMakeCache.txt').read_text() - # Command-line cache entries may be STRING or UNINITIALIZED. Check - # the deployment value, not CMake's incidental cache type spelling. - values = {line.split(':', 1)[0]: line.split('=', 1)[1] - for line in cache.splitlines() - if not line.startswith(('#', '//')) and ':' in line and '=' in line} - assert values.get('CMAKE_OSX_DEPLOYMENT_TARGET') == '13.4', values.get('CMAKE_OSX_DEPLOYMENT_TARGET') - print('PASS disabled option: no native backend sources or defines; macOS 13.4 retained') - PY - - name: Qualified development application - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 - with: - name: Cemu-Switch2Kit-${{ matrix.arch }} - path: | - build-switch2kit/integration-app.zip - build-switch2kit/integration-inspection.json - launch-results/launch.json - if-no-files-found: error - retention-days: 14 - - name: Native diagnostics - if: always() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 - with: - name: Cemu-Switch2Kit-${{ matrix.arch }}-diagnostics - path: | - native-build.log - build-switch2kit/integration-native-diagnostics.txt - build-switch2kit/CMakeCache.txt - build-switch2kit/CMakeFiles/CMakeConfigureLog.yaml - launch-results - sdk: - if: github.repository == 'jmonster/Cemu' - runs-on: macos-15 - timeout-minutes: 30 - env: - DEVELOPER_DIR: /Applications/Xcode_26.3.app/Contents/Developer - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - with: - persist-credentials: false - - run: git submodule update --init dependencies/Switch2Kit - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - with: - repository: libsdl-org/SDL - ref: f87239e71e42da91ca317a12eefb82cfbf3393eb - path: test-sdl - persist-credentials: false - - run: brew install cmake ninja boost - - name: Exercise the pinned SDK, motor packets, real SDL and Cemu motion consumer - run: | - set -o pipefail - swift test --package-path dependencies/Switch2Kit -Xswiftc -warnings-as-errors 2>&1 | tee sdk-tests.log - bash dependencies/Switch2Kit/tests/rumble/run.sh 2>&1 | tee motor-tests.log - bash dependencies/Switch2Kit/tests/session/run.sh 2>&1 | tee session-tests.log - S2K_SDL_SOURCE="$PWD/test-sdl" \ - bash dependencies/Switch2Kit/tests/sdl-inprocess/verify.sh 2>&1 | tee sdl-tests.log - S2K_SDL_SOURCE="$PWD/test-sdl" S2K_CEMU_SOURCE="$PWD" \ - bash dependencies/Switch2Kit/tests/emulator-host/verify.sh 2>&1 | tee host-tests.log - - name: Test diagnostics - if: always() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 - with: - name: Cemu-Switch2Kit-sdk-diagnostics - path: '*-tests.log' + c++ -std=c++20 -Wall -Wextra -Werror -Isrc \ + tests/switch2kit/smoke.cpp -o "$RUNNER_TEMP/switch2kit-smoke" + timeout 10s "$RUNNER_TEMP/switch2kit-smoke" diff --git a/.github/workflows/switch2kit-desktop.yml b/.github/workflows/switch2kit-desktop.yml deleted file mode 100644 index 83d21936ec..0000000000 --- a/.github/workflows/switch2kit-desktop.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Switch2Kit desktop platforms -on: - pull_request: - workflow_dispatch: -permissions: - contents: read -concurrency: - group: switch2kit-desktop-${{ github.ref }} - cancel-in-progress: true -jobs: - source: - runs-on: ubuntu-24.04 - timeout-minutes: 10 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - with: - persist-credentials: false - - run: git submodule update --init dependencies/Switch2Kit - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - with: - repository: libsdl-org/SDL - ref: f87239e71e42da91ca317a12eefb82cfbf3393eb - path: test-sdl - persist-credentials: false - - name: Archive the exact sources used by the controller integration - run: | - mkdir -p "$RUNNER_TEMP/switch2kit-source" - git archive HEAD -o "$RUNNER_TEMP/switch2kit-source/host.zip" - git -C dependencies/Switch2Kit archive HEAD -o "$RUNNER_TEMP/switch2kit-source/sdk.zip" - git -C test-sdl archive HEAD -o "$RUNNER_TEMP/switch2kit-source/sdl.zip" - { - git rev-parse HEAD - git submodule status dependencies/Switch2Kit - git -C test-sdl rev-parse HEAD - } > "$RUNNER_TEMP/switch2kit-source/revisions.txt" - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 - with: - name: Switch2Kit-desktop-source - path: ${{ runner.temp }}/switch2kit-source - retention-days: 7 - if-no-files-found: error diff --git a/.github/workflows/switch2kit-linux.yml b/.github/workflows/switch2kit-linux.yml deleted file mode 100644 index 32dfabac67..0000000000 --- a/.github/workflows/switch2kit-linux.yml +++ /dev/null @@ -1,77 +0,0 @@ -name: Switch2Kit Linux application -on: - pull_request: - workflow_dispatch: -permissions: - contents: read -concurrency: - group: switch2kit-linux-${{ github.ref }} - cancel-in-progress: false -jobs: - linux: - runs-on: ubuntu-24.04 - container: swift:6.2.1-noble - timeout-minutes: 120 - env: - DEBIAN_FRONTEND: noninteractive - VCPKG_MAX_CONCURRENCY: 3 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - with: - persist-credentials: false - submodules: recursive - - name: Install native dependencies - run: | - apt-get update - apt-get install -y --no-install-recommends build-essential cmake ninja-build python3 pkg-config curl zip unzip tar zstd nasm autoconf automake libtool libtool-bin gettext freeglut3-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev libudev-dev libbluetooth-dev libgl1-mesa-dev libglu1-mesa-dev libx11-xcb-dev libwayland-dev wayland-protocols extra-cmake-modules dbus xvfb xauth openbox wmctrl x11-utils - git config --global --add safe.directory "$PWD" - mkdir -p "$HOME/.cache/vcpkg/archives" - echo "VCPKG_BINARY_SOURCES=clear;files,$HOME/.cache/vcpkg/archives,readwrite" >> "$GITHUB_ENV" - - uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 - with: - path: ~/.cache/vcpkg/archives - key: switch2kit-cemu-linux-${{ hashFiles('vcpkg.json', 'vcpkg-configuration.json', 'dependencies/vcpkg_overlay_ports*/**') }} - restore-keys: switch2kit-cemu-linux- - - name: Build and install the controller-enabled application - shell: bash - run: | - set -o pipefail - bash scripts/build-switch2kit.sh 2>&1 | tee linux-build.log - - name: Execute controller policies - shell: bash - run: | - set -o pipefail - python3 tests/switch2kit/run.py --sanitize 2>&1 | tee linux-policies.log - - name: Relocate, resolve native dependencies and launch the installed binary - shell: bash - run: | - set -euo pipefail - mv build-switch2kit/install "$RUNNER_TEMP/Cemu-Switch2Kit-linux-x86_64" - app="$RUNNER_TEMP/Cemu-Switch2Kit-linux-x86_64" - mv build-switch2kit "$RUNNER_TEMP/disabled-build-tree" - test -n "$(find "$app" -name libSwitch2KitC.so -print -quit)" - ldd "$app/bin/Cemu_release" | tee linux-dependencies.log - ! grep -q 'not found' linux-dependencies.log - grep -F "$app" linux-dependencies.log | grep libSwitch2KitC - tar -C "$RUNNER_TEMP" -czf Cemu-Switch2Kit-linux-x86_64.tar.gz Cemu-Switch2Kit-linux-x86_64 - mv "$app" "$RUNNER_TEMP/unavailable-staged-application" - dbus-run-session -- xvfb-run -a python3 dependencies/Switch2Kit/tests/emulator-launch/linux.py cemu \ - "$PWD/Cemu-Switch2Kit-linux-x86_64.tar.gz" --report "$PWD/linux-launch.json" \ - --forbidden-root "$PWD" --forbidden-root "$RUNNER_TEMP/disabled-build-tree" \ - --forbidden-root "$RUNNER_TEMP/unavailable-staged-application" - - name: Controller-enabled development build - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 - with: - name: Cemu-Switch2Kit-linux-x86_64 - path: Cemu-Switch2Kit-linux-x86_64.tar.gz - retention-days: 14 - if-no-files-found: error - - name: Native diagnostics - if: always() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 - with: - name: Cemu-Switch2Kit-linux-diagnostics - path: | - linux-*.log - linux-launch.json - retention-days: 7 diff --git a/.github/workflows/switch2kit-windows.yml b/.github/workflows/switch2kit-windows.yml deleted file mode 100644 index 39f55bda03..0000000000 --- a/.github/workflows/switch2kit-windows.yml +++ /dev/null @@ -1,68 +0,0 @@ -name: Switch2Kit Windows application -on: - pull_request: - workflow_dispatch: -permissions: - contents: read -concurrency: - group: switch2kit-windows-${{ github.ref }} - cancel-in-progress: true -jobs: - windows: - runs-on: windows-2022 - timeout-minutes: 120 - env: - VCPKG_MAX_CONCURRENCY: 3 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - with: - persist-credentials: false - submodules: recursive - - uses: compnerd/gha-setup-swift@397094e75494a93fa8d81db0268dbc8f5d6cf7c6 - with: - swift-version: swift-6.2.1-release - swift-build: 6.2.1-RELEASE - - name: Build the complete controller-enabled application - shell: pwsh - run: | - & ./scripts/build-switch2kit.ps1 2>&1 | Tee-Object windows-build.log - if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - # The build helper initialized MSVC in this PowerShell process. - python tests/switch2kit/test_host_file.py 2>&1 | Tee-Object windows-host-file.log - if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - - name: Stage and launch the application away from the build tree - shell: pwsh - run: | - $ErrorActionPreference = 'Stop' - $stage = Join-Path $env:RUNNER_TEMP 'Cemu-Switch2Kit-windows-x86_64' - New-Item -ItemType Directory -Path $stage | Out-Null - # Robocopy follows directory junctions and copies linked resources. - robocopy 'bin' $stage /E /NFL /NDL /NJH /NJS - if ($LASTEXITCODE -ge 8) { throw 'Application staging failed.' } - $global:LASTEXITCODE = 0 - # Never distribute a developer/test profile. The supervisor seeds only - # its private extracted copy, after the distributable archive is made. - foreach ($profile in @('User', 'user', 'portable', 'portable.txt', 'settings.xml')) { - if (Test-Path (Join-Path $stage $profile)) { throw "Unexpected profile in application staging: $profile" } - } - Move-Item build-switch2kit-windows "$env:RUNNER_TEMP/disabled-build-tree" - Compress-Archive -Path $stage -DestinationPath 'Cemu-Switch2Kit-windows-x86_64.zip' - Move-Item $stage "$env:RUNNER_TEMP/unavailable-staged-application" - & ./tests/switch2kit/windows-launch.ps1 -Archive 'Cemu-Switch2Kit-windows-x86_64.zip' -ForbiddenRoot "$PWD" - if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - - name: Controller-enabled development application - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 - with: - name: Cemu-Switch2Kit-windows-x86_64 - path: Cemu-Switch2Kit-windows-x86_64.zip - retention-days: 14 - if-no-files-found: error - - name: Native diagnostics - if: always() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 - with: - name: Cemu-Switch2Kit-windows-diagnostics - path: | - windows-*.log - windows-launch.json - retention-days: 7 diff --git a/README.md b/README.md index 2d0898ce00..9fefc9e9a4 100644 --- a/README.md +++ b/README.md @@ -6,28 +6,23 @@ Use this controller-enabled Cemu directly: connect the controller, choose a play ## Quick start -Sign in to GitHub and select a successful application run for `feature/switch2kit-desktop-platforms` while this PR is unmerged. Choose the named **application** artifact below and check its native build/launch jobs, not a source or diagnostics archive. These are expiring development builds, not published production releases. Use [Build from source](#build-from-source-alternative) when a matching application artifact is unavailable. Ordinary upstream downloads do not include this integration. +**Controller-enabled application artifacts are no longer built for every pull request.** Use [Build from source](#build-from-source-alternative) for the checked-out revision. Older development artifacts may remain in the [Actions history](https://github.com/jmonster/Cemu/actions) until they expire; inspect the recorded revision and its original build/launch results before using one. Ordinary upstream downloads do not include this integration. ### macOS -Use macOS 15 or newer and [Native Switch2Kit builds](https://github.com/jmonster/Cemu/actions/workflows/native-switch2kit.yml). Download **Cemu-Switch2Kit-arm64** for Apple Silicon or **Cemu-Switch2Kit-x86_64** for Intel. Extract the outer ZIP, then `build-switch2kit/integration-app.zip` inside it. Move `Cemu_release.app` to Applications and open it. Enable Bluetooth and allow Cemu's Bluetooth request; denied access is managed under **System Settings > Privacy & Security > Bluetooth**. +Use macOS 15 or newer on Apple Silicon or Intel and follow the [platform build guide](docs/Switch2Kit.md#build-from-source). The helper creates `bin/Cemu_release.app`. Open that app, enable Bluetooth and allow Cemu's Bluetooth request; denied access is managed under **System Settings > Privacy & Security > Bluetooth**. These apps are ad-hoc signed, not notarized. For a source you trust, use Apple's [per-app Open Anyway procedure](https://support.apple.com/en-us/102445); do not disable Gatekeeper globally. ### Linux -Use Ubuntu 24.04 x86-64 with a desktop session, graphics drivers, normal system-bus permissions, a powered Bluetooth LE adapter and the BlueZ service. See the [Linux prerequisites](docs/Switch2Kit.md#linux). From [Switch2Kit Linux application builds](https://github.com/jmonster/Cemu/actions/workflows/switch2kit-linux.yml), download **Cemu-Switch2Kit-linux-x86_64**. Extract the outer artifact ZIP and then: - -```sh -tar -xzf Cemu-Switch2Kit-linux-x86_64.tar.gz -./Cemu-Switch2Kit-linux-x86_64/bin/Cemu_release -``` +Use Ubuntu 24.04 x86-64 with a desktop session, graphics drivers, normal system-bus permissions, a powered Bluetooth LE adapter and the BlueZ service. See the [Linux prerequisites](docs/Switch2Kit.md#linux) and [source build instructions](docs/Switch2Kit.md#build-from-source). The helper installs to `build-switch2kit/install`; launch `build-switch2kit/install/bin/Cemu_release`. Keep `bin`, `lib` and `share` together. The Swift runtime is packaged; no Swift installation or loader-path override is needed for a correctly staged application. Compatible system libraries and drivers remain required. This is not an AppImage or universal Linux binary. ### Windows -Use Windows 11 x64 for these experimental instructions, with a Bluetooth LE driver, graphics drivers and the Microsoft Visual C++ x64 runtime. Native CI uses Windows Server runners, not physical Windows 11 controllers. From [Switch2Kit Windows application builds](https://github.com/jmonster/Cemu/actions/workflows/switch2kit-windows.yml), download **Cemu-Switch2Kit-windows-x86_64**. Extract the outer artifact ZIP and then `Cemu-Switch2Kit-windows-x86_64.zip`. Open `Cemu-Switch2Kit-windows-x86_64/Cemu_release.exe`. +Use Windows 11 x64 for these experimental instructions, with a Bluetooth LE driver, graphics drivers and the Microsoft Visual C++ x64 runtime. Follow the [Windows build instructions](docs/Switch2Kit.md#build-from-source), then open `bin/Cemu_release.exe`. Physical Windows 11 controller support still requires hardware validation. Keep its DLLs, `resources`, `gameProfiles` and `Switch2KitNotices` beside it. The selected Swift runtime is included; launching does not require the Swift compiler or its PATH. Run normally, not as administrator, and do not disable operating-system security to bypass errors. See the [Windows source fallback](docs/Switch2Kit.md#windows). @@ -43,14 +38,14 @@ For Joy-Con 2, discover each half with Find/Sync, choose the emulated controller ### Build from source (alternative) -Follow the [platform build guide](docs/Switch2Kit.md#build-from-source). Start from this implementation branch while the PR is unmerged: +Follow the [platform build guide](docs/Switch2Kit.md#build-from-source). Check out this maintained fork and its pinned dependencies: ```sh -git clone --branch feature/switch2kit-desktop-platforms --recurse-submodules https://github.com/jmonster/Cemu.git cemu-switch2kit +git clone --branch main --recurse-submodules https://github.com/jmonster/Cemu.git cemu-switch2kit cd cemu-switch2kit ``` -The helpers enable Switch2Kit and use the pinned submodule; do not apply the SDK's separate upstream patches. The ordinary upstream instructions below do not enable it by default. Linux/Windows support remains experimental, and native build/launch tests do not establish physical-controller or gameplay acceptance. +The helpers enable Switch2Kit and use the pinned submodule; do not apply the SDK's separate upstream patches. The ordinary upstream instructions below do not enable it by default. Linux/Windows support remains experimental, and the CI smoke test does not establish full native integration, physical-controller or gameplay acceptance. ## Upstream Cemu information diff --git a/docs/Switch2Kit.md b/docs/Switch2Kit.md index fb4cee3c4f..02cd203f53 100644 --- a/docs/Switch2Kit.md +++ b/docs/Switch2Kit.md @@ -2,15 +2,15 @@ **This maintained Cemu fork embeds [Switch2Kit](https://github.com/jmonster/Switch2Kit) for NSO GameCube and Nintendo Switch 2 Pro controllers on macOS 15+, with experimental Linux x86-64 and Windows x64 support.** -Use the [controller-enabled downloads](../README.md#quick-start), launch that Cemu and [select your controller/player slot](#connect-and-play). Ordinary upstream downloads do not embed this backend. No separate dashboard, SDL override, network bridge, virtual-controller driver or Accessibility permission is required. Joy-Con 2 halves remain separate complementary sources; motion requires explicit measured calibration. +Use the [controller-enabled builds](../README.md#quick-start), launch that Cemu and [select your controller/player slot](#connect-and-play). Ordinary upstream downloads do not embed this backend. No separate dashboard, SDL override, network bridge, virtual-controller driver or Accessibility permission is required. Joy-Con 2 halves remain separate complementary sources; motion requires explicit measured calibration. ## Applications and prerequisites -Sign in to GitHub and select a successful run for `feature/switch2kit-desktop-platforms` while this PR is unmerged. Download the application artifact, not a source or diagnostics archive. The outer GitHub artifact ZIP contains the application ZIP/tarball. Desktop application artifacts expire after 14 days; use the source fallback when no matching successful artifact remains. These are development builds, not published production releases. A workflow configuration or a different revision's result is not qualification of the selected download. +Controller-enabled application builds are now a deliberate local operation using the [source helpers](#build-from-source), not extra CI builds on every pull request. The fork retains upstream workflows and one small session smoke test. That test does not produce downloads. Older development artifacts may remain in the [Actions history](https://github.com/jmonster/Cemu/actions) until their original retention expires; their recorded native build/launch results apply only to that exact revision. Unchanged upstream build artifacts do not enable Switch2Kit. ### macOS -Use macOS 15 or newer on Apple Silicon (`arm64`) or Intel (`x86_64`). From [Native Switch2Kit](https://github.com/jmonster/Cemu/actions/workflows/native-switch2kit.yml), download **Cemu-Switch2Kit-arm64** or **Cemu-Switch2Kit-x86_64**, extract the outer and inner ZIPs, move `Cemu_release.app` to Applications and open it. The controller library and runtime are embedded. Enable Bluetooth and permit Cemu under **System Settings > Privacy & Security > Bluetooth** when requested. +Use macOS 15 or newer on Apple Silicon (`arm64`) or Intel (`x86_64`). [Build from source](#build-from-source) with the native architecture, then open `bin/Cemu_release.app`. The helper embeds the controller library and runtime. Enable Bluetooth and permit Cemu under **System Settings > Privacy & Security > Bluetooth** when requested. The application is ad-hoc signed, not notarized. Use Apple's [per-app approval procedure](https://support.apple.com/en-us/102445) only for an application you trust; do not disable Gatekeeper globally. macOS emulator performance/support limitations are separate from controller input support. @@ -23,12 +23,7 @@ sudo apt-get update sudo apt-get install bluez libsystemd0 libgtk-3-0t64 libpulse0 libsecret-1-0 libgcrypt20 libudev1 libgl1 libegl1 libvulkan1 libx11-xcb1 ``` -Get **Cemu-Switch2Kit-linux-x86_64** from [the Linux application workflow](https://github.com/jmonster/Cemu/actions/workflows/switch2kit-linux.yml). Extract the outer ZIP, then: - -```sh -tar -xzf Cemu-Switch2Kit-linux-x86_64.tar.gz -./Cemu-Switch2Kit-linux-x86_64/bin/Cemu_release -``` +[Build from source](#build-from-source), then launch `build-switch2kit/install/bin/Cemu_release`. The helper's `--run` option opens it after building. Keep the whole prefix, including `lib`, `share/Cemu` and `share/Switch2KitNotices`. The selected Swift runtime is packaged; no Swift installation or `LD_LIBRARY_PATH` override is needed to launch it. System desktop libraries and drivers remain prerequisites. @@ -36,11 +31,11 @@ Turn Bluetooth on in your desktop settings. BlueZ must be running, the adapter p ### Windows -The experimental desktop target is Windows 11 x64 with a working Bluetooth LE adapter/driver and graphics drivers. Native CI uses Windows Server runners, not physical Windows 11 controllers. ARM64 and 32-bit Windows are not covered. Install the [Microsoft Visual C++ x64 runtime](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist) if required. +The experimental desktop target is Windows 11 x64 with a working Bluetooth LE adapter/driver and graphics drivers. Previous native qualification used Windows Server runners, not physical Windows 11 controllers. ARM64 and 32-bit Windows are not covered. Install the [Microsoft Visual C++ x64 runtime](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist) if required. -Get **Cemu-Switch2Kit-windows-x86_64** from [the Windows application workflow](https://github.com/jmonster/Cemu/actions/workflows/switch2kit-windows.yml). Extract the outer ZIP and its inner `Cemu-Switch2Kit-windows-x86_64.zip`, then open `Cemu-Switch2Kit-windows-x86_64/Cemu_release.exe`. Keep DLLs, `resources`, `gameProfiles` and `Switch2KitNotices` together. The package includes the Swift runtime; installing Swift or adding compiler directories to PATH is not a launch requirement. +[Build from source](#build-from-source) with the PowerShell helper, then open `bin/Cemu_release.exe`. Keep DLLs, `resources`, `gameProfiles` and `Switch2KitNotices` together. A correctly staged package includes the Swift runtime; installing Swift or adding compiler directories to PATH is not a launch requirement, though Swift is required to build it. -Enable **Settings > Bluetooth & devices > Bluetooth** and use Cemu's Find/Sync procedure. Honor legitimate system pairing/access prompts; the backend does not erase bonds or bypass pairing policy. Run as a normal user, not administrator, and do not disable SmartScreen/antivirus. A missing DLL before the GUI opens is a packaging/runtime prerequisite failure: re-extract the complete controller-enabled artifact and check the native launch result for its revision. +Enable **Settings > Bluetooth & devices > Bluetooth** and use Cemu's Find/Sync procedure. Honor legitimate system pairing/access prompts; the backend does not erase bonds or bypass pairing policy. Run as a normal user, not administrator, and do not disable SmartScreen/antivirus. A missing DLL before the GUI opens is a packaging/runtime prerequisite failure: use the complete output of the build helper and inspect the staged dependencies rather than copying only the executable. ## Connect and play @@ -104,10 +99,10 @@ backend does not invent sensor values or integrate across a disconnect or gap. ## Build from source -`ENABLE_SWITCH2KIT` is OFF by default. Disabled builds do not require Swift and retain upstream platform/deployment requirements. Enabled Linux/Windows builds use native SDL3 and the desktop backend; only enabled macOS builds require a macOS 15+ app bundle. Initialize the SDK revision selected by this maintained fork, not a moving SDK branch or the SDK's separate upstream patches. While the PR is unmerged: +`ENABLE_SWITCH2KIT` is OFF by default. Disabled builds do not require Swift and retain upstream platform/deployment requirements. Enabled Linux/Windows builds use native SDL3 and the desktop backend; only enabled macOS builds require a macOS 15+ app bundle. Initialize the SDK revision selected by this maintained fork, not a moving SDK branch or the SDK's separate upstream patches. Check out main and its recorded dependencies: ```sh -git clone --branch feature/switch2kit-desktop-platforms --recurse-submodules https://github.com/jmonster/Cemu.git cemu-switch2kit +git clone --branch main --recurse-submodules https://github.com/jmonster/Cemu.git cemu-switch2kit cd cemu-switch2kit ``` @@ -120,22 +115,22 @@ bash scripts/build-switch2kit.sh --run The helper builds and opens `bin/Cemu_release.app` with the native architecture. The finished app has its controller/runtime dependencies and notices embedded before ad-hoc signing. -**Linux:** install Swift **6.2.1** from [the official Linux instructions](https://www.swift.org/install/linux/), then the native dependencies used by the Ubuntu workflow: +**Linux:** install Swift **6.2.1** from [the official Linux instructions](https://www.swift.org/install/linux/), then the native build dependencies: ```sh sudo apt-get install build-essential cmake ninja-build python3 pkg-config curl zip unzip tar zstd nasm autoconf automake libtool libtool-bin gettext freeglut3-dev libgcrypt20-dev libglm-dev libgtk-3-dev libpulse-dev libsecret-1-dev libsystemd-dev libudev-dev libbluetooth-dev libgl1-mesa-dev libglu1-mesa-dev libx11-xcb-dev libwayland-dev wayland-protocols extra-cmake-modules dbus bash scripts/build-switch2kit.sh --run ``` -The helper builds through the recorded vcpkg dependencies, installs to `build-switch2kit/install` and opens `build-switch2kit/install/bin/Cemu_release`. Keep the entire install prefix. CI additionally installs Xvfb/Openbox/wmctrl for isolated graphical testing; an ordinary desktop does not need those test tools. +The helper builds through the recorded vcpkg dependencies, installs to `build-switch2kit/install` and opens `build-switch2kit/install/bin/Cemu_release`. Keep the entire install prefix. -**Windows:** use native x64 Swift **6.2.1**, Visual Studio **2022 Desktop development with C++** and its Windows SDK, CMake, Ninja, Git, Python 3 and 64-bit PowerShell 7. Follow [Swift's Windows installation guide](https://www.swift.org/install/windows/) for its toolchain. This combination matches Cemu's Windows job; do not combine Swift 6.2's bundled compiler with VS 2026 STL headers. The helper currently selects the latest installed Visual C++ instance, so use a build machine where that instance is the compatible VS 2022 toolchain. +**Windows:** use native x64 Swift **6.2.1**, Visual Studio **2022 Desktop development with C++** and its Windows SDK, CMake, Ninja, Git, Python 3 and 64-bit PowerShell 7. Follow [Swift's Windows installation guide](https://www.swift.org/install/windows/) for its toolchain. This combination was used by the retired native Windows workflow; do not combine Swift 6.2's bundled compiler with VS 2026 STL headers. The helper currently selects the latest installed Visual C++ instance, so use a build machine where that instance is the compatible VS 2022 toolchain. ```powershell ./scripts/build-switch2kit.ps1 -Run ``` -It bootstraps the pinned vcpkg checkout, builds `CemuBin` with Switch2Kit enabled, stages runtime dependencies and opens `bin/Cemu_release.exe`. Build-time PATH changes stay process-local; extracted packages are tested without the compiler PATH. +It bootstraps the pinned vcpkg checkout, builds `CemuBin` with Switch2Kit enabled, stages runtime dependencies and opens `bin/Cemu_release.exe`. Build-time PATH changes stay process-local. A successful local build is not qualification of a separately distributed package. For updates, quit Cemu, run `git pull --ff-only`, update the recorded submodules and rerun the same helper. Do not delete your settings, profiles or game data to make a new build launch. @@ -143,8 +138,19 @@ For updates, quit Cemu, run `git pull --ff-only`, update the recorded submodules A missing **Find Switch 2 Controllers** button means a backend-disabled binary was launched. For absent input, verify Bluetooth power/access, Sync mode, competing connections, physical selection and the emulated type accepted by the game, then retry Find. A controller already assigned to another slot must be removed there before the quick setup shortcut can move it. Cemu requires Find again after restarting; it does not implement Dolphin's automatic-reconnection option. A changed adapter or rotating device address can change physical identity, so verify player assignments after such a change. -Desktop CI builds the complete application, archives it, extracts that exact archive into a new location, checks that the GUI loads its packaged controller/Swift libraries, requests normal quit and relaunches with a private profile. It deliberately seeds noninteractive test settings; pristine first-use dialogs, downloaded-app security approval and physical hardware are not tested. No existing user configuration is erased. The artifact is qualified only after these jobs pass for its exact revision. +## CI policy + +Upstream's five workflow files and its tests are retained unchanged. The only added check is **Switch2Kit session smoke**: one C++ executable, compiled and run once on Ubuntu for each pull-request update, with a two-minute job limit and a ten-second execution limit. It is also available through manual dispatch. It has no platform matrix, dependency checkout, SDK test rerun, full application build, cache or artifact upload. No extra push or scheduled run is added. + +The test includes the production `Switch2KitSession.h` and controls only the external host boundary. Its single lifecycle scenario checks default-off behavior, failed discovery and explicit retry, preserving an active session after a failed rescan, and stopping polling on disconnect/shutdown. Run the same test locally from the repository root with any C++20 compiler; no submodules are needed: + +```sh +work=$(mktemp -d) +trap 'rm -rf "$work"' EXIT +c++ -std=c++20 -Wall -Wextra -Werror -Isrc tests/switch2kit/smoke.cpp -o "$work/smoke" +"$work/smoke" +``` -`python3 tests/switch2kit/run.py --sanitize` retains executable mapping, identity, lifecycle and rollback regressions against controlled host/storage boundaries. `python3 tests/switch2kit/test_host_file.py --sdk dependencies/Switch2Kit --sanitize` exercises the real bounded file reader; native MSVC coverage is retained. SDK tests cover protocol values, real C/SDL consumers, calibrated sample handling, rumble bounds, cancellation, runtime relocation and required notices. Source-contract checks supplement those tests, not prose length or English wording restrictions. +The additional mapping, identity, file-reader, source-wiring and platform-configuration harnesses, SDK reruns, and duplicate application/GUI qualification workflows have been removed, not hidden behind one aggregate test command. SDK regression testing belongs in the SDK repository. This narrow smoke check does not establish full Cemu/SDL/SDK integration, controller mappings, runtime packaging, GUI startup, platform compatibility or hardware acceptance. Before distributing a controller-enabled build, separately validate that exact build on its target platform using the normal build helpers and real hardware; routine upstream builds keep the backend disabled by default. Record separate hardware acceptance for each model/firmware/OS/adapter and tested commit: first pairing and denied-access retry; all controls and releases; independent GameCube trigger travel/clicks; rumble start/stop; two identical controllers reconnecting in reversed order; persisted player assignments after restart; Bluetooth/adapter loss; explicit disconnect; normal shutdown; measured motion where used; and an actual gameplay session. Joy-Con 2 acceptance must include both complementary sources in one slot. No fixture profile or automated pass substitutes for those physical results. diff --git a/tests/switch2kit/HostFileTests.cpp b/tests/switch2kit/HostFileTests.cpp deleted file mode 100644 index c7cd7ac992..0000000000 --- a/tests/switch2kit/HostFileTests.cpp +++ /dev/null @@ -1,80 +0,0 @@ -// Exercise the production SDK header without Cemu's precompiled header. -#include "HostFile.hpp" -#include -#include -#include -#include - -namespace fs = std::filesystem; -using Switch2Kit::HostFileResult; -using Switch2Kit::readHostFile; - -static std::string utf8(const fs::path& path) { - const auto bytes = path.u8string(); - return std::string(bytes.begin(), bytes.end()); -} - -static void require(bool condition, const char* message) { - if (!condition) throw std::runtime_error(message); -} - -static void reject(const std::string& path, size_t limit, - HostFileResult expected = HostFileResult::Invalid) { - std::string output = "unchanged"; - require(readHostFile(path, limit, output) == expected, "unexpected failure result"); - require(output == "unchanged", "failure changed the caller's output"); -} - -static void checkFile(const fs::path& path, const std::string& bytes, size_t limit) { - { - std::ofstream file(path, std::ios::binary); - file.write(bytes.data(), static_cast(bytes.size())); - require(file.good(), "could not create fixture"); - } - std::string output = "old contents"; - require(readHostFile(utf8(path), limit, output) == HostFileResult::OK, "regular file rejected"); - require(output == bytes, "file bytes changed or were truncated"); - if (bytes.size() > 1) reject(utf8(path), bytes.size() - 1); -} - -int main() { - try { - const auto root = fs::current_path() / "host-file-fixtures"; - require(fs::create_directory(root), "fixture directory already exists"); - const auto path = root / "profile.bin"; - checkFile(path, "", 1); - checkFile(path, std::string("a\r\nb\x1a\0\xff", 7), 7); // No CRT text translation. - checkFile(path, std::string(524288, 'x'), 524288); - checkFile(root / fs::path(u8"profile-\u03c0-\U0001f3ae.json"), "unicode path", 12); - reject(utf8(path), 0); - reject(utf8(path), 524289); - reject("", 1024); - reject(std::string(4097, 'x'), 1024); - reject("bad\npath", 1024); - reject("bad\x7fpath", 1024); - reject(std::string("bad\0path", 8), 1024); - reject(utf8(root), 1024); - reject(utf8(root / "missing"), 1024, HostFileResult::Missing); - reject(utf8(root / "absent" / "missing"), 1024, HostFileResult::Missing); -#if defined(_WIN32) - reject("NUL", 1024); - reject(std::string("\xc0\xaf", 2), 1024); // Invalid UTF-8, not an ANSI path. - const auto name = L"\\\\.\\pipe\\switch2kit-host-file-" + std::to_wstring(::GetCurrentProcessId()); - const HANDLE pipe = ::CreateNamedPipeW(name.c_str(), PIPE_ACCESS_DUPLEX, - PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, 4096, 4096, 0, nullptr); - require(pipe != INVALID_HANDLE_VALUE, "could not create named-pipe fixture"); - struct Close { HANDLE value; ~Close() { ::CloseHandle(value); } } close{pipe}; - reject("\\\\.\\pipe\\switch2kit-host-file-" + std::to_string(::GetCurrentProcessId()), 1024); -#else - reject("/dev/null", 1024); - const auto fifo = root / "fifo"; - require(::mkfifo(fifo.c_str(), 0600) == 0, "could not create FIFO fixture"); - reject(utf8(fifo), 1024); // Must not block waiting for a writer. -#endif - fs::remove_all(root); - std::cout << "Host-file regressions passed\n"; - } catch (const std::exception& error) { - std::cerr << error.what() << '\n'; - return 1; - } -} diff --git a/tests/switch2kit/PolicyTests.cpp b/tests/switch2kit/PolicyTests.cpp deleted file mode 100644 index aa8811d2a8..0000000000 --- a/tests/switch2kit/PolicyTests.cpp +++ /dev/null @@ -1,224 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "ControllerEnums.h" -#include "input/api/SDL/Switch2KitMapping.h" -#include "input/api/SDL/Switch2KitIdentity.h" -#include "input/api/SDL/Switch2KitSession.h" -#include "wxgui/input/Switch2KitSetupTransaction.h" -#include "wxgui/input/Switch2KitDeviceChanges.h" - -// Bluetooth is intentionally absent here. These execute the production policies -// with the actual Cemu/SDL/SDK enum values and controlled host/storage boundaries. -struct Host -{ - int result = 0, discoveries = 0, pumps = 0, stops = 0, shutdowns = 0; - int discover() { ++discoveries; return result; } - int pump() { ++pumps; return 0; } - int stop() { ++stops; return 0; } - void shutdown() { ++shutdowns; } -}; - -void SessionTests() -{ - Switch2KitSession session; - auto& host = session.GetHost(); - session.Pump(); - assert(!session.IsEnabled() && host.pumps == 0); - host.result = -1; - assert(session.Discover() == -1); - session.Pump(); - assert(!session.IsEnabled() && host.pumps == 0); - host.result = 0; - assert(session.Discover() == 0); - session.Pump(); - assert(session.IsEnabled() && host.pumps == 1); - // A failed additional scan must not tear down an already connected session. - host.result = -2; - assert(session.Discover() == -2 && session.IsEnabled()); - assert(session.Stop() == 0); - for (int i = 0; i < 1000; ++i) session.Pump(); - assert(!session.IsEnabled() && host.pumps == 1 && host.stops == 1); - assert(session.Discover() == -2 && !session.IsEnabled()); - host.result = 0; - assert(session.Discover() == 0); - session.Pump(); - assert(host.pumps == 2); - session.Shutdown(); - session.Pump(); - assert(!session.IsEnabled() && host.pumps == 2 && host.shutdowns == 1); - std::cout << "PASS startup consent, failed-start retry, disconnect fence and restart\n"; -} - -template -void MappingTests() -{ - using namespace CemuSwitch2Kit; - for (unsigned model : {S2K_PRO, S2K_GAMECUBE, S2K_JOYCON_LEFT, S2K_JOYCON_RIGHT}) - { - auto entries = GamepadMapping(model); - std::map mapping(entries.begin(), entries.end()); - assert(mapping.size() == entries.size()); - for (auto [button, input] : entries) - { - assert(button > Pad::kButtonId_None && button < Pad::kButtonId_Max); - assert(input < kButtonMAX); - } - if (model != S2K_JOYCON_RIGHT) - { - assert(mapping.at(Pad::kButtonId_StickL_Up) == kAxisYN); - assert(mapping.at(Pad::kButtonId_StickL_Down) == kAxisYP); - assert(mapping.at(Pad::kButtonId_StickL_Left) == kAxisXN); - assert(mapping.at(Pad::kButtonId_ZL) == kTriggerXP); - assert(mapping.at(Pad::kButtonId_Minus) == (model == S2K_GAMECUBE ? SDL_GAMEPAD_BUTTON_MISC2 : SDL_GAMEPAD_BUTTON_BACK)); - } - if (model != S2K_JOYCON_LEFT) - { - assert(mapping.at(Pad::kButtonId_A) == (model == S2K_GAMECUBE ? SDL_GAMEPAD_BUTTON_SOUTH : SDL_GAMEPAD_BUTTON_EAST)); - assert(mapping.at(Pad::kButtonId_B) == (model == S2K_GAMECUBE ? SDL_GAMEPAD_BUTTON_WEST : SDL_GAMEPAD_BUTTON_SOUTH)); - assert(mapping.at(Pad::kButtonId_X) == (model == S2K_GAMECUBE ? SDL_GAMEPAD_BUTTON_EAST : SDL_GAMEPAD_BUTTON_NORTH)); - assert(mapping.at(Pad::kButtonId_Y) == (model == S2K_GAMECUBE ? SDL_GAMEPAD_BUTTON_NORTH : SDL_GAMEPAD_BUTTON_WEST)); - assert(mapping.at(Pad::kButtonId_StickR_Up) == kRotationYN); - assert(mapping.at(Pad::kButtonId_StickR_Right) == kRotationXP); - assert(mapping.at(Pad::kButtonId_ZR) == kTriggerYP); - assert(mapping.at(Pad::kButtonId_R) == SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER); - } - if constexpr (requires { Pad::kButtonId_StickL; Pad::kButtonId_StickR; }) - { - assert(mapping.contains(Pad::kButtonId_StickL) == (model != S2K_GAMECUBE && model != S2K_JOYCON_RIGHT)); - assert(mapping.contains(Pad::kButtonId_StickR) == (model != S2K_GAMECUBE && model != S2K_JOYCON_LEFT)); - } - } - auto left = GamepadMapping(S2K_JOYCON_LEFT); - auto right = GamepadMapping(S2K_JOYCON_RIGHT); - std::map combined(left.begin(), left.end()); - combined.insert(right.begin(), right.end()); - assert(combined.size() == left.size() + right.size()); - auto pro = GamepadMapping(S2K_PRO); - assert((combined == std::map(pro.begin(), pro.end()))); - assert(!IsSupportedModel(0) && !IsSupportedModel(0x2009)); -} - -void RestoreSlotTests() -{ - struct Pad { int source = 0; }; - struct Manager - { - std::shared_ptr slot; - void delete_controller(std::size_t) { slot.reset(); } - void set_controller(const std::shared_ptr& next) - { - // Match InputManager's source inheritance for an empty replacement. - if (slot && next->source == 0) next->source = slot->source; - slot = next; - } - }; - for (int source : {0, 7}) - { - Manager manager{std::make_shared(Pad{8})}; - auto before = std::make_shared(Pad{source}); - CemuSwitch2Kit::RestoreSlot(manager, 0, before); - assert(manager.slot == before && manager.slot->source == source); - } - Manager manager{std::make_shared(Pad{8})}; - CemuSwitch2Kit::RestoreSlot(manager, 0, std::shared_ptr{}); - assert(!manager.slot); - std::cout << "PASS exact rollback of populated, empty and disabled slots\n"; -} - -void TransactionTests() -{ - for (int failure = 0; failure != 6; ++failure) - { - std::string order; - int slot = 7; - bool backedUp = false; - bool result = false; - try - { - result = CemuSwitch2Kit::CommitSetup( - [&] { - order += 'B'; - assert(slot == 7); - if (failure == 1) return false; - if (failure == 2) throw std::runtime_error("backup"); - backedUp = true; - return true; - }, - [&] { - order += 'A'; - assert(backedUp); - slot = 8; - if (failure == 3) throw std::runtime_error("apply"); - }, - [&] { - order += 'S'; - assert(slot == 8); - if (failure == 4) return false; - if (failure == 5) throw std::runtime_error("save"); - return true; - }, - [&] { order += 'R'; slot = 7; }); - } - catch (const std::runtime_error&) { assert(failure == 2); } - if (failure == 0) - assert(result && slot == 8 && order == "BAS"); - else - { - assert(!result && slot == 7); - assert(order == (failure < 3 ? "B" : failure == 3 ? "BAR" : "BASR")); - } - } - std::cout << "PASS backup-before-replace, backup refusal, save failure and exception rollback\n"; -} - -void DeviceChangeTests() -{ - using CemuSwitch2Kit::DeviceChanges; - auto windowState = std::make_shared(); - std::weak_ptr retired = windowState; - // Model the shared ownership retained by EventService's bound slot. No GUI - // object is captured, and the actual production flag implements coalescing. - std::function callback = [state = windowState] { state->Notify(); }; - assert(!windowState->Consume()); - callback(); - callback(); - assert(windowState->Consume() && !windowState->Consume()); - std::thread producer([callback] { for (int i = 0; i < 10000; ++i) callback(); }); - producer.join(); - assert(windowState->Consume() && !windowState->Consume()); - // A retained in-flight callback can finish after the old window releases - // its state without accessing that window or notifying a new instance. - windowState.reset(); - assert(!retired.expired()); - auto reopened = std::make_shared(); - std::thread finishing([callback] { callback(); }); - finishing.join(); - assert(!reopened->Consume()); - callback = {}; - assert(retired.expired()); - std::cout << "PASS coalesced notifications, in-flight window teardown and reopened-window isolation\n"; -} - -int main() -{ - using CemuSwitch2Kit::ValidPhysicalKey; - assert(ValidPhysicalKey("s2k:0123456789abcdef0123456789abcdef")); - for (const auto* invalid : {"", "s2k:", "s2k:0123456789abcdef0123456789abcdeg", "s2k:0123456789abcdef0123456789abcdeF", "0_0123456789abcdef0123456789abcdef", "s2k:0123456789abcdef0123456789abcdef0"}) - assert(!ValidPhysicalKey(invalid)); - std::cout << "PASS persistent identity validation without ordinal fallback\n"; - SessionTests(); - DeviceChangeTests(); - MappingTests(); - MappingTests(); - MappingTests(); - std::cout << "PASS GameCube/Pro labels, trigger/stick axes and complementary Joy-Con mappings for all three pad types\n"; - TransactionTests(); - RestoreSlotTests(); -} diff --git a/tests/switch2kit/run.py b/tests/switch2kit/run.py deleted file mode 100755 index 547bb6371e..0000000000 --- a/tests/switch2kit/run.py +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env python3 -"""Run controller policy regressions. No Bluetooth, GUI or emulation is faked as hardware. - -The small test Pad types contain verbatim enum blocks extracted from the actual -Cemu headers, not duplicated numbers. Complete macOS builds compile the mappings -against the real classes and the production wxWidgets integration. -""" -import argparse -import os -from pathlib import Path -import re -import shutil -import subprocess -import sys -import tempfile - -ROOT = Path(__file__).resolve().parents[2] - -def run(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('--sdk', type=Path, default=ROOT / 'dependencies/Switch2Kit') - parser.add_argument('--sdl', type=Path) - parser.add_argument('--sanitize', action='store_true') - args = parser.parse_args() - compiler = os.environ.get('CXX') or shutil.which('clang++') or shutil.which('g++') - if not compiler: - raise SystemExit('A C++20 compiler is required') - sdk = args.sdk.resolve() - if not (sdk / 'Sources/Switch2KitCABI/include/Switch2KitC.h').is_file(): - raise SystemExit('Initialize the Switch2Kit submodule first') - if args.sdl: - sdl = args.sdl.resolve() / 'include' - else: - sdl = ROOT / 'build-switch2kit/vcpkg_installed' - headers = list(sdl.glob('*/include/SDL3/SDL_gamepad.h')) - if len(headers) != 1: - raise SystemExit('Pass --sdl /path/to/SDL-source or build Cemu first') - sdl = headers[0].parents[1] - assert (sdl / 'SDL3/SDL_gamepad.h').is_file() - with tempfile.TemporaryDirectory(prefix='cemu-s2k-tests-') as directory: - work = Path(directory) - enums = [] - for name in ('VPADController', 'ProController', 'ClassicController'): - source = (ROOT / f'src/input/emulated/{name}.h').read_text() - match = re.search(r'enum ButtonId\s*\{.*?\};', source, re.S) - assert match, f'Missing production enum: {name}' - enums.append(f'struct {name} {{ {match[0]} }};') - (work / 'ControllerEnums.h').write_text('\n'.join(enums)) - binary = work / 'policies' - command = [compiler, '-std=c++20', '-pthread', '-Wall', '-Wextra', '-Werror', '-UNDEBUG', - '-I' + str(work), '-I' + str(ROOT / 'src'), '-I' + str(ROOT / 'src/gui'), - '-I' + str(sdl), '-I' + str(sdk / 'Sources/Switch2KitCABI/include'), - str(ROOT / 'tests/switch2kit/PolicyTests.cpp'), '-o', str(binary)] - if args.sanitize: - command += ['-fsanitize=address,undefined', '-fno-omit-frame-pointer'] - subprocess.run(command, check=True, timeout=120) - subprocess.run([str(binary)], check=True, timeout=30) - host_file = [sys.executable, str(ROOT / 'tests/switch2kit/test_host_file.py'), '--sdk', str(sdk)] - if args.sanitize: - host_file.append('--sanitize') - subprocess.run(host_file, check=True) - subprocess.run(['python3', str(ROOT / 'tests/switch2kit/test_wiring.py')], check=True) - subprocess.run(['python3', str(ROOT / 'tests/switch2kit/test_desktop_lifecycle.py'), - '--sdk', str(sdk)], check=True) - -if __name__ == '__main__': - run() diff --git a/tests/switch2kit/smoke.cpp b/tests/switch2kit/smoke.cpp new file mode 100644 index 0000000000..74d53cf64b --- /dev/null +++ b/tests/switch2kit/smoke.cpp @@ -0,0 +1,66 @@ +#include +#include +#include "input/api/SDL/Switch2KitSession.h" + +// One lifecycle scenario against Cemu's production session. Only the external +// host is controlled; this does not simulate Bluetooth, SDL or a running game. +struct RecordingHost +{ + int discoveryResult = 0, pumpResult = 0, stopResult = 0; + int discoveries = 0, pumps = 0, stops = 0, shutdowns = 0; + + int discover() { ++discoveries; return discoveryResult; } + int pump() { ++pumps; return pumpResult; } + int stop() { ++stops; return stopResult; } + void shutdown() { ++shutdowns; } +}; + +static void Check(bool condition, const char* message) +{ + // Do not use assert: the check must still execute in NDEBUG builds. + if (!condition) + { + std::cerr << "FAIL: " << message << '\n'; + std::exit(EXIT_FAILURE); + } +} + +int main() +{ + Switch2KitSession session; + auto& host = session.GetHost(); + Check(!session.IsEnabled() && session.Pump() == 0 && host.pumps == 0 && + host.discoveries == 0, "startup must not discover or pump without consent"); + + host.discoveryResult = -1; + Check(session.Discover() == -1 && host.discoveries == 1 && !session.IsEnabled(), + "failed discovery must propagate its error without enabling the session"); + Check(session.Pump() == 0 && host.pumps == 0, "failed startup must not enable polling"); + + host.discoveryResult = 0; + Check(session.Discover() == 0 && host.discoveries == 2 && session.IsEnabled(), + "explicit retry must enable a successfully started session"); + host.pumpResult = -2; + Check(session.Pump() == -2 && host.pumps == 1, "active polling must reach the host and return its result"); + + host.discoveryResult = -3; + Check(session.Discover() == -3 && host.discoveries == 3 && session.IsEnabled(), + "failed rescan must preserve an already active session"); + Check(session.Pump() == -2 && host.pumps == 2, "existing input must survive failed rescan"); + + host.stopResult = -4; + Check(session.Stop() == -4 && host.stops == 1 && !session.IsEnabled(), + "disconnect must fence polling even when the host reports a stop error"); + Check(session.Pump() == 0 && host.pumps == 2, "polling must not restart a stopped session"); + + host.discoveryResult = 0; + Check(session.Discover() == 0 && host.discoveries == 4 && session.IsEnabled(), + "a stopped session must permit an explicit restart"); + Check(session.Pump() == -2 && host.pumps == 3, "restarted session must forward polling"); + session.Shutdown(); + Check(host.shutdowns == 1 && !session.IsEnabled() && session.Pump() == 0 && host.pumps == 3, + "shutdown must reach the host and prevent subsequent polling"); + + std::cout << "PASS: Switch2Kit session lifecycle\n"; + return EXIT_SUCCESS; +} diff --git a/tests/switch2kit/test_desktop_lifecycle.py b/tests/switch2kit/test_desktop_lifecycle.py deleted file mode 100644 index 76d6fbbb7c..0000000000 --- a/tests/switch2kit/test_desktop_lifecycle.py +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/env python3 -"""Exercise production preprocessor ownership and isolated CMake admission gates. - -This does not emulate a controller or claim a native GUI/platform build. Only -includes are removed for preprocessing; the production conditional directives -and method bodies are retained. CMake imports are recorded rather than built. -""" -import argparse -import os -from pathlib import Path -import re -import shutil -import subprocess -import tempfile -import unittest - -ROOT = Path(__file__).resolve().parents[2] -SDK = ROOT / 'dependencies/Switch2Kit' - - -def text(path): - return (ROOT / path).read_text() - - -def between(source, start, end): - return source.split(start, 1)[1].split(end, 1)[0] - - -class DesktopLifecycle(unittest.TestCase): - def test_single_sdl_owner_for_each_platform_and_feature_mode(self): - compiler = os.environ.get('CXX') or shutil.which('clang++') or shutil.which('g++') - self.assertTrue(compiler, 'A C++ preprocessor is required') - for platform in ('MACOS', 'LINUX', 'WINDOWS'): - for native in (False, True): - with self.subTest(platform=platform, native=native): - flags = [f'-DBOOST_OS_{name}={int(name == platform)}' - for name in ('MACOS', 'LINUX', 'WINDOWS')] - if native: - flags.append('-DHAVE_SWITCH2KIT') - - def preprocess(path): - source = re.sub(r'^\s*#\s*(include|pragma)\b[^\n]*', '', - text(path), flags=re.M) - return subprocess.run( - [compiler, '-E', '-P', '-x', 'c++', *flags, '-'], - input=source, text=True, capture_output=True, - check=True, timeout=30).stdout - - app = preprocess('src/gui/wxgui/CemuApp.cpp') - app_header = preprocess('src/gui/wxgui/CemuApp.h') - provider = preprocess('src/input/api/SDL/SDLControllerProvider.cpp') - header = preprocess('src/input/api/SDL/SDLControllerProvider.h') - main = platform == 'MACOS' or native - constructor = between(provider, 'SDLControllerProvider::SDLControllerProvider()', - 'SDLControllerProvider::~SDLControllerProvider()') - destructor = between(provider, 'SDLControllerProvider::~SDLControllerProvider()', - 'SDLControllerProvider::get_controllers()') - self.assertEqual('s_thread = std::thread' in constructor, not main) - self.assertEqual('s_thread.join()' in destructor, not main) - self.assertEqual('void SDLControllerProvider::PumpSDLEvents()' in provider, main) - self.assertEqual('SDLControllerProvider::PumpSDLEvents();' in app, main) - self.assertEqual('void OnSDLEventPumpTimer(' in app_header, main) - self.assertEqual('static void PumpSDLEvents();' in header, main) - # No duplicated public/private SDL lifecycle declarations. - self.assertEqual(header.count('static void InitSDL();'), 1) - self.assertEqual(header.count('static void ShutdownSDL();'), 1) - self.assertEqual('NativeSession().Pump()' in provider, native) - if main: - startup = between(app, 'bool CemuApp::OnInit()', 'int CemuApp::OnExit()') - shutdown = between(app, 'int CemuApp::OnExit()', - 'void CemuApp::OnSDLEventPumpTimer(') - self.assertLess(startup.index('SDLControllerProvider::InitSDL();'), - startup.index('CemuCommonInit();')) - self.assertIn('m_sdlEventPumpTimer->Start(5, wxTIMER_CONTINUOUS)', startup) - self.assertLess(shutdown.index('m_sdlEventPumpTimer->Stop()'), - shutdown.index('InputManager::instance().Shutdown()')) - self.assertLess(shutdown.index('InputManager::instance().Shutdown()'), - shutdown.index('SDLControllerProvider::ShutdownSDL()')) - else: - self.assertNotIn('SDLControllerProvider::InitSDL();', app) - self.assertNotIn('SDLControllerProvider::ShutdownSDL();', app) - self.assertIn('SDL_WaitEvent(&event)', provider) - - def test_static_adapter_uses_the_hosts_crt_in_both_build_configurations(self): - # Include the complete production input CMake file, not a copied setter. - # These configure-only targets record CRT choices; the native Windows - # application workflow separately compiles and links the real binaries. - for enabled in (False, True): - for configuration in ('Debug', 'Release'): - with self.subTest(enabled=enabled, configuration=configuration): - with tempfile.TemporaryDirectory(prefix='cemu-crt-policy-') as directory: - root = Path(directory) - (root / 'fixture.cpp').write_text('int adapter_fixture;\n') - source = '''cmake_minimum_required(VERSION 3.24) -project(CemuCRTPolicy LANGUAGES CXX) -set(MSVC TRUE) -set(ENABLE_SDL ON) -function(cemu_use_precompiled_header) -endfunction() -add_library(CemuCommon INTERFACE) -add_library(CemuGui INTERFACE) -add_library(SDL3::SDL3 INTERFACE IMPORTED) -''' - source += f'set(ENABLE_SWITCH2KIT {"ON" if enabled else "OFF"})\n' - if enabled: - source += '''add_library(Switch2KitSDL3 STATIC fixture.cpp) -add_library(Switch2Kit::SDL3 ALIAS Switch2KitSDL3) -set_property(TARGET Switch2KitSDL3 PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") -''' - source += f'add_subdirectory("{(ROOT / "src/input").as_posix()}" input)\n' - source += 'file(GENERATE OUTPUT "${CMAKE_BINARY_DIR}/host-crt.txt" CONTENT "$>")\n' - if enabled: - source += 'file(GENERATE OUTPUT "${CMAKE_BINARY_DIR}/adapter-crt.txt" CONTENT "$>")\n' - else: - source += '''if(TARGET Switch2KitSDL3) - message(FATAL_ERROR "The disabled input target must not acquire the adapter") -endif() -''' - (root / 'CMakeLists.txt').write_text(source) - result = subprocess.run( - ['cmake', '-S', str(root), '-B', str(root / 'build'), - '-G', 'Ninja', f'-DCMAKE_BUILD_TYPE={configuration}'], - text=True, capture_output=True, timeout=30) - self.assertEqual(result.returncode, 0, result.stdout + result.stderr) - expected = 'MultiThreadedDebug' if configuration == 'Debug' else 'MultiThreaded' - self.assertEqual((root / 'build/host-crt.txt').read_text(), expected) - adapter = root / 'build/adapter-crt.txt' - self.assertEqual(adapter.exists(), enabled) - if enabled: - self.assertEqual(adapter.read_text(), expected) - - def test_production_cmake_gates(self): - cmake = shutil.which('cmake') - self.assertTrue(cmake, 'CMake is required') - policy = 'if(ENABLE_SWITCH2KIT)' + text('CMakeLists.txt').split( - 'if(ENABLE_SWITCH2KIT)', 1)[1].split('\n# glslang', 1)[0] - # platform, native, SDL, bundle, deployment, SDK exists, SDK reached, configure succeeds - cases = [ - ('Darwin', False, False, False, '13.4', True, False, True), - ('FreeBSD', False, False, False, '', False, False, True), - ('Darwin', True, True, True, '15.0', True, True, True), - ('Darwin', True, True, False, '15.0', True, False, False), - ('Darwin', True, True, True, '13.4', True, False, False), - ('Linux', True, True, False, '', True, True, True), - ('Windows', True, True, False, '', True, True, True), - ('Windows', True, False, False, '', True, False, False), - ('Windows', False, False, False, '', False, False, True), - ('Linux', True, False, False, '', True, False, False), - ('Linux', True, True, False, '', False, False, False), - ('FreeBSD', True, True, False, '', True, False, False), - ] - with tempfile.TemporaryDirectory(prefix='cemu-cmake-policy-') as directory: - script = Path(directory) / 'policy.cmake' - sdk = SDK - for platform, native, sdl, bundle, version, exists, admitted, valid in cases: - with self.subTest(platform=platform, native=native, sdl=sdl, - bundle=bundle, version=version, sdk=exists): - values = dict(APPLE=platform == 'Darwin', WIN32=platform == 'Windows', - CMAKE_SYSTEM_NAME=platform, ENABLE_SWITCH2KIT=native, - ENABLE_SDL=sdl, MACOS_BUNDLE=bundle, - CMAKE_OSX_DEPLOYMENT_TARGET=version, - SWITCH2KIT_SOURCE_DIR=str(sdk if exists else Path(directory)/'absent')) - setup = ''.join(f'set({key} "{value}")\n' for key, value in values.items()) - script.write_text(setup + ''' -function(add_subdirectory) - message(STATUS "SWITCH2KIT_ADMITTED") -endfunction() -function(add_compile_definitions) -endfunction() -function(include_directories) -endfunction() -''' + policy) - result = subprocess.run([cmake, '-P', str(script)], text=True, - capture_output=True, timeout=30) - output = result.stdout + result.stderr - self.assertEqual(result.returncode == 0, valid, output) - self.assertEqual('SWITCH2KIT_ADMITTED' in output, admitted, output) - - -if __name__ == '__main__': - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('--sdk', type=Path, default=SDK) - args = parser.parse_args() - SDK = args.sdk.resolve() - unittest.main(argv=[__file__], verbosity=2) diff --git a/tests/switch2kit/test_host_file.py b/tests/switch2kit/test_host_file.py deleted file mode 100644 index 6db5167ce8..0000000000 --- a/tests/switch2kit/test_host_file.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env python3 -"""Compile and run the SDK's actual host-file reader on the current platform.""" -import argparse -import os -from pathlib import Path -import shutil -import subprocess -import tempfile - -ROOT = Path(__file__).resolve().parents[2] - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('--sdk', type=Path, default=ROOT / 'dependencies/Switch2Kit') - parser.add_argument('--sanitize', action='store_true') - args = parser.parse_args() - include = args.sdk.resolve() / 'Integrations/Emulators' - if not (include / 'HostFile.hpp').is_file(): - parser.error('Initialize the Switch2Kit submodule first') - compiler = (shutil.which('cl') if os.name == 'nt' else - os.environ.get('CXX') or shutil.which('clang++') or shutil.which('g++')) - if not compiler: - parser.error('A C++20 compiler is required; on Windows initialize the MSVC environment') - source = ROOT / 'tests/switch2kit/HostFileTests.cpp' - with tempfile.TemporaryDirectory(prefix='cemu-host-file-') as directory: - work = Path(directory) - binary = work / ('host-file.exe' if os.name == 'nt' else 'host-file') - if os.name == 'nt': - command = [compiler, '/nologo', '/std:c++20', '/EHsc', '/W4', '/WX', '/utf-8', - '/I' + str(include), str(source), '/Fe:' + str(binary)] - else: - command = [compiler, '-std=c++20', '-Wall', '-Wextra', '-Werror', - '-I' + str(include), str(source), '-o', str(binary)] - if args.sanitize: - command += ['-fsanitize=address,undefined', '-fno-omit-frame-pointer'] - subprocess.run(command, cwd=work, check=True, timeout=120) - subprocess.run([str(binary)], cwd=work, check=True, timeout=30) - - -if __name__ == '__main__': - main() diff --git a/tests/switch2kit/test_wiring.py b/tests/switch2kit/test_wiring.py deleted file mode 100644 index 45f4daca34..0000000000 --- a/tests/switch2kit/test_wiring.py +++ /dev/null @@ -1,80 +0,0 @@ -#!/usr/bin/env python3 -"""Source-contract checks supplement, but never substitute for, native builds.""" -from pathlib import Path -import unittest - -ROOT = Path(__file__).resolve().parents[2] -def text(path): return (ROOT / path).read_text() - -class Wiring(unittest.TestCase): - def test_opt_in_bundle_and_target(self): - cmake = text('CMakeLists.txt') - # Preserve the opt-in contract without prescribing the help text. - self.assertRegex(cmake, r'option\s*\(\s*ENABLE_SWITCH2KIT\s+"(?:[^"\\]|\\.)*"\s+OFF\s*\)') - self.assertIn('if(NOT ENABLE_SDL)', cmake) - self.assertIn('if(NOT (APPLE OR WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Linux"))', cmake) - self.assertIn('if(APPLE AND (NOT MACOS_BUNDLE OR CMAKE_OSX_DEPLOYMENT_TARGET VERSION_LESS 15.0))', cmake) - self.assertIn('CMAKE_OSX_DEPLOYMENT_TARGET VERSION_LESS 15.0', cmake) - self.assertIn('${CMAKE_CURRENT_SOURCE_DIR}/dependencies/Switch2Kit', cmake) - self.assertIn('SWITCH2KIT_BLUETOOTH_USAGE', text('src/resource/MacOSXBundleInfo.plist.in')) - def test_real_input_loop_and_consent(self): - provider = text('src/input/api/SDL/SDLControllerProvider.cpp') - self.assertIn('NativeSession().Discover()', provider) - self.assertIn('NativeSession().Pump()', provider) - self.assertLess(provider.index('NativeSession().Shutdown()'), provider.index('SDL_QuitSubSystem')) - app = text('src/gui/wxgui/CemuApp.cpp') - self.assertIn('SDLControllerProvider::PumpSDLEvents()', app) - self.assertNotIn('FindSwitch2Controllers', app) - def test_enumeration_does_not_cancel_rumble(self): - provider = text('src/input/api/SDL/SDLControllerProvider.cpp') - self.assertIn('SDL_GetGamepadProductForID', provider) - ui = text('src/gui/wxgui/input/InputSettings2.cpp').split('void InputSettings2::RefreshSwitch2Controllers()')[1] - self.assertNotIn('->connect()', ui) - self.assertNotIn('set_default_mapping', ui) - controller = text('src/input/api/SDL/SDLController.cpp') - self.assertIn('SDL_PROP_GAMEPAD_CAP_RUMBLE_BOOLEAN', controller) - def test_backups_and_stale_dialog(self): - setup = text('src/gui/wxgui/input/Switch2KitSetup.cpp') - self.assertIn('ControllerConfigSnapshot(playerIndex) != snapshot', setup) - self.assertIn('wxNO_DEFAULT', setup) - self.assertIn('AssignedElsewhere', setup) - self.assertIn('CommitSetup(', setup) - self.assertIn('CemuSwitch2Kit::RestoreSlot(manager, playerIndex, before)', setup) - self.assertIn('manager.save(playerIndex, name, false)', setup) - self.assertIn('manager.is_gameprofile_set(playerIndex)', setup) - manager = text('src/input/InputManager.cpp') - self.assertIn('FileStream::WriteFileAtomic', manager) - def test_rejected_setup_does_not_open_controller(self): - setup = text('src/gui/wxgui/input/Switch2KitSetup.cpp').split('bool ApplySwitch2KitSetup(')[1] - connect = setup.index('!native->connect()') - self.assertLess(setup.index('manager.is_gameprofile_set(playerIndex)'), connect) - self.assertLess(setup.index('type == EmulatedController::Wiimote'), connect) - self.assertLess(setup.index('AssignedElsewhere(playerIndex, native)'), connect) - def test_saved_native_identity(self): - self.assertIn('starts_with("s2k:")', text('src/input/ControllerFactory.cpp')) - controller = text('src/input/api/SDL/SDLController.cpp') - self.assertIn('ValidPhysicalKey', controller) - self.assertIn('FindSwitch2Device(m_physical_key)', controller) - def test_motion_is_never_fabricated(self): - for path in ['src/input/emulated/VPADController.cpp', 'src/input/emulated/WPADController.cpp']: - self.assertIn('get_motion_data()', text(path)) - provider = text('src/input/api/SDL/SDLControllerProvider.cpp') - self.assertIn('availableSample()', provider) - self.assertIn('loadMotionProfile', provider) - def test_notification_callback_does_not_own_window(self): - ui = text('src/gui/wxgui/input/InputSettings2.cpp') - self.assertIn('&CemuSwitch2Kit::DeviceChanges::Notify, m_switch2DevicesChanged', ui) - self.assertIn('std::shared_ptr', text('src/gui/wxgui/input/InputSettings2.h')) - changed = ui.split('void InputSettings2::on_controller_changed()')[1].split('\n}')[0] - self.assertNotIn('m_switch2DevicesChanged = true', changed) - self.assertIn('wxASSERT(wxIsMainThread())', changed) - def test_timer_ids_and_rumble_cancel(self): - ui = text('src/gui/wxgui/input/settings/DefaultControllerSettings.cpp') - self.assertIn('this, m_timer->GetId()', ui) - self.assertIn('native->TryRumble(m_settings.rumble)', ui) - self.assertIn('m_controller->stop_rumble()', ui) - ui = text('src/gui/wxgui/input/InputSettings2.cpp') - self.assertIn('m_switch2DevicesChanged->Consume()', ui) - self.assertIn('delete m_switch2Timer;', ui) - -if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/tests/switch2kit/windows-launch.ps1 b/tests/switch2kit/windows-launch.ps1 deleted file mode 100644 index 31692418ea..0000000000 --- a/tests/switch2kit/windows-launch.ps1 +++ /dev/null @@ -1,12 +0,0 @@ -param([Parameter(Mandatory=$true)][string]$Archive, - [string]$Report = 'windows-launch.json', - [string]$ForbiddenRoot = '') -$ErrorActionPreference = 'Stop' -Set-StrictMode -Version Latest -$root = (Resolve-Path (Join-Path $PSScriptRoot '../..')).Path -$qualifier = Join-Path $root 'dependencies/Switch2Kit/tests/emulator-launch/windows.ps1' -if (-not (Test-Path $qualifier)) { throw 'Initialize the pinned Switch2Kit submodule before qualification.' } -# The SDK supervisor extracts the exact archive into a new path with spaces, -# launches with an isolated profile and OS-only PATH, inspects loaded DLLs, -# then normally closes and relaunches the real GUI. A failure is not a pass. -& $qualifier -Emulator cemu -Archive $Archive -Report $Report -ForbiddenRoot $ForbiddenRoot