Files
yq/pkg/yqlib/front_matter_test.go
cb7b707457 Close input files after processing each one (#2796) (#2808)
readStream opened an *os.File but returned a *bufio.Reader, so the
*os.File type switch in streamEvaluator.EvaluateFiles never matched and
no input file was ever closed. allAtOnceEvaluator.EvaluateFiles did not
attempt to close them at all. Descriptors were only released at process
exit or by the os.File finaliser, so a multi-file run could exhaust the
file descriptor limit.

readStream now returns an explicit cleanup function alongside the reader,
which both evaluators call once the file has been processed.

Co-authored-by: Pablo Garcia <pablito@MacBook-Neo-de-Pablo.local>
Co-authored-by: Mike Farah <mikefarah@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-19 14:17:53 +10:00

280 lines
5.5 KiB
Go

package yqlib
import (
"io"
"os"
"testing"
"github.com/mikefarah/yq/v4/test"
)
func createTestFile(content string) string {
tempFile, err := createTempFile()
if err != nil {
panic(err)
}
_, err = tempFile.Write([]byte(content))
if err != nil {
panic(err)
}
safelyCloseFile(tempFile)
return tempFile.Name()
}
func readFile(filename string) string {
bytes, err := os.ReadFile(filename)
if err != nil {
panic(err)
}
return string(bytes)
}
func TestFrontMatterSplitWithLeadingSep(t *testing.T) {
file := createTestFile(`---
a: apple
b: banana
---
not a
yaml: doc
`)
expectedYamlFm := `---
a: apple
b: banana
`
expectedContent := `---
not a
yaml: doc
`
fmHandler := NewFrontMatterHandler(file)
err := fmHandler.Split()
if err != nil {
panic(err)
}
yamlFm := readFile(fmHandler.GetYamlFrontMatterFilename())
test.AssertResult(t, expectedYamlFm, yamlFm)
contentBytes, err := io.ReadAll(fmHandler.GetContentReader())
if err != nil {
panic(err)
}
test.AssertResult(t, expectedContent, string(contentBytes))
tryRemoveTempFile(file)
fmHandler.CleanUp()
}
func TestFrontMatterSplitWithNoLeadingSep(t *testing.T) {
file := createTestFile(`a: apple
b: banana
---
not a
yaml: doc
`)
expectedYamlFm := `a: apple
b: banana
`
expectedContent := `---
not a
yaml: doc
`
fmHandler := NewFrontMatterHandler(file)
err := fmHandler.Split()
if err != nil {
panic(err)
}
yamlFm := readFile(fmHandler.GetYamlFrontMatterFilename())
test.AssertResult(t, expectedYamlFm, yamlFm)
contentBytes, err := io.ReadAll(fmHandler.GetContentReader())
if err != nil {
panic(err)
}
test.AssertResult(t, expectedContent, string(contentBytes))
tryRemoveTempFile(file)
fmHandler.CleanUp()
}
func TestFrontMatterFilenamePreserved(t *testing.T) {
// Regression test for https://github.com/mikefarah/yq/issues/2538
// When using --front-matter, the filename operator should return
// the original filename, not the path to the temporary file.
file := createTestFile(`---
name: john
---
Some content
`)
originalFilename := "/path/to/original/file.md"
fmHandler := NewFrontMatterHandler(file)
err := fmHandler.Split()
if err != nil {
panic(err)
}
tempFilename := fmHandler.GetYamlFrontMatterFilename()
// Register the alias (as the command code does)
SetFilenameAlias(tempFilename, originalFilename)
defer ClearFilenameAliases()
// Verify resolveFilename returns the original name
resolved := resolveFilename(tempFilename)
test.AssertResult(t, originalFilename, resolved)
// Read documents using the temp file, verify they get the original filename
reader, cleanup, err := readStream(tempFilename)
if err != nil {
panic(err)
}
defer cleanup()
decoder := NewYamlDecoder(ConfiguredYamlPreferences)
docs, err := readDocuments(reader, tempFilename, 0, decoder)
if err != nil {
panic(err)
}
if docs.Len() == 0 {
t.Fatal("expected at least one document")
}
firstDoc := docs.Front().Value.(*CandidateNode)
test.AssertResult(t, originalFilename, firstDoc.filename)
tryRemoveTempFile(file)
fmHandler.CleanUp()
}
func TestFrontMatterSplitWithBOM(t *testing.T) {
// Regression test for https://github.com/mikefarah/yq/issues/2496
// A UTF-8 BOM before the opening --- must be skipped, otherwise the
// separator isn't recognised and the opening --- is lost.
file := createTestFile("\ufeff---\na: apple\nb: banana\n---\nnot a\nyaml: doc\n")
expectedYamlFm := `---
a: apple
b: banana
`
expectedContent := `---
not a
yaml: doc
`
fmHandler := NewFrontMatterHandler(file)
err := fmHandler.Split()
if err != nil {
panic(err)
}
yamlFm := readFile(fmHandler.GetYamlFrontMatterFilename())
test.AssertResult(t, expectedYamlFm, yamlFm)
contentBytes, err := io.ReadAll(fmHandler.GetContentReader())
if err != nil {
panic(err)
}
test.AssertResult(t, expectedContent, string(contentBytes))
tryRemoveTempFile(file)
fmHandler.CleanUp()
}
func TestFrontMatterSplitWithBOMFromStdin(t *testing.T) {
// Regression test for https://github.com/mikefarah/yq/issues/2496
// A UTF-8 BOM must also be skipped when reading front matter from stdin.
originalStdin := os.Stdin
defer func() { os.Stdin = originalStdin }()
r, w, err := os.Pipe()
if err != nil {
panic(err)
}
os.Stdin = r
defer safelyCloseFile(r)
go func() {
_, writeErr := w.WriteString("\ufeff---\na: apple\nb: banana\n---\nnot a\nyaml: doc\n")
if writeErr != nil {
t.Errorf("failed to write front matter to the stdin pipe: %v", writeErr)
}
safelyCloseFile(w)
}()
expectedYamlFm := `---
a: apple
b: banana
`
expectedContent := `---
not a
yaml: doc
`
fmHandler := NewFrontMatterHandler("-")
err = fmHandler.Split()
if err != nil {
panic(err)
}
yamlFm := readFile(fmHandler.GetYamlFrontMatterFilename())
test.AssertResult(t, expectedYamlFm, yamlFm)
contentBytes, err := io.ReadAll(fmHandler.GetContentReader())
if err != nil {
panic(err)
}
test.AssertResult(t, expectedContent, string(contentBytes))
fmHandler.CleanUp()
}
func TestFrontMatterSplitWithArray(t *testing.T) {
file := createTestFile(`[1,2,3]
---
not a
yaml: doc
`)
expectedYamlFm := "[1,2,3]\n"
expectedContent := `---
not a
yaml: doc
`
fmHandler := NewFrontMatterHandler(file)
err := fmHandler.Split()
if err != nil {
panic(err)
}
yamlFm := readFile(fmHandler.GetYamlFrontMatterFilename())
test.AssertResult(t, expectedYamlFm, yamlFm)
contentBytes, err := io.ReadAll(fmHandler.GetContentReader())
if err != nil {
panic(err)
}
test.AssertResult(t, expectedContent, string(contentBytes))
tryRemoveTempFile(file)
fmHandler.CleanUp()
}