-- c99f979ad34f155fbeeea69b88bdc7458d89a21c by Derek Mauro <dmauro@google.com>: Remove a floating point division by zero test. This isn't testing behavior related to the library, and MSVC warns about it in opt mode. PiperOrigin-RevId: 285220804 -- 68b015491f0dbf1ab547994673281abd1f34cd4b by Gennadiy Rozental <rogeeff@google.com>: This CL introduces following changes to the class FlagImpl: * We eliminate the CommandLineFlagLocks struct. Instead callback guard and callback function are combined into a single CallbackData struct, while primary data lock is stored separately. * CallbackData member of class FlagImpl is initially set to be nullptr and is only allocated and initialized when a flag's callback is being set. For most flags we do not pay for the extra space and extra absl::Mutex now. * Primary data guard is stored in data_guard_ data member. This is a properly aligned character buffer of necessary size. During initialization of the flag we construct absl::Mutex in this space using placement new call. * We now avoid extra value copy after successful attempt to parse value out of string. Instead we swap flag's current value with tentative value we just produced. PiperOrigin-RevId: 285132636 -- ed45d118fb818969eb13094cf7827c885dfc562c by Tom Manshreck <shreck@google.com>: Change null-term* (and nul-term*) to NUL-term* in comments PiperOrigin-RevId: 285036610 -- 729619017944db895ce8d6d29c1995aa2e5628a5 by Derek Mauro <dmauro@google.com>: Use the Posix implementation of thread identity on MinGW. Some versions of MinGW suffer from thread_local bugs. PiperOrigin-RevId: 285022920 -- 39a25493503c76885bc3254c28f66a251c5b5bb0 by Greg Falcon <gfalcon@google.com>: Implementation detail change. Add further ABSL_NAMESPACE_BEGIN and _END annotation macros to files in Abseil. PiperOrigin-RevId: 285012012 GitOrigin-RevId: c99f979ad34f155fbeeea69b88bdc7458d89a21c Change-Id: I4c85d3704e45d11a9ac50d562f39640a6adbedc1
		
			
				
	
	
		
			394 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			394 lines
		
	
	
	
		
			12 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::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:
 | |
|   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', 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";
 | |
| 
 | |
|   // 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);
 | |
|   }
 | |
| }
 | |
| 
 | |
| 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
 | |
| ABSL_NAMESPACE_END
 | |
| }  // namespace absl
 |