Test Coverage Markers
This guide explains how to create and use test coverage markers for establishing traceability between Garden Linux features and tests, as defined in ADR-0032. The following sections detail the tooling, marker syntax, categories and placement guidelines.
Coverage.py and Test Coverage Analysis
Tool Location: tests/util/coverage.py
What is coverage.py?
coverage.py is a static analysis tool that generates coverage reports for Garden Linux, implementing ADR-0032, by analyzing the relationship between test coverage markers (GL-TESTCOV-*) next to a feature's setting and their corresponding test cases. Unlike traditional code coverage tools that require test execution, this tool performs static analysis to determine which features have test coverage.
These markers follow a specific format (GL-TESTCOV-$feature-$category-$description - see Marker Schema for details) that enables automated parsing and analysis. The tool scans both feature definitions and test files to establish traceability between what's configured and what's tested.
Key Capabilities
Static Coverage Analysis
- Analyzes test coverage markers in features without running tests
- Scans test files for
@pytest.mark.testcov()decorators - Generates coverage reports showing which features are tested
Coverage Reporting
- Lists all test coverage markers defined in features
- Shows which test coverage markers have corresponding tests
- Identifies untested features and gaps in test coverage
- Provides percentage coverage statistics
Traceability
- Establishes clear links between feature configurations and tests
- Enables audit trail for compliance requirements
- Supports version-independent testing strategies
How It Works
Step 1: Feature Scanning
- Scans feature directories (
features/*/) for test coverage markers - Extracts markers from inline comments in shell scripts (see Inline Comments)
- Parses
file.include.markers.yamlandinitrd.include.markers.yamlfiles for file-based markers (see Split ID Files) - Builds a complete inventory of all defined test coverage markers across all marker categories (see Category Reference)
Step 2: Test Scanning
- Scans test files (
tests/**/test_*.py) for pytest markers - Extracts setting IDs from
@pytest.mark.testcov()decorators (see Tests) - Maps each test function to its associated test coverage marker
Step 3: Coverage Analysis
- Matches test coverage markers from features with test coverage markers in tests
- Identifies covered and uncovered test coverage markers
- Calculates coverage percentages and statistics
Step 4: Report Generation
- Produces human-readable and machine-readable coverage reports
- Highlights gaps in test coverage
- Provides actionable insights for improving test coverage
Usage Example
# Generate coverage report
python tests/util/coverage.py
...
Loading feature excludes...
✓ Excluding 19 features: _bfpxe, _build, _curl, _debug, _dev, _initrdDebug, _nopkg, _oci, _readonly, _secureboot, bluefield, capi, cisPackages, container, firecracker, nodejs, python, sssd, stigDev
================================================================================
MARKER COVERAGE REPORT
================================================================================
Summary:
Total markers: 244
Tested markers: 28
Untested: 216
Orphaned markers: 91
Coverage: 11.5%
Total test functions: 160
Tests with markers: 0
Tests without markers: 160
Coverage by feature:
--------------------------------------------------------------------------------
⚠ _fips: 0/4 (0.0%)
⚠ _fwcfg: 0/1 (0.0%)
...
Features without markers (10):
--------------------------------------------------------------------------------
○ _ephemeral: 0/0 (N/A - no markers defined)
○ _iso: 0/0 (N/A - no markers defined)
...
Untested markers by feature:
--------------------------------------------------------------------------------
_fips:
Total markers: 7
Tested: 3
Untested: 4
Untested markers list:
- GL-TESTCOV-_fips-config-openssh-sshd-Ciphers
- GL-TESTCOV-_fips-config-openssh-sshd-KexAlgorithms
- GL-TESTCOV-_fips-config-openssl
- GL-TESTCOV-_fips-config-openssl-fipsinstall
...
================================================================================
✓ JSON report written to: tests/coverage_report.json
✓ JUnit XML report written to: tests/coverage_report.xmlIntegration with Development Workflow
During Feature Development
- Add test coverage markers to new features (see When to Create Markers for guidelines)
- Place markers appropriately using inline comments or split ID files (see Where to Place Markers)
- Use coverage.py to verify markers are correctly formatted and follow the Marker Schema
- Identify which tests need to be created
During Test Development
- Check coverage reports to find untested features
- Write tests with appropriate
@pytest.mark.testcov()decorators (see Tests) - Ensure test coverage markers match the exact settings from features
- Verify test coverage increases after adding new tests
In CI/CD Pipelines
- Run coverage.py as part of automated checks
- Enforce minimum coverage thresholds
- Block PRs that reduce test coverage
- Generate audit reports for compliance tracking
Marker Schema
All test coverage markers follow this format:
GL-TESTCOV-$feature-$category-$descriptionComponents
- Prefix:
GL-TESTCOV-(fixed) - Feature: Feature directory name (e.g.,
aws,ssh,cis,server) - Category: Type of setting (see Category Reference)
- Description: Hyphenated description of what the setting does
Examples
GL-TESTCOV-aws-service-aws-clocksource-enable
GL-TESTCOV-ssh-config-sshd-config-permissions
GL-TESTCOV-base-config-no-hostname
GL-TESTCOV-server-service-systemd-timesyncd-enable
GL-TESTCOV-cisOS-config-crontab-permissionsCategory Reference
Garden Linux uses four main categories for tes coverage markers:
1. config - Configuration Settings
Purpose: Configuration files, system settings, kernel parameters, security policies
Subcategories:
| Pattern | Meaning | Example |
|---|---|---|
config-$path-$setting | Configuration file content | config-ssh-sshd-config-permitrootlogin |
config-$path-permissions | File/directory permissions | config-hosts-permissions |
config-$path-link | Symbolic link | config-locale-link |
config-no-$item | File/directory absence | config-no-hostname |
config-$component-$parameter | Configuration parameter | config-audit-space-left-action |
Why Test This:
- ✅ Verify configuration files are correctly rendered
- ✅ Ensure permissions are set for security
- ✅ Validate kernel parameters are active
- ✅ Confirm files are removed/present as expected
Example Markers:
GL-TESTCOV-base-config-os-release- /etc/os-release contentGL-TESTCOV-server-config-hosts-permissions- /etc/hosts permissions (0644)GL-TESTCOV-server-config-locale-link- /etc/default/locale → /etc/locale.confGL-TESTCOV-base-config-no-hostname- /etc/hostname is removedGL-TESTCOV-cisAudit-config-audit-space-left-action- Audit daemon config parameter
2. service - Systemd Services and Timers
Purpose: Systemd units (services, timers, mounts, sockets)
Subcategories:
| Pattern | Meaning | Example |
|---|---|---|
service-$unit-enable | Service is enabled | service-systemd-timesyncd-enable |
service-$unit-disable | Service is disabled | service-systemd-timesyncd-disable |
service-$unit-mask | Service is masked | service-google-guest-agent-manager-mask |
service-$unit-override | Override service config | service-systemd-timesyncd-override |
service-timer-$unit-enable | Timer is enabled | service-timer-aide-check-enable |
Why Test This:
- ✅ Verify services are enabled/disabled as configured
- ✅ Ensure service states for functionality
Example Markers:
GL-TESTCOV-server-service-systemd-networkd-enable- systemd-networkd.service enabledGL-TESTCOV-azure-service-systemd-timesyncd-disable- systemd-timesyncd disabled (Azure uses chrony)GL-TESTCOV-gcp-service-google-guest-agent-manager-mask- GCP agent maskedGL-TESTCOV-aide-config-service-timer-aide-check-enable- AIDE check timer enabled
3. mount - Filesystem Mounts
Purpose: Filesystem mount points and mount options
Subcategories:
| Pattern | Meaning | Example |
|---|---|---|
config-mount-$path-enable | Mount unit enabled | config-mount-tmp-enable |
config-mount-$path | Mount configuration | config-mount-tmp |
Why Test This:
- ✅ Ensure partitions are mounted as specified
- ✅ Validate mount flags (nosuid, noexec, nodev)
Example Markers:
GL-TESTCOV-server-config-mount-tmp-enable- tmp.mount unit enabledGL-TESTCOV-server-config-mount-tmp- tmp.mount unit configuration
4. script - Executable Scripts
Purpose: Shell scripts, hooks, and executables
Subcategories:
| Pattern | Meaning | Example |
|---|---|---|
script-$name-$action | Script execution | script-clocksource-setup |
script-$component-$function | Script functionality | script-profile-autologout |
Why Test This:
- ✅ Verify scripts are present and executable
- ✅ Ensure scripts produce expected side effects
- ✅ Validate script configurations
Example Markers:
GL-TESTCOV-aws-script-clocksource-setup- AWS clocksource setup scriptGL-TESTCOV-cloud-script-profile-autologout- Auto-logout profile script
Special Patterns
Overview: -no- vs -disable
Markers use two distinct patterns to indicate negation, each with a specific semantic meaning:
| Pattern | Meaning | System State | Test Verification |
|---|---|---|---|
-no- | Absence (does not exist) | Completely removed | Check file/directory doesn't exist |
-disable | Deactivation (exists but off) | Present but turned off | Check state is "disabled" |
Why This Distinction Matters:
- Different Test Assertions: Tests verify different conditions
- Different Security Guarantees: Absence is stronger than deactivation
- Different Reversibility: Removed items need reinstallation, disabled items can be re-enabled
- Clear Intent: Makes it obvious whether something is missing or just turned off
The -no- Pattern (Absence/Removal)
Meaning: Indicates something does not exist or was completely removed
Use For:
- Files that must not exist (e.g., security-sensitive files)
- Directories that were cleaned up
- Configuration sections removed from files
- Systemd override files that don't exist
Common Patterns:
| Pattern | Meaning | Example |
|---|---|---|
config-no-$file | File does not exist | config-no-hostname |
config-no-$directory | Directory does not exist | config-no-initrd-img |
service-no-$unit-override | No systemd override exists | service-no-systemd-timesyncd-override |
config-$component-no-$feature | Feature removed from config | config-cloud-no-growpart |
Why Test -no- Patterns:
- ✅ Verify cleanup operations succeeded
- ✅ Ensure security-sensitive files are actually removed
- ✅ Confirm features are not present (stronger guarantee than disabled)
The -disable Pattern (Deactivation)
Meaning: Indicates something exists but is turned off or deactivated
Use For:
- Services that exist but are disabled
- Features in configuration files set to "false" or "no"
- Kernel modules that exist but are not loaded
- Functionality that can be re-enabled without reinstalling
Common Patterns:
| Pattern | Meaning | Example |
|---|---|---|
service-$unit-disable | Service exists but disabled | service-systemd-timesyncd-disable |
config-$component-$feature-disable | Feature set to disabled in config | config-sshd-x11forwarding-disable |
Why Test -disable Patterns:
- ✅ Verify services are in correct disabled state
- ✅ Ensure features are turned off in configuration
- ✅ Confirm functionality is deactivated (but can be re-enabled if needed)
When to Create Markers
✅ DO Create Markers For:
Runtime-Verifiable Settings:
- Configuration file content and structure
- File/directory permissions and ownership
- File/directory presence or absence
- Service enable/disable/mask states
- Systemd timer states
- Mount point configurations
- Symbolic links
- Kernel parameters (sysctl, cmdline)
- PAM configurations
- Audit rules
Why: These settings define system behavior that must be verified on a running system.
❌ DO NOT Create Markers For:
Package Installation:
- Packages in
pkg.include - Packages in
pkg.exclude - Package availability checks
:::note If a package in pkg.include installs an important service, it makes sense to add a $service-$unit-enable marker. Currently there is no "more elegant" way as we do not explicitly enable services. This is the Debian way of doing things. :::
Why:
- Package installation failures cause the build to fail - already validated
- If a package is missing, dependent configurations won't work (redundant testing)
- Testing
dpkg -l | grep packageadds no value - As discussed in ADR-0013, testing for package presence adds no value as version-independent testing should focus on functionality, not package lists
Build-Time Operations:
- File copying during build
- Directory creation during build
- Archive extraction during build
Why: These are build-time operations that cannot fail silently - build failures catch them.
Where to Place Markers
Features
Inline Comments (Preferred)
Files: exec.config, exec.early, exec.post, exec.late, pkg.exclude, file.exclude
Format:
- Add markers as simple inline comments.
- Multiple markers can be listed in multiple lines.
# GL-TESTCOV-$feature-$category1-$description1
# GL-TESTCOV-$feature-$category2-$description2
command or configurationExamples:
# features/server/exec.config
# GL-TESTCOV-server-service-systemd-networkd-enable
systemctl enable systemd-networkd
# GL-TESTCOV-server-config-group-wheel
addgroup --system wheel
# GL-TESTCOV-server-config-hosts-permissions
chmod g-w / /etc/hosts# features/base/exec.post
# GL-TESTCOV-base-config-no-hostname
rm "$rootfs/etc/hostname"
# GL-TESTCOV-base-config-resolv-conf
if [ -f "$rootfs/etc/resolv.conf" ] && [ ! -L "$rootfs/etc/resolv.conf" ]; then
rm "$rootfs/etc/resolv.conf"
fifile.include.ids.yaml and initrd.include.ids.yaml (For File Includes)
Use For: Files in file.include/ and initrd.include/ directories
Why: Markers should not appear in the final image files
Files:
file.include.ids.yaml- for files infile.include/directoryinitrd.include.ids.yaml- for files ininitrd.include/directory
Format:
# features/$feature/file.include.ids.yaml
testcov:
"path/to/file1":
- GL-TESTCOV-$feature-$category-$description1
"path/to/file2":
- GL-TESTCOV-$feature-$category-$description2Important:
- Paths must NOT have a leading slash
- Paths must be quoted strings
Example:
# features/firewall/file.include.ids.yaml
testcov:
"etc/nftables.conf":
- GL-TESTCOV-firewall-config-nftables-conf
"etc/nftables.d/gl-default.nft":
- GL-TESTCOV-firewall-config-nftables-default# features/_usi/initrd.include.ids.yaml
testcov:
"etc/repart.d/00-efi.conf":
- GL-TESTCOV-_usi-config-initrd-repart-efi
"usr/bin/repart-esp-disk":
- GL-TESTCOV-_usi-config-initrd-script-repart-esp-diskTests
Use For: Referencing test coverage markers in test functions
Format:
- Use the
@pytest.mark.testcov()decorator on test functions - Pass a list of marker strings (even for a single marker)
- Multiple markers can be listed for tests that verify multiple settings
@pytest.mark.testcov(["GL-TESTCOV-$feature-$category-$description"])
def test_something(client):
"""Test implementation"""
# test code hereExample:
# tests/test_cis.py
import pytest
@pytest.mark.testcov([
"GL-TESTCOV-cisOS-config-crontab-permissions",
"GL-TESTCOV-cisOS-config-cron-hourly-permissions",
"GL-TESTCOV-cisOS-config-cron-daily-permissions"
])
def test_cron_permissions(client):
"""Verify CIS-compliant permissions on cron directories"""
cron_paths = ["/etc/crontab", "/etc/cron.hourly", "/etc/cron.daily"]
for path in cron_paths:
result = client.execute(f"stat -c '%a' {path}")
assert result.stdout.strip() == "700"Related Architecture Decisions
The test coverage marker system is based on:
- ADR-0032: Static Coverage Analysis - Primary decision for marker system
- ADR-0013: Discontinue Musthave Tests - Version-independent testing approach