Files

165 lines
9.8 KiB
Python

from collections.abc import Iterator
from pathlib import Path
from unittest.mock import MagicMock
import pytest
from redis import Redis
from buttercup.common.stack_parsing import CrashSet, get_crash_data, get_inst_key, parse_stacktrace
# ruff: noqa: E501
JAVA_TEST_INST_KEY = "JXPathFuzzer\norg.apache.commons.beanutils.DynaBean\norg.apache.commons.jxpath.CompiledExpression\norg.apache.commons.jxpath.ExpressionContext\norg.apache.commons.jxpath.Function\norg.apache.commons.jxpath.Functions\norg.apache.commons.jxpath.JXPathContext\norg.apache.commons.jxpath.JXPathContextFactory\norg.apache.commons.jxpath.JXPathContextFactoryConfigurationError\norg.apache.commons.jxpath.JXPathException\norg.apache.commons.jxpath.JXPathFunctionNotFoundException\norg.apache.commons.jxpath.JXPathInvalidAccessException\norg.apache.commons.jxpath.JXPathInvalidSyntaxException\norg.apache.commons.jxpath.JXPathNotFoundException\norg.apache.commons.jxpath.JXPathTypeConversionException\norg.apache.commons.jxpath.NodeSet\norg.apache.commons.jxpath.PackageFunctions\norg.apache.commons.jxpath.Pointer\norg.apache.commons.jxpath.Variables\norg.apache.commons.jxpath.ri.Compiler\norg.apache.commons.jxpath.ri.EvalContext\norg.apache.commons.jxpath.ri.InfoSetUtil\norg.apache.commons.jxpath.ri.JXPathContextFactoryReferenceImpl\norg.apache.commons.jxpath.ri.JXPathContextReferenceImpl\norg.apache.commons.jxpath.ri.NamespaceResolver\norg.apache.commons.jxpath.ri.Parser\norg.apache.commons.jxpath.ri.QName\norg.apache.commons.jxpath.ri.axes.AncestorContext\norg.apache.commons.jxpath.ri.axes.AttributeContext\norg.apache.commons.jxpath.ri.axes.ChildContext\norg.apache.commons.jxpath.ri.axes.DescendantContext\norg.apache.commons.jxpath.ri.axes.InitialContext\norg.apache.commons.jxpath.ri.axes.NamespaceContext\norg.apache.commons.jxpath.ri.axes.NodeSetContext\norg.apache.commons.jxpath.ri.axes.ParentContext\norg.apache.commons.jxpath.ri.axes.PrecedingOrFollowingContext\norg.apache.commons.jxpath.ri.axes.PredicateContext\norg.apache.commons.jxpath.ri.axes.RootContext\norg.apache.commons.jxpath.ri.axes.SelfContext\norg.apache.commons.jxpath.ri.axes.UnionContext\norg.apache.commons.jxpath.ri.compiler.Constant\norg.apache.commons.jxpath.ri.compiler.CoreFunction\norg.apache.commons.jxpath.ri.compiler.CoreOperation\norg.apache.commons.jxpath.ri.compiler.CoreOperationGreaterThan\norg.apache.commons.jxpath.ri.compiler.CoreOperationNegate\norg.apache.commons.jxpath.ri.compiler.CoreOperationRelationalExpression\norg.apache.commons.jxpath.ri.compiler.Expression\norg.apache.commons.jxpath.ri.compiler.ExpressionPath\norg.apache.commons.jxpath.ri.compiler.LocationPath\norg.apache.commons.jxpath.ri.compiler.NodeNameTest\norg.apache.commons.jxpath.ri.compiler.NodeTest\norg.apache.commons.jxpath.ri.compiler.NodeTypeTest\norg.apache.commons.jxpath.ri.compiler.Operation\norg.apache.commons.jxpath.ri.compiler.Path\norg.apache.commons.jxpath.ri.compiler.Step\norg.apache.commons.jxpath.ri.compiler.TreeCompiler\norg.apache.commons.jxpath.ri.model.NodeIterator\norg.apache.commons.jxpath.ri.model.NodePointer\norg.apache.commons.jxpath.ri.model.NodePointerFactory\norg.apache.commons.jxpath.ri.model.VariablePointer\norg.apache.commons.jxpath.ri.model.VariablePointerFactory\norg.apache.commons.jxpath.ri.model.beans.BeanPointer\norg.apache.commons.jxpath.ri.model.beans.BeanPointerFactory\norg.apache.commons.jxpath.ri.model.beans.CollectionPointer\norg.apache.commons.jxpath.ri.model.beans.CollectionPointerFactory\norg.apache.commons.jxpath.ri.model.beans.NullPointer\norg.apache.commons.jxpath.ri.model.beans.NullPropertyPointer\norg.apache.commons.jxpath.ri.model.beans.PropertyOwnerPointer\norg.apache.commons.jxpath.ri.model.beans.PropertyPointer\norg.apache.commons.jxpath.ri.model.container.ContainerPointer\norg.apache.commons.jxpath.ri.model.container.ContainerPointerFactory\norg.apache.commons.jxpath.ri.model.dom.DOMNodePointer\norg.apache.commons.jxpath.ri.model.dom.DOMPointerFactory\norg.apache.commons.jxpath.ri.model.dynabeans.DynaBeanPointer\norg.apache.commons.jxpath.ri.model.dynabeans.DynaBeanPointerFactory\norg.apache.commons.jxpath.ri.model.dynamic.DynamicPointer\norg.apache.commons.jxpath.ri.model.dynamic.DynamicPointerFactory\norg.apache.commons.jxpath.ri.model.jdom.JDOMNodePointer\norg.apache.commons.jxpath.ri.model.jdom.JDOMPointerFactory\norg.apache.commons.jxpath.ri.parser.ParseException\norg.apache.commons.jxpath.ri.parser.SimpleCharStream\norg.apache.commons.jxpath.ri.parser.Token\norg.apache.commons.jxpath.ri.parser.TokenMgrError\norg.apache.commons.jxpath.ri.parser.XPathParser\norg.apache.commons.jxpath.ri.parser.XPathParserConstants\norg.apache.commons.jxpath.ri.parser.XPathParserTokenManager\norg.apache.commons.jxpath.util.ClassLoaderUtil\norg.jdom.Comment\norg.jdom.Content\norg.jdom.ContentList\norg.jdom.DocType\norg.jdom.Document\norg.jdom.Element\norg.jdom.IllegalAddException\norg.jdom.Parent\norg.jdom.ProcessingInstruction\norg.w3c.dom.Document\norg.w3c.dom.DocumentType\norg.w3c.dom.Element\norg.w3c.dom.ElementTraversal\norg.w3c.dom.Node\norg.w3c.dom.NodeList\norg.w3c.dom.TypeInfo\norg.w3c.dom.events.DocumentEvent\norg.w3c.dom.events.EventTarget\norg.w3c.dom.ranges.DocumentRange\norg.w3c.dom.traversal.DocumentTraversal\norg.xml.sax.ContentHandler\norg.xml.sax.DTDHandler\norg.xml.sax.EntityResolver\norg.xml.sax.ErrorHandler\norg.xml.sax.InputSource\norg.xml.sax.SAXException\norg.xml.sax.SAXParseException\norg.xml.sax.helpers.DefaultHandler"
def get_tc_file(name: str) -> Path:
"""Get testcase file"""
crash_file = Path(__file__).parent / "data" / "stacktrace_corpus" / f"{name}_stacktrace.txt"
if not crash_file.exists():
raise FileNotFoundError(f"Crash file not found at {crash_file}")
return crash_file
@pytest.fixture
def java_crash_testcase() -> Path:
"""Get java crash_test_case"""
return get_tc_file("java")
@pytest.fixture
def c_crash_testcase() -> Path:
"""Get c crash_test_case"""
return get_tc_file("c")
@pytest.fixture
def c2_crash_testcase() -> Path:
"""Get c crash_test_case"""
return get_tc_file("c_2")
@pytest.fixture
def mock_crash_set() -> Iterator[CrashSet]:
"""Get mock crash_set"""
res = CrashSet(MagicMock(spec=Redis))
set_data = set()
def add_mock(value: str) -> bool:
if value in set_data:
return True
set_data.add(value)
return False
res.set.add = add_mock
return res
def test_get_crash_data_basic():
# Simple stacktrace example
stacktrace = """
#0 0x7f339b644844 in foo::bar::crash() /src/foo/bar.cc:123:4
#1 0x7f339b644900 in main /src/main.cc:45:2
"""
crash_state = get_crash_data(stacktrace)
assert crash_state is not None
assert isinstance(crash_state, str)
def test_get_crash_data_symbolized():
# Test with symbolized stacktrace
stacktrace = """
#0 foo::bar::crash() /src/foo/bar.cc:123:4
#1 main /src/main.cc:45:2
"""
crash_state = get_crash_data(stacktrace, symbolized=True)
assert crash_state is not None
assert isinstance(crash_state, str)
def test_get_crash_data_empty():
# Test with empty stacktrace
stacktrace = ""
crash_state = get_crash_data(stacktrace)
assert crash_state is not None
assert isinstance(crash_state, str)
def test_get_crash_data_invalid():
# Test with invalid stacktrace format
stacktrace = "This is not a valid stacktrace"
crash_state = get_crash_data(stacktrace)
assert crash_state is not None
assert isinstance(crash_state, str)
def test_java_stacktrace(java_crash_testcase: Path):
with open(java_crash_testcase) as f:
trace = f.read()
crash_state = get_crash_data(trace)
expected = "org.apache.commons.jxpath.ri.compiler.CoreOperation.parenthesize\norg.apache.commons.jxpath.ri.compiler.CoreOperation.toString\norg.apache.commons.jxpath.ri.compiler.CoreOperation.parenthesize\n"
assert crash_state == expected
def test_c_stacktrace(c_crash_testcase: Path):
with open(c_crash_testcase) as f:
trace = f.read()
crash_state = get_crash_data(trace)
print(crash_state)
assert crash_state is not None
assert isinstance(crash_state, str)
expected = "cil_destroy_block\ncil_destroy_data\ncil_tree_node_destroy\n"
assert crash_state == expected
def test_c_instrumentation_key(c_crash_testcase: Path):
with open(c_crash_testcase) as f:
trace = f.read()
inst_key = get_inst_key(trace)
print(inst_key)
assert inst_key == ""
def test_java_instrumentation_key(java_crash_testcase: Path):
with open(java_crash_testcase) as f:
trace = f.read()
inst_key = get_inst_key(trace)
print(inst_key)
assert inst_key is not None
assert isinstance(inst_key, str)
assert inst_key == JAVA_TEST_INST_KEY
@pytest.mark.parametrize(
"stacktrace_path,line_number",
[
(get_tc_file("c"), 249),
(get_tc_file("c_2"), 230),
(get_tc_file("java"), 0),
],
)
def test_crash_set(mock_crash_set: CrashSet, stacktrace_path: Path, line_number: int):
with stacktrace_path.open("r") as f:
stacktrace = f.read()
assert not mock_crash_set.add("test", "test", "test", "test", stacktrace)
assert mock_crash_set.add("test", "test", "test", "test", stacktrace)
assert mock_crash_set._get_final_line_number(parse_stacktrace(stacktrace)) == line_number
def test_crash_set_diff_line_numbers(mock_crash_set: CrashSet, c_crash_testcase: Path, c2_crash_testcase: Path):
with c_crash_testcase.open("r") as f:
stacktrace1 = f.read()
with c2_crash_testcase.open("r") as f:
stacktrace2 = f.read()
assert not mock_crash_set.add("test", "test", "test", "test", stacktrace1)
assert not mock_crash_set.add("test", "test", "test", "test", stacktrace2)
assert mock_crash_set.add("test", "test", "test", "test", stacktrace1)
assert mock_crash_set.add("test", "test", "test", "test", stacktrace2)