You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by po...@apache.org on 2022/07/06 19:55:37 UTC

[airflow] branch main updated: Add more selective provider tests (#24666)

This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 3dedbd34e7 Add more selective provider tests (#24666)
3dedbd34e7 is described below

commit 3dedbd34e79c38d0fd5c55313d4b8fb3ced125ba
Author: Jarek Potiuk <ja...@polidea.com>
AuthorDate: Wed Jul 6 21:55:21 2022 +0200

    Add more selective provider tests (#24666)
    
    After implementing #24610 and few follow-up fixes, it is now easy
    to add more optimizations to our unit test execution in CI (and
    to give this capability back to our contributors).
    
    This PR adds capability of running tests for selected set of
    providers - not for the whole "Providers" group. You can
    specify `--test-type "Providers[airbyte,http]" to only run tests
    for the two selected providers.
    
    This is the step towards separating providers to separate
    repositories, but it also allows to optimize the experience of
    the contributors developing only single provider changes (which
    is vast majority of contributions).
    
    This also allows to optimize build and elapsed time needd to run
    tests for those PRs that only affects selected providers (again -
    vast majority of PRs).
    
    The CI selection of which provider tests is done now in Selective
    Checkcs - they are a bit smarter in just selecting the providers
    that has been changed, they also check if there are any other
    providers that depend on it (we keep automatically updated by
    pre-commit dependencies.json file and this file determines
    which files should be run.
---
 BREEZE.rst                                         |  53 ++++++-
 Dockerfile.ci                                      |   7 +-
 TESTING.rst                                        |  15 +-
 dev/breeze/SELECTIVE_CHECKS.md                     |   8 ++
 .../airflow_breeze/commands/testing_commands.py    |  10 +-
 .../src/airflow_breeze/utils/selective_checks.py   |  87 +++++++++--
 dev/breeze/tests/test_selective_checks.py          |  74 ++++++++--
 images/breeze/output-commands-hash.txt             |   2 +-
 images/breeze/output-tests.svg                     | 160 +++++++++++----------
 scripts/docker/entrypoint_ci.sh                    |   7 +-
 10 files changed, 311 insertions(+), 112 deletions(-)

diff --git a/BREEZE.rst b/BREEZE.rst
index f13a83b6c7..1ad387d267 100644
--- a/BREEZE.rst
+++ b/BREEZE.rst
@@ -500,9 +500,6 @@ Airflow Breeze is a bash script serving as a "swiss-army-knife" of Airflow testi
 hood it uses other scripts that you can also run manually if you have problem with running the Breeze
 environment. Breeze script allows performing the following tasks:
 
-Development tasks
------------------
-
 Those are commands mostly used by contributors:
 
 * Execute arbitrary command in the test environment with ``breeze shell`` command
@@ -512,6 +509,7 @@ Those are commands mostly used by contributors:
 * Initialize local virtualenv with ``./scripts/tools/initialize_virtualenv.py`` command
 * Run static checks with autocomplete support ``breeze static-checks`` command
 * Run test specified with ``breeze tests`` command
+* Run docker-compose tests with ``breeze docker-compose-tests`` command.
 * Build CI docker image with ``breeze build-image`` command
 * Cleanup breeze with ``breeze cleanup`` command
 
@@ -524,8 +522,53 @@ Additional management tasks:
 Tests
 -----
 
-* Run docker-compose tests with ``breeze docker-compose-tests`` command.
-* Run test specified with ``breeze tests`` command.
+You can regular unit tests with ``breeze`` in two different ways, either interactively run tests with
+the default ``shell`` command or via the ``tests`` command.
+
+Iterate on tests interactively
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can simply enter the ``breeze`` container and run ``pytest`` command there. You can enter the
+container via just ``breeze`` command or ``breeze shell`` command (the latter has more options
+useful when you run integration or system tests). This is the best way if you want to interactively
+run selected tests and iterate with the tests. Once you enter ``breeze`` environment it is ready
+out-of-the-box to run your tests by running the right ``pytest`` command (autocomplete should help
+you with autocompleting test name if you start typing ``pytest tests<TAB>``).
+
+Here are few examples:
+
+Running single test:
+
+.. code-block:: bash
+
+    pytest tests/core/test_core.py::TestCore::test_check_operators
+
+To run the whole test class:
+
+.. code-block:: bash
+
+    pytest tests/core/test_core.py::TestCore
+
+You can re-run the tests interactively, add extra parameters to pytest and modify the files before
+re-running the test to iterate over the tests. You can also add more flags when starting the
+``breeze shell`` command when you run integration tests or system tests. Read more details about it
+in the ``TESTING.rst <TESTING.rst#>`` where all the test types of our are explained and more information
+on how to run them.
+
+Running group of tests
+~~~~~~~~~~~~~~~~~~~~~~
+
+You can also run tests via built-in ``breeze tests`` command - similarly as iterative ``pytest`` command
+allows to run test individually, or by class or in any other way pytest allows to test them, but it
+also allows to run the tests in the same test "types" that are used to run the tests in CI: Core, Always
+API, Providers. This how our CI runs them - running each group in parallel to other groups and you can
+replicate this behaviour.
+
+Another interesting use of the ``breeze tests`` command is that you can easily specify sub-set of the
+tests for Providers. ``breeze tests --test-type "Providers[airbyte,http]`` for example will only run
+tests for airbyte and http providers.
+
+Here is the detailed set of options for the ``breeze tests`` command.
 
 .. image:: ./images/breeze/output-tests.svg
   :width: 100%
diff --git a/Dockerfile.ci b/Dockerfile.ci
index 06ada2775d..3e866bc6ed 100644
--- a/Dockerfile.ci
+++ b/Dockerfile.ci
@@ -952,13 +952,18 @@ else
             ${TEST_TYPE} == "Long" || \
             ${TEST_TYPE} == "Integration" ]]; then
         SELECTED_TESTS=("${ALL_TESTS[@]}")
+    elif [[ ${TEST_TYPE} =~ Providers\[(.*)\] ]]; then
+        SELECTED_TESTS=()
+        for provider in ${BASH_REMATCH[1]//,/ }
+        do
+            SELECTED_TESTS+=("tests/providers/${provider//./\/}")
+        done
     else
         echo
         echo  "${COLOR_RED}ERROR: Wrong test type ${TEST_TYPE}  ${COLOR_RESET}"
         echo
         exit 1
     fi
-
 fi
 readonly SELECTED_TESTS CLI_TESTS API_TESTS PROVIDERS_TESTS CORE_TESTS WWW_TESTS \
     ALL_TESTS ALL_PRESELECTED_TESTS
diff --git a/TESTING.rst b/TESTING.rst
index 4f7de58b76..0e2bd60289 100644
--- a/TESTING.rst
+++ b/TESTING.rst
@@ -109,7 +109,8 @@ To run unit tests from the Visual Studio Code:
     :alt: Running tests
 
 Running Unit Tests
---------------------------------
+------------------
+
 To run unit, integration, and system tests from the Breeze and your
 virtualenv, you can use the `pytest <http://doc.pytest.org/en/latest/>`_ framework.
 
@@ -188,6 +189,18 @@ You can also limit the tests to execute to specific group of tests
 
     breeze tests --test-type Core
 
+In case of Providers tests, you can run tests for all providers
+
+.. code-block:: bash
+
+    breeze tests --test-type Providers
+
+You can also limit the set of providers you would like to run tests of
+
+.. code-block:: bash
+
+    breeze tests --test-type "Providers[airbyte,http]"
+
 
 You can also write tests in "limited progress" mode (useful in the future to run CI). In this mode each
 test just prints "percentage" summary of the run as single line and only dumps full output of the test
diff --git a/dev/breeze/SELECTIVE_CHECKS.md b/dev/breeze/SELECTIVE_CHECKS.md
index 4504f8d9cd..c99139e34a 100644
--- a/dev/breeze/SELECTIVE_CHECKS.md
+++ b/dev/breeze/SELECTIVE_CHECKS.md
@@ -87,6 +87,14 @@ The logic implements the following rules:
 * If no Source files are changed - no tests are run and no further rules below are checked.
 * `Image building` is enabled if either test are run, docs are build or kubernetes tests are run. All those
   need `CI` or `PROD` images to be built.
+* In case of `Providers` test in regular PRs, additional check is done in order to determine which
+  providers are affected and the actual selection is made based on that:
+  * if directly provider code is changed (either in the provider, test or system tests) then this provider
+    is selected.
+  * if there are any providers that depend on the affected providers, they are also included in the list
+    of affected providers (but not recursively - only direct dependencies are added)
+  * if there are any changes to "common" provider code not belonging to any provider (usually system tests
+    or tests), then tests for all Providers are run
 * The specific unit test type is enabled only if changed files match the expected patterns for each type
   (`API`, `CLI`, `WWW`, `Providers`). The `Always` test type is added always if any unit tests are run.
   `Providers` tests are removed if current branch is different than `main`
diff --git a/dev/breeze/src/airflow_breeze/commands/testing_commands.py b/dev/breeze/src/airflow_breeze/commands/testing_commands.py
index 8c9200a840..b53333ea64 100644
--- a/dev/breeze/src/airflow_breeze/commands/testing_commands.py
+++ b/dev/breeze/src/airflow_breeze/commands/testing_commands.py
@@ -48,7 +48,7 @@ from airflow_breeze.utils.common_options import (
     option_verbose,
 )
 from airflow_breeze.utils.console import get_console, message_type_from_return_code
-from airflow_breeze.utils.custom_param_types import BetterChoice
+from airflow_breeze.utils.custom_param_types import NotVerifiedBetterChoice
 from airflow_breeze.utils.docker_command_utils import (
     get_env_variables_for_docker_commands,
     perform_environment_checks,
@@ -247,9 +247,10 @@ def run_with_progress(
 @option_mount_sources
 @click.option(
     "--test-type",
-    help="Type of test to run.",
+    help="Type of test to run. Note that with Providers, you can also specify which provider "
+    "tests should be run - for example --test-type \"Providers[airbyte,http]\"",
     default="All",
-    type=BetterChoice(ALLOWED_TEST_TYPE_CHOICES),
+    type=NotVerifiedBetterChoice(ALLOWED_TEST_TYPE_CHOICES),
 )
 @option_db_reset
 @click.argument('extra_pytest_args', nargs=-1, type=click.UNPROCESSED)
@@ -272,6 +273,9 @@ def tests(
     os.environ["RUN_TESTS"] = "true"
     if test_type:
         os.environ["TEST_TYPE"] = test_type
+        if "[" in test_type and not test_type.startswith("Providers"):
+            get_console().print("[error]Only 'Providers' test type can specify actual tests with \\[\\][/]")
+            sys.exit(1)
     if integration:
         if "trino" in integration:
             integration = integration + ("kerberos",)
diff --git a/dev/breeze/src/airflow_breeze/utils/selective_checks.py b/dev/breeze/src/airflow_breeze/utils/selective_checks.py
index 0b5c93bc48..26620a6b54 100644
--- a/dev/breeze/src/airflow_breeze/utils/selective_checks.py
+++ b/dev/breeze/src/airflow_breeze/utils/selective_checks.py
@@ -17,12 +17,19 @@
 
 from __future__ import annotations
 
+import json
+import os
 import sys
 from enum import Enum
 
+from rich.markup import escape
+
+from airflow_breeze.utils.path_utils import AIRFLOW_SOURCES_ROOT
+
 if sys.version_info >= (3, 8):
     from functools import cached_property
 else:
+    # noinspection PyUnresolvedReferences
     from cached_property import cached_property
 
 from functools import lru_cache
@@ -59,7 +66,7 @@ FULL_TESTS_NEEDED_LABEL = "full tests needed"
 def get_ga_output(name: str, value: Any) -> str:
     output_name = name.replace('_', '-')
     printed_value = str(value).lower() if isinstance(value, bool) else value
-    get_console().print(f"[info]{output_name}[/] = [green]{printed_value}[/]")
+    get_console().print(f"[info]{output_name}[/] = [green]{escape(str(printed_value))}[/]")
     return f"::set-output name={output_name}::{printed_value}"
 
 
@@ -127,6 +134,7 @@ CI_FILE_GROUP_MATCHES = HashableDict(
             r"^airflow/.*\.py$",
             r"^chart",
             r"^providers",
+            r"^tests/system",
             r"^CHANGELOG\.txt",
             r"^airflow/config_templates/config\.yml",
             r"^chart/RELEASE_NOTES\.txt",
@@ -148,6 +156,7 @@ CI_FILE_GROUP_MATCHES = HashableDict(
             r"^kubernetes_tests",
             r"^airflow/providers/cncf/kubernetes/",
             r"^tests/providers/cncf/kubernetes/",
+            r"^tests/system/providers/cncf/kubernetes/",
         ],
         FileGroupForCi.ALL_PYTHON_FILES: [
             r"\.py$",
@@ -178,11 +187,61 @@ TEST_TYPE_MATCHES = HashableDict(
         SelectiveUnitTestTypes.PROVIDERS: [
             "^airflow/providers/",
             "^tests/providers/",
+            "^tests/system/",
         ],
         SelectiveUnitTestTypes.WWW: ["^airflow/www", "^tests/www", "^airflow/ui"],
     }
 )
 
+TESTS_PROVIDERS_ROOT = AIRFLOW_SOURCES_ROOT / "tests" / "providers"
+SYSTEM_TESTS_PROVIDERS_ROOT = AIRFLOW_SOURCES_ROOT / "tests" / "system" / "providers"
+AIRFLOW_PROVIDERS_ROOT = AIRFLOW_SOURCES_ROOT / "airflow" / "providers"
+
+
+def find_provider_affected(changed_file: str) -> str | None:
+    file_path = AIRFLOW_SOURCES_ROOT / changed_file
+    # is_relative_to is only available in Python 3.9 - we should simplify this check when we are Python 3.9+
+    for provider_root in (TESTS_PROVIDERS_ROOT, SYSTEM_TESTS_PROVIDERS_ROOT, AIRFLOW_PROVIDERS_ROOT):
+        try:
+            file_path.relative_to(provider_root)
+            relative_base_path = provider_root
+            break
+        except ValueError:
+            pass
+    else:
+        return None
+
+    for parent_dir_path in file_path.parents:
+        if parent_dir_path == relative_base_path:
+            break
+        relative_path = parent_dir_path.relative_to(relative_base_path)
+        if (AIRFLOW_PROVIDERS_ROOT / relative_path / "provider.yaml").exists():
+            return str(parent_dir_path.relative_to(relative_base_path)).replace(os.sep, ".")
+    # If we got here it means that some "common" files were modified. so we need to test all Providers
+    return "Providers"
+
+
+def add_dependent_providers(
+    providers: set[str], provider_to_check: str, dependencies: dict[str, dict[str, list[str]]]
+):
+    for provider, provider_info in dependencies.items():
+        if provider_to_check in provider_info['cross-providers-deps']:
+            providers.add(provider)
+
+
+def find_all_providers_affected(changed_files: tuple[str, ...]) -> set[str]:
+    all_providers: set[str] = set()
+    for changed_file in changed_files:
+        provider = find_provider_affected(changed_file)
+        if provider == "Providers":
+            return set()
+        if provider is not None:
+            all_providers.add(provider)
+    dependencies = json.loads((AIRFLOW_SOURCES_ROOT / "generated" / "provider_dependencies.json").read_text())
+    for provider in list(all_providers):
+        add_dependent_providers(all_providers, provider, dependencies)
+    return all_providers
+
 
 class SelectiveChecks:
     __HASHABLE_FIELDS = {'_files', '_default_branch', '_commit_ref', "_pr_labels", "_github_event"}
@@ -446,6 +505,11 @@ class SelectiveChecks:
             get_console().print(remaining_files)
             candidate_test_types.update(all_selective_test_types())
         else:
+            if "Providers" in candidate_test_types:
+                affected_providers = find_all_providers_affected(changed_files=self._files)
+                if len(affected_providers) != 0:
+                    candidate_test_types.remove("Providers")
+                    candidate_test_types.add(f"Providers[{','.join(sorted(affected_providers))}]")
             get_console().print(
                 "[warning]There are no core/other files. Only tests relevant to the changed files are run.[/]"
             )
@@ -459,22 +523,25 @@ class SelectiveChecks:
         if not self.run_tests:
             return ""
         if self._run_everything:
-            current_test_types = list(all_selective_test_types())
+            current_test_types = set(all_selective_test_types())
         else:
-            current_test_types = self._get_test_types_to_run()
+            current_test_types = set(self._get_test_types_to_run())
         if self._default_branch != "main":
-            if "Providers" in current_test_types:
-                get_console().print(
-                    "[warning]Removing 'Providers' because the target branch "
-                    f"is {self._default_branch} and not main[/]"
-                )
-                current_test_types.remove("Providers")
+            test_types_to_remove: set[str] = set()
+            for test_type in current_test_types:
+                if test_type.startswith("Providers"):
+                    get_console().print(
+                        f"[warning]Removing {test_type} because the target branch "
+                        f"is {self._default_branch} and not main[/]"
+                    )
+                    test_types_to_remove.add(test_type)
             if "Integration" in current_test_types:
                 get_console().print(
                     "[warning]Removing 'Integration' because the target branch "
                     f"is {self._default_branch} and not main[/]"
                 )
-                current_test_types.remove("Integration")
+                test_types_to_remove.add("Integration")
+            current_test_types = current_test_types - test_types_to_remove
         return " ".join(sorted(current_test_types))
 
     @cached_property
diff --git a/dev/breeze/tests/test_selective_checks.py b/dev/breeze/tests/test_selective_checks.py
index e5c8321fe5..a81c348c9f 100644
--- a/dev/breeze/tests/test_selective_checks.py
+++ b/dev/breeze/tests/test_selective_checks.py
@@ -77,14 +77,15 @@ def assert_outputs_are_printed(expected_outputs: Dict[str, str], output: str):
                     "run-tests": "true",
                     "docs-build": "true",
                     "upgrade-to-newer-dependencies": "false",
-                    "test-types": "API Always Providers",
+                    "test-types": "API Always Providers[amazon,apache.beam,google,hashicorp,"
+                    "microsoft.azure,presto,trino]",
                 },
                 id="API and providers tests and docs should run",
             )
         ),
         (
             pytest.param(
-                ("tests/providers/google/file.py",),
+                ("tests/providers/apache/beam/file.py",),
                 {
                     "all-python-versions": "['3.7']",
                     "all-python-versions-list-as-string": "3.7",
@@ -94,9 +95,9 @@ def assert_outputs_are_printed(expected_outputs: Dict[str, str], output: str):
                     "docs-build": "false",
                     "run-kubernetes-tests": "false",
                     "upgrade-to-newer-dependencies": "false",
-                    "test-types": "Always Providers",
+                    "test-types": "Always Providers[apache.beam,google]",
                 },
-                id="Providers and docs should run",
+                id="Selected Providers and docs should run",
             )
         ),
         (
@@ -131,7 +132,8 @@ def assert_outputs_are_printed(expected_outputs: Dict[str, str], output: str):
                     "docs-build": "true",
                     "run-kubernetes-tests": "true",
                     "upgrade-to-newer-dependencies": "false",
-                    "test-types": "Always Providers",
+                    "test-types": "Always Providers[amazon,apache.beam,google,"
+                    "hashicorp,microsoft.azure,presto,trino]",
                 },
                 id="Helm tests, providers, kubernetes tests and docs should run",
             )
@@ -141,7 +143,52 @@ def assert_outputs_are_printed(expected_outputs: Dict[str, str], output: str):
                 (
                     "INTHEWILD.md",
                     "chart/aaaa.txt",
-                    "tests/providers/google/file.py",
+                    "tests/providers/http/file.py",
+                ),
+                {
+                    "all-python-versions": "['3.7']",
+                    "all-python-versions-list-as-string": "3.7",
+                    "image-build": "true",
+                    "needs-helm-tests": "true",
+                    "run-tests": "true",
+                    "docs-build": "true",
+                    "run-kubernetes-tests": "true",
+                    "upgrade-to-newer-dependencies": "false",
+                    "test-types": "Always Providers[airbyte,apache.livy,"
+                    "dbt.cloud,dingding,discord,http,slack]",
+                },
+                id="Helm tests, http and all relevant providers, kubernetes tests and "
+                "docs should run even if unimportant files were added",
+            )
+        ),
+        (
+            pytest.param(
+                (
+                    "INTHEWILD.md",
+                    "chart/aaaa.txt",
+                    "tests/system/providers/airbyte/file.py",
+                ),
+                {
+                    "all-python-versions": "['3.7']",
+                    "all-python-versions-list-as-string": "3.7",
+                    "image-build": "true",
+                    "needs-helm-tests": "true",
+                    "run-tests": "true",
+                    "docs-build": "true",
+                    "run-kubernetes-tests": "true",
+                    "upgrade-to-newer-dependencies": "false",
+                    "test-types": "Always Providers[airbyte]",
+                },
+                id="Helm tests, airbyte providers, kubernetes tests and "
+                "docs should run even if unimportant files were added",
+            )
+        ),
+        (
+            pytest.param(
+                (
+                    "INTHEWILD.md",
+                    "chart/aaaa.txt",
+                    "tests/system/utils/file.py",
                 ),
                 {
                     "all-python-versions": "['3.7']",
@@ -154,8 +201,8 @@ def assert_outputs_are_printed(expected_outputs: Dict[str, str], output: str):
                     "upgrade-to-newer-dependencies": "false",
                     "test-types": "Always Providers",
                 },
-                id="Helm tests, providers, kubernetes tests and docs should run even if "
-                "unimportant files were added",
+                id="Helm tests, all providers as common util system file changed, kubernetes tests and "
+                "docs should run even if unimportant files were added",
             )
         ),
         (
@@ -171,7 +218,8 @@ def assert_outputs_are_printed(expected_outputs: Dict[str, str], output: str):
                     "upgrade-to-newer-dependencies": "true",
                     "test-types": "API Always CLI Core Integration Other Providers WWW",
                 },
-                id="Everything should run and upgrading to newer requirements as setup.py changed",
+                id="Everything should run - including all providers and upgrading to "
+                "newer requirements as setup.py changed",
             )
         ),
         (
@@ -223,7 +271,7 @@ def test_expected_output_pull_request_main(
                     "upgrade-to-newer-dependencies": "false",
                     "test-types": "API Always CLI Core Integration Other Providers WWW",
                 },
-                id="Everything should run when full tests are needed",
+                id="Everything should run including all providers when full tests are needed",
             )
         ),
         (
@@ -243,7 +291,8 @@ def test_expected_output_pull_request_main(
                     "upgrade-to-newer-dependencies": "false",
                     "test-types": "API Always CLI Core Integration Other Providers WWW",
                 },
-                id="Everything should run when full tests are needed even with different label set as well",
+                id="Everything should run including full providers when full "
+                "tests are needed even with different label set as well",
             )
         ),
         (
@@ -260,7 +309,8 @@ def test_expected_output_pull_request_main(
                     "upgrade-to-newer-dependencies": "false",
                     "test-types": "API Always CLI Core Integration Other Providers WWW",
                 },
-                id="Everything should run when full tests are needed even if no files are changed",
+                id="Everything should run including full providers when"
+                "full tests are needed even if no files are changed",
             )
         ),
         (
diff --git a/images/breeze/output-commands-hash.txt b/images/breeze/output-commands-hash.txt
index 7b335b475d..df72bfd829 100644
--- a/images/breeze/output-commands-hash.txt
+++ b/images/breeze/output-commands-hash.txt
@@ -30,7 +30,7 @@ shell:4680295fdd8a276d51518d29360c365c
 start-airflow:92cf775a952439a32d409cd2536da507
 static-checks:bc6b9a121ea38404ac5f28e727146b90
 stop:8ebd8a42f1003495d37b884de5ac7ce6
-tests:ae8d62b505ff8f79bddc202fe9d575e3
+tests:e39111ecbd33a65ababb3cbfbac2b069
 verify-image:a6b3c70957aea96a5d4d261f23359a2d
 verify-prod-image:bf3cf39200e010e3015ef071fd387c6f
 verify-provider-packages:797e60067fc4611112527de808b5c1c1
diff --git a/images/breeze/output-tests.svg b/images/breeze/output-tests.svg
index 8bdcd0c29b..ed99d925d2 100644
--- a/images/breeze/output-tests.svg
+++ b/images/breeze/output-tests.svg
@@ -1,4 +1,4 @@
-<svg class="rich-terminal" viewBox="0 0 1482 830.8" xmlns="http://www.w3.org/2000/svg">
+<svg class="rich-terminal" viewBox="0 0 1482 855.1999999999999" xmlns="http://www.w3.org/2000/svg">
     <!-- Generated with Rich https://www.textualize.io -->
     <style>
 
@@ -19,169 +19,173 @@
         font-weight: 700;
     }
 
