Use nose skip exception conditionally

testtools and nose disagree on the exception raised for skipping
tests with python < 2.7 (see inline comments for exact details).

This monkey-patches testtools to recognise the nose skip exception.
However, due to nose being deprecated we hide this behind an
environment variable to make it an opt-in fix.

While we're there, modify tempest.test.api.utils @skip_unless_attr
decorator to raise from tempest's BaseTestCase rather than
testtools.TestCase directly.

Change-Id: I13a8f374e879799870bc9193a1ddbdc8d6abb73c
diff --git a/tempest/api/utils.py b/tempest/api/utils.py
index 0738201..69ab7fb 100644
--- a/tempest/api/utils.py
+++ b/tempest/api/utils.py
@@ -17,7 +17,7 @@
 
 """Common utilities used in testing."""
 
-from testtools import TestCase
+from tempest.test import BaseTestCase
 
 
 class skip_unless_attr(object):
@@ -32,7 +32,7 @@
             """Wrapped skipper function."""
             testobj = args[0]
             if not getattr(testobj, self.attr, False):
-                raise TestCase.skipException(self.message)
+                raise BaseTestCase.skipException(self.message)
             func(*args, **kw)
         _skipper.__name__ = func.__name__
         _skipper.__doc__ = func.__doc__
diff --git a/tempest/test.py b/tempest/test.py
index d7008a7..2e93056 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -15,6 +15,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import os
 import time
 
 import nose.plugins.attrib
@@ -54,6 +55,42 @@
     return decorator
 
 
+# there is a mis-match between nose and testtools for older pythons.
+# testtools will set skipException to be either
+# unittest.case.SkipTest, unittest2.case.SkipTest or an internal skip
+# exception, depending on what it can find. Python <2.7 doesn't have
+# unittest.case.SkipTest; so if unittest2 is not installed it falls
+# back to the internal class.
+#
+# The current nose skip plugin will decide to raise either
+# unittest.case.SkipTest or its own internal exception; it does not
+# look for unittest2 or the internal unittest exception.  Thus we must
+# monkey-patch testtools.TestCase.skipException to be the exception
+# the nose skip plugin expects.
+#
+# However, with the switch to testr nose may not be available, so we
+# require you to opt-in to this fix with an environment variable.
+#
+# This is temporary until upstream nose starts looking for unittest2
+# as testtools does; we can then remove this and ensure unittest2 is
+# available for older pythons; then nose and testtools will agree
+# unittest2.case.SkipTest is the one-true skip test exception.
+#
+#   https://review.openstack.org/#/c/33056
+#   https://github.com/nose-devs/nose/pull/699
+if 'TEMPEST_PY26_NOSE_COMPAT' in os.environ:
+    try:
+        import unittest.case.SkipTest
+        # convince pep8 we're using the import...
+        if unittest.case.SkipTest:
+            pass
+        raise RuntimeError("You have unittest.case.SkipTest; "
+                           "no need to override")
+    except ImportError:
+        LOG.info("Overriding skipException to nose SkipTest")
+        testtools.TestCase.skipException = nose.plugins.skip.SkipTest
+
+
 class BaseTestCase(testtools.TestCase,
                    testtools.testcase.WithAttributes,
                    testresources.ResourcedTestCase):