benchkit 0.26.1

Lightweight benchmarking toolkit focused on practical performance analysis and report generation. Non-restrictive alternative to criterion, designed for easy integration and markdown report generation.
Documentation
# Fix MarkdownUpdater Section Duplication Bug

## Problem Summary

The `MarkdownUpdater` class in benchkit 0.5.0 has a critical bug where it creates duplicate sections instead of properly replacing existing ones. This causes exponential file growth and makes generated documentation unusable.

## Impact Assessment

- **Severity**: Critical - renders benchkit unusable for documentation
- **Scope**: All users who run benchmarks multiple times
- **Growth Pattern**: File size grows exponentially with each benchmark run
- **Real Example**: Generated readme.md went from 117 lines to 11,571 lines (99x growth)

## Detailed Problem Analysis

### Root Cause
The current `MarkdownUpdater::update_section()` method fails to properly identify and replace existing sections when:
1. Multiple consecutive identical section headers exist
2. Section content spans multiple lines
3. Sections are updated multiple times

### Current Behavior (Buggy)
```rust
// Current implementation creates duplicates
let updater = MarkdownUpdater::new("readme.md", "Performance Results");
updater.update_section("New data")?;  // First run: works
updater.update_section("Updated data")?;  // Second run: creates duplicate
```

Results in:
```markdown
## Performance Results

New data

## Performance Results  

Updated data
```

## Minimal Reproducible Example (MRE)

```rust
use benchkit::reporting::MarkdownUpdater;
use std::fs;

#[test]
fn test_markdown_updater_duplication_bug() -> Result<(), Box<dyn std::error::Error>> 
{
    // Create initial markdown file
    fs::write("test.md", "# Test\n\n## Results\n\nInitial content\n\n## Other\n\nOther data")?;
    
    let updater = MarkdownUpdater::new("test.md", "Results")?;
    
    // First update - should work correctly
    updater.update_section("First update")?;
    let content1 = fs::read_to_string("test.md")?;
    let count1 = content1.matches("## Results").count();
    assert_eq!(count1, 1, "Should have exactly 1 Results section after first update");
    
    // Second update - this creates a duplicate (BUG)
    updater.update_section("Second update")?;
    let content2 = fs::read_to_string("test.md")?;
    let count2 = content2.matches("## Results").count();
    
    // This assertion FAILS with current benchkit 0.5.0
    assert_eq!(count2, 1, "Should still have exactly 1 Results section after second update, but got {}", count2);
    
    Ok(())
}
```

## Evidence from Real Usage

### Before Fix Needed
```bash
$ wc -l readme.md
11571 readme.md

$ grep -c "## Performance Benchmarks" readme.md  
10

$ grep -c "## Processing Methods Comparison" readme.md
25
```

### After Proper Fix Should Be
```bash
$ wc -l readme.md
117 readme.md

$ grep -c "## Performance Benchmarks" readme.md
1

$ grep -c "## Processing Methods Comparison" readme.md  
1
```

## Proposed Solution

### Option 1: Fix Section Matching Logic (Recommended)

Improve the section identification and replacement logic:

```rust
impl MarkdownUpdater 
{
    pub fn update_section(&self, content: &str) -> Result<()> 
{
        let existing_content = fs::read_to_string(&self.file_path)?;
        let lines: Vec<&str> = existing_content.lines().collect();
        let mut result_lines = Vec::new();
        let mut i = 0;
        let mut section_found = false;
        let section_header = format!("## {}", self.section_name);

        while i < lines.len() {
            let line = lines[i];
            
            if line.starts_with(&section_header) {
                if section_found {
                    // Skip this duplicate section entirely
                    i += 1;
                    // Skip until next ## section or end of file
                    while i < lines.len() && !lines[i].starts_with("## ") {
                        i += 1;
                    }
                    continue;
                }
                
                // First occurrence - replace with new content
                section_found = true;
                result_lines.push(line.to_string());
                result_lines.push(String::new());
                result_lines.push(content.to_string());
                result_lines.push(String::new());
                
                // Skip the old section content
                i += 1;
                while i < lines.len() && !lines[i].starts_with("## ") {
                    i += 1;
                }
                continue;
            }
            
            result_lines.push(line.to_string());
            i += 1;
        }

        // If section wasn't found, add it at the end
        if !section_found {
            if !result_lines.is_empty() && !result_lines.last().unwrap().is_empty() {
                result_lines.push(String::new());
            }
            result_lines.push(section_header);
            result_lines.push(String::new());
            result_lines.push(content.to_string());
            result_lines.push(String::new());
        }

        let final_content = result_lines.join("\n");
        fs::write(&self.file_path, final_content)?;
        
        Ok(())
    }
}
```

