Different implementation to set tracing to other threads for Python 3.7. Fixes #1587 (#1669)

This commit is contained in:
Fabio Zadrozny 2019-08-09 14:36:34 -03:00 committed by Karthik Nadig
parent 81ac055f3f
commit 152edebd4e
17 changed files with 365 additions and 88 deletions

View file

@ -103,6 +103,7 @@ else:
IS_PY3K = False
IS_PY34_OR_GREATER = False
IS_PY36_OR_GREATER = False
IS_PY37_OR_GREATER = False
IS_PY2 = True
IS_PY27 = False
IS_PY24 = False
@ -112,6 +113,7 @@ try:
IS_PY2 = False
IS_PY34_OR_GREATER = sys.version_info >= (3, 4)
IS_PY36_OR_GREATER = sys.version_info >= (3, 6)
IS_PY37_OR_GREATER = sys.version_info >= (3, 7)
elif sys.version_info[0] == 2 and sys.version_info[1] == 7:
IS_PY27 = True
elif sys.version_info[0] == 2 and sys.version_info[1] == 4:

View file

@ -4,6 +4,7 @@
#include "ref_utils.hpp"
#include "py_utils.hpp"
#include "python.h"
#include "py_settrace_37.hpp"
#include <unordered_set>
@ -36,7 +37,14 @@ DWORD GetPythonThreadId(PythonVersion version, PyThreadState* curThread) {
/**
* This function may be called to set a tracing function to existing python threads.
*/
int InternalSetSysTraceFunc(MODULE_TYPE module, bool isDebug, bool showDebugInfo, PyObjectHolder* traceFunc, PyObjectHolder* setTraceFunc, unsigned int threadId)
int InternalSetSysTraceFunc(
MODULE_TYPE module,
bool isDebug,
bool showDebugInfo,
PyObjectHolder* traceFunc,
PyObjectHolder* setTraceFunc,
unsigned int threadId,
PyObjectHolder* pyNone)
{
if(showDebugInfo){
@ -71,15 +79,8 @@ int InternalSetSysTraceFunc(MODULE_TYPE module, bool isDebug, bool showDebugInfo
intFromLong = intFromLongPy2;
}
DEFINE_PROC(errOccurred, PyErr_Occurred*, "PyErr_Occurred", 210);
DEFINE_PROC(pyErrFetch, PyErr_Fetch*, "PyErr_Fetch", 220);
DEFINE_PROC(pyErrRestore, PyErr_Restore*, "PyErr_Restore", 230);
DEFINE_PROC(pyImportMod, PyImport_ImportModule*, "PyImport_ImportModule", 240);
DEFINE_PROC(pyGetAttr, PyObject_GetAttrString*, "PyObject_GetAttrString", 250);
DEFINE_PROC(pyHasAttr, PyObject_HasAttrString*, "PyObject_HasAttrString", 260);
DEFINE_PROC(getThreadTls, PyThread_get_key_value*, "PyThread_get_key_value", 270);
DEFINE_PROC(setThreadTls, PyThread_set_key_value*, "PyThread_set_key_value", 280);
DEFINE_PROC(delThreadTls, PyThread_delete_key_value*, "PyThread_delete_key_value", 290);
DEFINE_PROC_NO_CHECK(PyCFrame_Type, PyTypeObject*, "PyCFrame_Type", 300); // optional
DEFINE_PROC_NO_CHECK(curPythonThread, PyThreadState**, "_PyThreadState_Current", 310); // optional
@ -111,67 +112,117 @@ int InternalSetSysTraceFunc(MODULE_TYPE module, bool isDebug, bool showDebugInfo
}
int threadStateIndex = -1;
for (int i = 0; i < 100000; i++) {
void* value = getThreadTls(i);
if (value == curPyThread) {
threadStateIndex = i;
if (version < PythonVersion_37)
{
DEFINE_PROC(errOccurred, PyErr_Occurred*, "PyErr_Occurred", 210);
DEFINE_PROC(pyErrFetch, PyErr_Fetch*, "PyErr_Fetch", 220);
DEFINE_PROC(pyErrRestore, PyErr_Restore*, "PyErr_Restore", 230);
DEFINE_PROC(getThreadTls, PyThread_get_key_value*, "PyThread_get_key_value", 270);
DEFINE_PROC(setThreadTls, PyThread_set_key_value*, "PyThread_set_key_value", 280);
DEFINE_PROC(delThreadTls, PyThread_delete_key_value*, "PyThread_delete_key_value", 290);
int threadStateIndex = -1;
for (int i = 0; i < 100000; i++) {
void* value = getThreadTls(i);
if (value == curPyThread) {
threadStateIndex = i;
break;
}
}
if(threadStateIndex == -1){
printf("Unable to find threadStateIndex for the current thread. curPyThread: %p\n", curPyThread);
return 350;
}
bool found = false;
for (auto curThread = threadHead(head); curThread != nullptr; curThread = threadNext(curThread)) {
if (GetPythonThreadId(version, curThread) != threadId) {
continue;
}
found = true;
// switch to our new thread so we can call sys.settrace on it...
// all of the work here needs to be minimal - in particular we shouldn't
// ever evaluate user defined code as we could end up switching to this
// thread on the main thread and corrupting state.
delThreadTls(threadStateIndex);
setThreadTls(threadStateIndex, curThread);
auto prevThread = threadSwap(curThread);
// save and restore the error in case something funky happens...
auto errOccured = errOccurred();
PyObject* type = nullptr;
PyObject* value = nullptr;
PyObject* traceback = nullptr;
if (errOccured) {
pyErrFetch(&type, &value, &traceback);
retVal = 1;
}
if(showDebugInfo){
printf("setting trace for thread: %d\n", threadId);
}
DecRef(call(setTraceFunc->ToPython(), traceFunc->ToPython(), nullptr), isDebug);
if (errOccured) {
pyErrRestore(type, value, traceback);
}
delThreadTls(threadStateIndex);
setThreadTls(threadStateIndex, prevThread);
threadSwap(prevThread);
break;
}
}
if(threadStateIndex == -1){
printf("Unable to find threadStateIndex for the current thread. curPyThread: %p\n", curPyThread);
return 350;
}
bool found = false;
for (auto curThread = threadHead(head); curThread != nullptr; curThread = threadNext(curThread)) {
if (GetPythonThreadId(version, curThread) != threadId) {
continue;
}
found = true;
// switch to our new thread so we can call sys.settrace on it...
// all of the work here needs to be minimal - in particular we shouldn't
// ever evaluate user defined code as we could end up switching to this
// thread on the main thread and corrupting state.
delThreadTls(threadStateIndex);
setThreadTls(threadStateIndex, curThread);
auto prevThread = threadSwap(curThread);
// save and restore the error in case something funky happens...
auto errOccured = errOccurred();
PyObject* type = nullptr;
PyObject* value = nullptr;
PyObject* traceback = nullptr;
if (errOccured) {
pyErrFetch(&type, &value, &traceback);
retVal = 1;
if(!found) {
retVal = 500;
}
if(showDebugInfo){
printf("setting trace for thread: %d\n", threadId);
}
else
{
// See comments on py_settrace_37.hpp for why we need a different implementation in Python 3.7 onwards.
DEFINE_PROC(pyUnicode_InternFromString, PyUnicode_InternFromString*, "PyUnicode_InternFromString", 520);
DEFINE_PROC(pyObject_FastCallDict, _PyObject_FastCallDict*, "_PyObject_FastCallDict", 530);
DEFINE_PROC(pyTraceBack_Here, PyTraceBack_Here*, "PyTraceBack_Here", 540);
DEFINE_PROC(pyEval_SetTrace, PyEval_SetTrace*, "PyEval_SetTrace", 550);
bool found = false;
for (PyThreadState* curThread = threadHead(head); curThread != nullptr; curThread = threadNext(curThread)) {
if (GetPythonThreadId(version, curThread) != threadId) {
continue;
}
found = true;
if(showDebugInfo){
printf("setting trace for thread: %d\n", threadId);
}
if(!InternalIsTraceInitialized_37())
{
InternalInitializeSettrace_37 *internalInitializeSettrace_37 = new InternalInitializeSettrace_37();
IncRef(pyNone->ToPython());
internalInitializeSettrace_37->pyNone = pyNone->ToPython();
internalInitializeSettrace_37->pyUnicode_InternFromString = pyUnicode_InternFromString;
internalInitializeSettrace_37->pyObject_FastCallDict = pyObject_FastCallDict;
internalInitializeSettrace_37->isDebug = isDebug;
internalInitializeSettrace_37->pyTraceBack_Here = pyTraceBack_Here;
internalInitializeSettrace_37->pyEval_SetTrace = pyEval_SetTrace;
InternalTraceInit_37(internalInitializeSettrace_37);
}
InternalPySetTrace_37(curThread, traceFunc, isDebug);
break;
}
DecRef(call(setTraceFunc->ToPython(), traceFunc->ToPython(), nullptr), isDebug);
if (errOccured) {
pyErrRestore(type, value, traceback);
if(!found) {
retVal = 501;
}
delThreadTls(threadStateIndex);
setThreadTls(threadStateIndex, prevThread);
threadSwap(prevThread);
break;
}
if(!found) {
retVal = 500;
}
return retVal;
}

View file

@ -0,0 +1,176 @@
#ifndef _PY_SETTRACE_37_HPP_
#define _PY_SETTRACE_37_HPP_
#include "python.h"
#include "py_utils.hpp"
// On Python 3.7 onwards the thread state is not kept in PyThread_set_key_value (rather
// it uses PyThread_tss_set using PyThread_tss_set(&_PyRuntime.gilstate.autoTSSkey, (void *)tstate)
// and we don't have access to that key from here (thus, we can't use the previous approach which
// made CPython think that the current thread had the thread state where we wanted to set the tracing).
//
// So, the solution implemented here is not faking that change and reimplementing PyEval_SetTrace.
// The implementation is mostly the same from the one in CPython, but we have one shortcoming:
//
// When CPython sets the tracing for a thread it increments _Py_TracingPossible (actually
// _PyRuntime.ceval.tracing_possible). This implementation has one issue: it only works on
// deltas when the tracing is set (so, a settrace(func) will increase the _Py_TracingPossible global value and a
// settrace(None) will decrease it, but when a thread dies it's kept as is and is not decreased).
// -- as we don't currently have access to _PyRuntime we have to create a thread, set the tracing
// and let it die so that the count is increased (this is really hacky, but better than having
// to create a local copy of the whole _PyRuntime (defined in pystate.h with several inner structs)
// which would need to be kept up to date for each new CPython version just to increment that variable).
struct InternalInitializeSettrace_37 {
PyUnicode_InternFromString* pyUnicode_InternFromString;
PyObject* pyNone;
_PyObject_FastCallDict* pyObject_FastCallDict;
PyTraceBack_Here* pyTraceBack_Here;
PyEval_SetTrace* pyEval_SetTrace;
bool isDebug;
};
/**
* Helper information to access CPython internals.
*/
static InternalInitializeSettrace_37 *internalInitializeSettrace_37 = NULL;
/*
* Cached interned string objects used for calling the profile and
* trace functions. Initialized by InternalTraceInit_37().
*/
static PyObject *InternalWhatstrings_37[8] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
static int
InternalIsTraceInitialized_37()
{
return internalInitializeSettrace_37 != NULL;
}
static int
InternalTraceInit_37(InternalInitializeSettrace_37 *p_internalInitializeSettrace_37)
{
internalInitializeSettrace_37 = p_internalInitializeSettrace_37;
static const char * const whatnames[8] = {
"call", "exception", "line", "return",
"c_call", "c_exception", "c_return",
"opcode"
};
PyObject *name;
int i;
for (i = 0; i < 8; ++i) {
if (InternalWhatstrings_37[i] == NULL) {
name = internalInitializeSettrace_37->pyUnicode_InternFromString(whatnames[i]);
if (name == NULL)
return -1;
InternalWhatstrings_37[i] = name;
}
}
return 0;
}
static PyObject *
InternalCallTrampoline_37(PyObject* callback,
PyFrameObject *frame, int what, PyObject *arg)
{
PyObject *result;
PyObject *stack[3];
// Note: this is commented out from CPython (we shouldn't need it and it adds a reasonable overhead).
// if (PyFrame_FastToLocalsWithError(frame) < 0) {
// return NULL;
// }
//
stack[0] = (PyObject *)frame;
stack[1] = InternalWhatstrings_37[what];
stack[2] = (arg != NULL) ? arg : internalInitializeSettrace_37->pyNone;
// call the Python-level function
// result = _PyObject_FastCall(callback, stack, 3);
//
// Note that _PyObject_FastCall is actually a define:
// #define _PyObject_FastCall(func, args, nargs) _PyObject_FastCallDict((func), (args), (nargs), NULL)
result = internalInitializeSettrace_37->pyObject_FastCallDict(callback, stack, 3, NULL);
// Note: this is commented out from CPython (we shouldn't need it and it adds a reasonable overhead).
// PyFrame_LocalsToFast(frame, 1);
if (result == NULL) {
internalInitializeSettrace_37->pyTraceBack_Here(frame);
}
return result;
}
static int
InternalTraceTrampoline_37(PyObject *self, PyFrameObject *frame,
int what, PyObject *arg)
{
PyObject *callback;
PyObject *result;
if (what == PyTrace_CALL){
callback = self;
} else {
callback = frame->f_trace;
}
if (callback == NULL){
return 0;
}
result = InternalCallTrampoline_37(callback, frame, what, arg);
if (result == NULL) {
// Note: calling the original sys.settrace here.
internalInitializeSettrace_37->pyEval_SetTrace(NULL, NULL);
PyObject *temp_f_trace = frame->f_trace;
frame->f_trace = NULL;
if(temp_f_trace != NULL){
DecRef(temp_f_trace, internalInitializeSettrace_37->isDebug);
}
return -1;
}
if (result != internalInitializeSettrace_37->pyNone) {
PyObject *tmp = frame->f_trace;
frame->f_trace = result;
DecRef(tmp, internalInitializeSettrace_37->isDebug);
}
else {
DecRef(result, internalInitializeSettrace_37->isDebug);
}
return 0;
}
void InternalPySetTrace_37(PyThreadState* curThread, PyObjectHolder* traceFunc, bool isDebug)
{
PyThreadState_37* tstate = reinterpret_cast<PyThreadState_37*>(curThread);
PyObject *temp = tstate->c_traceobj;
// We can't increase _Py_TracingPossible. Everything else should be equal to CPython.
// runtime->ceval.tracing_possible += (func != NULL) - (tstate->c_tracefunc != NULL);
PyObject *arg = traceFunc->ToPython();
IncRef(arg);
tstate->c_tracefunc = NULL;
tstate->c_traceobj = NULL;
/* Must make sure that profiling is not ignored if 'temp' is freed */
tstate->use_tracing = tstate->c_profilefunc != NULL;
if(temp != NULL){
DecRef(temp, isDebug);
}
tstate->c_tracefunc = InternalTraceTrampoline_37;
tstate->c_traceobj = arg;
/* Flag that tracing or profiling is turned on */
tstate->use_tracing = ((InternalTraceTrampoline_37 != NULL)
|| (tstate->c_profilefunc != NULL));
}
#endif //_PY_SETTRACE_37_HPP_

View file

@ -25,6 +25,13 @@ typedef int (PyThread_set_key_value)(int, void*);
typedef void (PyThread_delete_key_value)(int);
typedef int (PyObject_Not) (PyObject *o);
typedef PyObject* (PyDict_New)();
typedef PyObject* (PyUnicode_InternFromString)(const char *u);
typedef PyObject * (_PyObject_FastCallDict)(
PyObject *callable, PyObject *const *args, Py_ssize_t nargs, PyObject *kwargs);
typedef int (PyTraceBack_Here)(PyFrameObject *frame);
typedef void (PyEval_SetTrace)(Py_tracefunc, PyObject *);
typedef int (*Py_tracefunc)(PyObject *, PyFrameObject *frame, int, PyObject *);
// holder to ensure we release the GIL even in error conditions
class GilHolder {

View file

@ -40,13 +40,6 @@ int hello()
}
// This is the function which enables us to set the sys.settrace for all the threads
// which are already running.
// isDebug is pretty important! Must be true on python debug builds (python_d)
// If this value is passed wrongly the program will crash.
extern "C" int AttachDebuggerTracing(bool showDebugInfo, void* pSetTraceFunc, void* pTraceFunc, unsigned int threadId);
extern "C" int DoAttach(bool isDebug, const char *command, bool showDebugInfo);
// Internal function to keep on the tracing
int _PYDEVD_ExecWithGILSetSysStrace(bool showDebugInfo, bool isDebug);
@ -56,6 +49,9 @@ typedef int (*PyEval_ThreadsInitialized)();
typedef unsigned long (*_PyEval_GetSwitchInterval)(void);
typedef void (*_PyEval_SetSwitchInterval)(unsigned long microseconds);
// isDebug is pretty important! Must be true on python debug builds (python_d)
// If this value is passed wrongly the program will crash.
extern "C" int DoAttach(bool isDebug, const char *command, bool showDebugInfo);
int DoAttach(bool isDebug, const char *command, bool showDebugInfo)
{
@ -99,12 +95,17 @@ int DoAttach(bool isDebug, const char *command, bool showDebugInfo)
}
int AttachDebuggerTracing(bool showDebugInfo, void* pSetTraceFunc, void* pTraceFunc, unsigned int threadId)
// This is the function which enables us to set the sys.settrace for all the threads
// which are already running.
extern "C" int AttachDebuggerTracing(bool showDebugInfo, void* pSetTraceFunc, void* pTraceFunc, unsigned int threadId, void* pPyNone);
int AttachDebuggerTracing(bool showDebugInfo, void* pSetTraceFunc, void* pTraceFunc, unsigned int threadId, void* pPyNone)
{
void *module = dlopen(nullptr, 0x2);
bool isDebug = false;
PyObjectHolder traceFunc(isDebug, (PyObject*) pTraceFunc, true);
PyObjectHolder setTraceFunc(isDebug, (PyObject*) pSetTraceFunc, true);
return InternalSetSysTraceFunc(module, isDebug, showDebugInfo, &traceFunc, &setTraceFunc, threadId);
PyObjectHolder pyNone(isDebug, reinterpret_cast<PyObject*>(pPyNone), true);
return InternalSetSysTraceFunc(module, isDebug, showDebugInfo, &traceFunc, &setTraceFunc, threadId, &pyNone);
}

View file

@ -463,11 +463,15 @@ extern "C"
SuspendThreads(suspendedThreads, addPendingCall, threadsInited);
if(!threadsInited()){ // Check again with threads suspended.
std::cout << "ENTERED if (!threadsInited()) {" << std::endl << std::flush;
if (showDebugInfo) {
std::cout << "ENTERED if (!threadsInited()) {" << std::endl << std::flush;
}
auto curPyThread = getPythonThread ? getPythonThread() : *curPythonThread;
if (curPyThread == nullptr) {
std::cout << "ENTERED if (curPyThread == nullptr) {" << std::endl << std::flush;
if (showDebugInfo) {
std::cout << "ENTERED if (curPyThread == nullptr) {" << std::endl << std::flush;
}
// no threads are currently running, it is safe to initialize multi threading.
PyGILState_STATE gilState;
if (version >= PythonVersion_34) {
@ -497,7 +501,9 @@ extern "C"
gilState = PyGILState_LOCKED; // prevent compiler warning
}
std::cout << "Called initThreads()" << std::endl << std::flush;
if (showDebugInfo) {
std::cout << "Called initThreads()" << std::endl << std::flush;
}
// Initialize threads in our secondary thread (this is NOT ideal because
// this thread will be the thread head), but is still better than not being
// able to attach if the main thread is not actually running any code.
@ -670,7 +676,7 @@ extern "C"
/**
* This function may be called to set a tracing function to existing python threads.
**/
DECLDIR int AttachDebuggerTracing(bool showDebugInfo, void* pSetTraceFunc, void* pTraceFunc, unsigned int threadId)
DECLDIR int AttachDebuggerTracing(bool showDebugInfo, void* pSetTraceFunc, void* pTraceFunc, unsigned int threadId, void* pPyNone)
{
ModuleInfo moduleInfo = GetPythonModule();
if (moduleInfo.errorGettingModule != 0) {
@ -683,7 +689,9 @@ extern "C"
int attached = 0;
PyObjectHolder traceFunc(moduleInfo.isDebug, reinterpret_cast<PyObject*>(pTraceFunc), true);
PyObjectHolder setTraceFunc(moduleInfo.isDebug, reinterpret_cast<PyObject*>(pSetTraceFunc), true);
int temp = InternalSetSysTraceFunc(module, moduleInfo.isDebug, showDebugInfo, &traceFunc, &setTraceFunc, threadId);
PyObjectHolder pyNone(moduleInfo.isDebug, reinterpret_cast<PyObject*>(pPyNone), true);
int temp = InternalSetSysTraceFunc(module, moduleInfo.isDebug, showDebugInfo, &traceFunc, &setTraceFunc, threadId, &pyNone);
if (temp == 0) {
// we've successfully attached the debugger
return 0;

View file

@ -49,7 +49,8 @@ extern "C"
bool showDebugInfo,
void* pSetTraceFunc, // Actually PyObject*, but we don't want to include it here.
void* pTraceFunc, // Actually PyObject*, but we don't want to include it here.
unsigned int threadId
unsigned int threadId,
void* pPyNone // Actually PyObject*, but we don't want to include it here.
);
}

View file

@ -1,8 +1,8 @@
from _pydevd_bundle.pydevd_constants import get_frame, IS_CPYTHON, IS_64BIT_PROCESS, IS_WINDOWS, \
IS_LINUX, IS_MAC, IS_PY2, DebugInfoHolder
IS_LINUX, IS_MAC, IS_PY2, IS_PY37_OR_GREATER, DebugInfoHolder
from _pydev_imps._pydev_saved_modules import thread, threading
from _pydev_bundle import pydev_log
from _pydev_bundle import pydev_log, pydev_monkey
from os.path import os
try:
import ctypes
@ -146,7 +146,10 @@ def load_python_helper_lib():
lib = ctypes.pydll.LoadLibrary(filename)
return lib
except:
pydev_log.exception('Error loading: %s', filename)
if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 1:
# Only show message if tracing is on (we don't have pre-compiled
# binaries for all architectures -- i.e.: ARM).
pydev_log.exception('Error loading: %s', filename)
return None
@ -163,9 +166,12 @@ def set_trace_to_threads(tracing_func):
prev_value = get_interval()
ret = 0
try:
# Prevent going to any other thread... if we switch the thread during this operation we
# could potentially corrupt the interpreter.
set_interval(2 ** 15)
if not IS_PY37_OR_GREATER:
# Prevent going to any other thread... if we switch the thread during this operation we
# could potentially corrupt the interpreter.
# Note: on CPython 3.7 onwards this is not needed (we have a different implementation
# for setting the tracing for other threads in this case).
set_interval(2 ** 15)
set_trace_func = TracingFunctionHolder._original_tracing or sys.settrace
@ -216,12 +222,37 @@ def set_trace_to_threads(tracing_func):
# show_debug_info = 1 if DebugInfoHolder.DEBUG_TRACE_LEVEL >= 1 else 0
show_debug_info = 0
result = lib.AttachDebuggerTracing(ctypes.c_int(show_debug_info), ctypes.py_object(set_trace_func), ctypes.py_object(tracing_func), ctypes.c_uint(thread_ident))
if IS_PY37_OR_GREATER:
# Hack to increase _Py_TracingPossible.
# See comments on py_settrace_37.hpp
proceed = thread.allocate_lock()
proceed.acquire()
def dummy_trace_on_py37(frame, event, arg):
return dummy_trace_on_py37
def increase_tracing_count_on_py37():
SetTrace(dummy_trace_on_py37)
proceed.release()
start_new_thread = pydev_monkey.get_original_start_new_thread(thread)
start_new_thread(increase_tracing_count_on_py37, ())
proceed.acquire() # Only proceed after the release() is done.
proceed = None
result = lib.AttachDebuggerTracing(
ctypes.c_int(show_debug_info),
ctypes.py_object(set_trace_func),
ctypes.py_object(tracing_func),
ctypes.c_uint(thread_ident),
ctypes.py_object(None),
)
if result != 0:
pydev_log.info('Unable to set tracing for existing threads. Result: %s', result)
ret = result
finally:
set_interval(prev_value)
if not IS_PY37_OR_GREATER:
set_interval(prev_value)
return ret

View file

@ -728,7 +728,7 @@ def test_case_skipping_filters(case_setup, custom_setup):
other_filename = os.path.join(not_my_code_dir, 'other.py')
response = json_facade.write_set_breakpoints(1, filename=other_filename, verified=False)
assert response.body.breakpoints == [
{'verified': False, 'message': 'Breakpoint in file excluded by filters.', 'source': {}, 'line': 1}]
{'verified': False, 'message': 'Breakpoint in file excluded by filters.', 'source': {'path': other_filename}, 'line': 1}]
# Note: there's actually a use-case where we'd hit that breakpoint even if it was excluded
# by filters, so, we must actually clear it afterwards (the use-case is that when we're
# stepping into the context with the breakpoint we wouldn't skip it).
@ -737,7 +737,7 @@ def test_case_skipping_filters(case_setup, custom_setup):
other_filename = os.path.join(not_my_code_dir, 'file_that_does_not_exist.py')
response = json_facade.write_set_breakpoints(1, filename=other_filename, verified=False)
assert response.body.breakpoints == [
{'verified': False, 'message': 'Breakpoint in file that does not exist.', 'source': {}, 'line': 1}]
{'verified': False, 'message': 'Breakpoint in file that does not exist.', 'source': {'path': other_filename}, 'line': 1}]
elif custom_setup == 'set_exclude_launch_module_full':
json_facade.write_launch(
@ -767,7 +767,7 @@ def test_case_skipping_filters(case_setup, custom_setup):
assert response.body.breakpoints == [{
'verified': False,
'message': 'Breakpoint in file excluded by filters.\nNote: may be excluded because of "justMyCode" option (default == true).',
'source': {},
'source': {'path': other_filename},
'line': 14
}]
elif custom_setup == 'set_just_my_code_and_include':