Skip to content

Commit e1b4e50

Browse files
Add nox task to validate build/test artifacts (#311)
--------- Co-authored-by: Nicola Coretti <nicola.coretti@exasol.com>
1 parent 6d9f38c commit e1b4e50

File tree

6 files changed

+481
-15
lines changed

6 files changed

+481
-15
lines changed

.github/workflows/report.yml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,12 @@ jobs:
3131
working-directory: ./artifacts
3232
run: |
3333
poetry run coverage combine --keep coverage-python3.9*/.coverage
34-
cp .coverage ../
35-
cp lint-python3.9/.lint.txt ../
36-
cp security-python3.9/.security.json ../
34+
cp .coverage ../ || true
35+
cp lint-python3.9/.lint.txt ../ || true
36+
cp security-python3.9/.security.json ../ || true
37+
38+
- name: Validate Artifacts
39+
run: poetry run nox -s artifacts:validate
3740

3841
- name: Generate Report
3942
run: poetry run nox -s project:report -- -- --format json | tee metrics.json

doc/changes/unreleased.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@
3030
* `matrix-exasol.yml`
3131

3232
Among all of the above, the safest way is to set the matrix-related fields in your project config object in `noxconfig.py`.
33-
34-
33+
34+
* Added a nox task to validate the build/test artifacts and use it in the github workflow report
35+
36+
3537
## 📚 Documentation
3638

3739
* Added new entries to the frequently asked questions regarding `multiversion documentation`

exasol/toolbox/nox/_artifacts.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import json
2+
import pathlib
3+
import re
4+
import sqlite3
5+
import sys
6+
from pathlib import Path
7+
8+
import nox
9+
from nox import Session
10+
11+
from noxconfig import PROJECT_CONFIG
12+
13+
14+
@nox.session(name="artifacts:validate", python=False)
15+
def check_artifacts(session: Session) -> None:
16+
"""Validate that all project artifacts are available and consistent"""
17+
if not_available := _missing_files(
18+
{".lint.txt", ".security.json", ".coverage"}, PROJECT_CONFIG.root
19+
):
20+
print(f"not available: {not_available}")
21+
sys.exit(1)
22+
23+
error = False
24+
if msg := _validate_lint_txt(Path(PROJECT_CONFIG.root, ".lint.txt")):
25+
print(f"error in [.lint.txt]: {msg}")
26+
if msg := _validate_security_json(Path(PROJECT_CONFIG.root, ".security.json")):
27+
print(f"error in [.security.json]: {msg}")
28+
error = True
29+
if msg := _validate_coverage(Path(PROJECT_CONFIG.root, ".coverage")):
30+
print(f"error in [.coverage]: {msg}")
31+
error = True
32+
if error:
33+
sys.exit(1)
34+
35+
36+
def _missing_files(expected_files: set, directory: Path) -> set:
37+
files = {f.name for f in directory.iterdir() if f.is_file()}
38+
return expected_files - files
39+
40+
41+
def _validate_lint_txt(file: Path) -> str:
42+
try:
43+
content = file.read_text()
44+
except FileNotFoundError as ex:
45+
return f"Could not find file {file}, details: {ex}"
46+
expr = re.compile(r"^Your code has been rated at (\d+.\d+)/.*", re.MULTILINE)
47+
matches = expr.search(content)
48+
if not matches:
49+
return f"Could not find a rating"
50+
return ""
51+
52+
53+
def _validate_lint_json(file: Path) -> str:
54+
try:
55+
content = file.read_text()
56+
except FileNotFoundError as ex:
57+
return f"Could not find file {file}, details: {ex}"
58+
try:
59+
issues = json.loads(content)
60+
except json.JSONDecodeError as ex:
61+
return f"Invalid json file, details: {ex}"
62+
expected = {
63+
"type",
64+
"module",
65+
"obj",
66+
"line",
67+
"column",
68+
"endLine",
69+
"endColumn",
70+
"path",
71+
"symbol",
72+
"message",
73+
"message-id",
74+
}
75+
for number, issue in enumerate(issues):
76+
actual = set(issue.keys())
77+
missing = expected - actual
78+
if len(missing) > 0:
79+
return f"Invalid format, issue {number} is missing the following attributes {missing}"
80+
return ""
81+
82+
83+
def _validate_security_json(file: Path) -> str:
84+
try:
85+
content = file.read_text()
86+
except FileNotFoundError as ex:
87+
return f"Could not find file {file}, details: {ex}"
88+
try:
89+
actual = set(json.loads(content))
90+
except json.JSONDecodeError as ex:
91+
return f"Invalid json file, details: {ex}"
92+
expected = {"errors", "generated_at", "metrics", "results"}
93+
missing = expected - actual
94+
if len(missing) > 0:
95+
return f"Invalid format, the file is missing the following attributes {missing}"
96+
return ""
97+
98+
99+
def _validate_coverage(path: Path) -> str:
100+
try:
101+
conn = sqlite3.connect(path)
102+
except sqlite3.Error as ex:
103+
return f"database connection not possible, details: {ex}"
104+
cursor = conn.cursor()
105+
try:
106+
actual_tables = set(
107+
cursor.execute("select name from sqlite_schema where type == 'table'")
108+
)
109+
except sqlite3.Error as ex:
110+
return f"schema query not possible, details: {ex}"
111+
expected = {"coverage_schema", "meta", "file", "line_bits"}
112+
actual = {f[0] for f in actual_tables if (f[0] in expected)}
113+
missing = expected - actual
114+
if len(missing) > 0:
115+
return (
116+
f"Invalid database, the database is missing the following tables {missing}"
117+
)
118+
return ""

exasol/toolbox/nox/tasks.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ def check(session: Session) -> None:
5959
clean_docs,
6060
open_docs,
6161
)
62+
from exasol.toolbox.nox._release import prepare_release
6263
from exasol.toolbox.nox._shared import (
6364
Mode,
6465
_context,
@@ -74,5 +75,8 @@ def check(session: Session) -> None:
7475

7576
from exasol.toolbox.nox._release import prepare_release
7677

78+
from exasol.toolbox.nox._artifacts import (
79+
check_artifacts
80+
)
7781
# isort: on
7882
# fmt: on

poetry.lock

Lines changed: 10 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)