From 734953fdb34c0ca7ed804a660a385c619faabc7c Mon Sep 17 00:00:00 2001 From: Jacob Repp <jacobrepp@gmail.com> Date: Sat, 25 Feb 2017 07:09:31 -0800 Subject: [PATCH] Pushing latest fixes and features to dev (#46) * Adding 2017 to LICENSE * Adding inital test suite * Fix incorrect header argument * Adding game metadata support (MMR, etc) --- .gitignore | 1 + LICENSE | 2 +- s2protocol/__init__.py | 1 + s2protocol/s2_cli.py | 30 +++++---------- s2protocol/versions/__init__.py | 19 +++++---- tests/suite.py | 68 +++++++++++++++++++++++++++++++++ tests/test_versions.py | 18 +++++++++ 7 files changed, 109 insertions(+), 30 deletions(-) create mode 100644 tests/suite.py create mode 100644 tests/test_versions.py diff --git a/.gitignore b/.gitignore index 5e5ba13..7879315 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ *.swp *.swo +*~ *.SC2Replay diff --git a/LICENSE b/LICENSE index 08ffbea..eaa445c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2013 Blizzard Entertainment +Copyright (c) 2013, 2017 Blizzard Entertainment Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/s2protocol/__init__.py b/s2protocol/__init__.py index 54f0b87..dad74cc 100644 --- a/s2protocol/__init__.py +++ b/s2protocol/__init__.py @@ -1,2 +1,3 @@ __author__ = 'Blizzard Entertainment' __version__ = (1, 0, 1, 'dev') +__all__ = [ 'versions', 'diff', 's2_cli' ] diff --git a/s2protocol/s2_cli.py b/s2protocol/s2_cli.py index 0deb12d..4b4b65a 100755 --- a/s2protocol/s2_cli.py +++ b/s2protocol/s2_cli.py @@ -1,24 +1,4 @@ #!/usr/bin/env python -# -# Copyright (c) 2013 Blizzard Entertainment -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. import sys import argparse @@ -52,6 +32,7 @@ def process(self, event): print >> self._output, json.dumps(event, encoding='ISO-8859-1', ensure_ascii=True, indent=4) return event + class NDJSONOutputFilter(EventFilter): """ Added as a filter will format the event into NDJSON """ def __init__(self, output): @@ -171,6 +152,8 @@ def main(): action="store_true") parser.add_argument("--header", help="print protocol header", action="store_true") + parser.add_argument("--metadata", help="print game metadata", + action="store_true") parser.add_argument("--details", help="print protocol details", action="store_true") parser.add_argument("--initdata", help="print protocol initdata", @@ -249,7 +232,7 @@ def process_event(event): contents = archive.header['user_data_header']['content'] header = versions.latest().decode_replay_header(contents) if args.header: - process_event(args.header) + process_event(header) # The header's baseBuild determines which protocol to use baseBuild = header['m_version']['m_baseBuild'] @@ -259,6 +242,11 @@ def process_event(event): print >> sys.stderr, 'Unsupported base build: %d' % baseBuild sys.exit(1) + # Process game metadata + if args.all or args.metadata: + contents = archive.read_file('replay.gamemetadata.json') + process_event(json.loads(contents)) + # Print protocol details if args.all or args.details: contents = archive.read_file('replay.details') diff --git a/s2protocol/versions/__init__.py b/s2protocol/versions/__init__.py index 120f23c..385d093 100644 --- a/s2protocol/versions/__init__.py +++ b/s2protocol/versions/__init__.py @@ -1,26 +1,29 @@ + import os import re import imp import sys -def import_protocol(base_path, module_name): +def _import_protocol(base_path, protocol_module_name): """ Import a module from a base path, used to import protocol modules. """ # Try to return the module if it's been loaded already try: - return sys.modules[module_name] + return sys.modules[protocol_module_name] except KeyError: pass - # print 'importing', module_name, 'from', base_path # If any of the following calls raises an exception, # there's a problem we can't handle -- let the caller handle it. - fp, pathname, description = imp.find_module(module_name, [base_path]) + # + # Without the full module name in the load, the 'import decoders' will fail + # + fp, pathname, description = imp.find_module(protocol_module_name, [base_path]) try: - return imp.load_module(module_name, fp, pathname, description) + return imp.load_module('s2protocol.versions.' + protocol_module_name, fp, pathname, description) finally: # Since we may exit via an exception, close fp explicitly. if fp: @@ -29,7 +32,7 @@ def import_protocol(base_path, module_name): def list_all(base_path=None): """ - Returns a list of the current protocol version file names in the versions module. + Returns a list of the current protocol version file names in the versions module sorted by name. """ if base_path is None: base_path = os.path.dirname(__file__) @@ -58,7 +61,7 @@ def latest(): module_name = latest_version.split('.')[0] # Perform the import - return import_protocol(base_path, module_name) + return _import_protocol(base_path, module_name) @@ -67,5 +70,5 @@ def build(build_version): Get the module for a specific build version """ base_path = os.path.dirname(__file__) - return import_protocol(base_path, 'protocol{0}'.format(build_version)) + return _import_protocol(base_path, 'protocol{0}'.format(build_version)) diff --git a/tests/suite.py b/tests/suite.py new file mode 100644 index 0000000..c887b83 --- /dev/null +++ b/tests/suite.py @@ -0,0 +1,68 @@ +#!/usr/bin/python + +import sys +import unittest +import os +import inspect +import re + +from optparse import OptionParser + +# +# Fix up import path if running directly +# +if __name__ == '__main__': + filename = os.path.abspath(inspect.getfile(inspect.currentframe())) + thispath = os.path.dirname(filename) + normpath = os.path.normpath(os.path.join(thispath, os.pardir)) + sys.path.insert(0, normpath) + +import s2protocol +import test_versions + + +def run(): + parser = OptionParser() + parser.add_option('-l', '--list', dest='list', action='store_true', + help='List all test cases') + parser.add_option('-r', '--requests', dest='requests', action='store_true', + help='Enable logging of requests') + parser.add_option('-v', '--verbose', dest='verbose', action='store_true', + help='Enable verbose logging') + parser.add_option('-f', '--filter', dest='filter', type='string', action='store', + help='Filter test cases with a regular expression') + options, args = parser.parse_args() + + all_tests = [ + test_versions.suite(), + ] + + if options.list: + for suite in all_tests: + for t in suite: + print t.id() + return + + if options.filter: + pattern = re.compile(options.filter) + def expand_tests(): + for suite in all_tests: + for t in suite: + yield t + + def pattern_match(test): + name = test.id() + return pattern.match(name) is not None + all_tests = unittest.TestSuite(filter(pattern_match, expand_tests())) + else: + all_tests = unittest.TestSuite(all_tests) + + test_verbosity = 1 + if options.verbose: + test_verbosity = 3 + + unittest.TextTestRunner(verbosity=test_verbosity, failfast=True).run(all_tests) + +if __name__ == '__main__': + run() + diff --git a/tests/test_versions.py b/tests/test_versions.py new file mode 100644 index 0000000..8c42770 --- /dev/null +++ b/tests/test_versions.py @@ -0,0 +1,18 @@ +import unittest +from s2protocol import versions as _versions + +class VersionsTestCase(unittest.TestCase): + def test_latest(self): + p = _versions.latest() + self.assertIsNotNone(p) + + def test_specific(self): + p = _versions.build(49716) + self.assertIsNotNone(p) + + def test_missing(self): + self.assertRaises(ImportError, lambda: _versions.build(42)) + + +def suite(): + return unittest.TestLoader().loadTestsFromTestCase(VersionsTestCase)