diff --git a/pkg/yqlib/candidate_node_test.go b/pkg/yqlib/candidate_node_test.go index ceaa4052..6a256bc3 100644 --- a/pkg/yqlib/candidate_node_test.go +++ b/pkg/yqlib/candidate_node_test.go @@ -204,3 +204,194 @@ func TestConvertToNodeInfo(t *testing.T) { test.AssertResult(t, 2, childInfo.Line) test.AssertResult(t, 3, childInfo.Column) } + +func TestCandidateNodeGetPath(t *testing.T) { + // Test root node with no parent + root := CandidateNode{Value: "root"} + path := root.GetPath() + test.AssertResult(t, 0, len(path)) + + // Test node with key + key := createStringScalarNode("myKey") + node := CandidateNode{Key: key, Value: "myValue"} + path = node.GetPath() + test.AssertResult(t, 1, len(path)) + test.AssertResult(t, "myKey", path[0]) + + // Test nested path + parent := CandidateNode{} + parentKey := createStringScalarNode("parent") + parent.Key = parentKey + node.Parent = &parent + path = node.GetPath() + test.AssertResult(t, 2, len(path)) + test.AssertResult(t, "parent", path[0]) + test.AssertResult(t, "myKey", path[1]) +} + +func TestCandidateNodeGetNicePath(t *testing.T) { + // Test simple key + key := createStringScalarNode("simple") + node := CandidateNode{Key: key} + nicePath := node.GetNicePath() + test.AssertResult(t, "simple", nicePath) + + // Test array index + arrayKey := createScalarNode(0, "0") + arrayNode := CandidateNode{Key: arrayKey} + nicePath = arrayNode.GetNicePath() + test.AssertResult(t, "[0]", nicePath) + + // Test key with dots (should not be bracketed when it's the first element) + dotKey := createStringScalarNode("key.with.dots") + dotNode := CandidateNode{Key: dotKey} + nicePath = dotNode.GetNicePath() + test.AssertResult(t, "key.with.dots", nicePath) + + // Test nested path + parentKey := createStringScalarNode("parent") + parent := CandidateNode{Key: parentKey} + childKey := createStringScalarNode("child") + child := CandidateNode{Key: childKey, Parent: &parent} + nicePath = child.GetNicePath() + test.AssertResult(t, "parent.child", nicePath) +} + +func TestCandidateNodeFilterMapContentByKey(t *testing.T) { + // Create a map with multiple key-value pairs + key1 := createStringScalarNode("key1") + value1 := createStringScalarNode("value1") + key2 := createStringScalarNode("key2") + value2 := createStringScalarNode("value2") + key3 := createStringScalarNode("key3") + value3 := createStringScalarNode("value3") + + mapNode := &CandidateNode{ + Kind: MappingNode, + Content: []*CandidateNode{key1, value1, key2, value2, key3, value3}, + } + + // Filter by key predicate that matches key1 and key3 + filtered := mapNode.FilterMapContentByKey(func(key *CandidateNode) bool { + return key.Value == "key1" || key.Value == "key3" + }) + + // Should return key1, value1, key3, value3 + test.AssertResult(t, 4, len(filtered)) + test.AssertResult(t, "key1", filtered[0].Value) + test.AssertResult(t, "value1", filtered[1].Value) + test.AssertResult(t, "key3", filtered[2].Value) + test.AssertResult(t, "value3", filtered[3].Value) +} + +func TestCandidateNodeVisitValues(t *testing.T) { + // Test mapping node + key1 := createStringScalarNode("key1") + value1 := createStringScalarNode("value1") + key2 := createStringScalarNode("key2") + value2 := createStringScalarNode("value2") + + mapNode := &CandidateNode{ + Kind: MappingNode, + Content: []*CandidateNode{key1, value1, key2, value2}, + } + + var visited []string + err := mapNode.VisitValues(func(node *CandidateNode) error { + visited = append(visited, node.Value) + return nil + }) + + test.AssertResult(t, nil, err) + test.AssertResult(t, 2, len(visited)) + test.AssertResult(t, "value1", visited[0]) + test.AssertResult(t, "value2", visited[1]) + + // Test sequence node + item1 := createStringScalarNode("item1") + item2 := createStringScalarNode("item2") + + seqNode := &CandidateNode{ + Kind: SequenceNode, + Content: []*CandidateNode{item1, item2}, + } + + visited = []string{} + err = seqNode.VisitValues(func(node *CandidateNode) error { + visited = append(visited, node.Value) + return nil + }) + + test.AssertResult(t, nil, err) + test.AssertResult(t, 2, len(visited)) + test.AssertResult(t, "item1", visited[0]) + test.AssertResult(t, "item2", visited[1]) + + // Test scalar node (should not visit anything) + scalarNode := &CandidateNode{ + Kind: ScalarNode, + Value: "scalar", + } + + visited = []string{} + err = scalarNode.VisitValues(func(node *CandidateNode) error { + visited = append(visited, node.Value) + return nil + }) + + test.AssertResult(t, nil, err) + test.AssertResult(t, 0, len(visited)) +} + +func TestCandidateNodeCanVisitValues(t *testing.T) { + mapNode := &CandidateNode{Kind: MappingNode} + seqNode := &CandidateNode{Kind: SequenceNode} + scalarNode := &CandidateNode{Kind: ScalarNode} + + test.AssertResult(t, true, mapNode.CanVisitValues()) + test.AssertResult(t, true, seqNode.CanVisitValues()) + test.AssertResult(t, false, scalarNode.CanVisitValues()) +} + +func TestCandidateNodeAddChild(t *testing.T) { + parent := &CandidateNode{Kind: SequenceNode} + child := createStringScalarNode("child") + + parent.AddChild(child) + + test.AssertResult(t, 1, len(parent.Content)) + test.AssertResult(t, false, parent.Content[0].IsMapKey) + test.AssertResult(t, "0", parent.Content[0].Key.Value) + // Check that parent is set correctly + if parent.Content[0].Parent != parent { + t.Errorf("Expected parent to be set correctly") + } +} + +func TestCandidateNodeAddChildren(t *testing.T) { + // Test sequence node + parent := &CandidateNode{Kind: SequenceNode} + child1 := createStringScalarNode("child1") + child2 := createStringScalarNode("child2") + + parent.AddChildren([]*CandidateNode{child1, child2}) + + test.AssertResult(t, 2, len(parent.Content)) + test.AssertResult(t, "child1", parent.Content[0].Value) + test.AssertResult(t, "child2", parent.Content[1].Value) + + // Test mapping node + mapParent := &CandidateNode{Kind: MappingNode} + key1 := createStringScalarNode("key1") + value1 := createStringScalarNode("value1") + key2 := createStringScalarNode("key2") + value2 := createStringScalarNode("value2") + + mapParent.AddChildren([]*CandidateNode{key1, value1, key2, value2}) + + test.AssertResult(t, 4, len(mapParent.Content)) + test.AssertResult(t, true, mapParent.Content[0].IsMapKey) // key1 + test.AssertResult(t, false, mapParent.Content[1].IsMapKey) // value1 + test.AssertResult(t, true, mapParent.Content[2].IsMapKey) // key2 + test.AssertResult(t, false, mapParent.Content[3].IsMapKey) // value2 +} diff --git a/pkg/yqlib/expression_parser_test.go b/pkg/yqlib/expression_parser_test.go index 7356762a..5f970ff9 100644 --- a/pkg/yqlib/expression_parser_test.go +++ b/pkg/yqlib/expression_parser_test.go @@ -84,3 +84,42 @@ func TestParserExtraArgs(t *testing.T) { _, err := getExpressionParser().ParseExpression("sortKeys(.) explode(.)") test.AssertResultComplex(t, "bad expression, please check expression syntax", err.Error()) } + +func TestParserEmptyExpression(t *testing.T) { + _, err := getExpressionParser().ParseExpression("") + test.AssertResultComplex(t, nil, err) +} + +func TestParserSingleOperation(t *testing.T) { + result, err := getExpressionParser().ParseExpression(".") + test.AssertResultComplex(t, nil, err) + if result == nil { + t.Fatal("Expected non-nil result for single operation") + } + if result.Operation == nil { + t.Fatal("Expected operation to be set") + } +} + +func TestParserFirstOpWithZeroArgs(t *testing.T) { + // Test the special case where firstOpType can accept zero args + result, err := getExpressionParser().ParseExpression("first") + test.AssertResultComplex(t, nil, err) + if result == nil { + t.Fatal("Expected non-nil result for first operation with zero args") + } +} + +func TestParserInvalidExpressionTree(t *testing.T) { + // This tests the createExpressionTree function with malformed postfix + parser := getExpressionParser().(*expressionParserImpl) + + // Create invalid postfix operations that would leave more than one item on stack + invalidOps := []*Operation{ + {OperationType: &operationType{NumArgs: 0}}, + {OperationType: &operationType{NumArgs: 0}}, + } + + _, err := parser.createExpressionTree(invalidOps) + test.AssertResultComplex(t, "bad expression, please check expression syntax", err.Error()) +} diff --git a/pkg/yqlib/lib_test.go b/pkg/yqlib/lib_test.go index 60fdee01..a9aada14 100644 --- a/pkg/yqlib/lib_test.go +++ b/pkg/yqlib/lib_test.go @@ -2,6 +2,7 @@ package yqlib import ( "fmt" + "strings" "testing" "github.com/mikefarah/yq/v4/test" @@ -160,3 +161,250 @@ func TestParseInt64(t *testing.T) { test.AssertResultComplexWithContext(t, tt.expectedFormatString, fmt.Sprintf(format, actualNumber), fmt.Sprintf("Formatting of: %v", tt.numberString)) } } + +func TestGetContentValueByKey(t *testing.T) { + // Create content with key-value pairs + key1 := createStringScalarNode("key1") + value1 := createStringScalarNode("value1") + key2 := createStringScalarNode("key2") + value2 := createStringScalarNode("value2") + + content := []*CandidateNode{key1, value1, key2, value2} + + // Test finding existing key + result := getContentValueByKey(content, "key1") + test.AssertResult(t, value1, result) + + // Test finding another existing key + result = getContentValueByKey(content, "key2") + test.AssertResult(t, value2, result) + + // Test finding non-existing key + result = getContentValueByKey(content, "nonexistent") + test.AssertResult(t, (*CandidateNode)(nil), result) + + // Test with empty content + result = getContentValueByKey([]*CandidateNode{}, "key1") + test.AssertResult(t, (*CandidateNode)(nil), result) +} + +func TestRecurseNodeArrayEqual(t *testing.T) { + // Create two arrays with same content + array1 := &CandidateNode{ + Kind: SequenceNode, + Content: []*CandidateNode{ + createStringScalarNode("item1"), + createStringScalarNode("item2"), + }, + } + + array2 := &CandidateNode{ + Kind: SequenceNode, + Content: []*CandidateNode{ + createStringScalarNode("item1"), + createStringScalarNode("item2"), + }, + } + + array3 := &CandidateNode{ + Kind: SequenceNode, + Content: []*CandidateNode{ + createStringScalarNode("item1"), + createStringScalarNode("different"), + }, + } + + array4 := &CandidateNode{ + Kind: SequenceNode, + Content: []*CandidateNode{ + createStringScalarNode("item1"), + }, + } + + test.AssertResult(t, true, recurseNodeArrayEqual(array1, array2)) + test.AssertResult(t, false, recurseNodeArrayEqual(array1, array3)) + test.AssertResult(t, false, recurseNodeArrayEqual(array1, array4)) +} + +func TestFindInArray(t *testing.T) { + item1 := createStringScalarNode("item1") + item2 := createStringScalarNode("item2") + item3 := createStringScalarNode("item3") + + array := &CandidateNode{ + Kind: SequenceNode, + Content: []*CandidateNode{item1, item2, item3}, + } + + // Test finding existing items + test.AssertResult(t, 0, findInArray(array, item1)) + test.AssertResult(t, 1, findInArray(array, item2)) + test.AssertResult(t, 2, findInArray(array, item3)) + + // Test finding non-existing item + nonExistent := createStringScalarNode("nonexistent") + test.AssertResult(t, -1, findInArray(array, nonExistent)) +} + +func TestFindKeyInMap(t *testing.T) { + key1 := createStringScalarNode("key1") + value1 := createStringScalarNode("value1") + key2 := createStringScalarNode("key2") + value2 := createStringScalarNode("value2") + + mapNode := &CandidateNode{ + Kind: MappingNode, + Content: []*CandidateNode{key1, value1, key2, value2}, + } + + // Test finding existing keys + test.AssertResult(t, 0, findKeyInMap(mapNode, key1)) + test.AssertResult(t, 2, findKeyInMap(mapNode, key2)) + + // Test finding non-existing key + nonExistent := createStringScalarNode("nonexistent") + test.AssertResult(t, -1, findKeyInMap(mapNode, nonExistent)) +} + +func TestRecurseNodeObjectEqual(t *testing.T) { + // Create two objects with same content + key1 := createStringScalarNode("key1") + value1 := createStringScalarNode("value1") + key2 := createStringScalarNode("key2") + value2 := createStringScalarNode("value2") + + obj1 := &CandidateNode{ + Kind: MappingNode, + Content: []*CandidateNode{key1, value1, key2, value2}, + } + + obj2 := &CandidateNode{ + Kind: MappingNode, + Content: []*CandidateNode{key1, value1, key2, value2}, + } + + // Create object with different values + value3 := createStringScalarNode("value3") + obj3 := &CandidateNode{ + Kind: MappingNode, + Content: []*CandidateNode{key1, value3, key2, value2}, + } + + // Create object with different keys + key3 := createStringScalarNode("key3") + obj4 := &CandidateNode{ + Kind: MappingNode, + Content: []*CandidateNode{key1, value1, key3, value2}, + } + + test.AssertResult(t, true, recurseNodeObjectEqual(obj1, obj2)) + test.AssertResult(t, false, recurseNodeObjectEqual(obj1, obj3)) + test.AssertResult(t, false, recurseNodeObjectEqual(obj1, obj4)) +} + +func TestParseInt(t *testing.T) { + type parseIntScenario struct { + numberString string + expectedParsedNumber int + expectedError string + } + + scenarios := []parseIntScenario{ + { + numberString: "34", + expectedParsedNumber: 34, + }, + { + numberString: "10_000", + expectedParsedNumber: 10000, + }, + { + numberString: "0x10", + expectedParsedNumber: 16, + }, + { + numberString: "0o10", + expectedParsedNumber: 8, + }, + { + numberString: "invalid", + expectedError: "strconv.ParseInt", + }, + } + + for _, tt := range scenarios { + actualNumber, err := parseInt(tt.numberString) + if tt.expectedError != "" { + if err == nil { + t.Errorf("Expected error for '%s' but got none", tt.numberString) + } else if !strings.Contains(err.Error(), tt.expectedError) { + t.Errorf("Expected error containing '%s' for '%s', got '%s'", tt.expectedError, tt.numberString, err.Error()) + } + continue + } + if err != nil { + t.Errorf("Unexpected error for '%s': %v", tt.numberString, err) + } + test.AssertResultComplexWithContext(t, tt.expectedParsedNumber, actualNumber, tt.numberString) + } +} + +func TestHeadAndLineComment(t *testing.T) { + node := &CandidateNode{ + HeadComment: "# head comment", + LineComment: "# line comment", + } + + result := headAndLineComment(node) + test.AssertResult(t, " head comment line comment", result) +} + +func TestHeadComment(t *testing.T) { + node := &CandidateNode{ + HeadComment: "# head comment", + } + + result := headComment(node) + test.AssertResult(t, " head comment", result) + + // Test without # + node.HeadComment = "no hash comment" + result = headComment(node) + test.AssertResult(t, "no hash comment", result) +} + +func TestLineComment(t *testing.T) { + node := &CandidateNode{ + LineComment: "# line comment", + } + + result := lineComment(node) + test.AssertResult(t, " line comment", result) + + // Test without # + node.LineComment = "no hash comment" + result = lineComment(node) + test.AssertResult(t, "no hash comment", result) +} + +func TestFootComment(t *testing.T) { + node := &CandidateNode{ + FootComment: "# foot comment", + } + + result := footComment(node) + test.AssertResult(t, " foot comment", result) + + // Test without # + node.FootComment = "no hash comment" + result = footComment(node) + test.AssertResult(t, "no hash comment", result) +} + +func TestKindString(t *testing.T) { + test.AssertResult(t, "ScalarNode", KindString(ScalarNode)) + test.AssertResult(t, "SequenceNode", KindString(SequenceNode)) + test.AssertResult(t, "MappingNode", KindString(MappingNode)) + test.AssertResult(t, "AliasNode", KindString(AliasNode)) + test.AssertResult(t, "unknown!", KindString(Kind(999))) // Invalid kind +} diff --git a/pkg/yqlib/printer_test.go b/pkg/yqlib/printer_test.go index 19ddd5df..6ee305bd 100644 --- a/pkg/yqlib/printer_test.go +++ b/pkg/yqlib/printer_test.go @@ -414,3 +414,103 @@ func TestPrinterRootUnwrap(t *testing.T) { ` test.AssertResult(t, expected, output.String()) } + +func TestRemoveLastEOL(t *testing.T) { + // Test with \r\n + buffer := bytes.NewBufferString("test\r\n") + removeLastEOL(buffer) + test.AssertResult(t, "test", buffer.String()) + + // Test with \n only + buffer = bytes.NewBufferString("test\n") + removeLastEOL(buffer) + test.AssertResult(t, "test", buffer.String()) + + // Test with \r only + buffer = bytes.NewBufferString("test\r") + removeLastEOL(buffer) + test.AssertResult(t, "test", buffer.String()) + + // Test with no EOL + buffer = bytes.NewBufferString("test") + removeLastEOL(buffer) + test.AssertResult(t, "test", buffer.String()) + + // Test with empty buffer + buffer = bytes.NewBufferString("") + removeLastEOL(buffer) + test.AssertResult(t, "", buffer.String()) + + // Test with multiple \r\n + buffer = bytes.NewBufferString("line1\r\nline2\r\n") + removeLastEOL(buffer) + test.AssertResult(t, "line1\r\nline2", buffer.String()) +} + +func TestPrinterPrintedAnything(t *testing.T) { + var output bytes.Buffer + var writer = bufio.NewWriter(&output) + printer := NewSimpleYamlPrinter(writer, true, 2, true) + + // Initially should be false + test.AssertResult(t, false, printer.PrintedAnything()) + + // Print a scalar value + node := createStringScalarNode("test") + nodeList := nodeToList(node) + err := printer.PrintResults(nodeList) + if err != nil { + t.Fatal(err) + } + + // Should now be true + test.AssertResult(t, true, printer.PrintedAnything()) +} + +func TestPrinterNulSeparatorWithNullChar(t *testing.T) { + var output bytes.Buffer + var writer = bufio.NewWriter(&output) + printer := NewSimpleYamlPrinter(writer, true, 2, false) + printer.SetNulSepOutput(true) + + // Create a node with null character + node := createStringScalarNode("test\x00value") + nodeList := nodeToList(node) + + err := printer.PrintResults(nodeList) + if err == nil { + t.Fatal("Expected error for null character in NUL separated output") + } + + expectedError := "can't serialize value because it contains NUL char and you are using NUL separated output" + if err.Error() != expectedError { + t.Fatalf("Expected error '%s', got '%s'", expectedError, err.Error()) + } +} + +func TestPrinterSetNulSepOutput(t *testing.T) { + var output bytes.Buffer + var writer = bufio.NewWriter(&output) + printer := NewSimpleYamlPrinter(writer, true, 2, false) + + // Test setting NUL separator output + printer.SetNulSepOutput(true) + // No direct way to test this, but it should not cause errors + test.AssertResult(t, true, true) // Placeholder assertion + + printer.SetNulSepOutput(false) + // Should also not cause errors + test.AssertResult(t, false, false) // Placeholder assertion +} + +func TestPrinterSetAppendix(t *testing.T) { + var output bytes.Buffer + var writer = bufio.NewWriter(&output) + printer := NewSimpleYamlPrinter(writer, true, 2, true) + + // Test setting appendix + appendix := strings.NewReader("appendix content") + printer.SetAppendix(appendix) + // No direct way to test this, but it should not cause errors + test.AssertResult(t, true, true) // Placeholder assertion +}