yq/pkg/yqlib/operators_test.go

388 lines
9.8 KiB
Go
Raw Normal View History

2020-11-03 23:48:43 +00:00
package yqlib
import (
"bufio"
"bytes"
2020-11-06 03:37:01 +00:00
"container/list"
2020-11-03 23:48:43 +00:00
"fmt"
2020-11-13 09:58:01 +00:00
"io"
2020-11-03 23:48:43 +00:00
"os"
"sort"
2020-11-06 01:11:38 +00:00
"strings"
2020-11-03 23:48:43 +00:00
"testing"
"time"
2020-11-03 23:48:43 +00:00
"github.com/mikefarah/yq/v4/test"
2022-03-09 03:38:02 +00:00
logging "gopkg.in/op/go-logging.v1"
2020-11-03 23:48:43 +00:00
)
type expressionScenario struct {
2020-11-14 23:50:30 +00:00
description string
2020-11-17 23:32:30 +00:00
subdescription string
2023-09-01 02:17:54 +00:00
explanation []string
2022-02-01 03:58:53 +00:00
environmentVariables map[string]string
2020-11-14 23:50:30 +00:00
document string
2021-01-01 23:27:32 +00:00
document2 string
2020-11-14 23:50:30 +00:00
expression string
expected []string
skipDoc bool
2022-02-09 00:47:21 +00:00
expectedError string
2020-11-14 23:50:30 +00:00
dontFormatInputForDoc bool // dont format input doc for documentation generation
requiresFormat string
2020-11-03 23:48:43 +00:00
}
2022-01-27 01:47:06 +00:00
func TestMain(m *testing.M) {
logging.SetLevel(logging.DEBUG, "")
Now = func() time.Time {
return time.Date(2021, time.May, 19, 1, 2, 3, 4, time.UTC)
}
2022-01-27 01:47:06 +00:00
code := m.Run()
os.Exit(code)
}
2024-01-11 02:17:34 +00:00
func NewSimpleYamlPrinter(writer io.Writer, _ PrinterOutputFormat, unwrapScalar bool, colorsEnabled bool, indent int, printDocSeparators bool) Printer {
prefs := NewDefaultYamlPreferences()
prefs.PrintDocSeparators = printDocSeparators
prefs.UnwrapScalar = unwrapScalar
return NewPrinter(NewYamlEncoder(indent, colorsEnabled, prefs), NewSinglePrinterWriter(writer))
}
func readDocument(content string, fakefilename string, fakeFileIndex int) (*list.List, error) {
reader := bufio.NewReader(strings.NewReader(content))
2021-11-12 04:02:28 +00:00
return readDocuments(reader, fakefilename, fakeFileIndex, NewYamlDecoder(ConfiguredYamlPreferences))
2021-11-12 04:02:28 +00:00
}
2020-11-03 23:48:43 +00:00
func testScenario(t *testing.T, s *expressionScenario) {
2020-11-06 03:37:01 +00:00
var err error
2022-02-01 03:47:51 +00:00
node, err := getExpressionParser().ParseExpression(s.expression)
2020-11-13 02:19:54 +00:00
if err != nil {
2021-11-22 06:43:38 +00:00
t.Error(fmt.Errorf("Error parsing expression %v of %v: %w", s.expression, s.description, err))
2020-11-13 02:19:54 +00:00
return
}
inputs := list.New()
2020-11-06 03:37:01 +00:00
if s.document != "" {
inputs, err = readDocument(s.document, "sample.yml", 0)
2021-11-12 04:02:28 +00:00
2020-11-13 02:19:54 +00:00
if err != nil {
2020-12-26 10:37:08 +00:00
t.Error(err, s.document, s.expression)
2020-11-13 02:19:54 +00:00
return
}
2021-11-12 04:02:28 +00:00
2021-01-01 23:27:32 +00:00
if s.document2 != "" {
moreInputs, err := readDocument(s.document2, "another.yml", 1)
2021-01-01 23:27:32 +00:00
if err != nil {
2021-11-12 04:02:28 +00:00
t.Error(err, s.document2, s.expression)
2021-01-01 23:27:32 +00:00
return
}
inputs.PushBackList(moreInputs)
}
2020-12-01 03:06:49 +00:00
} else {
candidateNode := &CandidateNode{
document: 0,
filename: "",
Tag: "!!null",
Kind: ScalarNode,
fileIndex: 0,
2020-12-01 03:06:49 +00:00
}
inputs.PushBack(candidateNode)
2020-11-03 23:48:43 +00:00
}
2022-02-01 03:58:53 +00:00
for name, value := range s.environmentVariables {
os.Setenv(name, value)
2021-01-09 00:33:39 +00:00
}
context, err := NewDataTreeNavigator().GetMatchingNodes(Context{MatchingNodes: inputs}, node)
2020-11-13 02:19:54 +00:00
2022-02-09 00:47:21 +00:00
if s.expectedError != "" {
if err == nil {
t.Errorf("Expected error '%v' but it worked!", s.expectedError)
} else {
test.AssertResultComplexWithContext(t, s.expectedError, err.Error(), fmt.Sprintf("desc: %v\nexp: %v\ndoc: %v", s.description, s.expression, s.document))
}
2022-02-09 00:47:21 +00:00
return
}
if s.requiresFormat != "" {
format := s.requiresFormat
inputFormat, err := InputFormatFromString(format)
if err != nil {
t.Error(err)
}
if decoder := createDecoder(inputFormat); decoder == nil {
t.Skipf("no support for %s input format", format)
}
outputFormat, err := OutputFormatFromString(format)
if err != nil {
t.Error(err)
}
if encoder := configureEncoder(outputFormat, 4); encoder == nil {
t.Skipf("no support for %s output format", format)
}
}
2020-11-06 03:37:01 +00:00
if err != nil {
t.Error(fmt.Errorf("%w: %v: %v", err, s.description, s.expression))
2020-11-03 23:48:43 +00:00
return
}
2021-11-13 23:51:18 +00:00
test.AssertResultComplexWithContext(t, s.expected, resultsToString(t, context.MatchingNodes), fmt.Sprintf("desc: %v\nexp: %v\ndoc: %v", s.description, s.expression, s.document))
2020-11-03 23:48:43 +00:00
}
2021-12-21 04:02:07 +00:00
func resultToString(t *testing.T, n *CandidateNode) string {
var valueBuffer bytes.Buffer
log.Debugf("printing result %v", NodeToString(n))
printer := NewSimpleYamlPrinter(bufio.NewWriter(&valueBuffer), YamlOutputFormat, true, false, 4, true)
2021-12-21 04:02:07 +00:00
err := printer.PrintResults(n.AsList())
if err != nil {
t.Error(err)
return ""
}
tag := n.Tag
if n.Kind == AliasNode {
2021-12-21 04:02:07 +00:00
tag = "alias"
}
return fmt.Sprintf(`D%v, P%v, (%v)::%v`, n.GetDocument(), n.GetPath(), tag, valueBuffer.String())
2021-12-21 04:02:07 +00:00
}
2021-11-13 23:51:18 +00:00
func resultsToString(t *testing.T, results *list.List) []string {
var pretty = make([]string, 0)
2021-11-13 23:18:02 +00:00
2020-11-16 01:09:57 +00:00
for el := results.Front(); el != nil; el = el.Next() {
n := el.Value.(*CandidateNode)
2021-11-13 23:51:18 +00:00
2021-12-21 04:02:07 +00:00
output := resultToString(t, n)
2021-11-13 23:18:02 +00:00
pretty = append(pretty, output)
2020-11-16 01:09:57 +00:00
}
return pretty
}
2020-11-13 03:07:11 +00:00
func writeOrPanic(w *bufio.Writer, text string) {
_, err := w.WriteString(text)
if err != nil {
panic(err)
}
}
2022-02-06 03:39:46 +00:00
func copySnippet(source string, out *os.File) error {
2020-11-13 09:58:01 +00:00
_, err := os.Stat(source)
if os.IsNotExist(err) {
2020-11-13 10:22:05 +00:00
return nil
2020-11-13 09:58:01 +00:00
}
in, err := os.Open(source)
2020-11-13 09:58:01 +00:00
if err != nil {
2020-11-13 10:22:05 +00:00
return err
2020-11-13 09:58:01 +00:00
}
defer safelyCloseFile(in)
_, err = io.Copy(out, in)
2020-11-13 10:22:05 +00:00
return err
}
2021-01-01 23:27:32 +00:00
func formatYaml(yaml string, filename string) string {
2020-11-13 10:22:05 +00:00
var output bytes.Buffer
printer := NewSimpleYamlPrinter(bufio.NewWriter(&output), YamlOutputFormat, true, false, 2, true)
2020-11-13 10:22:05 +00:00
2022-02-01 03:47:51 +00:00
node, err := getExpressionParser().ParseExpression(".. style= \"\"")
2020-11-13 10:22:05 +00:00
if err != nil {
panic(err)
}
2020-11-22 00:56:28 +00:00
streamEvaluator := NewStreamEvaluator()
_, err = streamEvaluator.Evaluate(filename, strings.NewReader(yaml), node, printer, NewYamlDecoder(ConfiguredYamlPreferences))
2020-11-13 10:22:05 +00:00
if err != nil {
panic(err)
}
return output.String()
2020-11-13 09:58:01 +00:00
}
2021-12-21 04:02:07 +00:00
type documentScenarioFunc func(t *testing.T, writer *bufio.Writer, scenario interface{})
func documentScenarios(t *testing.T, folder string, title string, scenarios []interface{}, documentScenario documentScenarioFunc) {
f, err := os.Create(fmt.Sprintf("doc/%v/%v.md", folder, title))
2020-11-03 23:48:43 +00:00
if err != nil {
2020-11-13 02:19:54 +00:00
t.Error(err)
2020-11-13 09:58:01 +00:00
return
2020-11-03 23:48:43 +00:00
}
defer f.Close()
2020-11-13 09:58:01 +00:00
2022-02-06 03:39:46 +00:00
source := fmt.Sprintf("doc/%v/headers/%v.md", folder, title)
err = copySnippet(source, f)
if err != nil {
t.Error(err)
return
}
err = copySnippet("doc/notification-snippet.md", f)
2020-11-13 09:58:01 +00:00
if err != nil {
t.Error(err)
return
}
2020-11-03 23:48:43 +00:00
w := bufio.NewWriter(f)
2020-11-22 02:16:54 +00:00
writeOrPanic(w, "\n")
2020-11-13 09:58:01 +00:00
2020-11-22 02:16:54 +00:00
for _, s := range scenarios {
2021-12-21 04:02:07 +00:00
documentScenario(t, w, s)
}
w.Flush()
}
2020-11-03 23:48:43 +00:00
2021-12-21 04:02:07 +00:00
func documentOperatorScenarios(t *testing.T, title string, scenarios []expressionScenario) {
genericScenarios := make([]interface{}, len(scenarios))
for i, s := range scenarios {
genericScenarios[i] = s
}
documentScenarios(t, "operators", title, genericScenarios, documentOperatorScenario)
}
func documentOperatorScenario(t *testing.T, w *bufio.Writer, i interface{}) {
s := i.(expressionScenario)
if s.skipDoc {
return
}
writeOrPanic(w, fmt.Sprintf("## %v\n", s.description))
2020-11-22 02:16:54 +00:00
if s.subdescription != "" {
writeOrPanic(w, s.subdescription)
writeOrPanic(w, "\n\n")
}
2020-11-03 23:48:43 +00:00
formattedDoc, formattedDoc2 := documentInput(w, s)
2020-11-06 00:45:18 +00:00
writeOrPanic(w, "will output\n")
2021-01-01 23:27:32 +00:00
documentOutput(t, w, s, formattedDoc, formattedDoc2)
2023-09-01 02:17:54 +00:00
if len(s.explanation) > 0 {
writeOrPanic(w, "### Explanation:\n")
for _, text := range s.explanation {
writeOrPanic(w, fmt.Sprintf("- %v\n", text))
}
writeOrPanic(w, "\n")
}
}
2021-01-01 23:27:32 +00:00
func documentInput(w *bufio.Writer, s expressionScenario) (string, string) {
formattedDoc := ""
formattedDoc2 := ""
2022-01-27 06:21:10 +00:00
command := ""
2021-01-09 00:33:39 +00:00
envCommand := ""
envKeys := make([]string, 0, len(s.environmentVariables))
for k := range s.environmentVariables {
envKeys = append(envKeys, k)
}
sort.Strings(envKeys)
for _, name := range envKeys {
value := s.environmentVariables[name]
2022-02-01 03:58:53 +00:00
if envCommand == "" {
envCommand = fmt.Sprintf("%v=\"%v\" ", name, value)
} else {
envCommand = fmt.Sprintf("%v %v=\"%v\" ", envCommand, name, value)
}
os.Setenv(name, value)
2021-01-09 01:06:19 +00:00
}
if s.document != "" {
if s.dontFormatInputForDoc {
formattedDoc = s.document + "\n"
} else {
formattedDoc = formatYaml(s.document, "sample.yml")
}
2021-01-01 23:27:32 +00:00
writeOrPanic(w, "Given a sample.yml file of:\n")
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n", formattedDoc))
2021-01-01 23:27:32 +00:00
files := "sample.yml"
if s.document2 != "" {
if s.dontFormatInputForDoc {
formattedDoc2 = s.document2 + "\n"
} else {
formattedDoc2 = formatYaml(s.document2, "another.yml")
2021-01-01 23:27:32 +00:00
}
writeOrPanic(w, "And another sample another.yml file of:\n")
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n", formattedDoc2))
files = "sample.yml another.yml"
2022-01-27 06:21:10 +00:00
command = "eval-all "
}
writeOrPanic(w, "then\n")
2021-01-09 00:33:39 +00:00
if s.expression != "" {
writeOrPanic(w, fmt.Sprintf("```bash\n%vyq %v'%v' %v\n```\n", envCommand, command, strings.ReplaceAll(s.expression, "'", `'\''`), files))
} else {
2022-01-27 06:21:10 +00:00
writeOrPanic(w, fmt.Sprintf("```bash\n%vyq %v%v\n```\n", envCommand, command, files))
}
} else {
writeOrPanic(w, "Running\n")
2022-01-27 06:21:10 +00:00
writeOrPanic(w, fmt.Sprintf("```bash\n%vyq %v--null-input '%v'\n```\n", envCommand, command, s.expression))
}
return formattedDoc, formattedDoc2
}
func documentOutput(t *testing.T, w *bufio.Writer, s expressionScenario, formattedDoc string, formattedDoc2 string) {
var output bytes.Buffer
var err error
printer := NewSimpleYamlPrinter(bufio.NewWriter(&output), YamlOutputFormat, true, false, 2, true)
2022-02-01 03:47:51 +00:00
node, err := getExpressionParser().ParseExpression(s.expression)
if err != nil {
2021-11-22 06:43:38 +00:00
t.Error(fmt.Errorf("Error parsing expression %v of %v: %w", s.expression, s.description, err))
return
}
inputs := list.New()
if s.document != "" {
2021-11-12 04:02:28 +00:00
inputs, err = readDocument(formattedDoc, "sample.yml", 0)
if err != nil {
t.Error(err, s.document, s.expression)
return
}
if s.document2 != "" {
moreInputs, err := readDocument(formattedDoc2, "another.yml", 1)
2021-01-01 23:27:32 +00:00
if err != nil {
t.Error(err, s.document, s.expression)
return
2020-11-06 03:37:01 +00:00
}
inputs.PushBackList(moreInputs)
}
} else {
candidateNode := &CandidateNode{
document: 0,
filename: "",
Tag: "!!null",
Kind: ScalarNode,
fileIndex: 0,
}
inputs.PushBack(candidateNode)
2020-11-06 03:37:01 +00:00
}
2020-11-03 23:48:43 +00:00
context, err := NewDataTreeNavigator().GetMatchingNodes(Context{MatchingNodes: inputs}, node)
if s.expectedError != "" && err != nil {
writeOrPanic(w, fmt.Sprintf("```bash\nError: %v\n```\n\n", err.Error()))
return
} else if err != nil {
t.Error(err, s.expression)
return
}
2020-11-03 23:48:43 +00:00
err = printer.PrintResults(context.MatchingNodes)
if err != nil {
t.Error(err, s.expression)
2020-11-03 23:48:43 +00:00
}
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n\n", output.String()))
2020-11-03 23:48:43 +00:00
}