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

brbzull0 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new 70859231d8 Add `Test.AddConfigReload()` autest extension (#13075)
70859231d8 is described below

commit 70859231d86c384dd8b038e0a58a280c274bf8a9
Author: Damian Meden <[email protected]>
AuthorDate: Fri Jun 19 11:06:35 2026 +0200

    Add `Test.AddConfigReload()` autest extension (#13075)
    
    * Add Test.AddConfigReload() autest extension
    
    Replace the fragile pattern of fire-and-forget `traffic_ctl config
    reload` followed by sleep/log-grepping with a deterministic helper
    that uses monitor mode (`-m`) to block until completion and validates
    reload tasks via the get_reload_config_status JSONRPC endpoint.
    
    The extension supports expected exit codes (success/fail/timeout/any),
    task presence and absence assertions, per-task status validation,
    custom tokens, and a delay_start parameter for filesystem timestamp
    sensitivity.
    
    Migrate 21 existing tests to use the new helper, removing manual
    sleep synchronization and When.FileContains polling. Add an optional
    filename parameter to ConfigReloadTask so sub-tasks carry their
    associated config file for precise identification in test assertions.
    
    Includes RST documentation for the new extension API.
    
    
    ts::time_parser accepts space-separated duration strings like
    "11 hours 30m", but without shell quoting, such values would be split
    into multiple argv tokens and break the reload command. Apply
    _shell_quote consistently (matching token and data handling right
    above).
    
    * Address some PR comments.
    - remove wait_reload.sh
    - include ssl multicert filename on quic reload handling.
---
 doc/developer-guide/config-reload-framework.en.rst |  10 +-
 .../testing/config-reload-ext.en.rst               | 259 +++++++++++++++++++++
 doc/developer-guide/testing/index.en.rst           |   1 +
 include/mgmt/config/ConfigContext.h                |   2 +-
 include/mgmt/config/ConfigReloadTrace.h            |   2 +-
 src/iocore/net/SSLClientCoordinator.cc             |   6 +-
 src/mgmt/config/ConfigContext.cc                   |   4 +-
 src/mgmt/config/ConfigReloadTrace.cc               |   4 +-
 .../gold_tests/autest-site/config_reload.test.ext  | 225 ++++++++++++++++++
 tests/gold_tests/cache/cache_config_reload.test.py |  16 +-
 tests/gold_tests/dns/splitdns_reload.test.py       |  12 +-
 .../ip_allow/ip_allow_reload_triggered.test.py     |  52 +----
 tests/gold_tests/ip_allow/ip_category.test.py      |  14 +-
 tests/gold_tests/logging/log_retention.test.py     |   8 +-
 .../parent_config/parent_config_reload.test.py     |  12 +-
 .../regex_revalidate/regex_revalidate_miss.test.py |  16 +-
 tests/gold_tests/remap/remap_acl.test.py           |  19 +-
 tests/gold_tests/remap/remap_reload.test.py        |  15 +-
 tests/gold_tests/remap_yaml/remap_acl_yaml.test.py |  20 +-
 .../remap_yaml/remap_reload_yaml.test.py           |  18 +-
 tests/gold_tests/tls/ssl_key_dialog.test.py        |  16 +-
 tests/gold_tests/tls/ssl_multicert_loader.test.py  |  12 +-
 .../tls/tls_check_cert_selection_reload.test.py    |  10 +-
 tests/gold_tests/tls/tls_client_cert.test.py       |  18 +-
 .../tls/tls_client_cert_override_plugin.test.py    |  10 +-
 .../gold_tests/tls/tls_client_cert_plugin.test.py  |  18 +-
 tests/gold_tests/tls/tls_sni_yaml_reload.test.py   |  15 +-
 tests/gold_tests/tls/tls_tunnel.test.py            |  10 +-
 tests/gold_tests/tls/tls_verify4.test.py           |  17 +-
 .../traffic_ctl/remap_inc/remap_inc.test.py        |  15 +-
 .../traffic_ctl/remap_inc/wait_reload.sh           |  34 ---
 31 files changed, 557 insertions(+), 333 deletions(-)

diff --git a/doc/developer-guide/config-reload-framework.en.rst 
b/doc/developer-guide/config-reload-framework.en.rst
index 549512fec2..e6e327d670 100644
--- a/doc/developer-guide/config-reload-framework.en.rst
+++ b/doc/developer-guide/config-reload-framework.en.rst
@@ -993,8 +993,14 @@ After registering a new handler:
 7. Use :option:`traffic_ctl config status` ``--format json`` to inspect the raw
    :ref:`get_reload_config_status` response for automation testing.
 
-**Autests** — the project includes autest helpers for config reload testing. 
Use
-``AddJsonRPCClientRequest`` with ``Request.admin_config_reload()`` to trigger 
reloads, and
+**Autests** — the project includes autest helpers for config reload testing.
+
+For **end-to-end tests** that trigger a reload via ``traffic_ctl`` and 
validate the result, use
+the :ref:`autest-config-reload` extension (``Test.AddConfigReload()``).
+This is the recommended approach for most reload tests.
+
+For **JSONRPC-level tests** that need fine-grained control over request and 
response payloads,
+use ``AddJsonRPCClientRequest`` with ``Request.admin_config_reload()`` to 
trigger reloads, and
 ``Testers.CustomJSONRPCResponse`` to validate responses programmatically. See 
the existing tests
 for examples:
 
diff --git a/doc/developer-guide/testing/config-reload-ext.en.rst 
b/doc/developer-guide/testing/config-reload-ext.en.rst
new file mode 100644
index 0000000000..92e5ba1570
--- /dev/null
+++ b/doc/developer-guide/testing/config-reload-ext.en.rst
@@ -0,0 +1,259 @@
+.. Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.
+
+.. include:: ../../common.defs
+
+.. _autest-config-reload:
+
+Config Reload Test Extension
+****************************
+
+The ``config_reload.test.ext`` extension provides ``Test.AddConfigReload()`` to
+replace the legacy pattern of fire-and-forget ``traffic_ctl config reload``
+followed by log grepping with a deterministic, structured approach.
+
+The extension is loaded automatically from
+``tests/gold_tests/autest-site/config_reload.test.ext``.
+
+
+Why Use This Extension
+======================
+
+The legacy reload test pattern is fragile:
+
+.. code-block:: python
+
+   # OLD: fire-and-forget + sleep + log grep
+   tr = Test.AddTestRun("Reload config")
+   p = tr.Processes.Process("reload-1")
+   p.Command = 'traffic_ctl config reload; sleep 30'
+   p.Env = ts.Env
+   p.ReturnCode = Any(0, -2)
+   p.Ready = When.FileContains(
+       ts.Disk.diags_log.Name, "finished loading", 2)
+   p.Timeout = 20
+   tr.Processes.Default.StartBefore(p)
+   tr.Processes.Default.Command = 'echo "waiting for reload"'
+   tr.TimeOut = 25
+
+Problems with this approach:
+
+- Relies on exact log text that can change across versions.
+- Uses ``sleep`` for synchronization, leading to slow and flaky tests.
+- Does not validate *which* config handler ran.
+- Does not detect reload failures.
+
+The new pattern is a single call:
+
+.. code-block:: python
+
+   # NEW: deterministic, validates specific handlers
+   tr = Test.AddConfigReload(ts, expect_tasks=["sni.yaml"],
+                             description="Reload after sni.yaml touch")
+
+
+How It Works
+============
+
+``AddConfigReload`` uses ``traffic_ctl config reload -m`` (monitor mode) to
+trigger a reload and **block until it completes**. Monitor mode polls the 
server
+for the reload status, so there is no sleeping or guessing. The default timeout
+is **30 seconds** (configurable via the ``timeout`` parameter).
+
+When ``expect_tasks`` or ``expect_absent_tasks`` is set, a second test run
+queries the ``get_reload_config_status`` JSONRPC endpoint and validates the 
task
+tree via ``CustomJSONRPCResponse``. This gives tests access to the full
+structured result — including per-task status, subtasks, and descriptions —
+without relying on the human-readable output of ``traffic_ctl``.
+
+When neither ``expect_tasks`` nor ``expect_absent_tasks`` is set, only the exit
+code is validated (no JSONRPC query). This is useful for reloads where you only
+care that the reload succeeded (exit code 0).
+
+
+Test.AddConfigReload
+====================
+
+Triggers a config reload, blocks until completion, and validates the result.
+
+.. code-block:: python
+
+   tr = Test.AddConfigReload(
+       ts,                          # ATS process object
+       expect="success",            # "success", "fail", "timeout", or "any"
+       token=None,                  # custom token (auto-generated if None)
+       data=None,                   # inline YAML or @file path
+       force=False,                 # --force flag
+       timeout="30s",               # monitor timeout
+       initial_wait=1.0,            # seconds before first poll
+       refresh_int=0.5,             # seconds between polls
+       expect_tasks=None,           # list or dict of expected handler names
+       expect_absent_tasks=None,    # list of handler names that must NOT 
appear
+       description=None,            # test run description (recommended)
+   )
+
+Parameters
+----------
+
+``ts``
+   The ATS process object (from ``Test.MakeATSProcess()``).
+
+``expect``
+   Expected outcome:
+
+   - ``"success"`` — exit code 0 (all handlers succeeded)
+   - ``"fail"`` — exit code 2 (one or more handlers failed)
+   - ``"timeout"`` — exit code 75 (monitor timed out)
+   - ``"any"`` — exit code 0 or 2 (don't care about outcome)
+
+   Default: ``"success"``.
+
+``token``
+   A custom reload token string. If ``None``, an auto-generated token
+   (``autest-reload-1``, ``autest-reload-2``, ...) is used. Tokens are unique
+   per test file.
+
+``data``
+   Inline YAML content or a ``@file`` path to pass via ``--data``. When the
+   value starts with ``@``, it is passed as-is (e.g. ``@/path/to/file.yaml``).
+   Otherwise the string is shell-quoted and passed inline.
+
+   .. note::
+
+      The ``--data`` flag is accepted by ``traffic_ctl config reload`` but
+      individual reload handlers do not yet consume inline data. This parameter
+      is reserved for future use.
+
+``force``
+   If ``True``, adds the ``--force`` flag to start a new reload even when one
+   is already in progress. See the ``traffic_ctl config reload`` documentation
+   for details on force behavior.
+
+``timeout``
+   Duration string for the monitor timeout (e.g. ``"30s"``, ``"1m"``). This
+   controls how long ``traffic_ctl config reload -m`` will poll before giving
+   up. Default: ``"30s"``. Set to ``None`` to disable the timeout (not
+   recommended).
+
+``initial_wait``
+   Seconds to wait before the first poll, giving the server time to schedule
+   handlers. Default: ``1.0``.
+
+``refresh_int``
+   Seconds between status polls. Default: ``0.5``.
+
+``expect_tasks``
+   Expected handler/config names in the reload. Accepts two forms:
+
+   - **List** — checks that each name appears somewhere in the task tree:
+
+     .. code-block:: python
+
+        expect_tasks=["ip_allow.yaml", "sni.yaml"]
+
+   - **Dict** — checks presence *and* per-task status:
+
+     .. code-block:: python
+
+        expect_tasks={"sni.yaml": "fail", "SSLConfig": "success"}
+
+   When not set (``None``), no JSONRPC validation is performed — only the exit
+   code is checked.
+
+``expect_absent_tasks``
+   A list of handler/config names that must **not** appear in the reload task
+   tree. Useful for verifying that touching an unrelated file did not trigger
+   a specific handler.
+
+``description``
+   Description for the ``TestRun``. **Recommended** — always pass a description
+   for readable test output. When omitted, an auto-generated description is
+   used (e.g. ``"Reload config [autest-reload-1]"``).
+
+Return Value
+------------
+
+Returns the reload ``TestRun`` object (the first test run). Callers can add
+extra assertions or ``StillRunningAfter`` references:
+
+.. code-block:: python
+
+   tr = Test.AddConfigReload(ts, expect_tasks=["remap.config"],
+                             description="Reload after remap.config edit")
+   tr.StillRunningAfter = ts
+   tr.StillRunningAfter = origin_server
+
+
+.. note::
+
+   Standalone record-triggered reloads (via ``traffic_ctl config set`` without
+   an explicit ``config reload``) do not create tasks in the reload framework
+   and cannot be verified with this extension.
+
+
+Examples
+========
+
+Basic reload after touching a config file:
+
+.. code-block:: python
+
+   tr = Test.AddTestRun("Touch ip_allow.yaml")
+   tr.Processes.Default.Command = f"touch {config_dir}/ip_allow.yaml"
+   tr.Processes.Default.ReturnCode = 0
+   tr.StillRunningAfter = ts
+
+   tr = Test.AddConfigReload(ts, expect_tasks=["ip_allow.yaml"],
+                             description="Reload after ip_allow.yaml touch")
+
+Expecting a reload failure (e.g. broken sni.yaml):
+
+.. code-block:: python
+
+   tr = Test.AddConfigReload(ts, expect="fail", expect_tasks=["sni.yaml"],
+                             description="Reload with broken sni.yaml")
+
+Verifying a handler was NOT triggered:
+
+.. code-block:: python
+
+   tr = Test.AddConfigReload(ts, expect_absent_tasks=["ip_allow.yaml"],
+                             description="Reload (should NOT trigger 
ip_allow)")
+
+Per-task status validation:
+
+.. code-block:: python
+
+   tr = Test.AddConfigReload(
+       ts,
+       expect="fail",
+       expect_tasks={"sni.yaml": "fail", "SSLConfig": "success"},
+       description="Reload with mixed task outcomes",
+   )
+
+Reload with inline YAML data:
+
+.. code-block:: python
+
+   # NOTE: --data is accepted by traffic_ctl but individual reload handlers
+   # do not yet consume inline data. Reserved for future use.
+   tr = Test.AddConfigReload(
+       ts,
+       data="ip_allow:\n  - apply: in\n    ip_addrs: 0/0\n    action: allow",
+       expect_tasks=["ip_allow.yaml"],
+       description="Reload with inline ip_allow data",
+   )
diff --git a/doc/developer-guide/testing/index.en.rst 
b/doc/developer-guide/testing/index.en.rst
index 363dabc4e5..d14c2542e5 100644
--- a/doc/developer-guide/testing/index.en.rst
+++ b/doc/developer-guide/testing/index.en.rst
@@ -26,3 +26,4 @@ Testing Traffic Server
    :maxdepth: 2
 
    autests.en
+   config-reload-ext.en
diff --git a/include/mgmt/config/ConfigContext.h 
b/include/mgmt/config/ConfigContext.h
index 725b0a2be4..e788598872 100644
--- a/include/mgmt/config/ConfigContext.h
+++ b/include/mgmt/config/ConfigContext.h
@@ -170,7 +170,7 @@ public:
   /// Each dependent reports its own status (in_progress/complete/fail) and 
the parent
   /// task aggregates them. The dependent context also inherits the parent's 
supplied YAML node.
   ///
-  [[nodiscard]] ConfigContext add_dependent_ctx(std::string_view description = 
"");
+  [[nodiscard]] ConfigContext add_dependent_ctx(std::string_view description = 
"", std::string_view filename = "");
 
   /// Get supplied YAML node (for RPC-based reloads).
   /// Returns Undefined when no content was provided (operator bool() == 
false).
diff --git a/include/mgmt/config/ConfigReloadTrace.h 
b/include/mgmt/config/ConfigReloadTrace.h
index 7d9dadbca0..1db523e8a7 100644
--- a/include/mgmt/config/ConfigReloadTrace.h
+++ b/include/mgmt/config/ConfigReloadTrace.h
@@ -221,7 +221,7 @@ public:
 
   /// Create a child sub-task and return a ConfigContext wrapping it.
   /// The child inherits the parent's token and if passed, the supplied YAML 
content.
-  [[nodiscard]] ConfigContext add_child(std::string_view description = "");
+  [[nodiscard]] ConfigContext add_child(std::string_view description = "", 
std::string_view filename = "");
 
   self_type &log(std::string const &text);
   self_type &log(DiagsLevel level, std::string const &text);
diff --git a/src/iocore/net/SSLClientCoordinator.cc 
b/src/iocore/net/SSLClientCoordinator.cc
index e4af562498..97cf1731b9 100644
--- a/src/iocore/net/SSLClientCoordinator.cc
+++ b/src/iocore/net/SSLClientCoordinator.cc
@@ -37,10 +37,10 @@ SSLClientCoordinator::reconfigure(ConfigContext reconf_ctx)
   // The SSLConfig owns the client cert context storage and the SNIConfig will 
load
   // into it.
   SSLConfig::reconfigure(reconf_ctx.add_dependent_ctx("SSLConfig"));
-  SNIConfig::reconfigure(reconf_ctx.add_dependent_ctx("SNIConfig"));
-  
SSLCertificateConfig::reconfigure(reconf_ctx.add_dependent_ctx("SSLCertificateConfig"));
+  SNIConfig::reconfigure(reconf_ctx.add_dependent_ctx("SNIConfig", 
ts::filename::SNI));
+  
SSLCertificateConfig::reconfigure(reconf_ctx.add_dependent_ctx("SSLCertificateConfig",
 ts::filename::SSL_MULTICERT));
 #if TS_USE_QUIC == 1
-  QUICCertConfig::reconfigure(reconf_ctx.add_dependent_ctx("QUICCertConfig"));
+  QUICCertConfig::reconfigure(reconf_ctx.add_dependent_ctx("QUICCertConfig", 
ts::filename::SSL_MULTICERT));
 #endif
   reconf_ctx.complete("SSL configs reloaded");
 }
diff --git a/src/mgmt/config/ConfigContext.cc b/src/mgmt/config/ConfigContext.cc
index 4604911b5b..2f26c9b098 100644
--- a/src/mgmt/config/ConfigContext.cc
+++ b/src/mgmt/config/ConfigContext.cc
@@ -142,10 +142,10 @@ ConfigContext::get_description() const
 }
 
 ConfigContext
