forked from robstoll/atrium
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Integrate Custom Random Matrix Generation and Merge Ubuntu and Window…
…s Workflows (robstoll#1776) * ###feature/Integrate random matrix generation and merge Ubuntu and Windows workflows - Added a new CI workflow using vlsi/github-actions-random-matrix to generate a dynamic matrix for testing on both Ubuntu and Windows with JDK 11 and JDK 17 - Merged existing build steps for Ubuntu and Windows into a single workflow file.
- Loading branch information
1 parent
016ac33
commit e33fedc
Showing
5 changed files
with
331 additions
and
92 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
let { MatrixBuilder } = require('./matrix_builder'); | ||
const matrix = new MatrixBuilder(); | ||
|
||
// Add axes for the matrix | ||
matrix.addAxis({ | ||
name: 'os', | ||
title: x => x.replace('-latest', ''), | ||
values: [ | ||
'ubuntu-latest', | ||
'windows-latest' | ||
] | ||
}); | ||
matrix.addAxis({ | ||
name: 'java_version', | ||
values: ['11', '17'] | ||
}); | ||
matrix.addAxis({ | ||
name: 'java_distribution', | ||
title: x => x, | ||
values: [ | ||
'corretto', | ||
'liberica', | ||
'microsoft', | ||
'temurin', | ||
'zulu' | ||
] | ||
}); | ||
|
||
// Configure the order of the fields in job name | ||
matrix.setNamePattern(['os', 'java_version', 'java_distribution']); | ||
|
||
// Ensure at least one windows and at least one linux job is present (macos is almost the same as linux) | ||
matrix.generateRow({ os: 'windows-latest' }); | ||
matrix.generateRow({ os: 'ubuntu-latest' }); | ||
|
||
// Generate more rows, no duplicates would be generated | ||
const include = matrix.generateRows(process.env.MATRIX_JOBS || 5); | ||
if (include.length === 0) { | ||
throw new Error('Matrix list is empty'); | ||
} | ||
// Sort jobs by name, however, numeric parts are sorted appropriately | ||
// For instance, 'windows 8' would come before 'windows 11' | ||
include.sort((a, b) => a.name.localeCompare(b.name, undefined, { numeric: true })); | ||
|
||
console.log(include); | ||
console.log('::set-output name=matrix::' + JSON.stringify({ include })); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,237 @@ | ||
// License: Apache-2.0 | ||
// Copyright Vladimir Sitnikov, 2021 | ||
// See https://github.com/vlsi/github-actions-random-matrix | ||
|
||
class Axis { | ||
constructor({name, title, values}) { | ||
this.name = name; | ||
this.title = title; | ||
this.values = values; | ||
// If all entries have same weight, the axis has uniform distribution | ||
this.uniform = values.reduce((a, b) => a === (b.weight || 1) ? a : 0, values[0].weight || 1) !== 0 | ||
this.totalWeight = this.uniform ? values.length : values.reduce((a, b) => a + (b.weight || 1), 0); | ||
} | ||
|
||
static matches(row, filter) { | ||
if (typeof filter === 'function') { | ||
return filter(row); | ||
} | ||
if (Array.isArray(filter)) { | ||
// e.g. row={os: 'windows'}; filter=[{os: 'linux'}, {os: 'linux'}] | ||
return filter.find(v => Axis.matches(row, v)); | ||
} | ||
if (typeof filter === 'object') { | ||
// e.g. row={jdk: {name: 'openjdk', version: 8}}; filter={jdk: {version: 8}} | ||
for (const [key, value] of Object.entries(filter)) { | ||
if (!row.hasOwnProperty(key) || !Axis.matches(row[key], value)) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
return row === filter; | ||
} | ||
|
||
pickValue(filter) { | ||
let values = this.values; | ||
if (filter) { | ||
values = values.filter(v => Axis.matches(v, filter)); | ||
} | ||
if (values.length === 0) { | ||
const filterStr = typeof filter === 'string' ? filter.toString() : JSON.stringify(filter); | ||
throw Error(`No values produces for axis '${this.name}' from ${JSON.stringify(this.values)}, filter=${filterStr}`); | ||
} | ||
if (values.length === 1) { | ||
return values[0]; | ||
} | ||
if (this.uniform) { | ||
return values[Math.floor(Math.random() * values.length)]; | ||
} | ||
const totalWeight = !filter ? this.totalWeight : values.reduce((a, b) => a + (b.weight || 1), 0); | ||
let weight = Math.random() * totalWeight; | ||
for (let i = 0; i < values.length; i++) { | ||
const value = values[i]; | ||
weight -= value.weight || 1; | ||
if (weight <= 0) { | ||
return value; | ||
} | ||
} | ||
return values[values.length - 1]; | ||
} | ||
} | ||
|
||
class MatrixBuilder { | ||
constructor() { | ||
this.axes = []; | ||
this.axisByName = {}; | ||
this.rows = []; | ||
this.duplicates = {}; | ||
this.excludes = []; | ||
this.includes = []; | ||
this.failOnUnsatisfiableFilters = false; | ||
// this.namePattern = []; // Initialize as an empty array | ||
} | ||
|
||
/** | ||
* Specifies include filter (all the generated rows would comply with all the include filters) | ||
* @param filter | ||
*/ | ||
include(filter) { | ||
this.includes.push(filter); | ||
} | ||
|
||
/** | ||
* Specifies exclude filter (e.g. exclude a forbidden combination) | ||
* @param filter | ||
*/ | ||
exclude(filter) { | ||
this.excludes.push(filter); | ||
} | ||
|
||
addAxis({name, title, values}) { | ||
const axis = new Axis({name, title, values}); | ||
this.axes.push(axis); | ||
this.axisByName[name] = axis; | ||
return axis; | ||
} | ||
|
||
setNamePattern(names) { | ||
this.namePattern = names; | ||
} | ||
|
||
/** | ||
* Returns true if the row matches the include and exclude filters. | ||
* @param row input row | ||
* @returns {boolean} | ||
*/ | ||
matches(row) { | ||
return (this.excludes.length === 0 || !this.excludes.find(f => Axis.matches(row, f))) && | ||
(this.includes.length === 0 || this.includes.find(f => Axis.matches(row, f))); | ||
} | ||
|
||
failOnUnsatisfiableFilters(value) { | ||
this.failOnUnsatisfiableFilters = value; | ||
} | ||
|
||
/** | ||
* Adds a row that matches the given filter to the resulting matrix. | ||
* filter values could be | ||
* - literal values: filter={os: 'windows-latest'} | ||
* - arrays: filter={os: ['windows-latest', 'linux-latest']} | ||
* - functions: filter={os: x => x!='windows-latest'} | ||
* @param filter object with keys matching axes names | ||
* @returns {*} | ||
*/ | ||
generateRow(filter) { | ||
let res; | ||
if (filter) { | ||
// If matching row already exists, no need to generate more | ||
res = this.rows.find(v => Axis.matches(v, filter)); | ||
if (res) { | ||
return res; | ||
} | ||
} | ||
for (let i = 0; i < 142; i++) { | ||
res = this.axes.reduce( | ||
(prev, next) => | ||
Object.assign(prev, { | ||
[next.name]: next.pickValue(filter ? filter[next.name] : undefined) | ||
}), | ||
{} | ||
); | ||
if (!this.matches(res)) { | ||
continue; | ||
} | ||
const key = JSON.stringify(res); | ||
if (!this.duplicates.hasOwnProperty(key)) { | ||
this.duplicates[key] = true; | ||
res.name = | ||
this.namePattern.map(axisName => { | ||
let value = res[axisName]; | ||
const title = value.title; | ||
if (typeof title != 'undefined') { | ||
return title; | ||
} | ||
const computeTitle = this.axisByName[axisName].title; | ||
return computeTitle ? computeTitle(value) : value; | ||
}).filter(Boolean).join(", "); | ||
this.rows.push(res); | ||
return res; | ||
} | ||
} | ||
const filterStr = typeof filter === 'string' ? filter.toString() : JSON.stringify(filter); | ||
const msg = `Unable to generate row for ${filterStr}. Please check include and exclude filters`; | ||
if (this.failOnUnsatisfiableFilters) { | ||
throw Error(msg); | ||
} else { | ||
console.warn(msg); | ||
} | ||
} | ||
|
||
generateRows(maxRows, filter) { | ||
for (let i = 0; this.rows.length < maxRows && i < maxRows; i++) { | ||
this.generateRow(filter); | ||
} | ||
return this.rows; | ||
} | ||
|
||
/** | ||
* Computes the number of all the possible combinations. | ||
* @returns {{bad: number, good: number}} | ||
*/ | ||
summary() { | ||
let position = -1; | ||
let indices = []; | ||
let values = {}; | ||
const axes = this.axes; | ||
function resetValuesUpTo(nextPosition) { | ||
for(let i=0; i<nextPosition; i++) { | ||
const axis = axes[i]; | ||
values[axis.name] = axis.values[0]; | ||
indices[i] = 1; // next index | ||
} | ||
position = 0; | ||
} | ||
|
||
function nextAvailablePosition() { | ||
let size = axes.length; | ||
for (let i = position; i < size; i++) { | ||
if (indices[i] < axes[i].values.length) { | ||
return i; | ||
} | ||
} | ||
return -1; | ||
} | ||
// The first initialization of the values | ||
resetValuesUpTo(this.axes.length); | ||
let good = 0; | ||
let bad = 0; | ||
while (true) { | ||
if (indices[position] < this.axes[position].values.length) { | ||
// Advance iterator at the current position if possible | ||
const axis = this.axes[position]; | ||
values[axis.name] = axis.values[indices[position]]; | ||
indices[position]++; | ||
} else { | ||
// Advance the next iterator, and reset [0..nextPosition) | ||
position++; | ||
let nextPosition = nextAvailablePosition(); | ||
if (nextPosition === -1) { | ||
break; | ||
} | ||
const axis = this.axes[nextPosition]; | ||
values[axis.name] = axis.values[indices[nextPosition]]; | ||
indices[nextPosition]++; | ||
resetValuesUpTo(nextPosition); | ||
} | ||
if (this.matches(values)) { | ||
good++; | ||
} else { | ||
bad++; | ||
} | ||
} | ||
return {good: good, bad: bad}; | ||
} | ||
} | ||
|
||
module.exports = {Axis, MatrixBuilder}; |
Oops, something went wrong.