hcovguard: A coverage ratchet tool for Haskell projects using HPC

This is a package candidate release! Here you can preview how this package release will appear once published to the main package index (which can be accomplished via the 'maintain' link below). Please note that once a package has been published to the main package index it cannot be undone! Please consult the package uploading documentation for more information.

[maintain] [Publish]

Warnings:

hcovguard reads HPC-generated coverage data and checks it against configurable thresholds defined in a TOML file.


[Skip to Readme]

Properties

Versions 0.1.0.0
Change log CHANGELOG.md
Dependencies base (>=4.18 && <5), containers (>=0.6 && <0.9), directory (>=1.3.8 && <1.4), dlist (>=1.0 && <1.1), filepath (>=1.4.100 && <1.6), hpc (>=0.6 && <0.8), opt-env-conf (>=0.6 && <0.14), string-interpolate (>=0.3 && <0.4), text (>=1.2 && <2.2), tomland (>=1.3 && <1.4) [details]
License MIT
Copyright 2026 Trevis Elser
Author Trevis Elser
Maintainer oss@treviselser.com
Category Testing, Development
Home page https://gitlab.com/telser/hcovguard
Bug tracker https://gitlab.com/telser/hcovguard/issues
Source repo head: git clone https://gitlab.com/telser/hcovguard
Uploaded by TrevisElser at 2026-02-07T16:48:05Z

Flags

Manual Flags

NameDescriptionDefault
ci

More strict ghc options used for development and ci, not intended for end-use.

Disabled

Use -f <flag> to enable a flag, or -f -<flag> to disable that flag. More info

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees


Readme for hcovguard-0.1.0.0

[back to package description]

hcovguard

A coverage ratchet tool for Haskell projects

hcovguard reads HPC-generated coverage data and checks it against configurable thresholds defined in a TOML file.

Quick Start

# 1. Run your tests with coverage enabled
stack test --coverage
# or: cabal test --enable-coverage

# 2. Create a minimal config file
cat > hcovguard.toml << 'EOF'
[forAnyModule]

[forAnyModule.expression]
minimumCovered = 1
EOF

# 3. Run hcovguard with auto-discovery
hcovguard --tix $(stack path --local-hpc-root)/combined/all/all.tix --auto-discover

# 4. Adjust thresholds based on your current coverage and goals

Features

Installation

From Source

cabal install hcovguard

Or with Stack:

stack install hcovguard

Installing the Manual Page

The man page is included in the package and can be installed manually:

# Install the man page system-wide (typically requires root access)
mkdir -p /usr/local/share/man/man1
cp man/hcovguard.1 /usr/local/share/man/man1/
mandb  # Update man database (on some systems)

# Then view it with:
man hcovguard

To view the man page without installing, run from the repository:

man man/hcovguard.1

Usage

First, generate coverage data by running your tests with coverage enabled:

# With Stack
stack test --coverage

# With Cabal
cabal test --enable-coverage

Then run hcovguard against the generated .tix file:

hcovguard \
  --tix $(stack path --local-hpc-root)/combined/all/all.tix \
  --auto-discover \
  --config hcovguard.toml

Auto-Discovery of Mix Directories

The --auto-discover flag automatically searches for .mix files in common build output directories:

This is the recommended approach for most projects, as it eliminates the need to manually specify --mix-dir paths that change between platforms and GHC versions.

If you need to specify mix directories manually (e.g., for non-standard build configurations), use --mix-dir:

hcovguard \
  --tix path/to/coverage.tix \
  --mix-dir /custom/path/to/mix \
  --mix-dir /another/mix/directory

Command Line Options

Option Description
-c, --config FILE Path to the TOML configuration file (default: ./hcovguard.toml)
-t, --tix FILE Path to the .tix file generated by HPC (required)
-m, --mix-dir DIR Directory containing .mix files (can be specified multiple times)
-a, --auto-discover Auto-discover .mix directories in .stack-work, dist-newstyle, and .hpc
-v, --verbosity LEVEL Verbosity level: 0=quiet, 1=normal, 2=verbose (default: 1)
-b, --baseline Generate baseline config from current coverage (outputs TOML to stdout)
-n, --dry-run Show which modules match which patterns without running checks
-h, --help Show help text
--version Show version information
--render-man-page Generate a man page and output to stdout

Note: You must specify either --auto-discover or at least one --mix-dir. If neither is provided, hcovguard will exit with an error.

Generating a Baseline Config

Use --baseline to generate a configuration file from your current coverage:

hcovguard --tix coverage.tix --auto-discover --baseline > hcovguard.toml

This outputs TOML with thresholds set to your current coverage counts—useful for:

Dry Run Mode

Use --dry-run to see which patterns match which modules without running threshold checks:

hcovguard --tix coverage.tix --auto-discover --dry-run

Example output:

MyProject.Core: matched rule #1 (module = "MyProject.Core")
MyProject.Utils: matched rule #2 (pattern = "MyProject.*")
MyProject.Internal.Helpers: matched rule #3 (pattern = "**.Internal.**") (ignored)
Test.MyProject.CoreSpec: using [forAnyModule] defaults

This helps debug pattern matching issues and verify your configuration.

Configuration

Create a hcovguard.toml file in your project root, for example:

# Default thresholds for all modules
[forAnyModule]

[forAnyModule.expression]
minimumCovered = 50
maximumUncovered = 10

[forAnyModule.topLevel]
minimumCovered = 30

[forAnyModule.alternative]
maximumUncovered = 5

# Override thresholds for specific modules (exact match)
[[forSpecifiedModules]]
module = "MyProject.Core"

[forSpecifiedModules.expression]
minimumCovered = 100

