Skip to content

Commit

Permalink
Extracted some code from generator package into case_namer module.
Browse files Browse the repository at this point in the history
  • Loading branch information
mkwiatkowski committed Dec 8, 2010
1 parent 9dc961c commit 33b7a2c
Show file tree
Hide file tree
Showing 3 changed files with 217 additions and 200 deletions.
206 changes: 6 additions & 200 deletions pythoscope/generator/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,13 @@
generate_test_contents
from pythoscope.generator.cleaner import remove_objects_unworthy_of_naming
from pythoscope.generator.objects_namer import name_objects_on_timeline
from pythoscope.generator.case_namer import call2testname, name2testname,\
userobject2testname
from pythoscope.generator.selector import testable_objects, testable_calls
from pythoscope.serializer import SerializedObject, ImmutableObject,\
UnknownObject
from pythoscope.store import Class, Function, TestClass, TestMethod,\
ModuleNotFound, GeneratorObject
ModuleNotFound
from pythoscope.compat import all, sorted
from pythoscope.util import assert_argument_type, camelize, counted, \
key_for_value, pluralize, underscore
from pythoscope.util import camelize, pluralize, underscore


# :: Call | UserObject | Method | Function -> CodeString
Expand All @@ -37,167 +36,6 @@ def generate_test_case(testable_interaction, template):
assertions_for_interaction(testable_interaction))),
template)

# :: GeneratorObject -> SerializedObject | None
def generator_object_exception(gobject):
assert_argument_type(gobject, GeneratorObject)
for call in gobject.calls:
if call.raised_exception():
return call.exception

# :: GeneratorObject -> [SerializedObject]
def generator_object_yields(gobject):
assert_argument_type(gobject, GeneratorObject)
return [c.output for c in gobject.calls]

# :: SerializedObject -> string
def object2id(obj):
"""Convert object to string that can be used as an identifier.
Generator objects that were never activated get a generic name...
>>> def producer():
... yield 1
>>> gobject = GeneratorObject(producer())
>>> object2id(gobject)
'generator'
...but if we have a matching definition, the name is based on it.
>>> definition = Function('producer', is_generator=True)
>>> gobject.activate(definition, {}, definition)
>>> object2id(gobject)
'producer_instance'
Accepts only SerializedObjects as arguments.
>>> object2id(42)
Traceback (most recent call last):
...
TypeError: object2id() should be called with a SerializedObject argument, not 42
"""
assert_argument_type(obj, SerializedObject)
if isinstance(obj, GeneratorObject) and obj.is_activated():
return "%s_instance" % obj.definition.name
else:
return obj.human_readable_id

def objects_list_to_id(objects):
"""Convert given list of objects into string that can be used as an
identifier.
"""
if not objects:
return 'nothing'
return '_then_'.join(map(object2id, objects))

def arguments_as_string(args, always_use_argnames=False):
"""Generate an underscored description of given arguments.
>>> arguments_as_string({})
''
>>> arguments_as_string({'x': ImmutableObject(7), 'y': ImmutableObject(13)})
'x_equal_7_and_y_equal_13'
Usually doesn't use argument names when there's only a single argument:
>>> arguments_as_string({'x': ImmutableObject(1)})
'1'
but will use them if forced to:
>>> arguments_as_string({'x': ImmutableObject(1)}, always_use_argnames=True)
'x_equal_1'
"""
if not always_use_argnames and len(args) == 1:
return object2id(args.values()[0])
return "_and_".join(["%s_equal_%s" % (arg, object2id(value))
for arg, value in sorted(args.iteritems())])

def objcall2testname(object_name, args, output):
"""Generate a test method name that describes given object call.
>>> from test.helper import make_fresh_serialize
>>> serialize = make_fresh_serialize()
>>> objcall2testname('do_this', {}, serialize(True))
'test_do_this_returns_true'
>>> objcall2testname('compute', {}, serialize('whatever you say'))
'test_compute_returns_whatever_you_say'
>>> objcall2testname('square', {'x': serialize(7)}, serialize(49))
'test_square_returns_49_for_7'
>>> objcall2testname('capitalize', {'str': serialize('a word.')}, serialize('A word.'))
'test_capitalize_returns_A_word_for_a_word'
Two or more arguments are mentioned by name.
>>> objcall2testname('ackermann', {'m': serialize(3), 'n': serialize(2)}, serialize(29))
'test_ackermann_returns_29_for_m_equal_3_and_n_equal_2'
Will sort arguments alphabetically.
>>> objcall2testname('concat', {'s1': serialize('Hello '), 's2': serialize('world!')}, serialize('Hello world!'))
'test_concat_returns_Hello_world_for_s1_equal_Hello_and_s2_equal_world'
Always starts and ends a word with a letter or number.
>>> objcall2testname('strip', {'n': serialize(1), 's': serialize(' A bit of whitespace ')}, serialize(' A bit of whitespace '))
'test_strip_returns_A_bit_of_whitespace_for_n_equal_1_and_s_equal_A_bit_of_whitespace'
Uses argument name when argument is used as a return value.
>>> alist = serialize([])
>>> objcall2testname('identity', {'x': alist}, alist)
'test_identity_returns_x_for_x_equal_list'
"""
if args:
# If return value is present in arguments list, use its name as an
# identifier.
output_name = key_for_value(args, output)
if output_name:
call_description = "%s_for_%s" % (output_name, arguments_as_string(args, always_use_argnames=True))
else:
call_description = "%s_for_%s" % (object2id(output), arguments_as_string(args))
else:
call_description = object2id(output)

