first commit
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
# Every tracked file (not symlink) that starts with a bash or sh shebang (\043 is "#", which
|
||||
# make would otherwise read as the start of a comment)
|
||||
SCRIPTS := $(shell git ls-files -s | awk '$$1 != 120000 { print $$4 }' | xargs awk 'FNR == 1 && /^#!.*[\/ ](ba)?sh/ { print FILENAME }')
|
||||
|
||||
.PHONY: check lint test
|
||||
|
||||
check: lint test
|
||||
|
||||
lint:
|
||||
shellcheck --severity=warning $(SCRIPTS)
|
||||
|
||||
test:
|
||||
bats tests
|
||||
@@ -0,0 +1,91 @@
|
||||
# coredesktop-dev
|
||||
|
||||
Tools for working on [CoreDesktop](https://gitea.federated.computer/Federated_Computer/coredesktop)
|
||||
itself. Only contributors need these, so they're a CoreDesktop sibling project
|
||||
rather than part of CoreDesktop.
|
||||
|
||||
## Install
|
||||
|
||||
```
|
||||
sibling add <url of this repo>
|
||||
```
|
||||
|
||||
It asks where your working copy of CoreDesktop is (default
|
||||
`~/Projects/coredesktop`), and offers to clone it there. CoreDesktop's `update`
|
||||
keeps this project current from then on. To change the working copy, run
|
||||
`sibling add` again.
|
||||
|
||||
It installs shellcheck, bats, tea (Gitea's CLI), QEMU and xorriso.
|
||||
|
||||
## Why: the two copies of CoreDesktop
|
||||
|
||||
A developer's machine has two copies of CoreDesktop:
|
||||
|
||||
- **Deployed**, in `~/.local/share/coredesktop`: what the machine runs, and
|
||||
what `update` pulls (fast-forward only). Never edit it: a changed tracked
|
||||
file stops `update` from pulling.
|
||||
- **Working**, e.g. `~/Projects/coredesktop`: where you make changes. Nothing
|
||||
runs from here unless you run it.
|
||||
|
||||
These commands let you test the working copy without touching the deployed one.
|
||||
|
||||
## Commands
|
||||
|
||||
| Command | |
|
||||
|---------|---|
|
||||
| `coredesktop-dev doctor` | Check the tools, the working copy, and that the deployed copy can still update |
|
||||
| `coredesktop-dev check [--fast]` | `make lint` and `make test` in the working copy, plus shellcheck on scripts not committed yet. `--fast`: shellcheck only |
|
||||
| `coredesktop-dev try [--keep] [cmd...]` | Run the working copy's code as if it were installed, in a throwaway home with its own D-Bus session and no display, e.g. `coredesktop-dev try configure --list`. sudo, yay, pacman and systemctl still change this machine |
|
||||
| `coredesktop-dev deployed [status\|restore]` | Show the deployed copy's state, or undo local changes to it (after asking) |
|
||||
| `coredesktop-dev hooks [status\|install\|remove]` | A pre-push hook in the working copy that runs `check` first. `git push --no-verify` skips it |
|
||||
| `coredesktop-dev smoketest ...` | Test install and update on virtual machines (below) |
|
||||
|
||||
Each takes `--help`. In a Flatpak (an editor's built-in terminal, say),
|
||||
coredesktop-dev runs itself on the host with `flatpak-spawn --host`.
|
||||
|
||||
## Smoketest
|
||||
|
||||
Runs CoreDesktop's end-to-end scenarios, in `smoketest/` in the working copy,
|
||||
on throwaway QEMU virtual machines: a fresh install, updates of all kinds,
|
||||
failing migrations and hooks, low disk space, a month-old machine, and more.
|
||||
See `smoketest/README.md` in CoreDesktop for what's tested and how to add a
|
||||
scenario.
|
||||
|
||||
```
|
||||
coredesktop-dev smoketest list # the scenarios
|
||||
coredesktop-dev smoketest run # the quick ones (tier pr), against HEAD
|
||||
coredesktop-dev smoketest run --tier all # everything; hours
|
||||
coredesktop-dev smoketest run 40 43 # just these
|
||||
coredesktop-dev smoketest run --keep 40 # on failure, leave the VM up to look around
|
||||
coredesktop-dev smoketest shell installed # SSH into a throwaway VM
|
||||
```
|
||||
|
||||
What it needs: KVM (virtualization turned on in the firmware), about 8 GB of
|
||||
RAM and 4 CPUs per VM (`SMOKETEST_MEMORY`, `SMOKETEST_CPUS`), and disk space in
|
||||
`~/.cache/coredesktop-dev/smoketest`: VM disks grow as large as a real install,
|
||||
about 50 GB, and each run's VM disk is deleted when it's done.
|
||||
|
||||
The first run builds two images, which takes a while:
|
||||
|
||||
- **base**: Arch's official cloud image, with GNOME added the way archinstall's
|
||||
desktop profile installs it. The user `tester` (password `tester`) logs in to
|
||||
GNOME automatically and has passwordless sudo.
|
||||
- **installed**: base after CoreDesktop's `install.sh`, at the commit your
|
||||
branch started from. Scenarios on this image first `update` it to the commit
|
||||
under test. Rebuild it now and then (`smoketest images installed`) so that
|
||||
update has less to catch up on.
|
||||
|
||||
Downloaded Arch packages are kept in the cache folder and shared with every
|
||||
VM, so they download once. Flatpaks aren't cached.
|
||||
|
||||
To watch a VM's screen, set `SMOKETEST_VNC=1` and connect a VNC viewer to
|
||||
`127.0.0.1:5901`.
|
||||
|
||||
## Developing this project
|
||||
|
||||
```
|
||||
make check # shellcheck + bats
|
||||
```
|
||||
|
||||
The tests use fake commands, like CoreDesktop's: nothing touches the machine,
|
||||
and the smoketest tests use a fake QEMU.
|
||||
Executable
+46
@@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Purpose : Tools for working on CoreDesktop itself (see README.md)
|
||||
|
||||
SELF="$(readlink -f "${BASH_SOURCE[0]}")"
|
||||
LIB="$(dirname "$SELF")/../lib"
|
||||
|
||||
source "$LIB/common.sh"
|
||||
|
||||
usage() {
|
||||
cat << EOF
|
||||
Usage: coredesktop-dev <command> [--help]
|
||||
|
||||
doctor Check this machine is set up to work on CoreDesktop
|
||||
check Run the linters and tests on the working copy
|
||||
try Run the working copy's code as if it were installed, in a throwaway home
|
||||
deployed Show, or restore, the installed copy of CoreDesktop
|
||||
hooks Install or remove the pre-push hook that runs check
|
||||
smoketest Test install and update on a virtual machine
|
||||
EOF
|
||||
}
|
||||
|
||||
command="${1:-}"
|
||||
shift
|
||||
|
||||
case "$command" in
|
||||
doctor | check | try | deployed | hooks | smoketest) ;;
|
||||
"" | -h | --help | help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# In a Flatpak (an editor's terminal, say), pacman, systemctl, qemu and the
|
||||
# host's tools aren't there: run on the host instead
|
||||
if in_flatpak && [[ "${1:-}" != -h && "${1:-}" != --help ]]; then
|
||||
echo "Running in a Flatpak; running coredesktop-dev $command on the host (flatpak-spawn --host)." >&2
|
||||
exec flatpak-spawn --host --env=COREDESKTOP_DEV_VIA_FLATPAK=1 "$SELF" "$command" "$@"
|
||||
fi
|
||||
|
||||
source "$LIB/$command.sh"
|
||||
"cmd_$command" "$@"
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
# Installed by coredesktop-dev; remove with: coredesktop-dev hooks remove
|
||||
#
|
||||
# Runs coredesktop-dev check before git push, and stops the push if it fails.
|
||||
|
||||
CHECK="$HOME/.local/bin/coredesktop-dev"
|
||||
|
||||
if [ ! -x "$CHECK" ]; then
|
||||
echo "pre-push: coredesktop-dev isn't installed, so the checks didn't run."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! "$CHECK" check; then
|
||||
echo
|
||||
echo "pre-push: the checks failed, so nothing was pushed."
|
||||
echo "To push anyway: git push --no-verify"
|
||||
exit 1
|
||||
fi
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Purpose : Install coredesktop-dev (run by CoreDesktop's `sibling add`). Safe to
|
||||
# re-run: that's how to change the working copy or repair the install.
|
||||
|
||||
set -e
|
||||
|
||||
SELF="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
COREDESKTOP="$HOME/.local/share/coredesktop"
|
||||
|
||||
if [ ! -f "$COREDESKTOP/utils.sh" ]; then
|
||||
echo "CoreDesktop isn't installed in $COREDESKTOP; install it first."
|
||||
exit 1
|
||||
fi
|
||||
source "$COREDESKTOP/utils.sh"
|
||||
source "$SELF/lib/common.sh"
|
||||
|
||||
load_config
|
||||
default="${WORKING_COPY:-$HOME/Projects/coredesktop}"
|
||||
|
||||
# Ask until the answer is a CoreDesktop clone, or a folder to clone one into
|
||||
while true; do
|
||||
if ! read -rp "Your working copy of CoreDesktop, where you make changes [$default]: " answer; then
|
||||
echo
|
||||
die "No answer; run this again in a terminal: bash $SELF/install.sh"
|
||||
fi
|
||||
dir="${answer:-$default}"
|
||||
dir="${dir/#\~/$HOME}"
|
||||
dir="$(realpath -m "$dir")"
|
||||
|
||||
if is_deployed_copy "$dir"; then
|
||||
echo "$dir is the deployed copy, which update keeps current. Changing it stops"
|
||||
echo "update from pulling. Choose another folder, e.g. ~/Projects/coredesktop."
|
||||
continue
|
||||
fi
|
||||
|
||||
if is_coredesktop_clone "$dir"; then
|
||||
origin="$(git -C "$dir" remote get-url origin 2> /dev/null || true)"
|
||||
if [[ "$origin" != *coredesktop* ]]; then
|
||||
echo "Note: $dir's origin ($origin) doesn't look like CoreDesktop's repo."
|
||||
fi
|
||||
break
|
||||
fi
|
||||
|
||||
if [ -e "$dir" ] && [ -n "$(ls -A "$dir" 2> /dev/null)" ]; then
|
||||
echo "$dir isn't a clone of CoreDesktop, and isn't empty. Choose another folder."
|
||||
continue
|
||||
fi
|
||||
|
||||
url="$(git -C "$COREDESKTOP" remote get-url origin)"
|
||||
read -rp "Clone CoreDesktop from $url into $dir? [Y/n] " answer || answer=n
|
||||
if [[ "$answer" != [nN]* ]]; then
|
||||
git clone "$url" "$dir"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
save_config "$dir"
|
||||
WORKING_COPY="$dir"
|
||||
echo "Working copy: $dir (saved in $CONFIG)"
|
||||
|
||||
bash "$SELF/update.sh"
|
||||
|
||||
source "$SELF/lib/hooks.sh"
|
||||
if ! hook_is_ours "$(hook_path)"; then
|
||||
echo
|
||||
echo "A pre-push hook can run coredesktop-dev check before each git push from"
|
||||
echo "$dir, and stop the push if it fails."
|
||||
if confirm "Install it?"; then
|
||||
"$HOME/.local/bin/coredesktop-dev" hooks install || true
|
||||
fi
|
||||
fi
|
||||
|
||||
echo
|
||||
"$HOME/.local/bin/coredesktop-dev" doctor || true
|
||||
echo
|
||||
echo "coredesktop-dev installed. Run coredesktop-dev --help to see what it does."
|
||||
@@ -0,0 +1,63 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Purpose : coredesktop-dev check: lint and test the working copy, including
|
||||
# scripts not committed yet, which CoreDesktop's make lint skips
|
||||
|
||||
check_usage() {
|
||||
cat << EOF
|
||||
Usage: coredesktop-dev check [--fast]
|
||||
|
||||
Runs make lint and make test in the working copy, then shellcheck on the
|
||||
scripts git doesn't track yet. --fast runs only the shellcheck steps.
|
||||
EOF
|
||||
}
|
||||
|
||||
# Print each file named on stdin (NUL-separated) that starts with a bash or sh
|
||||
# shebang, as the Makefile picks them, or is a .sh file
|
||||
shell_scripts() {
|
||||
local file
|
||||
while IFS= read -r -d '' file; do
|
||||
[ -f "$file" ] && [ ! -L "$file" ] || continue
|
||||
if [[ "$file" == *.sh ]] || head -n 1 "$file" | grep -qE '^#!.*[/ ](ba)?sh'; then
|
||||
echo "$file"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
cmd_check() {
|
||||
local fast=0
|
||||
case "${1:-}" in
|
||||
-h | --help) check_usage; return 0 ;;
|
||||
--fast) fast=1 ;;
|
||||
"") ;;
|
||||
*) check_usage >&2; return 1 ;;
|
||||
esac
|
||||
require_working_copy
|
||||
cd "$WORKING_COPY" || return 1
|
||||
|
||||
local status=0 tracked untracked lint="ok" bats="skipped (--fast)"
|
||||
mapfile -t tracked < <(git ls-files -z | shell_scripts)
|
||||
mapfile -t untracked < <(git ls-files -z --others --exclude-standard | shell_scripts)
|
||||
|
||||
echo "== make lint"
|
||||
make --no-print-directory lint || { lint="FAILED"; status=1; }
|
||||
|
||||
if (( ${#untracked[@]} > 0 )); then
|
||||
echo "== shellcheck on untracked scripts: ${untracked[*]}"
|
||||
shellcheck --severity=warning "${untracked[@]}" || { lint="FAILED"; status=1; }
|
||||
fi
|
||||
|
||||
if (( fast == 0 )); then
|
||||
echo "== make test"
|
||||
if make --no-print-directory test; then
|
||||
bats="ok ($(bats --count tests 2> /dev/null) tests)"
|
||||
else
|
||||
bats="FAILED"
|
||||
status=1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "shellcheck: $lint (${#tracked[@]} files, ${#untracked[@]} untracked) bats: $bats"
|
||||
return $status
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Purpose : Paths, settings and helpers shared by coredesktop-dev's scripts
|
||||
|
||||
# The deployed copy: the machine's running CoreDesktop, which update pulls
|
||||
COREDESKTOP="$HOME/.local/share/coredesktop"
|
||||
|
||||
CONFIG_DIR="$HOME/.config/coredesktop-dev"
|
||||
CONFIG="$CONFIG_DIR/config"
|
||||
|
||||
# First line after the shebang of the pre-push hook this project installs, so it
|
||||
# never overwrites or removes a hook someone else wrote
|
||||
HOOK_MARKER="# Installed by coredesktop-dev; remove with: coredesktop-dev hooks remove"
|
||||
|
||||
die() {
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
confirm() {
|
||||
local answer
|
||||
read -rp "$1 [y/N] " answer
|
||||
[[ "$answer" == [yY]* ]]
|
||||
}
|
||||
|
||||
# Sets WORKING_COPY from the settings file (empty if there is none)
|
||||
load_config() {
|
||||
WORKING_COPY=""
|
||||
# shellcheck source=/dev/null
|
||||
[ -f "$CONFIG" ] && source "$CONFIG"
|
||||
return 0
|
||||
}
|
||||
|
||||
save_config() {
|
||||
mkdir -p "$CONFIG_DIR"
|
||||
printf 'WORKING_COPY=%q\n' "$1" > "$CONFIG"
|
||||
}
|
||||
|
||||
# True if DIR is the deployed copy, however it's spelled
|
||||
is_deployed_copy() {
|
||||
[ -e "$1" ] && [ "$(realpath "$1")" = "$(realpath "$COREDESKTOP" 2> /dev/null)" ]
|
||||
}
|
||||
|
||||
# True if DIR is the top of a git clone of CoreDesktop
|
||||
is_coredesktop_clone() {
|
||||
[ -d "$1" ] \
|
||||
&& [ "$(git -C "$1" rev-parse --show-toplevel 2> /dev/null)" = "$(realpath "$1")" ] \
|
||||
&& [ -f "$1/update" ] && [ -d "$1/updates.d" ]
|
||||
}
|
||||
|
||||
# Load the settings and stop unless they point at a CoreDesktop clone
|
||||
require_working_copy() {
|
||||
load_config
|
||||
[ -n "$WORKING_COPY" ] \
|
||||
|| die "No working copy is set. Run $(sibling_dir)/install.sh, or: sibling add <coredesktop-dev url>"
|
||||
is_coredesktop_clone "$WORKING_COPY" \
|
||||
|| die "The working copy, $WORKING_COPY, isn't a clone of CoreDesktop (set in $CONFIG)."
|
||||
}
|
||||
|
||||
in_flatpak() {
|
||||
[ -f /.flatpak-info ] || [ -n "${FLATPAK_ID:-}" ]
|
||||
}
|
||||
|
||||
# This project's folder
|
||||
sibling_dir() {
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Purpose : coredesktop-dev deployed: show the deployed copy of CoreDesktop
|
||||
# (~/.local/share/coredesktop), or put back its tracked files so
|
||||
# update can pull again
|
||||
|
||||
deployed_usage() {
|
||||
cat << EOF
|
||||
Usage: coredesktop-dev deployed [status|restore]
|
||||
|
||||
status Branch, commit, how far behind upstream, and local changes (default)
|
||||
restore Undo local changes to tracked files, after asking. Untracked files
|
||||
are listed and left alone.
|
||||
EOF
|
||||
}
|
||||
|
||||
cmd_deployed() {
|
||||
case "${1:-status}" in
|
||||
-h | --help) deployed_usage; return 0 ;;
|
||||
status | restore) ;;
|
||||
*) deployed_usage >&2; return 1 ;;
|
||||
esac
|
||||
git -C "$COREDESKTOP" rev-parse --git-dir &> /dev/null \
|
||||
|| die "CoreDesktop isn't installed in $COREDESKTOP."
|
||||
|
||||
"deployed_${1:-status}"
|
||||
}
|
||||
|
||||
deployed_status() {
|
||||
local behind changes
|
||||
echo "Deployed copy: $COREDESKTOP"
|
||||
echo "Branch: $(git -C "$COREDESKTOP" rev-parse --abbrev-ref HEAD)"
|
||||
echo "Commit: $(git -C "$COREDESKTOP" log -1 --format='%cd (%h) %s' --date=format:%Y.%m.%d)"
|
||||
|
||||
if GIT_TERMINAL_PROMPT=0 timeout 30 git -C "$COREDESKTOP" fetch --quiet &> /dev/null \
|
||||
&& behind="$(git -C "$COREDESKTOP" rev-list --count 'HEAD..@{upstream}' 2> /dev/null)"; then
|
||||
echo "Behind: $behind commits (update pulls them)"
|
||||
else
|
||||
echo "Behind: unknown (couldn't reach upstream)"
|
||||
fi
|
||||
|
||||
changes="$(git -C "$COREDESKTOP" status --porcelain --untracked-files=no)"
|
||||
if [ -z "$changes" ]; then
|
||||
echo "Changes: none"
|
||||
else
|
||||
echo "Changes: these stop update from pulling (coredesktop-dev deployed restore):"
|
||||
echo "$changes" | sed 's/^/ /'
|
||||
fi
|
||||
}
|
||||
|
||||
deployed_restore() {
|
||||
local untracked
|
||||
if [ -z "$(git -C "$COREDESKTOP" status --porcelain --untracked-files=no)" ]; then
|
||||
echo "The deployed copy has no local changes."
|
||||
return 0
|
||||
fi
|
||||
|
||||
git -C "$COREDESKTOP" diff HEAD --stat
|
||||
untracked="$(git -C "$COREDESKTOP" ls-files --others --exclude-standard)"
|
||||
if [ -n "$untracked" ]; then
|
||||
echo
|
||||
echo "Untracked files, which are left alone:"
|
||||
echo "$untracked" | sed 's/^/ /'
|
||||
fi
|
||||
echo
|
||||
|
||||
if ! confirm "Undo the changes above in $COREDESKTOP?"; then
|
||||
echo "Nothing changed."
|
||||
return 1
|
||||
fi
|
||||
git -C "$COREDESKTOP" checkout HEAD -- . || return 1
|
||||
|
||||
if [ -n "$(git -C "$COREDESKTOP" status --porcelain --untracked-files=no)" ]; then
|
||||
echo "Restored, but some changes remain (new files added to git's index?):"
|
||||
git -C "$COREDESKTOP" status --short --untracked-files=no
|
||||
return 1
|
||||
fi
|
||||
echo "Restored. update can pull again."
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Purpose : coredesktop-dev doctor: check this machine is set up to work on
|
||||
# CoreDesktop. Read-only.
|
||||
|
||||
doctor_usage() {
|
||||
cat << EOF
|
||||
Usage: coredesktop-dev doctor
|
||||
|
||||
Checks the tools are installed, the working copy is set up, and the deployed
|
||||
copy (~/.local/share/coredesktop) can still update. Exits 1 if anything needs
|
||||
fixing.
|
||||
EOF
|
||||
}
|
||||
|
||||
PROBLEMS=0
|
||||
|
||||
ok() {
|
||||
echo "ok $1"
|
||||
}
|
||||
|
||||
problem() {
|
||||
echo "PROBLEM $1"
|
||||
shift
|
||||
local line
|
||||
for line in "$@"; do
|
||||
echo " $line"
|
||||
done
|
||||
PROBLEMS=1
|
||||
}
|
||||
|
||||
note() {
|
||||
echo "note $1"
|
||||
}
|
||||
|
||||
cmd_doctor() {
|
||||
case "${1:-}" in
|
||||
-h | --help) doctor_usage; return 0 ;;
|
||||
"") ;;
|
||||
*) doctor_usage >&2; return 1 ;;
|
||||
esac
|
||||
|
||||
local tool missing=()
|
||||
for tool in shellcheck bats tea qemu-system-x86_64 qemu-img xorriso; do
|
||||
command -v "$tool" &> /dev/null || missing+=("$tool")
|
||||
done
|
||||
if (( ${#missing[@]} == 0 )); then
|
||||
ok "shellcheck, bats, tea, QEMU and xorriso are installed"
|
||||
else
|
||||
problem "not installed: ${missing[*]}" "fix: bash $(sibling_dir)/update.sh"
|
||||
fi
|
||||
|
||||
if [ -r /dev/kvm ] && [ -w /dev/kvm ]; then
|
||||
ok "KVM is available for the smoketest's virtual machines"
|
||||
else
|
||||
problem "can't use /dev/kvm, so the smoketest can't run VMs" \
|
||||
"fix: turn on virtualization (VT-x/AMD-V) in the firmware settings"
|
||||
fi
|
||||
|
||||
load_config
|
||||
if [ -z "$WORKING_COPY" ]; then
|
||||
problem "no working copy is set" "fix: bash $(sibling_dir)/install.sh"
|
||||
elif is_coredesktop_clone "$WORKING_COPY"; then
|
||||
ok "working copy: $WORKING_COPY"
|
||||
else
|
||||
problem "the working copy, $WORKING_COPY, isn't a clone of CoreDesktop" \
|
||||
"fix: bash $(sibling_dir)/install.sh"
|
||||
fi
|
||||
|
||||
doctor_deployed
|
||||
|
||||
if [ -n "${COREDESKTOP_DEV_VIA_FLATPAK:-}" ] || in_flatpak; then
|
||||
note "you ran this in a Flatpak, which has no pacman, systemctl or host /tmp;
|
||||
coredesktop-dev runs itself on the host for you (flatpak-spawn --host)"
|
||||
fi
|
||||
|
||||
if [ -n "$WORKING_COPY" ] && hook_is_ours "$(hook_path)"; then
|
||||
ok "the pre-push hook is installed"
|
||||
else
|
||||
note "the pre-push hook isn't installed (coredesktop-dev hooks install)"
|
||||
fi
|
||||
|
||||
return $PROBLEMS
|
||||
}
|
||||
|
||||
doctor_deployed() {
|
||||
if ! git -C "$COREDESKTOP" rev-parse --git-dir &> /dev/null; then
|
||||
problem "CoreDesktop isn't installed in $COREDESKTOP"
|
||||
return
|
||||
fi
|
||||
|
||||
local changes
|
||||
changes="$(git -C "$COREDESKTOP" status --porcelain --untracked-files=no)"
|
||||
if [ -z "$changes" ]; then
|
||||
ok "the deployed copy has no local changes"
|
||||
else
|
||||
problem "the deployed copy has local changes, so update can't pull:" \
|
||||
"${changes//$'\n'/$'\n' }" \
|
||||
"fix: coredesktop-dev deployed restore"
|
||||
fi
|
||||
|
||||
if ! GIT_TERMINAL_PROMPT=0 timeout 30 git -C "$COREDESKTOP" fetch --quiet &> /dev/null; then
|
||||
problem "couldn't reach the deployed copy's upstream to check it can update"
|
||||
elif git -C "$COREDESKTOP" merge-base --is-ancestor HEAD '@{upstream}' 2> /dev/null; then
|
||||
ok "the deployed copy can fast-forward to its upstream"
|
||||
else
|
||||
problem "the deployed copy has commits its upstream doesn't, so update's pull will fail" \
|
||||
"see: git -C $COREDESKTOP log @{upstream}..HEAD"
|
||||
fi
|
||||
}
|
||||
|
||||
# hooks.sh has hook_path and hook_is_ours
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/hooks.sh"
|
||||
@@ -0,0 +1,70 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Purpose : coredesktop-dev hooks: install or remove the working copy's pre-push
|
||||
# hook, which runs coredesktop-dev check before each push
|
||||
|
||||
hooks_usage() {
|
||||
cat << EOF
|
||||
Usage: coredesktop-dev hooks [status|install|remove]
|
||||
|
||||
status Show whether the pre-push hook is installed (the default)
|
||||
install Run coredesktop-dev check before each git push from the working copy
|
||||
remove Remove that hook
|
||||
|
||||
git push --no-verify skips the hook for one push. A pre-push hook that
|
||||
coredesktop-dev didn't install is never replaced or removed.
|
||||
EOF
|
||||
}
|
||||
|
||||
# Where git looks for the working copy's pre-push hook (core.hooksPath aware)
|
||||
hook_path() {
|
||||
local path
|
||||
path="$(git -C "$WORKING_COPY" rev-parse --git-path hooks/pre-push)" || return 1
|
||||
[[ "$path" == /* ]] || path="$WORKING_COPY/$path"
|
||||
echo "$path"
|
||||
}
|
||||
|
||||
hook_is_ours() {
|
||||
[ -f "$1" ] && grep -qxF "$HOOK_MARKER" "$1"
|
||||
}
|
||||
|
||||
cmd_hooks() {
|
||||
local hook
|
||||
case "${1:-status}" in
|
||||
-h | --help) hooks_usage; return 0 ;;
|
||||
status | install | remove) ;;
|
||||
*) hooks_usage >&2; return 1 ;;
|
||||
esac
|
||||
require_working_copy
|
||||
hook="$(hook_path)" || die "Couldn't find the working copy's git hooks folder."
|
||||
|
||||
case "${1:-status}" in
|
||||
status)
|
||||
if hook_is_ours "$hook"; then
|
||||
echo "The pre-push hook is installed: $hook"
|
||||
elif [ -e "$hook" ]; then
|
||||
echo "$hook is someone else's hook; coredesktop-dev leaves it alone."
|
||||
else
|
||||
echo "The pre-push hook isn't installed. To install it: coredesktop-dev hooks install"
|
||||
fi
|
||||
;;
|
||||
install)
|
||||
if [ -e "$hook" ] && ! hook_is_ours "$hook"; then
|
||||
die "$hook already exists and coredesktop-dev didn't write it, so it's left alone."
|
||||
fi
|
||||
mkdir -p "$(dirname "$hook")"
|
||||
install -m 755 "$(sibling_dir)/hooks/pre-push" "$hook"
|
||||
echo "Installed $hook: each git push now runs coredesktop-dev check first."
|
||||
;;
|
||||
remove)
|
||||
if hook_is_ours "$hook"; then
|
||||
rm "$hook"
|
||||
echo "Removed $hook."
|
||||
elif [ -e "$hook" ]; then
|
||||
die "$hook isn't coredesktop-dev's, so it's left alone."
|
||||
else
|
||||
echo "The pre-push hook isn't installed."
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Purpose : coredesktop-dev smoketest: run CoreDesktop's smoketest scenarios
|
||||
# (smoketest/ in the working copy) on QEMU virtual machines
|
||||
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/vm.sh"
|
||||
|
||||
CLOUD_IMAGE_URL="${SMOKETEST_CLOUD_IMAGE_URL:-https://geo.mirror.pkgbuild.com/images/latest/Arch-Linux-x86_64-cloudimg.qcow2}"
|
||||
DISK_SIZE=120G
|
||||
RUNS="$SMOKETEST_STATE/runs"
|
||||
|
||||
smoketest_usage() {
|
||||
cat << EOF
|
||||
Usage: coredesktop-dev smoketest <command>
|
||||
|
||||
list Show the scenarios
|
||||
run [options] [scenario...]
|
||||
Run scenarios against the working copy's HEAD
|
||||
--tier pr|all pr (the default): the quick ones; all: everything,
|
||||
including a fresh install and upgrades from older
|
||||
commits, which take hours
|
||||
--ref REF Test REF instead of HEAD
|
||||
--keep After a failure, leave its VM running to look
|
||||
around in, and stop
|
||||
images Show the VM images
|
||||
images base (Re)build the base image: Arch + GNOME
|
||||
images installed [REF] (Re)build the installed image: base + install.sh at
|
||||
REF (default: where HEAD branched from upstream)
|
||||
shell [base|installed] Boot a throwaway copy of an image and SSH in
|
||||
clean [--all] Delete old runs' logs (--all: images and the
|
||||
package cache too)
|
||||
|
||||
Scenarios are named by file (40-failing-migration) or number (40). Each run
|
||||
keeps its logs in $RUNS.
|
||||
Set SMOKETEST_VNC=1 to watch the VM's screen at VNC display 127.0.0.1:1.
|
||||
EOF
|
||||
}
|
||||
|
||||
cmd_smoketest() {
|
||||
local sub="${1:-}"
|
||||
shift || true
|
||||
case "$sub" in
|
||||
-h | --help | "") smoketest_usage; [ -n "$sub" ]; return ;;
|
||||
list | run | images | shell | clean) ;;
|
||||
*) smoketest_usage >&2; return 1 ;;
|
||||
esac
|
||||
require_working_copy
|
||||
SCENARIOS="$WORKING_COPY/smoketest/scenarios"
|
||||
[ -d "$SCENARIOS" ] || die "$WORKING_COPY has no smoketest/scenarios; is it an older CoreDesktop?"
|
||||
"smoketest_$sub" "$@"
|
||||
}
|
||||
|
||||
require_vm_tools() {
|
||||
local tool
|
||||
for tool in qemu-system-x86_64 qemu-img xorriso curl ssh scp ssh-keygen; do
|
||||
command -v "$tool" &> /dev/null \
|
||||
|| die "$tool isn't installed. Install coredesktop-dev's packages: bash $(sibling_dir)/update.sh"
|
||||
done
|
||||
[ -r "$KVM_DEVICE" ] && [ -w "$KVM_DEVICE" ] \
|
||||
|| die "Can't use $KVM_DEVICE. Turn on virtualization (VT-x/AMD-V) in the firmware settings."
|
||||
}
|
||||
|
||||
# scenario_info FILE: print IMAGE, TIER, DESCRIPTION and the VARIANTS (one per
|
||||
# line after the first three)
|
||||
scenario_info() {
|
||||
(
|
||||
source "$WORKING_COPY/smoketest/lib/scenario.sh"
|
||||
source "$1"
|
||||
printf '%s\n' "$IMAGE" "$TIER" "$DESCRIPTION"
|
||||
(( ${#VARIANTS[@]} == 0 )) || printf '%s\n' "${VARIANTS[@]}"
|
||||
)
|
||||
}
|
||||
|
||||
smoketest_list() {
|
||||
local file info
|
||||
for file in "$SCENARIOS"/*.sh; do
|
||||
mapfile -t info < <(scenario_info "$file")
|
||||
printf '%-28s %-8s %-10s %s' "$(basename "$file" .sh)" "${info[1]}" "${info[0]}" "${info[2]}"
|
||||
(( ${#info[@]} > 3 )) && printf ' (%d variants)' $(( ${#info[@]} - 3 ))
|
||||
echo
|
||||
done
|
||||
}
|
||||
|
||||
# Set SELECTED to the scenario files to run: those named, or those in the tier
|
||||
select_scenarios() {
|
||||
local tier="$1" name file found info
|
||||
shift
|
||||
SELECTED=()
|
||||
if (( $# > 0 )); then
|
||||
for name in "$@"; do
|
||||
found=""
|
||||
for file in "$SCENARIOS"/*.sh; do
|
||||
case "$(basename "$file" .sh)" in
|
||||
"$name" | "$name"-*) found="$file"; break ;;
|
||||
esac
|
||||
done
|
||||
[ -n "$found" ] || die "No scenario called $name (see: coredesktop-dev smoketest list)"
|
||||
SELECTED+=("$found")
|
||||
done
|
||||
return
|
||||
fi
|
||||
for file in "$SCENARIOS"/*.sh; do
|
||||
mapfile -t info < <(scenario_info "$file")
|
||||
[[ "$tier" == all || "${info[1]}" == "$tier" ]] && SELECTED+=("$file")
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
smoketest_run() {
|
||||
local tier=pr ref=HEAD keep=0 names=()
|
||||
while (( $# > 0 )); do
|
||||
case "$1" in
|
||||
--tier) tier="${2:-}"; shift 2 || die "--tier needs pr or all" ;;
|
||||
--ref) ref="${2:-}"; shift 2 || die "--ref needs a commit" ;;
|
||||
--keep) keep=1; shift ;;
|
||||
-*) die "Unknown option: $1" ;;
|
||||
*) names+=("$1"); shift ;;
|
||||
esac
|
||||
done
|
||||
[[ "$tier" == pr || "$tier" == all ]] || die "--tier is pr or all, not $tier"
|
||||
require_vm_tools
|
||||
|
||||
local sha
|
||||
sha="$(git -C "$WORKING_COPY" rev-parse --verify --quiet "$ref^{commit}")" || die "No such commit: $ref"
|
||||
if [ "$ref" = HEAD ] && [ -n "$(git -C "$WORKING_COPY" status --porcelain --untracked-files=no)" ]; then
|
||||
echo "Note: the working copy has uncommitted changes. Only HEAD is installed and"
|
||||
echo "updated in the VM; the scenarios themselves are read from the working copy."
|
||||
fi
|
||||
|
||||
select_scenarios "$tier" "${names[@]}"
|
||||
local files=("${SELECTED[@]}")
|
||||
(( ${#files[@]} > 0 )) || die "No scenarios to run."
|
||||
|
||||
# Build the images the chosen scenarios need, if they aren't there yet
|
||||
local file info need_installed=0
|
||||
for file in "${files[@]}"; do
|
||||
mapfile -t info < <(scenario_info "$file")
|
||||
[ "${info[0]}" = installed ] && need_installed=1
|
||||
done
|
||||
[ -f "$IMAGES/base.qcow2" ] || build_base_image
|
||||
if (( need_installed )) && [ ! -f "$IMAGES/installed.qcow2" ]; then
|
||||
build_installed_image "$(default_installed_ref)"
|
||||
fi
|
||||
|
||||
RUN="$RUNS/$(date +%Y%m%d-%H%M%S)"
|
||||
mkdir -p "$RUN/shared"
|
||||
echo "Testing $(git -C "$WORKING_COPY" log -1 --format='%h %s' "$sha"); logs in $RUN"
|
||||
|
||||
local results=() failed=0 variants variant status
|
||||
for file in "${files[@]}"; do
|
||||
mapfile -t info < <(scenario_info "$file")
|
||||
variants=("${info[@]:3}")
|
||||
(( ${#variants[@]} > 0 )) || variants=("")
|
||||
for variant in "${variants[@]}"; do
|
||||
run_scenario "$file" "$variant" "$sha" "$keep"
|
||||
status=$?
|
||||
local label
|
||||
label="$(basename "$file" .sh)${variant:+ @ $variant}"
|
||||
case $status in
|
||||
0) results+=("PASS $label") ;;
|
||||
3) results+=("SKIP $label") ;;
|
||||
*) results+=("FAIL $label"); failed=1 ;;
|
||||
esac
|
||||
if (( failed && keep )); then
|
||||
break 2
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
echo
|
||||
printf '%s\n' "${results[@]}" | tee "$RUN/summary"
|
||||
echo "Logs: $RUN"
|
||||
return $failed
|
||||
}
|
||||
|
||||
# run_scenario FILE VARIANT SHA KEEP: boot a throwaway copy of the scenario's
|
||||
# image and run it. Returns 0 for pass, 3 for skip, anything else for fail.
|
||||
run_scenario() {
|
||||
local file="$1" variant="$2" sha="$3" keep="$4"
|
||||
local name dir
|
||||
name="$(basename "$file" .sh)${variant:+@$variant}"
|
||||
dir="$RUN/$name"
|
||||
mkdir -p "$dir"
|
||||
echo
|
||||
echo "=== $name"
|
||||
|
||||
(
|
||||
export SMOKETEST_RESULTS="$dir" SMOKETEST_SHARED="$RUN/shared" SMOKETEST_REF="$sha" VARIANT="$variant"
|
||||
IMAGE_REF="$(cat "$IMAGES/installed.ref" 2> /dev/null)"
|
||||
export IMAGE_REF
|
||||
source "$WORKING_COPY/smoketest/lib/scenario.sh"
|
||||
source "$file"
|
||||
|
||||
qemu-img create -q -f qcow2 -b "$IMAGES/$IMAGE.qcow2" -F qcow2 "$dir/disk.qcow2" \
|
||||
|| die "Couldn't make the VM's disk."
|
||||
vm_start "$dir" "$dir/disk.qcow2"
|
||||
trap 'finish_scenario $? '"$keep" EXIT
|
||||
vm_wait_up
|
||||
smoketest_prepare
|
||||
set -e
|
||||
scenario
|
||||
) 2>&1 | tee "$dir/scenario.log"
|
||||
return "${PIPESTATUS[0]}"
|
||||
}
|
||||
|
||||
# Runs as the scenario's subshell exits: stop the VM and delete its disk,
|
||||
# unless it failed and --keep was given
|
||||
finish_scenario() {
|
||||
local status="$1" keep="$2"
|
||||
if (( status != 0 && status != SKIP_STATUS && keep )); then
|
||||
echo
|
||||
echo "The VM is still running. To look around: $(vm_ssh_command)"
|
||||
echo "To stop it: kill $(vm_pid) Its disk: $VM_DIR/disk.qcow2"
|
||||
return
|
||||
fi
|
||||
vm_kill
|
||||
rm -f "$VM_DIR/disk.qcow2"
|
||||
}
|
||||
|
||||
# Where the installed image is built by default: the commit HEAD branched
|
||||
# from upstream, so updating it to HEAD exercises the branch's changes
|
||||
default_installed_ref() {
|
||||
git -C "$WORKING_COPY" merge-base HEAD '@{upstream}' 2> /dev/null \
|
||||
|| git -C "$WORKING_COPY" merge-base HEAD origin/main 2> /dev/null \
|
||||
|| git -C "$WORKING_COPY" rev-parse HEAD
|
||||
}
|
||||
|
||||
download_cloud_image() {
|
||||
local image="$IMAGES/cloudimg.qcow2" expected actual
|
||||
[ -f "$image" ] && return 0
|
||||
mkdir -p "$IMAGES"
|
||||
echo "Downloading Arch's cloud image..."
|
||||
curl -fL --progress-bar -o "$image.part" "$CLOUD_IMAGE_URL" || die "Couldn't download $CLOUD_IMAGE_URL"
|
||||
expected="$(curl -fsSL "$CLOUD_IMAGE_URL.SHA256" | awk '{print $1}')" \
|
||||
|| die "Couldn't download $CLOUD_IMAGE_URL.SHA256"
|
||||
actual="$(sha256sum "$image.part" | awk '{print $1}')"
|
||||
[ -n "$expected" ] && [ "$expected" = "$actual" ] \
|
||||
|| die "The cloud image's checksum doesn't match; try again."
|
||||
mv "$image.part" "$image"
|
||||
}
|
||||
|
||||
build_base_image() {
|
||||
require_vm_tools
|
||||
download_cloud_image
|
||||
local build="$SMOKETEST_STATE/build-base"
|
||||
rm -rf "$build"
|
||||
mkdir -p "$build/seed"
|
||||
echo "Building the base image (Arch + GNOME); log: $build/build.log"
|
||||
|
||||
cp --reflink=auto "$IMAGES/cloudimg.qcow2" "$build/disk.qcow2"
|
||||
qemu-img resize -q "$build/disk.qcow2" "$DISK_SIZE"
|
||||
|
||||
ensure_ssh_key
|
||||
sed "s|@SSH_KEY@|$(cat "$SSH_KEY.pub")|" "$(sibling_dir)/vm/user-data" > "$build/seed/user-data"
|
||||
printf 'instance-id: coredesktop-smoketest\nlocal-hostname: coredesktop-smoketest\n' \
|
||||
> "$build/seed/meta-data"
|
||||
xorriso -as mkisofs -quiet -output "$build/seed.iso" -volid cidata -joliet -rock \
|
||||
"$build/seed/user-data" "$build/seed/meta-data" || die "Couldn't make the cloud-init disk."
|
||||
|
||||
(
|
||||
vm_start "$build" "$build/disk.qcow2" -cdrom "$build/seed.iso"
|
||||
trap vm_kill EXIT
|
||||
wait_for_ssh
|
||||
vm_ssh 'cloud-init status --wait' < /dev/null > /dev/null || true
|
||||
vm_put "$(sibling_dir)/vm/provision.sh" /tmp/provision.sh || die "Couldn't copy provision.sh to the VM."
|
||||
vm_exec 'sudo bash /tmp/provision.sh' || die "Provisioning failed; see $build/build.log"
|
||||
vm_stop
|
||||
) 2>&1 | tee "$build/build.log"
|
||||
(( PIPESTATUS[0] == 0 )) || die "Building the base image failed; see $build/build.log"
|
||||
|
||||
mv "$build/disk.qcow2" "$IMAGES/base.qcow2"
|
||||
# Built on the old base image
|
||||
rm -f "$IMAGES/installed.qcow2" "$IMAGES/installed.ref"
|
||||
rm -rf "$build"
|
||||
echo "Built $IMAGES/base.qcow2"
|
||||
}
|
||||
|
||||
# build_installed_image REF: the base image after install.sh at REF
|
||||
build_installed_image() {
|
||||
local ref sha build="$SMOKETEST_STATE/build-installed"
|
||||
ref="$1"
|
||||
sha="$(git -C "$WORKING_COPY" rev-parse --verify --quiet "$ref^{commit}")" || die "No such commit: $ref"
|
||||
require_vm_tools
|
||||
[ -f "$IMAGES/base.qcow2" ] || build_base_image
|
||||
rm -rf "$build"
|
||||
mkdir -p "$build"
|
||||
echo "Building the installed image at $(git -C "$WORKING_COPY" log -1 --format='%h %s' "$sha");" \
|
||||
"this takes an hour or more. Log: $build/build.log"
|
||||
|
||||
qemu-img create -q -f qcow2 -b "$IMAGES/base.qcow2" -F qcow2 "$build/disk.qcow2"
|
||||
(
|
||||
export SMOKETEST_RESULTS="$build" SMOKETEST_SHARED="$build" SMOKETEST_REF="$sha"
|
||||
IMAGE=base
|
||||
source "$WORKING_COPY/smoketest/lib/scenario.sh"
|
||||
vm_start "$build" "$build/disk.qcow2"
|
||||
trap vm_kill EXIT
|
||||
vm_wait_up
|
||||
smoketest_install_image "$sha"
|
||||
# The README says to reboot after installing
|
||||
vm_reboot
|
||||
vm_stop
|
||||
) 2>&1 | tee "$build/build.log"
|
||||
(( PIPESTATUS[0] == 0 )) || die "Building the installed image failed; logs in $build"
|
||||
|
||||
mv "$build/disk.qcow2" "$IMAGES/installed.qcow2"
|
||||
echo "$sha" > "$IMAGES/installed.ref"
|
||||
echo "Built $IMAGES/installed.qcow2"
|
||||
}
|
||||
|
||||
smoketest_images() {
|
||||
case "${1:-}" in
|
||||
"")
|
||||
local image
|
||||
for image in base installed; do
|
||||
if [ -f "$IMAGES/$image.qcow2" ]; then
|
||||
printf '%-10s built %s' "$image" "$(date -r "$IMAGES/$image.qcow2" '+%Y-%m-%d %H:%M')"
|
||||
[ "$image" = installed ] \
|
||||
&& printf ', at %s' "$(git -C "$WORKING_COPY" log -1 --format='%h %s' "$(cat "$IMAGES/installed.ref")" 2> /dev/null)"
|
||||
echo
|
||||
else
|
||||
printf '%-10s not built\n' "$image"
|
||||
fi
|
||||
done
|
||||
;;
|
||||
base) build_base_image ;;
|
||||
installed) build_installed_image "${2:-$(default_installed_ref)}" ;;
|
||||
*) smoketest_usage >&2; return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
smoketest_shell() {
|
||||
local image="${1:-installed}" dir
|
||||
[[ "$image" == base || "$image" == installed ]] || die "The image is base or installed, not $image."
|
||||
require_vm_tools
|
||||
[ -f "$IMAGES/$image.qcow2" ] || die "The $image image isn't built: coredesktop-dev smoketest images $image"
|
||||
dir="$SMOKETEST_STATE/shell"
|
||||
rm -rf "$dir"
|
||||
mkdir -p "$dir"
|
||||
qemu-img create -q -f qcow2 -b "$IMAGES/$image.qcow2" -F qcow2 "$dir/disk.qcow2"
|
||||
vm_start "$dir" "$dir/disk.qcow2"
|
||||
trap 'vm_kill; rm -rf "$dir"' EXIT
|
||||
echo "Starting a throwaway copy of the $image image..."
|
||||
vm_wait_up
|
||||
vm_ssh -t
|
||||
}
|
||||
|
||||
smoketest_clean() {
|
||||
case "${1:-}" in
|
||||
"") rm -rf "$RUNS" && echo "Deleted $RUNS" ;;
|
||||
--all) rm -rf "$SMOKETEST_STATE" && echo "Deleted $SMOKETEST_STATE" ;;
|
||||
*) smoketest_usage >&2; return 1 ;;
|
||||
esac
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Purpose : coredesktop-dev try: run the working copy's code as if it were the
|
||||
# deployed CoreDesktop, in a throwaway home with its own session
|
||||
|
||||
try_usage() {
|
||||
cat << EOF
|
||||
Usage: coredesktop-dev try [--keep] [command...]
|
||||
|
||||
Runs command (or a shell) with a throwaway HOME where
|
||||
~/.local/share/coredesktop is the working copy, and update, configure, version
|
||||
etc. in ~/.local/bin are the working copy's. It gets its own XDG_RUNTIME_DIR
|
||||
and D-Bus session, and no display, so it can't touch your desktop session.
|
||||
|
||||
sudo, yay, pacman and systemctl still change this machine.
|
||||
|
||||
--keep Keep the throwaway folder afterwards, and print where it is
|
||||
|
||||
Example: coredesktop-dev try configure --list
|
||||
EOF
|
||||
}
|
||||
|
||||
TRY_COMMANDS=(update refresh sibling version configure audit)
|
||||
|
||||
cmd_try() {
|
||||
local keep=0
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-h | --help) try_usage; return 0 ;;
|
||||
--keep) keep=1; shift ;;
|
||||
--) shift; break ;;
|
||||
*) break ;;
|
||||
esac
|
||||
done
|
||||
require_working_copy
|
||||
|
||||
local tmp home runtime cmd status
|
||||
tmp="$(mktemp -d "${TMPDIR:-/tmp}/coredesktop-dev-try.XXXXXX")" || return 1
|
||||
home="$tmp/home"
|
||||
runtime="$tmp/runtime"
|
||||
mkdir -p "$home/.local/share" "$home/.local/bin" "$home/.local/state" \
|
||||
"$home/.config" "$home/.cache"
|
||||
mkdir -m 700 "$runtime"
|
||||
|
||||
ln -s "$WORKING_COPY" "$home/.local/share/coredesktop"
|
||||
for cmd in "${TRY_COMMANDS[@]}"; do
|
||||
ln -s "$WORKING_COPY/$cmd" "$home/.local/bin/$cmd"
|
||||
done
|
||||
|
||||
echo "coredesktop-dev try: HOME is $home, with the working copy as CoreDesktop." >&2
|
||||
echo "Careful: sudo, yay, pacman and systemctl still change this machine." >&2
|
||||
|
||||
(( $# > 0 )) || set -- bash
|
||||
env -u DISPLAY -u WAYLAND_DISPLAY -u DBUS_SESSION_BUS_ADDRESS -u SSH_AUTH_SOCK \
|
||||
HOME="$home" \
|
||||
XDG_CONFIG_HOME="$home/.config" \
|
||||
XDG_DATA_HOME="$home/.local/share" \
|
||||
XDG_STATE_HOME="$home/.local/state" \
|
||||
XDG_CACHE_HOME="$home/.cache" \
|
||||
XDG_RUNTIME_DIR="$runtime" \
|
||||
PATH="$home/.local/bin:$PATH" \
|
||||
dbus-run-session -- "$@"
|
||||
status=$?
|
||||
|
||||
if (( keep )); then
|
||||
echo "Kept $tmp" >&2
|
||||
else
|
||||
rm -rf "$tmp"
|
||||
fi
|
||||
return $status
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Purpose : Start, reach and stop the smoketest's QEMU virtual machines. Defines
|
||||
# what CoreDesktop's smoketest/lib/scenario.sh expects of a driver:
|
||||
# vm_exec, vm_put, vm_reboot, VM_GIT_SSH and VM_GIT_URL.
|
||||
|
||||
SMOKETEST_STATE="${XDG_CACHE_HOME:-$HOME/.cache}/coredesktop-dev/smoketest"
|
||||
IMAGES="$SMOKETEST_STATE/images"
|
||||
# Shared with every VM as its /var/cache/pacman/pkg, so packages download once
|
||||
PKG_CACHE="$SMOKETEST_STATE/pkg"
|
||||
SSH_KEY="$SMOKETEST_STATE/id_ed25519"
|
||||
KVM_DEVICE="${KVM_DEVICE:-/dev/kvm}"
|
||||
|
||||
VM_USER=tester
|
||||
VM_MEMORY="${SMOKETEST_MEMORY:-8G}"
|
||||
VM_CPUS="${SMOKETEST_CPUS:-4}"
|
||||
# Longest a single command in the VM may run (an install takes an hour or more)
|
||||
VM_EXEC_TIMEOUT="${SMOKETEST_EXEC_TIMEOUT:-4h}"
|
||||
# Seconds to wait for the VM to answer on SSH, and for GNOME to start
|
||||
VM_SSH_TIMEOUT="${SMOKETEST_SSH_TIMEOUT:-900}"
|
||||
VM_DESKTOP_TIMEOUT="${SMOKETEST_DESKTOP_TIMEOUT:-300}"
|
||||
|
||||
SSH_OPTS=(
|
||||
-i "$SSH_KEY"
|
||||
-o IdentitiesOnly=yes
|
||||
-o StrictHostKeyChecking=no
|
||||
-o UserKnownHostsFile=/dev/null
|
||||
-o LogLevel=ERROR
|
||||
-o BatchMode=yes
|
||||
-o ConnectTimeout=10
|
||||
-o ServerAliveInterval=15
|
||||
-o ServerAliveCountMax=8
|
||||
)
|
||||
|
||||
# Commands in the VM run as they would in a GNOME Terminal: with the GNOME
|
||||
# session's environment, in an interactive bash that has read ~/.bashrc
|
||||
VM_SESSION_ENV='eval "$(systemctl --user show-environment 2> /dev/null | sed "s/^/export /")"
|
||||
export TERM="${TERM:-dumb}"'
|
||||
|
||||
ensure_ssh_key() {
|
||||
mkdir -p "$SMOKETEST_STATE"
|
||||
[ -f "$SSH_KEY" ] || ssh-keygen -q -t ed25519 -N '' -C coredesktop-smoketest -f "$SSH_KEY"
|
||||
}
|
||||
|
||||
free_port() {
|
||||
local port i
|
||||
for i in $(seq 50); do
|
||||
port=$((20000 + RANDOM % 20000))
|
||||
[ -z "$(ss -Hltn "( sport = :$port )" 2> /dev/null)" ] && { echo "$port"; return 0; }
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# vm_start DIR DISK [QEMU ARGS...]: boot DISK in the background. DIR gets the
|
||||
# pid file and the serial console's log.
|
||||
vm_start() {
|
||||
VM_DIR="$1"
|
||||
local disk="$2"
|
||||
shift 2
|
||||
local display=(-display none)
|
||||
[ -n "${SMOKETEST_VNC:-}" ] && display+=(-vnc "127.0.0.1:$SMOKETEST_VNC")
|
||||
|
||||
ensure_ssh_key
|
||||
mkdir -p "$VM_DIR" "$PKG_CACHE"
|
||||
VM_PORT="$(free_port)" || die "Couldn't find a free port for the VM's SSH."
|
||||
|
||||
qemu-system-x86_64 \
|
||||
-name coredesktop-smoketest \
|
||||
-machine q35,accel=kvm \
|
||||
-cpu host \
|
||||
-smp "$VM_CPUS" \
|
||||
-m "$VM_MEMORY" \
|
||||
-drive "file=$disk,if=virtio,format=qcow2,discard=unmap" \
|
||||
-nic "user,model=virtio-net-pci,hostfwd=tcp:127.0.0.1:$VM_PORT-:22" \
|
||||
-virtfs "local,path=$PKG_CACHE,mount_tag=pkgcache,security_model=mapped-xattr" \
|
||||
-vga virtio \
|
||||
"${display[@]}" \
|
||||
-serial "file:$VM_DIR/serial.log" \
|
||||
-pidfile "$VM_DIR/qemu.pid" \
|
||||
-daemonize \
|
||||
"$@" || die "QEMU didn't start."
|
||||
|
||||
VM_GIT_SSH="ssh$(printf ' %q' "${SSH_OPTS[@]}")"
|
||||
VM_GIT_URL="ssh://$VM_USER@127.0.0.1:$VM_PORT"
|
||||
export VM_GIT_SSH VM_GIT_URL
|
||||
}
|
||||
|
||||
vm_pid() {
|
||||
cat "$VM_DIR/qemu.pid" 2> /dev/null
|
||||
}
|
||||
|
||||
vm_running() {
|
||||
local pid
|
||||
pid="$(vm_pid)" && [ -n "$pid" ] && kill -0 "$pid" 2> /dev/null
|
||||
}
|
||||
|
||||
vm_ssh() {
|
||||
ssh "${SSH_OPTS[@]}" -p "$VM_PORT" "$VM_USER@127.0.0.1" "$@"
|
||||
}
|
||||
|
||||
# The command to SSH in by hand
|
||||
vm_ssh_command() {
|
||||
echo "ssh -i $SSH_KEY -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p $VM_PORT $VM_USER@127.0.0.1"
|
||||
}
|
||||
|
||||
wait_for_ssh() {
|
||||
local deadline=$((SECONDS + VM_SSH_TIMEOUT))
|
||||
until vm_ssh true < /dev/null 2> /dev/null; do
|
||||
vm_running || die "The VM stopped. Its console log: $VM_DIR/serial.log"
|
||||
(( SECONDS < deadline )) || die "The VM didn't answer on SSH. Its console log: $VM_DIR/serial.log"
|
||||
sleep 5
|
||||
done
|
||||
}
|
||||
|
||||
# Wait for the automatic GNOME login. Not fatal: the health checks catch a
|
||||
# desktop that didn't start.
|
||||
wait_for_desktop() {
|
||||
local deadline=$((SECONDS + VM_DESKTOP_TIMEOUT))
|
||||
until vm_ssh "pgrep -u $VM_USER -x gnome-shell" < /dev/null &> /dev/null; do
|
||||
if (( SECONDS >= deadline )); then
|
||||
echo "Warning: GNOME didn't start in the VM within ${VM_DESKTOP_TIMEOUT}s."
|
||||
return 0
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
}
|
||||
|
||||
vm_wait_up() {
|
||||
wait_for_ssh
|
||||
wait_for_desktop
|
||||
}
|
||||
|
||||
# vm_exec SCRIPT: run SCRIPT with bash in the VM, as tester, in the GNOME
|
||||
# session's environment. Interactive bash's complaints about having no
|
||||
# terminal are dropped.
|
||||
vm_exec() {
|
||||
timeout "$VM_EXEC_TIMEOUT" ssh "${SSH_OPTS[@]}" -p "$VM_PORT" -T "$VM_USER@127.0.0.1" \
|
||||
"bash -ic $(printf '%q' "$VM_SESSION_ENV
|
||||
$1")" < /dev/null \
|
||||
2> >(grep -v -e '^bash: cannot set terminal process group' -e '^bash: no job control in this shell' >&2)
|
||||
}
|
||||
|
||||
# vm_put LOCAL REMOTE
|
||||
vm_put() {
|
||||
scp -q "${SSH_OPTS[@]}" -P "$VM_PORT" "$1" "$VM_USER@127.0.0.1:$2"
|
||||
}
|
||||
|
||||
vm_boot_id() {
|
||||
vm_ssh 'cat /proc/sys/kernel/random/boot_id' < /dev/null 2> /dev/null
|
||||
}
|
||||
|
||||
vm_reboot() {
|
||||
local before deadline=$((SECONDS + VM_SSH_TIMEOUT))
|
||||
echo "== reboot"
|
||||
before="$(vm_boot_id)"
|
||||
vm_ssh 'sudo systemctl reboot' < /dev/null &> /dev/null || true
|
||||
until [ "$(vm_boot_id)" != "$before" ] && [ -n "$(vm_boot_id)" ]; do
|
||||
vm_running || die "The VM stopped while rebooting. Its console log: $VM_DIR/serial.log"
|
||||
(( SECONDS < deadline )) || die "The VM didn't come back after rebooting. Its console log: $VM_DIR/serial.log"
|
||||
sleep 5
|
||||
done
|
||||
wait_for_desktop
|
||||
}
|
||||
|
||||
# Shut the VM down cleanly (so its disk can be kept), killing it if it hangs
|
||||
vm_stop() {
|
||||
vm_running || return 0
|
||||
vm_ssh 'sudo systemctl poweroff' < /dev/null &> /dev/null || true
|
||||
local deadline=$((SECONDS + 120))
|
||||
while vm_running && (( SECONDS < deadline )); do
|
||||
sleep 2
|
||||
done
|
||||
vm_kill
|
||||
}
|
||||
|
||||
vm_kill() {
|
||||
local pid
|
||||
pid="$(vm_pid)" || return 0
|
||||
[ -n "$pid" ] && kill "$pid" 2> /dev/null
|
||||
rm -f "$VM_DIR/qemu.pid"
|
||||
return 0
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load test_helper
|
||||
|
||||
setup() {
|
||||
common_setup
|
||||
make_working_copy
|
||||
stub make
|
||||
# Fails on any file that contains BAD, as if it had a warning
|
||||
stub shellcheck 'for f in "$@"; do [ -f "$f" ] && grep -q BAD "$f" && exit 1; done; exit 0'
|
||||
stub bats 'echo 7'
|
||||
}
|
||||
|
||||
@test "check runs make lint and make test, and shellcheck on untracked scripts" {
|
||||
printf '%s\n' '#!/bin/bash' 'echo fine' > "$WC/new-script"
|
||||
printf '%s\n' 'echo fine' > "$WC/new-hook.sh"
|
||||
echo "not a script" > "$WC/notes.txt"
|
||||
|
||||
run cdd check
|
||||
[ "$status" -eq 0 ]
|
||||
called make --no-print-directory lint
|
||||
called make --no-print-directory test
|
||||
grep -q "^shellcheck --severity=warning .*new-script" "$STUB_LOG"
|
||||
grep -q "^shellcheck --severity=warning .*new-hook.sh" "$STUB_LOG"
|
||||
[[ "$output" == *"shellcheck: ok ("*" files, 2 untracked) bats: ok (7 tests)"* ]]
|
||||
run grep -q "^shellcheck .*notes.txt" "$STUB_LOG"
|
||||
[ "$status" -eq 1 ]
|
||||
}
|
||||
|
||||
@test "check fails on a warning in an untracked script" {
|
||||
printf '%s\n' '#!/bin/bash' 'BAD' > "$WC/new-script"
|
||||
|
||||
run cdd check
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" == *"shellcheck: FAILED"* ]]
|
||||
}
|
||||
|
||||
@test "check skips ignored files" {
|
||||
echo "ignored-script" > "$WC/.gitignore"
|
||||
printf '%s\n' '#!/bin/bash' 'BAD' > "$WC/ignored-script"
|
||||
|
||||
run cdd check
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "check --fast runs only the shellcheck steps" {
|
||||
run cdd check --fast
|
||||
[ "$status" -eq 0 ]
|
||||
called make --no-print-directory lint
|
||||
uncalled make --no-print-directory test
|
||||
[[ "$output" == *"bats: skipped (--fast)"* ]]
|
||||
}
|
||||
|
||||
@test "check fails when make test fails" {
|
||||
stub make '[ "$2" != test ]'
|
||||
run cdd check
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" == *"bats: FAILED"* ]]
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load test_helper
|
||||
|
||||
@test "deployed status shows the commit, how far behind, and local changes" {
|
||||
make_working_copy
|
||||
git -C "$WC" commit -q --allow-empty -m "new"
|
||||
git -C "$WC" push -q origin main
|
||||
echo "# edited" >> "$DEPLOYED/update"
|
||||
|
||||
run cdd deployed
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"Branch: main"* ]]
|
||||
[[ "$output" == *"CoreDesktop"* ]]
|
||||
[[ "$output" == *"Behind: 1 commits"* ]]
|
||||
[[ "$output" == *"M update"* ]]
|
||||
}
|
||||
|
||||
@test "deployed restore asks first, and changes nothing on no" {
|
||||
echo "# edited" >> "$DEPLOYED/update"
|
||||
|
||||
run cdd deployed restore <<< n
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" == *"Nothing changed."* ]]
|
||||
grep -q "# edited" "$DEPLOYED/update"
|
||||
}
|
||||
|
||||
@test "deployed restore puts back tracked files and leaves untracked ones" {
|
||||
echo "# edited" >> "$DEPLOYED/update"
|
||||
rm "$DEPLOYED/utils.sh"
|
||||
echo "mine" > "$DEPLOYED/untracked-file"
|
||||
|
||||
run cdd deployed restore <<< y
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"untracked-file"* ]]
|
||||
[ -f "$DEPLOYED/utils.sh" ]
|
||||
[ "$(cat "$DEPLOYED/untracked-file")" = mine ]
|
||||
[ -z "$(git -C "$DEPLOYED" status --porcelain --untracked-files=no)" ]
|
||||
run grep -q "# edited" "$DEPLOYED/update"
|
||||
[ "$status" -eq 1 ]
|
||||
}
|
||||
|
||||
@test "deployed restore puts back staged changes too" {
|
||||
echo "# edited" >> "$DEPLOYED/update"
|
||||
git -C "$DEPLOYED" add update
|
||||
|
||||
run cdd deployed restore <<< y
|
||||
[ "$status" -eq 0 ]
|
||||
[ -z "$(git -C "$DEPLOYED" status --porcelain --untracked-files=no)" ]
|
||||
}
|
||||
|
||||
@test "deployed restore with nothing to restore doesn't ask" {
|
||||
run cdd deployed restore < /dev/null
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"no local changes"* ]]
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load test_helper
|
||||
|
||||
@test "doctor reports local changes in the deployed copy and how to fix them" {
|
||||
make_working_copy
|
||||
echo "# edited" >> "$DEPLOYED/update"
|
||||
|
||||
run cdd doctor
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" == *"PROBLEM the deployed copy has local changes"* ]]
|
||||
[[ "$output" == *"M update"* ]]
|
||||
[[ "$output" == *"coredesktop-dev deployed restore"* ]]
|
||||
}
|
||||
|
||||
@test "doctor is happy with a clean deployed copy that can fast-forward" {
|
||||
make_working_copy
|
||||
# Upstream moves on; the deployed copy is behind, which is fine
|
||||
git -C "$WC" commit -q --allow-empty -m "new"
|
||||
git -C "$WC" push -q origin main
|
||||
|
||||
run cdd doctor
|
||||
[[ "$output" == *"ok the deployed copy has no local changes"* ]]
|
||||
[[ "$output" == *"ok the deployed copy can fast-forward to its upstream"* ]]
|
||||
[[ "$output" == *"ok working copy: $WC"* ]]
|
||||
}
|
||||
|
||||
@test "doctor reports a deployed copy with commits upstream doesn't have" {
|
||||
make_working_copy
|
||||
git -C "$DEPLOYED" commit -q --allow-empty -m "local commit"
|
||||
|
||||
run cdd doctor
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" == *"has commits its upstream doesn't"* ]]
|
||||
}
|
||||
|
||||
@test "doctor reports a missing working copy" {
|
||||
run cdd doctor
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" == *"PROBLEM no working copy is set"* ]]
|
||||
}
|
||||
|
||||
@test "doctor reports when it was run from a Flatpak" {
|
||||
make_working_copy
|
||||
COREDESKTOP_DEV_VIA_FLATPAK=1 run cdd doctor
|
||||
[[ "$output" == *"note you ran this in a Flatpak"* ]]
|
||||
}
|
||||
|
||||
@test "doctor reports the pre-push hook" {
|
||||
make_working_copy
|
||||
run cdd doctor
|
||||
[[ "$output" == *"the pre-push hook isn't installed"* ]]
|
||||
|
||||
cdd hooks install
|
||||
run cdd doctor
|
||||
[[ "$output" == *"ok the pre-push hook is installed"* ]]
|
||||
}
|
||||
|
||||
@test "in a Flatpak, coredesktop-dev re-runs itself on the host" {
|
||||
stub flatpak-spawn
|
||||
FLATPAK_ID=com.vscodium.codium run cdd doctor
|
||||
[ "$status" -eq 0 ]
|
||||
called flatpak-spawn --host --env=COREDESKTOP_DEV_VIA_FLATPAK=1 \
|
||||
"$(readlink -f "$PROJECT/bin/coredesktop-dev")" doctor
|
||||
}
|
||||
|
||||
@test "--help works in a Flatpak without going to the host" {
|
||||
stub flatpak-spawn
|
||||
FLATPAK_ID=com.vscodium.codium run cdd doctor --help
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"Usage: coredesktop-dev doctor"* ]]
|
||||
not_called flatpak-spawn
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load test_helper
|
||||
|
||||
setup() {
|
||||
common_setup
|
||||
make_working_copy
|
||||
HOOK="$WC/.git/hooks/pre-push"
|
||||
}
|
||||
|
||||
@test "hooks install installs the pre-push hook" {
|
||||
run cdd hooks install
|
||||
[ "$status" -eq 0 ]
|
||||
[ -x "$HOOK" ]
|
||||
cmp "$PROJECT/hooks/pre-push" "$HOOK"
|
||||
|
||||
run cdd hooks
|
||||
[[ "$output" == *"The pre-push hook is installed"* ]]
|
||||
}
|
||||
|
||||
@test "hooks install won't overwrite someone else's hook" {
|
||||
printf '%s\n' '#!/bin/bash' 'mine' > "$HOOK"
|
||||
|
||||
run cdd hooks install
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" == *"didn't write it"* ]]
|
||||
[ "$(tail -n 1 "$HOOK")" = mine ]
|
||||
}
|
||||
|
||||
@test "hooks remove removes only our hook" {
|
||||
cdd hooks install
|
||||
run cdd hooks remove
|
||||
[ "$status" -eq 0 ]
|
||||
[ ! -e "$HOOK" ]
|
||||
|
||||
printf '%s\n' '#!/bin/bash' 'mine' > "$HOOK"
|
||||
run cdd hooks remove
|
||||
[ "$status" -eq 1 ]
|
||||
[ -e "$HOOK" ]
|
||||
}
|
||||
|
||||
@test "hooks follows core.hooksPath" {
|
||||
git -C "$WC" config core.hooksPath .githooks
|
||||
run cdd hooks install
|
||||
[ "$status" -eq 0 ]
|
||||
[ -x "$WC/.githooks/pre-push" ]
|
||||
}
|
||||
|
||||
@test "the hook stops a push when check fails, and says how to skip it" {
|
||||
mkdir -p "$HOME/.local/bin"
|
||||
printf '%s\n' '#!/bin/bash' 'exit 1' > "$HOME/.local/bin/coredesktop-dev"
|
||||
chmod +x "$HOME/.local/bin/coredesktop-dev"
|
||||
|
||||
run bash "$PROJECT/hooks/pre-push"
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" == *"git push --no-verify"* ]]
|
||||
}
|
||||
|
||||
@test "the hook lets a push through when check passes" {
|
||||
mkdir -p "$HOME/.local/bin"
|
||||
printf '%s\n' '#!/bin/bash' 'exit 0' > "$HOME/.local/bin/coredesktop-dev"
|
||||
chmod +x "$HOME/.local/bin/coredesktop-dev"
|
||||
|
||||
run bash "$PROJECT/hooks/pre-push"
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load test_helper
|
||||
|
||||
config() {
|
||||
cat "$HOME/.config/coredesktop-dev/config"
|
||||
}
|
||||
|
||||
@test "install refuses the deployed copy, then takes a working copy" {
|
||||
git clone -q "$UPSTREAM" "$WC"
|
||||
|
||||
run bash "$PROJECT/install.sh" <<< "$(printf '%s\n' "$DEPLOYED" "$WC" n)"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"is the deployed copy"* ]]
|
||||
[ "$(config)" = "WORKING_COPY=$(printf '%q' "$WC")" ]
|
||||
[ "$(readlink "$HOME/.local/bin/coredesktop-dev")" = "$PROJECT/bin/coredesktop-dev" ]
|
||||
called install_packages shellcheck bats tea qemu-base libisoburn
|
||||
}
|
||||
|
||||
@test "install refuses the deployed copy however it's spelled" {
|
||||
git clone -q "$UPSTREAM" "$WC"
|
||||
|
||||
run bash "$PROJECT/install.sh" <<< "$(printf '%s\n' '~/.local/share/coredesktop/' "$WC" n)"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"is the deployed copy"* ]]
|
||||
[ "$(config)" = "WORKING_COPY=$(printf '%q' "$WC")" ]
|
||||
}
|
||||
|
||||
@test "install offers to clone the working copy from the deployed copy's origin" {
|
||||
run bash "$PROJECT/install.sh" <<< "$(printf '%s\n' "" y n)"
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$(git -C "$WC" remote get-url origin)" = "$UPSTREAM" ]
|
||||
[ "$(config)" = "WORKING_COPY=$(printf '%q' "$WC")" ]
|
||||
}
|
||||
|
||||
@test "install won't use a folder that isn't a clone and isn't empty" {
|
||||
mkdir -p "$HOME/other"
|
||||
touch "$HOME/other/file"
|
||||
git clone -q "$UPSTREAM" "$WC"
|
||||
|
||||
run bash "$PROJECT/install.sh" <<< "$(printf '%s\n' "$HOME/other" "$WC" n)"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"isn't a clone of CoreDesktop, and isn't empty"* ]]
|
||||
[ "$(config)" = "WORKING_COPY=$(printf '%q' "$WC")" ]
|
||||
}
|
||||
|
||||
@test "install is safe to re-run, and Enter keeps the saved working copy" {
|
||||
# Somewhere other than the default, ~/Projects/coredesktop
|
||||
other="$HOME/work/coredesktop"
|
||||
git clone -q "$UPSTREAM" "$other"
|
||||
run bash "$PROJECT/install.sh" <<< "$(printf '%s\n' "$other" n)"
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
run bash "$PROJECT/install.sh" <<< "$(printf '%s\n' "" n)"
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$(config)" = "WORKING_COPY=$(printf '%q' "$other")" ]
|
||||
[ ! -e "$WC" ]
|
||||
}
|
||||
|
||||
@test "install stops without an answer instead of looping" {
|
||||
run bash "$PROJECT/install.sh" < /dev/null
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" == *"No answer"* ]]
|
||||
[ ! -f "$HOME/.config/coredesktop-dev/config" ]
|
||||
}
|
||||
|
||||
@test "install offers the pre-push hook, and installs it on yes" {
|
||||
git clone -q "$UPSTREAM" "$WC"
|
||||
|
||||
run bash "$PROJECT/install.sh" <<< "$(printf '%s\n' "$WC" y)"
|
||||
[ "$status" -eq 0 ]
|
||||
grep -qxF "# Installed by coredesktop-dev; remove with: coredesktop-dev hooks remove" \
|
||||
"$WC/.git/hooks/pre-push"
|
||||
}
|
||||
|
||||
@test "install stops if CoreDesktop isn't installed" {
|
||||
rm -rf "$DEPLOYED"
|
||||
run bash "$PROJECT/install.sh" <<< ""
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" == *"CoreDesktop isn't installed"* ]]
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load test_helper
|
||||
|
||||
# The driver with fake QEMU: "VMs" are sleep processes, and ssh runs its command
|
||||
# right here. The working copy has a small fake smoketest of its own.
|
||||
|
||||
setup() {
|
||||
common_setup
|
||||
make_working_copy
|
||||
|
||||
export KVM_DEVICE="$BATS_TEST_TMPDIR/kvm"
|
||||
touch "$KVM_DEVICE"
|
||||
export SMOKETEST_DESKTOP_TIMEOUT=0 SMOKETEST_SSH_TIMEOUT=5
|
||||
STATE="$HOME/.cache/coredesktop-dev/smoketest"
|
||||
mkdir -p "$STATE/images"
|
||||
touch "$STATE/images/base.qcow2" "$STATE/images/installed.qcow2"
|
||||
git -C "$WC" rev-parse HEAD > "$STATE/images/installed.ref"
|
||||
|
||||
# A VM is a sleep that lets go of bats' output pipes
|
||||
stub qemu-system-x86_64 'while [ $# -gt 0 ]; do [ "$1" = -pidfile ] && pidfile="$2"; shift; done
|
||||
/usr/bin/sleep 300 > /dev/null 2>&1 3>&- &
|
||||
echo $! > "$pidfile"'
|
||||
stub qemu-img 'touch "${@: -1}"'
|
||||
stub xorriso
|
||||
stub curl
|
||||
stub ss
|
||||
stub ssh-keygen 'touch "${@: -1}" "${@: -1}.pub"'
|
||||
stub systemctl
|
||||
stub ssh 'while [ $# -gt 0 ]; do
|
||||
case "$1" in -i | -o | -p) shift 2 ;; *@*) shift; break ;; *) shift ;; esac
|
||||
done
|
||||
[ $# -gt 0 ] || exit 0
|
||||
cd "$HOME" && exec bash -c "$*"'
|
||||
stub scp 'dest="${*: -1}"; cp "${@: -2:1}" "${dest#*:}"'
|
||||
|
||||
SC="$WC/smoketest/scenarios"
|
||||
mkdir -p "$SC" "$WC/smoketest/lib"
|
||||
cat > "$WC/smoketest/lib/scenario.sh" << 'EOF'
|
||||
SKIP_STATUS=3
|
||||
smoketest_prepare() { echo "prepare $IMAGE $SMOKETEST_REF $IMAGE_REF"; }
|
||||
fail() { echo "FAIL: $*"; exit 1; }
|
||||
skip() { echo "SKIP: $*"; exit 3; }
|
||||
EOF
|
||||
scenario_file 10-passes pr installed 'vm_exec "echo in the vm"; echo "ref=$SMOKETEST_REF"'
|
||||
scenario_file 20-fails pr base 'false; echo "still going"'
|
||||
scenario_file 30-variants nightly base 'echo "variant $VARIANT"' 'VARIANTS=(one two)'
|
||||
scenario_file 40-skips pr base 'skip "not today"'
|
||||
}
|
||||
|
||||
# scenario_file NAME TIER IMAGE BODY [EXTRA]
|
||||
scenario_file() {
|
||||
printf '%s\n' "DESCRIPTION=\"$1 does things\"" "TIER=$2" "IMAGE=$3" "${5:-}" \
|
||||
"scenario() {" "$4" "}" > "$SC/$1.sh"
|
||||
}
|
||||
|
||||
last_run() {
|
||||
ls -d "$STATE/runs/"* | tail -n 1
|
||||
}
|
||||
|
||||
@test "list shows each scenario's tier, image and description" {
|
||||
run cdd smoketest list
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"10-passes"*"pr"*"installed"*"10-passes does things"* ]]
|
||||
[[ "$output" == *"30-variants"*"nightly"*"base"*"(2 variants)"* ]]
|
||||
}
|
||||
|
||||
@test "run runs the pr scenarios against HEAD and reports each" {
|
||||
run cdd smoketest run
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" == *"PASS 10-passes"* ]]
|
||||
[[ "$output" == *"FAIL 20-fails"* ]]
|
||||
[[ "$output" == *"SKIP 40-skips"* ]]
|
||||
[[ "$output" != *"30-variants"* ]]
|
||||
[[ "$output" == *"in the vm"* ]]
|
||||
[[ "$output" == *"ref=$(git -C "$WC" rev-parse HEAD)"* ]]
|
||||
[ -f "$(last_run)/summary" ]
|
||||
}
|
||||
|
||||
@test "a scenario stops at its first failing command" {
|
||||
run cdd smoketest run 20
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" != *"still going"* ]]
|
||||
}
|
||||
|
||||
@test "run picks scenarios by name or number" {
|
||||
run cdd smoketest run 10 40-skips
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"PASS 10-passes"* ]]
|
||||
[[ "$output" == *"SKIP 40-skips"* ]]
|
||||
[[ "$output" != *"20-fails"* ]]
|
||||
}
|
||||
|
||||
@test "run stops on an unknown scenario before starting any VM" {
|
||||
run cdd smoketest run 10 nosuch
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" == *"No scenario called nosuch"* ]]
|
||||
not_called qemu-system-x86_64
|
||||
}
|
||||
|
||||
@test "each variant runs in its own VM" {
|
||||
run cdd smoketest run 30
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"PASS 30-variants @ one"* ]]
|
||||
[[ "$output" == *"PASS 30-variants @ two"* ]]
|
||||
[ "$(grep -c '^qemu-system-x86_64 ' "$STUB_LOG")" -eq 2 ]
|
||||
grep -q "variant two" "$(last_run)/30-variants@two/scenario.log"
|
||||
}
|
||||
|
||||
@test "--tier all runs every scenario" {
|
||||
run cdd smoketest run --tier all
|
||||
[[ "$output" == *"30-variants @ one"* ]]
|
||||
[[ "$output" == *"10-passes"* ]]
|
||||
}
|
||||
|
||||
@test "the scenario boots a copy of its image, and gets the installed image's commit" {
|
||||
run cdd smoketest run 10
|
||||
[ "$status" -eq 0 ]
|
||||
grep -q "^qemu-img create -q -f qcow2 -b $STATE/images/installed.qcow2 " "$STUB_LOG"
|
||||
[[ "$output" == *"prepare installed $(git -C "$WC" rev-parse HEAD) $(cat "$STATE/images/installed.ref")"* ]]
|
||||
}
|
||||
|
||||
@test "the VM is stopped and its disk deleted after each scenario" {
|
||||
run cdd smoketest run 10 20
|
||||
for dir in "$(last_run)"/*/; do
|
||||
[ ! -e "$dir/qemu.pid" ]
|
||||
[ ! -e "$dir/disk.qcow2" ]
|
||||
done
|
||||
}
|
||||
|
||||
@test "--keep leaves a failed scenario's VM running, and stops the run" {
|
||||
run cdd smoketest run --keep 20 10
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" == *"The VM is still running"* ]]
|
||||
[[ "$output" != *"PASS 10-passes"* ]]
|
||||
pid="$(cat "$(last_run)/20-fails/qemu.pid")"
|
||||
kill -0 "$pid"
|
||||
kill "$pid"
|
||||
}
|
||||
|
||||
@test "run --ref tests another commit" {
|
||||
first="$(git -C "$WC" rev-parse HEAD)"
|
||||
git -C "$WC" commit -q --allow-empty -m second
|
||||
run cdd smoketest run --ref "$first" 10
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"ref=$first"* ]]
|
||||
}
|
||||
|
||||
@test "run stops without KVM" {
|
||||
rm "$KVM_DEVICE"
|
||||
run cdd smoketest run 10
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" == *"Can't use $KVM_DEVICE"* ]]
|
||||
}
|
||||
|
||||
@test "smoketest needs a working copy with a smoketest folder" {
|
||||
rm -rf "$WC/smoketest"
|
||||
run cdd smoketest list
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" == *"has no smoketest/scenarios"* ]]
|
||||
}
|
||||
|
||||
@test "images shows what's built" {
|
||||
rm "$STATE/images/installed.qcow2"
|
||||
run cdd smoketest images
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"base built"* ]]
|
||||
[[ "$output" == *"installed not built"* ]]
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Shared setup for the bats tests, after CoreDesktop's: each test gets its own
|
||||
# HOME and a bin dir at the front of PATH for fake commands. On top of that,
|
||||
# HOME holds a fake CoreDesktop: an upstream repo, the deployed copy cloned
|
||||
# from it, and a working copy at ~/Projects/coredesktop.
|
||||
|
||||
# Used by the .bats files that load this one
|
||||
# shellcheck disable=SC2034
|
||||
PROJECT="$(cd "$BATS_TEST_DIRNAME/.." && pwd)"
|
||||
|
||||
common_setup() {
|
||||
export HOME="$BATS_TEST_TMPDIR/home"
|
||||
export STUB_BIN="$BATS_TEST_TMPDIR/bin"
|
||||
export STUB_LOG="$BATS_TEST_TMPDIR/stub.log"
|
||||
export TMPDIR="$BATS_TEST_TMPDIR/tmp"
|
||||
mkdir -p "$HOME" "$STUB_BIN" "$TMPDIR"
|
||||
: > "$STUB_LOG"
|
||||
export PATH="$STUB_BIN:$PATH"
|
||||
unset XDG_CONFIG_HOME XDG_CACHE_HOME FLATPAK_ID COREDESKTOP_DEV_VIA_FLATPAK
|
||||
export GIT_AUTHOR_NAME=Test GIT_AUTHOR_EMAIL=test@localhost
|
||||
export GIT_COMMITTER_NAME=Test GIT_COMMITTER_EMAIL=test@localhost
|
||||
export GIT_CONFIG_NOSYSTEM=1
|
||||
|
||||
UPSTREAM="$BATS_TEST_TMPDIR/upstream.git"
|
||||
DEPLOYED="$HOME/.local/share/coredesktop"
|
||||
WC="$HOME/Projects/coredesktop"
|
||||
make_coredesktop
|
||||
}
|
||||
|
||||
setup() {
|
||||
common_setup
|
||||
}
|
||||
|
||||
# A CoreDesktop upstream with one commit, and the deployed copy cloned from it.
|
||||
# The fake utils.sh logs install_packages instead of installing anything.
|
||||
make_coredesktop() {
|
||||
local src="$BATS_TEST_TMPDIR/src"
|
||||
git init -q --initial-branch=main "$src"
|
||||
mkdir -p "$src/updates.d"
|
||||
printf '%s\n' '#!/bin/bash' 'echo update' > "$src/update"
|
||||
chmod +x "$src/update"
|
||||
touch "$src/updates.d/.keep"
|
||||
printf '%s\n' '#!/bin/bash' 'install_packages() { echo "install_packages $*" >> "$STUB_LOG"; }' \
|
||||
> "$src/utils.sh"
|
||||
printf 'check:\n\ttrue\n' > "$src/Makefile"
|
||||
git -C "$src" add -A
|
||||
git -C "$src" commit -qm "CoreDesktop"
|
||||
git clone -q --bare "$src" "$UPSTREAM"
|
||||
rm -rf "$src"
|
||||
git clone -q "$UPSTREAM" "$DEPLOYED"
|
||||
}
|
||||
|
||||
# Clone the working copy and point the settings at it
|
||||
make_working_copy() {
|
||||
git clone -q "$UPSTREAM" "$WC"
|
||||
mkdir -p "$HOME/.config/coredesktop-dev"
|
||||
printf 'WORKING_COPY=%q\n' "$WC" > "$HOME/.config/coredesktop-dev/config"
|
||||
}
|
||||
|
||||
# Run the coredesktop-dev command
|
||||
cdd() {
|
||||
"$PROJECT/bin/coredesktop-dev" "$@"
|
||||
}
|
||||
|
||||
# stub NAME [BODY]: put a fake NAME on PATH that appends "NAME args..." to
|
||||
# $STUB_LOG and then runs BODY, a bash snippet that sees the args as "$@"
|
||||
stub() {
|
||||
local name="$1" body="${2:-}"
|
||||
cat > "$STUB_BIN/$name" << EOF
|
||||
#!/bin/bash
|
||||
echo "$name \$*" >> "\$STUB_LOG"
|
||||
$body
|
||||
EOF
|
||||
chmod +x "$STUB_BIN/$name"
|
||||
}
|
||||
|
||||
# called NAME ARGS...: true if the stub log has exactly the line "NAME ARGS..."
|
||||
called() {
|
||||
grep -qxF "$*" "$STUB_LOG"
|
||||
}
|
||||
|
||||
# not_called NAME: true if the stub NAME never ran
|
||||
not_called() {
|
||||
! grep -q "^$1 " "$STUB_LOG"
|
||||
}
|
||||
|
||||
# uncalled NAME ARGS...: the opposite of called. (A bare "! called ..." would
|
||||
# not fail the test: bats, like set -e, ignores the status of a ! command.)
|
||||
uncalled() {
|
||||
! called "$@"
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load test_helper
|
||||
|
||||
setup() {
|
||||
common_setup
|
||||
make_working_copy
|
||||
stub dbus-run-session '[ "$1" = -- ] && shift; exec "$@"'
|
||||
export DISPLAY=:0 WAYLAND_DISPLAY=wayland-0 DBUS_SESSION_BUS_ADDRESS=unix:path=/nowhere SSH_AUTH_SOCK=/nowhere
|
||||
}
|
||||
|
||||
@test "try runs the command in a throwaway home with the working copy as CoreDesktop" {
|
||||
run cdd try bash -c 'echo "home=$HOME"
|
||||
echo "runtime=$XDG_RUNTIME_DIR $(stat -c %a "$XDG_RUNTIME_DIR")"
|
||||
echo "display=${DISPLAY-unset} ${WAYLAND_DISPLAY-unset} ${DBUS_SESSION_BUS_ADDRESS-unset} ${SSH_AUTH_SOCK-unset}"
|
||||
echo "coredesktop=$(readlink ~/.local/share/coredesktop)"
|
||||
echo "update=$(readlink "$(command -v update)")"
|
||||
echo "config=$XDG_CONFIG_HOME"'
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"home=$TMPDIR/coredesktop-dev-try."*"/home"* ]]
|
||||
[[ "$output" == *"runtime=$TMPDIR/coredesktop-dev-try."*"/runtime 700"* ]]
|
||||
[[ "$output" == *"display=unset unset unset unset"* ]]
|
||||
[[ "$output" == *"coredesktop=$WC"* ]]
|
||||
[[ "$output" == *"update=$WC/update"* ]]
|
||||
[[ "$output" == *"config=$TMPDIR/coredesktop-dev-try."*"/home/.config"* ]]
|
||||
[[ "$output" == *"still change this machine"* ]]
|
||||
grep -q "^dbus-run-session -- bash" "$STUB_LOG"
|
||||
}
|
||||
|
||||
@test "try cleans up afterwards, and passes on the command's exit status" {
|
||||
run cdd try bash -c 'exit 4'
|
||||
[ "$status" -eq 4 ]
|
||||
[ -z "$(ls "$TMPDIR")" ]
|
||||
}
|
||||
|
||||
@test "try --keep keeps the throwaway folder" {
|
||||
run cdd try --keep true
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"Kept $TMPDIR/coredesktop-dev-try."* ]]
|
||||
[ -n "$(ls "$TMPDIR")" ]
|
||||
}
|
||||
|
||||
@test "try doesn't touch the real home" {
|
||||
run cdd try bash -c 'touch ~/made-by-try'
|
||||
[ "$status" -eq 0 ]
|
||||
[ ! -e "$HOME/made-by-try" ]
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load test_helper
|
||||
|
||||
HOOK_MARKER="# Installed by coredesktop-dev; remove with: coredesktop-dev hooks remove"
|
||||
|
||||
@test "update installs the packages and links the command, without asking anything" {
|
||||
run bash "$PROJECT/update.sh" < /dev/null
|
||||
[ "$status" -eq 0 ]
|
||||
called install_packages shellcheck bats tea qemu-base libisoburn
|
||||
[ "$(readlink "$HOME/.local/bin/coredesktop-dev")" = "$PROJECT/bin/coredesktop-dev" ]
|
||||
}
|
||||
|
||||
@test "update fixes a link that points somewhere else" {
|
||||
mkdir -p "$HOME/.local/bin"
|
||||
ln -s /nowhere "$HOME/.local/bin/coredesktop-dev"
|
||||
|
||||
run bash "$PROJECT/update.sh" < /dev/null
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$(readlink "$HOME/.local/bin/coredesktop-dev")" = "$PROJECT/bin/coredesktop-dev" ]
|
||||
}
|
||||
|
||||
@test "update leaves an unchanged pre-push hook alone" {
|
||||
make_working_copy
|
||||
cp "$PROJECT/hooks/pre-push" "$WC/.git/hooks/pre-push"
|
||||
touch -d '2020-01-01' "$WC/.git/hooks/pre-push"
|
||||
|
||||
run bash "$PROJECT/update.sh" < /dev/null
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$(date -r "$WC/.git/hooks/pre-push" +%Y)" = 2020 ]
|
||||
[[ "$output" != *"Updated the pre-push hook"* ]]
|
||||
}
|
||||
|
||||
@test "update refreshes our pre-push hook when it has changed" {
|
||||
make_working_copy
|
||||
printf '%s\n' '#!/bin/bash' "$HOOK_MARKER" 'old version' > "$WC/.git/hooks/pre-push"
|
||||
|
||||
run bash "$PROJECT/update.sh" < /dev/null
|
||||
[ "$status" -eq 0 ]
|
||||
cmp "$PROJECT/hooks/pre-push" "$WC/.git/hooks/pre-push"
|
||||
[[ "$output" == *"Updated the pre-push hook"* ]]
|
||||
}
|
||||
|
||||
@test "update doesn't touch someone else's pre-push hook" {
|
||||
make_working_copy
|
||||
printf '%s\n' '#!/bin/bash' 'mine' > "$WC/.git/hooks/pre-push"
|
||||
|
||||
run bash "$PROJECT/update.sh" < /dev/null
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$(tail -n 1 "$WC/.git/hooks/pre-push")" = mine ]
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Purpose : Keep coredesktop-dev current (run by CoreDesktop's `update`).
|
||||
# Never asks anything.
|
||||
|
||||
set -e
|
||||
|
||||
SELF="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
COREDESKTOP="$HOME/.local/share/coredesktop"
|
||||
|
||||
source "$COREDESKTOP/utils.sh"
|
||||
source "$SELF/lib/common.sh"
|
||||
|
||||
PACKAGES=(
|
||||
shellcheck # make lint
|
||||
bats # make test
|
||||
tea # Gitea's CLI, for pull requests
|
||||
qemu-base # the smoketest's virtual machines
|
||||
libisoburn # xorriso, for the smoketest VM's cloud-init disk
|
||||
)
|
||||
|
||||
echo "Checking coredesktop-dev's packages..."
|
||||
install_packages "${PACKAGES[@]}"
|
||||
|
||||
# A symlink, so each pull updates the command
|
||||
mkdir -p "$HOME/.local/bin"
|
||||
if [ "$(readlink "$HOME/.local/bin/coredesktop-dev")" != "$SELF/bin/coredesktop-dev" ]; then
|
||||
ln -sfn "$SELF/bin/coredesktop-dev" "$HOME/.local/bin/coredesktop-dev"
|
||||
echo "Linked ~/.local/bin/coredesktop-dev"
|
||||
fi
|
||||
|
||||
# Refresh the working copy's pre-push hook, if it's ours and has changed
|
||||
load_config
|
||||
if [ -n "$WORKING_COPY" ] && is_coredesktop_clone "$WORKING_COPY"; then
|
||||
source "$SELF/lib/hooks.sh"
|
||||
hook="$(hook_path)"
|
||||
if hook_is_ours "$hook" && ! cmp -s "$SELF/hooks/pre-push" "$hook"; then
|
||||
install -m 755 "$SELF/hooks/pre-push" "$hook"
|
||||
echo "Updated the pre-push hook in $WORKING_COPY"
|
||||
fi
|
||||
fi
|
||||
@@ -0,0 +1,61 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Purpose : Turn Arch's cloud image into the machine CoreDesktop's README starts
|
||||
# from: Arch with archinstall's desktop profile and GNOME. Run as root
|
||||
# in the VM while building the smoketest's base image.
|
||||
#
|
||||
# Differences from a real archinstall machine, on purpose: the user (tester)
|
||||
# logs in to GNOME automatically and has passwordless sudo, so the smoketest
|
||||
# runs without anyone at the keyboard.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
USER_NAME=tester
|
||||
|
||||
echo "== Growing / to fill the disk"
|
||||
btrfs filesystem resize max / || echo "Couldn't grow /; continuing."
|
||||
df -h /
|
||||
|
||||
echo "== Sharing the host's package cache"
|
||||
if ! grep -q '^pkgcache ' /etc/fstab; then
|
||||
echo 'pkgcache /var/cache/pacman/pkg 9p trans=virtio,version=9p2000.L,msize=524288,nofail 0 0' >> /etc/fstab
|
||||
fi
|
||||
systemctl daemon-reload
|
||||
mountpoint -q /var/cache/pacman/pkg || mount /var/cache/pacman/pkg \
|
||||
|| echo "No shared package cache; packages download into the VM."
|
||||
|
||||
echo "== Installing GNOME"
|
||||
pacman -Sy --needed --noconfirm archlinux-keyring
|
||||
# archinstall's desktop profile with GNOME, NetworkManager and PipeWire
|
||||
pacman -Su --needed --noconfirm \
|
||||
gnome gnome-tweaks gdm \
|
||||
networkmanager \
|
||||
pipewire pipewire-alsa pipewire-pulse pipewire-jack wireplumber \
|
||||
base-devel git sudo openssh nano vim htop wget smartmontools xdg-utils
|
||||
|
||||
echo "== Locale"
|
||||
sed -i 's/^#en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen
|
||||
locale-gen
|
||||
echo LANG=en_US.UTF-8 > /etc/locale.conf
|
||||
|
||||
echo "== NetworkManager instead of systemd-networkd"
|
||||
systemctl disable systemd-networkd.service systemd-networkd.socket systemd-resolved.service || true
|
||||
# NetworkManager writes its own resolv.conf; the cloud image's points at
|
||||
# systemd-resolved
|
||||
rm -f /etc/resolv.conf
|
||||
systemctl enable NetworkManager.service gdm.service sshd.service
|
||||
|
||||
echo "== Automatic login to GNOME as $USER_NAME"
|
||||
cat > /etc/gdm/custom.conf << EOF
|
||||
[daemon]
|
||||
AutomaticLoginEnable=True
|
||||
AutomaticLogin=$USER_NAME
|
||||
EOF
|
||||
|
||||
echo "== Smoketest folder"
|
||||
install -d -o "$USER_NAME" -g "$USER_NAME" /srv/smoketest
|
||||
|
||||
# cloud-init has done its job (the user, their SSH key and sudo rights)
|
||||
touch /etc/cloud/cloud-init.disabled
|
||||
|
||||
echo "== Done"
|
||||
@@ -0,0 +1,17 @@
|
||||
#cloud-config
|
||||
# The smoketest VM's user. coredesktop-dev puts its SSH key in place of @SSH_KEY@.
|
||||
users:
|
||||
- name: tester
|
||||
shell: /bin/bash
|
||||
groups: [wheel]
|
||||
sudo: "ALL=(ALL) NOPASSWD:ALL"
|
||||
lock_passwd: false
|
||||
ssh_authorized_keys:
|
||||
- @SSH_KEY@
|
||||
# For logging in at the console (SMOKETEST_VNC) when something goes wrong
|
||||
chpasswd:
|
||||
expire: false
|
||||
users:
|
||||
- name: tester
|
||||
password: tester
|
||||
type: text
|
||||
Reference in New Issue
Block a user