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

dmeden 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 1f9186e7d8 Test: `traffic_ctl_output_test*`: Add validate functions 
for specific JSON fields validation (#12627)
1f9186e7d8 is described below

commit 1f9186e7d8c8efae7432b649c2aeeaf874ec4f5f
Author: Damian Meden <[email protected]>
AuthorDate: Tue Nov 4 10:41:34 2025 +0100

    Test: `traffic_ctl_output_test*`: Add validate functions for specific JSON 
fields validation (#12627)
    
    * Test: Add validate_json_contains for traffic_ctl output JSON validation
    
    Add validate_json_contains() to validate specific JSON fields without
    exact output matches. Updated traffic_ctl_server_output.test.py to use
    the new method for server status checks, making tests more maintainable.
    
    Also re-order some functions.
---
 .../traffic_ctl/traffic_ctl_server_output.test.py  | 13 ++---
 .../traffic_ctl/traffic_ctl_test_utils.py          | 68 ++++++++++++++++------
 2 files changed, 56 insertions(+), 25 deletions(-)

diff --git a/tests/gold_tests/traffic_ctl/traffic_ctl_server_output.test.py 
b/tests/gold_tests/traffic_ctl/traffic_ctl_server_output.test.py
index b42e29ebb5..237448d04c 100644
--- a/tests/gold_tests/traffic_ctl/traffic_ctl_server_output.test.py
+++ b/tests/gold_tests/traffic_ctl/traffic_ctl_server_output.test.py
@@ -40,16 +40,13 @@ Test.Summary = 'Basic test for traffic_ctl server command 
features.'
 traffic_ctl = Make_traffic_ctl(Test, records_yaml)
 ######
 # traffic_ctl server status
-traffic_ctl.server().status().validate_with_text(
-    '{"initialized_done": "true", "is_ssl_handshaking_stopped": "false", 
"is_draining": "false", "is_event_system_shut_down": "false", "thread_groups": 
[{"name": "ET_NET", "count": "``", "started": "true"}, {"name": "ET_TASK", 
"count": "2", "started": "true"}, {"name": "ET_UDP", "count": "0", "started": 
"false"}]}'
-)
+traffic_ctl.server().status().validate_json_contains(
+    initialized_done='true', is_ssl_handshaking_stopped='false', 
is_draining='false', is_event_system_shut_down='false')
 # Drain ats so we can check the output.
 traffic_ctl.server().drain().exec()
 
 # After the drain, server status should reflect this change.
-traffic_ctl.server().status().validate_with_text(
-    '{"initialized_done": "true", "is_ssl_handshaking_stopped": "false", 
"is_draining": "true", "is_event_system_shut_down": "false", "thread_groups": 
[{"name": "ET_NET", "count": "``", "started": "true"}, {"name": "ET_TASK", 
"count": "2", "started": "true"}, {"name": "ET_UDP", "count": "0", "started": 
"false"}]}'
-)
+traffic_ctl.server().status().validate_json_contains(initialized_done='true', 
is_draining='true')
 
 # Get basic and empty connection tracker info.
 traffic_ctl.rpc().invoke(
@@ -58,11 +55,11 @@ traffic_ctl.rpc().invoke(
 # default = outbound only
 traffic_ctl.rpc().invoke(
     
handler="get_connection_tracker_info").validate_result_with_text('{"outbound": 
{"count": "0", "list": []}}')
-# requets inbound oonly
+# request inbound only
 traffic_ctl.rpc().invoke(
     handler="get_connection_tracker_info",
     params='"table: inbound"').validate_result_with_text('{"inbound": 
{"count": "0", "list": []}}')
-# requets outbound only
+# request outbound only
 traffic_ctl.rpc().invoke(
     handler="get_connection_tracker_info",
     params='"table: outbound"').validate_result_with_text('{"outbound": 
{"count": "0", "list": []}}')
diff --git a/tests/gold_tests/traffic_ctl/traffic_ctl_test_utils.py 
b/tests/gold_tests/traffic_ctl/traffic_ctl_test_utils.py
index a51bb665cc..afd923b74c 100644
--- a/tests/gold_tests/traffic_ctl/traffic_ctl_test_utils.py
+++ b/tests/gold_tests/traffic_ctl/traffic_ctl_test_utils.py
@@ -71,6 +71,57 @@ class Common():
         self._finish_callback(self)
         return self
 
+    def validate_with_text(self, text: str):
+        """
+        Validate command output matches expected text exactly.
+
+        Example:
+            
traffic_ctl.config().get("proxy.config.product_name").validate_with_text("Apache
 Traffic Server")
+        """
+        self._tr.Processes.Default.Streams.stdout = MakeGoldFileWithText(text, 
self._dir, self._tn)
+        self._finish_callback(self)
+        return self
+
+    def validate_result_with_text(self, text: str):
+        """
+        Validate RPC result matches expected JSON exactly. Wraps text in 
JSON-RPC envelope.
+
+        Example:
+            
traffic_ctl.rpc().invoke(handler="get_connection_tracker_info").validate_result_with_text(
+                '{"outbound": {"count": "0", "list": []}}'
+            )
+        """
+        full_text = f'{{\"jsonrpc\": \"2.0\", \"result\": {text}, \"id\": 
{"``"}}}'
+        self._tr.Processes.Default.Streams.stdout = 
MakeGoldFileWithText(full_text, self._dir, self._tn)
+        self._finish_callback(self)
+        return self
+
+    def validate_json_contains(self, **field_checks):
+        """
+        Validate JSON output contains specific field:value pairs. Only checks 
specified fields.
+        Prints detailed error on failure: "FAIL: field_name = actual_value 
(expected expected_value)"
+        stream.all.txt will contain the actual output with the failed fields.
+
+        Example:
+            traffic_ctl.server().status().validate_json_contains(
+                initialized_done='true', is_draining='false'
+            )
+        """
+        import json
+        checks_str = ', '.join(f"'{k}': '{v}'" for k, v in 
field_checks.items())
+        self._cmd = (
+            f'{self._cmd} | python3 -c "'
+            f"import sys, json; "
+            f"d = json.load(sys.stdin); "
+            f"c = {{{checks_str}}}; "
+            f"failed = [(k, v, str(d.get(k))) for k, v in c.items() if 
str(d.get(k)) != v]; "
+            f"[print(f'FAIL: {{k}} = {{actual}} (expected {{expected}})', 
file=sys.stderr) "
+            f"for k, expected, actual in failed]; "
+            f"exit(0 if not failed else 1)"
+            f'"')
+        self._finish_callback(self)
+        return self
+
 
 class Config(Common):
     """
@@ -119,10 +170,6 @@ class Config(Common):
         self._tr.Processes.Default.Streams.stdout = os.path.join("gold", file)
         self.__finish()
 
-    def validate_with_text(self, text: str):
-        self._tr.Processes.Default.Streams.stdout = MakeGoldFileWithText(text, 
self._dir, self._tn)
-        self.__finish()
-
 
 class Server(Common):
     """
@@ -165,10 +212,6 @@ class Server(Common):
         """
         self._tr.Processes.Default.Command = self._cmd
 
-    def validate_with_text(self, text: str):
-        self._tr.Processes.Default.Streams.stdout = MakeGoldFileWithText(text, 
self._dir, self._tn)
-        self.__finish()
-
 
 class RPC(Common):
     """
@@ -197,15 +240,6 @@ class RPC(Common):
         """
         self._tr.Processes.Default.Command = self._cmd
 
-    def validate_with_text(self, text: str):
-        self._tr.Processes.Default.Streams.stdout = MakeGoldFileWithText(text, 
self._dir, self._tn)
-        self.__finish()
-
-    def validate_result_with_text(self, text: str):
-        full_text = f'{{\"jsonrpc\": \"2.0\", \"result\": {text}, \"id\": 
{"``"}}}'
-        self._tr.Processes.Default.Streams.stdout = 
MakeGoldFileWithText(full_text, self._dir, self._tn)
-        self.__finish()
-
 
 '''
 

Reply via email to