From c3c36d5afc70d1210f267fd9c78924e75f76e35d Mon Sep 17 00:00:00 2001 From: adamws Date: Sun, 24 Nov 2024 15:41:19 +0100 Subject: [PATCH] wip --- .github/workflows/build-and-test.yml | 94 +++++++++++++++++----------- src/ffmpeg.zig | 9 ++- src/layout_labels.zig | 53 ++++++++++++++++ src/textures.zig | 1 - tests/pytest.ini | 2 +- tests/src/conftest.py | 10 +-- tests/src/test_rendering.py | 48 +++++++++----- 7 files changed, 160 insertions(+), 57 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 6d0cb59..ed07254 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -51,65 +51,89 @@ jobs: name: Run functional tests needs: - build-and-test - runs-on: ubuntu-latest + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - uses: actions/download-artifact@v4 with: - name: klawa-ubuntu-latest + name: klawa-${{ matrix.os }} path: zig-out/bin - name: Install dependencies + shell: bash + run: | + pwd + ls -alh zig-out/bin + - if: matrix.os == 'ubuntu-latest' + name: Install dependencies shell: bash run: | sudo apt-get update sudo apt-get install ffmpeg xdotool x11-apps xvfb + - if: matrix.os == 'windows-latest' + name: Install dependencies + shell: bash + run: | + choco install autohotkey.portable ffmpeg + - if: matrix.os == 'windows-latest' + name: Install Mesa + shell: cmd + run: | + curl.exe -L --output mesa.7z --url https://github.com/pal1000/mesa-dist-win/releases/download/24.2.7/mesa3d-24.2.7-release-msvc.7z + "C:\Program Files\7-Zip\7z.exe" x mesa.7z + dir + systemwidedeploy.cmd 1 - name: Install python dependencies shell: bash run: | cd tests - python -m venv .env - . .env/bin/activate pip install -r dev-requirements.txt - - name: Run tests + - if: matrix.os == 'ubuntu-latest' + name: Fix executable permisions shell: bash run: | chmod +x zig-out/bin/klawa + - name: Run tests + shell: bash + run: | # not running with pytest-xdist because renders are way off # when framerate drops below expected 60fps: - cd tests && . .env/bin/activate && python -m pytest src/ + cd tests && python -m pytest src/ - uses: actions/upload-artifact@v4 if: always() with: - name: report + name: report-${{ matrix.os }} path: tests/report/ retention-days: 2 if-no-files-found: error - deploy-preview: - name: Deploy tests results - needs: - - run-functional-tests - runs-on: ubuntu-latest - defaults: - run: - shell: bash - working-directory: ./tests - steps: - - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4 - with: - name: report - path: tests/report - - name: Install Vercel CLI - run: npm install --global vercel@latest - - name: Pull Vercel Environment Information - run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }} - - name: Build Project Artifacts - run: vercel build --token=${{ secrets.VERCEL_TOKEN }} - - name: Deploy Project Artifacts to Vercel - run: vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }} > url.txt - - name: Add summary - shell: bash - run: | - echo '### Deployed' >> $GITHUB_STEP_SUMMARY - cat url.txt >> $GITHUB_STEP_SUMMARY +# deploy-preview: +# name: Deploy tests results +# needs: +# - run-functional-tests +# runs-on: ubuntu-latest +# defaults: +# run: +# shell: bash +# working-directory: ./tests +# steps: +# - uses: actions/checkout@v4 +# - uses: actions/download-artifact@v4 +# with: +# name: report +# path: tests/report +# - name: Install Vercel CLI +# run: npm install --global vercel@latest +# - name: Pull Vercel Environment Information +# run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }} +# - name: Build Project Artifacts +# run: vercel build --token=${{ secrets.VERCEL_TOKEN }} +# - name: Deploy Project Artifacts to Vercel +# run: vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }} > url.txt +# - name: Add summary +# shell: bash +# run: | +# echo '### Deployed' >> $GITHUB_STEP_SUMMARY +# cat url.txt >> $GITHUB_STEP_SUMMARY diff --git a/src/ffmpeg.zig b/src/ffmpeg.zig index a65bd35..61b8735 100644 --- a/src/ffmpeg.zig +++ b/src/ffmpeg.zig @@ -3,6 +3,8 @@ const process = std.process; const Child = process.Child; const File = std.fs.File; +const builtin = @import("builtin"); + pub const Ffmpeg = struct { child: Child, @@ -14,8 +16,13 @@ pub const Ffmpeg = struct { ) !Ffmpeg { const resolution = try std.fmt.allocPrint(allocator, "{d}x{d}", .{ width, height }); defer allocator.free(resolution); + const exec_name: []const u8 = switch (builtin.target.os.tag) { + .linux => "ffmpeg", + .windows => "ffmpeg.exe", + else => @compileError("unsupported platform"), + }; const args = [_][]const u8{ - "ffmpeg", "-y", + exec_name, "-y", "-f", "rawvideo", "-framerate", "60", "-s", resolution, diff --git a/src/layout_labels.zig b/src/layout_labels.zig index f674eed..11c4d8b 100644 --- a/src/layout_labels.zig +++ b/src/layout_labels.zig @@ -432,3 +432,56 @@ const layout_labels_lookup_slice = blk: { pub const labels_lookup = std.StaticStringMap(usize).initComptime(layout_labels_lookup_slice); +//const testing = @import("std").testing; +// +//test "test label mapping" { +// const allocator = std.testing.allocator; +// std.debug.print("{any}\n", .{x11.XK_BackSpace}); +// +// const display: *x11.Display = x11.XOpenDisplay(null) orelse { +// std.debug.print("Unable to connect to X server\n", .{}); +// return error.X11InitializationFailed; +// }; +// var min_keycode: c_int = 0; +// var max_keycode: c_int = 0; +// _ = x11.XDisplayKeycodes(display, &min_keycode, &max_keycode); +// +// var keysyms_per_keycode: c_int = 0; +// const origkeymap = x11.XGetKeyboardMapping(display, @intCast(min_keycode), (max_keycode - min_keycode + 1), &keysyms_per_keycode); +// //defer x11.XFree(origkeymap); +// +// std.debug.print("{} {} keysyms_per_keycode {}\n", .{min_keycode, max_keycode, keysyms_per_keycode}); +// +// //var lookup = try allocator.alloc(x11.KeySym, (max_keycode - min_keycode + 1) * keysyms_per_keycode); +// //var lookup_index: usize = 0; +// +// var map = std.AutoHashMap(x11.KeySym, usize).init(allocator); +// defer map.deinit(); +// +// const no_symbol = "NoSymbol"; +// var keymap = origkeymap; +// for (@as(usize, @intCast(min_keycode))..@as(usize, @intCast(max_keycode+1))) |keycode| { +// var max = keysyms_per_keycode - 1; +// while (max >= 0 and keymap[@as(usize, @intCast(max))] == x11.NoSymbol) { +// max -= 1; +// } +// std.debug.print("{}\t", .{keycode}); +// for (0..@as(usize, @intCast(max+1))) |j| { +// const ks = keymap[j]; +// +// //lookup[lookup_index] = ks; +// //lookup_index += 1; +// try map.put(ks, keycode); +// +// const s = if (ks != x11.NoSymbol) x11.XKeysymToString(ks) else no_symbol.ptr; +// std.debug.print("0x{x} ({s})\t", .{ks, s}); +// } +// keymap += @as(usize, @intCast(keysyms_per_keycode)); +// std.debug.print("\n", .{}); +// } +// +// var iterator = map.iterator(); +// while (iterator.next()) |entry| { +// std.debug.print("{x}: {}\n", .{entry.key_ptr.*, entry.value_ptr.*}); +// } +//} diff --git a/src/textures.zig b/src/textures.zig index 5860da8..d1a5472 100644 --- a/src/textures.zig +++ b/src/textures.zig @@ -79,7 +79,6 @@ fn getPositions(sizes: []const rl.Vector2) [keycap_sizes.len]rl.Vector2 { } pub fn getPositionBySize(size: rl.Vector2) rl.Vector2 { - std.debug.print("Looking for {d} {d}\n", .{size.x, size.y}); for (keycap_sizes, 0..) |s, i| { if (size.x == s.x and size.y == s.y) { return atlas_positions[i]; diff --git a/tests/pytest.ini b/tests/pytest.ini index 4e538a3..43db534 100644 --- a/tests/pytest.ini +++ b/tests/pytest.ini @@ -1,7 +1,7 @@ [pytest] addopts = --html=report/index.html - --app-path=../zig-out/bin/klawa + --app-dir=../zig-out/bin generate_report_on_test = True render_collapsed = log_cli = True diff --git a/tests/src/conftest.py b/tests/src/conftest.py index 4fb4c25..5edd1d4 100644 --- a/tests/src/conftest.py +++ b/tests/src/conftest.py @@ -2,6 +2,7 @@ import logging import os import shutil +import sys from pathlib import Path import pytest @@ -11,18 +12,19 @@ def pytest_addoption(parser) -> None: parser.addoption( - "--app-path", + "--app-dir", action="store", - help="Path to klawa executable", + help="Path to directory of klawa executable", default=False, ) @pytest.fixture(scope="session") def app_path(request) -> Path: - app_path = request.config.getoption("--app-path") + app_path = request.config.getoption("--app-dir") assert app_path, "App path is required" - return Path(os.path.realpath(app_path)) + app_name = "klawa.exe" if sys.platform == "win32" else "klawa" + return Path(os.path.realpath(app_path)) / app_name @pytest.fixture diff --git a/tests/src/test_rendering.py b/tests/src/test_rendering.py index 2440c9e..758a937 100644 --- a/tests/src/test_rendering.py +++ b/tests/src/test_rendering.py @@ -1,6 +1,5 @@ import logging import os -from os.path import isfile import signal import shutil import subprocess @@ -58,7 +57,7 @@ def get_screen_manager(): else: return HostScreenManager() else: - pytest.skip(f"Platform '{sys.platform}' is not supported") + return HostScreenManager() def set_keyboard_layout(lang): @@ -91,20 +90,22 @@ def set_keyboard_layout(lang): def screen_manager(): with get_screen_manager() as _: - # this is a trick to keep one keyboard layout for full display lifetime, - # otherwise server would regenerate layout on last client disconection - # https://stackoverflow.com/questions/75919741/how-to-add-keyboard-layouts-to-xvfb - # 1. run some app in the background for full duration on test - # 2. configure keyboard layout - # 3. test - dummy = subprocess.Popen(["xlogo"]) - time.sleep(0.5) + if sys.platform == "linux": + # this is a trick to keep one keyboard layout for full display lifetime, + # otherwise server would regenerate layout on last client disconection + # https://stackoverflow.com/questions/75919741/how-to-add-keyboard-layouts-to-xvfb + # 1. run some app in the background for full duration on test + # 2. configure keyboard layout + # 3. test + dummy = subprocess.Popen(["xlogo"]) + time.sleep(0.5) - set_keyboard_layout("pl") + set_keyboard_layout("pl") yield - dummy.kill() + if sys.platform == "linux": + dummy.kill() def log_config(tmpdir) -> None: @@ -152,10 +153,25 @@ def run_process_capture_logs(command, cwd, name="", process_holder=None) -> None process.wait() +def run_typing_process(workdir, text: str) -> None: + if sys.platform == "win32": + with open(workdir / "type.ahk", "w", encoding="utf-8") as f: + f.write("SetKeyDelay 400, 25\n") # 75ms between keys, 25ms between down/up. + f.write(f"SendEvent \"{text}\"\n") + result = subprocess.run(["AutoHotkey.exe", "/ErrorStdOut", "type.ahk"], cwd=workdir) + else: + result = subprocess.run(["xdotool", "type", "--delay", "400", text]) + logger.info(f"stdout: {result.stdout}") + logger.info(f"stderr: {result.stderr}") + assert result.returncode == 0 + + def __get_parameters(): texts = [ - "The quick brown fox jumps over the lazy dog", - "Dość błazeństw, żrą mój pęk luźnych fig", + #"The quick brown fox jumps over the lazy dog", + "Thequickbrownfoxjumpsoverthelazydog", + "Thequickbrownfoxjumpsoverthelazydog", + #"Dość błazeństw, żrą mój pęk luźnych fig", ] examples = [ "", # default @@ -192,7 +208,7 @@ def test_record_and_render(app_isolation, text: str, example) -> None: thread.start() time.sleep(2) - subprocess.run(["xdotool", "type", "--delay", "400", text]) + run_typing_process(app_dir, text) process = processes.get("klawa") if process and process.poll() is None: @@ -202,3 +218,5 @@ def test_record_and_render(app_isolation, text: str, example) -> None: args = [app, "--replay", "events.bin", "--render", "output.webm"] run_process_capture_logs(args, app_dir) + assert (app_dir / "output.webm").is_file() +