Merge "Deduplicate negative test calls"
diff --git a/tempest/api/compute/admin/test_flavors_negative.py b/tempest/api/compute/admin/test_flavors_negative.py
index 162e419..b37d32c 100644
--- a/tempest/api/compute/admin/test_flavors_negative.py
+++ b/tempest/api/compute/admin/test_flavors_negative.py
@@ -101,13 +101,9 @@
                           self.flavor_ref_alt)
 
 
+@test.SimpleNegativeAutoTest
 class FlavorCreateNegativeTestJSON(base.BaseV2ComputeAdminTest,
                                    test.NegativeAutoTest):
     _interface = 'json'
     _service = 'compute'
     _schema_file = 'compute/admin/flavor_create.json'
-
-    @test.attr(type=['negative', 'gate'])
-    def test_create_flavor(self):
-        # flavor details are not returned for non-existent flavors
-        self.execute(self._schema_file)
diff --git a/tempest/api/compute/flavors/test_flavors_negative.py b/tempest/api/compute/flavors/test_flavors_negative.py
index a81b7d9..1638f2d 100644
--- a/tempest/api/compute/flavors/test_flavors_negative.py
+++ b/tempest/api/compute/flavors/test_flavors_negative.py
@@ -21,16 +21,14 @@
 load_tests = test.NegativeAutoTest.load_tests
 
 
-class FlavorsListNegativeTestJSON(base.BaseV2ComputeTest,
-                                  test.NegativeAutoTest):
+@test.SimpleNegativeAutoTest
+class FlavorsListWithDetailsNegativeTestJSON(base.BaseV2ComputeTest,
+                                             test.NegativeAutoTest):
     _service = 'compute'
     _schema_file = 'compute/flavors/flavors_list.json'
 
-    @test.attr(type=['negative', 'gate'])
-    def test_list_flavors_with_detail(self):
-        self.execute(self._schema_file)
 
-
+@test.SimpleNegativeAutoTest
 class FlavorDetailsNegativeTestJSON(base.BaseV2ComputeTest,
                                     test.NegativeAutoTest):
     _service = 'compute'
@@ -40,8 +38,3 @@
     def setUpClass(cls):
         super(FlavorDetailsNegativeTestJSON, cls).setUpClass()
         cls.set_resource("flavor", cls.flavor_ref)
-
-    @test.attr(type=['negative', 'gate'])
-    def test_get_flavor_details(self):
-        # flavor details are not returned for non-existent flavors
-        self.execute(self._schema_file)
diff --git a/tempest/api/compute/servers/test_servers_negative_new.py b/tempest/api/compute/servers/test_servers_negative_new.py
index f860ff9..43ddb3a 100644
--- a/tempest/api/compute/servers/test_servers_negative_new.py
+++ b/tempest/api/compute/servers/test_servers_negative_new.py
@@ -21,6 +21,7 @@
 load_tests = test.NegativeAutoTest.load_tests
 
 
+@test.SimpleNegativeAutoTest
 class GetConsoleOutputNegativeTestJSON(base.BaseV2ComputeTest,
                                        test.NegativeAutoTest):
     _service = 'compute'
@@ -31,7 +32,3 @@
         super(GetConsoleOutputNegativeTestJSON, cls).setUpClass()
         _resp, server = cls.create_test_server()
         cls.set_resource("server", server['id'])
-
-    @test.attr(type=['negative', 'gate'])
-    def test_get_console_output(self):
-        self.execute(self._schema_file)
diff --git a/tempest/api/compute/v3/flavors/test_flavors_negative.py b/tempest/api/compute/v3/flavors/test_flavors_negative.py
index 1c0e4fb..657e2cd 100644
--- a/tempest/api/compute/v3/flavors/test_flavors_negative.py
+++ b/tempest/api/compute/v3/flavors/test_flavors_negative.py
@@ -20,16 +20,14 @@
 load_tests = test.NegativeAutoTest.load_tests
 
 
+@test.SimpleNegativeAutoTest
 class FlavorsListNegativeV3Test(base.BaseV3ComputeTest,
                                 test.NegativeAutoTest):
     _service = 'computev3'
     _schema_file = 'compute/flavors/flavors_list_v3.json'
 
-    @test.attr(type=['negative', 'gate'])
-    def test_list_flavors_with_detail(self):
-        self.execute(self._schema_file)
 
-
+@test.SimpleNegativeAutoTest
 class FlavorDetailsNegativeV3Test(base.BaseV3ComputeTest,
                                   test.NegativeAutoTest):
     _service = 'computev3'
@@ -39,8 +37,3 @@
     def setUpClass(cls):
         super(FlavorDetailsNegativeV3Test, cls).setUpClass()
         cls.set_resource("flavor", cls.flavor_ref)
-
-    @test.attr(type=['negative', 'gate'])
-    def test_get_flavor_details(self):
-        # flavor details are not returned for non-existent flavors
-        self.execute(self._schema_file)
diff --git a/tempest/test.py b/tempest/test.py
index abf42c0..e4019f9 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -17,6 +17,7 @@
 import functools
 import json
 import os
+import re
 import sys
 import time
 import urllib
@@ -581,6 +582,24 @@
         return None
 
 
+def SimpleNegativeAutoTest(klass):
+    """
+    This decorator registers a test function on basis of the class name.
+    """
+    @attr(type=['negative', 'gate'])
+    def generic_test(self):
+        self.execute(self._schema_file)
+
+    cn = klass.__name__
+    cn = cn.replace('JSON', '')
+    cn = cn.replace('Test', '')
+    # NOTE(mkoderer): replaces uppercase chars inside the class name with '_'
+    lower_cn = re.sub('(?<!^)(?=[A-Z])', '_', cn).lower()
+    func_name = 'test_%s' % lower_cn
+    setattr(klass, func_name, generic_test)
+    return klass
+
+
 def call_until_true(func, duration, sleep_for):
     """
     Call the given function until it returns True (and return True) or