Index: shunit2
===================================================================
--- shunit2	(revision 337)
+++ shunit2	(working copy)
@@ -16,7 +16,7 @@
 # return if shunit already loaded
 [ -n "${SHUNIT_VERSION:-}" ] && exit 0
 
-SHUNIT_VERSION='2.1.6'
+SHUNIT_VERSION='2.1.6plugin'
 
 SHUNIT_TRUE=0
 SHUNIT_FALSE=1
@@ -758,7 +758,7 @@
     # add 128 to signal and exit
     exit `expr ${_shunit_signal_} + 128`
   elif [ ${__shunit_reportGenerated} -eq ${SHUNIT_FALSE} ] ; then
-    _shunit_assertFail 'Unknown failure encountered running a test'
+    _shunit_assertFail 'Unknown failure encountered running a test prevented report generation'
     _shunit_generateReport
     exit ${SHUNIT_ERROR}
   fi
@@ -778,16 +778,19 @@
     # disable skipping
     endSkipping
 
-    # execute the per-test setup function
-    setUp
+    # Declare the last test status seen.  It begins at Unspecified.
+    # The various assert functions can move it to a more serious status
+    # (e.g. from Unspecified to Pass, or from Pass to Fail), but not to
+    # a less serious status (the status of a test never goes from Fail
+    # to Pass).
+    _shunit_last_test_status='U'
 
-    # execute the test
-    echo "${_shunit_test_}"
-    eval ${_shunit_test_}
+    # A plugin performs the actual setUp, exec, tearDown of the
+    # test so that the plugin may capture the output or other
+    # information if desired.  It is assumed that the plugin
+    # will perform setUp, the test and tearDown in order.
+    shunit_plugin_execute "${_shunit_test_}"
 
-    # execute the per-test tear-down function
-    tearDown
-
     # update stats
     if [ ${__shunit_testSuccess} -eq ${SHUNIT_TRUE} ]; then
       __shunit_testsPassed=`expr ${__shunit_testsPassed} + 1`
@@ -858,6 +861,41 @@
   _shunit_assertSkip
 }
 
+####
+# global variable _shunit_last_test_status:
+#
+# When you run a test, it performs one or more assertions.
+# _shunit_last_test_status collects the "most serious" result from all the
+# assertions performed in a single function.  This status is then
+# available for a plugin to report.
+#
+# The possible status values are:
+#   U unspecified - we do not have a status for this test yet
+#   S skipped - the test is skipped
+#   P pass - the test passed
+#   F fail - the test failed
+#
+# These are listed in order of seriousness.  An assert function can set the
+# status to a more serious value, but not a less serious value.  That is,
+# if the first assertion fails, a second assertion cannot change the test
+# status back to P even if the assertion is true.
+#
+# If you somehow run a whole test and the status is still U, then there
+# were no assertions and you can assume the test passed.  A plugin would
+# have to make this conversion itself.
+#
+# n.b. status values not on this list are all more serious than anything
+# that is listed here.  The shunit2-to-Pandokia plugin implements a
+# status of
+#   E error - an error running the test
+# that is more serious than any other status.  Other than being mentioned
+# here, this value is not known or used within shunit, though.
+# 
+
+# this is here to prevent crashing in the event that somebody calls an
+# assert function without running a test first
+_shunit_last_test_status='U'
+
 # Records a successful test.
 #
 # Args:
@@ -866,6 +904,13 @@
 {
   __shunit_assertsPassed=`expr ${__shunit_assertsPassed} + 1`
   __shunit_assertsTotal=`expr ${__shunit_assertsTotal} + 1`
+
+  # this aspect of the test passed - we may escalate the test status
+  # from Unspecified or Skip to Pass
+  case "$_shunit_last_test_status"
+  in
+  [US]) _shunit_last_test_status='P' ;;
+  esac
 }
 
 # Records a test failure.
@@ -881,6 +926,12 @@
   __shunit_assertsTotal=`expr ${__shunit_assertsTotal} + 1`
   echo "${__SHUNIT_ASSERT_MSG_PREFIX}${_shunit_msg_}"
 
+  # this aspect of the test failed - we may escalate the status to Fail, but only
+  # if nothing worse has happened yet
+  case "$_shunit_last_test_status"
+  in
+  [USP]) _shunit_last_test_status='F' ;;
+  esac
   unset _shunit_msg_
 }
 
@@ -892,8 +943,17 @@
 {
   __shunit_assertsSkipped=`expr ${__shunit_assertsSkipped} + 1`
   __shunit_assertsTotal=`expr ${__shunit_assertsTotal} + 1`
+  # the test is being skipped - we may escalate the status to Skip, but 
+  # only if we have no status reported yet.  i.e. If a test runs an assert
+  # and then calls out a skip, the test status is pass/fail according to
+  # how the assert worked out.
+  case "$_shunit_last_test_status"
+  in
+  [U]) _shunit_last_test_status='S' ;;
+  esac
 }
 
