Skip to content
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/.cache/
/.claude/
/yts/testdata/
/go-yaml
5 changes: 3 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,11 @@ foo: &a1 bar
- Fork and clone the repository
- Make your changes
- Run tests, linters and formatters
- `make test-all`
- `make lint`
- `make fmt`
- `make tidy`
- `make lint`
- `make test`
- You can use `make check` to run all of the above
- Submit a [Pull Request](https://github.com/yaml/go-yaml/pulls)


Expand Down
25 changes: 16 additions & 9 deletions GNUmakefile
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ MAKES-CLEAN := $(CLI-BINARY)
MAKES-REALCLEAN := $(dir $(YTS-DIR))

# Setup and include go.mk and shell.mk:
GO-FILES := $(shell find -not \( -path ./.cache -prune \) -name '*.go' | sort)
GO-CMDS-SKIP := test fmt vet
ifndef GO-VERSION-NEEDED
GO-NO-DEP-GO := true
Expand Down Expand Up @@ -58,19 +57,25 @@ SHELL-NAME := makes go-yaml
include $(MAKES)/clean.mk
include $(MAKES)/shell.mk

MAKES-CLEAN := $(dir $(YTS-DIR)) $(GOLANGCI-LINT)
MAKES-CLEAN += $(GOLANGCI-LINT)

MAKE := $(MAKE) --no-print-directory

v ?=
count ?= 1


# Test rules:
test: $(GO-DEPS)
go test$(if $v, -v) -vet=off ./...
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are removing the vet=off we just added 😁 also the ./...

check:
$(MAKE) fmt
$(MAKE) tidy
$(MAKE) lint
$(MAKE) test

test-data: $(YTS-DIR)
test: test-unit test-cli test-yts-all

test-all: test test-yts-all
test-unit: $(GO-DEPS)
go test$(if $v, -v)

test-yts: $(GO-DEPS) $(YTS-DIR)
go test$(if $v, -v) ./yts -count=$(count)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
go test$(if $v, -v) ./yts -count=$(count)
go test$(if $v, -v) ./yts/... -count=$(count)

Expand All @@ -83,12 +88,14 @@ test-yts-fail: $(GO-DEPS) $(YTS-DIR)
@echo 'Testing yaml-test-suite failures'
@RUNFAILING=1 bash -c "$$yts_pass_fail"

test-cli: $(GO-DEPS) cli
go test$(if $v, -v) ./cmd/go-yaml -count=$(count)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
go test$(if $v, -v) ./cmd/go-yaml -count=$(count)
go test$(if $v, -v) ./cmd/go-yaml/... -count=$(count)


get-test-data: $(YTS-DIR)

# Install golangci-lint for GitHub Actions:
golangci-lint-install: $(GOLANGCI-LINT)

fmt: $(GOLANGCI-LINT-VERSIONED)
$< fmt ./...

Comment on lines -89 to -91
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no reason to remove make fmt

lint: $(GOLANGCI-LINT-VERSIONED)
$< run ./...

Expand Down
193 changes: 193 additions & 0 deletions cmd/go-yaml/cli_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
package main

import (
"bytes"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"

"go.yaml.in/yaml/v4"
)

// TestCase represents a single test case from a test file
type TestCase struct {
Name string `yaml:"name"`
Text string `yaml:"text"`
Token string `yaml:"token,omitempty"`
TOKEN string `yaml:"TOKEN,omitempty"`
Event string `yaml:"event,omitempty"`
EVENT string `yaml:"EVENT,omitempty"`
Node string `yaml:"node,omitempty"`
NODE string `yaml:"NODE,omitempty"`
Yaml string `yaml:"yaml,omitempty"`
YAML string `yaml:"YAML,omitempty"`
Json string `yaml:"json,omitempty"`
JSON string `yaml:"JSON,omitempty"`
}

// TestSuite is a sequence of test cases
type TestSuite []TestCase

// flagMapping maps test file field names to CLI flags
var flagMapping = map[string]string{
"token": "-t",
"TOKEN": "-T",
"event": "-e",
"EVENT": "-E",
"node": "-n",
"NODE": "-N",
"yaml": "-y",
"YAML": "-Y",
"json": "-j",
"JSON": "-J",
}

func TestCLI(t *testing.T) {
// Find all test files in testdata/
testFiles, err := filepath.Glob("testdata/*.yaml")
if err != nil {
t.Fatalf("Failed to find test files: %v", err)
}

if len(testFiles) == 0 {
t.Skip("No test files found in testdata/")
}

// Build the CLI binary if it doesn't exist
binaryPath := "../../go-yaml"
if _, err := os.Stat(binaryPath); os.IsNotExist(err) {
t.Logf("Building go-yaml binary...")
cmd := exec.Command("go", "build", "-o", binaryPath, ".")
if output, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("Failed to build go-yaml: %v\n%s", err, output)
}
}
Comment on lines +58 to +66
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not logic to build it in Go.

go run can be used

Also, code should be written in a way go test could be enough to emulate everything

The usual and idiomatic way in Go is to use os.ExecPipe and to play with it to pass data to Stdin and read result of Stdout

Using exec and string.Buffer is not the way to run and test go code. It would be of the binary launched was a blackbox binary

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for @SVilgelm


// Process each test file
for _, testFile := range testFiles {
testFileName := filepath.Base(testFile)
t.Run(testFileName, func(t *testing.T) {
runTestFile(t, testFile, binaryPath)
})
}
}

