Matrix in CI and mach try with presets (#31141)

* Matrix in CI and mach try with presets

* small fixups

* names in trigger try run comment

* let

* f

* rename step

* fix running try on win

* fix try branch full

* py3.10

* typo

* Make unit-tests default to false, except in basic os runs

Fixes https://github.com/servo/servo/issues/31174

* make full use linux-wpt & linux-wpt also include unit-tests

so full is equal to main workflow

* Stylish fixes

* cmp json as dict
This commit is contained in:
Samson 2024-01-26 13:29:37 +01:00 committed by GitHub
parent 266a082206
commit a5c512808a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 582 additions and 322 deletions

View file

@ -11,10 +11,8 @@ on:
profile:
required: false
default: "release"
type: string
type: choice
options: ["release", "debug", "production"]
push:
branches: ["try-android"]
env:
RUST_BACKTRACE: 1

55
.github/workflows/dispatch-workflow.yml vendored Normal file
View file

@ -0,0 +1,55 @@
name: Dispatch Workflow
on:
workflow_call:
inputs:
workflow:
required: true
type: string
profile:
required: true
type: string
wpt-tests-to-run:
required: true
type: string
wpt-layout:
required: true
type: string
unit-tests:
required: true
type: boolean
jobs:
win:
if: ${{ inputs.workflow == 'windows' }}
uses: ./.github/workflows/windows.yml
secrets: inherit
with:
profile: ${{ inputs.profile }}
unit-tests: ${{ inputs.unit-tests }}
macos:
if: ${{ inputs.workflow == 'macos' }}
uses: ./.github/workflows/mac.yml
secrets: inherit
with:
profile: ${{ inputs.profile }}
wpt-layout: ${{ inputs.wpt-layout }}
unit-tests: ${{ inputs.unit-tests }}
wpt-tests-to-run: ${{ inputs.wpt-tests-to-run }}
linux:
if: ${{ inputs.workflow == 'linux' }}
uses: ./.github/workflows/linux.yml
secrets: inherit
with:
profile: ${{ inputs.profile }}
wpt-layout: ${{ inputs.wpt-layout }}
unit-tests: ${{ inputs.unit-tests }}
wpt-tests-to-run: ${{ inputs.wpt-tests-to-run }}
android:
if: ${{ inputs.workflow == 'android' }}
uses: ./.github/workflows/android.yml
secrets: inherit
with:
profile: ${{ inputs.profile }}

View file

@ -11,124 +11,36 @@ on:
branches: ["**"]
merge_group:
types: [checks_requested]
workflow_call:
inputs:
configuration:
required: true
type: string
workflow_dispatch:
inputs:
profile:
required: false
default: "release"
type: choice
options: ["release", "debug", "production"]
wpt-tests-to-run:
default: ""
required: false
type: string
platform:
required: false
type: choice
options: ["linux", "windows", "macos", "all"]
wpt-layout:
required: false
type: choice
options: ["none", "2013", "2020", "all"]
unit-tests:
required: false
type: boolean
jobs:
decision:
name: Decision
runs-on: ubuntu-20.04
outputs:
configuration: ${{ steps.configuration.outputs.result }}
steps:
- name: Configuration
id: configuration
uses: actions/github-script@v6
with:
script: |
// If this is a workflow call with a configuration object,
// then just return it immediately.
let configuration = ${{ inputs.configuration || '""' }};
if (configuration != "") {
console.log("Using configuration: " + JSON.stringify(configuration));
return configuration;
}
// We need to pick defaults if the inputs are not provided. Unprovided inputs
// are empty strings in this template.
let platform = "${{ inputs.platform }}" || "linux";
let profile = "${{ inputs.profile }}" || "release";
let wpt_layout = "${{ inputs.wpt-layout }}" || "none";
let wpt_tests_to_run = "${{ inputs.wpt-tests-to-run }}" || "";
let unit_tests = Boolean(${{ inputs.unit-tests }})
// Merge queue runs and pushes to `main` should always trigger a full build and test.
if (["push", "merge_group"].includes(context.eventName)) {
platform = "all";
wpt_layout = "all";
unit_tests = true;
}
let platforms = [];
if (platform == "all") {
platforms = [ "linux", "windows", "macos", "android" ];
} else {
platforms = [ platform ];
}
let returnValue = {
platforms,
wpt_layout,
unit_tests,
profile,
wpt_tests_to_run,
};
console.log("Using configuration: " + JSON.stringify(returnValue));
return returnValue;
build-win:
name: Windows
needs: ["decision"]
if: ${{ contains(fromJson(needs.decision.outputs.configuration).platforms, 'windows') }}
if: ${{ github.event_name != 'pull_request' }}
uses: ./.github/workflows/windows.yml
with:
profile: ${{ fromJson(needs.decision.outputs.configuration).profile }}
unit-tests: ${{ fromJson(needs.decision.outputs.configuration).unit_tests }}
unit-tests: true
secrets: inherit
build-mac:
name: Mac
needs: ["decision"]
if: ${{ contains(fromJson(needs.decision.outputs.configuration).platforms, 'macos') }}
if: ${{ github.event_name != 'pull_request' }}
uses: ./.github/workflows/mac.yml
with:
wpt-tests-to-run: ${{ fromJson(needs.decision.outputs.configuration).wpt_tests_to_run }}
profile: ${{ fromJson(needs.decision.outputs.configuration).profile }}
unit-tests: ${{ fromJson(needs.decision.outputs.configuration).unit_tests }}
unit-tests: true
secrets: inherit
build-linux:
name: Linux
needs: ["decision"]
if: ${{ contains(fromJson(needs.decision.outputs.configuration).platforms, 'linux') }}
uses: ./.github/workflows/linux.yml
with:
wpt-tests-to-run: ${{ fromJson(needs.decision.outputs.configuration).wpt_tests_to_run }}
profile: ${{ fromJson(needs.decision.outputs.configuration).profile }}
wpt-layout: ${{ fromJson(needs.decision.outputs.configuration).wpt_layout }}
unit-tests: ${{ fromJson(needs.decision.outputs.configuration).unit_tests }}
unit-tests: true
wpt-layout: ${{ github.event_name == 'pull_request' && 'none' || 'all' }}
secrets: inherit
build-android:
name: Android
needs: ["decision"]
if: ${{ contains(fromJson(needs.decision.outputs.configuration).platforms, 'android') }}
if: ${{ github.event_name != 'pull_request' }}
uses: ./.github/workflows/android.yml
with:
profile: "release"
@ -140,15 +52,11 @@ jobs:
if: always()
# needs all build to detect cancellation
needs:
- "decision"
- "build-win"
- "build-mac"
- "build-linux"
- "build-android"
steps:
- name: Mark skipped jobs as successful
if: ${{ fromJson(needs.decision.outputs.configuration).platforms[0] != null }}
run: exit 0
- name: Mark the job as successful
if: ${{ !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') }}
run: exit 0

View file

@ -1,203 +1,123 @@
name: Try
on:
pull_request_target:
types: [labeled]
push:
branches: ["try", "try-linux", "try-mac", "try-wpt-mac", "try-wpt-mac-2020", "try-wpt", "try-wpt-2020", "try-windows"]
branches: ["try"]
workflow_dispatch:
inputs:
profile:
required: false
default: "release"
type: choice
options: ["release", "debug", "production"]
wpt-tests-to-run:
default: ""
required: false
type: string
wpt-layout:
required: false
type: choice
options: ["none", "2013", "2020", "all"]
unit-tests:
required: false
type: boolean
jobs:
parse-comment:
name: Trigger Try
runs-on: ubuntu-latest
concurrency:
group: try-${{ github.event.number }}
decision:
name: Generate Try Configuration
runs-on: ubuntu-20.04
outputs:
configuration: ${{ steps.configuration.outputs.result }}
steps:
- uses: actions/github-script@v6
- uses: actions/setup-python@v5
with:
python-version: '3.10'
- uses: actions/checkout@v3
with:
fetch-depth: 1
sparse-checkout: |
python/servo/try_parser.py
sparse-checkout-cone-mode: false
- name: Get Full Configuration
id: full_config
run: |
{
echo 'config<<EOF'
python ./python/servo/try_parser.py
echo EOF
} >> $GITHUB_OUTPUT
- name: Configuration
id: configuration
uses: actions/github-script@v6
with:
script: |
function makeComment(body) {
console.log(body);
// When triggered via a push try the `try` branch, search the last commit for a configuration object.
if (${{ github.ref_name == 'try' }}) {
let commit_msg = context.payload.head_commit.message;
try {
var config = JSON.parse(commit_msg.split('\n').slice(-1));
if (context.eventName != 'pull_request_target')
return;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body
})
}
function combineWPTLayoutOptions(layout, newLayout) {
let has2013 = layout == "2013" || layout == "all";
let has2020 = layout == "2020" || layout == "all";
let adding2013 = newLayout == "2013";
let adding2020 = newLayout == "2020";
if ((adding2020 && has2020) || (adding2013 && has2013)) {
return layout;
if (config && typeof config === "object") {
console.log("Using try commit configuration: " + JSON.stringify(config));
return config;
}
if (adding2020) {
return has2013 ? "all" : "2020";
}
if (adding2013) {
return has2020 ? "all" : "2013";
}
return layout;
}
function addPlatformToConfiguration(platform, configuration) {
if (!configuration.platforms.includes(platform)) {
configuration.platforms.push(platform);
}
catch (exception) {
console.log("Could not parse try configuration from commit message: " + exception);
console.log("Triggering full try run.");
}
}
function updateConfigurationFromString(tryString, configuration) {
if (tryString.includes("full")) {
configuration.platforms = ["linux", "macos", "windows"];
configuration.unit_tests = true;
configuration.wpt_layout = "all";
return configuration;
}
// If we reach here we are likely doing a full run.
configuration = ${{ steps.full_config.outputs.config }};
if (tryString.includes("linux")) {
addPlatformToConfiguration("linux", configuration);
configuration.unit_tests = true;
} else if (tryString.includes("mac")) {
addPlatformToConfiguration("macos", configuration);
configuration.unit_tests = true;
} else if (tryString.includes("win")) {
addPlatformToConfiguration("windows", configuration);
configuration.unit_tests = true;
}
// Process `workflow_dispatch` provided configuration overrides.
if (context.eventName == "workflow_dispatch") {
// WPT-related overrides only affect Linux currently, as tests don't run by default on other platforms.
configuration.matrix[0].wpt_layout = "${{ inputs.wpt-layout }}" || "none";
configuration.matrix[0].wpt_tests_to_run = "${{ inputs.wpt-tests-to-run }}" || "";
if (tryString.includes("wpt")) {
addPlatformToConfiguration("linux", configuration);
if (tryString.includes("2020")) {
configuration.wpt_layout = combineWPTLayoutOptions(configuration.wpt_layout, "2020");
} else {
configuration.wpt_layout = combineWPTLayoutOptions(configuration.wpt_layout, "2013");
}
}
}
let configuration = {
platforms: [],
wpt_layout: "none",
unit_tests: false,
profile: "release",
wpt_tests_to_run: "",
};
if (context.eventName == 'pull_request_target') {
for (const label of context.payload.pull_request.labels) {
if (!label.name.startsWith("T-")) {
continue;
}
// Try to remove the label. If that fails, it's likely that another
// workflow has already processed it or a user has removed it.
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
name: label.name,
});
} catch (exception) {
console.log("Assuming '" + label.name + "' is already removed: " + exception);
continue;
}
console.log("Found label: " + label.name);
updateConfigurationFromString(label.name, configuration);
}
} else {
let ref_name = "${{ github.ref_name || 'empty' }}";
if (ref_name == "try") {
updateConfigurationFromString("full", configuration);
} else {
updateConfigurationFromString(ref_name, configuration);
let unit_tests = Boolean(${{ inputs.unit-tests }});
let profile = '${{ inputs.profile }}';
for (const platform of configuration.matrix) {
platform.profile = profile;
platform.unit_tests = unit_tests;
}
}
console.log(JSON.stringify(configuration));
if (configuration.platforms.length == 0) {
return { platforms: [] };
}
let username = context.payload.sender.login;
let result = await github.rest.repos.getCollaboratorPermissionLevel({
owner: context.repo.owner,
repo: context.repo.repo,
username
});
if (!result.data.user.permissions.push) {
makeComment('🔒 User @' + username + ' does not have permission to trigger try jobs.');
return { platforms: [] };
}
const url = context.serverUrl +
"/" + context.repo.owner +
"/" + context.repo.repo +
"/actions/runs/" + context.runId;
const formattedURL = "[#" + context.runId + "](" + url + ")";
let platformsString = configuration.platforms.toString();
makeComment("🔨 Triggering try run (" + formattedURL + ") with platforms=" +
platformsString + " and layout=" + configuration.wpt_layout);
console.log("Using configuration: " + JSON.stringify(configuration));
return configuration;
run-try:
name: Run Try
needs: ["parse-comment"]
if: ${{ fromJson(needs.parse-comment.outputs.configuration).platforms[0] != null }}
uses: ./.github/workflows/main.yml
build:
needs: ["decision"]
name: ${{ matrix.name }}
strategy:
fail-fast: ${{ fromJson(needs.decision.outputs.configuration).fail_fast }}
matrix:
include: ${{ fromJson(needs.decision.outputs.configuration).matrix }}
# We need to use `dipatch-workflow.yml` because workflows do not support using: ${}
uses: ./.github/workflows/dispatch-workflow.yml
secrets: inherit
with:
configuration: ${{ needs.parse-comment.outputs.configuration }}
workflow: ${{ matrix.workflow }}
wpt-layout: ${{ matrix.wpt_layout }}
profile: ${{ matrix.profile }}
unit-tests: ${{ matrix.unit_tests }}
wpt-tests-to-run: ${{ matrix.wpt_tests_to_run }}
results:
name: Results
needs: ["parse-comment", "run-try"]
build-result:
name: Result
runs-on: ubuntu-latest
if: ${{ always() && fromJson(needs.parse-comment.outputs.configuration).platforms[0] != null }}
if: always()
# `needs: "build"` is necessary to detect cancellation.
needs:
- "decision"
- "build"
steps:
- name: Success
if: ${{ github.event_name == 'pull_request_target' && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') }}
uses: actions/github-script@v6
with:
script: |
const url = context.serverUrl +
"/" + context.repo.owner +
"/" + context.repo.repo +
"/actions/runs/" + context.runId;
const formattedURL = "[#" + context.runId + "](" + url + ")";
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: "✨ Try run (" + formattedURL + ") " + "succeeded.",
});
- name: Failure
if: ${{ github.event_name == 'pull_request_target' && contains(needs.*.result, 'failure') }}
uses: actions/github-script@v6
with:
script: |
const url = context.serverUrl +
"/" + context.repo.owner +
"/" + context.repo.repo +
"/actions/runs/" + context.runId;
const formattedURL = "[#" + context.runId + "](" + url + ")";
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: "⚠️ Try run (" + formattedURL + ") " + "failed.",
});
- name: Mark the job as successful
if: ${{ !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') }}
run: exit 0
- name: Mark the job as unsuccessful
if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
run: exit 1

184
.github/workflows/try_labels.yml vendored Normal file
View file

@ -0,0 +1,184 @@
name: Try (Label)
on:
pull_request_target:
types: [labeled]
jobs:
parse-comment:
name: Trigger Try
runs-on: ubuntu-latest
concurrency:
group: try-${{ github.event.number }}
outputs:
configuration: ${{ steps.configuration.outputs.config }}
try_string: ${{ steps.try_string.outputs.result }}
steps:
- name: Collect Labels
uses: actions/github-script@v6
id: try_string
with:
result-encoding: string
script: |
function makeComment(body) {
console.log(body);
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body
})
}
let try_string = "";
for (let label of context.payload.pull_request.labels) {
if (!label.name.startsWith("T-")) {
continue;
}
// Try to remove the label. If that fails, it's likely that another
// workflow has already processed it or a user has removed it.
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
name: label.name,
});
} catch (exception) {
console.log("Assuming '" + label.name + "' is already removed: " + exception);
continue;
}
console.log("Found label: " + label.name);
// Remove the "T-" prefix.
label = label.name.slice(2);
try_string += " " + label;
}
console.log(try_string);
// Exit early if the try string is empty (no try triggered).
if (!try_string.trim()) {
return "";
}
let username = context.payload.sender.login;
let result = await github.rest.repos.getCollaboratorPermissionLevel({
owner: context.repo.owner,
repo: context.repo.repo,
username
});
if (!result.data.user.permissions.push) {
makeComment('🔒 User @' + username + ' does not have permission to trigger try jobs.');
return "";
}
return try_string;
- uses: actions/setup-python@v5
with:
python-version: '3.10'
- uses: actions/checkout@v3
with:
# This is necessary to checkout the pull request if this run was triggered
# via an `label` action on a pull request.
ref: refs/pull/${{ github.event.number }}/head
fetch-depth: 1
sparse-checkout: |
python/servo/try_parser.py
sparse-checkout-cone-mode: false
- name: Parse Labels
if: ${{ steps.try_string.outputs.result }}
id: configuration
run: |
{
echo 'config<<EOF'
python ./python/servo/try_parser.py ${{ steps.try_string.outputs.result }}
echo EOF
} >> $GITHUB_OUTPUT
- name: Comment Run Start
if: ${{ steps.try_string.outputs.result }}
uses: actions/github-script@v6
with:
result-encoding: string
script: |
let config = ${{ steps.configuration.outputs.config }};
function makeComment(body) {
console.log(body);
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body
})
}
const url = context.serverUrl +
"/" + context.repo.owner +
"/" + context.repo.repo +
"/actions/runs/" + context.runId;
const formattedURL = "[#" + context.runId + "](" + url + ")";
makeComment("🔨 Triggering try run (" + formattedURL + ") for "
+ config.matrix.map(m => m.name).join(", "));
run-try:
if: ${{ needs.parse-comment.outputs.try_string }}
needs: ["parse-comment"]
name: ${{ matrix.name }}
strategy:
fail-fast: ${{ fromJson(needs.parse-comment.outputs.configuration).fail_fast }}
matrix:
include: ${{ fromJson(needs.parse-comment.outputs.configuration).matrix }}
# We need to use `dipatch-workflow.yml` because workflows do not support using: ${}.
uses: ./.github/workflows/dispatch-workflow.yml
secrets: inherit
with:
workflow: ${{ matrix.workflow }}
wpt-layout: ${{ matrix.wpt_layout }}
profile: ${{ matrix.profile }}
unit-tests: ${{ matrix.unit_tests }}
wpt-tests-to-run: ${{ matrix.wpt_tests_to_run }}
results:
name: Results
needs: ["parse-comment", "run-try"]
runs-on: ubuntu-latest
if: ${{ always() && needs.parse-comment.outputs.try_string }}
steps:
- name: Success
if: ${{ !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') }}
uses: actions/github-script@v6
with:
script: |
const url = context.serverUrl +
"/" + context.repo.owner +
"/" + context.repo.repo +
"/actions/runs/" + context.runId;
const formattedURL = "[#" + context.runId + "](" + url + ")";
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: "✨ Try run (" + formattedURL + ") " + "succeeded.",
});
- name: Failure
if: ${{ contains(needs.*.result, 'failure') }}
uses: actions/github-script@v6
with:
script: |
const url = context.serverUrl +
"/" + context.repo.owner +
"/" + context.repo.repo +
"/actions/runs/" + context.runId;
const formattedURL = "[#" + context.runId + "](" + url + ")";
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: "⚠️ Try run (" + formattedURL + ") " + "failed.",
});