return "test_%s_returns_%s" % (underscore(object_name), call_description)

def exccall2testname(object_name, args, exception):
"""Generate a test method name that describes given object call raising
an exception.
>>> exccall2testname('do_this', {}, UnknownObject(Exception()))
'test_do_this_raises_exception'
>>> exccall2testname('square', {'x': ImmutableObject('a string')}, UnknownObject(TypeError()))
'test_square_raises_type_error_for_a_string'
"""
if args:
call_description = "%s_for_%s" % (object2id(exception), arguments_as_string(args))
else:
call_description = object2id(exception)
return "test_%s_raises_%s" % (underscore(object_name), call_description)

def gencall2testname(object_name, args, yields):
"""Generate a test method name that describes given generator object call
yielding some values.
>>> gencall2testname('generate', {}, [])
'test_generate_yields_nothing'
>>> gencall2testname('generate', {}, [ImmutableObject(1), ImmutableObject(2), ImmutableObject(3)])
'test_generate_yields_1_then_2_then_3'
>>> gencall2testname('backwards', {'x': ImmutableObject(321)}, [ImmutableObject('one'), ImmutableObject('two'), ImmutableObject('three')])
'test_backwards_yields_one_then_two_then_three_for_321'
"""
if args:
call_description = "%s_for_%s" % (objects_list_to_id(yields), arguments_as_string(args))
else:
call_description = objects_list_to_id(yields)
return "test_%s_yields_%s" % (underscore(object_name), call_description)

def call2testname(call, object_name):
# Generators can be treated as calls, which take arguments during
# initialization and return a list of yielded values.
if isinstance(call, GeneratorObject):
exception = generator_object_exception(call)
if exception:
return exccall2testname(object_name, call.args, exception)
else:
return gencall2testname(object_name, call.args, generator_object_yields(call))
elif call.raised_exception():
return exccall2testname(object_name, call.input, call.exception)
else:
return objcall2testname(object_name, call.input, call.output)

# :: [TestMethodDescription] -> [TestMethodDescription]
def sorted_test_method_descriptions(descriptions):
return sorted(descriptions, key=lambda md: md.name)
Expand All @@ -217,11 +55,6 @@ def resolve_name_duplicates(descriptions):
num += 1
return descriptions

def name2testname(name):
if name[0].isupper():
return "Test%s" % name
return "test_%s" % name

def should_ignore_method(method):
return method.is_private()

Expand Down Expand Up @@ -391,35 +224,8 @@ def _method_descriptions_from_function(self, function):
yield TestMethodDescription(name, generate_test_case(call, self.template))

def _method_description_from_user_object(self, user_object):
init_call = user_object.get_init_call()
external_calls = testable_calls(user_object.get_external_calls())

def test_name():
if len(external_calls) == 0 and init_call:
test_name = "test_creation"
if init_call.input:
test_name += "_with_%s" % arguments_as_string(init_call.input)
if init_call.raised_exception():
test_name += "_raises_%s" % object2id(init_call.exception)
else:
if len(external_calls) == 1:
call = external_calls[0]
test_name = call2testname(call, call.definition.name)
# Methods with more than one external call use more brief
# descriptions that don't include inputs and outputs.
else:
methods = []
for method, calls_count in counted([call.definition.name for call in external_calls]):
if calls_count == 1:
methods.append(method)
else:
methods.append("%s_%d_times" % (method, calls_count))
test_name = "test_%s" % '_and_'.join(methods)
if init_call and init_call.input:
test_name += "_after_creation_with_%s" % arguments_as_string(init_call.input)
return test_name

return TestMethodDescription(test_name(), generate_test_case(user_object, self.template))
return TestMethodDescription(userobject2testname(user_object),
generate_test_case(user_object, self.template))

class UnittestTestGenerator(TestGenerator):
main_snippet = parse_fragment("if __name__ == '__main__':\n unittest.main()\n")
Expand Down
Loading

0 comments on commit 33b7a2c

Please sign in to comment.