Skip to content

Commit ccd272a

Browse files
authored
Merge PR #130: Add configurable file-size limit to prevent OOM
2 parents 1bde75e + ece99e8 commit ccd272a

28 files changed

Lines changed: 1199 additions & 219 deletions

File tree

PLAN.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ footer: |
3838
| 78 | 🔲 | [Query subcommand for front-matter filtering](plan/78_query-command.md) |
3939
| 79 || [Nested front-matter access](plan/79_nested-frontmatter-access.md) |
4040
| 80 || [Terminal recording in README](plan/80_terminal-recording-readme.md) |
41-
| 81 | 🔲 | [OOM mitigation: configurable file-size limit](plan/81_oom-file-size-limit.md) |
41+
| 81 | | [OOM mitigation: configurable file-size limit](plan/81_oom-file-size-limit.md) |
4242
| 82 || [YAML billion-laughs mitigation](plan/82_yaml-billion-laughs.md) |
4343
| 83 | 🔳 | [Security hardening batch](plan/83_security-hardening-batch.md) |
4444
| 84 | 🔲 | [Symlink default-deny for file discovery](plan/84_symlink-default-deny.md) |

cmd/mdsmith/e2e_test.go

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1390,3 +1390,175 @@ func TestE2E_MergeDriver_SectionMarkersInsideConflict_Preserved(t *testing.T) {
13901390
assert.Contains(t, content, "=======", "expected ======= separator preserved")
13911391
assert.Contains(t, content, ">>>>>>>", "expected >>>>>>> marker preserved")
13921392
}
1393+
1394+
// ── max-input-size ──────────────────────────────────────────────
1395+
1396+
func TestCheck_MaxInputSize_ExceedingLimit(t *testing.T) {
1397+
dir := t.TempDir()
1398+
isolateDir(t, dir)
1399+
1400+
// Create a file larger than 100 bytes.
1401+
bigContent := make([]byte, 200)
1402+
for i := range bigContent {
1403+
bigContent[i] = 'x'
1404+
}
1405+
bigContent[0] = '#'
1406+
bigContent[1] = ' '
1407+
writeFixture(t, dir, "big.md", string(bigContent))
1408+
1409+
_, stderr, exitCode := runBinaryInDir(t, dir, "",
1410+
"check", "--max-input-size", "100", "big.md")
1411+
assert.Equal(t, 2, exitCode, "expected exit code 2 for oversized file")
1412+
assert.Contains(t, stderr, "file too large")
1413+
assert.Contains(t, stderr, "max 100")
1414+
}
1415+
1416+
func TestCheck_MaxInputSize_UnderLimit(t *testing.T) {
1417+
dir := t.TempDir()
1418+
isolateDir(t, dir)
1419+
1420+
writeFixture(t, dir, "small.md", "# Hello\n")
1421+
1422+
_, _, exitCode := runBinaryInDir(t, dir, "",
1423+
"check", "--max-input-size", "2MB", "small.md")
1424+
assert.Equal(t, 0, exitCode, "expected exit code 0 for small file")
1425+
}
1426+
1427+
func TestCheck_MaxInputSize_Unlimited(t *testing.T) {
1428+
dir := t.TempDir()
1429+
isolateDir(t, dir)
1430+
1431+
writeFixture(t, dir, "any.md", "# Hello\n")
1432+
1433+
_, _, exitCode := runBinaryInDir(t, dir, "",
1434+
"check", "--max-input-size", "0", "any.md")
1435+
assert.Equal(t, 0, exitCode, "expected exit code 0 with unlimited size")
1436+
}
1437+
1438+
func TestFix_MaxInputSize_ExceedingLimit(t *testing.T) {
1439+
dir := t.TempDir()
1440+
isolateDir(t, dir)
1441+
1442+
bigContent := make([]byte, 200)
1443+
for i := range bigContent {
1444+
bigContent[i] = 'x'
1445+
}
1446+
bigContent[0] = '#'
1447+
bigContent[1] = ' '
1448+
writeFixture(t, dir, "big.md", string(bigContent))
1449+
1450+
_, stderr, exitCode := runBinaryInDir(t, dir, "",
1451+
"fix", "--max-input-size", "100", "big.md")
1452+
assert.Equal(t, 2, exitCode, "expected exit code 2 for oversized file")
1453+
assert.Contains(t, stderr, "file too large")
1454+
}
1455+
1456+
func TestCheck_MaxInputSize_ConfigOverride(t *testing.T) {
1457+
dir := t.TempDir()
1458+
// Write config with max-input-size: 5 (very small)
1459+
require.NoError(t, os.MkdirAll(filepath.Join(dir, ".git"), 0o755))
1460+
require.NoError(t, os.WriteFile(
1461+
filepath.Join(dir, ".mdsmith.yml"),
1462+
[]byte("rules: {}\nmax-input-size: \"5\"\n"), 0o644,
1463+
))
1464+
1465+
writeFixture(t, dir, "small.md", "# Hello\n")
1466+
1467+
// Config sets 5-byte limit → 8-byte file should fail.
1468+
_, stderr, exitCode := runBinaryInDir(t, dir, "",
1469+
"check", "small.md")
1470+
assert.Equal(t, 2, exitCode, "expected exit code 2 from config limit")
1471+
assert.Contains(t, stderr, "file too large")
1472+
1473+
// CLI flag overrides config → unlimited.
1474+
_, _, exitCode2 := runBinaryInDir(t, dir, "",
1475+
"check", "--max-input-size", "0", "small.md")
1476+
assert.Equal(t, 0, exitCode2, "expected exit code 0 with CLI override to unlimited")
1477+
}
1478+
1479+
func TestCheck_MaxInputSize_InvalidValue(t *testing.T) {
1480+
dir := t.TempDir()
1481+
isolateDir(t, dir)
1482+
writeFixture(t, dir, "a.md", "# Hello\n")
1483+
1484+
_, stderr, exitCode := runBinaryInDir(t, dir, "",
1485+
"check", "--max-input-size", "not-a-size", "a.md")
1486+
assert.Equal(t, 2, exitCode, "expected exit code 2 for invalid size")
1487+
assert.Contains(t, stderr, "invalid max-input-size")
1488+
}
1489+
1490+
func TestFix_MaxInputSize_InvalidValue(t *testing.T) {
1491+
dir := t.TempDir()
1492+
isolateDir(t, dir)
1493+
writeFixture(t, dir, "a.md", "# Hello\n")
1494+
1495+
_, stderr, exitCode := runBinaryInDir(t, dir, "",
1496+
"fix", "--max-input-size", "1TB", "a.md")
1497+
assert.Equal(t, 2, exitCode, "expected exit code 2 for unrecognized unit")
1498+
assert.Contains(t, stderr, "invalid max-input-size")
1499+
}
1500+
1501+
func TestCheckStdin_MaxInputSize_ExceedingLimit(t *testing.T) {
1502+
dir := t.TempDir()
1503+
isolateDir(t, dir)
1504+
1505+
bigContent := make([]byte, 200)
1506+
for i := range bigContent {
1507+
bigContent[i] = 'x'
1508+
}
1509+
bigContent[0] = '#'
1510+
bigContent[1] = ' '
1511+
1512+
_, stderr, exitCode := runBinaryInDir(t, dir, string(bigContent),
1513+
"check", "--max-input-size", "50", "-")
1514+
assert.Equal(t, 2, exitCode, "expected exit code 2 for oversized stdin")
1515+
assert.Contains(t, stderr, "file too large")
1516+
}
1517+
1518+
func TestCheckStdin_MaxInputSize_Unlimited(t *testing.T) {
1519+
dir := t.TempDir()
1520+
isolateDir(t, dir)
1521+
1522+
_, _, exitCode := runBinaryInDir(t, dir, "# Hello\n",
1523+
"check", "--max-input-size", "0", "-")
1524+
assert.Equal(t, 0, exitCode, "expected exit code 0 with unlimited stdin")
1525+
}
1526+
1527+
func TestMetricsRank_MaxInputSize_InvalidValue(t *testing.T) {
1528+
dir := t.TempDir()
1529+
isolateDir(t, dir)
1530+
writeFixture(t, dir, "a.md", "# Hello\n")
1531+
1532+
_, stderr, exitCode := runBinaryInDir(t, dir, "",
1533+
"metrics", "rank", "--max-input-size", "bad-value", "a.md")
1534+
assert.Equal(t, 2, exitCode, "expected exit code 2 for invalid size")
1535+
assert.Contains(t, stderr, "invalid max-input-size")
1536+
}
1537+
1538+
func TestMetricsRank_MaxInputSize_RespectsConfig(t *testing.T) {
1539+
dir := t.TempDir()
1540+
require.NoError(t, os.MkdirAll(filepath.Join(dir, ".git"), 0o755))
1541+
require.NoError(t, os.WriteFile(
1542+
filepath.Join(dir, ".mdsmith.yml"),
1543+
[]byte("rules: {}\nmax-input-size: \"5\"\n"), 0o644,
1544+
))
1545+
1546+
writeFixture(t, dir, "a.md", "# Hello world\n")
1547+
1548+
// Config sets 5-byte limit → 14-byte file should fail.
1549+
_, stderr, exitCode := runBinaryInDir(t, dir, "",
1550+
"metrics", "rank", "a.md")
1551+
assert.Equal(t, 2, exitCode, "expected exit code 2 for oversized file")
1552+
assert.Contains(t, stderr, "file too large")
1553+
}
1554+
1555+
func TestQuery_MaxInputSize_InvalidValue(t *testing.T) {
1556+
dir := t.TempDir()
1557+
isolateDir(t, dir)
1558+
writeFixture(t, dir, "a.md", "---\nid: 1\n---\n# Hello\n")
1559+
1560+
_, stderr, exitCode := runBinaryInDir(t, dir, "",
1561+
"query", "--max-input-size", "not-a-size", "id: 1", "a.md")
1562+
assert.Equal(t, 2, exitCode, "expected exit code 2 for invalid size")
1563+
assert.Contains(t, stderr, "invalid max-input-size")
1564+
}

0 commit comments

Comments
 (0)