blob: 8e18832bf036ce1a4c0edfe3191050ae90f0dc51 [file]
// 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)
}
}