#!/bin/bashset -euTEST_REFERENCE_SUBDIR="${TEST_REFERENCE_SUBDIR:-test/reference}"REPO=pixel-test-referenceGITHUB_BASE="${GITHUB_BASE:-cockpit-project/cockpit}"GITHUB_REPOSITORY="${GITHUB_BASE%/*}/${REPO}"CLONE_REMOTE="https://github.com/${GITHUB_REPOSITORY}"PUSH_REMOTE="git@github.com:${GITHUB_REPOSITORY}"message() {[ "${V-}" != 0 ] || printf " %-8s %s\n" "$1" "$2"}cmd_init() {git submodule add -b empty "$CLONE_REMOTE" "$TEST_REFERENCE_SUBDIR"}cmd_update() {git submodule update --init -- "$TEST_REFERENCE_SUBDIR" || (echo ""echo "Updating test/reference has failed, maybe because of"echo "local changes that have been accidentally made while"echo "it was out of date."echo ""echo "If you want to throw away these local changes, run"echo ""echo " $ ./test/common/pixel-tests reset"echo ""exit 1)}cmd_pull() {cmd_update}cmd_status() {cmd_update( cd "$TEST_REFERENCE_SUBDIR"git rm --force --cached --quiet '*.png'git add *.pngif git diff-index --name-status --cached --exit-code HEAD; thenecho No changesfi)}cmd_push() {cmd_update( cd "$TEST_REFERENCE_SUBDIR"git rm --force --cached --quiet '*.png'git add *.pngif ! git diff-index --name-status --cached --exit-code HEAD; thengit fetch origin empty:emptygit reset --soft emptygit commit --quiet -m "$(date)"elseecho No changesfitag="sha-$(git rev-parse HEAD)"[ $(git tag -l "$tag") ] || git tag "$tag" HEAD --no-signgit push "$PUSH_REMOTE" "$tag")git add "$TEST_REFERENCE_SUBDIR"if [ -n "$(git status --porcelain "$TEST_REFERENCE_SUBDIR")" ]; thenecho ""echo "The test/reference link has changed. The next step is to commit and"echo "push this change, just like any other change to a file."echo ""echo "The change has already been added with 'git add', so you could now"echo "amend your current HEAD commit with it like this:"echo ""echo " $ git commit --amend"echo ""echo "Then the HEAD commit can be pushed like normally. There is nothing"echo "special about committing and pushing a change to test/reference."fi}cmd_reset() {rm -rf "$TEST_REFERENCE_SUBDIR"cmd_update}pixel_test_logs_urls() {urls=()prs=()for arg in "${@:-}"; doif [[ "$arg" == http* ]]; thenurls+=("$arg")elseprs+=("$arg")fidoneif [ ${#urls[@]} -gt 0 ]; thenecho "${urls[@]}"returnfirevisions=()remote=$(git remote get-url upstream || git remote get-url origin)repo=$(echo $remote | sed -re 's,git@github.com:|https://github.com/,,' -e 's,\.git$,,')if [ ${#prs[@]} -eq 0 ]; thenrevisions+=("$(git rev-parse "@{upstream}")")elsefor pr in "${prs[@]}"; dorevisions+=("$(curl -s "https://api.github.com/repos/$repo/pulls/$pr" | python3 -c "import jsonimport sysprint(json.load(sys.stdin)['head']['sha'])")")donefifor revision in "${revisions[@]}"; docontext=$(cat test/reference-image)curl -s "https://api.github.com/repos/$repo/statuses/$revision?per_page=100" | python3 -c "import jsonimport sysseen = set()for s in json.load(sys.stdin):c = s['context']if 'firefox' in c or 'devel' in c:continueif c.split('/')[0] == sys.argv[1] and s['target_url']:url=s['target_url']if url not in seen:seen.add(url)print(url)" "$context"done}cmd_fetch() {read -ra urls <<<"$(pixel_test_logs_urls "${@:-}")"if [ "${#urls[@]}" -eq 0 ]; thenecho >&2 "Can't find test results for $(cat test/reference-image), sorry."exit 1elif [ "${#urls[@]}" -ne ${#@} ]; thenecho "Something went wrong fetching the links, aborting early"exit 1ficmd_updatefor url in "${urls[@]}"; dourl=${url/\/log.html/}echo "Fetching new pixel test references from $url"pixels=$(curl -s "$url/index.html" | grep '[^=><"]*-pixels.png' -o | uniq)for f in ${pixels}; doecho "$f"curl -s --output-dir test/reference/ -O "$url/$f"donedone}cmd_help() {cat <<EOF$0 - Maintain the test/reference directoryThe following commands are available:update - Ensure that test/reference matches the checked out branchThe test/reference directory is a git submodule, and it needs tobe kept in sync with the main worktree (just like any othersubmodule). This is necessary whenever you check out a differentbranch and then start working on pixel tests, for example.You can use "git checkout --recurse-submodules" when changingbranches to achieve this, or some variation of "git submoduleupdate test/reference", or run "pixel-tests update".Most other pixel-tests commands will run "pixel-tests update"automatically, so you might not actually need to run it explicitlyvery often, but it's good to remember that this step isunfortunately necessary.Like other ways to update submodules, this command will notoverwrite your local changes, so it is safe to use often and "justin case". If your local changes conflict with the changes thatwould be necessary for updating test/reference, the update willnot be done.When "pixel-tests update" fails, it is probably easiest to throwaway local changes with "pixel-tests reset" and re-acquire the newreference images that you want to install, maybe with "pixel-testsfetch".pull - Old name for "update".reset - Throw away local changesIf you want to get rid of unpushed local changes, run "pixel-testsreset". This will remove the test/reference directory completelyand then run "pixel-tests update" to recreate it.status - Show local changesAfter writing new reference images into test/reference, running"pixel-test status" will summarize the local changes. It willlist all images that have been modified (with a "M" prefix), added("A"), or deleted ("D").The "status" command will run "update" as the first step, so itmight fail if it detects conflicts. To avoid that risk, run"update" explicitly before making any changes in test/reference.push - Upload local changes and prepare test/reference for committingOnce you have finished writing new reference images intotest/reference (maybe by running "pixel-tests fetch") and areready to make them part of your pull request, you need to uploadthem to Github and record them in the main source repository.First, run "pixel-tests push" to do the uploading and to stage achanged test/reference in the main source repository.In the main source reository, test/reference is a special kind ofobject. It's not a file, or directory, and not even a symlink(although it is similar to a symlink). It's a "gitlink". Butwhatever it is, changes to it need to be staged, committed, andpushed, just like changes to regular files.When "pixel-tests push" is done, the change to test/reference hasalready been staged with "git add" to remind you that there issomething to commit.Committing the change is identical to committing any other change."push" will also run "update" as the first step.fetch - Download fresh reference images from a test runWhen code or tests are changed, we often need to install new orchanged reference images for the pixel tests. A good way to dothat is to run the tests in our CI machinery, let them fail, andgrab the new reference images from the test log directory.Running "pixel-tests fetch" can automate this.Without any arguments, "fetch" will figure out all by itself whereto download the images from. For this to work, your "origin"remote needs to point to the repository on Github that the currentPR will be merged into. For our main Cockpit repository, thatwould be "cockpit-project/cockpit", for example.You can also pass a PR number to "fetch" if the current branchdoesn't correspond to the PR with the new pixels. Or you can passthe URL of the log results."push" will also run "update" as the first step.EOF}main() {local cmd="${1-}"if [ -z "${cmd}" ]; thenecho 'This command requires a subcommand: update status push reset fetch'echo "Run '$0 help' for a longer explanation"exit 1elif ! type -t "cmd_${cmd}" | grep -q function; thenecho "Unknown subcommand ${cmd}"exit 1fishift[ "${V-0}" = 0 ] || set -x"cmd_$cmd" "$@"}main "$@"