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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.1.1] — Unreleased

### Added
- **dependgen**: Human-readable dependency documentation in generated files ([#3](https://github.com/srgg/testify/issues/3))

### 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
Expand Down
153 changes: 151 additions & 2 deletions depend/cmd/dependgen/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,149 @@ import (
"fmt"
"os"
"path/filepath"
"runtime/debug"
"strings"
"text/template"
)

// getVersion returns the module version from build info, or "(devel)" if unavailable.
// This matches the behavior of standard Go tools like stringer.
func getVersion() string {
info, ok := debug.ReadBuildInfo()
if !ok {
return "(devel)"
}
if info.Main.Version != "" && info.Main.Version != "(devel)" {
return info.Main.Version
}
return "(devel)"
}

// formatSuiteDocs generates human-readable documentation for a single suite.
func formatSuiteDocs(suite SuiteInfo) string {
var buf strings.Builder

fmt.Fprintf(&buf, "// • %s (%s)\n", suite.Name, suite.SourceFile)

// Check if any tests have dependencies
hasDependencies := false
for _, test := range suite.Tests {
if len(test.DependsOn) > 0 {
hasDependencies = true
break
}
}

if !hasDependencies {
buf.WriteString("// Dependencies: none\n")
} else {
buf.WriteString("// Dependencies:\n")
// Only list tests that have dependencies
for _, test := range suite.Tests {
if len(test.DependsOn) > 0 {
deps := strings.Join(test.DependsOn, ", ")
line := fmt.Sprintf("// %s: %s", test.Name, deps)
buf.WriteString(wrapLine(line, 80, "// "))
buf.WriteString("\n")
}
}
}

// Calculate and display execution order
order := calculateExecutionOrder(suite)
orderLine := "// Order: " + order
buf.WriteString(wrapLine(orderLine, 80, "// "))
buf.WriteString("\n")

return buf.String()
}

// wrapLine wraps a line at maxLen characters, using the continuation prefix for subsequent lines.
func wrapLine(line string, maxLen int, continuationPrefix string) string {
if len(line) <= maxLen {
return line
}

var result strings.Builder
remaining := line

for len(remaining) > maxLen {
breakPoint := maxLen

// Find a good break point (space, comma, or arrow)
foundBreak := false
for i := breakPoint; i > 0 && i < len(remaining); i-- {
if remaining[i] == ' ' || remaining[i] == ',' {
breakPoint = i
if remaining[i] == ',' {
breakPoint++ // Include the comma on this line
}
foundBreak = true
break
}
}

if !foundBreak {
// No good break point found, break at maxLen
breakPoint = maxLen
}

result.WriteString(remaining[:breakPoint])
result.WriteString("\n")
remaining = strings.TrimSpace(remaining[breakPoint:])

if len(remaining) > 0 {
result.WriteString(continuationPrefix)
}
}

if len(remaining) > 0 {
result.WriteString(remaining)
}

return result.String()
}

// calculateExecutionOrder determines the test execution order based on dependencies.
// Returns a string representation using arrows (→) to show the sequence.
func calculateExecutionOrder(suite SuiteInfo) string {
if len(suite.Tests) == 0 {
return ""
}

// Build dependency map
deps := make(map[string][]string)
for _, test := range suite.Tests {
deps[test.Name] = test.DependsOn
}

// Topological sort to get execution order
var order []string
visited := make(map[string]struct{})

var visit func(string)
visit = func(testName string) {
if _, exists := visited[testName]; exists {
return
}
visited[testName] = struct{}{}

// Visit dependencies first
for _, dep := range deps[testName] {
visit(dep)
}

order = append(order, testName)
}

// Visit all tests
for _, test := range suite.Tests {
visit(test.Name)
}

return strings.Join(order, " → ")
}

// Generator handles code generation for test suites.
// It encapsulates the template and configuration needed to generate
// dependency configuration code for testify/depend test suites.
Expand All @@ -20,10 +159,20 @@ type Generator struct {
// NewGenerator creates a new code generator with the given configuration.
// The template is parsed at creation time and is safe for concurrent use.
func NewGenerator(cfg *Config) *Generator {
tpl := template.Must(template.New("reg").Parse(`{{- range .BuildTags}}
// Register template helper functions
funcMap := template.FuncMap{
"version": getVersion,
"suiteDocs": formatSuiteDocs,
}

tpl := template.Must(template.New("reg").Funcs(funcMap).Parse(`{{- range .BuildTags}}
{{.}}
{{end -}}
// Code generated by dependgen — DO NOT EDIT.
// Code generated by testify/dependgen {{version}}. DO NOT EDIT.
//
{{range $i, $suite := .Suites -}}
{{if $i}}//
{{end}}{{suiteDocs $suite}}{{end}}
package {{.Package}}

import "github.com/srgg/testify/depend"
Expand Down
34 changes: 30 additions & 4 deletions depend/cmd/dependgen/generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,14 @@ func (s *GeneratorSuite) TestGenerateFile() {
},
},
expectedFilename: "my_depend_test.go",
expectedContent: `// Code generated by dependgen — DO NOT EDIT.
expectedContent: `// Code generated by testify/dependgen (devel). DO NOT EDIT.
//
// • MySuite (my_test.go)
// Dependencies:
// TestB: TestA
// TestC: TestA, TestB
// Order: TestA → TestB → TestC

package testpkg

import "github.com/srgg/testify/depend"
Expand Down Expand Up @@ -147,7 +154,12 @@ func (s *MySuite) GeneratedDependConfig() *depend.SuiteConfig {
},
},
expectedFilename: "simple_depend_test.go",
expectedContent: `// Code generated by dependgen — DO NOT EDIT.
expectedContent: `// Code generated by testify/dependgen (devel). DO NOT EDIT.
//
// • SimpleSuite (simple_test.go)
// Dependencies: none
// Order: TestA → TestB

package testpkg

import "github.com/srgg/testify/depend"
Expand Down Expand Up @@ -193,7 +205,12 @@ func (s *SimpleSuite) GeneratedDependConfig() *depend.SuiteConfig {
},
},
expectedFilename: "http_handler_depend_test.go",
expectedContent: `// Code generated by dependgen — DO NOT EDIT.
expectedContent: `// Code generated by testify/dependgen (devel). DO NOT EDIT.
//
// • HTTPHandlerSuite (http_handler_test.go)
// Dependencies: none
// Order: TestSetup

package testpkg

import "github.com/srgg/testify/depend"
Expand Down Expand Up @@ -245,7 +262,16 @@ func (s *HTTPHandlerSuite) GeneratedDependConfig() *depend.SuiteConfig {
},
},
expectedFilename: "multi_depend_test.go",
expectedContent: `// Code generated by dependgen — DO NOT EDIT.
expectedContent: `// Code generated by testify/dependgen (devel). DO NOT EDIT.
//
// • FirstSuite (multi_test.go)
// Dependencies: none
// Order: TestA
//
// • SecondSuite (multi_test.go)
// Dependencies: none
// Order: TestX

package testpkg

import "github.com/srgg/testify/depend"
Expand Down
10 changes: 7 additions & 3 deletions depend/cmd/dependgen/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,11 @@ func (s *IntegrationSuite) TestAutoDetectSingleSuite() {
s.Assert().NoError(err, "MUST generate successfully for single suite")

s.assertGeneratedFile(testDir, "suite_depend_test.go",
"Code generated by dependgen",
"Code generated by testify/dependgen",
"DO NOT EDIT",
"• MySuite (suite_test.go)",
"Dependencies:",
"Order:",
"MySuiteTestRegistry",
"TestA",
"TestB")
Expand Down Expand Up @@ -610,11 +614,11 @@ func TestMySuite(t *testing.T) {
generatedCode := s.assertGeneratedFile(testDir, "suite_depend_test.go",
"//go:build test",
"// +build test",
"Code generated by dependgen",
"Code generated by testify/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,
s.Assert().Regexp(`(?s)//go:build test\s+// \+build test\s+// Code generated by testify/dependgen`, generatedCode,
"MUST have build tags before generated code comment")

// Copyright should NOT be copied to generated file
Expand Down
Loading