Copilot commented on code in PR #13032:
URL: https://github.com/apache/trafficserver/pull/13032#discussion_r4268817016


##########
include/shared/rpc/yaml_codecs.h:
##########
@@ -63,11 +63,33 @@ template <> struct convert<shared::rpc::JSONRPCError> {
   static bool
   decode(Node const &node, shared::rpc::JSONRPCError &error)
   {
+    using DataEntry = shared::rpc::JSONRPCError::DataEntry;
+
     error.code    = helper::try_extract<int32_t>(node, "code");
     error.message = helper::try_extract<std::string>(node, "message");
-    if (auto data = node["data"]) {
-      for (auto &&err : data) {
-        error.data.emplace_back(helper::try_extract<int32_t>(err, "code"), 
helper::try_extract<std::string>(err, "message"));
+    // Read only: the same tree is printed as received in json output.
+    if (auto const data = node["data"]; data && !data.IsSequence()) {
+      // Not a list of annotations: nothing in it can be read as one.
+      error.data.push_back({0, DataEntry::INVALID_SEVERITY, {}});
+    } else if (data) {
+      for (auto const &err : data) {
+        DataEntry entry;
+        entry.code    = helper::try_extract<int32_t>(err, "code");
+        entry.message = helper::try_extract<std::string>(err, "message");
+        if (!err.IsMap()) {
+          entry.severity = DataEntry::INVALID_SEVERITY;
+        } else if (auto const severity = err["severity"]) {
+          entry.severity = DataEntry::INVALID_SEVERITY;
+          // A quoted value is text, not a severity.
+          if (severity.IsScalar() && severity.Tag() != "!") {
+            try {
+              entry.severity = severity.as<int32_t>();
+            } catch (YAML::Exception const &) {
+              // Not an integer: stays INVALID_SEVERITY.
+            }
+          }
+        }
+        error.data.push_back(std::move(entry));
       }

Review Comment:
   For non-map `data` entries you already classify the entry as invalid 
severity, but you still unconditionally attempt to extract `code`/`message` 
from `err` before checking `err.IsMap()`. Restructure this so non-map entries 
skip keyed extraction and use defaults, which makes the behavior independent of 
`helper::try_extract` details and reduces the chance of unexpected exceptions 
or misleading extracted values (recommended).



##########
src/traffic_ctl/CtrlCommands.cc:
##########
@@ -93,6 +93,16 @@ display_errors(BasePrinter *printer, 
std::vector<ConfigReloadResponse::Error> co
   }
 }
 
+/// A request the command depends on failed, and its error is already printed 
and graded. Below --error-level that grade is 0,
+/// but what was asked was not done, so the command exits CTRL_EX_TEMPFAIL 
instead.
+void
+mark_not_done()
+{
+  if (App_Exit_Status_Code == CTRL_EX_OK) {
+    App_Exit_Status_Code = CTRL_EX_TEMPFAIL;
+  }
+}

Review Comment:
   `mark_not_done()` is a non-static free function at file scope, which exports 
a global symbol unnecessarily and increases the risk of future name 
collisions/ODR issues. Make it `static` or place it in an anonymous namespace 
(mandatory).



##########
tests/gold_tests/traffic_ctl/jsonrpc_error_stub.py:
##########
@@ -0,0 +1,81 @@
+'''
+A stand-in JSONRPC node for traffic_ctl tests. It answers every request with
+the reply configured for its method, so a test can show traffic_ctl server
+errors that no real handler produces. A method configured with a list of
+replies gets them in turn, starting over after the last one.
+'''
+#  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 argparse
+import json
+import os
+import socket
+
+METHOD_NOT_FOUND = {'error': {'code': -32601, 'message': 'Method not found'}}
+
+
+def read_request(conn: socket.socket) -> dict | None:
+    data = b''
+    while True:
+        chunk = conn.recv(65536)
+        if not chunk:
+            return None
+        data += chunk
+        try:
+            return json.loads(data)

Review Comment:
   As written, `read_request()` will loop forever if the client sends multiple 
JSON values back-to-back on the same connection (common failure mode: 
`JSONDecodeError: Extra data`), since additional reads won’t fix that. Consider 
parsing with `json.JSONDecoder().raw_decode()` (consume one object and 
ignore/retain remainder), or treating `Extra data` as end-of-message; also 
consider a socket timeout to avoid a hung gold test when framing expectations 
are violated (recommended).



##########
src/mgmt/rpc/jsonrpc/unit_tests/test_basic_protocol.cc:
##########
@@ -21,11 +21,15 @@
 #include <catch2/catch_test_macros.hpp> /* catch unit-test framework */
 
 #include <tsutil/ts_bw_format.h>
+#include <tsutil/ts_errata.h>
 
 #include "mgmt/rpc/jsonrpc/JsonRPCManager.h"
 #include "mgmt/rpc/jsonrpc/JsonRPC.h"
 #include "mgmt/rpc/handlers/common/ErrorUtils.h"
 
+// Defined in src/tsutil/ts_diags.cc; traffic_server calls it at startup.
+void Initialize_Errata_Settings();
+

Review Comment:
   Forward-declaring `Initialize_Errata_Settings()` from a `.cc` file is 
brittle (signature drift, link surprises depending on target link sets). Prefer 
including the header that declares it (if available), or expose a stable 
test/utility API in `ts_errata`/`ts_diags` for initializing production errata 
settings (mandatory if a proper declaration exists; otherwise recommended to 
reduce fragility).



##########
src/mgmt/rpc/jsonrpc/unit_tests/test_basic_protocol.cc:
##########
@@ -79,6 +83,66 @@ test_callback_ok_or_error(std::string_view const & /* id 
ATS_UNUSED */, YAML::No
   return resp;
 }
 
+inline swoc::Rv<YAML::Node>
+test_callback_with_severity(std::string_view const & /* id ATS_UNUSED */, 
YAML::Node const & /* params ATS_UNUSED */)
+{
+  swoc::Rv<YAML::Node> resp;
+  resp.errata()
+    .assign(ERR1)
+    .note(ERRATA_WARN, "this is a warning")
+    .note("this has no severity")
+    .note(ERRATA_FATAL, "this is fatal")
+    .note(ERRATA_DIAG, "this is a diag");
+  return resp;
+}
+
+inline swoc::Rv<YAML::Node>
+test_callback_note_only(std::string_view const & /* id ATS_UNUSED */, 
YAML::Node const & /* params ATS_UNUSED */)
+{
+  swoc::Rv<YAML::Node> resp;
+  resp.errata().assign(ERR1).note(ERRATA_NOTE, "just a note");
+  return resp;
+}
+
+inline swoc::Rv<YAML::Node>
+test_callback_warn_only(std::string_view const & /* id ATS_UNUSED */, 
YAML::Node const & /* params ATS_UNUSED */)
+{
+  swoc::Rv<YAML::Node> resp;
+  resp.errata().assign(ERR1).note(ERRATA_WARN, "already in that state");
+  return resp;
+}
+
+inline swoc::Rv<YAML::Node>
+test_callback_note_next_to_failure(std::string_view const & /* id ATS_UNUSED 
*/, YAML::Node const & /* params ATS_UNUSED */)
+{
+  swoc::Rv<YAML::Node> resp;
+  resp.errata().assign(ERR1).note(ERRATA_NOTE, "just a note").note("the real 
failure");
+  return resp;
+}
+
+inline swoc::Rv<YAML::Node>
+test_callback_errata_severity_apart(std::string_view const & /* id ATS_UNUSED 
*/, YAML::Node const & /* params ATS_UNUSED */)
+{
+  swoc::Rv<YAML::Node> resp;
+  resp.errata().assign(ERR1).assign(ERRATA_ERROR).note(ERRATA_NOTE, "just a 
note");
+  return resp;
+}
+
+// traffic_server's errata settings, restored on scope exit. The unit tests 
run with libswoc's defaults otherwise, which treat
+// lower severities as failures.
+struct ProductionErrataSeverities {
+  swoc::Errata::Severity              
saved_default{swoc::Errata::DEFAULT_SEVERITY};
+  swoc::Errata::Severity              
saved_failure{swoc::Errata::FAILURE_SEVERITY};
+  swoc::MemSpan<swoc::TextView const> 
saved_names{swoc::Errata::SEVERITY_NAMES};
+  ProductionErrataSeverities() { Initialize_Errata_Settings(); }
+  ~ProductionErrataSeverities()
+  {
+    swoc::Errata::DEFAULT_SEVERITY = saved_default;
+    swoc::Errata::FAILURE_SEVERITY = saved_failure;
+    swoc::Errata::SEVERITY_NAMES   = saved_names;
+  }
+};

Review Comment:
   This RAII helper mutates global errata configuration. If the unit test 
runner executes tests in parallel (or if future tests do), this can cause 
cross-test interference/flakiness. Consider guarding the mutation with a 
process-wide mutex, or structuring these tests so they run in a single-threaded 
section/fixture that serializes access (recommended).



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to