From 33f4b58f779915bf9a9b39fb475de001eb815292 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=A9rome=20Perrin?= <jerome@nexedi.com>
Date: Thu, 6 Dec 2007 20:37:25 +0000
Subject: [PATCH] add a new -D option to drop in debugger on error and
 failures.

git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@18097 20353a03-c40f-0410-a6d1-a30d3c3de9de
---
 product/ERP5Type/tests/runUnitTest.py | 67 +++++++++++++++++++++++----
 1 file changed, 58 insertions(+), 9 deletions(-)

diff --git a/product/ERP5Type/tests/runUnitTest.py b/product/ERP5Type/tests/runUnitTest.py
index 04a5f58dce..7408e27e4b 100755
--- a/product/ERP5Type/tests/runUnitTest.py
+++ b/product/ERP5Type/tests/runUnitTest.py
@@ -1,6 +1,7 @@
 #!/usr/bin/python2.4
 import os
 import sys
+import pdb
 import re
 import getopt
 import unittest
@@ -57,6 +58,8 @@ Options:
                              Run only specified test methods delimited with
                              commas (e.g. testFoo,testBar). This can be regular
                              expressions.
+  -D                         
+                             Invoke debugger on errors / failures.
 """
 
 def getUnitTestFile():
@@ -64,6 +67,7 @@ def getUnitTestFile():
   This is used by template tool to run unit tests."""
   return os.path.abspath(__file__)
 
+
 def initializeInstanceHome(tests_framework_home,
                            real_instance_home,
                            instance_home):
@@ -146,6 +150,7 @@ initializeInstanceHome(tests_framework_home, real_instance_home, instance_home)
 if '__INSTANCE_HOME' not in globals().keys() :
   __INSTANCE_HOME = instance_home
 
+
 class ERP5TypeTestLoader(unittest.TestLoader):
   """Load test cases from the name passed on the command line.
   """
@@ -171,7 +176,39 @@ class ERP5TypeTestLoader(unittest.TestLoader):
       return module.test_suite()
     return unittest.TestLoader.loadTestsFromModule(self, module)
 
-def runUnitTestList(test_list, verbosity=1):
+
+class DebugTestResult:
+  """Wrap an unittest.TestResult, invoking pdb on errors / failures
+  """
+  def __init__(self, result):
+    self.result = result
+
+  def _start_debugger(self, tb):
+    try:
+      # try ipython if available
+      import IPython
+      IPython.Shell.IPShell(argv=[])
+      p = IPython.Debugger.Pdb(color_scheme='Linux')
+      p.reset()
+      while tb.tb_next is not None:
+        tb = tb.tb_next
+      p.interaction(tb.tb_frame, tb)
+    except ImportError:
+      pdb.post_mortem(tb)
+
+  def addError(self, test, err):
+    self._start_debugger(err[2])
+    self.result.addError(test, err)
+
+  def addFailure(self, test, err):
+    self._start_debugger(err[2])
+    self.result.addFailure(test, err)
+
+  def __getattr__(self, attr):
+    return getattr(self.result, attr)
+
+
+def runUnitTestList(test_list, verbosity=1, debug=0):
   if not test_list:
     print "No test to run, exiting immediately."
     return
@@ -257,17 +294,24 @@ def runUnitTestList(test_list, verbosity=1):
   
   suite = ERP5TypeTestLoader(save=save).loadTestsFromNames(test_list)
 
-  # Hack the profiler to run only specified test methods.
-  run_only = os.environ.get('run_only')
-  if run_only and not save:
+  # Hack the profiler to run only specified test methods, and wrap results when
+  # running in debug mode.
+  run_only = os.environ.get('run_only', '')
+  if not save:
     test_method_list = run_only.split(',')
     from Testing.ZopeTestCase import profiler
     run_orig = profiler.Profiled.__call__
-    def run(self, *args, **kw):
+    
+    def run(self, result=None):
+      if debug and result:
+        result = DebugTestResult(result)
+      if not test_method_list:
+        return run_orig(self, result)
       test_method_name = self.id().rsplit('.', 1)[-1]
       for valid_test_method_name_re in test_method_list:
         if re.search(valid_test_method_name_re, test_method_name):
-          return run_orig(self, *args, **kw)
+          return run_orig(self, result)
+
     profiler.Profiled.__call__ = run
 
   # change current directory to the test home, to create zLOG.log in this dir.
@@ -284,7 +328,7 @@ def usage(stream, msg=None):
 def main():
   try:
     opts, args = getopt.getopt(sys.argv[1:],
-        "hpv", ["help", "verbose", "profile", "portal_id=", "data_fs_path=",
+        "hpvD", ["help", "verbose", "profile", "portal_id=", "data_fs_path=",
         "bt5_path=",
         "recreate_catalog=", "erp5_sql_connection_string=",
         "cmf_activity_sql_connection_string=",
@@ -303,6 +347,7 @@ def main():
 
   os.environ["erp5_tests_recreate_catalog"] = "0"
   verbosity = 1
+  debug = 0
   
   for opt, arg in opts:
     if opt in ("-v", "--verbose"):
@@ -311,7 +356,9 @@ def main():
     elif opt in ("-h", "--help"):
       usage(sys.stdout)
       sys.exit()
-    if opt in ("-p", "--profile"):
+    elif opt == '-D':
+      debug = 1
+    elif opt in ("-p", "--profile"):
       os.environ['PROFILE_TESTS'] = "1"
       # profiling of setup and teardown is disabled by default, just set
       # environment variables yourself if you want to enable them, but keep in
@@ -350,7 +397,9 @@ def main():
     print "No test to run, exiting immediately."
     sys.exit(1)
 
-  result = runUnitTestList(test_list=test_list, verbosity=verbosity)
+  result = runUnitTestList(test_list=test_list,
+                           verbosity=verbosity,
+                           debug=debug)
   from Testing.ZopeTestCase import profiler
   profiler.print_stats()
   sys.exit(len(result.failures) + len(result.errors))
-- 
2.30.9