summaryrefslogtreecommitdiff
path: root/absl/strings/internal/str_format/parser_test.cc
diff options
context:
space:
mode:
authorGravatar Abseil Team <absl-team@google.com>2018-06-21 12:55:12 -0700
committerGravatar Shaindel Schwartz <shaindel@google.com>2018-06-22 08:55:41 -0400
commit4491d606df34c44efda47b6d17b605262f17e182 (patch)
tree5c5a2717289760c229fed502a6310d70ca4ed35a /absl/strings/internal/str_format/parser_test.cc
parentd89dba27e35462d7457121b978fd79214205e686 (diff)
Export of internal Abseil changes.
-- 70f43a482d7d4ae4a255f17ca02b0106653dd600 by Shaindel Schwartz <shaindel@google.com>: Internal change PiperOrigin-RevId: 201571193 -- 93e6e9c2e683158be49d9dd1f5cb1a91d0c0f556 by Abseil Team <absl-team@google.com>: Internal change. PiperOrigin-RevId: 201567108 -- fbd8ee94fbe9f2448e5adf5e88706f9c8216048f by Juemin Yang <jueminyang@google.com>: str_format release PiperOrigin-RevId: 201565129 -- 387faa301555a8a888c4429df52734aa806dca46 by Abseil Team <absl-team@google.com>: Adds a defaulted allocator parameter to the size_type constructor of InlinedVector PiperOrigin-RevId: 201558711 -- 39b15ea2c68d7129d70cbde7e71af900032595ec by Matt Calabrese <calabrese@google.com>: Update the variant implementation to eliminate unnecessary checking on alternative access when the index is known or required to be correct. PiperOrigin-RevId: 201529535 -- adab77f1f7bb363aa534297f22aae2b0f08889ea by Abseil Team <absl-team@google.com>: Import of CCTZ from GitHub. PiperOrigin-RevId: 201458388 -- a701dc0ba62e3cadf0de14203415b91df4ee8151 by Greg Falcon <gfalcon@google.com>: Internal cleanup PiperOrigin-RevId: 201394836 -- 8a7191410b8f440fdfa27f722ff05e451502ab61 by Abseil Team <absl-team@google.com>: Import of CCTZ from GitHub. PiperOrigin-RevId: 201369269 GitOrigin-RevId: 70f43a482d7d4ae4a255f17ca02b0106653dd600 Change-Id: I8ab073b30b4e27405a3b6da2c826bb4f3f0b9af6
Diffstat (limited to 'absl/strings/internal/str_format/parser_test.cc')
-rw-r--r--absl/strings/internal/str_format/parser_test.cc379
1 files changed, 379 insertions, 0 deletions
diff --git a/absl/strings/internal/str_format/parser_test.cc b/absl/strings/internal/str_format/parser_test.cc
new file mode 100644
index 00000000..e698020b
--- /dev/null
+++ b/absl/strings/internal/str_format/parser_test.cc
@@ -0,0 +1,379 @@
+#include "absl/strings/internal/str_format/parser.h"
+
+#include <string.h>
+#include "gtest/gtest.h"
+#include "absl/base/macros.h"
+
+namespace absl {
+namespace str_format_internal {
+
+namespace {
+
+TEST(LengthModTest, Names) {
+ struct Expectation {
+ int line;
+ LengthMod::Id id;
+ const char *name;
+ };
+ const Expectation kExpect[] = {
+ {__LINE__, LengthMod::none, "" },
+ {__LINE__, LengthMod::h, "h" },
+ {__LINE__, LengthMod::hh, "hh"},
+ {__LINE__, LengthMod::l, "l" },
+ {__LINE__, LengthMod::ll, "ll"},
+ {__LINE__, LengthMod::L, "L" },
+ {__LINE__, LengthMod::j, "j" },
+ {__LINE__, LengthMod::z, "z" },
+ {__LINE__, LengthMod::t, "t" },
+ {__LINE__, LengthMod::q, "q" },
+ };
+ EXPECT_EQ(ABSL_ARRAYSIZE(kExpect), LengthMod::kNumValues);
+ for (auto e : kExpect) {
+ SCOPED_TRACE(e.line);
+ LengthMod mod = LengthMod::FromId(e.id);
+ EXPECT_EQ(e.id, mod.id());
+ EXPECT_EQ(e.name, mod.name());
+ }
+}
+
+TEST(ConversionCharTest, Names) {
+ struct Expectation {
+ ConversionChar::Id id;
+ char name;
+ };
+ // clang-format off
+ const Expectation kExpect[] = {
+#define X(c) {ConversionChar::c, #c[0]}
+ X(c), X(C), X(s), X(S), // text
+ X(d), X(i), X(o), X(u), X(x), X(X), // int
+ X(f), X(F), X(e), X(E), X(g), X(G), X(a), X(A), // float
+ X(n), X(p), // misc
+#undef X
+ {ConversionChar::none, '\0'},
+ };
+ // clang-format on
+ EXPECT_EQ(ABSL_ARRAYSIZE(kExpect), ConversionChar::kNumValues);
+ for (auto e : kExpect) {
+ SCOPED_TRACE(e.name);
+ ConversionChar v = ConversionChar::FromId(e.id);
+ EXPECT_EQ(e.id, v.id());
+ EXPECT_EQ(e.name, v.Char());
+ }
+}
+
+class ConsumeUnboundConversionTest : public ::testing::Test {
+ public:
+ typedef UnboundConversion Props;
+ string_view Consume(string_view* src) {
+ int next = 0;
+ const char* prev_begin = src->begin();
+ o = UnboundConversion(); // refresh
+ ConsumeUnboundConversion(src, &o, &next);
+ return {prev_begin, static_cast<size_t>(src->begin() - prev_begin)};
+ }
+
+ bool Run(const char *fmt, bool force_positional = false) {
+ string_view src = fmt;
+ int next = force_positional ? -1 : 0;
+ o = UnboundConversion(); // refresh
+ return ConsumeUnboundConversion(&src, &o, &next) && src.empty();
+ }
+ UnboundConversion o;
+};
+
+TEST_F(ConsumeUnboundConversionTest, ConsumeSpecification) {
+ struct Expectation {
+ int line;
+ const char *src;
+ const char *out;
+ const char *src_post;
+ };
+ const Expectation kExpect[] = {
+ {__LINE__, "", "", "" },
+ {__LINE__, "b", "", "b" }, // 'b' is invalid
+ {__LINE__, "ba", "", "ba"}, // 'b' is invalid
+ {__LINE__, "l", "", "l" }, // just length mod isn't okay
+ {__LINE__, "d", "d", "" }, // basic
+ {__LINE__, "d ", "d", " " }, // leave suffix
+ {__LINE__, "dd", "d", "d" }, // don't be greedy
+ {__LINE__, "d9", "d", "9" }, // leave non-space suffix
+ {__LINE__, "dzz", "d", "zz"}, // length mod as suffix
+ {__LINE__, "1$*2$d", "1$*2$d", "" }, // arg indexing and * allowed.
+ {__LINE__, "0-14.3hhd", "0-14.3hhd", ""}, // precision, width
+ {__LINE__, " 0-+#14.3hhd", " 0-+#14.3hhd", ""}, // flags
+ };
+ for (const auto& e : kExpect) {
+ SCOPED_TRACE(e.line);
+ string_view src = e.src;
+ EXPECT_EQ(e.src, src);
+ string_view out = Consume(&src);
+ EXPECT_EQ(e.out, out);
+ EXPECT_EQ(e.src_post, src);
+ }
+}
+
+TEST_F(ConsumeUnboundConversionTest, BasicConversion) {
+ EXPECT_FALSE(Run(""));
+ EXPECT_FALSE(Run("z"));
+
+ EXPECT_FALSE(Run("dd")); // no excess allowed
+
+ EXPECT_TRUE(Run("d"));
+ EXPECT_EQ('d', o.conv.Char());
+ EXPECT_FALSE(o.width.is_from_arg());
+ EXPECT_LT(o.width.value(), 0);
+ EXPECT_FALSE(o.precision.is_from_arg());
+ EXPECT_LT(o.precision.value(), 0);
+ EXPECT_EQ(1, o.arg_position);
+ EXPECT_EQ(LengthMod::none, o.length_mod.id());
+}
+
+TEST_F(ConsumeUnboundConversionTest, ArgPosition) {
+ EXPECT_TRUE(Run("d"));
+ EXPECT_EQ(1, o.arg_position);
+ EXPECT_TRUE(Run("3$d"));
+ EXPECT_EQ(3, o.arg_position);
+ EXPECT_TRUE(Run("1$d"));
+ EXPECT_EQ(1, o.arg_position);
+ EXPECT_TRUE(Run("1$d", true));
+ EXPECT_EQ(1, o.arg_position);
+ EXPECT_TRUE(Run("123$d"));
+ EXPECT_EQ(123, o.arg_position);
+ EXPECT_TRUE(Run("123$d", true));
+ EXPECT_EQ(123, o.arg_position);
+ EXPECT_TRUE(Run("10$d"));
+ EXPECT_EQ(10, o.arg_position);
+ EXPECT_TRUE(Run("10$d", true));
+ EXPECT_EQ(10, o.arg_position);
+
+ // Position can't be zero.
+ EXPECT_FALSE(Run("0$d"));
+ EXPECT_FALSE(Run("0$d", true));
+ EXPECT_FALSE(Run("1$*0$d"));
+ EXPECT_FALSE(Run("1$.*0$d"));
+
+ // Position can't start with a zero digit at all. That is not a 'decimal'.
+ EXPECT_FALSE(Run("01$p"));
+ EXPECT_FALSE(Run("01$p", true));
+ EXPECT_FALSE(Run("1$*01$p"));
+ EXPECT_FALSE(Run("1$.*01$p"));
+}
+
+TEST_F(ConsumeUnboundConversionTest, WidthAndPrecision) {
+ EXPECT_TRUE(Run("14d"));
+ EXPECT_EQ('d', o.conv.Char());
+ EXPECT_FALSE(o.width.is_from_arg());
+ EXPECT_EQ(14, o.width.value());
+ EXPECT_FALSE(o.precision.is_from_arg());
+ EXPECT_LT(o.precision.value(), 0);
+
+ EXPECT_TRUE(Run("14.d"));
+ EXPECT_FALSE(o.width.is_from_arg());
+ EXPECT_FALSE(o.precision.is_from_arg());
+ EXPECT_EQ(14, o.width.value());
+ EXPECT_EQ(0, o.precision.value());
+
+ EXPECT_TRUE(Run(".d"));
+ EXPECT_FALSE(o.width.is_from_arg());
+ EXPECT_LT(o.width.value(), 0);
+ EXPECT_FALSE(o.precision.is_from_arg());
+ EXPECT_EQ(0, o.precision.value());
+
+ EXPECT_TRUE(Run(".5d"));
+ EXPECT_FALSE(o.width.is_from_arg());
+ EXPECT_LT(o.width.value(), 0);
+ EXPECT_FALSE(o.precision.is_from_arg());
+ EXPECT_EQ(5, o.precision.value());
+
+ EXPECT_TRUE(Run(".0d"));
+ EXPECT_FALSE(o.width.is_from_arg());
+ EXPECT_LT(o.width.value(), 0);
+ EXPECT_FALSE(o.precision.is_from_arg());
+ EXPECT_EQ(0, o.precision.value());
+
+ EXPECT_TRUE(Run("14.5d"));
+ EXPECT_FALSE(o.width.is_from_arg());
+ EXPECT_FALSE(o.precision.is_from_arg());
+ EXPECT_EQ(14, o.width.value());
+ EXPECT_EQ(5, o.precision.value());
+
+ EXPECT_TRUE(Run("*.*d"));
+ EXPECT_TRUE(o.width.is_from_arg());
+ EXPECT_EQ(1, o.width.get_from_arg());
+ EXPECT_TRUE(o.precision.is_from_arg());
+ EXPECT_EQ(2, o.precision.get_from_arg());
+ EXPECT_EQ(3, o.arg_position);
+
+ EXPECT_TRUE(Run("*d"));
+ EXPECT_TRUE(o.width.is_from_arg());
+ EXPECT_EQ(1, o.width.get_from_arg());
+ EXPECT_FALSE(o.precision.is_from_arg());
+ EXPECT_LT(o.precision.value(), 0);
+ EXPECT_EQ(2, o.arg_position);
+
+ EXPECT_TRUE(Run(".*d"));
+ EXPECT_FALSE(o.width.is_from_arg());
+ EXPECT_LT(o.width.value(), 0);
+ EXPECT_TRUE(o.precision.is_from_arg());
+ EXPECT_EQ(1, o.precision.get_from_arg());
+ EXPECT_EQ(2, o.arg_position);
+
+ // mixed implicit and explicit: didn't specify arg position.
+ EXPECT_FALSE(Run("*23$.*34$d"));
+
+ EXPECT_TRUE(Run("12$*23$.*34$d"));
+ EXPECT_EQ(12, o.arg_position);
+ EXPECT_TRUE(o.width.is_from_arg());
+ EXPECT_EQ(23, o.width.get_from_arg());
+ EXPECT_TRUE(o.precision.is_from_arg());
+ EXPECT_EQ(34, o.precision.get_from_arg());
+
+ EXPECT_TRUE(Run("2$*5$.*9$d"));
+ EXPECT_EQ(2, o.arg_position);
+ EXPECT_TRUE(o.width.is_from_arg());
+ EXPECT_EQ(5, o.width.get_from_arg());
+ EXPECT_TRUE(o.precision.is_from_arg());
+ EXPECT_EQ(9, o.precision.get_from_arg());
+
+ EXPECT_FALSE(Run(".*0$d")) << "no arg 0";
+}
+
+TEST_F(ConsumeUnboundConversionTest, Flags) {
+ static const char kAllFlags[] = "-+ #0";
+ static const int kNumFlags = ABSL_ARRAYSIZE(kAllFlags) - 1;
+ for (int rev = 0; rev < 2; ++rev) {
+ for (int i = 0; i < 1 << kNumFlags; ++i) {
+ std::string fmt;
+ for (int k = 0; k < kNumFlags; ++k)
+ if ((i >> k) & 1) fmt += kAllFlags[k];
+ // flag order shouldn't matter
+ if (rev == 1) { std::reverse(fmt.begin(), fmt.end()); }
+ fmt += 'd';
+ SCOPED_TRACE(fmt);
+ EXPECT_TRUE(Run(fmt.c_str()));
+ EXPECT_EQ(fmt.find('-') == std::string::npos, !o.flags.left);
+ EXPECT_EQ(fmt.find('+') == std::string::npos, !o.flags.show_pos);
+ EXPECT_EQ(fmt.find(' ') == std::string::npos, !o.flags.sign_col);
+ EXPECT_EQ(fmt.find('#') == std::string::npos, !o.flags.alt);
+ EXPECT_EQ(fmt.find('0') == std::string::npos, !o.flags.zero);
+ }
+ }
+}
+
+TEST_F(ConsumeUnboundConversionTest, BasicFlag) {
+ // Flag is on
+ for (const char* fmt : {"d", "llx", "G", "1$X"}) {
+ SCOPED_TRACE(fmt);
+ EXPECT_TRUE(Run(fmt));
+ EXPECT_TRUE(o.flags.basic);
+ }
+
+ // Flag is off
+ for (const char* fmt : {"3d", ".llx", "-G", "1$#X"}) {
+ SCOPED_TRACE(fmt);
+ EXPECT_TRUE(Run(fmt));
+ EXPECT_FALSE(o.flags.basic);
+ }
+}
+
+struct SummarizeConsumer {
+ std::string* out;
+ explicit SummarizeConsumer(std::string* out) : out(out) {}
+
+ bool Append(string_view s) {
+ *out += "[" + std::string(s) + "]";
+ return true;
+ }
+
+ bool ConvertOne(const UnboundConversion& conv, string_view s) {
+ *out += "{";
+ *out += std::string(s);
+ *out += ":";
+ *out += std::to_string(conv.arg_position) + "$";
+ if (conv.width.is_from_arg()) {
+ *out += std::to_string(conv.width.get_from_arg()) + "$*";
+ }
+ if (conv.precision.is_from_arg()) {
+ *out += "." + std::to_string(conv.precision.get_from_arg()) + "$*";
+ }
+ *out += conv.conv.Char();
+ *out += "}";
+ return true;
+ }
+};
+
+std::string SummarizeParsedFormat(const ParsedFormatBase& pc) {
+ std::string out;
+ if (!pc.ProcessFormat(SummarizeConsumer(&out))) out += "!";
+ return out;
+}
+
+class ParsedFormatTest : public testing::Test {};
+
+TEST_F(ParsedFormatTest, ValueSemantics) {
+ ParsedFormatBase p1({}, true, {}); // empty format
+ EXPECT_EQ("", SummarizeParsedFormat(p1));
+
+ ParsedFormatBase p2 = p1; // copy construct (empty)
+ EXPECT_EQ(SummarizeParsedFormat(p1), SummarizeParsedFormat(p2));
+
+ p1 = ParsedFormatBase("hello%s", true, {Conv::s}); // move assign
+ EXPECT_EQ("[hello]{s:1$s}", SummarizeParsedFormat(p1));
+
+ ParsedFormatBase p3 = p1; // copy construct (nonempty)
+ EXPECT_EQ(SummarizeParsedFormat(p1), SummarizeParsedFormat(p3));
+
+ using std::swap;
+ swap(p1, p2);
+ EXPECT_EQ("", SummarizeParsedFormat(p1));
+ EXPECT_EQ("[hello]{s:1$s}", SummarizeParsedFormat(p2));
+ swap(p1, p2); // undo
+
+ p2 = p1; // copy assign
+ EXPECT_EQ(SummarizeParsedFormat(p1), SummarizeParsedFormat(p2));
+}
+
+struct ExpectParse {
+ const char* in;
+ std::initializer_list<Conv> conv_set;
+ const char* out;
+};
+
+TEST_F(ParsedFormatTest, Parsing) {
+ // Parse should be equivalent to that obtained by ConversionParseIterator.
+ // No need to retest the parsing edge cases here.
+ const ExpectParse kExpect[] = {
+ {"", {}, ""},
+ {"ab", {}, "[ab]"},
+ {"a%d", {Conv::d}, "[a]{d:1$d}"},
+ {"a%+d", {Conv::d}, "[a]{+d:1$d}"},
+ {"a% d", {Conv::d}, "[a]{ d:1$d}"},
+ {"a%b %d", {}, "[a]!"}, // stop after error
+ };
+ for (const auto& e : kExpect) {
+ SCOPED_TRACE(e.in);
+ EXPECT_EQ(e.out,
+ SummarizeParsedFormat(ParsedFormatBase(e.in, false, e.conv_set)));
+ }
+}
+
+TEST_F(ParsedFormatTest, ParsingFlagOrder) {
+ const ExpectParse kExpect[] = {
+ {"a%+ 0d", {Conv::d}, "[a]{+ 0d:1$d}"},
+ {"a%+0 d", {Conv::d}, "[a]{+0 d:1$d}"},
+ {"a%0+ d", {Conv::d}, "[a]{0+ d:1$d}"},
+ {"a% +0d", {Conv::d}, "[a]{ +0d:1$d}"},
+ {"a%0 +d", {Conv::d}, "[a]{0 +d:1$d}"},
+ {"a% 0+d", {Conv::d}, "[a]{ 0+d:1$d}"},
+ {"a%+ 0+d", {Conv::d}, "[a]{+ 0+d:1$d}"},
+ };
+ for (const auto& e : kExpect) {
+ SCOPED_TRACE(e.in);
+ EXPECT_EQ(e.out,
+ SummarizeParsedFormat(ParsedFormatBase(e.in, false, e.conv_set)));
+ }
+}
+
+} // namespace
+} // namespace str_format_internal
+} // namespace absl