Skip to content
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

Support pdb in test cases #96

Open
chdsbd opened this issue Jul 12, 2019 · 1 comment
Open

Support pdb in test cases #96

chdsbd opened this issue Jul 12, 2019 · 1 comment

Comments

@chdsbd
Copy link

chdsbd commented Jul 12, 2019

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

@thatch
Copy link
Contributor

thatch commented Jul 15, 2019

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants