class TestBytecodeTracer:
    def setup(self):
        self._traces = []
        self.btracer = BytecodeTracer()

    def _trace(self, frame, event, arg):
        try:
            if arg is not sys.settrace:
                for ret in self.btracer.trace(frame, event):
                    if ret[0] is not None:
                        self._traces.append(ret)
        except TypeError:
            pass
        return self._trace

    def assert_trace(self, *traces):
        assert_equal(self._traces, list(traces))

    def assert_trace_slice(self, start, end, *traces):
        assert_equal(self._traces[start:end], list(traces))

    def trace_function(self, fun):
        dis.dis(fun.func_code)
        rewrite_function(fun)
        self.btracer.setup()
        sys.settrace(self._trace)
        try:
            fun()
        finally:
            sys.settrace(None)
            self.btracer.teardown()
Esempio n. 2
0
class TestBytecodeTracer:
    def setup(self):
        self._traces = []
        self._ignored_events = ['load_global', 'store_global']
        self.btracer = BytecodeTracer()

    def _trace(self, frame, event, arg):
        try:
            if arg is not sys.settrace:
                for ret in self.btracer.trace(frame, event):
                    if ret[0] is not None and ret[
                            0] not in self._ignored_events:
                        self._traces.append(ret)
        except TypeError:
            pass
        return self._trace

    def assert_trace(self, *traces):
        assert_equal(self._traces, list(traces))

    def assert_trace_slice(self, start, end, *traces):
        assert_equal(self._traces[start:end], list(traces))

    def trace_function(self, fun):
        dis.dis(fun.func_code)
        rewrite_function(fun)
        self.btracer.setup()
        sys.settrace(self._trace)
        try:
            fun()
        finally:
            sys.settrace(None)
            self.btracer.teardown()
Esempio n. 3
0
class StandardTracer(object):
    """Wrapper around basic C{sys.settrace} mechanism that maps 'call', 'return'
    and 'exception' events into more meaningful callbacks.

    See L{ICallback} for details on events that tracer reports.
    """
    def __init__(self, callback):
        self.callback = callback

        self.btracer = BytecodeTracer()

        self.top_level_function = None
        self.sys_modules = None

    # :: function | str -> None
    def trace(self, code):
        """Trace execution of given code. Code may be either a function
        or a string with Python code.

        This method may be invoked many times for a single tracer instance.
        """
        self.setup(code)
        self.btracer.setup()
        rewrite_function(self.top_level_function)
        sys.settrace(self.tracer)
        try:
            self.top_level_function()
        finally:
            sys.settrace(None)
            self.teardown()
            self.btracer.teardown()

    def setup(self, code):
        self.top_level_function = make_callable(code)
        self.sys_modules = sys.modules.keys()

    def teardown(self):
        # Revert any changes to sys.modules.
        # This unfortunatelly doesn't include changes to the modules' state itself.
        # Replaced module instances in sys.modules are also not reverted.
        modnames = [m for m in sys.modules.keys() if m not in self.sys_modules]
        for modname in modnames:
            del sys.modules[modname]

        self.top_level_function = None
        self.sys_modules = None

    def tracer(self, frame, event, arg):
        # Bytecode tracing is unreliable without the rewrite step, so we have
        # to ignore all interactions inside that code. That usually concerns
        # modules that were imported before the tracer started.
        if not has_been_rewritten(frame.f_code):
            return
        # We don't want to trace our own internals.
        if is_called_from_code_rewriting_importer(frame):
            return
        bytecode_events = list(self.btracer.trace(frame, event))
        if bytecode_events:
            for ev, args in bytecode_events:
                # Exceptions originating in C code are reported only after
                # execution goes back to the Python level. To regain
                # consistency with other exception events, we simulate
                # an exception raised inside C code just before its return.
                if ev == 'c_return' and event == 'exception':
                    self.handle_standard_tracer_event(frame, event, arg)
                self.handle_bytecode_tracer_event(ev, args)
        return self.handle_standard_tracer_event(frame, event, arg)

    def handle_bytecode_tracer_event(self, event, args):
        if event == 'c_call':
            self.record_c_call(*args)
        elif event == 'c_return':
            self.callback.c_returned(args)
        elif event == 'print':
            pass  # TODO
        elif event == 'print_to':
            value, output = args
            pass  # TODO
        elif event == 'store_attr':
            obj, name, value = args
            self.callback.attribute_rebound(obj, name, value)
        elif event == 'delete_attr':
            obj, name = args
            pass  # TODO
        elif event == 'load_global':
            module, name, value = args
            if name not in builtins_names:
                self.callback.global_read(module, name, value)
        elif event == 'store_global':
            module, name, value = args
            self.callback.global_rebound(module, name, value)
        elif event == 'delete_global':
            pass  # TODO

    def handle_standard_tracer_event(self, frame, event, arg):
        if event == 'call':
            if not self.should_ignore_frame(frame):
                if self.record_call(frame):
                    return self.tracer
        elif event == 'return':
            self.callback.returned(arg)
        elif event == 'exception':
            if not is_generator_exit(arg[0]):
                # There are three cases here, each requiring different handling
                # of values in arg[0] and arg[1]. First, we may get a regular
                # exception generated by the `raise` statement. Second, we may
                # get an exception generated inside the interpreter, like an
                # IndexError or NameError. Finally, code in Python < 2.6 can
                # raise a string exception.
                #
                # In each case, arg[0] and arg[1] have different values,
                # described in the table below.
                #
                #               +------------------+---------------------------+
                #               |      arg[0]      |          arg[1]           |
                # +-------------+------------------+---------------------------+
                # | regular     | exception type   | exception instance        |
                # |  exceptions |  (e.g. TypeError)|                           |
                # +-------------+------------------+---------------------------+
                # | interpreter | exception type   | message (a string) or     |
                # |  exceptions |  (e.g. NameError)|  exception initialization |
                # |             |                  |  arguments (a tuple)      |
                # +-------------+------------------+---------------------------+
                # | string      | string itself    | value or None             |
                # |  exceptions |  (e.g. "Error")  |                           |
                # +-------------+------------------+---------------------------+
                #
                # arg[2] in all cases contains an exception traceback.
                if isinstance(arg[0], str):
                    # Return the string itself as an exception and ignore
                    # the value, as it's not used during test generation,
                    # at least for now.
                    exception = arg[0]
                elif isinstance(arg[1], str):
                    # Recreate instance of a single-argument interpreter
                    # exception.
                    exception = arg[0](arg[1])
                elif isinstance(arg[1], tuple):
                    # Recreate instance of a multi-argument interpreter
                    # exception.
                    exception = arg[0](*arg[1])
                else:
                    exception = arg[1]
                self.callback.raised(exception, arg[2])

    def should_ignore_frame(self, frame):
        return is_class_definition(frame) or self.is_ignored_code(frame.f_code)

    def is_ignored_code(self, code):
        if code.co_name in IGNORED_NAMES:
            return True
        if self.top_level_function is not None \
                and code is self.top_level_function.func_code:
            return True
        return False

    def record_call(self, frame):
        code = frame.f_code
        name = code.co_name

        try:
            obj, input = get_method_information(frame)
            return self.callback.method_called(name, obj, input, code, frame)
        except NotMethodFrame:
            input = input_from_argvalues(*inspect.getargvalues(frame))
            return self.callback.function_called(name, input, code, frame)

    def record_c_call(self, func, pargs, kargs):
        func_name = func.__name__
        obj = get_self_from_method(func)
        if obj is not None:
            klass = type(obj)
            self.callback.c_method_called(obj, klass, func_name, pargs)
        else:
            self.callback.c_function_called(func_name, pargs)
Esempio n. 4
0
class StandardTracer(object):
    """Wrapper around basic C{sys.settrace} mechanism that maps 'call', 'return'
    and 'exception' events into more meaningful callbacks.

    See L{ICallback} for details on events that tracer reports.
    """
    def __init__(self, callback):
        self.callback = callback

        self.btracer = BytecodeTracer()

        self.top_level_function = None
        self.sys_modules = None

    # :: function | str -> None
    def trace(self, code):
        """Trace execution of given code. Code may be either a function
        or a string with Python code.

        This method may be invoked many times for a single tracer instance.
        """
        self.setup(code)
        self.btracer.setup()
        rewrite_function(self.top_level_function)
        sys.settrace(self.tracer)
        try:
            self.top_level_function()
        finally:
            sys.settrace(None)
            self.teardown()
            self.btracer.teardown()

    def setup(self, code):
        self.top_level_function = make_callable(code)
        self.sys_modules = sys.modules.keys()

    def teardown(self):
        # Revert any changes to sys.modules.
        # This unfortunatelly doesn't include changes to the modules' state itself.
        # Replaced module instances in sys.modules are also not reverted.
        modnames = [m for m in sys.modules.keys() if m not in self.sys_modules]
        for modname in modnames:
            del sys.modules[modname]

        self.top_level_function = None
        self.sys_modules = None

    def tracer(self, frame, event, arg):
        # Bytecode tracing is unreliable without the rewrite step, so we have
        # to ignore all interactions inside that code. That usually concerns
        # modules that were imported before the tracer started.
        if not has_been_rewritten(frame.f_code):
            return
        # We don't want to trace our own internals.
        if is_called_from_code_rewriting_importer(frame):
            return
        bytecode_events = list(self.btracer.trace(frame, event))
        if bytecode_events:
            for ev, args in bytecode_events:
                # Exceptions originating in C code are reported only after
                # execution goes back to the Python level. To regain
                # consistency with other exception events, we simulate
                # an exception raised inside C code just before its return.
                if ev == 'c_return' and event == 'exception':
                    self.handle_standard_tracer_event(frame, event, arg)
                self.handle_bytecode_tracer_event(ev, args)
        return self.handle_standard_tracer_event(frame, event, arg)

    def handle_bytecode_tracer_event(self, event, args):
        if event == 'c_call':
            self.record_c_call(*args)
        elif event == 'c_return':
            self.callback.c_returned(args)
        elif event == 'print':
            pass # TODO
        elif event == 'print_to':
            #value, output = args
            pass # TODO
        elif event == 'store_attr':
            obj, name, value = args
            self.callback.attribute_rebound(obj, name, value)
        elif event == 'delete_attr':
            obj, name = args
            pass # TODO
        elif event == 'load_global':
            module, name, value = args
            if name not in builtins_names:
                self.callback.global_read(module, name, value)
        elif event == 'store_global':
            module, name, value = args
            self.callback.global_rebound(module, name, value)
        elif event == 'delete_global':
            pass # TODO

    def handle_standard_tracer_event(self, frame, event, arg):
        if event == 'call':
            if not self.should_ignore_frame(frame):
                if self.record_call(frame):
                    return self.tracer
        elif event == 'return':
            self.callback.returned(arg)
        elif event == 'exception':
            if not is_generator_exit(arg[0]):
                # There are three cases here, each requiring different handling
                # of values in arg[0] and arg[1]. First, we may get a regular
                # exception generated by the `raise` statement. Second, we may
                # get an exception generated inside the interpreter, like an
                # IndexError or NameError. Finally, code in Python < 2.6 can
                # raise a string exception.
                #
                # In each case, arg[0] and arg[1] have different values,
                # described in the table below.
                #
                #               +------------------+---------------------------+
                #               |      arg[0]      |          arg[1]           |
                # +-------------+------------------+---------------------------+
                # | regular     | exception type   | exception instance        |
                # |  exceptions |  (e.g. TypeError)|                           |
                # +-------------+------------------+---------------------------+
                # | interpreter | exception type   | message (a string) or     |
                # |  exceptions |  (e.g. NameError)|  exception initialization |
                # |             |                  |  arguments (a tuple)      |
                # +-------------+------------------+---------------------------+
                # | string      | string itself    | value or None             |
                # |  exceptions |  (e.g. "Error")  |                           |
                # +-------------+------------------+---------------------------+
                #
                # arg[2] in all cases contains an exception traceback.
                if isinstance(arg[0], str):
                    # Return the string itself as an exception and ignore
                    # the value, as it's not used during test generation,
                    # at least for now.
                    exception = arg[0]
                elif isinstance(arg[1], str):
                    # Recreate instance of a single-argument interpreter
                    # exception.
                    exception = arg[0](arg[1])
                elif isinstance(arg[1], tuple):
                    # Recreate instance of a multi-argument interpreter
                    # exception.
                    exception = arg[0](*arg[1])
                else:
                    exception = arg[1]
                self.callback.raised(exception, arg[2])

    def should_ignore_frame(self, frame):
        return is_class_definition(frame) or self.is_ignored_code(frame.f_code)

    def is_ignored_code(self, code):
        if code.co_name in IGNORED_NAMES:
            return True
        if self.top_level_function is not None \
                and code is self.top_level_function.func_code:
            return True
        return False

    def record_call(self, frame):
        code = frame.f_code
        name = code.co_name

        try:
            obj, input = get_method_information(frame)
            return self.callback.method_called(name, obj, input, code, frame)
        except NotMethodFrame:
            input = input_from_argvalues(*inspect.getargvalues(frame))
            return self.callback.function_called(name, input, code, frame)

    def record_c_call(self, func, pargs, kargs):
        func_name = func.__name__
        obj = get_self_from_method(func)
        print ("TODO: CHECK c_call:%s" % kargs)
        if obj is not None:
            klass = type(obj)
            self.callback.c_method_called(obj, klass, func_name, pargs)
        else:
            self.callback.c_function_called(func_name, pargs)