-    .terminal-3386261188-matrix {
+    .terminal-3016257943-matrix {
         font-family: Fira Code, monospace;
         font-size: 20px;
         line-height: 24.4px;
         font-variant-east-asian: full-width;
     }
 
-    .terminal-3386261188-title {
+    .terminal-3016257943-title {
         font-size: 18px;
         font-weight: bold;
         font-family: arial;
     }
 
-    .terminal-3386261188-r1 { fill: #c5c8c6;font-weight: bold }
-.terminal-3386261188-r2 { fill: #c5c8c6 }
-.terminal-3386261188-r3 { fill: #d0b344;font-weight: bold }
-.terminal-3386261188-r4 { fill: #868887 }
-.terminal-3386261188-r5 { fill: #68a0b3;font-weight: bold }
-.terminal-3386261188-r6 { fill: #8d7b39 }
-.terminal-3386261188-r7 { fill: #98a84b;font-weight: bold }
+    .terminal-3016257943-r1 { fill: #c5c8c6;font-weight: bold }
+.terminal-3016257943-r2 { fill: #c5c8c6 }
+.terminal-3016257943-r3 { fill: #d0b344;font-weight: bold }
+.terminal-3016257943-r4 { fill: #868887 }
+.terminal-3016257943-r5 { fill: #68a0b3;font-weight: bold }
+.terminal-3016257943-r6 { fill: #8d7b39 }
+.terminal-3016257943-r7 { fill: #98a84b;font-weight: bold }
     </style>
 
     <defs>
-    <clipPath id="terminal-3386261188-clip-terminal">
-      <rect x="0" y="0" width="1463.0" height="779.8" />
+    <clipPath id="terminal-3016257943-clip-terminal">
+      <rect x="0" y="0" width="1463.0" height="804.1999999999999" />
     </clipPath>
-    <clipPath id="terminal-3386261188-line-0">
+    <clipPath id="terminal-3016257943-line-0">
     <rect x="0" y="1.5" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-3386261188-line-1">
+<clipPath id="terminal-3016257943-line-1">
     <rect x="0" y="25.9" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-3386261188-line-2">
+<clipPath id="terminal-3016257943-line-2">
     <rect x="0" y="50.3" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-3386261188-line-3">
+<clipPath id="terminal-3016257943-line-3">
     <rect x="0" y="74.7" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-3386261188-line-4">
+<clipPath id="terminal-3016257943-line-4">
     <rect x="0" y="99.1" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-3386261188-line-5">
+<clipPath id="terminal-3016257943-line-5">
     <rect x="0" y="123.5" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-3386261188-line-6">
+<clipPath id="terminal-3016257943-line-6">
     <rect x="0" y="147.9" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-3386261188-line-7">
+<clipPath id="terminal-3016257943-line-7">
     <rect x="0" y="172.3" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-3386261188-line-8">
+<clipPath id="terminal-3016257943-line-8">
     <rect x="0" y="196.7" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-3386261188-line-9">
+<clipPath id="terminal-3016257943-line-9">
     <rect x="0" y="221.1" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-3386261188-line-10">
+<clipPath id="terminal-3016257943-line-10">
     <rect x="0" y="245.5" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-3386261188-line-11">
+<clipPath id="terminal-3016257943-line-11">
     <rect x="0" y="269.9" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-3386261188-line-12">
+<clipPath id="terminal-3016257943-line-12">
     <rect x="0" y="294.3" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-3386261188-line-13">
+<clipPath id="terminal-3016257943-line-13">
     <rect x="0" y="318.7" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-3386261188-line-14">
+<clipPath id="terminal-3016257943-line-14">
     <rect x="0" y="343.1" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-3386261188-line-15">
+<clipPath id="terminal-3016257943-line-15">
     <rect x="0" y="367.5" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-3386261188-line-16">
+<clipPath id="terminal-3016257943-line-16">
     <rect x="0" y="391.9" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-3386261188-line-17">
+<clipPath id="terminal-3016257943-line-17">
     <rect x="0" y="416.3" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-3386261188-line-18">
+<clipPath id="terminal-3016257943-line-18">
     <rect x="0" y="440.7" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-3386261188-line-19">
+<clipPath id="terminal-3016257943-line-19">
     <rect x="0" y="465.1" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-3386261188-line-20">
+<clipPath id="terminal-3016257943-line-20">
     <rect x="0" y="489.5" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-3386261188-line-21">
+<clipPath id="terminal-3016257943-line-21">
     <rect x="0" y="513.9" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-3386261188-line-22">
+<clipPath id="terminal-3016257943-line-22">
     <rect x="0" y="538.3" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-3386261188-line-23">
+<clipPath id="terminal-3016257943-line-23">
     <rect x="0" y="562.7" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-3386261188-line-24">
+<clipPath id="terminal-3016257943-line-24">
     <rect x="0" y="587.1" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-3386261188-line-25">
+<clipPath id="terminal-3016257943-line-25">
     <rect x="0" y="611.5" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-3386261188-line-26">
+<clipPath id="terminal-3016257943-line-26">
     <rect x="0" y="635.9" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-3386261188-line-27">
+<clipPath id="terminal-3016257943-line-27">
     <rect x="0" y="660.3" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-3386261188-line-28">
+<clipPath id="terminal-3016257943-line-28">
     <rect x="0" y="684.7" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-3386261188-line-29">
+<clipPath id="terminal-3016257943-line-29">
     <rect x="0" y="709.1" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-3386261188-line-30">
+<clipPath id="terminal-3016257943-line-30">
     <rect x="0" y="733.5" width="1464" height="24.65"/>
             </clipPath>
+<clipPath id="terminal-3016257943-line-31">
+    <rect x="0" y="757.9" width="1464" height="24.65"/>
+            </clipPath>
     </defs>
 
-    <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="1480" height="828.8" rx="8"/><text class="terminal-3386261188-title" fill="#c5c8c6" text-anchor="middle" x="740" y="27">Command:&#160;tests</text>
+    <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="1480" height="853.2" rx="8"/><text class="terminal-3016257943-title" fill="#c5c8c6" text-anchor="middle" x="740" y="27">Command:&#160;tests</text>
             <g transform="translate(26,22)">
             <circle cx="0" cy="0" r="7" fill="#ff5f57"/>
             <circle cx="22" cy="0" r="7" fill="#febc2e"/>
             <circle cx="44" cy="0" r="7" fill="#28c840"/>
             </g>
         
-    <g transform="translate(9, 41)" clip-path="url(#terminal-3386261188-clip-terminal)">
+    <g transform="translate(9, 41)" clip-path="url(#terminal-3016257943-clip-terminal)">
     
-    <g class="terminal-3386261188-matrix">
-    <text class="terminal-3386261188-r2" x="1464" y="20" textLength="12.2" clip-path="url(#terminal-3386261188-line-0)">
-</text><text class="terminal-3386261188-r3" x="12.2" y="44.4" textLength="85.4" clip-path="url(#terminal-3386261188-line-1)">Usage:&#160;</text><text class="terminal-3386261188-r1" x="97.6" y="44.4" textLength="549" clip-path="url(#terminal-3386261188-line-1)">breeze&#160;tests&#160;[OPTIONS]&#160;[EXTRA_PYTEST_ARGS]...</text><text class="terminal-3386261188-r2" x="1464" y="44.4" textLength="12.2" clip-path="url(#terminal-3386261188-line-1)">
-</text><text class="terminal-3386261188-r2" x="1464" y="68.8" textLength="12.2" clip-path="url(#terminal-3386261188-line-2)">
-</text><text class="terminal-3386261188-r2" x="12.2" y="93.2" textLength="1110.2" clip-path="url(#terminal-3386261188-line-3)">Run&#160;the&#160;specified&#160;unit&#160;test&#160;targets.&#160;Multiple&#160;targets&#160;may&#160;be&#160;specified&#160;separated&#160;by&#160;spaces.</text><text class="terminal-3386261188-r2" x="1464" y="93.2" textLength="12.2" clip-path="url(#terminal-3386261188-line-3)">
-</text><text class="terminal-3386261188-r2" x="1464" y="117.6" textLength="12.2" clip-path="url(#terminal-3386261188-line-4)">
-</text><text class="terminal-3386261188-r4" x="0" y="142" textLength="24.4" clip-path="url(#terminal-3386261188-line-5)">╭─</text><text class="terminal-3386261188-r4" x="24.4" y="142" textLength="1415.2" clip-path="url(#terminal-3386261188-line-5)">&#160;Basic&#160;flag&#160;for&#160;tests&#160;command&#160;──────────────────────────────────────────────────────────────────────────────────────</text><text class="terminal-3386261188-r4" x="1439.6" y="142" textLength="24.4" clip-path="url(# [...]
-</text><text class="terminal-3386261188-r4" x="0" y="166.4" textLength="12.2" clip-path="url(#terminal-3386261188-line-6)">│</text><text class="terminal-3386261188-r5" x="24.4" y="166.4" textLength="12.2" clip-path="url(#terminal-3386261188-line-6)">-</text><text class="terminal-3386261188-r5" x="36.6" y="166.4" textLength="146.4" clip-path="url(#terminal-3386261188-line-6)">-integration</text><text class="terminal-3386261188-r2" x="317.2" y="166.4" textLength="1110.2" clip-path="url(#te [...]
-</text><text class="terminal-3386261188-r4" x="0" y="190.8" textLength="12.2" clip-path="url(#terminal-3386261188-line-7)">│</text><text class="terminal-3386261188-r6" x="317.2" y="190.8" textLength="1110.2" clip-path="url(#terminal-3386261188-line-7)">(cassandra&#160;|&#160;kerberos&#160;|&#160;mongo&#160;|&#160;openldap&#160;|&#160;pinot&#160;|&#160;rabbitmq&#160;|&#160;redis&#160;|&#160;statsd&#160;|&#160;trino&#160;|&#160;all)</text><text class="terminal-3386261188-r4" x="1451.8" y=" [...]
-</text><text class="terminal-3386261188-r4" x="0" y="215.2" textLength="12.2" clip-path="url(#terminal-3386261188-line-8)">│</text><text class="terminal-3386261188-r5" x="24.4" y="215.2" textLength="12.2" clip-path="url(#terminal-3386261188-line-8)">-</text><text class="terminal-3386261188-r5" x="36.6" y="215.2" textLength="61" clip-path="url(#terminal-3386261188-line-8)">-test</text><text class="terminal-3386261188-r5" x="97.6" y="215.2" textLength="61" clip-path="url(#terminal-33862611 [...]
-</text><text class="terminal-3386261188-r4" x="0" y="239.6" textLength="12.2" clip-path="url(#terminal-3386261188-line-9)">│</text><text class="terminal-3386261188-r6" x="317.2" y="239.6" textLength="1122.4" clip-path="url(#terminal-3386261188-line-9)">(All&#160;|&#160;Always&#160;|&#160;API&#160;|&#160;Always&#160;|&#160;CLI&#160;|&#160;Core&#160;|&#160;Integration&#160;|&#160;Other&#160;|&#160;Providers&#160;|&#160;WWW&#160;|&#160;Helm&#160;|&#160;&#160;</text><text class="terminal-338 [...]
-</text><text class="terminal-3386261188-r4" x="0" y="264" textLength="12.2" clip-path="url(#terminal-3386261188-line-10)">│</text><text class="terminal-3386261188-r6" x="317.2" y="264" textLength="1122.4" clip-path="url(#terminal-3386261188-line-10)">Postgres&#160;|&#160;MySQL&#160;|&#160;Integration&#160;|&#160;Other&#160;|&#160;Quarantine)&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160 [...]
-</text><text class="terminal-3386261188-r4" x="0" y="288.4" textLength="12.2" clip-path="url(#terminal-3386261188-line-11)">│</text><text class="terminal-3386261188-r5" x="24.4" y="288.4" textLength="12.2" clip-path="url(#terminal-3386261188-line-11)">-</text><text class="terminal-3386261188-r5" x="36.6" y="288.4" textLength="36.6" clip-path="url(#terminal-3386261188-line-11)">-db</text><text class="terminal-3386261188-r5" x="73.2" y="288.4" textLength="73.2" clip-path="url(#terminal-338 [...]
-</text><text class="terminal-3386261188-r4" x="0" y="312.8" textLength="12.2" clip-path="url(#terminal-3386261188-line-12)">│</text><text class="terminal-3386261188-r5" x="24.4" y="312.8" textLength="12.2" clip-path="url(#terminal-3386261188-line-12)">-</text><text class="terminal-3386261188-r5" x="36.6" y="312.8" textLength="97.6" clip-path="url(#terminal-3386261188-line-12)">-backend</text><text class="terminal-3386261188-r7" x="268.4" y="312.8" textLength="24.4" clip-path="url(#termin [...]
-</text><text class="terminal-3386261188-r4" x="0" y="337.2" textLength="12.2" clip-path="url(#terminal-3386261188-line-13)">│</text><text class="terminal-3386261188-r5" x="24.4" y="337.2" textLength="12.2" clip-path="url(#terminal-3386261188-line-13)">-</text><text class="terminal-3386261188-r5" x="36.6" y="337.2" textLength="85.4" clip-path="url(#terminal-3386261188-line-13)">-python</text><text class="terminal-3386261188-r7" x="268.4" y="337.2" textLength="24.4" clip-path="url(#termina [...]
-</text><text class="terminal-3386261188-r4" x="0" y="361.6" textLength="12.2" clip-path="url(#terminal-3386261188-line-14)">│</text><text class="terminal-3386261188-r4" x="317.2" y="361.6" textLength="732" clip-path="url(#terminal-3386261188-line-14)">[default:&#160;3.7]&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160 [...]
-</text><text class="terminal-3386261188-r4" x="0" y="386" textLength="12.2" clip-path="url(#terminal-3386261188-line-15)">│</text><text class="terminal-3386261188-r5" x="24.4" y="386" textLength="12.2" clip-path="url(#terminal-3386261188-line-15)">-</text><text class="terminal-3386261188-r5" x="36.6" y="386" textLength="109.8" clip-path="url(#terminal-3386261188-line-15)">-postgres</text><text class="terminal-3386261188-r5" x="146.4" y="386" textLength="97.6" clip-path="url(#terminal-338 [...]
-</text><text class="terminal-3386261188-r4" x="0" y="410.4" textLength="12.2" clip-path="url(#terminal-3386261188-line-16)">│</text><text class="terminal-3386261188-r5" x="24.4" y="410.4" textLength="12.2" clip-path="url(#terminal-3386261188-line-16)">-</text><text class="terminal-3386261188-r5" x="36.6" y="410.4" textLength="73.2" clip-path="url(#terminal-3386261188-line-16)">-mysql</text><text class="terminal-3386261188-r5" x="109.8" y="410.4" textLength="97.6" clip-path="url(#terminal [...]
-</text><text class="terminal-3386261188-r4" x="0" y="434.8" textLength="12.2" clip-path="url(#terminal-3386261188-line-17)">│</text><text class="terminal-3386261188-r5" x="24.4" y="434.8" textLength="12.2" clip-path="url(#terminal-3386261188-line-17)">-</text><text class="terminal-3386261188-r5" x="36.6" y="434.8" textLength="73.2" clip-path="url(#terminal-3386261188-line-17)">-mssql</text><text class="terminal-3386261188-r5" x="109.8" y="434.8" textLength="97.6" clip-path="url(#terminal [...]
-</text><text class="terminal-3386261188-r4" x="0" y="459.2" textLength="1464" clip-path="url(#terminal-3386261188-line-18)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="terminal-3386261188-r2" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-3386261188-line-18)">
-</text><text class="terminal-3386261188-r4" x="0" y="483.6" textLength="24.4" clip-path="url(#terminal-3386261188-line-19)">╭─</text><text class="terminal-3386261188-r4" x="24.4" y="483.6" textLength="1415.2" clip-path="url(#terminal-3386261188-line-19)">&#160;Advanced&#160;flag&#160;for&#160;tests&#160;command&#160;───────────────────────────────────────────────────────────────────────────────────</text><text class="terminal-3386261188-r4" x="1439.6" y="483.6" textLength="24.4" clip-pat [...]
-</text><text class="terminal-3386261188-r4" x="0" y="508" textLength="12.2" clip-path="url(#terminal-3386261188-line-20)">│</text><text class="terminal-3386261188-r5" x="24.4" y="508" textLength="12.2" clip-path="url(#terminal-3386261188-line-20)">-</text><text class="terminal-3386261188-r5" x="36.6" y="508" textLength="73.2" clip-path="url(#terminal-3386261188-line-20)">-limit</text><text class="terminal-3386261188-r5" x="109.8" y="508" textLength="195.2" clip-path="url(#terminal-338626 [...]
-</text><text class="terminal-3386261188-r4" x="0" y="532.4" textLength="12.2" clip-path="url(#terminal-3386261188-line-21)">│</text><text class="terminal-3386261188-r5" x="24.4" y="532.4" textLength="12.2" clip-path="url(#terminal-3386261188-line-21)">-</text><text class="terminal-3386261188-r5" x="36.6" y="532.4" textLength="73.2" clip-path="url(#terminal-3386261188-line-21)">-image</text><text class="terminal-3386261188-r5" x="109.8" y="532.4" textLength="48.8" clip-path="url(#terminal [...]
-</text><text class="terminal-3386261188-r4" x="0" y="556.8" textLength="12.2" clip-path="url(#terminal-3386261188-line-22)">│</text><text class="terminal-3386261188-r5" x="24.4" y="556.8" textLength="12.2" clip-path="url(#terminal-3386261188-line-22)">-</text><text class="terminal-3386261188-r5" x="36.6" y="556.8" textLength="73.2" clip-path="url(#terminal-3386261188-line-22)">-mount</text><text class="terminal-3386261188-r5" x="109.8" y="556.8" textLength="97.6" clip-path="url(#terminal [...]
-</text><text class="terminal-3386261188-r4" x="0" y="581.2" textLength="12.2" clip-path="url(#terminal-3386261188-line-23)">│</text><text class="terminal-3386261188-r2" x="378.2" y="581.2" textLength="1061.4" clip-path="url(#terminal-3386261188-line-23)">selected).&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160 [...]
-</text><text class="terminal-3386261188-r4" x="0" y="605.6" textLength="12.2" clip-path="url(#terminal-3386261188-line-24)">│</text><text class="terminal-3386261188-r6" x="378.2" y="605.6" textLength="1061.4" clip-path="url(#terminal-3386261188-line-24)">(selected&#160;|&#160;all&#160;|&#160;skip&#160;|&#160;remove)&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;& [...]
-</text><text class="terminal-3386261188-r4" x="0" y="630" textLength="12.2" clip-path="url(#terminal-3386261188-line-25)">│</text><text class="terminal-3386261188-r4" x="378.2" y="630" textLength="1061.4" clip-path="url(#terminal-3386261188-line-25)">[default:&#160;selected]&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;& [...]
-</text><text class="terminal-3386261188-r4" x="0" y="654.4" textLength="1464" clip-path="url(#terminal-3386261188-line-26)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="terminal-3386261188-r2" x="1464" y="654.4" textLength="12.2" clip-path="url(#terminal-3386261188-line-26)">
-</text><text class="terminal-3386261188-r4" x="0" y="678.8" textLength="24.4" clip-path="url(#terminal-3386261188-line-27)">╭─</text><text class="terminal-3386261188-r4" x="24.4" y="678.8" textLength="1415.2" clip-path="url(#terminal-3386261188-line-27)">&#160;Options&#160;───────────────────────────────────────────────────────────────────────────────────────────────────────────</text><text class="terminal-3386261188-r4" x="1439.6" y="678.8" textLength="24.4" clip-path="url(#terminal-338 [...]
-</text><text class="terminal-3386261188-r4" x="0" y="703.2" textLength="12.2" clip-path="url(#terminal-3386261188-line-28)">│</text><text class="terminal-3386261188-r5" x="24.4" y="703.2" textLength="12.2" clip-path="url(#terminal-3386261188-line-28)">-</text><text class="terminal-3386261188-r5" x="36.6" y="703.2" textLength="48.8" clip-path="url(#terminal-3386261188-line-28)">-dry</text><text class="terminal-3386261188-r5" x="85.4" y="703.2" textLength="48.8" clip-path="url(#terminal-33 [...]
-</text><text class="terminal-3386261188-r4" x="0" y="727.6" textLength="12.2" clip-path="url(#terminal-3386261188-line-29)">│</text><text class="terminal-3386261188-r5" x="24.4" y="727.6" textLength="12.2" clip-path="url(#terminal-3386261188-line-29)">-</text><text class="terminal-3386261188-r5" x="36.6" y="727.6" textLength="97.6" clip-path="url(#terminal-3386261188-line-29)">-verbose</text><text class="terminal-3386261188-r7" x="158.6" y="727.6" textLength="24.4" clip-path="url(#termin [...]
-</text><text class="terminal-3386261188-r4" x="0" y="752" textLength="12.2" clip-path="url(#terminal-3386261188-line-30)">│</text><text class="terminal-3386261188-r5" x="24.4" y="752" textLength="12.2" clip-path="url(#terminal-3386261188-line-30)">-</text><text class="terminal-3386261188-r5" x="36.6" y="752" textLength="61" clip-path="url(#terminal-3386261188-line-30)">-help</text><text class="terminal-3386261188-r7" x="158.6" y="752" textLength="24.4" clip-path="url(#terminal-3386261188 [...]
-</text><text class="terminal-3386261188-r4" x="0" y="776.4" textLength="1464" clip-path="url(#terminal-3386261188-line-31)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="terminal-3386261188-r2" x="1464" y="776.4" textLength="12.2" clip-path="url(#terminal-3386261188-line-31)">
+    <g class="terminal-3016257943-matrix">
+    <text class="terminal-3016257943-r2" x="1464" y="20" textLength="12.2" clip-path="url(#terminal-3016257943-line-0)">
+</text><text class="terminal-3016257943-r3" x="12.2" y="44.4" textLength="85.4" clip-path="url(#terminal-3016257943-line-1)">Usage:&#160;</text><text class="terminal-3016257943-r1" x="97.6" y="44.4" textLength="549" clip-path="url(#terminal-3016257943-line-1)">breeze&#160;tests&#160;[OPTIONS]&#160;[EXTRA_PYTEST_ARGS]...</text><text class="terminal-3016257943-r2" x="1464" y="44.4" textLength="12.2" clip-path="url(#terminal-3016257943-line-1)">
+</text><text class="terminal-3016257943-r2" x="1464" y="68.8" textLength="12.2" clip-path="url(#terminal-3016257943-line-2)">
+</text><text class="terminal-3016257943-r2" x="12.2" y="93.2" textLength="1110.2" clip-path="url(#terminal-3016257943-line-3)">Run&#160;the&#160;specified&#160;unit&#160;test&#160;targets.&#160;Multiple&#160;targets&#160;may&#160;be&#160;specified&#160;separated&#160;by&#160;spaces.</text><text class="terminal-3016257943-r2" x="1464" y="93.2" textLength="12.2" clip-path="url(#terminal-3016257943-line-3)">
+</text><text class="terminal-3016257943-r2" x="1464" y="117.6" textLength="12.2" clip-path="url(#terminal-3016257943-line-4)">
+</text><text class="terminal-3016257943-r4" x="0" y="142" textLength="24.4" clip-path="url(#terminal-3016257943-line-5)">╭─</text><text class="terminal-3016257943-r4" x="24.4" y="142" textLength="1415.2" clip-path="url(#terminal-3016257943-line-5)">&#160;Basic&#160;flag&#160;for&#160;tests&#160;command&#160;──────────────────────────────────────────────────────────────────────────────────────</text><text class="terminal-3016257943-r4" x="1439.6" y="142" textLength="24.4" clip-path="url(# [...]
+</text><text class="terminal-3016257943-r4" x="0" y="166.4" textLength="12.2" clip-path="url(#terminal-3016257943-line-6)">│</text><text class="terminal-3016257943-r5" x="24.4" y="166.4" textLength="12.2" clip-path="url(#terminal-3016257943-line-6)">-</text><text class="terminal-3016257943-r5" x="36.6" y="166.4" textLength="146.4" clip-path="url(#terminal-3016257943-line-6)">-integration</text><text class="terminal-3016257943-r2" x="317.2" y="166.4" textLength="1110.2" clip-path="url(#te [...]
+</text><text class="terminal-3016257943-r4" x="0" y="190.8" textLength="12.2" clip-path="url(#terminal-3016257943-line-7)">│</text><text class="terminal-3016257943-r6" x="317.2" y="190.8" textLength="1110.2" clip-path="url(#terminal-3016257943-line-7)">(cassandra&#160;|&#160;kerberos&#160;|&#160;mongo&#160;|&#160;openldap&#160;|&#160;pinot&#160;|&#160;rabbitmq&#160;|&#160;redis&#160;|&#160;statsd&#160;|&#160;trino&#160;|&#160;all)</text><text class="terminal-3016257943-r4" x="1451.8" y=" [...]
+</text><text class="terminal-3016257943-r4" x="0" y="215.2" textLength="12.2" clip-path="url(#terminal-3016257943-line-8)">│</text><text class="terminal-3016257943-r5" x="24.4" y="215.2" textLength="12.2" clip-path="url(#terminal-3016257943-line-8)">-</text><text class="terminal-3016257943-r5" x="36.6" y="215.2" textLength="61" clip-path="url(#terminal-3016257943-line-8)">-test</text><text class="terminal-3016257943-r5" x="97.6" y="215.2" textLength="61" clip-path="url(#terminal-30162579 [...]
+</text><text class="terminal-3016257943-r4" x="0" y="239.6" textLength="12.2" clip-path="url(#terminal-3016257943-line-9)">│</text><text class="terminal-3016257943-r2" x="317.2" y="239.6" textLength="341.6" clip-path="url(#terminal-3016257943-line-9)">should&#160;be&#160;run&#160;-&#160;for&#160;example&#160;</text><text class="terminal-3016257943-r5" x="658.8" y="239.6" textLength="12.2" clip-path="url(#terminal-3016257943-line-9)">-</text><text class="terminal-3016257943-r5" x="671" y= [...]
+</text><text class="terminal-3016257943-r4" x="0" y="264" textLength="12.2" clip-path="url(#terminal-3016257943-line-10)">│</text><text class="terminal-3016257943-r6" x="317.2" y="264" textLength="1122.4" clip-path="url(#terminal-3016257943-line-10)">(All&#160;|&#160;Always&#160;|&#160;API&#160;|&#160;Always&#160;|&#160;CLI&#160;|&#160;Core&#160;|&#160;Integration&#160;|&#160;Other&#160;|&#160;Providers&#160;|&#160;WWW&#160;|&#160;Helm&#160;|&#160;&#160;</text><text class="terminal-30162 [...]
+</text><text class="terminal-3016257943-r4" x="0" y="288.4" textLength="12.2" clip-path="url(#terminal-3016257943-line-11)">│</text><text class="terminal-3016257943-r6" x="317.2" y="288.4" textLength="1122.4" clip-path="url(#terminal-3016257943-line-11)">Postgres&#160;|&#160;MySQL&#160;|&#160;Integration&#160;|&#160;Other&#160;|&#160;Quarantine)&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;& [...]
+</text><text class="terminal-3016257943-r4" x="0" y="312.8" textLength="12.2" clip-path="url(#terminal-3016257943-line-12)">│</text><text class="terminal-3016257943-r5" x="24.4" y="312.8" textLength="12.2" clip-path="url(#terminal-3016257943-line-12)">-</text><text class="terminal-3016257943-r5" x="36.6" y="312.8" textLength="36.6" clip-path="url(#terminal-3016257943-line-12)">-db</text><text class="terminal-3016257943-r5" x="73.2" y="312.8" textLength="73.2" clip-path="url(#terminal-301 [...]
+</text><text class="terminal-3016257943-r4" x="0" y="337.2" textLength="12.2" clip-path="url(#terminal-3016257943-line-13)">│</text><text class="terminal-3016257943-r5" x="24.4" y="337.2" textLength="12.2" clip-path="url(#terminal-3016257943-line-13)">-</text><text class="terminal-3016257943-r5" x="36.6" y="337.2" textLength="97.6" clip-path="url(#terminal-3016257943-line-13)">-backend</text><text class="terminal-3016257943-r7" x="268.4" y="337.2" textLength="24.4" clip-path="url(#termin [...]
+</text><text class="terminal-3016257943-r4" x="0" y="361.6" textLength="12.2" clip-path="url(#terminal-3016257943-line-14)">│</text><text class="terminal-3016257943-r5" x="24.4" y="361.6" textLength="12.2" clip-path="url(#terminal-3016257943-line-14)">-</text><text class="terminal-3016257943-r5" x="36.6" y="361.6" textLength="85.4" clip-path="url(#terminal-3016257943-line-14)">-python</text><text class="terminal-3016257943-r7" x="268.4" y="361.6" textLength="24.4" clip-path="url(#termina [...]
+</text><text class="terminal-3016257943-r4" x="0" y="386" textLength="12.2" clip-path="url(#terminal-3016257943-line-15)">│</text><text class="terminal-3016257943-r4" x="317.2" y="386" textLength="732" clip-path="url(#terminal-3016257943-line-15)">[default:&#160;3.7]&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#1 [...]
+</text><text class="terminal-3016257943-r4" x="0" y="410.4" textLength="12.2" clip-path="url(#terminal-3016257943-line-16)">│</text><text class="terminal-3016257943-r5" x="24.4" y="410.4" textLength="12.2" clip-path="url(#terminal-3016257943-line-16)">-</text><text class="terminal-3016257943-r5" x="36.6" y="410.4" textLength="109.8" clip-path="url(#terminal-3016257943-line-16)">-postgres</text><text class="terminal-3016257943-r5" x="146.4" y="410.4" textLength="97.6" clip-path="url(#term [...]
+</text><text class="terminal-3016257943-r4" x="0" y="434.8" textLength="12.2" clip-path="url(#terminal-3016257943-line-17)">│</text><text class="terminal-3016257943-r5" x="24.4" y="434.8" textLength="12.2" clip-path="url(#terminal-3016257943-line-17)">-</text><text class="terminal-3016257943-r5" x="36.6" y="434.8" textLength="73.2" clip-path="url(#terminal-3016257943-line-17)">-mysql</text><text class="terminal-3016257943-r5" x="109.8" y="434.8" textLength="97.6" clip-path="url(#terminal [...]
+</text><text class="terminal-3016257943-r4" x="0" y="459.2" textLength="12.2" clip-path="url(#terminal-3016257943-line-18)">│</text><text class="terminal-3016257943-r5" x="24.4" y="459.2" textLength="12.2" clip-path="url(#terminal-3016257943-line-18)">-</text><text class="terminal-3016257943-r5" x="36.6" y="459.2" textLength="73.2" clip-path="url(#terminal-3016257943-line-18)">-mssql</text><text class="terminal-3016257943-r5" x="109.8" y="459.2" textLength="97.6" clip-path="url(#terminal [...]
+</text><text class="terminal-3016257943-r4" x="0" y="483.6" textLength="1464" clip-path="url(#terminal-3016257943-line-19)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="terminal-3016257943-r2" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-3016257943-line-19)">
+</text><text class="terminal-3016257943-r4" x="0" y="508" textLength="24.4" clip-path="url(#terminal-3016257943-line-20)">╭─</text><text class="terminal-3016257943-r4" x="24.4" y="508" textLength="1415.2" clip-path="url(#terminal-3016257943-line-20)">&#160;Advanced&#160;flag&#160;for&#160;tests&#160;command&#160;───────────────────────────────────────────────────────────────────────────────────</text><text class="terminal-3016257943-r4" x="1439.6" y="508" textLength="24.4" clip-path="url [...]
+</text><text class="terminal-3016257943-r4" x="0" y="532.4" textLength="12.2" clip-path="url(#terminal-3016257943-line-21)">│</text><text class="terminal-3016257943-r5" x="24.4" y="532.4" textLength="12.2" clip-path="url(#terminal-3016257943-line-21)">-</text><text class="terminal-3016257943-r5" x="36.6" y="532.4" textLength="73.2" clip-path="url(#terminal-3016257943-line-21)">-limit</text><text class="terminal-3016257943-r5" x="109.8" y="532.4" textLength="195.2" clip-path="url(#termina [...]
+</text><text class="terminal-3016257943-r4" x="0" y="556.8" textLength="12.2" clip-path="url(#terminal-3016257943-line-22)">│</text><text class="terminal-3016257943-r5" x="24.4" y="556.8" textLength="12.2" clip-path="url(#terminal-3016257943-line-22)">-</text><text class="terminal-3016257943-r5" x="36.6" y="556.8" textLength="73.2" clip-path="url(#terminal-3016257943-line-22)">-image</text><text class="terminal-3016257943-r5" x="109.8" y="556.8" textLength="48.8" clip-path="url(#terminal [...]
+</text><text class="terminal-3016257943-r4" x="0" y="581.2" textLength="12.2" clip-path="url(#terminal-3016257943-line-23)">│</text><text class="terminal-3016257943-r5" x="24.4" y="581.2" textLength="12.2" clip-path="url(#terminal-3016257943-line-23)">-</text><text class="terminal-3016257943-r5" x="36.6" y="581.2" textLength="73.2" clip-path="url(#terminal-3016257943-line-23)">-mount</text><text class="terminal-3016257943-r5" x="109.8" y="581.2" textLength="97.6" clip-path="url(#terminal [...]
+</text><text class="terminal-3016257943-r4" x="0" y="605.6" textLength="12.2" clip-path="url(#terminal-3016257943-line-24)">│</text><text class="terminal-3016257943-r2" x="378.2" y="605.6" textLength="1061.4" clip-path="url(#terminal-3016257943-line-24)">selected).&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160 [...]
+</text><text class="terminal-3016257943-r4" x="0" y="630" textLength="12.2" clip-path="url(#terminal-3016257943-line-25)">│</text><text class="terminal-3016257943-r6" x="378.2" y="630" textLength="1061.4" clip-path="url(#terminal-3016257943-line-25)">(selected&#160;|&#160;all&#160;|&#160;skip&#160;|&#160;remove)&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160 [...]
+</text><text class="terminal-3016257943-r4" x="0" y="654.4" textLength="12.2" clip-path="url(#terminal-3016257943-line-26)">│</text><text class="terminal-3016257943-r4" x="378.2" y="654.4" textLength="1061.4" clip-path="url(#terminal-3016257943-line-26)">[default:&#160;selected]&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#1 [...]
+</text><text class="terminal-3016257943-r4" x="0" y="678.8" textLength="1464" clip-path="url(#terminal-3016257943-line-27)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="terminal-3016257943-r2" x="1464" y="678.8" textLength="12.2" clip-path="url(#terminal-3016257943-line-27)">
+</text><text class="terminal-3016257943-r4" x="0" y="703.2" textLength="24.4" clip-path="url(#terminal-3016257943-line-28)">╭─</text><text class="terminal-3016257943-r4" x="24.4" y="703.2" textLength="1415.2" clip-path="url(#terminal-3016257943-line-28)">&#160;Options&#160;───────────────────────────────────────────────────────────────────────────────────────────────────────────</text><text class="terminal-3016257943-r4" x="1439.6" y="703.2" textLength="24.4" clip-path="url(#terminal-301 [...]
+</text><text class="terminal-3016257943-r4" x="0" y="727.6" textLength="12.2" clip-path="url(#terminal-3016257943-line-29)">│</text><text class="terminal-3016257943-r5" x="24.4" y="727.6" textLength="12.2" clip-path="url(#terminal-3016257943-line-29)">-</text><text class="terminal-3016257943-r5" x="36.6" y="727.6" textLength="48.8" clip-path="url(#terminal-3016257943-line-29)">-dry</text><text class="terminal-3016257943-r5" x="85.4" y="727.6" textLength="48.8" clip-path="url(#terminal-30 [...]
+</text><text class="terminal-3016257943-r4" x="0" y="752" textLength="12.2" clip-path="url(#terminal-3016257943-line-30)">│</text><text class="terminal-3016257943-r5" x="24.4" y="752" textLength="12.2" clip-path="url(#terminal-3016257943-line-30)">-</text><text class="terminal-3016257943-r5" x="36.6" y="752" textLength="97.6" clip-path="url(#terminal-3016257943-line-30)">-verbose</text><text class="terminal-3016257943-r7" x="158.6" y="752" textLength="24.4" clip-path="url(#terminal-30162 [...]
+</text><text class="terminal-3016257943-r4" x="0" y="776.4" textLength="12.2" clip-path="url(#terminal-3016257943-line-31)">│</text><text class="terminal-3016257943-r5" x="24.4" y="776.4" textLength="12.2" clip-path="url(#terminal-3016257943-line-31)">-</text><text class="terminal-3016257943-r5" x="36.6" y="776.4" textLength="61" clip-path="url(#terminal-3016257943-line-31)">-help</text><text class="terminal-3016257943-r7" x="158.6" y="776.4" textLength="24.4" clip-path="url(#terminal-30 [...]
+</text><text class="terminal-3016257943-r4" x="0" y="800.8" textLength="1464" clip-path="url(#terminal-3016257943-line-32)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="terminal-3016257943-r2" x="1464" y="800.8" textLength="12.2" clip-path="url(#terminal-3016257943-line-32)">
 </text>
     </g>
     </g>
diff --git a/scripts/docker/entrypoint_ci.sh b/scripts/docker/entrypoint_ci.sh
index 2994604fc9..42dc3f5712 100755
--- a/scripts/docker/entrypoint_ci.sh
+++ b/scripts/docker/entrypoint_ci.sh
@@ -363,13 +363,18 @@ else
             ${TEST_TYPE} == "Long" || \
             ${TEST_TYPE} == "Integration" ]]; then
         SELECTED_TESTS=("${ALL_TESTS[@]}")
+    elif [[ ${TEST_TYPE} =~ Providers\[(.*)\] ]]; then
+        SELECTED_TESTS=()
+        for provider in ${BASH_REMATCH[1]//,/ }
+        do
+            SELECTED_TESTS+=("tests/providers/${provider//./\/}")
+        done
     else
         echo
         echo  "${COLOR_RED}ERROR: Wrong test type ${TEST_TYPE}  ${COLOR_RESET}"
         echo
         exit 1
     fi
-
 fi
 readonly SELECTED_TESTS CLI_TESTS API_TESTS PROVIDERS_TESTS CORE_TESTS WWW_TESTS \
     ALL_TESTS ALL_PRESELECTED_TESTS