From 11d803fd6333019861dbd0a27acfc3223509de4a Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 27 Apr 2011 16:00:50 +0300 Subject: [PATCH] Some love for the interface & tests --- pokedex/tests/test_movesets.py | 106 ++++++++++++++++++++------------- pokedex/util/movesets.py | 95 +++++++++++++++++------------ 2 files changed, 121 insertions(+), 80 deletions(-) diff --git a/pokedex/tests/test_movesets.py b/pokedex/tests/test_movesets.py index af71cde..247547a 100644 --- a/pokedex/tests/test_movesets.py +++ b/pokedex/tests/test_movesets.py @@ -1,62 +1,82 @@ +import sys + from pokedex.db import connect from pokedex.util.movesets import main +testcase_args = u""" +NO muk +NO beedrill rage pursuit agility endeavor toxic +NO ditto psystrike aeroblast mist-ball judgment +OK metapod tackle harden +OK lugia aeroblast punishment dive snore +OK yanmega bug-bite bug-buzz tackle whirlwind +OK crobat brave-bird quick-attack gust zen-headbutt +OK bagon defense-curl fire-fang hydro-pump shadow-claw +OK volcarona endure toxic fly fire-blast +OK hippopotas curse revenge sleep-talk swallow +OK hippopotas curse revenge sleep-talk snore +OK smeargle bug-bite bug-buzz splash fire-blast +NO smeargle bug-bite chatter splash fire-blast +NO azurill muddy-water iron-tail scald mimic +OK salamence dragon-dance dragon-claw fire-blast earthquake -v platinum +OK crawdaunt brick-break rock-slide facade toxic -v platinum +NO cleffa tickle wish amnesia splash +OK tyrogue pursuit +NO happiny softboiled +NO mamoswine bite body-slam curse double-edge +OK shedinja swords-dance +NO shedinja swords-dance screech +OK shedinja screech double-team fury-cutter x-scissor +OK raichu volt-tackle +OK raichu surf -v gold +OK pikachu volt-tackle thunderbolt bide +OK gyarados flail thrash iron-head outrage +OK drifblim memento gust thunderbolt pain-split +OK crobat nasty-plot brave-bird +NO crobat nasty-plot hypnosis +OK garchomp double-edge thrash outrage +OK nidoking counter disable amnesia head-smash +OK aggron stomp smellingsalt screech fire-punch +OK tyranitar dragon-dance outrage thunder-wave surf +NO butterfree morning-sun harden +OK pikachu reversal bide nasty-plot discharge +NO pikachu surf charge +NO blissey wish counter +NO clefairy copycat dynamicpunch +""" + result_map = {'OK': True, 'NO': False} def test_cases(): session = connect() - for argstring in u""" - NO muk - NO beedrill rage pursuit agility endeavor toxic - NO ditto psystrike aeroblast mist-ball judgment - OK lugia aeroblast punishment dive snore - OK yanmega bug-bite bug-buzz tackle whirlwind - OK crobat brave-bird quick-attack gust zen-headbutt - OK bagon defense-curl fire-fang hydro-pump shadow-claw - OK volcarona endure toxic fly fire-blast - OK hippopotas curse revenge sleep-talk swallow - OK hippopotas curse revenge sleep-talk snore - OK smeargle bug-bite bug-buzz splash fire-blast - NO smeargle bug-bite chatter splash fire-blast - NO azurill muddy-water iron-tail scald mimic - OK salamence dragon-dance dragon-claw fire-blast earthquake -v platinum - OK crawdaunt brick-break rock-slide facade toxic -v platinum - NO cleffa tickle wish amnesia splash - OK tyrogue pursuit - NO happiny softboiled - NO mamoswine bite body-slam curse double-edge - OK raichu volt-tackle - OK raichu surf -v gold - OK pikachu volt-tackle thunderbolt bide - OK gyarados flail thrash iron-head outrage - OK drifblim memento gust thunderbolt pain-split - OK crobat nasty-plot brave-bird - NO crobat nasty-plot hypnosis - OK garchomp double-edge thrash outrage - OK nidoking counter disable amnesia head-smash - OK aggron stomp smellingsalt screech fire-punch - OK tyranitar dragon-dance outrage thunder-wave surf - NO butterfree morning-sun harden - OK pikachu reversal bide nasty-plot discharge - NO pikachu surf charge - NO blissey wish counter - NO clefairy copycat dynamicpunch - """.strip().splitlines(): + for argstring in testcase_args.strip().splitlines(): def run_test(argstring): - args = argstring.split() - assert main(args[1:], session=session) == result_map[args[0]] + args = argstring.split() + ['-q'] + assert bool(main(args[1:], session=session)) == result_map[args[0]] run_test.description = 'Moveset checker test: ' + argstring.strip() yield run_test, argstring.strip() if __name__ == '__main__': + # Nose's default profiler, the unmaintained hotshot, sucks. + # Use cProfile instead. filename = 'movesets.profile' print 'Profiling the moveset checker' import cProfile - def header(str): - print - print str - cProfile.runctx("[(header(argv), f(argv)) for f, argv in test_cases()]", + ok_fail = [0, 0] + def run_case(f, argv): + print argv, '...', + sys.stdout.flush() + try: + f(argv) + ok_fail[0] += 1 + print 'ok' + except AssertionError: + ok_fail[1] += 1 + print 'FAIL' + cases = list(test_cases()) + cProfile.runctx("[(run_case(f, argv)) for f, argv in cases]", globals(), locals(), filename=filename) + print "{0} tests: {1[0]} OK, {1[1]} failed".format(sum(ok_fail), ok_fail) print 'Profile stats saved to', filename diff --git a/pokedex/util/movesets.py b/pokedex/util/movesets.py index bd1144f..8b63560 100755 --- a/pokedex/util/movesets.py +++ b/pokedex/util/movesets.py @@ -45,7 +45,7 @@ class MovesetSearch(object): _cache = WeakKeyDictionary() def __init__(self, session, pokemon, version, moves, level=100, costs=None, - exclude_versions=(), exclude_pokemon=(), debug_level=False): + exclude_versions=(), exclude_pokemon=(), debug_level=0): self.session = session @@ -1263,9 +1263,23 @@ class GoalNode(PokemonNode): return True ### -### CLI interface +### Interface ### +def verify_moveset(session, pokemon, version, moves, level=100, **kwargs): + """Verify the given moveset. + + Returns a result with a hint on how to obtain the moveset, if it is valid. + Otherwise, returns a false value. + """ + try: + search = MovesetSearch(session, pokemon, version, moves, level, **kwargs) + except IllegalMoveCombination, e: + return False + else: + for result in search: + return result + def print_result(result, moves=()): template = u"{cost:4} {est:4} {action:45.45}{long:1} {pokemon:10}{level:>3}{nl:1}{versions:2} {moves}" print template.format(cost='Cost', est='Est.', action='Action', pokemon='Pokemon', @@ -1304,6 +1318,10 @@ def main(argv, session=None): default='black', help='Version to search in.') + parser.add_argument('-q', '--quiet', action='store_true', default=False, + help="Don't print out the result, only indicate it by the return " + "value.") + parser.add_argument('-V', '--exclude-version', metavar='VER', type=unicode, action='append', default=[], help='Versions to exclude (along with their ' @@ -1316,8 +1334,8 @@ def main(argv, session=None): parser.add_argument('-d', '--debug', action='append_const', const=1, default=[], - help='Output timing and debugging information (can be specified more ' - 'than once).') + help='Output timing and debugging information. Can be specified more ' + 'than once for even more verbosity.') args = parser.parse_args(argv) args.debug = len(args.debug) @@ -1331,54 +1349,57 @@ def main(argv, session=None): if args.debug: print 'Parsing arguments' + class BadArgs(ValueError): pass + def _get_list(table, idents, name): + if not idents: + return [] result = [] + query = session.query(table).filter(table.identifier.in_(idents)) + query = query.order_by(table.id.desc()) # overwrite pokemon alt. forms + ident_map = dict((thing.identifier, thing) for thing in query) for ident in idents: try: - result.append(util.get(session, table, identifier=ident)) - except NoResultFound: + result.append(ident_map[ident]) + except KeyError: print>>sys.stderr, ('%s %s not found. Please use ' 'the identifier.' % (name, ident)) - return False + raise BadArgs return result - pokemon = _get_list(tables.Pokemon, [args.pokemon], 'Pokemon')[0] - moves = _get_list(tables.Move, args.move, 'Move') - version = _get_list(tables.Version, [args.version], 'Version')[0] - excl_versions = _get_list(tables.Version, args.exclude_version, 'Version') - excl_pokemon = _get_list(tables.Pokemon, args.exclude_pokemon, 'Pokemon') + try: + all_pokemon = _get_list(tables.Pokemon, + [args.pokemon] + args.exclude_pokemon, 'Pokemon') + all_versions = _get_list(tables.Version, + [args.version] + args.exclude_version, 'Version') + + pokemon = all_pokemon[0] + moves = _get_list(tables.Move, args.move, 'Move') + version = all_versions[0] + excl_versions = all_versions[1:] + excl_pokemon = all_pokemon[1:] + except BadArgs: + return False if args.debug: print 'Starting search' - no_results = True - try: - search = MovesetSearch(session, pokemon, version, moves, args.level, - exclude_versions=excl_versions, exclude_pokemon=excl_pokemon, - debug_level=args.debug) - except IllegalMoveCombination, e: - print 'Error:', e + result = verify_moveset(session, pokemon, version, moves, args.level, + exclude_versions=excl_versions, exclude_pokemon=excl_pokemon, + debug_level=args.debug) + # XXX: Support more than one result + if result: + if args.debug: + print '-' * 79 + if not args.quiet: + print_result(result, moves=moves) else: if args.debug: - print 'Setup done' + print ' ' * 79 + if not args.quiet: + print 'Illegal move combination.' - for result in search: - if args.debug and search.output_objects: - print '**warning: search looked up output objects**' - no_results = False - print '-' * 79 - print_result(result, moves=moves) - # XXX: Support more than one result - break - - if args.debug: - print - print 'Done' - - if no_results: - print 'Illegal move combination.' - - return (not no_results) + return result if __name__ == '__main__': sys.exit(not main(sys.argv[1:]))