gorgon

module
v0.0.0-...-3cc2fd5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 20, 2026 License: Apache-2.0

README

Gorgon v0.6

Go mutation testing tool. Onto version 0.6 now!

Benchmarks: benchmarks/current_benchmark.txt

Usage

gorgon ./path/to/code
gorgon -operators=arithmetic,logical ./path
gorgon -concurrent=all ./path       # use all CPU cores (default)
gorgon -concurrent=half ./path      # use half of CPU cores
gorgon -concurrent=2 ./path         # use exactly 2 concurrent test runners
gorgon -threshold=80 ./path         # fail if mutation score is below 80%
gorgon -cache ./path                # cache results between runs
gorgon -diff HEAD~1 ./path          # only mutate changed lines
gorgon -diff=path/to/file.patch ./path

# Go workspaces (go.work) are automatically detected
gorgon ./...                        # tests all modules in workspace

Flags

Flag Default Description
-config "" Path to YAML config file (disables all other flags)
-concurrent all Max parallel test runs: all, half, or a number
-operators all Comma-separated operator names or categories
-print-ast false Print AST tree and exit
-threshold 0 Fail if mutation score is below this percentage (0-100)
-cache false Cache mutation results between runs
-dry-run false Preview mutants without running tests
-progbar false Show progress percentage during execution
-exclude "" Comma-separated glob patterns for files to exclude
-include "" Comma-separated glob patterns for files to include
-skip "" Comma-separated relative file paths to skip entirely
-skip-func "" Comma-separated file:function pairs to skip (e.g. foo/bar.go:MyFunc)
-tests "" Comma-separated relative paths to test files/folders to run
-diff "" Only mutate changed lines (e.g. HEAD~1, HEAD, or path/to/file.patch)
-debug false Show detailed debug output during execution
-show-killed false Show killed mutants with test attribution
-show-survived false Show survived mutants in output
-format textfile Output format for report file (textfile, html, junit, sarif, json)
-output "" Write report to file (e.g. report.txt)
-debug-files false Also write debug info to {output}.debug.txt
-cpu-profile "" Write CPU profile to file (analyzable with go tool pprof)
-no-regression false Fail if mutation score drops below saved baseline
-baseline-file "" Path to baseline file (default: .gorgon-baseline.json)
-baseline-tolerance 0 Allow this many percentage points of score drop before failing

Baseline / Ratchet Mode

Large codebases can't jump from 0% to 80% overnight. Baseline mode lets teams improve incrementally without being blocked from day one — the same adoption trick golangci-lint uses.

gorgon baseline ./path          # save current score as baseline
gorgon -no-regression ./path    # fail only if score drops from baseline
gorgon -no-regression -baseline-tolerance=1 ./path  # allow 1pp of drift

On the first -no-regression run with no baseline file, Gorgon auto-saves the current score instead of failing, so teams are never blocked on day one.

Config

baseline:
  no_regression: true
  tolerance: 1.0          # allow 1pp of drift (optional)
  file: ".gorgon-baseline.json"  # override path (optional)

The baseline file (.gorgon-baseline.json) should be committed to version control so CI can compare against it.

Config

Use -config to load a YAML file. All flags must be omitted when using -config.

# === Core Mutation Settings ===
operators:
  - all
threshold: 80

# === Execution Settings ===
concurrent: all
cache: true
dry_run: false
progbar: false

# === Test Configuration ===
unit_tests_enabled: true
tests: []

# === External Test Suites ===
external_suites:
  enabled: false
  run_mode: after_unit
  suites: []

# === File Filtering ===
exclude:
  - "*_test.go"
include: []
skip:
  - vendor/
skip_func:
  - foo/bar.go:MyFunc

# === Advanced Options ===
diff: ""
base: ""
debug: false
go_version: ""  # Override detected Go version (e.g., "1.25", "1.26")
chunk_large_files: true  # Split files with >500 mutants to reduce memory (default: true)

# === Baseline / Ratchet ===
baseline:
  no_regression: false
  tolerance: 0.0

# === Output Settings ===
show_killed: false
show_survived: false
outputs:
  - textfile:report.txt
  - junit:mutation-results.xml
  - sarif:mutation-results.sarif
  - html:gorgon-report
  - json:mutation-results.json
cpu_profile: ""
badge: ""  # Generate badge: "json" or "svg"

