| // Copyright 2026 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package gentools | |
| import ( | |
| "bytes" | |
| "fmt" | |
| "os" | |
| "path/filepath" | |
| "strings" | |
| "testing" | |
| ) | |
| func TestGoFileFormatting(t *testing.T) { | |
| t.Parallel() | |
| var outBuf bytes.Buffer | |
| var files Files | |
| files.Options = &Options{ | |
| Txtar: true, | |
| Output: &outBuf, | |
| } | |
| buf := files.NewGoFile("test.go") | |
| buf.WriteString("package test\nfunc Foo() int { return 42 }\n") | |
| err := files.Flush() | |
| if err != nil { | |
| t.Fatalf("Flush failed: %v", err) | |
| } | |
| outStr := outBuf.String() | |
| expected := "-- src/test.go --\npackage test\n\nfunc Foo() int { return 42 }\n" | |
| if outStr != expected { | |
| t.Errorf("got txtar output:\n%q\nwant:\n%q", outStr, expected) | |
| } | |
| } | |
| func TestFormattingError(t *testing.T) { | |
| t.Parallel() | |
| var errBuf bytes.Buffer | |
| var files Files | |
| files.Options = &Options{ | |
| ErrOutput: &errBuf, | |
| } | |
| buf := files.NewGoFile("bad.go") | |
| buf.WriteString("package test\n\tfunc Foo( {\n") // invalid syntax with tab indentation | |
| err := files.Flush() | |
| if err == nil { | |
| t.Fatalf("expected formatting error, got nil") | |
| } | |
| if !strings.Contains(err.Error(), "error formatting bad.go") { | |
| t.Errorf("unexpected error message: %v", err) | |
| } | |
| const expectedErrOutput = "package test\n\tfunc Foo( {\n\t ^\n2:12: expected ')', found '{'\n" | |
| if errBuf.String() != expectedErrOutput { | |
| t.Errorf("got error output:\n%q\nwant:\n%q", errBuf.String(), expectedErrOutput) | |
| } | |
| } | |
| func TestWriteMode(t *testing.T) { | |
| t.Parallel() | |
| tmpDir := t.TempDir() | |
| var files Files | |
| files.Options = &Options{ | |
| GOROOT: tmpDir, | |
| Write: true, | |
| } | |
| gobuf := files.NewGoFile("pkg/a.go") | |
| gobuf.WriteString("package pkg\nconst X = 1\n") | |
| const expectedRawContent = "raw content\n" | |
| rawbuf := files.NewRawFile("pkg/a.txt") | |
| rawbuf.WriteString(expectedRawContent) | |
| if err := files.Flush(); err != nil { | |
| t.Fatalf("Flush failed: %v", err) | |
| } | |
| aGo, err := os.ReadFile(filepath.Join(tmpDir, "src", "pkg", "a.go")) | |
| if err != nil { | |
| t.Fatalf("reading a.go: %v", err) | |
| } | |
| if string(aGo) != "package pkg\n\nconst X = 1\n" { | |
| t.Errorf("unexpected a.go content: %q", string(aGo)) | |
| } | |
| aTxt, err := os.ReadFile(filepath.Join(tmpDir, "src", "pkg", "a.txt")) | |
| if err != nil { | |
| t.Fatalf("reading a.txt: %v", err) | |
| } | |
| if string(aTxt) != expectedRawContent { | |
| t.Errorf("unexpected a.txt content: %q", string(aTxt)) | |
| } | |
| } | |
| func TestWriteAsideMode(t *testing.T) { | |
| t.Parallel() | |
| tmpDir := t.TempDir() | |
| tmpDir2 := t.TempDir() | |
| var files Files | |
| files.Options = &Options{ | |
| GOROOT: tmpDir, | |
| outDir: tmpDir2, | |
| Write: true, | |
| } | |
| gobuf := files.NewGoFile("pkg/a.go") | |
| gobuf.WriteString("package pkg\nconst X = 1\n") | |
| const expectedRawContent = "raw content\n" | |
| rawbuf := files.NewRawFile("pkg/a.txt") | |
| rawbuf.WriteString(expectedRawContent) | |
| if err := files.Flush(); err != nil { | |
| t.Fatalf("Flush failed: %v", err) | |
| } | |
| aGo, err := os.ReadFile(filepath.Join(tmpDir2, "src", "pkg", "a.go")) | |
| if err != nil { | |
| t.Fatalf("reading a.go: %v", err) | |
| } | |
| if string(aGo) != "package pkg\n\nconst X = 1\n" { | |
| t.Errorf("unexpected a.go content: %q", string(aGo)) | |
| } | |
| aTxt, err := os.ReadFile(filepath.Join(tmpDir2, "src", "pkg", "a.txt")) | |
| if err != nil { | |
| t.Fatalf("reading a.txt: %v", err) | |
| } | |
| if string(aTxt) != expectedRawContent { | |
| t.Errorf("unexpected a.txt content: %q", string(aTxt)) | |
| } | |
| } | |
| func TestWriteAsideRefusesOverwrite(t *testing.T) { | |
| t.Parallel() | |
| tmpDir := t.TempDir() | |
| outDir := t.TempDir() | |
| // Pre-create a file in the output directory | |
| existingFile := filepath.Join(outDir, "src", "pkg", "a.go") | |
| if err := os.MkdirAll(filepath.Dir(existingFile), 0755); err != nil { | |
| t.Fatal(err) | |
| } | |
| if err := os.WriteFile(existingFile, []byte("existing"), 0644); err != nil { | |
| t.Fatal(err) | |
| } | |
| var files Files | |
| files.Options = &Options{ | |
| GOROOT: tmpDir, | |
| outDir: outDir, | |
| Write: true, | |
| } | |
| defer func() { | |
| r := recover() | |
| if r == nil { | |
| t.Fatalf("expected panic when creating file that exists in output directory, got none") | |
| } | |
| msg, ok := r.(string) | |
| if !ok || !strings.Contains(msg, "file already exists in output directory") { | |
| t.Errorf("unexpected panic message: %v", r) | |
| } | |
| }() | |
| files.NewGoFile("pkg/a.go") | |
| } | |
| func TestDuplicateFileCreationPanics(t *testing.T) { | |
| t.Parallel() | |
| var files Files | |
| files.NewGoFile("pkg/a.go") | |
| defer func() { | |
| r := recover() | |
| if r == nil { | |
| t.Fatalf("expected panic on duplicate file creation, got none") | |
| } | |
| msg, ok := r.(string) | |
| if !ok || !strings.Contains(msg, "generator already created file") { | |
| t.Errorf("unexpected panic message: %v", r) | |
| } | |
| }() | |
| files.NewGoFile("pkg/a.go") | |
| } | |
| func TestDiffMode(t *testing.T) { | |
| t.Parallel() | |
| tmpDir := t.TempDir() | |
| targetFile := filepath.Join(tmpDir, "src", "pkg", "a.go") | |
| os.MkdirAll(filepath.Dir(targetFile), 0755) | |
| os.WriteFile(targetFile, []byte("package pkg\n\nconst X = 1\n"), 0644) | |
| // Test matching content | |
| var files1 Files | |
| files1.Options = &Options{ | |
| GOROOT: tmpDir, | |
| Diff: true, | |
| } | |
| buf1 := files1.NewGoFile("pkg/a.go") | |
| buf1.WriteString("package pkg\nconst X = 1\n") | |
| if err := files1.Flush(); err != nil { | |
| t.Errorf("expected no diff error, got: %v", err) | |
| } | |
| // Test non-matching content | |
| var outBuf bytes.Buffer | |
| var files2 Files | |
| files2.Options = &Options{ | |
| GOROOT: tmpDir, | |
| Diff: true, | |
| Output: &outBuf, | |
| } | |
| buf2 := files2.NewGoFile("pkg/a.go") | |
| buf2.WriteString("package pkg\nconst X = 2\n") | |
| err := files2.Flush() | |
| if err == nil { | |
| t.Errorf("expected diff error, got nil") | |
| } | |
| if !strings.Contains(outBuf.String(), "-const X = 1") || !strings.Contains(outBuf.String(), "+const X = 2") { | |
| t.Errorf("unexpected diff output:\n%s", outBuf.String()) | |
| } | |
| } | |
| func TestPostProcessor(t *testing.T) { | |
| t.Parallel() | |
| var outBuf bytes.Buffer | |
| var files Files | |
| files.Options = &Options{ | |
| Txtar: true, | |
| Output: &outBuf, | |
| } | |
| files.AddPostProcessor(func(relPath string, isGo bool, content []byte) ([]byte, error) { | |
| if !isGo { | |
| return append([]byte("# header\n"), content...), nil | |
| } | |
| // In Go files, replace Bar with Baz | |
| return bytes.ReplaceAll(content, []byte("Bar"), []byte("Baz")), nil | |
| }) | |
| goBuf := files.NewGoFile("test.go") | |
| goBuf.WriteString("package test\nfunc Bar() int { return 1 }\n") | |
| rawBuf := files.NewRawFile("test.txt") | |
| rawBuf.WriteString("hello\n") | |
| if err := files.Flush(); err != nil { | |
| t.Fatalf("Flush failed: %v", err) | |
| } | |
| outStr := outBuf.String() | |
| expected := "-- src/test.go --\npackage test\n\nfunc Baz() int { return 1 }\n\n-- src/test.txt --\n# header\nhello\n" | |
| if outStr != expected { | |
| t.Errorf("got txtar output:\n%q\nwant:\n%q", outStr, expected) | |
| } | |
| } | |
| func TestPostProcessorError(t *testing.T) { | |
| t.Parallel() | |
| var files Files | |
| files.Options = &Options{ | |
| Txtar: true, | |
| Output: &bytes.Buffer{}, | |
| } | |
| files.AddPostProcessor(func(relPath string, isGo bool, content []byte) ([]byte, error) { | |
| if relPath == "bad.go" { | |
| return nil, fmt.Errorf("custom hook error on %s", relPath) | |
| } | |
| return content, nil | |
| }) | |
| buf := files.NewGoFile("bad.go") | |
| buf.WriteString("package test\n") | |
| err := files.Flush() | |
| if err == nil { | |
| t.Fatalf("expected post-processor error, got nil") | |
| } | |
| if !strings.Contains(err.Error(), "custom hook error on bad.go") { | |
| t.Errorf("unexpected error: %v", err) | |
| } | |
| } |