1
0
Fork 0
mirror of https://code.forgejo.org/actions/cascading-pr synced 2025-03-15 06:46:59 +01:00
cascading-pr/tests/run.sh
Earl Warren 0c3c8b591b
allow running on a ref instead of a PR
When running from a PR, the ref is the head of the PR in the origin
repository. The PR is used to:

* display comments about the location of the destination PR
* asynchronously close / merge the destination PR when something
  happens in the origin PR

When only the ref is available, the destination PR must be closed and
the corresponding branch destroyed immediately after it concludes
because there is no convenient way to know what or when something
else will happen.
2024-01-03 16:12:23 +01:00

498 lines
16 KiB
Bash
Executable file

#!/bin/bash
# SPDX-License-Identifier: MIT
set -e
set -o posix
SELF_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
TMPDIR=/tmp/cascading-pr-test
export LOOP_DELAY=5
mkdir -p $TMPDIR
source $SELF_DIR/../cascading-pr-lib.sh
function push_self() {
log_verbose "push cascading-pr action to user1/cascading-pr"
forgejo-test-helper.sh push_self_action http://user1:admin1234@${options[host_port]} user1 cascading-pr vTest
}
function user_login() {
local username=$1
(
export DOT=$TMPDIR/$username
forgejo-curl.sh logout
forgejo-curl.sh --user $username --password "${options[password]}" login ${options[url]}
)
}
function user_curl() {
local username=$1
shift
DOT=$TMPDIR/$username forgejo-curl.sh "$@"
}
function user_token() {
local username=$1 name=$2
curl -sS -f -H Content-Type:application/json --user "$username:${options[password]}" --data '{"name":"'$name'","scopes":["write:repository","write:issue","read:organization","read:user"]}' ${options[url]}/api/v1/users/$username/tokens | jq --raw-output .sha1 | tee $TMPDIR/$username/repo-token
}
function user_secret() {
local username=$1 name=$2 token=$3
log_verbose "(re)set ${username} secret ${name}"
user_curl $username api_json -X PUT --data '{"data":"'$token'"}' ${options[url]}/api/v1/user/actions/secrets/$name
}
function orgs_delete() {
forgejo-curl.sh api_json ${options[url]}/api/v1/orgs | jq --raw-output '.[] | .name' | while read owner ; do
forgejo-curl.sh api_json ${options[url]}/api/v1/orgs/$owner/repos | jq --raw-output '.[] | .name' | while read repo ; do
forgejo-curl.sh api_json -X DELETE ${options[url]}/api/v1/repos/$owner/$repo
done
forgejo-curl.sh api_json -X DELETE ${options[url]}/api/v1/orgs/$owner
done
}
function user_create() {
local username="$1" email="$2"
log_verbose "(re)create user $username"
forgejo-curl.sh api_json -X DELETE ${options[url]}/api/v1/admin/users/$username?purge=true >& /dev/null || true
forgejo-curl.sh api_json --data '{"username":"'$username'","email":"'$email'","password":"'${options[password]}'","must_change_password":false}' ${options[url]}/api/v1/admin/users
user_login $username
}
function close_pull_request() {
local repo=$1
log_verbose "close all pull requests in user1/$repo"
forgejo-curl.sh api_json ${options[url]}/api/v1/repos/user1/${repo}/pulls | jq --raw-output '.[] | .number' | while read pr ; do
forgejo-curl.sh api_json -X PATCH --data '{"state":"closed"}' ${options[url]}/api/v1/repos/user1/${repo}/issues/$pr
done
}
function merge_pull_request() {
local repo=$1
log_verbose "merge all pull requests in user1/$repo"
forgejo-curl.sh api_json ${options[url]}/api/v1/repos/user1/${repo}/pulls | jq --raw-output '.[] | .number' | while read pr ; do
forgejo-curl.sh api_json --data '{"Do":"merge"}' ${options[url]}/api/v1/repos/user1/${repo}/pulls/$pr/merge
done
}
function has_cascade_pull_request() {
local repo="${1:-destinationrepo}"
log_verbose "verify an open cascade pull request exists"
test "$(cascade_open_pull_request_count $repo)" -gt 0
}
function has_no_cascade_pull_request() {
local repo="${1:-destinationrepo}"
log_verbose "verify there is no open cascade pull request"
test "$(cascade_open_pull_request_count $repo)" = 0
}
function cascade_open_pull_request_count() {
local repo="$1"
cascade_pull_request $repo | jq '[ .[] | select(.state == "open") ] | length'
}
function cascade_pull_request_count() {
local repo="$1"
cascade_pull_request $repo | jq '. | length'
}
function cascade_pull_request() {
local repo="$1"
forgejo-curl.sh api_json ${options[url]}/api/v1/repos/user2/${repo}/pulls
}
function create_branch1() {
local owner=$1 repo=$2 modify=$3
log_verbose "(re)create branch1 in $owner/$repo"
forgejo-curl.sh api_json -X DELETE ${options[url]}/api/v1/repos/$owner/${repo}/branches/branch1 >& /dev/null || true
forgejo-curl.sh api_json --data '{"new_branch_name":"branch1"}' ${options[url]}/api/v1/repos/$owner/${repo}/branches
(
cd $TMPDIR
rm -fr ${repo}
git clone -b branch1 http://user1:admin1234@${options[host_port]}/$owner/${repo}
cd ${repo}
echo CONTENT > README
if test "$modify" ; then
log_verbose "modify branch1 in $owner/$repo with $modify"
$modify
fi
git config user.email root@example.com
git config user.name username
git add .
git commit -m 'update'
git push origin branch1
git rev-parse HEAD > ../${owner}-${repo}.sha
)
}
function delete_pull_requests() {
local owner=$1 repo=$2
forgejo-curl.sh api_json ${options[url]}/api/v1/repos/$owner/${repo}/pulls | jq --raw-output '.[] | .number' | while read pr ; do
forgejo-curl.sh api_json -X DELETE ${options[url]}/api/v1/repos/$owner/${repo}/issues/$pr
done
}
function create_pull_request() {
local baseowner=$1 headowner=$2 repo=$3
local head
if test $baseowner == $headowner; then
head=branch1
else
head=$headowner:branch1
fi
cat > $TMPDIR/data <<EOF
{"title":"PR","base":"main","head":"$head"}
EOF
log_verbose "create pull request in $baseowner/$repo from $head"
forgejo-curl.sh api_json --data @$TMPDIR/data ${options[url]}/api/v1/repos/$baseowner/${repo}/pulls
}
function create_pull_request_case1() {
local baseowner=$1 headowner=$2 repo=$3
delete_pull_requests $baseowner $repo
create_branch1 $headowner $repo
create_pull_request $baseowner $headowner $repo
}
function unit_finalize_options() {
sanity_check_pr_or_ref A B || test $? = 1
sanity_check_pr_or_ref '' '' || test $? = 2
sanity_check_pr_or_ref A ''
sanity_check_pr_or_ref '' B
}
function unit_retry_fail() {
local file=$1
local value=$(cat $file)
expr $value - 1 > $file
echo RESULT
if test $value -gt 0 ; then
return 1
else
return 0
fi
}
function unit_retry() {
local two=$TMPDIR/unit_retry_two
rm -f $TMPDIR/retry.*
#
# Succeeds after two tries
#
echo 2 > $TMPDIR/unit_retry_two
if ! RETRY_DELAYS='1 1 1 1' retry unit_retry_fail $two > $TMPDIR/retry.test-result 2> $TMPDIR/retry.test-log ; then
cat $TMPDIR/retry.test-result $TMPDIR/retry.test-log
return 1
fi
test "$(cat $TMPDIR/retry.test-result)" = "RESULT"
if test "$(grep -c '^waiting 1 unit_retry_fail' $TMPDIR/retry.test-log)" != 2 ; then
cat $TMPDIR/retry.test-log
return 1
fi
#
# Succeeds immediately
#
if ! RETRY_DELAYS='1' retry unit_retry_fail $two > $TMPDIR/retry.test-result 2> $TMPDIR/retry.test-log ; then
cat $TMPDIR/retry.test-result $TMPDIR/retry.test-log
return 1
fi
test "$(cat $TMPDIR/retry.test-result)" = "RESULT"
if test "$(grep -c 'waiting 1 unit_retry_fail' $TMPDIR/retry.test-log)" != 0 ; then
cat $TMPDIR/retry.test-log
return 1
fi
#
# Verify the output is only the output of the last run and is not polluted by
# unrelated output
#
echo 2 > $TMPDIR/unit_retry_two
test "$(RETRY_DELAYS='1 1 1' retry unit_retry_fail $two)" = "RESULT"
#
# Fails after one try
#
echo 2 > $TMPDIR/unit_retry_two
if RETRY_DELAYS='1' retry unit_retry_fail $two > $TMPDIR/retry.test-result 2> $TMPDIR/retry.test-log ; then
cat $TMPDIR/retry.test-result $TMPDIR/retry.test-log
return 1
fi
grep --quiet 'retry failed' $TMPDIR/retry.test-log
}
function fixture() {
local origin=$1
local destination=$2
orgs_delete
user_create user2 user2@example.com
log_verbose push tests/${destination} repository to user2/${destination}
forgejo-test-helper.sh push tests/${destination} http://user2:admin1234@${options[host_port]} user2 ${destination}
user_create user3 user3@example.com
log_verbose create organization destination-fork
user_curl user3 api_json --data '{"username":"destination-fork"}' ${options[url]}/api/v1/orgs
user_create user1 user1@example.com
log_verbose push tests/${origin} repository to user1/${origin}
forgejo-test-helper.sh push tests/${origin} http://user1:admin1234@${options[host_port]} user1 ${origin} cascading-pr
log_verbose create organization origin-fork
user_curl user1 api_json --data '{"username":"origin-fork"}' ${options[url]}/api/v1/orgs
log_verbose fork user1/${origin} to origin-fork/${origin}
user_curl user1 api_json --data '{"organization":"origin-fork"}' ${options[url]}/api/v1/repos/user1/${origin}/forks
user_secret user1 ORIGIN_TOKEN $(user_token user1 ORIGIN_TOKEN)
push_self
}
function no_change_no_cascade_pr() {
fixture originrepo-do-nothing destinationrepo
user_secret user1 DESTINATION_TOKEN $(user_token user2 DESTINATION_TOKEN)
create_pull_request_case1 user1 user1 originrepo-do-nothing
wait_success ${options[url]}/api/v1/repos/user1/originrepo-do-nothing $(cat $TMPDIR/user1-originrepo-do-nothing.sha)
has_no_cascade_pull_request
}
function branch_and_success() {
local origin_repo=origin-branch
local destination_repo=destinationrepo
fixture ${origin_repo} ${destination_repo}
user_secret user1 DESTINATION_TOKEN $(user_token user2 DESTINATION_TOKEN)
test $(cascade_pull_request_count ${destination_repo}) = 0
create_branch1 user1 ${origin_repo}
wait_success ${options[url]}/api/v1/repos/user1/${origin_repo} $(cat $TMPDIR/user1-${origin_repo}.sha)
test $(cascade_pull_request_count ${destination_repo}) = 1
has_no_cascade_pull_request ${destination_repo}
}
function branch_and_fail() {
local origin_repo=origin-branch-fail
local destination_repo=destination-fail
fixture ${origin_repo} ${destination_repo}
user_secret user1 DESTINATION_TOKEN $(user_token user2 DESTINATION_TOKEN)
test $(cascade_pull_request_count ${destination_repo}) = 0
create_branch1 user1 ${origin_repo}
wait_failure ${options[url]}/api/v1/repos/user1/${origin_repo} $(cat $TMPDIR/user1-${origin_repo}.sha)
test $(cascade_pull_request_count ${destination_repo}) = 1
has_no_cascade_pull_request ${destination_repo}
}
function create_and_close() {
fixture originrepo destinationrepo
user_secret user1 DESTINATION_TOKEN $(user_token user2 DESTINATION_TOKEN)
create_pull_request_case1 user1 user1 originrepo
wait_success ${options[url]}/api/v1/repos/user1/originrepo $(cat $TMPDIR/user1-originrepo.sha)
has_cascade_pull_request
close_pull_request originrepo
wait_success ${options[url]}/api/v1/repos/user1/originrepo $(cat $TMPDIR/user1-originrepo.sha)
}
function taint_update() {
if ! test -f upgraded ; then
echo upgraded file not found
return 1
fi
echo 'TAINTED' > upgraded
}
function create_from_origin_fork_and_close() {
fixture originrepo destinationrepo
user_secret user1 DESTINATION_TOKEN $(user_token user2 DESTINATION_TOKEN)
delete_pull_requests user1 originrepo
create_branch1 origin-fork originrepo taint_update
create_pull_request user1 origin-fork originrepo
wait_success ${options[url]}/api/v1/repos/user1/originrepo $(cat $TMPDIR/origin-fork-originrepo.sha)
has_cascade_pull_request
close_pull_request originrepo
wait_success ${options[url]}/api/v1/repos/user1/originrepo $(cat $TMPDIR/origin-fork-originrepo.sha)
}
function create_and_merge() {
fixture originrepo destinationrepo
user_secret user1 DESTINATION_TOKEN $(user_token user2 DESTINATION_TOKEN)
create_pull_request_case1 user1 user1 originrepo
wait_success ${options[url]}/api/v1/repos/user1/originrepo $(cat $TMPDIR/user1-originrepo.sha)
has_cascade_pull_request
merge_pull_request originrepo
wait_success ${options[url]}/api/v1/repos/user1/originrepo $(cat $TMPDIR/user1-originrepo.sha)
has_cascade_pull_request
}
function create_in_destination_fork_and_close() {
fixture origin-fork-destination destinationrepo
user_secret user1 DESTINATION_TOKEN $(user_token user3 DESTINATION_TOKEN)
create_pull_request_case1 user1 user1 origin-fork-destination
wait_success ${options[url]}/api/v1/repos/user1/origin-fork-destination $(cat $TMPDIR/user1-origin-fork-destination.sha)
has_cascade_pull_request
close_pull_request origin-fork-destination
wait_success ${options[url]}/api/v1/repos/user1/origin-fork-destination $(cat $TMPDIR/user1-origin-fork-destination.sha)
}
function create_in_existing_destination_fork_and_close() {
fixture origin-organization-fork-destination destinationrepo
user_secret user1 DESTINATION_TOKEN $(user_token user3 DESTINATION_TOKEN)
log_verbose fork user2/destinationrepo to destination-fork/destinationrepo
user_curl user3 api_json --data '{"organization":"destination-fork"}' ${options[url]}/api/v1/repos/user2/destinationrepo/forks
create_pull_request_case1 user1 user1 origin-organization-fork-destination
wait_success ${options[url]}/api/v1/repos/user1/origin-organization-fork-destination $(cat $TMPDIR/user1-origin-organization-fork-destination.sha)
has_cascade_pull_request
close_pull_request origin-organization-fork-destination
wait_success ${options[url]}/api/v1/repos/user1/origin-organization-fork-destination $(cat $TMPDIR/user1-origin-organization-fork-destination.sha)
}
function create_in_organization_destination_fork_and_close() {
fixture origin-organization-fork-destination destinationrepo
user_secret user1 DESTINATION_TOKEN $(user_token user3 DESTINATION_TOKEN)
create_pull_request_case1 user1 user1 origin-organization-fork-destination
wait_success ${options[url]}/api/v1/repos/user1/origin-organization-fork-destination $(cat $TMPDIR/user1-origin-organization-fork-destination.sha)
has_cascade_pull_request
close_pull_request origin-organization-fork-destination
wait_success ${options[url]}/api/v1/repos/user1/origin-organization-fork-destination $(cat $TMPDIR/user1-origin-organization-fork-destination.sha)
}
function create_and_merge_close() {
fixture originrepo-close-merge destinationrepo
user_secret user1 DESTINATION_TOKEN $(user_token user2 DESTINATION_TOKEN)
create_pull_request_case1 user1 user1 originrepo-close-merge
wait_success ${options[url]}/api/v1/repos/user1/originrepo-close-merge $(cat $TMPDIR/user1-originrepo-close-merge.sha)
has_cascade_pull_request
merge_pull_request originrepo-close-merge
wait_success ${options[url]}/api/v1/repos/user1/originrepo-close-merge $(cat $TMPDIR/user1-originrepo-close-merge.sha)
has_no_cascade_pull_request
}
function run() {
local fun=$1
shift
echo "Start running $fun ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
if $DEBUG ; then
$fun "$@"
else
mkdir -p $TMPDIR
> $TMPDIR/$fun.out
tail --follow $TMPDIR/$fun.out | sed --unbuffered -n -e "/^$PREFIX/s/^$PREFIX //p" &
pid=$!
if ! ${BASH_SOURCE[0]} --debug ${options[args]} $fun "$@" >& $TMPDIR/$fun.out ; then
kill $pid
cat $TMPDIR/$fun.out
echo Failure running $fun
return 1
fi
kill $pid
fi
echo "Success running $fun ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
}
function integration() {
run branch_and_success
run no_change_no_cascade_pr
run create_in_destination_fork_and_close
run create_and_close
run create_from_origin_fork_and_close
run create_and_merge
run create_in_destination_fork_and_close
run create_in_existing_destination_fork_and_close
run create_in_organization_destination_fork_and_close
run create_and_merge_close
}
function unit() {
unit_finalize_options
unit_retry
}
function run_tests() {
unit
integration
}
function finalize_options() {
if test -f $DIR/forgejo-ip; then
: ${options[host_port]:=$(cat $DIR/forgejo-ip):3000}
fi
options[url]=http://${options[host_port]}
if test -f $DIR/forgejo-token; then
: ${options[token]:=$(cat $DIR/forgejo-token)}
fi
options[password]=admin1234
}
function main() {
local command=run
while true; do
case "$1" in
--verbose)
shift
verbose
;;
--debug)
shift
debug
;;
--host_port)
shift
options[args]="${options[args]} --host_port $1"
options[host_port]=$1
shift
;;
--url)
shift
options[args]="${options[args]} --url $1"
options[url]=$1
shift
;;
--token)
shift
options[args]="${options[args]} --token $1"
options[token]=$1
shift
;;
*)
finalize_options
"${1:-run_tests}" "${@:2}"
return 0
;;
esac
done
}
dependencies
${MAIN:-main} "${@}"