Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ inputs:
required: false
default: ""
type: string

source-dir:
description: Sets relative path to source files.
required: false
default: ""
type: string

# Individual properties
profile:
description: Path to the coverage profile file. Overrides value from configuration.
Expand Down Expand Up @@ -132,6 +137,7 @@ runs:
args:
- --config=${{ inputs.config || '''''' }}
- --profile=${{ inputs.profile || '''''' }}
- --source-dir=${{ inputs.source-dir || '''''' }}
- --github-action-output=true
- --threshold-file=${{ inputs.threshold-file }}
- --threshold-package=${{ inputs.threshold-package }}
Expand Down
13 changes: 13 additions & 0 deletions docs/github_action.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,19 @@ Alternatively, if you don't need advanced configuration options from a config fi

Note: When using a config file alongside action properties, specifying these parameters will override the corresponding values in the config file.

## Source Directory

Some projects, such as monorepos with multiple projects under the root directory, may require specifying the path to a project's source.
In such cases, the `source-dir` property can be used to specify the source files location relative to the root directory.

```yml
- name: check test coverage
uses: vladopajic/go-test-coverage@v2
with:
config: ./.testcoverage.yml
source-dir: ./some_project
```

## Liberal Coverage Check

The `go-test-coverage` GitHub Action can be configured to report the current test coverage without enforcing specific thresholds. To enable this functionality in your GitHub workflow, include the `continue-on-error: true` property in the job step configuration. This ensures that the workflow proceeds even if the coverage check fails.
Expand Down
6 changes: 6 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type args struct {
ConfigPath string `arg:"-c,--config"`
Profile string `arg:"-p,--profile" help:"path to coverage profile"`
LocalPrefix string `arg:"-l,--local-prefix"` // deprecated
SourceDir string `arg:"-s,--source-dir"`
GithubActionOutput bool `arg:"-o,--github-action-output"`
ThresholdFile int `arg:"-f,--threshold-file"`
ThresholdPackage int `arg:"-k,--threshold-package"`
Expand Down Expand Up @@ -56,6 +57,7 @@ func newArgs() args {
ConfigPath: ciDefaultString,
Profile: ciDefaultString,
LocalPrefix: ciDefaultString,
SourceDir: ciDefaultString,
GithubActionOutput: false,
ThresholdFile: ciDefaultInt,
ThresholdPackage: ciDefaultInt,
Expand Down Expand Up @@ -102,6 +104,10 @@ func (a *args) overrideConfig(cfg testcoverage.Config) (testcoverage.Config, err
cfg.LocalPrefixDeprecated = a.LocalPrefix
}

if !isCIDefaultString(a.SourceDir) {
cfg.SourceDir = a.SourceDir
}

if !isCIDefaultInt(a.ThresholdFile) {
cfg.Threshold.File = a.ThresholdFile
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/testcoverage/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func GenerateCoverageStats(cfg Config) ([]coverage.Stats, error) {
return coverage.GenerateCoverageStats(coverage.Config{ //nolint:wrapcheck // err wrapped above
Profiles: strings.Split(cfg.Profile, ","),
ExcludePaths: cfg.Exclude.Paths,
RootDir: cfg.RootDir,
SourceDir: cfg.SourceDir,
})
}

Expand Down
32 changes: 16 additions & 16 deletions pkg/testcoverage/check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ const (
breakdownOK = testdataDir + testdata.BreakdownOK
breakdownNOK = testdataDir + testdata.BreakdownNOK

prefix = "github.com/vladopajic/go-test-coverage/v2"
rootDir = "../../"
prefix = "github.com/vladopajic/go-test-coverage/v2"
sourceDir = "../../"
)

func TestCheck(t *testing.T) {
Expand Down Expand Up @@ -60,7 +60,7 @@ func TestCheck(t *testing.T) {
t.Parallel()

buf := &bytes.Buffer{}
cfg := Config{Profile: profileOK, Threshold: Threshold{Total: 65}, RootDir: rootDir}
cfg := Config{Profile: profileOK, Threshold: Threshold{Total: 65}, SourceDir: sourceDir}
pass := Check(buf, cfg)
assert.True(t, pass)
assertGithubActionErrorsCount(t, buf.String(), 0)
Expand All @@ -79,7 +79,7 @@ func TestCheck(t *testing.T) {
Exclude: Exclude{
Paths: []string{`cdn\.go$`, `github\.go$`, `cover\.go$`, `check\.go$`, `path\.go$`},
},
RootDir: rootDir,
SourceDir: sourceDir,
}
pass := Check(buf, cfg)
assert.True(t, pass)
Expand All @@ -92,7 +92,7 @@ func TestCheck(t *testing.T) {
t.Parallel()

buf := &bytes.Buffer{}
cfg := Config{Profile: profileOK, Threshold: Threshold{Total: 100}, RootDir: rootDir}
cfg := Config{Profile: profileOK, Threshold: Threshold{Total: 100}, SourceDir: sourceDir}
pass := Check(buf, cfg)
assert.False(t, pass)
assertGithubActionErrorsCount(t, buf.String(), 0)
Expand All @@ -113,7 +113,7 @@ func TestCheck(t *testing.T) {
Profile: profileOK,
Threshold: Threshold{File: 100},
Override: []Override{{Threshold: 10, Path: "^pkg"}},
RootDir: rootDir,
SourceDir: sourceDir,
}
pass := Check(buf, cfg)
assert.True(t, pass)
Expand All @@ -131,7 +131,7 @@ func TestCheck(t *testing.T) {
Profile: profileOK,
Threshold: Threshold{File: 10},
Override: []Override{{Threshold: 100, Path: "^pkg"}},
RootDir: rootDir,
SourceDir: sourceDir,
}
pass := Check(buf, cfg)
assert.False(t, pass)
Expand All @@ -153,7 +153,7 @@ func TestCheck(t *testing.T) {
Profile: profileOK,
Threshold: Threshold{File: 70},
Override: []Override{{Threshold: 60, Path: "pkg/testcoverage/badgestorer/github.go"}},
RootDir: rootDir,
SourceDir: sourceDir,
}
pass := Check(buf, cfg)
assert.True(t, pass)
Expand All @@ -171,7 +171,7 @@ func TestCheck(t *testing.T) {
Profile: profileOK,
Threshold: Threshold{File: 70},
Override: []Override{{Threshold: 80, Path: "pkg/testcoverage/badgestorer/github.go"}},
RootDir: rootDir,
SourceDir: sourceDir,
}
pass := Check(buf, cfg)
assert.False(t, pass)
Expand All @@ -195,7 +195,7 @@ func TestCheck(t *testing.T) {
Badge: Badge{
FileName: t.TempDir(), // should failed because this is dir
},
RootDir: rootDir,
SourceDir: sourceDir,
}
pass := Check(buf, cfg)
assert.False(t, pass)
Expand All @@ -209,7 +209,7 @@ func TestCheck(t *testing.T) {
cfg := Config{
Profile: profileOK,
BreakdownFileName: t.TempDir(), // should failed because this is dir
RootDir: rootDir,
SourceDir: sourceDir,
}
pass := Check(buf, cfg)
assert.False(t, pass)
Expand All @@ -223,7 +223,7 @@ func TestCheck(t *testing.T) {
cfg := Config{
Profile: profileOK,
BreakdownFileName: t.TempDir() + "/breakdown.testcoverage",
RootDir: rootDir,
SourceDir: sourceDir,
}
pass := Check(buf, cfg)
assert.True(t, pass)
Expand All @@ -246,7 +246,7 @@ func TestCheck(t *testing.T) {
Diff: Diff{
BaseBreakdownFileName: t.TempDir(), // should failed because this is dir
},
RootDir: rootDir,
SourceDir: sourceDir,
}
pass := Check(buf, cfg)
assert.False(t, pass)
Expand All @@ -268,7 +268,7 @@ func TestCheckNoParallel(t *testing.T) {
Profile: profileOK,
GithubActionOutput: true,
Threshold: Threshold{Total: 100},
RootDir: rootDir,
SourceDir: sourceDir,
}
pass := Check(buf, cfg)
assert.False(t, pass)
Expand All @@ -283,7 +283,7 @@ func TestCheckNoParallel(t *testing.T) {
Profile: profileOK,
GithubActionOutput: true,
Threshold: Threshold{Total: 10},
RootDir: rootDir,
SourceDir: sourceDir,
}
pass := Check(buf, cfg)
assert.True(t, pass)
Expand All @@ -302,7 +302,7 @@ func TestCheckNoParallel(t *testing.T) {
Profile: profileOK,
GithubActionOutput: true,
Threshold: Threshold{Total: 100},
RootDir: rootDir,
SourceDir: sourceDir,
}
pass := Check(buf, cfg)
assert.False(t, pass)
Expand Down
2 changes: 1 addition & 1 deletion pkg/testcoverage/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ var (
type Config struct {
Profile string `yaml:"profile"`
LocalPrefixDeprecated string `yaml:"-"`
SourceDir string `yaml:"-"`
Threshold Threshold `yaml:"threshold"`
Override []Override `yaml:"override,omitempty"`
Exclude Exclude `yaml:"exclude"`
BreakdownFileName string `yaml:"breakdown-file-name"`
GithubActionOutput bool `yaml:"github-action-output"`
Diff Diff `yaml:"diff"`
Badge Badge `yaml:"-"`
RootDir string `yaml:"-"`
}

type Threshold struct {
Expand Down
35 changes: 19 additions & 16 deletions pkg/testcoverage/coverage/cover.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const IgnoreText = "coverage-ignore"
type Config struct {
Profiles []string
ExcludePaths []string
RootDir string
SourceDir string
}

func GenerateCoverageStats(cfg Config) ([]Stats, error) {
Expand All @@ -30,7 +30,7 @@ func GenerateCoverageStats(cfg Config) ([]Stats, error) {
return nil, fmt.Errorf("parsing profiles: %w", err)
}

files, err := findFiles(profiles, cfg.RootDir)
files, err := findFiles(profiles, cfg.SourceDir)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -121,17 +121,7 @@ func findFiles(profiles []*cover.Profile, rootDir string) (map[string]fileInfo,
return result, nil
}

func defaultRootDir(rootDir string) string {
if rootDir == "" {
rootDir = "."
}

return rootDir
}

func findFileCreator(rootDir string) func(file string) (string, string, bool) {
rootDir = defaultRootDir(rootDir)

cache := make(map[string]*build.Package)
findBuildImport := func(file string) (string, string, bool) {
dir, file := filepath.Split(file)
Expand All @@ -155,6 +145,7 @@ func findFileCreator(rootDir string) func(file string) (string, string, bool) {
return file, noPrefixName, err == nil
}

rootDir = defaultRootDir(rootDir)
prefix := findModuleDirective(rootDir)
files := listAllFiles(rootDir)
findWalk := func(file, prefix string) (string, string, bool) {
Expand All @@ -177,9 +168,24 @@ func findFileCreator(rootDir string) func(file string) (string, string, bool) {
}
}

func defaultRootDir(rootDir string) string {
if rootDir == "" {
rootDir = "."
}

return rootDir
}

func listAllFiles(rootDir string) []fileInfo {
files := make([]fileInfo, 0)

makeName := func(file string) string {
name, _ := strings.CutPrefix(file, rootDir)
name = path.NormalizeForTool(name)

return name
}

//nolint:errcheck // error ignored because there is fallback mechanism for finding files
filepath.Walk(rootDir, func(file string, info os.FileInfo, err error) error {
if err != nil { // coverage-ignore
Expand All @@ -189,12 +195,9 @@ func listAllFiles(rootDir string) []fileInfo {
if !info.IsDir() &&
strings.HasSuffix(file, ".go") &&
!strings.HasSuffix(file, "_test.go") {
name, _ := strings.CutPrefix(file, rootDir)
name = path.NormalizeForTool(name)

files = append(files, fileInfo{
path: file,
name: name,
name: makeName(file),
})
}

Expand Down
20 changes: 10 additions & 10 deletions pkg/testcoverage/coverage/cover_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const (
prefix = "github.com/vladopajic/go-test-coverage/v2"
coverFilename = "pkg/testcoverage/coverage/cover.go"

rootDir = "../../../"
sourceDir = "../../../"
)

func Test_GenerateCoverageStats(t *testing.T) {
Expand All @@ -42,16 +42,16 @@ func Test_GenerateCoverageStats(t *testing.T) {

// should get error parsing invalid profile file
stats, err = GenerateCoverageStats(Config{
Profiles: []string{profileNOK},
RootDir: rootDir,
Profiles: []string{profileNOK},
SourceDir: sourceDir,
})
assert.Error(t, err)
assert.Empty(t, stats)

// should be okay to read valid profile
stats1, err := GenerateCoverageStats(Config{
Profiles: []string{profileOK},
RootDir: rootDir,
Profiles: []string{profileOK},
SourceDir: sourceDir,
})
assert.NoError(t, err)
assert.NotEmpty(t, stats1)
Expand All @@ -60,7 +60,7 @@ func Test_GenerateCoverageStats(t *testing.T) {
stats2, err := GenerateCoverageStats(Config{
Profiles: []string{profileOK},
ExcludePaths: []string{`cover\.go$`},
RootDir: rootDir,
SourceDir: sourceDir,
})
assert.NoError(t, err)
assert.NotEmpty(t, stats2)
Expand All @@ -69,17 +69,17 @@ func Test_GenerateCoverageStats(t *testing.T) {

// should have total coverage because of second profile
stats3, err := GenerateCoverageStats(Config{
Profiles: []string{profileOK, profileOKFull},
RootDir: rootDir,
Profiles: []string{profileOK, profileOKFull},
SourceDir: sourceDir,
})
assert.NoError(t, err)
assert.NotEmpty(t, stats3)
assert.Equal(t, 100, StatsCalcTotal(stats3).CoveredPercentage())

// should not have `badge/generate.go` in statistics because it has no statements
stats4, err := GenerateCoverageStats(Config{
Profiles: []string{profileOKNoStatements},
RootDir: rootDir,
Profiles: []string{profileOKNoStatements},
SourceDir: sourceDir,
})
assert.NoError(t, err)
assert.Len(t, stats4, 1)
Expand Down
Loading