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
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,15 @@ All notable changes to this project will be documented in this file.
This project follows the [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) format
and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.1.0] — Unreleased
## [0.1.1] — Unreleased

### Fixed
- **dependgen**: Build tags (`//go:build` and `// +build` directives) are now correctly preserved from source test files to generated `*_depend_test.go` files ([#1](https://github.com/srgg/testify/issues/1))
- Scans all comment groups before package declaration to find build constraints
- Handles files with copyright notices or other comments before build tags
- Ensures generated files maintain the same build constraints as source files

## [0.1.0] — 2024-11-03

Initial public release of `testify/depend`, bringing first-class dependency management
to Go test suites built with `stretchr/testify`.
Expand Down
13 changes: 9 additions & 4 deletions depend/cmd/dependgen/generator.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

95 changes: 95 additions & 0 deletions depend/cmd/dependgen/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,101 @@ func TestSuite(t *testing.T) {
}
}

func (s *IntegrationSuite) TestBuildTagsPreservedInGeneratedFile() {
// GOAL: Verify build tags are preserved from source file to generated file
// Fixes GitHub issue #1: https://github.com/srgg/testify/issues/1
//
// TEST SCENARIO: Create test file with //go:build and // +build tags → run dependgen →
// generated file preserves both build tags before the "Code generated" comment

testCases := []struct {
name string
fileContent string
}{
{
name: "Build tags as first comment group",
fileContent: `//go:build test
// +build test

package test

import (
"testing"
"github.com/srgg/testify/depend"
"github.com/stretchr/testify/suite"
)

type MySuite struct {
suite.Suite
}

func (s *MySuite) TestA() {}
func (s *MySuite) TestB() {}

func TestMySuite(t *testing.T) {
depend.RunSuite(t, new(MySuite))
}`,
},
{
name: "Build tags after copyright comment",
fileContent: `// Copyright 2024 Example Corp
// All rights reserved

//go:build test
// +build test

package test

import (
"testing"
"github.com/srgg/testify/depend"
"github.com/stretchr/testify/suite"
)

type MySuite struct {
suite.Suite
}

func (s *MySuite) TestA() {}
func (s *MySuite) TestB() {}

func TestMySuite(t *testing.T) {
depend.RunSuite(t, new(MySuite))
}`,
},
}

for _, tc := range testCases {
s.Run(tc.name, func() {
testDir := s.TempDir().Build()

testFile := filepath.Join(testDir, "suite_test.go")
err := os.WriteFile(testFile, []byte(tc.fileContent), 0644)
s.Require().NoError(err, "MUST write test file")

// Run dependgen in auto-detect mode
output, err := s.runDependgen(testDir, []string{"-v"}, map[string]string{"GOFILE": "suite_test.go"})
s.Assert().NoError(err, "MUST generate successfully: %s", output)

// Verify generated file contains both build tags
generatedCode := s.assertGeneratedFile(testDir, "suite_depend_test.go",
"//go:build test",
"// +build test",
"Code generated by dependgen",
"MySuiteTestRegistry")

// Verify build tags appear BEFORE the generated code comment (critical for Go toolchain)
s.Assert().Regexp(`(?s)//go:build test\s+// \+build test\s+// Code generated by dependgen`, generatedCode,
"MUST have build tags before generated code comment")

// Copyright should NOT be copied to generated file
if tc.name == "Build tags after copyright comment" {
s.Assert().NotContains(generatedCode, "Copyright", "MUST NOT copy copyright to generated file")
}
})
}
}

func TestDependgenSuite(t *testing.T) {
suite.Run(t, new(IntegrationSuite))
}
53 changes: 50 additions & 3 deletions depend/cmd/dependgen/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,21 @@ type SuiteInfo struct {
Package string // Package name (e.g., "depend")
Tests []TestMethodInfo // Test methods in this suite
SourceFile string // Source file where suite was defined (e.g., "runtime_test.go")
BuildTags []string // Build tags from source file (e.g., ["//go:build test", "// +build test"])
}

// suiteCollector collects suite information during scanning.
// It encapsulates the logic for tracking suites and their test methods.
type suiteCollector struct {
suites map[string]SuiteInfo
suites map[string]SuiteInfo
srcFileBuildTags map[string][]string // sourceFile -> buildTags
}

// newSuiteCollector creates a new suite collector.
func newSuiteCollector() *suiteCollector {
return &suiteCollector{
suites: make(map[string]SuiteInfo),
suites: make(map[string]SuiteInfo),
srcFileBuildTags: make(map[string][]string),
}
}

Expand All @@ -46,6 +49,11 @@ func (c *suiteCollector) addSuiteName(name string) {
}
}

// setBuildTags stores build tags for a source file.
func (c *suiteCollector) setBuildTags(sourceFile string, buildTags []string) {
c.srcFileBuildTags[sourceFile] = buildTags
}

// addTest adds a test method to a suite.
// If the suite doesn't have metadata set, it sets them.
func (c *suiteCollector) addTest(suiteName, packageName, sourceFile string, test TestMethodInfo) {
Expand All @@ -59,6 +67,10 @@ func (c *suiteCollector) addTest(suiteName, packageName, sourceFile string, test
if info.SourceFile == "" {
info.SourceFile = sourceFile
}
// Set build tags from the map if not already set
if info.BuildTags == nil {
info.BuildTags = c.srcFileBuildTags[sourceFile]
}
info.Tests = append(info.Tests, test)
c.suites[suiteName] = info
}
Expand Down Expand Up @@ -141,8 +153,12 @@ func (s *SuiteScanner) processFile(f *ast.File, sourceFile string) (bool, error)
return false, nil
}

// Extract package name from the AST file
// Extract package name and build tags from the AST file
packageName := f.Name.Name
buildTags := extractBuildTags(f)

// Store build tags for this source file
s.collector.setBuildTags(sourceFile, buildTags)

// Single-pass AST traversal: process both type declarations and method declarations in one iteration
for _, decl := range f.Decls {
Expand Down Expand Up @@ -313,3 +329,34 @@ func parseDependsOnComment(commentText string) []string {
}
return deps
}

// extractBuildTags extracts build constraint comments from an AST file.
// It looks for //go:build and // +build directives at the top of the file.
// These comments appear before the package declaration and must be preserved
// in generated files to maintain build constraints.
//
// Returns a slice of complete comment lines including the "//" prefix.
// Example output: ["//go:build test", "// +build test"]
func extractBuildTags(f *ast.File) []string {
var buildTags []string

// Build tags can appear in any comment group before the package declaration.
// Scan all comment groups that appear before the package keyword.
for _, commentGroup := range f.Comments {
// Stop if we've reached comments after the package declaration
if commentGroup.Pos() >= f.Package {
break
}

// Check each comment in the group for build tags
for _, comment := range commentGroup.List {
text := comment.Text
// Look for build constraint comments
if strings.HasPrefix(text, "//go:build") || strings.HasPrefix(text, "// +build") {
buildTags = append(buildTags, text)
}
}
}

return buildTags
}
Loading