Matthew Treinish | e8ab5f9 | 2017-03-01 15:25:39 -0500 | [diff] [blame] | 1 | .. _tempest_plugin: |
| 2 | |
Matthew Treinish | 3a851dc | 2015-07-30 11:34:03 -0400 | [diff] [blame] | 3 | ============================= |
| 4 | Tempest Test Plugin Interface |
| 5 | ============================= |
| 6 | |
| 7 | Tempest has an external test plugin interface which enables anyone to integrate |
deepak_mourya | e495cd2 | 2018-07-16 12:38:17 +0530 | [diff] [blame] | 8 | an external test suite as part of a Tempest run. This will let any project |
| 9 | leverage being run with the rest of the Tempest suite while not requiring the |
| 10 | tests live in the Tempest tree. |
Matthew Treinish | 3a851dc | 2015-07-30 11:34:03 -0400 | [diff] [blame] | 11 | |
| 12 | Creating a plugin |
| 13 | ================= |
| 14 | |
| 15 | Creating a plugin is fairly straightforward and doesn't require much additional |
Andrea Frittoli (andreaf) | 1370baf | 2016-04-29 14:26:22 -0500 | [diff] [blame] | 16 | effort on top of creating a test suite using tempest.lib. One thing to note with |
deepak_mourya | e495cd2 | 2018-07-16 12:38:17 +0530 | [diff] [blame] | 17 | doing this is that the interfaces exposed by Tempest are not considered stable |
| 18 | (with the exception of configuration variables whichever effort goes into |
| 19 | ensuring backward compatibility). You should not need to import anything from |
| 20 | Tempest itself except where explicitly noted. |
Kiall Mac Innes | 9e6f974 | 2016-05-23 16:20:55 +0100 | [diff] [blame] | 21 | |
| 22 | Stable Tempest APIs plugins may use |
| 23 | ----------------------------------- |
| 24 | |
deepak_mourya | e495cd2 | 2018-07-16 12:38:17 +0530 | [diff] [blame] | 25 | As noted above, several Tempest APIs are acceptable to use from plugins, while |
Kiall Mac Innes | 9e6f974 | 2016-05-23 16:20:55 +0100 | [diff] [blame] | 26 | others are not. A list of stable APIs available to plugins is provided below: |
| 27 | |
| 28 | * tempest.lib.* |
| 29 | * tempest.config |
| 30 | * tempest.test_discover.plugins |
Andrea Frittoli | 17347f0 | 2017-07-26 16:18:30 +0100 | [diff] [blame] | 31 | * tempest.common.credentials_factory |
Andrea Frittoli | bf142fc | 2017-10-23 17:30:18 +0200 | [diff] [blame] | 32 | * tempest.clients |
| 33 | * tempest.test |
Ghanshyam Mann | c2ca52d | 2021-03-25 21:29:14 -0500 | [diff] [blame] | 34 | * tempest.scenario.manager |
Kiall Mac Innes | 9e6f974 | 2016-05-23 16:20:55 +0100 | [diff] [blame] | 35 | |
deepak_mourya | e495cd2 | 2018-07-16 12:38:17 +0530 | [diff] [blame] | 36 | If there is an interface from Tempest that you need to rely on in your plugin |
Kiall Mac Innes | 9e6f974 | 2016-05-23 16:20:55 +0100 | [diff] [blame] | 37 | which is not listed above, it likely needs to be migrated to tempest.lib. In |
| 38 | that situation, file a bug, push a migration patch, etc. to expedite providing |
| 39 | the interface in a reliable manner. |
Matthew Treinish | 3a851dc | 2015-07-30 11:34:03 -0400 | [diff] [blame] | 40 | |
Marc Koderer | 66210aa | 2015-10-26 10:52:32 +0100 | [diff] [blame] | 41 | Plugin Cookiecutter |
| 42 | ------------------- |
| 43 | |
| 44 | In order to create the basic structure with base classes and test directories |
| 45 | you can use the tempest-plugin-cookiecutter project:: |
| 46 | |
Ghanshyam Mann | 0aa0636 | 2020-07-15 13:38:42 -0500 | [diff] [blame] | 47 | > pip install -U cookiecutter && cookiecutter https://opendev.org/openstack/tempest-plugin-cookiecutter.git |
Marc Koderer | 66210aa | 2015-10-26 10:52:32 +0100 | [diff] [blame] | 48 | |
| 49 | Cloning into 'tempest-plugin-cookiecutter'... |
| 50 | remote: Counting objects: 17, done. |
| 51 | remote: Compressing objects: 100% (13/13), done. |
| 52 | remote: Total 17 (delta 1), reused 14 (delta 1) |
| 53 | Unpacking objects: 100% (17/17), done. |
| 54 | Checking connectivity... done. |
| 55 | project (default is "sample")? foo |
| 56 | testclass (default is "SampleTempestPlugin")? FooTempestPlugin |
| 57 | |
| 58 | This would create a folder called ``foo_tempest_plugin/`` with all necessary |
| 59 | basic classes. You only need to move/create your test in |
| 60 | ``foo_tempest_plugin/tests``. |
| 61 | |
| 62 | Entry Point |
| 63 | ----------- |
| 64 | |
| 65 | Once you've created your plugin class you need to add an entry point to your |
deepak_mourya | e495cd2 | 2018-07-16 12:38:17 +0530 | [diff] [blame] | 66 | project to enable Tempest to find the plugin. The entry point must be added |
Marc Koderer | 66210aa | 2015-10-26 10:52:32 +0100 | [diff] [blame] | 67 | to the "tempest.test_plugins" namespace. |
| 68 | |
| 69 | If you are using pbr this is fairly straightforward, in the setup.cfg just add |
Yushiro FURUKAWA | 836361d | 2016-09-30 23:26:58 +0900 | [diff] [blame] | 70 | something like the following: |
| 71 | |
| 72 | .. code-block:: ini |
Marc Koderer | 66210aa | 2015-10-26 10:52:32 +0100 | [diff] [blame] | 73 | |
| 74 | [entry_points] |
| 75 | tempest.test_plugins = |
| 76 | plugin_name = module.path:PluginClass |
| 77 | |
Matthew Treinish | 00686f2 | 2016-03-09 15:39:19 -0500 | [diff] [blame] | 78 | Standalone Plugin vs In-repo Plugin |
| 79 | ----------------------------------- |
| 80 | |
deepak_mourya | e495cd2 | 2018-07-16 12:38:17 +0530 | [diff] [blame] | 81 | Since all that's required for a plugin to be detected by Tempest is a valid |
Matthew Treinish | 00686f2 | 2016-03-09 15:39:19 -0500 | [diff] [blame] | 82 | setuptools entry point in the proper namespace there is no difference from the |
deepak_mourya | e495cd2 | 2018-07-16 12:38:17 +0530 | [diff] [blame] | 83 | Tempest perspective on either creating a separate python package to |
Matthew Treinish | 00686f2 | 2016-03-09 15:39:19 -0500 | [diff] [blame] | 84 | house the plugin or adding the code to an existing python project. However, |
| 85 | there are tradeoffs to consider when deciding which approach to take when |
| 86 | creating a new plugin. |
| 87 | |
| 88 | If you create a separate python project for your plugin this makes a lot of |
| 89 | things much easier. Firstly it makes packaging and versioning much simpler, you |
| 90 | can easily decouple the requirements for the plugin from the requirements for |
| 91 | the other project. It lets you version the plugin independently and maintain a |
| 92 | single version of the test code across project release boundaries (see the |
| 93 | `Branchless Tempest Spec`_ for more details on this). It also greatly |
| 94 | simplifies the install time story for external users. Instead of having to |
deepak_mourya | e495cd2 | 2018-07-16 12:38:17 +0530 | [diff] [blame] | 95 | install the right version of a project in the same python namespace as Tempest |
Matthew Treinish | 00686f2 | 2016-03-09 15:39:19 -0500 | [diff] [blame] | 96 | they simply need to pip install the plugin in that namespace. It also means |
deepak_mourya | e495cd2 | 2018-07-16 12:38:17 +0530 | [diff] [blame] | 97 | that users don't have to worry about inadvertently installing a Tempest plugin |
Matthew Treinish | 00686f2 | 2016-03-09 15:39:19 -0500 | [diff] [blame] | 98 | when they install another package. |
| 99 | |
sunqingliang6 | 8606c83 | 2018-11-09 14:25:17 +0800 | [diff] [blame] | 100 | .. _Branchless Tempest Spec: https://specs.openstack.org/openstack/qa-specs/specs/tempest/implemented/branchless-tempest.html |
Matthew Treinish | 00686f2 | 2016-03-09 15:39:19 -0500 | [diff] [blame] | 101 | |
| 102 | The sole advantage to integrating a plugin into an existing python project is |
| 103 | that it enables you to land code changes at the same time you land test changes |
| 104 | in the plugin. This reduces some of the burden on contributors by not having |
| 105 | to land 2 changes to add a new API feature and then test it and doing it as a |
| 106 | single combined commit. |
| 107 | |
| 108 | |
Matthew Treinish | 3a851dc | 2015-07-30 11:34:03 -0400 | [diff] [blame] | 109 | Plugin Class |
Marc Koderer | 66210aa | 2015-10-26 10:52:32 +0100 | [diff] [blame] | 110 | ============ |
Matthew Treinish | 3a851dc | 2015-07-30 11:34:03 -0400 | [diff] [blame] | 111 | |
deepak_mourya | e495cd2 | 2018-07-16 12:38:17 +0530 | [diff] [blame] | 112 | To provide Tempest with all the required information it needs to be able to run |
| 113 | your plugin you need to create a plugin class which Tempest will load and call |
| 114 | to get information when it needs. To simplify creating this Tempest provides an |
Matthew Treinish | 3a851dc | 2015-07-30 11:34:03 -0400 | [diff] [blame] | 115 | abstract class that should be used as the parent for your plugin. To use this |
Yushiro FURUKAWA | 836361d | 2016-09-30 23:26:58 +0900 | [diff] [blame] | 116 | you would do something like the following: |
| 117 | |
| 118 | .. code-block:: python |
Matthew Treinish | 3a851dc | 2015-07-30 11:34:03 -0400 | [diff] [blame] | 119 | |
YAMAMOTO Takashi | cb2ac6e | 2015-10-19 15:54:42 +0900 | [diff] [blame] | 120 | from tempest.test_discover import plugins |
Matthew Treinish | 3a851dc | 2015-07-30 11:34:03 -0400 | [diff] [blame] | 121 | |
YAMAMOTO Takashi | cb2ac6e | 2015-10-19 15:54:42 +0900 | [diff] [blame] | 122 | class MyPlugin(plugins.TempestPlugin): |
Matthew Treinish | 3a851dc | 2015-07-30 11:34:03 -0400 | [diff] [blame] | 123 | |
Andrea Frittoli (andreaf) | e07579c | 2016-08-05 07:27:02 +0100 | [diff] [blame] | 124 | Then you need to ensure you locally define all of the mandatory methods in the |
| 125 | abstract class, you can refer to the api doc below for a reference of what that |
| 126 | entails. |
Matthew Treinish | 3a851dc | 2015-07-30 11:34:03 -0400 | [diff] [blame] | 127 | |
Matthew Treinish | 3a851dc | 2015-07-30 11:34:03 -0400 | [diff] [blame] | 128 | Abstract Plugin Class |
Marc Koderer | 66210aa | 2015-10-26 10:52:32 +0100 | [diff] [blame] | 129 | --------------------- |
Matthew Treinish | 3a851dc | 2015-07-30 11:34:03 -0400 | [diff] [blame] | 130 | |
| 131 | .. autoclass:: tempest.test_discover.plugins.TempestPlugin |
| 132 | :members: |
| 133 | |
Matthew Treinish | 3a851dc | 2015-07-30 11:34:03 -0400 | [diff] [blame] | 134 | Plugin Structure |
Marc Koderer | 66210aa | 2015-10-26 10:52:32 +0100 | [diff] [blame] | 135 | ================ |
zhufl | e9241b5 | 2017-12-06 15:41:08 +0800 | [diff] [blame] | 136 | While there are no hard and fast rules for the structure of a plugin, there are |
Matthew Treinish | 3a851dc | 2015-07-30 11:34:03 -0400 | [diff] [blame] | 137 | basically no constraints on what the plugin looks like as long as the 2 steps |
| 138 | above are done. However, there are some recommended patterns to follow to make |
| 139 | it easy for people to contribute and work with your plugin. For example, if you |
| 140 | create a directory structure with something like:: |
| 141 | |
| 142 | plugin_dir/ |
| 143 | config.py |
| 144 | plugin.py |
| 145 | tests/ |
| 146 | api/ |
| 147 | scenario/ |
| 148 | services/ |
| 149 | client.py |
| 150 | |
deepak_mourya | e495cd2 | 2018-07-16 12:38:17 +0530 | [diff] [blame] | 151 | That will mirror what people expect from Tempest. The file |
Matthew Treinish | 3a851dc | 2015-07-30 11:34:03 -0400 | [diff] [blame] | 152 | |
| 153 | * **config.py**: contains any plugin specific configuration variables |
| 154 | * **plugin.py**: contains the plugin class used for the entry point |
| 155 | * **tests**: the directory where test discovery will be run, all tests should |
| 156 | be under this dir |
| 157 | * **services**: where the plugin specific service clients are |
| 158 | |
| 159 | Additionally, when you're creating the plugin you likely want to follow all |
deepak_mourya | e495cd2 | 2018-07-16 12:38:17 +0530 | [diff] [blame] | 160 | of the Tempest developer and reviewer documentation to ensure that the tests |
| 161 | being added in the plugin act and behave like the rest of Tempest. |
Matthew Treinish | 3a851dc | 2015-07-30 11:34:03 -0400 | [diff] [blame] | 162 | |
Matthew Treinish | 9392a83 | 2015-08-24 10:00:49 -0400 | [diff] [blame] | 163 | Dealing with configuration options |
Marc Koderer | 66210aa | 2015-10-26 10:52:32 +0100 | [diff] [blame] | 164 | ---------------------------------- |
Matthew Treinish | 9392a83 | 2015-08-24 10:00:49 -0400 | [diff] [blame] | 165 | |
deepak_mourya | e495cd2 | 2018-07-16 12:38:17 +0530 | [diff] [blame] | 166 | Historically, Tempest didn't provide external guarantees on its configuration |
| 167 | options. However, with the introduction of the plugin interface, this is no |
Matthew Treinish | 9392a83 | 2015-08-24 10:00:49 -0400 | [diff] [blame] | 168 | longer the case. An external plugin can rely on using any configuration option |
| 169 | coming from Tempest, there will be at least a full deprecation cycle for any |
| 170 | option before it's removed. However, just the options provided by Tempest |
| 171 | may not be sufficient for the plugin. If you need to add any plugin specific |
| 172 | configuration options you should use the ``register_opts`` and |
| 173 | ``get_opt_lists`` methods to pass them to Tempest when the plugin is loaded. |
| 174 | When adding configuration options the ``register_opts`` method gets passed the |
deepak_mourya | e495cd2 | 2018-07-16 12:38:17 +0530 | [diff] [blame] | 175 | CONF object from Tempest. This enables the plugin to add options to both |
Matthew Treinish | 9392a83 | 2015-08-24 10:00:49 -0400 | [diff] [blame] | 176 | existing sections and also create new configuration sections for new options. |
| 177 | |
Andrea Frittoli (andreaf) | e07579c | 2016-08-05 07:27:02 +0100 | [diff] [blame] | 178 | Service Clients |
| 179 | --------------- |
| 180 | |
| 181 | If a plugin defines a service client, it is beneficial for it to implement the |
| 182 | ``get_service_clients`` method in the plugin class. All service clients which |
| 183 | are exposed via this interface will be automatically configured and be |
| 184 | available in any instance of the service clients class, defined in |
| 185 | ``tempest.lib.services.clients.ServiceClients``. In case multiple plugins are |
| 186 | installed, all service clients from all plugins will be registered, making it |
| 187 | easy to write tests which rely on multiple APIs whose service clients are in |
| 188 | different plugins. |
| 189 | |
Yushiro FURUKAWA | 836361d | 2016-09-30 23:26:58 +0900 | [diff] [blame] | 190 | Example implementation of ``get_service_clients``: |
| 191 | |
| 192 | .. code-block:: python |
Andrea Frittoli (andreaf) | e07579c | 2016-08-05 07:27:02 +0100 | [diff] [blame] | 193 | |
| 194 | def get_service_clients(self): |
| 195 | # Example implementation with two service clients |
| 196 | my_service1_config = config.service_client_config('my_service') |
| 197 | params_my_service1 = { |
| 198 | 'name': 'my_service_v1', |
| 199 | 'service_version': 'my_service.v1', |
| 200 | 'module_path': 'plugin_tempest_tests.services.my_service.v1', |
| 201 | 'client_names': ['API1Client', 'API2Client'], |
| 202 | } |
| 203 | params_my_service1.update(my_service_config) |
| 204 | my_service2_config = config.service_client_config('my_service') |
| 205 | params_my_service2 = { |
| 206 | 'name': 'my_service_v2', |
| 207 | 'service_version': 'my_service.v2', |
| 208 | 'module_path': 'plugin_tempest_tests.services.my_service.v2', |
| 209 | 'client_names': ['API1Client', 'API2Client'], |
| 210 | } |
| 211 | params_my_service2.update(my_service2_config) |
| 212 | return [params_my_service1, params_my_service2] |
| 213 | |
| 214 | Parameters: |
| 215 | |
| 216 | * **name**: Name of the attribute used to access the ``ClientsFactory`` from |
| 217 | the ``ServiceClients`` instance. See example below. |
| 218 | * **service_version**: Tempest enforces a single implementation for each |
| 219 | service client. Available service clients are held in a ``ClientsRegistry`` |
| 220 | singleton, and registered with ``service_version``, which means that |
| 221 | ``service_version`` must be unique and it should represent the service API |
| 222 | and version implemented by the service client. |
| 223 | * **module_path**: Relative to the service client module, from the root of the |
| 224 | plugin. |
| 225 | * **client_names**: Name of the classes that implement service clients in the |
| 226 | service clients module. |
| 227 | |
Yushiro FURUKAWA | 836361d | 2016-09-30 23:26:58 +0900 | [diff] [blame] | 228 | Example usage of the service clients in tests: |
| 229 | |
| 230 | .. code-block:: python |
Andrea Frittoli (andreaf) | e07579c | 2016-08-05 07:27:02 +0100 | [diff] [blame] | 231 | |
| 232 | # my_creds is instance of tempest.lib.auth.Credentials |
| 233 | # identity_uri is v2 or v3 depending on the configuration |
| 234 | from tempest.lib.services import clients |
| 235 | |
| 236 | my_clients = clients.ServiceClients(my_creds, identity_uri) |
| 237 | my_service1_api1_client = my_clients.my_service_v1.API1Client() |
| 238 | my_service2_api1_client = my_clients.my_service_v2.API1Client(my_args='any') |
| 239 | |
| 240 | Automatic configuration and registration of service clients imposes some extra |
| 241 | constraints on the structure of the configuration options exposed by the |
| 242 | plugin. |
| 243 | |
| 244 | First ``service_version`` should be in the format `service_config[.version]`. |
| 245 | The `.version` part is optional, and should only be used if there are multiple |
| 246 | versions of the same API available. The `service_config` must match the name of |
| 247 | a configuration options group defined by the plugin. Different versions of one |
| 248 | API must share the same configuration group. |
| 249 | |
| 250 | Second the configuration options group `service_config` must contain the |
| 251 | following options: |
| 252 | |
| 253 | * `catalog_type`: corresponds to `service` in the catalog |
| 254 | * `endpoint_type` |
| 255 | |
| 256 | The following options will be honoured if defined, but they are not mandatory, |
| 257 | as they do not necessarily apply to all service clients. |
| 258 | |
| 259 | * `region`: default to identity.region |
| 260 | * `build_timeout` : default to compute.build_timeout |
| 261 | * `build_interval`: default to compute.build_interval |
| 262 | |
| 263 | Third the service client classes should inherit from ``RestClient``, should |
| 264 | accept generic keyword arguments, and should pass those arguments to the |
| 265 | ``__init__`` method of ``RestClient``. Extra arguments can be added. For |
Yushiro FURUKAWA | 836361d | 2016-09-30 23:26:58 +0900 | [diff] [blame] | 266 | instance: |
| 267 | |
| 268 | .. code-block:: python |
Andrea Frittoli (andreaf) | e07579c | 2016-08-05 07:27:02 +0100 | [diff] [blame] | 269 | |
| 270 | class MyAPIClient(rest_client.RestClient): |
| 271 | |
Roman Dobosz | edda62a | 2020-12-01 14:40:45 +0100 | [diff] [blame] | 272 | def __init__(self, auth_provider, service, region, |
| 273 | my_arg, my_arg2=True, **kwargs): |
| 274 | super(MyAPIClient, self).__init__( |
| 275 | auth_provider, service, region, **kwargs) |
| 276 | self.my_arg = my_arg |
| 277 | self.my_args2 = my_arg |
Andrea Frittoli (andreaf) | e07579c | 2016-08-05 07:27:02 +0100 | [diff] [blame] | 278 | |
| 279 | Finally the service client should be structured in a python module, so that all |
| 280 | service client classes are importable from it. Each major API version should |
| 281 | have its own module. |
| 282 | |
| 283 | The following folder and module structure is recommended for a single major |
| 284 | API version:: |
| 285 | |
| 286 | plugin_dir/ |
| 287 | services/ |
| 288 | __init__.py |
| 289 | client_api_1.py |
| 290 | client_api_2.py |
| 291 | |
Yushiro FURUKAWA | 836361d | 2016-09-30 23:26:58 +0900 | [diff] [blame] | 292 | The content of __init__.py module should be: |
| 293 | |
| 294 | .. code-block:: python |
Andrea Frittoli (andreaf) | e07579c | 2016-08-05 07:27:02 +0100 | [diff] [blame] | 295 | |
| 296 | from client_api_1.py import API1Client |
| 297 | from client_api_2.py import API2Client |
| 298 | |
| 299 | __all__ = ['API1Client', 'API2Client'] |
| 300 | |
| 301 | The following folder and module structure is recommended for multiple major |
| 302 | API version:: |
| 303 | |
| 304 | plugin_dir/ |
| 305 | services/ |
| 306 | v1/ |
| 307 | __init__.py |
| 308 | client_api_1.py |
| 309 | client_api_2.py |
| 310 | v2/ |
| 311 | __init__.py |
| 312 | client_api_1.py |
| 313 | client_api_2.py |
| 314 | |
Yushiro FURUKAWA | 836361d | 2016-09-30 23:26:58 +0900 | [diff] [blame] | 315 | The content each of __init__.py module under vN should be: |
| 316 | |
| 317 | .. code-block:: python |
Andrea Frittoli (andreaf) | e07579c | 2016-08-05 07:27:02 +0100 | [diff] [blame] | 318 | |
| 319 | from client_api_1.py import API1Client |
| 320 | from client_api_2.py import API2Client |
| 321 | |
| 322 | __all__ = ['API1Client', 'API2Client'] |
| 323 | |
Matthew Treinish | 3a851dc | 2015-07-30 11:34:03 -0400 | [diff] [blame] | 324 | Using Plugins |
| 325 | ============= |
| 326 | |
| 327 | Tempest will automatically discover any installed plugins when it is run. So by |
| 328 | just installing the python packages which contain your plugin you'll be using |
deepak_mourya | e495cd2 | 2018-07-16 12:38:17 +0530 | [diff] [blame] | 329 | them with Tempest, nothing else is really required. |
Matthew Treinish | 3a851dc | 2015-07-30 11:34:03 -0400 | [diff] [blame] | 330 | |
| 331 | However, you should take care when installing plugins. By their very nature |
deepak_mourya | e495cd2 | 2018-07-16 12:38:17 +0530 | [diff] [blame] | 332 | there are no guarantees when running Tempest with plugins enabled about the |
Matthew Treinish | 3a851dc | 2015-07-30 11:34:03 -0400 | [diff] [blame] | 333 | quality of the plugin. Additionally, while there is no limitation on running |
deepak_mourya | e495cd2 | 2018-07-16 12:38:17 +0530 | [diff] [blame] | 334 | with multiple plugins, it's worth noting that poorly written plugins might not |
Matthew Treinish | 3a851dc | 2015-07-30 11:34:03 -0400 | [diff] [blame] | 335 | properly isolate their tests which could cause unexpected cross interactions |
| 336 | between plugins. |
| 337 | |
| 338 | Notes for using plugins with virtualenvs |
| 339 | ---------------------------------------- |
| 340 | |
deepak_mourya | e495cd2 | 2018-07-16 12:38:17 +0530 | [diff] [blame] | 341 | When using a Tempest inside a virtualenv (like when running under tox) you have |
Matthew Treinish | 3a851dc | 2015-07-30 11:34:03 -0400 | [diff] [blame] | 342 | to ensure that the package that contains your plugin is either installed in the |
| 343 | venv too or that you have system site-packages enabled. The virtualenv will |
deepak_mourya | e495cd2 | 2018-07-16 12:38:17 +0530 | [diff] [blame] | 344 | isolate the Tempest install from the rest of your system so just installing the |
| 345 | plugin package on your system and then running Tempest inside a venv will not |
Matthew Treinish | 3a851dc | 2015-07-30 11:34:03 -0400 | [diff] [blame] | 346 | work. |
| 347 | |
Lukáš Piwowarski | be19f21 | 2022-10-07 13:47:11 +0200 | [diff] [blame] | 348 | For example, you can use tox to install and run tests from a tempest plugin like |
| 349 | this:: |
| 350 | |
| 351 | [~/tempest] $ tox -e venv-tempest -- pip install (path to the plugin directory) |
| 352 | [~/tempest] $ tox -e all |