# === Directory Rules ===
dir_rules:
  - dir: internal/core
    whitelist:
      - arithmetic_flip
      - boundary_value
  - dir: internal/api
    blacklist:
      - all

# === Suppressions (Auto-managed) ===
suppress: []
gorgon -config=gorgon.yml ./path

Badge Generation

Generate shields.io-compatible badges to display mutation score in your README.

Configuration

Add to your gorgon.yml:

badge: json  # or "svg"

When you run Gorgon, it will automatically generate the badge file in your project directory.

Generate JSON Badge

# gorgon.yml
badge: json
outputs:
  - textfile:report.txt
gorgon -config=gorgon.yml ./...
# Creates: mutation-badge.json

Output:

{
  "schemaVersion": 1,
  "label": "mutation",
  "message": "85.3%",
  "color": "#4c1"
}

Host this JSON file and use it with shields.io:

![Mutation Score](https://img.shields.io/endpoint?url=https://your-site.com/mutation-badge.json)

Generate SVG Badge

# gorgon.yml
badge: svg
gorgon -config=gorgon.yml ./...
# Creates: mutation-badge.svg

Commit the SVG to your repo and reference it:

![Mutation Score](./mutation-badge.svg)

Badge Colors

  • Green (≥80%): #4c1
  • Yellow-Green (≥60%): #97ca00
  • Yellow (≥40%): #dfb317
  • Orange (≥20%): #fe7d37
  • Red (<20%): #e05d44

GitHub Actions

Quick Setup (2 lines)

Add to .github/workflows/mutation-test.yml:

- name: Run Mutation Testing
  uses: gorgon/gorgon-action@v1

That's it! The action automatically:

  • Installs Gorgon
  • Runs mutation testing
  • Uploads badge and reports as artifacts
  • Fails the build if threshold not met

Full Configuration

name: Mutation Testing

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  mutation-test:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Run Gorgon
        uses: gorgon/gorgon-action@v1
        with:
          config: 'gorgon.yml'          # Optional: path to config
          targets: './...'               # Optional: target paths
          threshold: '70'                # Optional: minimum score
          fail-on-threshold: 'true'      # Optional: fail build
          upload-badge: 'true'           # Optional: upload badge
          upload-reports: 'true'         # Optional: upload reports
      
      - name: Comment PR
        if: github.event_name == 'pull_request'
        uses: actions/github-script@v7
        with:
          script: |
            const fs = require('fs');
            const report = fs.readFileSync('gorgon-report.txt', 'utf8');
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.name,
              body: `## 🧬 Mutation Testing Results\n\n\`\`\`\n${report}\n\`\`\``
            });

Action Inputs

Input Description Default
version Gorgon version to use latest
config Path to gorgon.yml ""
targets Target paths (space-separated) ./...
threshold Minimum mutation score (0-100) 0
fail-on-threshold Fail build if threshold not met true
upload-badge Upload badge as artifact true
upload-reports Upload reports as artifacts true

Action Outputs

Output Description
mutation-score The mutation score percentage
total-mutants Total number of mutants
killed Number of killed mutants
survived Number of survived mutants

Using Outputs

- name: Run Gorgon
  id: gorgon
  uses: gorgon/gorgon-action@v1

- name: Check Score
  run: |
    echo "Mutation Score: ${{ steps.gorgon.outputs.mutation-score }}%"
    echo "Killed: ${{ steps.gorgon.outputs.killed }}/${{ steps.gorgon.outputs.total }}"

Artifacts

The action uploads two artifacts:

  1. mutation-badge - JSON and SVG badge files
  2. mutation-reports - Text report and baseline file

Download from the Actions tab or use in subsequent steps.

CI Integration

Gorgon supports multiple output formats for CI/CD pipelines and test dashboards.

Multiple Outputs

Specify all output formats in the config file using the outputs list with format:filepath pairs:

outputs:
  - textfile:report.txt
  - junit:mutation-results.xml
  - sarif:mutation-results.sarif
  - html:gorgon-report
  - json:mutation-results.json

All formats are written in a single run. Or via CLI (single format only):

gorgon -format=junit -output=mutation-results.xml ./path

JUnit XML

For Jenkins, TeamCity, and other CI systems that parse JUnit reports:

gorgon -format=junit -output=mutation-results.xml ./path

Survived mutants appear as test failures, compilation errors as errors, and untested mutants as skipped.

SARIF

For GitHub Code Scanning and other SARIF-compatible tools:

gorgon -format=sarif -output=mutation-results.sarif ./path

