Merge "Make sure cli CommandFailed prints out stdout and stderr"
diff --git a/tempest/cli/__init__.py b/tempest/cli/__init__.py
index ba94c82..d7b4a16 100644
--- a/tempest/cli/__init__.py
+++ b/tempest/cli/__init__.py
@@ -133,7 +133,7 @@
             raise CommandFailed(proc.returncode,
                                 cmd,
                                 result,
-                                stderr=result_err)
+                                result_err)
         return result
 
     def assertTableStruct(self, items, field_names):
@@ -148,9 +148,15 @@
                              % lines[:3]))
 
 
-class CommandFailed(subprocess.CalledProcessError):
-    # adds output attribute for python2.6
-    def __init__(self, returncode, cmd, output, stderr=""):
-        super(CommandFailed, self).__init__(returncode, cmd)
-        self.output = output
+class CommandFailed(Exception):
+    def __init__(self, returncode, cmd, output, stderr):
+        super(CommandFailed, self).__init__()
+        self.returncode = returncode
+        self.cmd = cmd
+        self.stdout = output
         self.stderr = stderr
+
+    def __str__(self):
+        return ("Command '%s' returned non-zero exit status %d.\n"
+        "stdout:\n%s\n"
+        "stderr:\n%s" % (self.cmd, self.returncode, self.stdout, self.stderr))
diff --git a/tempest/cli/simple_read_only/test_cinder.py b/tempest/cli/simple_read_only/test_cinder.py
index 946b89e..9a6b159 100644
--- a/tempest/cli/simple_read_only/test_cinder.py
+++ b/tempest/cli/simple_read_only/test_cinder.py
@@ -15,17 +15,16 @@
 
 import logging
 import re
-import subprocess
 import testtools
 
-import tempest.cli
+from tempest import cli
 from tempest import config
 
 CONF = config.CONF
 LOG = logging.getLogger(__name__)
 
 
-class SimpleReadOnlyCinderClientTest(tempest.cli.ClientTestBase):
+class SimpleReadOnlyCinderClientTest(cli.ClientTestBase):
     """Basic, read-only tests for Cinder CLI client.
 
     Checks return values and output of read-only commands.
@@ -41,7 +40,7 @@
         super(SimpleReadOnlyCinderClientTest, cls).setUpClass()
 
     def test_cinder_fake_action(self):
-        self.assertRaises(subprocess.CalledProcessError,
+        self.assertRaises(cli.CommandFailed,
                           self.cinder,
                           'this-does-not-exist')
 
@@ -66,7 +65,7 @@
                                       'Attached to'])
         self.cinder('list', params='--all-tenants 1')
         self.cinder('list', params='--all-tenants 0')
-        self.assertRaises(subprocess.CalledProcessError,
+        self.assertRaises(cli.CommandFailed,
                           self.cinder,
                           'list',
                           params='--all-tenants bad')
diff --git a/tempest/cli/simple_read_only/test_glance.py b/tempest/cli/simple_read_only/test_glance.py
index 6b3f763..3fb1120 100644
--- a/tempest/cli/simple_read_only/test_glance.py
+++ b/tempest/cli/simple_read_only/test_glance.py
@@ -14,9 +14,8 @@
 #    under the License.
 
 import re
-import subprocess
 
-import tempest.cli
+from tempest import cli
 from tempest import config
 from tempest.openstack.common import log as logging
 
@@ -25,7 +24,7 @@
 LOG = logging.getLogger(__name__)
 
 
-class SimpleReadOnlyGlanceClientTest(tempest.cli.ClientTestBase):
+class SimpleReadOnlyGlanceClientTest(cli.ClientTestBase):
     """Basic, read-only tests for Glance CLI client.
 
     Checks return values and output of read-only commands.
@@ -41,7 +40,7 @@
         super(SimpleReadOnlyGlanceClientTest, cls).setUpClass()
 
     def test_glance_fake_action(self):
-        self.assertRaises(subprocess.CalledProcessError,
+        self.assertRaises(cli.CommandFailed,
                           self.glance,
                           'this-does-not-exist')
 
diff --git a/tempest/cli/simple_read_only/test_keystone.py b/tempest/cli/simple_read_only/test_keystone.py
index dda65c1..f8dcdba 100644
--- a/tempest/cli/simple_read_only/test_keystone.py
+++ b/tempest/cli/simple_read_only/test_keystone.py
@@ -14,9 +14,8 @@
 #    under the License.
 
 import re
-import subprocess
 
-import tempest.cli
+from tempest import cli
 from tempest import config
 from tempest.openstack.common import log as logging
 
@@ -26,7 +25,7 @@
 LOG = logging.getLogger(__name__)
 
 
-class SimpleReadOnlyKeystoneClientTest(tempest.cli.ClientTestBase):
+class SimpleReadOnlyKeystoneClientTest(cli.ClientTestBase):
     """Basic, read-only tests for Keystone CLI client.
 
     Checks return values and output of read-only commands.
@@ -35,7 +34,7 @@
     """
 
     def test_admin_fake_action(self):
-        self.assertRaises(subprocess.CalledProcessError,
+        self.assertRaises(cli.CommandFailed,
                           self.keystone,
                           'this-does-not-exist')
 
diff --git a/tempest/cli/simple_read_only/test_neutron.py b/tempest/cli/simple_read_only/test_neutron.py
index 49d079e..2643596 100644
--- a/tempest/cli/simple_read_only/test_neutron.py
+++ b/tempest/cli/simple_read_only/test_neutron.py
@@ -14,7 +14,6 @@
 #    under the License.
 
 import re
-import subprocess
 
 from tempest import cli
 from tempest import config
@@ -43,7 +42,7 @@
 
     @test.attr(type='smoke')
     def test_neutron_fake_action(self):
-        self.assertRaises(subprocess.CalledProcessError,
+        self.assertRaises(cli.CommandFailed,
                           self.neutron,
                           'this-does-not-exist')
 
diff --git a/tempest/cli/simple_read_only/test_nova.py b/tempest/cli/simple_read_only/test_nova.py
index 1c1ddf1..70eb9ef 100644
--- a/tempest/cli/simple_read_only/test_nova.py
+++ b/tempest/cli/simple_read_only/test_nova.py
@@ -13,11 +13,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import subprocess
-
 import testtools
 
-import tempest.cli
+from tempest import cli
 from tempest import config
 from tempest.openstack.common import log as logging
 import tempest.test
@@ -27,7 +25,7 @@
 LOG = logging.getLogger(__name__)
 
 
-class SimpleReadOnlyNovaClientTest(tempest.cli.ClientTestBase):
+class SimpleReadOnlyNovaClientTest(cli.ClientTestBase):
 
     """
     This is a first pass at a simple read only python-novaclient test. This
@@ -49,7 +47,7 @@
         super(SimpleReadOnlyNovaClientTest, cls).setUpClass()
 
     def test_admin_fake_action(self):
-        self.assertRaises(subprocess.CalledProcessError,
+        self.assertRaises(cli.CommandFailed,
                           self.nova,
                           'this-does-nova-exist')
 
@@ -86,11 +84,11 @@
         self.nova('endpoints')
 
     def test_admin_flavor_acces_list(self):
-        self.assertRaises(subprocess.CalledProcessError,
+        self.assertRaises(cli.CommandFailed,
                           self.nova,
                           'flavor-access-list')
         # Failed to get access list for public flavor type
-        self.assertRaises(subprocess.CalledProcessError,
+        self.assertRaises(cli.CommandFailed,
                           self.nova,
                           'flavor-access-list',
                           params='--flavor m1.tiny')
@@ -127,7 +125,7 @@
         self.nova('list')
         self.nova('list', params='--all-tenants 1')
         self.nova('list', params='--all-tenants 0')
-        self.assertRaises(subprocess.CalledProcessError,
+        self.assertRaises(cli.CommandFailed,
                           self.nova,
                           'list',
                           params='--all-tenants bad')
diff --git a/tempest/cli/simple_read_only/test_nova_manage.py b/tempest/cli/simple_read_only/test_nova_manage.py
index f1fee2e..67c19d8 100644
--- a/tempest/cli/simple_read_only/test_nova_manage.py
+++ b/tempest/cli/simple_read_only/test_nova_manage.py
@@ -13,9 +13,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import subprocess
-
-import tempest.cli
+from tempest import cli
 from tempest import config
 from tempest.openstack.common import log as logging
 
@@ -24,7 +22,7 @@
 LOG = logging.getLogger(__name__)
 
 
-class SimpleReadOnlyNovaManageTest(tempest.cli.ClientTestBase):
+class SimpleReadOnlyNovaManageTest(cli.ClientTestBase):
 
     """
     This is a first pass at a simple read only nova-manage test. This
@@ -48,7 +46,7 @@
         super(SimpleReadOnlyNovaManageTest, cls).setUpClass()
 
     def test_admin_fake_action(self):
-        self.assertRaises(subprocess.CalledProcessError,
+        self.assertRaises(cli.CommandFailed,
                           self.nova_manage,
                           'this-does-nova-exist')
 
diff --git a/tempest/cli/simple_read_only/test_sahara.py b/tempest/cli/simple_read_only/test_sahara.py
index f00dcae..773921a 100644
--- a/tempest/cli/simple_read_only/test_sahara.py
+++ b/tempest/cli/simple_read_only/test_sahara.py
@@ -14,7 +14,6 @@
 # limitations under the License.
 import logging
 import re
-import subprocess
 
 from tempest import cli
 from tempest import config
@@ -42,7 +41,7 @@
 
     @test.attr(type='negative')
     def test_sahara_fake_action(self):
-        self.assertRaises(subprocess.CalledProcessError,
+        self.assertRaises(cli.CommandFailed,
                           self.sahara,
                           'this-does-not-exist')
 
diff --git a/tempest/cli/simple_read_only/test_swift.py b/tempest/cli/simple_read_only/test_swift.py
index 6d6caa7..c778542 100644
--- a/tempest/cli/simple_read_only/test_swift.py
+++ b/tempest/cli/simple_read_only/test_swift.py
@@ -14,15 +14,14 @@
 #    under the License.
 
 import re
-import subprocess
 
-import tempest.cli
+from tempest import cli
 from tempest import config
 
 CONF = config.CONF
 
 
-class SimpleReadOnlySwiftClientTest(tempest.cli.ClientTestBase):
+class SimpleReadOnlySwiftClientTest(cli.ClientTestBase):
     """Basic, read-only tests for Swift CLI client.
 
     Checks return values and output of read-only commands.
@@ -38,7 +37,7 @@
         super(SimpleReadOnlySwiftClientTest, cls).setUpClass()
 
     def test_swift_fake_action(self):
-        self.assertRaises(subprocess.CalledProcessError,
+        self.assertRaises(cli.CommandFailed,
                           self.swift,
                           'this-does-not-exist')
 
diff --git a/tempest/tests/cli/test_command_failed.py b/tempest/tests/cli/test_command_failed.py
new file mode 100644
index 0000000..c539ac6
--- /dev/null
+++ b/tempest/tests/cli/test_command_failed.py
@@ -0,0 +1,30 @@
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from tempest import cli
+from tempest.tests import base
+
+
+class TestOutputParser(base.TestCase):
+
+    def test_command_failed_exception(self):
+        returncode = 1
+        cmd = "foo"
+        stdout = "output"
+        stderr = "error"
+        try:
+            raise cli.CommandFailed(returncode, cmd, stdout, stderr)
+        except cli.CommandFailed as e:
+            self.assertIn(str(returncode), str(e))
+            self.assertIn(cmd, str(e))
+            self.assertIn(stdout, str(e))
+            self.assertIn(stderr, str(e))