Bowler
Bowler copied to clipboard
Support pdb in test cases
Currently it doesn't appear possible to set a breakpoint with import pdb; pdb.set_trace()
in a BowlerTestCase
. It looks like in_process
is set to true when calling the modifier, but I still get a traceback when I attempt to use a debugger in the modifier.
I think it could be helpful to support using pdb in test cases.
from bowler.tests.lib import BowlerTestCase
from bowler.types import Leaf, TOKEN
class ExampleTestCase(BowlerTestCase):
def test_modifier_return_value(self):
input = "a+b"
def modifier(node, capture, filename):
new_op = Leaf(TOKEN.MINUS, "-")
import pdb; pdb.set_trace() # works without this statement
return new_op
output = self.run_bowler_modifier(input, "'+'", modifier)
self.assertEqual("a-b", output)
self = <refactoring.test.ExampleTestCase testMethod=test_modifier_return_value>
def test_modifier_return_value(self):
input = "a+b"
def modifier(node, capture, filename):
new_op = Leaf(TOKEN.MINUS, "-")
import pdb; pdb.set_trace()
return new_op
> output = self.run_bowler_modifier(input, "'+'", modifier)
test.py:17:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <refactoring.test.ExampleTestCase testMethod=test_modifier_return_value>, input_text = 'a+b', selector = "'+'"
modifier = <function ExampleTestCase.test_modifier_return_value.<locals>.modifier at 0x11fc7c598>, selector_func = None, modifier_func = None, in_process = True
query_func = <function BowlerTestCase.run_bowler_modifier.<locals>.default_query_func at 0x11fcaaf28>
def run_bowler_modifier(
self,
input_text,
selector=None,
modifier=None,
selector_func=None,
modifier_func=None,
in_process=True,
query_func=None,
):
"""Returns the modified text."""
if not (selector or selector_func or query_func):
raise ValueError("Pass selector")
if not (modifier or modifier_func or query_func):
raise ValueError("Pass modifier")
exception_queue = multiprocessing.Queue()
def store_exceptions_on(func):
@functools.wraps(func)
def inner(node, capture, filename):
# When in_process=False, this runs in another process. See notes below.
try:
return func(node, capture, filename)
except Exception as e:
exception_queue.put(e)
return inner
def default_query_func(files):
if selector_func:
q = selector_func(files)
else:
q = Query(files).select(selector)
if modifier_func:
q = modifier_func(q)
else:
q = q.modify(modifier)
return q
if query_func is None:
query_func = default_query_func
with tempfile.NamedTemporaryFile(suffix=".py") as f:
# TODO: I'm almost certain this will not work on Windows, since
# NamedTemporaryFile has it already open for writing. Consider
# using mktemp directly?
with open(f.name, "w") as fw:
fw.write(input_text + "\n")
query = query_func([f.name])
assert query is not None, "Remember to return the Query"
assert query.retcode is None, "Return before calling .execute"
assert len(query.transforms) == 1, "TODO: Support multiple"
for i in range(len(query.current.callbacks)):
query.current.callbacks[i] = store_exceptions_on(
query.current.callbacks[i]
)
# We require the in_process parameter in order to record coverage properly,
# but it also helps in bubbling exceptions and letting tests read state set
# by modifiers.
query.execute(
interactive=False, write=True, silent=False, in_process=in_process
)
# In the case of in_process=False (mirroring normal use of the tool) we use
# the queue to ship back exceptions from local_process, which can actually
# fail the test. Normally exceptions in modifiers are not printed
# at all unless you pass --debug, and even then you don't get the
# traceback.
# See https://github.com/facebookincubator/Bowler/issues/63
if not exception_queue.empty():
> raise AssertionError from exception_queue.get()
E AssertionError
I suspect this is related to the stdout replacement at https://github.com/facebookincubator/Bowler/blob/master/bowler/tests/lib.py#L31 -- can you try restoring the write
method? (Additionally, there may be further buffering in unittest itself, try something like sys.stdout=os.open(1, "w")
)
If you give me a couple of days I can try to reproduce this directly.