-
-
Notifications
You must be signed in to change notification settings - Fork 159
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Move expected tracer.events from test code to golden files #133
Changes from 14 commits
24a7716
8a55728
ad6d847
d099926
6f05635
a9a4b02
575227c
3d823f8
c38c778
fdc4f7a
46dbcb4
8effcbf
be40db8
d7c0d1e
0e75b80
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,8 +5,9 @@ | |
import os | ||
import pytest | ||
import responses | ||
import attr | ||
|
||
from cyberbrain import _TracerFSM, trace | ||
from cyberbrain import _TracerFSM, trace, Symbol | ||
from utils import python_version, get_os_type | ||
|
||
|
||
|
@@ -40,37 +41,98 @@ def fixture_trace(request): | |
trace.tracer_state = _TracerFSM.INITIAL | ||
|
||
|
||
def get_golden_data(golden_filepath, key): | ||
directory = os.path.dirname(golden_filepath) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. type hints |
||
if not os.path.exists(directory): | ||
os.makedirs(directory) | ||
|
||
if not os.path.exists(golden_filepath): | ||
return None | ||
|
||
with open(golden_filepath, "r") as f: | ||
golden_frame_data = json.load(f) | ||
return golden_frame_data.get(key) | ||
|
||
|
||
def update_golden_data(golden_filepath, key, value): | ||
golden_frame_data = {} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto |
||
if os.path.exists(golden_filepath): | ||
with open(golden_filepath, "r") as f: | ||
golden_frame_data = json.load(f) | ||
|
||
golden_frame_data[key] = value | ||
|
||
with open(golden_filepath, "w") as f: | ||
json.dump(golden_frame_data, f, ensure_ascii=False, indent=4) | ||
|
||
|
||
def serialize_symbol(symbol): | ||
snapshot = symbol.snapshot and { | ||
"location": symbol.snapshot.location, | ||
"events_pointer": symbol.snapshot.events_pointer, | ||
} | ||
return {"name": symbol.name, "snapshot": snapshot} | ||
|
||
|
||
def get_serialized_events(): | ||
tracer_events = [] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this can be extracted to a separate function. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add a doc string with a simple example. |
||
for event in trace.events: | ||
event_dict = attr.asdict(event) | ||
for key, val in event_dict.items(): | ||
if type(val) == Symbol: | ||
event_dict[key] = serialize_symbol(val) | ||
elif type(val) == list: | ||
event_dict[key] = sorted( | ||
[serialize_symbol(sym) for sym in val if type(sym) == Symbol], | ||
key=lambda x: x["name"], | ||
) | ||
event_dict["__class__"] = event.__class__.__name__ | ||
tracer_events.append(event_dict) | ||
return tracer_events | ||
|
||
|
||
@pytest.fixture(scope="function") | ||
def check_tracer_events(): | ||
yield | ||
|
||
tracer_events = get_serialized_events() | ||
|
||
golden_filepath = f"test/data/{python_version}/{trace.frame.frame_name}.json" | ||
golden_tracer_events = get_golden_data(golden_filepath, "tracer.events") | ||
if golden_tracer_events is None: | ||
update_golden_data(golden_filepath, "tracer.events", tracer_events) | ||
else: | ||
assert tracer_events == golden_tracer_events, json.dumps( | ||
tracer_events, indent=4 | ||
) | ||
|
||
|
||
@pytest.fixture | ||
def mocked_responses(request): | ||
def check_response(request): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel like this function is doing multiple things and we should break into smaller ones, something like golden_response = get_golden_response()
if golden_response:
assert ...
else:
update_golden_response() This makese the intent more clear. A good side effect: skip assertion if golden_response does not exist (now assertion still happens if the file exists). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same with |
||
with responses.RequestsMock() as resp: | ||
resp.add( | ||
responses.POST, | ||
f"http://localhost:{trace.rpc_client.port}/frame", | ||
status=200, | ||
content_type="application/octet-stream", | ||
) | ||
yield resp | ||
yield | ||
|
||
frame_data = msgpack.unpackb(resp.calls[0].request.body) | ||
frame_name = frame_data["metadata"]["frame_name"] | ||
|
||
# Generates golden data. | ||
golden_filepath = f"test/data/{python_version}/{frame_name}.json" | ||
directory = os.path.dirname(golden_filepath) | ||
if not os.path.exists(directory): | ||
os.makedirs(directory) | ||
|
||
if not os.path.exists(golden_filepath): | ||
with open(golden_filepath, "wt") as f: | ||
json.dump(frame_data, f, ensure_ascii=False, indent=4) | ||
return | ||
|
||
# Assuming run in root directory. | ||
with open(golden_filepath, "rt") as f: | ||
golden_frame_data = json.loads(f.read()) | ||
response = msgpack.unpackb(resp.calls[0].request.body) | ||
frame_name = response["metadata"]["frame_name"] | ||
|
||
# Don't check request body on Windows because it has a different format. | ||
if get_os_type() == "windows" and frame_name in {"test_numpy", "test_pandas"}: | ||
return | ||
|
||
assert frame_data == golden_frame_data, json.dumps(frame_data, indent=4) | ||
golden_filepath = f"test/data/{python_version}/{frame_name}.json" | ||
golden_response = get_golden_data(golden_filepath, "response") | ||
if golden_response is None: | ||
update_golden_data(golden_filepath, "response", response) | ||
else: | ||
assert response == golden_response, json.dumps(response, indent=4) | ||
|
||
|
||
@pytest.fixture | ||
def check_golden_file(check_tracer_events, check_response): | ||
yield |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
{ | ||
"response": { | ||
"metadata": { | ||
"frame_id": "attribute_error", | ||
"frame_name": "attribute_error", | ||
"filename": "test_exception.py", | ||
"defined_lineno": 72 | ||
}, | ||
"identifiers": [ | ||
"a" | ||
], | ||
"loops": [], | ||
"events": [ | ||
{ | ||
"lineno": 74, | ||
"index": 0, | ||
"offset": 14, | ||
"filename": "test_exception.py", | ||
"id": "test_attribute_error:0", | ||
"target": "a", | ||
"value": "1", | ||
"repr": "1", | ||
"type": "Binding" | ||
}, | ||
{ | ||
"lineno": 84, | ||
"index": 1, | ||
"offset": 126, | ||
"filename": "test_exception.py", | ||
"id": "test_attribute_error:1", | ||
"value": "null", | ||
"repr": "None", | ||
"type": "Return" | ||
} | ||
], | ||
"tracingResult": {} | ||
}, | ||
"tracer.events": [ | ||
{ | ||
"lineno": 74, | ||
"index": 0, | ||
"offset": 14, | ||
"filename": "test_exception.py", | ||
"id": "test_attribute_error:0", | ||
"target": { | ||
"name": "a", | ||
"snapshot": null | ||
}, | ||
"value": "1", | ||
"repr": "1", | ||
"sources": [], | ||
"__class__": "Binding" | ||
}, | ||
{ | ||
"lineno": 84, | ||
"index": 1, | ||
"offset": 126, | ||
"filename": "test_exception.py", | ||
"id": "test_attribute_error:1", | ||
"value": "null", | ||
"repr": "None", | ||
"sources": [], | ||
"__class__": "Return" | ||
} | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
{ | ||
"response": { | ||
"metadata": { | ||
"frame_id": "binary_op_zero_division", | ||
"frame_name": "binary_op_zero_division", | ||
"filename": "test_exception.py", | ||
"defined_lineno": 19 | ||
}, | ||
"identifiers": [], | ||
"loops": [], | ||
"events": [ | ||
{ | ||
"lineno": 21, | ||
"index": 0, | ||
"offset": 32, | ||
"filename": "test_exception.py", | ||
"id": "test_binary_op_zero_division:0", | ||
"value": "null", | ||
"repr": "None", | ||
"type": "Return" | ||
} | ||
], | ||
"tracingResult": {} | ||
}, | ||
"tracer.events": [ | ||
{ | ||
"lineno": 21, | ||
"index": 0, | ||
"offset": 32, | ||
"filename": "test_exception.py", | ||
"id": "test_binary_op_zero_division:0", | ||
"value": "null", | ||
"repr": "None", | ||
"sources": [], | ||
"__class__": "Return" | ||
} | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
{ | ||
"response": { | ||
"metadata": { | ||
"frame_id": "build_map_type_error", | ||
"frame_name": "build_map_type_error", | ||
"filename": "test_exception.py", | ||
"defined_lineno": 100 | ||
}, | ||
"identifiers": [], | ||
"loops": [], | ||
"events": [ | ||
{ | ||
"lineno": 102, | ||
"index": 0, | ||
"offset": 34, | ||
"filename": "test_exception.py", | ||
"id": "test_build_map_type_error:0", | ||
"value": "null", | ||
"repr": "None", | ||
"type": "Return" | ||
} | ||
], | ||
"tracingResult": {} | ||
}, | ||
"tracer.events": [ | ||
{ | ||
"lineno": 102, | ||
"index": 0, | ||
"offset": 34, | ||
"filename": "test_exception.py", | ||
"id": "test_build_map_type_error:0", | ||
"value": "null", | ||
"repr": "None", | ||
"sources": [], | ||
"__class__": "Return" | ||
} | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
{ | ||
"response": { | ||
"metadata": { | ||
"frame_id": "build_set_type_error", | ||
"frame_name": "build_set_type_error", | ||
"filename": "test_exception.py", | ||
"defined_lineno": 91 | ||
}, | ||
"identifiers": [], | ||
"loops": [], | ||
"events": [ | ||
{ | ||
"lineno": 93, | ||
"index": 0, | ||
"offset": 30, | ||
"filename": "test_exception.py", | ||
"id": "test_build_set_type_error:0", | ||
"value": "null", | ||
"repr": "None", | ||
"type": "Return" | ||
} | ||
], | ||
"tracingResult": {} | ||
}, | ||
"tracer.events": [ | ||
{ | ||
"lineno": 93, | ||
"index": 0, | ||
"offset": 30, | ||
"filename": "test_exception.py", | ||
"id": "test_build_set_type_error:0", | ||
"value": "null", | ||
"repr": "None", | ||
"sources": [], | ||
"__class__": "Return" | ||
} | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
{ | ||
"response": { | ||
"metadata": { | ||
"frame_id": "call_method_type_error", | ||
"frame_name": "call_method_type_error", | ||
"filename": "test_exception.py", | ||
"defined_lineno": 8 | ||
}, | ||
"identifiers": [ | ||
"s" | ||
], | ||
"loops": [], | ||
"events": [ | ||
{ | ||
"lineno": 9, | ||
"index": 0, | ||
"offset": 2, | ||
"filename": "test_exception.py", | ||
"id": "test_call_method_type_error:0", | ||
"target": "s", | ||
"value": "\"hello world\"", | ||
"repr": "\"hello world\"", | ||
"type": "Binding" | ||
}, | ||
{ | ||
"lineno": 12, | ||
"index": 1, | ||
"offset": 58, | ||
"filename": "test_exception.py", | ||
"id": "test_call_method_type_error:1", | ||
"value": "null", | ||
"repr": "None", | ||
"type": "Return" | ||
} | ||
], | ||
"tracingResult": {} | ||
}, | ||
"tracer.events": [ | ||
{ | ||
"lineno": 9, | ||
"index": 0, | ||
"offset": 2, | ||
"filename": "test_exception.py", | ||
"id": "test_call_method_type_error:0", | ||
"target": { | ||
"name": "s", | ||
"snapshot": null | ||
}, | ||
"value": "\"hello world\"", | ||
"repr": "\"hello world\"", | ||
"sources": [], | ||
"__class__": "Binding" | ||
}, | ||
{ | ||
"lineno": 12, | ||
"index": 1, | ||
"offset": 58, | ||
"filename": "test_exception.py", | ||
"id": "test_call_method_type_error:1", | ||
"value": "null", | ||
"repr": "None", | ||
"sources": [], | ||
"__class__": "Return" | ||
} | ||
] | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we need this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not particularly needed for this PR but if you use it in
attr.asdict
without the filter removing sources, it throws an error. This is due to it recursing into the sources list and becoming simple types with no fields. I added this as a sanity check to avoid that error if we happen to useattr.asdict
for other purposes. If you don't think it's necessary, I'll remove it.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can leave it here and add a comment