Integration and coverage tests. (Fixes issue #2)
authorJoe Wreschnig <joe.wreschnig@gmail.com>
Sat, 20 Mar 2010 09:14:40 +0000 (02:14 -0700)
committerJoe Wreschnig <joe.wreschnig@gmail.com>
Sat, 20 Mar 2010 09:14:40 +0000 (02:14 -0700)
MANIFEST.in
bulletml/parser.py
setup.py
tests/__init__.py [new file with mode: 0644]
tests/test_examples.py [new file with mode: 0644]

index 9b06366..ea8405a 100644 (file)
@@ -1,4 +1,5 @@
 include examples/*.xml
 include examples/*/*.xml
 include examples/*.xml
 include examples/*/*.xml
+include tests/*.py
 include README.txt
 
 include README.txt
 
index 9d515a2..5435925 100644 (file)
@@ -361,9 +361,6 @@ class Accel(object):
 class BulletDef(object):
     """Bullet definition."""
 
 class BulletDef(object):
     """Bullet definition."""
 
-    direction = None
-    speed = None
-
     def __init__(self, actions=(), direction=None, speed=None, tags=()):
         self.direction = direction
         self.speed = speed
     def __init__(self, actions=(), direction=None, speed=None, tags=()):
         self.direction = direction
         self.speed = speed
@@ -557,7 +554,9 @@ class Offset(object):
 
     def __setstate__(self, state):
         state = dict(state)
 
     def __setstate__(self, state):
         state = dict(state)
-        self.__init__(state["type"], state.get("x"), state.get("y"))
+        x = NumberDef(state["x"]) if "x" in state else None
+        y = NumberDef(state["y"]) if "y" in state else None
+        self.__init__(state["type"], x, y)
 
     @classmethod
     def FromXML(cls, doc, element):
 
     @classmethod
     def FromXML(cls, doc, element):
index 1b383c2..5784907 100755 (executable)
--- a/setup.py
+++ b/setup.py
@@ -3,9 +3,11 @@
 import glob
 import os
 import shutil
 import glob
 import os
 import shutil
+import sys
 
 
-from distutils.core import setup
+from distutils.core import setup, Command
 from distutils.command.clean import clean as distutils_clean
 from distutils.command.clean import clean as distutils_clean
+from distutils.command.sdist import sdist as distutils_sdist
 
 class clean(distutils_clean):
     def run(self):
 
 class clean(distutils_clean):
     def run(self):
@@ -22,8 +24,8 @@ class clean(distutils_clean):
         for pathname, dirs, files in os.walk(os.path.dirname(__file__)):
             for filename in filter(should_remove, files):
                 try: os.unlink(os.path.join(pathname, filename))
         for pathname, dirs, files in os.walk(os.path.dirname(__file__)):
             for filename in filter(should_remove, files):
                 try: os.unlink(os.path.join(pathname, filename))
-                except EnvironmentError, err:
-                    print str(err)
+                except EnvironmentError as err:
+                    print(str(err))
 
         try: os.unlink("MANIFEST")
         except OSError: pass
 
         try: os.unlink("MANIFEST")
         except OSError: pass
@@ -33,10 +35,77 @@ class clean(distutils_clean):
              if os.path.isdir(path):
                  shutil.rmtree(path)
 
              if os.path.isdir(path):
                  shutil.rmtree(path)
 
+class coverage_cmd(Command):
+    description = "generate test coverage data"
+    user_options = []
+
+    def initialize_options(self):
+        pass
+    
+    def finalize_options(self):
+        pass
+
+    def run(self):
+        import trace
+        tracer = trace.Trace(
+            count=True, trace=False,
+            ignoredirs=[sys.prefix, sys.exec_prefix])
+        def run_tests():
+            import bulletml
+            try:
+                reload(bulletml)
+            except NameError:
+                pass
+            self.run_command("test")
+        tracer.runfunc(run_tests)
+        results = tracer.results()
+        coverage = os.path.join(os.path.dirname(__file__), "coverage")
+        results.write_results(show_missing=True, coverdir=coverage)
+        map(os.unlink, glob.glob(os.path.join(coverage, "[!b]*.cover")))
+        try: os.unlink(os.path.join(coverage, "..setup.cover"))
+        except OSError: pass
+
+        total_lines = 0
+        bad_lines = 0
+        for filename in glob.glob(os.path.join(coverage, "*.cover")):
+            lines = open(filename, "rU").readlines()
+            total_lines += len(lines)
+            bad_lines += len(
+                [line for line in lines if
+                 (line.startswith(">>>>>>") and
+                  "finally:" not in line and '"""' not in line)])
+        pct = 100.0 * (total_lines - bad_lines) / float(total_lines)
+        print("Coverage data written to %s (%d/%d, %0.2f%%)" % (
+            coverage, total_lines - bad_lines, total_lines, pct))
+
+class sdist(distutils_sdist):
+    def run(self):
+        self.run_command("test")
+        distutils_sdist.run(self)
+
+class test_cmd(Command):
+    description = "run automated tests"
+    user_options = [
+        ("to-run=", None, "list of tests to run (default all)"),
+        ]
+
+    def initialize_options(self):
+        self.to_run = []
+        self.quick = False
+
+    def finalize_options(self):
+        if self.to_run:
+            self.to_run = self.to_run.split(",")
+
+    def run(self):
+        import tests
+        if tests.unit(self.to_run):
+            raise SystemExit("Test failures are listed above.")
+
 if __name__ == "__main__":
 if __name__ == "__main__":
-    from bulletml import VERSION_STRING
-    setup(cmdclass=dict(clean=clean),
-          name="python-bulletml", version=VERSION_STRING,
+    setup(cmdclass=dict(
+            clean=clean, test=test_cmd, coverage=coverage_cmd, sdist=sdist),
+          name="python-bulletml", version="0.1",
           url="http://code.google.com/p/python-bulletml/",
           description="parse and run BulletML scripts",
           author="Joe Wreschnig",
           url="http://code.google.com/p/python-bulletml/",
           description="parse and run BulletML scripts",
           author="Joe Wreschnig",
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644 (file)
index 0000000..8640d5a
--- /dev/null
@@ -0,0 +1,72 @@
+"""Generic test runner.
+
+This module scans for filenames begining with test_ and runs the tests
+in them. It is less noisy than the default Python test runners.
+"""
+
+from __future__ import division
+from __future__ import print_function
+
+import glob
+import os
+import sys
+import unittest
+
+from unittest import TestCase
+
+suites = []
+add = suites.append
+
+for name in glob.glob(os.path.join(os.path.dirname(__file__), "test_*.py")):
+    module = "tests." + os.path.basename(name)
+    __import__(module[:-3], {}, {}, [])
+
+class Result(unittest.TestResult):
+
+    separator1 = '=' * 70
+    separator2 = '-' * 70
+
+    def addSuccess(self, test):
+        unittest.TestResult.addSuccess(self, test)
+        sys.stdout.write('.')
+
+    def addError(self, test, err):
+        unittest.TestResult.addError(self, test, err)
+        sys.stdout.write('E')
+
+    def addFailure(self, test, err):
+        unittest.TestResult.addFailure(self, test, err)
+        sys.stdout.write('F')
+
+    def printErrors(self):
+        succ = self.testsRun - (len(self.errors) + len(self.failures))
+        v = "%3d" % succ
+        count = 50 - self.testsRun
+        sys.stdout.write((" " * count) + v + "\n")
+        self.printErrorList('ERROR', self.errors)
+        self.printErrorList('FAIL', self.failures)
+
+    def printErrorList(self, flavour, errors):
+        for test, err in errors:
+            sys.stdout.write(self.separator1 + "\n")
+            sys.stdout.write("%s: %s\n" % (flavour, str(test)))
+            sys.stdout.write(self.separator2 + "\n")
+            sys.stdout.write("%s\n" % err)
+
+class Runner(object):
+    def run(self, test):
+        suite = unittest.makeSuite(test)
+        pref = '%s (%d): ' % (test.__name__, len(suite._tests))
+        print(pref + " " * (25 - len(pref)), end=" ")
+        result = Result()
+        suite(result)
+        result.printErrors()
+        return bool(result.failures + result.errors)
+
+def unit(run=[]):
+    runner = Runner()
+    failures = False
+    for test in suites:
+        if not run or test.__name__ in run or test.__name__.lstrip("T") in run:
+            failures |= runner.run(test)
+    return failures
diff --git a/tests/test_examples.py b/tests/test_examples.py
new file mode 100644 (file)
index 0000000..d9d59d7
--- /dev/null
@@ -0,0 +1,54 @@
+import os
+import glob
+
+from bulletml import BulletML, Bullet, bulletyaml
+from tests import TestCase, add
+
+class Texamples_xml(TestCase):
+    pass
+
+class Texamples_yaml(TestCase):
+    pass
+
+class Texamples_repr(TestCase):
+    pass
+
+class Texamples_run(TestCase):
+    pass
+
+for filename in glob.glob("examples/*/*.xml"):
+    basename = os.path.basename(filename)[:-4].replace("-", "_")
+
+    def test_xml(self, filename=filename):
+        BulletML.FromDocument(open(filename, "rU"))
+    setattr(Texamples_xml, "test_" + basename, test_xml)
+
+    try:
+        import yaml
+    except ImportError:
+        pass
+    else:
+        def test_yaml(self, filename=filename):
+            doc = BulletML.FromDocument(open(filename, "rU"))
+            doc = yaml.load(yaml.dump(doc))
+            doc = yaml.load(yaml.dump(doc))
+        setattr(Texamples_yaml, "test_" + basename, test_yaml)
+
+    def test_repr(self, filename=filename):
+        doc = BulletML.FromDocument(open(filename, "rU"))
+        repr(doc)
+    setattr(Texamples_repr, "test_" + basename, test_repr)
+
+    def test_run(self, filename=filename):
+        doc = BulletML.FromDocument(open(filename, "rU"))
+        bullets = [Bullet.FromDocument(doc)]
+        for i in range(100):
+            for bullet in bullets:
+                bullets.extend(bullet.step())
+    setattr(Texamples_run, "test_" + basename, test_run)
+                
+
+add(Texamples_xml)
+add(Texamples_yaml)
+add(Texamples_repr)
+add(Texamples_run)