Upload to GitHub Actions:

- name: Upload SARIF
  uses: github/codeql-action/upload-sarif@v2
  with:
    sarif_file: mutation-results.sarif

HTML

For local review and dashboards:

gorgon -format=html -output=gorgon-report ./path

JSON

For programmatic consumption and custom tooling:

gorgon -format=json -output=mutation-results.json ./path

Output structure:

{
  "summary": {
    "total": 100,
    "killed": 85,
    "survived": 10,
    "errors": 3,
    "untested": 2,
    "score": 89.47
  },
  "mutants": [
    {
      "id": 1,
      "status": "killed",
      "operator": "arithmetic_flip",
      "file": "pkg/example.go",
      "line": 42,
      "column": 10,
      "killed_by": "TestExample"
    }
  ]
}

External Test Suites

Run black-box tests from external packages (e.g., /tests/, /integration/) to kill mutations. This allows tests outside the main package to contribute to mutation detection.

Configuration

Add external_suites to your config:

unit_tests_enabled: true          # Run unit tests (default: true)
external_suites:
  enabled: true
  run_mode: after_unit            # Options: after_unit, only, alongside
  suites:
    - name: integration
      paths:
        - ./tests/integration
        - ./tests/regression
      tags: [integration]          # Optional: build tags
      short_circuit: true          # Stop on first kill (default: true)
    
    - name: e2e
      paths:
        - ./tests/e2e
      tags: [e2e]

Auto-Discovery

Use glob patterns to automatically discover all test packages:

external_suites:
  enabled: true
  suites:
    - name: all-tests
      paths:
        - ./tests/...              # Recursively finds all test packages

Run Modes

  • after_unit (default): Run external suites only on mutants that survived unit tests
  • only: Skip unit tests, run only external suites
  • alongside: Run external suites on all mutants regardless of unit test results

How It Works

  1. Unit Phase (if unit_tests_enabled: true): Gorgon runs local package tests
  2. External Phase (if external_suites.enabled: true):
    • Discovers test packages from configured paths
    • Builds test binaries for each package
    • Runs surviving mutants against each binary
    • Mutants killed by external tests are marked with suite name (e.g., TestName [integration])

Example: Integration Tests Kill Mutations

// examples/mutations/arithmetic_flip/example2.go
package arithmetic_flip

func Example2(a, b int) int {
	return a + b
}
// tests/integration/arithmetic_flip_test.go
package testing_test

import (
	"testing"
	"github.com/myorg/myproject/examples/mutations/arithmetic_flip"
)

func TestExample2(t *testing.T) {
	result := arithmetic_flip.Example2(2, 3)
	if result != 5 {
		t.Errorf("Expected 5, got %d", result)
	}
}

When a + b is mutated to a - b, the external test kills it:

Top Killing Tests:
  TestExample2 [integration]                         1 kills

Disabling Unit Tests

To run only external suites:

unit_tests_enabled: false
external_suites:
  enabled: true
  run_mode: after_unit
  suites:
    - name: all-tests
      paths: [./tests/...]

Suppressions

Suppress mutations using inline comments or config file entries.

Inline Comments

Add //gorgon:ignore above code to suppress mutations on the next line:

//gorgon:ignore
return "pass"

//gorgon:ignore panic_removal
panic("error")

//gorgon:ignore arithmetic_flip:9
x = a + b

Config File

Add suppressions to your YAML config:

suppress:
  - location: path/to/file.go:5
    operators:
      - arithmetic_flip
      - panic_removal
  
  # Omit operators to suppress ALL operators on that line
  - location: path/to/file.go:10

Paths are relative to the project root (nearest go.mod). Override with base:

base: examples  # use this dir as root instead of go.mod
suppress:
  - location: mutations/panic_removal/file.go:5

Auto Syncing

When running with -config, inline //gorgon:ignore comments are automatically added to the config file's suppress: section. Each comment becomes a YAML entry with the relative file path, line number, and suppressed operators:

suppress:
  - location: pkg/file.go:12
    operators:
      - panic_removal

Existing config suppressions are preserved and merged with inline comments. Paths are always relative to the project root, regardless of which subfolder you run Gorgon on.

Mutations

Arithmetic

  • arithmetic_flip - + ↔ -, * ↔ /

Logical

  • condition_negation - == ↔ !=, < ↔ >=, <= ↔ >, > ↔ <=
  • negate_condition - if (x) → if (!x)
  • logical_operator - && ↔ ||

