mirror of
https://github.com/erlef/setup-beam.git
synced 2026-07-23 15:16:08 +00:00
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.
This commit is contained in:
Vendored
+66
@@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+14
-9
@@ -57588,6 +57588,7 @@ async function installOTP(otpSpec) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
setOutput('otp-version', otpVersion)
|
setOutput('otp-version', otpVersion)
|
||||||
|
maybeEnableProblemMatchers('erlang')
|
||||||
endGroup()
|
endGroup()
|
||||||
|
|
||||||
return otpVersion
|
return otpVersion
|
||||||
@@ -57609,7 +57610,7 @@ async function maybeInstallElixir(elixirSpec) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
setOutput('elixir-version', elixirVersion)
|
setOutput('elixir-version', elixirVersion)
|
||||||
maybeEnableElixirProblemMatchers()
|
maybeEnableProblemMatchers('elixir')
|
||||||
endGroup()
|
endGroup()
|
||||||
|
|
||||||
installed = true
|
installed = true
|
||||||
@@ -57618,14 +57619,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]}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+14
-9
@@ -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]}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ import path from 'node:path'
|
|||||||
import { describe, it } from 'node:test'
|
import { describe, it } from 'node:test'
|
||||||
import * as csv from 'csv-parse/sync'
|
import * as csv from 'csv-parse/sync'
|
||||||
import elixirMatchers from '../matchers/elixir-matchers.json' with { type: 'json' }
|
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 } = elixirMatchers
|
||||||
|
const { problemMatcher: erlangProblemMatcher } = erlangMatchers
|
||||||
|
|
||||||
process.env.NODE_ENV = 'test'
|
process.env.NODE_ENV = 'test'
|
||||||
|
|
||||||
@@ -1159,6 +1161,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, '')
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user