Add comprehensive tests for XML parser functionality
- Introduced benchmark tests for various XML parsing methods, including parsing from strings, bytes, and readers, to evaluate performance. - Added unit tests for the parser interface and validation mechanisms, ensuring robust error handling and validation logic. - Implemented tests for utility functions related to XML processing, enhancing overall test coverage and reliability. - Created a test suite for the TED domain, validating the structure and functionality of contract notices and related entities. - Developed a comprehensive test runner script to facilitate automated testing, coverage analysis, and performance benchmarking for the XML parser package.
This commit is contained in:
@@ -0,0 +1,614 @@
|
||||
package xmlparser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Benchmark data of different sizes
|
||||
var (
|
||||
smallXML = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MockElement id="small">
|
||||
<name>Small Test</name>
|
||||
<value>Small value</value>
|
||||
</MockElement>`
|
||||
|
||||
mediumXML = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MockElement id="medium">
|
||||
<name>Medium Test Document</name>
|
||||
<value>This is a medium-sized XML document for benchmarking purposes</value>
|
||||
<details>
|
||||
<description>Medium XML document with nested elements</description>
|
||||
<category>benchmark</category>
|
||||
<priority>medium</priority>
|
||||
<tags>
|
||||
<tag>xml</tag>
|
||||
<tag>benchmark</tag>
|
||||
<tag>test</tag>
|
||||
</tags>
|
||||
</details>
|
||||
<metadata>
|
||||
<created>2025-01-09</created>
|
||||
<author>Test Suite</author>
|
||||
<version>1.0</version>
|
||||
</metadata>
|
||||
</MockElement>`
|
||||
|
||||
// Generate large XML dynamically in benchmarks to avoid bloating the source
|
||||
largeXMLTemplate = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MockElement id="large">
|
||||
<name>Large Test Document</name>
|
||||
<value>This is a large XML document for benchmarking performance</value>
|
||||
<data>%s</data>
|
||||
</MockElement>`
|
||||
)
|
||||
|
||||
// generateLargeXML creates a large XML document for benchmarking
|
||||
func generateLargeXML() string {
|
||||
var buffer strings.Builder
|
||||
|
||||
// Generate repetitive data to create a large document
|
||||
for i := 0; i < 1000; i++ {
|
||||
buffer.WriteString(fmt.Sprintf(`
|
||||
<item id="item-%d">
|
||||
<title>Item %d Title</title>
|
||||
<description>This is item number %d with detailed description for benchmarking purposes. It contains enough text to make the document significantly larger.</description>
|
||||
<category>category-%d</category>
|
||||
<tags>
|
||||
<tag>tag1</tag>
|
||||
<tag>tag2</tag>
|
||||
<tag>tag3</tag>
|
||||
</tags>
|
||||
</item>`, i, i, i, i%10))
|
||||
}
|
||||
|
||||
return fmt.Sprintf(largeXMLTemplate, buffer.String())
|
||||
}
|
||||
|
||||
// BenchmarkParseString benchmarks parsing from string
|
||||
func BenchmarkParseString(b *testing.B) {
|
||||
parser := NewParser[MockParsable](nil)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := parser.ParseString(smallXML)
|
||||
if err != nil {
|
||||
b.Errorf("Parse error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkParseBytes benchmarks parsing from byte slice
|
||||
func BenchmarkParseBytes(b *testing.B) {
|
||||
parser := NewParser[MockParsable](nil)
|
||||
xmlData := []byte(smallXML)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := parser.ParseBytes(xmlData)
|
||||
if err != nil {
|
||||
b.Errorf("Parse error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkParse benchmarks parsing from reader
|
||||
func BenchmarkParse(b *testing.B) {
|
||||
parser := NewParser[MockParsable](nil)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
reader := strings.NewReader(smallXML)
|
||||
_, err := parser.Parse(reader)
|
||||
if err != nil {
|
||||
b.Errorf("Parse error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkParseSmallXML benchmarks parsing small XML documents
|
||||
func BenchmarkParseSmallXML(b *testing.B) {
|
||||
parser := NewParser[MockParsable](nil)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := parser.ParseString(smallXML)
|
||||
if err != nil {
|
||||
b.Errorf("Parse error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkParseMediumXML benchmarks parsing medium XML documents
|
||||
func BenchmarkParseMediumXML(b *testing.B) {
|
||||
parser := NewParser[MockParsable](nil)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := parser.ParseString(mediumXML)
|
||||
if err != nil {
|
||||
b.Errorf("Parse error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkParseLargeXML benchmarks parsing large XML documents
|
||||
func BenchmarkParseLargeXML(b *testing.B) {
|
||||
parser := NewParser[MockParsable](nil)
|
||||
largeXML := generateLargeXML()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := parser.ParseString(largeXML)
|
||||
if err != nil {
|
||||
b.Errorf("Parse error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkParseWithResult benchmarks parsing with detailed results
|
||||
func BenchmarkParseWithResult(b *testing.B) {
|
||||
parser := NewParser[MockParsable](nil)
|
||||
genericParser := parser.(*GenericParser[MockParsable])
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
reader := strings.NewReader(mediumXML)
|
||||
_, err := genericParser.ParseWithResult(reader)
|
||||
if err != nil {
|
||||
b.Errorf("Parse error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkValidation benchmarks XML validation
|
||||
func BenchmarkValidation(b *testing.B) {
|
||||
parser := NewParser[MockParsable](nil)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
reader := strings.NewReader(mediumXML)
|
||||
err := parser.Validate(reader)
|
||||
if err != nil {
|
||||
b.Errorf("Validation error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkConcurrentParsing benchmarks concurrent parsing performance
|
||||
func BenchmarkConcurrentParsing(b *testing.B) {
|
||||
parser := NewParser[MockParsable](nil)
|
||||
|
||||
b.ResetTimer()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_, err := parser.ParseString(smallXML)
|
||||
if err != nil {
|
||||
b.Errorf("Parse error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// BenchmarkConcurrentParsingWithWaitGroup benchmarks coordinated concurrent parsing
|
||||
func BenchmarkConcurrentParsingWithWaitGroup(b *testing.B) {
|
||||
parser := NewParser[MockParsable](nil)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
var wg sync.WaitGroup
|
||||
numGoroutines := 4
|
||||
|
||||
for j := 0; j < numGoroutines; j++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
_, err := parser.ParseString(smallXML)
|
||||
if err != nil {
|
||||
b.Errorf("Parse error: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkMemoryAllocation benchmarks memory allocation patterns
|
||||
func BenchmarkMemoryAllocation(b *testing.B) {
|
||||
parser := NewParser[MockParsable](nil)
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
result, err := parser.ParseString(mediumXML)
|
||||
if err != nil {
|
||||
b.Errorf("Parse error: %v", err)
|
||||
}
|
||||
// Use result to prevent optimization
|
||||
_ = result.GetID()
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkMemoryAllocationsLarge benchmarks memory allocations for large documents
|
||||
func BenchmarkMemoryAllocationsLarge(b *testing.B) {
|
||||
parser := NewParser[MockParsable](nil)
|
||||
largeXML := generateLargeXML()
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
result, err := parser.ParseString(largeXML)
|
||||
if err != nil {
|
||||
b.Errorf("Parse error: %v", err)
|
||||
}
|
||||
// Use result to prevent optimization
|
||||
_ = result.GetID()
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkSanitizeXMLString benchmarks XML string sanitization
|
||||
func BenchmarkSanitizeXMLString(b *testing.B) {
|
||||
input := "Test string with\x00invalid\x01characters\x02and normal text"
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
result := SanitizeXMLString(input)
|
||||
// Use result to prevent optimization
|
||||
_ = len(result)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkValidateXMLName benchmarks XML name validation
|
||||
func BenchmarkValidateXMLName(b *testing.B) {
|
||||
testNames := []string{
|
||||
"ValidElement",
|
||||
"invalid-element-123",
|
||||
"_privateElement",
|
||||
"123Invalid",
|
||||
"element-with-hyphen",
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
name := testNames[i%len(testNames)]
|
||||
result := ValidateXMLName(name)
|
||||
// Use result to prevent optimization
|
||||
_ = result
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkIsValidNamespace benchmarks namespace validation
|
||||
func BenchmarkIsValidNamespace(b *testing.B) {
|
||||
testNamespaces := []string{
|
||||
"http://example.com",
|
||||
"https://ted.europa.eu/schema",
|
||||
"urn:oasis:names:specification:ubl",
|
||||
"invalid-namespace",
|
||||
"",
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
namespace := testNamespaces[i%len(testNamespaces)]
|
||||
result := IsValidNamespace(namespace)
|
||||
// Use result to prevent optimization
|
||||
_ = result
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkNormalizeWhitespace benchmarks whitespace normalization
|
||||
func BenchmarkNormalizeWhitespace(b *testing.B) {
|
||||
input := " Multiple \t\n whitespace\r\n characters "
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
result := NormalizeWhitespace(input)
|
||||
// Use result to prevent optimization
|
||||
_ = len(result)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkTruncateString benchmarks string truncation
|
||||
func BenchmarkTruncateString(b *testing.B) {
|
||||
input := "This is a very long string that needs to be truncated for display purposes"
|
||||
maxLength := 50
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
result := TruncateString(input, maxLength)
|
||||
// Use result to prevent optimization
|
||||
_ = len(result)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkParserCreation benchmarks parser creation
|
||||
func BenchmarkParserCreation(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
parser := NewParser[MockParsable](nil)
|
||||
// Use parser to prevent optimization
|
||||
_ = parser
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkParserCreationWithOptions benchmarks parser creation with custom options
|
||||
func BenchmarkParserCreationWithOptions(b *testing.B) {
|
||||
options := &ParserOptions{
|
||||
StrictValidation: false,
|
||||
AllowEmptyElements: true,
|
||||
PreserveWhitespace: false,
|
||||
IgnoreUnknownElements: true,
|
||||
MaxDepth: 100,
|
||||
MaxSize: 10 * 1024 * 1024,
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
parser := NewParser[MockParsable](options)
|
||||
// Use parser to prevent optimization
|
||||
_ = parser
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkValidationError benchmarks validation error creation
|
||||
func BenchmarkValidationError(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
err := ValidationError{
|
||||
Field: "testField",
|
||||
Message: "test error message",
|
||||
Value: "test value",
|
||||
}
|
||||
// Use error to prevent optimization
|
||||
_ = err.Error()
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkValidationErrors benchmarks multiple validation errors
|
||||
func BenchmarkValidationErrors(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
var errors ValidationErrors
|
||||
errors.Add("field1", "error1", "value1")
|
||||
errors.Add("field2", "error2", "value2")
|
||||
errors.Add("field3", "error3", "value3")
|
||||
|
||||
// Use errors to prevent optimization
|
||||
_ = errors.Error()
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkMockValidation benchmarks mock object validation
|
||||
func BenchmarkMockValidation(b *testing.B) {
|
||||
mock := MockParsable{
|
||||
ID: "test-123",
|
||||
Name: "Test Mock",
|
||||
Value: "test value",
|
||||
Valid: true,
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
err := mock.Validate()
|
||||
// Use error to prevent optimization
|
||||
_ = err
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkDifferentXMLSizes compares performance across different document sizes
|
||||
func BenchmarkDifferentXMLSizes(b *testing.B) {
|
||||
parser := NewParser[MockParsable](nil)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
xml string
|
||||
}{
|
||||
{"Small", smallXML},
|
||||
{"Medium", mediumXML},
|
||||
}
|
||||
|
||||
// Add large XML dynamically to avoid memory overhead during compilation
|
||||
largeXML := generateLargeXML()
|
||||
testCases = append(testCases, struct {
|
||||
name string
|
||||
xml string
|
||||
}{"Large", largeXML})
|
||||
|
||||
for _, tc := range testCases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := parser.ParseString(tc.xml)
|
||||
if err != nil {
|
||||
b.Errorf("Parse error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkParsingMethods compares different parsing methods
|
||||
func BenchmarkParsingMethods(b *testing.B) {
|
||||
parser := NewParser[MockParsable](nil)
|
||||
xmlData := []byte(mediumXML)
|
||||
|
||||
b.Run("ParseString", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := parser.ParseString(mediumXML)
|
||||
if err != nil {
|
||||
b.Errorf("Parse error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("ParseBytes", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := parser.ParseBytes(xmlData)
|
||||
if err != nil {
|
||||
b.Errorf("Parse error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("Parse", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
reader := bytes.NewReader(xmlData)
|
||||
_, err := parser.Parse(reader)
|
||||
if err != nil {
|
||||
b.Errorf("Parse error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// BenchmarkUtilityFunctions benchmarks all utility functions together
|
||||
func BenchmarkUtilityFunctions(b *testing.B) {
|
||||
testData := struct {
|
||||
dirtyString string
|
||||
xmlName string
|
||||
namespace string
|
||||
whitespace string
|
||||
longString string
|
||||
}{
|
||||
dirtyString: "Dirty\x00string\x01with\x02control\x03characters",
|
||||
xmlName: "ValidElementName",
|
||||
namespace: "http://example.com/namespace",
|
||||
whitespace: " Multiple \t\n spaces ",
|
||||
longString: "This is a very long string that needs truncation for testing purposes",
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
// Run all utility functions
|
||||
_ = SanitizeXMLString(testData.dirtyString)
|
||||
_ = ValidateXMLName(testData.xmlName)
|
||||
_ = IsValidNamespace(testData.namespace)
|
||||
_ = NormalizeWhitespace(testData.whitespace)
|
||||
_ = TruncateString(testData.longString, 50)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkParserOptionsEffects benchmarks parsing with different options
|
||||
func BenchmarkParserOptionsEffects(b *testing.B) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
options *ParserOptions
|
||||
}{
|
||||
{
|
||||
name: "Default",
|
||||
options: nil,
|
||||
},
|
||||
{
|
||||
name: "StrictDisabled",
|
||||
options: &ParserOptions{
|
||||
StrictValidation: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "PreserveWhitespace",
|
||||
options: &ParserOptions{
|
||||
PreserveWhitespace: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "IgnoreUnknown",
|
||||
options: &ParserOptions{
|
||||
IgnoreUnknownElements: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
parser := NewParser[MockParsable](tc.options)
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := parser.ParseString(mediumXML)
|
||||
if err != nil {
|
||||
b.Errorf("Parse error: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkErrorHandling benchmarks error creation and handling
|
||||
func BenchmarkErrorHandling(b *testing.B) {
|
||||
b.Run("ValidationError", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
err := ValidationError{
|
||||
Field: "testField",
|
||||
Message: "test message",
|
||||
Value: "test value",
|
||||
}
|
||||
_ = err.Error()
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("XMLParseError", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
err := XMLParseError{
|
||||
Line: 10,
|
||||
Column: 5,
|
||||
Element: "TestElement",
|
||||
Message: "test parse error",
|
||||
}
|
||||
_ = err.Error()
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("ValidationErrors", func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
var errors ValidationErrors
|
||||
errors.Add("field1", "message1", "value1")
|
||||
errors.Add("field2", "message2", "value2")
|
||||
_ = errors.Error()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// BenchmarkMemoryEfficiency measures memory efficiency across operations
|
||||
func BenchmarkMemoryEfficiency(b *testing.B) {
|
||||
parser := NewParser[MockParsable](nil)
|
||||
|
||||
b.Run("SmallDocuments", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
result, err := parser.ParseString(smallXML)
|
||||
if err != nil {
|
||||
b.Errorf("Parse error: %v", err)
|
||||
}
|
||||
// Ensure result is used
|
||||
_ = result.GetID()
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("MediumDocuments", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
result, err := parser.ParseString(mediumXML)
|
||||
if err != nil {
|
||||
b.Errorf("Parse error: %v", err)
|
||||
}
|
||||
// Ensure result is used
|
||||
_ = result.GetID()
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user