aboutsummaryrefslogtreecommitdiff
path: root/pw_string
diff options
context:
space:
mode:
authorWyatt Hepler <hepler@google.com>2019-11-11 10:45:48 -0800
committerWyatt Hepler <hepler@google.com>2019-11-25 18:08:31 +0000
commitce9b952f0578ec13fc658eabe035e143343671fe (patch)
tree3d3fd8404eb195fb1909a5b29be24083a335bcd0 /pw_string
parent204b00d56153175abfa853d09810248291c3254d (diff)
downloadpigweed-ce9b952f0578ec13fc658eabe035e143343671fe.tar.gz
pw_string: StringBuilder utility class
StringBuilder can be used to flexibly and safely build strings in fixed-size buffers. Change-Id: Ie4453e1bcd11ef522bb0211304e9cf758ac17827
Diffstat (limited to 'pw_string')
-rw-r--r--pw_string/BUILD11
-rw-r--r--pw_string/BUILD.gn22
-rw-r--r--pw_string/public/pw_string/string_builder.h422
-rw-r--r--pw_string/public/pw_string/type_to_string.h6
-rw-r--r--pw_string/public/pw_string/util.h38
-rw-r--r--pw_string/string_builder.cc123
-rw-r--r--pw_string/string_builder_test.cc569
-rw-r--r--pw_string/to_string_test.cc24
-rw-r--r--pw_string/type_to_string.cc4
-rw-r--r--pw_string/util_test.cc41
10 files changed, 1244 insertions, 16 deletions
diff --git a/pw_string/BUILD b/pw_string/BUILD
index 3351fdc4b..544a1de7d 100644
--- a/pw_string/BUILD
+++ b/pw_string/BUILD
@@ -26,12 +26,15 @@ pw_cc_library(
name = "pw_string",
srcs = [
"format.cc",
+ "string_builder.cc",
"type_to_string.cc",
],
hdrs = [
"public/pw_string/format.h",
+ "public/pw_string/string_builder.h",
"public/pw_string/to_string.h",
"public/pw_string/type_to_string.h",
+ "public/pw_string/util.h",
],
deps = [
"//pw_preprocessor",
@@ -56,6 +59,14 @@ pw_cc_test(
)
pw_cc_test(
+ name = "string_builder_test",
+ srcs = ["string_builder_test.cc"],
+ deps = [
+ "//pw_string",
+ ],
+)
+
+pw_cc_test(
name = "to_string_test",
srcs = ["to_string_test.cc"],
deps = [
diff --git a/pw_string/BUILD.gn b/pw_string/BUILD.gn
index 3221e0214..1e5481be1 100644
--- a/pw_string/BUILD.gn
+++ b/pw_string/BUILD.gn
@@ -28,9 +28,11 @@ source_set("pw_string") {
"public/pw_string/format.h",
"public/pw_string/to_string.h",
"public/pw_string/type_to_string.h",
+ "public/pw_string/util.h",
]
sources = [
"format.cc",
+ "string_builder.cc",
"type_to_string.cc",
]
sources += public
@@ -63,6 +65,16 @@ pw_test("format_test") {
]
}
+pw_test("string_builder_test") {
+ deps = [
+ ":pw_string",
+ "$dir_pw_unit_test:main",
+ ]
+ sources = [
+ "string_builder_test.cc",
+ ]
+}
+
pw_test("to_string_test") {
deps = [
":pw_string",
@@ -81,6 +93,16 @@ pw_test("type_to_string_test") {
]
}
+pw_test("util_test") {
+ deps = [
+ ":pw_string",
+ "$dir_pw_unit_test:main",
+ ]
+ sources = [
+ "util_test.cc",
+ ]
+}
+
pw_size_report("format_size_report") {
title = "Using pw::string::Format instead of snprintf"
diff --git a/pw_string/public/pw_string/string_builder.h b/pw_string/public/pw_string/string_builder.h
new file mode 100644
index 000000000..cb735b7af
--- /dev/null
+++ b/pw_string/public/pw_string/string_builder.h
@@ -0,0 +1,422 @@
+// Copyright 2019 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+#pragma once
+
+#include <algorithm>
+#include <cstdarg>
+#include <cstddef>
+#include <cstring>
+#include <string_view>
+#include <type_traits>
+#include <utility>
+
+#include "pw_preprocessor/compiler.h"
+#include "pw_span/span.h"
+#include "pw_status/status.h"
+#include "pw_status/status_with_size.h"
+#include "pw_string/to_string.h"
+
+namespace pw {
+
+// StringBuilder facilitates building formatted strings in a fixed-size buffer.
+// StringBuilders are always null terminated (unless they are constructed with
+// an empty buffer) and never overflow. Status is tracked for each operation and
+// an overall status is maintained, which reflects the most recent error.
+//
+// A StringBuilder does not own the buffer it writes to. It can be used to write
+// strings to any buffer. The StringBuffer template class, defined below,
+// allocates a buffer alongside a StringBuilder.
+//
+// StringBuilder supports C++-style << output, similar to std::ostringstream. It
+// also supports std::string-like append functions and printf-style output.
+//
+// StringBuilder uses the ToString function to support arbitrary types. Defining
+// a ToString overload in the pw::string namespace allows writing that type to a
+// StringBuilder with <<.
+//
+// For example, the following ToString overload allows writing MyStatus objects
+// to StringBuilders:
+//
+// namespace pw::string {
+//
+// StatusWithSize ToString(MyStatus value, const span<char>& buffer) {
+// return CopyString(MyStatusString(value), buffer);
+// }
+//
+// } // namespace pw::string
+//
+// For complex types, it may be easier to override StringBuilder's << operator,
+// similar to the standard library's std::ostream. For example:
+//
+// namespace pw::string {
+//
+// StringBuilder& operator<<(StringBuilder& sb, const MyType& value) {
+// return sb << "MyType(" << value.foo << ", " << value.bar << ')';
+// }
+//
+// } // namespace pw::string
+//
+// Alternately, complex types may use a StringBuilder in their ToString, but it
+// is likely to be simpler to override StringBuilder's operator<<.
+//
+// StringBuilder is safe, flexible, and results in much smaller code size than
+// using std::ostringstream. However, applications sensitive to code size should
+// use StringBuilder with care.
+//
+// The fixed code size cost of StringBuilder is significant, though smaller than
+// std::snprintf. Using StringBuilder's << and append methods exclusively in
+// place of snprintf reduces code size, but snprintf may be difficult to avoid.
+//
+// The incremental code size cost of StringBuilder is comparable to snprintf if
+// errors are handled. Each argument to StringBuilder's << expands to a function
+// call, but one or two StringBuilder appends may have a smaller code size
+// impact than a single snprintf call. See the size report for further analysis.
+class StringBuilder {
+ public:
+ // Creates an empty StringBuilder.
+ constexpr StringBuilder(const span<char>& buffer)
+ : buffer_(buffer), size_(0) {
+ NullTerminate();
+ }
+
+ // Disallow copy/assign to avoid confusion about where the string is actually
+ // stored. StringBuffers may be copied into one another.
+ StringBuilder(const StringBuilder&) = delete;
+
+ StringBuilder& operator=(const StringBuilder&) = delete;
+
+ // Returns the contents of the string buffer. Always null-terminated.
+ const char* data() const { return buffer_.data(); }
+ const char* c_str() const { return data(); }
+
+ // Returns a std::string_view of the contents of this StringBuilder. The
+ // std::string_view is invalidated if the StringBuilder contents change.
+ std::string_view view() const { return std::string_view(data(), size()); }
+
+ // Allow implicit conversions to std::string_view so StringBuilders can be
+ // passed into functions that take a std::string_view.
+ operator std::string_view() const { return view(); }
+
+ // Returns a span<const std::byte> representation of this StringBuffer.
+ span<const std::byte> as_bytes() const {
+ return span(reinterpret_cast<const std::byte*>(buffer_.data()), size_);
+ }
+
+ // Returns the StringBuilder's status, which reflects the most recent error
+ // that occurred while updating the string. After an update fails, the status
+ // remains non-OK until it is cleared with clear() or clear_status(). Returns:
+ //
+ // OK if no errors have occurred
+ // RESOURCE_EXHAUSTED if output to the StringBuilder was truncated
+ // INVALID_ARGUMENT if printf-style formatting failed
+ // OUT_OF_RANGE if an operation outside the buffer was attempted
+ //
+ Status status() const { return status_; }
+
+ // Returns status() and size() as a StatusWithSize.
+ StatusWithSize status_with_size() const {
+ return StatusWithSize(status_, size_);
+ }
+
+ // The status from the last operation. May be OK while status() is not OK.
+ Status last_status() const { return last_status_; }
+
+ // True if status() is Status::OK.
+ bool ok() const { return status_.ok(); }
+
+ // True if the string is empty.
+ bool empty() const { return size() == 0u; }
+
+ // Returns the current length of the string, excluding the null terminator.
+ size_t size() const { return size_; }
+
+ // Returns the maximum length of the string, excluding the null terminator.
+ size_t max_size() const { return buffer_.empty() ? 0u : buffer_.size() - 1; }
+
+ // Clears the string and resets its error state.
+ void clear();
+
+ // Sets the statuses to Status::OK;
+ void clear_status() {
+ status_ = Status::OK;
+ last_status_ = Status::OK;
+ }
+
+ // Appends a single character. Stets the status to RESOURCE_EXHAUSTED if the
+ // character cannot be added because the buffer is full.
+ void push_back(char ch) { append(1, ch); }
+
+ // Removes the last character. Sets the status to OUT_OF_RANGE if the buffer
+ // is empty.
+ void pop_back() { resize(size() - 1); }
+
+ // Appends the provided character count times.
+ StringBuilder& append(size_t count, char ch);
+
+ // Appends count characters from str to the end of the StringBuilder. If count
+ // exceeds the remaining space in the StringBuffer, max_size() - size()
+ // characters are appended and the status is set to RESOURCE_EXHAUSTED.
+ //
+ // str is not considered null-terminated and may contain null characters.
+ StringBuilder& append(const char* str, size_t count);
+
+ // Appends characters from the null-terminated string to the end of the
+ // StringBuilder. If the string's length exceeds the remaining space in the
+ // buffer, max_size() - size() characters are copied and the status is set to
+ // RESOURCE_EXHAUSTED.
+ //
+ // This function uses string::Length instead of std::strlen to avoid unbounded
+ // reads if the string is not null terminated.
+ StringBuilder& append(const char* str);
+
+ // Appends a std::string_view to the end of the StringBuilder.
+ StringBuilder& append(const std::string_view& str);
+
+ // Appends a substring from the std::string_view to the StringBuilder. Copies
+ // up to count characters starting from pos to the end of the StringBuilder.
+ // If pos > str.size(), sets the status to OUT_OF_RANGE.
+ StringBuilder& append(const std::string_view& str,
+ size_t pos,
+ size_t count = std::string_view::npos);
+
+ // Appends to the end of the StringBuilder using the << operator. This enables
+ // C++ stream-style formatted to StringBuilders.
+ template <typename T>
+ StringBuilder& operator<<(const T& value) {
+ // For std::string_view-compatible types, use the append function, which
+ // gives smaller code size.
+ if constexpr (std::is_convertible_v<T, std::string_view>) {
+ append(value);
+ } else {
+ HandleStatusWithSize(ToString(value, buffer_.subspan(size_)));
+ }
+ return *this;
+ }
+
+ // Provide a few additional operator<< overloads that reduce code size.
+ StringBuilder& operator<<(bool value) {
+ return append(value ? "true" : "false");
+ }
+
+ StringBuilder& operator<<(char value) {
+ push_back(value);
+ return *this;
+ }
+
+ StringBuilder& operator<<(std::nullptr_t) {
+ return append(string::kNullPointerString);
+ }
+
+ StringBuilder& operator<<(Status status) { return *this << status.str(); }
+
+ // Appends a printf-style string to the end of the StringBuilder. If the
+ // formatted string does not fit, the results are truncated and the status is
+ // set to RESOURCE_EXHAUSTED.
+ //
+ // Internally, calls string::Format, which calls std::vsnprintf.
+ PW_PRINTF_FORMAT(2, 3) StringBuilder& Format(const char* format, ...);
+
+ // Appends a vsnprintf-style string with va_list arguments to the end of the
+ // StringBuilder. If the formatted string does not fit, the results are
+ // truncated and the status is set to RESOURCE_EXHAUSTED.
+ //
+ // Internally, calls string::Format, which calls std::vsnprintf.
+ StringBuilder& Format(const char* format, va_list args);
+
+ // Sets the StringBuilder's size. This function only truncates; if
+ // new_size > size(), it sets status to OUT_OF_RANGE and does nothing.
+ void resize(size_t new_size);
+
+ protected:
+ // Functions to support StringBuffer copies.
+ constexpr StringBuilder(const span<char>& buffer, const StringBuilder& other)
+ : buffer_(buffer),
+ size_(other.size_),
+ status_(other.status_),
+ last_status_(other.last_status_) {}
+
+ void CopySizeAndStatus(const StringBuilder& other);
+
+ private:
+ size_t ResizeAndTerminate(size_t chars_to_append);
+
+ void HandleStatusWithSize(StatusWithSize written);
+
+ constexpr void NullTerminate() {
+ if (!buffer_.empty()) {
+ buffer_[size_] = '\0';
+ }
+ }
+
+ void SetErrorStatus(Status status);
+
+ const span<char> buffer_;
+
+ size_t size_;
+ Status status_;
+ Status last_status_;
+};
+
+// StringBuffers declare a buffer along with a StringBuilder. StringBuffer can
+// be used as a statically allocated replacement for std::ostringstream or
+// std::string. For example:
+//
+// StringBuffer<32> str;
+// str << "The answer is " << number << "!"; // with number = 42
+// str.c_str(); // null terminated C string "The answer is 42."
+// str.view(); // std::string_view of "The answer is 42."
+//
+template <size_t kSizeBytes>
+class StringBuffer : public StringBuilder {
+ public:
+ StringBuffer() : StringBuilder(buffer_) {}
+
+ // StringBuffers of the same size may be copied and assigned into one another.
+ StringBuffer(const StringBuffer& other) : StringBuilder(buffer_, other) {
+ CopyContents(other);
+ }
+
+ // A smaller StringBuffer may be copied or assigned into a larger one.
+ template <size_t kOtherSizeBytes>
+ StringBuffer(const StringBuffer<kOtherSizeBytes>& other)
+ : StringBuilder(buffer_, other) {
+ static_assert(StringBuffer<kOtherSizeBytes>::max_size() <= max_size(),
+ "A StringBuffer cannot be copied into a smaller buffer");
+ CopyContents(other);
+ }
+
+ template <size_t kOtherSizeBytes>
+ StringBuffer& operator=(const StringBuffer<kOtherSizeBytes>& other) {
+ return assign<kOtherSizeBytes>(other);
+ }
+
+ StringBuffer& operator=(const StringBuffer& other) {
+ return assign<kSizeBytes>(other);
+ }
+
+ template <size_t kOtherSizeBytes>
+ StringBuffer& assign(const StringBuffer<kOtherSizeBytes>& other) {
+ static_assert(StringBuffer<kOtherSizeBytes>::max_size() <= max_size(),
+ "A StringBuffer cannot be copied into a smaller buffer");
+ CopySizeAndStatus(other);
+ CopyContents(other);
+ return *this;
+ }
+
+ // Returns the maximum length of the string, excluding the null terminator.
+ static constexpr size_t max_size() { return kSizeBytes - 1; }
+
+ // Returns a StringBuffer<kSizeBytes>& instead of a generic StringBuilder& for
+ // append calls and stream-style operations.
+ template <typename... Args>
+ StringBuffer& append(Args&&... args) {
+ StringBuilder::append(std::forward<Args>(args)...);
+ return *this;
+ }
+
+ template <typename T>
+ StringBuffer& operator<<(T&& value) {
+ static_cast<StringBuilder&>(*this) << std::forward<T>(value);
+ return *this;
+ }
+
+ private:
+ template <size_t kOtherSize>
+ void CopyContents(const StringBuffer<kOtherSize>& other) {
+ std::memcpy(buffer_, other.data(), other.size() + 1); // include the \0
+ }
+
+ static_assert(kSizeBytes >= 1u, "StringBuffers must be at least 1 byte long");
+ char buffer_[kSizeBytes];
+};
+
+namespace string_internal {
+
+// Internal code for determining the default size of StringBuffers created with
+// MakeString.
+//
+// StringBuffers created with MakeString default to at least 24 bytes. This is
+// large enough to fit the largest 64-bit integer (20 digits plus a \0), rounded
+// up to the nearest multiple of 4.
+inline constexpr size_t kDefaultMinimumStringBufferSize = 24;
+
+// By default, MakeString uses a buffer size large enough to fit all string
+// literal arguments. ArgLength uses this value as an estimate of the number of
+// characters needed to represent a non-string argument.
+inline constexpr size_t kDefaultArgumentSize = 4;
+
+// Returns a string literal's length or kDefaultArgumentSize for non-strings.
+template <typename T>
+constexpr size_t ArgLength() {
+ using Arg = std::remove_reference_t<T>;
+
+ // If the argument is an array of const char, assume it is a string literal.
+ if constexpr (std::is_array_v<Arg>) {
+ using Element = std::remove_reference_t<decltype(std::declval<Arg>()[0])>;
+
+ if constexpr (std::is_same_v<Element, const char>) {
+ return std::extent_v<Arg> > 0u ? std::extent_v<Arg> - 1 : size_t(0);
+ }
+ }
+
+ return kDefaultArgumentSize;
+}
+
+// This function returns the default string buffer size used by MakeString.
+template <typename... Args>
+constexpr size_t DefaultStringBufferSize() {
+ return std::max((size_t(1) + ... + ArgLength<Args>()),
+ kDefaultMinimumStringBufferSize);
+}
+
+// Internal version of MakeString with const reference arguments instead of
+// deduced types, which include the lengths of string literals. Having this
+// function can reduce code size.
+template <size_t kBufferSize, typename... Args>
+auto InitializeStringBuffer(const Args&... args) {
+ return (StringBuffer<kBufferSize>() << ... << args);
+}
+
+} // namespace string_internal
+
+// Makes a StringBuffer with a string version of a series of values. This is
+// useful for creating and initializing a StringBuffer or for conveniently
+// getting a null-terminated string. For example:
+//
+// LOG_INFO("The MAC address is %s", MakeString(mac_address).c_str());
+//
+// By default, the buffer size is 24 bytes, large enough to fit any 64-bit
+// integer. If string literal arguments are provided, the default size will be
+// large enough to fit them and a null terminator, plus 4 additional bytes for
+// each argument. To use a fixed buffer size, set the kBufferSize template
+// argument. For example:
+//
+// // Creates a default-size StringBuffer (10 + 10 + 4 + 1 + 1 = 26 bytes).
+// auto sb = MakeString("1234567890", "1234567890", number, "!");
+//
+// // Creates a 32-byte StringBuffer.
+// auto sb = MakeString<32>("1234567890", "1234567890", number, "!");
+//
+// Keep in mind that each argument to MakeString expands to a function call.
+// MakeString may increase code size more than an equivalent pw::string::Format
+// (or std::snprintf) call.
+template <size_t kBufferSize = 0u, typename... Args>
+auto MakeString(Args&&... args) {
+ constexpr size_t kSize =
+ kBufferSize == 0u ? string_internal::DefaultStringBufferSize<Args...>()
+ : kBufferSize;
+ return string_internal::InitializeStringBuffer<kSize>(args...);
+}
+
+} // namespace pw
diff --git a/pw_string/public/pw_string/type_to_string.h b/pw_string/public/pw_string/type_to_string.h
index 4ce31dffc..b9bc52ddc 100644
--- a/pw_string/public/pw_string/type_to_string.h
+++ b/pw_string/public/pw_string/type_to_string.h
@@ -97,7 +97,11 @@ StatusWithSize FloatAsIntToString(float value, const span<char>& buffer);
// Writes a bool as "true" or "false". Semantics match CopyEntireString.
StatusWithSize BoolToString(bool value, const span<char>& buffer);
-// Writes the pointer's address or "(null)". Semantics match CopyEntireString.
+// String used to represent null pointers.
+inline constexpr std::string_view kNullPointerString("(null)");
+
+// Writes the pointer's address or kNullPointerString. Semantics match
+// CopyEntireString.
StatusWithSize PointerToString(const void* pointer, const span<char>& buffer);
// Copies the string to the buffer, truncating if the full string does not fit.
diff --git a/pw_string/public/pw_string/util.h b/pw_string/public/pw_string/util.h
new file mode 100644
index 000000000..0375802fa
--- /dev/null
+++ b/pw_string/public/pw_string/util.h
@@ -0,0 +1,38 @@
+// Copyright 2019 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+#pragma once
+
+#include <cstddef>
+
+namespace pw::string {
+
+// Calculates the length of a null-terminated string up to the specified maximum
+// length. If str is nullptr, returns 0.
+//
+// This function is a constexpr version of C11's strnlen_s.
+constexpr size_t Length(const char* str, size_t max_len) {
+ size_t length = 0;
+
+ if (str != nullptr) {
+ for (; length < max_len; ++length) {
+ if (str[length] == '\0') {
+ break;
+ }
+ }
+ }
+
+ return length;
+}
+
+} // namespace pw::string
diff --git a/pw_string/string_builder.cc b/pw_string/string_builder.cc
new file mode 100644
index 000000000..4765126c4
--- /dev/null
+++ b/pw_string/string_builder.cc
@@ -0,0 +1,123 @@
+// Copyright 2019 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+#include "pw_string/string_builder.h"
+
+#include <cstdio>
+
+#include "pw_string/format.h"
+#include "pw_string/util.h"
+
+namespace pw {
+
+void StringBuilder::clear() {
+ size_ = 0;
+ NullTerminate();
+ status_ = Status::OK;
+ last_status_ = Status::OK;
+}
+
+StringBuilder& StringBuilder::append(size_t count, char ch) {
+ char* const append_destination = &buffer_[size_];
+ std::memset(append_destination, ch, ResizeAndTerminate(count));
+ return *this;
+}
+
+StringBuilder& StringBuilder::append(const char* str, size_t count) {
+ char* const append_destination = &buffer_[size_];
+ std::memcpy(append_destination, str, ResizeAndTerminate(count));
+ return *this;
+}
+
+StringBuilder& StringBuilder::append(const char* str) {
+ // Use buffer_.size() - size() as the maximum length so that strings too long
+ // to fit in the buffer will request one character too many, which sets the
+ // status to RESOURCE_EXHAUSTED.
+ return append(str, string::Length(str, buffer_.size() - size()));
+}
+
+StringBuilder& StringBuilder::append(const std::string_view& str) {
+ return append(str.data(), str.size());
+}
+
+StringBuilder& StringBuilder::append(const std::string_view& str,
+ size_t pos,
+ size_t count) {
+ if (pos > str.size()) {
+ SetErrorStatus(Status::OUT_OF_RANGE);
+ return *this;
+ }
+
+ return append(str.data() + pos, std::min(str.size() - pos, count));
+}
+
+size_t StringBuilder::ResizeAndTerminate(size_t chars_to_append) {
+ const size_t copied = std::min(chars_to_append, max_size() - size());
+ size_ += copied;
+ NullTerminate();
+
+ if (buffer_.empty() || chars_to_append != copied) {
+ SetErrorStatus(Status::RESOURCE_EXHAUSTED);
+ } else {
+ last_status_ = Status::OK;
+ }
+ return copied;
+}
+
+void StringBuilder::resize(size_t new_size) {
+ if (new_size <= size_) {
+ size_ = new_size;
+ NullTerminate();
+ last_status_ = Status::OK;
+ } else {
+ SetErrorStatus(Status::OUT_OF_RANGE);
+ }
+}
+
+StringBuilder& StringBuilder::Format(const char* format, ...) {
+ va_list args;
+ va_start(args, format);
+ Format(format, args);
+ va_end(args);
+
+ return *this;
+}
+
+StringBuilder& StringBuilder::Format(const char* format, va_list args) {
+ HandleStatusWithSize(string::Format(buffer_.subspan(size_), format, args));
+ return *this;
+}
+
+void StringBuilder::CopySizeAndStatus(const StringBuilder& other) {
+ size_ = other.size_;
+ status_ = other.status_;
+ last_status_ = other.last_status_;
+}
+
+void StringBuilder::HandleStatusWithSize(StatusWithSize written) {
+ const Status status = written.status();
+ last_status_ = status;
+ if (!status.ok()) {
+ status_ = status;
+ }
+
+ size_ += written.size();
+}
+
+void StringBuilder::SetErrorStatus(Status status) {
+ last_status_ = status;
+ status_ = status;
+}
+
+} // namespace pw
diff --git a/pw_string/string_builder_test.cc b/pw_string/string_builder_test.cc
new file mode 100644
index 000000000..b2701e88d
--- /dev/null
+++ b/pw_string/string_builder_test.cc
@@ -0,0 +1,569 @@
+// Copyright 2019 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+#include "pw_string//string_builder.h"
+
+#include <cinttypes>
+#include <cmath>
+#include <cstdint>
+#include <cstring>
+#include <string_view>
+
+#include "gtest/gtest.h"
+#include "pw_string/format.h"
+
+namespace {
+
+struct CustomType {
+ uint32_t a;
+ uint32_t b;
+
+ static constexpr const char* kToString = "This is a CustomType";
+
+ CustomType() = default;
+
+ // Non-copyable to verify StringBuffer's << operator doesn't copy it.
+ CustomType(const CustomType&) = delete;
+ CustomType& operator=(const CustomType&) = delete;
+};
+
+} // namespace
+
+namespace pw {
+
+StatusWithSize ToString(const ::CustomType&, const span<char>& buffer) {
+ return string::Format(buffer, ::CustomType::kToString);
+}
+
+namespace {
+
+TEST(StringBuilder, EmptyBuffer_SizeAndMaxSizeAreCorrect) {
+ StringBuilder sb(span<char>{});
+
+ EXPECT_TRUE(sb.empty());
+ EXPECT_EQ(0u, sb.size());
+ EXPECT_EQ(0u, sb.max_size());
+}
+
+using namespace std::literals::string_view_literals;
+
+constexpr std::string_view kNoTouch = "DO NOT TOUCH\0VALUE SHOULD NOT CHANGE"sv;
+
+TEST(StringBuilder, EmptyBuffer_StreamOutput_WritesNothing) {
+ char buffer[kNoTouch.size()];
+ std::memcpy(buffer, kNoTouch.data(), sizeof(buffer));
+
+ StringBuilder sb(span(buffer, 0));
+
+ sb << CustomType() << " is " << 12345;
+ EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.status());
+ EXPECT_EQ(kNoTouch, std::string_view(buffer, sizeof(buffer)));
+}
+
+TEST(StringBuilder, EmptyBuffer_Append_WritesNothing) {
+ char buffer[kNoTouch.size()];
+ std::memcpy(buffer, kNoTouch.data(), sizeof(buffer));
+
+ StringBuilder sb(span(buffer, 0));
+
+ EXPECT_FALSE(sb.append("Hello").ok());
+ EXPECT_EQ(kNoTouch, std::string_view(buffer, sizeof(buffer)));
+}
+
+TEST(StringBuilder, EmptyBuffer_Resize_WritesNothing) {
+ char buffer[kNoTouch.size()];
+ std::memcpy(buffer, kNoTouch.data(), sizeof(buffer));
+
+ StringBuilder sb(span(buffer, 0));
+
+ sb.resize(0);
+ EXPECT_TRUE(sb.ok());
+ EXPECT_EQ(kNoTouch, std::string_view(buffer, sizeof(buffer)));
+}
+
+TEST(StringBuilder, EmptyBuffer_AppendEmpty_ResourceExhausted) {
+ StringBuilder sb(span<char>{});
+ EXPECT_EQ(Status::OK, sb.last_status());
+ EXPECT_EQ(Status::OK, sb.status());
+
+ sb << "";
+
+ EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.last_status());
+ EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.status());
+}
+
+TEST(StringBuilder, Status_StartsOk) {
+ StringBuffer<16> sb;
+ EXPECT_EQ(Status::OK, sb.status());
+ EXPECT_EQ(Status::OK, sb.last_status());
+}
+
+TEST(StringBuilder, Status_StatusAndLastStatusUpdate) {
+ StringBuffer<16> sb;
+ sb << "Well, if only there were enough room in here for this string";
+ EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.status());
+ EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.last_status());
+
+ sb.resize(1029);
+ EXPECT_EQ(Status::OUT_OF_RANGE, sb.status());
+ EXPECT_EQ(Status::OUT_OF_RANGE, sb.last_status());
+
+ sb << "";
+ EXPECT_EQ(Status::OUT_OF_RANGE, sb.status());
+ EXPECT_EQ(Status::OK, sb.last_status());
+}
+
+TEST(StringBuilder, Status_ClearStatus_SetsStatuesToOk) {
+ StringBuffer<2> sb = MakeString<2>("Won't fit!!!!!");
+ EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.status());
+ EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.last_status());
+
+ sb.clear_status();
+ EXPECT_EQ(Status::OK, sb.status());
+ EXPECT_EQ(Status::OK, sb.last_status());
+}
+
+TEST(StringBuilder, StreamOutput_OutputSelf) {
+ auto sb = MakeString<32>("echo!");
+ sb << sb;
+
+ EXPECT_STREQ("echo!echo!", sb.data());
+ EXPECT_EQ(10u, sb.size());
+}
+
+TEST(StringBuilder, PushBack) {
+ StringBuffer<12> sb;
+ sb.push_back('?');
+ EXPECT_EQ(Status::OK, sb.last_status());
+ EXPECT_EQ(1u, sb.size());
+ EXPECT_STREQ("?", sb.data());
+}
+
+TEST(StringBuilder, PushBack_Full) {
+ StringBuffer<1> sb;
+ sb.push_back('!');
+ EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.last_status());
+ EXPECT_EQ(0u, sb.size());
+}
+
+TEST(StringBuilder, PopBack) {
+ auto sb = MakeString<12>("Welcome!");
+ sb.pop_back();
+ EXPECT_EQ(Status::OK, sb.last_status());
+ EXPECT_EQ(7u, sb.size());
+ EXPECT_STREQ("Welcome", sb.data());
+}
+
+TEST(StringBuilder, PopBack_Empty) {
+ StringBuffer<12> sb;
+ sb.pop_back();
+ EXPECT_EQ(Status::OUT_OF_RANGE, sb.last_status());
+ EXPECT_EQ(0u, sb.size());
+}
+
+TEST(StringBuilder, Append_NonTerminatedString) {
+ static char bad_string[256];
+ std::memset(bad_string, '?', sizeof(bad_string));
+
+ StringBuffer<6> sb;
+ EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.append(bad_string).last_status());
+ EXPECT_STREQ("?????", sb.data());
+}
+
+TEST(StringBuilder, Append_Chars) {
+ StringBuffer<8> sb;
+
+ EXPECT_TRUE(sb.append(7, '?').ok());
+ EXPECT_STREQ("???????", sb.data());
+}
+
+TEST(StringBuilder, Append_Chars_Full) {
+ StringBuffer<8> sb;
+
+ EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.append(8, '?').last_status());
+ EXPECT_STREQ("???????", sb.data());
+}
+
+TEST(StringBuilder, Append_PartialCString) {
+ StringBuffer<12> sb;
+ EXPECT_TRUE(sb.append("123456", 4).ok());
+ EXPECT_EQ(4u, sb.size());
+ EXPECT_STREQ("1234", sb.data());
+}
+
+TEST(StringBuilder, Append_CString) {
+ auto sb = MakeString("hello");
+ EXPECT_TRUE(sb.append(" goodbye").ok());
+ EXPECT_STREQ("hello goodbye", sb.data());
+ EXPECT_EQ(13u, sb.size());
+}
+
+TEST(StringBuilder, Append_CString_Full) {
+ auto sb = MakeString<6>("hello");
+ EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.append("890123", 1).last_status());
+ EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.status());
+ EXPECT_EQ(sb.max_size(), sb.size());
+ EXPECT_STREQ("hello", sb.data());
+}
+
+TEST(StringBuilder, Append_StringView) {
+ auto sb = MakeString<32>("hello");
+ EXPECT_TRUE(sb.append("???"sv).ok());
+ EXPECT_EQ("hello???"sv, sb);
+}
+
+TEST(StringBuilder, Append_StringView_Substring) {
+ auto sb = MakeString<32>("I like ");
+ EXPECT_TRUE(sb.append("your shoes!!!"sv, 5, 5).ok());
+ EXPECT_EQ("I like shoes"sv, sb);
+}
+
+TEST(StringBuilder, Append_StringView_RemainingSubstring) {
+ auto sb = MakeString<32>("I like ");
+ EXPECT_TRUE(sb.append("your shoes!!!"sv, 5).ok());
+ EXPECT_EQ("I like shoes!!!"sv, sb);
+}
+
+TEST(StringBuilder, Resize_Smaller) {
+ auto sb = MakeString<12>("Four");
+ sb.resize(2);
+ EXPECT_TRUE(sb.ok());
+ EXPECT_EQ(2u, sb.size());
+ EXPECT_STREQ("Fo", sb.data());
+}
+
+TEST(StringBuilder, Resize_Clear) {
+ auto sb = MakeString<12>("Four");
+ sb.resize(0);
+ EXPECT_TRUE(sb.ok());
+ EXPECT_EQ(0u, sb.size());
+ EXPECT_STREQ("", sb.data());
+}
+
+TEST(StringBuilder, Resize_Larger_Fails) {
+ auto sb = MakeString<12>("Four");
+ EXPECT_EQ(4u, sb.size());
+ sb.resize(10);
+ EXPECT_EQ(sb.status(), Status::OUT_OF_RANGE);
+ EXPECT_EQ(4u, sb.size());
+}
+
+TEST(StringBuilder, Resize_LargerThanCapacity_Fails) {
+ auto sb = MakeString<12>("Four");
+ sb.resize(1234);
+ EXPECT_EQ(sb.status(), Status::OUT_OF_RANGE);
+ EXPECT_EQ(4u, sb.size());
+ EXPECT_STREQ("Four", sb.data());
+}
+
+TEST(StringBuilder, Format_Normal) {
+ StringBuffer<64> sb;
+ EXPECT_TRUE(sb.Format("0x%x", 0xabc).ok());
+ EXPECT_STREQ("0xabc", sb.data());
+
+ sb << "def";
+
+ EXPECT_TRUE(sb.Format("GHI").ok());
+ EXPECT_STREQ("0xabcdefGHI", sb.data());
+}
+
+TEST(StringBuilder, Format_ExhaustBuffer) {
+ StringBuffer<6> sb;
+ EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.Format("012345").status());
+
+ EXPECT_STREQ("01234", sb.data());
+ EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.status());
+}
+
+TEST(StringBuilder, StreamOutput_MultipleTypes) {
+ constexpr const char* kExpected = "This is -1true example\n of this";
+ constexpr const char* kExample = "example";
+
+ StringBuffer<64> sb;
+ sb << "This is " << -1 << true << ' ' << kExample << '\n' << " of this";
+
+ EXPECT_STREQ(kExpected, sb.data());
+ EXPECT_EQ(std::strlen(kExpected), sb.size());
+}
+
+TEST(StringBuilder, StreamOutput_FullBufferIgnoresExtraStrings) {
+ StringBuffer<6> sb;
+ EXPECT_EQ(5u, sb.max_size()); // max_size() excludes the null terminator
+
+ sb << 1 - 1;
+ EXPECT_TRUE(sb.ok());
+ EXPECT_STREQ("0", sb.data());
+
+ sb << true << "Now it's way " << static_cast<unsigned char>(2) << " long";
+ EXPECT_FALSE(sb.ok());
+ EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.status());
+ EXPECT_STREQ("0true", sb.data());
+}
+
+TEST(StringBuilder, StreamOutput_ExhaustBuffer_InOneString) {
+ StringBuffer<9> sb;
+ EXPECT_EQ(8u, sb.max_size());
+
+ sb << "0123456789"; // write 10 chars
+ EXPECT_FALSE(sb.ok());
+ EXPECT_STREQ("01234567", sb.data()); // only can fit 8
+ EXPECT_EQ(8u, sb.size());
+
+ sb << "no"
+ << " more "
+ << "room" << '?';
+ EXPECT_STREQ("01234567", sb.data());
+}
+
+TEST(StringBuilder, StreamOutput_ExhaustBuffer_InTwoStrings) {
+ StringBuffer<4> sb;
+
+ sb << "01"; // fill 3/4 of buffer
+ EXPECT_EQ(2u, sb.size());
+ sb << "234";
+ EXPECT_STREQ("012", sb.data());
+ EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.status());
+ EXPECT_EQ(3u, sb.size());
+}
+
+TEST(StringBuilder, StreamOutput_NonTerminatedString) {
+ static char bad_string[256];
+ std::memset(bad_string, '?', sizeof(bad_string));
+
+ StringBuffer<6> sb;
+ sb << "hey" << bad_string;
+
+ EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.status());
+ EXPECT_STREQ("hey??", sb.data());
+}
+
+TEST(StringBuilder, SteamOutput_StringView) {
+ StringBuffer<6> buffer;
+ constexpr std::string_view hello("hello");
+
+ buffer << hello;
+ EXPECT_EQ(Status::OK, buffer.status());
+ EXPECT_STREQ("hello", buffer.data());
+}
+
+TEST(StringBuilder, StreamOutput_EmptyStringView) {
+ StringBuffer<4> buffer;
+ buffer << "hi" << std::string_view() << "!";
+ EXPECT_TRUE(buffer.ok());
+ EXPECT_STREQ("hi!", buffer.data());
+}
+
+TEST(StringBuffer, Assign) {
+ StringBuffer<10> one;
+ StringBuffer<10> two;
+
+ one << "What";
+ ASSERT_STREQ("What", one.data());
+ two = one;
+ EXPECT_STREQ("What", two.data());
+ EXPECT_NE(one.data(), two.data());
+ one << " the";
+ two << " heck";
+
+ EXPECT_STREQ("What the", one.data());
+ EXPECT_STREQ("What heck", two.data());
+
+ two << "0123456789";
+ ASSERT_STREQ("What heck", two.data());
+ ASSERT_EQ(Status::RESOURCE_EXHAUSTED, two.status());
+ ASSERT_EQ(Status::RESOURCE_EXHAUSTED, two.last_status());
+
+ one = two;
+ EXPECT_STREQ("What heck", one.data());
+ EXPECT_EQ(Status::RESOURCE_EXHAUSTED, one.status());
+ EXPECT_EQ(Status::RESOURCE_EXHAUSTED, one.last_status());
+
+ StringBuffer<12> three;
+ three = two;
+ EXPECT_STREQ(three.data(), two.data());
+ EXPECT_EQ(three.size(), two.size());
+}
+
+TEST(StringBuffer, CopyConstructFromSameSize) {
+ StringBuffer<10> one;
+
+ one << "What";
+ ASSERT_STREQ("What", one.data());
+ StringBuffer<10> two(one);
+ EXPECT_STREQ("What", two.data());
+ EXPECT_NE(one.data(), two.data());
+ one << " the";
+ two << " heck";
+
+ EXPECT_STREQ("What the", one.data());
+ EXPECT_STREQ("What heck", two.data());
+
+ two << "0123456789";
+ two << "";
+ ASSERT_STREQ("What heck", two.data());
+ ASSERT_EQ(Status::RESOURCE_EXHAUSTED, two.status());
+ ASSERT_EQ(Status::OK, two.last_status());
+}
+
+TEST(StringBuffer, CopyConstructFromSmaller) {
+ StringBuffer<10> one = MakeString<10>("You are the chosen one.");
+ StringBuffer<12> two(one);
+
+ EXPECT_STREQ("You are t", two.data());
+ EXPECT_EQ(Status::RESOURCE_EXHAUSTED, two.status());
+}
+
+TEST(MakeString, Object) {
+ CustomType custom;
+ const auto sb = MakeString<64>(custom);
+
+ EXPECT_STREQ(CustomType::kToString, sb.data());
+ EXPECT_EQ(std::strlen(CustomType::kToString), sb.size());
+}
+
+TEST(MakeString, IntegerTypes) {
+ EXPECT_STREQ("0123-4567",
+ MakeString(0ll,
+ 1u,
+ 2l,
+ 3,
+ -4,
+ static_cast<unsigned short>(5),
+ static_cast<short>(6),
+ static_cast<unsigned char>(7))
+ .data());
+}
+
+TEST(MakeString, Char) {
+ EXPECT_STREQ("a b c", MakeString('a', ' ', 'b', ' ', 'c').data());
+}
+
+TEST(MakeString, Float) { EXPECT_STREQ("-inf", MakeString(-INFINITY).data()); }
+
+TEST(MakeString, Pointer_Null) {
+ EXPECT_STREQ("(null)", MakeString(nullptr).data());
+ EXPECT_STREQ("(null)", MakeString(static_cast<void*>(nullptr)).data());
+}
+
+TEST(MakeString, Pointer_NonNull) {
+ EXPECT_STREQ("1", MakeString(reinterpret_cast<void*>(0x1)).data());
+ EXPECT_STREQ("123", MakeString(reinterpret_cast<int*>(0x123)).data());
+}
+
+TEST(MakeString, Pointer_CustomType) {
+ char expected[32] = {};
+
+ CustomType custom;
+ std::snprintf(expected,
+ sizeof(expected),
+ "%" PRIxPTR,
+ reinterpret_cast<uintptr_t>(&custom));
+
+ EXPECT_STREQ(expected, MakeString(&custom).data());
+}
+
+TEST(MakeString, Bool) {
+ EXPECT_STREQ("true", MakeString(true).data());
+ EXPECT_STREQ("false", MakeString(false).data());
+}
+
+TEST(MakeString, MutableString) {
+ char chars[] = {'C', 'o', 'o', 'l', '\0'};
+ EXPECT_STREQ("Cool?", MakeString(chars, "?").data());
+}
+
+TEST(MakeString, Empty_IsEmpty) { EXPECT_TRUE(MakeString().empty()); }
+
+constexpr char kLongestString[] = "18446744073709551615"; // largest uint64_t
+
+TEST(MakeString, DefaultSizeString_FitsWholeString) {
+ EXPECT_STREQ(
+ kLongestString,
+ MakeString(184, "467", u'\x04', "40", '7', '3', '7', "0", "", 955ul, 1615)
+ .data());
+}
+
+TEST(MakeString, LargerThanDefaultSize_Truncates) {
+ auto sb = MakeString("1844674407", 3709551615, 123456);
+
+ EXPECT_EQ(Status::RESOURCE_EXHAUSTED, sb.status());
+ EXPECT_STREQ(kLongestString, sb.data());
+}
+
+TEST(MakeString, StringLiteral_ResizesToFitWholeLiteral) {
+ EXPECT_STREQ("", MakeString().data());
+
+ auto normal = MakeString("");
+ static_assert(normal.max_size() == decltype(MakeString(1))::max_size());
+
+ auto resized = MakeString("This string is reeeeeeeeeaaaaallly long!!!!!");
+ static_assert(resized.max_size() > decltype(MakeString(1))::max_size());
+ static_assert(resized.max_size() ==
+ sizeof("This string is reeeeeeeeeaaaaallly long!!!!!") - 1);
+}
+
+TEST(MakeString, StringLiteral_UsesLongerFixedSize) {
+ auto fixed_size = MakeString<64>("");
+ static_assert(fixed_size.max_size() == 63u);
+ EXPECT_EQ(fixed_size.max_size(), 63u);
+ EXPECT_STREQ("", fixed_size.data());
+}
+
+TEST(MakeString, StringLiteral_TruncatesShorterFixedSize) {
+ EXPECT_STREQ("Goo", MakeString<4>("Google").data());
+ EXPECT_STREQ("Google", MakeString<7>("Google").data());
+ EXPECT_EQ(MakeString().max_size(), MakeString("Google").max_size());
+ EXPECT_STREQ("Google", MakeString("Google").data());
+}
+
+TEST(MakeString, DefaultSize_FitsMaxAndMinInts) {
+ EXPECT_STREQ("-9223372036854775808",
+ MakeString(std::numeric_limits<int64_t>::min()).data());
+ EXPECT_STREQ("18446744073709551615",
+ MakeString(std::numeric_limits<uint64_t>::max()).data());
+}
+
+TEST(MakeString, OutputToTemporaryStringBuffer) {
+ EXPECT_STREQ("hello", (MakeString<6>("hello ") << "world").data());
+ EXPECT_STREQ("hello world", (MakeString("hello ") << "world").data());
+}
+
+// Test MakeString's default size calculations.
+template <typename... Args>
+constexpr size_t DefaultStringBufferSize(Args&&...) {
+ return string_internal::DefaultStringBufferSize<Args...>();
+}
+
+// Default sizes are rounded up to 24 bytes.
+static_assert(DefaultStringBufferSize("") == 24);
+static_assert(DefaultStringBufferSize("123") == 24);
+static_assert(DefaultStringBufferSize("123", "456", "78901234567890") == 24);
+static_assert(DefaultStringBufferSize("1234567890", "1234567890", "123") == 24);
+static_assert(DefaultStringBufferSize(1234, 5678, 9012) == 24);
+
+// The buffer is sized to fix strings needing more than 24 bytes.
+static_assert(DefaultStringBufferSize("1234567890", "1234567890", "1234") ==
+ 25);
+static_assert(DefaultStringBufferSize("1234567890", "1234567890", "12345") ==
+ 26);
+static_assert(DefaultStringBufferSize("1234567890", "1234567890", "12345678") ==
+ 29);
+
+// Four bytes are allocated for each non-string argument.
+static_assert(DefaultStringBufferSize(1234, 5678, 9012, 3456, 7890, 1234) ==
+ 25);
+static_assert(DefaultStringBufferSize('a', nullptr, 'b', 4, 5, 6, 7, 8) == 33);
+
+} // namespace
+} // namespace pw
diff --git a/pw_string/to_string_test.cc b/pw_string/to_string_test.cc
index 125cda0d3..437d1340c 100644
--- a/pw_string/to_string_test.cc
+++ b/pw_string/to_string_test.cc
@@ -22,6 +22,7 @@
#include "gtest/gtest.h"
#include "pw_status/status.h"
+#include "pw_string/type_to_string.h"
namespace pw {
@@ -117,8 +118,6 @@ TEST(ToString, Float) {
EXPECT_STREQ("-NaN", buffer);
}
-constexpr std::string_view kNullString = "(null)";
-
TEST(ToString, Pointer_NonNull_WritesValue) {
CustomType custom;
const size_t length = std::snprintf(expected,
@@ -135,24 +134,25 @@ TEST(ToString, Pointer_NonNull_WritesValue) {
}
TEST(ToString, Pointer_Nullptr_WritesNull) {
- EXPECT_EQ(kNullString.size(), ToString(nullptr, buffer).size());
- EXPECT_EQ(kNullString, buffer);
+ EXPECT_EQ(string::kNullPointerString.size(),
+ ToString(nullptr, buffer).size());
+ EXPECT_EQ(string::kNullPointerString, buffer);
}
TEST(ToString, Pointer_NullValuedPointer_WritesNull) {
- EXPECT_EQ(kNullString.size(),
+ EXPECT_EQ(string::kNullPointerString.size(),
ToString(static_cast<const CustomType*>(nullptr), buffer).size());
- EXPECT_EQ(kNullString, buffer);
+ EXPECT_EQ(string::kNullPointerString, buffer);
}
TEST(ToString, Pointer_NullValuedCString_WritesNull) {
- EXPECT_EQ(kNullString.size(),
+ EXPECT_EQ(string::kNullPointerString.size(),
ToString(static_cast<char*>(nullptr), buffer).size());
- EXPECT_EQ(kNullString, buffer);
+ EXPECT_EQ(string::kNullPointerString, buffer);
- EXPECT_EQ(kNullString.size(),
+ EXPECT_EQ(string::kNullPointerString.size(),
ToString(static_cast<const char*>(nullptr), buffer).size());
- EXPECT_EQ(kNullString, buffer);
+ EXPECT_EQ(string::kNullPointerString, buffer);
}
TEST(ToString, String_Literal) {
@@ -218,9 +218,9 @@ TEST(ToString, StdArrayAsBuffer) {
EXPECT_STREQ("false", test_buffer.data());
EXPECT_EQ(2u, ToString("Hi", test_buffer).size());
EXPECT_STREQ("Hi", test_buffer.data());
- EXPECT_EQ(kNullString.size(),
+ EXPECT_EQ(string::kNullPointerString.size(),
ToString(static_cast<void*>(nullptr), test_buffer).size());
- EXPECT_EQ(kNullString, test_buffer.data());
+ EXPECT_EQ(string::kNullPointerString, test_buffer.data());
}
TEST(ToString, StringView) {
diff --git a/pw_string/type_to_string.cc b/pw_string/type_to_string.cc
index d3175672b..8255e2848 100644
--- a/pw_string/type_to_string.cc
+++ b/pw_string/type_to_string.cc
@@ -172,10 +172,8 @@ StatusWithSize BoolToString(bool value, const span<char>& buffer) {
}
StatusWithSize PointerToString(const void* pointer, const span<char>& buffer) {
- static constexpr std::string_view kNullString("(null)");
-
if (pointer == nullptr) {
- return CopyEntireString(kNullString, buffer);
+ return CopyEntireString(kNullPointerString, buffer);
}
return IntToHexString(reinterpret_cast<uintptr_t>(pointer), buffer);
}
diff --git a/pw_string/util_test.cc b/pw_string/util_test.cc
new file mode 100644
index 000000000..25375b956
--- /dev/null
+++ b/pw_string/util_test.cc
@@ -0,0 +1,41 @@
+// Copyright 2019 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+#include "pw_string/util.h"
+
+#include "gtest/gtest.h"
+
+namespace pw::string {
+namespace {
+
+TEST(Length, Nullptr_Returns0) { EXPECT_EQ(0u, Length(nullptr, 100)); }
+
+TEST(Length, EmptyString_Returns0) {
+ EXPECT_EQ(0u, Length("", 0));
+ EXPECT_EQ(0u, Length("", 100));
+}
+
+TEST(Length, MaxLongerThanString_ReturnsStrlen) {
+ EXPECT_EQ(5u, Length("12345", 100));
+}
+
+TEST(Length, StringMaxLongerThanMax_ReturnsMax) {
+ EXPECT_EQ(0u, Length("12345", 0));
+ EXPECT_EQ(4u, Length("12345", 4));
+}
+
+TEST(Length, LengthEqualsMax) { EXPECT_EQ(5u, Length("12345", 5)); }
+
+} // namespace
+} // namespace pw::string