Pigweed supports a vast matrix of configuration options in the embedded space, making exhaustive testing impossible. We need a systematic approach to selecting test configurations that maximizes coverage under fixed compute budgets or minimizes cost.
We implemented a Declarative Modeling approach using Google OR-Tools (CP-SAT).
We define the problem using high-level dataclasses in Python, ensuring type safety and validation.
Parameter: A dimension of variation (e.g., “Compiler”) with a list of Options.Option: A choice (e.g., “GCC”) with an integer cost.Exclusion: A hard constraint prohibiting specific combinations (e.g., IF OS=Win THEN Compiler!=GCC).PointConstraint: A mandatory requirement to include a specific partial configuration (e.g., MUST HAVE {OS: Win, Target: Qemu32}).CoverageGoal: An explicit request to cover all valid combinations of a subset of parameters (e.g., ["Compiler", "CppStd"]).For small domains (< 200k combinations), we fully enumerate the valid search space. For large domains (e.g., Pigweed's ~80M combinations), full enumeration is intractable. We use Random Constraint-Satisfying Sampling:
is_valid() (checking Exclusion rules).max_candidates (e.g., 10,000 or 100,000) valid configurations. This pool becomes the domain for the set cover optimizer.Random sampling works well for broad coverage but struggles to find specific “needle in a haystack” configurations. For Point Constraints, we use Constructive Generation:
We model the selection as a Weighted Set Cover Problem:
select_i for each candidate configuration.CoverageGoals), ensures $\sum_{c \in CoveringConfig} select_c \ge 1$.We support Existing Coverage via the ExistingCoverage dataclass to respect pre-existing test investments (e.g., “This exact config runs in CQ”).
ExistingCoverage(..., partial=True) for configs that only specify a subset of parameters.Exclusion constraints and parameter definitions.The solver produces optimal, cost-aware test suites. For example, if “Mac” costs 500 and “Linux” costs 10:
Decision: We moved away from implicit “pairwise coverage of everything” to Explicit Coverage Goals.
Crypto vs RustEdition might not matter).CoverageGoal(["Target"]), CoverageGoal(["Compiler", "CppStd"])). This makes the solver faster and the output more meaningful.We implemented strict validation in the TestModel:
ValueError if a CoverageGoal refers to a typo'd parameter.partial=True) raise immediate errors.Optimizers prefer Integers.
* 100) to keep the logic transparent.Don't just solve; Explain.
random.choice is non-deterministic across runs.Exclusion is simple (“IF A=x THEN B!=y”).(A=x OR B=y) IMPLIES (C!=z)).To improve debuggability, we propose a human-readable __str__ format for TestModel that mimics PICT's clarity but captures our specific features (costs, coverage goals, existing_coverage).
repr() artifacts.IF ... THEN ...).Parameters: OS: Mac ($50), Win ($50), Linux Compiler: GCC, Clang Arch: x64, arm64 BuildSystem: Bazel, GN ($10) Constraints: 1. IF OS=Win THEN Compiler!=GCC 2. IF Arch=arm64 THEN OS!={Win, Linux} Point Constraints: 1. {OS=Win, Arch=x64} Coverage Goals: 1. (OS, Compiler) 2. (Arch) Existing Coverage: 1. {OS: Linux, Compiler: GCC, Arch: x64, BuildSystem: GN} 2. {OS: Win} (Partial)
($Cost) next to a value only if Cost > 1.Exclusion rules as IF <Criteria> THEN <Forbidden>.Key=Val for single values and Key!={V1, V2} for sets.{Key=Val, ...}.(Param1, Param2, ...) listing the interaction dimensions.Key: Val.(Partial) tag if partial=True.