Files
rocksdb/util/string_util_test.cc
Peter Dillinger 9ef369c606 Make StringToMap entries self-contained for direct round-trip (#14805)
Summary:
StringToMap previously stripped the outer braces from nested values,
making single-entry round-trip via `key=value;` (e.g. SetOptions)
silently corrupt values that contain ';'. For example, a filter_policy
with sub-options serialized as
  filter_policy={id=ribbonfilter:10:-1;bloom_before_level=-1;}
parsed to map[filter_policy] = "id=ribbonfilter:10:-1;bloom_before_level=-1;",
and embedding that map entry directly into another `key=value;` string
re-exposed the inner ';' as a top-level delimiter.

This change is a step toward the "essential view": the outer `{...}` of
a nested value is part of the value's identity. StringToMap preserves it, so
each map entry is in self-contained form (a simple value or a single
balanced `{...}` block) and can be embedded directly without further
escaping by the caller. However some permissiveness is kept for
compatibility: list-style typed parsers (ParseVector, ParseArray,
kStringMap, the listener parser) and scalar dispatch in
OptionTypeInfo::Parse accept either the bare or the wrapped form via a
new OptionTypeInfo::StripOuterBraces helper that peels at most one
outer-pair-matched layer. So braced scalar input like `key={42};` and
`key={true};` still parses, and `key={};` denotes an empty list,
distinct from `key={{}};` which denotes a one-element list with an
empty element.

A new public MapToString is added to convenience.h as the symmetric
inverse of StringToMap: a trivial `key=value;` joiner that relies on
each value already being self-contained (which StringToMap guarantees).

Also fixed and refactored our trim() function because it would do the wrong
thing for a single space. This problem was detected by expanding the
StringToMapRandomTest based on code review feedback, and should only
improve existing callers to trim().

The OPTIONS-file serializer code is untouched, so the on-disk format
is byte-for-byte identical and format-compatibility is preserved.

Bonus: fixes / clarifications to CLAUDE.md's build-system note.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/14805

Test Plan:
db_bloom_filter_test extends MutableFilterPolicy with the OPTIONS-file
format for filter_policy to confirm that form round-trips through
SetOptions.

options_test updates StringToMapTest expectations for the new
brace-preserving behavior, and adds:
- MapToStringTest covering the new joiner.
- StringToMapMapToStringRoundTripTest including a single-entry
  pull-and-embed scenario that previously corrupted on round-trip.
- FullOptionsStringToMapRoundTripTest that drives a populated
  DBOptions and CFOptions through GetStringFromXxx -> StringToMap ->
  per-entry single round-trip -> MapToString -> GetXxxFromString ->
  VerifyXxxOptions, catching any custom serializer that emits a
  value with ';' without enclosing it in '{}'.
- EmptyBracedVectorAndBracedScalarTest covering `key={};` (empty
  list) and braced scalar input like `key={42};`, `key={true};`.
- Expanded StringToMapRandomTest with round-tripping.

Some explicit trim() tests added to string_util_test.cc

Format compatibility verified with
  SHORT_TEST=1 tools/check_format_compatible.sh

Other existing tests in options_test, customizable_test, options_settable_test,
listener_test, options_file_test, options_util_test, db_options_test,
and compaction_service_test have extensive coverage of serialization /
deserialization logic.

Reviewed By: hx235, xingbowang

Differential Revision: D106855466

Pulled By: pdillinger

fbshipit-source-id: 872aac8d819c7b90c807b92bab85569b48fa2aa0
2026-06-01 17:07:11 -07:00

61 lines
1.9 KiB
C++

// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
// This source code is licensed under both the GPLv2 (found in the
// COPYING file in the root directory) and Apache 2.0 License
// (found in the LICENSE.Apache file in the root directory).
//
#include "string_util.h"
#include <gtest/gtest.h>
#include <port/stack_trace.h>
#include "test_util/testutil.h"
namespace ROCKSDB_NAMESPACE {
TEST(StringUtilTest, NumberToHumanString) {
ASSERT_EQ("-9223372036G", NumberToHumanString(INT64_MIN));
ASSERT_EQ("9223372036G", NumberToHumanString(INT64_MAX));
ASSERT_EQ("0", NumberToHumanString(0));
ASSERT_EQ("9999", NumberToHumanString(9999));
ASSERT_EQ("10K", NumberToHumanString(10000));
ASSERT_EQ("10M", NumberToHumanString(10000000));
ASSERT_EQ("10G", NumberToHumanString(10000000000));
ASSERT_EQ("-9999", NumberToHumanString(-9999));
ASSERT_EQ("-10K", NumberToHumanString(-10000));
ASSERT_EQ("-10M", NumberToHumanString(-10000000));
ASSERT_EQ("-10G", NumberToHumanString(-10000000000));
}
TEST(StringUtilTest, Trim) {
// Empty input.
EXPECT_EQ("", trim(""));
// No whitespace to strip.
EXPECT_EQ("a", trim("a"));
EXPECT_EQ("abc", trim("abc"));
// Leading whitespace only.
EXPECT_EQ("a", trim(" a"));
EXPECT_EQ("abc", trim(" abc"));
// Trailing whitespace only.
EXPECT_EQ("a", trim("a "));
EXPECT_EQ("abc", trim("abc "));
// Both ends.
EXPECT_EQ("a", trim(" a "));
EXPECT_EQ("abc", trim(" abc "));
EXPECT_EQ("a b c", trim(" a b c ")); // interior whitespace preserved
// All-whitespace inputs of various lengths must trim to empty.
EXPECT_EQ("", trim(" "));
EXPECT_EQ("", trim(" "));
EXPECT_EQ("", trim(" "));
EXPECT_EQ("", trim("\t"));
EXPECT_EQ("", trim("\n"));
EXPECT_EQ("", trim(" \t\n\r"));
}
} // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) {
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}