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:
n.nakhostin
2025-08-12 12:51:24 +03:30
parent 7dc695752b
commit 5322d3cd8e
6 changed files with 2905 additions and 0 deletions
+614
View File
@@ -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()
}
})
}