Improve StringOutputBuffer to act more like an o(string)stream
* buffer << 1 now works, allowing it to be used as a drop-in replacement
for ostream.
* EXPECT_EQ("blah", buffer) now works, making testing simpler.
Change-Id: Ic47d68fb8786b63614e271b2397c1b146a6a6964
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/27680
Reviewed-by: Takuto Ikuta <tikuta@google.com>
Commit-Queue: Matt Stark <msta@google.com>
diff --git a/src/gn/string_output_buffer.cc b/src/gn/string_output_buffer.cc
index d02f707..e422a05 100644
--- a/src/gn/string_output_buffer.cc
+++ b/src/gn/string_output_buffer.cc
@@ -122,3 +122,7 @@
return WriteToFile(file_path, err);
}
+
+std::string Pretty(const StringOutputBuffer& buffer) {
+ return buffer.str();
+}
diff --git a/src/gn/string_output_buffer.h b/src/gn/string_output_buffer.h
index 98c3b9a..e6fc94d 100644
--- a/src/gn/string_output_buffer.h
+++ b/src/gn/string_output_buffer.h
@@ -5,8 +5,10 @@
#ifndef TOOLS_GN_STRING_OUTPUT_BUFFER_H_
#define TOOLS_GN_STRING_OUTPUT_BUFFER_H_
+#include <charconv>
#include <concepts>
#include <cstring>
+#include <limits>
#include <memory>
#include <streambuf>
#include <string>
@@ -35,10 +37,30 @@
}
template <typename T>
-concept Writable = requires(const T& val, char buf[], size_t& upto) {
- { MaximumSize(val) } -> std::convertible_to<size_t>;
- WriteToBuf(val, buf, upto);
-};
+ requires(std::integral<T> && !std::same_as<T, char> && !std::same_as<T, bool>)
+inline size_t MaximumSize(T) {
+ // digits10 is the number of digits such that *every* number of this length
+ // can be represented. For int32 for example, this returns the number of
+ // digits required to store 999M (since we can store every number < 1B).
+ // +1 because although we can't represent *every* number in the billions, we
+ // can represent *some* numbers in the billions.
+ // +1 for negative sign.
+ return std::numeric_limits<T>::digits10 + 2;
+}
+
+template <typename T>
+ requires(std::integral<T> && !std::same_as<T, char> && !std::same_as<T, bool>)
+inline void WriteToBuf(T val, char buf[], size_t& upto) {
+ upto =
+ std::to_chars(&buf[upto], &buf[upto + MaximumSize(val)], val).ptr - buf;
+}
+
+template <typename T>
+concept Writable = !std::same_as<std::remove_cvref_t<T>, bool> &&
+ requires(const T& val, char buf[], size_t& upto) {
+ { MaximumSize(val) } -> std::convertible_to<size_t>;
+ WriteToBuf(val, buf, upto);
+ };
// An append-only very large storage area for string data. Useful for the parts
// of GN that need to generate huge output files (e.g. --ide=json will create
@@ -138,4 +160,6 @@
size_t ro_size_ = 0;
};
+std::string Pretty(const StringOutputBuffer& buffer);
+
#endif // TOOLS_GN_STRING_OUTPUT_BUFFER_H_
diff --git a/src/gn/string_output_buffer_unittest.cc b/src/gn/string_output_buffer_unittest.cc
index 9067fd1..440e931 100644
--- a/src/gn/string_output_buffer_unittest.cc
+++ b/src/gn/string_output_buffer_unittest.cc
@@ -4,6 +4,8 @@
#include "gn/string_output_buffer.h"
+#include <limits>
+
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
@@ -86,14 +88,19 @@
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
const size_t data_size = 100000;
- std::string data = CreateTestString(data_size);
+ std::string elem = CreateTestString(data_size);
+ std::string data = elem + elem;
base::FilePath file_path = temp_dir.GetPath().AppendASCII("foo.txt");
base::WriteFile(file_path, data.c_str(), static_cast<int>(data.size()));
{
StringOutputBuffer buffer;
- buffer.Append(data);
+ // If we just call buffer.Append(data), then it remains in the same
+ // allocation, and thus our code isn't tested properly.
+ buffer.Append(elem);
+ buffer.Append(elem);
+ EXPECT_EQ(data, buffer);
EXPECT_TRUE(buffer.ContentsEqual(file_path));
@@ -158,12 +165,17 @@
static_assert(Writable<char>);
static_assert(Writable<std::string>);
static_assert(Writable<const char*>);
+static_assert(Writable<int>);
+static_assert(Writable<int64_t>);
+static_assert(Writable<uint64_t>);
+static_assert(!Writable<bool>);
static_assert(!Writable<Unwritable>);
} // namespace
TEST(StringOutputBuffer, CustomWritable) {
StringOutputBuffer buffer;
- buffer << CustomGreeting{"world"} << '!';
- EXPECT_EQ(buffer.str(), "Hello, world!");
-}
+ buffer << CustomGreeting{"world "} << std::numeric_limits<int>::min() << " "
+ << std::numeric_limits<int>::max() << '!';
+ EXPECT_EQ("Hello, world -2147483648 2147483647!", buffer);
+}
\ No newline at end of file
diff --git a/src/util/test/gn_test.cc b/src/util/test/gn_test.cc
index 0ec1d30..1c98bf1 100644
--- a/src/util/test/gn_test.cc
+++ b/src/util/test/gn_test.cc
@@ -23,6 +23,7 @@
#include "base/strings/stringprintf.h"
#include "gn/exec_process.h"
#include "gn/standard_out.h"
+#include "gn/string_output_buffer.h"
#include "util/test/test.h"
#if defined(OS_WIN)
@@ -278,3 +279,7 @@
fflush(stdout);
return passed ? EXIT_SUCCESS : EXIT_FAILURE;
}
+
+bool operator==(std::string_view expected, const StringOutputBuffer& actual) {
+ return expected == actual.str();
+}
diff --git a/src/util/test/test.h b/src/util/test/test.h
index ebb2b1c..9144840 100644
--- a/src/util/test/test.h
+++ b/src/util/test/test.h
@@ -254,6 +254,12 @@
} // namespace testing
+class StringOutputBuffer;
+
+// This should not be used in production code, so we put it in test.h instead
+// of string_output_buffer.h.
+bool operator==(std::string_view expected, const StringOutputBuffer& actual);
+
void RegisterTest(testing::Test* (*)(), const char*);
#define TEST_F_(x, y, name) \