Skip to content

Commit f34a5ac

Browse files
author
Nils Bars
committed
Add CI workflow and fix linting/type errors
- Add GitHub Actions CI workflow with lint, typecheck, and test jobs - Fix ruff lint errors (line length, bare except, type comparisons) - Fix ruff formatting across all files - Fix pyright type errors: - Correct decorator return type annotations - Add missing join_with_space_if_list function - Fix Optional[TracebackType] for exception hook - Use Sequence instead of List for covariant parameter - Fix fixture return types to Iterator[None]
1 parent 336d1c1 commit f34a5ac

20 files changed

Lines changed: 348 additions & 166 deletions

.github/workflows/ci.yml

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main, master, dev]
6+
pull_request:
7+
branches: [main, master, dev]
8+
9+
jobs:
10+
lint:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- name: Install uv
16+
uses: astral-sh/setup-uv@v4
17+
18+
- name: Set up Python
19+
run: uv python install 3.10
20+
21+
- name: Install dependencies
22+
run: uv sync --dev
23+
24+
- name: Run ruff check
25+
run: uv run ruff check .
26+
27+
- name: Run ruff format check
28+
run: uv run ruff format --check .
29+
30+
typecheck:
31+
runs-on: ubuntu-latest
32+
steps:
33+
- uses: actions/checkout@v4
34+
35+
- name: Install uv
36+
uses: astral-sh/setup-uv@v4
37+
38+
- name: Set up Python
39+
run: uv python install 3.10
40+
41+
- name: Install dependencies
42+
run: uv sync --dev
43+
44+
- name: Run pyright
45+
run: uv run pyright
46+
47+
test:
48+
runs-on: ubuntu-latest
49+
strategy:
50+
matrix:
51+
python-version: ["3.10", "3.11", "3.12"]
52+
steps:
53+
- uses: actions/checkout@v4
54+
55+
- name: Install uv
56+
uses: astral-sh/setup-uv@v4
57+
58+
- name: Set up Python ${{ matrix.python-version }}
59+
run: uv python install ${{ matrix.python-version }}
60+
61+
- name: Install dependencies
62+
run: uv sync --dev
63+
64+
- name: Run tests with coverage
65+
run: uv run pytest --cov=ref_utils --cov-report=term-missing --cov-report=xml
66+
67+
- name: Upload coverage reports
68+
uses: codecov/codecov-action@v4
69+
if: matrix.python-version == '3.10'
70+
with:
71+
files: ./coverage.xml
72+
fail_ci_if_error: false

ref_utils/__init__.py

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,51 @@
1-
"""Import functions to avoid .dot import for user""" # pylint: disable = invalid-name
1+
"""Import functions to avoid .dot import for user""" # pylint: disable = invalid-name
2+
23
__all__ = [
34
# Modules
4-
'process', 'assertion', 'utils', 'decorator', 'config', 'serialization',
5+
"process",
6+
"assertion",
7+
"utils",
8+
"decorator",
9+
"config",
10+
"serialization",
511
# Config
6-
'Config', 'get_config', 'set_config', 'override_config',
12+
"Config",
13+
"get_config",
14+
"set_config",
15+
"override_config",
716
# Assertion
8-
'assert_is_dir', 'assert_is_exec', 'assert_is_file',
17+
"assert_is_dir",
18+
"assert_is_exec",
19+
"assert_is_file",
920
# Decorator
10-
'TestResult', 'TaskTestResult', 'environment_test', 'submission_test',
11-
'add_environment_test', 'add_submission_test', 'run_tests',
21+
"TestResult",
22+
"TaskTestResult",
23+
"environment_test",
24+
"submission_test",
25+
"add_environment_test",
26+
"add_submission_test",
27+
"run_tests",
1228
# Process
13-
'drop_privileges', 'run', 'run_capture_output', 'run_with_payload',
14-
'get_payload_from_executable', 'ref_util_install_global_exception_hook',
29+
"drop_privileges",
30+
"run",
31+
"run_capture_output",
32+
"run_with_payload",
33+
"get_payload_from_executable",
34+
"ref_util_install_global_exception_hook",
1535
# Serialization
16-
'IPCSerializer', 'TypeCodec', 'safe_dumps', 'safe_loads', 'get_serializer',
36+
"IPCSerializer",
37+
"TypeCodec",
38+
"safe_dumps",
39+
"safe_loads",
40+
"get_serializer",
1741
# Utils
18-
'print_ok', 'print_warn', 'print_err', 'write_stdout',
19-
'decode_or_str', 'test_result_will_be_submitted', 'get_user_environment',
42+
"print_ok",
43+
"print_warn",
44+
"print_err",
45+
"write_stdout",
46+
"decode_or_str",
47+
"test_result_will_be_submitted",
48+
"get_user_environment",
2049
]
2150
from .assertion import assert_is_dir, assert_is_exec, assert_is_file
2251
from .config import Config, get_config, override_config, set_config

