Skip to content

Commit

Permalink
tasks: Add local mock PR run to run-local.sh
Browse files Browse the repository at this point in the history
This is an "almost end to end" test which runs fully locally and requires no
GitHub interaction or token. Add a little mock GitHub server (utilizing bots'
`task.test_mock_server`) to respond to the required GitHub APIs for handling an
"opened PR" webhook event.

This is useful for playing around with our AMQP queue/job execution engine
locally (when using the mock GH server in interactive mode), or
validating changes to run-queue/AMQP structure.
  • Loading branch information
martinpitt committed Mar 4, 2024
1 parent 0200fdd commit b0f9a4c
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 0 deletions.
85 changes: 85 additions & 0 deletions tasks/mock-github-pr
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#!/usr/bin/env python3
# Mock GitHub API server for testing an opened PR
# You can run this manually in `tasks/run-local.sh -i` with:
# PYTHONPATH=. ./mock-github-pr cockpit-project/bots $(git rev-parse HEAD) &
# export GITHUB_API=http://127.0.0.7:8443
# PYTHONPATH=. ./mock-github-pr --print-event cockpit-project/bots $(git rev-parse HEAD) | \
# ./publish-queue --amqp localhost:5671 --queue webhook
#
# and then two `./run-queue --amqp localhost:5671`
# first to process webhook → tests-scan → public, second to actually run it

import argparse
import json
import os
import tempfile

from task.test_mock_server import MockHandler, MockServer

repo = None
sha = None


class Handler(MockHandler):
def do_GET(self):
if self.path in self.server.data:
self.replyJson(self.server.data[self.path])
elif self.path.startswith(f'/repos/{repo}/pulls?'):
self.replyJson([self.server.data[f'/repos/{repo}/pulls/1']])
elif self.path == f'/{repo}/{sha}/.cockpit-ci/container':
self.replyData('quay.io/cockpit/tasks')
else:
self.send_error(404, 'Mock Not Found: ' + self.path)

def do_POST(self):
if self.path.startswith(f'/repos/{repo}/statuses/{sha}'):
self.replyJson({})
else:
self.send_error(405, 'Method not allowed: ' + self.path)


argparser = argparse.ArgumentParser()
argparser.add_argument('--port', type=int, default=8443, help="Port to listen on (default: %(default)s)")
argparser.add_argument('--print-event', action='store_true', help="Print GitHub webhook pull_request event and exit")
argparser.add_argument('repo', metavar='USER/PROJECT', help="GitHub user/org and project name")
argparser.add_argument('sha', help="SHA to test in repo for the mock PR")
args = argparser.parse_args()
repo = args.repo
sha = args.sha

ADDRESS = ('127.0.0.7', args.port)

GITHUB_DATA = {
f'/repos/{repo}/pulls/1': {
'title': 'mock PR',
'number': 1,
'state': 'open',
'body': "This is the body",
'base': {'repo': {'full_name': repo}, 'ref': 'main'},
'head': {'sha': args.sha, 'user': {'login': repo.split('/')[0]}},
'labels': [],
'updated_at': 0,
},
f'/repos/{repo}/commits/{args.sha}/status?page=1&per_page=100': {
'state': 'pending',
'statuses': [],
'sha': sha,
},
}

if args.print_event:
print(json.dumps({
'event': 'pull_request',
'request': {
'action': 'opened',
'pull_request': GITHUB_DATA[f'/repos/{repo}/pulls/1']
}
}, indent=4))
exit(0)

temp = tempfile.TemporaryDirectory()
cache_dir = os.path.join(temp.name, 'cache')
os.environ['XDG_CACHE_HOME'] = cache_dir
server = MockServer(ADDRESS, Handler, GITHUB_DATA)
server.start()
print(f'export GITHUB_API=http://{ADDRESS[0]}:{ADDRESS[1]}')
41 changes: 41 additions & 0 deletions tasks/run-local.sh
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,45 @@ test_image() {
'
}

test_mock_pr() {
podman cp "$MYDIR/mock-github-pr" cockpituous-tasks:/work/bots/mock-github-pr
podman exec -i cockpituous-tasks sh -euxc "
cd bots
# test mock PR against our checkout, so that cloning will work
SHA=\$(git rev-parse HEAD)
# start mock GH server
PYTHONPATH=. ./mock-github-pr cockpit-project/bots \$SHA &
GH_MOCK_PID=\$!
export GITHUB_API=http://127.0.0.7:8443
until curl --silent \$GITHUB_API; do sleep 0.1; done
# simulate GitHub webhook event, put that into the webhook queue
PYTHONPATH=. ./mock-github-pr --print-event cockpit-project/bots \$SHA | \
./publish-queue --amqp $AMQP_POD --queue webhook
./inspect-queue --amqp $AMQP_POD
# first run-queue processes webhook → tests-scan → public queue
./run-queue --amqp localhost:5671
./inspect-queue --amqp $AMQP_POD
# second run-queue actually runs the test
./run-queue --amqp localhost:5671
kill \$GH_MOCK_PID
"

LOGS_URL="$S3_URL_HOST/logs/"
CURL="curl --cacert $SECRETS/ca.pem --silent --fail --show-error"
LOG_MATCH="$($CURL $LOGS_URL| grep -o "pull-1-[[:alnum:]-]*-unit-tests/log<")" && break
LOG="$($CURL "${LOGS_URL}${LOG_MATCH%<}")"
echo "--------------- mock PR test log -----------------"
echo "$LOG"
echo "--------------- mock PR test log end -------------"
assert_in 'Test run finished' "$LOG"
}

test_pr() {
# need to use real GitHub token for this
[ -z "$TOKEN" ] || cp -fv "$TOKEN" "$SECRETS"/webhook/.config--github-token
Expand Down Expand Up @@ -310,6 +349,8 @@ else
# tests which don't need GitHub interaction
test_image
test_queue
# "almost" end-to-end, starting with GitHub webhook JSON payload injection; fully localy, no privs
test_mock_pr
# if we have a PR number, run a unit test inside local deployment, and update PR status
[ -z "$PR" ] || test_pr
fi
Expand Down

0 comments on commit b0f9a4c

Please sign in to comment.