Boundary

  • boundary_value - < ↔ <=, > ↔ >=

Assignment

  • assignment_operator - = → +=, += ↔ -=, *= ↔ /=

Function Body

  • empty_body - Replace void function body with {}

Binary Operators

  • binary_math - % ↔ *, & ↔ |, << ↔ >>
  • inc_dec_flip - ++ ↔ --
  • sign_toggle - Unary -x ↔ +x

Literal

  • constant_replacement - Replace literals with different values
  • variable_replacement - Replace variable with another of same type
  • zero_value_return_numeric - Replace numeric literals with 0
  • zero_value_return_string - Replace string literals with ""
  • zero_value_return_bool - Replace bool literals with false
  • zero_value_return_error - Replace fmt.Errorf() with nil

Early Return

  • early_return_removal - Remove early return statements inside if blocks

Reference Returns

  • pointer_returns - return &x → return nil
  • slice_returns - return []T{} → return nil
  • map_returns - return map[K]V{} → return nil
  • channel_returns - return make(chan T) → return nil
  • interface_returns - return "foo" → return nil (interface{} only)

Switch

  • switch_remove_default - Remove default case from switch
  • swap_case_bodies - Swap case bodies within same switch

Conditional Expression

  • if_condition_true - if (a > b) → if (true)
  • if_condition_false - if (a > b) → if (false)
  • for_condition_true - for i < 10 {} → for true {}
  • for_condition_false - for i < 10 {} → for false {}

Loop

  • loop_body_removal - Remove loop body, leaving empty loop
  • loop_break_first - Add break after first iteration
  • loop_break_removal - Remove break statements inside loops

Statement

  • defer_removal - Remove defer statements

Engine

  • Context-aware: passes type info to mutators
  • Extensible: implement Operator or ContextualOperator interface
  • Parallel test execution: mutants run concurrently across CPU cores

Kill Attribution

When a mutant is killed, Gorgon tracks which test detected it:

Top Killing Tests:
  TestMainHandlesFlagErrors                        42 kills
  TestMainHandlesValidationErrors                  115 kills

Killed Mutants:
- #12 cmd/gorgon/main.go:34:5 (if_condition_false) killed by TestMainHandlesFlagErrors (12ms)
- #15 cmd/gorgon/main.go:38:9 (negate_condition) killed by TestMainHandlesValidationErrors (8ms)
...

Use -show-killed or show_killed: true in config to display killed mutants. The output includes:

  • Which test killed the mutant (parsed from --- FAIL: TestName)
  • How long it took to detect (duration from test start to failure)
  • Compiler kills: mutations that cause compilation failures are also tracked as kills (attributed to (compiler))

Test Isolation

When -tests is specified, Gorgon only tests mutants in the packages covered by those test files. Mutants in other packages are marked as survived since no tests target them.

For example, with tests: [cmd/gorgon/main_test.go]:

  • Mutants in cmd/gorgon/ are tested (the tests target this package)
  • Mutants in pkg/config/, examples/, etc. are marked survived (no tests cover them)

This prevents false "kill" counts where tests appear to kill mutants they don't actually test.

Diff Filtering

Use -diff to only mutate lines that have changed since a specific git reference or patch file:

gorgon -diff HEAD~1 ./path      # last commit
gorgon -diff HEAD ./path        # staged changes
gorgon -diff main ./path        # divergence from main branch
gorgon -diff abc1234 ./path     # specific commit SHA
gorgon -diff=path/to/file.patch ./path

This is useful for CI/CD pipelines to focus mutation testing on changed code only.

Config

diff: "HEAD~1"

When -config is used, inline //gorgon:ignore comments are automatically added to the config file's suppress: section. Each comment becomes a YAML entry with the relative file path, line number, and suppressed operators:

suppress:
  - location: pkg/file.go:12
    operators:
      - panic_removal

Existing config suppressions are preserved and merged with inline comments. Paths are always relative to the project root, regardless of which subfolder you run Gorgon on.

Per-Directory Operator Rules

Use dir_rules in your config file to control which operators apply to specific directories. This is useful for enforcing different mutation testing policies across different parts of your codebase.

Whitelist

Only allow specific operators in a directory:

dir_rules:
  - dir: internal/core
    whitelist:
      - arithmetic_flip
      - boundary_value
      - condition_negation

Blacklist

Block specific operators in a directory:

dir_rules:
  - dir: internal/api
    blacklist:
      - defer_removal
      - concurrency

Exclude Entire Directory

Use all as a blacklist value to skip an entire directory:

dir_rules:
  - dir: vendor/
    blacklist:
      - all

How It Works

  • Rules match by directory prefix (longest match wins)
  • Whitelist takes precedence over blacklist
  • If no rule matches, all operators apply
  • dir_rules is config-file-only (not a CLI flag)

Example Config

dir_rules:
  - dir: internal/core
    whitelist:
      - arithmetic_flip
      - math_operators
      - boundary_value
  - dir: internal/api
    blacklist:
      - all
  - dir: pkg/config
    blacklist:
      - concurrency
      - defer_removal

Go Workspace Support

Gorgon fully supports Go workspaces (go.work) for multi-module monorepos. Workspace detection is automatic — no configuration required.

How It Works

When you run Gorgon on a directory, it:

  1. Searches for go.work by walking up the directory tree
  2. Falls back to go.mod if no workspace is found (single-module mode)
  3. Enumerates all workspace members from use directives
  4. Copies all modules to the temporary test environment
  5. Preserves cross-module dependencies via go.work and go.work.sum

Example Workspace Layout

monorepo/
├── go.work              # Workspace root
├── go.work.sum
├── gorgon.yml           # Optional: root config
├── service-a/
│   ├── go.mod
│   ├── go.sum
│   ├── gorgon.yml       # Optional: service-a specific config
│   └── pkg/
│       └── handler.go
├── service-b/
│   ├── go.mod
│   ├── go.sum
│   ├── gorgon.yml       # Optional: service-b specific config
│   └── pkg/
│       └── api.go
└── shared/
    ├── go.mod
    └── pkg/
        └── common.go

go.work:

go 1.25

use ./service-a
use ./service-b
use ./shared

Running Gorgon on a Workspace

# From workspace root - tests all modules
gorgon ./...

# Target specific module
gorgon ./service-a

# Target multiple modules
gorgon ./service-a ./service-b

# With config (discovers sub-configs in all modules)
gorgon -config=gorgon.yml ./...

Cross-Module Imports

Workspace mode preserves cross-module dependencies. If service-a imports shared, mutations in shared are correctly tested by service-a's tests.

// service-a/pkg/handler.go
import "monorepo/shared/pkg"

func Handle() {
    return shared.Common() // mutation in shared.Common() is tested
}

Sub-Configs in Workspaces

Each workspace member can have its own gorgon.yml:

# service-a/gorgon.yml
threshold: 90
operators:
  - arithmetic_flip
  - boundary_value

# service-b/gorgon.yml  
threshold: 70
operators:
  - all

Sub-config discovery walks all workspace members, not just the workspace root. See Per-Package Configuration for details on sub-config resolution.

Single-Module Compatibility

Projects without go.work continue to work exactly as before:

project/
├── go.mod
├── go.sum
└── pkg/
    └── code.go
gorgon ./...  # Works identically to v0.5

Limitations

Out-of-tree modules: If go.work references modules outside the workspace root (e.g., use ../sibling), Gorgon will reject files from those modules with a clear error. This is an uncommon pattern and can be addressed if needed.

Organization-Level Policy (Enterprise Governance)

Large organizations need top-down policy enforcement that teams cannot weaken. The gorgon-org.yml file defines hard minimums and required settings that apply across all projects.

How It Works

  1. Discovery: Gorgon searches for gorgon-org.yml by walking up from the project root, or via GORGON_ORG_POLICY env var
  2. Enforcement: Policy constraints are applied after all sub-config resolution
  3. Non-overrideable: Teams cannot opt out or weaken org policy settings

Example Org Policy

# gorgon-org.yml - placed at org root or set via GORGON_ORG_POLICY env var

# Minimum score any package must achieve
threshold_floor: 65.0

# These operators run everywhere, regardless of team config
required_operators:
  - condition_negation
  - arithmetic_flip
  - error_handling

# These operators are prohibited org-wide
forbidden_operators:
  - empty_body

# Teams cannot change these settings
locked_settings:
  - skip_func      # teams cannot exempt functions
  - exclude        # teams cannot exclude files

# Generated code skipped everywhere
forced_skip_paths:
  - "*.pb.go"
  - "mock_*.go"

# All CI machines must use at least 4 cores
min_concurrent: 4

# Cache must always be on
require_cache: true

