diff --git a/include/dsn/utility/output_utils.h b/include/dsn/utility/output_utils.h index f502dbf453..d59354a559 100644 --- a/include/dsn/utility/output_utils.h +++ b/include/dsn/utility/output_utils.h @@ -86,8 +86,8 @@ class table_printer enum class data_mode { kUninitialized = 0, - KSingleColumn = 1, - KMultiColumns = 2 + kSingleColumn = 1, + kMultiColumns = 2 }; public: @@ -112,28 +112,28 @@ class table_printer { } - // KMultiColumns mode. + // kMultiColumns mode. void add_title(const std::string &title, alignment align = alignment::kLeft); void add_column(const std::string &col_name, alignment align = alignment::kLeft); template void add_row(const T &row_name) { - check_mode(data_mode::KMultiColumns); + check_mode(data_mode::kMultiColumns); _matrix_data.emplace_back(std::vector()); append_data(row_name); } template void append_data(const T &data) { - check_mode(data_mode::KMultiColumns); + check_mode(data_mode::kMultiColumns); append_string_data(to_string(data)); } - // KSingleColumn mode. + // kSingleColumn mode. template void add_row_name_and_data(const std::string &row_name, const T &data) { - check_mode(data_mode::KSingleColumn); + check_mode(data_mode::kSingleColumn); add_row_name_and_string_data(row_name, to_string(data)); } diff --git a/src/core/core/output_utils.cpp b/src/core/core/output_utils.cpp index 10a923d507..2d78bc17c8 100644 --- a/src/core/core/output_utils.cpp +++ b/src/core/core/output_utils.cpp @@ -34,8 +34,12 @@ namespace utils { template void json_encode(Writer &writer, const table_printer &tp) { + if (tp._matrix_data.empty()) { + return; + } + dsn::json::json_encode(writer, tp._name); // table_printer name - if (tp._mode == table_printer::data_mode::KMultiColumns) { + if (tp._mode == table_printer::data_mode::kMultiColumns) { writer.StartObject(); // The 1st row elements are column names, skip it. for (size_t row = 1; row < tp._matrix_data.size(); ++row) { @@ -48,7 +52,7 @@ void json_encode(Writer &writer, const table_printer &tp) writer.EndObject(); } writer.EndObject(); - } else if (tp._mode == table_printer::data_mode::KSingleColumn) { + } else if (tp._mode == table_printer::data_mode::kSingleColumn) { writer.StartObject(); for (size_t row = 0; row < tp._matrix_data.size(); ++row) { dsn::json::json_encode(writer, tp._matrix_data[row][0]); // row name @@ -62,7 +66,7 @@ void json_encode(Writer &writer, const table_printer &tp) void table_printer::add_title(const std::string &title, alignment align) { - check_mode(data_mode::KMultiColumns); + check_mode(data_mode::kMultiColumns); dassert(_matrix_data.empty() && _max_col_width.empty(), "`add_title` must be called only once"); _max_col_width.push_back(title.length()); _align_left.push_back(align == alignment::kLeft); @@ -71,9 +75,9 @@ void table_printer::add_title(const std::string &title, alignment align) void table_printer::add_column(const std::string &col_name, alignment align) { - check_mode(data_mode::KMultiColumns); + check_mode(data_mode::kMultiColumns); dassert(_matrix_data.size() == 1, "`add_column` must be called before real data appendding"); - _max_col_width.emplace_back(col_name.length()); + _max_col_width.push_back(col_name.length()); _align_left.push_back(align == alignment::kLeft); append_data(col_name); } @@ -118,15 +122,16 @@ void table_printer::output_in_tabular(std::ostream &out) const } std::string separator; - if (_mode == data_mode::KSingleColumn) { + if (_mode == data_mode::kSingleColumn) { separator = ": "; } else { - dassert(_mode == data_mode::KMultiColumns, "Unknown mode"); + dassert(_mode == data_mode::kMultiColumns, "Unknown mode"); } if (!_name.empty()) { out << "[" << _name << "]" << std::endl; } + int i = 0; for (const auto &row : _matrix_data) { for (size_t col = 0; col < row.size(); ++col) { auto data = (col == 0 ? "" : separator) + row[col]; @@ -140,9 +145,11 @@ void table_printer::output_in_tabular(std::ostream &out) const void table_printer::append_string_data(const std::string &data) { _matrix_data.rbegin()->emplace_back(data); + int last_index = _matrix_data.rbegin()->size() - 1; + dassert(last_index <= _max_col_width.size(), "column data exceed"); // update column max length - int &cur_len = _max_col_width[_matrix_data.rbegin()->size() - 1]; + int &cur_len = _max_col_width[last_index]; if (cur_len < data.size()) { cur_len = data.size(); } diff --git a/src/core/tests/output_utils_test.cpp b/src/core/tests/output_utils_test.cpp new file mode 100644 index 0000000000..3eb39810e2 --- /dev/null +++ b/src/core/tests/output_utils_test.cpp @@ -0,0 +1,191 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 Microsoft Corporation + * + * -=- Robust Distributed System Nucleus (rDSN) -=- + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "dsn/utility/output_utils.h" + +#include + +#include +#include + +using std::vector; +using std::string; +using dsn::utils::table_printer; + +namespace dsn { + +const vector + single_column_tp_output({"[tp1]\n" + "row1 : 1.23\n" + "row2 : 2345\n" + "row3 : 3456\n", + R"*("tp1":{"row1":"1.23","row2":"2345","row3":"3456"})*", + R"*( "tp1": {)*" + "\n" + R"*( "row1": "1.23",)*" + "\n" + R"*( "row2": "2345",)*" + "\n" + R"*( "row3": "3456")*" + "\n" + " }"}); + +const vector multi_columns_tp_output( + {"[tp2]\n" + "multi_columns_test col0 col1 col2 \n" + "row0 data00 data01 data02 \n" + "row1 data10 data11 data12 \n" + "row2 data20 data21 data22 \n", + R"*("tp2":{"row0":{"multi_columns_test":"row0","col0":"data00","col1":"data01","col2":"data02"},"row1":{"multi_columns_test":"row1","col0":"data10","col1":"data11","col2":"data12"},"row2":{"multi_columns_test":"row2","col0":"data20","col1":"data21","col2":"data22"}})*", + R"*( "tp2": {)*" + "\n" + R"*( "row0": {)*" + "\n" + R"*( "multi_columns_test": "row0",)*" + "\n" + R"*( "col0": "data00",)*" + "\n" + R"*( "col1": "data01",)*" + "\n" + R"*( "col2": "data02")*" + "\n" + " },\n" + R"*( "row1": {)*" + "\n" + R"*( "multi_columns_test": "row1",)*" + "\n" + R"*( "col0": "data10",)*" + "\n" + R"*( "col1": "data11",)*" + "\n" + R"*( "col2": "data12")*" + "\n" + " },\n" + R"*( "row2": {)*" + "\n" + R"*( "multi_columns_test": "row2",)*" + "\n" + R"*( "col0": "data20",)*" + "\n" + R"*( "col1": "data21",)*" + "\n" + R"*( "col2": "data22")*" + "\n" + " }\n" + " }"}); + +utils::table_printer generate_single_column_tp() +{ + utils::table_printer tp("tp1", 2, 2); + tp.add_row_name_and_data("row1", 1.234); + tp.add_row_name_and_data("row2", 2345); + tp.add_row_name_and_data("row3", "3456"); + return tp; +} + +utils::table_printer generate_multi_columns_tp() +{ + int kColumnCount = 3; + int kRowCount = 3; + utils::table_printer tp("tp2", 2, 2); + tp.add_title("multi_columns_test"); + for (int i = 0; i < kColumnCount; i++) { + tp.add_column("col" + std::to_string(i)); + } + for (int i = 0; i < kRowCount; i++) { + tp.add_row("row" + std::to_string(i)); + for (int j = 0; j < kColumnCount; j++) { + tp.append_data("data" + std::to_string(i) + std::to_string(j)); + } + } + return tp; +} + +template +void check_output(const P &printer, const vector &expect_output) +{ + static vector output_formats( + {table_printer::output_format::kTabular, + table_printer::output_format::kJsonCompact, + table_printer::output_format::kJsonPretty}); + ASSERT_EQ(expect_output.size(), output_formats.size()); + for (int i = 0; i < output_formats.size(); i++) { + std::ostringstream out; + printer.output(out, output_formats[i]); + ASSERT_EQ(expect_output[i], out.str()); + } +} + +TEST(table_printer_test, empty_content_test) +{ + utils::table_printer tp; + ASSERT_NO_FATAL_FAILURE(check_output(tp, {"", "{}\n", "{}\n"})); +} + +TEST(table_printer_test, single_column_test) +{ + utils::table_printer tp(generate_single_column_tp()); + ASSERT_NO_FATAL_FAILURE(check_output(tp, + {single_column_tp_output[0], + "{" + single_column_tp_output[1] + "}\n", + "{\n" + single_column_tp_output[2] + "\n}\n"})); +} + +TEST(table_printer_test, multi_columns_test) +{ + utils::table_printer tp(generate_multi_columns_tp()); + ASSERT_NO_FATAL_FAILURE(check_output(tp, + {multi_columns_tp_output[0], + "{" + multi_columns_tp_output[1] + "}\n", + "{\n" + multi_columns_tp_output[2] + "\n}\n"})); +} + +TEST(multi_table_printer_test, empty_content_test) +{ + utils::multi_table_printer mtp; + ASSERT_NO_FATAL_FAILURE(check_output(mtp, {"", "{}\n", "{}\n"})); +} + +TEST(multi_table_printer_test, single_empty_sub_test) +{ + utils::multi_table_printer mtp; + utils::table_printer tp; + mtp.add(std::move(tp)); + ASSERT_NO_FATAL_FAILURE(check_output(mtp, {"\n", "{}\n", "{}\n"})); +} + +TEST(multi_table_printer_test, multi_sub_test) +{ + utils::multi_table_printer mtp; + mtp.add(generate_single_column_tp()); + mtp.add(generate_multi_columns_tp()); + ASSERT_NO_FATAL_FAILURE(check_output( + mtp, + {single_column_tp_output[0] + "\n" + multi_columns_tp_output[0] + "\n", + "{" + single_column_tp_output[1] + "," + multi_columns_tp_output[1] + "}\n", + "{\n" + single_column_tp_output[2] + ",\n" + multi_columns_tp_output[2] + "\n}\n"})); +} +} // namespace dsn