def test_example_coverage(self) -> None: # Try to get examples that highlist the differences in the code. # Here, we add more conditions for the `return True` path and # another case where we used to just `return False`. def isack1(s: str) -> bool: if s in ("y", "yes"): return True return False def isack2(s: str) -> Optional[bool]: if s in ("y", "yes", "Y", "YES"): return True if s in ("n", "no", "N", "NO"): return False return None diffs = diff_behavior( FunctionInfo.from_fn(isack1), FunctionInfo.from_fn(isack2), DEFAULT_OPTIONS.overlay(max_iterations=20, per_condition_timeout=5), ) debug("diffs=", diffs) assert not isinstance(diffs, str) return_vals = set( (d.result1.return_repr, d.result2.return_repr) for d in diffs) self.assertEqual(return_vals, {("False", "None"), ("False", "True")})
def test_CompositeConditionParser(): composite = CompositeConditionParser() composite.parsers.append(Pep316Parser(composite)) composite.parsers.append(AssertsParser(composite)) assert composite.get_fn_conditions( FunctionInfo.from_fn(single_line_condition) ).has_any() assert composite.get_fn_conditions(FunctionInfo.from_fn(avg_with_asserts)).has_any()
def test_locally_defined_raises_condition(self) -> None: conditions = Pep316Parser().get_fn_conditions( FunctionInfo.from_fn(locally_defined_raises_condition) ) assert conditions is not None self.assertEqual([], list(conditions.syntax_messages())) self.assertEqual(set([LocallyDefiendException]), conditions.raises)
def test_tricky_raises_condition(self) -> None: conditions = Pep316Parser().get_fn_conditions( FunctionInfo.from_fn(tricky_raises_condition) ) assert conditions is not None self.assertEqual([], list(conditions.syntax_messages())) self.assertEqual(set([KeyError, OSError]), conditions.raises)
def test_implies_condition(self): conditions = Pep316Parser().get_fn_conditions( FunctionInfo.from_fn(implies_condition) ) assert conditions is not None # This shouldn't explode (avoid a KeyError on record['override']): conditions.post[0].evaluate({"record": {}, "_": 0})
def tests_simple_parse(self) -> None: conditions = AssertsParser().get_fn_conditions( FunctionInfo.from_fn(avg_with_asserts)) assert conditions is not None conditions.fn([]) self.assertEqual(conditions.fn([2.2]), 2.2) with self.assertRaises(AssertionError): conditions.fn([9.2, 17.8])
def test_single_line_condition(self) -> None: conditions = Pep316Parser().get_fn_conditions( FunctionInfo.from_fn(single_line_condition) ) assert conditions is not None self.assertEqual( set([c.expr_source for c in conditions.post]), set(["__return__ >= x"]) )
def test_CompositeConditionParser_adds_completion_conditions(): composite_parser = CompositeConditionParser() pep316_parser = Pep316Parser(composite_parser) composite_parser.parsers.append(pep316_parser) fn = FunctionInfo.from_fn(no_postconditions) assert len(pep316_parser.get_fn_conditions(fn).pre) == 1 assert len(pep316_parser.get_fn_conditions(fn).post) == 0 assert len(composite_parser.get_fn_conditions(fn).post) == 1
def test_conditions_with_closure_references_and_string_type(self) -> None: # This is a function that refers to something in its closure. # Ensure we can still look up string-based types: def referenced_fn(): return 4 def fn_with_closure(foo: "Foo"): referenced_fn() # Ensure we don't error trying to resolve "Foo": Pep316Parser().get_fn_conditions(FunctionInfo.from_fn(fn_with_closure))
def test_diff_behavior_mutation(self) -> None: def cut_out_item1(a: List[int], i: int): a[i:i + 1] = [] def cut_out_item2(a: List[int], i: int): a[:] = a[:i] + a[i + 1:] # TODO: this takes longer than I'd like (few iterations though): opts = DEFAULT_OPTIONS.overlay(max_iterations=20, per_path_timeout=10, per_condition_timeout=10) diffs = diff_behavior( FunctionInfo.from_fn(cut_out_item1), FunctionInfo.from_fn(cut_out_item2), opts, ) assert not isinstance(diffs, str) self.assertEqual(len(diffs), 1) diff = diffs[0] self.assertGreater(len(diff.args["a"]), 1) self.assertEqual(diff.args["i"], "-1")
def tests_extra_ast_nodes(self) -> None: conditions = AssertsParser().get_fn_conditions( FunctionInfo.from_fn(fn_with_docstring_comments_and_assert)) assert conditions is not None # Empty list does not pass precondition, ignored: conditions.fn([]) # normal, passing case: nums = [3, 1, 2] conditions.fn(nums) self.assertEqual(nums, [3, 2]) # Failing case (duplicate minimum values): with self.assertRaises(AssertionError): nums = [3, 1, 1, 2] conditions.fn(nums)
def _wrap_fn(self, fn: Callable, conditions: Optional[Conditions] = None) -> Callable: wrapper = self.wrapper_map.get(fn) if wrapper is not None: return wrapper if conditions is None: conditions = self.condition_parser.get_fn_conditions( FunctionInfo.from_fn(fn)) # type: ignore if conditions and conditions.has_any(): wrapper = EnforcementWrapper(self.interceptor(fn), conditions, self) functools.update_wrapper(wrapper, fn) else: wrapper = fn self.wrapper_map[fn] = wrapper self.original_map[IdentityWrapper(wrapper)] = fn return wrapper
def test_simple_parse(self): @icontract.require(lambda l: len(l) > 0) @icontract.ensure(lambda l, result: min(l) <= result <= max(l)) def avg(l): return sum(l) / len(l) conditions = IcontractParser().get_fn_conditions(FunctionInfo.from_fn(avg)) assert conditions is not None self.assertEqual(len(conditions.pre), 1) self.assertEqual(len(conditions.post), 1) self.assertEqual(conditions.pre[0].evaluate({"l": []}), False) post_args = { "l": [42, 43], "__old__": AttributeHolder({}), "__return__": 40, "_": 40, } self.assertEqual(conditions.post[0].evaluate(post_args), False) self.assertEqual(len(post_args), 4) # (check args are unmodified)
from crosshair.core_and_libs import * def _foo(x: int) -> int: if x > 100: return 100 return x def _regex(x: str) -> bool: compiled = re.compile("f(o)+") return bool(compiled.fullmatch(x)) OPTS = DEFAULT_OPTIONS.overlay(max_iterations=10, per_condition_timeout=10.0) foo = FunctionInfo.from_fn(_foo) regex = FunctionInfo.from_fn(_regex) def test_path_cover() -> None: paths = list(path_cover(foo, OPTS, CoverageType.OPCODE)) assert len(paths) == 2 small, large = sorted(paths, key=lambda p: p.result) # type: ignore assert large.result == 100 assert large.args.arguments["x"] > 100 assert small.result == small.args.arguments["x"] def test_path_cover_regex() -> None: paths = list(path_cover(regex, OPTS, CoverageType.OPCODE)) assert len(paths) == 1
def tests_empty_parse(self) -> None: conditions = AssertsParser().get_fn_conditions(FunctionInfo.from_fn(debug)) self.assertEqual(conditions, None)
def test_builtin_conditions_are_null(self) -> None: self.assertIsNone(Pep316Parser().get_fn_conditions(FunctionInfo.from_fn(zip)))
from crosshair.diff_behavior import diff_behavior from crosshair.fnutil import walk_qualname from crosshair.fnutil import FunctionInfo from crosshair.options import AnalysisOptions from crosshair.options import DEFAULT_OPTIONS from crosshair.util import debug from crosshair.util import set_debug def _foo1(x: int) -> int: if x >= 100: return 100 return x foo1 = FunctionInfo.from_fn(_foo1) def _foo2(x: int) -> int: return min(x, 100) foo2 = FunctionInfo.from_fn(_foo2) def _foo3(x: int) -> int: if x > 1000: return 1000 elif x > 100: return 100 else: