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 ee8f2d474a Tools & Test: Add autest to validate `traffic_ctl config` 
output and also add a new `--default` option. (#11740)
ee8f2d474a is described below

commit ee8f2d474a398b03d5f74e82c5f2180bb0f6de62
Author: Damian Meden <[email protected]>
AuthorDate: Tue Sep 24 10:51:31 2024 +0200

    Tools & Test: Add autest to validate `traffic_ctl config` output and also 
add a new `--default` option. (#11740)
    
    * Tools: traffic_ctl and new tests - Include option to show default values 
alonside with
    the current values for config get and match options.
    This also includes a few test cases to make sure the output generated
    by traffic_ctl does not break.
---
 doc/appendices/command-line/traffic_ctl.en.rst     |  21 ++-
 src/traffic_ctl/CtrlCommands.cc                    |  38 ++--
 src/traffic_ctl/CtrlCommands.h                     |   3 +-
 src/traffic_ctl/CtrlPrinters.cc                    |  16 +-
 src/traffic_ctl/CtrlPrinters.h                     |  60 ++++--
 src/traffic_ctl/traffic_ctl.cc                     |   6 +-
 tests/gold_tests/traffic_ctl/gold/describe.gold    |  14 ++
 tests/gold_tests/traffic_ctl/gold/diff.gold        |  15 ++
 tests/gold_tests/traffic_ctl/gold/diff_yaml.gold   |  10 +
 tests/gold_tests/traffic_ctl/gold/match.gold       |   5 +
 tests/gold_tests/traffic_ctl/gold/t1_yaml.gold     |   4 +
 tests/gold_tests/traffic_ctl/gold/t2_yaml.gold     |   5 +
 tests/gold_tests/traffic_ctl/gold/t3_yaml.gold     |   7 +
 tests/gold_tests/traffic_ctl/gold/t4_yaml.gold     |  10 +
 .../traffic_ctl/traffic_ctl_config_output.test.py  | 210 +++++++++++++++++++++
 15 files changed, 375 insertions(+), 49 deletions(-)

diff --git a/doc/appendices/command-line/traffic_ctl.en.rst 
b/doc/appendices/command-line/traffic_ctl.en.rst
index 7ff02f133c..8eaee48e6d 100644
--- a/doc/appendices/command-line/traffic_ctl.en.rst
+++ b/doc/appendices/command-line/traffic_ctl.en.rst
@@ -136,6 +136,23 @@ traffic_ctl config
 
    Display the config output in YAML format. This out can be used directly 
into ATS if needed.
 
+
+   .. program:: traffic_ctl config
+   .. option:: --default
+
+   Include the default value alonside with the current value. This can be used 
in combination with ``--records``
+
+   .. code-block:: bash
+
+      $ traffic_ctl config match proxy.config.diags.debug --records --default
+      records:
+         diags:
+            debug:
+               client_ip: "null"  # default: null
+               enabled: 1  # default: 0
+               tags: quic  # default: http|dns
+               throttling_interval_msec: 0  # default: 0
+
 .. program:: traffic_ctl config
 .. option:: defaults [--records]
 
@@ -161,7 +178,7 @@ traffic_ctl config
    behavior as :option:`traffic_ctl config get --records`.
 
 .. program:: traffic_ctl config
-.. option:: get [--records] RECORD [RECORD...]
+.. option:: get [--records, --default] RECORD [RECORD...]
 
    :ref:`admin_lookup_records`
 
@@ -175,7 +192,7 @@ Display the current value of a configuration record.
    The option :ref:`--cold <traffic_ctl_config_cold>` is available to get the 
values from a file.
 
 .. program:: traffic_ctl config
-.. option:: match [--records] REGEX [REGEX...]
+.. option:: match [--records, --default] REGEX [REGEX...]
 
    :ref:`admin_lookup_records`
 
diff --git a/src/traffic_ctl/CtrlCommands.cc b/src/traffic_ctl/CtrlCommands.cc
index b061ac94fc..ae290c463e 100644
--- a/src/traffic_ctl/CtrlCommands.cc
+++ b/src/traffic_ctl/CtrlCommands.cc
@@ -39,35 +39,35 @@ namespace
 /// We use yamlcpp as codec implementation.
 using Codec = yamlcpp_json_emitter;
 
-using StringToOutputFormatMap                  = 
std::unordered_map<std::string_view, BasePrinter::Options::OutputFormat>;
-const StringToOutputFormatMap _Fmt_str_to_enum = {
-  {"json", BasePrinter::Options::OutputFormat::JSON},
-  {"rpc",  BasePrinter::Options::OutputFormat::RPC }
+using StringToFormatFlagsMap                  = 
std::unordered_map<std::string_view, BasePrinter::Options::FormatFlags>;
+const StringToFormatFlagsMap _Fmt_str_to_enum = {
+  {"json", BasePrinter::Options::FormatFlags::JSON},
+  {"rpc",  BasePrinter::Options::FormatFlags::RPC }
 };
 } // namespace
 
-BasePrinter::Options::OutputFormat
-parse_format(ts::Arguments *args)
+BasePrinter::Options::FormatFlags
+parse_print_opts(ts::Arguments *args)
 {
-  if (args->get("records")) {
-    return BasePrinter::Options::OutputFormat::RECORDS;
+  BasePrinter::Options::FormatFlags 
val{BasePrinter::Options::FormatFlags::NOT_SET};
+
+  if (args->get("default")) {
+    val |= BasePrinter::Options::FormatFlags::SHOW_DEFAULT;
   }
 
-  BasePrinter::Options::OutputFormat 
val{BasePrinter::Options::OutputFormat::NOT_SET};
+  if (args->get("records")) { // records overrule the rest of the formats.
+    val |= BasePrinter::Options::FormatFlags::RECORDS;
+    return val;
+  }
 
   if (auto data = args->get("format"); data) {
-    StringToOutputFormatMap::const_iterator search = 
_Fmt_str_to_enum.find(data.value());
+    StringToFormatFlagsMap::const_iterator search = 
_Fmt_str_to_enum.find(data.value());
     if (search != std::end(_Fmt_str_to_enum)) {
-      val = search->second;
+      val |= search->second;
     }
   }
   return val;
 }
-BasePrinter::Options
-parse_print_opts(ts::Arguments *args)
-{
-  return {parse_format(args)};
-}
 
 std::atomic_int CtrlCommand::Signal_Flagged{0};
 
//------------------------------------------------------------------------------------------------------------------------------------
@@ -121,7 +121,7 @@ RPCAccessor::invoke_rpc(shared::rpc::ClientRequest const 
&request, std::string &
 // 
-----------------------------------------------------------------------------------------------------------------------------------
 ConfigCommand::ConfigCommand(ts::Arguments *args) : RecordCommand(args)
 {
-  BasePrinter::Options printOpts{parse_print_opts(args)};
+  BasePrinter::Options printOpts(parse_print_opts(args));
   if (args->get(MATCH_STR)) {
     _printer      = std::make_unique<RecordPrinter>(printOpts);
     _invoked_func = [&]() { config_match(); };
@@ -450,9 +450,9 @@ DirectRPCCommand::DirectRPCCommand(ts::Arguments *args) : 
CtrlCommand(args)
     _invoked_func = [&]() { read_from_input(); };
   } else if (get_parsed_arguments()->get(INVOKE_STR)) {
     _invoked_func = [&]() { invoke_method(); };
-    if (printOpts._format == BasePrinter::Options::OutputFormat::NOT_SET) {
+    if (printOpts._format & BasePrinter::Options::FormatFlags::NOT_SET) {
       // overwrite this and let it drop json instead.
-      printOpts._format = BasePrinter::Options::OutputFormat::RPC;
+      printOpts._format |= BasePrinter::Options::FormatFlags::RPC;
     }
   }
 
diff --git a/src/traffic_ctl/CtrlCommands.h b/src/traffic_ctl/CtrlCommands.h
index 13b1475b73..3da9222291 100644
--- a/src/traffic_ctl/CtrlCommands.h
+++ b/src/traffic_ctl/CtrlCommands.h
@@ -237,5 +237,4 @@ private:
 };
 // 
-----------------------------------------------------------------------------------------------------------------------------------
 
-BasePrinter::Options::OutputFormat parse_format(ts::Arguments *args);
-BasePrinter::Options               parse_print_opts(ts::Arguments *args);
+BasePrinter::Options::FormatFlags parse_print_opts(ts::Arguments *args);
diff --git a/src/traffic_ctl/CtrlPrinters.cc b/src/traffic_ctl/CtrlPrinters.cc
index 860af4787c..72dfc53d46 100644
--- a/src/traffic_ctl/CtrlPrinters.cc
+++ b/src/traffic_ctl/CtrlPrinters.cc
@@ -113,15 +113,19 @@ RecordPrinter::write_output(YAML::Node const &result)
                 << ": Unrecognized configuration value. Record is a 
configuration name/value but is not registered\n";
       continue;
     }
-    if (!_printAsRecords) {
-      std::cout << recordInfo.name << ": " << recordInfo.currentValue << '\n';
+    if (!is_records_format()) {
+      std::cout << recordInfo.name << ": " << recordInfo.currentValue;
+      if (should_include_default()) {
+        std::cout << " # default " << recordInfo.defaultValue;
+      }
+      std::cout << '\n';
     } else {
       recordList.push_back(std::make_tuple(recordInfo.name, 
recordInfo.currentValue, recordInfo.defaultValue));
     }
   }
 
-  if (_printAsRecords && recordList.size() > 0) {
-    std::cout << RecNameToYaml{recordList, WithoutDefaults}.string() << '\n';
+  if (is_records_format() && recordList.size() > 0) {
+    std::cout << RecNameToYaml{recordList, should_include_default()}.string() 
<< '\n';
   }
   // we print errors if found.
   print_record_error_list(response.errorList);
@@ -150,7 +154,7 @@ DiffConfigPrinter::write_output(YAML::Node const &result)
     auto const &defaultValue = recordInfo.defaultValue;
     const bool  hasChanged   = (currentValue != defaultValue);
     if (hasChanged) {
-      if (!_printAsRecords) {
+      if (!is_records_format()) {
         std::cout << swoc::bwprint(text, "{} has changed\n", recordInfo.name);
         std::cout << swoc::bwprint(text, "\tCurrent Value: {}\n", 
currentValue);
         std::cout << swoc::bwprint(text, "\tDefault Value: {}\n", 
defaultValue);
@@ -160,7 +164,7 @@ DiffConfigPrinter::write_output(YAML::Node const &result)
     }
   }
 
-  if (_printAsRecords && recordList.size() > 0) {
+  if (is_records_format() && recordList.size() > 0) {
     std::cout << RecNameToYaml{recordList, WithDefaults}.string() << '\n';
   }
 }
diff --git a/src/traffic_ctl/CtrlPrinters.h b/src/traffic_ctl/CtrlPrinters.h
index 5c5392391a..d0e8a4bbd8 100644
--- a/src/traffic_ctl/CtrlPrinters.h
+++ b/src/traffic_ctl/CtrlPrinters.h
@@ -65,15 +65,16 @@ class BasePrinter
 public:
   /// This enum maps the --format flag coming from traffic_ctl. (also 
--records is included here, see comments down below.)
   struct Options {
-    enum class OutputFormat {
-      NOT_SET = 0, // nothing set.
-      JSON,        // Json formatting
-      RECORDS,     // only valid for configs, but it's handy to have it here.
-      RPC          // Print JSONRPC request and response + default output.
+    enum FormatFlags {
+      NOT_SET      = 0,      // nothing set.
+      JSON         = 1 << 0, // Json formatting
+      RECORDS      = 1 << 1, // only valid for configs, but it's handy to have 
it here.
+      RPC          = 1 << 2, // Print JSONRPC request and response + default 
output.
+      SHOW_DEFAULT = 1 << 3  // Add the default values alongside with the 
actual value.
     };
     Options() = default;
-    Options(OutputFormat fmt) : _format(fmt) {}
-    OutputFormat _format{OutputFormat::NOT_SET}; //!< selected(passed) format.
+    Options(FormatFlags flags) : _format(flags) {}
+    mutable FormatFlags _format{FormatFlags::NOT_SET}; //!< selected(passed) 
format.
   };
 
   /// Printer constructor. Needs the format as it will be used by derived 
classes.
@@ -105,18 +106,37 @@ public:
   virtual void write_output(std::string_view output) const;
   virtual void write_debug(std::string_view output) const;
 
-  /// OutputFormat getters.
-  Options::OutputFormat get_format() const;
-  bool                  print_rpc_message() const;
-  bool                  is_json_format() const;
-  bool                  is_records_format() const;
+  /// FormatFlags getters.
+  Options::FormatFlags get_format() const;
+  bool                 print_rpc_message() const;
+  bool                 is_json_format() const;
+  bool                 is_records_format() const;
+  bool                 should_include_default() const;
 
 protected:
   void    write_output_json(YAML::Node const &node) const;
   Options _printOpt;
 };
 
-inline BasePrinter::Options::OutputFormat
+constexpr enum BasePrinter::Options::FormatFlags
+operator|(const enum BasePrinter::Options::FormatFlags rhs, const enum 
BasePrinter::Options::FormatFlags lhs)
+{
+  return 
static_cast<BasePrinter::Options::FormatFlags>(static_cast<uint32_t>(rhs) | 
static_cast<uint32_t>(lhs));
+}
+
+constexpr enum BasePrinter::Options::FormatFlags &
+operator|=(BasePrinter::Options::FormatFlags &rhs, 
BasePrinter::Options::FormatFlags lhs)
+{
+  return rhs = rhs | lhs;
+}
+
+constexpr enum BasePrinter::Options::FormatFlags
+operator&(BasePrinter::Options::FormatFlags rhs, 
BasePrinter::Options::FormatFlags lhs)
+{
+  return 
static_cast<BasePrinter::Options::FormatFlags>(static_cast<uint32_t>(rhs) & 
static_cast<uint32_t>(lhs));
+}
+
+inline BasePrinter::Options::FormatFlags
 BasePrinter::get_format() const
 {
   return _printOpt._format;
@@ -125,19 +145,24 @@ BasePrinter::get_format() const
 inline bool
 BasePrinter::print_rpc_message() const
 {
-  return get_format() == Options::OutputFormat::RPC;
+  return _printOpt._format & Options::FormatFlags::RPC;
 }
 
 inline bool
 BasePrinter::is_json_format() const
 {
-  return get_format() == Options::OutputFormat::JSON;
+  return _printOpt._format & Options::FormatFlags::JSON;
 }
 
 inline bool
 BasePrinter::is_records_format() const
 {
-  return get_format() == Options::OutputFormat::RECORDS;
+  return _printOpt._format & Options::FormatFlags::RECORDS;
+}
+inline bool
+BasePrinter::should_include_default() const
+{
+  return _printOpt._format & Options::FormatFlags::SHOW_DEFAULT;
 }
 
//------------------------------------------------------------------------------------------------------------------------------------
 class GenericPrinter : public BasePrinter
@@ -157,10 +182,9 @@ class RecordPrinter : public BasePrinter
   void write_output(YAML::Node const &result) override;
 
 public:
-  RecordPrinter(Options opt) : BasePrinter(opt) { _printAsRecords = 
is_records_format(); }
+  RecordPrinter(Options opt) : BasePrinter(opt) {}
 
 protected:
-  bool _printAsRecords{false};
 };
 
 class MetricRecordPrinter : public BasePrinter
diff --git a/src/traffic_ctl/traffic_ctl.cc b/src/traffic_ctl/traffic_ctl.cc
index b7954c2489..d760681e1e 100644
--- a/src/traffic_ctl/traffic_ctl.cc
+++ b/src/traffic_ctl/traffic_ctl.cc
@@ -107,11 +107,13 @@ main([[maybe_unused]] int argc, const char **argv)
     .add_option("--cold", "-c",
                 "Save the value in a configuration file. This does not save 
the value in TS. Local file change only",
                 "TS_RECORD_YAML", MORE_THAN_ZERO_ARG_N)
-    .add_option("--records", "", "Emit output in YAML format");
+    .add_option("--records", "", "Emit output in YAML format")
+    .add_option("--default", "", "Include default value");
   config_command
     .add_command("match", "Get configuration matching a regular expression", 
"", MORE_THAN_ONE_ARG_N, [&]() { command->execute(); })
     .add_example_usage("traffic_ctl config match [OPTIONS] REGEX [REGEX ...]")
-    .add_option("--records", "", "Emit output in YAML format");
+    .add_option("--records", "", "Emit output in YAML format")
+    .add_option("--default", "", "Include the default value");
   config_command.add_command("reload", "Request a configuration reload", [&]() 
{ command->execute(); })
     .add_example_usage("traffic_ctl config reload");
   config_command.add_command("status", "Check the configuration status", [&]() 
{ command->execute(); })
diff --git a/tests/gold_tests/traffic_ctl/gold/describe.gold 
b/tests/gold_tests/traffic_ctl/gold/describe.gold
new file mode 100644
index 0000000000..a549a65bc6
--- /dev/null
+++ b/tests/gold_tests/traffic_ctl/gold/describe.gold
@@ -0,0 +1,14 @@
+name            : proxy.config.http.server_ports
+current value   : ``
+default value   : ``
+record type     : ``
+data type       : ``
+access control  : ``
+update type     : ``
+update status   : ``
+source          : ``
+syntax check    : ``
+overridable     : ``
+version         : ``
+order           : ``
+raw stat block  : ``
\ No newline at end of file
diff --git a/tests/gold_tests/traffic_ctl/gold/diff.gold 
b/tests/gold_tests/traffic_ctl/gold/diff.gold
new file mode 100644
index 0000000000..ab50c4c93d
--- /dev/null
+++ b/tests/gold_tests/traffic_ctl/gold/diff.gold
@@ -0,0 +1,15 @@
+proxy.config.diags.debug.enabled has changed
+``Current Value: 1
+``Default Value: 0
+proxy.config.diags.debug.tags has changed
+``Current Value: rpc
+``Default Value: http|dns
+proxy.config.http.server_ports has changed
+``Current Value: ``
+``Default Value: 8080 8080:ipv6
+proxy.config.http.wait_for_cache has changed
+``Current Value: 1
+``Default Value: 0
+proxy.config.udp.threads has changed
+``Current Value: 1
+``Default Value: 0
diff --git a/tests/gold_tests/traffic_ctl/gold/diff_yaml.gold 
b/tests/gold_tests/traffic_ctl/gold/diff_yaml.gold
new file mode 100644
index 0000000000..5780dc4304
--- /dev/null
+++ b/tests/gold_tests/traffic_ctl/gold/diff_yaml.gold
@@ -0,0 +1,10 @@
+records:
+  diags:
+    debug:
+      enabled: 1  # default: 0
+      tags: rpc  # default: http|dns
+  http:
+    server_ports: ``  # default: 8080 8080:ipv6
+    wait_for_cache: 1  # default: 0
+  udp:
+    threads: 1  # default: 0
diff --git a/tests/gold_tests/traffic_ctl/gold/match.gold 
b/tests/gold_tests/traffic_ctl/gold/match.gold
new file mode 100644
index 0000000000..a945c8f49a
--- /dev/null
+++ b/tests/gold_tests/traffic_ctl/gold/match.gold
@@ -0,0 +1,5 @@
+proxy.config.udp.threads: 1 # default 0
+proxy.config.accept_threads: 1 # default 1
+proxy.config.task_threads: 2 # default 2
+proxy.config.cache.threads_per_disk: 8 # default 8
+proxy.config.log.preproc_threads: 1 # default 1
diff --git a/tests/gold_tests/traffic_ctl/gold/t1_yaml.gold 
b/tests/gold_tests/traffic_ctl/gold/t1_yaml.gold
new file mode 100644
index 0000000000..b087513b4b
--- /dev/null
+++ b/tests/gold_tests/traffic_ctl/gold/t1_yaml.gold
@@ -0,0 +1,4 @@
+records:
+  diags:
+    debug:
+      tags: rpc
diff --git a/tests/gold_tests/traffic_ctl/gold/t2_yaml.gold 
b/tests/gold_tests/traffic_ctl/gold/t2_yaml.gold
new file mode 100644
index 0000000000..86a5ad200c
--- /dev/null
+++ b/tests/gold_tests/traffic_ctl/gold/t2_yaml.gold
@@ -0,0 +1,5 @@
+records:
+  diags:
+    debug:
+      tags: rpc  # default: http|dns
+``
\ No newline at end of file
diff --git a/tests/gold_tests/traffic_ctl/gold/t3_yaml.gold 
b/tests/gold_tests/traffic_ctl/gold/t3_yaml.gold
new file mode 100644
index 0000000000..6770e8b5a9
--- /dev/null
+++ b/tests/gold_tests/traffic_ctl/gold/t3_yaml.gold
@@ -0,0 +1,7 @@
+records:
+  diags:
+    debug:
+      enabled: 1  # default: 0
+      tags: rpc  # default: http|dns
+      throttling_interval_msec: 0  # default: 0
+``
\ No newline at end of file
diff --git a/tests/gold_tests/traffic_ctl/gold/t4_yaml.gold 
b/tests/gold_tests/traffic_ctl/gold/t4_yaml.gold
new file mode 100644
index 0000000000..9be103d339
--- /dev/null
+++ b/tests/gold_tests/traffic_ctl/gold/t4_yaml.gold
@@ -0,0 +1,10 @@
+records:
+  diags:
+    logfile:
+      filename: diags.log
+      rolling_enabled: 0
+      rolling_interval_sec: 3600
+      rolling_min_count: 0
+      rolling_size_mb: 10
+    logfile_perm: rw-r--r--
+``
\ No newline at end of file
diff --git a/tests/gold_tests/traffic_ctl/traffic_ctl_config_output.test.py 
b/tests/gold_tests/traffic_ctl/traffic_ctl_config_output.test.py
new file mode 100644
index 0000000000..cdfe65ab72
--- /dev/null
+++ b/tests/gold_tests/traffic_ctl/traffic_ctl_config_output.test.py
@@ -0,0 +1,210 @@
+#  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 ruamel.yaml Uncomment only when GoldFilePathFor is used.
+
+Test.Summary = '''
+Test traffic_ctl config output responses.
+'''
+
+Test.ContinueOnFail = True
+
+TestNumber = 0
+
+
+def IncTestNumber():
+    global TestNumber
+    TestNumber = TestNumber + 1
+
+
+# This function can(eventually) be used to have a single yaml file and read 
nodes from it.
+# The idea would be to avoid having multiple gold files with yaml content.
+# The only issue would be the comments, this is because how the yaml lib reads 
yaml,
+# comments  aren't rendered in the same way as traffic_ctl throws it, it 
should only
+# be used if no comments need to be compared.
+#
+# def GoldFilePathFor(node:str, main_file="gold/test_gold_file.yaml"):
+#     if node == "":
+#         raise Exception("node should not be empty")
+
+#     yaml = ruamel.yaml.YAML()
+#     yaml.indent(sequence=4, offset=2)
+#     with open(os.path.join(Test.TestDirectory, main_file), 'r') as f:
+#         content = yaml.load(f)
+
+#     node_data = content[node]
+#     data_dirname = 'generated_gold_files'
+#     data_path = os.path.join(Test.TestDirectory, data_dirname)
+#     os.makedirs(data_path, exist_ok=True)
+#     gold_filepath = os.path.join(data_path, f'test_{TestNumber}.gold')
+#     with open(os.path.join(data_path, f'test_{TestNumber}.gold'), 'w') as 
gold_file:
+#         yaml.dump(node_data, gold_file)
+
+#     return gold_filepath
+
+
+def MakeGoldFileWithText(content, add_new_line=True):
+    data_path = os.path.join(Test.TestDirectory, "gold")
+    os.makedirs(data_path, exist_ok=True)
+    gold_filepath = os.path.join(data_path, f'test_{TestNumber}.gold')
+    with open(gold_filepath, 'w') as gold_file:
+        if add_new_line:
+            content = f"{content}\n"
+        gold_file.write(content)
+
+    return gold_filepath
+
+
+class Config():
+    """
+        Handy class to map traffic_ctl config options.
+    """
+
+    def __init__(self, tr):
+        self._cmd = "traffic_ctl config "
+        self._tr = tr
+
+    def diff(self):
+        self._cmd = f'{self._cmd} diff'
+        return self
+
+    def get(self, value):
+        self._cmd = f'{self._cmd} get {value}'
+        return self
+
+    def match(self, value):
+        self._cmd = f'{self._cmd}  match {value}'
+        return self
+
+    def describe(self, value):
+        self._cmd = f'{self._cmd}  describe {value}'
+        return self
+
+    def as_records(self):
+        self._cmd = f'{self._cmd} --records'
+        return self
+
+    def with_default(self):
+        self._cmd = f'{self._cmd}  --default'
+        return self
+
+    def __finish(self):
+        """
+            Sets the command to the test. Make sure this gets called after
+            validation is set. Without this call the test will fail.
+        """
+        self._tr.Processes.Default.Command = self._cmd
+
+    def validate_with_goldfile(self, file: str):
+        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.__finish()
+
+
+class TrafficCtl(Config):
+    """
+        Single TS instance with multiple tests.
+        Every time a config() is called, a new test is created.
+    """
+
+    def __init__(self, records_yaml=None):
+        self._current_test_number = TestNumber
+
+        self._ts = Test.MakeATSProcess(f"ts_{TestNumber}")
+        if records_yaml != None:
+            self._ts.Disk.records_config.update(records_yaml)
+        self._tests = []
+
+    def __get_index(self):
+        return self._current_test_number
+
+    def add_test(self):
+
+        tr = Test.AddTestRun(f"test {TestNumber}")
+        if TestNumber == 0:
+            tr.Processes.Default.StartBefore(self._ts)
+        IncTestNumber()
+
+        tr.Processes.Default.Env = self._ts.Env
+        tr.DelayStart = 3
+        tr.Processes.Default.ReturnCode = 0
+        tr.StillRunningAfter = self._ts
+
+        self._tests.insert(self.__get_index(), tr)
+        return self
+
+    def config(self):
+        self.add_test()
+        return Config(self._tests[self.__get_index()])
+
+
+def Make_traffic_ctl(records_yaml):
+    tctl = TrafficCtl(records_yaml)
+    return tctl
+
+
+records_yaml = '''
+    udp:
+      threads: 1
+    diags:
+      debug:
+        enabled: 1
+        tags: rpc
+        throttling_interval_msec: 0
+    '''
+
+traffic_ctl = Make_traffic_ctl(records_yaml)
+
+##### CONFIG GET
+
+# YAML output
+traffic_ctl.config().get("proxy.config.diags.debug.tags").as_records().validate_with_goldfile("t1_yaml.gold")
+# Default output
+traffic_ctl.config().get("proxy.config.diags.debug.enabled").validate_with_text("proxy.config.diags.debug.enabled:
 1")
+# Default output with default.
+traffic_ctl.config().get("proxy.config.diags.debug.tags").with_default() \
+    .validate_with_text("proxy.config.diags.debug.tags: rpc # default 
http|dns")
+
+# Now same output test but with defaults, traffic_ctl supports adding default 
value
+# when using --records.
+traffic_ctl.config().get("proxy.config.diags.debug.tags").as_records().with_default().validate_with_goldfile("t2_yaml.gold")
+traffic_ctl.config().get(
+    "proxy.config.diags.debug.tags proxy.config.diags.debug.enabled 
proxy.config.diags.debug.throttling_interval_msec").as_records(
+    ).with_default().validate_with_goldfile("t3_yaml.gold")
+
+##### CONFIG MATCH
+traffic_ctl.config().match("threads").with_default().validate_with_goldfile("match.gold")
+
+# The idea is to check the traffic_ctl yaml emitter when a value starts with 
the
+# same prefix of a node like:
+# diags:
+#    logfile:
+#    logfile_perm: rw-r--r--
+#
+# traffic_ctl have a special logic to deal with cases like this, so better 
test it.
+traffic_ctl.config().match("diags.logfile").as_records().validate_with_goldfile("t4_yaml.gold")
+
+##### CONFIG DIFF
+traffic_ctl.config().diff().validate_with_goldfile("diff.gold")
+traffic_ctl.config().diff().as_records().validate_with_goldfile("diff_yaml.gold")
+
+##### CONFIG DESCRIBE
+# don't really care about values, but just output and that the command 
actually went through
+traffic_ctl.config().describe("proxy.config.http.server_ports").validate_with_goldfile("describe.gold")

Reply via email to