Compare commits

...

16 Commits

Author SHA1 Message Date
GitHub Actions ee09b1e59b Automation: update setup-beam version output to f8b54b5 2026-03-14 20:10:39 +00:00
Paulo F. Oliveira f8b54b559e Run ARM-specific tests consistently (#434)
* Run ARM-specific test consistently

* Fix for builds.hex.pm

https://builds.hex.pm/builds/otp/arm64/ubuntu-<x>.04/builds.txt

file is only available when <x> >= 20.

* Fix for builds.hex.pm

https://builds.hex.pm/builds/otp/arm64/ubuntu-20.04/builds.txt

does not contain Erlang 19 any more...
2026-03-14 20:10:19 +00:00
GitHub Actions bf1ce74034 Automation: update setup-beam version output to 559a930 2026-03-14 16:57:33 +00:00
dependabot[bot] 559a930751 Bump undici from 6.23.0 to 6.24.0 in the npm_and_yarn group across 1 directory (#432)
* Bump undici in the npm_and_yarn group across 1 directory

Bumps the npm_and_yarn group with 1 update in the / directory: [undici](https://github.com/nodejs/undici).


Updates `undici` from 6.23.0 to 6.24.0
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v6.23.0...v6.24.0)

---
updated-dependencies:
- dependency-name: undici
  dependency-version: 6.24.0
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>

* Help Dependabot (exec: npm run build-dist)

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Paulo F. Oliveira <paulo.ferraz.oliveira@gmail.com>
2026-03-14 16:57:11 +00:00
GitHub Actions 1380ebe712 Automation: update setup-beam version output to 0a54959 2026-03-14 16:50:39 +00:00
Sebastian Weddmark Olsson 0a549596b0 Compatibility matrix strategy (#389)
* Compatibility matrix strategy

Example on how one can maintain a compatibility matrix

* v2 -> v4

Co-authored-by: Kian-Meng Ang <kianmeng.ang@gmail.com>

* ubuntu-18 not longer available

* Update README.md after review suggestions

* Link rebar3 compat

Apparently rebar3 has started to maintain a version compatability table themselves. Missing older versions though

| 3.10.0 | 17         |
| 3.11.1 | 18         |

* Tweak README change proposal

* Apply suggestions from code review

Apply changes to resolve linter warnings

Co-authored-by: Paulo F. Oliveira <paulo.ferraz.oliveira@gmail.com>

---------

Co-authored-by: Kian-Meng Ang <kianmeng.ang@gmail.com>
Co-authored-by: Paulo F. Oliveira <paulo.ferraz.oliveira@gmail.com>
2026-03-14 16:50:22 +00:00
GitHub Actions 3a3c94fd9e Automation: update setup-beam version output to cfafb69 2026-03-14 16:43:34 +00:00
Daniel Widgren cfafb69ddd feat: add Erlang problem matchers (#433)
* feat: add Erlang problem matchers for erlc, dialyzer, CT, and EUnit

Adds GitHub Actions problem matchers for Erlang tooling, matching the
existing Elixir matcher support. Covers erlc errors/warnings (which
also catches dialyzer output), Common Test failures, and EUnit failures.

Closes #390

* chore: rebuild dist bundle and format test file

* refactor: move Erlang matchers into installOTP function

Moves maybeEnableErlangProblemMatchers() call inside installOTP(),
between setOutput and endGroup, matching the Elixir pattern.

* refactor: extract generic maybeEnableProblemMatchers function

Replaces separate maybeEnableErlangProblemMatchers and
maybeEnableElixirProblemMatchers with a single generic
maybeEnableProblemMatchers(language) and a problemMatchersEnabled()
helper.

* fix: use static paths for ncc to resolve matcher JSON files

ncc cannot trace dynamic template literals, so the matcher JSON files
were not being copied to dist/. Use a static map of paths instead.
2026-03-14 16:43:14 +00:00
GitHub Actions b3cbb6afbf Automation: update setup-beam version output to 2abe6cd 2026-03-14 16:30:13 +00:00
peter madsen-mygdal 2abe6cda7a fix: download correct Gleam binary for macOS and Linux ARM64 (#428)
The Gleam installer had two arch-related bugs:

1. Darwin reused the Linux config (installOpts.darwin = installOpts.linux),
   downloading x86_64-unknown-linux-musl on macOS, causing ENOEXEC.

2. Linux hardcoded x86_64, downloading the wrong binary on ARM64 runners,
   causing 'Syntax error' when trying to execute an x86_64 ELF.

Add arch detection via getRunnerOSArchitecture() to both Linux and
Darwin download URLs, selecting the correct aarch64 or x86_64 binary.
Add a proper Darwin config that downloads apple-darwin archives.
2026-03-14 16:29:49 +00:00
GitHub Actions d55fdba43f Automation: update setup-beam version output to eb86ad6 2026-03-14 16:29:25 +00:00
peter madsen-mygdal eb86ad6523 feat: add macOS-26 to supported ImageOS mappings (#430)
Add macos26 to the ImageOSToContainer map so runners using
macOS-26 are recognized instead of throwing an error.

Update macos.yml

Update README.md
2026-03-14 16:28:58 +00:00
GitHub Actions af6da5da48 Automation: update setup-beam version output to f018c1e 2026-03-13 23:46:13 +00:00
peter madsen-mygdal f018c1e6d8 fix: use dynamic import in tests to prevent ESM hoisting race (#429)
Static ESM imports are hoisted above top-level statements, causing
setup-beam.js to evaluate its main() guard before process.env.NODE_ENV
is set to 'test'. This triggered core.setFailed() during module load,
setting process.exitCode=1 and failing the test file even though all
individual test cases passed.

Switch setup-beam.js to a dynamic await import() so that the env and
input setup runs first, ensuring the NODE_ENV='test' guard works
correctly.

Amp-Thread-ID: https://ampcode.com/threads/T-019ce36a-a18f-7494-ac0a-c094a84f06ad

Co-authored-by: Amp <amp@ampcode.com>
2026-03-13 23:45:53 +00:00
GitHub Actions d843ebb78b Automation: update setup-beam version output to c8e0954 2026-03-11 17:47:37 +00:00
github-actions[bot] c8e09547a7 Update 3rd party licenses (automation) (#425)
Update 3rd party licenses

Co-authored-by: GitHub Actions <actions@user.noreply.github.com>
2026-03-11 17:47:17 +00:00
10 changed files with 612 additions and 124 deletions
+18 -3
View File
@@ -63,7 +63,12 @@ jobs:
unit_tests_ubuntu: unit_tests_ubuntu:
name: Unit tests (Ubuntu) name: Unit tests (Ubuntu)
runs-on: ubuntu-latest strategy:
matrix:
os:
- ubuntu-24.04-arm # ARM64
- ubuntu-latest # x86_64
runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v6.0.2 - uses: actions/checkout@v6.0.2
- uses: actions/setup-node@v6.3.0 - uses: actions/setup-node@v6.3.0
@@ -84,7 +89,12 @@ jobs:
unit_tests_windows: unit_tests_windows:
name: Unit tests (Windows) name: Unit tests (Windows)
runs-on: windows-latest strategy:
matrix:
os:
- windows-11-arm # ARM64
- windows-latest # x86_64
runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v6.0.2 - uses: actions/checkout@v6.0.2
- uses: actions/setup-node@v6.3.0 - uses: actions/setup-node@v6.3.0
@@ -105,7 +115,12 @@ jobs:
unit_tests_macos: unit_tests_macos:
name: Unit tests (macOS) name: Unit tests (macOS)
runs-on: macos-latest strategy:
matrix:
os:
- macos-latest # ARM64
- macos-15-intel # x86_64
runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v6.0.2 - uses: actions/checkout@v6.0.2
- uses: actions/setup-node@v6.3.0 - uses: actions/setup-node@v6.3.0
+3
View File
@@ -45,6 +45,9 @@ jobs:
version-type: 'strict' version-type: 'strict'
- otp-version: '25' - otp-version: '25'
os: 'macos-15' os: 'macos-15'
- otp-version: '27'
elixir-version: '1.18'
os: 'macos-26'
- otp-version: '26' - otp-version: '26'
elixir-version: '1.16' elixir-version: '1.16'
rebar3-version: '3.25' rebar3-version: '3.25'
+79 -50
View File
@@ -312,30 +312,6 @@ THE SOFTWARE.
----- -----
The following software may be included in this product: @fastify/busboy. A copy of the source code may be downloaded from git+https://github.com/fastify/busboy.git. This software contains the following license and notice below:
Copyright Brian White. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
-----
The following software may be included in this product: @felipecrs/decompress-tarxz, @xhmikosr/decompress-tar, decompress, decompress-tar, decompress-tarbz2, decompress-targz, shebang-command. A copy of the source code may be downloaded from git+https://github.com/felipecrs/decompress-tarxz.git (@felipecrs/decompress-tarxz), git+https://github.com/XhmikosR/decompress-tar.git (@xhmikosr/decompress-tar), https://github.com/kevva/decompress.git (decompress), https://github.com/kevva/decompress-tar.git (decompress-tar), https://github.com/kevva/decompress-tarbz2.git (decompress-tarbz2), https://github.com/kevva/decompress-targz.git (decompress-targz), https://github.com/kevva/shebang-command.git (shebang-command). This software contains the following license and notice below: The following software may be included in this product: @felipecrs/decompress-tarxz, @xhmikosr/decompress-tar, decompress, decompress-tar, decompress-tarbz2, decompress-targz, shebang-command. A copy of the source code may be downloaded from git+https://github.com/felipecrs/decompress-tarxz.git (@felipecrs/decompress-tarxz), git+https://github.com/XhmikosR/decompress-tar.git (@xhmikosr/decompress-tar), https://github.com/kevva/decompress.git (decompress), https://github.com/kevva/decompress-tar.git (decompress-tar), https://github.com/kevva/decompress-tarbz2.git (decompress-tarbz2), https://github.com/kevva/decompress-targz.git (decompress-targz), https://github.com/kevva/shebang-command.git (shebang-command). This software contains the following license and notice below:
MIT License MIT License
@@ -350,6 +326,32 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
----- -----
The following software may be included in this product: @ota-meshi/ast-token-store. A copy of the source code may be downloaded from git+https://github.com/ota-meshi/ast-token-store.git. This software contains the following license and notice below:
MIT License
Copyright (c) 2026 Yosuke Ota
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-----
The following software may be included in this product: @tokenizer/inflate. A copy of the source code may be downloaded from https://github.com/Borewit/tokenizer-inflate.git. This software contains the following license and notice below: The following software may be included in this product: @tokenizer/inflate. A copy of the source code may be downloaded from https://github.com/Borewit/tokenizer-inflate.git. This software contains the following license and notice below:
Copyright (c) 2024, Borewit Copyright (c) 2024, Borewit
@@ -1626,6 +1628,32 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
----- -----
The following software may be included in this product: diff-sequences. A copy of the source code may be downloaded from https://github.com/jestjs/jest.git. This software contains the following license and notice below:
MIT License
Copyright (c) Meta Platforms, Inc. and affiliates.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-----
The following software may be included in this product: dunder-proto, math-intrinsics. A copy of the source code may be downloaded from git+https://github.com/es-shims/dunder-proto.git (dunder-proto), git+https://github.com/es-shims/math-intrinsics.git (math-intrinsics). This software contains the following license and notice below: The following software may be included in this product: dunder-proto, math-intrinsics. A copy of the source code may be downloaded from git+https://github.com/es-shims/dunder-proto.git (dunder-proto), git+https://github.com/es-shims/math-intrinsics.git (math-intrinsics). This software contains the following license and notice below:
MIT License MIT License
@@ -1747,32 +1775,6 @@ SOFTWARE.
----- -----
The following software may be included in this product: eslint-compat-utils. A copy of the source code may be downloaded from git+https://github.com/ota-meshi/eslint-compat-utils.git. This software contains the following license and notice below:
MIT License
Copyright (c) 2023 Yosuke Ota
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-----
The following software may be included in this product: eslint-plugin-yml, yaml-eslint-parser. A copy of the source code may be downloaded from git+https://github.com/ota-meshi/eslint-plugin-yml.git (eslint-plugin-yml), git+https://github.com/ota-meshi/yaml-eslint-parser.git (yaml-eslint-parser). This software contains the following license and notice below: The following software may be included in this product: eslint-plugin-yml, yaml-eslint-parser. A copy of the source code may be downloaded from git+https://github.com/ota-meshi/eslint-plugin-yml.git (eslint-plugin-yml), git+https://github.com/ota-meshi/yaml-eslint-parser.git (yaml-eslint-parser). This software contains the following license and notice below:
MIT License MIT License
@@ -4018,6 +4020,33 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
----- -----
The following software may be included in this product: toml. A copy of the source code may be downloaded from git://github.com/BinaryMuse/toml-node.git. This software contains the following license and notice below:
Copyright (c) 2012 Michelle Tilley
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
-----
The following software may be included in this product: tslib. A copy of the source code may be downloaded from https://github.com/Microsoft/tslib.git. This software contains the following license and notice below: The following software may be included in this product: tslib. A copy of the source code may be downloaded from https://github.com/Microsoft/tslib.git. This software contains the following license and notice below:
Copyright (c) Microsoft Corporation. Copyright (c) Microsoft Corporation.
+35
View File
@@ -88,10 +88,16 @@ and Erlang/OTP.
| `macOS-13` | 25.0 - 28 | x86_64, arm64 | ✅ | `macOS-13` | 25.0 - 28 | x86_64, arm64 | ✅
| `macOS-14` | 25.0 - 28 | x86_64, arm64 | ✅ | `macOS-14` | 25.0 - 28 | x86_64, arm64 | ✅
| `macOS-15` | 25.0 - 28 | x86_64, arm64 | ✅ | `macOS-15` | 25.0 - 28 | x86_64, arm64 | ✅
| `macOS-26` | 25.0 - 28 | x86_64, arm64 | ✅
**Note** \*: prior to 23, Windows builds are only available for minor versions, e.g. 21.0, 21.3, **Note** \*: prior to 23, Windows builds are only available for minor versions, e.g. 21.0, 21.3,
22.0, etc. 22.0, etc.
### Compatibility between Erlang/OTP and rebar3
Check [version compatibility](https://github.com/erlang/rebar3?tab=readme-ov-file#compatibility-between-rebar3-and-erlangotp)
in `erlang/rebar3`.
### Self-hosted runners ### Self-hosted runners
Self-hosted runners need to set env. variable `ImageOS` to one of the following, since the action Self-hosted runners need to set env. variable `ImageOS` to one of the following, since the action
@@ -107,6 +113,7 @@ uses that to download assets:
| `macos13` | `macOS-13` | `macos13` | `macOS-13`
| `macos14` | `macOS-14` | `macos14` | `macOS-14`
| `macos15` | `macOS-15` | `macos15` | `macOS-15`
| `macos26` | `macOS-26`
as per the following example: as per the following example:
@@ -290,6 +297,34 @@ jobs:
- run: rebar3 ct - run: rebar3 ct
``` ```
### Compatibility
To execute matrix testing with specific dependency versions, configure the environment matrix to
use pinned, compatible releases:
```yaml
test:
runs-on: ${{matrix.compat.os}}
name: Erlang/OTP ${{matrix.compat.otp}} / rebar3 ${{matrix.compat.rebar3}}
strategy:
matrix:
compat:
- otp: "26.2.5.5"
rebar3: "3.22.1"
os: ubuntu-24.04
- otp: "27.1.2"
rebar3: "3.22.1"
os: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: erlef/setup-beam@v1
with:
otp-version: ${{matrix.compat.otp}}
rebar3-version: ${{matrix.compat.rebar3}}
version-type: strict
- run: rebar3 eunit
```
### Erlang/OTP + `rebar3`, on Windows ### Erlang/OTP + `rebar3`, on Windows
```yaml ```yaml
+66
View File
@@ -0,0 +1,66 @@
{
"problemMatcher": [
{
"owner": "erlc-warning",
"severity": "warning",
"pattern": [
{
"regexp": "^(\\S+):(\\d+):(\\d+): [wW]arning: (.+)$",
"file": 1,
"line": 2,
"column": 3,
"message": 4
}
]
},
{
"owner": "erlc-error",
"severity": "error",
"pattern": [
{
"regexp": "^(\\S+):(\\d+):(\\d+): (?![wW]arning)(.+)$",
"file": 1,
"line": 2,
"column": 3,
"message": 4
}
]
},
{
"owner": "ct-failure",
"severity": "error",
"pattern": [
{
"regexp": "^(\\S+) failed on line (\\d+)$",
"file": 1,
"line": 2
},
{
"regexp": "^Reason: (.+)$",
"message": 1
}
]
},
{
"owner": "eunit-failure",
"severity": "error",
"pattern": [
{
"regexp": "^\\S+: .+\\.\\.\\.\\*failed\\*$"
},
{
"regexp": "^in function .+ \\((.+), line (\\d+)\\)$",
"file": 1,
"line": 2
},
{
"regexp": "^in call from .+$"
},
{
"regexp": "^\\*\\*error:(.+)$",
"message": 1
}
]
}
]
}
+174 -28
View File
@@ -26921,6 +26921,24 @@ class SecureProxyConnectionError extends UndiciError {
[kSecureProxyConnectionError] = true [kSecureProxyConnectionError] = true
} }
const kMessageSizeExceededError = Symbol.for('undici.error.UND_ERR_WS_MESSAGE_SIZE_EXCEEDED')
class MessageSizeExceededError extends UndiciError {
constructor (message) {
super(message)
this.name = 'MessageSizeExceededError'
this.message = message || 'Max decompressed message size exceeded'
this.code = 'UND_ERR_WS_MESSAGE_SIZE_EXCEEDED'
}
static [Symbol.hasInstance] (instance) {
return instance && instance[kMessageSizeExceededError] === true
}
get [kMessageSizeExceededError] () {
return true
}
}
module.exports = { module.exports = {
AbortError, AbortError,
HTTPParserError, HTTPParserError,
@@ -26944,7 +26962,8 @@ module.exports = {
ResponseExceededMaxSizeError, ResponseExceededMaxSizeError,
RequestRetryError, RequestRetryError,
ResponseError, ResponseError,
SecureProxyConnectionError SecureProxyConnectionError,
MessageSizeExceededError
} }
@@ -27021,6 +27040,10 @@ class Request {
throw new InvalidArgumentError('upgrade must be a string') throw new InvalidArgumentError('upgrade must be a string')
} }
if (upgrade && !isValidHeaderValue(upgrade)) {
throw new InvalidArgumentError('invalid upgrade header')
}
if (headersTimeout != null && (!Number.isFinite(headersTimeout) || headersTimeout < 0)) { if (headersTimeout != null && (!Number.isFinite(headersTimeout) || headersTimeout < 0)) {
throw new InvalidArgumentError('invalid headersTimeout') throw new InvalidArgumentError('invalid headersTimeout')
} }
@@ -27315,13 +27338,19 @@ function processHeader (request, key, val) {
val = `${val}` val = `${val}`
} }
if (request.host === null && headerName === 'host') { if (headerName === 'host') {
if (request.host !== null) {
throw new InvalidArgumentError('duplicate host header')
}
if (typeof val !== 'string') { if (typeof val !== 'string') {
throw new InvalidArgumentError('invalid host header') throw new InvalidArgumentError('invalid host header')
} }
// Consumed by Client // Consumed by Client
request.host = val request.host = val
} else if (request.contentLength === null && headerName === 'content-length') { } else if (headerName === 'content-length') {
if (request.contentLength !== null) {
throw new InvalidArgumentError('duplicate content-length header')
}
request.contentLength = parseInt(val, 10) request.contentLength = parseInt(val, 10)
if (!Number.isFinite(request.contentLength)) { if (!Number.isFinite(request.contentLength)) {
throw new InvalidArgumentError('invalid content-length header') throw new InvalidArgumentError('invalid content-length header')
@@ -50038,20 +50067,38 @@ module.exports = {
const { createInflateRaw, Z_DEFAULT_WINDOWBITS } = __nccwpck_require__(8522) const { createInflateRaw, Z_DEFAULT_WINDOWBITS } = __nccwpck_require__(8522)
const { isValidClientWindowBits } = __nccwpck_require__(8625) const { isValidClientWindowBits } = __nccwpck_require__(8625)
const { MessageSizeExceededError } = __nccwpck_require__(8707)
const tail = Buffer.from([0x00, 0x00, 0xff, 0xff]) const tail = Buffer.from([0x00, 0x00, 0xff, 0xff])
const kBuffer = Symbol('kBuffer') const kBuffer = Symbol('kBuffer')
const kLength = Symbol('kLength') const kLength = Symbol('kLength')
// Default maximum decompressed message size: 4 MB
const kDefaultMaxDecompressedSize = 4 * 1024 * 1024
class PerMessageDeflate { class PerMessageDeflate {
/** @type {import('node:zlib').InflateRaw} */ /** @type {import('node:zlib').InflateRaw} */
#inflate #inflate
#options = {} #options = {}
constructor (extensions) { /** @type {number} */
#maxDecompressedSize
/** @type {boolean} */
#aborted = false
/** @type {Function|null} */
#currentCallback = null
/**
* @param {Map<string, string>} extensions
* @param {{ maxDecompressedMessageSize?: number }} [options]
*/
constructor (extensions, options = {}) {
this.#options.serverNoContextTakeover = extensions.has('server_no_context_takeover') this.#options.serverNoContextTakeover = extensions.has('server_no_context_takeover')
this.#options.serverMaxWindowBits = extensions.get('server_max_window_bits') this.#options.serverMaxWindowBits = extensions.get('server_max_window_bits')
this.#maxDecompressedSize = options.maxDecompressedMessageSize ?? kDefaultMaxDecompressedSize
} }
decompress (chunk, fin, callback) { decompress (chunk, fin, callback) {
@@ -50060,6 +50107,11 @@ class PerMessageDeflate {
// payload of the message. // payload of the message.
// 2. Decompress the resulting data using DEFLATE. // 2. Decompress the resulting data using DEFLATE.
if (this.#aborted) {
callback(new MessageSizeExceededError())
return
}
if (!this.#inflate) { if (!this.#inflate) {
let windowBits = Z_DEFAULT_WINDOWBITS let windowBits = Z_DEFAULT_WINDOWBITS
@@ -50072,13 +50124,37 @@ class PerMessageDeflate {
windowBits = Number.parseInt(this.#options.serverMaxWindowBits) windowBits = Number.parseInt(this.#options.serverMaxWindowBits)
} }
this.#inflate = createInflateRaw({ windowBits }) try {
this.#inflate = createInflateRaw({ windowBits })
} catch (err) {
callback(err)
return
}
this.#inflate[kBuffer] = [] this.#inflate[kBuffer] = []
this.#inflate[kLength] = 0 this.#inflate[kLength] = 0
this.#inflate.on('data', (data) => { this.#inflate.on('data', (data) => {
this.#inflate[kBuffer].push(data) if (this.#aborted) {
return
}
this.#inflate[kLength] += data.length this.#inflate[kLength] += data.length
if (this.#inflate[kLength] > this.#maxDecompressedSize) {
this.#aborted = true
this.#inflate.removeAllListeners()
this.#inflate.destroy()
this.#inflate = null
if (this.#currentCallback) {
const cb = this.#currentCallback
this.#currentCallback = null
cb(new MessageSizeExceededError())
}
return
}
this.#inflate[kBuffer].push(data)
}) })
this.#inflate.on('error', (err) => { this.#inflate.on('error', (err) => {
@@ -50087,16 +50163,22 @@ class PerMessageDeflate {
}) })
} }
this.#currentCallback = callback
this.#inflate.write(chunk) this.#inflate.write(chunk)
if (fin) { if (fin) {
this.#inflate.write(tail) this.#inflate.write(tail)
} }
this.#inflate.flush(() => { this.#inflate.flush(() => {
if (this.#aborted || !this.#inflate) {
return
}
const full = Buffer.concat(this.#inflate[kBuffer], this.#inflate[kLength]) const full = Buffer.concat(this.#inflate[kBuffer], this.#inflate[kLength])
this.#inflate[kBuffer].length = 0 this.#inflate[kBuffer].length = 0
this.#inflate[kLength] = 0 this.#inflate[kLength] = 0
this.#currentCallback = null
callback(null, full) callback(null, full)
}) })
@@ -50150,14 +50232,23 @@ class ByteParser extends Writable {
/** @type {Map<string, PerMessageDeflate>} */ /** @type {Map<string, PerMessageDeflate>} */
#extensions #extensions
constructor (ws, extensions) { /** @type {{ maxDecompressedMessageSize?: number }} */
#options
/**
* @param {import('./websocket').WebSocket} ws
* @param {Map<string, string>|null} extensions
* @param {{ maxDecompressedMessageSize?: number }} [options]
*/
constructor (ws, extensions, options = {}) {
super() super()
this.ws = ws this.ws = ws
this.#extensions = extensions == null ? new Map() : extensions this.#extensions = extensions == null ? new Map() : extensions
this.#options = options
if (this.#extensions.has('permessage-deflate')) { if (this.#extensions.has('permessage-deflate')) {
this.#extensions.set('permessage-deflate', new PerMessageDeflate(extensions)) this.#extensions.set('permessage-deflate', new PerMessageDeflate(extensions, options))
} }
} }
@@ -50292,6 +50383,7 @@ class ByteParser extends Writable {
const buffer = this.consume(8) const buffer = this.consume(8)
const upper = buffer.readUInt32BE(0) const upper = buffer.readUInt32BE(0)
const lower = buffer.readUInt32BE(4)
// 2^31 is the maximum bytes an arraybuffer can contain // 2^31 is the maximum bytes an arraybuffer can contain
// on 32-bit systems. Although, on 64-bit systems, this is // on 32-bit systems. Although, on 64-bit systems, this is
@@ -50299,14 +50391,12 @@ class ByteParser extends Writable {
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Invalid_array_length // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Invalid_array_length
// https://source.chromium.org/chromium/chromium/src/+/main:v8/src/common/globals.h;drc=1946212ac0100668f14eb9e2843bdd846e510a1e;bpv=1;bpt=1;l=1275 // https://source.chromium.org/chromium/chromium/src/+/main:v8/src/common/globals.h;drc=1946212ac0100668f14eb9e2843bdd846e510a1e;bpv=1;bpt=1;l=1275
// https://source.chromium.org/chromium/chromium/src/+/main:v8/src/objects/js-array-buffer.h;l=34;drc=1946212ac0100668f14eb9e2843bdd846e510a1e // https://source.chromium.org/chromium/chromium/src/+/main:v8/src/objects/js-array-buffer.h;l=34;drc=1946212ac0100668f14eb9e2843bdd846e510a1e
if (upper > 2 ** 31 - 1) { if (upper !== 0 || lower > 2 ** 31 - 1) {
failWebsocketConnection(this.ws, 'Received payload length > 2^31 bytes.') failWebsocketConnection(this.ws, 'Received payload length > 2^31 bytes.')
return return
} }
const lower = buffer.readUInt32BE(4) this.#info.payloadLength = lower
this.#info.payloadLength = (upper << 8) + lower
this.#state = parserStates.READ_DATA this.#state = parserStates.READ_DATA
} else if (this.#state === parserStates.READ_DATA) { } else if (this.#state === parserStates.READ_DATA) {
if (this.#byteOffset < this.#info.payloadLength) { if (this.#byteOffset < this.#info.payloadLength) {
@@ -50336,7 +50426,7 @@ class ByteParser extends Writable {
} else { } else {
this.#extensions.get('permessage-deflate').decompress(body, this.#info.fin, (error, data) => { this.#extensions.get('permessage-deflate').decompress(body, this.#info.fin, (error, data) => {
if (error) { if (error) {
closeWebSocketConnection(this.ws, 1007, error.message, error.message.length) failWebsocketConnection(this.ws, error.message)
return return
} }
@@ -50940,6 +51030,12 @@ function parseExtensions (extensions) {
* @param {string} value * @param {string} value
*/ */
function isValidClientWindowBits (value) { function isValidClientWindowBits (value) {
// Must have at least one character
if (value.length === 0) {
return false
}
// Check all characters are ASCII digits
for (let i = 0; i < value.length; i++) { for (let i = 0; i < value.length; i++) {
const byte = value.charCodeAt(i) const byte = value.charCodeAt(i)
@@ -50948,7 +51044,9 @@ function isValidClientWindowBits (value) {
} }
} }
return true // Check numeric range: zlib requires windowBits in range 8-15
const num = Number.parseInt(value, 10)
return num >= 8 && num <= 15
} }
// https://nodejs.org/api/intl.html#detecting-internationalization-support // https://nodejs.org/api/intl.html#detecting-internationalization-support
@@ -51039,6 +51137,9 @@ class WebSocket extends EventTarget {
/** @type {SendQueue} */ /** @type {SendQueue} */
#sendQueue #sendQueue
/** @type {{ maxDecompressedMessageSize?: number }} */
#options
/** /**
* @param {string} url * @param {string} url
* @param {string|string[]} protocols * @param {string|string[]} protocols
@@ -51112,6 +51213,11 @@ class WebSocket extends EventTarget {
// 10. Set this's url to urlRecord. // 10. Set this's url to urlRecord.
this[kWebSocketURL] = new URL(urlRecord.href) this[kWebSocketURL] = new URL(urlRecord.href)
// Store options for later use (e.g., maxDecompressedMessageSize)
this.#options = {
maxDecompressedMessageSize: options.maxDecompressedMessageSize
}
// 11. Let client be this's relevant settings object. // 11. Let client be this's relevant settings object.
const client = environmentSettingsObject.settingsObject const client = environmentSettingsObject.settingsObject
@@ -51426,11 +51532,11 @@ class WebSocket extends EventTarget {
* @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol * @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol
*/ */
#onConnectionEstablished (response, parsedExtensions) { #onConnectionEstablished (response, parsedExtensions) {
// processResponse is called when the "responses header list has been received and initialized." // processResponse is called when the "response's header list has been received and initialized."
// once this happens, the connection is open // once this happens, the connection is open
this[kResponse] = response this[kResponse] = response
const parser = new ByteParser(this, parsedExtensions) const parser = new ByteParser(this, parsedExtensions, this.#options)
parser.on('drain', onParserDrain) parser.on('drain', onParserDrain)
parser.on('error', onParserError.bind(this)) parser.on('error', onParserError.bind(this))
@@ -51533,6 +51639,19 @@ webidl.converters.WebSocketInit = webidl.dictionaryConverter([
{ {
key: 'headers', key: 'headers',
converter: webidl.nullableConverter(webidl.converters.HeadersInit) converter: webidl.nullableConverter(webidl.converters.HeadersInit)
},
{
key: 'maxDecompressedMessageSize',
converter: webidl.nullableConverter((V) => {
V = webidl.converters['unsigned long long'](V)
if (V <= 0) {
throw webidl.errors.exception({
header: 'WebSocket constructor',
message: 'maxDecompressedMessageSize must be greater than 0'
})
}
return V
})
} }
]) ])
@@ -57566,7 +57685,7 @@ async function main() {
await maybeInstallRebar3(rebar3Spec) await maybeInstallRebar3(rebar3Spec)
// undefined is replaced by a function, post- main branch merge // undefined is replaced by a function, post- main branch merge
const setupBeamVersion = '98c13a9' const setupBeamVersion = 'f8b54b5'
setOutput('setup-beam-version', setupBeamVersion) setOutput('setup-beam-version', setupBeamVersion)
} }
@@ -57588,6 +57707,7 @@ async function installOTP(otpSpec) {
}, },
}) })
setOutput('otp-version', otpVersion) setOutput('otp-version', otpVersion)
maybeEnableProblemMatchers('erlang')
endGroup() endGroup()
return otpVersion return otpVersion
@@ -57609,7 +57729,7 @@ async function maybeInstallElixir(elixirSpec) {
}, },
}) })
setOutput('elixir-version', elixirVersion) setOutput('elixir-version', elixirVersion)
maybeEnableElixirProblemMatchers() maybeEnableProblemMatchers('elixir')
endGroup() endGroup()
installed = true installed = true
@@ -57618,14 +57738,18 @@ async function maybeInstallElixir(elixirSpec) {
return installed return installed
} }
function maybeEnableElixirProblemMatchers() { function problemMatchersEnabled() {
const disableProblemMatchers = setup_beam_getInput('disable_problem_matchers', false) return setup_beam_getInput('disable_problem_matchers', false) === 'false'
if (disableProblemMatchers === 'false') { }
// path.join helps ncc figure this out
const elixirMatchers = external_node_path_namespaceObject.join( // path.join with static strings helps ncc resolve and bundle the JSON files
__nccwpck_require__.ab + "elixir-matchers.json", function maybeEnableProblemMatchers(language) {
) if (problemMatchersEnabled()) {
info(`##[add-matcher]${elixirMatchers}`) const matcherFiles = {
erlang: external_node_path_namespaceObject.join(__nccwpck_require__.ab + "erlang-matchers.json"),
elixir: external_node_path_namespaceObject.join(__nccwpck_require__.ab + "elixir-matchers.json"),
}
info(`##[add-matcher]${matcherFiles[language]}`)
} }
} }
@@ -58172,6 +58296,7 @@ function getRunnerOSVersion() {
macos13: 'macOS-13', macos13: 'macOS-13',
macos14: 'macOS-14', macos14: 'macOS-14',
macos15: 'macOS-15', macos15: 'macOS-15',
macos26: 'macOS-26',
} }
const deprecatedImageOSToContainer = { const deprecatedImageOSToContainer = {
ubuntu18: 'ubuntu-18.04', ubuntu18: 'ubuntu-18.04',
@@ -58557,12 +58682,14 @@ async function install(toolName, opts) {
tool: 'Gleam', tool: 'Gleam',
linux: { linux: {
downloadToolURL: () => { downloadToolURL: () => {
const arch = getRunnerOSArchitecture()
let gz let gz
if ( if (
versionSpec === 'nightly' || versionSpec === 'nightly' ||
node_modules_semver.gt(versionSpec, 'v0.22.1') node_modules_semver.gt(versionSpec, 'v0.22.1')
) { ) {
gz = `gleam-${versionSpec}-x86_64-unknown-linux-musl.tar.gz` const target = arch === 'arm64' ? 'aarch64' : 'x86_64'
gz = `gleam-${versionSpec}-${target}-unknown-linux-musl.tar.gz`
} else { } else {
gz = `gleam-${versionSpec}-linux-amd64.tar.gz` gz = `gleam-${versionSpec}-linux-amd64.tar.gz`
} }
@@ -58626,7 +58753,26 @@ async function install(toolName, opts) {
}, },
}, },
} }
installOpts.darwin = installOpts.linux installOpts.darwin = {
downloadToolURL: () => {
const arch = getRunnerOSArchitecture()
let gz
if (versionSpec === 'nightly' || node_modules_semver.gt(versionSpec, 'v0.22.1')) {
const target = arch === 'arm64' ? 'aarch64' : 'x86_64'
gz = `gleam-${versionSpec}-${target}-apple-darwin.tar.gz`
} else {
gz =
arch === 'arm64'
? `gleam-${versionSpec}-macos-arm64.tar.gz`
: `gleam-${versionSpec}-macos.tar.gz`
}
return `https://github.com/gleam-lang/gleam/releases/download/${versionSpec}/${gz}`
},
extract: installOpts.linux.extract,
postExtract: installOpts.linux.postExtract,
reportVersion: installOpts.linux.reportVersion,
}
break break
case 'rebar3': case 'rebar3':
installOpts = { installOpts = {
+66
View File
@@ -0,0 +1,66 @@
{
"problemMatcher": [
{
"owner": "erlc-warning",
"severity": "warning",
"pattern": [
{
"regexp": "^(\\S+):(\\d+):(\\d+): [wW]arning: (.+)$",
"file": 1,
"line": 2,
"column": 3,
"message": 4
}
]
},
{
"owner": "erlc-error",
"severity": "error",
"pattern": [
{
"regexp": "^(\\S+):(\\d+):(\\d+): (?![wW]arning)(.+)$",
"file": 1,
"line": 2,
"column": 3,
"message": 4
}
]
},
{
"owner": "ct-failure",
"severity": "error",
"pattern": [
{
"regexp": "^(\\S+) failed on line (\\d+)$",
"file": 1,
"line": 2
},
{
"regexp": "^Reason: (.+)$",
"message": 1
}
]
},
{
"owner": "eunit-failure",
"severity": "error",
"pattern": [
{
"regexp": "^\\S+: .+\\.\\.\\.\\*failed\\*$"
},
{
"regexp": "^in function .+ \\((.+), line (\\d+)\\)$",
"file": 1,
"line": 2
},
{
"regexp": "^in call from .+$"
},
{
"regexp": "^\\*\\*error:(.+)$",
"message": 1
}
]
}
]
}
+3 -3
View File
@@ -3626,9 +3626,9 @@
} }
}, },
"node_modules/undici": { "node_modules/undici": {
"version": "6.23.0", "version": "6.24.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz", "resolved": "https://registry.npmjs.org/undici/-/undici-6.24.0.tgz",
"integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==", "integrity": "sha512-lVLNosgqo5EkGqh5XUDhGfsMSoO8K0BAN0TyJLvwNRSl4xWGZlCVYsAIpa/OpA3TvmnM01GWcoKmc3ZWo5wKKA==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=18.17" "node": ">=18.17"
+39 -12
View File
@@ -58,7 +58,7 @@ async function main() {
await maybeInstallRebar3(rebar3Spec) await maybeInstallRebar3(rebar3Spec)
// undefined is replaced by a function, post- main branch merge // undefined is replaced by a function, post- main branch merge
const setupBeamVersion = '98c13a9' const setupBeamVersion = 'f8b54b5'
core.setOutput('setup-beam-version', setupBeamVersion) core.setOutput('setup-beam-version', setupBeamVersion)
} }
@@ -80,6 +80,7 @@ async function installOTP(otpSpec) {
}, },
}) })
core.setOutput('otp-version', otpVersion) core.setOutput('otp-version', otpVersion)
maybeEnableProblemMatchers('erlang')
core.endGroup() core.endGroup()
return otpVersion return otpVersion
@@ -101,7 +102,7 @@ async function maybeInstallElixir(elixirSpec) {
}, },
}) })
core.setOutput('elixir-version', elixirVersion) core.setOutput('elixir-version', elixirVersion)
maybeEnableElixirProblemMatchers() maybeEnableProblemMatchers('elixir')
core.endGroup() core.endGroup()
installed = true installed = true
@@ -110,14 +111,18 @@ async function maybeInstallElixir(elixirSpec) {
return installed return installed
} }
function maybeEnableElixirProblemMatchers() { function problemMatchersEnabled() {
const disableProblemMatchers = getInput('disable_problem_matchers', false) return getInput('disable_problem_matchers', false) === 'false'
if (disableProblemMatchers === 'false') { }
// path.join helps ncc figure this out
const elixirMatchers = path.join( // path.join with static strings helps ncc resolve and bundle the JSON files
`${__dirname}/../matchers/elixir-matchers.json`, function maybeEnableProblemMatchers(language) {
) if (problemMatchersEnabled()) {
core.info(`##[add-matcher]${elixirMatchers}`) const matcherFiles = {
erlang: path.join(`${__dirname}/../matchers/erlang-matchers.json`),
elixir: path.join(`${__dirname}/../matchers/elixir-matchers.json`),
}
core.info(`##[add-matcher]${matcherFiles[language]}`)
} }
} }
@@ -665,6 +670,7 @@ function getRunnerOSVersion() {
macos13: 'macOS-13', macos13: 'macOS-13',
macos14: 'macOS-14', macos14: 'macOS-14',
macos15: 'macOS-15', macos15: 'macOS-15',
macos26: 'macOS-26',
} }
const deprecatedImageOSToContainer = { const deprecatedImageOSToContainer = {
ubuntu18: 'ubuntu-18.04', ubuntu18: 'ubuntu-18.04',
@@ -1050,12 +1056,14 @@ async function install(toolName, opts) {
tool: 'Gleam', tool: 'Gleam',
linux: { linux: {
downloadToolURL: () => { downloadToolURL: () => {
const arch = getRunnerOSArchitecture()
let gz let gz
if ( if (
versionSpec === 'nightly' || versionSpec === 'nightly' ||
semver.gt(versionSpec, 'v0.22.1') semver.gt(versionSpec, 'v0.22.1')
) { ) {
gz = `gleam-${versionSpec}-x86_64-unknown-linux-musl.tar.gz` const target = arch === 'arm64' ? 'aarch64' : 'x86_64'
gz = `gleam-${versionSpec}-${target}-unknown-linux-musl.tar.gz`
} else { } else {
gz = `gleam-${versionSpec}-linux-amd64.tar.gz` gz = `gleam-${versionSpec}-linux-amd64.tar.gz`
} }
@@ -1119,7 +1127,26 @@ async function install(toolName, opts) {
}, },
}, },
} }
installOpts.darwin = installOpts.linux installOpts.darwin = {
downloadToolURL: () => {
const arch = getRunnerOSArchitecture()
let gz
if (versionSpec === 'nightly' || semver.gt(versionSpec, 'v0.22.1')) {
const target = arch === 'arm64' ? 'aarch64' : 'x86_64'
gz = `gleam-${versionSpec}-${target}-apple-darwin.tar.gz`
} else {
gz =
arch === 'arm64'
? `gleam-${versionSpec}-macos-arm64.tar.gz`
: `gleam-${versionSpec}-macos.tar.gz`
}
return `https://github.com/gleam-lang/gleam/releases/download/${versionSpec}/${gz}`
},
extract: installOpts.linux.extract,
postExtract: installOpts.linux.postExtract,
reportVersion: installOpts.linux.reportVersion,
}
break break
case 'rebar3': case 'rebar3':
installOpts = { installOpts = {
+129 -28
View File
@@ -1,3 +1,15 @@
import assert from 'node:assert'
import http from 'node:http'
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import { describe, it } from 'node:test'
import * as csv from 'csv-parse/sync'
import elixirMatchers from '../matchers/elixir-matchers.json' with { type: 'json' }
import erlangMatchers from '../matchers/erlang-matchers.json' with { type: 'json' }
const { problemMatcher } = elixirMatchers
const { problemMatcher: erlangProblemMatcher } = erlangMatchers
process.env.NODE_ENV = 'test' process.env.NODE_ENV = 'test'
simulateInput('otp-version', '25.1.2') simulateInput('otp-version', '25.1.2')
@@ -9,16 +21,7 @@ simulateInput('install-hex', 'true')
simulateInput('github-token', process.env.GITHUB_TOKEN) simulateInput('github-token', process.env.GITHUB_TOKEN)
simulateInput('hexpm-mirrors', 'https://builds.hex.pm', { multiline: true }) simulateInput('hexpm-mirrors', 'https://builds.hex.pm', { multiline: true })
import assert from 'node:assert' const { default: setupBeam } = await import('../src/setup-beam.js')
import http from 'node:http'
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import { describe, it } from 'node:test'
import * as csv from 'csv-parse/sync'
import setupBeam from '../src/setup-beam.js'
import elixirMatchers from '../matchers/elixir-matchers.json' with { type: 'json' }
const { problemMatcher } = elixirMatchers
const matrix = { const matrix = {
otp: { otp: {
@@ -174,11 +177,8 @@ describe('.getOTPVersion(_) - Erlang', () => {
'https://repo.hex.pm, https://cdn.jsdelivr.net/hex', 'https://repo.hex.pm, https://cdn.jsdelivr.net/hex',
{ multiline: true }, { multiline: true },
) )
const previousRunnerArch = process.env.RUNNER_ARCH
if (process.platform === 'linux') { if (process.platform === 'linux') {
process.env.RUNNER_ARCH = 'X64'
it('is Ok for known linux version', async () => { it('is Ok for known linux version', async () => {
before = simulateInput('version-type', 'strict') before = simulateInput('version-type', 'strict')
spec = '27.0' spec = '27.0'
@@ -196,21 +196,21 @@ describe('.getOTPVersion(_) - Erlang', () => {
assert.deepStrictEqual(got, expected) assert.deepStrictEqual(got, expected)
simulateInput('version-type', before) simulateInput('version-type', before)
spec = '19.3.x' spec = '20.3.x'
osVersion = 'ubuntu-16.04' osVersion = 'ubuntu-20.04'
expected = 'OTP-19.3.6.13' expected = 'OTP-20.3.8.26'
got = await setupBeam.getOTPVersion(spec, osVersion) got = await setupBeam.getOTPVersion(spec, osVersion)
assert.deepStrictEqual(got, expected) assert.deepStrictEqual(got, expected)
spec = '^19.3.6' spec = '^20.3.8'
osVersion = 'ubuntu-16.04' osVersion = 'ubuntu-20.04'
expected = 'OTP-19.3.6.13' expected = 'OTP-20.3.8.26'
got = await setupBeam.getOTPVersion(spec, osVersion) got = await setupBeam.getOTPVersion(spec, osVersion)
assert.deepStrictEqual(got, expected) assert.deepStrictEqual(got, expected)
spec = '^19.3' spec = '^20.3'
osVersion = 'ubuntu-18.04' osVersion = 'ubuntu-20.04'
expected = 'OTP-19.3.6.13' expected = 'OTP-20.3.8.26'
got = await setupBeam.getOTPVersion(spec, osVersion) got = await setupBeam.getOTPVersion(spec, osVersion)
assert.deepStrictEqual(got, expected) assert.deepStrictEqual(got, expected)
@@ -306,6 +306,7 @@ describe('.getOTPVersion(_) - Erlang', () => {
if (process.platform === 'linux') { if (process.platform === 'linux') {
it('is main-... only if strict/maint-... is used as input', async () => { it('is main-... only if strict/maint-... is used as input', async () => {
const arm64Options = setupBeam.githubARMRunnerArchs() const arm64Options = setupBeam.githubARMRunnerArchs()
const previousRunnerArch = process.env.RUNNER_ARCH
process.env.RUNNER_ARCH = process.env.RUNNER_ARCH =
arm64Options[Math.floor(Math.random() * arm64Options.length)] arm64Options[Math.floor(Math.random() * arm64Options.length)]
@@ -341,6 +342,7 @@ describe('.getOTPVersion(_) - Erlang', () => {
it('is Ok for known linux ARM64 version', async () => { it('is Ok for known linux ARM64 version', async () => {
const arm64Options = setupBeam.githubARMRunnerArchs() const arm64Options = setupBeam.githubARMRunnerArchs()
const previousRunnerArch = process.env.RUNNER_ARCH
process.env.RUNNER_ARCH = process.env.RUNNER_ARCH =
arm64Options[Math.floor(Math.random() * arm64Options.length)] arm64Options[Math.floor(Math.random() * arm64Options.length)]
@@ -401,10 +403,13 @@ describe('.getOTPVersion(_) - Erlang', () => {
expected = 'master' expected = 'master'
got = await setupBeam.getOTPVersion(spec, osVersion) got = await setupBeam.getOTPVersion(spec, osVersion)
assert.deepStrictEqual(got, expected) assert.deepStrictEqual(got, expected)
process.env.RUNNER_ARCH = previousRunnerArch
}) })
it('is Ok for known linux AMD64 version', async () => { it('is Ok for known linux AMD64 version', async () => {
const amd64Options = setupBeam.githubAMDRunnerArchs() const amd64Options = setupBeam.githubAMDRunnerArchs()
const previousRunnerArch = process.env.RUNNER_ARCH
process.env.RUNNER_ARCH = process.env.RUNNER_ARCH =
amd64Options[Math.floor(Math.random() * amd64Options.length)] amd64Options[Math.floor(Math.random() * amd64Options.length)]
@@ -483,12 +488,15 @@ describe('.getOTPVersion(_) - Erlang', () => {
expected = 'master' expected = 'master'
got = await setupBeam.getOTPVersion(spec, osVersion) got = await setupBeam.getOTPVersion(spec, osVersion)
assert.deepStrictEqual(got, expected) assert.deepStrictEqual(got, expected)
process.env.RUNNER_ARCH = previousRunnerArch
}) })
} }
if (process.platform === 'darwin') { if (process.platform === 'darwin') {
it('is main-... only if strict/maint-... is used as input', async () => { it('is main-... only if strict/maint-... is used as input', async () => {
const arm64Options = setupBeam.githubARMRunnerArchs() const arm64Options = setupBeam.githubARMRunnerArchs()
const previousRunnerArch = process.env.RUNNER_ARCH
process.env.RUNNER_ARCH = process.env.RUNNER_ARCH =
arm64Options[Math.floor(Math.random() * arm64Options.length)] arm64Options[Math.floor(Math.random() * arm64Options.length)]
@@ -518,6 +526,7 @@ describe('.getOTPVersion(_) - Erlang', () => {
it('is Ok for known macos ARM64 version', async () => { it('is Ok for known macos ARM64 version', async () => {
const arm64Options = setupBeam.githubARMRunnerArchs() const arm64Options = setupBeam.githubARMRunnerArchs()
const previousRunnerArch = process.env.RUNNER_ARCH
process.env.RUNNER_ARCH = process.env.RUNNER_ARCH =
arm64Options[Math.floor(Math.random() * arm64Options.length)] arm64Options[Math.floor(Math.random() * arm64Options.length)]
@@ -528,10 +537,13 @@ describe('.getOTPVersion(_) - Erlang', () => {
got = await setupBeam.getOTPVersion(spec, osVersion) got = await setupBeam.getOTPVersion(spec, osVersion)
assert.deepStrictEqual(got, expected) assert.deepStrictEqual(got, expected)
simulateInput('version-type', before) simulateInput('version-type', before)
process.env.RUNNER_ARCH = previousRunnerArch
}) })
it('is Ok for known macos AMD64 version', async () => { it('is Ok for known macos AMD64 version', async () => {
const amd64Options = setupBeam.githubARMRunnerArchs() const amd64Options = setupBeam.githubARMRunnerArchs()
const previousRunnerArch = process.env.RUNNER_ARCH
process.env.RUNNER_ARCH = process.env.RUNNER_ARCH =
amd64Options[Math.floor(Math.random() * amd64Options.length)] amd64Options[Math.floor(Math.random() * amd64Options.length)]
@@ -542,11 +554,12 @@ describe('.getOTPVersion(_) - Erlang', () => {
got = await setupBeam.getOTPVersion(spec, osVersion) got = await setupBeam.getOTPVersion(spec, osVersion)
assert.deepStrictEqual(got, expected) assert.deepStrictEqual(got, expected)
simulateInput('version-type', before) simulateInput('version-type', before)
process.env.RUNNER_ARCH = previousRunnerArch
}) })
} }
simulateInput('hexpm-mirrors', hexMirrors, { multiline: true }) simulateInput('hexpm-mirrors', hexMirrors, { multiline: true })
process.env.RUNNER_ARCH = previousRunnerArch
}) })
describe('OTP arch-specific install', () => { describe('OTP arch-specific install', () => {
@@ -569,6 +582,7 @@ describe('OTP arch-specific install', () => {
`Fetching OTP Version with invalid Github runner architecture is supposed to fail`, `Fetching OTP Version with invalid Github runner architecture is supposed to fail`,
) )
} }
process.env.RUNNER_ARCH = previousRunnerArch process.env.RUNNER_ARCH = previousRunnerArch
}) })
}) })
@@ -579,11 +593,8 @@ describe('.getOTPVersion(_) - Elixir', () => {
let spec let spec
let otpVersion let otpVersion
let before let before
const previousRunnerArch = process.env.RUNNER_ARCH
if (process.platform === 'linux') { if (process.platform === 'linux') {
process.env.RUNNER_ARCH = 'X64'
it('returns the expected value', async () => { it('returns the expected value', async () => {
spec = '1.18.x' spec = '1.18.x'
otpVersion = 'OTP-27' otpVersion = 'OTP-27'
@@ -641,8 +652,6 @@ describe('.getOTPVersion(_) - Elixir', () => {
simulateInput('version-type', before) simulateInput('version-type', before)
}) })
} }
process.env.RUNNER_ARCH = previousRunnerArch
}) })
describe('.getOTPVersion(_) - Gleam', () => { describe('.getOTPVersion(_) - Gleam', () => {
@@ -1158,6 +1167,98 @@ describe("Elixir Mix matcher's", () => {
}) })
}) })
describe("Erlang matcher's", () => {
it('erlc errors are properly matched', () => {
const [matcher] = erlangProblemMatcher.find(
({ owner }) => owner === 'erlc-error',
).pattern
const output = 'src/mymod.erl:42:5: head mismatch'
const match = output.match(matcher.regexp)
assert.equal(match[1], 'src/mymod.erl')
assert.equal(match[2], '42')
assert.equal(match[3], '5')
assert.equal(match[4], 'head mismatch')
})
it('erlc errors do not match warnings', () => {
const [matcher] = erlangProblemMatcher.find(
({ owner }) => owner === 'erlc-error',
).pattern
const output = 'src/mymod.erl:42:5: Warning: variable X is unused'
const match = output.match(matcher.regexp)
assert.equal(match, null)
})
it('erlc warnings are properly matched', () => {
const [matcher] = erlangProblemMatcher.find(
({ owner }) => owner === 'erlc-warning',
).pattern
const output = 'src/mymod.erl:10:3: Warning: variable X is unused'
const match = output.match(matcher.regexp)
assert.equal(match[1], 'src/mymod.erl')
assert.equal(match[2], '10')
assert.equal(match[3], '3')
assert.equal(match[4], 'variable X is unused')
})
it('dialyzer warnings are matched via erlc patterns', () => {
const [matcher] = erlangProblemMatcher.find(
({ owner }) => owner === 'erlc-warning',
).pattern
const output =
'src/mymod.erl:25:1: warning: Function foo/0 has no local return'
const match = output.match(matcher.regexp)
assert.equal(match[1], 'src/mymod.erl')
assert.equal(match[2], '25')
assert.equal(match[3], '1')
assert.equal(match[4], 'Function foo/0 has no local return')
})
it('CT failures are properly matched', () => {
const [filePattern, reasonPattern] = erlangProblemMatcher.find(
({ owner }) => owner === 'ct-failure',
).pattern
const firstOutput = 'test/mymod_SUITE.erl failed on line 55'
const secondOutput = 'Reason: {badmatch,{error,timeout}}'
const fileMatch = firstOutput.match(filePattern.regexp)
assert.equal(fileMatch[1], 'test/mymod_SUITE.erl')
assert.equal(fileMatch[2], '55')
const reasonMatch = secondOutput.match(reasonPattern.regexp)
assert.equal(reasonMatch[1], '{badmatch,{error,timeout}}')
})
it('EUnit failures are properly matched', () => {
const [headerPattern, funcPattern, callPattern, errorPattern] =
erlangProblemMatcher.find(
({ owner }) => owner === 'eunit-failure',
).pattern
const header = 'mymod_test: hello_test....*failed*'
const func =
'in function mymod_test:hello_test/0 (test/mymod_test.erl, line 12)'
const call = 'in call from mymod_test:hello_test/0'
const error = '**error:{badmatch,false}'
assert.ok(header.match(headerPattern.regexp))
const funcMatch = func.match(funcPattern.regexp)
assert.equal(funcMatch[1], 'test/mymod_test.erl')
assert.equal(funcMatch[2], '12')
assert.ok(call.match(callPattern.regexp))
const errorMatch = error.match(errorPattern.regexp)
assert.equal(errorMatch[1], '{badmatch,false}')
})
})
function unsimulateInput(key) { function unsimulateInput(key) {
return simulateInput(key, '') return simulateInput(key, '')
} }