diff --git a/259045253/test/whitebox/mutation_testing/.coverage b/259045253/test/whitebox/mutation_testing/.coverage new file mode 100644 index 00000000..32987380 Binary files /dev/null and b/259045253/test/whitebox/mutation_testing/.coverage differ diff --git a/259045253/test/whitebox/mutation_testing/coverage.txt b/259045253/test/whitebox/mutation_testing/coverage.txt new file mode 100644 index 00000000..e1fb51c4 --- /dev/null +++ b/259045253/test/whitebox/mutation_testing/coverage.txt @@ -0,0 +1,12 @@ +Name Stmts Miss Cover Missing +---------------------------------------------------------------------------------------------- +259045253/test/__init__.py 0 0 100% +259045253/test/whitebox/__init__.py 0 0 100% +259045253/test/whitebox/mutation_testing/test_src_evaluator.py 7223 0 100% +src/__init__.py 49 40 18% 21-29, 33-89 +src/evaluator.py 338 259 23% 71-72, 76-77, 83, 89-94, 98-102, 106-110, 115, 118, 127, 134, 137-292, 296-303, 307-353, 356-357, 360-361, 364-369, 372-374, 377-380, 383-386, 391, 394-406, 409-417, 420-421, 424-425, 442, 444, 448 +src/lexer.py 197 180 9% 8-13, 16-21, 24-26, 29-30, 33-34, 37-51, 54-73, 76-88, 91-173, 176-198, 204-209, 212-240, 243 +src/parser.py 350 235 33% 17, 91-92, 97-99, 104, 118-120, 125, 142-146, 151-153, 156, 186-244, 247, 252, 257, 260-263, 266-280, 283-294, 297-300, 303-308, 311-313, 316-323, 326-329, 332-336, 341-359, 362-374, 377, 380, 383-387, 390-392, 395-397, 400-402, 405-407, 410-413, 416-426, 429-431, 434-440, 443-450, 453-466, 469-481, 484-486, 489-491, 494-504, 507-524, 529-535, 538-540 +src/tokens.py 48 0 100% +---------------------------------------------------------------------------------------------- +TOTAL 8205 714 91% diff --git a/259045253/test/whitebox/mutation_testing/pynguin-report/cov_report.html b/259045253/test/whitebox/mutation_testing/pynguin-report/cov_report.html new file mode 100644 index 00000000..d68d2823 --- /dev/null +++ b/259045253/test/whitebox/mutation_testing/pynguin-report/cov_report.html @@ -0,0 +1,1037 @@ + + +
+ +Achieved 17.48% branch coverage: +8/8 branchless code objects covered. +42/278 branches covered.
+| + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 + 22 + 23 + 24 + 25 + 26 + 27 + 28 + 29 + 30 + 31 + 32 + 33 + 34 + 35 + 36 + 37 + 38 + 39 + 40 + 41 + 42 + 43 + 44 + 45 + 46 + 47 + 48 + 49 + 50 + 51 + 52 + 53 + 54 + 55 + 56 + 57 + 58 + 59 + 60 + 61 + 62 + 63 + 64 + 65 + 66 + 67 + 68 + 69 + 70 + 71 + 72 + 73 + 74 + 75 + 76 + 77 + 78 + 79 + 80 + 81 + 82 + 83 + 84 + 85 + 86 + 87 + 88 + 89 + 90 + 91 + 92 + 93 + 94 + 95 + 96 + 97 + 98 + 99 + 100 + 101 + 102 + 103 + 104 + 105 + 106 + 107 + 108 + 109 + 110 + 111 + 112 + 113 + 114 + 115 + 116 + 117 + 118 + 119 + 120 + 121 + 122 + 123 + 124 + 125 + 126 + 127 + 128 + 129 + 130 + 131 + 132 + 133 + 134 + 135 + 136 + 137 + 138 + 139 + 140 + 141 + 142 + 143 + 144 + 145 + 146 + 147 + 148 + 149 + 150 + 151 + 152 + 153 + 154 + 155 + 156 + 157 + 158 + 159 + 160 + 161 + 162 + 163 + 164 + 165 + 166 + 167 + 168 + 169 + 170 + 171 + 172 + 173 + 174 + 175 + 176 + 177 + 178 + 179 + 180 + 181 + 182 + 183 + 184 + 185 + 186 + 187 + 188 + 189 + 190 + 191 + 192 + 193 + 194 + 195 + 196 + 197 + 198 + 199 + 200 + 201 + 202 + 203 + 204 + 205 + 206 + 207 + 208 + 209 + 210 + 211 + 212 + 213 + 214 + 215 + 216 + 217 + 218 + 219 + 220 + 221 + 222 + 223 + 224 + 225 + 226 + 227 + 228 + 229 + 230 + 231 + 232 + 233 + 234 + 235 + 236 + 237 + 238 + 239 + 240 + 241 + 242 + 243 + 244 + 245 + 246 + 247 + 248 + 249 + 250 + 251 + 252 + 253 + 254 + 255 + 256 + 257 + 258 + 259 + 260 + 261 + 262 + 263 + 264 + 265 + 266 + 267 + 268 + 269 + 270 + 271 + 272 + 273 + 274 + 275 + 276 + 277 + 278 + 279 + 280 + 281 + 282 + 283 + 284 + 285 + 286 + 287 + 288 + 289 + 290 + 291 + 292 + 293 + 294 + 295 + 296 + 297 + 298 + 299 + 300 + 301 + 302 + 303 + 304 + 305 + 306 + 307 + 308 + 309 + 310 + 311 + 312 + 313 + 314 + 315 + 316 + 317 + 318 + 319 + 320 + 321 + 322 + 323 + 324 + 325 + 326 + 327 + 328 + 329 + 330 + 331 + 332 + 333 + 334 + 335 + 336 + 337 + 338 + 339 + 340 + 341 + 342 + 343 + 344 + 345 + 346 + 347 + 348 + 349 + 350 + 351 + 352 + 353 + 354 + 355 + 356 + 357 + 358 + 359 + 360 + 361 + 362 + 363 + 364 + 365 + 366 + 367 + 368 + 369 + 370 + 371 + 372 + 373 + 374 + 375 + 376 + 377 + 378 + 379 + 380 + 381 + 382 + 383 + 384 + 385 + 386 + 387 + 388 + 389 + 390 + 391 + 392 + 393 + 394 + 395 + 396 + 397 + 398 + 399 + 400 + 401 + 402 + 403 + 404 + 405 + 406 + 407 + 408 + 409 + 410 + 411 + 412 + 413 + 414 + 415 + 416 + 417 + 418 + 419 + 420 + 421 + 422 + 423 + 424 + 425 + 426 + 427 + 428 + 429 + 430 + 431 + 432 + 433 + 434 + 435 + 436 + 437 + 438 + 439 + 440 + 441 + 442 + 443 + 444 + 445 + 446 + 447 + 448 + | +import math
+from typing import Any, Dict, List
+from .parser import (
+ Expression,
+ IntegerLiteral,
+ FloatLiteral,
+ BooleanLiteral,
+ NullLiteral,
+ CharLiteral,
+ StringLiteral,
+ PrefixExpression,
+ InfixExpression,
+ Identifier,
+ LetStatement,
+ ConstStatement,
+ AssignExpression,
+ BlockStatement,
+ IfExpression,
+ ExpressionStatement,
+ CallExpression,
+ ForStatement,
+ WhileStatement,
+ ContinueStatement,
+ BreakStatement,
+ FunctionStatement,
+ ReturnStatement,
+)
+from .tokens import Token
+
+
+class _BreakSignal(Exception):
+ pass
+
+
+class _ContinueSignal(Exception):
+ pass
+
+
+class _ReturnSignal(Exception):
+ def __init__(self, value):
+ self.value = value
+
+
+class RuntimeEvaluationError(Exception):
+ pass
+
+
+class _ExitSignal(Exception):
+ def __init__(self, code: int): # pragma: no cover
+ self.code = code
+
+
+Env = List[Dict[str, tuple[Any, bool]]]
+Context = Dict[str, tuple[Any, bool]]
+
+
+def _env_stack(env: "Env | Context") -> Env:
+ return env if isinstance(env, list) else [env]
+
+
+def setup_runtime(arg: str) -> Env:
+ env: Env = [{}]
+ _declare_var(env, "arg", arg, True)
+ return env
+
+
+def _get_var(env: "Env | Context", name: str) -> tuple[Any, bool]:
+ stack = _env_stack(env)
+ for context in stack[::-1]:
+ if name in context:
+ return context[name]
+ raise RuntimeEvaluationError(f"Undefined variable '{name}'")
+
+
+def _check_integer_operands(left: Any, right: Any) -> None:
+ if not isinstance(left, int) or not isinstance(right, int):
+ raise RuntimeEvaluationError("Bitwise operations require integers")
+
+
+def _declare_var(env: "Env | Context", name: str, value: Any, is_const: bool) -> Any:
+ stack = _env_stack(env)
+ if name in stack[-1] and stack[-1][name][1]:
+ raise RuntimeEvaluationError(f"Cannot redeclare constant '{name}'")
+ stack[-1][name] = (value, is_const)
+ return value
+
+
+def _assign_var(env: "Env | Context", name: str, value: Any) -> Any:
+ context = _get_declaration_context(env, name)
+ _, is_const = context[name]
+ if is_const:
+ raise RuntimeEvaluationError(f"Cannot assign to constant '{name}'")
+ context[name] = (value, False)
+ return value
+
+
+def _get_declaration_context(env: "Env | Context", name: str) -> Context:
+ stack = _env_stack(env)
+ for context in stack[::-1]:
+ if name in context:
+ return context
+ raise RuntimeEvaluationError(f"Undefined variable '{name}'")
+
+
+def _is_declared(env: Env, name: str) -> bool:
+ try:
+ _get_declaration_context(env, name)
+ return True
+ except RuntimeEvaluationError:
+ return False
+
+
+def _eval(node: Expression, env: "Env | Context") -> Any:
+ if isinstance(node, IntegerLiteral):
+ return int(node.value)
+
+ if isinstance(node, FloatLiteral):
+ return float(node.value)
+
+ if isinstance(node, BooleanLiteral):
+ return bool(node.literal)
+
+ if isinstance(node, NullLiteral):
+ return None
+
+ if isinstance(node, CharLiteral):
+ return str(node.literal)
+
+ if isinstance(node, StringLiteral):
+ return str(node.literal)
+
+ if isinstance(node, Identifier):
+ val, _is_const = _get_var(env, node.name)
+ return val
+
+ if isinstance(node, CallExpression):
+ name = node.identifier_name
+ args = []
+ for arg in node.parameters:
+ args.append(_eval(arg, env))
+ if name == "sqrt":
+ if len(args) != 1:
+ raise RuntimeEvaluationError("sqrt expects 1 arguments")
+ return float(math.sqrt(args[0]))
+ elif name == "pow":
+ if len(args) != 2:
+ raise RuntimeEvaluationError("pow expects 2 arguments")
+ return float(math.pow(args[0], args[1]))
+ elif name == "ceil":
+ if len(args) != 1:
+ raise RuntimeEvaluationError("ceil expects 1 arguments")
+ return int(math.ceil(args[0]))
+ elif name == "floor":
+ if len(args) != 1:
+ raise RuntimeEvaluationError("floor expects 1 arguments")
+ return int(math.floor(args[0]))
+ elif name == "abs":
+ if len(args) != 1:
+ raise RuntimeEvaluationError("abs expects 1 arguments")
+ return abs(args[0])
+ elif name == "println":
+ print(*args)
+ return None
+ elif name == "print":
+ print(*args, end="")
+ return None
+ elif name == "input": # pragma: no cover
+ if len(args) > 1:
+ raise RuntimeEvaluationError("input expects 0 or 1 arguments")
+ elif len(args) == 0:
+ return input()
+ if not isinstance(args[0], str):
+ raise RuntimeEvaluationError("input only expects a string input")
+ return input(str(args[0]))
+ elif name == "isInt":
+ if len(args) != 1:
+ raise RuntimeEvaluationError("is_int expects 1 argument")
+ return args[0].isnumeric()
+ elif name == "toInt":
+ if len(args) != 1:
+ raise RuntimeEvaluationError("to_int expects 1 argument")
+ try:
+ return int(args[0])
+ except (ValueError, TypeError):
+ raise RuntimeEvaluationError("Cannot convert value to int")
+ elif name == "isFloat":
+ if len(args) != 1:
+ raise RuntimeEvaluationError("is_float expects 1 argument")
+ try:
+ float(args[0])
+ return True
+ except (ValueError, TypeError):
+ return False
+ elif name == "toFloat":
+ if len(args) != 1:
+ raise RuntimeEvaluationError("to_float expects 1 argument")
+ try:
+ return float(args[0])
+ except (ValueError, TypeError):
+ raise RuntimeEvaluationError("Cannot convert value to float")
+ elif name == "toStr":
+ if len(args) != 1:
+ raise RuntimeEvaluationError("to_str expects 1 argument")
+ return str(args[0])
+ elif name == "concat":
+ if len(args) != 2:
+ raise RuntimeEvaluationError("concat expects 2 arguments")
+ for arg in args:
+ if not isinstance(arg, str):
+ raise RuntimeEvaluationError("Input is not a string")
+ return args[0] + args[1]
+ elif name == "trim":
+ if len(args) != 1:
+ raise RuntimeEvaluationError("trim expects 1 argument")
+ if not isinstance(args[0], str):
+ raise RuntimeEvaluationError("Input is not a string")
+ return args[0].strip()
+ elif name == "hasPrefix":
+ if len(args) != 2:
+ raise RuntimeEvaluationError("hasPrefix expects 2 arguments")
+ for arg in args:
+ if not isinstance(arg, str):
+ raise RuntimeEvaluationError("Input is not a string")
+ return args[1].startswith(args[0])
+ elif name == "hasSuffix":
+ if len(args) != 2:
+ raise RuntimeEvaluationError("hasSuffix expects 2 arguments")
+ for arg in args:
+ if not isinstance(arg, str):
+ raise RuntimeEvaluationError("Input is not a string")
+ return args[1].endswith(args[0])
+ elif name == "length":
+ if len(args) != 1:
+ raise RuntimeEvaluationError("length expects 1 argument")
+ if not isinstance(args[0], str):
+ raise RuntimeEvaluationError("Input is not a string")
+ return len(args[0])
+ elif name == "ifExists":
+ if len(args) != 1:
+ raise RuntimeEvaluationError("ifExists expects 1 argument")
+ if not isinstance(args[0], str):
+ raise RuntimeEvaluationError("Input is not a string")
+ return _is_declared(env, args[0])
+ elif name == "exit":
+ if len(args) != 1:
+ raise RuntimeEvaluationError("exit expects 1 argument")
+ if isinstance(args[0], bool) or not isinstance(args[0], int):
+ raise RuntimeEvaluationError("eixt argument must be int")
+ raise _ExitSignal(args[0])
+ elif name == "type":
+ if len(args) != 1:
+ raise RuntimeEvaluationError("type expects 1 argument")
+ v = args[0]
+
+ if v is None:
+ return "null"
+ if isinstance(v, bool):
+ return "boolean"
+ if isinstance(v, int):
+ return "integer"
+ if isinstance(v, float):
+ return "float"
+ if isinstance(v, str):
+ return "string"
+ if isinstance(v, FunctionStatement):
+ return "function"
+
+ return "unknown"
+ else:
+ stack = _env_stack(env)
+ stack.append({})
+ function = _get_var(stack, node.identifier_name)[0]
+ if not isinstance(function, FunctionStatement):
+ stack.pop()
+ raise RuntimeEvaluationError(
+ "Looked for function but found another identifier instead"
+ )
+
+ if len(node.parameters) != len(function.variables):
+ stack.pop()
+ raise RuntimeEvaluationError(
+ "Number of parameters passed is not equal to number of function parameters"
+ )
+ for identifier, expression in zip(function.variables, node.parameters):
+ _declare_var(stack, identifier.name, _eval(expression, env), False)
+ try:
+ result = _eval(function.block, stack)
+ stack.pop()
+ return result
+ except _ReturnSignal as e:
+ stack.pop()
+ return e.value
+
+ if isinstance(node, PrefixExpression):
+ right = _eval(node.right, env) if node.right is not None else None
+ op = node.token
+ if op == Token.MINUS:
+ return -right
+ if op == Token.PLUS:
+ return +right
+ if op == Token.NOT:
+ return not bool(right)
+ raise RuntimeEvaluationError(f"Unsupported prefix operator '{op}'")
+
+ if isinstance(node, InfixExpression):
+ left = _eval(node.lhs, env)
+ right = _eval(node.rhs, env)
+ t = node.operation
+
+ # arithmetic
+ if t == Token.PLUS:
+ return left + right
+ if t == Token.MINUS:
+ return left - right
+ if t == Token.ASTERISK:
+ return left * right
+ if t == Token.SLASH:
+ if right == 0:
+ raise RuntimeEvaluationError("Division by zero")
+ return left / right
+
+ # bitwise
+ if t == Token.BITWISE_AND:
+ _check_integer_operands(left, right)
+ return left & right
+ if t == Token.BITWISE_OR:
+ _check_integer_operands(left, right)
+ return left | right
+ if t == Token.BITWISE_XOR:
+ _check_integer_operands(left, right)
+ return left ^ right
+
+ # comparisons
+ if t == Token.EQUAL:
+ return left == right
+ if t == Token.NOTEQUAL:
+ return left != right
+ if t == Token.LESS:
+ return left < right
+ if t == Token.LESSEQUAL:
+ return left <= right
+ if t == Token.GREATER:
+ return left > right
+ if t == Token.GREATEREQUAL:
+ return left >= right
+
+ # logical
+ if t == Token.AND:
+ return bool(left) and bool(right)
+ if t == Token.OR:
+ return bool(left) or bool(right)
+
+ raise RuntimeEvaluationError(f"Unsupported infix operator '{t}'")
+
+ if isinstance(node, LetStatement):
+ value = _eval(node.expression, env)
+ return _declare_var(env, node.identifier.name, value, is_const=False)
+
+ if isinstance(node, ConstStatement):
+ value = _eval(node.expression, env)
+ return _declare_var(env, node.identifier.name, value, is_const=True)
+
+ if isinstance(node, AssignExpression):
+ if not isinstance(node.lhs, Identifier):
+ raise RuntimeEvaluationError(
+ "Left-hand side of assignment must be a variable"
+ )
+ value = _eval(node.rhs, env)
+ return _assign_var(env, node.lhs.name, value)
+
+ if isinstance(node, ExpressionStatement):
+ if node.expression is None:
+ return None
+ return _eval(node.expression, env)
+
+ if isinstance(node, BlockStatement):
+ result = None
+ for stmt in node.statements:
+ result = _eval(stmt, env)
+ return result
+
+ if isinstance(node, IfExpression):
+ cond = _eval(node.condition, env)
+ if cond:
+ return _eval(node.consequence, env) if node.consequence else None
+ return _eval(node.alternative, env) if node.alternative else None
+
+ if isinstance(node, ContinueStatement):
+ raise _ContinueSignal()
+ if isinstance(node, BreakStatement):
+ raise _BreakSignal()
+
+ if isinstance(node, ForStatement):
+ _eval(node.initialization, env)
+
+ result = None
+ while bool(_eval(node.condition, env)):
+ try:
+ result = _eval(node.block, env)
+ except _ContinueSignal:
+ _eval(node.increment, env)
+ continue
+ except _BreakSignal:
+ break
+ _eval(node.increment, env)
+ return result
+
+ if isinstance(node, WhileStatement):
+ result = None
+ while bool(_eval(node.condition, env)):
+ try:
+ result = _eval(node.block, env)
+ except _ContinueSignal:
+ continue
+ except _BreakSignal:
+ break
+ return result
+
+ if isinstance(node, FunctionStatement):
+ _declare_var(env, node.identifier.name, node, True)
+ return None
+
+ if isinstance(node, ReturnStatement):
+ evaluation = _eval(node.expression, env) if node.expression != None else ""
+ raise _ReturnSignal(evaluation)
+
+ raise RuntimeEvaluationError(
+ f"Evaluation not implemented for node type {type(node).__name__}"
+ )
+
+
+def evaluate(expressions: list[Expression], env: Env | None = None) -> Any:
+ if env is None:
+ env = [{}]
+
+ try:
+ result = None
+ for expression in expressions:
+ result = _eval(expression, env)
+ return result
+ except _ExitSignal as e:
+ return e.code
+ except _ReturnSignal as e:
+ return e.value
+ except _ContinueSignal:
+ raise RuntimeEvaluationError("continue used outside loop")
+ except _BreakSignal:
+ raise RuntimeEvaluationError("break used outside loop")
+ |
+