Comprehensive testing and analysis of md-explode ↔ md-implode roundtrip functionality: ## Test Infrastructure Created - 77 comprehensive tests covering all roundtrip scenarios - 4 simplified tests for behavior analysis and documentation - Automated content preservation analysis and reporting - Error handling and edge case validation ## Key Findings - ✅ Both commands execute successfully as individual tools - ✅ Complete functionality for unidirectional use cases - ⚠️ Content duplication prevents lossless bidirectional roundtrips - 📊 0% perfect match rate due to overlapping file architecture ## Analysis Results - md-explode creates overlapping content in hierarchical files - md-implode processes all files independently, causing duplication - Content growth factor: 1.5-2.7x in typical roundtrip scenarios - Root cause: Architectural incompatibility between commands ## Deliverables - Comprehensive roundtrip test suite (test_issue_140_roundtrip.py) - Simplified behavior analysis tests (test_issue_140_roundtrip_simplified.py) - Detailed analysis report (ISSUE_140_ROUNDTRIP_ANALYSIS.md) - Usage guidelines and recommendations for users ## Recommendations - Document limitations in command help text - Provide clear usage guidelines for unidirectional workflows - Consider architectural improvements for future versions Commands work excellently individually but require careful usage for roundtrip scenarios. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
371 lines
12 KiB
Python
371 lines
12 KiB
Python
"""
|
|
Simplified roundtrip tests for Issue #140: md-explode and md-implode compatibility.
|
|
|
|
Tests the actual behavior of explode→implode and implode→explode roundtrips
|
|
to document current functionality and identify any issues.
|
|
"""
|
|
|
|
import pytest
|
|
import tempfile
|
|
import shutil
|
|
import subprocess
|
|
from pathlib import Path
|
|
from textwrap import dedent
|
|
|
|
|
|
class TestActualRoundtripBehavior:
|
|
"""Test actual roundtrip behavior to document current functionality."""
|
|
|
|
def setup_method(self):
|
|
"""Set up temporary directory for each test."""
|
|
self.temp_dir = Path(tempfile.mkdtemp())
|
|
|
|
def teardown_method(self):
|
|
"""Clean up temporary directory after each test."""
|
|
if self.temp_dir.exists():
|
|
shutil.rmtree(self.temp_dir)
|
|
|
|
def run_markitect_command(self, args, check=True):
|
|
"""Helper to run markitect commands."""
|
|
cmd = ["python", "-m", "markitect.cli"] + args
|
|
result = subprocess.run(
|
|
cmd,
|
|
cwd="/home/worsch/markitect_project",
|
|
capture_output=True,
|
|
text=True
|
|
)
|
|
if check and result.returncode != 0:
|
|
pytest.fail(f"Command failed: {' '.join(args)}\nStdout: {result.stdout}\nStderr: {result.stderr}")
|
|
return result
|
|
|
|
def test_explode_then_implode_basic(self):
|
|
"""Test basic explode→implode sequence and document behavior."""
|
|
|
|
# Create a simple markdown file
|
|
original_content = dedent("""
|
|
# Test Document
|
|
|
|
Introduction content.
|
|
|
|
## Chapter 1
|
|
|
|
Chapter 1 content.
|
|
|
|
### Section 1.1
|
|
|
|
Section 1.1 content.
|
|
|
|
## Chapter 2
|
|
|
|
Chapter 2 content.
|
|
""").strip()
|
|
|
|
original_file = self.temp_dir / "test.md"
|
|
original_file.write_text(original_content)
|
|
|
|
# Step 1: Explode to directory
|
|
exploded_dir = self.temp_dir / "test_exploded"
|
|
result = self.run_markitect_command([
|
|
"md-explode", str(original_file),
|
|
"--output-dir", str(exploded_dir),
|
|
"--verbose"
|
|
])
|
|
assert result.returncode == 0
|
|
assert exploded_dir.exists()
|
|
|
|
# Document what explode actually creates
|
|
files_created = []
|
|
for md_file in exploded_dir.rglob("*.md"):
|
|
files_created.append(str(md_file.relative_to(exploded_dir)))
|
|
|
|
print(f"Files created by md-explode: {files_created}")
|
|
|
|
# Step 2: Implode back to markdown
|
|
imploded_file = self.temp_dir / "imploded.md"
|
|
result = self.run_markitect_command([
|
|
"md-implode", str(exploded_dir),
|
|
"--output", str(imploded_file),
|
|
"--verbose"
|
|
])
|
|
assert result.returncode == 0
|
|
assert imploded_file.exists()
|
|
|
|
# Document the result
|
|
imploded_content = imploded_file.read_text()
|
|
print(f"Original content length: {len(original_content)}")
|
|
print(f"Imploded content length: {len(imploded_content)}")
|
|
|
|
# Basic verification - key headings should be present
|
|
assert "# Test Document" in imploded_content
|
|
assert "## Chapter 1" in imploded_content
|
|
assert "### Section 1.1" in imploded_content
|
|
assert "## Chapter 2" in imploded_content
|
|
|
|
# Save both for comparison
|
|
comparison_file = self.temp_dir / "comparison.txt"
|
|
comparison_content = f"""
|
|
ORIGINAL CONTENT:
|
|
{'-' * 50}
|
|
{original_content}
|
|
|
|
IMPLODED CONTENT:
|
|
{'-' * 50}
|
|
{imploded_content}
|
|
|
|
CONTENT MATCH: {original_content.strip() == imploded_content.strip()}
|
|
"""
|
|
comparison_file.write_text(comparison_content)
|
|
print(f"Comparison saved to: {comparison_file}")
|
|
|
|
def test_directory_implode_then_explode(self):
|
|
"""Test directory→markdown→directory sequence."""
|
|
|
|
# Create a directory structure manually
|
|
base_dir = self.temp_dir / "manual_structure"
|
|
base_dir.mkdir()
|
|
|
|
# Create files
|
|
(base_dir / "intro.md").write_text("# Manual Structure\n\nIntroduction content.")
|
|
|
|
chapter_dir = base_dir / "chapter_1"
|
|
chapter_dir.mkdir()
|
|
(chapter_dir / "index.md").write_text("## Chapter 1\n\nChapter content.")
|
|
(chapter_dir / "section_1.md").write_text("### Section 1\n\nSection content.")
|
|
|
|
# Step 1: Implode directory to markdown
|
|
imploded_file = self.temp_dir / "manual_imploded.md"
|
|
result = self.run_markitect_command([
|
|
"md-implode", str(base_dir),
|
|
"--output", str(imploded_file),
|
|
"--verbose"
|
|
])
|
|
assert result.returncode == 0
|
|
|
|
imploded_content = imploded_file.read_text()
|
|
print(f"Directory imploded to {len(imploded_content)} characters")
|
|
|
|
# Step 2: Explode back to directory
|
|
re_exploded_dir = self.temp_dir / "re_exploded"
|
|
result = self.run_markitect_command([
|
|
"md-explode", str(imploded_file),
|
|
"--output-dir", str(re_exploded_dir),
|
|
"--verbose"
|
|
])
|
|
assert result.returncode == 0
|
|
|
|
# Document the results
|
|
original_files = list(base_dir.rglob("*.md"))
|
|
re_exploded_files = list(re_exploded_dir.rglob("*.md"))
|
|
|
|
print(f"Original files: {len(original_files)}")
|
|
print(f"Re-exploded files: {len(re_exploded_files)}")
|
|
|
|
# Basic verification
|
|
assert len(re_exploded_files) > 0
|
|
|
|
# Check that key content is preserved
|
|
all_re_exploded_content = ""
|
|
for file in re_exploded_files:
|
|
all_re_exploded_content += file.read_text() + "\n"
|
|
|
|
assert "# Manual Structure" in all_re_exploded_content
|
|
assert "## Chapter 1" in all_re_exploded_content
|
|
assert "### Section 1" in all_re_exploded_content
|
|
|
|
def test_content_preservation_analysis(self):
|
|
"""Analyze what content is preserved and what changes in roundtrips."""
|
|
|
|
# Test various content types
|
|
test_cases = [
|
|
{
|
|
"name": "simple_hierarchy",
|
|
"content": dedent("""
|
|
# Main Title
|
|
|
|
Introduction.
|
|
|
|
## Section
|
|
|
|
Section content.
|
|
""").strip()
|
|
},
|
|
{
|
|
"name": "deep_hierarchy",
|
|
"content": dedent("""
|
|
# Level 1
|
|
|
|
Content 1.
|
|
|
|
## Level 2
|
|
|
|
Content 2.
|
|
|
|
### Level 3
|
|
|
|
Content 3.
|
|
|
|
#### Level 4
|
|
|
|
Content 4.
|
|
""").strip()
|
|
},
|
|
{
|
|
"name": "multiple_sections",
|
|
"content": dedent("""
|
|
# Document
|
|
|
|
Intro.
|
|
|
|
## Part 1
|
|
|
|
Part 1 content.
|
|
|
|
## Part 2
|
|
|
|
Part 2 content.
|
|
|
|
## Part 3
|
|
|
|
Part 3 content.
|
|
""").strip()
|
|
}
|
|
]
|
|
|
|
results = []
|
|
|
|
for test_case in test_cases:
|
|
# Create original file
|
|
original_file = self.temp_dir / f"{test_case['name']}.md"
|
|
original_file.write_text(test_case['content'])
|
|
|
|
# Explode → Implode
|
|
exploded_dir = self.temp_dir / f"{test_case['name']}_exploded"
|
|
self.run_markitect_command([
|
|
"md-explode", str(original_file),
|
|
"--output-dir", str(exploded_dir)
|
|
])
|
|
|
|
imploded_file = self.temp_dir / f"{test_case['name']}_imploded.md"
|
|
self.run_markitect_command([
|
|
"md-implode", str(exploded_dir),
|
|
"--output", str(imploded_file)
|
|
])
|
|
|
|
imploded_content = imploded_file.read_text()
|
|
|
|
# Analyze results
|
|
result = {
|
|
"test_case": test_case['name'],
|
|
"original_length": len(test_case['content']),
|
|
"imploded_length": len(imploded_content),
|
|
"content_match": test_case['content'].strip() == imploded_content.strip(),
|
|
"files_created": len(list(exploded_dir.rglob("*.md")))
|
|
}
|
|
results.append(result)
|
|
|
|
print(f"Test case '{test_case['name']}': {result}")
|
|
|
|
# Summary
|
|
total_tests = len(results)
|
|
perfect_matches = sum(1 for r in results if r['content_match'])
|
|
|
|
print(f"\nSUMMARY:")
|
|
print(f"Total tests: {total_tests}")
|
|
print(f"Perfect matches: {perfect_matches}")
|
|
print(f"Success rate: {perfect_matches/total_tests:.1%}")
|
|
|
|
# At least some basic functionality should work
|
|
assert total_tests > 0
|
|
assert any(len(list((self.temp_dir / f"{r['test_case']}_exploded").rglob("*.md"))) > 0 for r in results)
|
|
|
|
def test_roundtrip_functionality_status(self):
|
|
"""Document the current status of roundtrip functionality."""
|
|
|
|
# Test a representative case
|
|
content = dedent("""
|
|
# Test Document
|
|
|
|
Introduction paragraph.
|
|
|
|
## Chapter One
|
|
|
|
First chapter content.
|
|
|
|
### Section A
|
|
|
|
Section A details.
|
|
|
|
### Section B
|
|
|
|
Section B details.
|
|
|
|
## Chapter Two
|
|
|
|
Second chapter content.
|
|
""").strip()
|
|
|
|
original_file = self.temp_dir / "status_test.md"
|
|
original_file.write_text(content)
|
|
|
|
# Test explode
|
|
exploded_dir = self.temp_dir / "status_exploded"
|
|
explode_result = self.run_markitect_command([
|
|
"md-explode", str(original_file),
|
|
"--output-dir", str(exploded_dir),
|
|
"--verbose"
|
|
], check=False)
|
|
|
|
explode_success = explode_result.returncode == 0
|
|
files_created = len(list(exploded_dir.rglob("*.md"))) if exploded_dir.exists() else 0
|
|
|
|
# Test implode if explode succeeded
|
|
implode_success = False
|
|
content_match = False
|
|
|
|
if explode_success:
|
|
imploded_file = self.temp_dir / "status_imploded.md"
|
|
implode_result = self.run_markitect_command([
|
|
"md-implode", str(exploded_dir),
|
|
"--output", str(imploded_file),
|
|
"--verbose"
|
|
], check=False)
|
|
|
|
implode_success = implode_result.returncode == 0
|
|
|
|
if implode_success:
|
|
imploded_content = imploded_file.read_text().strip()
|
|
content_match = content == imploded_content
|
|
|
|
# Create status report
|
|
status_report = f"""
|
|
ROUNDTRIP FUNCTIONALITY STATUS REPORT
|
|
=====================================
|
|
|
|
Original content: {len(content)} characters
|
|
Explode success: {explode_success}
|
|
Files created: {files_created}
|
|
Implode success: {implode_success}
|
|
Perfect content match: {content_match}
|
|
|
|
OVERALL STATUS: {'✅ WORKING' if explode_success and implode_success else '⚠️ ISSUES DETECTED'}
|
|
|
|
RECOMMENDATIONS:
|
|
{'- Roundtrip functionality is operational' if content_match else '- Content duplication or loss detected in roundtrip'}
|
|
{'- Both commands execute successfully' if explode_success and implode_success else '- Command execution issues detected'}
|
|
- Further testing recommended for production use
|
|
- Document any known limitations for users
|
|
"""
|
|
|
|
print(status_report)
|
|
|
|
status_file = self.temp_dir / "roundtrip_status_report.txt"
|
|
status_file.write_text(status_report)
|
|
|
|
# Basic assertions for functionality
|
|
assert explode_success, "md-explode should execute successfully"
|
|
assert implode_success, "md-implode should execute successfully"
|
|
assert files_created > 0, "md-explode should create files"
|
|
|
|
|
|
if __name__ == "__main__":
|
|
pytest.main([__file__, "-v", "-s"]) # -s to show print statements |