Release 1.4.0.
[cfd] / mdwsetup.py
index 06990a8..876297e 100644 (file)
@@ -28,12 +28,29 @@ from __future__ import with_statement
 import sys as SYS
 import os as OS
 import re as RE
 import sys as SYS
 import os as OS
 import re as RE
+import signal as SIG
 import subprocess as SUB
 
 import distutils.core as DC
 import distutils.log as DL
 
 ###--------------------------------------------------------------------------
 import subprocess as SUB
 
 import distutils.core as DC
 import distutils.log as DL
 
 ###--------------------------------------------------------------------------
+### Preliminaries.
+
+## Turn off Python's `SIGINT' handler.  If we get stuck in a native-code loop
+## then ^C will just set a flag that will be noticed by the main interpreter
+## loop if we ever get to it again.  And raising `SIGINT' is how Emacs `C-c
+## C-k' aborts a compilation, so this is really unsatisfactory.
+SIG.signal(SIG.SIGINT, SIG.SIG_DFL)
+
+###--------------------------------------------------------------------------
+### Compatibility hacks.
+
+def with_metaclass(meta, *supers):
+  return meta("#<anonymous base %s>" % meta.__name__,
+              supers or (object,), dict())
+
+###--------------------------------------------------------------------------
 ### Random utilities.
 
 def uniquify(seq):
 ### Random utilities.
 
 def uniquify(seq):
@@ -73,9 +90,12 @@ def progoutput(command):
   The COMMAND must produce exactly one line of output, and must exit with
   status zero.
   """
   The COMMAND must produce exactly one line of output, and must exit with
   status zero.
   """
-  kid = SUB.Popen(command, stdout = SUB.PIPE)
-  out = kid.stdout.readline()
-  junk = kid.stdout.read()
+  kid = SUB.Popen(command, stdout = SUB.PIPE, universal_newlines = True)
+  try:
+    out = kid.stdout.readline()
+    junk = kid.stdout.read(1)
+  finally:
+    kid.stdout.close()
   if junk != '': raise ValueError \
     ("Child process `%s' produced unspected output %r" % (command, junk))
   rc = kid.wait()
   if junk != '': raise ValueError \
     ("Child process `%s' produced unspected output %r" % (command, junk))
   rc = kid.wait()
@@ -224,7 +244,7 @@ class CommandClass (type):
     else: CMDS[name] = c
     return c
 
     else: CMDS[name] = c
     return c
 
-class Command (DC.Command, object):
+class Command (with_metaclass(CommandClass, DC.Command, object)):
   """
   Base class for `mdwsetup' command classes.
 
   """
   Base class for `mdwsetup' command classes.
 
@@ -247,9 +267,9 @@ class distdir (Command):
   description = "print the distribution directory name to stdout"
   def run(me):
     d = me.distribution
   description = "print the distribution directory name to stdout"
   def run(me):
     d = me.distribution
-    print '%s-%s' % (d.get_name(), d.get_version())
+    print('%s-%s' % (d.get_name(), d.get_version()))
 
 
-class build_gen(Command):
+class build_gen (Command):
   """
   Generate files, according to the `genfiles'.
 
   """
   Generate files, according to the `genfiles'.
 
@@ -278,7 +298,47 @@ class build (_build, Command):
   sub_commands = [('build_gen', lambda me: me.distribution.genfiles)]
   sub_commands += _build.sub_commands
 
   sub_commands = [('build_gen', lambda me: me.distribution.genfiles)]
   sub_commands += _build.sub_commands
 
-class clean_gen(Command):
+class test (Command):
+  """
+  Run unit tests, according to the `unittests'.
+
+  The `unittests' keyword argument to `setup' lists module names (or other
+  things acceptable to the `loadTestsFromNames' test-loader method) to be
+  run.  The build library directory is prepended to the load path before
+  running the tests to ensure that the newly built modules are tested.  If
+  `unittest_dir' is set, then this is appended to the load path so that test
+  modules can be found there.
+  """
+  NAME = "test"
+  description = "run the included test suite"
+
+  user_options = \
+    [('build-lib=', 'b', "directory containing compiled moules"),
+     ('tests=', 't', "tests to run"),
+     ('verbose-test', 'V', "run tests verbosely")]
+
+  def initialize_options(me):
+    me.build_lib = None
+    me.verbose_test = False
+    me.tests = None
+  def finalize_options(me):
+    me.set_undefined_options('build', ('build_lib', 'build_lib'))
+  def run(me):
+    import unittest as U
+    d = me.distribution
+    SYS.path = [me.build_lib] + SYS.path
+    if d.unittest_dir is not None: SYS.path.append(d.unittest_dir)
+    if me.tests is not None: tests = me.tests.split(",")
+    else: tests = d.unittests
+    suite = U.defaultTestLoader.loadTestsFromNames(tests)
+    runner = U.TextTestRunner(verbosity = me.verbose_test and 2 or 1)
+    if me.dry_run: return
+    result = runner.run(suite)
+    if result.errors or result.failures or \
+       getattr(result, "unexpectedSuccesses", 0):
+      SYS.exit(2)
+
+class clean_gen (Command):
   """
   Remove the generated files, as listed in `genfiles'.
 
   """
   Remove the generated files, as listed in `genfiles'.
 
@@ -290,7 +350,7 @@ class clean_gen(Command):
     d = me.distribution
     for g in d.genfiles: g.clean(dry_run_p = me.dry_run)
 
     d = me.distribution
     for g in d.genfiles: g.clean(dry_run_p = me.dry_run)
 
-class clean_others(Command):
+class clean_others (Command):
   """
   Remove the files listed in the `cleanfiles' argument to `setup'.
   """
   """
   Remove the files listed in the `cleanfiles' argument to `setup'.
   """
@@ -341,6 +401,8 @@ class Dist (DC.Distribution):
   ## our enhanced commands.
   def __init__(me, attrs = None):
     me.genfiles = []
   ## our enhanced commands.
   def __init__(me, attrs = None):
     me.genfiles = []
+    me.unittest_dir = None
+    me.unittests = []
     me.cleanfiles = []
     me._auto_version_p = False
     DC.Distribution.__init__(me, attrs)
     me.cleanfiles = []
     me._auto_version_p = False
     DC.Distribution.__init__(me, attrs)