Add problem matchers for Github actions
- Add a basic problem matcher for illuaminate errors. - Add a script (tools/parse-reports.py) which parses the XML reports generated by checkstyle and junit, extracts source locations, and emits them in a manner which can be consumed by another set of matchers. This should make it a little easier to see problems for folks who just rely on CI to test things (though also, please don't do this if you can help it).
This commit is contained in:
parent
e10e30f82b
commit
953b94fd08
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"problemMatcher": [
|
||||||
|
{
|
||||||
|
"owner": "checkstyle",
|
||||||
|
"pattern": [
|
||||||
|
{
|
||||||
|
"regexp": "^([a-z]+) ([\\w./-]+):(\\d+):(\\d+): (.*)$",
|
||||||
|
"severity": 1,
|
||||||
|
"file": 2,
|
||||||
|
"line": 3,
|
||||||
|
"column": 4,
|
||||||
|
"message": 5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"problemMatcher": [
|
||||||
|
{
|
||||||
|
"owner": "illuaminate",
|
||||||
|
"severity": "warning",
|
||||||
|
"pattern": [
|
||||||
|
{
|
||||||
|
"regexp": "^([\\w./-]+):\\[(\\d+):(\\d+)\\-(?:\\d+):(?:\\d+)\\]: (.*) \\[([a-z:-]+)\\]$",
|
||||||
|
"file": 1,
|
||||||
|
"line": 2,
|
||||||
|
"column": 3,
|
||||||
|
"message": 4,
|
||||||
|
"code": 5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"problemMatcher": [
|
||||||
|
{
|
||||||
|
"owner": "junit",
|
||||||
|
"pattern": [
|
||||||
|
{
|
||||||
|
"regexp": "^## ([\\w./-]+):(\\d+): (.*)$",
|
||||||
|
"file": 1,
|
||||||
|
"line": 2,
|
||||||
|
"message": 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -41,8 +41,11 @@ jobs:
|
||||||
path: build/libs
|
path: build/libs
|
||||||
|
|
||||||
- name: Upload Coverage
|
- name: Upload Coverage
|
||||||
run: bash <(curl -s https://codecov.io/bash)
|
uses: codecov/codecov-action@v1
|
||||||
continue-on-error: true
|
|
||||||
|
- name: Parse test reports
|
||||||
|
run: ./tools/parse-reports.py
|
||||||
|
if: ${{ failure() }}
|
||||||
|
|
||||||
- name: Cache pre-commit
|
- name: Cache pre-commit
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v2
|
||||||
|
@ -51,6 +54,7 @@ jobs:
|
||||||
key: ${{ runner.os }}-pre-commit-${{ hashFiles('config/pre-commit/config.yml') }}
|
key: ${{ runner.os }}-pre-commit-${{ hashFiles('config/pre-commit/config.yml') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-pre-commit-
|
${{ runner.os }}-pre-commit-
|
||||||
|
|
||||||
- name: Run linters
|
- name: Run linters
|
||||||
run: |
|
run: |
|
||||||
pip install pre-commit
|
pip install pre-commit
|
||||||
|
|
|
@ -5,5 +5,12 @@ test -d bin || mkdir bin
|
||||||
test -f bin/illuaminate || curl -s -obin/illuaminate https://squiddev.cc/illuaminate/linux-x86-64/illuaminate
|
test -f bin/illuaminate || curl -s -obin/illuaminate https://squiddev.cc/illuaminate/linux-x86-64/illuaminate
|
||||||
chmod +x bin/illuaminate
|
chmod +x bin/illuaminate
|
||||||
|
|
||||||
|
if [ -n ${GITHUB_ACTIONS+x} ]; then
|
||||||
|
# Register a problem matcher (see https://github.com/actions/toolkit/blob/master/docs/problem-matchers.md)
|
||||||
|
# for illuaminate.
|
||||||
|
echo "::add-matcher::.github/matchers/illuaminate.json"
|
||||||
|
trap 'echo "::remove-matcher owner=illuaminate::"' EXIT
|
||||||
|
fi
|
||||||
|
|
||||||
./gradlew luaJavadoc
|
./gradlew luaJavadoc
|
||||||
bin/illuaminate lint
|
bin/illuaminate lint
|
||||||
|
|
|
@ -0,0 +1,114 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Parse reports generated by Gradle and convert them into GitHub annotations.
|
||||||
|
|
||||||
|
See https://github.com/actions/toolkit/blob/master/docs/problem-matchers.md and
|
||||||
|
https://github.com/actions/toolkit/blob/master/docs/commands.md.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Optional, Tuple
|
||||||
|
import pathlib
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
import re
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
|
||||||
|
LUA_ERROR_LOCATION = re.compile(r"^\s+(/[\w./-]+):(\d+):", re.MULTILINE)
|
||||||
|
JAVA_ERROR_LOCATION = re.compile(r"^\tat ([\w.]+)\.[\w]+\([\w.]+:(\d+)\)$", re.MULTILINE)
|
||||||
|
ERROR_MESSAGE = re.compile(r"(.*)\nstack traceback:", re.DOTALL)
|
||||||
|
|
||||||
|
SPACES = re.compile(r"\s+")
|
||||||
|
|
||||||
|
SOURCE_LOCATIONS = [
|
||||||
|
"src/main/java",
|
||||||
|
"src/main/resources/data/computercraft/lua",
|
||||||
|
"src/test/java",
|
||||||
|
"src/test/resources",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def find_file(path: str) -> Optional[str]:
|
||||||
|
while len(path) > 0 and path[0] == '/':
|
||||||
|
path = path[1:]
|
||||||
|
|
||||||
|
for source_dir in SOURCE_LOCATIONS:
|
||||||
|
child_path = os.path.join(source_dir, path)
|
||||||
|
if os.path.exists(child_path):
|
||||||
|
return child_path
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def find_location(message: str) -> Optional[Tuple[str, str]]:
|
||||||
|
location = LUA_ERROR_LOCATION.search(message)
|
||||||
|
if location:
|
||||||
|
file = find_file(location[1])
|
||||||
|
if file:
|
||||||
|
return file, location[2]
|
||||||
|
|
||||||
|
for location in JAVA_ERROR_LOCATION.findall(message):
|
||||||
|
file = find_file(location[0].replace(".", "/") + ".java")
|
||||||
|
if file:
|
||||||
|
return file, location[1]
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def parse_junit() -> None:
|
||||||
|
"""
|
||||||
|
Scrape JUnit test reports for errors. We determine the location from the Lua
|
||||||
|
or Java stacktrace.
|
||||||
|
"""
|
||||||
|
print("::add-matcher::.github/matchers/junit.json")
|
||||||
|
|
||||||
|
for path in pathlib.Path("build/test-results/test").glob("TEST-*.xml"):
|
||||||
|
for testcase in ET.parse(path).getroot():
|
||||||
|
if testcase.tag != "testcase":
|
||||||
|
continue
|
||||||
|
|
||||||
|
for result in testcase:
|
||||||
|
if result.tag != "failure":
|
||||||
|
continue
|
||||||
|
|
||||||
|
name = f'{testcase.attrib["classname"]}.{testcase.attrib["name"]}'
|
||||||
|
message = result.attrib.get('message')
|
||||||
|
|
||||||
|
location = find_location(result.text)
|
||||||
|
error = ERROR_MESSAGE.match(message)
|
||||||
|
if error:
|
||||||
|
error = error[1]
|
||||||
|
else:
|
||||||
|
error = message
|
||||||
|
|
||||||
|
if location:
|
||||||
|
print(f'## {location[0]}:{location[1]}: {name} failed: {SPACES.sub(" ", error)}')
|
||||||
|
else:
|
||||||
|
print(f'::error::{name} failed')
|
||||||
|
|
||||||
|
print("::group::Full error message")
|
||||||
|
print(result.text)
|
||||||
|
print("::endgroup")
|
||||||
|
|
||||||
|
print("::remove-matcher owner=junit::")
|
||||||
|
|
||||||
|
|
||||||
|
def parse_checkstyle() -> None:
|
||||||
|
"""
|
||||||
|
Scrape JUnit test reports for errors. We determine the location from the Lua
|
||||||
|
or Java stacktrace.
|
||||||
|
"""
|
||||||
|
print("::add-matcher::.github/matchers/checkstyle.json")
|
||||||
|
|
||||||
|
for path in pathlib.Path("build/reports/checkstyle/").glob("*.xml"):
|
||||||
|
for file in ET.parse(path).getroot():
|
||||||
|
for error in file:
|
||||||
|
filename = os.path.relpath(file.attrib['name'])
|
||||||
|
|
||||||
|
attrib = error.attrib
|
||||||
|
print(f'{attrib["severity"]} {filename}:{attrib["line"]}:{attrib.get("column", 1)}: {SPACES.sub(" ", attrib["message"])}')
|
||||||
|
|
||||||
|
print("::remove-matcher owner=checkstyle::")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
parse_junit()
|
||||||
|
parse_checkstyle()
|
Loading…
Reference in New Issue