View file

@ -4,7 +4,8 @@ on:
workflow_call:
inputs:
profile:
required: true
required: false
default: "release"
type: string
unit-tests:
required: false

View file

@ -22,17 +22,7 @@ from mach.decorators import (
)
from servo.command_base import CommandBase, cd, call
VALID_TRY_BRACHES = [
"try",
"try-linux",
"try-mac",
"try-windows",
"try-wpt",
"try-wpt-2020",
"try-wpt-mac",
"try-wpt-mac-2020"
]
from servo.try_parser import Config
@CommandProvider
@ -284,38 +274,35 @@ class MachCommands(CommandBase):
return p.wait()
@Command('try',
description='Runs try jobs by force pushing to personal fork try branches',
description='Runs try jobs by force pushing to try branch',
category='devenv')
@CommandArgument(
'jobs', default=["try"], nargs='...',
help="Name(s) of job(s) (ex: try, linux, mac, windows, wpt)")
def try_jobs(self, jobs):
branches = []
# we validate branches because force pushing is destructive
for job in jobs:
# branches must start with try-
if "try" not in job:
job = "try-" + job
if job not in VALID_TRY_BRACHES:
print(job + " job doesn't exist")
return -1
branches.append(job)
remote = "origin"
if "servo/servo" in subprocess.check_output(["git", "config", "--get", "remote.origin.url"]).decode():
# if we have servo/servo for origin check try remote
try:
if "servo/servo" in subprocess.check_output(["git", "config", "--get", "remote.try.url"]).decode():
# User has servo/servo for try remote
print("You should not use servo/servo for try remote!")
return -1
else:
remote = "try"
except subprocess.CalledProcessError:
print("It looks like you are patching in upstream servo.")
print("Set try remote to your personal fork with `git remote add try https://github.com/user/servo`")
return -1
for b in branches:
res = call(["git", "push", remote, "--force", f"HEAD:{b}"], env=self.build_env())
if res != 0:
return res
return 0
'--remote', '-r', default="origin",
help='git remote to use for try pushes')
@CommandArgument(
'try_string', default=None, nargs='...',
help="Try string")
def try_jobs(self, remote="origin", try_string=None):
if not try_string:
try_string = "full"
else:
try_string = " ".join(try_string)
conf = Config(try_string)
result = call(["git", "commit", "--allow-empty", "-m", try_string, "-m", f"{conf.to_json()}"])
if result != 0:
return result
git_remote = subprocess.check_output(["git", "config", "--get", f"remote.{remote}.url"]).decode()
if "servo/servo" in git_remote:
print("WARNING: You are triggering try build in upstream repo!")
result = call(["git", "push", remote, "--force", "HEAD:try"])
if result != 0:
return result
git_remote = git_remote.replace(".git", "/actions")
print(f"You can find triggered workflow here: {git_remote}")
# Remove the last commit which only contains the try configuration.
result += call(["git", "reset", "--soft", "HEAD~1"])
return result

