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
82 changes: 72 additions & 10 deletions golden/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,25 @@ import (

const goldenExtension = ".golden"

// NewConfig creates a new Config with default values.
func NewConfig() Config {
return Config{
GoldenExtension: goldenExtension,
}
}

// NewScriptConfig creates a new ScriptConfig with default values.
func NewScriptConfig() ScriptConfig {
return ScriptConfig{
DisplayStdout: true,
DisplayStderr: true,
ScriptExtensions: []ScriptExtension{
{Extension: ".sh", Command: "bash"},
},
GoldenExtension: goldenExtension,
}
}

// Config lets a user configure the golden file tests.
type Config struct {
// VerifyFunc is used to validate output against input, if provided.
Expand Down Expand Up @@ -65,32 +84,71 @@ type Config struct {
ExecutionConfig *ExecutionConfig
}

// BashConfig defines the configuration for a golden bash test.
type BashConfig struct {
// ScriptConfig defines the configuration for a golden script test.
type ScriptConfig struct {
// DisplayStdout indicates whether to display or suppress stdout.
DisplayStdout bool
// DisplayStderr indicates whether to display or suppress stderr.
DisplayStderr bool
// OutputProcessConfig defines how to process the output before comparison.
OutputProcessConfig OutputProcessConfig
// ScriptExtensions is a list of script file extensions to be considered for
// golden file tests alongside their respective command to execute them.
// A typical definition for bash scripts looks like this:
// ScriptExtensions: []ScriptExtension{
// {Extension: ".sh", Command: "bash"},
// }
ScriptExtensions []ScriptExtension
// GoldenExtension is the file extension to use for the golden file. If not
// provided, then the default extension (.golden) is used.
GoldenExtension string
// Envs specifies the environment variables to set for execution.
Envs [][2]string
// PostProcessFunctions defines a list of functions to be executed after the bash
// script has been run. This can be used to make use of the output of the bash script
// and perform additional operations on it. The functions are executed in the order
// they are defined and are not used for comparison.
// PostProcessFunctions defines a list of functions to be executed after the
// script has been run. This can be used to make use of the output of the
// script and perform additional operations on it. The functions are
// executed in the order they are defined and are not used for comparison.
PostProcessFunctions []func(goldenFile string) error
// WorkingDir is the directory where the bash script(s) will be
// executed.
// WorkingDir is the directory where the script(s) will be executed.
WorkingDir string
// WaitBefore adds a delay before running the bash script. This is useful
// when throttling is needed, e.g., when dealing with rate limiting.
// WaitBefore adds a delay before running the script. This is useful when
// throttling is needed, e.g., when dealing with rate limiting.
WaitBefore time.Duration
}

// NewLineStyle defines the style of new lines to be used on the output before
// comparison.
type NewLineStyle string

const (
// NewLineStyleUntouched indicates that the output should be kept untouched
// and not modified.
NewLineStyleUntouched NewLineStyle = ""
// NewLineStyleLF indicates that the output should be converted to LF (Line
// Feed) before comparison. This is the default style used in Unix-like
// systems.
NewLineStyleLF NewLineStyle = "LF"
// NewLineStyleCRLF indicates that the output should be converted to CRLF
// (Carriage Return + Line Feed) before comparison. This is the default
// style used in Windows systems.
NewLineStyleCRLF NewLineStyle = "CRLF"
)

// ScriptExtension defines a script file extension and the command to execute
// it. This is used in the ScriptConfig to define which scripts should be
// considered for golden file tests and how to execute them.
type ScriptExtension struct {
// Extension is the file extension of the script, e.g., ".sh".
Extension string
// Command is the command to execute the script, e.g., "bash".
Command string
// PrefixArgs are the arguments to be passed to the command before the
// script file name. This is useful for commands that require additional
// arguments, e.g., "powershell" requires "-File" before the script file
// name.
PrefixArgs []string
}

// TransientField represents a field that is transient, this is, dynamic in
// nature. Examples of such fields include durations, times, versions, etc.
// Transient fields are replaced in golden file tests to always obtain the same
Expand Down Expand Up @@ -174,6 +232,10 @@ type OutputProcessConfig struct {
// KeepVolatileData indicates whether to keep or replace frequently
// changing data.
KeepVolatileData bool
// NewLineStyle defines the new line style to be used on the output. I.e.,
// the output can be kept untouched, or converted to LF or CRLF before
// comparison.
NewLineStyle NewLineStyle
// TransientFields are keys that hold values which are transient (dynamic)
// in nature, such as the elapsed time, version, start time, etc. Transient
// fields have a special parsing in the .golden file and they are
Expand Down
39 changes: 31 additions & 8 deletions golden/dag.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package golden

import (
"fmt"
"path/filepath"
"strings"
"sync"
"testing"
Expand All @@ -11,12 +12,12 @@ import (
type DagTestCase struct {
Name string
Needs []string
Config *BashConfig
Config *ScriptConfig
Path string
}

// DagTest runs a set of test cases in topological order.
// Each test case is a BashTest, and the test cases are connected by their
// Each test case is a ScriptTest, and the test cases are connected by their
// dependencies. If a test case has dependencies, it will only be run after all
// of its dependencies have been run.
//
Expand All @@ -26,13 +27,13 @@ type DagTestCase struct {
// {
// name: "app-create",
// needs: []string{},
// config: BashConfig{ /**/ },
// config: ScriptConfig{ /**/ },
// path: "app-create",
// },
// {
// name: "app-push",
// needs: []string{"app-create"},
// config: BashConfig{ /**/ },
// config: ScriptConfig{ /**/ },
// path: "app-push",
// },
// }
Expand Down Expand Up @@ -79,16 +80,26 @@ func DagTest(t *testing.T, cases []DagTestCase) {
var wg sync.WaitGroup
for _, nextCase := range next {
wg.Add(1)
config := BashConfig{}
config := NewScriptConfig()
if nextCase.Config != nil {
config = *nextCase.Config
}
if len(config.ScriptExtensions) == 0 {
// Default script extension if none is provided.
config.ScriptExtensions = []ScriptExtension{{Extension: ".sh", Command: "bash"}}
}

// Get the script extension for the test case.
ext, err := dagGetScriptExtension(nextCase.Path, config)
if err != nil {
t.Fatal(err)
}

nextCase := nextCase
nextCase := nextCase // Capture the variable for the goroutine.
go func() {
defer wg.Done()
// Run the test case.
BashTestFile(t, nextCase.Path, config)
wg.Done()
ScriptTestFile(t, ext.Command, nextCase.Path, config)
}()
}

Expand All @@ -108,6 +119,18 @@ func DagTest(t *testing.T, cases []DagTestCase) {
}
}

func dagGetScriptExtension(path string, config ScriptConfig) (ScriptExtension, error) {
// Get extension from the path.
ext := filepath.Ext(path)
// Search for fitting script definition among config.ScriptExtensions.
for _, def := range config.ScriptExtensions {
if def.Extension == ext {
return def, nil
}
}
return ScriptExtension{}, fmt.Errorf("no script definition found for path %s with extension %s", path, ext)
}

func validate(cases []DagTestCase) error {
// Ensure that all cases have unique names.
names := make(map[string]bool)
Expand Down
20 changes: 20 additions & 0 deletions golden/eol.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package golden

import (
"bytes"
)

// convertNewLineStyle converts the new line style of the content based on the
// specified NewLineStyle.
func convertNewLineStyle(content []byte, style NewLineStyle) []byte {
switch style {
case NewLineStyleUntouched:
return content // Keep the original line endings
case NewLineStyleLF:
return bytes.ReplaceAll(content, []byte{'\r', '\n'}, []byte{'\n'})
case NewLineStyleCRLF:
return bytes.ReplaceAll(content, []byte{'\n'}, []byte{'\r', '\n'})
default:
return content // Default case, keep as is
}
}
2 changes: 2 additions & 0 deletions golden/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ func comparison(
)
}

actualBytes = convertNewLineStyle(actualBytes, config.OutputProcessConfig.NewLineStyle)

outputWithTransient := map[string]any{}
flattenedOutput := map[string]any{}
if !config.CompareConfig.TxtParse {
Expand Down
Loading
Loading