diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml deleted file mode 100644 index d0d5831cbd..0000000000 --- a/.github/workflows/build-windows.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Windows - -on: [ push, pull_request ] - -jobs: - build: - env: - JAVA_VERSION: ${{ matrix.java_version }} - runs-on: windows-latest - strategy: - fail-fast: true - matrix: - java_version: [ 11, 17 ] - - steps: - - uses: actions/checkout@v4 - - name: "Set up JDK ${{ matrix.java_version }}" - uses: actions/setup-java@v4 - with: - distribution: 'adopt' - java-version: ${{ matrix.java_version }} - - name: Setup Gradle - uses: gradle/gradle-build-action@v3.4.1 - - - name: "build using jdk ${{ matrix.java_version }}" - run: ./gradlew build - - - name: Upload windows build code coverage - uses: codecov/codecov-action@v4.5.0 - if: ${{ github.event_name == 'pull_request' || (github.event_name == 'push' && github.ref == 'refs/heads/main') }} - with: - token: ${{ secrets.CODECOV_TOKEN }} - flags: current_windows - env_vars: JAVA_VERSION - # too many timeout errors, let's not fail at the moment - #fail_ci_if_error: true - - # TODO 3.0.0 re-activate scala API - # - name: composite build atrium-scala2 - # run: ./gradlew build - # working-directory: misc\tools\atrium-scala2-test - - - name: composite build atrium-samples-test - run: ./gradlew build - working-directory: misc\tools\atrium-samples-test - - # TODO 1.3.0 activate again once we use JS IR -# - name: composite build atrium-js-sample-test -# run: ./gradlew build -# working-directory: misc\tools\atrium-js-sample-test diff --git a/.github/workflows/matrix.js b/.github/workflows/matrix.js new file mode 100644 index 0000000000..bf5fcdef0a --- /dev/null +++ b/.github/workflows/matrix.js @@ -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 })); diff --git a/.github/workflows/matrix_builder.js b/.github/workflows/matrix_builder.js new file mode 100644 index 0000000000..a98ede8e6c --- /dev/null +++ b/.github/workflows/matrix_builder.js @@ -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; iandroid.sha256 + curl -o android.jar -L "https://github.com/Sable/android-platforms/blob/master/android-26/android.jar?raw=true" + echo "cdc1846376a14b0370cc63454a129606b4a52cc50ada75ef0d4cf956b1ad2daa android.jar" > android.sha256 if ! sha256sum -c android.sha256; then echo >&2 "wrong sha256 for android.jar, expected:" cat >&2 android.sha256 @@ -59,24 +80,9 @@ jobs: exit 1; fi - - name: check Atrium's -jvm.jar can be dexed + - name: Check Atrium's -jvm.jar can be dexed run: ATRIUM_ANDROID_JAR="$PWD/android-jar-cache/android.jar" ./gradlew checkDexer - # TODO 3.0.0 re-activate scala API - # - name: composite build atrium-scala2 - # run: ./gradlew build - # working-directory: misc/tools/atrium-scala2-test - - - - name: composite build atrium-samples-test - run: ./gradlew build - working-directory: misc/tools/atrium-samples-test - - # TODO 1.3.0 activate again once we use JS IR - # - name: composite build atrium-js-sample-test - # run: ./gradlew build - # working-directory: misc/tools/atrium-js-sample-test - forwardCompatibility: env: JAVA_VERSION: 17 @@ -88,13 +94,14 @@ jobs: kotlin_version: [ '1.5', '1.6', '1.7', '1.8', '1.9', '2.0' ] steps: - uses: actions/checkout@v4 - - name: "Set up JDK 17" + - name: Set up JDK 17 uses: actions/setup-java@v4 with: distribution: 'adopt' java-version: 17 + - name: Setup Gradle uses: gradle/gradle-build-action@v3.4.1 - - name: "build using kotlin ${{ matrix.kotlin_version }}" + - name: Build using Kotlin ${{ matrix.kotlin_version }} run: ./gradlew build diff --git a/README.md b/README.md index 476f74c9a8..1d0d76a55d 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,7 @@ [![Download](https://img.shields.io/badge/Download-1.2.0-%23007ec6)](https://central.sonatype.com/artifact/ch.tutteli.atrium/atrium-fluent/1.2.0) [![EUPL](https://img.shields.io/badge/%E2%9A%96-EUPL%201.2-%230b45a6)](https://joinup.ec.europa.eu/collection/eupl/eupl-text-11-12 "License") [![atrium @ kotlinlang.slack.com](https://img.shields.io/static/v1?label=kotlinlang&message=atrium&color=blue&logo=slack)](https://kotlinlang.slack.com/messages/atrium "See invitation link under section FAQ") -[![Build Status Ubuntu](https://github.com/robstoll/atrium/workflows/Ubuntu/badge.svg?event=push&branch=main)](https://github.com/robstoll/atrium/actions?query=workflow%3AUbuntu+branch%3Amain) -[![Build Status Windows](https://github.com/robstoll/atrium/workflows/Windows/badge.svg?event=push&branch=main)](https://github.com/robstoll/atrium/actions?query=workflow%3AWindows+branch%3Amain) +[![Quality Assurance](https://github.com/robstoll/atrium/actions/workflows/quality-assurance.yml/badge.svg?event=push&branch=main)](https://github.com/robstoll/atrium/actions/workflows/quality-assurance.yml?query=branch%3Amain) [![Coverage](https://codecov.io/gh/robstoll/atrium/branch/main/graph/badge.svg)](https://app.codecov.io/github/robstoll/atrium/branch/main) [![Newcomers Welcome](https://img.shields.io/badge/%F0%9F%91%8B-Newcomers%20Welcome-blueviolet)](https://github.com/robstoll/atrium/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22 "Ask in slack for help")