def call_in_test(call, already_assigned_names): if isinstance(call, GeneratorObject) or (isinstance(call, MethodCallContext) and isinstance(call.call, GeneratorObject)): callstring = call_as_string_for(call_name(call, already_assigned_names), call.args, call.definition, already_assigned_names) callstring = combine(callstring, str(len(call.calls)), template="list(islice(%s, %s))") callstring = addimport(callstring, ("itertools", "islice")) else: callstring = call_as_string_for(call_name(call, already_assigned_names), call.input, call.definition, already_assigned_names) callstring = addimport(callstring, import_for(call.definition)) return callstring
def call_as_string(object_name, args, assigned_names={}): """Generate code for calling an arbitrary object with given arguments. Use `call_as_string_for` when you have a definition to base the call on. >>> from test.helper import make_fresh_serialize >>> serialize = make_fresh_serialize() Since we don't have a definition to base the generated call on, we use keywords to name all arguments: >>> call_as_string('fun', {'a': serialize(1), 'b': serialize(2)}) 'fun(a=1, b=2)' >>> call_as_string('capitalize', {'str': serialize('string')}) "capitalize(str='string')" Uses references to existing objects where possible... >>> result = call_as_string('call', {'f': serialize(call_as_string)}) >>> result 'call(f=call_as_string)' >>> result.uncomplete False ...but marks the resulting call as uncomplete if at least one of objects appearing in a call cannot be constructed. >>> result = call_as_string('map', {'f': serialize(lambda x: 42), 'L': serialize([1,2,3])}) >>> result 'map(L=[1, 2, 3], f=<TODO: function>)' >>> result.uncomplete True Uses names already assigned to objects instead of inlining their construction code. >>> mutable = serialize([]) >>> call_as_string('merge', {'seq1': mutable, 'seq2': serialize([1,2,3])}, ... {mutable: 'alist'}) 'merge(seq1=alist, seq2=[1, 2, 3])' """ arguments = [] for arg, value in sorted(args.iteritems()): constructor = constructor_as_string(value, assigned_names) arguments.append(combine(arg, constructor, template="%s=%s")) return combine(object_name, join(", ", arguments), template="%s(%s)")
def variable_assignment_line(left, right, already_assigned_names): if isinstance(right, ModuleVariableReference): constructor = code_string_from_module_variable_reference(right) elif isinstance(right, str): constructor = CodeString(right) elif isinstance(right, (Call, MethodCallContext)): constructor = call_in_test(right, already_assigned_names) # Associate the name with the call's output, not the call itself. already_assigned_names[right.output] = left else: constructor = constructor_as_string(right, already_assigned_names) already_assigned_names[right] = left return combine(left, constructor, "%s = %s")
def attribute_assignment_line(left, right, already_assigned_names): try: constructor = CodeString(already_assigned_names[right]) except KeyError: constructor = constructor_as_string(right, already_assigned_names) return combine(left, constructor, "%s = %s")
def call_as_string_for(object_name, args, definition, assigned_names={}): """Generate code for calling an object with given arguments. >>> from test.helper import make_fresh_serialize >>> serialize = make_fresh_serialize() Puts varargs at the end of arguments list. >>> call_as_string_for('build_url', ... {'proto': serialize('http'), 'params': serialize(('user', 'session', 'new'))}, ... Function('build_url', ['proto', '*params'])) "build_url('http', 'user', 'session', 'new')" Works for lone varargs too. >>> call_as_string_for('concat', {'args': serialize(([1,2,3], [4,5], [6]))}, ... Function('concat', ['*args'])) 'concat([1, 2, 3], [4, 5], [6])' Uses assigned name for varargs as well. >>> args = serialize((1, 2, 3)) >>> call_as_string_for('add', {'args': args}, Function('add', ['*args']), {args: 'atuple'}) 'add(*atuple)' Inlines extra keyword arguments in the call... >>> call_as_string_for('dict', {'kwargs': serialize({'one': 1, 'two': 2})}, ... Function('dict', ['**kwargs'])) 'dict(one=1, two=2)' ...even when they are combined with varargs. >>> call_as_string_for('wrap', {'a': serialize((1, 2, 3)), 'k': serialize({'x': 4, 'y': 5})}, ... Function('wrap', ['*a', '**k'])) 'wrap(1, 2, 3, x=4, y=5)' Uses assigned name for kwarg if present. >>> kwargs = serialize({'id': 42, 'model': 'user'}) >>> call_as_string_for('filter_params', {'kwargs': kwargs}, ... Function('filter_params', ['**kwargs']), {kwargs: 'params'}) 'filter_params(**params)' Generates valid code when vararg has been named and kwarg wasn't. >>> args = serialize((1, 2, 3)) >>> call_as_string_for('wrap', {'args': args, 'kwargs': serialize({'a': 6, 'b': 7})}, ... Function('wrap', ['*args', '**kwargs']), {args: 'atuple'}) 'wrap(a=6, b=7, *atuple)' When varargs are present all preceding arguments are positioned, not named. >>> call_as_string_for('sum', {'x': serialize(1), 'rest': serialize((2, 3))}, ... Function('sum', ['x', '*rest'])) 'sum(1, 2, 3)' When argument type requires import, the import is present in the imports list. >>> m = Module(None, 'myclasses') >>> cs = call_as_string_for('display', ... {'obj': UserObject(None, Class('MyWindow', module=m))}, ... Function('display', ['obj'])) >>> cs 'display(MyWindow())' >>> cs.imports set([('myclasses', 'MyWindow')]) """ positional_args = [] keyword_args = [] vararg = None kwarg = None def getvalue(argname): return args[argname.lstrip("*")] skipped_an_arg = False for argname in arguments_of(definition): try: value = getvalue(argname) if argname.startswith("**"): if value in assigned_names.keys(): kwarg = CodeString("**%s" % assigned_names[value]) else: for karg, kvalue in map_as_kwargs(value): valuecs = constructor_as_string(kvalue, assigned_names) keyword_args.append(combine(karg, valuecs, "%s=%s")) elif argname.startswith("*"): if value in assigned_names.keys(): vararg = CodeString("*%s" % assigned_names[value]) else: code_strings = get_contained_objects_info(value, assigned_names) positional_args.extend(code_strings) else: constructor = constructor_as_string(value, assigned_names) if skipped_an_arg: keyword_args.append(combine(argname, constructor, "%s=%s")) else: positional_args.append(constructor) except KeyError: skipped_an_arg = True arguments = join(', ', filter(None, (positional_args + keyword_args + [vararg] + [kwarg]))) return combine(object_name, arguments, "%s(%s)")
def get_objects_mapping_info(mapping, assigned_names): for key, value in mapping: keycs = constructor_as_string(key, assigned_names) valuecs = constructor_as_string(value, assigned_names) yield combine(keycs, valuecs, "%s: %s")
def equal_assertion(self, expected, actual): return combine(expected, actual, "self.assertEqual(%s, %s)")
def raises_assertion(self, exception, call): return combine(exception, call, "self.assertRaises(%s, %s)")
def call_as_string_for(object_name, args, definition, assigned_names={}): """Generate code for calling an object with given arguments. >>> from test.helper import make_fresh_serialize >>> serialize = make_fresh_serialize() Puts varargs at the end of arguments list. >>> call_as_string_for('build_url', ... {'proto': serialize('http'), 'params': serialize(('user', 'session', 'new'))}, ... Function('build_url', ['proto', '*params'])) "build_url('http', 'user', 'session', 'new')" Works for lone varargs too. >>> call_as_string_for('concat', {'args': serialize(([1,2,3], [4,5], [6]))}, ... Function('concat', ['*args'])) 'concat([1, 2, 3], [4, 5], [6])' Uses assigned name for varargs as well. >>> args = serialize((1, 2, 3)) >>> call_as_string_for('add', {'args': args}, Function('add', ['*args']), {args: 'atuple'}) 'add(*atuple)' Inlines extra keyword arguments in the call... >>> call_as_string_for('dict', {'kwargs': serialize({'one': 1, 'two': 2})}, ... Function('dict', ['**kwargs'])) 'dict(one=1, two=2)' ...even when they are combined with varargs. >>> call_as_string_for('wrap', {'a': serialize((1, 2, 3)), 'k': serialize({'x': 4, 'y': 5})}, ... Function('wrap', ['*a', '**k'])) 'wrap(1, 2, 3, x=4, y=5)' Uses assigned name for kwarg if present. >>> kwargs = serialize({'id': 42, 'model': 'user'}) >>> call_as_string_for('filter_params', {'kwargs': kwargs}, ... Function('filter_params', ['**kwargs']), {kwargs: 'params'}) 'filter_params(**params)' Generates valid code when vararg has been named and kwarg wasn't. >>> args = serialize((1, 2, 3)) >>> call_as_string_for('wrap', {'args': args, 'kwargs': serialize({'a': 6, 'b': 7})}, ... Function('wrap', ['*args', '**kwargs']), {args: 'atuple'}) 'wrap(a=6, b=7, *atuple)' When varargs are present all preceding arguments are positioned, not named. >>> call_as_string_for('sum', {'x': serialize(1), 'rest': serialize((2, 3))}, ... Function('sum', ['x', '*rest'])) 'sum(1, 2, 3)' When argument type requires import, the import is present in the imports list. >>> m = Module(None, 'myclasses') >>> cs = call_as_string_for('display', ... {'obj': UserObject(None, Class('MyWindow', module=m))}, ... Function('display', ['obj'])) >>> cs 'display(MyWindow())' >>> cs.imports set([('myclasses', 'MyWindow')]) """ positional_args = [] keyword_args = [] vararg = None kwarg = None def getvalue(argname): return args[argname.lstrip("*")] skipped_an_arg = False for argname in arguments_of(definition): try: value = getvalue(argname) if argname.startswith("**"): if value in assigned_names.keys(): kwarg = CodeString("**%s" % assigned_names[value]) else: for karg, kvalue in map_as_kwargs(value): valuecs = constructor_as_string(kvalue, assigned_names) keyword_args.append(combine(karg, valuecs, "%s=%s")) elif argname.startswith("*"): if value in assigned_names.keys(): vararg = CodeString("*%s" % assigned_names[value]) else: code_strings = get_contained_objects_info( value, assigned_names) positional_args.extend(code_strings) else: constructor = constructor_as_string(value, assigned_names) if skipped_an_arg: keyword_args.append(combine(argname, constructor, "%s=%s")) else: positional_args.append(constructor) except KeyError: skipped_an_arg = True arguments = join( ', ', filter(None, (positional_args + keyword_args + [vararg] + [kwarg]))) return combine(object_name, arguments, "%s(%s)")
def add_newline(code_string): return combine(code_string, "\n")
def raises_assertion(self, exception, call): return addimport(combine(exception, call, "assert_raises(%s, %s)"), ('nose.tools', 'assert_raises'))
def equal_assertion(self, expected, actual): return addimport(combine(expected, actual, "assert_equal(%s, %s)"), ('nose.tools', 'assert_equal'))
def generate_test_contents(events, template): contents = CodeString("") all_uncomplete = False already_assigned_names = {} for event in events: if isinstance(event, Assign): line = variable_assignment_line(event.name, event.obj, already_assigned_names) elif isinstance(event, BindingChange): if event.name.obj in already_assigned_names.keys(): already_assigned_names[ event.obj] = code_string_from_object_attribute_reference( event.name, already_assigned_names) continue # This is not a real test line, so just go directly to the next line. 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, ModuleVariableReference): actual = code_string_from_module_variable_reference( event.actual) elif isinstance(event.actual, ObjectAttributeReference): actual = code_string_from_object_attribute_reference( event.actual, already_assigned_names) elif isinstance(event.actual, str): actual = CodeString(event.actual) else: actual = constructor_as_string(event.actual, already_assigned_names) if expected.uncomplete: expected = type_as_string(event.expected) actual = type_of(actual) line = template.equal_assertion(expected, actual) elif isinstance(event, GeneratorAssertionLine): call = event.generator_call yields = generator_object_yields(call) expected = constructor_as_string(yields, already_assigned_names) actual = call_in_test(call, already_assigned_names) if expected.uncomplete: expected = type_as_string(yields) actual = map_types(actual) actual = addimport(actual, 'types') line = template.equal_assertion(expected, actual) elif isinstance(event, RaisesAssertionLine): actual = call_in_test(event.call, already_assigned_names) actual = in_lambda(actual) if is_serialized_string(event.expected_exception): exception = todo_value(event.expected_exception.reconstructor) else: exception = CodeString(event.expected_exception.type_name) exception = addimport(exception, event.expected_exception.type_import) line = template.raises_assertion(exception, actual) elif isinstance(event, CommentLine): line = CodeString(event.comment) elif isinstance(event, SkipTestLine): line = template.skip_test() elif isinstance(event, EqualAssertionStubLine): line = template.equal_assertion( CodeString('expected', uncomplete=True), event.actual) elif isinstance(event, BuiltinMethodWithPositionArgsSideEffect): # All objects affected by side effects are named. object_name = already_assigned_names[event.obj] line = call_as_string_for( "%s.%s" % (object_name, event.definition.name), event.args_mapping(), event.definition, already_assigned_names) elif isinstance(event, AttributeRebind): # All objects affected by side effects are named. object_name = already_assigned_names[event.obj] line = attribute_assignment_line( "%s.%s" % (object_name, event.name), event.value, already_assigned_names) else: raise TypeError( "Don't know how to generate test contents for event %r." % event) if line.uncomplete: all_uncomplete = True if all_uncomplete and not isinstance(event, SkipTestLine): line = combine("# ", line) contents = combine(contents, add_newline(line)) return contents
def generate_test_contents(events, template): contents = CodeString("") all_uncomplete = False already_assigned_names = {} for event in events: if isinstance(event, Assign): line = variable_assignment_line(event.name, event.obj, already_assigned_names) elif isinstance(event, BindingChange): if event.name.obj in already_assigned_names.keys(): already_assigned_names[event.obj] = code_string_from_object_attribute_reference(event.name, already_assigned_names) continue # This is not a real test line, so just go directly to the next line. 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, ModuleVariableReference): actual = code_string_from_module_variable_reference(event.actual) elif isinstance(event.actual, ObjectAttributeReference): actual = code_string_from_object_attribute_reference(event.actual, already_assigned_names) elif isinstance(event.actual, str): actual = CodeString(event.actual) else: actual = constructor_as_string(event.actual, already_assigned_names) if expected.uncomplete: expected = type_as_string(event.expected) actual = type_of(actual) line = template.equal_assertion(expected, actual) elif isinstance(event, GeneratorAssertionLine): call = event.generator_call yields = generator_object_yields(call) expected = constructor_as_string(yields, already_assigned_names) actual = call_in_test(call, already_assigned_names) if expected.uncomplete: expected = type_as_string(yields) actual = map_types(actual) actual = addimport(actual, 'types') line = template.equal_assertion(expected, actual) elif isinstance(event, RaisesAssertionLine): actual = call_in_test(event.call, already_assigned_names) actual = in_lambda(actual) if is_serialized_string(event.expected_exception): exception = todo_value(event.expected_exception.reconstructor) else: exception = CodeString(event.expected_exception.type_name) exception = addimport(exception, event.expected_exception.type_import) line = template.raises_assertion(exception, actual) elif isinstance(event, CommentLine): line = CodeString(event.comment) elif isinstance(event, SkipTestLine): line = template.skip_test() elif isinstance(event, EqualAssertionStubLine): line = template.equal_assertion(CodeString('expected', uncomplete=True), event.actual) elif isinstance(event, BuiltinMethodWithPositionArgsSideEffect): # All objects affected by side effects are named. object_name = already_assigned_names[event.obj] line = call_as_string_for("%s.%s" % (object_name, event.definition.name), event.args_mapping(), event.definition, already_assigned_names) elif isinstance(event, AttributeRebind): # All objects affected by side effects are named. object_name = already_assigned_names[event.obj] line = attribute_assignment_line("%s.%s" % (object_name, event.name), event.value, already_assigned_names) else: raise TypeError("Don't know how to generate test contents for event %r." % event) if line.uncomplete: all_uncomplete = True if all_uncomplete and not isinstance(event, SkipTestLine): line = combine("# ", line) contents = combine(contents, add_newline(line)) return contents