ref_utils/assertion.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Exception-free assert functions"""
2+
23
import os
34
from pathlib import Path
45
from typing import Any, Union # pylint: disable = unused-import
@@ -14,20 +15,26 @@ def _assert(condition: bool, error_msg: str, silent: bool) -> bool:
1415
return False
1516
return True
1617

17-
def assert_is_exec(executable: Union[str, 'os.PathLike[Any]', Path], silent: bool = False) -> bool:
18+
19+
def assert_is_exec(executable: Union[str, "os.PathLike[Any]", Path], silent: bool = False) -> bool:
1820
"""Assert file exists and is executable"""
1921
if not isinstance(executable, Path):
2022
return assert_is_exec(Path(executable))
21-
return _assert(executable.exists() and executable.is_file() and os.access(executable, os.X_OK),
22-
f"Executable file {executable} not found or not executable", silent)
23+
return _assert(
24+
executable.exists() and executable.is_file() and os.access(executable, os.X_OK),
25+
f"Executable file {executable} not found or not executable",
26+
silent,
27+
)
2328

24-
def assert_is_file(file_: Union[str, 'os.PathLike[Any]', Path], silent: bool = False) -> bool:
29+
30+
def assert_is_file(file_: Union[str, "os.PathLike[Any]", Path], silent: bool = False) -> bool:
2531
"""Assert file exists"""
2632
if not isinstance(file_, Path):
2733
return assert_is_file(Path(file_))
2834
return _assert(file_.exists() and file_.is_file(), f"File {file_} not found", silent)
2935

30-
def assert_is_dir(directory: Union[str, 'os.PathLike[Any]', Path], silent: bool = False) -> bool:
36+
37+
def assert_is_dir(directory: Union[str, "os.PathLike[Any]", Path], silent: bool = False) -> bool:
3138
"""Assert directory exists"""
3239
if not isinstance(directory, Path):
3340
return assert_is_dir(Path(directory))

ref_utils/checks.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Various checks you may want to run during submission tests"""
2+
23
import os
34
import subprocess
45
from pathlib import Path
@@ -12,6 +13,7 @@
1213
_ENV_VAL_TRUE = "1"
1314
_ENV_VAL_FALSE = "0"
1415

16+
1517
def contains_flag(flag: str, python_script: Path, silent: bool = False) -> bool:
1618
"""
1719
Run submitted file and match whether it contains the flag value.
@@ -32,15 +34,18 @@ def run_pylint(python_files: List[Path]) -> bool:
3234
"""
3335
Run pylint with custom config on user code (only interesting if submission contains .py files)
3436
"""
35-
if not python_files or os.environ.get(_NO_LINT_ENV_VAR, '') == _ENV_VAL_TRUE:
37+
if not python_files or os.environ.get(_NO_LINT_ENV_VAR, "") == _ENV_VAL_TRUE:
3638
return SUCCESS
37-
result = run(["pylint", "--exit-zero", "--rcfile", str(get_config().pylint_config_path)] +
38-
[str(f.resolve()) for f in python_files],
39-
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
39+
result = run(
40+
["pylint", "--exit-zero", "--rcfile", str(get_config().pylint_config_path)]
41+
+ [str(f.resolve()) for f in python_files],
42+
stdout=subprocess.PIPE,
43+
stderr=subprocess.STDOUT,
44+
)
4045
lint_output = result.stdout.decode() if result.stdout else ""
4146
if lint_output != "":
4247
print_warn("[!] pylint's syntax and coding style checks failed:")
43-
print_warn(' ' + '\n '.join(lint_output.split('\n')))
48+
print_warn(" " + "\n ".join(lint_output.split("\n")))
4449
return FAILURE
4550
print_ok("[+] pylint's syntax and coding style checks passed")
4651
return SUCCESS
@@ -50,15 +55,15 @@ def run_mypy(python_files: List[Path]) -> bool:
5055
"""
5156
Run mypy with custom config on user code (only interesting if submission contains typed .py files)
5257
"""
53-
if not python_files or os.environ.get(_NO_LINT_ENV_VAR, '') == _ENV_VAL_TRUE:
58+
if not python_files or os.environ.get(_NO_LINT_ENV_VAR, "") == _ENV_VAL_TRUE:
5459
return SUCCESS
5560
cmd = ["mypy", "--config-file", str(get_config().mypy_config_path)]
5661
cmd += [str(f.resolve()) for f in python_files]
5762
result = run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
5863
lint_output = result.stdout.decode() if result.stdout else ""
5964
if lint_output != "":
6065
print_warn("[!] mypy's type checks failed:")
61-
print_warn(' ' + '\n '.join(lint_output.split('\n')))
66+
print_warn(" " + "\n ".join(lint_output.split("\n")))
6267
return FAILURE
6368
print_ok("[+] mypy's type checks passed")
6469
return SUCCESS
@@ -70,9 +75,9 @@ def check_all_python_files() -> bool:
7075
"""
7176
tests_passed = True
7277
python_files = [f for f in get_config().user_home_path.glob("**/*.py") if not f.name.startswith(".")]
73-
if not python_files or os.environ.get(_NO_LINT_ENV_VAR, '') == _ENV_VAL_TRUE:
78+
if not python_files or os.environ.get(_NO_LINT_ENV_VAR, "") == _ENV_VAL_TRUE:
7479
return tests_passed
75-
print_ok(f'[+] Testing {len(python_files)} Python source code files')
80+
print_ok(f"[+] Testing {len(python_files)} Python source code files")
7681
tests_passed &= run_pylint(python_files)
7782
tests_passed &= run_mypy(python_files)
7883
return tests_passed

ref_utils/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Configuration module for ref-utils with testable path abstraction."""
2+
23
from contextlib import contextmanager
34
from contextvars import ContextVar
45
from dataclasses import dataclass, field

0 commit comments

Comments
 (0)