+
 # Prepare a script filename for sourcing.
 #
 # Args:
@@ -976,18 +1036,136 @@
 }
 
 #------------------------------------------------------------------------------
+# This version of shunit2 has a plugin architecture to allow different types
+# of reporting.  Here are the default plugin functions.  A plugin will
+# override these by redefining the functions it needs changed.
+#
+# To provide a plugin named "xyzzy", create a shell script named
+# "shunit2_plugin_xyzzy" that contains function definitions for any of
+# these functions that you want to override.
+#
+# To invoke your plugin, use "shunit2 --plugin xyzzy tests.sh" or 
+# "export SHUNIT2_PLUGIN=xyzzy ; ./tests.sh"
+#
+# The expectation is that new plugin features may define new default
+# functions, but that old plugins that do not know about them will
+# continue to work with the new defaults.  So, your plugin may
+# omit functions that it does not need to redefine.
+
+#---------------------------------------
+# parse plugin-specific argv values; if you consume some parameters from
+# argv, set shunit_plugin_argv_return to a string that the caller
+# can eval to shift away the argv values that you used.  For example,
+# if you used two parameters, you might set shunit_plugin_argv_return
+# to "shift; shift"
+
+shunit_plugin_argv_return=true
+shunit_plugin_argv() {
+  # nothing to do here.
+  shunit_plugin_argv_return=true
+}
+
+#---------------------------------------
+# initialize the plugin.  Happens after shunit_plugin_argv
+# Some uses of shunit may cause this to be initialized more than once.
+# If this is a problem for your plugin, guard against it in this function.
+
+shunit_plugin_init() {
+  :
+}
+
+
+#---------------------------------------
+# actually run the test.  Happens once for each test function.
+#
+# This is the standard way to run the test.  If a plugin provides this
+# function, it MUST do these steps, but it may also gather information
+# that this function does not.  For example, the shunit-to-Pandokia plugin
+# gathers the start time, end time, and stdout/stderr of the test.
+# A Junit/XML plugin might gather the duration of the test.
+#
+
+shunit_plugin_execute() {
+  # execute the per-test setup function
+  setUp
+
+  # execute the test
+  echo "$1"
+  eval $1
+
+  # execute the per-test tear-down function
+  tearDown
+}
+
+#---------------------------------------
+# final report
+
+shunit_plugin_final_report() {
+  # not quite sure what this is about, but this is what shunit2
+  # 2.1.6 did at this stage  - Mark S.
+  _shunit_generateReport
+}
+
+#---------------------------------------
+# perform whatever you need to do at the very end of the test run.
+
+shunit_plugin_finish() {
+  # shunit2 has no finalization
+  :
+}
+
+
+
+#------------------------------------------------------------------------------
 # main
 #
-
 # determine the operating mode
 if [ $# -eq 0 ]; then
+
   __shunit_script=${__SHUNIT_PARENT}
   __shunit_mode=${__SHUNIT_MODE_SOURCED}
+
+  if [ "${SHUNIT_PLUGIN:-}" != "" ]
+  then
+    . shunit2_plugin_$SHUNIT_PLUGIN
+  fi
+
+  # some plugins may not work in __SHUNIT_MODE_SOURCED yet - 
+  # the init/finalize may be called more than once if shunit is calling 
+  # itself recursively.
+  shunit_plugin_init
+
 else
+
+  # see if they specified a plugin
+  if [ "$1" = "--plugin" ]
+  then
+    # note the plugin name for anything that may want to know about it later
+    SHUNIT_PLUGIN=$2
+    export SHUNIT_PLUGIN
+
+    # shift away the args
+    shift ; shift
+
+    # load the plugin; this writes over anything that the plugin wants to,
+    # but we generally assume it will only be the shunit_plugin_* funcs
+    . shunit2_plugin_$SHUNIT_PLUGIN
+  fi
+
+  # offer the plugin an opportunity to pick up additional args from the
+  # command line
+  shunit_plugin_argv_return=true
+  shunit_plugin_argv $*
+  eval $shunit_plugin_argv_return
+
   __shunit_script=$1
   [ -r "${__shunit_script}" ] || \
       _shunit_fatal "unable to read from ${__shunit_script}"
   __shunit_mode=${__SHUNIT_MODE_STANDALONE}
+
+  # initialize the plugin (this is a nop if the plugin does not need it)
+  shunit_plugin_init
+
 fi
 
 # create a temporary storage location
@@ -1040,9 +1218,12 @@
 # execute the oneTimeTearDown function (if it exists)
 oneTimeTearDown
 
-# generate the report
-_shunit_generateReport
+# ask the plugin to do any final reporting
+shunit_plugin_final_report
 
+# ask the plugin to finish itself
+shunit_plugin_finish
+
 # that's it folks
 [ ${__shunit_testsFailed} -eq 0 ]
 exit $?
