Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Prod Testing Workflows #10061

Draft
wants to merge 48 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
09393fe
Try adding a release test job
oskirby Nov 21, 2024
f67600d
Try to install the VPN client
oskirby Nov 21, 2024
c401ff1
YOLO: Lets run some tests
oskirby Nov 21, 2024
87b540a
Fix the addon build
oskirby Nov 21, 2024
103e84c
Cache addons
oskirby Nov 21, 2024
5a9b3c6
Remove ARTIFACT_DIR
oskirby Nov 21, 2024
bc8dfd0
YOLO: Lets do MacOS too
oskirby Nov 21, 2024
79a8205
Try making the functional tests a resuable workflow
oskirby Nov 21, 2024
f0fcbce
Handle taskcluster retries
oskirby Nov 21, 2024
4589110
YOLO: Lets try windows too
oskirby Nov 21, 2024
18804c9
Try to fix duplicate addons artifact
oskirby Nov 21, 2024
13b8e23
Actually - we want repackage-msi
oskirby Nov 21, 2024
e83f400
Try to run with xvfb-run on Linux hosts
oskirby Nov 21, 2024
7037e9d
Try to fix msiexec and xvfb-run usage
oskirby Nov 21, 2024
15c43a2
Use a concurrency group
oskirby Nov 21, 2024
f8040b0
Try installing MSI with powershell
oskirby Nov 21, 2024
8b6d6cc
Typo
oskirby Nov 21, 2024
7b5af97
Fix bad argument to msiexec
oskirby Nov 21, 2024
f36868f
Try again
oskirby Nov 21, 2024
0f98a51
More debug
oskirby Nov 21, 2024
f6382ec
Try creating the log file first
oskirby Nov 21, 2024
b5d3f64
Try again to install MSIs via bash
oskirby Nov 21, 2024
8b2a410
Syntax error
oskirby Nov 21, 2024
f409a3b
Back to powershell for Windows
oskirby Nov 21, 2024
9b0e255
Terminate VPN client after MSI install
oskirby Nov 22, 2024
1f33077
Try to use tasks from current commit
oskirby Nov 22, 2024
383c7bb
Fix URL with github.api_url
oskirby Nov 22, 2024
0d1a65b
Another URL bug
oskirby Nov 22, 2024
92b28aa
Maybe github.event.pull_request.head.sha is what we need
oskirby Nov 22, 2024
842277f
Oops, typo
oskirby Nov 22, 2024
c9160a5
Fix taskgraph URLs
oskirby Nov 22, 2024
a670e11
Must use unsigned builds on pull_request
oskirby Nov 22, 2024
4911194
Add support for .tar.gz archives
oskirby Nov 22, 2024
c1b3b91
Bump build-linux64/reease-deb worker to b-linux-large
oskirby Nov 22, 2024
0f6fe4f
Fix path oops in tar extraction
oskirby Nov 23, 2024
b23fab2
Fix duplicate tar flags
oskirby Nov 23, 2024
3f25aba
We need to await the decision task
oskirby Nov 23, 2024
b9c36ba
Refactor taskcluster artifact download into a composite action
oskirby Nov 23, 2024
63c1080
I guess we do have to checkout after all
oskirby Nov 23, 2024
b883ae7
Huh? Make it a little closer to the example
oskirby Nov 23, 2024
45b9e2e
Fix args in composite action
oskirby Nov 23, 2024
7b440f4
Once more with feeling
oskirby Nov 23, 2024
8038659
So many typos, I should be emberassed
oskirby Nov 23, 2024
a9a4cc4
Rebase and remove xvfb-run hack for Linux
oskirby Nov 25, 2024
e3b39f3
Try linking to qoffscreen plugin for static builds
oskirby Dec 2, 2024
573e7bd
Fix plugin import
oskirby Dec 2, 2024
94d8f75
Link QOffscreenIntegrationPlugin for Linux only
oskirby Dec 2, 2024
84e6613
Some debug
oskirby Dec 3, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/actions/taskcluster-artifact/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Download taskcluster artifact
descrion: Download artifacts from a Taskcluster task
inputs:
taskid:
description: 'Taskcluster task identifier to fetch artifacts from'
required: true
type: string
artifact:
description: "Artifact to download from the task"
required: true
type: string
path:
description: Location to save the artifact
default: ${{ github.workspace }}
type: string

runs:
using: "composite"
steps:
- name: Wait for taskcluster job
id: taskgraph
shell: bash
run: |
pip install requests
${{ github.action_path }}/await-taskcluster-status.py -t 3600 ${{ inputs.taskid }} | tee ${{ runner.temp }}/taskcluster-job-status.json
echo run-id=$(jq '.runs[] | select(.state == "completed").runId' ${{ runner.temp }}/taskcluster-job-status.json) >> $GITHUB_OUTPUT

