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/06/14 20:32:59 UTC

[airflow] branch main updated: Add CI-friendly progress output for tests (#24236)

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 41fefa146a Add CI-friendly progress output for tests (#24236)
41fefa146a is described below

commit 41fefa146ac64379447db503b7dba82d5121f06a
Author: Jarek Potiuk <ja...@polidea.com>
AuthorDate: Tue Jun 14 22:32:49 2022 +0200

    Add CI-friendly progress output for tests (#24236)
    
    This is the first step to run breeze tests in parallel in CI.
    This flag adds "limited progress" output when running tests
    which means that the runnig tests will just print few lines with
    percent progress and color status indication from last few
    progress lines of Pytest output, but when it completes, the whole output is
    printed in a CI group - colored depending on status.
    
    The final version (wnen we implement parallel test execution) should
    also defer writing the output to until all tests are completed, but
    this should be a follow-up PR.
---
 TESTING.rst                                        |  15 ++
 .../configuration_and_maintenance_commands.py      |   6 +-
 .../airflow_breeze/commands/testing_commands.py    | 163 +++++++++++++++++++--
 dev/breeze/src/airflow_breeze/utils/ci_group.py    |   8 +-
 dev/breeze/src/airflow_breeze/utils/console.py     |  14 ++
 images/breeze/output-commands-hash.txt             |   2 +-
 images/breeze/output-docker-compose-tests.svg      |  92 ++++++------
 images/breeze/output-tests.svg                     | 132 ++++++++++-------
 8 files changed, 319 insertions(+), 113 deletions(-)

diff --git a/TESTING.rst b/TESTING.rst
index 12983726e1..2271e73ecf 100644
--- a/TESTING.rst
+++ b/TESTING.rst
@@ -182,6 +182,21 @@ You can also specify individual tests or a group of tests:
 
     breeze tests --db-reset tests/core/test_core.py::TestCore
 
+You can also limit the tests to execute to specific group of tests
+
+.. code-block:: bash
+
+    breeze tests --test-type Core
+
+
+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
+after it completes.
+
+.. code-block:: bash
+
+    breeze tests --test-type Core --limit-progress-output
+
 
 Running Tests of a specified type from the Host
 -----------------------------------------------
diff --git a/dev/breeze/src/airflow_breeze/commands/configuration_and_maintenance_commands.py b/dev/breeze/src/airflow_breeze/commands/configuration_and_maintenance_commands.py
index d4ca3bcf46..116319a2ef 100644
--- a/dev/breeze/src/airflow_breeze/commands/configuration_and_maintenance_commands.py
+++ b/dev/breeze/src/airflow_breeze/commands/configuration_and_maintenance_commands.py
@@ -157,9 +157,9 @@ def cleanup(verbose: bool, dry_run: bool, github_repository: str, all: bool, ans
         )
         images = command_result.stdout.splitlines() if command_result and command_result.stdout else []
         if images:
-            get_console().print("[light_blue]Removing images:[/]")
+            get_console().print("[info]Removing images:[/]")
             for image in images:
-                get_console().print(f"[light_blue] * {image}[/]")
+                get_console().print(f"[info] * {image}[/]")
             get_console().print()
             docker_rmi_command_to_execute = [
                 'docker',
@@ -173,7 +173,7 @@ def cleanup(verbose: bool, dry_run: bool, github_repository: str, all: bool, ans
             elif given_answer == Answer.QUIT:
                 sys.exit(0)
         else:
-            get_console().print("[light_blue]No locally downloaded images to remove[/]\n")
+            get_console().print("[info]No locally downloaded images to remove[/]\n")
     get_console().print("Pruning docker images")
     given_answer = user_confirm("Are you sure with the removal?")
     if given_answer == Answer.YES:
diff --git a/dev/breeze/src/airflow_breeze/commands/testing_commands.py b/dev/breeze/src/airflow_breeze/commands/testing_commands.py
index 84bfd29d0e..ebe4701b73 100644
--- a/dev/breeze/src/airflow_breeze/commands/testing_commands.py
+++ b/dev/breeze/src/airflow_breeze/commands/testing_commands.py
@@ -14,10 +14,16 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-
+import errno
 import os
+import re
+import shutil
+import subprocess
 import sys
-from typing import Tuple
+import tempfile
+from threading import Event, Thread
+from time import sleep
+from typing import Dict, List, Tuple
 
 import click
 
@@ -25,24 +31,29 @@ from airflow_breeze.commands.main_command import main
 from airflow_breeze.global_constants import ALLOWED_TEST_TYPES
 from airflow_breeze.params.build_prod_params import BuildProdParams
 from airflow_breeze.params.shell_params import ShellParams
+from airflow_breeze.utils.ci_group import ci_group
 from airflow_breeze.utils.common_options import (
+    option_backend,
     option_db_reset,
     option_dry_run,
     option_github_repository,
     option_image_name,
     option_image_tag,
     option_integration,
+    option_mssql_version,
+    option_mysql_version,
+    option_postgres_version,
     option_python,
     option_verbose,
 )
-from airflow_breeze.utils.console import get_console
+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.docker_command_utils import (
     get_env_variables_for_docker_commands,
     perform_environment_checks,
 )
 from airflow_breeze.utils.run_tests import run_docker_compose_tests
-from airflow_breeze.utils.run_utils import run_command
+from airflow_breeze.utils.run_utils import RunCommandResult, run_command
 
 TESTING_COMMANDS = {
     "name": "Testing",
@@ -55,8 +66,8 @@ TESTING_PARAMETERS = {
             "name": "Docker-compose tests flag",
             "options": [
                 "--image-name",
-                "--python",
                 "--image-tag",
+                "--python",
             ],
         }
     ],
@@ -66,7 +77,13 @@ TESTING_PARAMETERS = {
             "options": [
                 "--integration",
                 "--test-type",
+                "--limit-progress-output",
                 "--db-reset",
+                "--backend",
+                "--python",
+                "--postgres-version",
+                "--mysql-version",
+                "--mssql-version",
             ],
         }
     ],
@@ -112,6 +129,91 @@ def docker_compose_tests(
     sys.exit(return_code)
 
 
+class MonitoringThread(Thread):
+    """Thread class with a stop() method. The thread itself has to check
+    regularly for the stopped() condition."""
+
+    def __init__(self, title: str, file_name: str):
+        super().__init__(target=self.peek_percent_at_last_lines_of_file, daemon=True)
+        self._stop_event = Event()
+        self.title = title
+        self.file_name = file_name
+
+    def peek_percent_at_last_lines_of_file(self) -> None:
+        max_line_length = 400
+        matcher = re.compile(r"^.*\[([^\]]*)\]$")
+        while not self.stopped():
+            if os.path.exists(self.file_name):
+                try:
+                    with open(self.file_name, 'rb') as temp_f:
+                        temp_f.seek(-(max_line_length * 2), os.SEEK_END)
+                        tail = temp_f.read().decode()
+                    try:
+                        two_last_lines = tail.splitlines()[-2:]
+                        previous_no_ansi_line = escape_ansi(two_last_lines[0])
+                        m = matcher.match(previous_no_ansi_line)
+                        if m:
+                            get_console().print(f"[info]{self.title}:[/] {m.group(1).strip()}")
+                            print(f"\r{two_last_lines[0]}\r")
+                            print(f"\r{two_last_lines[1]}\r")
+                    except IndexError:
+                        pass
+                except OSError as e:
+                    if e.errno == errno.EINVAL:
+                        pass
+                    else:
+                        raise
+            sleep(5)
+
+    def stop(self):
+        self._stop_event.set()
+
+    def stopped(self):
+        return self._stop_event.is_set()
+
+
+def escape_ansi(line):
+    ansi_escape = re.compile(r'(?:\x1B[@-_]|[\x80-\x9F])[0-?]*[ -/]*[@-~]')
+    return ansi_escape.sub('', line)
+
+
+def run_with_progress(
+    cmd: List[str],
+    env_variables: Dict[str, str],
+    test_type: str,
+    python: str,
+    backend: str,
+    version: str,
+    verbose: bool,
+    dry_run: bool,
+) -> RunCommandResult:
+    title = f"Running tests: {test_type}, Python: {python}, Backend: {backend}:{version}"
+    try:
+        with tempfile.NamedTemporaryFile(mode='w+t', delete=False) as f:
+            get_console().print(f"[info]Starting test = {title}[/]")
+            thread = MonitoringThread(title=title, file_name=f.name)
+            thread.start()
+            try:
+                result = run_command(
+                    cmd,
+                    verbose=verbose,
+                    dry_run=dry_run,
+                    env=env_variables,
+                    check=False,
+                    stdout=f,
+                    stderr=subprocess.STDOUT,
+                )
+            finally:
+                thread.stop()
+                thread.join()
+        with ci_group(f"Result of {title}", message_type=message_type_from_return_code(result.returncode)):
+            with open(f.name) as f:
+                shutil.copyfileobj(f, sys.stdout)
+    finally:
+        os.unlink(f.name)
+    return result
+
+
 @main.command(
     name='tests',
     help="Run the specified unit test targets. Multiple targets may be specified separated by spaces.",
@@ -122,10 +224,19 @@ def docker_compose_tests(
 )
 @option_dry_run
 @option_verbose
+@option_python
+@option_backend
+@option_postgres_version
+@option_mysql_version
+@option_mssql_version
 @option_integration
+@click.option(
+    '--limit-progress-output',
+    help="Limit progress to percentage only and just show the summary when tests complete.",
+    is_flag=True,
+)
 @click.argument('extra_pytest_args', nargs=-1, type=click.UNPROCESSED)
 @click.option(
-    "-tt",
     "--test-type",
     help="Type of test to run.",
     default="All",
@@ -135,6 +246,12 @@ def docker_compose_tests(
 def tests(
     dry_run: bool,
     verbose: bool,
+    python: str,
+    backend: str,
+    postgres_version: str,
+    mysql_version: str,
+    mssql_version: str,
+    limit_progress_output: bool,
     integration: Tuple,
     extra_pytest_args: Tuple,
     test_type: str,
@@ -149,11 +266,39 @@ def tests(
         os.environ["LIST_OF_INTEGRATION_TESTS_TO_RUN"] = ' '.join(list(integration))
     if db_reset:
         os.environ["DB_RESET"] = "true"
-
-    exec_shell_params = ShellParams(verbose=verbose, dry_run=dry_run)
+    exec_shell_params = ShellParams(
+        verbose=verbose,
+        dry_run=dry_run,
+        python=python,
+        backend=backend,
+        postgres_version=postgres_version,
+        mysql_version=mysql_version,
+        mssql_version=mssql_version,
+    )
     env_variables = get_env_variables_for_docker_commands(exec_shell_params)
     perform_environment_checks(verbose=verbose)
     cmd = ['docker-compose', 'run', '--service-ports', '--rm', 'airflow']
     cmd.extend(list(extra_pytest_args))
-    result = run_command(cmd, verbose=verbose, dry_run=dry_run, env=env_variables, check=False)
+    version = (
+        mssql_version
+        if backend == "mssql"
+        else mysql_version
+        if backend == "mysql"
+        else postgres_version
+        if backend == "postgres"
+        else "none"
+    )
+    if limit_progress_output:
+        result = run_with_progress(
+            cmd=cmd,
+            env_variables=env_variables,
+            test_type=test_type,
+            python=python,
+            backend=backend,
+            version=version,
+            verbose=verbose,
+            dry_run=dry_run,
+        )
+    else:
+        result = run_command(cmd, verbose=verbose, dry_run=dry_run, env=env_variables, check=False)
     sys.exit(result.returncode)
diff --git a/dev/breeze/src/airflow_breeze/utils/ci_group.py b/dev/breeze/src/airflow_breeze/utils/ci_group.py
index e65751a322..96525b5525 100644
--- a/dev/breeze/src/airflow_breeze/utils/ci_group.py
+++ b/dev/breeze/src/airflow_breeze/utils/ci_group.py
@@ -18,11 +18,11 @@
 import os
 from contextlib import contextmanager
 
-from airflow_breeze.utils.console import get_console
+from airflow_breeze.utils.console import MessageType, get_console
 
 
 @contextmanager
-def ci_group(title: str, enabled: bool = True):
+def ci_group(title: str, enabled: bool = True, message_type: MessageType = MessageType.INFO):
     """
     If used in GitHub Action, creates an expandable group in the GitHub Action log.
     Otherwise, display simple text groups.
@@ -34,9 +34,9 @@ def ci_group(title: str, enabled: bool = True):
         yield
         return
     if os.environ.get('GITHUB_ACTIONS', 'false') != "true":
-        get_console().print(f"[info]{title}[/]")
+        get_console().print(f"[{message_type.value}]{title}[/]")
         yield
         return
-    get_console().print(f"::group::<CLICK_TO_EXPAND>: [info]{title}[/]")
+    get_console().print(f"::group::<CLICK_TO_EXPAND>: [{message_type.value}]{title}[/]")
     yield
     get_console().print("::endgroup::")
diff --git a/dev/breeze/src/airflow_breeze/utils/console.py b/dev/breeze/src/airflow_breeze/utils/console.py
index 9a14d91eae..41ae65ef61 100644
--- a/dev/breeze/src/airflow_breeze/utils/console.py
+++ b/dev/breeze/src/airflow_breeze/utils/console.py
@@ -19,6 +19,7 @@ Console used by all processes. We are forcing colors and terminal output as Bree
 to be only run in CI or real development terminal - in both cases we want to have colors on.
 """
 import os
+from enum import Enum
 from functools import lru_cache
 
 from rich.console import Console
@@ -56,6 +57,19 @@ def get_theme() -> Theme:
     )
 
 
+class MessageType(Enum):
+    SUCCESS = "success"
+    INFO = "info"
+    WARNING = "warning"
+    ERROR = "error"
+
+
+def message_type_from_return_code(return_code: int) -> MessageType:
+    if return_code == 0:
+        return MessageType.SUCCESS
+    return MessageType.ERROR
+
+
 @lru_cache(maxsize=None)
 def get_console() -> Console:
     return Console(
diff --git a/images/breeze/output-commands-hash.txt b/images/breeze/output-commands-hash.txt
index 9fcea53a4b..50377a3fee 100644
--- a/images/breeze/output-commands-hash.txt
+++ b/images/breeze/output-commands-hash.txt
@@ -1 +1 @@
-7f2019004f86eeab48332eb0ea11114d
+2942c0bca323521e3e9af5922d527201
diff --git a/images/breeze/output-docker-compose-tests.svg b/images/breeze/output-docker-compose-tests.svg
index 4830ca1215..75f5c1a31b 100644
--- a/images/breeze/output-docker-compose-tests.svg
+++ b/images/breeze/output-docker-compose-tests.svg
@@ -19,109 +19,109 @@
         font-weight: 700;
     }
 
-    .terminal-25948600-matrix {
+    .terminal-1448538552-matrix {
         font-family: Fira Code, monospace;
         font-size: 20px;
         line-height: 24.4px;
         font-variant-east-asian: full-width;
     }
 
-    .terminal-25948600-title {
+    .terminal-1448538552-title {
         font-size: 18px;
         font-weight: bold;
         font-family: arial;
     }
 
-    .terminal-25948600-r1 { fill: #c5c8c6;font-weight: bold }
-.terminal-25948600-r2 { fill: #c5c8c6 }
-.terminal-25948600-r3 { fill: #d0b344;font-weight: bold }
-.terminal-25948600-r4 { fill: #868887 }
-.terminal-25948600-r5 { fill: #68a0b3;font-weight: bold }
-.terminal-25948600-r6 { fill: #98a84b;font-weight: bold }
-.terminal-25948600-r7 { fill: #8d7b39 }
+    .terminal-1448538552-r1 { fill: #c5c8c6;font-weight: bold }
+.terminal-1448538552-r2 { fill: #c5c8c6 }
+.terminal-1448538552-r3 { fill: #d0b344;font-weight: bold }
+.terminal-1448538552-r4 { fill: #868887 }
+.terminal-1448538552-r5 { fill: #68a0b3;font-weight: bold }
+.terminal-1448538552-r6 { fill: #98a84b;font-weight: bold }
+.terminal-1448538552-r7 { fill: #8d7b39 }
     </style>
 
     <defs>
-    <clipPath id="terminal-25948600-clip-terminal">
+    <clipPath id="terminal-1448538552-clip-terminal">
       <rect x="0" y="0" width="1463.0" height="413.79999999999995" />
     </clipPath>
-    <clipPath id="terminal-25948600-line-0">
+    <clipPath id="terminal-1448538552-line-0">
     <rect x="0" y="1.5" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-25948600-line-1">
+<clipPath id="terminal-1448538552-line-1">
     <rect x="0" y="25.9" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-25948600-line-2">
+<clipPath id="terminal-1448538552-line-2">
     <rect x="0" y="50.3" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-25948600-line-3">
+<clipPath id="terminal-1448538552-line-3">
     <rect x="0" y="74.7" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-25948600-line-4">
+<clipPath id="terminal-1448538552-line-4">
     <rect x="0" y="99.1" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-25948600-line-5">
+<clipPath id="terminal-1448538552-line-5">
     <rect x="0" y="123.5" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-25948600-line-6">
+<clipPath id="terminal-1448538552-line-6">
     <rect x="0" y="147.9" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-25948600-line-7">
+<clipPath id="terminal-1448538552-line-7">
     <rect x="0" y="172.3" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-25948600-line-8">
+<clipPath id="terminal-1448538552-line-8">
     <rect x="0" y="196.7" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-25948600-line-9">
+<clipPath id="terminal-1448538552-line-9">
     <rect x="0" y="221.1" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-25948600-line-10">
+<clipPath id="terminal-1448538552-line-10">
     <rect x="0" y="245.5" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-25948600-line-11">
+<clipPath id="terminal-1448538552-line-11">
     <rect x="0" y="269.9" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-25948600-line-12">
+<clipPath id="terminal-1448538552-line-12">
     <rect x="0" y="294.3" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-25948600-line-13">
+<clipPath id="terminal-1448538552-line-13">
     <rect x="0" y="318.7" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-25948600-line-14">
+<clipPath id="terminal-1448538552-line-14">
     <rect x="0" y="343.1" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-25948600-line-15">
+<clipPath id="terminal-1448538552-line-15">
     <rect x="0" y="367.5" 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="462.8" rx="8"/><text class="terminal-25948600-title" fill="#c5c8c6" text-anchor="middle" x="740" y="27">Command:&#160;docker-compose-tests</text>
+    <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="1480" height="462.8" rx="8"/><text class="terminal-1448538552-title" fill="#c5c8c6" text-anchor="middle" x="740" y="27">Command:&#160;docker-compose-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-25948600-clip-terminal)">
+    <g transform="translate(9, 41)" clip-path="url(#terminal-1448538552-clip-terminal)">
     
-    <g class="terminal-25948600-matrix">
-    <text class="terminal-25948600-r2" x="1464" y="20" textLength="12.2" clip-path="url(#terminal-25948600-line-0)">
-</text><text class="terminal-25948600-r3" x="12.2" y="44.4" textLength="85.4" clip-path="url(#terminal-25948600-line-1)">Usage:&#160;</text><text class="terminal-25948600-r1" x="97.6" y="44.4" textLength="732" clip-path="url(#terminal-25948600-line-1)">breeze&#160;docker-compose-tests&#160;[OPTIONS]&#160;[EXTRA_PYTEST_ARGS]...</text><text class="terminal-25948600-r2" x="1464" y="44.4" textLength="12.2" clip-path="url(#terminal-25948600-line-1)">
-</text><text class="terminal-25948600-r2" x="1464" y="68.8" textLength="12.2" clip-path="url(#terminal-25948600-line-2)">
-</text><text class="terminal-25948600-r2" x="12.2" y="93.2" textLength="305" clip-path="url(#terminal-25948600-line-3)">Run&#160;docker-compose&#160;tests.</text><text class="terminal-25948600-r2" x="1464" y="93.2" textLength="12.2" clip-path="url(#terminal-25948600-line-3)">
-</text><text class="terminal-25948600-r2" x="1464" y="117.6" textLength="12.2" clip-path="url(#terminal-25948600-line-4)">
-</text><text class="terminal-25948600-r4" x="0" y="142" textLength="24.4" clip-path="url(#terminal-25948600-line-5)">╭─</text><text class="terminal-25948600-r4" x="24.4" y="142" textLength="1415.2" clip-path="url(#terminal-25948600-line-5)">&#160;Docker-compose&#160;tests&#160;flag&#160;─────────────────────────────────────────────────────────────────────────────────────────</text><text class="terminal-25948600-r4" x="1439.6" y="142" textLength="24.4" clip-path="url(#terminal-25948600-li [...]
-</text><text class="terminal-25948600-r4" x="0" y="166.4" textLength="12.2" clip-path="url(#terminal-25948600-line-6)">│</text><text class="terminal-25948600-r5" x="24.4" y="166.4" textLength="12.2" clip-path="url(#terminal-25948600-line-6)">-</text><text class="terminal-25948600-r5" x="36.6" y="166.4" textLength="73.2" clip-path="url(#terminal-25948600-line-6)">-image</text><text class="terminal-25948600-r5" x="109.8" y="166.4" textLength="61" clip-path="url(#terminal-25948600-line-6)"> [...]
-</text><text class="terminal-25948600-r4" x="0" y="190.8" textLength="12.2" clip-path="url(#terminal-25948600-line-7)">│</text><text class="terminal-25948600-r5" x="24.4" y="190.8" textLength="12.2" clip-path="url(#terminal-25948600-line-7)">-</text><text class="terminal-25948600-r5" x="36.6" y="190.8" textLength="85.4" clip-path="url(#terminal-25948600-line-7)">-python</text><text class="terminal-25948600-r6" x="195.2" y="190.8" textLength="24.4" clip-path="url(#terminal-25948600-line-7 [...]
-</text><text class="terminal-25948600-r4" x="0" y="215.2" textLength="12.2" clip-path="url(#terminal-25948600-line-8)">│</text><text class="terminal-25948600-r4" x="244" y="215.2" textLength="732" clip-path="url(#terminal-25948600-line-8)">[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;&#160;&#160 [...]
-</text><text class="terminal-25948600-r4" x="0" y="239.6" textLength="12.2" clip-path="url(#terminal-25948600-line-9)">│</text><text class="terminal-25948600-r5" x="24.4" y="239.6" textLength="12.2" clip-path="url(#terminal-25948600-line-9)">-</text><text class="terminal-25948600-r5" x="36.6" y="239.6" textLength="73.2" clip-path="url(#terminal-25948600-line-9)">-image</text><text class="terminal-25948600-r5" x="109.8" y="239.6" textLength="48.8" clip-path="url(#terminal-25948600-line-9) [...]
-</text><text class="terminal-25948600-r4" x="0" y="264" textLength="1464" clip-path="url(#terminal-25948600-line-10)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="terminal-25948600-r2" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-25948600-line-10)">
-</text><text class="terminal-25948600-r4" x="0" y="288.4" textLength="24.4" clip-path="url(#terminal-25948600-line-11)">╭─</text><text class="terminal-25948600-r4" x="24.4" y="288.4" textLength="1415.2" clip-path="url(#terminal-25948600-line-11)">&#160;Options&#160;───────────────────────────────────────────────────────────────────────────────────────────────────────────</text><text class="terminal-25948600-r4" x="1439.6" y="288.4" textLength="24.4" clip-path="url(#terminal-25948600-line [...]
-</text><text class="terminal-25948600-r4" x="0" y="312.8" textLength="12.2" clip-path="url(#terminal-25948600-line-12)">│</text><text class="terminal-25948600-r5" x="24.4" y="312.8" textLength="12.2" clip-path="url(#terminal-25948600-line-12)">-</text><text class="terminal-25948600-r5" x="36.6" y="312.8" textLength="97.6" clip-path="url(#terminal-25948600-line-12)">-verbose</text><text class="terminal-25948600-r6" x="280.6" y="312.8" textLength="24.4" clip-path="url(#terminal-25948600-li [...]
-</text><text class="terminal-25948600-r4" x="0" y="337.2" textLength="12.2" clip-path="url(#terminal-25948600-line-13)">│</text><text class="terminal-25948600-r5" x="24.4" y="337.2" textLength="12.2" clip-path="url(#terminal-25948600-line-13)">-</text><text class="terminal-25948600-r5" x="36.6" y="337.2" textLength="48.8" clip-path="url(#terminal-25948600-line-13)">-dry</text><text class="terminal-25948600-r5" x="85.4" y="337.2" textLength="48.8" clip-path="url(#terminal-25948600-line-13 [...]
-</text><text class="terminal-25948600-r4" x="0" y="361.6" textLength="12.2" clip-path="url(#terminal-25948600-line-14)">│</text><text class="terminal-25948600-r5" x="24.4" y="361.6" textLength="12.2" clip-path="url(#terminal-25948600-line-14)">-</text><text class="terminal-25948600-r5" x="36.6" y="361.6" textLength="85.4" clip-path="url(#terminal-25948600-line-14)">-github</text><text class="terminal-25948600-r5" x="122" y="361.6" textLength="134.2" clip-path="url(#terminal-25948600-line [...]
-</text><text class="terminal-25948600-r4" x="0" y="386" textLength="12.2" clip-path="url(#terminal-25948600-line-15)">│</text><text class="terminal-25948600-r5" x="24.4" y="386" textLength="12.2" clip-path="url(#terminal-25948600-line-15)">-</text><text class="terminal-25948600-r5" x="36.6" y="386" textLength="61" clip-path="url(#terminal-25948600-line-15)">-help</text><text class="terminal-25948600-r6" x="280.6" y="386" textLength="24.4" clip-path="url(#terminal-25948600-line-15)">-h</t [...]
-</text><text class="terminal-25948600-r4" x="0" y="410.4" textLength="1464" clip-path="url(#terminal-25948600-line-16)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="terminal-25948600-r2" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-25948600-line-16)">
+    <g class="terminal-1448538552-matrix">
+    <text class="terminal-1448538552-r2" x="1464" y="20" textLength="12.2" clip-path="url(#terminal-1448538552-line-0)">
+</text><text class="terminal-1448538552-r3" x="12.2" y="44.4" textLength="85.4" clip-path="url(#terminal-1448538552-line-1)">Usage:&#160;</text><text class="terminal-1448538552-r1" x="97.6" y="44.4" textLength="732" clip-path="url(#terminal-1448538552-line-1)">breeze&#160;docker-compose-tests&#160;[OPTIONS]&#160;[EXTRA_PYTEST_ARGS]...</text><text class="terminal-1448538552-r2" x="1464" y="44.4" textLength="12.2" clip-path="url(#terminal-1448538552-line-1)">
+</text><text class="terminal-1448538552-r2" x="1464" y="68.8" textLength="12.2" clip-path="url(#terminal-1448538552-line-2)">
+</text><text class="terminal-1448538552-r2" x="12.2" y="93.2" textLength="305" clip-path="url(#terminal-1448538552-line-3)">Run&#160;docker-compose&#160;tests.</text><text class="terminal-1448538552-r2" x="1464" y="93.2" textLength="12.2" clip-path="url(#terminal-1448538552-line-3)">
+</text><text class="terminal-1448538552-r2" x="1464" y="117.6" textLength="12.2" clip-path="url(#terminal-1448538552-line-4)">
+</text><text class="terminal-1448538552-r4" x="0" y="142" textLength="24.4" clip-path="url(#terminal-1448538552-line-5)">╭─</text><text class="terminal-1448538552-r4" x="24.4" y="142" textLength="1415.2" clip-path="url(#terminal-1448538552-line-5)">&#160;Docker-compose&#160;tests&#160;flag&#160;─────────────────────────────────────────────────────────────────────────────────────────</text><text class="terminal-1448538552-r4" x="1439.6" y="142" textLength="24.4" clip-path="url(#terminal-1 [...]
+</text><text class="terminal-1448538552-r4" x="0" y="166.4" textLength="12.2" clip-path="url(#terminal-1448538552-line-6)">│</text><text class="terminal-1448538552-r5" x="24.4" y="166.4" textLength="12.2" clip-path="url(#terminal-1448538552-line-6)">-</text><text class="terminal-1448538552-r5" x="36.6" y="166.4" textLength="73.2" clip-path="url(#terminal-1448538552-line-6)">-image</text><text class="terminal-1448538552-r5" x="109.8" y="166.4" textLength="61" clip-path="url(#terminal-1448 [...]
+</text><text class="terminal-1448538552-r4" x="0" y="190.8" textLength="12.2" clip-path="url(#terminal-1448538552-line-7)">│</text><text class="terminal-1448538552-r5" x="24.4" y="190.8" textLength="12.2" clip-path="url(#terminal-1448538552-line-7)">-</text><text class="terminal-1448538552-r5" x="36.6" y="190.8" textLength="73.2" clip-path="url(#terminal-1448538552-line-7)">-image</text><text class="terminal-1448538552-r5" x="109.8" y="190.8" textLength="48.8" clip-path="url(#terminal-14 [...]
+</text><text class="terminal-1448538552-r4" x="0" y="215.2" textLength="12.2" clip-path="url(#terminal-1448538552-line-8)">│</text><text class="terminal-1448538552-r5" x="24.4" y="215.2" textLength="12.2" clip-path="url(#terminal-1448538552-line-8)">-</text><text class="terminal-1448538552-r5" x="36.6" y="215.2" textLength="85.4" clip-path="url(#terminal-1448538552-line-8)">-python</text><text class="terminal-1448538552-r6" x="195.2" y="215.2" textLength="24.4" clip-path="url(#terminal-1 [...]
+</text><text class="terminal-1448538552-r4" x="0" y="239.6" textLength="12.2" clip-path="url(#terminal-1448538552-line-9)">│</text><text class="terminal-1448538552-r4" x="244" y="239.6" textLength="732" clip-path="url(#terminal-1448538552-line-9)">[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-1448538552-r4" x="0" y="264" textLength="1464" clip-path="url(#terminal-1448538552-line-10)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="terminal-1448538552-r2" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-1448538552-line-10)">
+</text><text class="terminal-1448538552-r4" x="0" y="288.4" textLength="24.4" clip-path="url(#terminal-1448538552-line-11)">╭─</text><text class="terminal-1448538552-r4" x="24.4" y="288.4" textLength="1415.2" clip-path="url(#terminal-1448538552-line-11)">&#160;Options&#160;───────────────────────────────────────────────────────────────────────────────────────────────────────────</text><text class="terminal-1448538552-r4" x="1439.6" y="288.4" textLength="24.4" clip-path="url(#terminal-144 [...]
+</text><text class="terminal-1448538552-r4" x="0" y="312.8" textLength="12.2" clip-path="url(#terminal-1448538552-line-12)">│</text><text class="terminal-1448538552-r5" x="24.4" y="312.8" textLength="12.2" clip-path="url(#terminal-1448538552-line-12)">-</text><text class="terminal-1448538552-r5" x="36.6" y="312.8" textLength="97.6" clip-path="url(#terminal-1448538552-line-12)">-verbose</text><text class="terminal-1448538552-r6" x="280.6" y="312.8" textLength="24.4" clip-path="url(#termin [...]
+</text><text class="terminal-1448538552-r4" x="0" y="337.2" textLength="12.2" clip-path="url(#terminal-1448538552-line-13)">│</text><text class="terminal-1448538552-r5" x="24.4" y="337.2" textLength="12.2" clip-path="url(#terminal-1448538552-line-13)">-</text><text class="terminal-1448538552-r5" x="36.6" y="337.2" textLength="48.8" clip-path="url(#terminal-1448538552-line-13)">-dry</text><text class="terminal-1448538552-r5" x="85.4" y="337.2" textLength="48.8" clip-path="url(#terminal-14 [...]
+</text><text class="terminal-1448538552-r4" x="0" y="361.6" textLength="12.2" clip-path="url(#terminal-1448538552-line-14)">│</text><text class="terminal-1448538552-r5" x="24.4" y="361.6" textLength="12.2" clip-path="url(#terminal-1448538552-line-14)">-</text><text class="terminal-1448538552-r5" x="36.6" y="361.6" textLength="85.4" clip-path="url(#terminal-1448538552-line-14)">-github</text><text class="terminal-1448538552-r5" x="122" y="361.6" textLength="134.2" clip-path="url(#terminal [...]
+</text><text class="terminal-1448538552-r4" x="0" y="386" textLength="12.2" clip-path="url(#terminal-1448538552-line-15)">│</text><text class="terminal-1448538552-r5" x="24.4" y="386" textLength="12.2" clip-path="url(#terminal-1448538552-line-15)">-</text><text class="terminal-1448538552-r5" x="36.6" y="386" textLength="61" clip-path="url(#terminal-1448538552-line-15)">-help</text><text class="terminal-1448538552-r6" x="280.6" y="386" textLength="24.4" clip-path="url(#terminal-1448538552 [...]
+</text><text class="terminal-1448538552-r4" x="0" y="410.4" textLength="1464" clip-path="url(#terminal-1448538552-line-16)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="terminal-1448538552-r2" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-1448538552-line-16)">
 </text>
     </g>
     </g>
diff --git a/images/breeze/output-tests.svg b/images/breeze/output-tests.svg
index 7c02458342..914f2c4587 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 489.2" xmlns="http://www.w3.org/2000/svg">
+<svg class="rich-terminal" viewBox="0 0 1482 684.4" xmlns="http://www.w3.org/2000/svg">
     <!-- Generated with Rich https://www.textualize.io -->
     <style>
 
@@ -19,113 +19,145 @@
         font-weight: 700;
     }
 
-    .terminal-4193911706-matrix {
+    .terminal-1343491645-matrix {
         font-family: Fira Code, monospace;
         font-size: 20px;
         line-height: 24.4px;
         font-variant-east-asian: full-width;
     }
 
-    .terminal-4193911706-title {
+    .terminal-1343491645-title {
         font-size: 18px;
         font-weight: bold;
         font-family: arial;
     }
 
-    .terminal-4193911706-r1 { fill: #c5c8c6;font-weight: bold }
-.terminal-4193911706-r2 { fill: #c5c8c6 }
-.terminal-4193911706-r3 { fill: #d0b344;font-weight: bold }
-.terminal-4193911706-r4 { fill: #868887 }
-.terminal-4193911706-r5 { fill: #68a0b3;font-weight: bold }
-.terminal-4193911706-r6 { fill: #8d7b39 }
-.terminal-4193911706-r7 { fill: #98a84b;font-weight: bold }
+    .terminal-1343491645-r1 { fill: #c5c8c6;font-weight: bold }
+.terminal-1343491645-r2 { fill: #c5c8c6 }
+.terminal-1343491645-r3 { fill: #d0b344;font-weight: bold }
+.terminal-1343491645-r4 { fill: #868887 }
+.terminal-1343491645-r5 { fill: #68a0b3;font-weight: bold }
+.terminal-1343491645-r6 { fill: #8d7b39 }
+.terminal-1343491645-r7 { fill: #98a84b;font-weight: bold }
     </style>
 
     <defs>
-    <clipPath id="terminal-4193911706-clip-terminal">
-      <rect x="0" y="0" width="1463.0" height="438.2" />
+    <clipPath id="terminal-1343491645-clip-terminal">
+      <rect x="0" y="0" width="1463.0" height="633.4" />
     </clipPath>
-    <clipPath id="terminal-4193911706-line-0">
+    <clipPath id="terminal-1343491645-line-0">
     <rect x="0" y="1.5" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-4193911706-line-1">
+<clipPath id="terminal-1343491645-line-1">
     <rect x="0" y="25.9" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-4193911706-line-2">
+<clipPath id="terminal-1343491645-line-2">
     <rect x="0" y="50.3" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-4193911706-line-3">
+<clipPath id="terminal-1343491645-line-3">
     <rect x="0" y="74.7" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-4193911706-line-4">
+<clipPath id="terminal-1343491645-line-4">
     <rect x="0" y="99.1" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-4193911706-line-5">
+<clipPath id="terminal-1343491645-line-5">
     <rect x="0" y="123.5" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-4193911706-line-6">
+<clipPath id="terminal-1343491645-line-6">
     <rect x="0" y="147.9" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-4193911706-line-7">
+<clipPath id="terminal-1343491645-line-7">
     <rect x="0" y="172.3" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-4193911706-line-8">
+<clipPath id="terminal-1343491645-line-8">
     <rect x="0" y="196.7" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-4193911706-line-9">
+<clipPath id="terminal-1343491645-line-9">
     <rect x="0" y="221.1" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-4193911706-line-10">
+<clipPath id="terminal-1343491645-line-10">
     <rect x="0" y="245.5" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-4193911706-line-11">
+<clipPath id="terminal-1343491645-line-11">
     <rect x="0" y="269.9" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-4193911706-line-12">
+<clipPath id="terminal-1343491645-line-12">
     <rect x="0" y="294.3" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-4193911706-line-13">
+<clipPath id="terminal-1343491645-line-13">
     <rect x="0" y="318.7" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-4193911706-line-14">
+<clipPath id="terminal-1343491645-line-14">
     <rect x="0" y="343.1" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-4193911706-line-15">
+<clipPath id="terminal-1343491645-line-15">
     <rect x="0" y="367.5" width="1464" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-4193911706-line-16">
+<clipPath id="terminal-1343491645-line-16">
     <rect x="0" y="391.9" width="1464" height="24.65"/>
             </clipPath>
+<clipPath id="terminal-1343491645-line-17">
+    <rect x="0" y="416.3" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="terminal-1343491645-line-18">
+    <rect x="0" y="440.7" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="terminal-1343491645-line-19">
+    <rect x="0" y="465.1" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="terminal-1343491645-line-20">
+    <rect x="0" y="489.5" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="terminal-1343491645-line-21">
+    <rect x="0" y="513.9" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="terminal-1343491645-line-22">
+    <rect x="0" y="538.3" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="terminal-1343491645-line-23">
+    <rect x="0" y="562.7" width="1464" height="24.65"/>
+            </clipPath>
+<clipPath id="terminal-1343491645-line-24">
+    <rect x="0" y="587.1" 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="487.2" rx="8"/><text class="terminal-4193911706-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="682.4" rx="8"/><text class="terminal-1343491645-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-4193911706-clip-terminal)">
+    <g transform="translate(9, 41)" clip-path="url(#terminal-1343491645-clip-terminal)">
     
-    <g class="terminal-4193911706-matrix">
-    <text class="terminal-4193911706-r2" x="1464" y="20" textLength="12.2" clip-path="url(#terminal-4193911706-line-0)">
-</text><text class="terminal-4193911706-r3" x="12.2" y="44.4" textLength="85.4" clip-path="url(#terminal-4193911706-line-1)">Usage:&#160;</text><text class="terminal-4193911706-r1" x="97.6" y="44.4" textLength="549" clip-path="url(#terminal-4193911706-line-1)">breeze&#160;tests&#160;[OPTIONS]&#160;[EXTRA_PYTEST_ARGS]...</text><text class="terminal-4193911706-r2" x="1464" y="44.4" textLength="12.2" clip-path="url(#terminal-4193911706-line-1)">
-</text><text class="terminal-4193911706-r2" x="1464" y="68.8" textLength="12.2" clip-path="url(#terminal-4193911706-line-2)">
-</text><text class="terminal-4193911706-r2" x="12.2" y="93.2" textLength="1110.2" clip-path="url(#terminal-4193911706-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-4193911706-r2" x="1464" y="93.2" textLength="12.2" clip-path="url(#terminal-4193911706-line-3)">
-</text><text class="terminal-4193911706-r2" x="1464" y="117.6" textLength="12.2" clip-path="url(#terminal-4193911706-line-4)">
-</text><text class="terminal-4193911706-r4" x="0" y="142" textLength="24.4" clip-path="url(#terminal-4193911706-line-5)">╭─</text><text class="terminal-4193911706-r4" x="24.4" y="142" textLength="1415.2" clip-path="url(#terminal-4193911706-line-5)">&#160;Basic&#160;flag&#160;for&#160;tests&#160;command&#160;──────────────────────────────────────────────────────────────────────────────────────</text><text class="terminal-4193911706-r4" x="1439.6" y="142" textLength="24.4" clip-path="url(# [...]
-</text><text class="terminal-4193911706-r4" x="0" y="166.4" textLength="12.2" clip-path="url(#terminal-4193911706-line-6)">│</text><text class="terminal-4193911706-r5" x="24.4" y="166.4" textLength="12.2" clip-path="url(#terminal-4193911706-line-6)">-</text><text class="terminal-4193911706-r5" x="36.6" y="166.4" textLength="146.4" clip-path="url(#terminal-4193911706-line-6)">-integration</text><text class="terminal-4193911706-r2" x="268.4" y="166.4" textLength="1110.2" clip-path="url(#te [...]
-</text><text class="terminal-4193911706-r4" x="0" y="190.8" textLength="12.2" clip-path="url(#terminal-4193911706-line-7)">│</text><text class="terminal-4193911706-r6" x="268.4" y="190.8" textLength="1110.2" clip-path="url(#terminal-4193911706-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-4193911706-r4" x="1451.8" y=" [...]
-</text><text class="terminal-4193911706-r4" x="0" y="215.2" textLength="12.2" clip-path="url(#terminal-4193911706-line-8)">│</text><text class="terminal-4193911706-r5" x="24.4" y="215.2" textLength="12.2" clip-path="url(#terminal-4193911706-line-8)">-</text><text class="terminal-4193911706-r5" x="36.6" y="215.2" textLength="61" clip-path="url(#terminal-4193911706-line-8)">-test</text><text class="terminal-4193911706-r5" x="97.6" y="215.2" textLength="61" clip-path="url(#terminal-41939117 [...]
-</text><text class="terminal-4193911706-r4" x="0" y="239.6" textLength="12.2" clip-path="url(#terminal-4193911706-line-9)">│</text><text class="terminal-4193911706-r6" x="268.4" y="239.6" textLength="1171.2" clip-path="url(#terminal-4193911706-line-9)">(All&#160;|&#160;Always&#160;|&#160;Core&#160;|&#160;Providers&#160;|&#160;API&#160;|&#160;CLI&#160;|&#160;Integration&#160;|&#160;Other&#160;|&#160;WWW&#160;|&#160;Postgres&#160;|&#160;MySQL&#160;|&#160;&#160;&#160;</text><text class="ter [...]
-</text><text class="terminal-4193911706-r4" x="0" y="264" textLength="12.2" clip-path="url(#terminal-4193911706-line-10)">│</text><text class="terminal-4193911706-r6" x="268.4" y="264" textLength="1171.2" clip-path="url(#terminal-4193911706-line-10)">Helm&#160;|&#160;Quarantined)&#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-4193911706-r4" x="0" y="288.4" textLength="12.2" clip-path="url(#terminal-4193911706-line-11)">│</text><text class="terminal-4193911706-r5" x="24.4" y="288.4" textLength="12.2" clip-path="url(#terminal-4193911706-line-11)">-</text><text class="terminal-4193911706-r5" x="36.6" y="288.4" textLength="36.6" clip-path="url(#terminal-4193911706-line-11)">-db</text><text class="terminal-4193911706-r5" x="73.2" y="288.4" textLength="73.2" clip-path="url(#terminal-419 [...]
-</text><text class="terminal-4193911706-r4" x="0" y="312.8" textLength="1464" clip-path="url(#terminal-4193911706-line-12)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="terminal-4193911706-r2" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-4193911706-line-12)">
-</text><text class="terminal-4193911706-r4" x="0" y="337.2" textLength="24.4" clip-path="url(#terminal-4193911706-line-13)">╭─</text><text class="terminal-4193911706-r4" x="24.4" y="337.2" textLength="1415.2" clip-path="url(#terminal-4193911706-line-13)">&#160;Options&#160;───────────────────────────────────────────────────────────────────────────────────────────────────────────</text><text class="terminal-4193911706-r4" x="1439.6" y="337.2" textLength="24.4" clip-path="url(#terminal-419 [...]
-</text><text class="terminal-4193911706-r4" x="0" y="361.6" textLength="12.2" clip-path="url(#terminal-4193911706-line-14)">│</text><text class="terminal-4193911706-r5" x="24.4" y="361.6" textLength="12.2" clip-path="url(#terminal-4193911706-line-14)">-</text><text class="terminal-4193911706-r5" x="36.6" y="361.6" textLength="48.8" clip-path="url(#terminal-4193911706-line-14)">-dry</text><text class="terminal-4193911706-r5" x="85.4" y="361.6" textLength="48.8" clip-path="url(#terminal-41 [...]
-</text><text class="terminal-4193911706-r4" x="0" y="386" textLength="12.2" clip-path="url(#terminal-4193911706-line-15)">│</text><text class="terminal-4193911706-r5" x="24.4" y="386" textLength="12.2" clip-path="url(#terminal-4193911706-line-15)">-</text><text class="terminal-4193911706-r5" x="36.6" y="386" textLength="97.6" clip-path="url(#terminal-4193911706-line-15)">-verbose</text><text class="terminal-4193911706-r7" x="158.6" y="386" textLength="24.4" clip-path="url(#terminal-41939 [...]
-</text><text class="terminal-4193911706-r4" x="0" y="410.4" textLength="12.2" clip-path="url(#terminal-4193911706-line-16)">│</text><text class="terminal-4193911706-r5" x="24.4" y="410.4" textLength="12.2" clip-path="url(#terminal-4193911706-line-16)">-</text><text class="terminal-4193911706-r5" x="36.6" y="410.4" textLength="61" clip-path="url(#terminal-4193911706-line-16)">-help</text><text class="terminal-4193911706-r7" x="158.6" y="410.4" textLength="24.4" clip-path="url(#terminal-41 [...]
-</text><text class="terminal-4193911706-r4" x="0" y="434.8" textLength="1464" clip-path="url(#terminal-4193911706-line-17)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="terminal-4193911706-r2" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-4193911706-line-17)">
+    <g class="terminal-1343491645-matrix">
+    <text class="terminal-1343491645-r2" x="1464" y="20" textLength="12.2" clip-path="url(#terminal-1343491645-line-0)">
+</text><text class="terminal-1343491645-r3" x="12.2" y="44.4" textLength="85.4" clip-path="url(#terminal-1343491645-line-1)">Usage:&#160;</text><text class="terminal-1343491645-r1" x="97.6" y="44.4" textLength="549" clip-path="url(#terminal-1343491645-line-1)">breeze&#160;tests&#160;[OPTIONS]&#160;[EXTRA_PYTEST_ARGS]...</text><text class="terminal-1343491645-r2" x="1464" y="44.4" textLength="12.2" clip-path="url(#terminal-1343491645-line-1)">
+</text><text class="terminal-1343491645-r2" x="1464" y="68.8" textLength="12.2" clip-path="url(#terminal-1343491645-line-2)">
+</text><text class="terminal-1343491645-r2" x="12.2" y="93.2" textLength="1110.2" clip-path="url(#terminal-1343491645-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-1343491645-r2" x="1464" y="93.2" textLength="12.2" clip-path="url(#terminal-1343491645-line-3)">
+</text><text class="terminal-1343491645-r2" x="1464" y="117.6" textLength="12.2" clip-path="url(#terminal-1343491645-line-4)">
+</text><text class="terminal-1343491645-r4" x="0" y="142" textLength="24.4" clip-path="url(#terminal-1343491645-line-5)">╭─</text><text class="terminal-1343491645-r4" x="24.4" y="142" textLength="1415.2" clip-path="url(#terminal-1343491645-line-5)">&#160;Basic&#160;flag&#160;for&#160;tests&#160;command&#160;──────────────────────────────────────────────────────────────────────────────────────</text><text class="terminal-1343491645-r4" x="1439.6" y="142" textLength="24.4" clip-path="url(# [...]
+</text><text class="terminal-1343491645-r4" x="0" y="166.4" textLength="12.2" clip-path="url(#terminal-1343491645-line-6)">│</text><text class="terminal-1343491645-r5" x="24.4" y="166.4" textLength="12.2" clip-path="url(#terminal-1343491645-line-6)">-</text><text class="terminal-1343491645-r5" x="36.6" y="166.4" textLength="146.4" clip-path="url(#terminal-1343491645-line-6)">-integration</text><text class="terminal-1343491645-r2" x="378.2" y="166.4" textLength="1061.4" clip-path="url(#te [...]
+</text><text class="terminal-1343491645-r4" x="0" y="190.8" textLength="12.2" clip-path="url(#terminal-1343491645-line-7)">│</text><text class="terminal-1343491645-r6" x="378.2" y="190.8" textLength="1061.4" clip-path="url(#terminal-1343491645-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;</text><text class="terminal-1343491645-r4" x="1451.8" y="190. [...]
+</text><text class="terminal-1343491645-r4" x="0" y="215.2" textLength="12.2" clip-path="url(#terminal-1343491645-line-8)">│</text><text class="terminal-1343491645-r6" x="378.2" y="215.2" textLength="1061.4" clip-path="url(#terminal-1343491645-line-8)">all)&#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;&#160;& [...]
+</text><text class="terminal-1343491645-r4" x="0" y="239.6" textLength="12.2" clip-path="url(#terminal-1343491645-line-9)">│</text><text class="terminal-1343491645-r5" x="24.4" y="239.6" textLength="12.2" clip-path="url(#terminal-1343491645-line-9)">-</text><text class="terminal-1343491645-r5" x="36.6" y="239.6" textLength="61" clip-path="url(#terminal-1343491645-line-9)">-test</text><text class="terminal-1343491645-r5" x="97.6" y="239.6" textLength="61" clip-path="url(#terminal-13434916 [...]
+</text><text class="terminal-1343491645-r4" x="0" y="264" textLength="12.2" clip-path="url(#terminal-1343491645-line-10)">│</text><text class="terminal-1343491645-r6" x="378.2" y="264" textLength="1061.4" clip-path="url(#terminal-1343491645-line-10)">(All&#160;|&#160;Always&#160;|&#160;Core&#160;|&#160;Providers&#160;|&#160;API&#160;|&#160;CLI&#160;|&#160;Integration&#160;|&#160;Other&#160;|&#160;WWW&#160;|&#160;Postgres&#160;|&#160;&#160;</text><text class="terminal-1343491645-r4" x="14 [...]
+</text><text class="terminal-1343491645-r4" x="0" y="288.4" textLength="12.2" clip-path="url(#terminal-1343491645-line-11)">│</text><text class="terminal-1343491645-r6" x="378.2" y="288.4" textLength="1061.4" clip-path="url(#terminal-1343491645-line-11)">MySQL&#160;|&#160;Helm&#160;|&#160;Quarantined)&#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;&#16 [...]
+</text><text class="terminal-1343491645-r4" x="0" y="312.8" textLength="12.2" clip-path="url(#terminal-1343491645-line-12)">│</text><text class="terminal-1343491645-r5" x="24.4" y="312.8" textLength="12.2" clip-path="url(#terminal-1343491645-line-12)">-</text><text class="terminal-1343491645-r5" x="36.6" y="312.8" textLength="73.2" clip-path="url(#terminal-1343491645-line-12)">-limit</text><text class="terminal-1343491645-r5" x="109.8" y="312.8" textLength="195.2" clip-path="url(#termina [...]
+</text><text class="terminal-1343491645-r4" x="0" y="337.2" textLength="12.2" clip-path="url(#terminal-1343491645-line-13)">│</text><text class="terminal-1343491645-r5" x="24.4" y="337.2" textLength="12.2" clip-path="url(#terminal-1343491645-line-13)">-</text><text class="terminal-1343491645-r5" x="36.6" y="337.2" textLength="36.6" clip-path="url(#terminal-1343491645-line-13)">-db</text><text class="terminal-1343491645-r5" x="73.2" y="337.2" textLength="73.2" clip-path="url(#terminal-134 [...]
+</text><text class="terminal-1343491645-r4" x="0" y="361.6" textLength="12.2" clip-path="url(#terminal-1343491645-line-14)">│</text><text class="terminal-1343491645-r5" x="24.4" y="361.6" textLength="12.2" clip-path="url(#terminal-1343491645-line-14)">-</text><text class="terminal-1343491645-r5" x="36.6" y="361.6" textLength="97.6" clip-path="url(#terminal-1343491645-line-14)">-backend</text><text class="terminal-1343491645-r7" x="329.4" y="361.6" textLength="24.4" clip-path="url(#termin [...]
+</text><text class="terminal-1343491645-r4" x="0" y="386" textLength="12.2" clip-path="url(#terminal-1343491645-line-15)">│</text><text class="terminal-1343491645-r5" x="24.4" y="386" textLength="12.2" clip-path="url(#terminal-1343491645-line-15)">-</text><text class="terminal-1343491645-r5" x="36.6" y="386" textLength="85.4" clip-path="url(#terminal-1343491645-line-15)">-python</text><text class="terminal-1343491645-r7" x="329.4" y="386" textLength="24.4" clip-path="url(#terminal-134349 [...]
+</text><text class="terminal-1343491645-r4" x="0" y="410.4" textLength="12.2" clip-path="url(#terminal-1343491645-line-16)">│</text><text class="terminal-1343491645-r4" x="378.2" y="410.4" textLength="732" clip-path="url(#terminal-1343491645-line-16)">[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-1343491645-r4" x="0" y="434.8" textLength="12.2" clip-path="url(#terminal-1343491645-line-17)">│</text><text class="terminal-1343491645-r5" x="24.4" y="434.8" textLength="12.2" clip-path="url(#terminal-1343491645-line-17)">-</text><text class="terminal-1343491645-r5" x="36.6" y="434.8" textLength="109.8" clip-path="url(#terminal-1343491645-line-17)">-postgres</text><text class="terminal-1343491645-r5" x="146.4" y="434.8" textLength="97.6" clip-path="url(#term [...]
+</text><text class="terminal-1343491645-r4" x="0" y="459.2" textLength="12.2" clip-path="url(#terminal-1343491645-line-18)">│</text><text class="terminal-1343491645-r5" x="24.4" y="459.2" textLength="12.2" clip-path="url(#terminal-1343491645-line-18)">-</text><text class="terminal-1343491645-r5" x="36.6" y="459.2" textLength="73.2" clip-path="url(#terminal-1343491645-line-18)">-mysql</text><text class="terminal-1343491645-r5" x="109.8" y="459.2" textLength="97.6" clip-path="url(#terminal [...]
+</text><text class="terminal-1343491645-r4" x="0" y="483.6" textLength="12.2" clip-path="url(#terminal-1343491645-line-19)">│</text><text class="terminal-1343491645-r5" x="24.4" y="483.6" textLength="12.2" clip-path="url(#terminal-1343491645-line-19)">-</text><text class="terminal-1343491645-r5" x="36.6" y="483.6" textLength="73.2" clip-path="url(#terminal-1343491645-line-19)">-mssql</text><text class="terminal-1343491645-r5" x="109.8" y="483.6" textLength="97.6" clip-path="url(#terminal [...]
+</text><text class="terminal-1343491645-r4" x="0" y="508" textLength="1464" clip-path="url(#terminal-1343491645-line-20)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="terminal-1343491645-r2" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-1343491645-line-20)">
+</text><text class="terminal-1343491645-r4" x="0" y="532.4" textLength="24.4" clip-path="url(#terminal-1343491645-line-21)">╭─</text><text class="terminal-1343491645-r4" x="24.4" y="532.4" textLength="1415.2" clip-path="url(#terminal-1343491645-line-21)">&#160;Options&#160;───────────────────────────────────────────────────────────────────────────────────────────────────────────</text><text class="terminal-1343491645-r4" x="1439.6" y="532.4" textLength="24.4" clip-path="url(#terminal-134 [...]
+</text><text class="terminal-1343491645-r4" x="0" y="556.8" textLength="12.2" clip-path="url(#terminal-1343491645-line-22)">│</text><text class="terminal-1343491645-r5" x="24.4" y="556.8" textLength="12.2" clip-path="url(#terminal-1343491645-line-22)">-</text><text class="terminal-1343491645-r5" x="36.6" y="556.8" textLength="48.8" clip-path="url(#terminal-1343491645-line-22)">-dry</text><text class="terminal-1343491645-r5" x="85.4" y="556.8" textLength="48.8" clip-path="url(#terminal-13 [...]
+</text><text class="terminal-1343491645-r4" x="0" y="581.2" textLength="12.2" clip-path="url(#terminal-1343491645-line-23)">│</text><text class="terminal-1343491645-r5" x="24.4" y="581.2" textLength="12.2" clip-path="url(#terminal-1343491645-line-23)">-</text><text class="terminal-1343491645-r5" x="36.6" y="581.2" textLength="97.6" clip-path="url(#terminal-1343491645-line-23)">-verbose</text><text class="terminal-1343491645-r7" x="158.6" y="581.2" textLength="24.4" clip-path="url(#termin [...]
+</text><text class="terminal-1343491645-r4" x="0" y="605.6" textLength="12.2" clip-path="url(#terminal-1343491645-line-24)">│</text><text class="terminal-1343491645-r5" x="24.4" y="605.6" textLength="12.2" clip-path="url(#terminal-1343491645-line-24)">-</text><text class="terminal-1343491645-r5" x="36.6" y="605.6" textLength="61" clip-path="url(#terminal-1343491645-line-24)">-help</text><text class="terminal-1343491645-r7" x="158.6" y="605.6" textLength="24.4" clip-path="url(#terminal-13 [...]
+</text><text class="terminal-1343491645-r4" x="0" y="630" textLength="1464" clip-path="url(#terminal-1343491645-line-25)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text class="terminal-1343491645-r2" x="1464" y="630" textLength="12.2" clip-path="url(#terminal-1343491645-line-25)">
 </text>
     </g>
     </g>