func runTestFile(t *testing.T, testFile, binaryPath string) {
// Read and parse the test file
data, err := os.ReadFile(testFile)
if err != nil {
t.Fatalf("Failed to read test file %s: %v", testFile, err)
}

var suite TestSuite
if err := yaml.Unmarshal(data, &suite); err != nil {
t.Fatalf("Failed to parse test file %s: %v", testFile, err)
}

// Run each test case
for _, testCase := range suite {
t.Run(testCase.Name, func(t *testing.T) {
runTestCase(t, testCase, binaryPath)
})
}
}

func runTestCase(t *testing.T, tc TestCase, binaryPath string) {
// Test each output format that has an expected value
tests := []struct {
field string
flag string
expected string
}{
{"token", flagMapping["token"], tc.Token},
{"TOKEN", flagMapping["TOKEN"], tc.TOKEN},
{"event", flagMapping["event"], tc.Event},
{"EVENT", flagMapping["EVENT"], tc.EVENT},
{"node", flagMapping["node"], tc.Node},
{"NODE", flagMapping["NODE"], tc.NODE},
{"yaml", flagMapping["yaml"], tc.Yaml},
{"YAML", flagMapping["YAML"], tc.YAML},
{"json", flagMapping["json"], tc.Json},
{"JSON", flagMapping["JSON"], tc.JSON},
}

for _, test := range tests {
if test.expected == "" {
continue // Skip if no expected output for this format
}

t.Run(test.field, func(t *testing.T) {
// Run the CLI command
cmd := exec.Command(binaryPath, test.flag)
cmd.Stdin = strings.NewReader(tc.Text)

var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr

if err := cmd.Run(); err != nil {
t.Fatalf("Command failed: %v\nStderr: %s", err, stderr.String())
}

// Normalize output for comparison
actual := normalizeOutput(stdout.String())
expected := normalizeOutput(test.expected)

if actual != expected {
t.Errorf("Output mismatch for flag %s\nExpected:\n%s\n\nActual:\n%s\n\nDiff:\n%s",
test.flag, expected, actual, diff(expected, actual))
}
})
}
}

// normalizeOutput trims whitespace and ensures consistent line endings
func normalizeOutput(s string) string {
s = strings.TrimSpace(s)
s = strings.ReplaceAll(s, "\r\n", "\n")
return s
}

// diff provides a simple diff output for debugging
func diff(expected, actual string) string {
expLines := strings.Split(expected, "\n")
actLines := strings.Split(actual, "\n")

maxLines := len(expLines)
if len(actLines) > maxLines {
maxLines = len(actLines)
}

var result strings.Builder
for i := 0; i < maxLines; i++ {
expLine := ""
actLine := ""

if i < len(expLines) {
expLine = expLines[i]
}
if i < len(actLines) {
actLine = actLines[i]
}

if expLine != actLine {
result.WriteString("Line ")
result.WriteString(strings.Repeat(" ", len(strings.TrimSpace(expLine))+1))
result.WriteString("\n")
if expLine != "" {
result.WriteString("- ")
result.WriteString(expLine)
result.WriteString("\n")
}
if actLine != "" {
result.WriteString("+ ")
result.WriteString(actLine)
result.WriteString("\n")
}
}
}

return result.String()
}
Loading
Loading