- name: Download build artifact
shell: bash
env:
TASKGRAPH_TASK_URL: https://firefox-ci-tc.services.mozilla.com/api/queue/v1/task/${{ inputs.taskid }}
run: |
curl -sSL -o ${{ inputs.path }}/$(basename ${{ inputs.artifact }}) \
${TASKGRAPH_TASK_URL}/runs/${{ steps.taskgraph.outputs.run-id }}/artifacts/${{ inputs.artifact }}
59 changes: 59 additions & 0 deletions .github/actions/taskcluster-artifact/await-taskcluster-status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/usr/bin/env python
import argparse
import requests
import json
import os
import sys
import time

def get_job_status(taskid, api):
# Request the job status from taskcluster
r = requests.post(api, json={"taskIds": [taskid]})
if r.status_code != 200:
r.raise_for_status()

# We only requested one job status, so return it.
return r.json()["statuses"]

if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Await a external check to complete")
parser.add_argument("taskid", metavar="TASKID", type=str, action="store",
help="Task ID to wait for")
parser.add_argument("-i", "--interval", metavar="SEC", type=int, action="store", default=15,
help="Interval between retries")
parser.add_argument("-t", "--timeout", metavar="SEC", type=int, action="store", default=300,
help="Task timeout in seconds")
parser.add_argument("-a", "--api", metavar="URL", type=str, action="store",
default='https://firefox-ci-tc.services.mozilla.com/api/queue/v1/tasks/status',
help="Taskgraph status API")
args = parser.parse_args()

expiration = time.monotonic() + args.timeout
while time.monotonic() < expiration:
try:
js = get_job_status(args.taskid, args.api)
if len(js) == 0:
raise Exception("not found")
status = js[0]["status"]
except Exception as e:
status = {
"taskId": args.taskid,
"state": str(e),
"runs": [],
}

if status["state"] == 'completed':
print(f'Task {args.taskid} - {status["state"]} - exiting', file=sys.stderr)
print(f'{json.dumps(status, indent=3)}')
sys.exit(0)

# Wait and try again
maxsleep = expiration - time.monotonic()
if maxsleep > args.interval:
maxsleep = args.interval

print(f'Task {args.taskid} - {status["state"]} - retrying in {maxsleep}', file=sys.stderr)
time.sleep(maxsleep)

# Otherwise a timeout occurred.
sys.exit(1)
51 changes: 51 additions & 0 deletions .github/scripts/await-taskcluster-job.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/usr/bin/env python3
import argparse
import requests
import json
import os
import sys

GITHUB_TOKEN = os.environ.get('GITHUB_TOKEN')

def fetch(url, version='2022-11-28', timeout=None):
headers = {
'Accept': 'application/vnd.github+json',
'X-GitHub-Api-Version': version,
}
if GITHUB_TOKEN is not None:
headers['Authorization'] = f"Bearer {GITHUB_TOKEN}"

r = requests.get(url, headers)
if r.status_code != 200:
r.raise_for_status()
return r.json()

if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Await a external check to complete")
parser.add_argument("ref", metavar="REF", type=str, action="store",
help="Github reference fetch checks for")
parser.add_argument("name", metavar="NAME", type=str, action="store",
help="Name of the check to wait for")
parser.add_argument("-a", "--app", metavar="SUITE", type=str, action="store",
help="App name to fetch checks from")
args = parser.parse_args()

# Find the URL to fetch checks from.
checks_url = None
if args.app:
js = fetch(f"https://api.github.com/repos/mozilla-mobile/mozilla-vpn-client/commits/{args.ref}/check-suites")
for suite in js['check_suites']:
if suite["app"]["name"] == args.app:
checks_url = suite["check_runs_url"]
if not checks_url:
raise KeyError(f"No suite {args.app} found for ${args.ref}")
else:
checks_url = f"https://api.github.com/repos/mozilla-mobile/mozilla-vpn-client/commits/{args.ref}/check-runs"

# Fetch the JSON for the desired check.
js = fetch(checks_url)
for check in js['check_runs']:
if check["name"] == args.name:
json.dump(check, sys.stdout)

print(f"DEBUG: {checks_url}")
179 changes: 179 additions & 0 deletions .github/workflows/prod-test-suite.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
# A reusable workflow to build addons
on:
workflow_call:
inputs:
artifact-name:
description: 'Name of the taskcluster artifact to download and test'
required: true
type: string
ref:
description: 'The branch, tag or SHA of functional tests to checkout.'
default: ${{ github.ref }}
type: string
runs-on:
description: 'The type of machine to run the job on.'
default: 'ubuntu-latest'
type: string
group-name:
description: 'Group name for intermediate test artifacts'
default: 'test'
type: string
task-id:
description: 'Taskcluster task to fetch artifacts from'
required: true
type: string

jobs:
build:
name: Build Test Artifacts
runs-on: ubuntu-latest
outputs:
tests: ${{ steps.enumerate.outputs.tests }}
runners: ${{ steps.enumerate.outputs.runners }}
uniqueid: ${{ steps.enumerate.outputs.uniqueid }}
steps:
- name: Clone repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}

