bd317cae3b
-- 0b3113ff8d252be11c35d8a77cdb23cc03e993d2 by Benjamin Barenblat <bbaren@google.com>: Correct Apache license headers These files have always been licensed under the Apache license (per the repository LICENSE), but they lacked headers to locally indicate that fact. Add the appropriate headers. PiperOrigin-RevId: 310223650 -- 4f92b0819781279c02b48bcfce4c10d547e35a49 by Greg Falcon <gfalcon@google.com>: Internal change PiperOrigin-RevId: 310223203 -- 378cab69ce3290d20b48d839daa988c3e63ed031 by Matthew Brown <matthewbr@google.com>: Internal Change PiperOrigin-RevId: 310180198 -- 93a1851be0f3726f7435cef56afaa1b8664f40e7 by Gennadiy Rozental <rogeeff@google.com>: Internal change PiperOrigin-RevId: 310003669 -- fbee3221cb1f8717fdf34e1cc0ea248a7719227a by Gennadiy Rozental <rogeeff@google.com>: Make 4 argument version of ParseFrom interface private in CommandLineFlag. Instroduce 2 argument version of this interface. PiperOrigin-RevId: 309995363 GitOrigin-RevId: 0b3113ff8d252be11c35d8a77cdb23cc03e993d2 Change-Id: Ibfdf948d3d081f5630ce7ebff992ab85c75cecba
413 lines
13 KiB
C++
413 lines
13 KiB
C++
#include "absl/strings/internal/str_format/parser.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
#include "absl/base/macros.h"
|
|
|
|
namespace absl {
|
|
ABSL_NAMESPACE_BEGIN
|
|
namespace str_format_internal {
|
|
|
|
namespace {
|
|
|
|
using testing::Pair;
|
|
|
|
TEST(LengthModTest, Names) {
|
|
struct Expectation {
|
|
int line;
|
|
LengthMod mod;
|
|
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), 10);
|
|
for (auto e : kExpect) {
|
|
SCOPED_TRACE(e.line);
|
|
EXPECT_EQ(e.name, LengthModToString(e.mod));
|
|
}
|
|
}
|
|
|
|
TEST(ConversionCharTest, Names) {
|
|
struct Expectation {
|
|
FormatConversionChar id;
|
|
char name;
|
|
};
|
|
// clang-format off
|
|
const Expectation kExpect[] = {
|
|
#define X(c) {FormatConversionCharInternal::c, #c[0]}
|
|
X(c), 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
|
|
{FormatConversionCharInternal::kNone, '\0'},
|
|
};
|
|
// clang-format on
|
|
for (auto e : kExpect) {
|
|
SCOPED_TRACE(e.name);
|
|
FormatConversionChar v = e.id;
|
|
EXPECT_EQ(e.name, FormatConversionCharToChar(v));
|
|
}
|
|
}
|
|
|
|
class ConsumeUnboundConversionTest : public ::testing::Test {
|
|
public:
|
|
std::pair<string_view, string_view> Consume(string_view src) {
|
|
int next = 0;
|
|
o = UnboundConversion(); // refresh
|
|
const char* p = ConsumeUnboundConversion(
|
|
src.data(), src.data() + src.size(), &o, &next);
|
|
if (!p) return {{}, src};
|
|
return {string_view(src.data(), p - src.data()),
|
|
string_view(p, src.data() + src.size() - p)};
|
|
}
|
|
|
|
bool Run(const char *fmt, bool force_positional = false) {
|
|
int next = force_positional ? -1 : 0;
|
|
o = UnboundConversion(); // refresh
|
|
return ConsumeUnboundConversion(fmt, fmt + strlen(fmt), &o, &next) ==
|
|
fmt + strlen(fmt);
|
|
}
|
|
UnboundConversion o;
|
|
};
|
|
|
|
TEST_F(ConsumeUnboundConversionTest, ConsumeSpecification) {
|
|
struct Expectation {
|
|
int line;
|
|
string_view src;
|
|
string_view out;
|
|
string_view 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);
|
|
EXPECT_THAT(Consume(e.src), Pair(e.out, e.src_post));
|
|
}
|
|
}
|
|
|
|
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', FormatConversionCharToChar(o.conv));
|
|
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);
|
|
}
|
|
|
|
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', FormatConversionCharToChar(o.conv));
|
|
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";
|
|
|
|
// Large values
|
|
EXPECT_TRUE(Run("999999999.999999999d"));
|
|
EXPECT_FALSE(o.width.is_from_arg());
|
|
EXPECT_EQ(999999999, o.width.value());
|
|
EXPECT_FALSE(o.precision.is_from_arg());
|
|
EXPECT_EQ(999999999, o.precision.value());
|
|
|
|
EXPECT_FALSE(Run("1000000000.999999999d"));
|
|
EXPECT_FALSE(Run("999999999.1000000000d"));
|
|
EXPECT_FALSE(Run("9999999999d"));
|
|
EXPECT_FALSE(Run(".9999999999d"));
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
TEST_F(ConsumeUnboundConversionTest, LengthMod) {
|
|
EXPECT_TRUE(Run("d"));
|
|
EXPECT_EQ(LengthMod::none, o.length_mod);
|
|
EXPECT_TRUE(Run("hd"));
|
|
EXPECT_EQ(LengthMod::h, o.length_mod);
|
|
EXPECT_TRUE(Run("hhd"));
|
|
EXPECT_EQ(LengthMod::hh, o.length_mod);
|
|
EXPECT_TRUE(Run("ld"));
|
|
EXPECT_EQ(LengthMod::l, o.length_mod);
|
|
EXPECT_TRUE(Run("lld"));
|
|
EXPECT_EQ(LengthMod::ll, o.length_mod);
|
|
EXPECT_TRUE(Run("Lf"));
|
|
EXPECT_EQ(LengthMod::L, o.length_mod);
|
|
EXPECT_TRUE(Run("qf"));
|
|
EXPECT_EQ(LengthMod::q, o.length_mod);
|
|
EXPECT_TRUE(Run("jd"));
|
|
EXPECT_EQ(LengthMod::j, o.length_mod);
|
|
EXPECT_TRUE(Run("zd"));
|
|
EXPECT_EQ(LengthMod::z, o.length_mod);
|
|
EXPECT_TRUE(Run("td"));
|
|
EXPECT_EQ(LengthMod::t, o.length_mod);
|
|
}
|
|
|
|
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 += FormatConversionCharToChar(conv.conv);
|
|
*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,
|
|
{FormatConversionCharSetInternal::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<FormatConversionCharSet> 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", {FormatConversionCharSetInternal::d}, "[a]{d:1$d}"},
|
|
{"a%+d", {FormatConversionCharSetInternal::d}, "[a]{+d:1$d}"},
|
|
{"a% d", {FormatConversionCharSetInternal::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", {FormatConversionCharSetInternal::d}, "[a]{+ 0d:1$d}"},
|
|
{"a%+0 d", {FormatConversionCharSetInternal::d}, "[a]{+0 d:1$d}"},
|
|
{"a%0+ d", {FormatConversionCharSetInternal::d}, "[a]{0+ d:1$d}"},
|
|
{"a% +0d", {FormatConversionCharSetInternal::d}, "[a]{ +0d:1$d}"},
|
|
{"a%0 +d", {FormatConversionCharSetInternal::d}, "[a]{0 +d:1$d}"},
|
|
{"a% 0+d", {FormatConversionCharSetInternal::d}, "[a]{ 0+d:1$d}"},
|
|
{"a%+ 0+d", {FormatConversionCharSetInternal::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
|
|
ABSL_NAMESPACE_END
|
|
} // namespace absl
|