View file

@ -244,6 +244,10 @@ class MachCommands(CommandBase):
print("Running tidy tests...")
passed = tidy.run_tests() and passed
import python.servo.try_parser as try_parser
print("Running try_parser tests...")
passed = try_parser.run_tests() and passed
print("Running WPT tests...")
passed = wpt.run_tests() and passed

203
python/servo/try_parser.py Normal file
View file

@ -0,0 +1,203 @@
#!/usr/bin/env python
# Copyright 2023 The Servo Project Developers. See the COPYRIGHT
# file at the top-level directory of this distribution.
#
# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
# http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
# <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
# option. This file may not be copied, modified, or distributed
# except according to those terms.
import json
import sys
from typing import Optional
import unittest
import logging
from dataclasses import dataclass
from enum import Enum, Flag, auto
class Layout(Flag):
none = 0
layout2013 = auto()
layout2020 = auto()
@staticmethod
def all():
return Layout.layout2013 | Layout.layout2020
def to_string(self):
if Layout.all() in self:
return "all"
elif Layout.layout2020 in self:
return "2020"
elif Layout.layout2013 in self:
return "2013"
else:
return "none"
class Workflow(str, Enum):
LINUX = 'linux'
MACOS = 'macos'
WINDOWS = 'windows'
ANDROID = 'android'
@dataclass
class JobConfig(object):
name: str
workflow: Workflow = Workflow.LINUX
wpt_layout: Layout = Layout.none
profile: str = "release"
unit_tests: bool = False
wpt_tests_to_run: str = ""
def handle_preset(s: str) -> Optional[JobConfig]:
s = s.lower()
if s == "linux":
return JobConfig("Linux", Workflow.LINUX, unit_tests=True)
elif s in ["mac", "macos"]:
return JobConfig("MacOS", Workflow.MACOS, unit_tests=True)
elif s in ["win", "windows"]:
return JobConfig("Windows", Workflow.WINDOWS, unit_tests=True)
elif s in ["wpt", "linux-wpt"]:
return JobConfig("Linux WPT", Workflow.LINUX, unit_tests=True, wpt_layout=Layout.all())
elif s in ["wpt-2013", "linux-wpt-2013"]:
return JobConfig("Linux WPT legacy-layout", Workflow.LINUX, wpt_layout=Layout.layout2013)
elif s in ["wpt-2020", "linux-wpt-2020"]:
return JobConfig("Linux WPT layout-2020", Workflow.LINUX, wpt_layout=Layout.layout2020)
elif s in ["mac-wpt", "wpt-mac"]:
return JobConfig("MacOS WPT", Workflow.MACOS, wpt_layout=Layout.all())
elif s == "mac-wpt-2013":
return JobConfig("MacOS WPT legacy-layout", Workflow.MACOS, wpt_layout=Layout.layout2013)
elif s == "mac-wpt-2020":
return JobConfig("MacOS WPT layout-2020", Workflow.MACOS, wpt_layout=Layout.layout2020)
elif s == "android":
return JobConfig("Android", Workflow.ANDROID)
elif s == "webgpu":
return JobConfig("WebGPU CTS", Workflow.LINUX,
wpt_layout=Layout.layout2020, # reftests are mode for new layout
wpt_tests_to_run="_webgpu", # run only webgpu cts
profile="production", # WebGPU works to slow with debug assert
unit_tests=False) # production profile does not work with unit-tests
else:
return None
class Encoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, (Config, JobConfig)):
return o.__dict__
if isinstance(o, Layout):
return o.to_string()
return json.JSONEncoder.default(self, o)
class Config(object):
def __init__(self, s: Optional[str] = None):
self.fail_fast: bool = False
self.matrix: list[JobConfig] = list()
if s is not None:
self.parse(s)
def parse(self, input: str):
input = input.lower().strip()
if not input:
input = "full"
words: list[str] = input.split(" ")
for word in words:
# Handle keywords.
if word in ["fail-fast", "failfast", "fail_fast"]:
self.fail_fast = True
continue # skip over keyword
if word == "full":
words.extend(["linux-wpt", "macos", "windows", "android"])
continue # skip over keyword
preset = handle_preset(word)
if preset is None:
print(f"Ignoring unknown preset {word}")
else:
self.matrix.append(preset)
def to_json(self) -> str:
return json.dumps(self, cls=Encoder)
def main():
conf = Config(" ".join(sys.argv[1:]))
print(conf.to_json())
if __name__ == "__main__":
main()
class TestParser(unittest.TestCase):
def test_string(self):
self.assertDictEqual(json.loads(Config("linux fail-fast").to_json()),
{'fail_fast': True,
'matrix': [{
'name': 'Linux',
'profile': 'release',
'unit_tests': True,
'workflow': 'linux',
'wpt_layout': 'none',
'wpt_tests_to_run': ''
}]
})
def test_empty(self):
self.assertDictEqual(json.loads(Config("").to_json()),
{"fail_fast": False, "matrix": [
{
"name": "Linux WPT",
"workflow": "linux",
"wpt_layout": "all",
"profile": "release",
"unit_tests": True,
"wpt_tests_to_run": ""
},
{
"name": "MacOS",
"workflow": "macos",
"wpt_layout": "none",
"profile": "release",
"unit_tests": True,
"wpt_tests_to_run": ""
},
{
"name": "Windows",
"workflow": "windows",
"wpt_layout": "none",
"profile": "release",
"unit_tests": True,
"wpt_tests_to_run": ""
},
{
"name": "Android",
"workflow": "android",
"wpt_layout": "none",
"profile": "release",
"unit_tests": False,
"wpt_tests_to_run": ""
}
]})
def test_full(self):
self.assertDictEqual(json.loads(Config("linux-wpt macos windows android").to_json()),
json.loads(Config("").to_json()))
def run_tests():
verbosity = 1 if logging.getLogger().level >= logging.WARN else 2
suite = unittest.TestLoader().loadTestsFromTestCase(TestParser)
return unittest.TextTestRunner(verbosity=verbosity).run(suite).wasSuccessful()