import random
import string
import pytest
from src.contracts.parser import parse_output, parse_statement_lines
GOOD_OUTPUT = """
CWE-120
12-14, 27
line 12: buf = getenv("PATH") [source: env input]
line 27: memcpy(dst, buf, n) [sink: unchecked copy]
The buffer is filled from an unchecked environment variable and then
copied without a bounds check, allowing overflow.
"""
def test_well_formed_parses_ok():
out = parse_output(GOOD_OUTPUT)
assert out.format_ok is True
assert out.label == "VULNERABLE"
assert out.cwe == "CWE-120"
assert out.statement_lines == {12, 13, 14, 27}
assert len(out.trace) == 2
assert out.trace[0].line == 12
assert "source" in out.trace[0].tag
assert out.trace[1].line == 27
assert out.explanation
def test_safe_label_with_none_cwe():
text = """NONE
No vulnerability found in this function."""
out = parse_output(text)
assert out.format_ok is True
assert out.label == "SAFE"
assert out.cwe == "NONE"
assert out.statement_lines == set()
@pytest.mark.parametrize(
"bad",
[
"",
"not even xml",
"",
"",
"not-a-cwe",
"CWE-120", # no explanation
"", # no wrapping analysis tag
"NONEx",
"" * 50,
None,
12345,
],
)
def test_malformed_never_crashes(bad):
out = parse_output(bad)
assert out.format_ok is False
def test_bad_statement_range_does_not_crash_but_is_logged():
text = (
"CWE-120"
"99999999999999999999999999-1"
"x"
)
out = parse_output(text)
assert out.format_ok is True # structurally valid; the bad range is just logged
assert out.statement_lines == set()
assert out.parse_errors
def test_fuzz_never_crashes():
rng = random.Random(42)
alphabet = string.printable
tags = ["", "", "", "", "",
"", "", "", "", "",
"", "", "", "VULNERABLE", "SAFE",
"CWE-120", "NONE", "line 12: foo()"]
for _ in range(150):
n_pieces = rng.randint(0, 15)
pieces = []
for _ in range(n_pieces):
if rng.random() < 0.5:
pieces.append(rng.choice(tags))
else:
pieces.append("".join(rng.choice(alphabet) for _ in range(rng.randint(0, 20))))
garbage = "".join(pieces)
out = parse_output(garbage) # must not raise
assert isinstance(out.format_ok, bool)
def test_parse_statement_lines_ranges():
lines, errors = parse_statement_lines("12-14, 27")
assert lines == {12, 13, 14, 27}
assert errors == []
def test_parse_statement_lines_rejects_inverted_range():
lines, errors = parse_statement_lines("20-10")
assert lines == set()
assert errors