Permalink
Browse files

Handling global variables reading, both in the inspector and the gen…

…erator.
  • Loading branch information...
1 parent 63778e7 commit 7a9129ceb22d395218dcf1f30ba680dea3d00cda @mkwiatkowski committed with Dec 12, 2010
@@ -256,8 +256,12 @@ def trace(self, frame, event):
elif bcode.name == "LOAD_GLOBAL":
module = frame_module(frame)
if module:
- yield 'load_global', (module.__name__,
- name_from_arg(frame, bcode))
+ try:
+ name = name_from_arg(frame, bcode)
+ value = frame.f_globals[name]
+ yield 'load_global', (module.__name__, name, value)
+ except KeyError:
+ pass
elif bcode.name == "STORE_GLOBAL":
module = frame_module(frame)
if module:
@@ -10,7 +10,7 @@
from pythoscope.generator.selector import testable_calls
from pythoscope.serializer import BuiltinException, ImmutableObject, MapObject,\
UnknownObject, SequenceObject
-from pythoscope.side_effect import SideEffect, GlobalRebind,\
+from pythoscope.side_effect import SideEffect, GlobalRead, GlobalRebind,\
BuiltinMethodWithPositionArgsSideEffect
from pythoscope.store import Function, FunctionCall, UserObject, MethodCall,\
GeneratorObject, GeneratorObjectInvocation, Call, CallToC, Method
@@ -150,17 +150,23 @@ def copy_object_at(obj, timestamp):
# :: ([Event], [SideEffect]) -> None
def add_test_events_for_side_effects(events, side_effects):
+ first_timestamp = events[0].timestamp
last_timestamp = events[-1].timestamp
for side_effect in side_effects:
- if isinstance(side_effect, GlobalRebind):
+ # TODO support multiple assertions and setups/teardowns for global accesses.
+ if isinstance(side_effect, GlobalRead):
+ tmp_name = "old_%s_%s" % (side_effect.module, side_effect.name)
+ ref = VariableReference(side_effect.module, side_effect.name, first_timestamp-4.2)
+ # SETUP: old_module_variable = module.variable
+ events.insert(0, Assign(tmp_name, ref, first_timestamp-3.2))
+ # SETUP: module.variable = value
+ events.insert(1, Assign(side_effect.get_full_name(), side_effect.value, first_timestamp-2.2))
+ # TEARDOWN: module.variable = old_module_variable
+ events.append(Assign(side_effect.get_full_name(), tmp_name, last_timestamp+3.2))
+ elif isinstance(side_effect, GlobalRebind):
events.append(EqualAssertionLine(side_effect.value,
- VariableReference(side_effect.module, side_effect.name, last_timestamp+0.5),
- last_timestamp+1))
- # ref = CodeString("%s.%s" % (side_effect.module, side_effect.name),
- # imports=set([side_effect.module]))
- # TODO global read should cause setup & teardown
- # events.insert(0, Assign(side_effect.get_full_name(), ) # TODO setup
- # events.append(None) # TODO teardown
+ VariableReference(side_effect.module, side_effect.name, last_timestamp+1.1),
+ last_timestamp+2.1))
# :: Call|GeneratorObject -> [SideEffect]
def side_effects_of_call(call):
@@ -6,7 +6,7 @@
from pythoscope.generator.method_call_context import MethodCallContext
from pythoscope.generator.objects_namer import Assign
-from pythoscope.serializer import is_serialized_string
+from pythoscope.serializer import SerializedObject, is_serialized_string
from pythoscope.side_effect import BuiltinMethodWithPositionArgsSideEffect
from pythoscope.store import GeneratorObject, Call, Method
from pythoscope.util import assert_argument_type
@@ -95,23 +95,30 @@ def generator_object_yields(gobject):
assert_argument_type(gobject, (GeneratorObject, MethodCallContext))
return [c.output for c in gobject.calls]
+def code_string_from_variable_reference(ref):
+ return CodeString("%s.%s" % (ref.module, ref.name), imports=set([ref.module]))
+
# :: ([Event], Template) -> CodeString
def generate_test_contents(events, template):
contents = CodeString("")
all_uncomplete = False
already_assigned_names = {}
for event in events:
if isinstance(event, Assign):
- constructor = constructor_as_string(event.obj, already_assigned_names)
+ if isinstance(event.obj, VariableReference):
+ constructor = code_string_from_variable_reference(event.obj)
+ elif isinstance(event.obj, str):
+ constructor = CodeString(event.obj)
+ else:
+ constructor = constructor_as_string(event.obj, already_assigned_names)
+ already_assigned_names[event.obj] = event.name
line = combine(event.name, constructor, "%s = %s")
- already_assigned_names[event.obj] = event.name
elif isinstance(event, EqualAssertionLine):
expected = constructor_as_string(event.expected, already_assigned_names)
if isinstance(event.actual, (Call, MethodCallContext)):
actual = call_in_test(event.actual, already_assigned_names)
elif isinstance(event.actual, VariableReference):
- actual = CodeString("%s.%s" % (event.actual.module, event.actual.name),
- imports=set([event.actual.module]))
+ actual = code_string_from_variable_reference(event.actual)
else:
actual = constructor_as_string(event.actual, already_assigned_names)
if expected.uncomplete:
@@ -109,6 +109,10 @@ def get_contained_objects(obj):
return get_contained_objects(obj.generator_call)
elif isinstance(obj, RaisesAssertionLine):
return get_those_and_contained_objects([obj.call, obj.expected_exception])
+ elif isinstance(obj, Assign):
+ if isinstance(obj.obj, SerializedObject):
+ return get_those_and_contained_objects([obj.obj])
+ return []
elif isinstance(obj, (ImmutableObject, UnknownObject, CallToC, CommentLine,
SkipTestLine, EqualAssertionStubLine, VariableReference)):
return []
@@ -53,3 +53,6 @@ def __init__(self, name, obj, timestamp):
Line.__init__(self, timestamp)
self.name = name
self.obj = obj
+
+ def __repr__(self):
+ return "Assign(%r, %r)" % (self.name, self.obj)
@@ -2,9 +2,10 @@
import sys
from pythoscope.side_effect import recognize_side_effect, MissingSideEffectType,\
- GlobalRebind
+ GlobalRebind, GlobalRead
from pythoscope.store import CallToC, UnknownCall
from pythoscope.tracer import ICallback, Tracer
+from pythoscope.util import get_names
class CallStack(object):
@@ -64,6 +65,11 @@ def side_effect(self, side_effect):
else:
self.top_level_side_effects.append(side_effect)
+# :: (Module, str) -> bool
+def has_defined_name(module, name):
+ # TODO: also look at the list of imports
+ return name in get_names(module.objects)
+
class Inspector(ICallback):
"""Controller of the dynamic inspection process. It receives information
from the tracer and propagates it to Execution and CallStack objects.
@@ -123,6 +129,15 @@ def called(self, call):
self.call_stack.called(UnknownCall())
return True
+ def global_read(self, module_name, name, value):
+ try:
+ if has_defined_name(self.execution.project[module_name], name):
+ return
+ except:
+ pass
+ se = GlobalRead(module_name, name, self.execution.serialize(value))
+ self.call_stack.side_effect(se)
+
def global_rebound(self, module, name, value):
se = GlobalRebind(module, name, self.execution.serialize(value))
self.call_stack.side_effect(se)
View
@@ -34,15 +34,26 @@ def __init__(self, affected_objects, only_referenced_objects):
self.affected_objects = affected_objects
self.referenced_objects = affected_objects + only_referenced_objects
-class GlobalRebind(SideEffect):
+class GlobalVariableSideEffect(SideEffect):
+ def get_full_name(self):
+ return "%s.%s" % (self.module, self.name)
+
+ def __repr__(self):
+ return "%s(%r, %r, %r)" % (self.__class__.name, self.module, self.name, self.value)
+
+class GlobalRead(GlobalVariableSideEffect):
def __init__(self, module, name, value):
- super(GlobalRebind, self).__init__([], []) # TODO: module's __dict__ is affected
+ super(GlobalRead, self).__init__([], [])
self.module = module
self.name = name
self.value = value
- def get_full_name(self):
- return "%s.%s" % (self.module, self.name)
+class GlobalRebind(GlobalVariableSideEffect):
+ def __init__(self, module, name, value):
+ super(GlobalRebind, self).__init__([], []) # TODO: module's __dict__ is affected
+ self.module = module
+ self.name = name
+ self.value = value
class BuiltinMethodWithPositionArgsSideEffect(SideEffect):
definition = None # set in a subclass
View
@@ -119,6 +119,9 @@ def is_generator_exit(obj):
except NameError:
return False
+import __builtin__
+builtins_names = dir(__builtin__)
+
class StandardTracer(object):
"""Wrapper around basic C{sys.settrace} mechanism that maps 'call', 'return'
and 'exception' events into more meaningful callbacks.
@@ -201,7 +204,9 @@ def handle_bytecode_tracer_event(self, event, args):
object, name = args
pass # TODO
elif event == 'load_global':
- pass # TODO
+ 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)
@@ -391,6 +396,14 @@ def raised(self, exception, traceback):
raise NotImplementedError("Method raised() not defined.")
# :: (str, str, object) -> None
+ def global_read(self, module, name, value):
+ """Reported when a global variable is read.
+
+ Return value is ignored.
+ """
+ raise NotImplementedError("Method global_read() not defined.")
+
+ # :: (str, str, object) -> None
def global_rebound(self, module, name, value):
"""Reported when a global variable is rebound.
@@ -4,8 +4,11 @@
class TestMain(unittest.TestCase):
def test_main_returns_1(self):
- self.assertEqual(1, main())
+ old_module_var = module.var
+ module.var = 1
+ self.assertEqual(module.var, main())
self.assertEqual(2, module.var)
+ module.var = old_module_var
if __name__ == '__main__':
unittest.main()
@@ -523,10 +523,12 @@ def setup(self):
self._ignored_events = []
def test_handles_global_variable_reading(self):
+ global return_value
+ return_value = 42
def fun():
return return_value
self.trace_function(fun)
- self.assert_trace(('load_global', ('test.test_bytecode_tracer', 'return_value')))
+ self.assert_trace(('load_global', ('test.test_bytecode_tracer', 'return_value', 42)))
def test_handles_global_variable_rebinding(self):
def fun():
@@ -1,6 +1,7 @@
from pythoscope.serializer import SequenceObject, ImmutableObject
from pythoscope.store import Function, FunctionCall
-from pythoscope.side_effect import SideEffect, ListAppend, GlobalRebind
+from pythoscope.side_effect import SideEffect, ListAppend, GlobalRead,\
+ GlobalRebind
from pythoscope.generator.assertions import assertions_for_interaction
from pythoscope.generator.objects_namer import name_objects_on_timeline, Assign
from pythoscope.generator.cleaner import remove_objects_unworthy_of_naming,\
@@ -87,6 +88,23 @@ def test_object_copy_includes_its_side_effects(self):
assert alists[1] in side_effect.affected_objects
class TestSideEffectSetupTeardownAndAssertions:
+ def test_creates_setup_and_teardown_for_global_read_side_effect(self):
+ call = create(FunctionCall, args={})
+ old_value = ImmutableObject('old_value')
+ se = GlobalRead('mod', 'var', old_value)
+ call.add_side_effect(se)
+ put_on_timeline(se, call, call.output)
+
+ timeline = assertions_for_interaction(call)
+ assert_length(timeline, 6)
+ setup_1, setup_2 = timeline[0:2]
+ teardown = timeline[4]
+ assert_instance(setup_1, Assign)
+ assert_equal('old_mod_var', setup_1.name)
+ assert_variable_reference(setup_1.obj, 'mod', 'var')
+ assert_assignment(setup_2, 'mod.var', old_value)
+ assert_assignment(teardown, 'mod.var', 'old_mod_var')
+
def test_creates_assertion_for_global_rebind_side_effect(self):
call = create(FunctionCall, args={})
new_value = ImmutableObject('new_value')
@@ -96,11 +114,14 @@ def test_creates_assertion_for_global_rebind_side_effect(self):
timeline = assertions_for_interaction(call)
assert_length(timeline, 4)
- rebind_assertion = timeline[1]
+ rebind_assertion = timeline[2]
assert_equal(new_value, rebind_assertion.expected)
- assert_instance(rebind_assertion.actual, VariableReference)
- assert_equal('mod', rebind_assertion.actual.module)
- assert_equal('var', rebind_assertion.actual.name)
+ assert_variable_reference(rebind_assertion.actual, 'mod', 'var')
+
+def assert_variable_reference(ref, expected_module, expected_name):
+ assert_instance(ref, VariableReference)
+ assert_equal(expected_module, ref.module)
+ assert_equal(expected_name, ref.name)
class TestObjectUsageCounts:
def setUp(self):
@@ -194,11 +215,21 @@ def test_names_objects_appropriatelly(self):
unittest_template = UnittestTemplate()
class TestGenerateTestContents:
- def test_generates_assignment_line(self):
+ def test_generates_assignment_line_with_object(self):
assign = Assign('foo', create(SequenceObject), 1)
assert_equal_strings("foo = []\n",
generate_test_contents([assign], None))
+ def test_generates_assignment_line_with_name(self):
+ assign = Assign('foo', 'bar', 1)
+ assert_equal_strings("foo = bar\n",
+ generate_test_contents([assign], None))
+
+ def test_generates_assignment_line_with_variable_reference(self):
+ assign = Assign('foo', VariableReference('mod', 'var', 0), 1)
+ assert_equal_strings("foo = mod.var\n",
+ generate_test_contents([assign], None))
+
def test_generates_assertion_line(self):
aline = EqualAssertionLine(create(SequenceObject), create(FunctionCall), 1)
assert_equal_strings("self.assertEqual([], function())\n",
@@ -1,5 +1,5 @@
from pythoscope.side_effect import ListAppend, ListExtend, ListInsert, ListPop,\
- GlobalRebind
+ GlobalRebind, GlobalRead
from assertions import *
from inspector_assertions import *
@@ -62,15 +62,29 @@ def foo(x):
assert_equal([], call.side_effects)
class TestGlobalVariables:
+ def test_handles_reading(self):
+ global was_run
+ was_run = False
+ def function_reading_global_variable():
+ def function():
+ return was_run
+ function()
+ call = inspect_returning_single_call(function_reading_global_variable)
+ se = assert_one_element_and_return(call.side_effects)
+ assert_instance(se, GlobalRead)
+ assert_equal('test.test_tracing_side_effects', se.module)
+ assert_equal('was_run', se.name)
+ assert_serialized(False, se.value)
+
def test_handles_rebinding(self):
def function_rebinding_global_variable():
def function():
global was_run
- was_run = False
+ was_run = 0
function()
call = inspect_returning_single_call(function_rebinding_global_variable)
se = assert_one_element_and_return(call.side_effects)
assert_instance(se, GlobalRebind)
assert_equal('test.test_tracing_side_effects', se.module)
assert_equal('was_run', se.name)
- assert_serialized(False, se.value)
+ assert_serialized(0, se.value)

0 comments on commit 7a9129c

Please sign in to comment.