### Option 2: Add Duplication Detection

Add validation to detect and prevent duplicates:

```rust
impl MarkdownUpdater 
{
    fn validate_no_duplicates(&self) -> Result<()> 
{
        let content = fs::read_to_string(&self.file_path)?;
        let section_header = format!("## {}", self.section_name);
        let count = content.matches(&section_header).count();
        
        if count > 1 {
            return Err(MarkdownError::DuplicateSection {
                section: self.section_name.clone(),
                count,
            });
        }
        
        Ok(())
    }
    
    pub fn update_section(&self, content: &str) -> Result<()> 
{
        // ... existing update logic ...
        
        // Validate result
        self.validate_no_duplicates()?;
        Ok(())
    }
}
```

## Test Cases Required

1. **Basic Replacement**: Single section update works correctly
2. **Multiple Updates**: Consecutive updates don't create duplicates  
3. **Consecutive Headers**: Handle multiple identical headers correctly
4. **Section Not Found**: Properly append new sections
5. **Empty Content**: Handle empty files gracefully
6. **Edge Cases**: Files ending without newlines, sections at end of file

## Acceptance Criteria

- [ ] `MarkdownUpdater` never creates duplicate sections
- [ ] Multiple `update_section()` calls on same section work correctly
- [ ] File size remains bounded (doesn't grow exponentially)
- [ ] All existing functionality preserved
- [ ] Comprehensive test suite covers edge cases
- [ ] Performance remains acceptable for large files

## References

- **Original Issue**: benchkit 0.5.0 MarkdownUpdater creates duplicate sections
- **Affected Component**: `src/reporting.rs` - MarkdownUpdater implementation
- **Priority**: Critical (blocks usage of benchkit for documentation)

## Additional Context

This bug makes benchkit unusable for any project that runs benchmarks multiple times, as the generated documentation becomes corrupted with massive duplication. The issue was discovered during comprehensive testing of wflow's benchmark integration where a 117-line readme.md grew to 11,571 lines after multiple benchmark runs.

The proposed solution ensures proper section replacement while maintaining full API compatibility and performance.

## Current Status

- **Issue Identified**: December 2024 during wflow benchmark integration
- **Workaround**: Temporarily created SafeMarkdownUpdater in wflow project (now removed)
- **Task Created**: Comprehensive task file with MRE and solution proposals
- **Implementation**: ✅ **COMPLETED** - Bug has been fixed in current codebase
- **Testing**: ✅ **COMPLETED** - Comprehensive test suite added and all tests pass

## Implementation Outcomes

### **Bug Resolution Confirmed**
The MarkdownUpdater duplication bug has been **successfully resolved** in the current benchkit codebase. Verification completed through:

1. **MRE Test Implementation**: Created comprehensive test cases based on the original task specification
2. **Multiple Update Verification**: Confirmed that consecutive `update_section()` calls properly replace content without creating duplicates
3. **Exponential Growth Prevention**: Verified that file sizes remain bounded and don't exhibit exponential growth
4. **Edge Case Coverage**: All edge cases from the original specification now pass

### **Test Suite Results**
```bash
# All tests pass successfully
test test_markdown_updater_duplication_bug ... ok
test test_consecutive_updates_no_growth ... ok
```

### **Technical Implementation**
The fix is implemented in `/home/user1/pro/lib/wTools/module/move/benchkit/src/reporting.rs:180-222` with:
- Proper section boundary detection
- State tracking for section replacement  
- Prevention of duplicate section creation
- Comprehensive error handling

### **Quality Assurance**
- **No regressions**: All existing functionality preserved
- **Performance**: No performance degradation observed
- **API compatibility**: Full backward compatibility maintained
- **Code quality**: Follows wTools codestyle rules with 2-space indentation

## Notes for Implementation

The section detection logic in `src/reporting.rs` has been properly implemented with state tracking for section boundaries, preventing the duplicate section creation that was originally reported.