Skip to content

Go: in a go.work workspace or with a relative replace, workers cannot build, so every mutant counts as KILLED #27

Open

Description

@vrazdalovschi

What happens

In a Go module that is part of a go.work workspace, or that points at a sibling module with a relative replace directory, no mutant can build inside its worker. Every mutant fails with [setup failed] and counts as KILLED, so the package scores 100% even though no test ran (the symptom in #18, with a different cause, like #15).

Repro

mutator a5a0cb8, crapper 6fd92c4, Go 1.27.3, Linux.

repo/
  go.work         use ./lib ./svc
  lib/go.mod      module example.com/acme/lib
  lib/lib.go      package lib; func Zero() int { return 0 }
  svc/go.mod      module example.com/acme/svc
                  require example.com/acme/lib v0.0.0
                  replace example.com/acme/lib => ../lib
  svc/calc/calc.go
  svc/calc/calc_test.go

svc/calc/calc.go:

package calc

import "example.com/acme/lib"

func Max(a, b int) int {
	if a > b {
		return a
	}
	return b + lib.Zero()
}

svc/calc/calc_test.go:

package calc

import "testing"

func TestMax(t *testing.T) {
	if Max(2, 1) != 2 || Max(1, 2) != 2 {
		t.Fatal("wrong max")
	}
}

Both mutation sites are equivalent mutants (>= gives the same Max, and b - 0 == b + 0), so both should survive. go test ./calc passes in svc/.

$ cd svc && mutator --no-coverage --mutate-all calc/calc.go
KILLED    calc/calc.go:6 > -> >=
KILLED    calc/calc.go:9 + -> -
Total     2   0   0   2  100.0%

$ GOWORK=off mutator --no-coverage --mutate-all calc/calc.go
Total     2   0   0   2  100.0%

Control: the same function in a single module with no workspace and no replace reports both sites SURVIVED.

Cause, as far as I can tell

The worker is a directory under target/mutation-workers/ with the module's files linked into it. Running go test there, by hand in an equivalent directory:

  • With the workspace active, Go resolves the worker to the original module, whose directory does not contain the worker path:

    main module (example.com/acme/svc) does not contain package example.com/acme/svc/target/probe/calc
    FAIL	./calc [setup failed]
    
  • With GOWORK=off, the relative replace is resolved from the worker directory, where ../lib does not exist:

    calc/calc.go:3:8: example.com/acme/lib@v0.0.0: replacement directory ../lib does not exist
    FAIL	example.com/acme/svc/calc [setup failed]
    

Why it matters

Monorepos commonly use go.work, and modules often keep relative replace lines so they also build without the workspace. On those layouts, mutator reports 100% for every package and exits 0.

Possible fix

  • Give the worker its own workspace: write a go.work into the worker that uses the worker directory plus the other workspace modules by absolute path, and set GOWORK to it.
  • For relative replace directories, run with -modfile pointing at a copy of go.mod whose replace paths are made absolute.
  • Independently of this, Mutants that do not compile count as KILLED, which inflates the score (Go: + on strings) #18's suggestion (report [setup failed] and [build failed] as their own outcome, left out of the score) would turn this from a silent 100% into a visible error.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions