pw_unit_test: Display the contents of unknown objects

- Have UnknownTypeToString output the first 8 or 9 bytes of objects.
  This is helpful when debugging test failures.
- Increase the expectation string buffer size so it can fit the new
  output.

Change-Id: I29ddc32d40a069c48e323d994fe874ca539ddc45
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/128070
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
Pigweed-Auto-Submit: Wyatt Hepler <hepler@google.com>
Reviewed-by: Alexei Frolov <frolv@google.com>
diff --git a/pw_unit_test/framework_test.cc b/pw_unit_test/framework_test.cc
index 939c818..e5e12b3 100644
--- a/pw_unit_test/framework_test.cc
+++ b/pw_unit_test/framework_test.cc
@@ -208,5 +208,52 @@
   value_ = 3210;
 }
 
+TEST(UnknownTypeToString, SmallObjectDisplaysFullContents) {
+  struct {
+    char a = 0xa1;
+  } object;
+
+  StringBuffer<64> expected;
+  expected << "<1-byte object at 0x" << &object << " | a1>";
+  ASSERT_EQ(OkStatus(), expected.status());
+
+  StringBuffer<64> actual;
+  actual << object;
+  ASSERT_EQ(OkStatus(), actual.status());
+  EXPECT_STREQ(expected.c_str(), actual.c_str());
+}
+
+TEST(UnknownTypeToString, MaxSizeToDisplayFullContents) {
+  struct {
+    char a[9] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
+  } object;
+
+  StringBuffer<64> expected;
+  expected << "<9-byte object at 0x" << &object
+           << " | 01 02 03 04 05 06 07 08 09>";
+  ASSERT_EQ(OkStatus(), expected.status());
+
+  StringBuffer<64> actual;
+  actual << object;
+  ASSERT_EQ(OkStatus(), actual.status());
+  EXPECT_STREQ(expected.c_str(), actual.c_str());
+}
+
+TEST(UnknownTypeToString, TruncatedContents) {
+  struct {
+    char a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+  } object;
+
+  StringBuffer<72> expected;
+  expected << "<10-byte object at 0x" << &object
+           << " | 01 02 03 04 05 06 07 08 …>";
+  ASSERT_EQ(OkStatus(), expected.status());
+
+  StringBuffer<72> actual;
+  actual << object;
+  ASSERT_EQ(OkStatus(), actual.status());
+  EXPECT_STREQ(expected.c_str(), actual.c_str());
+}
+
 }  // namespace
 }  // namespace pw
diff --git a/pw_unit_test/public/pw_unit_test/internal/framework.h b/pw_unit_test/public/pw_unit_test/internal/framework.h
index 73af392..5db8fdd 100644
--- a/pw_unit_test/public/pw_unit_test/internal/framework.h
+++ b/pw_unit_test/public/pw_unit_test/internal/framework.h
@@ -155,7 +155,26 @@
 template <typename T>
 StatusWithSize UnknownTypeToString(const T& value, span<char> buffer) {
   StringBuilder sb(buffer);
-  sb << '<' << sizeof(value) << "-byte object at 0x" << &value << '>';
+  sb << '<' << sizeof(value) << "-byte object at 0x" << &value << " |";
+
+  // Always show the first 8 bytes of the object.
+  constexpr size_t kBytesToPrint = std::min(sizeof(value), size_t{8});
+
+  // reinterpret_cast to std::byte is permitted by C++'s type aliasing rules.
+  const std::byte* bytes = reinterpret_cast<const std::byte*>(&value);
+
+  for (size_t i = 0; i < kBytesToPrint; ++i) {
+    sb << ' ' << bytes[i];
+  }
+
+  // If there's just one more byte, output it. Otherwise, output ellipsis.
+  if (sizeof(value) == kBytesToPrint + 1) {
+    sb << ' ' << bytes[sizeof(value) - 1];
+  } else if (sizeof(value) > kBytesToPrint) {
+    sb << " …";
+  }
+
+  sb << '>';
   return sb.status_with_size();
 }
 
@@ -274,7 +293,7 @@
     // version of the arguments. This buffer is allocated on the unit test's
     // stack, so it shouldn't be too large.
     // TODO(hepler): Make this configurable.
-    [[maybe_unused]] constexpr size_t kExpectationBufferSizeBytes = 128;
+    [[maybe_unused]] constexpr size_t kExpectationBufferSizeBytes = 192;
 
     const bool success = expectation(lhs, rhs);
     CurrentTestExpectSimple(