tests/emulator, .github/workflows/emulator: add

This commit is contained in:
Alexander Sosedkin 2024-06-15 19:41:11 +02:00
parent d72ab2a167
commit 291970c4c4
9 changed files with 374 additions and 0 deletions

View file

@ -0,0 +1,43 @@
from common import screenshot, wait_for, APK, BOOTSTRAP_URL
import time
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)
screenshot(d, 'entered-url')
for i in range(2):
if 'text="OK"' not in d.ui.dump_hierarchy():
d.ui.press('back')
time.sleep(.5)
else:
break
screenshot(d, 'entered-url-back')
d.ui(text='OK').click()
screenshot(d, 'ok-clicked')
wait_for(d, 'Welcome to Nix-on-Droid!')
screenshot(d, 'bootstrap-begins')
wait_for(d, 'Do you want to set it up with flakes? (y/N)')
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=600)
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.2$')
screenshot(d, 'bootstrap-ends')
d('input text "echo smoke-test | base64"')
d.ui.press('enter')
wait_for(d, 'c21va2UtdGVzdAo=')
screenshot(d, 'success-bootstrap-channels')

View file

@ -0,0 +1,43 @@
from common import screenshot, wait_for, APK, BOOTSTRAP_URL
import time
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)
screenshot(d, 'entered-url')
for i in range(2):
if 'text="OK"' not in d.ui.dump_hierarchy():
d.ui.press('back')
time.sleep(.5)
else:
break
screenshot(d, 'entered-url-back')
d.ui(text='OK').click()
screenshot(d, 'ok-clicked')
wait_for(d, 'Welcome to Nix-on-Droid!')
screenshot(d, 'bootstrap-begins')
wait_for(d, 'Do you want to set it up with flakes? (y/N)')
d('input text y')
d.ui.press('enter')
wait_for(d, 'Setting up Nix-on-Droid with flakes...')
wait_for(d, 'Installing flake from default template...')
wait_for(d, 'Overriding input urls / arch in flake...')
wait_for(d, 'Installing first Nix-on-Droid generation...', timeout=600)
wait_for(d, 'Building activation package')
wait_for(d, 'Congratulations!', timeout=900)
wait_for(d, 'bash-5.2$')
screenshot(d, 'bootstrap-ends')
d('input text "echo smoke-test | base64"') # remove
d.ui.press('enter')
wait_for(d, 'c21va2UtdGVzdAo=')
screenshot(d, 'success-bootstrap-flakes')

36
tests/emulator/common.py Normal file
View file

