Skip to content

Commit fab5826

Browse files
enforce proper encoding on lock file, mypy typing, and ruff tooling
1 parent 9198166 commit fab5826

17 files changed

Lines changed: 104 additions & 152 deletions

.github/workflows/ci.yml

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,47 @@ env:
1414
VCPKG_COMMIT: "66c0373dc7fca549e5803087b9487edfe3aca0a1"
1515

1616
jobs:
17+
python-lint:
18+
name: Python Linting (ruff)
19+
runs-on: ubuntu-24.04
20+
timeout-minutes: 10
21+
22+
steps:
23+
- name: Checkout
24+
uses: actions/checkout@v4
25+
with:
26+
submodules: recursive
27+
28+
- name: Validate requirements lockfile
29+
shell: bash
30+
run: |
31+
set -euo pipefail
32+
LOCKFILE="requirements-lock.txt"
33+
test -f "$LOCKFILE"
34+
if od -An -tx1 "$LOCKFILE" | tr -s ' ' '\n' | grep -qx '00'; then
35+
echo "ERROR: $LOCKFILE contains NUL bytes (binary corruption)" >&2
36+
exit 1
37+
fi
38+
iconv -f UTF-8 -t UTF-8 "$LOCKFILE" > /dev/null 2>&1
39+
echo "OK: $LOCKFILE is valid UTF-8 and contains no NUL bytes"
40+
41+
- name: Setup Python and dependencies
42+
uses: actions/setup-python@v5
43+
with:
44+
python-version: "3.12"
45+
cache: "pip"
46+
cache-dependency-path: requirements-lock.txt
47+
48+
- name: Install Python dependencies
49+
run: |
50+
python -m pip install --upgrade pip
51+
python -m pip install -r requirements-lock.txt
52+
53+
- name: Run ruff checks
54+
run: |
55+
ruff check tests examples --output-format=github
56+
ruff format --check tests examples
57+
1758
python-typecheck:
1859
name: Python Type Checking (mypy)
1960
runs-on: ubuntu-24.04
@@ -51,7 +92,7 @@ jobs:
5192
python -m pip install -r requirements-lock.txt
5293
5394
- name: Run mypy
54-
run: mypy tests
95+
run: mypy tests examples
5596

5697
linux-release:
5798
name: ${{ matrix.lane }}

examples/inert_mode/test_inert.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,8 @@
4141
def run_inert_example():
4242
# Find provider executable
4343
provider_paths = [
44-
Path(__file__).parent.parent.parent
45-
/ "build"
46-
/ "Release"
47-
/ "anolis-provider-sim.exe",
48-
Path(__file__).parent.parent.parent
49-
/ "build-standalone"
50-
/ "Release"
51-
/ "anolis-provider-sim.exe",
44+
Path(__file__).parent.parent.parent / "build" / "Release" / "anolis-provider-sim.exe",
45+
Path(__file__).parent.parent.parent / "build-standalone" / "Release" / "anolis-provider-sim.exe",
5246
Path(__file__).parent.parent.parent / "build" / "anolis-provider-sim.exe",
5347
]
5448

examples/non_interacting_mode/test_non_interacting.py

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,8 @@
4141
def run_non_interacting_example():
4242
# Find provider executable
4343
provider_paths = [
44-
Path(__file__).parent.parent.parent
45-
/ "build"
46-
/ "Release"
47-
/ "anolis-provider-sim.exe",
48-
Path(__file__).parent.parent.parent
49-
/ "build-standalone"
50-
/ "Release"
51-
/ "anolis-provider-sim.exe",
44+
Path(__file__).parent.parent.parent / "build" / "Release" / "anolis-provider-sim.exe",
45+
Path(__file__).parent.parent.parent / "build-standalone" / "Release" / "anolis-provider-sim.exe",
5246
Path(__file__).parent.parent.parent / "build" / "anolis-provider-sim.exe",
5347
]
5448

@@ -127,9 +121,7 @@ def run_non_interacting_example():
127121

128122
# Verify convergence
129123
if temps[-1] <= temps[0]:
130-
print(
131-
f"[FAIL] Temperature did not increase: {temps[0]:.1f} -> {temps[-1]:.1f}"
132-
)
124+
print(f"[FAIL] Temperature did not increase: {temps[0]:.1f} -> {temps[-1]:.1f}")
133125
return False
134126

135127
if temps[-1] < 60.0:

examples/sim_mode/test_sim.py

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,7 @@ def find_fluxgraph_server():
4343
"""Find FluxGraph server executable"""
4444
candidates = [
4545
Path("../../../fluxgraph/build-server/server/Release/fluxgraph-server.exe"),
46-
Path(
47-
"../../../fluxgraph/build-release-server/server/Release/fluxgraph-server.exe"
48-
),
46+
Path("../../../fluxgraph/build-release-server/server/Release/fluxgraph-server.exe"),
4947
Path("../../../fluxgraph/build/server/Release/fluxgraph-server.exe"),
5048
Path("../../../fluxgraph/build-server/server/fluxgraph-server"),
5149
Path("../../../fluxgraph/build/server/fluxgraph-server"),
@@ -59,10 +57,7 @@ def find_fluxgraph_server():
5957
def find_provider():
6058
"""Find provider executable"""
6159
candidates = [
62-
Path(__file__).parent.parent.parent
63-
/ "build"
64-
/ "Release"
65-
/ "anolis-provider-sim.exe",
60+
Path(__file__).parent.parent.parent / "build" / "Release" / "anolis-provider-sim.exe",
6661
Path(__file__).parent.parent.parent / "build" / "anolis-provider-sim.exe",
6762
]
6863
for path in candidates:
@@ -84,9 +79,7 @@ def run_sim_example():
8479
print("\nThen run this test again.")
8580
return False
8681

87-
server = subprocess.Popen(
88-
[server_exe, "--port", "50051"], stdout=subprocess.PIPE, stderr=subprocess.PIPE
89-
)
82+
server = subprocess.Popen([server_exe, "--port", "50051"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
9083

9184
try:
9285
time.sleep(2.0) # Let server start and bind port
@@ -118,9 +111,7 @@ def run_sim_example():
118111
# Check if provider is still running
119112
if provider.poll() is not None:
120113
stderr_pipe = provider.stderr
121-
stderr_output = (
122-
stderr_pipe.read().decode() if stderr_pipe is not None else ""
123-
)
114+
stderr_output = stderr_pipe.read().decode() if stderr_pipe is not None else ""
124115
print("[FAIL] Provider terminated unexpectedly:")
125116
print(stderr_output)
126117
return False
@@ -180,9 +171,7 @@ def run_sim_example():
180171
relay = values["relay1_state"].value.bool_value
181172

182173
temps.append(tc1)
183-
print(
184-
f" t={i * 1.5:.1f}s: TC1={tc1:.1f}degC, TC2={tc2:.1f}degC, relay={'ON' if relay else 'OFF'}"
185-
)
174+
print(f" t={i * 1.5:.1f}s: TC1={tc1:.1f}degC, TC2={tc2:.1f}degC, relay={'ON' if relay else 'OFF'}")
186175

187176
# Verify convergence
188177
if temps[-1] <= temps[0]:

mypy.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[mypy]
22
python_version = 3.12
3-
files = tests
3+
files = tests, examples
44
warn_unused_configs = True
55
check_untyped_defs = True
66
no_implicit_optional = True

pyproject.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[tool.ruff]
2+
line-length = 120
3+
target-version = "py312"
4+
5+
[tool.ruff.lint]
6+
select = ["E", "F"]
7+
8+
[tool.ruff.format]
9+
quote-style = "double"
10+
indent-style = "space"
11+
skip-magic-trailing-comma = false
12+
line-ending = "auto"

tests/support/assertions.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@ def status_text(resp: Any) -> str:
1414
def assert_status(resp: Any, expected_code: int, context: str) -> None:
1515
actual = resp.status.code
1616
if actual != expected_code:
17-
raise AssertionError(
18-
f"{context}: expected status {expected_code}, got {status_text(resp)}"
19-
)
17+
raise AssertionError(f"{context}: expected status {expected_code}, got {status_text(resp)}")
2018

2119

2220
def assert_ok(resp: Any, context: str) -> None:

tests/support/env.py

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,7 @@ def resolve_provider_executable(root: Path | None = None) -> Path:
4444
candidate = root / candidate
4545
if candidate.exists():
4646
return candidate.resolve()
47-
raise FileNotFoundError(
48-
f"ANOLIS_PROVIDER_SIM_EXE points to missing file: {candidate}"
49-
)
47+
raise FileNotFoundError(f"ANOLIS_PROVIDER_SIM_EXE points to missing file: {candidate}")
5048

5149
candidates = [
5250
root / "build" / "Release" / "anolis-provider-sim.exe",
@@ -60,11 +58,7 @@ def resolve_provider_executable(root: Path | None = None) -> Path:
6058
root / "build" / "ci-linux-release" / "anolis-provider-sim",
6159
root / "build" / "ci-linux-release-strict" / "anolis-provider-sim",
6260
root / "build" / "ci-windows-release" / "Release" / "anolis-provider-sim.exe",
63-
root
64-
/ "build"
65-
/ "ci-windows-release-strict"
66-
/ "Release"
67-
/ "anolis-provider-sim.exe",
61+
root / "build" / "ci-windows-release-strict" / "Release" / "anolis-provider-sim.exe",
6862
root / "build" / "ci-linux-release-fluxgraph" / "anolis-provider-sim",
6963
root / "build" / "ci-linux-release-fluxgraph-strict" / "anolis-provider-sim",
7064
]
@@ -73,9 +67,7 @@ def resolve_provider_executable(root: Path | None = None) -> Path:
7367
return candidate.resolve()
7468

7569
candidate_text = "\n".join(f" - {path}" for path in candidates)
76-
raise FileNotFoundError(
77-
"Could not find anolis-provider-sim executable. Checked:\n" + candidate_text
78-
)
70+
raise FileNotFoundError("Could not find anolis-provider-sim executable. Checked:\n" + candidate_text)
7971

8072

8173
def resolve_fluxgraph_server(root: Path | None = None) -> Path:
@@ -89,17 +81,11 @@ def resolve_fluxgraph_server(root: Path | None = None) -> Path:
8981
candidate = root / candidate
9082
if candidate.exists():
9183
return candidate.resolve()
92-
raise FileNotFoundError(
93-
f"FLUXGRAPH_SERVER_EXE points to missing file: {candidate}"
94-
)
84+
raise FileNotFoundError(f"FLUXGRAPH_SERVER_EXE points to missing file: {candidate}")
9585

9686
fluxgraph_root = root.parent / "fluxgraph"
9787
candidates = [
98-
fluxgraph_root
99-
/ "build-release-server"
100-
/ "server"
101-
/ "Release"
102-
/ "fluxgraph-server.exe",
88+
fluxgraph_root / "build-release-server" / "server" / "Release" / "fluxgraph-server.exe",
10389
fluxgraph_root / "build-server" / "server" / "Release" / "fluxgraph-server.exe",
10490
fluxgraph_root / "build" / "server" / "Release" / "fluxgraph-server.exe",
10591
fluxgraph_root / "build-release-server" / "server" / "fluxgraph-server",
@@ -111,9 +97,7 @@ def resolve_fluxgraph_server(root: Path | None = None) -> Path:
11197
return candidate.resolve()
11298

11399
candidate_text = "\n".join(f" - {path}" for path in candidates)
114-
raise FileNotFoundError(
115-
"Could not find fluxgraph-server executable. Checked:\n" + candidate_text
116-
)
100+
raise FileNotFoundError("Could not find fluxgraph-server executable. Checked:\n" + candidate_text)
117101

118102

119103
def resolve_config_path(path: str | Path, root: Path | None = None) -> Path:

tests/support/framed_client.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,7 @@ def _read_exact(self, length: int) -> bytes:
6969
def send_request(self, request: Any) -> Any:
7070
if not self.is_running():
7171
raise RuntimeError(
72-
f"Provider process exited before request send (code={self.process.poll()})\n"
73-
f"{self.output_tail(100)}"
72+
f"Provider process exited before request send (code={self.process.poll()})\n{self.output_tail(100)}"
7473
)
7574

7675
payload = request.SerializeToString()
@@ -126,9 +125,7 @@ def read_signals(self, device_id: str, signal_ids: list[str] | None = None) -> A
126125
request.read_signals.signal_ids.extend(signal_ids)
127126
return self.send_request(request)
128127

129-
def call_function(
130-
self, device_id: str, function_id: int, args: dict[str, Any] | None = None
131-
) -> Any:
128+
def call_function(self, device_id: str, function_id: int, args: dict[str, Any] | None = None) -> Any:
132129
request = self.protocol.Request(request_id=self._request_id())
133130
request.call.device_id = device_id
134131
request.call.function_id = function_id

tests/support/process.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,7 @@ def poll(self) -> int | None:
115115
def assert_running(self, context: str) -> None:
116116
if self.is_running():
117117
return
118-
raise RuntimeError(
119-
f"{self.name} exited early during {context} with code {self.poll()}\n"
120-
f"{self.output_tail(80)}"
121-
)
118+
raise RuntimeError(f"{self.name} exited early during {context} with code {self.poll()}\n{self.output_tail(80)}")
122119

123120
def output_tail(self, lines: int = 80) -> str:
124121
stdout_tail = self.stdout.tail(lines)

0 commit comments

Comments
 (0)