Policy Fields

Field Description
threshold_floor Minimum mutation score. Sub-configs setting lower thresholds are raised to this value
required_operators Operators that must run everywhere. Injected into all configs
forbidden_operators Operators that are never allowed. Removed from all configs
locked_settings Settings teams cannot override: skip, skip_func, exclude, include, tests, cache, concurrent, operators, threshold
forced_skip_paths Paths always skipped (e.g., generated code)
forced_exclude_patterns Glob patterns always excluded
min_concurrent Minimum concurrency level
require_cache Force cache to be enabled

Discovery Locations

Gorgon searches for gorgon-org.yml in this order:

  1. GORGON_ORG_POLICY env var - Explicit path (highest priority)
  2. Walk up from project root - Searches parent directories
  3. $XDG_CONFIG_HOME/gorgon/gorgon-org.yml - User/org config directory

Violation Reporting

When org policy constraints are applied, violations are logged:

Org policy applied 3 constraint(s):
  org policy: threshold was "40.00", enforced to "65.00" (below org threshold_floor)
  org policy: operators was "boundary_value", enforced to "condition_negation (injected)" (required by org policy)
  org policy: cache was "false", enforced to "true" (require_cache set in org policy)

Control violation behavior in your team config:

# gorgon.yml
violation_mode: fail    # fail (default), warn, or silent

The org policy can lock this setting to prevent teams from silencing violations:

# gorgon-org.yml
locked_settings:
  - violation_mode

Team Config Options

Teams can configure how they work within policy constraints:

# gorgon.yml

# How sub-configs inherit from parents
sub_config_mode: merge  # merge (default), replace, or isolate

# Propagate root threshold to sub-configs without their own
threshold_inherit: true

# How to handle policy violations
violation_mode: fail  # fail (default), warn, or silent

Benefits

Compliance: Security-critical operators can be mandated org-wide
Prevent gaming: Teams cannot set threshold: 0 to bypass quality gates
Consistent standards: Generated code handling, concurrency, caching enforced uniformly
Separation of concerns: Platform teams own policy, app teams own their configs
Transparent: Violations are logged so teams understand what was enforced

Per-Package / Per-Module Configuration Overrides

This is a meaningful feature for monorepos. Sub-configs are named gorgon.yml and discovered during a tree walk. The root config is identified by being explicitly passed via -config; everything else found during the tree walk is a sub-config.

Discovery Model

  • Sub-configs are named gorgon.yml — same filename as the root
  • Discovery skips vendor/, .git/, and _-prefixed directories
  • Chaining behavior: root → core/gorgon.yml → core/auth/gorgon.yml for a file in core/auth/
  • Each level in the chain applies in order

What's Overrideable

Replace semantics (deepest sub-config wins outright):

  • operators — core risk profile decision, a subtree owns this entirely
  • threshold — different quality bars per package (generated code vs. core library)
  • tests — a subtree may have its own test suite or integration tests
  • concurrent — a subtree with expensive tests may want to throttle parallelism

Merge/additive semantics (all levels in the chain contribute):

  • exclude / include — accumulated; deeper configs add more file filters
  • skip / skip_func — accumulated; you never want a parent to un-skip something a child skipped
  • suppress — accumulated; suppressions only grow as you go deeper
  • dir_rules — accumulated; deeper rules are more specific and evaluated with existing longest-prefix logic

Not overrideable (global only):

  • cache, dry_run, debug, progbar — run-mode flags
  • format, output, cpu_profile — output concerns, single report
  • diff — a global VCS filter
  • base — structural, set once

How It Works

  1. Discovery: Gorgon walks the project tree looking for gorgon.yml files (excluding vendor/, .git/, _-prefixed dirs)
  2. Chaining: For each file, Gorgon builds a chain of all sub-configs that are ancestors-or-self
  3. Resolution:
    • operators, threshold, tests, concurrent: deepest sub-config wins
    • exclude, include, skip, skip_func, suppress, dir_rules: accumulated across all levels

Example

# Root config (gorgon.yml at project root)
threshold: 80
operators:
  - all

# In internal/core/gorgon.yml
threshold: 90
operators:
  - arithmetic_flip
  - boundary_value

# In internal/api/gorgon.yml
threshold: 70
concurrent: 2

