Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,5 +78,8 @@ jobs:
- name: Run bats (changelog.just)
run: bats tests/test_changelog.bats

- name: Run bats (update.just)
run: bats tests/test_update_just.bats

- name: Run bats (ublue-fastfetch)
run: bats tests/test_ublue_fastfetch.bats
4 changes: 4 additions & 0 deletions docs/skills/qa.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ Testing-lab uses KubeVirt VMs only. The following bug classes are **invisible**

See each repo's AGENTS.md for repo-specific test commands. Common entry points:

### Testing `update.just` on GitHub Actions

`tests/test_update_just.bats` must avoid `bwrap` bind-mount tricks for `/etc` and `/var/home` on GitHub-hosted runners. Use PATH mocks for `systemctl`, `sudo`, `bootc`, `flatpak`, `gum`, and `grep`, and keep the assertions focused on the bootc, flatpak, and toggle-updates branches that are reliable in CI. Parameterize the extracted brew path inside the test harness so a host-owned `/var/home/linuxbrew/.linuxbrew/bin/brew` cannot make the brew-absent branch nondeterministic on local machines.

```bash
# common
just check # lint + validate
Expand Down
202 changes: 202 additions & 0 deletions tests/test_update_just.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
#!/usr/bin/env bats
# Tests for update.just and toggle-updates recipes

UPDATE_JUST="${BATS_TEST_DIRNAME}/../system_files/shared/usr/share/ublue-os/just/update.just"
WORKDIR=""
MOCKDIR=""
COMMAND_LOG=""
UPDATE_SCRIPT=""
TOGGLE_SCRIPT=""

_extract_script() {
local recipe="$1" out_file="$2"
awk -v recipe="$recipe" '
$0 ~ ("^" recipe "([[:space:]].*)?:$") { in_recipe=1; next }
in_recipe && /^ #!\/usr\/bin\/bash/ { found=1; next }
found && /^[^[:space:]]/ { exit }
found { sub(/^ /, ""); print }
' "${UPDATE_JUST}" > "${out_file}"
}

_write_mock() {
local name="$1"
cat > "${MOCKDIR}/${name}"
chmod +x "${MOCKDIR}/${name}"
}

setup() {
WORKDIR="${BATS_TEST_DIRNAME}/.test-update-just-${BATS_TEST_NUMBER}-$$"
rm -rf "${WORKDIR}"
mkdir -p "${WORKDIR}"

MOCKDIR="${WORKDIR}/bin"
COMMAND_LOG="${WORKDIR}/commands.log"
mkdir -p "${MOCKDIR}"
: > "${COMMAND_LOG}"

UPDATE_SCRIPT="${WORKDIR}/update.sh"
TOGGLE_SCRIPT="${WORKDIR}/toggle-updates.sh"
_extract_script "update" "${UPDATE_SCRIPT}"
_extract_script "toggle-updates" "${TOGGLE_SCRIPT}"
python - <<'PY2' "${UPDATE_SCRIPT}"
from pathlib import Path
import sys
p = Path(sys.argv[1])
text = p.read_text()
text = text.replace("/var/home/linuxbrew/.linuxbrew/bin/brew", "${MOCK_BREW_BIN:-/var/home/linuxbrew/.linuxbrew/bin/brew}")
p.write_text(text)
PY2
chmod +x "${UPDATE_SCRIPT}" "${TOGGLE_SCRIPT}"

_write_mock "systemctl" <<'MOCK'
#!/bin/bash
echo "systemctl $*" >> "${COMMAND_LOG}"
case "$1" in
cat)
[[ "$3" == "uupd.timer" && "${MOCK_HAS_UUPD_TIMER:-0}" == "1" ]] && exit 0 || exit 1
;;
is-active)
[[ "${3:-$2}" == "${MOCK_ACTIVE_SERVICE:-}" ]] && exit 0 || exit 1
;;
is-enabled)
[[ "${3:-$2}" == "${MOCK_ENABLED_TIMER:-}" ]] && exit 0 || exit 1
;;
enable|disable)
exit 0
;;
*)
exit 0
;;
esac
MOCK

_write_mock "sudo" <<'MOCK'
#!/bin/bash
echo "sudo $*" >> "${COMMAND_LOG}"
exec "$@"
MOCK

for cmd in bootc rpm-ostree; do
cat > "${MOCKDIR}/${cmd}" <<MOCK
#!/bin/bash
echo "${cmd} \$*" >> "\${COMMAND_LOG}"
MOCK
chmod +x "${MOCKDIR}/${cmd}"
done

_write_mock "flatpak" <<'MOCK'
#!/bin/bash
echo "flatpak $*" >> "${COMMAND_LOG}"
if [[ "$1" == "remotes" ]]; then
printf '%s\n' "${MOCK_FLATPAK_REMOTES:-}"
fi
MOCK

_write_mock "gum" <<'MOCK'
#!/bin/bash
if [[ "$1" == "choose" ]]; then
printf '%s\n' "${MOCK_GUM_CHOICE:-}"
fi
MOCK

_write_mock "grep" <<'MOCK'
#!/bin/bash
for arg in "$@"; do
if [[ "$arg" == "/etc/rpm-ostreed.conf" ]]; then
[[ "${MOCK_LOCK_LAYERING_FALSE:-0}" == "1" ]] && exit 0 || exit 1
fi
done
exec /usr/bin/grep "$@"
MOCK

chmod -R a+rwX "${WORKDIR}"
}

teardown() {
rm -rf "${WORKDIR}"
}

_run() {
run env \
PATH="${MOCKDIR}:${PATH}" \
COMMAND_LOG="${COMMAND_LOG}" \
MOCK_HAS_UUPD_TIMER="${MOCK_HAS_UUPD_TIMER:-0}" \
MOCK_ACTIVE_SERVICE="${MOCK_ACTIVE_SERVICE:-}" \
MOCK_ENABLED_TIMER="${MOCK_ENABLED_TIMER:-}" \
MOCK_FLATPAK_REMOTES="${MOCK_FLATPAK_REMOTES:-}" \
MOCK_GUM_CHOICE="${MOCK_GUM_CHOICE:-}" \
MOCK_LOCK_LAYERING_FALSE="${MOCK_LOCK_LAYERING_FALSE:-0}" \
MOCK_BREW_BIN="${MOCK_BREW_BIN:-${WORKDIR}/missing-brew}" \
bash "$@"
}

@test "update: uses uupd.service when uupd.timer is present" {
MOCK_HAS_UUPD_TIMER=1 _run "${UPDATE_SCRIPT}"
[ "${status}" -eq 0 ]
grep -qF "systemctl is-active --quiet uupd.service" "${COMMAND_LOG}"
}

@test "update: falls back to rpm-ostreed-automatic.service when uupd.timer absent" {
_run "${UPDATE_SCRIPT}"
[ "${status}" -eq 0 ]
grep -qF "systemctl is-active --quiet rpm-ostreed-automatic.service" "${COMMAND_LOG}"
}

@test "update: exits early when update service is active" {
MOCK_HAS_UUPD_TIMER=1 MOCK_ACTIVE_SERVICE="uupd.service" _run "${UPDATE_SCRIPT}"
[ "${status}" -eq 1 ]
[[ "${output}" == *"automatic updates are currently running"* ]]
! grep -qF "bootc upgrade" "${COMMAND_LOG}"
}

@test "update: runs bootc upgrade when LockLayering=false is not detected" {
_run "${UPDATE_SCRIPT}"
[ "${status}" -eq 0 ]
grep -qF "sudo bootc upgrade" "${COMMAND_LOG}"
! grep -qF "rpm-ostree upgrade" "${COMMAND_LOG}"
}

@test "update: updates system flatpaks when system remote exists" {
MOCK_FLATPAK_REMOTES=$'system' _run "${UPDATE_SCRIPT}"
[ "${status}" -eq 0 ]
grep -qF "flatpak update -y" "${COMMAND_LOG}"
! grep -qF "flatpak update --user -y" "${COMMAND_LOG}"
}

@test "update: updates user flatpaks when user remote exists" {
MOCK_FLATPAK_REMOTES=$'user' _run "${UPDATE_SCRIPT}"
[ "${status}" -eq 0 ]
grep -qF "flatpak update --user -y" "${COMMAND_LOG}"
! grep -qF "flatpak update -y" "${COMMAND_LOG}"
}

@test "update: skips flatpak when no remotes exist" {
_run "${UPDATE_SCRIPT}"
[ "${status}" -eq 0 ]
! grep -qF "flatpak update" "${COMMAND_LOG}"
}

@test "update: skips brew when binary is absent" {
_run "${UPDATE_SCRIPT}"
[ "${status}" -eq 0 ]
! grep -qF "brew upgrade" "${COMMAND_LOG}"
}

@test "toggle-updates: enables uupd.timer when present and Enable chosen" {
MOCK_HAS_UUPD_TIMER=1 MOCK_GUM_CHOICE="Enable" _run "${TOGGLE_SCRIPT}"
[ "${status}" -eq 0 ]
grep -qF "systemctl enable uupd.timer" "${COMMAND_LOG}"
}

@test "toggle-updates: disables rpm-ostreed timer when uupd absent and Disable chosen" {
MOCK_GUM_CHOICE="Disable" _run "${TOGGLE_SCRIPT}"
[ "${status}" -eq 0 ]
grep -qF "systemctl disable rpm-ostreed-automatic.timer" "${COMMAND_LOG}"
}

@test "toggle-updates: exits cleanly on Cancel" {
MOCK_GUM_CHOICE="Cancel" _run "${TOGGLE_SCRIPT}"
[ "${status}" -eq 0 ]
! grep -qF "systemctl enable" "${COMMAND_LOG}"
! grep -qF "systemctl disable" "${COMMAND_LOG}"
}
Loading