From b4af19181c71a0a0c950967a8720ebcd8a9e663b Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Tue, 22 Aug 2023 16:43:11 +0300 Subject: [PATCH 1/4] Include utbot-executor, support global variables and add poetry installation --- settings.gradle.kts | 1 + utbot-python-executor/.gitignore | 26 ++ utbot-python-executor/build.gradle.kts | 109 +++++ .../kotlin/org/utbot/python/UtbotExecutor.kt | 4 + .../src/main/python/utbot_executor/README.md | 127 +++++ .../main/python/utbot_executor/pyproject.toml | 21 + .../python/utbot_executor/tests/__init__.py | 0 .../python/utbot_executor/tests/pytest.ini | 2 + .../tests/test_deep_serialization.py | 396 ++++++++++++++++ .../utbot_executor/utbot_executor/__init__.py | 0 .../utbot_executor/utbot_executor/__main__.py | 36 ++ .../deep_serialization/__init__.py | 0 .../deep_serialization/config.py | 5 + .../deep_serialization/deep_serialization.py | 65 +++ .../deep_serialization/example.py | 87 ++++ .../deep_serialization/json_converter.py | 259 +++++++++++ .../deep_serialization/memory_objects.py | 437 ++++++++++++++++++ .../deep_serialization/test_json.json | 202 ++++++++ .../deep_serialization/utils.py | 182 ++++++++ .../utbot_executor/example/__init__.py | 0 .../utbot_executor/example/example.py | 26 ++ .../utbot_executor/example/my_func.py | 14 + .../utbot_executor/utbot_executor/executor.py | 212 +++++++++ .../utbot_executor/utbot_executor/listener.py | 88 ++++ .../utbot_executor/memory_compressor.py | 17 + .../utbot_executor/utbot_executor/parser.py | 99 ++++ .../utbot_executor/ut_tracer.py | 62 +++ .../utbot_executor/utbot_executor/utils.py | 14 + .../src/main/resources/utbot_executor_version | 1 + .../python/executor/TestDeepSerialization.kt | 12 + utbot-python-types/build.gradle.kts | 21 +- utbot-python/build.gradle.kts | 1 + utbot-python/docs/python_packages.md | 23 +- .../samples/type_inference/annotations2.py | 5 + utbot-python/samples/test_configuration.json | 2 +- .../evaluation/PythonCoverageReceiver.kt | 9 +- .../serialiation/PythonObjectParser.kt | 9 +- .../utbot/python/utils/RequirementsUtils.kt | 9 +- 38 files changed, 2571 insertions(+), 12 deletions(-) create mode 100644 utbot-python-executor/.gitignore create mode 100644 utbot-python-executor/build.gradle.kts create mode 100644 utbot-python-executor/src/main/kotlin/org/utbot/python/UtbotExecutor.kt create mode 100644 utbot-python-executor/src/main/python/utbot_executor/README.md create mode 100644 utbot-python-executor/src/main/python/utbot_executor/pyproject.toml create mode 100644 utbot-python-executor/src/main/python/utbot_executor/tests/__init__.py create mode 100644 utbot-python-executor/src/main/python/utbot_executor/tests/pytest.ini create mode 100644 utbot-python-executor/src/main/python/utbot_executor/tests/test_deep_serialization.py create mode 100644 utbot-python-executor/src/main/python/utbot_executor/utbot_executor/__init__.py create mode 100644 utbot-python-executor/src/main/python/utbot_executor/utbot_executor/__main__.py create mode 100644 utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/__init__.py create mode 100644 utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/config.py create mode 100644 utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/deep_serialization.py create mode 100644 utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/example.py create mode 100644 utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/json_converter.py create mode 100644 utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/memory_objects.py create mode 100644 utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/test_json.json create mode 100644 utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/utils.py create mode 100644 utbot-python-executor/src/main/python/utbot_executor/utbot_executor/example/__init__.py create mode 100644 utbot-python-executor/src/main/python/utbot_executor/utbot_executor/example/example.py create mode 100644 utbot-python-executor/src/main/python/utbot_executor/utbot_executor/example/my_func.py create mode 100644 utbot-python-executor/src/main/python/utbot_executor/utbot_executor/executor.py create mode 100644 utbot-python-executor/src/main/python/utbot_executor/utbot_executor/listener.py create mode 100644 utbot-python-executor/src/main/python/utbot_executor/utbot_executor/memory_compressor.py create mode 100644 utbot-python-executor/src/main/python/utbot_executor/utbot_executor/parser.py create mode 100644 utbot-python-executor/src/main/python/utbot_executor/utbot_executor/ut_tracer.py create mode 100644 utbot-python-executor/src/main/python/utbot_executor/utbot_executor/utils.py create mode 100644 utbot-python-executor/src/main/resources/utbot_executor_version create mode 100644 utbot-python-executor/src/test/kotlin/org/utbot/python/executor/TestDeepSerialization.kt diff --git a/settings.gradle.kts b/settings.gradle.kts index 28ebb71bdc..78ff02e22e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -66,6 +66,7 @@ if (pythonIde.split(",").contains(ideType)) { include("utbot-intellij-python") include("utbot-python-parser") include("utbot-python-types") + include("utbot-python-executor") } include("utbot-spring-sample") diff --git a/utbot-python-executor/.gitignore b/utbot-python-executor/.gitignore new file mode 100644 index 0000000000..644f16b8e6 --- /dev/null +++ b/utbot-python-executor/.gitignore @@ -0,0 +1,26 @@ +dist/ +__pycache__/ +build/ +develop-eggs/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST +.pytest_cache/ +poetry.lock +.env/ +.venv/ +env/ +venv/ +.mypy_cache/ +.dmypy.json +dmypy.json \ No newline at end of file diff --git a/utbot-python-executor/build.gradle.kts b/utbot-python-executor/build.gradle.kts new file mode 100644 index 0000000000..65d3776738 --- /dev/null +++ b/utbot-python-executor/build.gradle.kts @@ -0,0 +1,109 @@ +val kotlinLoggingVersion: String? by rootProject + +val utbotExecutorVersion = File(project.projectDir, "src/main/resources/utbot_executor_version").readText() +// these two properties --- from GRADLE_USER_HOME/gradle.properties +val pypiToken: String? by project +val pythonInterpreter: String? by project +val utbotExecutorPath = File(project.projectDir, "src/main/python/utbot_executor") +val localUtbotExecutorPath = File(utbotExecutorPath, "dist") + +tasks.register("cleanDist") { + group = "python" + delete(localUtbotExecutorPath.canonicalPath) +} + +val installPoetry = + if (pythonInterpreter != null) { + tasks.register("installPoetry") { + group = "python" + workingDir = utbotExecutorPath + commandLine(pythonInterpreter, "-m", "pip", "install", "poetry") + } + } else { + null + } + +val setExecutorVersion = + if (pythonInterpreter != null) { + tasks.register("setVersion") { + dependsOn(installPoetry!!) + group = "python" + workingDir = utbotExecutorPath + commandLine(pythonInterpreter, "-m", "poetry", "version", utbotExecutorVersion) + } + } else { + null + } + +val buildExecutor = + if (pythonInterpreter != null) { + tasks.register("buildUtbotExecutor") { + dependsOn(setExecutorVersion!!) + group = "python" + workingDir = utbotExecutorPath + commandLine(pythonInterpreter, "-m", "poetry", "build") + } + } else { + null + } + +if (pythonInterpreter != null && pypiToken != null) { + tasks.register("publishUtbotExecutor") { + dependsOn(buildExecutor!!) + group = "python" + workingDir = utbotExecutorPath + commandLine( + pythonInterpreter, + "-m", + "poetry", + "publish", + "-u", + "__token__", + "-p", + pypiToken + ) + } +} + +val installExecutor = + if (pythonInterpreter != null) { + tasks.register("installUtbotExecutor") { + dependsOn(buildExecutor!!) + group = "python" + environment("PIP_FIND_LINKS" to localUtbotExecutorPath.canonicalPath) + commandLine( + pythonInterpreter, + "-m", + "pip", + "install", + "utbot_executor==$utbotExecutorVersion" + ) + } + } else { + null + } + +val installPytest = + if (pythonInterpreter != null) { + tasks.register("installPytest") { + group = "pytest" + workingDir = utbotExecutorPath + commandLine(pythonInterpreter, "-m", "pip", "install", "pytest") + } + } else { + null + } + +if (pythonInterpreter != null) { + tasks.register("runTests") { + dependsOn(installExecutor!!) + dependsOn(installPytest!!) + group = "pytest" + workingDir = utbotExecutorPath + commandLine( + pythonInterpreter, + "-m", + "pytest", + ) + } +} diff --git a/utbot-python-executor/src/main/kotlin/org/utbot/python/UtbotExecutor.kt b/utbot-python-executor/src/main/kotlin/org/utbot/python/UtbotExecutor.kt new file mode 100644 index 0000000000..e4b05cc9e3 --- /dev/null +++ b/utbot-python-executor/src/main/kotlin/org/utbot/python/UtbotExecutor.kt @@ -0,0 +1,4 @@ +package org.utbot.python + +class UtbotExecutor { +} \ No newline at end of file diff --git a/utbot-python-executor/src/main/python/utbot_executor/README.md b/utbot-python-executor/src/main/python/utbot_executor/README.md new file mode 100644 index 0000000000..05197bc444 --- /dev/null +++ b/utbot-python-executor/src/main/python/utbot_executor/README.md @@ -0,0 +1,127 @@ +# UtBot Executor + +Util for python code execution and state serialization. + +## Installation + +You can install module from [PyPI](https://pypi.org/project/utbot-executor/): + +```bash +python -m pip install utbot-executor +``` + +## Usage + +### From console with socket listener + +Run with your `` and `` for socket connection +```bash +$ python -m utbot_executor [] +``` + +### Request format +```json +{ + "functionName": "f", + "functionModule": "my_module.submod1", + "imports": ["sys", "math", "json"], + "syspaths": ["/home/user/my_project/"], + "argumentsIds": ["1", "2"], + "kwargumentsIds": ["4", "5"], + "serializedMemory": "string", + "filepath": ["/home/user/my_project/my_module/submod1.py"], + "coverageId": "1" +} +``` + +* `functionName` - name of the tested function +* `functionModule` - name of the module of the tested function +* `imports` - all modules which need to run function with current arguments +* `syspaths` - all syspaths which need to import modules (usually it is a project root) +* `argumentsIds` - list of argument's ids +* `kwargumentsIds` - list of keyword argument's ids +* `serializedMemory` - serialized memory throw `deep_serialization` algorithm +* `filepath` - path to the tested function's containing file +* `coverageId` - special id witch will be used for sending information about covered lines + +### Response format: + +If execution is successful: +```json +{ + "status": "success", + "isException": false, + "statements": [1, 2, 3], + "missedStatements": [4, 5], + "stateInit": "string", + "stateBefore": "string", + "stateAfter": "string", + "diffIds": ["3", "4"], + "argsIds": ["1", "2", "3"], + "kwargs": ["4", "5", "6"], + "resultId": "7" +} +``` + +* `status` - always "success" +* `isException` - boolean value, if it is `true`, execution ended with an exception +* `statements` - list of the numbers of covered rows +* `missedStatements` - list of numbers of uncovered rows +* `stateInit` - serialized states from request +* `stateBefore` - serialized states of arguments before execution +* `stateAfter` - serialized states of arguments after execution +* `diffIds` - ids of the objects which have been changed +* `argsIds` - ids of the function's arguments +* `kwargsIds` - ids of the function's keyword arguments +* `resultId` - id of the returned value + +or error format if there was exception in running algorith: + +```json +{ + "status": "fail", + "exception": "stacktrace" +} +``` +* `status` - always "fail" +* `exception` - string representation of the exception stack trace + +### Submodule `deep_serialization` + +JSON serializer and deserializer for python objects + +#### States memory json-format + +```json +{ + "objects": { + "id": { + "id": "1", + "strategy": "strategy name", + "typeinfo": { + "module": "builtins", + "kind": "int" + }, + "comparable": true, + + // iff strategy is 'repr' + "value": "1", + + // iff strategy is 'list' or 'dict' + "items": ["3", "2"], + + // iff strategy = 'reduce' + "constructor": "mymod.A.__new__", + "args": ["mymod.A"], + "state": {"a": "4", "b": "5"}, + "listitems": ["7", "8"], + "dictitems": {"ka": "10"} + } + } +} +``` + + +## Source + +GitHub [repository](https://github.com/tamarinvs19/utbot_executor) diff --git a/utbot-python-executor/src/main/python/utbot_executor/pyproject.toml b/utbot-python-executor/src/main/python/utbot_executor/pyproject.toml new file mode 100644 index 0000000000..19808b6cf3 --- /dev/null +++ b/utbot-python-executor/src/main/python/utbot_executor/pyproject.toml @@ -0,0 +1,21 @@ +[tool.poetry] +name = "utbot-executor" +version = "1.4.39" +description = "" +authors = ["Vyacheslav Tamarin "] +readme = "README.md" +packages = [{include = "utbot_executor"}] + +[tool.poetry.dependencies] +python = "^3.8" + +[tool.poetry.dev-dependencies] +pytest = "^7.3" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry.scripts] +utbot-executor = "utbot_executor:utbot_executor" + diff --git a/utbot-python-executor/src/main/python/utbot_executor/tests/__init__.py b/utbot-python-executor/src/main/python/utbot_executor/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python-executor/src/main/python/utbot_executor/tests/pytest.ini b/utbot-python-executor/src/main/python/utbot_executor/tests/pytest.ini new file mode 100644 index 0000000000..d7d93225a4 --- /dev/null +++ b/utbot-python-executor/src/main/python/utbot_executor/tests/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +python_files = test_*.py *_test.py *_tests.py \ No newline at end of file diff --git a/utbot-python-executor/src/main/python/utbot_executor/tests/test_deep_serialization.py b/utbot-python-executor/src/main/python/utbot_executor/tests/test_deep_serialization.py new file mode 100644 index 0000000000..6c6a7d4b63 --- /dev/null +++ b/utbot-python-executor/src/main/python/utbot_executor/tests/test_deep_serialization.py @@ -0,0 +1,396 @@ +import collections +import dataclasses +import datetime +import json +import re +import sys +import typing + +import pytest + +from utbot_executor.deep_serialization import json_converter +from utbot_executor.deep_serialization.deep_serialization import ( + serialize_objects_dump, + deserialize_objects, +) + + +def get_deserialized_obj(obj: typing.Any, imports: typing.List[str]): + serialized_obj_ids, _, serialized_memory_dump = serialize_objects_dump([obj], True) + deserialized_objs = deserialize_objects( + serialized_obj_ids, serialized_memory_dump, imports + ) + return deserialized_objs[serialized_obj_ids[0]] + + +def template_test_assert(obj: typing.Any, imports: typing.List[str]): + assert obj == get_deserialized_obj(obj, imports) + + +@pytest.mark.parametrize( + "obj", + [ + (1,), + ("abc",), + (1.23,), + (False,), + (True,), + (b"123",), + (r"1\n23",), + ([1, 2, 3],), + (["a", 2, 3],), + ([],), + ({1, 2},), + (set(),), + ({1: 2, "3": "4"},), + ({},), + ((1, 2, 3),), + (tuple(),), + ], +) +def test_primitives(obj: typing.Any): + template_test_assert(obj, []) + + +@pytest.mark.parametrize( + "obj,imports", + [ + (datetime.datetime(2023, 6, 23), ["datetime"]), + (collections.deque([1, 2, 3]), ["collections"]), + (collections.Counter("jflaskdfjslkdruovnerjf:a"), ["collections"]), + (collections.defaultdict(int, [(1, 2)]), ["collections"]), + ], +) +def test_with_imports(obj: typing.Any, imports: typing.List[str]): + template_test_assert(obj, imports) + + +@dataclasses.dataclass +class MyDataClass: + a: int + b: str + c: typing.List[int] + d: typing.Dict[str, bytes] + + +@pytest.mark.parametrize( + "obj,imports", + [ + ( + MyDataClass(1, "a", [1, 2], {"a": b"c"}), + ["tests.test_deep_serialization"], + ), + ( + MyDataClass(1, "a--------------\n\t", [], {}), + ["tests.test_deep_serialization"], + ), + ], +) +def test_dataclasses(obj: typing.Any, imports: typing.List[str]): + template_test_assert(obj, imports) + + +class MyClass: + def __init__(self, a: int, b: str, c: typing.List[int], d: typing.Dict[str, bytes]): + self.a = a + self.b = b + self.c = c + self.d = d + + def __eq__(self, other): + if not isinstance(other, MyClass): + return False + return ( + self.a == other.a + and self.b == other.b + and self.c == other.c + and self.d == other.d + ) + + +class EmptyClass: + def __eq__(self, other): + return isinstance(other, EmptyClass) + + +class EmptyInitClass: + def __init__(self): + pass + + def __eq__(self, other): + return isinstance(other, EmptyInitClass) + + +@pytest.mark.parametrize( + "obj,imports", + [ + ( + MyClass(1, "a", [1, 2], {"a": b"c"}), + ["tests.test_deep_serialization"], + ), + ( + MyClass(1, "a--------------\n\t", [], {}), + ["tests.test_deep_serialization"], + ), + (EmptyClass(), ["tests.test_deep_serialization"]), + (EmptyInitClass(), ["tests.test_deep_serialization"]), + ], +) +def test_classes(obj: typing.Any, imports: typing.List[str]): + template_test_assert(obj, imports) + + +class MyClassWithSlots: + __slots__ = ["a", "b", "c", "d"] + + def __init__(self, a: int, b: str, c: typing.List[int], d: typing.Dict[str, bytes]): + self.a = a + self.b = b + self.c = c + self.d = d + + def __eq__(self, other): + if not isinstance(other, MyClassWithSlots): + return False + return ( + self.a == other.a + and self.b == other.b + and self.c == other.c + and self.d == other.d + ) + + def __str__(self): + return f"" + + def __setstate__(self, state): + for key, value in state[1].items(): + self.__setattr__(key, value) + + +@pytest.mark.parametrize( + "obj,imports", + [ + ( + MyClassWithSlots(1, "a", [1, 2], {"a": b"c"}), + ["tests.test_deep_serialization", "copyreg"], + ), + ( + MyClassWithSlots(1, "a--------------\n\t", [], {}), + ["tests.test_deep_serialization", "copyreg"], + ), + ], +) +def test_classes_with_slots(obj: typing.Any, imports: typing.List[str]): + template_test_assert(obj, imports) + + +def test_comparable(): + obj = EmptyClass() + serialized_obj_ids, _, serialized_memory_dump = serialize_objects_dump([obj], True) + memory_dump = json_converter.deserialize_memory_objects(serialized_memory_dump) + assert memory_dump.objects[serialized_obj_ids[0]].comparable + + +class IncomparableClass: + pass + + +def test_incomparable(): + obj = IncomparableClass() + serialized_obj_ids, _, serialized_memory_dump = serialize_objects_dump([obj], True) + memory_dump = json_converter.deserialize_memory_objects(serialized_memory_dump) + assert not memory_dump.objects[serialized_obj_ids[0]].comparable + + +def test_recursive_list(): + obj = [1, 2, 3] + obj.append(obj) + + serialized_obj_ids, _, serialized_memory_dump = serialize_objects_dump([obj], True) + memory_dump = json_converter.deserialize_memory_objects(serialized_memory_dump) + assert not memory_dump.objects[serialized_obj_ids[0]].comparable + + deserialized_objs = deserialize_objects( + serialized_obj_ids, + serialized_memory_dump, + ["tests.test_deep_serialization", "copyreg"], + ) + deserialized_obj = deserialized_objs[serialized_obj_ids[0]] + assert deserialized_obj[0] == deserialized_obj[-1][0] + + +def test_recursive_list_in_tuple(): + ls = [] + ts = (ls, 1) + ls.append(ts) + + serialized_obj_ids, _, serialized_memory_dump = serialize_objects_dump([ts], True) + memory_dump = json_converter.deserialize_memory_objects(serialized_memory_dump) + assert not memory_dump.objects[serialized_obj_ids[0]].comparable + + deserialized_objs = deserialize_objects( + serialized_obj_ids, + serialized_memory_dump, + ["tests.test_deep_serialization", "copyreg"], + ) + deserialized_obj = deserialized_objs[serialized_obj_ids[0]] + assert isinstance(deserialized_obj, tuple) + assert isinstance(deserialized_obj[0], list) + assert deserialized_obj == deserialized_obj[0][0] + assert deserialized_obj[1] == 1 + + +def test_recursive_dict(): + obj = {1: 2} + obj[3] = obj + + serialized_obj_ids, _, serialized_memory_dump = serialize_objects_dump([obj], True) + memory_dump = json_converter.deserialize_memory_objects(serialized_memory_dump) + assert not memory_dump.objects[serialized_obj_ids[0]].comparable + + deserialized_objs = deserialize_objects( + serialized_obj_ids, + serialized_memory_dump, + ["tests.test_deep_serialization", "copyreg"], + ) + deserialized_obj = deserialized_objs[serialized_obj_ids[0]] + assert deserialized_obj[1] == deserialized_obj[3][1] + + +def test_deep_recursive_list(): + inner_inner = [7, 8, 9] + inner = [4, 5, 6] + obj = [1, 2, 3] + obj.append(inner) + inner.append(inner_inner) + inner_inner.append(obj) + + serialized_obj_ids, _, serialized_memory_dump = serialize_objects_dump([obj], True) + memory_dump = json_converter.deserialize_memory_objects(serialized_memory_dump) + assert not memory_dump.objects[serialized_obj_ids[0]].comparable + + deserialized_objs = deserialize_objects( + serialized_obj_ids, + serialized_memory_dump, + ["tests.test_deep_serialization", "copyreg"], + ) + deserialized_obj = deserialized_objs[serialized_obj_ids[0]] + assert deserialized_obj == deserialized_obj[-1][-1][-1] + + +class Node: + def __init__(self, name: str): + self.name = name + self.children: typing.List[Node] = [] + + def __eq__(self, other): + return self.name == other.name + + +def test_recursive_object(): + node1 = Node("1") + node2 = Node("2") + node3 = Node("3") + node1.children.append(node2) + node2.children.append(node3) + node3.children.append(node1) + + serialized_obj_ids, _, serialized_memory_dump = serialize_objects_dump( + [node1], True + ) + deserialized_objs = deserialize_objects( + serialized_obj_ids, + serialized_memory_dump, + ["tests.test_deep_serialization", "copyreg"], + ) + deserialized_obj = deserialized_objs[serialized_obj_ids[0]] + assert deserialized_obj == deserialized_obj.children[0].children[0].children[0] + + +@pytest.mark.parametrize( + "obj,imports", + [ + ( + collections.Counter("abcababa"), + ["tests.test_deep_serialization", "collections"], + ), + ( + collections.UserDict({1: "a"}), + ["tests.test_deep_serialization", "collections"], + ), + ( + collections.deque([1, 2, 3]), + ["tests.test_deep_serialization", "collections"], + ), + ], +) +def test_collections(obj: typing.Any, imports: typing.List[str]): + template_test_assert(obj, imports) + + +@pytest.mark.parametrize( + "obj,strategy", + [ + (1, "repr"), + ("1", "repr"), + ([1, 2], "list"), + ({1, 2}, "list"), + ((1, 2), "list"), + ({1: 2}, "dict"), + (collections.Counter("faksjdf"), "reduce"), + ], +) +def test_strategy(obj: typing.Any, strategy: str): + serialized_obj_ids, _, serialized_memory_dump = serialize_objects_dump([obj], True) + deserialized_data = json.loads(serialized_memory_dump) + assert deserialized_data["objects"][serialized_obj_ids[0]]["strategy"] == strategy + + +@pytest.mark.parametrize( + "obj,imports", + [ + (re.compile(r"\d+jflsf"), ["tests.test_deep_serialization", "re"]), + ( + collections.abc.KeysView, + ["tests.test_deep_serialization", "collections"], + ), + ( + collections.abc.KeysView({}), + [ + "tests.test_deep_serialization", + "collections", + "collections.abc", + ], + ), + ], +) +def test_corner_cases(obj: typing.Any, imports: typing.List[str]): + template_test_assert(obj, imports) + + +T = typing.TypeVar("T") + + +@pytest.mark.skipif( + sys.version_info.major <= 3 and sys.version_info.minor < 11, + reason="typing.TypeVarTuple (PEP 646) has been added in Python 3.11", + ) +def test_type_var_tuple(): + globals()["T2"] = typing.TypeVarTuple("T2") + obj = typing.TypeVarTuple("T2") + imports = ["tests.test_deep_serialization", "typing"] + + deserialized_obj = get_deserialized_obj(obj, imports) + assert deserialized_obj.__name__ == obj.__name__ + + +@pytest.mark.parametrize( + "obj,imports", + [ + (typing.TypeVar("T"), ["tests.test_deep_serialization", "typing"]), + (T, ["tests.test_deep_serialization", "typing"]), + ], +) +def test_type_var(obj: typing.Any, imports: typing.List[str]): + deserialized_obj = get_deserialized_obj(obj, imports) + assert deserialized_obj.__name__ == obj.__name__ diff --git a/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/__init__.py b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/__main__.py b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/__main__.py new file mode 100644 index 0000000000..ab1ce3fb60 --- /dev/null +++ b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/__main__.py @@ -0,0 +1,36 @@ +import argparse +import logging + +from utbot_executor.listener import PythonExecuteServer + + +def main(hostname: str, port: int, coverage_hostname: str, coverage_port: str): + server = PythonExecuteServer(hostname, port, coverage_hostname, coverage_port) + server.run() + + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + prog='UtBot Python Executor', + description='Listen socket stream and execute function value', + ) + parser.add_argument('hostname') + parser.add_argument('port', type=int) + parser.add_argument('--logfile', default=None) + parser.add_argument( + '--loglevel', + choices=["DEBUG", "INFO", "ERROR"], + default="ERROR", + ) + parser.add_argument('coverage_hostname') + parser.add_argument('coverage_port', type=int) + args = parser.parse_args() + + loglevel = {"DEBUG": logging.DEBUG, "INFO": logging.INFO, "ERROR": logging.ERROR}[args.loglevel] + logging.basicConfig( + filename=args.logfile, + format='%(asctime)s | %(levelname)s | %(funcName)s - %(message)s', + datefmt='%m/%d/%Y %H:%M:%S', + level=loglevel, + ) + main(args.hostname, args.port, args.coverage_hostname, args.coverage_port) diff --git a/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/__init__.py b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/config.py b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/config.py new file mode 100644 index 0000000000..08503cc203 --- /dev/null +++ b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/config.py @@ -0,0 +1,5 @@ +from typing import Final, NewType + +PICKLE_PROTO: Final = 4 + +PythonId = NewType('PythonId', str) diff --git a/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/deep_serialization.py b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/deep_serialization.py new file mode 100644 index 0000000000..2d0a506c97 --- /dev/null +++ b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/deep_serialization.py @@ -0,0 +1,65 @@ +import json +from typing import Any, Dict, Tuple, List + +from utbot_executor.deep_serialization.memory_objects import PythonSerializer, MemoryDump +from utbot_executor.deep_serialization.json_converter import MemoryDumpEncoder, deserialize_memory_objects, DumpLoader +from utbot_executor.deep_serialization.utils import PythonId + + +def serialize_memory_dump(dump: MemoryDump): + return json.dumps({'objects': dump}, cls=MemoryDumpEncoder) + + +def serialize_object(obj: Any) -> Tuple[str, str]: + """ + Serialize one object. + Returns the object id and memory dump. + """ + + serializer = PythonSerializer() + id_ = serializer.write_object_to_memory(obj) + return id_, serialize_memory_dump(serializer.memory) + + +def serialize_objects(objs: List[Any], clear_visited: bool = False) -> Tuple[List[PythonId], str]: + """ + Serialize objects with shared memory. + Returns list of object ids and memory dump. + """ + + serializer = PythonSerializer() + if clear_visited: + serializer.clear_visited() + ids = [ + serializer.write_object_to_memory(obj) + for obj in objs + ] + return ids, serialize_memory_dump(serializer.memory) + + +def serialize_objects_dump(objs: List[Any], clear_visited: bool = False) -> Tuple[List[PythonId], MemoryDump, str]: + """ + Serialize objects with shared memory. + Returns list of object ids and memory dump. + """ + + serializer = PythonSerializer() + if clear_visited: + serializer.clear_visited() + ids = [ + serializer.write_object_to_memory(obj) + for obj in objs + ] + return ids, serializer.memory, serialize_memory_dump(serializer.memory) + + +def deserialize_objects(ids: List[str], memory: str, imports: List[str]) -> Dict[str, object]: + """ + Deserialize objects from shared memory. + Returns dictionary where keys are ID and values are deserialized objects. + """ + + memory_dump = deserialize_memory_objects(memory) + loader = DumpLoader(memory_dump) + loader.add_imports(imports) + return {python_id: loader.load_object(PythonId(python_id)) for python_id in ids} diff --git a/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/example.py b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/example.py new file mode 100644 index 0000000000..25430d2d53 --- /dev/null +++ b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/example.py @@ -0,0 +1,87 @@ +import datetime +import json +from pprint import pprint + +from utbot_executor.deep_serialization.bad_class import BadField +from utbot_executor.deep_serialization.memory_objects import PythonSerializer +from utbot_executor.deep_serialization.json_converter import MemoryDumpEncoder +from utbot_executor.deep_serialization.deep_serialization import deserialize_objects + + +class B: + def __init__(self, b1, b2, b3): + self.b1 = b1 + self.b2 = b2 + self.b3 = b3 + self.time = datetime.datetime.now() + + +class Node: + def __init__(self, name: str): + self.name = name + self.children = [] + + def __eq__(self, other): + return self.name == other.name + + +def serialize_bad_obj(): + a = BadField("1") + s = PythonSerializer() + s.write_object_to_memory(a) + pprint(s.memory.objects) + with open('test_bad_field.json', 'w') as fout: + print(json.dumps({'objects': s.memory}, cls=MemoryDumpEncoder, indent=True), file=fout) + + +def deserialize_bad_obj(): + # run() + with open('test_bad_field.json', 'r') as fin: + data = fin.read() + pprint(data) + pprint(deserialize_objects(["140543796187856"], data, [ + 'copyreg', + 'utbot_executor.deep_serialization.example', + ])) + + +def run(): + from pprint import pprint + + # c = ["Alex"] + # b = B(1, 2, 3) + # b.b1 = B(4, 5, b) + # a = [1, 2, float('inf'), "abc", {1: 1}, None, b, c] + x = Node("x") + y = Node("y") + x.children.append(y) + y.children.append(x) + serializer_ = PythonSerializer() + pprint(serializer_.write_object_to_memory(x)) + pprint(serializer_.memory.objects) + with open('test_json.json', 'w') as fout: + print(json.dumps({'objects': serializer_.memory}, cls=MemoryDumpEncoder, indent=True), file=fout) + + +def deserialize(): + # run() + with open('test_json.json', 'r') as fin: + data = fin.read() + print(data) + pprint(deserialize_objects(["140340324106560"], data, [ + 'copyreg', + 'utbot_executor.deep_serialization.example', + 'datetime' + ])) + + +if __name__ == '__main__': + # serialize_bad_obj() + # deserialize_bad_obj() + with open('test_bad.json', 'r') as fin: + data = fin.read() + pprint(data) + pprint(deserialize_objects(["1500000374"], data, [ + 'copyreg', + 'utbot_executor.deep_serialization.example', + ])) diff --git a/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/json_converter.py b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/json_converter.py new file mode 100644 index 0000000000..753fd8a060 --- /dev/null +++ b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/json_converter.py @@ -0,0 +1,259 @@ +import copy +import importlib +import json +import sys +from typing import Dict, Iterable, Union +from utbot_executor.deep_serialization.memory_objects import ( + MemoryObject, + ReprMemoryObject, + ListMemoryObject, + DictMemoryObject, + ReduceMemoryObject, + MemoryDump, +) +from utbot_executor.deep_serialization.utils import PythonId, TypeInfo + + +class MemoryObjectEncoder(json.JSONEncoder): + def default(self, o): + if isinstance(o, MemoryObject): + base_json = { + "strategy": o.strategy, + "id": o.id_value(), + "typeinfo": o.typeinfo, + "comparable": o.comparable, + } + if isinstance(o, ReprMemoryObject): + base_json["value"] = o.value + elif isinstance(o, (ListMemoryObject, DictMemoryObject)): + base_json["items"] = o.items + elif isinstance(o, ReduceMemoryObject): + base_json["constructor"] = o.constructor + base_json["args"] = o.args + base_json["state"] = o.state + base_json["listitems"] = o.listitems + base_json["dictitems"] = o.dictitems + return base_json + return json.JSONEncoder.default(self, o) + + +class MemoryDumpEncoder(json.JSONEncoder): + def default(self, o): + if isinstance(o, MemoryDump): + return { + id_: MemoryObjectEncoder().default(o) for id_, o in o.objects.items() + } + if isinstance(o, TypeInfo): + return { + "kind": o.kind, + "module": o.module, + } + return json.JSONEncoder.default(self, o) + + +def as_repr_object(dct: Dict) -> Union[MemoryObject, Dict]: + if "strategy" in dct: + obj: MemoryObject + if dct["strategy"] == "repr": + obj = ReprMemoryObject.__new__(ReprMemoryObject) + obj.value = dct["value"] + obj.typeinfo = TypeInfo( + kind=dct["typeinfo"]["kind"], module=dct["typeinfo"]["module"] + ) + obj.comparable = dct["comparable"] + return obj + if dct["strategy"] == "list": + obj = ListMemoryObject.__new__(ListMemoryObject) + obj.items = dct["items"] + obj.typeinfo = TypeInfo( + kind=dct["typeinfo"]["kind"], module=dct["typeinfo"]["module"] + ) + obj.comparable = dct["comparable"] + return obj + if dct["strategy"] == "dict": + obj = DictMemoryObject.__new__(DictMemoryObject) + obj.items = dct["items"] + obj.typeinfo = TypeInfo( + kind=dct["typeinfo"]["kind"], module=dct["typeinfo"]["module"] + ) + obj.comparable = dct["comparable"] + return obj + if dct["strategy"] == "reduce": + obj = ReduceMemoryObject.__new__(ReduceMemoryObject) + obj.constructor = TypeInfo( + kind=dct["constructor"]["kind"], + module=dct["constructor"]["module"], + ) + obj.args = dct["args"] + obj.state = dct["state"] + obj.listitems = dct["listitems"] + obj.dictitems = dct["dictitems"] + obj.typeinfo = TypeInfo( + kind=dct["typeinfo"]["kind"], module=dct["typeinfo"]["module"] + ) + obj.comparable = dct["comparable"] + return obj + return dct + + +def deserialize_memory_objects(memory_dump: str) -> MemoryDump: + parsed_data = json.loads(memory_dump, object_hook=as_repr_object) + return MemoryDump(parsed_data["objects"]) + + +class DumpLoader: + def __init__(self, memory_dump: MemoryDump): + self.memory_dump = memory_dump + self.memory: Dict[PythonId, object] = {} # key is new id, value is real object + self.dump_id_to_real_id: Dict[PythonId, PythonId] = {} + + def reload_id(self) -> MemoryDump: + new_memory_objects: Dict[PythonId, MemoryObject] = {} + for id_, obj in self.memory_dump.objects.items(): + new_memory_object = copy.deepcopy(obj) + read_id = self.dump_id_to_real_id[id_] + new_memory_object.obj = self.memory[read_id] + if isinstance(new_memory_object, ReprMemoryObject): + pass + elif isinstance(new_memory_object, ListMemoryObject): + new_memory_object.items = [ + self.dump_id_to_real_id[id_] for id_ in new_memory_object.items + ] + elif isinstance(new_memory_object, DictMemoryObject): + new_memory_object.items = { + self.dump_id_to_real_id[id_key]: self.dump_id_to_real_id[id_value] + for id_key, id_value in new_memory_object.items.items() + } + elif isinstance(new_memory_object, ReduceMemoryObject): + new_memory_object.args = self.dump_id_to_real_id[new_memory_object.args] + new_memory_object.state = self.dump_id_to_real_id[ + new_memory_object.state + ] + new_memory_object.listitems = self.dump_id_to_real_id[ + new_memory_object.listitems + ] + new_memory_object.dictitems = self.dump_id_to_real_id[ + new_memory_object.dictitems + ] + new_memory_objects[self.dump_id_to_real_id[id_]] = new_memory_object + return MemoryDump(new_memory_objects) + + @staticmethod + def add_syspaths(syspaths: Iterable[str]): + for path in syspaths: + if path not in sys.path: + sys.path.insert(0, path) + + @staticmethod + def add_imports(imports: Iterable[str]): + for module in imports: + for i in range(1, module.count(".") + 2): + submodule_name = ".".join(module.split(".", maxsplit=i)[:i]) + globals()[submodule_name] = importlib.import_module(submodule_name) + + def load_object(self, python_id: PythonId) -> object: + if python_id in self.dump_id_to_real_id: + return self.memory[self.dump_id_to_real_id[python_id]] + + dump_object = self.memory_dump.objects[python_id] + real_object: object + if isinstance(dump_object, ReprMemoryObject): + real_object = eval(dump_object.value) + elif isinstance(dump_object, ListMemoryObject): + if dump_object.typeinfo.fullname == "builtins.set": + real_object = set(self.load_object(item) for item in dump_object.items) + elif dump_object.typeinfo.fullname == "builtins.tuple": + real_object = tuple( + self.load_object(item) for item in dump_object.items + ) + else: + real_object = [] + + id_ = PythonId(str(id(real_object))) + self.dump_id_to_real_id[python_id] = id_ + self.memory[id_] = real_object + + for item in dump_object.items: + real_object.append(self.load_object(item)) + elif isinstance(dump_object, DictMemoryObject): + real_object = {} + + id_ = PythonId(str(id(real_object))) + self.dump_id_to_real_id[python_id] = id_ + self.memory[id_] = real_object + + for key, value in dump_object.items.items(): + real_object[self.load_object(key)] = self.load_object(value) + elif isinstance(dump_object, ReduceMemoryObject): + constructor = eval(dump_object.constructor.qualname) + args = self.load_object(dump_object.args) + if args is None: # It is a global var + real_object = constructor + else: + real_object = constructor(*args) + + id_ = PythonId(str(id(real_object))) + self.dump_id_to_real_id[python_id] = id_ + self.memory[id_] = real_object + + if args is not None: + state = self.load_object(dump_object.state) + if isinstance(state, dict): + for field, value in state.items(): + try: + setattr(real_object, field, value) + except AttributeError: + pass + elif hasattr(real_object, "__setstate__"): + real_object.__setstate__(state) + if isinstance(state, tuple) and len(state) == 2: + _, slotstate = state + if slotstate: + for key, value in slotstate.items(): + try: + setattr(real_object, key, value) + except AttributeError: + pass + + listitems = self.load_object(dump_object.listitems) + if isinstance(listitems, Iterable): + for listitem in listitems: + real_object.append(listitem) + + dictitems = self.load_object(dump_object.dictitems) + if isinstance(dictitems, Dict): + for key, dictitem in dictitems.items(): + real_object[key] = dictitem + else: + raise TypeError(f"Invalid type {dump_object}") + + id_ = PythonId(str(id(real_object))) + self.dump_id_to_real_id[python_id] = id_ + self.memory[id_] = real_object + + return real_object + + +def main(): + with open("test_json.json", "r") as fin: + data = fin.read() + memory_dump = deserialize_memory_objects(data) + loader = DumpLoader(memory_dump) + loader.add_imports( + [ + "copyreg._reconstructor", + "deep_serialization.example.B", + "datetime.datetime", + "builtins.int", + "builtins.float", + "builtins.bool", + "types.NoneType", + "builtins.list", + "builtins.dict", + "builtins.str", + "builtins.tuple", + "builtins.bytes", + "builtins.type", + ] + ) + print(loader.load_object("140239390887040")) diff --git a/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/memory_objects.py b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/memory_objects.py new file mode 100644 index 0000000000..a65924f488 --- /dev/null +++ b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/memory_objects.py @@ -0,0 +1,437 @@ +from __future__ import annotations + +import inspect +import logging +import re +import sys +import typing +from itertools import zip_longest +import pickle +from typing import Any, Callable, Dict, List, Optional, Set, Type, Iterable + +from utbot_executor.deep_serialization.config import PICKLE_PROTO +from utbot_executor.deep_serialization.utils import ( + PythonId, + get_kind, + has_reduce, + check_comparability, + get_repr, + has_repr, + TypeInfo, + get_constructor_kind, + has_reduce_ex, + get_constructor_info, +) + + +class MemoryObject: + strategy: str + typeinfo: TypeInfo + comparable: bool + is_draft: bool + deserialized_obj: object + obj: object + + def __init__(self, obj: object) -> None: + self.is_draft = True + self.typeinfo = get_kind(obj) + self.obj = obj + + def _initialize( + self, deserialized_obj: object = None, comparable: bool = True + ) -> None: + self.deserialized_obj = deserialized_obj + self.comparable = comparable + self.is_draft = False + + def initialize(self) -> None: + self._initialize() + + def id_value(self) -> str: + return str(id(self.obj)) + + def __repr__(self) -> str: + if hasattr(self, "obj"): + return str(self.obj) + return str(self.typeinfo) + + def __str__(self) -> str: + return str(self.obj) + + @property + def qualname(self) -> str: + return self.typeinfo.qualname + + +class ReprMemoryObject(MemoryObject): + strategy: str = "repr" + value: str + + def __init__(self, repr_object: object) -> None: + super().__init__(repr_object) + self.value = get_repr(repr_object) + + def initialize(self) -> None: + try: + deserialized_obj = pickle.loads(pickle.dumps(self.obj)) + comparable = check_comparability(self.obj, deserialized_obj) + except Exception: + deserialized_obj = self.obj + comparable = False + + super()._initialize(deserialized_obj, comparable) + + +class ListMemoryObject(MemoryObject): + strategy: str = "list" + items: List[PythonId] = [] + + def __init__(self, list_object: object) -> None: + self.items: List[PythonId] = [] + super().__init__(list_object) + + def initialize(self) -> None: + serializer = PythonSerializer() + self.deserialized_obj = [] # for recursive collections + self.comparable = False # for recursive collections + + for elem in self.obj: + elem_id = serializer.write_object_to_memory(elem) + self.items.append(elem_id) + self.deserialized_obj.append(serializer[elem_id]) + + deserialized_obj = self.deserialized_obj + if self.typeinfo.fullname == "builtins.tuple": + deserialized_obj = tuple(deserialized_obj) + elif self.typeinfo.fullname == "builtins.set": + deserialized_obj = set(deserialized_obj) + + comparable = all(serializer.get_by_id(elem).comparable for elem in self.items) + + super()._initialize(deserialized_obj, comparable) + + def __repr__(self) -> str: + if hasattr(self, "obj"): + return str(self.obj) + return f"{self.typeinfo.kind}{self.items}" + + +class DictMemoryObject(MemoryObject): + strategy: str = "dict" + items: Dict[PythonId, PythonId] = {} + + def __init__(self, dict_object: object) -> None: + self.items: Dict[PythonId, PythonId] = {} + super().__init__(dict_object) + + def initialize(self) -> None: + self.obj: Dict + serializer = PythonSerializer() + self.deserialized_obj = {} # for recursive dicts + self.comparable = False # for recursive dicts + + for key, value in self.obj.items(): + key_id = serializer.write_object_to_memory(key) + value_id = serializer.write_object_to_memory(value) + self.items[key_id] = value_id + self.deserialized_obj[serializer[key_id]] = serializer[value_id] + + deserialized_obj = self.deserialized_obj + equals_len = len(self.obj) == len(deserialized_obj) + comparable = equals_len and all( + serializer.get_by_id(value_id).comparable + for value_id in self.items.values() + ) + + super()._initialize(deserialized_obj, comparable) + + def __repr__(self) -> str: + if hasattr(self, "obj"): + return str(self.obj) + return f"{self.typeinfo.kind}{self.items}" + + +class ReduceMemoryObject(MemoryObject): + strategy: str = "reduce" + constructor: TypeInfo + args: PythonId + state: PythonId + listitems: PythonId + dictitems: PythonId + + reduce_value: List[Any] = [] + + def __init__(self, reduce_object: object) -> None: + super().__init__(reduce_object) + serializer = PythonSerializer() + + if has_reduce_ex(reduce_object): + py_object_reduce = reduce_object.__reduce_ex__(PICKLE_PROTO) + else: + py_object_reduce = reduce_object.__reduce__() + + if isinstance(py_object_reduce, str): + name = getattr(reduce_object, "__qualname__", None) + if name is None: + name = reduce_object.__name__ + module_name = pickle.whichmodule(reduce_object, name) + try: + __import__(module_name, level=0) + module = sys.modules[module_name] + obj2, parent = pickle._getattribute(module, name) + except (ImportError, KeyError, AttributeError): + raise pickle.PicklingError( + "Can't pickle %r: it's not found as %s.%s" + % (reduce_object, module_name, name) + ) from None + else: + if obj2 is not reduce_object: + self.comparable = False + typeinfo = TypeInfo(module_name, name) + self.constructor = typeinfo + self.deserialized_obj = obj2 + self.reduce_value = [] + else: + self.reduce_value = [ + default if obj is None else obj + for obj, default in zip_longest( + py_object_reduce, [None, [], {}, [], {}], fillvalue=None + ) + ] + + constructor_arguments, callable_constructor = self.constructor_builder() + + self.constructor = get_constructor_info(callable_constructor) + logging.debug("Constructor: %s", callable_constructor) + logging.debug("Constructor info: %s", self.constructor) + logging.debug("Constructor args: %s", constructor_arguments) + self.args = serializer.write_object_to_memory(constructor_arguments) + + if isinstance(constructor_arguments, Iterable): + logging.debug("Constructor args: %s", constructor_arguments) + self.deserialized_obj = callable_constructor(*constructor_arguments) + + def constructor_builder(self) -> typing.Tuple[typing.Any, typing.Callable]: + constructor_kind = get_constructor_kind(self.reduce_value[0]) + + is_reconstructor = constructor_kind.qualname == "copyreg._reconstructor" + is_reduce_user_type = ( + len(self.reduce_value[1]) == 3 + and isinstance(self.reduce_value[1][0], type(self.obj)) + and self.reduce_value[1][1] is object + and self.reduce_value[1][2] is None + ) + is_reduce_ex_user_type = len(self.reduce_value[1]) == 1 and isinstance( + self.reduce_value[1][0], type(self.obj) + ) + is_user_type = is_reduce_user_type or is_reduce_ex_user_type + is_newobj = constructor_kind.qualname in { + "copyreg.__newobj__", + "copyreg.__newobj_ex__", + } + + logging.debug("Params: %s, %s, %s", is_reconstructor, is_user_type, is_newobj) + + obj_type = self.obj if isinstance(self.obj, type) else type(self.obj) + + callable_constructor: Callable + constructor_arguments: Any + + if is_user_type and hasattr(self.obj, "__init__"): + init_method = getattr(obj_type, "__init__") + init_from_object = init_method is object.__init__ + logging.debug( + "init_from_object = %s, signature_size = %s", + init_from_object, + len(inspect.signature(init_method).parameters), + ) + if ( + not init_from_object + and len(inspect.signature(init_method).parameters) == 1 + ) or init_from_object: + logging.debug("init with one argument! %s", init_method) + constructor_arguments = [] + callable_constructor = obj_type + return constructor_arguments, callable_constructor + + # Special case + if isinstance(self.obj, re.Pattern): + constructor_arguments = (self.obj.pattern, self.obj.flags) + callable_constructor = re.compile + return constructor_arguments, callable_constructor + # ---- + + if is_newobj: + constructor_arguments = self.reduce_value[1] + callable_constructor = getattr(obj_type, "__new__") + return constructor_arguments, callable_constructor + + if is_reconstructor and is_user_type: + constructor_arguments = self.reduce_value[1] + if ( + len(constructor_arguments) == 3 + and constructor_arguments[-1] is None + and constructor_arguments[-2] == object + ): + del constructor_arguments[1:] + callable_constructor = object.__new__ + return constructor_arguments, callable_constructor + + callable_constructor = self.reduce_value[0] + constructor_arguments = self.reduce_value[1] + return constructor_arguments, callable_constructor + + def initialize(self) -> None: + serializer = PythonSerializer() + + self.comparable = True # for recursive objects + deserialized_obj = self.deserialized_obj + if len(self.reduce_value) == 0: + # It is global var + self.args = serializer.write_object_to_memory(None) + self.state = serializer.write_object_to_memory(None) + self.listitems = serializer.write_object_to_memory(None) + self.dictitems = serializer.write_object_to_memory(None) + else: + self.state = serializer.write_object_to_memory(self.reduce_value[2]) + self.listitems = serializer.write_object_to_memory( + list(self.reduce_value[3]) + ) + self.dictitems = serializer.write_object_to_memory( + dict(self.reduce_value[4]) + ) + + state = serializer[self.state] + if isinstance(state, dict): + for key, value in state.items(): + setattr(deserialized_obj, key, value) + elif hasattr(deserialized_obj, "__setstate__"): + deserialized_obj.__setstate__(state) + elif isinstance(state, tuple) and len(state) == 2: + _, slotstate = state + if slotstate: + for key, value in slotstate.items(): + setattr(deserialized_obj, key, value) + + items = serializer[self.listitems] + if isinstance(items, Iterable): + for item in items: + deserialized_obj.append(item) + + dictitems = serializer[self.dictitems] + if isinstance(dictitems, Dict): + for key, value in dictitems.items(): + deserialized_obj[key] = value + + comparable = self.obj == deserialized_obj + + super()._initialize(deserialized_obj, comparable) + + +class MemoryObjectProvider(object): + @staticmethod + def get_serializer(obj: object) -> Optional[Type[MemoryObject]]: + pass + + +class ListMemoryObjectProvider(MemoryObjectProvider): + @staticmethod + def get_serializer(obj: object) -> Optional[Type[MemoryObject]]: + if any(type(obj) == t for t in (list, set, tuple, frozenset)): + return ListMemoryObject + return None + + +class DictMemoryObjectProvider(MemoryObjectProvider): + @staticmethod + def get_serializer(obj: object) -> Optional[Type[MemoryObject]]: + if type(obj) == dict: + return DictMemoryObject + return None + + +class ReduceMemoryObjectProvider(MemoryObjectProvider): + @staticmethod + def get_serializer(obj: object) -> Optional[Type[MemoryObject]]: + if has_reduce(obj): + return ReduceMemoryObject + return None + + +class ReduceExMemoryObjectProvider(MemoryObjectProvider): + @staticmethod + def get_serializer(obj: object) -> Optional[Type[MemoryObject]]: + if has_reduce_ex(obj): + return ReduceMemoryObject + return None + + +class ReprMemoryObjectProvider(MemoryObjectProvider): + @staticmethod + def get_serializer(obj: object) -> Optional[Type[MemoryObject]]: + if has_repr(obj): + return ReprMemoryObject + return None + + +class MemoryDump: + objects: Dict[PythonId, MemoryObject] + + def __init__(self, objects: Optional[Dict[PythonId, MemoryObject]] = None): + if objects is None: + objects = {} + self.objects = objects + + +class PythonSerializer: + instance: PythonSerializer + memory: MemoryDump + created: bool = False + + visited: Set[PythonId] = set() + + providers: List[MemoryObjectProvider] = [ + ListMemoryObjectProvider, + DictMemoryObjectProvider, + ReduceMemoryObjectProvider, + ReprMemoryObjectProvider, + ReduceExMemoryObjectProvider, + ] + + def __new__(cls): + if not cls.created: + cls.instance = super(PythonSerializer, cls).__new__(cls) + cls.memory = MemoryDump() + cls.created = True + return cls.instance + + def clear(self): + self.memory = MemoryDump() + + def get_by_id(self, id_: PythonId) -> MemoryObject: + return self.memory.objects[id_] + + def __getitem__(self, id_: PythonId) -> object: + return self.get_by_id(id_).deserialized_obj + + def clear_visited(self): + self.visited.clear() + + def write_object_to_memory(self, py_object: object) -> PythonId: + """Save serialized py_object to memory and return id.""" + + id_ = PythonId(str(id(py_object))) + + if id_ in self.visited: + return id_ + + for provider in self.providers: + serializer = provider.get_serializer(py_object) + if serializer is not None: + self.visited.add(id_) + mem_obj = serializer(py_object) + self.memory.objects[id_] = mem_obj + mem_obj.initialize() + return id_ + + raise ValueError(f"Can not find provider for object {py_object}.") diff --git a/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/test_json.json b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/test_json.json new file mode 100644 index 0000000000..0d2de626b9 --- /dev/null +++ b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/test_json.json @@ -0,0 +1,202 @@ +{ + "objects": { + "140571957551616": { + "strategy": "list", + "id": "140571957551616", + "typeinfo": { + "kind": "list", + "module": "builtins" + }, + "comparable": true, + "items": [ + "94401662884064" + ] + }, + "94401662884064": { + "strategy": "repr", + "id": "94401662884064", + "typeinfo": { + "kind": "Node", + "module": "__main__" + }, + "comparable": true, + "value": "__main__.Node" + }, + "140571958508416": { + "strategy": "reduce", + "id": "140571958508416", + "typeinfo": { + "kind": "Node", + "module": "__main__" + }, + "comparable": true, + "constructor": { + "kind": "object.__new__", + "module": "builtins" + }, + "args": "140571957551616", + "state": "140571959370880", + "listitems": "140571956166528", + "dictitems": "140571956216128" + }, + "140571959370880": { + "strategy": "dict", + "id": "140571959370880", + "typeinfo": { + "kind": "dict", + "module": "builtins" + }, + "comparable": true, + "items": { + "140571963755248": "140571964146736", + "140571963755632": "140571957549760" + } + }, + "140571963755248": { + "strategy": "repr", + "id": "140571963755248", + "typeinfo": { + "kind": "str", + "module": "builtins" + }, + "comparable": true, + "value": "'name'" + }, + "140571964146736": { + "strategy": "repr", + "id": "140571964146736", + "typeinfo": { + "kind": "str", + "module": "builtins" + }, + "comparable": true, + "value": "'x'" + }, + "140571963755632": { + "strategy": "repr", + "id": "140571963755632", + "typeinfo": { + "kind": "str", + "module": "builtins" + }, + "comparable": true, + "value": "'children'" + }, + "140571957549760": { + "strategy": "list", + "id": "140571957549760", + "typeinfo": { + "kind": "list", + "module": "builtins" + }, + "comparable": true, + "items": [ + "140571958508320" + ] + }, + "140571955997632": { + "strategy": "list", + "id": "140571955997632", + "typeinfo": { + "kind": "list", + "module": "builtins" + }, + "comparable": true, + "items": [ + "94401662884064" + ] + }, + "140571958508320": { + "strategy": "reduce", + "id": "140571958508320", + "typeinfo": { + "kind": "Node", + "module": "__main__" + }, + "comparable": true, + "constructor": { + "kind": "object.__new__", + "module": "builtins" + }, + "args": "140571955997632", + "state": "140571956156480", + "listitems": "140571956165504", + "dictitems": "140571956165888" + }, + "140571956156480": { + "strategy": "dict", + "id": "140571956156480", + "typeinfo": { + "kind": "dict", + "module": "builtins" + }, + "comparable": true, + "items": { + "140571963755248": "140571958388272", + "140571963755632": "140571957550016" + } + }, + "140571958388272": { + "strategy": "repr", + "id": "140571958388272", + "typeinfo": { + "kind": "str", + "module": "builtins" + }, + "comparable": true, + "value": "'y'" + }, + "140571957550016": { + "strategy": "list", + "id": "140571957550016", + "typeinfo": { + "kind": "list", + "module": "builtins" + }, + "comparable": true, + "items": [ + "140571958508416" + ] + }, + "140571956165504": { + "strategy": "list", + "id": "140571956165504", + "typeinfo": { + "kind": "list", + "module": "builtins" + }, + "comparable": true, + "items": [] + }, + "140571956165888": { + "strategy": "dict", + "id": "140571956165888", + "typeinfo": { + "kind": "dict", + "module": "builtins" + }, + "comparable": true, + "items": {} + }, + "140571956166528": { + "strategy": "list", + "id": "140571956166528", + "typeinfo": { + "kind": "list", + "module": "builtins" + }, + "comparable": true, + "items": [] + }, + "140571956216128": { + "strategy": "dict", + "id": "140571956216128", + "typeinfo": { + "kind": "dict", + "module": "builtins" + }, + "comparable": true, + "items": {} + } + } +} diff --git a/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/utils.py b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/utils.py new file mode 100644 index 0000000000..6dcdd94483 --- /dev/null +++ b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/utils.py @@ -0,0 +1,182 @@ +from __future__ import annotations +import dataclasses +import importlib +import pickle +from typing import NewType + +from utbot_executor.deep_serialization.config import PICKLE_PROTO + +PythonId = NewType("PythonId", str) + + +@dataclasses.dataclass +class TypeInfo: + module: str + kind: str + + @property + def qualname(self): + if self.module == "" or self.module == "builtins": + return self.kind + return f"{self.module}.{self.kind}" + + @property + def fullname(self): + if self.module == "": + return self.kind + else: + return f"{self.module}.{self.kind}" + + @staticmethod + def from_str(representation: str) -> TypeInfo: + if "." in representation: + return TypeInfo( + representation.rsplit(".", 1)[0], representation.rsplit(".", 1)[1] + ) + return TypeInfo("", representation) + + def __str__(self): + return self.qualname + + +def check_comparability(py_object: object, deserialized_py_object: object) -> bool: + return py_object == deserialized_py_object + + +def get_kind(py_object: object) -> TypeInfo: + """Get module and name of type""" + if py_object is None: + return TypeInfo("types", "NoneType") + if isinstance(py_object, type): + return TypeInfo(py_object.__module__, py_object.__qualname__) + if callable(py_object): + return TypeInfo("typing", "Callable") + module = type(py_object).__module__ + qualname = type(py_object).__qualname__ + return TypeInfo(module, qualname) + + +def get_constructor_kind(py_object: object) -> TypeInfo: + """Get module and name of object""" + if py_object is None: + return TypeInfo("types", "NoneType") + if isinstance(py_object, type): + return TypeInfo(py_object.__module__, py_object.__qualname__) + if callable(py_object): + return TypeInfo(py_object.__module__, py_object.__qualname__) + module = type(py_object).__module__ + qualname = type(py_object).__qualname__ + return TypeInfo(module, qualname) + + +def get_constructor_info(constructor: object) -> TypeInfo: + if constructor == object.__init__: + return TypeInfo("builtins", "object.__new__") + if constructor == object.__new__: + return TypeInfo("builtins", "object.__new__") + if constructor is None: + return TypeInfo("types", "NoneType") + return TypeInfo(constructor.__module__, constructor.__qualname__) + + +def has_reduce(py_object: object) -> bool: + reduce = getattr(py_object, "__reduce__", None) + if reduce is None: + return False + try: + reduce() + return True + except TypeError: + return False + except pickle.PicklingError: + return False + except Exception: + return False + + +def has_reduce_ex(py_object: object) -> bool: + reduce_ex = getattr(py_object, "__reduce_ex__", None) + if reduce_ex is None: + return False + try: + reduce_ex(PICKLE_PROTO) + return True + except TypeError: + return False + except pickle.PicklingError: + return False + except Exception: + return False + + +def get_repr(py_object: object) -> str: + if isinstance(py_object, type): + return str(get_kind(py_object)) + if isinstance(py_object, float): + if repr(py_object) == "nan": + return "float('nan')" + if repr(py_object) == "inf": + return "float('inf')" + if repr(py_object) == "-inf": + return "float('-inf')" + return repr(py_object) + if isinstance(py_object, complex): + return ( + f"complex(real={get_repr(py_object.real)}, imag={get_repr(py_object.imag)})" + ) + return repr(py_object) + + +def add_imports(module: str): + for i in range(1, module.count(".") + 2): + submodule_name = ".".join(module.split(".", maxsplit=i)[:i]) + if submodule_name not in globals(): + try: + globals()[submodule_name] = importlib.import_module(submodule_name) + except ModuleNotFoundError: + pass + + +def check_eval(py_object: object) -> bool: + module = get_kind(py_object).module + add_imports(module) + try: + eval(get_repr(py_object)) + return True + except Exception: + return False + + +def has_repr(py_object: object) -> bool: + reprable_types = [ + type(None), + int, + bool, + float, + bytes, + bytearray, + str, + # tuple, + # list, + # dict, + # set, + # frozenset, + type, + ] + if type(py_object) in reprable_types: + return True + + if check_eval(py_object): + repr_value = get_repr(py_object) + evaluated = eval(repr_value) + if get_repr(evaluated) == repr_value: + return True + + return False + + +def getattr_by_path(py_object: object, path: str) -> object: + current_object = py_object + for layer in path.split("."): + current_object = getattr(current_object, layer) + return current_object diff --git a/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/example/__init__.py b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/example/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/example/example.py b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/example/example.py new file mode 100644 index 0000000000..c16550ba84 --- /dev/null +++ b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/example/example.py @@ -0,0 +1,26 @@ +from utbot_executor.executor import PythonExecutor +from utbot_executor.parser import ExecutionRequest, ExecutionSuccessResponse + + +def test_execution(): + executor = PythonExecutor("", 0) + id_ = '1500926645' + serialized_arg = r'{"objects":{"1500926644":{"strategy":"repr","id":"1500926644","typeinfo":{"module":"builtins","kind":"int"},"comparable":true,"value":"170141183460469231731687303715749887999"},"1500926652":{"strategy":"list","id":"1500926652","typeinfo":{"module":"builtins","kind":"list"},"comparable":true,"items":["1500926644"]},"1500926650":{"strategy":"repr","id":"1500926650","typeinfo":{"module":"builtins","kind":"str"},"comparable":true,"value":"\"x\""},"1500926646":{"strategy":"repr","id":"1500926646","typeinfo":{"module":"builtins","kind":"int"},"comparable":true,"value":"1"},"1500926651":{"strategy":"dict","id":"1500926651","typeinfo":{"module":"builtins","kind":"dict"},"comparable":true,"items":{"1500926650":"1500926646"}},"1500926653":{"strategy":"list","id":"1500926653","typeinfo":{"module":"builtins","kind":"list"},"comparable":true,"items":[]},"1500926654":{"strategy":"dict","id":"1500926654","typeinfo":{"module":"builtins","kind":"dict"},"comparable":true,"items":{}},"1500926645":{"strategy":"reduce","id":"1500926645","typeinfo":{"module":"my_func","kind":"A"},"comparable":true,"constructor":{"module":"my_func","kind":"A"},"args":"1500926652","state":"1500926651","listitems":"1500926653","dictitems":"1500926654"}}}' + request = ExecutionRequest( + 'f', + 'my_func', + ['my_func'], + ['/home/vyacheslav/Projects/utbot_executor/utbot_executor/tests'], + [id_], + {}, + serialized_arg, + '/home/vyacheslav/Projects/utbot_executor/utbot_executor/tests/my_func.py', + '0x1' + ) + response = executor.run_function(request) + + assert isinstance(response, ExecutionSuccessResponse) + + assert response.status == "success" + assert response.is_exception is False + assert response.diff_ids diff --git a/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/example/my_func.py b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/example/my_func.py new file mode 100644 index 0000000000..1a97070725 --- /dev/null +++ b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/example/my_func.py @@ -0,0 +1,14 @@ +import math + + +class A: + def __init__(self, x: int): + self.x = x + + +def f(a: A): + if a.x > 0: + return 100500 + if math.pi * a.x == 0: + return 2 + return a.x diff --git a/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/executor.py b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/executor.py new file mode 100644 index 0000000000..5460c0f7bd --- /dev/null +++ b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/executor.py @@ -0,0 +1,212 @@ +"""Python code executor for UnitTestBot""" +import copy +import importlib +import inspect +import logging +import pathlib +import socket +import sys +import traceback +import typing +from typing import Any, Callable, Dict, Iterable, List, Tuple + +from utbot_executor.deep_serialization.deep_serialization import serialize_memory_dump, \ + serialize_objects_dump +from utbot_executor.deep_serialization.json_converter import DumpLoader, deserialize_memory_objects +from utbot_executor.deep_serialization.memory_objects import MemoryDump, PythonSerializer +from utbot_executor.deep_serialization.utils import PythonId, getattr_by_path +from utbot_executor.memory_compressor import compress_memory +from utbot_executor.parser import ExecutionRequest, ExecutionResponse, ExecutionFailResponse, ExecutionSuccessResponse +from utbot_executor.ut_tracer import UtTracer +from utbot_executor.utils import suppress_stdout as __suppress_stdout + +__all__ = ['PythonExecutor'] + + +def _update_states(init_memory_dump: MemoryDump, state_before: MemoryDump) -> MemoryDump: + for id_, obj in state_before.objects.items(): + if id_ not in init_memory_dump.objects: + init_memory_dump.objects[id_] = obj + return init_memory_dump + + +def _load_objects(objs: List[Any]) -> MemoryDump: + serializer = PythonSerializer() + serializer.clear_visited() + for obj in objs: + serializer.write_object_to_memory(obj) + return serializer.memory + + +class PythonExecutor: + def __init__(self, coverage_hostname: str, coverage_port: int): + self.coverage_hostname = coverage_hostname + self.coverage_port = coverage_port + + @staticmethod + def add_syspaths(syspaths: Iterable[str]): + for path in syspaths: + if path not in sys.path: + sys.path.insert(0, path) + + @staticmethod + def add_imports(imports: Iterable[str]): + for module in imports: + for i in range(1, module.count('.') + 2): + submodule_name = '.'.join(module.split('.', maxsplit=i)[:i]) + logging.debug("Submodule #%d: %s", i, submodule_name) + if submodule_name not in globals(): + try: + globals()[submodule_name] = importlib.import_module(submodule_name) + except ModuleNotFoundError: + logging.warning("Import submodule %s failed", submodule_name) + logging.debug("Submodule #%d: OK", i) + + def run_function(self, request: ExecutionRequest) -> ExecutionResponse: + logging.debug("Prepare to run function `%s`", request.function_name) + try: + memory_dump = deserialize_memory_objects(request.serialized_memory) + loader = DumpLoader(memory_dump) + except Exception as _: + logging.debug("Error \n%s", traceback.format_exc()) + return ExecutionFailResponse("fail", traceback.format_exc()) + logging.debug("Dump loader have been created") + + try: + logging.debug("Imports: %s", request.imports) + logging.debug("Syspaths: %s", request.syspaths) + self.add_syspaths(request.syspaths) + self.add_imports(request.imports) + loader.add_syspaths(request.syspaths) + loader.add_imports(request.imports) + except Exception as _: + logging.debug("Error \n%s", traceback.format_exc()) + return ExecutionFailResponse("fail", traceback.format_exc()) + logging.debug("Imports have been added") + + try: + function = getattr_by_path( + importlib.import_module(request.function_module), + request.function_name + ) + if not callable(function): + return ExecutionFailResponse( + "fail", + f"Invalid function path {request.function_module}.{request.function_name}" + ) + logging.debug("Function initialized") + args = [loader.load_object(PythonId(arg_id)) for arg_id in request.arguments_ids] + logging.debug("Arguments: %s", args) + kwargs = {name: loader.load_object(PythonId(kwarg_id)) for name, kwarg_id in request.kwarguments_ids.items()} + except Exception as _: + logging.debug("Error \n%s", traceback.format_exc()) + return ExecutionFailResponse("fail", traceback.format_exc()) + logging.debug("Arguments have been created") + + try: + state_before_memory = _load_objects(args + list(kwargs.values())) + init_state_before = _update_states(loader.reload_id(), state_before_memory) + serialized_state_init = serialize_memory_dump(init_state_before) + + def _coverage_sender(info: typing.Tuple[str, int]): + if pathlib.Path(info[0]) == pathlib.Path(request.filepath): + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + logging.debug("Coverage message: %s:%d", request.coverage_id, info[1]) + logging.debug("Port: %d", self.coverage_port) + message = bytes(f'{request.coverage_id}:{info[1]}', encoding='utf-8') + sock.sendto(message, (self.coverage_hostname, self.coverage_port)) + logging.debug("ID: %s, Coverage: %s", request.coverage_id, info) + + value = _run_calculate_function_value( + function, + args, + kwargs, + request.filepath, + serialized_state_init, + tracer=UtTracer(_coverage_sender) + ) + except Exception as _: + logging.debug("Error \n%s", traceback.format_exc()) + return ExecutionFailResponse("fail", traceback.format_exc()) + logging.debug("Value have been calculated: %s", value) + return value + + +def _serialize_state( + args: List[Any], + kwargs: Dict[str, Any], + result: Any = None, + ) -> Tuple[List[PythonId], Dict[str, PythonId], PythonId, MemoryDump, str]: + """Serialize objects from args, kwargs and result. + + Returns: tuple of args ids, kwargs ids, result id and serialized memory.""" + + all_arguments = args + list(kwargs.values()) + [result] + + ids, memory, serialized_memory = serialize_objects_dump(all_arguments, True) + return ( + ids[:len(args)], + dict(zip(kwargs.keys(), ids[len(args):len(args)+len(kwargs)])), + ids[-1], + copy.deepcopy(memory), + serialized_memory, + ) + + +def _run_calculate_function_value( + function: Callable, + args: List[Any], + kwargs: Dict[str, Any], + fullpath: str, + state_init: str, + tracer: UtTracer, + ) -> ExecutionResponse: + """ Calculate function evaluation result. + + Return serialized data: status, coverage info, object ids and memory.""" + + _, _, _, state_before, serialized_state_before = _serialize_state(args, kwargs) + + __is_exception = False + + (__sources, __start, ) = inspect.getsourcelines(function) + __end = __start + len(__sources) + + __tracer = tracer + + try: + with __suppress_stdout(): + __result = __tracer.runfunc(function, *args, **kwargs) + except Exception as __exception: + __result = __exception + __is_exception = True + logging.debug("Function call finished: %s", __result) + + logging.debug("Coverage: %s", __tracer.counts) + logging.debug("Fullpath: %s", fullpath) + module_path = pathlib.PurePath(fullpath) + __stmts = [x[1] for x in __tracer.counts if pathlib.PurePath(x[0]) == module_path] + __stmts_filtered = [x for x in range(__start, __end) if x in __stmts] + __stmts_filtered_with_def = [__start] + __stmts_filtered + __missed_filtered = [x for x in range(__start, __end) if x not in __stmts_filtered_with_def] + logging.debug("Covered lines: %s", __stmts_filtered_with_def) + logging.debug("Missed lines: %s", __missed_filtered) + + args_ids, kwargs_ids, result_id, state_after, serialized_state_after = _serialize_state(args, kwargs, __result) + ids = args_ids + list(kwargs_ids.values()) + # state_before, state_after = compress_memory(ids, state_before, state_after) + diff_ids = compress_memory(ids, state_before, state_after) + + return ExecutionSuccessResponse( + status="success", + is_exception=__is_exception, + statements=__stmts_filtered_with_def, + missed_statements=__missed_filtered, + state_init=state_init, + state_before=serialized_state_before, + state_after=serialized_state_after, + diff_ids=diff_ids, + args_ids=args_ids, + kwargs_ids=kwargs_ids, + result_id=result_id, + ) diff --git a/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/listener.py b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/listener.py new file mode 100644 index 0000000000..78df1a507c --- /dev/null +++ b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/listener.py @@ -0,0 +1,88 @@ +import logging +import os +import socket +import traceback + +from utbot_executor.deep_serialization.memory_objects import PythonSerializer +from utbot_executor.parser import parse_request, serialize_response, ExecutionFailResponse +from utbot_executor.executor import PythonExecutor + + +RECV_SIZE = 2**15 + + +class PythonExecuteServer: + def __init__( + self, + hostname: str, + port: int, + coverage_hostname: str, + coverage_port: str, + ): + logging.info('PythonExecutor is creating...') + self.clientsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.clientsocket.connect((hostname, port)) + self.executor = PythonExecutor(coverage_hostname, coverage_port) + + def run(self) -> None: + logging.info('PythonExecutor is ready...') + try: + self.handler() + finally: + self.clientsocket.close() + + def handler(self) -> None: + logging.info('Start working...') + + while True: + command = self.clientsocket.recv(4) + + if command == b'STOP': + break + if command == b'DATA': + message_size = int(self.clientsocket.recv(16).decode()) + logging.debug('Got message size: %d bytes', message_size) + message_body = bytearray() + + while len(message_body) < message_size: + message = self.clientsocket.recv( + min(RECV_SIZE, message_size - len(message_body)) + ) + message_body += message + logging.debug('Message: %s, size: %d', message, len(message)) + logging.debug( + 'Update content, current size: %d / %d bytes', + len(message_body), + message_size, + ) + + try: + request = parse_request(message_body.decode()) + logging.debug('Parsed request: %s', request) + response = self.executor.run_function(request) + except Exception as ex: + logging.debug('Exception: %s', traceback.format_exc()) + response = ExecutionFailResponse('fail', traceback.format_exc()) + + logging.debug('Response: %s', response) + + try: + serialized_response = serialize_response(response) + except Exception as ex: + serialized_response = serialize_response(ExecutionFailResponse('fail', '')) + finally: + PythonSerializer().clear() + + logging.debug('Serialized response: %s', serialized_response) + + bytes_data = serialized_response.encode() + logging.debug('Encoded response: %s', bytes_data) + response_size = str(len(bytes_data)) + self.clientsocket.send((response_size + os.linesep).encode()) + + sended_size = 0 + while len(bytes_data) > sended_size: + sended_size += self.clientsocket.send(bytes_data[sended_size:]) + + logging.debug('Sent all data') + logging.info('All done...') diff --git a/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/memory_compressor.py b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/memory_compressor.py new file mode 100644 index 0000000000..feb6ea869b --- /dev/null +++ b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/memory_compressor.py @@ -0,0 +1,17 @@ +import typing + +from utbot_executor.deep_serialization.memory_objects import MemoryDump +from utbot_executor.deep_serialization.utils import PythonId + + +def compress_memory( + ids: typing.List[PythonId], + state_before: MemoryDump, + state_after: MemoryDump +) -> typing.List[PythonId]: + diff_ids: typing.List[PythonId] = [] + for id_ in ids: + if id_ in state_before.objects and id_ in state_after.objects: + if state_before.objects[id_].obj != state_after.objects[id_].obj: + diff_ids.append(id_) + return diff_ids diff --git a/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/parser.py b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/parser.py new file mode 100644 index 0000000000..838711d305 --- /dev/null +++ b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/parser.py @@ -0,0 +1,99 @@ +import dataclasses +import json +from typing import Dict, List, Union + + +@dataclasses.dataclass +class ExecutionRequest: + function_name: str + function_module: str + imports: List[str] + syspaths: List[str] + arguments_ids: List[str] + kwarguments_ids: Dict[str, str] + serialized_memory: str + filepath: str + coverage_id: str + + +class ExecutionResponse: + status: str + + +@dataclasses.dataclass +class ExecutionSuccessResponse(ExecutionResponse): + status: str + is_exception: bool + statements: List[int] + missed_statements: List[int] + state_init: str + state_before: str + state_after: str + diff_ids: List[str] + args_ids: List[str] + kwargs_ids: Dict[str, str] + result_id: str + + +@dataclasses.dataclass +class ExecutionFailResponse(ExecutionResponse): + status: str + exception: str + + +def as_execution_result(dct: Dict) -> Union[ExecutionRequest, Dict]: + if set(dct.keys()) == { + 'functionName', + 'functionModule', + 'imports', + 'syspaths', + 'argumentsIds', + 'kwargumentsIds', + 'serializedMemory', + 'filepath', + 'coverageId', + }: + return ExecutionRequest( + dct['functionName'], + dct['functionModule'], + dct['imports'], + dct['syspaths'], + dct['argumentsIds'], + dct['kwargumentsIds'], + dct['serializedMemory'], + dct['filepath'], + dct['coverageId'], + ) + return dct + + +def parse_request(request: str) -> ExecutionRequest: + return json.loads(request, object_hook=as_execution_result) + + +class ResponseEncoder(json.JSONEncoder): + def default(self, o): + if isinstance(o, ExecutionSuccessResponse): + return { + "status": o.status, + "isException": o.is_exception, + "statements": o.statements, + "missedStatements": o.missed_statements, + "stateInit": o.state_init, + "stateBefore": o.state_before, + "stateAfter": o.state_after, + "diffIds": o.diff_ids, + "argsIds": o.args_ids, + "kwargsIds": o.kwargs_ids, + "resultId": o.result_id, + } + if isinstance(o, ExecutionFailResponse): + return { + "status": o.status, + "exception": o.exception + } + return json.JSONEncoder.default(self, o) + + +def serialize_response(response: ExecutionResponse) -> str: + return json.dumps(response, cls=ResponseEncoder) diff --git a/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/ut_tracer.py b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/ut_tracer.py new file mode 100644 index 0000000000..d761c02d74 --- /dev/null +++ b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/ut_tracer.py @@ -0,0 +1,62 @@ +import os +import sys +import typing + + +def _modname(path): + base = os.path.basename(path) + filename, _ = os.path.splitext(base) + return filename + + +class UtTracer: + def __init__(self, sender: typing.Callable[[typing.Tuple[str, int]], None]): + self.globaltrace = self.globaltrace_lt + self.counts = {} + self.localtrace = self.localtrace_count + self.globaltrace = self.globaltrace_lt + self.sender = sender + + def runfunc(self, func, /, *args, **kw): + result = None + sys.settrace(self.globaltrace) + try: + result = func(*args, **kw) + finally: + sys.settrace(None) + return result + + def coverage(self, filename: str) -> typing.List[int]: + filename = _modname(filename) + return [line for file, line in self.counts.keys() if file == filename] + + def localtrace_count(self, frame, why, arg): + if why == "line": + filename = frame.f_code.co_filename + lineno = frame.f_lineno + key = filename, lineno + if key not in self.counts: + try: + self.sender(key) + except Exception: + pass + self.counts[key] = self.counts.get(key, 0) + 1 + return self.localtrace + + def globaltrace_lt(self, frame, why, arg): + if why == 'call': + filename = frame.f_globals.get('__file__', None) + if filename: + modulename = _modname(filename) + if modulename is not None: + return self.localtrace + else: + return None + + +class PureTracer: + def __init__(self): + self.counts = [] + + def runfunc(self, func, /, *args, **kw): + return func(*args, **kw) diff --git a/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/utils.py b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/utils.py new file mode 100644 index 0000000000..35c10d0f83 --- /dev/null +++ b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/utils.py @@ -0,0 +1,14 @@ +import os +import sys +from contextlib import contextmanager + + +@contextmanager +def suppress_stdout(): + with open(os.devnull, "w") as devnull: + old_stdout = sys.stdout + sys.stdout = devnull + try: + yield + finally: + sys.stdout = old_stdout diff --git a/utbot-python-executor/src/main/resources/utbot_executor_version b/utbot-python-executor/src/main/resources/utbot_executor_version new file mode 100644 index 0000000000..3d568d1270 --- /dev/null +++ b/utbot-python-executor/src/main/resources/utbot_executor_version @@ -0,0 +1 @@ +1.4.39 \ No newline at end of file diff --git a/utbot-python-executor/src/test/kotlin/org/utbot/python/executor/TestDeepSerialization.kt b/utbot-python-executor/src/test/kotlin/org/utbot/python/executor/TestDeepSerialization.kt new file mode 100644 index 0000000000..b5841c4c3d --- /dev/null +++ b/utbot-python-executor/src/test/kotlin/org/utbot/python/executor/TestDeepSerialization.kt @@ -0,0 +1,12 @@ +package org.utbot.python.executor + +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test + +internal class DeepSerializationTest { + + @Test + fun testUtBotExecutor() { + assertTrue(true) + } +} diff --git a/utbot-python-types/build.gradle.kts b/utbot-python-types/build.gradle.kts index 370aaaf9d2..67bf926986 100644 --- a/utbot-python-types/build.gradle.kts +++ b/utbot-python-types/build.gradle.kts @@ -19,12 +19,25 @@ tasks.register("cleanDist") { delete(localMypyPath.canonicalPath) } +val installPoetry = + if (pythonInterpreter != null) { + tasks.register("installPoetry") { + group = "python" + workingDir = utbotMypyRunnerPath + commandLine(pythonInterpreter, "-m", "pip", "install", "poetry") + } + } else { + null + } + val setMypyRunnerVersion = - if (pythonInterpreter != null) + if (pythonInterpreter != null) { tasks.register("setVersion") { - group = "python" - workingDir = utbotMypyRunnerPath - commandLine(pythonInterpreter, "-m", "poetry", "version", utbotMypyRunnerVersion) + dependsOn(installPoetry!!) + group = "python" + workingDir = utbotMypyRunnerPath + commandLine(pythonInterpreter, "-m", "poetry", "version", utbotMypyRunnerVersion) + } } else { null } diff --git a/utbot-python/build.gradle.kts b/utbot-python/build.gradle.kts index 9f547702b4..8d0c4b02c6 100644 --- a/utbot-python/build.gradle.kts +++ b/utbot-python/build.gradle.kts @@ -12,6 +12,7 @@ dependencies { api(project(":utbot-framework")) api(project(":utbot-python-parser")) api(project(":utbot-python-types")) + api(project(":utbot-python-executor")) testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") implementation(group = "org.apache.commons", name = "commons-lang3", version = "3.12.0") diff --git a/utbot-python/docs/python_packages.md b/utbot-python/docs/python_packages.md index 951cc469ee..8001a40729 100644 --- a/utbot-python/docs/python_packages.md +++ b/utbot-python/docs/python_packages.md @@ -33,4 +33,25 @@ Add the following files locally (they are listed in `.gitignore`): - `utbot-python/src/main/resources/local_pip_setup/use_local_python_packages` - Write here `true` if you want to build `utbot-python` that uses local versions of UTBot Python packages. \ No newline at end of file + Write here `true` if you want to build `utbot-python` that uses local versions of UTBot Python packages. + +## utbot_executor + +### Version + +Write version in file `utbot-python-executor/src/main/resources/utbot_executor_version`. + +Gradle task `utbot-python-executor:setVersion` will update `pyproject.toml`. + +### Usage of local version + +Add the following files locally (they are listed in `.gitignore`): + +- `utbot-python/src/main/resources/local_pip_setup/local_utbot_executor_path` + + Add here absolute path to `utbot_executor/dist` directory. + + +- `utbot-python/src/main/resources/local_pip_setup/use_local_python_packages` + + Write here `true` if you want to build `utbot-python` that uses local versions of UTBot Python packages. diff --git a/utbot-python/samples/samples/type_inference/annotations2.py b/utbot-python/samples/samples/type_inference/annotations2.py index 5dfa3d36ee..3c34fa150f 100644 --- a/utbot-python/samples/samples/type_inference/annotations2.py +++ b/utbot-python/samples/samples/type_inference/annotations2.py @@ -6,6 +6,11 @@ XXX = TypeVar("XXX", "A", int) +def f(x: int): + a = [x, XXX] + return a + + class A(Generic[XXX]): self_: XXX diff --git a/utbot-python/samples/test_configuration.json b/utbot-python/samples/test_configuration.json index 91dce2d34c..1d42c2c2c3 100644 --- a/utbot-python/samples/test_configuration.json +++ b/utbot-python/samples/test_configuration.json @@ -569,7 +569,7 @@ { "classes": null, "methods": null, - "timeout": 120, + "timeout": 140, "coverage": 100 } ] diff --git a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonCoverageReceiver.kt b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonCoverageReceiver.kt index 7ed61a429a..413ff85aa4 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonCoverageReceiver.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonCoverageReceiver.kt @@ -39,9 +39,12 @@ class PythonCoverageReceiver( val buf = ByteArray(256) val request = DatagramPacket(buf, buf.size) socket.receive(request) - val (id, line) = request.data.decodeToString().take(request.length).split(":") - val lineNumber = line.toInt() - coverageStorage.getOrPut(id) { mutableSetOf() }.add(lineNumber) + val requestData = request.data.decodeToString().take(request.length).split(":") + if (requestData.size == 2) { + val (id, line) = requestData + val lineNumber = line.toInt() + coverageStorage.getOrPut(id) { mutableSetOf() }.add(lineNumber) + } } } catch (ex: SocketException) { logger.debug { ex.message } diff --git a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialiation/PythonObjectParser.kt b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialiation/PythonObjectParser.kt index 5210ad173d..e209cd6f2a 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialiation/PythonObjectParser.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialiation/PythonObjectParser.kt @@ -240,7 +240,14 @@ fun MemoryObject.toPythonTree( val stateObjsDraft = memoryDump.getById(state) val customState = stateObjsDraft !is DictMemoryObject - val arguments = memoryDump.getById(args) as ListMemoryObject + val argumentsDump = memoryDump.getById(args) + if (argumentsDump is ReprMemoryObject && argumentsDump.value == "None") { // This is global variable + return@getOrPut PythonTree.PrimitiveNode( + PythonClassId(this.constructor.module, this.constructor.kind), + this.constructor.qualname + ) + } + val arguments = argumentsDump as ListMemoryObject val listitemsObjs = memoryDump.getById(listitems) as ListMemoryObject val dictitemsObjs = memoryDump.getById(dictitems) as DictMemoryObject val prevObj = PythonTree.ReduceNode( diff --git a/utbot-python/src/main/kotlin/org/utbot/python/utils/RequirementsUtils.kt b/utbot-python/src/main/kotlin/org/utbot/python/utils/RequirementsUtils.kt index 63d086fd48..ec279c7de7 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/utils/RequirementsUtils.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/utils/RequirementsUtils.kt @@ -1,19 +1,24 @@ package org.utbot.python.utils +import org.utbot.python.UtbotExecutor import org.utbot.python.newtyping.mypy.MypyInfoBuild object RequirementsUtils { private val utbotMypyRunnerVersion = MypyInfoBuild::class.java.getResource("/utbot_mypy_runner_version")!!.readText() + private val utbotExecutorVersion = + UtbotExecutor::class.java.getResource("/utbot_executor_version")!!.readText() private val useLocalPythonPackages = // "true" must be set only for debugging this::class.java.getResource("/local_pip_setup/use_local_python_packages")?.readText()?.toBoolean() ?: false private val localMypyRunnerPath = this::class.java.getResource("/local_pip_setup/local_utbot_mypy_path")?.readText() + private val localExecutorPath = + this::class.java.getResource("/local_pip_setup/local_utbot_executor_path")?.readText() private val pipFindLinks: List = - if (useLocalPythonPackages) listOf(localMypyRunnerPath!!) else emptyList() + if (useLocalPythonPackages) listOfNotNull(localMypyRunnerPath, localExecutorPath) else emptyList() val requirements: List = listOf( "utbot-mypy-runner==$utbotMypyRunnerVersion", - "utbot-executor==1.4.37", + "utbot-executor==$utbotExecutorVersion", ) private val requirementsScriptContent: String = From 257a6771759133f6f3da44e8f7ab4dbcdd62fa5b Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Tue, 22 Aug 2023 17:59:02 +0300 Subject: [PATCH 2/4] Code refactoring --- .../python/executor/TestDeepSerialization.kt | 12 ------------ .../main/kotlin/org/utbot/python/PythonEngine.kt | 4 ++-- .../utbot/python/evaluation/CodeEvaluationApi.kt | 2 +- .../python/evaluation/PythonCodeSocketExecutor.kt | 15 +++++++-------- .../ExecutionRequestSerializer.kt | 2 +- .../ExecutionResultDeserializer.kt | 2 +- .../PythonObjectParser.kt | 2 +- 7 files changed, 13 insertions(+), 26 deletions(-) delete mode 100644 utbot-python-executor/src/test/kotlin/org/utbot/python/executor/TestDeepSerialization.kt rename utbot-python/src/main/kotlin/org/utbot/python/evaluation/{serialiation => serialization}/ExecutionRequestSerializer.kt (93%) rename utbot-python/src/main/kotlin/org/utbot/python/evaluation/{serialiation => serialization}/ExecutionResultDeserializer.kt (97%) rename utbot-python/src/main/kotlin/org/utbot/python/evaluation/{serialiation => serialization}/PythonObjectParser.kt (99%) diff --git a/utbot-python-executor/src/test/kotlin/org/utbot/python/executor/TestDeepSerialization.kt b/utbot-python-executor/src/test/kotlin/org/utbot/python/executor/TestDeepSerialization.kt deleted file mode 100644 index b5841c4c3d..0000000000 --- a/utbot-python-executor/src/test/kotlin/org/utbot/python/executor/TestDeepSerialization.kt +++ /dev/null @@ -1,12 +0,0 @@ -package org.utbot.python.executor - -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.Test - -internal class DeepSerializationTest { - - @Test - fun testUtBotExecutor() { - assertTrue(true) - } -} diff --git a/utbot-python/src/main/kotlin/org/utbot/python/PythonEngine.kt b/utbot-python/src/main/kotlin/org/utbot/python/PythonEngine.kt index d3bcdc4c8d..6899ad1265 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/PythonEngine.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/PythonEngine.kt @@ -9,8 +9,8 @@ import org.utbot.fuzzing.NoSeedValueException import org.utbot.fuzzing.fuzz import org.utbot.fuzzing.utils.Trie import org.utbot.python.evaluation.* -import org.utbot.python.evaluation.serialiation.MemoryDump -import org.utbot.python.evaluation.serialiation.toPythonTree +import org.utbot.python.evaluation.serialization.MemoryDump +import org.utbot.python.evaluation.serialization.toPythonTree import org.utbot.python.evaluation.utils.CoverageIdGenerator import org.utbot.python.evaluation.utils.coveredLinesToInstructions import org.utbot.python.framework.api.python.PythonTree diff --git a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/CodeEvaluationApi.kt b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/CodeEvaluationApi.kt index 06f0538871..3030ed6870 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/CodeEvaluationApi.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/CodeEvaluationApi.kt @@ -3,7 +3,7 @@ package org.utbot.python.evaluation import org.utbot.framework.plugin.api.Coverage import org.utbot.python.FunctionArguments import org.utbot.python.PythonMethod -import org.utbot.python.evaluation.serialiation.MemoryDump +import org.utbot.python.evaluation.serialization.MemoryDump interface PythonCodeExecutor { val method: PythonMethod diff --git a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonCodeSocketExecutor.kt b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonCodeSocketExecutor.kt index 8447db8f67..e4c53f02d6 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonCodeSocketExecutor.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/PythonCodeSocketExecutor.kt @@ -5,13 +5,13 @@ import org.utbot.framework.plugin.api.Coverage import org.utbot.framework.plugin.api.Instruction import org.utbot.python.FunctionArguments import org.utbot.python.PythonMethod -import org.utbot.python.evaluation.serialiation.ExecutionRequest -import org.utbot.python.evaluation.serialiation.ExecutionRequestSerializer -import org.utbot.python.evaluation.serialiation.ExecutionResultDeserializer -import org.utbot.python.evaluation.serialiation.FailExecution -import org.utbot.python.evaluation.serialiation.PythonExecutionResult -import org.utbot.python.evaluation.serialiation.SuccessExecution -import org.utbot.python.evaluation.serialiation.serializeObjects +import org.utbot.python.evaluation.serialization.ExecutionRequest +import org.utbot.python.evaluation.serialization.ExecutionRequestSerializer +import org.utbot.python.evaluation.serialization.ExecutionResultDeserializer +import org.utbot.python.evaluation.serialization.FailExecution +import org.utbot.python.evaluation.serialization.PythonExecutionResult +import org.utbot.python.evaluation.serialization.SuccessExecution +import org.utbot.python.evaluation.serialization.serializeObjects import org.utbot.python.evaluation.utils.CoverageIdGenerator import org.utbot.python.framework.api.python.util.pythonAnyClassId import org.utbot.python.newtyping.PythonCallableTypeDescription @@ -19,7 +19,6 @@ import org.utbot.python.newtyping.pythonDescription import org.utbot.python.newtyping.pythonTypeName import org.utbot.python.newtyping.pythonTypeRepresentation import org.utbot.python.newtyping.utils.isNamed -import org.utbot.python.newtyping.utils.isRequired import java.net.SocketException private val logger = KotlinLogging.logger {} diff --git a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialiation/ExecutionRequestSerializer.kt b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialization/ExecutionRequestSerializer.kt similarity index 93% rename from utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialiation/ExecutionRequestSerializer.kt rename to utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialization/ExecutionRequestSerializer.kt index 80efc3393d..d4b9d7bbc6 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialiation/ExecutionRequestSerializer.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialization/ExecutionRequestSerializer.kt @@ -1,4 +1,4 @@ -package org.utbot.python.evaluation.serialiation +package org.utbot.python.evaluation.serialization import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory diff --git a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialiation/ExecutionResultDeserializer.kt b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialization/ExecutionResultDeserializer.kt similarity index 97% rename from utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialiation/ExecutionResultDeserializer.kt rename to utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialization/ExecutionResultDeserializer.kt index efd1347dc4..2909143096 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialiation/ExecutionResultDeserializer.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialization/ExecutionResultDeserializer.kt @@ -1,4 +1,4 @@ -package org.utbot.python.evaluation.serialiation +package org.utbot.python.evaluation.serialization import com.squareup.moshi.JsonEncodingException import com.squareup.moshi.Moshi diff --git a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialiation/PythonObjectParser.kt b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialization/PythonObjectParser.kt similarity index 99% rename from utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialiation/PythonObjectParser.kt rename to utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialization/PythonObjectParser.kt index e209cd6f2a..ae1d1aa253 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialiation/PythonObjectParser.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/evaluation/serialization/PythonObjectParser.kt @@ -1,4 +1,4 @@ -package org.utbot.python.evaluation.serialiation +package org.utbot.python.evaluation.serialization import com.squareup.moshi.Moshi import com.squareup.moshi.adapters.PolymorphicJsonAdapterFactory From 72383c5622f377472873deb074d9fd25aab86fa6 Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Wed, 23 Aug 2023 16:44:18 +0300 Subject: [PATCH 3/4] Update utbot-executor, cli and codegen --- .../python/PythonGenerateTestsCommand.kt | 16 ++++++++------- .../main/python/utbot_executor/pyproject.toml | 2 +- .../deep_serialization/memory_objects.py | 13 +++++++++++- .../deep_serialization/utils.py | 7 +++++++ .../utbot_executor/utbot_executor/executor.py | 10 ++++++---- .../src/main/resources/utbot_executor_version | 2 +- utbot-python/docs/CLI.md | 20 +++++++++++++------ .../{collections => collection}/__init__.py | 0 .../{collections => collection}/dicts.py | 0 .../{collections => collection}/lists.py | 0 .../{collections => collection}/recursive.py | 0 .../{collections => collection}/sets.py | 0 .../{collections => collection}/tuples.py | 0 .../using_collections.py | 0 .../samples/{math => mathematics}/__init__.py | 0 utbot-python/samples/test_configuration.json | 18 ++++++++--------- .../tree/PythonCgMethodConstructor.kt | 2 +- 17 files changed, 60 insertions(+), 30 deletions(-) rename utbot-python/samples/samples/{collections => collection}/__init__.py (100%) rename utbot-python/samples/samples/{collections => collection}/dicts.py (100%) rename utbot-python/samples/samples/{collections => collection}/lists.py (100%) rename utbot-python/samples/samples/{collections => collection}/recursive.py (100%) rename utbot-python/samples/samples/{collections => collection}/sets.py (100%) rename utbot-python/samples/samples/{collections => collection}/tuples.py (100%) rename utbot-python/samples/samples/{collections => collection}/using_collections.py (100%) rename utbot-python/samples/samples/{math => mathematics}/__init__.py (100%) diff --git a/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt index c82cf08bb1..7cc8e65311 100644 --- a/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt +++ b/utbot-cli-python/src/main/kotlin/org/utbot/cli/language/python/PythonGenerateTestsCommand.kt @@ -39,6 +39,8 @@ class PythonGenerateTestsCommand : CliktCommand( help = "File with Python code to generate tests for." ) + private fun absSourceFile() = sourceFile.toAbsolutePath() + private val pythonClass by option( "-c", "--class", help = "Specify top-level (ordinary, not nested) class under test. " + @@ -132,7 +134,7 @@ class PythonGenerateTestsCommand : CliktCommand( Success( topLevelFunctions .mapNotNull { parseFunctionDefinition(it) } - .map { PythonMethodHeader(it.name.toString(), sourceFile, null) } + .map { PythonMethodHeader(it.name.toString(), absSourceFile(), null) } ) else { val topLevelClassMethods = topLevelClasses @@ -142,7 +144,7 @@ class PythonGenerateTestsCommand : CliktCommand( .mapNotNull { parseFunctionDefinition(it) } .map { function -> val parsedClassName = PythonClassId(cls.name.toString()) - PythonMethodHeader(function.name.toString(), sourceFile, parsedClassName) + PythonMethodHeader(function.name.toString(), absSourceFile(), parsedClassName) } } if (topLevelClassMethods.isNotEmpty()) { @@ -154,7 +156,7 @@ class PythonGenerateTestsCommand : CliktCommand( val pythonMethodsOpt = selectedMethods.map { functionName -> topLevelFunctions .mapNotNull { parseFunctionDefinition(it) } - .map { PythonMethodHeader(it.name.toString(), sourceFile, null) } + .map { PythonMethodHeader(it.name.toString(), absSourceFile(), null) } .find { it.name == functionName } ?.let { Success(it) } ?: Fail("Couldn't find top-level function $functionName in the source file.") @@ -174,7 +176,7 @@ class PythonGenerateTestsCommand : CliktCommand( val fineMethods = methods .filter { !forbiddenMethods.contains(it.name.toString()) } .map { - PythonMethodHeader(it.name.toString(), sourceFile, parsedClassId) + PythonMethodHeader(it.name.toString(), absSourceFile(), parsedClassId) } if (fineMethods.isNotEmpty()) Success(fineMethods) @@ -201,8 +203,8 @@ class PythonGenerateTestsCommand : CliktCommand( @Suppress("UNCHECKED_CAST") private fun calculateValues(): Optional { - val currentPythonModuleOpt = findCurrentPythonModule(directoriesForSysPath, sourceFile) - sourceFileContent = File(sourceFile).readText() + val currentPythonModuleOpt = findCurrentPythonModule(directoriesForSysPath, absSourceFile()) + sourceFileContent = File(absSourceFile()).readText() val pythonMethodsOpt = bind(currentPythonModuleOpt) { getPythonMethods() } return bind(pack(currentPythonModuleOpt, pythonMethodsOpt)) { @@ -232,7 +234,7 @@ class PythonGenerateTestsCommand : CliktCommand( val config = PythonTestGenerationConfig( pythonPath = pythonPath, - testFileInformation = TestFileInformation(sourceFile.toAbsolutePath(), sourceFileContent, currentPythonModule.dropInitFile()), + testFileInformation = TestFileInformation(absSourceFile(), sourceFileContent, currentPythonModule.dropInitFile()), sysPathDirectories = directoriesForSysPath.map { it.toAbsolutePath() } .toSet(), testedMethods = pythonMethods, timeout = timeout, diff --git a/utbot-python-executor/src/main/python/utbot_executor/pyproject.toml b/utbot-python-executor/src/main/python/utbot_executor/pyproject.toml index 19808b6cf3..c5a2ec36aa 100644 --- a/utbot-python-executor/src/main/python/utbot_executor/pyproject.toml +++ b/utbot-python-executor/src/main/python/utbot_executor/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "utbot-executor" -version = "1.4.39" +version = "1.4.40" description = "" authors = ["Vyacheslav Tamarin "] readme = "README.md" diff --git a/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/memory_objects.py b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/memory_objects.py index a65924f488..e66e081946 100644 --- a/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/memory_objects.py +++ b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/memory_objects.py @@ -1,5 +1,6 @@ from __future__ import annotations +import copyreg import inspect import logging import re @@ -202,6 +203,8 @@ def __init__(self, reduce_object: object) -> None: constructor_arguments, callable_constructor = self.constructor_builder() self.constructor = get_constructor_info(callable_constructor) + logging.debug("Object: %s", self.obj) + logging.debug("Type: %s", type(self.obj)) logging.debug("Constructor: %s", callable_constructor) logging.debug("Constructor info: %s", self.constructor) logging.debug("Constructor args: %s", constructor_arguments) @@ -264,7 +267,15 @@ def constructor_builder(self) -> typing.Tuple[typing.Any, typing.Callable]: if is_newobj: constructor_arguments = self.reduce_value[1] callable_constructor = getattr(obj_type, "__new__") - return constructor_arguments, callable_constructor + try: + if callable_constructor.__module__ == self.obj.__class__.__module__: + return constructor_arguments, callable_constructor + except Exception: + pass + if constructor_kind.qualname == "copyreg.__newobj__": + return constructor_arguments, copyreg.__newobj__ + else: + return constructor_arguments, copyreg.__newobj_ex__ if is_reconstructor and is_user_type: constructor_arguments = self.reduce_value[1] diff --git a/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/utils.py b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/utils.py index 6dcdd94483..58260001b3 100644 --- a/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/utils.py +++ b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/utils.py @@ -1,6 +1,7 @@ from __future__ import annotations import dataclasses import importlib +import logging import pickle from typing import NewType @@ -14,6 +15,12 @@ class TypeInfo: module: str kind: str + def __init__(self, module: str, kind: str): + if module is None: + logging.error("Module is None") + self.module = module + self.kind = kind + @property def qualname(self): if self.module == "" or self.module == "builtins": diff --git a/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/executor.py b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/executor.py index 5460c0f7bd..293b676134 100644 --- a/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/executor.py +++ b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/executor.py @@ -170,6 +170,8 @@ def _run_calculate_function_value( __is_exception = False (__sources, __start, ) = inspect.getsourcelines(function) + __not_empty_lines = [i for i, line in enumerate(__sources, __start) if len(line.strip()) != 0] + logging.debug("Not empty lines %s", __not_empty_lines) __end = __start + len(__sources) __tracer = tracer @@ -184,11 +186,11 @@ def _run_calculate_function_value( logging.debug("Coverage: %s", __tracer.counts) logging.debug("Fullpath: %s", fullpath) - module_path = pathlib.PurePath(fullpath) - __stmts = [x[1] for x in __tracer.counts if pathlib.PurePath(x[0]) == module_path] - __stmts_filtered = [x for x in range(__start, __end) if x in __stmts] + module_path = pathlib.Path(fullpath) + __stmts = [x[1] for x in __tracer.counts if pathlib.Path(x[0]) == module_path] + __stmts_filtered = [x for x in __not_empty_lines if x in __stmts] __stmts_filtered_with_def = [__start] + __stmts_filtered - __missed_filtered = [x for x in range(__start, __end) if x not in __stmts_filtered_with_def] + __missed_filtered = [x for x in __not_empty_lines if x not in __stmts_filtered_with_def] logging.debug("Covered lines: %s", __stmts_filtered_with_def) logging.debug("Missed lines: %s", __missed_filtered) diff --git a/utbot-python-executor/src/main/resources/utbot_executor_version b/utbot-python-executor/src/main/resources/utbot_executor_version index 3d568d1270..6a8a867018 100644 --- a/utbot-python-executor/src/main/resources/utbot_executor_version +++ b/utbot-python-executor/src/main/resources/utbot_executor_version @@ -1 +1 @@ -1.4.39 \ No newline at end of file +1.4.40 \ No newline at end of file diff --git a/utbot-python/docs/CLI.md b/utbot-python/docs/CLI.md index bdc5c5e0b0..71aa95e76a 100644 --- a/utbot-python/docs/CLI.md +++ b/utbot-python/docs/CLI.md @@ -6,13 +6,13 @@ - Required Java version: 11. - - Prefered Python version: 3.8 or 3.9. + - Prefered Python version: 3.8+. Make sure that your Python has `pip` installed (this is usually the case). [Read more about pip installation](https://pip.pypa.io/en/stable/installation/). Before running utbot install pip requirements (or use `--install-requirements` flag in `generate_python` command): - python -m pip install mypy==0.971 astor typeshed-client coverage + python -m pip install utbot_executor utbot_mypy_runner ## Basic usage @@ -66,10 +66,6 @@ Run generated tests: Turn off Python requirements check (to speed up). -- `--visit-only-specified-source` - - Do not search for classes and imported modules in other Python files from `--sys-path` option. - - `-t, --timeout INT` Specify the maximum time in milliseconds to spend on generating tests (60000 by default). @@ -81,6 +77,18 @@ Run generated tests: - `--test-framework [pytest|Unittest]` Test framework to be used. + +- `--do-not-generate-regression-suite` + + Do not generate regression test suite. + +- `--runtime-exception-behaviour [PASS|FAIL]` + + Runtime exception behaviour (assert exceptions or not). + +- `--coverage` + + File to save coverage report. ### `run_python` options diff --git a/utbot-python/samples/samples/collections/__init__.py b/utbot-python/samples/samples/collection/__init__.py similarity index 100% rename from utbot-python/samples/samples/collections/__init__.py rename to utbot-python/samples/samples/collection/__init__.py diff --git a/utbot-python/samples/samples/collections/dicts.py b/utbot-python/samples/samples/collection/dicts.py similarity index 100% rename from utbot-python/samples/samples/collections/dicts.py rename to utbot-python/samples/samples/collection/dicts.py diff --git a/utbot-python/samples/samples/collections/lists.py b/utbot-python/samples/samples/collection/lists.py similarity index 100% rename from utbot-python/samples/samples/collections/lists.py rename to utbot-python/samples/samples/collection/lists.py diff --git a/utbot-python/samples/samples/collections/recursive.py b/utbot-python/samples/samples/collection/recursive.py similarity index 100% rename from utbot-python/samples/samples/collections/recursive.py rename to utbot-python/samples/samples/collection/recursive.py diff --git a/utbot-python/samples/samples/collections/sets.py b/utbot-python/samples/samples/collection/sets.py similarity index 100% rename from utbot-python/samples/samples/collections/sets.py rename to utbot-python/samples/samples/collection/sets.py diff --git a/utbot-python/samples/samples/collections/tuples.py b/utbot-python/samples/samples/collection/tuples.py similarity index 100% rename from utbot-python/samples/samples/collections/tuples.py rename to utbot-python/samples/samples/collection/tuples.py diff --git a/utbot-python/samples/samples/collections/using_collections.py b/utbot-python/samples/samples/collection/using_collections.py similarity index 100% rename from utbot-python/samples/samples/collections/using_collections.py rename to utbot-python/samples/samples/collection/using_collections.py diff --git a/utbot-python/samples/samples/math/__init__.py b/utbot-python/samples/samples/mathematics/__init__.py similarity index 100% rename from utbot-python/samples/samples/math/__init__.py rename to utbot-python/samples/samples/mathematics/__init__.py diff --git a/utbot-python/samples/test_configuration.json b/utbot-python/samples/test_configuration.json index 1d42c2c2c3..bb7ebf9685 100644 --- a/utbot-python/samples/test_configuration.json +++ b/utbot-python/samples/test_configuration.json @@ -21,7 +21,7 @@ "classes": null, "methods": null, "timeout": 10, - "coverage": 100 + "coverage": 91 } ] }, @@ -143,7 +143,7 @@ ] }, { - "path": "samples/collections", + "path": "samples/collection", "files": [ { "name": "dicts", @@ -206,8 +206,8 @@ { "classes": null, "methods": null, - "timeout": 60, - "coverage": 88 + "timeout": 30, + "coverage": 92 } ] } @@ -223,7 +223,7 @@ "classes": null, "methods": null, "timeout": 100, - "coverage": 100 + "coverage": 46 } ] }, @@ -434,7 +434,7 @@ "classes": null, "methods": null, "timeout": 60, - "coverage": 100 + "coverage": 92 } ] }, @@ -456,7 +456,7 @@ "classes": null, "methods": null, "timeout": 160, - "coverage": 110 + "coverage": 100 } ] } @@ -592,7 +592,7 @@ "classes": null, "methods": null, "timeout": 30, - "coverage": 100 + "coverage": 80 } ] }, @@ -643,4 +643,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgMethodConstructor.kt b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgMethodConstructor.kt index cf52ceb0cd..9658a2ecdf 100644 --- a/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgMethodConstructor.kt +++ b/utbot-python/src/main/kotlin/org/utbot/python/framework/codegen/model/constructor/tree/PythonCgMethodConstructor.kt @@ -122,7 +122,7 @@ class PythonCgMethodConstructor(context: CgContext) : CgMethodConstructor(contex recordActualResult() generateResultAssertions() - if (methodType == CgTestMethodType.PASSED_EXCEPTION) { + if (currentExecution?.result !is UtExecutionFailure && methodType == CgTestMethodType.SUCCESSFUL) { generateFieldStateAssertions(stateAssertions, assertThisObject, testSet.executableUnderTest) } } From 445fccb5f71bd321124928293b64e6713992ef1a Mon Sep 17 00:00:00 2001 From: Vyacheslav Tamarin Date: Wed, 23 Aug 2023 17:15:20 +0300 Subject: [PATCH 4/4] Fix executor --- .../main/python/utbot_executor/pyproject.toml | 2 +- .../tests/test_deep_serialization.py | 5 +++++ .../deep_serialization/memory_objects.py | 12 ++--------- .../deep_serialization/utils.py | 21 ++++++++++++------- .../src/main/resources/utbot_executor_version | 2 +- 5 files changed, 23 insertions(+), 19 deletions(-) diff --git a/utbot-python-executor/src/main/python/utbot_executor/pyproject.toml b/utbot-python-executor/src/main/python/utbot_executor/pyproject.toml index c5a2ec36aa..8f855fb4d2 100644 --- a/utbot-python-executor/src/main/python/utbot_executor/pyproject.toml +++ b/utbot-python-executor/src/main/python/utbot_executor/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "utbot-executor" -version = "1.4.40" +version = "1.4.41" description = "" authors = ["Vyacheslav Tamarin "] readme = "README.md" diff --git a/utbot-python-executor/src/main/python/utbot_executor/tests/test_deep_serialization.py b/utbot-python-executor/src/main/python/utbot_executor/tests/test_deep_serialization.py index 6c6a7d4b63..290785206c 100644 --- a/utbot-python-executor/src/main/python/utbot_executor/tests/test_deep_serialization.py +++ b/utbot-python-executor/src/main/python/utbot_executor/tests/test_deep_serialization.py @@ -1,6 +1,7 @@ import collections import dataclasses import datetime +import importlib.metadata import json import re import sys @@ -362,6 +363,10 @@ def test_strategy(obj: typing.Any, strategy: str): "collections.abc", ], ), + ( + importlib.metadata.SelectableGroups([["1", "2"]]), + ["tests.test_deep_serialization", "importlib.metadata"], + ), ], ) def test_corner_cases(obj: typing.Any, imports: typing.List[str]): diff --git a/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/memory_objects.py b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/memory_objects.py index e66e081946..ac9c32199b 100644 --- a/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/memory_objects.py +++ b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/memory_objects.py @@ -202,7 +202,7 @@ def __init__(self, reduce_object: object) -> None: constructor_arguments, callable_constructor = self.constructor_builder() - self.constructor = get_constructor_info(callable_constructor) + self.constructor = get_constructor_info(callable_constructor, self.obj) logging.debug("Object: %s", self.obj) logging.debug("Type: %s", type(self.obj)) logging.debug("Constructor: %s", callable_constructor) @@ -267,15 +267,7 @@ def constructor_builder(self) -> typing.Tuple[typing.Any, typing.Callable]: if is_newobj: constructor_arguments = self.reduce_value[1] callable_constructor = getattr(obj_type, "__new__") - try: - if callable_constructor.__module__ == self.obj.__class__.__module__: - return constructor_arguments, callable_constructor - except Exception: - pass - if constructor_kind.qualname == "copyreg.__newobj__": - return constructor_arguments, copyreg.__newobj__ - else: - return constructor_arguments, copyreg.__newobj_ex__ + return constructor_arguments, callable_constructor if is_reconstructor and is_user_type: constructor_arguments = self.reduce_value[1] diff --git a/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/utils.py b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/utils.py index 58260001b3..da06aaeb0a 100644 --- a/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/utils.py +++ b/utbot-python-executor/src/main/python/utbot_executor/utbot_executor/deep_serialization/utils.py @@ -76,14 +76,21 @@ def get_constructor_kind(py_object: object) -> TypeInfo: return TypeInfo(module, qualname) -def get_constructor_info(constructor: object) -> TypeInfo: +def get_constructor_info(constructor: object, obj: object) -> TypeInfo: if constructor == object.__init__: - return TypeInfo("builtins", "object.__new__") - if constructor == object.__new__: - return TypeInfo("builtins", "object.__new__") - if constructor is None: - return TypeInfo("types", "NoneType") - return TypeInfo(constructor.__module__, constructor.__qualname__) + result = TypeInfo("builtins", "object.__new__") + elif constructor == object.__new__: + result = TypeInfo("builtins", "object.__new__") + elif constructor.__module__ is None: + result = TypeInfo("builtins", "object.__new__") + elif constructor is None: + result = TypeInfo("types", "NoneType") + else: + result = TypeInfo(constructor.__module__, constructor.__qualname__) + + if result.kind == "object.__new__" and obj.__new__.__module__ is None: + result = TypeInfo(obj.__module__, f"{obj.__class__.__name__}.__new__") + return result def has_reduce(py_object: object) -> bool: diff --git a/utbot-python-executor/src/main/resources/utbot_executor_version b/utbot-python-executor/src/main/resources/utbot_executor_version index 6a8a867018..f4b5f63c9a 100644 --- a/utbot-python-executor/src/main/resources/utbot_executor_version +++ b/utbot-python-executor/src/main/resources/utbot_executor_version @@ -1 +1 @@ -1.4.40 \ No newline at end of file +1.4.41 \ No newline at end of file