Document job exception handling machinations in comments and remove an
except Exception: case in job.run_test() that was impossible to reach.

Allow error.JobError raised from a test (run via run_test or run_group)
to be recorded as an ABORT and passed on up to actually ABORT things.

Signed-off-by: Gregory Smith <gps@google.com>



git-svn-id: http://test.kernel.org/svn/autotest/trunk@2522 592f7852-d20e-0410-864c-8624ca9c26a4
diff --git a/client/bin/job.py b/client/bin/job.py
index c104485..6cf19dc 100755
--- a/client/bin/job.py
+++ b/client/bin/job.py
@@ -384,8 +384,14 @@
                 pid = parallel.fork_start(self.resultdir, l)
                 parallel.fork_waitfor(self.resultdir, pid)
             except error.TestBaseException:
+                # These are already classified with an error type (exit_status)
                 raise
+            except error.JobError:
+                raise  # Caught further up and turned into an ABORT.
             except Exception, e:
+                # Converts all other exceptions thrown by the test regardless
+                # of phase into a TestError(TestBaseException) subclass that
+                # reports them with their full stack trace.
                 raise error.UnhandledTestError(e)
         finally:
             # Reset the logging level to client level
@@ -470,11 +476,9 @@
             try:
                 self._runtest(url, tag, args, dargs)
             except error.TestBaseException, detail:
+                # The error is already classified, record it properly.
                 self.record(detail.exit_status, subdir, testname, str(detail))
                 raise
-            except Exception, detail:
-                self.record('FAIL', subdir, testname, str(detail))
-                raise
             else:
                 self.record('GOOD', subdir, testname, 'completed successfully')
 
@@ -483,9 +487,13 @@
             if container:
                 self.release_container()
             return True
-        except error.TestBaseException, e:
+        except error.TestBaseException:
             return False
         # Any other exception here will be given to the caller
+        #
+        # NOTE: The only exception possible from the control file here
+        # is error.JobError as _runtest() turns all others into an
+        # UnhandledTestError that is caught above.
 
 
     def _rungroup(self, subdir, testname, function, *args, **dargs):
@@ -513,10 +521,19 @@
             self._decrement_group_level()
             self.record('END %s' % e.exit_status, subdir, testname, str(e))
             raise
+        except error.JobError, e:
+            self._decrement_group_level()
+            self.record('END ABORT', subdir, testname, str(e))
+            raise
         except Exception, e:
+            # This should only ever happen due to a bug in the given
+            # function's code.  The common case of being called by
+            # run_test() will never reach this.  If a control file called
+            # run_group() itself, bugs in its function will be caught
+            # here.
             self._decrement_group_level()
             err_msg = str(e) + '\n' + traceback.format_exc()
-            self.record('END FAIL', subdir, testname, err_msg)
+            self.record('END ERROR', subdir, testname, err_msg)
             raise
 
 
@@ -540,11 +557,12 @@
                                   function=function, **dargs)
         except error.TestError:
             pass
-        # if there was a non-TestError exception, raise it
+        # if there was a non-TestError exception, re-raise it as a TestError
         except Exception, e:
             exc_info = sys.exc_info()
             err = ''.join(traceback.format_exception(*exc_info))
-            raise error.TestError(name + ' failed\n' + err)
+            del exc_info
+            raise error.TestError('%s failed:\n%s' % (name, err))
 
 
     _RUN_NUMBER_STATE = '__run_number'
@@ -1221,7 +1239,8 @@
                 myjob.record('ABORT', None, command, instance.args[0])
             myjob._decrement_group_level()
             myjob.record('END ABORT', None, None, instance.args[0])
-            assert(myjob.group_level == 0)
+            assert (myjob.group_level == 0), ('myjob.group_level must be 0,'
+                                              ' not %d' % myjob.group_level)
             myjob.complete(1)
         else:
             sys.exit(1)
diff --git a/client/common_lib/error.py b/client/common_lib/error.py
index 16b742d..8c002aa 100644
--- a/client/common_lib/error.py
+++ b/client/common_lib/error.py
@@ -42,33 +42,30 @@
 
 class TestBaseException(AutotestError):
     """The parent of all test exceptions."""
-    pass
+    # Children are required to override this.  Never instantiate directly.
+    exit_status="NEVER_RAISE_THIS"
 
 
 class TestError(TestBaseException):
     """Indicates that something went wrong with the test harness itself."""
     exit_status="ERROR"
-    pass
 
 
 class TestNAError(TestBaseException):
     """Indictates that the test is Not Applicable.  Should be thrown
     when various conditions are such that the test is inappropriate."""
     exit_status="TEST_NA"
-    pass
 
 
 class TestFail(TestBaseException):
     """Indicates that the test failed, but the job will not continue."""
     exit_status="FAIL"
-    pass
 
 
 class TestWarn(TestBaseException):
     """Indicates that bad things (may) have happened, but not an explicit
     failure."""
     exit_status="WARN"
-    pass
 
 
 class UnhandledTestError(TestError):
@@ -138,6 +135,7 @@
 
 
 class AutotestRunError(AutotestError):
+    """Indicates a problem running server side control files."""
     pass