instrument script supports function execution
This commit is contained in:
parent
af6d21dbb1
commit
a68f405674
12 changed files with 80 additions and 17 deletions
|
@ -1,6 +1,6 @@
|
|||
|
||||
|
||||
def anagram_check(s1: str, s2: str) -> bool:
|
||||
def anagram_check_instrumented(s1: str, s2: str) -> bool:
|
||||
if (evaluate_condition(1, 'Eq', len(s1), 1) and evaluate_condition(2, 'Eq', len(s2), 1)):
|
||||
return (s1 == s2)
|
||||
if evaluate_condition(3, 'NotEq', len(s1), len(s2)):
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
|
||||
def encrypt(strng: str, key: int) -> str:
|
||||
def encrypt_instrumented(strng: str, key: int) -> str:
|
||||
assert (0 < key <= 94)
|
||||
encrypted = ''
|
||||
for x in strng:
|
||||
|
@ -10,7 +10,7 @@ def encrypt(strng: str, key: int) -> str:
|
|||
encrypted = (encrypted + chr(indx))
|
||||
return encrypted
|
||||
|
||||
def decrypt(strng: str, key: int) -> str:
|
||||
def decrypt_instrumented(strng: str, key: int) -> str:
|
||||
assert (0 < key <= 94)
|
||||
decrypted = ''
|
||||
for x in strng:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
|
||||
def check_armstrong(n: int) -> bool:
|
||||
def check_armstrong_instrumented(n: int) -> bool:
|
||||
assert (n >= 0)
|
||||
if (evaluate_condition(1, 'Eq', n, 0) or evaluate_condition(2, 'Eq', n, 1)):
|
||||
return True
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
'\nThe function takes two integers as input and return the number of common divisors of\nthat pair\n'
|
||||
|
||||
def cd_count(a: int, b: int) -> int:
|
||||
def cd_count_instrumented(a: int, b: int) -> int:
|
||||
if (evaluate_condition(1, 'Eq', a, 0) or evaluate_condition(2, 'Eq', b, 0)):
|
||||
return 2
|
||||
a = (((- 1) * a) if evaluate_condition(3, 'Lt', a, 0) else a)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
|
||||
def exponentiation(baseNumber: int, power: int) -> float:
|
||||
def exponentiation_instrumented(baseNumber: int, power: int) -> float:
|
||||
assert (not ((baseNumber == 0) or (power <= 0)))
|
||||
answer = None
|
||||
if evaluate_condition(1, 'Gt', power, 1):
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
|
||||
def gcd(a: int, b: int) -> int:
|
||||
def gcd_instrumented(a: int, b: int) -> int:
|
||||
assert ((a > 0) and (b > 0))
|
||||
if (evaluate_condition(1, 'Eq', a, 1) or evaluate_condition(2, 'Eq', b, 1)):
|
||||
return 1
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
|
||||
def longest_sorted_substr(s: str) -> str:
|
||||
def longest_sorted_substr_instrumented(s: str) -> str:
|
||||
count = 0
|
||||
max_count = 0
|
||||
end_position = 0
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
|
||||
def rabin_karp_search(pat: str, txt: str) -> list:
|
||||
def rabin_karp_search_instrumented(pat: str, txt: str) -> list:
|
||||
assert (len(pat) <= len(txt))
|
||||
d = 2560
|
||||
q = 101
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
|
||||
def railencrypt(st: str, k: int) -> str:
|
||||
def railencrypt_instrumented(st: str, k: int) -> str:
|
||||
assert (k > 1)
|
||||
c = 0
|
||||
x = 0
|
||||
|
@ -25,7 +25,7 @@ def railencrypt(st: str, k: int) -> str:
|
|||
result.append(chr(m[i][j]))
|
||||
return ''.join(result)
|
||||
|
||||
def raildecrypt(st: str, k: int) -> str:
|
||||
def raildecrypt_instrumented(st: str, k: int) -> str:
|
||||
assert (k > 1)
|
||||
(c, x) = (0, 0)
|
||||
m = [([0] * len(st)) for i in range(k)]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
|
||||
def zeller(d: int, m: int, y: int) -> str:
|
||||
def zeller_instrumented(d: int, m: int, y: int) -> str:
|
||||
assert (abs(d) >= 1)
|
||||
assert (abs(m) >= 1)
|
||||
assert ((0 <= abs(y) <= 99) or (1000 <= abs(y) <= 3000))
|
||||
|
|
|
@ -87,7 +87,7 @@ def int_str_check(a: any, b: any) -> bool:
|
|||
return True
|
||||
if type(a) != str or type(b) != str:
|
||||
return False
|
||||
return len(a) == 1 or len(b) == 1
|
||||
return len(a) == 1 and len(b) == 1
|
||||
|
||||
|
||||
def int_str_convert(x: int | str) -> int:
|
||||
|
@ -135,7 +135,7 @@ def compute_distances(name: str, lhs: any, rhs: any) -> tuple[int, int]:
|
|||
if name not in str_by_name:
|
||||
raise ValueError(f"'{name}' is not a valid CmpOp name for 'str' operators")
|
||||
|
||||
op = int_str_by_name[name]
|
||||
op = str_by_name[name]
|
||||
return op.true_dist(lhs, rhs), op.false_dist(lhs, rhs)
|
||||
|
||||
raise ValueError(f"'{lhs}' and '{rhs}' are not suitable for both 'int_str' and 'str' operators")
|
||||
|
|
|
@ -56,6 +56,7 @@ class BranchTransformer(ast.NodeTransformer):
|
|||
|
||||
def visit_FunctionDef(self, ast_node):
|
||||
self.instrumented_name = ast_node.name
|
||||
ast_node.name = BranchTransformer.to_instrumented_name(ast_node.name)
|
||||
inner_node = self.generic_visit(ast_node)
|
||||
self.instrumented_name = None
|
||||
return inner_node
|
||||
|
@ -212,12 +213,22 @@ def generate():
|
|||
coverage.append(cov)
|
||||
|
||||
|
||||
ArgType = str
|
||||
Arg = tuple[str, ArgType]
|
||||
SignatureDict = dict[str, list[Arg]]
|
||||
|
||||
|
||||
functions: SignatureDict = {}
|
||||
|
||||
|
||||
def instrument(source_path: str, target_path: str):
|
||||
global functions
|
||||
|
||||
with open(source_path, "r") as f:
|
||||
source = f.read()
|
||||
|
||||
node = ast.parse(source)
|
||||
print(ast.dump(node, indent=2))
|
||||
# print(ast.dump(node, indent=2))
|
||||
BranchTransformer().visit(node)
|
||||
node = ast.fix_missing_locations(node) # Make sure the line numbers are ok before printing
|
||||
|
||||
|
@ -226,7 +237,46 @@ def instrument(source_path: str, target_path: str):
|
|||
|
||||
current_module = sys.modules[__name__]
|
||||
code = compile(node, filename="<ast>", mode="exec")
|
||||
exec(code, current_module.__dict__) # try: cgi_decode_instrumented("a%20%32"), print distances_true
|
||||
exec(code, current_module.__dict__)
|
||||
|
||||
# Figure out the top level function definitions
|
||||
assert isinstance(node, ast.Module)
|
||||
top_level_f_ast: list[ast.FunctionDef] = [f for f in node.body if isinstance(f, ast.FunctionDef)]
|
||||
|
||||
for f in top_level_f_ast:
|
||||
if f.name in functions:
|
||||
raise ValueError(f"Function '{f.name}' already loaded from another file")
|
||||
|
||||
arg_types: list[Arg] = []
|
||||
|
||||
for arg in f.args.args:
|
||||
# fetch annotation type if found else fetch none
|
||||
# noinspection PyUnresolvedReferences
|
||||
arg_type = None if arg.annotation is None else arg.annotation.id
|
||||
|
||||
arg_types.append((arg.arg, arg_type))
|
||||
|
||||
functions[f.name] = arg_types
|
||||
|
||||
|
||||
def invoke_signature(fun_name: str, arg_values: dict[str]) -> any:
|
||||
global functions
|
||||
|
||||
current_module = sys.modules[__name__]
|
||||
|
||||
if fun_name not in functions:
|
||||
raise ValueError(f"Function '{fun_name}' not loaded")
|
||||
|
||||
args = functions[fun_name]
|
||||
for arg_name, arg_type in args:
|
||||
if arg_name not in arg_values:
|
||||
raise ValueError(f"Required argument '{arg_name}' not provided")
|
||||
|
||||
|
||||
arg_str = ",".join([f"{k}={v}" for k, v in arg_values.items()])
|
||||
print(f"Calling {fun_name}({arg_str})")
|
||||
|
||||
return getattr(current_module, fun_name)(**arg_values)
|
||||
|
||||
|
||||
def find_py_files(search_dir: str):
|
||||
|
@ -237,9 +287,22 @@ def find_py_files(search_dir: str):
|
|||
|
||||
|
||||
def main():
|
||||
global functions
|
||||
|
||||
for file in find_py_files(IN_DIR):
|
||||
instrument(file, os.path.join(OUT_DIR, os.path.basename(file)))
|
||||
# generate()
|
||||
|
||||
for function, arg_signatures in functions.items():
|
||||
args = {}
|
||||
for arg_name, arg_type in arg_signatures:
|
||||
if arg_type == 'int':
|
||||
args[arg_name] = 42
|
||||
elif arg_type == 'str':
|
||||
args[arg_name] = 'hello world'
|
||||
else:
|
||||
args[arg_name] = None
|
||||
|
||||
invoke_signature(function, args)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
Reference in a new issue