class BuiltinException(CompositeObject):
    """A built-in exception instance, which may hold any kind of objects in its
    `args` attribute.

    It doesn't matter if an exception was generated by the interpreter or by
    the user code, it will be serialized as BuiltinException nevertheless. This
    means we can't assume anything about the contents of `args`.

    User-defined exceptions, both deriving from BaseException and those not,
    are serialized as UserObjects.
    """
    def __init__(self, obj, serialize):
        CompositeObject.__init__(self, obj)

        self.args = map(serialize, obj.args)
        self.constructor_format = "%s(%%s)" % class_name(obj)
        self.imports = set()

        if class_of(obj) in BUILTIN_ENVIRONMENT_ERROR_TYPES and obj.filename is not None:
            self.args.append(serialize(obj.filename)) class ImmutableObject(SerializedObject):
    """A serialized object which identity doesn't matter.

    Immutable objects (like strings or integers) and objects with well-defined
    location (like non-anonymous functions) are all considered ImmutableObjects.

    ImmutableObjects are certain to have a reconstructor.

    :IVariables:
      reconstructor : str
        A string representing code that will construct the object.
      imports : set
        A set of import descriptions needed for the reconstructor code to work.
    """
    def __init__(self, obj):
        SerializedObject.__init__(self, obj)

        self.reconstructor, self.imports = ImmutableObject.get_reconstructor_with_imports(obj)

    def __eq__(self, other):
        return isinstance(other, ImmutableObject) and \
            self.reconstructor == other.reconstructor and \
            self.imports == other.imports

    def __hash__(self):
        return hash(self.reconstructor)

    def __repr__(self):
        if self.imports:
            return "ImmutableObject(%r, imports=%r)" % (self.reconstructor, self.imports)
        return "ImmutableObject(%r)" % self.reconstructor

    # :: object -> (string, set)
    def get_reconstructor_with_imports(obj):
        """
        >>> reconstructor, imports = ImmutableObject.get_reconstructor_with_imports(re.compile('abcd'))
        >>> reconstructor
        "re.compile('abcd')"
        >>> imports == set(['re'])
        True
        >>> reconstructor, imports = ImmutableObject.get_reconstructor_with_imports(re.compile('abcd', re.I | re.M))
        >>> reconstructor
        "re.compile('abcd', re.IGNORECASE | re.MULTILINE)"
        >>> imports == set(['re'])
        True
        """
        if isinstance(obj, (int, long, float, str, unicode, types.NoneType)):
            # Bultin types has very convienient representation.
            return repr(obj), set()
        elif isinstance(obj, RePatternType):
            flags = regexp_flags_as_string(obj.flags)
            if flags:
                return ('re.compile(%r, %s)' % (obj.pattern, flags), set(['re']))
            else:
                return ('re.compile(%r)' % obj.pattern, set(['re']))
        elif isinstance(obj, types.FunctionType):
            function = obj.func_name
            module = obj.__module__
            return (function, set([(module, function)]))
        else:
            raise TypeError("Unknown type of an ImmutableObject: %r." % obj)
    get_reconstructor_with_imports = staticmethod(get_reconstructor_with_imports) class SequenceObject(CompositeObject):
    """A builtin object that contains an ordered sequence of other objects
    inside it.

    Tuples and other immutable builtin types are still serialized into
    a SequenceObject, because they may contain a mutable element inside them.
    """
    type_formats_with_imports = {
        list: ("[%s]", set()),
        frozenset: ("frozenset([%s])", set()),
        set: ("set([%s])", set()),
        sets.ImmutableSet: ("ImmutableSet([%s])", set([("sets", "ImmutableSet")])),
        sets.Set: ("Set([%s])", set([("sets", "Set")])),
        tuple: ("(%s)", set()),
    }

    def __init__(self, obj, serialize):
        # Serialize the parts first and only after that call super, so that
        # the parts get a lower timestamp than the whole object.
        self.contained_objects = map(serialize, obj)

        CompositeObject.__init__(self, obj)

        # Arrays constructor needs to include a typecode.
        if isinstance(obj, array.array):
            self.constructor_format = "array.array('%s', [%%s])" % obj.typecode
            self.imports = set(["array"])
        # Special case for tuples with a single element.
        elif isinstance(obj, tuple) and len(obj) == 1:
            self.constructor_format = "(%s,)"
            self.imports = set()
        else:
            self.constructor_format = self.type_formats_with_imports[type(obj)][0]
            self.imports = self.type_formats_with_imports[type(obj)][1]

    def __repr__(self):
        return "SequenceObject(%s)" % (self.constructor_format % self.contained_objects) class MapObject(CompositeObject):
    """A mutable object that contains unordered mapping of key/value pairs.
    """
    def __init__(self, obj, serialize):
        # Serialize the parts first and only after that call super, so that
        # the parts get a lower timestamp than the whole object.
        self.mapping = [(serialize(k), serialize(v)) for k,v in obj.items()]

        CompositeObject.__init__(self, obj)

        self.constructor_format = "{%s}"
        self.imports = set() class LibraryObject(SerializedObject):
    type_formats_with_imports = {
        ('xml.dom.minidom', 'Element'):
            ("Element(%s)",
             ["tagName", "namespaceURI", "prefix"],
             set([("xml.dom.minidom", "Element")])),
        ('datetime', 'datetime'):
            ("datetime.datetime(%s)",
             ["year", "month", "day", "hour", "minute", "second", "microsecond", "tzinfo"],
             set(["datetime"])),
    }

    def __init__(self, obj, serialize):
        con, argnames, imp = self.type_formats_with_imports[id_of_class_of(obj)]

        self.constructor_format = con
        self.arguments = map(serialize, [getattr(obj, a, None) for a in argnames])
        self.imports = imp

        # Arguments were serialized first, before a call to super, so that they
        # get a lower timestamp than the whole object.
        SerializedObject.__init__(self, obj) class Class(ObjectInModule):
    def __init__(self, name, methods=[], bases=[], code=None, module=None):
        ObjectInModule.__init__(self, name, code)
        self.methods = []
        self.bases = bases
        self.module = module
        self.user_objects = []

        self.add_methods(methods)

    def _set_class_for_method(self, method):
        if method.klass is not None:
            raise TypeError("Trying to add %r to class %r, while the "
                            "method is already inside %r." % \
                                (method, self.name, method.klass.name))
        method.klass = self

    def add_methods(self, methods):
        for method in methods:
            self._set_class_for_method(method)
            self.methods.append(method)

    def add_user_object(self, user_object):
        self.user_objects.append(user_object)

    def get_traced_method_names(self):
        traced_method_names = set()
        for user_object in self.user_objects:
            for call in user_object.calls:
                traced_method_names.add(call.definition.name)
        return traced_method_names

    def get_untraced_methods(self):
        traced_method_names = self.get_traced_method_names()
        def is_untraced(method):
            return method.name not in traced_method_names
        return filter(is_untraced, self.methods)

    def find_method_by_name(self, name):
        for method in self.methods:
            if method.name == name:
                return method

    def get_creational_method(self):
        """Return either __new__ or __init__ method of this class, with __new__
        taking precedence.
        """
        method = self.find_method_by_name('__new__')
        if method:
            return method
        return self.find_method_by_name('__init__')

    def __repr__(self):
        return "Class(name=%s)" % self.name class Function(Definition, Callable):
    def __init__(self, name, args=None, code=None, calls=None, is_generator=False, module=None):
        Definition.__init__(self, name, args=args, code=code, is_generator=is_generator)
        Callable.__init__(self, calls)
        self.module = module
        if is_generator:
            self.calls_type = GeneratorObject
        else:
            self.calls_type = FunctionCall

    def get_unique_calls(self):
        return set(self.calls)

    def __repr__(self):
        return "Function(name=%s, args=%r, calls=%r)" % (self.name, self.args, self.calls) class FunctionCall(Call):
    pass class GeneratorObject(Callable, SerializedObject):
    """Representation of a generator object - a callable with an input and many
    outputs (here called "yields").
    """
    calls_type = GeneratorObjectInvocation

    def __init__(self, obj, generator=None, args=None, callable=None):
        Callable.__init__(self)
        SerializedObject.__init__(self, obj)
        if generator is not None and args is not None and callable is not None:
            self.activate(generator, args, callable)

    def activate(self, generator, args, callable):
        assert_argument_type(generator, (Function, Method))
        assert_argument_type(callable, (Function, UserObject))
        if self.is_activated():
            raise ValueError("This generator has already been activated.")
        if not generator.is_generator:
            raise TypeError("Tried to activate GeneratorObject with %r as a generator definition." % generator)
        self.definition = generator
        self.args = args
        # Finally register this GeneratorObject with its callable context
        # (which will be a Function or an UserObject). This has to be
        # done only once for each GeneratorObject.
        callable.add_call(self)

    def is_activated(self):
        return hasattr(self, 'args')

    def raised_exception(self):
        return any([c.raised_exception() for c in self.calls])

    def _get_exception(self):
        for invocation in self.calls:
            if invocation.raised_exception():
                return invocation.exception
    exception = property(_get_exception)

    def __repr__(self):
        if self.is_activated():
            return "GeneratorObject(generator=%r, args=%r)" % \
                (self.definition.name, self.args)
        else:
            return "GeneratorObject()" class GeneratorObjectInvocation(Call):
    """Representation of a single generator invocation.

    Each time a generator is resumed a new GeneratorObjectInvocation is created.
    """ class Method(Definition):
    def __init__(self, name, args=None, code=None, is_generator=False, klass=None):
        Definition.__init__(self, name, args=args, code=code, is_generator=is_generator)
        self.klass = klass

    def get_call_args(self):
        """Return names of arguments explicitly passed during call to this
        method.

        In other words, it removes "self" from the list of arguments, as "self"
        is passed implicitly.
        """
        if self.args and self.args[0].startswith('*'):
            return self.args
        return self.args[1:]

    def is_creational(self):
        return self.name in CREATIONAL_METHODS

    def is_private(self):
        """Private methods (by convention) start with a single or double
        underscore.

        Note: Special methods are *not* considered private.
        """
        return self.name.startswith('_') and not self.is_special()

    def is_special(self):
        """Special methods, as defined in
        <http://docs.python.org/reference/datamodel.html#specialnames>
        have names starting and ending with a double underscore.
        """
        return self.name.startswith('__') and self.name.endswith('__')

    def __repr__(self):
        return "Method(name=%s, args=%r)" % (self.name, self.args) class UserObject(Callable, SerializedObject):
    """Serialized instance of a user-defined type.

    UserObjects are also callables that aggregate MethodCall instances,
    capturing the whole life of an object, from initialization to destruction.
    """
    calls_type = (MethodCall, GeneratorObject)

    def __init__(self, obj, klass):
        Callable.__init__(self)
        SerializedObject.__init__(self, obj)
        self.klass = klass
        self.type_name = self.klass.name

    # Defined lazily to ease testing - classes may be assigned to modules after
    # creation of UserObject, or never at all.
    module_name = property(lambda self: self.klass.module.locator, lambda s,v: None)

    def get_init_call(self):
        """Return a call to __init__ or None if it wasn't called.
        """
        return findfirst(lambda call: call.definition.name == '__init__', self.calls)

    def get_external_calls(self):
        """Return all calls to this object made from the outside.

        Note: __init__ is considered an internal call.
        """
        def is_not_init_call(call):
            return call.definition.name != '__init__'
        return filter(is_not_init_call, filter(self.is_external_call, self.calls))

    def get_init_and_external_calls(self):
        return filter(self.is_external_call, self.calls)

    def __repr__(self):
        return "UserObject(id=%s, klass=%r)" % (id(self), self.klass.name)

    def is_external_call(self, call):
        if isinstance(call, GeneratorObject):
            return True
        return (not call.caller) or (call.caller not in self.calls) def all(iterable):
    for element in iterable:
        if not element:
            return False
    return True def findfirst(pred, seq):
    """Return the first element of given sequence that matches predicate.
    """
    for item in seq:
        if pred(item):
            return item def generator_has_ended(generator):
    """Return True if the generator has been exhausted and False otherwise.

    >>> generator_has_ended(1)
    Traceback (most recent call last):
      ...
    TypeError: argument is not a generator
    """
    if not isinstance(generator, types.GeneratorType):
        raise TypeError("argument is not a generator")
    return _generator_has_ended(generator)
