Public Access
DEVX-67: refactor: make molecule weights configurable via pyproject.toml
Post-merge / detect-type (push) Successful in 1m22s
Post-merge / validate-commit-msg (push) Successful in 1m4s
Post-merge / release (push) Successful in 1m15s
Post-merge / sync-wiki (push) Successful in 1m16s
Post-merge / vikunja (push) Successful in 55s
Post-merge / badges (push) Successful in 1m33s
Post-merge / configure-repo (push) Successful in 51s
Post-merge / publish (push) Successful in 1m4s
Post-merge / detect-type (push) Successful in 1m22s
Post-merge / validate-commit-msg (push) Successful in 1m4s
Post-merge / release (push) Successful in 1m15s
Post-merge / sync-wiki (push) Successful in 1m16s
Post-merge / vikunja (push) Successful in 55s
Post-merge / badges (push) Successful in 1m33s
Post-merge / configure-repo (push) Successful in 51s
Post-merge / publish (push) Successful in 1m4s
This commit was merged in pull request #107.
This commit is contained in:
@@ -19,6 +19,7 @@ Usage:
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import tomllib
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
@@ -132,70 +133,75 @@ def build_multi_role_pairs(
|
||||
return [MultiRoleTestPair(r, s, p) for r, s in role_scenarios for p in platforms]
|
||||
|
||||
|
||||
# Heuristic weights for known heavy molecule scenarios.
|
||||
# These are estimated from CI run times — scenarios that pull large Docker
|
||||
# images or run complex Ansible playbooks take longer.
|
||||
# --- Molecule weight configuration ---
|
||||
#
|
||||
# Weights are calibrated from actual CI execution times (converge→destroy):
|
||||
# nextcloud: ~7.7m → 15
|
||||
# restore/default: ~5.7m → 11
|
||||
# customer-apps: ~5.5m → 11
|
||||
# zitadel/default: ~5.0m → 10
|
||||
# docker_base/default: ~3.9m → 8
|
||||
# app_hardening/default: ~1.9m → 4
|
||||
# app_container/default: ~1.8m → 3
|
||||
# postgres-upgrade: ~1.7m → 3
|
||||
# observability/default: ~1.7m → 3
|
||||
# storage/default: ~1.5m → 3
|
||||
# storage/object-storage: ~1.0m → 2
|
||||
# vaultwarden: ~0.7m → 2
|
||||
# simple-app: ~0.7m → 2
|
||||
# Weights are loaded from ``[tool.devx.molecule.weights]`` in
|
||||
# ``pyproject.toml``. Each project (infra, grm, …) contributes its own
|
||||
# weights calibrated from actual CI execution times.
|
||||
#
|
||||
# Role-specific weights take priority over scenario-name weights.
|
||||
# The (role, scenario) tuple is checked first, then the scenario name
|
||||
# alone, then the default weight.
|
||||
_ROLE_SCENARIO_WEIGHTS: dict[tuple[str, str], int] = {
|
||||
("app_container", "nextcloud"): 15,
|
||||
("app_container", "customer-apps"): 11,
|
||||
("app_container", "vaultwarden"): 2,
|
||||
("app_container", "simple-app"): 2,
|
||||
("app_container", "postgres-upgrade"): 3,
|
||||
("app_container", "default"): 3,
|
||||
("restore", "default"): 11,
|
||||
("zitadel", "default"): 10,
|
||||
("docker_base", "default"): 8,
|
||||
("observability", "default"): 3,
|
||||
("app_hardening", "default"): 4,
|
||||
("storage", "default"): 3,
|
||||
("storage", "object-storage"): 2,
|
||||
}
|
||||
# Two key formats are supported:
|
||||
# - ``"scenario" = weight`` — applies to any role with that scenario name
|
||||
# - ``"role/scenario" = weight`` — role-specific (takes priority)
|
||||
#
|
||||
# Example pyproject.toml::
|
||||
#
|
||||
# [tool.devx.molecule.weights]
|
||||
# "nextcloud" = 15
|
||||
# "app_container/customer-apps" = 11
|
||||
# "restore/default" = 11
|
||||
# "default" = 3
|
||||
#
|
||||
# If no configuration is found, a generic default weight is used for all
|
||||
# scenarios (producing a round-robin distribution).
|
||||
|
||||
# Fallback weights by scenario name only (for single-role projects or
|
||||
# scenarios not in the role-specific table).
|
||||
_SCENARIO_WEIGHTS: dict[str, int] = {
|
||||
"nextcloud": 15,
|
||||
"customer-apps": 11,
|
||||
"restore": 11,
|
||||
"zitadel": 10,
|
||||
"docker-base": 8,
|
||||
"postgresql": 3,
|
||||
"postgres-upgrade": 3,
|
||||
"gitea": 8,
|
||||
"redis": 5,
|
||||
"backup": 5,
|
||||
"vaultwarden": 2,
|
||||
"simple-app": 2,
|
||||
"object-storage": 2,
|
||||
"default": 3,
|
||||
"binary": 2,
|
||||
}
|
||||
_DEFAULT_SCENARIO_WEIGHT = 3
|
||||
|
||||
|
||||
def _load_molecule_weights(pyproject_path: str = "pyproject.toml") -> tuple[dict[str, int], dict[tuple[str, str], int]]:
|
||||
"""Load molecule weights from ``[tool.devx.molecule.weights]`` in pyproject.toml.
|
||||
|
||||
Returns a tuple of ``(scenario_weights, role_scenario_weights)``:
|
||||
- ``scenario_weights``: maps scenario name → weight (applies to any role)
|
||||
- ``role_scenario_weights``: maps (role, scenario) → weight (role-specific)
|
||||
"""
|
||||
path = Path(pyproject_path)
|
||||
if not path.exists():
|
||||
return {}, {}
|
||||
try:
|
||||
with open(path, "rb") as f: # noqa: PTH123
|
||||
data = tomllib.load(f)
|
||||
except (tomllib.TOMLDecodeError, OSError):
|
||||
return {}, {}
|
||||
|
||||
weights_raw = data.get("tool", {}).get("devx", {}).get("molecule", {}).get("weights", {})
|
||||
if not isinstance(weights_raw, dict):
|
||||
return {}, {}
|
||||
|
||||
scenario_weights: dict[str, int] = {}
|
||||
role_scenario_weights: dict[tuple[str, str], int] = {}
|
||||
|
||||
for key, value in weights_raw.items():
|
||||
if not isinstance(value, int):
|
||||
continue
|
||||
if "/" in key:
|
||||
role, scenario = key.split("/", 1)
|
||||
role_scenario_weights[(role.lower(), scenario.lower())] = value
|
||||
else:
|
||||
scenario_weights[key.lower()] = value
|
||||
|
||||
return scenario_weights, role_scenario_weights
|
||||
|
||||
|
||||
# Load weights once at import time (like devx.config and classify_changes)
|
||||
_SCENARIO_WEIGHTS, _ROLE_SCENARIO_WEIGHTS = _load_molecule_weights()
|
||||
|
||||
|
||||
def _scenario_weight(scenario: str, role: str | None = None) -> int:
|
||||
"""Estimate a weight for a scenario based on its name and optionally its role.
|
||||
|
||||
Role-specific weights take priority over scenario-name-only weights.
|
||||
Role-specific weights (``"role/scenario"``) take priority over
|
||||
scenario-name-only weights (``"scenario"``). Falls back to the
|
||||
default weight if no configuration matches.
|
||||
"""
|
||||
s = scenario.lower()
|
||||
if role is not None:
|
||||
|
||||
Reference in New Issue
Block a user