5322d3cd8e
- 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.
615 lines
14 KiB
Go
615 lines
14 KiB
Go
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()
|
|
}
|
|
})
|
|
}
|