import os.path
import sys
                import pythoscope.store
from nose import SkipTest
from pythoscope.inspector.static import inspect_code
from pythoscope.inspector.dynamic import inspect_code_in_context,\
    inspect_point_of_entry
from pythoscope.serializer import BuiltinException, ImmutableObject,\
    SequenceObject, MapObject, LibraryObject
from pythoscope.store import Class, Function, FunctionCall, GeneratorObject,\
    GeneratorObjectInvocation, Method, UserObject
from pythoscope.compat import all
from pythoscope.util import findfirst, generator_has_ended
from assertions import *
from inspector_assertions import *
from inspector_helper import *
from helper import ProjectInDirectory, PointOfEntryMock, EmptyProjectExecution, \
    IgnoredWarnings, putfile, TempDirectory, CapturedLogger, noindent
from testing_project import TestingProject
        from xml.dom.minidom import Element

        execution = make_execution_with_single_generator_function("generator")
        inspect_code_in_context(function_calling_empty_generator, execution)

        assert_function_with_single_generator_object("generator", {'x': 123}, [], execution)

    def test_handles_generator_objects_that_werent_destroyed(self):
        def function():
            def generator():
                yield 1
            g = generator()
            g.next()
            globals()['__generator_yielding_one'] = g

        execution = make_execution_with_single_generator_function("generator")
        inspect_code_in_context(function, execution)

        assert_function_with_single_generator_object("generator", {}, [1], execution)

    def test_handles_generator_objects_that_yield_none_and_dont_get_destroyed(self):
        if hasattr(generator_has_ended, 'unreliable'):
            raise SkipTest

        def function():
            def generator():
                yield None
            g = generator()
            g.next()
            globals()['__generator_yielding_none'] = g

        execution = make_execution_with_single_generator_function("generator")
        inspect_code_in_context(function, execution)

        assert_function_with_single_generator_object("generator", {}, [None], execution)

    def test_handles_generator_methods(self):
        def function_calling_generator_method():
            class GenClass(object):
                def genmeth(self):
                    yield 0
                    yield 1
                    yield 0
            [x for x in GenClass().genmeth()]

        execution = TestingProject()\
            .with_all_catch_module()\
            .with_object(Class("GenClass", methods=[Method("genmeth", is_generator=True)]))\
            .make_new_execution()
        inspect_code_in_context(function_calling_generator_method, execution)

        klass = execution.project.find_object(Class, "GenClass")
        user_object = assert_one_element_and_return(klass.user_objects)
        assert_instance(user_object, UserObject)

        gobject = assert_one_element_and_return(user_object.calls)
        assert_generator_object({}, [0, 1, 0], gobject)

    def test_handles_generators_called_not_from_top_level(self):
        def function_calling_function_that_uses_generator():
            def generator(x):
                yield x
                yield x + 1
                yield x * 2
                yield x ** 3
            def function(y):
                return [x for x in generator(y)]
            function(2)

        execution = TestingProject()\
            .with_all_catch_module()\
            .with_object(Function("generator", is_generator=True))\
            .with_object(Function("function"))\
            .make_new_execution()
        inspect_code_in_context(function_calling_function_that_uses_generator, execution)

        assert_function_with_single_generator_object("generator",
            {'x': 2}, [2, 3, 4, 8], execution)

    def test_serializes_generator_objects_passed_as_values(self):
        def function():
            def invoke(g):
                return [x for x in g]
            def generator():
                yield 1
                yield 2
            invoke(generator())

        execution = TestingProject()\
            .with_all_catch_module()\
            .with_object(Function("invoke"))\
            .with_object(Function("generator", is_generator=True))\
            .make_new_execution()
        inspect_code_in_context(function, execution)

        function = execution.project.find_object(Function, "invoke")
        fcall = assert_one_element_and_return(function.calls)

        gobject = fcall.input['g']
        assert_instance(gobject, GeneratorObject)
        assert_generator_object({}, [1, 2], gobject)

    def test_generator_can_be_invoked_multiple_times_at_the_same_place(self):
        def function():
            def sth_else():
                return False
            def generator():
                yield 1
                yield 2
            g = generator()
            g.next()
            sth_else()
            g.next()

        execution = TestingProject()\
            .with_all_catch_module()\
            .with_object(Function("generator", is_generator=True))\
            .with_object(Function("sth_else"))\
            .make_new_execution()
        inspect_code_in_context(function, execution)


        assert_instance(execution.call_graph[0], GeneratorObjectInvocation)