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")