Skip to content

Commit 634257b

Browse files
committed
tasks: Add local mock PR run to run-local.sh
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.
1 parent b84e773 commit 634257b

File tree

2 files changed

+127
-0
lines changed

2 files changed

+127
-0
lines changed

tasks/mock-github-pr

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
#!/usr/bin/env python3
2+
# Mock GitHub API server for testing an opened PR
3+
# You can run this manually in `tasks/run-local.sh -i` with `podman cp` and running
4+
# cd bots
5+
# PYTHONPATH=. ./mock-github-pr cockpit-project/bots $(git rev-parse HEAD) &
6+
# export GITHUB_API=http://127.0.0.7:8443
7+
# PYTHONPATH=. ./mock-github-pr --print-event cockpit-project/bots $(git rev-parse HEAD) | \
8+
# ./publish-queue --amqp localhost:5671 --queue webhook
9+
#
10+
# and then two `./run-queue --amqp localhost:5671`
11+
# first to process webhook → tests-scan → public, second to actually run it
12+
13+
import argparse
14+
import json
15+
import os
16+
import tempfile
17+
18+
from task.test_mock_server import MockHandler, MockServer
19+
20+
repo = None
21+
sha = None
22+
23+
24+
class Handler(MockHandler):
25+
def do_GET(self):
26+
if self.path in self.server.data:
27+
self.replyJson(self.server.data[self.path])
28+
elif self.path.startswith(f'/repos/{repo}/pulls?'):
29+
self.replyJson([self.server.data[f'/repos/{repo}/pulls/1']])
30+
elif self.path == f'/{repo}/{sha}/.cockpit-ci/container':
31+
self.replyData('quay.io/cockpit/tasks')
32+
else:
33+
self.send_error(404, 'Mock Not Found: ' + self.path)
34+
35+
def do_POST(self):
36+
if self.path.startswith(f'/repos/{repo}/statuses/{sha}'):
37+
self.replyJson({})
38+
else:
39+
self.send_error(405, 'Method not allowed: ' + self.path)
40+
41+
42+
argparser = argparse.ArgumentParser()
43+
argparser.add_argument('--port', type=int, default=8443, help="Port to listen on (default: %(default)s)")
44+
argparser.add_argument('--print-event', action='store_true', help="Print GitHub webhook pull_request event and exit")
45+
argparser.add_argument('repo', metavar='USER/PROJECT', help="GitHub user/org and project name")
46+
argparser.add_argument('sha', help="SHA to test in repo for the mock PR")
47+
args = argparser.parse_args()
48+
repo = args.repo
49+
sha = args.sha
50+
51+
ADDRESS = ('127.0.0.7', args.port)
52+
53+
GITHUB_DATA = {
54+
f'/repos/{repo}/pulls/1': {
55+
'title': 'mock PR',
56+
'number': 1,
57+
'state': 'open',
58+
'body': "This is the body",
59+
'base': {'repo': {'full_name': repo}, 'ref': 'main'},
60+
'head': {'sha': args.sha, 'user': {'login': repo.split('/')[0]}},
61+
'labels': [],
62+
'updated_at': 0,
63+
},
64+
f'/repos/{repo}/commits/{args.sha}/status?page=1&per_page=100': {
65+
'state': 'pending',
66+
'statuses': [],
67+
'sha': sha,
68+
},
69+
}
70+
71+
if args.print_event:
72+
print(json.dumps({
73+
'event': 'pull_request',
74+
'request': {
75+
'action': 'opened',
76+
'pull_request': GITHUB_DATA[f'/repos/{repo}/pulls/1']
77+
}
78+
}, indent=4))
79+
exit(0)
80+
81+
temp = tempfile.TemporaryDirectory()
82+
cache_dir = os.path.join(temp.name, 'cache')
83+
os.environ['XDG_CACHE_HOME'] = cache_dir
84+
server = MockServer(ADDRESS, Handler, GITHUB_DATA)
85+
server.start()
86+
print(f'export GITHUB_API=http://{ADDRESS[0]}:{ADDRESS[1]}')

tasks/run-local.sh

+41
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,45 @@ test_image() {
234234
'
235235
}
236236

237+
test_mock_pr() {
238+
podman cp "$MYDIR/mock-github-pr" cockpituous-tasks:/work/bots/mock-github-pr
239+
podman exec -i cockpituous-tasks sh -euxc "
240+
cd bots
241+
# test mock PR against our checkout, so that cloning will work
242+
SHA=\$(git rev-parse HEAD)
243+
244+
# start mock GH server
245+
PYTHONPATH=. ./mock-github-pr cockpit-project/bots \$SHA &
246+
GH_MOCK_PID=\$!
247+
export GITHUB_API=http://127.0.0.7:8443
248+
until curl --silent \$GITHUB_API; do sleep 0.1; done
249+
250+
# simulate GitHub webhook event, put that into the webhook queue
251+
PYTHONPATH=. ./mock-github-pr --print-event cockpit-project/bots \$SHA | \
252+
./publish-queue --amqp $AMQP_POD --create --queue webhook
253+
254+
./inspect-queue --amqp $AMQP_POD
255+
256+
# first run-queue processes webhook → tests-scan → public queue
257+
./run-queue --amqp $AMQP_POD
258+
./inspect-queue --amqp $AMQP_POD
259+
260+
# second run-queue actually runs the test
261+
./run-queue --amqp $AMQP_POD
262+
263+
kill \$GH_MOCK_PID
264+
"
265+
266+
LOGS_URL="$S3_URL_HOST/logs/"
267+
CURL="curl --cacert $SECRETS/ca.pem --silent --fail --show-error"
268+
LOG_MATCH="$($CURL $LOGS_URL| grep -o "pull-1-[[:alnum:]-]*-unit-tests/log<")"
269+
LOG="$($CURL "${LOGS_URL}${LOG_MATCH%<}")"
270+
echo "--------------- mock PR test log -----------------"
271+
echo "$LOG"
272+
echo "--------------- mock PR test log end -------------"
273+
assert_in 'Test run finished' "$LOG"
274+
}
275+
237276
test_pr() {
238277
# need to use real GitHub token for this
239278
[ -z "$TOKEN" ] || cp -fv "$TOKEN" "$SECRETS"/webhook/.config--github-token
@@ -313,6 +352,8 @@ else
313352
# tests which don't need GitHub interaction
314353
test_image
315354
test_queue
355+
# "almost" end-to-end, starting with GitHub webhook JSON payload injection; fully localy, no privs
356+
test_mock_pr
316357
# if we have a PR number, run a unit test inside local deployment, and update PR status
317358
[ -z "$PR" ] || test_pr
318359
fi

0 commit comments

Comments
 (0)