From 3b174e64bb19d034cdd5f7326ebd2dd360e25497 Mon Sep 17 00:00:00 2001 From: Alexander Sosedkin Date: Thu, 13 Jun 2024 20:26:21 +0200 Subject: [PATCH] WIP: emulator-based tests --- .github/workflows/emulator.yml | 76 +++++++++++++++++++++++++++++ .gitignore | 1 + tests/emulator/bootstrap-channel.py | 29 +++++++++++ tests/emulator/common.py | 23 +++++++++ tests/emulator/on-device-tests.py | 23 +++++++++ 5 files changed, 152 insertions(+) create mode 100644 .github/workflows/emulator.yml create mode 100644 tests/emulator/bootstrap-channel.py create mode 100644 tests/emulator/common.py create mode 100644 tests/emulator/on-device-tests.py diff --git a/.github/workflows/emulator.yml b/.github/workflows/emulator.yml new file mode 100644 index 00000000..a01f1032 --- /dev/null +++ b/.github/workflows/emulator.yml @@ -0,0 +1,76 @@ +name: Test nix-on-droid in an emulator +on: + pull_request: + push: + schedule: + - cron: 0 0 * * 1 + +jobs: + fakedroid-channel: + runs-on: ubuntu-latest + + strategy: + matrix: + api-level: [29] + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Nix / enable KVM + uses: DeterminateSystems/nix-installer-action@main + + - name: Setup cachix + uses: cachix/cachix-action@v14 + with: + name: nix-on-droid + signingKey: "${{ secrets.CACHIX_SIGNING_KEY }}" + + - name: Initialize fakedroid for channel setup + run: nix run --impure .#fakedroid -- echo INIT + + - name: Configure AVD cache + uses: actions/cache@v4 + id: avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-${{ matrix.api-level }} + + - name: Create AVD and generate snapshot for caching + if: steps.avd-cache.outputs.cache-hit != 'true' + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ matrix.api-level }} + force-avd-creation: false + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: false + script: echo "Generated AVD snapshot for caching." + + - name: Run tests + run: | + nix run --impure .#fakedroid -- mkdir -p .cache/nix-on-droid-self-test + nix run --impure .#fakedroid -- touch .cache/nix-on-droid-self-test/confirmation-granted + nix run --impure .#fakedroid -- nix-on-droid on-device-test + + - name: Run on-device-tests with api-level=${{ matrix.api-level }} + uses: reactivecircus/android-emulator-runner@v2 + with: + target: default + api-level: ${{ matrix.api-level }} + arch: x86_64 + script: | + pushd tests/emulator + nix run . -- run bootstrap-channel.py + nix run . -- run on-device-tests.py + popd + +# TODO: push to cachix from device +# - name: Push to cachix +# if: always() && github.event_name != 'pull_request' +# run: ... + +# TODO: parametrize: channel vs flakes +# TODO: externally-driven test +# TODO: more API levels diff --git a/.gitignore b/.gitignore index dea04aa6..d8dfe4c8 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /source.tar.gz result result-* +**/__pycache__ diff --git a/tests/emulator/bootstrap-channel.py b/tests/emulator/bootstrap-channel.py new file mode 100644 index 00000000..4a343958 --- /dev/null +++ b/tests/emulator/bootstrap-channel.py @@ -0,0 +1,29 @@ +from common import wait_for, APK, BOOTSTRAP_URL + + +def run(d): + nod = d.app('com.termux.nix', url=APK) + nod.permissions.allow_notifications() + nod.launch() + + wait_for(d, 'Bootstrap zipball location') + d.ui(className='android.widget.EditText').set_text(BOOTSTRAP_URL) + d.ui(text='OK').click() + + wait_for(d, 'Welcome to Nix-on-Droid!') + wait_for(d, 'Do you want to set it up with flakes? (y/N)') + d('input text N') # remove + d.ui.press('enter') + wait_for(d, 'Setting up Nix-on-Droid with channels...') + + wait_for(d, 'Installing and updating nix-channels...') + wait_for(d, 'unpacking channels...') + wait_for(d, 'Installing first Nix-on-Droid generation...', timeout=180) + wait_for(d, 'Copying default Nix-on-Droid config...', timeout=180) + wait_for(d, 'Congratulations!') + wait_for(d, 'See config file for further information!') + wait_for(d, 'bash-5.25$') + + d('input text "echo smoke-test | base64"') # remove + d.ui.press('enter') + wait_for(d, 'c21va2UtdGVzdAo=') diff --git a/tests/emulator/common.py b/tests/emulator/common.py new file mode 100644 index 00000000..749ae972 --- /dev/null +++ b/tests/emulator/common.py @@ -0,0 +1,23 @@ +import sys +import time + +SERVER = 'https://nix-on-droid.unboiled.info' +APK = f'{SERVER}/com.termux.nix_188035-x86_64.apk' +BOOTSTRAP_URL = f'{SERVER}/testing' + + +def wait_for(d, on_screen_text, timeout=30, critical=True): + start = time.time() + last_displayed_time = None + while (elapsed := time.time() - start) < timeout: + display_time = int(timeout - elapsed) + if display_time != last_displayed_time: + print(f'waiting for `{on_screen_text}`: {display_time}s...') + last_displayed_time = display_time + if on_screen_text in d.ui.dump_hierarchy(): + print(f'found: {on_screen_text} after {elapsed:.1f}s') + return + time.sleep(.75) + print(f'NOT FOUND: {on_screen_text} after {timeout}s') + if critical: + sys.exit(1) diff --git a/tests/emulator/on-device-tests.py b/tests/emulator/on-device-tests.py new file mode 100644 index 00000000..4d6efbd6 --- /dev/null +++ b/tests/emulator/on-device-tests.py @@ -0,0 +1,23 @@ +from common import wait_for + + +def run(d): + #wait_for(d, 'bash-5.2$') + + #d('input text "nix-on-droid on-device-test"') + #wait_for(d, 'These semi-automated tests are destructive', timeout=180) + #wait_for(d, 'Proceeding will wreck your installation.') + #wait_for(d, 'Do you still wish to proceed?') + #d('input text "I do"') + #d.ui.press('enter') + + d.ui.open_notification() + d.ui(text='Nix').right(resourceId='android:id/expand_button').click() + d.ui(text='Acquire wakelock').click() + d.ui(text='Release wakelock').wait() + d.ui.press('back') + + if 'Allow' in d.ui.dump_hierarchy(): + d.ui.click('Allow') + + wait_for(d, 'tests, 0 failures in', timeout=1800)