-ConfigContext::add_dependent_ctx(std::string_view description)
+ConfigContext::add_dependent_ctx(std::string_view description, 
std::string_view filename)
 {
   if (auto p = _task.lock()) {
-    auto child = p->add_child(description);
+    auto child = p->add_child(description, filename);
     // child task will get the full content of the parent task
     // TODO: eventually we can have a "key" passed so child module
     // only gets their node of interest.
diff --git a/src/mgmt/config/ConfigReloadTrace.cc 
b/src/mgmt/config/ConfigReloadTrace.cc
index bf26a6489f..8d3285c8a1 100644
--- a/src/mgmt/config/ConfigReloadTrace.cc
+++ b/src/mgmt/config/ConfigReloadTrace.cc
@@ -79,13 +79,13 @@ ConfigReloadProgress::get_configured_check_interval()
 }
 
 ConfigContext
-ConfigReloadTask::add_child(std::string_view description)
+ConfigReloadTask::add_child(std::string_view description, std::string_view 
filename)
 {
   std::unique_lock<std::shared_mutex> lock(_mutex);
   // Read token directly - can't call get_token() as it would deadlock (tries 
to acquire shared_lock on same mutex)
   auto trace = std::make_shared<ConfigReloadTask>(_info.token, description, 
false, shared_from_this());
   _info.sub_tasks.push_back(trace);
-  return ConfigContext{trace, description};
+  return ConfigContext{trace, description, filename};
 }
 
 ConfigReloadTask &
