retracesoftware-proxy 0.1.22__py3-none-any.whl → 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- retracesoftware/__main__.py +266 -0
- retracesoftware/autoenable.py +53 -0
- retracesoftware/config.json +19 -295
- retracesoftware/install/config.py +6 -0
- retracesoftware/install/edgecases.py +23 -1
- retracesoftware/install/patcher.py +93 -649
- retracesoftware/install/patchfindspec.py +117 -0
- retracesoftware/install/phases.py +338 -0
- retracesoftware/install/record.py +97 -37
- retracesoftware/install/replace.py +28 -0
- retracesoftware/install/replay.py +55 -11
- retracesoftware/install/tracer.py +87 -77
- retracesoftware/modules.toml +384 -0
- retracesoftware/preload.txt +216 -0
- retracesoftware/proxy/messagestream.py +204 -0
- retracesoftware/proxy/proxysystem.py +283 -64
- retracesoftware/proxy/proxytype.py +34 -10
- retracesoftware/proxy/record.py +97 -64
- retracesoftware/proxy/replay.py +62 -219
- retracesoftware/proxy/serializer.py +28 -0
- retracesoftware/proxy/startthread.py +40 -0
- retracesoftware/proxy/stubfactory.py +42 -19
- retracesoftware/replay.py +104 -0
- retracesoftware/run.py +375 -0
- retracesoftware/stackdifference.py +133 -0
- {retracesoftware_proxy-0.1.22.dist-info → retracesoftware_proxy-0.2.1.dist-info}/METADATA +2 -1
- retracesoftware_proxy-0.2.1.dist-info/RECORD +42 -0
- retracesoftware_proxy-0.1.22.dist-info/RECORD +0 -29
- {retracesoftware_proxy-0.1.22.dist-info → retracesoftware_proxy-0.2.1.dist-info}/WHEEL +0 -0
- {retracesoftware_proxy-0.1.22.dist-info → retracesoftware_proxy-0.2.1.dist-info}/top_level.txt +0 -0
|
@@ -5,21 +5,29 @@ from retracesoftware.install import globals
|
|
|
5
5
|
import os, json, re, glob
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from datetime import datetime
|
|
8
|
+
from retracesoftware.install.config import env_truthy
|
|
8
9
|
|
|
9
|
-
def
|
|
10
|
-
"""
|
|
11
|
-
Given a strftime-style filename pattern (e.g. "recordings/%Y%m%d_%H%M%S_%f"),
|
|
12
|
-
return the path to the most recent matching file, or None if no files exist.
|
|
13
|
-
"""
|
|
10
|
+
def recordings(pattern):
|
|
14
11
|
# Turn strftime placeholders into '*' for globbing
|
|
15
12
|
# (very simple replacement: %... -> *)
|
|
16
13
|
glob_pattern = re.sub(r"%[a-zA-Z]", "*", pattern)
|
|
17
14
|
|
|
15
|
+
base_pattern = os.path.basename(pattern)
|
|
16
|
+
|
|
18
17
|
# Find all matching files
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
for path in glob.glob(glob_pattern):
|
|
19
|
+
try:
|
|
20
|
+
name = os.path.basename(path)
|
|
21
|
+
datetime.strptime(name, base_pattern)
|
|
22
|
+
yield path
|
|
23
|
+
except:
|
|
24
|
+
pass
|
|
22
25
|
|
|
26
|
+
def latest_from_pattern(pattern: str) -> str | None:
|
|
27
|
+
"""
|
|
28
|
+
Given a strftime-style filename pattern (e.g. "recordings/%Y%m%d_%H%M%S_%f"),
|
|
29
|
+
return the path to the most recent matching file, or None if no files exist.
|
|
30
|
+
"""
|
|
23
31
|
# Derive the datetime format from the pattern (basename only)
|
|
24
32
|
base_pattern = os.path.basename(pattern)
|
|
25
33
|
|
|
@@ -28,11 +36,11 @@ def latest_from_pattern(pattern: str) -> str | None:
|
|
|
28
36
|
return datetime.strptime(name, base_pattern)
|
|
29
37
|
|
|
30
38
|
# Find the latest by parsed timestamp
|
|
31
|
-
|
|
32
|
-
return latest
|
|
39
|
+
return max(recordings(pattern), key=parse_time)
|
|
33
40
|
|
|
34
41
|
def replay_system(thread_state, immutable_types, config):
|
|
35
42
|
|
|
43
|
+
verbose = env_truthy('RETRACE_VERBOSE', False)
|
|
36
44
|
recording_path = Path(latest_from_pattern(config['recording_path']))
|
|
37
45
|
|
|
38
46
|
# print(f"replay running against path: {recording_path}")
|
|
@@ -51,8 +59,44 @@ def replay_system(thread_state, immutable_types, config):
|
|
|
51
59
|
with open(recording_path / "mainscript", "r", encoding="utf-8") as f:
|
|
52
60
|
mainscript = f.read()
|
|
53
61
|
|
|
62
|
+
call_trace_file = recording_path / "call_trace.txt"
|
|
63
|
+
|
|
64
|
+
if call_trace_file.exists():
|
|
65
|
+
class CallTracer:
|
|
66
|
+
def __init__(self):
|
|
67
|
+
self.file = open(call_trace_file, 'r')
|
|
68
|
+
|
|
69
|
+
def __call__(self, obj):
|
|
70
|
+
recording = json.loads(self.file.readline())
|
|
71
|
+
if obj != recording:
|
|
72
|
+
breakpoint()
|
|
73
|
+
|
|
74
|
+
def close(self):
|
|
75
|
+
self.file.close()
|
|
76
|
+
|
|
77
|
+
# def on_call(self, function_name):
|
|
78
|
+
# recording = self.file.readline()
|
|
79
|
+
# if f'call: {function_name}\n' != recording:
|
|
80
|
+
# breakpoint()
|
|
81
|
+
|
|
82
|
+
# assert f'call: {function_name}\n' == recording
|
|
83
|
+
|
|
84
|
+
# def on_return(self, function_name):
|
|
85
|
+
# recording = self.file.readline()
|
|
86
|
+
# if f'return: {function_name}\n' != recording:
|
|
87
|
+
# breakpoint()
|
|
88
|
+
|
|
89
|
+
# assert f'return: {function_name}\n' == recording
|
|
90
|
+
|
|
91
|
+
call_tracer = CallTracer()
|
|
92
|
+
else:
|
|
93
|
+
call_tracer = None
|
|
94
|
+
|
|
54
95
|
return ReplayProxySystem(thread_state = thread_state,
|
|
55
96
|
immutable_types = immutable_types,
|
|
56
97
|
tracing_config = tracing_config,
|
|
57
98
|
mainscript = mainscript,
|
|
58
|
-
path = recording_path / 'trace.bin'
|
|
99
|
+
path = recording_path / 'trace.bin',
|
|
100
|
+
tracecalls = env_truthy('RETRACE_ALL', False),
|
|
101
|
+
verbose = verbose,
|
|
102
|
+
magic_markers = env_truthy('RETRACE_MAGIC_MARKERS', False))
|
|
@@ -2,9 +2,12 @@ import retracesoftware.functional as functional
|
|
|
2
2
|
import retracesoftware_utils as utils
|
|
3
3
|
from retracesoftware.proxy.proxytype import Proxy
|
|
4
4
|
|
|
5
|
+
from retracesoftware.proxy.serializer import serializer
|
|
6
|
+
|
|
5
7
|
import types
|
|
6
8
|
import os
|
|
7
9
|
import sys
|
|
10
|
+
import functools
|
|
8
11
|
|
|
9
12
|
def format_kwargs(kwargs):
|
|
10
13
|
result = []
|
|
@@ -49,7 +52,8 @@ class Tracer:
|
|
|
49
52
|
|
|
50
53
|
def __init__(self, config, writer):
|
|
51
54
|
self.config = config
|
|
52
|
-
serialize =
|
|
55
|
+
serialize = serializer
|
|
56
|
+
# functional.walker(self.serialize)
|
|
53
57
|
self.writer = functional.mapargs(transform = serialize, function = writer)
|
|
54
58
|
|
|
55
59
|
def systrace(self, frame, event, arg):
|
|
@@ -74,101 +78,101 @@ class Tracer:
|
|
|
74
78
|
print(f'systrace RAISED ERROR!')
|
|
75
79
|
raise
|
|
76
80
|
|
|
77
|
-
def trace_calls(self, thread_state):
|
|
78
|
-
|
|
79
|
-
|
|
81
|
+
# def trace_calls(self, thread_state):
|
|
82
|
+
# if 'tracecalls' in self.config:
|
|
83
|
+
# # def on_call(frame):
|
|
80
84
|
|
|
81
|
-
|
|
85
|
+
# # func = frame.function
|
|
82
86
|
|
|
83
|
-
|
|
87
|
+
# # key = (func.__module__, func.__name__)
|
|
84
88
|
|
|
85
|
-
|
|
86
|
-
|
|
89
|
+
# # ignore = set([('abc', '__subclasscheck__'),
|
|
90
|
+
# # ('threading', '_shutdown')])
|
|
87
91
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
92
|
+
# # if key in ignore:
|
|
93
|
+
# # print('About to throw!!!')
|
|
94
|
+
# # raise Exception()
|
|
91
95
|
|
|
92
|
-
|
|
96
|
+
# # objtype = None
|
|
93
97
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
98
|
+
# # if 'self' in frame.locals:
|
|
99
|
+
# # this = frame.locals['self']
|
|
100
|
+
# # objtype = type(this)
|
|
97
101
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
+
# # # if objtype:
|
|
103
|
+
# # # print(f'Called!!!!: {func.__module__}.{objtype.__name__}.{func.__name__}')
|
|
104
|
+
# # # else:
|
|
105
|
+
# # # print(f'Called!!!!: {func.__module__}.{func.__name__}')
|
|
102
106
|
|
|
103
|
-
|
|
107
|
+
# # qualname = (func.__module__, objtype, func.__name__)
|
|
104
108
|
|
|
105
|
-
|
|
109
|
+
# # self.writer(f'{func.__module__}.{func.__name__}')
|
|
106
110
|
|
|
107
|
-
|
|
108
|
-
|
|
111
|
+
# # # if func.__name__ == '_path_stat':
|
|
112
|
+
# # # utils.sigtrap('_path_stat start')
|
|
109
113
|
|
|
110
|
-
|
|
114
|
+
# # return qualname
|
|
111
115
|
|
|
112
|
-
|
|
113
|
-
|
|
116
|
+
# # def on_result(qualname, res):
|
|
117
|
+
# # mod, obj, func = qualname
|
|
114
118
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
+
# # # if obj:
|
|
120
|
+
# # # print(f'Returning!!!!: {mod}.{obj}.{func}')
|
|
121
|
+
# # # else:
|
|
122
|
+
# # # print(f'Returning!!!!: {mod}.{func}')
|
|
119
123
|
|
|
120
|
-
|
|
121
|
-
|
|
124
|
+
# # # if func == '_path_stat':
|
|
125
|
+
# # # utils.sigtrap('_path_stat res')
|
|
122
126
|
|
|
123
|
-
|
|
124
|
-
|
|
127
|
+
# # if func != '__hash__':
|
|
128
|
+
# # ignore = set(('typing', 'inner'))
|
|
125
129
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
130
|
+
# # if qualname not in ignore:
|
|
131
|
+
# # if type(res) in [str, int]:
|
|
132
|
+
# # self.writer(res)
|
|
133
|
+
# # else:
|
|
134
|
+
# # self.writer(str(type(res)))
|
|
131
135
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
136
|
+
# # def on_call_disabled(frame):
|
|
137
|
+
# # func = frame.function
|
|
138
|
+
# # print(f'disabled: {func.__module__}.{func.__name__}')
|
|
135
139
|
|
|
136
|
-
|
|
137
|
-
|
|
140
|
+
# callback = CallTracer(writer = self.writer, thread_state = thread_state)
|
|
141
|
+
# utils.intercept_frame_eval(callback)
|
|
138
142
|
|
|
139
|
-
def serialize(self, obj):
|
|
140
|
-
|
|
141
|
-
|
|
143
|
+
# def serialize(self, obj):
|
|
144
|
+
# try:
|
|
145
|
+
# if obj is None: return None
|
|
142
146
|
|
|
143
|
-
|
|
147
|
+
# cls = functional.typeof(obj)
|
|
144
148
|
|
|
145
|
-
|
|
146
|
-
|
|
149
|
+
# if issubclass(cls, Proxy):
|
|
150
|
+
# return str(cls)
|
|
147
151
|
|
|
148
|
-
|
|
149
|
-
|
|
152
|
+
# if issubclass(cls, (int, str)):
|
|
153
|
+
# return obj
|
|
150
154
|
|
|
151
|
-
|
|
155
|
+
# return str(cls)
|
|
152
156
|
|
|
153
|
-
|
|
154
|
-
|
|
157
|
+
# # if issubclass(cls, Proxy):
|
|
158
|
+
# # return f'<Proxy>'
|
|
155
159
|
|
|
156
|
-
|
|
157
|
-
|
|
160
|
+
# # if issubclass(cls, types.TracebackType):
|
|
161
|
+
# # return '<traceback>'
|
|
158
162
|
|
|
159
|
-
|
|
160
|
-
|
|
163
|
+
# # elif issubclass(cls, utils.wrapped_function):
|
|
164
|
+
# # return utils.unwrap(obj).__name__
|
|
161
165
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
166
|
+
# # elif hasattr(obj, '__module__') and hasattr(obj, '__name__'):
|
|
167
|
+
# # return f'{obj.__module__}.{obj.__name__}'
|
|
168
|
+
# # # elif isinstance(obj, type):
|
|
169
|
+
# # # return f'{obj.__module__}.{obj.__name__}'
|
|
170
|
+
# # else:
|
|
171
|
+
# # return '<other>'
|
|
172
|
+
# # # return f'instance type: {str(self.serialize(type(obj)))}'
|
|
173
|
+
# except:
|
|
174
|
+
# print("ERROR in tracer serialize!!!!")
|
|
175
|
+
# os._exit(1)
|
|
172
176
|
|
|
173
177
|
def log(self, name, message):
|
|
174
178
|
if name in self.config:
|
|
@@ -180,17 +184,23 @@ class Tracer:
|
|
|
180
184
|
# def stacktrace(self):
|
|
181
185
|
# self.writer('stacktrace', utils.stacktrace())
|
|
182
186
|
|
|
187
|
+
def _write_call(self, name, *args, **kwargs):
|
|
188
|
+
# self.stacktrace()
|
|
189
|
+
if len(args) > 0:
|
|
190
|
+
funcname = args[0].__name__
|
|
191
|
+
# if funcname == '__del__':
|
|
192
|
+
# utils.sigtrap('FOO')
|
|
193
|
+
|
|
194
|
+
self.writer(name, funcname, args[1:], kwargs)
|
|
195
|
+
else:
|
|
196
|
+
utils.sigtrap('FOO')
|
|
197
|
+
breakpoint()
|
|
198
|
+
|
|
183
199
|
def __call__(self, name, func = None):
|
|
184
200
|
if name in self.config:
|
|
185
201
|
if name.endswith('.call'):
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
if len(args) > 0:
|
|
189
|
-
self.writer(name, args[0].__name__, args[1:], kwargs)
|
|
190
|
-
else:
|
|
191
|
-
breakpoint()
|
|
192
|
-
|
|
193
|
-
return functional.firstof(write_call, func) if func else write_call
|
|
202
|
+
f = functools.partial(self._write_call, name)
|
|
203
|
+
return functional.firstof(f, func) if func else f
|
|
194
204
|
|
|
195
205
|
elif name.endswith('.result'):
|
|
196
206
|
def write_result(obj):
|