@ -0,0 +1,36 @@
import os
import sys
import time
SERVER = 'https://nix-on-droid.unboiled.info'
# Just use F-Droid through fdroidctl later when F-Droid has x86_64 builds
APK = f'{SERVER}/com.termux.nix_188035-x86_64.apk'
BOOTSTRAP_URL = 'file:///data/local/tmp/n-o-d'
def screenshot(d, suffix=''):
os.makedirs('screenshots', exist_ok=True)
fname_base = f'screenshots/{time.time()}-{suffix}'
d.ui.screenshot(f'{fname_base}.png')
with open(f'{fname_base}.xml', 'w') as f:
f.write(d.ui.dump_hierarchy())
print(f'screenshotted: {fname_base}.{{png,xml}}')
def wait_for(d, on_screen_text, timeout=90, 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...')
sys.stdout.flush()
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')
screenshot(d, suffix='error')
if critical:
sys.exit(1)

View file

@ -0,0 +1,33 @@
from common import screenshot, wait_for
def run(d):
wait_for(d, 'bash-5.2$')
d('input text "nix-on-droid on-device-test"')
d.ui.press('enter')
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')
screenshot(d, 'tests-started')
d.ui.open_notification()
d.ui(text='Nix').right(resourceId='android:id/expand_button').click()
screenshot(d, 'notification_expanded')
d.ui(description='Acquire wakelock').click()
screenshot(d, 'wakelock_acquired')
d.ui(description='Release wakelock').wait()
screenshot(d, 'gotta-go-back')
d.ui.press('back')
screenshot(d, 'went-back')
if 'text="Allow"' in d.ui.dump_hierarchy():
d.ui(text='Allow').click()
elif 'text="ALLOW"' in d.ui.dump_hierarchy():
d.ui(text='ALLOW').click()
screenshot(d, 'tests-running')
wait_for(d, 'tests, 0 failures in', timeout=1800)
screenshot(d, 'tests-finished')

View file

@ -0,0 +1,54 @@
import bootstrap_channels
from common import screenshot, wait_for
def run(d):
bootstrap_channels.run(d)
d('input text "zip"')
d.ui.press('enter')
wait_for(d, 'bash: zip: command not found')
screenshot(d, 'no-zip')
# Smoke-test nix-shell + change config + apply config
d('input text "nix-shell -p gnumake -p gnused"')
d.ui.press('enter')
wait_for(d, '[nix-shell:~]$')
d('input text "make"')
d.ui.press('enter')
wait_for(d, 'No targets specified and no makefile found.')
screenshot(d, 'nix-shell-with-make-and-sed')
# Change config and apply it
d('input text \'sed -i "s|#zip|zip|g" .config/nixpkgs/nix-on-droid.nix\'')
d.ui.press('enter')
d('input text "exit"')
d.ui.press('enter')
screenshot(d, 'pre-switch')
d('input text "nix-on-droid switch"')
d.ui.press('enter')
screenshot(d, 'post-switch')
# Verify zip is there
d('input text "zip -v | head -n2"')
d.ui.press('enter')
wait_for(d, 'This is Zip')
screenshot(d, 'zip-appears')
# Re-login and make sure login is still operational
d('input text "exit"')
d.ui.press('enter')
nod = d.app('com.termux.nix')
nod.launch()
screenshot(d, 're-login')
wait_for(d, 'Installing new login-inner...')
wait_for(d, 'bash-5.2$')
screenshot(d, 're-login-done')
# And verify zip is still there
d('input text "zip -v | head -n2"')
d.ui.press('enter')
wait_for(d, 'This is Zip')
screenshot(d, 'zip-is-still-there')

View file

@ -0,0 +1,57 @@
import bootstrap_channels
import subprocess
import sys
from common import screenshot, wait_for
STD = '/data/data/com.termux.nix/files/home/.cache/nix-on-droid-self-test'
def run(d):
bootstrap_channels.run(d)
# re-login for variety. the other on-device-test (uiautomator one) does not
d('input text "exit"')
screenshot(d, 'pre-relogin')
d.ui.press('enter')
nod = d.app('com.termux.nix')
nod.launch()
d.ui.press('enter')
screenshot(d, 'post-relogin')
wait_for(d, 'bash-5.2$')
# run tests in a way that'd display progress in CI
user = d.su('stat -c %U /data/data/com.termux.nix').output.strip()
# WARNING: assumes `su 0` style `su` that doesn't support -c from now on
print(f'{user=}')
sys.stdout.flush()
sys.stderr.flush()
for cmd in [
'id',
f'mkdir -p {STD}',
f'touch {STD}/confirmation-granted',
'/data/data/com.termux.nix/files/usr/bin/login echo test',
'/data/data/com.termux.nix/files/usr/bin/login id',
('cd /data/data/com.termux.nix/files/home; '
'pwd; '
'id; '
'/data/data/com.termux.nix/files/usr/bin/login '
' nix-on-droid on-device-test')
]:
print(f'running {cmd} as {user} with capture:')
p = subprocess.Popen(['adb', 'shell', 'su', '0', 'su', user,
'sh', '-c', f"'{cmd}'"],
encoding='utf-8',
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
out = ''
while p.poll() is None:
line = p.stdout.readline()
out += line
sys.stdout.write('> ' + line)
sys.stdout.flush()
print(f'returncode: {p.returncode}')
# guess what, it can swallow the exit code!
assert 'tests, 0 failures in' in out # of the last command

View file

@ -0,0 +1,7 @@
import bootstrap_channels
import on_device_tests
def run(d):
bootstrap_channels.run(d)
on_device_tests.run(d)