diff --git a/tests/gold_tests/autest-site/config_reload.test.ext 
b/tests/gold_tests/autest-site/config_reload.test.ext
new file mode 100644
index 0000000000..8a9f352223
--- /dev/null
+++ b/tests/gold_tests/autest-site/config_reload.test.ext
@@ -0,0 +1,225 @@
+'''
+AuTest extension for config reload operations.
+
+Provides Test.AddConfigReload() to replace the legacy pattern of
+fire-and-forget `traffic_ctl config reload` followed by log grepping via
+When.FileContains("finished loading", N).
+
+Test.AddConfigReload(ts, ...)
+    Triggers a reload via `traffic_ctl config reload -m` (monitor mode),
+    which blocks until the reload reaches a terminal state (default timeout
+    30s), then optionally validates the task tree via the
+    get_reload_config_status JSONRPC endpoint.
+
+Note: standalone record-triggered reloads (via traffic_ctl config set, with
+no explicit config reload) do not create tasks in the reload framework and
+cannot be verified with this extension.
+'''
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+import os
+import jsonrpc
+
+_reload_counter = 0
+
+_EXPECT_EXIT_CODES = {
+    "success": 0,
+    "fail": 2,
+    "timeout": 75,
+}
+
+
+def _next_token():
+    global _reload_counter
+    _reload_counter += 1
+    return f"autest-reload-{_reload_counter}"
+
+
+def _shell_quote(s):
+    """Single-quote a string for shell, escaping embedded single quotes."""
+    return "'" + s.replace("'", "'\\''") + "'"
+
+
+def _task_matches(info, name):
+    """Check if a task matches the given name by description, filename, or 
basename."""
+    for field in ("description", "filename"):
+        val = info.get(field)
+        if not val or val == "<none>":
+            continue
+        if val == name or os.path.basename(val) == name:
+            return True
+    return False
+
+
+def _collect_task_names(info, names=None):
+    """Recursively collect human-friendly task identifiers from the task 
tree."""
+    if names is None:
+        names = set()
+    if isinstance(info, dict):
+        for field in ("description", "filename"):
+            val = info.get(field)
+            if val and val != "<none>":
+                basename = os.path.basename(val)
+                names.add(basename if basename != val else val)
+        for sub in info.get("sub_tasks", []):
+            _collect_task_names(sub, names)
+    return names
+
+
+def _find_task(info, name):
+    """Find a task by description or filename (supports basename matching)."""
+    if isinstance(info, dict):
+        if _task_matches(info, name):
+            return info
+        for sub in info.get("sub_tasks", []):
+            found = _find_task(sub, name)
+            if found:
+                return found
+    return None
+
+
+def _make_task_validator(expect_tasks, expect_absent_tasks):
+    """Build a CustomJSONRPCResponse callback for task validation."""
+
+    def validator(resp):
+        if resp.is_error():
+            return (False, f"RPC error: {resp.error_as_str()}")
+
+        result = resp.__dict__.get("result", {})
+        tasks_list = result.get("tasks", [])
+        if not tasks_list:
+            if expect_tasks:
+                return (False, "No reload tasks in response")
+            if expect_absent_tasks:
+                return (True, "No tasks present, absence confirmed")
+            return (True, "No tasks expected, none found")
+
+        info = tasks_list[0]
+        all_names = _collect_task_names(info)
+
+        if expect_tasks:
+            items = expect_tasks.items() if isinstance(expect_tasks, dict) 
else [(t, None) for t in expect_tasks]
+            for task_name, task_status in items:
+                if task_name not in all_names:
+                    return (False, f"Task '{task_name}' not found in reload. 
Found: {sorted(all_names)}")
+                if task_status is not None:
+                    task = _find_task(info, task_name)
+                    actual = task.get("status", "unknown") if task else 
"unknown"
+                    if actual != task_status:
+                        return (False, f"Task '{task_name}' status is 
'{actual}', expected '{task_status}'")
+
+        if expect_absent_tasks:
+            for task_name in expect_absent_tasks:
+                if task_name in all_names:
+                    return (False, f"Task '{task_name}' should NOT be present 
but was found in: {sorted(all_names)}")
+
+        return (True, "All task assertions passed")
+
+    return validator
+
+
+def AddConfigReload(
+    self,
+    ts,
+    expect="success",
+    token=None,
+    data=None,
+    force=False,
+    timeout="30s",
+    initial_wait=1.0,
+    refresh_int=0.5,
+    expect_tasks=None,
+    expect_absent_tasks=None,
+    delay_start=None,
+    description=None,
+):
+    """Trigger a config reload, block until completion, and validate the 
result.
+
+    Creates one or two TestRuns:
+      1. Runs `traffic_ctl config reload -m` to trigger and monitor the reload.
+         Asserts the exit code based on the `expect` parameter.
+      2. (Optional) If expect_tasks or expect_absent_tasks is set, queries the
+         get_reload_config_status JSONRPC endpoint and validates the task tree
+         using CustomJSONRPCResponse.
+
+    Args:
+        ts: ATS process object.
+        expect: "success" (exit 0), "fail" (exit 2), "timeout" (exit 75),
+            or "any" (exit 0 or 2).
+        token: Custom reload token. Auto-generated if None.
+        data: Inline YAML string or @file path for --data flag.
+        force: If True, adds --force.
+        timeout: Timeout duration string for --timeout (e.g. "30s", "1m").
+            Set to None to disable.
+        initial_wait: Seconds before first poll (--initial-wait).
+        refresh_int: Seconds between polls (--refresh-int).
+        expect_tasks: List of task names expected in the reload, or a dict
+            mapping task names to expected statuses (e.g. {"sni.yaml": 
"fail"}).
+        expect_absent_tasks: List of task names that must NOT appear in the 
reload.
+        delay_start: Seconds to wait before starting the reload TestRun.
+            Useful when the previous step writes a config file and CI may
+            run fast enough that the filesystem timestamp hasn't changed.
+        description: Custom TestRun description.
+
+    Returns:
+        The reload TestRun object (first test run).
+    """
+    if expect not in _EXPECT_EXIT_CODES and expect != "any":
+        raise ValueError(f"expect must be one of {list(_EXPECT_EXIT_CODES) + 
['any']}, got '{expect}'")
+
+    if token is None:
+        token = _next_token()
+
+    if description is None:
+        description = f"Reload config [{token}]"
+        if expect != "success":
+            description += f" (expect {expect})"
+
+    tr = self.AddTestRun(description)
+    if delay_start is not None:
+        tr.DelayStart = delay_start
+    tr.Processes.Default.Env = ts.Env
+    tr.StillRunningAfter = ts
+
+    cmd = f"traffic_ctl config reload -m -t {_shell_quote(token)}"
+    cmd += f" -w {initial_wait} -r {refresh_int}"
+
+    if timeout is not None:
+        cmd += f" -T {_shell_quote(timeout)}"
+    if force:
+        cmd += " --force"
+    if data is not None:
+        cmd += f" --data {_shell_quote(data)}"
+
+    tr.Processes.Default.Command = cmd
+
+    if expect == "any":
+        tr.Processes.Default.ReturnCode = Any(0, 2)
+    else:
+        tr.Processes.Default.ReturnCode = _EXPECT_EXIT_CODES[expect]
+
+    if expect_tasks or expect_absent_tasks:
+        vtr = self.AddTestRun(f"Verify [{token}] tasks")
+        vtr.AddJsonRPCClientRequest(ts, 
jsonrpc.Request.get_reload_config_status(token=token))
+        vtr.Processes.Default.Streams.stdout = Testers.CustomJSONRPCResponse(
+            _make_task_validator(expect_tasks, expect_absent_tasks))
+        vtr.StillRunningAfter = ts
+
+    return tr
+
+
+ExtendTest(AddConfigReload, name="AddConfigReload")
diff --git a/tests/gold_tests/cache/cache_config_reload.test.py 
b/tests/gold_tests/cache/cache_config_reload.test.py
index 21bb799828..52ca11aad6 100644
--- a/tests/gold_tests/cache/cache_config_reload.test.py
+++ b/tests/gold_tests/cache/cache_config_reload.test.py
@@ -49,12 +49,8 @@ tr.Processes.Default.Command = f"touch 
{os.path.join(config_dir, 'cache.config')
 tr.Processes.Default.ReturnCode = 0
 tr.StillRunningAfter = ts
 
-tr = Test.AddTestRun("Reload after cache.config touch")
-tr.Processes.Default.Env = ts.Env
-tr.Processes.Default.Command = 'traffic_ctl config reload --show-details 
--token reload_cache_test'
-tr.Processes.Default.ReturnCode = Any(0, 2)
-tr.StillRunningAfter = ts
-tr.Processes.Default.Streams.stdout = 
Testers.ContainsExpression("cache.config", "Reload output should reference 
cache.config")
+tr = Test.AddConfigReload(
+    ts, expect="any", expect_tasks=["cache.config"], 
token="reload_cache_test", description="Reload after cache.config touch")
 
 # --- Test 2: Touch hosting.config and reload ---
 
@@ -64,9 +60,5 @@ tr.Processes.Default.Command = f"touch 
{os.path.join(config_dir, 'hosting.config
 tr.Processes.Default.ReturnCode = 0
 tr.StillRunningAfter = ts
 
-tr = Test.AddTestRun("Reload after hosting.config touch")
-tr.Processes.Default.Env = ts.Env
-tr.Processes.Default.Command = 'traffic_ctl config reload --show-details 
--token reload_hosting_test'
-tr.Processes.Default.ReturnCode = Any(0, 2)
-tr.StillRunningAfter = ts
-tr.Processes.Default.Streams.stdout = 
Testers.ContainsExpression("hosting.config", "Reload output should reference 
hosting.config")
+tr = Test.AddConfigReload(
+    ts, expect="any", expect_tasks=["hosting.config"], 
token="reload_hosting_test", description="Reload after hosting.config touch")
diff --git a/tests/gold_tests/dns/splitdns_reload.test.py 
b/tests/gold_tests/dns/splitdns_reload.test.py
index aa05867530..0e254e0a17 100644
--- a/tests/gold_tests/dns/splitdns_reload.test.py
+++ b/tests/gold_tests/dns/splitdns_reload.test.py
@@ -73,17 +73,7 @@ class SplitDNSReloadTest:
         tr.Processes.Default.ReturnCode = 0
         tr.StillRunningAfter = self.ts
 
-        tr = Test.AddTestRun("Reload after splitdns.config touch")
-        p = tr.Processes.Process("reload-1")
-        p.Command = 'traffic_ctl config reload; sleep 30'
-        p.Env = self.ts.Env
-        p.ReturnCode = Any(0, -2)
-        # Wait for 2nd "finished loading" (1st is startup)
-        p.Ready = When.FileContains(self.ts.Disk.diags_log.Name, 
"splitdns.config finished loading", 2)
-        p.Timeout = 20
-        tr.Processes.Default.StartBefore(p)
-        tr.Processes.Default.Command = ('echo "waiting for splitdns.config 
reload"')
-        tr.TimeOut = 25
+        tr = Test.AddConfigReload(self.ts, expect_tasks=["splitdns.config"], 
description="Reload after splitdns.config touch")
         tr.StillRunningAfter = self.ts
 
 
diff --git a/tests/gold_tests/ip_allow/ip_allow_reload_triggered.test.py 
b/tests/gold_tests/ip_allow/ip_allow_reload_triggered.test.py
index 3466e9de06..4a8195c037 100644
--- a/tests/gold_tests/ip_allow/ip_allow_reload_triggered.test.py
+++ b/tests/gold_tests/ip_allow/ip_allow_reload_triggered.test.py
@@ -112,16 +112,7 @@ tr.StillRunningAfter = ts
 tr.StillRunningAfter = server
 
 reload_counter += 1
-tr = Test.AddTestRun("Reload after ip_allow.yaml touch")
-p = tr.Processes.Process(f"reload-{reload_counter}")
-p.Command = 'traffic_ctl config reload; sleep 30'
-p.Env = ts.Env
-p.ReturnCode = Any(0, -2)
-p.Ready = When.FileContains(ts.Disk.diags_log.Name, "ip_allow.yaml finished 
loading", 1 + reload_counter)
-p.Timeout = 20
-tr.Processes.Default.StartBefore(p)
-tr.Processes.Default.Command = 'echo "waiting for ip_allow reload after 
ip_allow.yaml touch"'
-tr.TimeOut = 25
+tr = Test.AddConfigReload(ts, expect_tasks=["ip_allow.yaml"], 
description="Reload after ip_allow.yaml touch")
 tr.StillRunningAfter = ts
 
 # ================================================================
@@ -136,16 +127,7 @@ tr.Processes.Default.ReturnCode = 0
 tr.StillRunningAfter = ts
 
 reload_counter += 1
-tr = Test.AddTestRun("Reload after ip_categories touch")
-p = tr.Processes.Process(f"reload-{reload_counter}")
-p.Command = 'traffic_ctl config reload; sleep 30'
-p.Env = ts.Env
-p.ReturnCode = Any(0, -2)
-p.Ready = When.FileContains(ts.Disk.diags_log.Name, "ip_allow.yaml finished 
loading", 1 + reload_counter)
-p.Timeout = 20
-tr.Processes.Default.StartBefore(p)
-tr.Processes.Default.Command = 'echo "waiting for ip_allow reload after 
ip_categories touch"'
-tr.TimeOut = 25
+tr = Test.AddConfigReload(ts, expect_tasks=["ip_allow.yaml"], 
description="Reload after ip_categories touch")
 tr.StillRunningAfter = ts
 
 # ================================================================
@@ -156,24 +138,13 @@ tr.StillRunningAfter = ts
 #         instead of ip_categories.yaml when the record was "").
 # ================================================================
 
-tr = Test.AddTestRun("Touch hosting.config and reload (should NOT trigger 
ip_allow)")
-tr.Processes.Default.Command = (
-    f"touch {os.path.join(config_dir, 'hosting.config')} && "
-    f"sleep 1 && "
-    f"traffic_ctl config reload && "
-    f"sleep 5")
-tr.Processes.Default.Env = ts.Env
-tr.Processes.Default.ReturnCode = Any(0, -2)
-tr.Processes.Default.Timeout = 15
-tr.StillRunningAfter = ts
-
-tr = Test.AddTestRun("Verify ip_allow loaded exactly 3 times (no false 
trigger)")
-tr.DelayStart = 3
-tr.Processes.Default.Command = (f"grep -c 'ip_allow.yaml finished loading' 
{ts.Disk.diags_log.Name} "
-                                f"| grep -qx 3")
+tr = Test.AddTestRun("Touch hosting.config before reload")
+tr.Processes.Default.Command = f"touch {os.path.join(config_dir, 
'hosting.config')}"
 tr.Processes.Default.ReturnCode = 0
 tr.StillRunningAfter = ts
 
+tr = Test.AddConfigReload(ts, expect_absent_tasks=["ip_allow.yaml"], 
description="Reload (should NOT trigger ip_allow)")
+
 # ================================================================
 # Test 4: Functional — change ip_categories content, verify behavior
 #         Proves that add_file_dependency() not only triggers the
@@ -198,16 +169,7 @@ tr.StillRunningAfter = ts
 
 # 4c: Reload and wait for ip_allow to pick up the change
 reload_counter += 1
-tr = Test.AddTestRun("Reload after ip_categories content change")
-p = tr.Processes.Process(f"reload-{reload_counter}")
-p.Command = 'traffic_ctl config reload; sleep 30'
-p.Env = ts.Env
-p.ReturnCode = Any(0, -2)
-p.Ready = When.FileContains(ts.Disk.diags_log.Name, "ip_allow.yaml finished 
loading", 1 + reload_counter)
-p.Timeout = 20
-tr.Processes.Default.StartBefore(p)
-tr.Processes.Default.Command = 'echo "waiting for reload after ip_categories 
content change"'
-tr.TimeOut = 25
+tr = Test.AddConfigReload(ts, expect_tasks=["ip_allow.yaml"], 
description="Reload after ip_categories content change")
 tr.StillRunningAfter = ts
 
 # 4d: GET should now be denied (falls to catch-all: only HEAD allowed)
diff --git a/tests/gold_tests/ip_allow/ip_category.test.py 
b/tests/gold_tests/ip_allow/ip_category.test.py
index 13a9899e80..77a8069f99 100644
--- a/tests/gold_tests/ip_allow/ip_category.test.py
+++ b/tests/gold_tests/ip_allow/ip_category.test.py
@@ -197,21 +197,9 @@ class Test_ip_category:
 
             # Reload the ip_allow.yaml file.
             ts = Test_ip_category._ts
-            tr = Test.AddTestRun(f"Reload the configuration file.")
             Test_ip_category._reload_counter += 1
-            p = 
tr.Processes.Process(f"reload-{Test_ip_category._reload_counter}")
-            # The sleep is added to give time for the reload to happen.
-            p.Command = 'traffic_ctl config reload; sleep 30'
-            p.Env = ts.Env
-            # Killing the sleep can result in a -2 return code.
-            p.ReturnCode = Any(0, -2)
-            p.Ready = When.FileContains(
-                ts.Disk.diags_log.Name, "ip_allow.yaml finished loading", 1 + 
Test_ip_category._reload_counter)
-            p.Timeout = 20
+            tr = Test.AddConfigReload(ts, expect_tasks=["ip_allow.yaml"], 
description="Reload the configuration")
             tr.StillRunningAfter = ts
-            tr.Processes.Default.StartBefore(p)
-            tr.Processes.Default.Command = 'echo "waiting upon traffic server 
to reload"'
-            tr.TimeOut = 20
 
             return
         ts = Test.MakeATSProcess("ts", enable_cache=False, enable_tls=True)
diff --git a/tests/gold_tests/logging/log_retention.test.py 
b/tests/gold_tests/logging/log_retention.test.py
index 8b21343d0e..0743dd814e 100644
--- a/tests/gold_tests/logging/log_retention.test.py
+++ b/tests/gold_tests/logging/log_retention.test.py
@@ -478,13 +478,7 @@ test.tr.Processes.Default.ReturnCode = 0
 test.tr.StillRunningAfter = test.ts
 test.tr.StillRunningAfter = test.server
 
-tr = Test.AddTestRun("Perform a config reload")
-tr.Processes.Default.Command = "traffic_ctl config reload"
-tr.Processes.Default.Env = test.ts.Env
-tr.Processes.Default.ReturnCode = 0
-tr.Processes.Default.TimeOut = 5
-tr.TimeOut = 5
-tr.StillRunningAfter = test.ts
+tr = Test.AddConfigReload(test.ts, description="Perform a config reload")
 tr.StillRunningAfter = test.server
 
 tr = Test.AddTestRun("Get the log to rotate.")
diff --git a/tests/gold_tests/parent_config/parent_config_reload.test.py 
b/tests/gold_tests/parent_config/parent_config_reload.test.py
index acdf31a5cb..5e71a82bfb 100644
--- a/tests/gold_tests/parent_config/parent_config_reload.test.py
+++ b/tests/gold_tests/parent_config/parent_config_reload.test.py
@@ -50,17 +50,7 @@ tr.Processes.Default.Command = f"sleep 3 && touch 
{os.path.join(config_dir, 'par
 tr.Processes.Default.ReturnCode = 0
 tr.StillRunningAfter = ts
 
-tr = Test.AddTestRun("Reload after parent.config touch")
-p = tr.Processes.Process("reload-1")
-p.Command = 'traffic_ctl config reload; sleep 30'
-p.Env = ts.Env
-p.ReturnCode = Any(0, -2)
-# Wait for the 2nd "finished loading" (1st is startup)
-p.Ready = When.FileContains(ts.Disk.diags_log.Name, "parent.config finished 
loading", 2)
-p.Timeout = 20
-tr.Processes.Default.StartBefore(p)
-tr.Processes.Default.Command = 'echo "waiting for parent.config reload after 
file touch"'
-tr.TimeOut = 25
+tr = Test.AddConfigReload(ts, expect_tasks=["parent.config"], 
description="Reload after parent.config touch")
 tr.StillRunningAfter = ts
 
 # ================================================================
diff --git 
a/tests/gold_tests/pluginTest/regex_revalidate/regex_revalidate_miss.test.py 
b/tests/gold_tests/pluginTest/regex_revalidate/regex_revalidate_miss.test.py
index 96ba108f14..fb99820981 100644
--- a/tests/gold_tests/pluginTest/regex_revalidate/regex_revalidate_miss.test.py
+++ b/tests/gold_tests/pluginTest/regex_revalidate/regex_revalidate_miss.test.py
@@ -222,21 +222,19 @@ ps.ReturnCode = 0
 ps.Streams.stdout.Content = Testers.ContainsExpression("X-Cache: hit-fresh", 
"expected cache hit response")
 tr.StillRunningAfter = ts
 
-# 12 Stage - Reload of same time updated rules file
-tr = Test.AddTestRun("Reload same config")
-ps = tr.Processes.Default
+# 12 Stage - Touch the rules file to trigger reload detection
+tr = Test.AddTestRun("Touch regex_revalidate config")
+tr.Processes.Default.Command = f'touch {regex_revalidate_conf_path}'
+tr.Processes.Default.ReturnCode = 0
 tr.StillRunningAfter = ts
 tr.StillRunningAfter = server
-ps.Command = f'touch {regex_revalidate_conf_path} ; traffic_ctl config reload'
-ps.Env = ts.Env
-ps.ReturnCode = 0
-ps.TimeOut = 5
-tr.TimeOut = 5
+
+tr = Test.AddConfigReload(ts, description="Reload same config")
+tr.StillRunningAfter = server
 
 # 13 Request, Cache hit expected
 tr = Test.AddTestRun("Cache hit path1")
 ps = tr.Processes.Default
-tr.DelayStart = 5
 tr.MakeCurlCommand(curl_and_args + ' http://ats/path1', ts=ts)
 ps.ReturnCode = 0
 ps.Streams.stdout.Content = Testers.ContainsExpression("X-Cache: hit-fresh", 
"expected cache hit response")
diff --git a/tests/gold_tests/remap/remap_acl.test.py 
b/tests/gold_tests/remap/remap_acl.test.py
index 0f5b0f5f23..33fef92e3c 100644
--- a/tests/gold_tests/remap/remap_acl.test.py
+++ b/tests/gold_tests/remap/remap_acl.test.py
@@ -153,25 +153,10 @@ class Test_remap_acl:
                     remap_cfg_path, '\n'.join(remap_config_lines), 
ip_allow_path, '\n'.join(self._ip_allow_lines)))
 
             #
-            # Kick off the ATS config reload.
+            # Kick off the ATS config reload and await completion.
             #
-            tr = Test.AddTestRun("Reload the ATS configuration")
-            p = tr.Processes.Default
-            p.Command = 'traffic_ctl config reload'
-            p.Env = ts.Env
-            tr.StillRunningAfter = ts
-
-            #
-            # Await the config reload to finish.
-            #
-            tr = Test.AddTestRun("Await config reload")
-            p = tr.Processes.Default
             Test_remap_acl._ts_reload_counter += 1
-            count = 1 + Test_remap_acl._ts_reload_counter
-            condwait = os.path.join(Test.Variables.AtsTestToolsDir, 'condwait')
-            p.Command = (f"'{condwait}' 30 --contains 
'{ts.Disk.diags_log.Name}' "
-                         f"'remap.config finished loading' {count}")
-            p.Env = ts.Env
+            tr = Test.AddConfigReload(ts, expect_tasks=["remap.config"], 
description="Reload the ATS configuration")
             tr.StillRunningAfter = ts
 
         else:
diff --git a/tests/gold_tests/remap/remap_reload.test.py 
b/tests/gold_tests/remap/remap_reload.test.py
index a0337fc944..b49a1ab4e9 100644
--- a/tests/gold_tests/remap/remap_reload.test.py
+++ b/tests/gold_tests/remap/remap_reload.test.py
@@ -80,15 +80,10 @@ p.Setup.Lambda(
             f"map http://bravo.ex http://bravo.ex:{pv_port}";,
         ]))
 
-tr = Test.AddTestRun("remap_config reload, fails")
-tr.Processes.Default.Env = tm.Env
-tr.Processes.Default.Command = 'sleep 2; traffic_ctl config reload'
+tr = Test.AddConfigReload(tm, expect="fail", expect_tasks=["remap.config"], 
delay_start=2, description="remap_config reload, fails")
 
 tr = Test.AddTestRun("after first reload")
-await_config_reload = tr.Processes.Process('config_reload_failed', 'sleep 30')
-await_config_reload.Ready = When.FileContains(tm.Disk.diags_log.Name, 
"configuration is invalid")
 tr.AddVerifierClientProcess("client_2", replay_file_2, 
http_ports=[tm.Variables.port])
-tr.Processes.Default.StartBefore(await_config_reload)
 
 tr = Test.AddTestRun("Change remap.config to have more than three lines")
 p = tr.Processes.Default
@@ -104,14 +99,10 @@ p.Setup.Lambda(
             f"map http://india.ex http://india.ex:{pv_port}";,
         ]))
 
-tr = Test.AddTestRun("remap_config reload, succeeds")
-tr.Processes.Default.Env = tm.Env
-tr.Processes.Default.Command = 'sleep 2; traffic_ctl config reload'
+tr = Test.AddConfigReload(
+    tm, expect="success", expect_tasks=["remap.config"], delay_start=2, 
description="remap_config reload, succeeds")
 
 tr = Test.AddTestRun("post update charlie")
-await_config_reload = tr.Processes.Process('config_reload_succeeded', 'sleep 
30')
-await_config_reload.Ready = When.FileContains(tm.Disk.diags_log.Name, 
"remap.config finished loading", 2)
-tr.Processes.Default.StartBefore(await_config_reload)
 tr.AddVerifierClientProcess("client_3", replay_file_3, 
http_ports=[tm.Variables.port])
 
 tr = Test.AddTestRun("post update golf")
diff --git a/tests/gold_tests/remap_yaml/remap_acl_yaml.test.py 
b/tests/gold_tests/remap_yaml/remap_acl_yaml.test.py
index edb6d04386..de79a17a58 100644
--- a/tests/gold_tests/remap_yaml/remap_acl_yaml.test.py
+++ b/tests/gold_tests/remap_yaml/remap_acl_yaml.test.py
@@ -162,26 +162,10 @@ class Test_remap_acl:
                     remap_yaml_path, '\n'.join(remap_yaml_lines), 
ip_allow_path, '\n'.join(self._ip_allow_lines)))
 
             #
-            # Kick off the ATS config reload.
+            # Kick off the ATS config reload and await completion.
             #
-            tr = Test.AddTestRun("Reload the ATS configuration")
-            p = tr.Processes.Default
-            p.Command = 'traffic_ctl config reload'
-            p.Env = ts.Env
-            tr.StillRunningAfter = ts
-
-            #
-            # Await the config reload to finish.
-            #
-            tr = Test.AddTestRun("Await config reload")
-            p = tr.Processes.Default
             Test_remap_acl._ts_reload_counter += 1
-            count = 1 + Test_remap_acl._ts_reload_counter
-            condwait = os.path.join(Test.Variables.AtsTestToolsDir, 'condwait')
-            p.Command = (f"'{condwait}' 30 --contains 
'{ts.Disk.diags_log.Name}' "
-                         f"'remap.yaml finished loading' {count}")
-            p.Env = ts.Env
-            tr.StillRunningAfter = ts
+            Test.AddConfigReload(ts, expect_tasks=["remap.yaml"], 
description="Reload the ATS configuration")
 
         else:
             record_config = {
diff --git a/tests/gold_tests/remap_yaml/remap_reload_yaml.test.py 
b/tests/gold_tests/remap_yaml/remap_reload_yaml.test.py
index cd006903cc..af45fde8f9 100644
--- a/tests/gold_tests/remap_yaml/remap_reload_yaml.test.py
+++ b/tests/gold_tests/remap_yaml/remap_reload_yaml.test.py
@@ -39,8 +39,7 @@ replay_file_2 = "reload_2.replay.yaml"
 replay_file_3 = "reload_3.replay.yaml"
 replay_file_4 = "reload_4.replay.yaml"
 
-tm = Test.MakeATSProcess("ts")
-tm.Disk.diags_log.Content = Testers.ContainsExpression("remap.yaml failed to 
load", "Remap should fail to load")
+tm = Test.MakeATSProcess("ts", disable_log_checks=True)
 remap_cfg_path = os.path.join(tm.Variables.CONFIGDIR, 'remap.yaml')
 
 pv = Test.MakeVerifierServerProcess("pv", "reload_server.replay.yaml")
@@ -106,15 +105,10 @@ remap:
       url: http://bravo.ex:{pv_port}
     '''.split("\n")))
 
-tr = Test.AddTestRun("remap_yaml reload, fails")
-tr.Processes.Default.Env = tm.Env
-tr.Processes.Default.Command = 'sleep 2; traffic_ctl config reload'
+tr = Test.AddConfigReload(tm, expect="fail", expect_tasks=["remap.yaml"], 
delay_start=2, description="remap_yaml reload, fails")
 
 tr = Test.AddTestRun("after first reload")
-await_config_reload = tr.Processes.Process('config_reload_failed', 'sleep 30')
-await_config_reload.Ready = When.FileContains(tm.Disk.diags_log.Name, 
"configuration is invalid")
 tr.AddVerifierClientProcess("client_2", replay_file_2, 
http_ports=[tm.Variables.port])
-tr.Processes.Default.StartBefore(await_config_reload)
 
 tr = Test.AddTestRun("Change remap.yaml to have more than three remap rules")
 p = tr.Processes.Default
@@ -151,14 +145,10 @@ remap:
       url: http://india.ex:{pv_port}
     '''.split("\n")))
 
-tr = Test.AddTestRun("remap_yaml reload, succeeds")
-tr.Processes.Default.Env = tm.Env
-tr.Processes.Default.Command = 'sleep 2; traffic_ctl config reload'
+tr = Test.AddConfigReload(
+    tm, expect="success", expect_tasks=["remap.yaml"], delay_start=2, 
description="remap_yaml reload, succeeds")
 
 tr = Test.AddTestRun("post update charlie")
-await_config_reload = tr.Processes.Process('config_reload_succeeded', 'sleep 
30')
-await_config_reload.Ready = When.FileContains(tm.Disk.diags_log.Name, 
"remap.yaml finished loading", 2)
-tr.Processes.Default.StartBefore(await_config_reload)
 tr.AddVerifierClientProcess("client_3", replay_file_3, 
http_ports=[tm.Variables.port])
 
 tr = Test.AddTestRun("post update golf")
diff --git a/tests/gold_tests/tls/ssl_key_dialog.test.py 
b/tests/gold_tests/tls/ssl_key_dialog.test.py
index 107e4d722f..9926bab88a 100644
--- a/tests/gold_tests/tls/ssl_key_dialog.test.py
+++ b/tests/gold_tests/tls/ssl_key_dialog.test.py
@@ -84,22 +84,8 @@ tr2.Processes.Default.Command = 'echo Updated configs'
 tr2.Processes.Default.Env = ts.Env
 tr2.Processes.Default.ReturnCode = 0
 
-tr2reload = Test.AddTestRun("Reload config")
-tr2reload.StillRunningAfter = ts
+tr2reload = Test.AddConfigReload(ts, expect_tasks=["ssl_multicert.yaml"], 
description="Reload config")
 tr2reload.StillRunningAfter = server
-tr2reload.Processes.Default.Command = 'traffic_ctl config reload'
-# Need to copy over the environment so traffic_ctl knows where to find the 
unix domain socket
-tr2reload.Processes.Default.Env = ts.Env
-tr2reload.Processes.Default.ReturnCode = 0
-
-tr2reload = Test.AddTestRun("Await config reload")
-p = tr2reload.Processes.Default
-p.Command = 'echo awaiting config reload'
-p.Env = ts.Env
-p.ReturnCode = 0
-await_config_reload = tr.Processes.Process(f'config_reload_succeeded', 'sleep 
30')
-await_config_reload.Ready = When.FileContains(ts.Disk.diags_log.Name, 
"ssl_multicert.yaml finished loading", 2)
-p.StartBefore(await_config_reload)
 
 tr3 = Test.AddTestRun("use a key with passphrase")
 tr3.Setup.Copy("ssl/signer.pem")
diff --git a/tests/gold_tests/tls/ssl_multicert_loader.test.py 
b/tests/gold_tests/tls/ssl_multicert_loader.test.py
index 5eb95a7f53..db3a15dbaa 100644
--- a/tests/gold_tests/tls/ssl_multicert_loader.test.py
+++ b/tests/gold_tests/tls/ssl_multicert_loader.test.py
@@ -20,7 +20,7 @@ Test reloading ssl_multicert.yaml with errors and keeping 
around the old ssl con
 
 sni_domain = 'example.com'
 
-ts = Test.MakeATSProcess("ts", enable_tls=True)
+ts = Test.MakeATSProcess("ts", enable_tls=True, disable_log_checks=True)
 server = Test.MakeOriginServer("server")
 server2 = Test.MakeOriginServer("server2")
 request_header = {"headers": f"GET / HTTP/1.1\r\nHost: {sni_domain}\r\n\r\n", 
"timestamp": "1469733493.993", "body": ""}
@@ -78,19 +78,13 @@ tr2.Processes.Default.Command = 'echo Updated configs'
 tr2.Processes.Default.Env = ts.Env
 tr2.Processes.Default.ReturnCode = 0
 
-tr2reload = Test.AddTestRun("Reload config")
-tr2reload.StillRunningAfter = ts
+tr2reload = Test.AddConfigReload(ts, expect="fail", 
expect_tasks=["ssl_multicert.yaml"], description="Reload config")
 tr2reload.StillRunningAfter = server
-tr2reload.Processes.Default.Command = 'traffic_ctl config reload'
-tr2reload.Processes.Default.Env = ts.Env
-tr2reload.Processes.Default.ReturnCode = 0
-ts.Disk.diags_log.Content = Testers.ContainsExpression('ERROR: ', 'ERROR')
 
 # Reload of ssl_multicert.yaml should fail, BUT the old config structure
 # should be in place to successfully answer for the test domain
 tr3 = Test.AddTestRun("Make request again for $sni_domain")
-# Wait for the reload to complete
-tr3.Processes.Default.StartBefore(server2, 
ready=When.FileContains(ts.Disk.diags_log.Name, 'failed to load certificate ', 
1))
+tr3.Processes.Default.StartBefore(server2)
 tr3.StillRunningAfter = ts
 tr3.StillRunningAfter = server
 tr3.MakeCurlCommand(
diff --git a/tests/gold_tests/tls/tls_check_cert_selection_reload.test.py 
b/tests/gold_tests/tls/tls_check_cert_selection_reload.test.py
index 84d526bf4f..327b61f300 100644
--- a/tests/gold_tests/tls/tls_check_cert_selection_reload.test.py
+++ b/tests/gold_tests/tls/tls_check_cert_selection_reload.test.py
@@ -99,17 +99,11 @@ tr.Processes.Default.Command = 'touch 
{0}/signed-bar.pem'.format(ts.Variables.SS
 # Need to copy over the environment so traffic_ctl knows where to find the 
unix domain socket
 tr.Processes.Default.ReturnCode = 0
 
-tr = Test.AddTestRun("Reload config")
-tr.StillRunningAfter = ts
+tr = Test.AddConfigReload(ts, expect_tasks=["ssl_multicert.yaml"], 
description="Reload config")
 tr.StillRunningAfter = server
-tr.Processes.Default.Command = 'traffic_ctl config reload'
-# Need to copy over the environment so traffic_ctl knows where to find the 
unix domain socket
-tr.Processes.Default.Env = ts.Env
-tr.Processes.Default.ReturnCode = 0
 
 tr = Test.AddTestRun("Try with signer 1 again")
-# Wait for the reload to complete
-tr.Processes.Default.StartBefore(server3, 
ready=When.FileContains(ts.Disk.diags_log.Name, 'ssl_multicert.yaml finished 
loading', 2))
+tr.Processes.Default.StartBefore(server3)
 tr.StillRunningAfter = ts
 tr.StillRunningAfter = server
 tr.MakeCurlCommand(
diff --git a/tests/gold_tests/tls/tls_client_cert.test.py 
b/tests/gold_tests/tls/tls_client_cert.test.py
index 605ad843c1..eef2839068 100644
--- a/tests/gold_tests/tls/tls_client_cert.test.py
+++ b/tests/gold_tests/tls/tls_client_cert.test.py
@@ -188,19 +188,13 @@ tr2.Processes.Default.Command = 'echo Updated configs'
 tr2.Processes.Default.Env = ts.Env
 tr2.Processes.Default.ReturnCode = 0
 
-tr2reload = Test.AddTestRun("Reload config")
-tr2reload.StillRunningAfter = ts
+tr2reload = Test.AddConfigReload(ts, expect_tasks=["sni.yaml"], 
description="Reload config")
 tr2reload.StillRunningAfter = server
 tr2reload.StillRunningAfter = server2
-tr2reload.Processes.Default.Command = 'traffic_ctl config reload'
-# Need to copy over the environment so traffic_ctl knows where to find the 
unix domain socket
-tr2reload.Processes.Default.Env = ts.Env
-tr2reload.Processes.Default.ReturnCode = 0
 
 # Should succeed
 tr3bar = Test.AddTestRun("Make request with other bar cert to first server")
-# Wait for the reload to complete
-tr3bar.Processes.Default.StartBefore(server3, 
ready=When.FileContains(ts.Disk.diags_log.Name, 'sni.yaml finished loading', 2))
+tr3bar.Processes.Default.StartBefore(server3)
 tr3bar.StillRunningAfter = ts
 tr3bar.StillRunningAfter = server
 tr3bar.StillRunningAfter = server2
@@ -251,18 +245,12 @@ trupdate.Processes.Default.Command = 'traffic_ctl config 
set proxy.config.ssl.cl
 trupdate.Processes.Default.Env = ts.Env
 trupdate.Processes.Default.ReturnCode = 0
 
-trreload = Test.AddTestRun("Reload config after renaming certs")
-trreload.StillRunningAfter = ts
+trreload = Test.AddConfigReload(ts, expect_tasks=["sni.yaml"], 
description="Reload config after renaming certs")
 trreload.StillRunningAfter = server
 trreload.StillRunningAfter = server2
-trreload.Processes.Default.Command = 'traffic_ctl config reload'
-trreload.Processes.Default.Env = ts.Env
-trreload.Processes.Default.ReturnCode = 0
 
 # Should succeed
 tr4bar = Test.AddTestRun("Make request with renamed bar cert to second server")
-# Wait for the reload to complete
-tr4bar.DelayStart = 10
 tr4bar.StillRunningAfter = ts
 tr4bar.StillRunningAfter = server
 tr4bar.StillRunningAfter = server2
diff --git a/tests/gold_tests/tls/tls_client_cert_override_plugin.test.py 
b/tests/gold_tests/tls/tls_client_cert_override_plugin.test.py
index 3b7b8a47d0..039fd10f02 100644
--- a/tests/gold_tests/tls/tls_client_cert_override_plugin.test.py
+++ b/tests/gold_tests/tls/tls_client_cert_override_plugin.test.py
@@ -180,18 +180,12 @@ trupdate.Processes.Default.Command = 'traffic_ctl config 
set proxy.config.ssl.cl
 trupdate.Processes.Default.Env = ts.Env
 trupdate.Processes.Default.ReturnCode = 0
 
-tr2reload = Test.AddTestRun("Reload config")
-tr2reload.StillRunningAfter = ts
+tr2reload = Test.AddConfigReload(ts, expect_tasks=["sni.yaml"], 
description="Reload config")
 tr2reload.StillRunningAfter = server
 tr2reload.StillRunningAfter = server2
-tr2reload.Processes.Default.Command = 'traffic_ctl config reload'
-# Need to copy over the environment so traffic_ctl knows where to find the 
unix domain socket
-tr2reload.Processes.Default.Env = ts.Env
-tr2reload.Processes.Default.ReturnCode = 0
 
 tr3bar = Test.AddTestRun("Make request with other foo.  badcase1 should now 
work")
-# Wait for the reload to complete
-tr3bar.Processes.Default.StartBefore(server3, 
ready=When.FileContains(ts.Disk.diags_log.Name, 'sni.yaml finished loading', 3))
+tr3bar.Processes.Default.StartBefore(server3)
 tr3bar.StillRunningAfter = ts
 tr3bar.StillRunningAfter = server
 tr3bar.StillRunningAfter = server2
diff --git a/tests/gold_tests/tls/tls_client_cert_plugin.test.py 
b/tests/gold_tests/tls/tls_client_cert_plugin.test.py
index 6061b46cc6..1e5d6f9913 100644
--- a/tests/gold_tests/tls/tls_client_cert_plugin.test.py
+++ b/tests/gold_tests/tls/tls_client_cert_plugin.test.py
@@ -185,19 +185,13 @@ tr2.Processes.Default.Command = 'echo Updated configs'
 tr2.Processes.Default.Env = ts.Env
 tr2.Processes.Default.ReturnCode = 0
 
-tr2reload = Test.AddTestRun("Reload config")
-tr2reload.StillRunningAfter = ts
+tr2reload = Test.AddConfigReload(ts, expect_tasks=["sni.yaml"], 
description="Reload config")
 tr2reload.StillRunningAfter = server
 tr2reload.StillRunningAfter = server2
-tr2reload.Processes.Default.Command = 'traffic_ctl config reload'
-# Need to copy over the environment so traffic_ctl knows where to find the 
unix domain socket
-tr2reload.Processes.Default.Env = ts.Env
-tr2reload.Processes.Default.ReturnCode = 0
 
 # Should succeed
 tr3bar = Test.AddTestRun("Make request with other bar cert to first server")
-# Wait for the reload to complete
-tr3bar.Processes.Default.StartBefore(server3, 
ready=When.FileContains(ts.Disk.diags_log.Name, 'sni.yaml finished loading', 2))
+tr3bar.Processes.Default.StartBefore(server3)
 tr3bar.StillRunningAfter = ts
 tr3bar.StillRunningAfter = server
 tr3bar.StillRunningAfter = server2
@@ -248,18 +242,12 @@ trupdate.Processes.Default.Command = 'traffic_ctl config 
set proxy.config.ssl.cl
 trupdate.Processes.Default.Env = ts.Env
 trupdate.Processes.Default.ReturnCode = 0
 
-trreload = Test.AddTestRun("Reload config after renaming certs")
-trreload.StillRunningAfter = ts
+trreload = Test.AddConfigReload(ts, expect_tasks=["sni.yaml"], 
description="Reload config after renaming certs")
 trreload.StillRunningAfter = server
 trreload.StillRunningAfter = server2
-trreload.Processes.Default.Command = 'traffic_ctl config reload'
-trreload.Processes.Default.Env = ts.Env
-trreload.Processes.Default.ReturnCode = 0
 
 # Should succeed
 tr4bar = Test.AddTestRun("Make request with renamed bar cert to second server")
-# Wait for the reload to complete
-tr4bar.DelayStart = 10
 tr4bar.StillRunningAfter = ts
 tr4bar.StillRunningAfter = server
 tr4bar.StillRunningAfter = server2
diff --git a/tests/gold_tests/tls/tls_sni_yaml_reload.test.py 
b/tests/gold_tests/tls/tls_sni_yaml_reload.test.py
index 133ecaf896..cadd04e5e8 100644
--- a/tests/gold_tests/tls/tls_sni_yaml_reload.test.py
+++ b/tests/gold_tests/tls/tls_sni_yaml_reload.test.py
@@ -20,7 +20,7 @@ Test reloading sni.yaml behaves as expected
 
 sni_domain = 'example.com'
 
-ts = Test.MakeATSProcess("ts", enable_tls=True)
+ts = Test.MakeATSProcess("ts", enable_tls=True, disable_log_checks=True)
 server = Test.MakeOriginServer("server")
 server2 = Test.MakeOriginServer("server3")
 request_header = {"headers": f"GET / HTTP/1.1\r\nHost: {sni_domain}\r\n\r\n", 
"timestamp": "1469733493.993", "body": ""}
@@ -84,8 +84,6 @@ tr.MakeCurlCommand(
 tr.Processes.Default.ReturnCode = 0
 tr.Processes.Default.Streams.stdout = Testers.ExcludesExpression("Could Not 
Connect", "Verify curl could successfully connect")
 tr.Processes.Default.Streams.stderr = 
Testers.IncludesExpression(f"CN={sni_domain}", f"Verify curl used the 
{sni_domain} SNI")
-ts.Disk.diags_log.Content = Testers.IncludesExpression(
-    "SSL negotiation finished successfully", "Verify that the TLS handshake 
was successful")
 
 # This config reload should fail because it references non-existent TLS key 
files
 trupd = Test.AddTestRun("Update config file")
@@ -108,20 +106,13 @@ trupd.Processes.Default.Command = 'echo Updated configs'
 trupd.Processes.Default.Env = ts.Env
 trupd.Processes.Default.ReturnCode = 0
 
-tr2reload = Test.AddTestRun("Reload config")
-tr2reload.StillRunningAfter = ts
+tr2reload = Test.AddConfigReload(ts, expect="fail", expect_tasks=["sni.yaml"], 
description="Reload config")
 tr2reload.StillRunningAfter = server
-tr2reload.Processes.Default.Command = 'traffic_ctl config reload'
-tr2reload.Processes.Default.Env = ts.Env
-tr2reload.Processes.Default.ReturnCode = 0
-ts.Disk.diags_log.Content = Testers.ContainsExpression(
-    'sni.yaml failed to load', 'reload should result in failure to load 
sni.yaml')
 
 tr3 = Test.AddTestRun(f"Make request again for {sni_domain} that should still 
work")
-# Wait for the reload to complete
 tr3.Setup.Copy("ssl/signed-bar.pem")
 tr3.Setup.Copy("ssl/signed-bar.key")
-tr3.Processes.Default.StartBefore(server2, 
ready=When.FileContains(ts.Disk.diags_log.Name, "signed-notexist.pem", 1))
+tr3.Processes.Default.StartBefore(server2)
 tr3.StillRunningAfter = ts
 tr3.StillRunningAfter = server
 tr3.MakeCurlCommand(
diff --git a/tests/gold_tests/tls/tls_tunnel.test.py 
b/tests/gold_tests/tls/tls_tunnel.test.py
index ab02018111..9c715aadac 100644
--- a/tests/gold_tests/tls/tls_tunnel.test.py
+++ b/tests/gold_tests/tls/tls_tunnel.test.py
@@ -339,21 +339,15 @@ p.Streams.All += Testers.ContainsExpression('dummy 
SERVER_HELLO', 'Verify a dumm
 p.Streams.All += Testers.ContainsExpression('data: 0', 'Verify that the first 
data packet was received.')
 p.Streams.All += Testers.ContainsExpression('data: 1', 'Verify that the second 
data packet was received.')
 
-trreload = Test.AddTestRun("Reload config")
-trreload.StillRunningAfter = ts
+trreload = Test.AddConfigReload(ts, expect="success", 
expect_tasks=["sni.yaml"], description="Reload config")
 trreload.StillRunningAfter = server_foo
 trreload.StillRunningAfter = server_bar
-trreload.Processes.Default.Command = 'traffic_ctl config reload'
-# Need to copy over the environment so traffic_ctl knows where to find the 
unix domain socket
-trreload.Processes.Default.Env = ts.Env
-trreload.Processes.Default.ReturnCode = 0
 
 # Should terminate on traffic_server (not tunnel)
 tr = Test.AddTestRun("foo.com no Tunnel-test")
 tr.TimeOut = 30
 tr.StillRunningAfter = ts
-# Wait for the reload to complete by running the sni_reload_done test
-tr.Processes.Default.StartBefore(server2, 
ready=When.FileContains(ts.Disk.diags_log.Name, 'sni.yaml finished loading', 2))
+tr.Processes.Default.StartBefore(server2)
 tr.MakeCurlCommand("-v --resolve 'foo.com:{0}:127.0.0.1' -k  
https://foo.com:{0}".format(ts.Variables.ssl_port), ts=ts)
 tr.Processes.Default.Streams.All += Testers.ContainsExpression("Not Found on 
Accelerato", "Terminates on on Traffic Server")
 tr.Processes.Default.Streams.All += Testers.ContainsExpression("ATS", 
"Terminate on Traffic Server")
diff --git a/tests/gold_tests/tls/tls_verify4.test.py 
b/tests/gold_tests/tls/tls_verify4.test.py
index 437d582bd5..6bddabf38a 100644
--- a/tests/gold_tests/tls/tls_verify4.test.py
+++ b/tests/gold_tests/tls/tls_verify4.test.py
@@ -134,15 +134,8 @@ tr2.Processes.Default.Command = 'echo Updated configs'
 tr2.Processes.Default.Env = ts.Env
 tr2.Processes.Default.ReturnCode = 0
 
-# Change the config to PERMISSIVE.  Same command should work now
-trreload = Test.AddTestRun("Reload config")
-trreload.StillRunningAfter = ts
+trreload = Test.AddConfigReload(ts, description="Reload config")
 trreload.StillRunningAfter = server
-# takes a few seconds for the reload to be ready for the next connection
-trreload.Processes.Default.Command = 'traffic_ctl config reload; sleep 5'
-# Need to copy over the environment so traffic_ctl knows where to find the 
unix domain socket
-trreload.Processes.Default.Env = ts.Env
-trreload.Processes.Default.ReturnCode = 0
 
 tragain = Test.AddTestRun("permissive-after-update")
 tragain.MakeCurlCommand("-k -H \"host: random3.com\"  
https://127.0.0.1:{0}".format(ts.Variables.ssl_port), ts=ts)
@@ -178,14 +171,8 @@ tr2.Processes.Default.Command = 'echo Updated configs to 
ENFORCED'
 tr2.Processes.Default.Env = ts.Env
 tr2.Processes.Default.ReturnCode = 0
 
-trreload = Test.AddTestRun("Reload config again")
-trreload.StillRunningAfter = ts
+trreload = Test.AddConfigReload(ts, description="Reload config to ENFORCED")
 trreload.StillRunningAfter = server
-# takes a few seconds for the reload to be ready for the next connection
-trreload.Processes.Default.Command = 'traffic_ctl config reload; sleep 5'
-# Need to copy over the environment so traffic_ctl knows where to find the 
unix domain socket
-trreload.Processes.Default.Env = ts.Env
-trreload.Processes.Default.ReturnCode = 0
 
 tragain = Test.AddTestRun("enforced-after-update")
 tragain.MakeCurlCommand("-k -H \"host: random4.com\"  
https://127.0.0.1:{0}".format(ts.Variables.ssl_port), ts=ts)
diff --git a/tests/gold_tests/traffic_ctl/remap_inc/remap_inc.test.py 
b/tests/gold_tests/traffic_ctl/remap_inc/remap_inc.test.py
index 52984a8ad6..7e7824cb29 100644
--- a/tests/gold_tests/traffic_ctl/remap_inc/remap_inc.test.py
+++ b/tests/gold_tests/traffic_ctl/remap_inc/remap_inc.test.py
@@ -14,16 +14,12 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
-import os
-
 Test.Summary = '''
 Test traffic_ctl config reload with remap.config .include directive
 '''
 
 Test.ContinueOnFail = False
 
-Test.Setup.Copy("wait_reload.sh")
-
 # Define ATS and configure
 ts = Test.MakeATSProcess("ts", enable_cache=False)
 nameserver = Test.MakeDNServer("dns", default='127.0.0.1')
@@ -53,16 +49,7 @@ tr.Processes.Default.Command = (
 tr.Processes.Default.ReturnCode = 0
 tr.StillRunningAfter = ts
 
-tr = Test.AddTestRun("Reload config")
-tr.StillRunningAfter = ts
-tr.Processes.Default.Command = f'traffic_ctl config reload'
-# Need to copy over the environment so traffic_ctl knows where to find the 
unix domain socket
-tr.Processes.Default.Env = ts.Env
-tr.Processes.Default.ReturnCode = 0
-
-tr = Test.AddTestRun("Wait for config reload")
-tr.Processes.Default.Command = './wait_reload.sh ' + 
os.path.join(ts.Variables.LOGDIR, 'diags.log')
-tr.Processes.Default.ReturnCode = 0
+tr = Test.AddConfigReload(ts, expect_tasks=["remap.config"], 
description="Reload remap.config")
 tr.StillRunningAfter = ts
 
 tr = Test.AddTestRun("Get response from generator")
diff --git a/tests/gold_tests/traffic_ctl/remap_inc/wait_reload.sh 
b/tests/gold_tests/traffic_ctl/remap_inc/wait_reload.sh
deleted file mode 100755
index e0ace41f87..0000000000
--- a/tests/gold_tests/traffic_ctl/remap_inc/wait_reload.sh
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/bin/bash
-
-#  Licensed to the Apache Software Foundation (ASF) under one
-#  or more contributor license agreements.  See the NOTICE file
-#  distributed with this work for additional information
-#  regarding copyright ownership.  The ASF licenses this file
-#  to you under the Apache License, Version 2.0 (the
-#  "License"); you may not use this file except in compliance
-#  with the License.  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
-
-# Wait till remap.config finishes loading two times.
-
-WAIT=60
-LOG_FILE="$1"
-
-while (( WAIT > 0 ))
-do
-    N=$( grep -F 'NOTE: remap.config finished loading' $LOG_FILE | wc -l )
-    if [[ $N = 2 ]] ; then
-        exit 0
-    fi
-    sleep 1
-    let WAIT=WAIT-1
-done
-echo TIMEOUT
-exit 1

Reply via email to