Skip to content
Merged
Changes from all commits
Commits
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
113 changes: 64 additions & 49 deletions .github/workflows/coverage-upload.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
uses: actions/github-script@v8
with:
script: |
/* ================================================================================= */
/* ================================================================================= */
/* ================================================================================= */
/* =================================== Context ===================================== */
/* ================================================================================= */
Expand All @@ -58,28 +58,29 @@ jobs:
//Sleep `seconds` seconds (works with floats)
const sleep = (seconds) => new Promise(r => setTimeout(r, seconds * 1000));

//Check if a json object contains something
//Check if a json object contains something or is just {}
const is_empty = (json) => Object.keys(json).length === 0;

//List all "Test for <package>" runs for the current event
// List all "Test for <package>" runs for each package in packages.
// If strict is true, only _event is considered
// Otherwise a priority based search strategy is made
const get_corresponding_test_runs = async (packages, _event=event, strict=false) => {
const test_runs = {};


// Define the filter strategies in order as it's only triggered by changes


const base_strategies = [
{branch : branch, head_sha: sha, event : _event }, // Runs that are triggered by any modification on this branch from this event
{branch : branch, head_sha: sha }, // Then we try with the same commit but different event
{branch : branch}, // Then anything else, as if no run was triggered, the code wasn't changed
{} // Otherwise anything else, as if there was no triggered run on the commit
{branch : branch, head_sha: sha, event : _event }, // Runs directly triggered by this event
{branch : branch, head_sha: sha }, // Runs from the same commit
{branch : branch}, // Runs from the same branch
{} // Any run
];

// strict => we only want runs triggered by this event so, only the first strategy
const strategies = strict ? [base_strategies[0]] : base_strategies;

for (const package of packages) {
let run = null;

// Try each strategy untila run is found
// Try each strategy until a run is found
for (const strategy of strategies) {
const { data } = await github.rest.actions.listWorkflowRunsForRepo({
owner : owner,
Expand All @@ -97,7 +98,7 @@ jobs:
return test_runs;
};

// Wait `attempts` times for `seconds` seconds that `run_id` run is over
// Wait `attempts` times for `seconds` seconds for `run_id` run to complete
// return null if timed out.
const wait_for_run_completion = async (run_id, seconds = 60, attempts=10) => {
let i = 0;
Expand All @@ -116,7 +117,7 @@ jobs:
return null;
};

// Return a workflow by its name
// Return a workflow from its name
const get_workflow_by_name = async (name) => {
const { data: workflows } = await github.rest.actions.listRepoWorkflows({
owner: owner,
Expand All @@ -126,18 +127,42 @@ jobs:
return workflows.workflows.find(w => w.name === name);
};

// Return the reference of an artifact by its name, and an optional associated
// run id. If run id is provided the artifact is fetched from this run,
// otherwise from any workflow, The first one found (most recent) is returned
const get_artifact_from_name = async (name, run_id=undefined) => {
let artifacts;
if (run_id) {
({ data: artifacts } = await github.rest.actions.listWorkflowRunArtifacts({
owner: owner,
repo: repo,
run_id: run_id,
name : name
}));
}
else {
({ data: artifacts } = await github.rest.actions.listArtifactsForRepo({
owner: owner,
repo: repo,
name : name
}));
}
return artifacts.artifacts?.[0]
};

// Download and save an artifact as zip file from its name.
// It is saved in the globally defined location `artifact_save_base_path`
const save_artifact = async (artifact) => {
const {data : artifact_data } = await github.rest.actions.downloadArtifact({
const {data : data } = await github.rest.actions.downloadArtifact({
owner : owner,
repo : repo,
artifact_id : artifact.id,
archive_format : "zip"
});

const filename = path.join(artifact_save_base_path, `${artifact.name}.zip`)
fs.writeFileSync(filename, Buffer.from(artifact_data, "base64"))
}
fs.writeFileSync(filename, Buffer.from(data, "base64"))
};
/* ================================================================================= */
/* ================================================================================= */
/* ================================== Entrypoint =================================== */
Expand All @@ -149,7 +174,9 @@ jobs:


//Create artifact download folder
if (!fs.existsSync(artifact_save_base_path)) fs.mkdirSync(artifact_save_base_path, { recursive: true });
if (!fs.existsSync(artifact_save_base_path)){
fs.mkdirSync(artifact_save_base_path, {recursive: true});
}

//Wait 10 seconds so that we're "sure" that everything else is started
await sleep(10);
Expand Down Expand Up @@ -181,18 +208,12 @@ jobs:
// For each package_name, get the corresponding run,
// and check if one of its artifact is "merged-coverage-<package-name>"
// If a package doesn't have an artifact, keep track of it
// Otherwise, download it
// Otherwise, download and save it
const packages_without_coverage_artifact = [];
for (const package of packages) {
console.log(`Looking for artifacts in ${package}`)
const { data: artifacts } = await github.rest.actions.listArtifactsForRepo({
owner: owner,
repo: repo,
name : `merged-coverage-${package.split("_")[1] ?? package}`
});

let artifact = artifacts.artifacts?.[0]

const artifact = await get_artifact_from_name(`merged-coverage-${package.split("_")[1] ?? package}`);

if (artifact) {
await save_artifact(artifact)
console.log(`Saved artifact ${artifact.name} for ${package}`)
Expand All @@ -209,7 +230,7 @@ jobs:
for (const package of packages_without_coverage_artifact) {
console.log(`Triggering test for ${package}...`);


//Start the workflow
const workflow = await get_workflow_by_name(`Test for ${package}`);
if (workflow) {
await github.rest.actions.createWorkflowDispatch({
Expand All @@ -219,21 +240,20 @@ jobs:
ref : branch
});

for (let i = 0; i < 30; i++) {
await sleep(10);
const runs = await get_corresponding_test_runs([package], _event='workflow_dispatch', strict=true);
if (runs[package]) {
console.log(`Triggered ${package}, run id = ${runs[package].id}`);
triggered_runs[package] = runs[package];
break;
}
else{
console.log(`Failed to detect triggered run for ${package}`)
}
//Try to get the run corresponding to the started workflow
// by polling the API after 30 seconds.
await sleep(10);
({ [package] : triggered_runs[package]} = await get_corresponding_test_runs([package], _event='workflow_dispatch', strict=true));
if (triggered_runs[package]) {
console.log(`Triggered ${package}, run id = ${triggered_runs[package].id}`);
}
else{
console.log(`Failed to detect triggered run for ${package}`)
}
}
}

//Wait for any triggered run
if (!is_empty(triggered_runs)){
// Sleep for 10 minutes should be enough for all test runs to be over
await sleep(10*60);
Expand All @@ -244,22 +264,14 @@ jobs:
const run = await wait_for_run_completion(triggered_runs[package].id);
if (run){
console.log(`Triggered run of ${package} is over.`);

const { data: artifacts } = await github.rest.actions.listWorkflowRunArtifacts({
owner: owner,
repo: repo,
run_id: test_runs[package].id
});

const artifact = artifacts.artifacts.find(a => a.name === `merged-coverage-${package.split("_")[1] ?? package}`);
const artifact = await get_artifact_from_name(`merged-coverage-${package.split("_")[1] ?? package}`, run.id);

if (artifact) {
await save_artifact(artifact)
console.log(`Saved artifact ${artifact.name} for ${package}`)
}
else {
console.log(`${package} is missing coverage artifact`);
packages_without_coverage_artifact.push(package);
}
}
else {
Expand All @@ -268,6 +280,7 @@ jobs:
}
}


- name: Merge coverage files and convert to xml
run: |
unzip \*.zip
Expand All @@ -280,4 +293,6 @@ jobs:
uses: codecov/codecov-action@v5.5.1
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: coverage.xml
files: coverage.xml

await github.rest.actions.listWorkflowRunArtifacts({owner,repo, run_id : run.id, name : `merged-coverage-${package.split("_")[1] ?? package}`});