For a file in internal/api/handler.go:

  • Uses threshold: 70 (from api sub-config)
  • Uses operators: [all] (from root, since api doesn't specify operators)
  • Uses concurrent: 2 (from api sub-config)

Per-Package Threshold Checking

When sub-configs are present, mutation score thresholds are checked per-package. Each package can have its own threshold defined in its sub-config. The reporter will show which packages failed their threshold check:

Packages below threshold:
 pkg/testdata/subconfig: 85.00% (threshold 90.00%)

Logging

When sub-configs are discovered, Gorgon logs the count:

Loaded sub-configs from 3 directories

In debug mode, additional details about operator filtering per directory are shown:

[DEBUG] Dir rule internal/core: whitelist 3 operators for internal/core/handler.go

Important Notes

  • Sub-configs work with or without go.mod files in subdirectories
  • The operators field in a sub-config applies to ALL files in that directory subtree
  • To have different operators for nested directories, create separate gorgon.yml files in each nested directory
  • dir_rules in a sub-config provides fine-grained control WITHIN that directory's subtree

Sub-Config File Format

A sub-config file (gorgon.yml) in a subdirectory can contain any of the overrideable fields:

# Example: internal/core/gorgon.yml
threshold: 90
operators:
  - arithmetic_flip
  - boundary_value
  - condition_negation
concurrent: 2
tests:
  - internal/core/core_test.go
exclude:
  - '*_generated.go'
skip:
  - internal/core/legacy/
skip_func:
  - internal/core/legacy/legacy.go:OldFunc
suppress:
  - location: internal/core/legacy/legacy.go:42
    operators:
      - arithmetic_flip
dir_rules:
  - dir: internal/core/internal
    blacklist:
      - all

Sub-Config Precedence

When multiple sub-configs apply to a file (e.g., root → core → core/auth), the precedence is:

  1. For replace fields (operators, threshold, tests, concurrent):

    • Deepest sub-config wins
    • If a field is not set in a sub-config, it falls back to the parent
  2. For merge fields (exclude, include, skip, skip_func, suppress, dir_rules):

    • All sub-configs contribute
    • Order: root → deeper → deepest
    • Later entries are appended to earlier ones
  3. For dir_rules specifically:

    • Rules from all levels are accumulated
    • Longest-prefix matching still applies within the merged set
    • Whitelist takes precedence over blacklist

Example: Nested Sub-Configs

project/
├── gorgon.yml              # root: threshold=80, operators=[all]
├── internal/
│   ├── gorgon.yml          # internal: threshold=90, operators=[arithmetic_flip]
│   └── core/
│       ├── gorgon.yml      # core: threshold=95, operators=[boundary_value]
│       └── handler.go      # Uses: threshold=95, operators=[boundary_value]

For internal/core/handler.go:

  • threshold: 95 (from core sub-config)
  • operators: [boundary_value] (from core sub-config)
  • exclude, skip, etc.: accumulated from all three configs

Deep Dive: How Sub-Configs Are Resolved

1. Discovery Phase

Gorgon walks the directory tree starting from the project root:

project/
├── gorgon.yml              # Root config (explicitly passed via -config)
├── internal/
│   ├── gorgon.yml          # Sub-config #1
│   └── core/
│       ├── gorgon.yml      # Sub-config #2
│       └── handler.go

Each gorgon.yml found (except the root config) is stored as a sub-config entry with its directory path.

2. Chain Building

For each file being mutated, Gorgon builds a chain of applicable sub-configs:

File: project/internal/core/handler.go

Chain (shallowest → deepest):
1. project/internal/gorgon.yml
2. project/internal/core/gorgon.yml

The chain includes all sub-configs that are ancestors of the file's directory.

3. Field Resolution

Replace fields (last one wins):

  • operators: Walk chain deepest→shallowest, return first non-empty list
  • threshold: Walk chain deepest→shallowest, return first non-nil pointer
  • tests: Walk chain deepest→shallowest, return first non-empty list
  • concurrent: Walk chain deepest→shallowest, return first non-empty string

Merge fields (accumulate all):

  • exclude: root.Exclude + chain[0].Exclude + chain[1].Exclude + ...
  • include: root.Include + chain[0].Include + chain[1].Include + ...
  • skip: root.Skip + chain[0].Skip + chain[1].Skip + ...
  • skip_func: root.SkipFunc + chain[0].SkipFunc + chain[1].SkipFunc + ...
  • suppress: root.Suppress + chain[0].Suppress + chain[1].Suppress + ...
  • dir_rules: root.DirRules + chain[0].DirRules + chain[1].DirRules + ...
4. Operator Application

For each mutation site:

  1. Start with root operators (or all operators if root has all)
  2. Apply sub-config operator override (replace semantics)
  3. Apply dir_rules filtering (merge semantics from all levels)
  4. Generate mutants with the final operator list
5. Threshold Checking

When reporting results:

  1. Group mutants by their package directory
  2. For each package, look up the effective threshold from the chain
  3. Calculate mutation score for that package
  4. Check if score meets the package's threshold
  5. Report any packages that failed their threshold

Go Version Configuration

Gorgon automatically detects the Go version from your project's go.mod file and uses it when generating temporary test modules. This ensures compatibility as Go evolves.

Auto-Detection

By default, Gorgon reads the go X.Y directive from your project's go.mod:

// go.mod
module myproject

go 1.25  // ← Gorgon uses this version

If detection fails, it falls back to 1.25.

Manual Override

Override the detected version in your config file:

# gorgon.yml
go_version: "1.26"  # Force specific Go version

Use cases:

  • Testing compatibility with a newer Go version
  • Working in environments where go.mod detection fails
  • Standardizing across multiple projects

Note: The version must be in X.Y format (e.g., 1.25, 1.26).

Memory Optimization

For extremely large files with many mutants (>500), Gorgon can split them into separate compilation units to prevent out-of-memory errors during compilation.

Chunking (Default: Enabled)

# gorgon.yml
chunk_large_files: true  # Split files with >500 mutants (default)

When enabled, files with more than 500 mutants are automatically split into chunks:

  • Each chunk compiles independently with ≤500 mutants
  • Reduces peak memory usage during compilation
  • Prevents compiler OOM kills on extremely large files

Example: A file with 1200 mutants splits into:

  • Chunk 1: mutants 1-500 → internal/cli/
  • Chunk 2: mutants 501-1000 → internal/cli_chunk2/
  • Chunk 3: mutants 1001-1200 → internal/cli_chunk3/

Disable Chunking

chunk_large_files: false  # Compile all mutants together

Disable if:

  • You have sufficient RAM (32GB+)
  • No single file has >500 mutants
  • You want slightly faster execution

Trade-off: Faster execution but may cause OOM on extremely large files.

Note: The preflight optimizations handle most cases efficiently. Chunking is only a safety net for truly massive files.

Output Files

Write the report to a file instead of (or in addition to) stdout:

gorgon -output=report.txt ./path

This writes the full report (mutation score, top killers, killed/survived mutants) to report.txt while still printing to stdout.

Debug Files

Enable -debug-files to also write detailed debug information:

gorgon -output=report.txt -debug-files ./path

This creates two files:

  • report.txt — the standard report (stats, killed mutants, survived mutants)
  • report.debug.txt — detailed debug info (error summaries, per-mutant compilation errors)

Config

format: textfile
output: "report.txt"
debug_files: true

Currently textfile and html format is supported.

CPU Profiling

Use -cpu-profile=file.out or cpu_profile: "file.out" to write a CPU profile. Analyze it with:

go tool pprof -http=:8080 file.out

Use cpu_profile: "true" to write to gorgon.cpuprofile in the current directory.

Directories

Path Synopsis
cmd
gorgon command
Package main provides the gorgon command-line tool.
Package main provides the gorgon command-line tool.
examples
internal
cli
pkg
mutator
Package mutator provides mutation operators for the gorgon project.
Package mutator provides mutation operators for the gorgon project.
mutator/analysis
utilities used by mutation operators.
utilities used by mutation operators.
mutator/operators/arithmetic_flip
Package arithmetic_flip provides arithmetic operator flip mutations.
Package arithmetic_flip provides arithmetic operator flip mutations.
mutator/operators/assignment_operator
Package assignment_operator provides assignment operator mutation operators.
Package assignment_operator provides assignment operator mutation operators.
mutator/operators/boundary_value
Package boundary_value provides boundary comparison mutation operators.
Package boundary_value provides boundary comparison mutation operators.
mutator/operators/condition_negation
Package condition_negation provides condition negation mutations.
Package condition_negation provides condition negation mutations.
mutator/operators/logical_operator
Package logical_operator provides logical operator replacement mutations.
Package logical_operator provides logical operator replacement mutations.
mutator/tokens
Package tokens defines operator flip maps used by token-based mutation operators.
Package tokens defines operator flip maps used by token-based mutation operators.
tests

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL