Skip to content

Commit 332fbf4

Browse files
cweillclaude
andauthored
test: improve code coverage from 28.1% to 84.6% (#196)
Add comprehensive unit tests across all packages, with special focus on previously untested generics code in render helpers. This brings overall coverage from 28.1% to 84.6% (201% increase). ## New Test Files - **internal/render/helpers_test.go** (650+ lines) - Complete coverage of all 11 helper functions (93-100%) - Comprehensive generics tests: type arguments, constraint mapping - Tests for all constraint types: any, comparable, unions, approximations - Numeric constraint tests: constraints.Integer, Ordered, etc. - Type parameter substitution in fields and receivers - **internal/models/models_test.go** (700+ lines) - 17 test functions covering all model methods - Expression, Field, Function, Receiver, Path tests - Coverage: 0% → 98.0% - **internal/goparser/goparser_test.go** (150+ lines) - Parser tests with generics support - Import handling, error cases - Coverage: 0% → 73.3% - **internal/render/render_test.go** (215+ lines) - Template rendering and loading tests - Coverage: 0% → 96.4% - **internal/output/helpers_test.go** - File existence helper tests - Coverage improvement: 8.3% → 69.4% ## Enhanced Test Files - **internal/output/options_test.go** - Added Process() method tests with various configurations - Tests for subtests, print inputs, go-cmp options - **gotests/process/process_test.go** - Added comprehensive option tests - Template parameter tests, JSON validation - Coverage: 60.7% → 78.7% ## Coverage Results by Package | Package | Before | After | Improvement | |---------|--------|-------|-------------| | internal/render (helpers) | 0% | 93-100% | ✅ ALL generics | | internal/models | 0% | 98.0% | ✅ | | internal/goparser | 0% | 73.3% | ✅ | | internal/render | 0% | 96.4% | ✅ | | internal/output | 8.3% | 69.4% | ✅ | | gotests/process | 60.7% | 78.7% | ✅ | | **Overall** | **28.1%** | **84.6%** | **+201%** | ## Key Achievement All generics support code (critical for Go 1.18+) now has comprehensive test coverage, ensuring constraint-to-type mappings work correctly for: - Basic constraints (any, comparable) - Union types (int | float64) - Approximation types (~int) - Numeric constraints (constraints.Integer, etc.) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <[email protected]>
1 parent 97f0e5f commit 332fbf4

File tree

7 files changed

+2254
-1
lines changed

7 files changed

+2254
-1
lines changed

gotests/process/process_test.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package process
22

33
import (
44
"bytes"
5+
"os"
6+
"path/filepath"
57
"testing"
68
)
79

@@ -58,3 +60,123 @@ func TestRun(t *testing.T) {
5860
}
5961
}
6062
}
63+
64+
func TestRun_WithValidFile(t *testing.T) {
65+
tmpDir := t.TempDir()
66+
srcPath := filepath.Join(tmpDir, "test.go")
67+
content := `package mypackage
68+
69+
func Add(a, b int) int {
70+
return a + b
71+
}
72+
`
73+
if err := os.WriteFile(srcPath, []byte(content), 0644); err != nil {
74+
t.Fatal(err)
75+
}
76+
77+
tests := []struct {
78+
name string
79+
opts *Options
80+
}{
81+
{
82+
name: "all funcs",
83+
opts: &Options{AllFuncs: true},
84+
},
85+
{
86+
name: "exported funcs",
87+
opts: &Options{ExportedFuncs: true},
88+
},
89+
{
90+
name: "with only regex",
91+
opts: &Options{OnlyFuncs: "Add"},
92+
},
93+
{
94+
name: "with excl regex",
95+
opts: &Options{AllFuncs: true, ExclFuncs: "Subtract"},
96+
},
97+
{
98+
name: "with print inputs",
99+
opts: &Options{AllFuncs: true, PrintInputs: true},
100+
},
101+
{
102+
name: "with subtests",
103+
opts: &Options{AllFuncs: true, Subtests: true},
104+
},
105+
{
106+
name: "with parallel",
107+
opts: &Options{AllFuncs: true, Parallel: true},
108+
},
109+
{
110+
name: "with named",
111+
opts: &Options{AllFuncs: true, Named: true},
112+
},
113+
{
114+
name: "with go-cmp",
115+
opts: &Options{AllFuncs: true, UseGoCmp: true},
116+
},
117+
}
118+
119+
for _, tt := range tests {
120+
t.Run(tt.name, func(t *testing.T) {
121+
out := &bytes.Buffer{}
122+
Run(out, []string{srcPath}, tt.opts)
123+
output := out.String()
124+
if output == "" {
125+
t.Error("Run() produced no output")
126+
}
127+
})
128+
}
129+
}
130+
131+
func TestRun_WithTemplateParams(t *testing.T) {
132+
tmpDir := t.TempDir()
133+
srcPath := filepath.Join(tmpDir, "test.go")
134+
content := `package mypackage
135+
136+
func Greet(name string) string {
137+
return "Hello, " + name
138+
}
139+
`
140+
if err := os.WriteFile(srcPath, []byte(content), 0644); err != nil {
141+
t.Fatal(err)
142+
}
143+
144+
jsonParams := `{"customParam": "value"}`
145+
opts := &Options{
146+
AllFuncs: true,
147+
TemplateParams: jsonParams,
148+
}
149+
150+
out := &bytes.Buffer{}
151+
Run(out, []string{srcPath}, opts)
152+
output := out.String()
153+
if output == "" {
154+
t.Error("Run() with template params produced no output")
155+
}
156+
}
157+
158+
func TestRun_InvalidTemplateParams(t *testing.T) {
159+
tmpDir := t.TempDir()
160+
srcPath := filepath.Join(tmpDir, "test.go")
161+
content := `package mypackage
162+
163+
func Test() {}
164+
`
165+
if err := os.WriteFile(srcPath, []byte(content), 0644); err != nil {
166+
t.Fatal(err)
167+
}
168+
169+
opts := &Options{
170+
AllFuncs: true,
171+
TemplateParams: `{invalid json`,
172+
}
173+
174+
out := &bytes.Buffer{}
175+
Run(out, []string{srcPath}, opts)
176+
output := out.String()
177+
178+
// Should produce an error message about invalid JSON
179+
if output == "" {
180+
t.Error("Run() should produce error message for invalid JSON")
181+
}
182+
}

internal/goparser/goparser_test.go

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
package goparser
2+
3+
import (
4+
"go/importer"
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
9+
"github.com/cweill/gotests/internal/models"
10+
)
11+
12+
func TestParser_Parse(t *testing.T) {
13+
tests := []struct {
14+
name string
15+
setupFn func(t *testing.T) (string, []models.Path)
16+
wantErr bool
17+
wantFuncs int
18+
wantPkg string
19+
}{
20+
{
21+
name: "simple function",
22+
setupFn: func(t *testing.T) (string, []models.Path) {
23+
tmpDir := t.TempDir()
24+
srcPath := filepath.Join(tmpDir, "test.go")
25+
content := `package mypackage
26+
27+
func Add(a, b int) int {
28+
return a + b
29+
}
30+
`
31+
if err := os.WriteFile(srcPath, []byte(content), 0644); err != nil {
32+
t.Fatal(err)
33+
}
34+
return srcPath, nil
35+
},
36+
wantErr: false,
37+
wantFuncs: 1,
38+
wantPkg: "mypackage",
39+
},
40+
{
41+
name: "multiple functions",
42+
setupFn: func(t *testing.T) (string, []models.Path) {
43+
tmpDir := t.TempDir()
44+
srcPath := filepath.Join(tmpDir, "test.go")
45+
content := `package calc
46+
47+
func Add(a, b int) int {
48+
return a + b
49+
}
50+
51+
func Subtract(a, b int) int {
52+
return a - b
53+
}
54+
55+
func Multiply(a, b int) int {
56+
return a * b
57+
}
58+
`
59+
if err := os.WriteFile(srcPath, []byte(content), 0644); err != nil {
60+
t.Fatal(err)
61+
}
62+
return srcPath, nil
63+
},
64+
wantErr: false,
65+
wantFuncs: 3,
66+
wantPkg: "calc",
67+
},
68+
{
69+
name: "function with method receiver",
70+
setupFn: func(t *testing.T) (string, []models.Path) {
71+
tmpDir := t.TempDir()
72+
srcPath := filepath.Join(tmpDir, "test.go")
73+
content := `package service
74+
75+
type Handler struct {
76+
name string
77+
}
78+
79+
func (h *Handler) Process() error {
80+
return nil
81+
}
82+
83+
func NewHandler() *Handler {
84+
return &Handler{}
85+
}
86+
`
87+
if err := os.WriteFile(srcPath, []byte(content), 0644); err != nil {
88+
t.Fatal(err)
89+
}
90+
return srcPath, nil
91+
},
92+
wantErr: false,
93+
wantFuncs: 2,
94+
wantPkg: "service",
95+
},
96+
{
97+
name: "empty file returns error",
98+
setupFn: func(t *testing.T) (string, []models.Path) {
99+
tmpDir := t.TempDir()
100+
srcPath := filepath.Join(tmpDir, "empty.go")
101+
if err := os.WriteFile(srcPath, []byte(""), 0644); err != nil {
102+
t.Fatal(err)
103+
}
104+
return srcPath, nil
105+
},
106+
wantErr: true,
107+
},
108+
{
109+
name: "nonexistent file returns error",
110+
setupFn: func(t *testing.T) (string, []models.Path) {
111+
tmpDir := t.TempDir()
112+
return filepath.Join(tmpDir, "nonexistent.go"), nil
113+
},
114+
wantErr: true,
115+
},
116+
{
117+
name: "generic function",
118+
setupFn: func(t *testing.T) (string, []models.Path) {
119+
tmpDir := t.TempDir()
120+
srcPath := filepath.Join(tmpDir, "test.go")
121+
content := `package generic
122+
123+
func Transform[T, U any](input []T, fn func(T) U) []U {
124+
result := make([]U, len(input))
125+
for i, v := range input {
126+
result[i] = fn(v)
127+
}
128+
return result
129+
}
130+
`
131+
if err := os.WriteFile(srcPath, []byte(content), 0644); err != nil {
132+
t.Fatal(err)
133+
}
134+
return srcPath, nil
135+
},
136+
wantErr: false,
137+
wantFuncs: 1,
138+
wantPkg: "generic",
139+
},
140+
}
141+
142+
for _, tt := range tests {
143+
t.Run(tt.name, func(t *testing.T) {
144+
srcPath, files := tt.setupFn(t)
145+
p := &Parser{
146+
Importer: importer.Default(),
147+
}
148+
149+
result, err := p.Parse(srcPath, files)
150+
151+
if (err != nil) != tt.wantErr {
152+
t.Errorf("Parser.Parse() error = %v, wantErr %v", err, tt.wantErr)
153+
return
154+
}
155+
156+
if tt.wantErr {
157+
return
158+
}
159+
160+
if result == nil {
161+
t.Fatal("Parser.Parse() returned nil result")
162+
}
163+
164+
if result.Header == nil {
165+
t.Fatal("Parser.Parse() returned nil header")
166+
}
167+
168+
if result.Header.Package != tt.wantPkg {
169+
t.Errorf("Parser.Parse() package = %v, want %v", result.Header.Package, tt.wantPkg)
170+
}
171+
172+
if len(result.Funcs) != tt.wantFuncs {
173+
t.Errorf("Parser.Parse() returned %d functions, want %d", len(result.Funcs), tt.wantFuncs)
174+
}
175+
})
176+
}
177+
}
178+
179+
func TestParser_Parse_WithImports(t *testing.T) {
180+
tmpDir := t.TempDir()
181+
srcPath := filepath.Join(tmpDir, "test.go")
182+
content := `package mypackage
183+
184+
import (
185+
"fmt"
186+
"strings"
187+
)
188+
189+
func Greet(name string) string {
190+
return fmt.Sprintf("Hello, %s", strings.ToUpper(name))
191+
}
192+
`
193+
if err := os.WriteFile(srcPath, []byte(content), 0644); err != nil {
194+
t.Fatal(err)
195+
}
196+
197+
p := &Parser{
198+
Importer: importer.Default(),
199+
}
200+
201+
result, err := p.Parse(srcPath, nil)
202+
if err != nil {
203+
t.Fatalf("Parser.Parse() error = %v", err)
204+
}
205+
206+
if len(result.Header.Imports) < 2 {
207+
t.Errorf("Parser.Parse() returned %d imports, want at least 2", len(result.Header.Imports))
208+
}
209+
210+
// Check that imports were parsed
211+
hasImport := func(path string) bool {
212+
for _, imp := range result.Header.Imports {
213+
if imp.Path == `"`+path+`"` {
214+
return true
215+
}
216+
}
217+
return false
218+
}
219+
220+
if !hasImport("fmt") {
221+
t.Error("Parser.Parse() did not find fmt import")
222+
}
223+
if !hasImport("strings") {
224+
t.Error("Parser.Parse() did not find strings import")
225+
}
226+
}
227+
228+
func TestErrEmptyFile(t *testing.T) {
229+
if ErrEmptyFile == nil {
230+
t.Error("ErrEmptyFile should not be nil")
231+
}
232+
233+
if ErrEmptyFile.Error() != "file is empty" {
234+
t.Errorf("ErrEmptyFile.Error() = %q, want %q", ErrEmptyFile.Error(), "file is empty")
235+
}
236+
}

0 commit comments

Comments
 (0)