forked from goodboy/tractor
				
			Add `breakpoint()` hook restoration example + test
							parent
							
								
									cf3e6c1218
								
							
						
					
					
						commit
						b3ee20d3b9
					
				| 
						 | 
				
			
			@ -6,19 +6,46 @@ import tractor
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
async def main() -> None:
 | 
			
		||||
    async with tractor.open_nursery(debug_mode=True) as an:
 | 
			
		||||
 | 
			
		||||
        assert os.environ['PYTHONBREAKPOINT'] == 'tractor._debug._set_trace'
 | 
			
		||||
    # intially unset, no entry.
 | 
			
		||||
    orig_pybp_var: int = os.environ.get('PYTHONBREAKPOINT')
 | 
			
		||||
    assert orig_pybp_var in {None, "0"}
 | 
			
		||||
 | 
			
		||||
    async with tractor.open_nursery(
 | 
			
		||||
        debug_mode=True,
 | 
			
		||||
    ) as an:
 | 
			
		||||
        assert an
 | 
			
		||||
        assert (
 | 
			
		||||
            (pybp_var := os.environ['PYTHONBREAKPOINT'])
 | 
			
		||||
            ==
 | 
			
		||||
            'tractor.devx._debug._sync_pause_from_builtin'
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # TODO: an assert that verifies the hook has indeed been, hooked
 | 
			
		||||
        # XD
 | 
			
		||||
        assert sys.breakpointhook is not tractor._debug._set_trace
 | 
			
		||||
        assert (
 | 
			
		||||
            (pybp_hook := sys.breakpointhook)
 | 
			
		||||
            is not tractor.devx._debug._set_trace
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        print(
 | 
			
		||||
            f'$PYTHONOBREAKPOINT: {pybp_var!r}\n'
 | 
			
		||||
            f'`sys.breakpointhook`: {pybp_hook!r}\n'
 | 
			
		||||
        )
 | 
			
		||||
        breakpoint()
 | 
			
		||||
        pass  # first bp, tractor hook set.
 | 
			
		||||
 | 
			
		||||
    # TODO: an assert that verifies the hook is unhooked..
 | 
			
		||||
    # XXX AFTER EXIT (of actor-runtime) verify the hook is unset..
 | 
			
		||||
    #
 | 
			
		||||
    # YES, this is weird but it's how stdlib docs say to do it..
 | 
			
		||||
    # https://docs.python.org/3/library/sys.html#sys.breakpointhook
 | 
			
		||||
    assert os.environ.get('PYTHONBREAKPOINT') is orig_pybp_var
 | 
			
		||||
    assert sys.breakpointhook
 | 
			
		||||
 | 
			
		||||
    # now ensure a regular builtin pause still works
 | 
			
		||||
    breakpoint()
 | 
			
		||||
    pass  # last bp, stdlib hook restored
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    trio.run(main)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1231,6 +1231,53 @@ def test_shield_pause(
 | 
			
		|||
    child.expect(EOF)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_breakpoint_hook_restored(
 | 
			
		||||
    spawn,
 | 
			
		||||
):
 | 
			
		||||
    '''
 | 
			
		||||
    Ensures our actor runtime sets a custom `breakpoint()` hook
 | 
			
		||||
    on open then restores the stdlib's default on close.
 | 
			
		||||
 | 
			
		||||
    The hook state validation is done via `assert`s inside the
 | 
			
		||||
    invoked script with only `breakpoint()` (not `tractor.pause()`)
 | 
			
		||||
    calls used.
 | 
			
		||||
 | 
			
		||||
    '''
 | 
			
		||||
    child = spawn('restore_builtin_breakpoint')
 | 
			
		||||
 | 
			
		||||
    child.expect(PROMPT)
 | 
			
		||||
    assert_before(
 | 
			
		||||
        child,
 | 
			
		||||
        [
 | 
			
		||||
            _pause_msg,
 | 
			
		||||
            "<Task '__main__.main'",
 | 
			
		||||
            "('root'",
 | 
			
		||||
            "first bp, tractor hook set",
 | 
			
		||||
        ]
 | 
			
		||||
    )
 | 
			
		||||
    child.sendline('c')
 | 
			
		||||
    child.expect(PROMPT)
 | 
			
		||||
    assert_before(
 | 
			
		||||
        child,
 | 
			
		||||
        [
 | 
			
		||||
            "last bp, stdlib hook restored",
 | 
			
		||||
        ]
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    # since the stdlib hook was already restored there should be NO
 | 
			
		||||
    # `tractor` `log.pdb()` content from console!
 | 
			
		||||
    assert not in_prompt_msg(
 | 
			
		||||
        child,
 | 
			
		||||
        [
 | 
			
		||||
            _pause_msg,
 | 
			
		||||
            "<Task '__main__.main'",
 | 
			
		||||
            "('root'",
 | 
			
		||||
        ],
 | 
			
		||||
    )
 | 
			
		||||
    child.sendline('c')
 | 
			
		||||
    child.expect(EOF)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# TODO: better error for "non-ideal" usage from the root actor.
 | 
			
		||||
# -[ ] if called from an async scope emit a message that suggests
 | 
			
		||||
#    using `await tractor.pause()` instead since it's less overhead
 | 
			
		||||
| 
						 | 
				
			
			@ -1248,6 +1295,7 @@ def test_sync_pause_from_bg_task_in_root_actor_():
 | 
			
		|||
    '''
 | 
			
		||||
    ...
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# TODO: needs ANSI code stripping tho, see `assert_before()` # above!
 | 
			
		||||
def test_correct_frames_below_hidden():
 | 
			
		||||
    '''
 | 
			
		||||
| 
						 | 
				
			
			@ -1264,8 +1312,9 @@ def test_cant_pause_from_paused_task():
 | 
			
		|||
    '''
 | 
			
		||||
    Pausing from with an already paused task should raise an error.
 | 
			
		||||
 | 
			
		||||
    Normally this should only happen in practise while debugging the call stack of `tractor.pause()` itself, likely
 | 
			
		||||
    by a `.pause()` line somewhere inside our runtime.
 | 
			
		||||
    Normally this should only happen in practise while debugging the
 | 
			
		||||
    call stack of `tractor.pause()` itself, likely by a `.pause()`
 | 
			
		||||
    line somewhere inside our runtime.
 | 
			
		||||
 | 
			
		||||
    '''
 | 
			
		||||
    ...
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue