def check_post_err( fn: Callable, optionset: AnalysisOptionSet = AnalysisOptionSet() ) -> ComparableLists: local_opts = AnalysisOptionSet(max_iterations=20) options = local_opts.overlay(optionset) states = [m.state for m in run_checkables(analyze_function(fn, options))] return (states, [MessageType.POST_ERR])
def collect_options(thing: Any) -> AnalysisOptionSet: parent_opts = AnalysisOptionSet() is_package = thing.__name__ == getattr(thing, "__package__", None) if getattr(thing, "__module__", None): parent_opts = collect_options(sys.modules[thing.__module__]) elif getattr(thing, "__package__", None): if is_package: parent_pkg, _, _ = thing.__package__.rpartition(".") else: parent_pkg = thing.__package__ if parent_pkg: parent_opts = collect_options(sys.modules[parent_pkg]) lines: Iterable[str] if is_package: try: lines = importlib.resources.read_text(thing, "__init__.py").splitlines() except FileNotFoundError: lines = [] else: _file, _start, lines = sourcelines(thing) directives = get_directives(lines) if inspect.ismodule(thing): # Only look at toplevel comments in modules # (we don't want to catch directives for functions inside it) # TODO: detect directives at other levels like classes etc and warn that they # will be ignored. directives = [(l, c, t) for (l, c, t) in directives if c == 0] my_opts = parse_directives(directives) return parent_opts.overlay(my_opts)
def check_fail( fn: Callable, optionset: AnalysisOptionSet = AnalysisOptionSet() ) -> ComparableLists: local_opts = AnalysisOptionSet(max_iterations=40, per_condition_timeout=5) options = local_opts.overlay(optionset) states = [m.state for m in run_checkables(analyze_function(fn, options))] return (states, [MessageType.POST_FAIL])
def parse_directives( directive_lines: Iterable[Tuple[int, int, str]]) -> AnalysisOptionSet: """ Parse options from directives in comments. >>> parse_directives([(1, 0, "off")]).enabled False """ result = AnalysisOptionSet() for lineno, _colno, directive in directive_lines: for part in directive.split(): if part == "on": part = "enabled=1" if part == "off": part = "enabled=" pair = part.split("=", 2) if len(pair) != 2: raise InvalidDirective(f'Malformed option: "{part}"', lineno) key, strvalue = pair if key not in AnalysisOptionSet.directive_fields: raise InvalidDirective(f'Unknown option: "{key}"', lineno) value = AnalysisOptionSet.parse_field(key, strvalue) if value is None: raise InvalidDirective( f'"{strvalue}" is not a valid "{key}" value', lineno) if getattr(result, key) is not None: raise InvalidDirective( f'Option "{key}" is set multiple times at the same scope', lineno) result = result.overlay(AnalysisOptionSet(**{key: value})) return result
def check_unknown( fn: Callable, optionset: AnalysisOptionSet = AnalysisOptionSet() ) -> ComparableLists: local_opts = AnalysisOptionSet(max_iterations=40, per_condition_timeout=3) options = local_opts.overlay(optionset) messages = [(m.state, m.message, m.traceback) for m in run_checkables(analyze_function(fn, options))] return (messages, [(MessageType.CANNOT_CONFIRM, "Not confirmed.", "")])
def check_ok( fn: Callable, optionset: AnalysisOptionSet = AnalysisOptionSet() ) -> ComparableLists: local_opts = AnalysisOptionSet(per_condition_timeout=5) options = local_opts.overlay(optionset) messages = [ message for message in run_checkables(analyze_function(fn, options)) if message.state != MessageType.CONFIRMED ] return (messages, [])
def check_exec_err( fn: Callable, message_prefix="", optionset: AnalysisOptionSet = AnalysisOptionSet() ) -> ComparableLists: local_opts = AnalysisOptionSet(max_iterations=20, per_condition_timeout=5) options = local_opts.overlay(optionset) messages = run_checkables(analyze_function(fn, options)) if all(m.message.startswith(message_prefix) for m in messages): return ([m.state for m in messages], [MessageType.EXEC_ERR]) else: return ( [(m.state, m.message) for m in messages], [(MessageType.EXEC_ERR, message_prefix)], )
def run_iteration( self, max_condition_timeout=0.5 ) -> Iterator[Tuple[Counter[str], List[AnalysisMessage]]]: debug(f"starting pass " f"with a condition timeout of {max_condition_timeout}") debug("Files:", self._modtimes.keys()) pool = self._pool for filename in self._modtimes.keys(): worker_timeout = max(10.0, max_condition_timeout * 20.0) iter_options = AnalysisOptionSet( per_condition_timeout=max_condition_timeout, per_path_timeout=max_condition_timeout / 4, ) options = self._options.overlay(iter_options) pool.submit((filename, options, time.time() + worker_timeout)) pool.garden_workers() while pool.is_working(): result = pool.get_result(timeout=1.0) if result is not None: (_, counters, messages) = result yield (counters, messages) if pool.has_result(): continue change_detected = self.check_changed() if change_detected: self._change_flag = True debug("Aborting iteration on change detection") pool.terminate() self._pool = self.startpool() return pool.garden_workers() debug("Worker pool tasks complete") yield (Counter(), [])
def unwalled_main(cmd_args: Union[List[str], argparse.Namespace]) -> None: if isinstance(cmd_args, argparse.Namespace): args = cmd_args else: args = command_line_parser().parse_args(cmd_args) set_debug(args.verbose) options = option_set_from_dict(args.__dict__) if sys.path and sys.path[0] != "": # fall back to current directory to look up modules sys.path.append("") if args.action == "check": exitcode = check(args, options, sys.stdout, sys.stderr) elif args.action == "diffbehavior": defaults = DEFAULT_OPTIONS.overlay( AnalysisOptionSet( per_condition_timeout=2.5, per_path_timeout=30.0, # mostly, we don't want to time out paths )) exitcode = diffbehavior(args, defaults.overlay(options), sys.stdout, sys.stderr) elif args.action == "watch": exitcode = watch(args, options) else: print(f'Unknown action: "{args.action}"', file=sys.stderr) exitcode = 2 sys.exit(exitcode)
def test_static_method(self) -> None: messages = analyze_any( walk_qualname(Person, "a_static_method"), AnalysisOptionSet(per_condition_timeout=5), ) self.assertEqual( *check_messages(messages, state=MessageType.CONFIRMED))
def test_symbolic_types_without_literal_types(self) -> None: def f(typ1: Type, typ2: Type, typ3: Type): """ post: implies(_, issubclass(typ1, typ3)) """ return issubclass(typ2, typ3) and typ2 != typ3 self.assertEqual(*check_fail( f, AnalysisOptionSet(max_iterations=60, per_condition_timeout=10)))
def TODO_test_subtype_union(self) -> None: def f(s: Set[Union[int, str]]) -> Set[Union[int, str]]: """ post: not ((42 in s) and ('42' in s)) """ return s self.assertEqual( *check_fail(f, AnalysisOptionSet(per_condition_timeout=7.0)))
def test_icontract_basic(self): @icontract.ensure(lambda result, x: result > x) def some_func(x: int, y: int = 5) -> int: return x - y self.assertEqual(*check_fail( some_func, AnalysisOptionSet(analysis_kind=[AnalysisKind.icontract])))
def test_datetime_fail(self) -> None: def f(dtime: datetime.datetime) -> int: """ post: _ != 22 """ return dtime.second self.assertEqual(*check_fail(f, AnalysisOptionSet(max_iterations=60)))
def test_icontract_weaken(self): @icontract.require(lambda x: x in (2, 3)) @icontract.ensure(lambda: True) def trynum(x: int): IcontractB().weakenedfunc(x) self.assertEqual(*check_ok( trynum, AnalysisOptionSet(analysis_kind=[AnalysisKind.icontract])))
def test_nonuniform_list_types_2(self) -> None: def f(a: Set[FrozenSet[int]]) -> object: """ pre: a == {frozenset({7}), frozenset({42})} post: _ in ('{frozenset({7}), frozenset({42})}', '{frozenset({42}), frozenset({7})}') """ return repr(a) check_ok(f, AnalysisOptionSet(per_path_timeout=5, per_condition_timeout=5))
def test_methods_directly(self) -> None: # Running analysis on individual methods directly works a little # differently, especially for staticmethod/classmethod. Confirm these # don't explode: messages = analyze_any( walk_qualname(Person, "a_regular_method"), AnalysisOptionSet(per_condition_timeout=5), ) self.assertEqual(*check_messages(messages, state=MessageType.CONFIRMED))
def test_compare_ok(self) -> None: def f(a: str, b: str) -> bool: """ pre: a and b post: implies(__return__, a[0] <= b[0]) """ return a < b self.assertEqual(*check_ok(f, AnalysisOptionSet(per_path_timeout=5)))
def collect_options(thing: Any) -> AnalysisOptionSet: parent_opts = AnalysisOptionSet() if getattr(thing, "__module__", None): parent_opts = collect_options(sys.modules[thing.__module__]) elif hasattr(thing, "__package__"): if thing.__package__ and thing.__package__ != thing.__name__: parent_opts = collect_options(sys.modules[thing.__package__]) _file, start, lines = sourcelines(thing) directives = get_directives(lines) if inspect.ismodule(thing): # Only look at toplevel comments in modules # (we don't want to catch directives for functions inside it) # TODO: detect directives at other levels like classes etc and warn that they # will be ignored. directives = [(l, c, t) for (l, c, t) in directives if c == 0] my_opts = parse_directives(directives) return parent_opts.overlay(my_opts)
def test_builtin(fn_name: str) -> None: opts = AnalysisOptionSet( max_iterations=20, per_condition_timeout=20, per_path_timeout=5 ) this_module = sys.modules[__name__] fn = getattr(this_module, fn_name) messages = run_checkables(analyze_function(fn, opts)) errors = [m for m in messages if m.state > MessageType.PRE_UNSAT] assert errors == []
def test_symbolic_months_fail(self) -> None: def f(num_months: int) -> datetime.date: """ pre: 0 <= num_months <= 100 post: _.year != 2003 """ dt = datetime.date(2000, 1, 1) return dt + datetime.timedelta(days=30 * num_months) self.assertEqual(*check_fail(f, AnalysisOptionSet( per_path_timeout=10)))
def test_sets_eq(self) -> None: def f(a: Set[FrozenSet[int]]) -> object: """ pre: a == {frozenset({7}), frozenset({42})} post: _ in ('{frozenset({7}), frozenset({42})}', '{frozenset({42}), frozenset({7})}') """ return repr(a) self.assertEqual(*check_ok( f, AnalysisOptionSet(per_path_timeout=10, per_condition_timeout=10)))
def test_number_parse(self) -> None: number_re = re.compile(r"(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?") def f(s: str): """ pre: len(s) == 4 post: not _ """ return bool(number_re.fullmatch(s)) self.assertEqual(*check_fail( f, AnalysisOptionSet(max_iterations=20, per_condition_timeout=10)))
def test_csv_example(self) -> None: def f(lines: List[str]) -> List[str]: """ pre: all(',' in line for line in lines) post: __return__ == [line.split(',')[0] for line in lines] """ return [line[:line.index(",")] for line in lines] # TODO: the model generation doesn't work right here (getting a lot of empty strings): options = AnalysisOptionSet(per_path_timeout=0.5, per_condition_timeout=5) self.assertEqual(*check_unknown(f, options))
def unwalled_main(cmd_args: Union[List[str], argparse.Namespace]) -> int: parser = command_line_parser() if isinstance(cmd_args, argparse.Namespace): args = cmd_args else: args = parser.parse_args(cmd_args) if not args.action: parser.print_help(sys.stderr) return 2 set_debug(args.verbose) debug("Installed plugins:", installed_plugins) options = option_set_from_dict(args.__dict__) # fall back to current directory to look up modules with add_to_pypath(*([""] if sys.path and sys.path[0] != "" else [])): if args.action == "check": return check(args, options, sys.stdout, sys.stderr) elif args.action == "diffbehavior": defaults = DEFAULT_OPTIONS.overlay( AnalysisOptionSet( per_condition_timeout=2.5, per_path_timeout= 30.0, # mostly, we don't want to time out paths )) return diffbehavior(args, defaults.overlay(options), sys.stdout, sys.stderr) elif args.action == "cover": defaults = DEFAULT_OPTIONS.overlay( AnalysisOptionSet( per_condition_timeout=2.5, per_path_timeout= 30.0, # mostly, we don't want to time out paths )) return cover(args, defaults.overlay(options), sys.stdout, sys.stderr) elif args.action == "watch": return watch(args, options) else: print(f'Unknown action: "{args.action}"', file=sys.stderr) return 2
def test_icontract_nesting(self): @icontract.require(lambda name: name.startswith("a")) def innerfn(name: str): pass @icontract.ensure(lambda: True) @icontract.require(lambda name: len(name) > 0) def outerfn(name: str): innerfn("00" + name) self.assertEqual(*check_exec_err( outerfn, message_prefix="PreconditionFailed", optionset=AnalysisOptionSet( analysis_kind=[AnalysisKind.icontract]), ))
def test_getattr(self) -> None: class Otter: def do_cute_human_things_with_hands(self) -> str: return "cuteness" def f(s: str) -> str: """ post: _ != "cuteness" """ try: return getattr(Otter(), s)() except: return "" messages = run_checkables( analyze_function( f, AnalysisOptionSet(max_iterations=20, per_condition_timeout=5))) self.assertEqual(len(messages), 1) self.assertEqual( messages[0].message, "false when calling f(s = 'do_cute_human_things_with_hands') (which returns 'cuteness')", )
def test_builtins() -> None: opts = AnalysisOptionSet(max_iterations=5, per_condition_timeout=10) messages = run_checkables(analyze_module(sys.modules[__name__], opts)) errors = [m for m in messages if m.state > MessageType.PRE_UNSAT] assert errors == []
def check_states( fn: Callable, optionset: AnalysisOptionSet = AnalysisOptionSet() ) -> Set[MessageType]: local_opts = AnalysisOptionSet(max_iterations=40, per_condition_timeout=5) options = local_opts.overlay(optionset) return set([m.state for m in run_checkables(analyze_function(fn, options))])