From e3092064db5407177e2b39fd0a63531e0a5fa192 Mon Sep 17 00:00:00 2001 From: Neil Halelamien Date: Thu, 22 Jan 2026 22:10:29 -0800 Subject: [PATCH 1/4] Add shell testing infrastructure and core helpers - Dockerfile for Alpine-based test environment - BATS helper setup (mocks.bash, test_helper.bash) - Makefile targets: test-shell, test-shell-debug - CI Workflow for automated testing --- .github/workflows/test-shell.yml | 34 +++++++++++ Makefile | 29 +++++++++- test/shell-tests/Dockerfile | 22 ++++++++ test/shell-tests/README.md | 30 ++++++++++ test/shell-tests/support/mocks.bash | 69 +++++++++++++++++++++++ test/shell-tests/support/test_helper.bash | 31 ++++++++++ 6 files changed, 213 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/test-shell.yml create mode 100644 test/shell-tests/Dockerfile create mode 100644 test/shell-tests/README.md create mode 100644 test/shell-tests/support/mocks.bash create mode 100644 test/shell-tests/support/test_helper.bash diff --git a/.github/workflows/test-shell.yml b/.github/workflows/test-shell.yml new file mode 100644 index 0000000000..bc679c636c --- /dev/null +++ b/.github/workflows/test-shell.yml @@ -0,0 +1,34 @@ +name: Shell Integration Tests + +on: + push: + paths: + - 'src/**/*.sh' + - 'static/**/*.sh' + - 'test/shell-tests/**' + pull_request: + paths: + - 'src/**/*.sh' + - 'static/**/*.sh' + - 'test/shell-tests/**' + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build test image with cache + uses: docker/build-push-action@v5 + with: + context: test/shell-tests + load: true + tags: onion-shell-test:latest + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Run shell tests + run: make test-shell diff --git a/Makefile b/Makefile index 6d00138a47..43110abb60 100644 --- a/Makefile +++ b/Makefile @@ -54,7 +54,7 @@ include ./src/common/commands.mk ########################################################### -.PHONY: all version core apps external release clean deepclean git-clean with-toolchain patch lib test +.PHONY: all version core apps external release clean deepclean git-clean with-toolchain patch lib test test-shell test-shell-debug test-all all: dist @@ -227,7 +227,8 @@ release: dist clean: @$(ECHO) $(PRINT_RECIPE) @rm -rf $(BUILD_DIR) $(BUILD_TEST_DIR) $(ROOT_DIR)/dist $(TEMP_DIR)/configs - @rm -f $(CACHE)/.setup + @rm -f $(CACHE)/.setup $(CACHE)/.shell-test-image + @docker rmi onion-shell-test:latest 2>/dev/null || true @find include src -type f -name *.o -exec rm -f {} \; deepclean: clean @@ -279,3 +280,27 @@ static-analysis: external-libs format: @find ./src -regex '.*\.\(c\|h\|cpp\|hpp\)' -exec clang-format -style=file -i {} \; + +# Shell script testing (Docker + BATS) +SHELL_TEST_IMAGE := onion-shell-test:latest +SHELL_TEST_DIR := $(TEST_SRC_DIR)/shell-tests + +$(CACHE)/.shell-test-image: $(SHELL_TEST_DIR)/Dockerfile + docker build -t $(SHELL_TEST_IMAGE) $(SHELL_TEST_DIR) + $(createfile) $(CACHE)/.shell-test-image + +test-shell: $(CACHE)/.shell-test-image + @$(ECHO) $(COLOR_BLUE)"Running shell tests..."$(COLOR_NORMAL) + docker run --rm \ + -v "$(ROOT_DIR)":/root/workspace:ro \ + $(SHELL_TEST_IMAGE) \ + -r /root/workspace/test/shell-tests/ + +test-shell-debug: $(CACHE)/.shell-test-image + @$(ECHO) $(COLOR_BLUE)"Starting interactive debug session..."$(COLOR_NORMAL) + docker run -it --rm \ + -v "$(ROOT_DIR)":/root/workspace \ + --entrypoint /bin/bash \ + $(SHELL_TEST_IMAGE) + +test-all: test test-shell diff --git a/test/shell-tests/Dockerfile b/test/shell-tests/Dockerfile new file mode 100644 index 0000000000..1d16a03ab8 --- /dev/null +++ b/test/shell-tests/Dockerfile @@ -0,0 +1,22 @@ +FROM alpine:3.18 + +# Install BATS and shell dependencies +RUN apk add --no-cache bash coreutils findutils grep sed unzip git jq 7zip \ + && apk add --no-cache --repository=https://dl-cdn.alpinelinux.org/alpine/edge/testing bats + +# Install BATS helper libraries +RUN git clone --depth 1 https://github.com/bats-core/bats-support /opt/bats-support && \ + git clone --depth 1 https://github.com/bats-core/bats-assert /opt/bats-assert && \ + git clone --depth 1 https://github.com/bats-core/bats-file /opt/bats-file + +# Create mock Miyoo Mini filesystem +RUN mkdir -p /mnt/SDCARD/.tmp_update/bin \ + /mnt/SDCARD/.tmp_update/config \ + /mnt/SDCARD/App/PackageManager/data \ + /mnt/SDCARD/Emu \ + /mnt/SDCARD/RApp \ + /mnt/SDCARD/Saves/CurrentProfile \ + /mnt/SDCARD/RetroArch/.retroarch + +WORKDIR /root/workspace +ENTRYPOINT ["bats"] diff --git a/test/shell-tests/README.md b/test/shell-tests/README.md new file mode 100644 index 0000000000..b280f78412 --- /dev/null +++ b/test/shell-tests/README.md @@ -0,0 +1,30 @@ +# Shell Tests + +Integration tests using Docker + BATS. + +```bash +make test-shell # Run all tests +make test-shell-debug # Interactive debug +``` + +## Adding Tests + +1. Create `.bats` in appropriate folder (`package_manager/`, `packages/`, `theme_switcher/`) +2. Load helpers and use `setup()`: + ```bash + load '../../support/test_helper' + load '../../support/mocks' + + setup() { + cleanup_test_data + mock_system_commands + } + ``` +3. Run scripts with `run sh "$PROJECT/path/to/script.sh" args` + +## Available Mocks + +- `mock_system_commands` - No-ops `sync`, `sleep`; logs `reboot`, `poweroff` +- `mock_date [timestamp]` - Returns fixed timestamp +- `mock_md5sum [hash]` - Returns fixed hash +- `mock_binary ` - Creates logging stub diff --git a/test/shell-tests/support/mocks.bash b/test/shell-tests/support/mocks.bash new file mode 100644 index 0000000000..3993d4b704 --- /dev/null +++ b/test/shell-tests/support/mocks.bash @@ -0,0 +1,69 @@ +# Mock utilities for shell script testing + +# Create a mock executable that logs usage +mock_binary() { + local name="$1" + local bin_path="/usr/local/bin/$name" + + export PATH="/usr/local/bin:$PATH" + + echo '#!/bin/sh' > "$bin_path" + echo "echo \"called $name with: \$*\" >> $MOCK_LOG" >> "$bin_path" + echo "exit 0" >> "$bin_path" + chmod +x "$bin_path" +} + +# Mock standard system commands for performance/safety +mock_system_commands() { + eval "sync() { :; }" + export -f sync + + eval "sleep() { :; }" + export -f sleep + + eval "reboot() { echo 'reboot called' >> $MOCK_LOG; }" + export -f reboot + + eval "poweroff() { echo 'poweroff called' >> $MOCK_LOG; }" + export -f poweroff +} + +# Mock date command to return predictable values +mock_date() { + local timestamp="${1:-1700000000}" + local bin_path="/usr/local/bin/date" + + export PATH="/usr/local/bin:$PATH" + + cat > "$bin_path" << 'MOCK_EOF' +#!/bin/sh +if [ "$1" = "+%s" ]; then + if [ "$2" = "-s" ]; then + echo "date set to: $3" >> /tmp/mock_calls.log + exit 0 + fi + echo "MOCK_TIMESTAMP" + exit 0 +fi +/bin/date "$@" +MOCK_EOF + sed -i "s/MOCK_TIMESTAMP/$timestamp/" "$bin_path" + chmod +x "$bin_path" +} + +# Mock themeSwitcher binary +mock_themeSwitcher() { + mock_binary "themeSwitcher" +} + +# Mock md5sum to return predictable hash +# Usage: mock_md5sum [hash] +mock_md5sum() { + local hash="${1:-abc123def456}" + local bin_path="/usr/local/bin/md5sum" + export PATH="/usr/local/bin:$PATH" + + echo '#!/bin/sh' > "$bin_path" + echo "echo \"$hash \$1\"" >> "$bin_path" + chmod +x "$bin_path" +} diff --git a/test/shell-tests/support/test_helper.bash b/test/shell-tests/support/test_helper.bash new file mode 100644 index 0000000000..04e2ef9b3e --- /dev/null +++ b/test/shell-tests/support/test_helper.bash @@ -0,0 +1,31 @@ +# Core test helper - loads BATS libraries and provides base setup + +# Load BATS helper libraries +load '/opt/bats-support/load' +load '/opt/bats-assert/load' +load '/opt/bats-file/load' + +# Common paths +export SDCARD="/mnt/SDCARD" +export TEST_DATA="/tmp/test_data" +export PROJECT="/root/workspace" +export MOCK_LOG="/tmp/mock_calls.log" + +# Reset mock SDCARD to clean state +setup_sdcard() { + rm -rf "$SDCARD" + mkdir -p "$SDCARD/App" \ + "$SDCARD/Emu" \ + "$SDCARD/RApp" \ + "$SDCARD/.tmp_update/bin" \ + "$SDCARD/.tmp_update/config" \ + "$SDCARD/Saves/CurrentProfile" + mkdir -p "$TEST_DATA" +} + +# Clean up test data +cleanup_test_data() { + rm -rf "$TEST_DATA" + mkdir -p "$TEST_DATA" + rm -f "$MOCK_LOG" +} From 785c3546bb2d798039b0c1446e8a3738b29b6be2 Mon Sep 17 00:00:00 2001 From: Neil Halelamien Date: Thu, 22 Jan 2026 22:10:51 -0800 Subject: [PATCH 2/4] Add tests for package manager and common utilities - pacman_install.bats: Tests for JSON config parsing - apply.bats: Tests for common package installer --- .../package_manager/pacman_install.bats | 53 +++++++++++++++++++ test/shell-tests/packages/common/apply.bats | 48 +++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 test/shell-tests/package_manager/pacman_install.bats create mode 100644 test/shell-tests/packages/common/apply.bats diff --git a/test/shell-tests/package_manager/pacman_install.bats b/test/shell-tests/package_manager/pacman_install.bats new file mode 100644 index 0000000000..27a9ec6969 --- /dev/null +++ b/test/shell-tests/package_manager/pacman_install.bats @@ -0,0 +1,53 @@ +#!/usr/bin/env bats + +load '../support/test_helper' +load '../support/mocks' + +setup() { + setup_sdcard + cleanup_test_data + mock_system_commands + mkdir -p "$TEST_DATA/TestPackage/App/TestApp" +} + +@test "pacman_install.sh copies package files to SDCARD" { + echo '{"label": "New App"}' > "$TEST_DATA/TestPackage/App/TestApp/config.json" + + run sh "$PROJECT/src/packageManager/script/pacman_install.sh" "$TEST_DATA" TestPackage + + assert_success + assert_file_exists "$SDCARD/App/TestApp/config.json" +} + +@test "pacman_install.sh preserves existing label and imgpath" { + # IMPORTANT: Use multi-line JSON - script parser is fragile with single-line + cat > "$TEST_DATA/TestPackage/App/TestApp/config.json" < "$SDCARD/App/TestApp/config.json" < "$TEST_DATA/TestPackage/App/TestApp/config.json" + + run sh "$PROJECT/src/packageManager/script/pacman_install.sh" "$TEST_DATA" TestPackage + + assert_success + assert_file_contains "$SDCARD/App/TestApp/config.json" '"label": "Fresh Install"' +} diff --git a/test/shell-tests/packages/common/apply.bats b/test/shell-tests/packages/common/apply.bats new file mode 100644 index 0000000000..1e8400561f --- /dev/null +++ b/test/shell-tests/packages/common/apply.bats @@ -0,0 +1,48 @@ +#!/usr/bin/env bats + +load '../../support/test_helper' +load '../../support/mocks' + +setup() { + setup_sdcard + cleanup_test_data + mock_system_commands + + # Create mock source directory + # apply.sh expects a folder named the same as the target's basename in its own directory + mkdir -p "$TEST_DATA/common/App" + touch "$TEST_DATA/common/App/common_script.sh" + cp "$PROJECT/static/packages/common/apply.sh" "$TEST_DATA/common/apply.sh" + + # Create target structure on SDCARD + mkdir -p "$SDCARD/App/Package1" + mkdir -p "$SDCARD/App/Package2" + mkdir -p "$SDCARD/App/NoConfig" + + echo '{"name": "p1"}' > "$SDCARD/App/Package1/config.json" + echo '{"name": "p2"}' > "$SDCARD/App/Package2/config.json" +} + +@test "apply.sh copies scripts to directories with config.json" { + run sh "$TEST_DATA/common/apply.sh" "$SDCARD/App" + + assert_success + assert_file_exists "$SDCARD/App/Package1/common_script.sh" + assert_file_exists "$SDCARD/App/Package2/common_script.sh" +} + +@test "apply.sh skips directories without config.json" { + run sh "$TEST_DATA/common/apply.sh" "$SDCARD/App" + + assert_success + assert_file_not_exists "$SDCARD/App/NoConfig/common_script.sh" +} + +@test "apply.sh exits gracefully if source directory missing" { + rm -rf "$TEST_DATA/common/App" + + run sh "$TEST_DATA/common/apply.sh" "$SDCARD/App" + + assert_success # Script just exits, no error code + assert_file_not_exists "$SDCARD/App/Package1/common_script.sh" +} From 786ad9f20e2719593a6a4fc765a916f458c0a1b0 Mon Sep 17 00:00:00 2001 From: Neil Halelamien Date: Thu, 22 Jan 2026 22:11:11 -0800 Subject: [PATCH 3/4] Add tests for Guest Mode and Theme Switcher - Guest Mode: Comprehensive tests for switching, time persistence, and launching - Theme Switcher: Tests for theme extraction and preview generation - Added guest_mode_helper.bash and theme_helper.bash for test setup --- .../packages/guest_mode/guest_mode.bats | 158 ++++++++++++++++++ .../guest_mode/guest_mode_helper.bash | 30 ++++ .../theme_switcher/extract_previews.bats | 53 ++++++ .../theme_switcher/extract_theme.bats | 72 ++++++++ .../theme_switcher/theme_helper.bash | 30 ++++ 5 files changed, 343 insertions(+) create mode 100644 test/shell-tests/packages/guest_mode/guest_mode.bats create mode 100644 test/shell-tests/packages/guest_mode/guest_mode_helper.bash create mode 100644 test/shell-tests/theme_switcher/extract_previews.bats create mode 100644 test/shell-tests/theme_switcher/extract_theme.bats create mode 100644 test/shell-tests/theme_switcher/theme_helper.bash diff --git a/test/shell-tests/packages/guest_mode/guest_mode.bats b/test/shell-tests/packages/guest_mode/guest_mode.bats new file mode 100644 index 0000000000..638da7f1d4 --- /dev/null +++ b/test/shell-tests/packages/guest_mode/guest_mode.bats @@ -0,0 +1,158 @@ +#!/usr/bin/env bats + +load '../../support/test_helper' +load '../../support/mocks' +load 'guest_mode_helper' + +setup() { + cleanup_test_data + mock_system_commands + setup_guest_mode + setup_guest_mode_scripts + mock_themeSwitcher +} + +# install.sh tests + +@test "Guest Mode: install.sh sets configON when MainProfile exists" { + mkdir -p "$SDCARD/Saves/MainProfile" + + cd "$SDCARD/App/Guest_Mode" + run sh ./install.sh + + assert_success + run diff ./config.json ./data/configON.json + assert_success +} + +@test "Guest Mode: install.sh does nothing when MainProfile missing" { + echo '{"original": true}' > "$SDCARD/App/Guest_Mode/config.json" + + cd "$SDCARD/App/Guest_Mode" + run sh ./install.sh + + assert_success + assert_file_contains "$SDCARD/App/Guest_Mode/config.json" '"original": true' +} + +# saveTime.sh tests + +@test "Guest Mode: saveTime.sh saves timestamp to currentTime.txt" { + mock_date "1700000000" + + cd "$SDCARD/App/Guest_Mode" + run sh ./saveTime.sh + + assert_success + assert_file_exists "$SDCARD/Saves/CurrentProfile/saves/currentTime.txt" + assert_file_contains "$SDCARD/Saves/CurrentProfile/saves/currentTime.txt" "1700000000" +} + +# loadTime.sh tests + +@test "Guest Mode: loadTime.sh adds default 4-hour offset" { + mock_date "1700000000" + echo "1700000000" > "$SDCARD/Saves/CurrentProfile/saves/currentTime.txt" + + cd "$SDCARD/App/Guest_Mode" + run sh ./loadTime.sh + + assert_success + assert_file_contains "$MOCK_LOG" "date set to: @1700014400" +} + +@test "Guest Mode: loadTime.sh uses custom hours offset" { + mock_date "1700000000" + echo "1700000000" > "$SDCARD/Saves/CurrentProfile/saves/currentTime.txt" + echo "2" > "$SDCARD/.tmp_update/config/startup/addHours" + + cd "$SDCARD/App/Guest_Mode" + run sh ./loadTime.sh + + assert_success + assert_file_contains "$MOCK_LOG" "date set to: @1700007200" +} + +@test "Guest Mode: loadTime.sh skips offset when NTP enabled" { + mock_date "1700000000" + echo "1700000000" > "$SDCARD/Saves/CurrentProfile/saves/currentTime.txt" + touch "$SDCARD/.tmp_update/config/.ntpState" + + cd "$SDCARD/App/Guest_Mode" + run sh ./loadTime.sh + + assert_success + assert_file_contains "$MOCK_LOG" "date set to: @1700000000" +} + +@test "Guest Mode: loadTime.sh handles missing currentTime.txt" { + mock_date "1700000000" + rm -f "$SDCARD/Saves/CurrentProfile/saves/currentTime.txt" + + cd "$SDCARD/App/Guest_Mode" + run sh ./loadTime.sh + + assert_success + assert_file_contains "$MOCK_LOG" "date set to: @14400" +} + +# launch.sh tests + +@test "Guest Mode: launch.sh switches Main to Guest" { + mkdir -p "$SDCARD/Saves/GuestProfile/theme" + mkdir -p "$SDCARD/Saves/GuestProfile/lists" + echo "/mnt/SDCARD/Themes/GuestTheme/" > "$SDCARD/Saves/GuestProfile/theme/currentTheme" + echo '{"guest": "favorites"}' > "$SDCARD/Saves/GuestProfile/lists/favorites.json" + + echo "/mnt/SDCARD/Themes/MainTheme/" > "$SDCARD/Saves/CurrentProfile/theme/currentTheme" + echo '{"main": "recents"}' > "$SDCARD/Roms/recentlist.json" + + mock_date "1700000000" + + cd "$SDCARD/App/Guest_Mode" + run sh ./launch.sh + + assert_success + assert_dir_exists "$SDCARD/Saves/MainProfile" + assert_dir_not_exists "$SDCARD/Saves/GuestProfile" +} + +@test "Guest Mode: launch.sh switches Guest to Main" { + mkdir -p "$SDCARD/Saves/MainProfile/theme" + mkdir -p "$SDCARD/Saves/MainProfile/lists" + echo "/mnt/SDCARD/Themes/MainTheme/" > "$SDCARD/Saves/MainProfile/theme/currentTheme" + + mock_date "1700000000" + + cd "$SDCARD/App/Guest_Mode" + run sh ./launch.sh + + assert_success + assert_dir_exists "$SDCARD/Saves/GuestProfile" + assert_dir_not_exists "$SDCARD/Saves/MainProfile" +} + +@test "Guest Mode: launch.sh calls themeSwitcher" { + mkdir -p "$SDCARD/Saves/GuestProfile" + mock_date "1700000000" + + cd "$SDCARD/App/Guest_Mode" + run sh ./launch.sh + + assert_success + assert_file_contains "$MOCK_LOG" "called themeSwitcher with: --reapply_icons" +} + +@test "Guest Mode: launch.sh preserves theme via system.json" { + mkdir -p "$SDCARD/Saves/GuestProfile/theme" + echo "/mnt/SDCARD/Themes/GuestTheme/" > "$SDCARD/Saves/GuestProfile/theme/currentTheme" + + mock_date "1700000000" + + cd "$SDCARD/App/Guest_Mode" + run sh ./launch.sh + + assert_success + run jq -r .theme "$SDCARD/system.json" + assert_output "/mnt/SDCARD/Themes/GuestTheme/" +} diff --git a/test/shell-tests/packages/guest_mode/guest_mode_helper.bash b/test/shell-tests/packages/guest_mode/guest_mode_helper.bash new file mode 100644 index 0000000000..804b0ff0e8 --- /dev/null +++ b/test/shell-tests/packages/guest_mode/guest_mode_helper.bash @@ -0,0 +1,30 @@ +# Guest Mode test fixtures and setup + +setup_guest_mode() { + setup_sdcard + + mkdir -p "$SDCARD/Saves/CurrentProfile/theme" + mkdir -p "$SDCARD/Saves/CurrentProfile/lists" + mkdir -p "$SDCARD/Saves/CurrentProfile/saves" + mkdir -p "$SDCARD/Roms" + mkdir -p "$SDCARD/Themes/Silky by DiMo" + mkdir -p "$SDCARD/.tmp_update/config/startup" + + cat > "$SDCARD/system.json" << 'EOF' +{ + "theme": "/mnt/SDCARD/Themes/Silky by DiMo/" +} +EOF +} + +setup_guest_mode_scripts() { + local script_dir="$SDCARD/App/Guest_Mode" + local src_dir="$PROJECT/static/packages/App/Guest Mode/App/Guest_Mode" + mkdir -p "$script_dir/data" + + if [ -d "$src_dir" ]; then + cp "$src_dir/"*.sh "$script_dir/" 2>/dev/null || true + cp "$src_dir/data/"*.json "$script_dir/data/" 2>/dev/null || true + chmod +x "$script_dir/"*.sh 2>/dev/null || true + fi +} diff --git a/test/shell-tests/theme_switcher/extract_previews.bats b/test/shell-tests/theme_switcher/extract_previews.bats new file mode 100644 index 0000000000..fa2c4c6e45 --- /dev/null +++ b/test/shell-tests/theme_switcher/extract_previews.bats @@ -0,0 +1,53 @@ +#!/usr/bin/env bats + +load '../support/test_helper' +load '../support/mocks' +load 'theme_helper' + +setup() { + setup_sdcard + cleanup_test_data + mock_system_commands + mock_md5sum + + mkdir -p "$SDCARD/Themes" +} + +@test "themes_extract_previews.sh extracts new previews" { + create_theme_archive "NewTheme" "$SDCARD/Themes" + + run sh "$PROJECT/src/themeSwitcher/script/themes_extract_previews.sh" + + assert_success + assert_file_exists "$SDCARD/Themes/.previews/NewTheme/config.json" + assert_file_exists "$SDCARD/Themes/.previews/NewTheme/md5hash" + assert_file_exists "$SDCARD/Themes/.previews/NewTheme/source" + assert_file_contains "$SDCARD/Themes/.previews/NewTheme/source" "NewTheme.7z" +} + +@test "themes_extract_previews.sh skips if theme already installed" { + create_theme_archive "ExistingTheme" "$SDCARD/Themes" + + # Mark as installed + mkdir -p "$SDCARD/Themes/ExistingTheme" + echo "abc123def456" > "$SDCARD/Themes/ExistingTheme/md5hash" + + run sh "$PROJECT/src/themeSwitcher/script/themes_extract_previews.sh" + + assert_success + assert_file_not_exists "$SDCARD/Themes/ExistingTheme.7z" + assert_output --partial "[IGNORE] theme already installed: ExistingTheme" +} + +@test "themes_extract_previews.sh skips if preview already exists" { + create_theme_archive "PreviewedTheme" "$SDCARD/Themes" + + # Mark as previewed + mkdir -p "$SDCARD/Themes/.previews/PreviewedTheme" + echo "abc123def456" > "$SDCARD/Themes/.previews/PreviewedTheme/md5hash" + + run sh "$PROJECT/src/themeSwitcher/script/themes_extract_previews.sh" + + assert_success + assert_output --partial "[IGNORE] found preview for: PreviewedTheme" +} diff --git a/test/shell-tests/theme_switcher/extract_theme.bats b/test/shell-tests/theme_switcher/extract_theme.bats new file mode 100644 index 0000000000..564a560f09 --- /dev/null +++ b/test/shell-tests/theme_switcher/extract_theme.bats @@ -0,0 +1,72 @@ +#!/usr/bin/env bats + +load '../support/test_helper' +load '../support/mocks' +load 'theme_helper' + +setup() { + setup_sdcard + cleanup_test_data + mock_system_commands + mock_md5sum + + # Setup test directories + mkdir -p "$SDCARD/Themes" +} + +teardown() { + rm -f "/tmp/remove_theme_archive" +} + +@test "themes_extract_theme.sh extracts theme and generates md5" { + create_theme_archive "GenericTheme" "$TEST_DATA/theme_src" "icons/icon.png" + + # Script expects preview_dir as $1 and looks for $1/source + mkdir -p "$SDCARD/Themes/.previews/GenericTheme" + echo "$TEST_DATA/theme_src/GenericTheme.7z" > "$SDCARD/Themes/.previews/GenericTheme/source" + + run sh "$PROJECT/src/themeSwitcher/script/themes_extract_theme.sh" \ + "$SDCARD/Themes/.previews/GenericTheme" + + assert_success + assert_file_exists "$SDCARD/Themes/GenericTheme/md5hash" + assert_file_contains "$SDCARD/Themes/GenericTheme/md5hash" "abc123def456" + assert_file_exists "$SDCARD/Themes/GenericTheme/icons/icon.png" +} + +@test "themes_extract_theme.sh removes archive if flag set" { + create_theme_archive "GenericTheme" "$TEST_DATA/theme_src" + + touch "/tmp/remove_theme_archive" + mkdir -p "$SDCARD/Themes/.previews/GenericTheme" + echo "$TEST_DATA/theme_src/GenericTheme.7z" > "$SDCARD/Themes/.previews/GenericTheme/source" + + # We need the output dir to exist for the script to continue after extraction + mkdir -p "$SDCARD/Themes/GenericTheme" + + run sh "$PROJECT/src/themeSwitcher/script/themes_extract_theme.sh" \ + "$SDCARD/Themes/.previews/GenericTheme" + + assert_success + assert_file_not_exists "$TEST_DATA/theme_src/GenericTheme.7z" +} + +@test "themes_extract_theme.sh preserves existing icons if archive has no config.json at depth" { + # Create archive manually here because we need a specific non-standard structure (no config at root) + mkdir -p "$TEST_DATA/no_config/icons" + touch "$TEST_DATA/no_config/icons/new_icon.png" + cd "$TEST_DATA" + 7z a NoConfigTheme.7z no_config/ >/dev/null + + mkdir -p "$SDCARD/Themes/.previews/NoConfigTheme" + echo "$TEST_DATA/NoConfigTheme.7z" > "$SDCARD/Themes/.previews/NoConfigTheme/source" + + mkdir -p "$SDCARD/Themes/NoConfigTheme/icons" + touch "$SDCARD/Themes/NoConfigTheme/icons/old_icon.png" + + run sh "$PROJECT/src/themeSwitcher/script/themes_extract_theme.sh" \ + "$SDCARD/Themes/.previews/NoConfigTheme" + + assert_success + assert_file_exists "$SDCARD/Themes/NoConfigTheme/icons/old_icon.png" +} diff --git a/test/shell-tests/theme_switcher/theme_helper.bash b/test/shell-tests/theme_switcher/theme_helper.bash new file mode 100644 index 0000000000..a61bb2272a --- /dev/null +++ b/test/shell-tests/theme_switcher/theme_helper.bash @@ -0,0 +1,30 @@ +# Helper functions for Theme Switcher tests + +# Create a standard theme 7z archive +# Usage: create_theme_archive [output_dir] [extra_files...] +create_theme_archive() { + local theme_name="$1" + local output_dir="${2:-$TEST_DATA/theme_src}" + shift 2 + + local src_dir="$TEST_DATA/tmp_src_$theme_name" + mkdir -p "$src_dir/$theme_name" + touch "$src_dir/$theme_name/config.json" + touch "$src_dir/$theme_name/preview.png" + + # Create extra files if specified + # Format: relative/path/to/file + for file in "$@"; do + local dir=$(dirname "$src_dir/$theme_name/$file") + mkdir -p "$dir" + touch "$src_dir/$theme_name/$file" + done + + mkdir -p "$output_dir" + local cwd=$(pwd) + cd "$src_dir" + 7z a "$output_dir/${theme_name}.7z" "$theme_name/" >/dev/null + cd "$cwd" + + rm -rf "$src_dir" +} From d30ded417fa4ea3abfc11460805c56cd5193fc0a Mon Sep 17 00:00:00 2001 From: Neil Halelamien Date: Thu, 5 Feb 2026 20:45:12 -0800 Subject: [PATCH 4/4] Fix: Ensure cache directory exists before creating marker file --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 43110abb60..4c40ec4617 100644 --- a/Makefile +++ b/Makefile @@ -287,6 +287,7 @@ SHELL_TEST_DIR := $(TEST_SRC_DIR)/shell-tests $(CACHE)/.shell-test-image: $(SHELL_TEST_DIR)/Dockerfile docker build -t $(SHELL_TEST_IMAGE) $(SHELL_TEST_DIR) + @mkdir -p $(CACHE) $(createfile) $(CACHE)/.shell-test-image test-shell: $(CACHE)/.shell-test-image