- name: Setup addons cache
id: addons-cache
uses: actions/cache@v4
with:
path: build-addons/
key: cache-addons-${{ hashFiles('addons/', 'test/functional/addons/') }}

- name: Enumerate tests and runners
id: enumerate
run: |
echo -n "tests=" >> $GITHUB_OUTPUT
for test in $(find tests/functional -name 'test*.js' | sort); do
printf '{"name": "%s", "path": "%s"}' $(basename ${test%.js} | sed -n 's/test//p') $test
done | jq -s -c >> $GITHUB_OUTPUT

echo -n "runners=" >> $GITHUB_OUTPUT
echo "${{ inputs.runs-on }}" | jq --raw-input -c '[., inputs] | map(select(length > 0))' >> $GITHUB_OUTPUT

- name: Install build dependencies
if: steps.addons-cache.outputs.cache-hit != 'true'
shell: bash
run: |
git submodule init
git submodule update 3rdparty/i18n
sudo apt-get update
sudo apt-get install -y $(./scripts/linux/getdeps.py -b linux/debian/control)

- name: Build test addons
if: steps.addons-cache.outputs.cache-hit != 'true'
shell: bash
run: |
mkdir -p build-addons/
cmake -S $(pwd)/tests/functional/addons -B build-addons/
cmake --build build-addons/

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.group-name }}-addons
path: build-addons/

- name: Fetch taskgraph artifact
uses: ./.github/actions/taskcluster-artifact
with:
taskid: ${{ inputs.task-id }}
artifact: public/build/${{ inputs.artifact-name }}

- uses: actions/upload-artifact@v4
with:
name: ${{ inputs.group-name }}-artifact-${{ inputs.artifact-name }}
path: ${{ inputs.artifact-name }}

run-tests:
needs:
- build
timeout-minutes: 45
strategy:
fail-fast: false # Don't cancel other jobs if a test fails
matrix:
runner: ${{ fromJson(needs.build.outputs.runners) }}
test: ${{ fromJson(needs.build.outputs.tests) }}

runs-on: ${{ matrix.runner }}
name: Test ${{ matrix.test.name }} (${{ matrix.runner }})
steps:
- name: Clone repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}

- uses: actions/download-artifact@v4
with:
name: ${{ inputs.group-name }}-artifact-${{ inputs.artifact-name }}
path: build/

- uses: actions/download-artifact@v4
with:
name: ${{ inputs.group-name }}-addons
path: build/addons/

- uses: actions/setup-python@v5
with:
python-version: "3.9"
cache: "pip"
- run: pip install -r requirements.txt

- uses: actions/setup-node@v4
with:
node-version: 18
cache: "npm"
- run: npm install

- name: Install VPN client
if: ${{ runner.os != 'Windows' }}
shell: bash
run: |
case ${{ inputs.artifact-name }} in
*.deb)
sudo apt install ${{ github.workspace }}/build/${{ inputs.artifact-name }}
echo "MVPN_BIN=$(which mozillavpn)" >> $GITHUB_ENV
;;

*.pkg)
sudo installer -pkg ${{ github.workspace }}/build/${{ inputs.artifact-name }} -target /
echo "MVPN_BIN=/Applications/Mozilla VPN.app/Contents/MacOS/Mozilla VPN" >> $GITHUB_ENV
;;

*.tar.gz)
tar -xzvf ${{ github.workspace }}/build/${{ inputs.artifact-name }} -C ${{ github.workspace }}/build --strip-components=1
ls -al ${{ github.workspace }}/build
echo MVPN_BIN="${{ github.workspace }}/build/Mozilla VPN.app/Contents/MacOS/Mozilla VPN" >> $GITHUB_ENV
;;

*)
echo "Unsupported installation file type: ${{ inputs.artifact-name }}"
exit 1
;;
esac
echo "MVPN_ADDONS_PATH=${{ github.workspace }}/build/addons" >> $GITHUB_ENV

- name: Install VPN client
if: ${{ runner.os == 'Windows' }}
shell: powershell
run: |
New-Item -Path . -Name "msiexec.log" -ItemType "file"
$pmsi = Start-Process "msiexec" "/i ${{ github.workspace }}\build\${{ inputs.artifact-name }} /qn /l*! msiexec.log" -NoNewWindow -PassThru
$plog = Start-Process "powershell" "Get-Content -Path msiexec.log -Wait" -NoNewWindow -PassThru
$pmsi.WaitForExit()
$plog.Kill()

Add-Content -Path $env:GITHUB_ENV -Value "MVPN_BIN=C:\Program Files\Mozilla\Mozilla VPN\Mozilla VPN.exe"
Add-Content -Path $env:GITHUB_ENV -Value "MVPN_ADDONS_PATH=${{ github.workspace }}\build\addons"

taskkill /f /fi "USERNAME eq $env:username" /im "Mozilla VPN.exe"

- name: Running ${{ matrix.test.name }} Tests
shell: bash
env:
TZ: Europe/London
HEADLESS: yes
run: npm run functionalTest -- --retries 3 ${{ matrix.test.path }}
Loading
Loading