[forSpecifiedModules.topLevel]
minimumCovered = 50

# Override thresholds using pattern matching
[[forSpecifiedModules]]
pattern = "MyProject.Internal.**"

[forSpecifiedModules.expression]
minimumCovered = 20

# Ignore specific modules
[[forSpecifiedModules]]
module = "Test.Helpers"
ignore = true

# Ignore modules matching a pattern
[[forSpecifiedModules]]
pattern = "**.Test.**"
ignore = true

Threshold Options

Thresholds control the minimum coverage requirements. Both options are optional and can be used independently or together.

Field Type Required Description
minimumCovered Int No Minimum number of items that must be covered
maximumUncovered Int No Maximum number of items that can remain uncovered

Coverage Categories

Thresholds can be set for four coverage categories, each as a nested table:

Category Description
expression Expression coverage (most granular)
topLevel Top-level function/binding coverage
alternative Case branches and guards coverage
local Local binding (let/where) coverage

[forAnyModule]

Default thresholds applied to all modules unless overridden by [[forSpecifiedModules]].

Field Type Required Description
expression Table No Expression threshold options
topLevel Table No Top-level threshold options
alternative Table No Alternative threshold options
local Table No Local binding threshold options

Example:

[forAnyModule]

[forAnyModule.expression]
minimumCovered = 50

[forAnyModule.topLevel]
maximumUncovered = 5

[[forSpecifiedModules]]

Per-module overrides. Each entry uses one of two forms:

Form 1: Exact Module Match

Match a specific module by its exact name.

Field Type Required Description
module String Yes Exact module name to match
ignore Bool No If true, skip this module entirely (default: false)
expression Table No Expression threshold options
topLevel Table No Top-level threshold options
alternative Table No Alternative threshold options
local Table No Local binding threshold options

Example:

[[forSpecifiedModules]]
module = "MyProject.Core"

[forSpecifiedModules.expression]
minimumCovered = 100

Form 2: Pattern Match

Match multiple modules using glob patterns.

Field Type Required Description
pattern String Yes Glob pattern to match module names
ignore Bool No If true, skip matching modules entirely (default: false)
expression Table No Expression threshold options
topLevel Table No Top-level threshold options
alternative Table No Alternative threshold options
local Table No Local binding threshold options

Example:

[[forSpecifiedModules]]
pattern = "MyProject.Internal.**"

[forSpecifiedModules.expression]
minimumCovered = 20

Note: Each [[forSpecifiedModules]] entry must use exactly one form. Specifying both module and pattern is an error.

Pattern Syntax

Examples:

Pattern Match Order

When multiple [[forSpecifiedModules]] entries could match a module, the first matching entry wins. Entries are checked in the order they appear in the configuration file.

This means you should order your patterns from most specific to least specific:

# CORRECT: Specific patterns first
[[forSpecifiedModules]]
module = "MyProject.Core.Critical"  # Exact match checked first
[forSpecifiedModules.expression]
minimumCovered = 200

[[forSpecifiedModules]]
pattern = "MyProject.Core.*"        # Then specific pattern
[forSpecifiedModules.expression]
minimumCovered = 100

[[forSpecifiedModules]]
pattern = "MyProject.**"            # Then broad pattern
[forSpecifiedModules.expression]
minimumCovered = 50

If no [[forSpecifiedModules]] entry matches, the [forAnyModule] defaults apply.

Why hcovguard?

Troubleshooting

"Mix file not found" error

HPC generates two types of files:

If you see this error, hcovguard found the .tix file but cannot locate the corresponding .mix files. Solutions:

  1. Use --auto-discover: This searches common build directories automatically.

  2. Check your build output: Mix files are typically located in:

    • Stack: .stack-work/install/<platform>/<snapshot>/hpc/
    • Cabal: dist-newstyle/build/<platform>/<compiler>/<package>/hpc/
  3. Verify mix files exist: Run find . -name "*.mix" to locate them, then use --mix-dir with the containing directory.

  4. Rebuild with coverage: Mix files are only generated when building with coverage enabled. Re-run stack test --coverage or cabal test --enable-coverage.

No modules checked / empty output

This typically means:

  1. Wrong tix file path: The .tix file might be empty or point to a different test run. Verify the file exists and has recent modification time.

  2. Module name mismatch: The modules in your .tix file don't match any patterns in your config. Run with --verbosity 2 to see which modules are being processed.

  3. All modules ignored: Check that your ignore = true patterns aren't too broad.

Pattern not matching expected modules

Remember:

Common mistakes:

Run with --verbosity 2 to see exactly which modules match which patterns.

Coverage counts seem wrong

HPC counts can be surprising because:

Use --verbosity 2 to see the actual counts per category for each module, then adjust your thresholds accordingly.

"No .mix directories specified" error

This error occurs when you don't specify how hcovguard should find .mix files. You must either:

  1. Use --auto-discover to search common build directories automatically, or
  2. Specify at least one --mix-dir path explicitly

"Pattern matched no modules" warning

If you see a warning like:

Warning: [Hcovguard-8451] Pattern matched no modules
  pattern = "MyProject.Internal.**"

This means a [[forSpecifiedModules]] entry in your config didn't match any modules in the .tix file. This often indicates:

  1. Typo in the pattern: Double-check the module name or pattern syntax.
  2. Modules not included in coverage: The modules might not have been compiled with coverage enabled.
  3. Stale configuration: The modules may have been renamed or removed.

This is a warning, not an error — hcovguard will continue checking other modules. However, you should review your configuration to ensure patterns are intentional.

License

MIT License - see LICENSE for details.

Contributing

Contributions are welcome! Please feel free to submit issues and pull requests.