pw_status: StatusWithSize class

- Create StatusWithSize, which efficiently stores a Status and a
  non-negative integer.
- Add tests for Status and StatusWithSize.

Change-Id: I34ade117d890318d45a27664fe53303365f21f68
diff --git a/pw_status/BUILD.gn b/pw_status/BUILD.gn
index c3172af..1659341 100644
--- a/pw_status/BUILD.gn
+++ b/pw_status/BUILD.gn
@@ -12,6 +12,8 @@
 # License for the specific language governing permissions and limitations under
 # the License.
 
+import("$dir_pw_unit_test/test.gni")
+
 config("default_config") {
   include_dirs = [ "public" ]
 }
@@ -23,6 +25,27 @@
   ]
   public = [
     "public/pw_status/status.h",
+    "public/pw_status/status_with_size.h",
   ]
   sources = [ "status.cc" ] + public
 }
+
+pw_test("status_test") {
+  deps = [
+    ":pw_status",
+    "$dir_pw_unit_test:main",
+  ]
+  sources = [
+    "status_test.cc",
+  ]
+}
+
+pw_test("status_with_size_test") {
+  deps = [
+    ":pw_status",
+    "$dir_pw_unit_test:main",
+  ]
+  sources = [
+    "status_with_size_test.cc",
+  ]
+}
diff --git a/pw_status/public/pw_status/status_with_size.h b/pw_status/public/pw_status/status_with_size.h
new file mode 100644
index 0000000..e3f9814
--- /dev/null
+++ b/pw_status/public/pw_status/status_with_size.h
@@ -0,0 +1,83 @@
+// 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>
+
+#include "pw_status/status.h"
+
+namespace pw {
+
+// StatusWithSize stores a status and an unsigned integer. The integer must not
+// exceed StatusWithSize::max_size(), which is 134,217,727 (2**27 - 1) on 32-bit
+// systems.
+//
+// StatusWithSize is useful for reporting the number of bytes read or written in
+// an operation along with the status. For example, a function that writes a
+// formatted string may want to report both the number of characters written and
+// whether it ran out of space.
+//
+// StatusWithSize is more efficient than its alternatives. It packs a status and
+// size into a single word, which can be returned from a function in a register.
+// Because they are packed together, the size is limited to max_size().
+//
+// StatusWithSize's alternatives result in larger code size. For example:
+//
+//   1. Return status, pass size output as a pointer argument.
+//
+//      Requires an additional argument and forces the output argument to the
+//      stack in order to pass an address, increasing code size.
+//
+//   2. Return an object with Status and size members.
+//
+//      At least for ARMv7-M, the returned struct is created on the stack, which
+//      increases code size.
+//
+class StatusWithSize {
+ public:
+  // Creates a StatusWithSize with Status::OK and the provided size.
+  // TODO(hepler): Add debug-only assert that size <= max_size().
+  explicit constexpr StatusWithSize(size_t size = 0) : size_(size) {}
+
+  // Creates a StatusWithSize with the provided status and size.
+  explicit constexpr StatusWithSize(Status status, size_t size)
+      : StatusWithSize(size |
+                       (static_cast<size_t>(status.code()) << kStatusShift)) {}
+
+  constexpr StatusWithSize(const StatusWithSize&) = default;
+  constexpr StatusWithSize& operator=(const StatusWithSize&) = default;
+
+  // Returns the size. The size is always present, even if status() is an error.
+  constexpr size_t size() const { return size_ & kSizeMask; }
+
+  // The maximum valid value for size.
+  static constexpr size_t max_size() { return kSizeMask; }
+
+  // True if status() == Status::OK.
+  constexpr bool ok() const { return (size_ & kStatusMask) == 0u; }
+
+  constexpr Status status() const {
+    return static_cast<Status::Code>((size_ & kStatusMask) >> kStatusShift);
+  }
+
+ private:
+  static constexpr size_t kStatusBits = 5;
+  static constexpr size_t kSizeMask = ~static_cast<size_t>(0) >> kStatusBits;
+  static constexpr size_t kStatusMask = ~kSizeMask;
+  static constexpr size_t kStatusShift = sizeof(size_t) * 8 - kStatusBits;
+
+  size_t size_;
+};
+
+}  // namespace pw
diff --git a/pw_status/status_test.cc b/pw_status/status_test.cc
new file mode 100644
index 0000000..d7e28a4
--- /dev/null
+++ b/pw_status/status_test.cc
@@ -0,0 +1,80 @@
+// 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_status/status.h"
+
+#include "gtest/gtest.h"
+
+namespace pw::string {
+namespace {
+
+constexpr Status::Code kInvalidCode = static_cast<Status::Code>(30);
+
+TEST(Status, Default) {
+  Status status;
+  EXPECT_TRUE(status.ok());
+  EXPECT_EQ(Status(), status);
+}
+
+TEST(Status, ConstructWithStatusCode) {
+  Status status(Status::ABORTED);
+  EXPECT_EQ(Status::ABORTED, status.code());
+}
+
+TEST(Status, AssignFromStatusCode) {
+  Status status;
+  status = Status::INTERNAL;
+  EXPECT_EQ(Status::INTERNAL, status.code());
+}
+
+TEST(Status, CompareToStatusCode) {
+  EXPECT_EQ(Status(), Status::OK);
+  EXPECT_EQ(Status::ABORTED, Status(Status::ABORTED));
+  EXPECT_NE(Status(), Status::ABORTED);
+}
+
+TEST(Status, Ok_OkIsTrue) {
+  EXPECT_TRUE(Status().ok());
+  EXPECT_TRUE(Status(Status::OK).ok());
+}
+
+TEST(Status, NotOk_OkIsFalse) {
+  EXPECT_FALSE(Status(Status::DATA_LOSS).ok());
+  EXPECT_FALSE(Status(kInvalidCode).ok());
+}
+
+TEST(Status, KnownString) {
+  EXPECT_EQ("OK", Status(Status::OK).str());
+  EXPECT_EQ("CANCELLED", Status(Status::CANCELLED).str());
+  EXPECT_EQ("DEADLINE_EXCEEDED", Status(Status::DEADLINE_EXCEEDED).str());
+  EXPECT_EQ("NOT_FOUND", Status(Status::NOT_FOUND).str());
+  EXPECT_EQ("ALREADY_EXISTS", Status(Status::ALREADY_EXISTS).str());
+  EXPECT_EQ("PERMISSION_DENIED", Status(Status::PERMISSION_DENIED).str());
+  EXPECT_EQ("UNAUTHENTICATED", Status(Status::UNAUTHENTICATED).str());
+  EXPECT_EQ("RESOURCE_EXHAUSTED", Status(Status::RESOURCE_EXHAUSTED).str());
+  EXPECT_EQ("FAILED_PRECONDITION", Status(Status::FAILED_PRECONDITION).str());
+  EXPECT_EQ("ABORTED", Status(Status::ABORTED).str());
+  EXPECT_EQ("OUT_OF_RANGE", Status(Status::OUT_OF_RANGE).str());
+  EXPECT_EQ("UNIMPLEMENTED", Status(Status::UNIMPLEMENTED).str());
+  EXPECT_EQ("INTERNAL", Status(Status::INTERNAL).str());
+  EXPECT_EQ("UNAVAILABLE", Status(Status::UNAVAILABLE).str());
+  EXPECT_EQ("DATA_LOSS", Status(Status::DATA_LOSS).str());
+}
+
+TEST(Status, UnknownString) {
+  EXPECT_EQ("INVALID STATUS", Status(static_cast<Status::Code>(30)).str());
+}
+
+}  // namespace
+}  // namespace pw::string
diff --git a/pw_status/status_with_size_test.cc b/pw_status/status_with_size_test.cc
new file mode 100644
index 0000000..7b07992
--- /dev/null
+++ b/pw_status/status_with_size_test.cc
@@ -0,0 +1,101 @@
+// 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_status/status_with_size.h"
+
+#include "gtest/gtest.h"
+
+namespace pw::string {
+namespace {
+
+static_assert(StatusWithSize::max_size() ==
+              (static_cast<size_t>(1) << (sizeof(size_t) * 8 - 5)) - 1);
+
+TEST(StatusWithSize, Default) {
+  StatusWithSize result;
+  EXPECT_TRUE(result.ok());
+  EXPECT_EQ(Status::OK, result.status());
+  EXPECT_EQ(0u, result.size());
+}
+
+TEST(StatusWithSize, ConstructWithSize) {
+  StatusWithSize result = StatusWithSize(456);
+  EXPECT_TRUE(result.ok());
+  EXPECT_EQ(Status::OK, result.status());
+  EXPECT_EQ(456u, result.size());
+}
+
+TEST(StatusWithSize, ConstructWithError) {
+  StatusWithSize result(Status::RESOURCE_EXHAUSTED, 123);
+  EXPECT_FALSE(result.ok());
+  EXPECT_EQ(Status::RESOURCE_EXHAUSTED, result.status());
+  EXPECT_EQ(123u, result.size());
+}
+
+TEST(StatusWithSize, ConstructWithOkAndSize) {
+  StatusWithSize result(Status::OK, 99);
+  EXPECT_TRUE(result.ok());
+  EXPECT_EQ(Status::OK, result.status());
+  EXPECT_EQ(99u, result.size());
+}
+
+TEST(StatusWithSize, AllStatusValues_ZeroSize) {
+  for (int i = 0; i < 32; ++i) {
+    StatusWithSize result(static_cast<Status::Code>(i), 0);
+    EXPECT_EQ(result.ok(), i == 0);
+    EXPECT_EQ(i, static_cast<int>(result.status().code()));
+    EXPECT_EQ(0u, result.size());
+  }
+}
+
+TEST(StatusWithSize, AllStatusValues_SameSize) {
+  for (int i = 0; i < 32; ++i) {
+    StatusWithSize result(static_cast<Status::Code>(i), i);
+    EXPECT_EQ(result.ok(), i == 0);
+    EXPECT_EQ(i, static_cast<int>(result.status().code()));
+    EXPECT_EQ(static_cast<size_t>(i), result.size());
+  }
+}
+
+TEST(StatusWithSize, AllStatusValues_MaxSize) {
+  for (int i = 0; i < 32; ++i) {
+    StatusWithSize result(static_cast<Status::Code>(i),
+                          StatusWithSize::max_size());
+    EXPECT_EQ(result.ok(), i == 0);
+    EXPECT_EQ(i, static_cast<int>(result.status().code()));
+    EXPECT_EQ(result.max_size(), result.size());
+  }
+}
+
+TEST(StatusWithSize, Assignment) {
+  StatusWithSize result = StatusWithSize(Status::INTERNAL, 0x123);
+  EXPECT_FALSE(result.ok());
+  EXPECT_EQ(Status::INTERNAL, result.status());
+  EXPECT_EQ(0x123u, result.size());
+
+  result = StatusWithSize(300);
+  EXPECT_TRUE(result.ok());
+  EXPECT_EQ(Status::OK, result.status());
+  EXPECT_EQ(300u, result.size());
+}
+
+TEST(StatusWithSize, Constexpr) {
+  constexpr StatusWithSize result(Status::CANCELLED, 1234);
+  static_assert(Status::CANCELLED == result.status());
+  static_assert(!result.ok());
+  static_assert(1234u == result.size());
+}
+
+}  // namespace
+}  // namespace pw::string