Public Access
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4dda91e24 | ||
|
|
3e21e774f7 | ||
|
|
7c11215e57 | ||
|
|
5384269c83 | ||
|
|
b3d0dd8ca7 |
@@ -27,7 +27,7 @@ jobs:
|
|||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
run: |
|
run: |
|
||||||
. .venv/bin/activate
|
. .venv/bin/activate
|
||||||
python3 -m devx.tools.check_test_speed --max-seconds 60 --max-single-seconds 2.0
|
python3 -m devx.tools.check_test_speed --max-seconds 4 --max-single-seconds 0.5
|
||||||
- name: Documentation coverage check
|
- name: Documentation coverage check
|
||||||
env:
|
env:
|
||||||
PYTHONPATH: src
|
PYTHONPATH: src
|
||||||
|
|||||||
@@ -2,6 +2,19 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## [0.8.3] - 2026-06-23
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Lower check_test_speed threshold to 4 seconds
|
||||||
|
- Pass --break-system-packages to pip in CI environments
|
||||||
|
|
||||||
|
## [0.8.2] - 2026-06-23
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Encode spaces in pair commands to survive shell word-splitting
|
||||||
|
|
||||||
## [0.8.1] - 2026-06-23
|
## [0.8.1] - 2026-06-23
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|||||||
+1
-1
@@ -4,4 +4,4 @@
|
|||||||
# Aligned with CI (ci.yml uses same thresholds).
|
# Aligned with CI (ci.yml uses same thresholds).
|
||||||
set -e
|
set -e
|
||||||
export PYTHONPATH=src
|
export PYTHONPATH=src
|
||||||
python3 -m devx.tools.check_test_speed --max-seconds 10 --max-single-seconds 0.5
|
python3 -m devx.tools.check_test_speed --max-seconds 4 --max-single-seconds 0.5
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
"""devx — reusable development and CI/CD tools for oblachno-oss projects."""
|
"""devx — reusable development and CI/CD tools for oblachno-oss projects."""
|
||||||
|
|
||||||
__version__ = "0.8.1"
|
__version__ = "0.8.3"
|
||||||
|
|||||||
@@ -41,7 +41,8 @@ class TestPair:
|
|||||||
|
|
||||||
def encode(self) -> str:
|
def encode(self) -> str:
|
||||||
"""Serialize to a pipe-delimited string for CI consumption."""
|
"""Serialize to a pipe-delimited string for CI consumption."""
|
||||||
return f"{self.scenario}|{self.platform['name']}|{self.platform['image']}|{self.platform['command']}"
|
cmd = self.platform["command"].replace(" ", "__SPACE__")
|
||||||
|
return f"{self.scenario}|{self.platform['name']}|{self.platform['image']}|{cmd}"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def decode(encoded: str) -> TestPair:
|
def decode(encoded: str) -> TestPair:
|
||||||
@@ -49,7 +50,7 @@ class TestPair:
|
|||||||
parts = encoded.split("|")
|
parts = encoded.split("|")
|
||||||
return TestPair(
|
return TestPair(
|
||||||
scenario=parts[0],
|
scenario=parts[0],
|
||||||
platform={"name": parts[1], "image": parts[2], "command": parts[3]},
|
platform={"name": parts[1], "image": parts[2], "command": parts[3].replace("__SPACE__", " ")},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -63,9 +64,8 @@ class MultiRoleTestPair:
|
|||||||
|
|
||||||
def encode(self) -> str:
|
def encode(self) -> str:
|
||||||
"""Serialize to a pipe-delimited string: ``role|scenario|platform_name|image|command``."""
|
"""Serialize to a pipe-delimited string: ``role|scenario|platform_name|image|command``."""
|
||||||
return (
|
cmd = self.platform["command"].replace(" ", "__SPACE__")
|
||||||
f"{self.role}|{self.scenario}|{self.platform['name']}|{self.platform['image']}|{self.platform['command']}"
|
return f"{self.role}|{self.scenario}|{self.platform['name']}|{self.platform['image']}|{cmd}"
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def decode(encoded: str) -> MultiRoleTestPair:
|
def decode(encoded: str) -> MultiRoleTestPair:
|
||||||
@@ -74,7 +74,7 @@ class MultiRoleTestPair:
|
|||||||
return MultiRoleTestPair(
|
return MultiRoleTestPair(
|
||||||
role=parts[0],
|
role=parts[0],
|
||||||
scenario=parts[1],
|
scenario=parts[1],
|
||||||
platform={"name": parts[2], "image": parts[3], "command": parts[4]},
|
platform={"name": parts[2], "image": parts[3], "command": parts[4].replace("__SPACE__", " ")},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -114,12 +114,14 @@ def parse_pair(pair: str) -> tuple[str, str, str, str, str]:
|
|||||||
|
|
||||||
Supports both 4-part (single-role) and 5-part (multi-role) formats.
|
Supports both 4-part (single-role) and 5-part (multi-role) formats.
|
||||||
For 4-part pairs, role is empty (caller uses default role dir).
|
For 4-part pairs, role is empty (caller uses default role dir).
|
||||||
|
Spaces in the command field are encoded as ``__SPACE__`` to survive
|
||||||
|
shell word-splitting when ``$TEST_PAIRS`` is expanded unquoted.
|
||||||
"""
|
"""
|
||||||
parts = pair.split("|")
|
parts = pair.split("|")
|
||||||
if len(parts) == 4:
|
if len(parts) == 4:
|
||||||
return "", parts[0], parts[1], parts[2], parts[3]
|
return "", parts[0], parts[1], parts[2], parts[3].replace("__SPACE__", " ")
|
||||||
if len(parts) == 5:
|
if len(parts) == 5:
|
||||||
return parts[0], parts[1], parts[2], parts[3], parts[4]
|
return parts[0], parts[1], parts[2], parts[3], parts[4].replace("__SPACE__", " ")
|
||||||
raise click.ClickException(f"Invalid pair format: {pair!r} (expected 4 or 5 pipe-delimited parts)")
|
raise click.ClickException(f"Invalid pair format: {pair!r} (expected 4 or 5 pipe-delimited parts)")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,12 @@ def _run(cmd: list[str]) -> None:
|
|||||||
def _install_python_deps(bin_dir: str, extras: str = "dev") -> None:
|
def _install_python_deps(bin_dir: str, extras: str = "dev") -> None:
|
||||||
"""Install the project with the specified extras in editable mode."""
|
"""Install the project with the specified extras in editable mode."""
|
||||||
pip = str(Path(bin_dir) / "pip")
|
pip = str(Path(bin_dir) / "pip")
|
||||||
_run([pip, "install", "-e", f".[{extras}]"])
|
cmd = [pip, "install", "-e", f".[{extras}]"]
|
||||||
|
# In CI (system Python), --break-system-packages allows upgrading
|
||||||
|
# debian-installed packages (e.g. platformdirs) that lack RECORD files.
|
||||||
|
if os.environ.get("PIP_BREAK_SYSTEM_PACKAGES") == "1":
|
||||||
|
cmd.append("--break-system-packages")
|
||||||
|
_run(cmd)
|
||||||
|
|
||||||
|
|
||||||
def _install_pre_commit_hooks(bin_dir: str) -> None:
|
def _install_pre_commit_hooks(bin_dir: str) -> None:
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
"""Unit tests for devx.tools.setup."""
|
"""Unit tests for devx.tools.setup."""
|
||||||
|
|
||||||
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
@@ -47,6 +48,12 @@ class TestInstallPythonDeps:
|
|||||||
_install_python_deps(".venv/bin", "ci,lint")
|
_install_python_deps(".venv/bin", "ci,lint")
|
||||||
mock_run.assert_called_once_with([".venv/bin/pip", "install", "-e", ".[ci,lint]"])
|
mock_run.assert_called_once_with([".venv/bin/pip", "install", "-e", ".[ci,lint]"])
|
||||||
|
|
||||||
|
@patch("devx.tools.setup._run")
|
||||||
|
def test_install_with_break_system_packages(self, mock_run: MagicMock) -> None:
|
||||||
|
with patch.dict(os.environ, {"PIP_BREAK_SYSTEM_PACKAGES": "1"}):
|
||||||
|
_install_python_deps(".venv/bin", "ci")
|
||||||
|
mock_run.assert_called_once_with([".venv/bin/pip", "install", "-e", ".[ci]", "--break-system-packages"])
|
||||||
|
|
||||||
|
|
||||||
class TestInstallPreCommitHooks:
|
class TestInstallPreCommitHooks:
|
||||||
@patch("devx.tools.setup._run")
|
@patch("devx.tools.setup._run")
|
||||||
|
|||||||
Reference in New Issue
Block a user