pw_assert: PW_CHECK_OK() macro for Status
This adds PW_CHECK_OK() for both C and C++ to assert that a Status
object or enum is OK.
Change-Id: I85e963bffc39510769feeb93e0b331f044557e7e
diff --git a/pw_assert/BUILD.gn b/pw_assert/BUILD.gn
index 0fac58d..63b61b6 100644
--- a/pw_assert/BUILD.gn
+++ b/pw_assert/BUILD.gn
@@ -48,6 +48,7 @@
"public/pw_assert/internal/assert_impl.h",
"pw_assert_test/fake_backend.h",
]
+ deps = [ dir_pw_status ]
}
pw_test("assert_backend_compile_test") {
@@ -55,6 +56,7 @@
deps = [
":pw_assert",
dir_pw_assert_backend,
+ dir_pw_status,
]
sources = [
"assert_backend_compile_test.c",
diff --git a/pw_assert/assert_backend_compile_test.c b/pw_assert/assert_backend_compile_test.c
index 599fa93..fb81a94 100644
--- a/pw_assert/assert_backend_compile_test.c
+++ b/pw_assert/assert_backend_compile_test.c
@@ -24,6 +24,7 @@
#define PW_ASSERT_USE_SHORT_NAMES 1
#include "pw_assert/assert.h"
+#include "pw_status/status.h"
#ifdef __cplusplus
#error "This file must be compiled as plain C to verify C compilation works."
@@ -182,4 +183,13 @@
CHECK_INT_LE(Add3(1, 2, 3), Add3(1, 2, 3), "INT: " FAIL_IF_DISPLAYED);
CHECK_INT_LE(x_int, y_int, "INT: " FAIL_IF_DISPLAYED_ARGS, z);
}
+
+ { // Compile tests for PW_ASSERT_OK().
+ PW_CHECK_OK(PW_STATUS_OK);
+ PW_CHECK_OK(PW_STATUS_OK, "msg");
+ PW_CHECK_OK(PW_STATUS_OK, "msg: %d", 5);
+ PW_DCHECK_OK(PW_STATUS_OK);
+ PW_DCHECK_OK(PW_STATUS_OK, "msg");
+ PW_DCHECK_OK(PW_STATUS_OK, "msg: %d", 5);
+ }
}
diff --git a/pw_assert/assert_backend_compile_test.cc b/pw_assert/assert_backend_compile_test.cc
index 47df056..372810b 100644
--- a/pw_assert/assert_backend_compile_test.cc
+++ b/pw_assert/assert_backend_compile_test.cc
@@ -40,6 +40,7 @@
#include "pw_assert/assert.h"
// clang-format on
+#include "pw_status/status.h"
#include "gtest/gtest.h"
// This is a global constant to feed into the formatter for tests.
@@ -197,6 +198,32 @@
CHECK_INT_LE(x_int, y_int, "INT: " FAIL_IF_DISPLAYED_ARGS, z);
}
+pw::Status MakeStatus(pw::Status status) { return status; }
+
+TEST(Check, CheckOkMacrosCompile) {
+ MAYBE_SKIP_TEST;
+ pw::Status status = pw::Status::UNKNOWN;
+
+ // Typical case with long names.
+ PW_CHECK_OK(status);
+ PW_CHECK_OK(status, "msg");
+ PW_CHECK_OK(status, "msg: %d", 5);
+
+ // Short names.
+ CHECK_OK(status);
+ CHECK_OK(status, "msg");
+ CHECK_OK(status, "msg: %d", 5);
+
+ // Status from a literal.
+ PW_CHECK_OK(pw::Status::OK);
+
+ // Status from a function.
+ PW_CHECK_OK(MakeStatus(pw::Status::OK));
+
+ // Status from C enums.
+ PW_CHECK_OK(PW_STATUS_OK);
+}
+
// These are defined in assert_test.c, to test C compatibility.
extern "C" void AssertBackendCompileTestsInC();
diff --git a/pw_assert/assert_facade_test.cc b/pw_assert/assert_facade_test.cc
index c4b0890..a234562 100644
--- a/pw_assert/assert_facade_test.cc
+++ b/pw_assert/assert_facade_test.cc
@@ -420,6 +420,65 @@
CHECK_INT_LE(1, 2, "msg: %d", 5);
}
+// Verify PW_CHECK_OK, including message handling.
+TEST_F(AssertFail, StatusNotOK) {
+ pw::Status status = pw::Status::UNKNOWN;
+ PW_CHECK_OK(status);
+ EXPECT_MESSAGE("Check failed: status (=UNKNOWN) == Status::OK (=OK). ");
+}
+
+TEST_F(AssertFail, StatusNotOKMessageNoArguments) {
+ pw::Status status = pw::Status::UNKNOWN;
+ PW_CHECK_OK(status, "msg");
+ EXPECT_MESSAGE("Check failed: status (=UNKNOWN) == Status::OK (=OK). msg");
+}
+
+TEST_F(AssertFail, StatusNotOKMessageArguments) {
+ pw::Status status = pw::Status::UNKNOWN;
+ PW_CHECK_OK(status, "msg: %d", 5);
+ EXPECT_MESSAGE("Check failed: status (=UNKNOWN) == Status::OK (=OK). msg: 5");
+}
+
+// Example expression for the test below.
+pw::Status DoTheThing() { return pw::Status::RESOURCE_EXHAUSTED; }
+
+TEST_F(AssertFail, NonTrivialExpression) {
+ PW_CHECK_OK(DoTheThing());
+ EXPECT_MESSAGE(
+ "Check failed: DoTheThing() (=RESOURCE_EXHAUSTED) == Status::OK (=OK). ");
+}
+
+// Note: This function seems pointless but it is not, since pw::Status::FOO
+// constants are not actually status objects, but code objects. This way we can
+// ensure the macros work with both real status objects and literals.
+pw::Status MakeStatus(pw::Status status) { return status; }
+TEST_F(AssertPass, Constant) { PW_CHECK_OK(pw::Status::OK); }
+TEST_F(AssertPass, Dynamic) { PW_CHECK_OK(MakeStatus(pw::Status::OK)); }
+TEST_F(AssertPass, Enum) { PW_CHECK_OK(PW_STATUS_OK); }
+TEST_F(AssertFail, Constant) { PW_CHECK_OK(pw::Status::UNKNOWN); }
+TEST_F(AssertFail, Dynamic) { PW_CHECK_OK(MakeStatus(pw::Status::UNKNOWN)); }
+TEST_F(AssertFail, Enum) { PW_CHECK_OK(PW_STATUS_UNKNOWN); }
+
+#if PW_ASSERT_ENABLE_DCHECK
+
+// In debug mode, the asserts should check their arguments.
+TEST_F(AssertPass, DCheckConstant) { PW_DCHECK_OK(pw::Status::OK); }
+TEST_F(AssertPass, DCheckDynamic) { PW_DCHECK_OK(MakeStatus(pw::Status::OK)); }
+TEST_F(AssertFail, DCheckConstant) { PW_DCHECK_OK(pw::Status::UNKNOWN); }
+TEST_F(AssertFail, DCheckDynamic) {
+ PW_DCHECK_OK(MakeStatus(pw::Status::UNKNOWN));
+}
+#else // PW_ASSERT_ENABLE_DCHECK
+
+// In release mode, all the asserts should pass.
+TEST_F(AssertPass, DCheckConstant) { PW_DCHECK_OK(pw::Status::OK); }
+TEST_F(AssertPass, DCheckDynamic) { PW_DCHECK_OK(MakeStatus(pw::Status::OK)); }
+TEST_F(AssertPass, DCheckConstant) { PW_DCHECK_OK(pw::Status::UNKNOWN); }
+TEST_F(AssertPass, DCheckDynamic) {
+ PW_DCHECK_OK(MakeStatus(pw::Status::UNKNOWN));
+}
+#endif // PW_ASSERT_ENABLE_DCHECK
+
// TODO: Figure out how to run some of these tests is C.
} // namespace
diff --git a/pw_assert/docs.rst b/pw_assert/docs.rst
index 5608043..b3c6452 100644
--- a/pw_assert/docs.rst
+++ b/pw_assert/docs.rst
@@ -174,22 +174,29 @@
.. attention::
- Don't use use ``PW_CHECK`` for binary comparisons!
+ Don't use use ``PW_CHECK`` for binary comparisons or status checks!
Instead, use the ``PW_CHECK_<TYPE>_<OP>`` macros. These macros enable
capturing the value of the operands, and also tokenizing them if using a
tokenizing assert backend. For example, if ``x`` and ``b`` are integers,
use instead ``PW_CHECK_INT_LT(x, b)``.
- +---------------------------------+-------------------------------------+
- | **Do NOT do this** | **Do this instead** |
- +---------------------------------+-------------------------------------+
- | ``PW_CHECK(a_int < b_int)`` | ``PW_CHECK_INT_LT(a_int, b_int)`` |
- +---------------------------------+-------------------------------------+
- | ``PW_CHECK(a_ptr <= b_ptr)`` | ``PW_CHECK_PTR_LE(a_ptr, b_ptr)`` |
- +---------------------------------+-------------------------------------+
- | ``PW_CHECK(Temp() <= 10.0)`` | ``PW_CHECK_FLOAT_LE(Temp(), 10.0)`` |
- +---------------------------------+-------------------------------------+
+ Additionally, use ``PW_CHECK_OK(status)`` when checking for a
+ ``Status::OK``, since it enables showing a human-readable status string
+ rather than an integer (e.g. ``status == RESOURCE_EXHAUSTED`` instead of
+ ``status == 5``.
+
+ +------------------------------------+-------------------------------------+
+ | **Do NOT do this** | **Do this instead** |
+ +------------------------------------+-------------------------------------+
+ | ``PW_CHECK(a_int < b_int)`` | ``PW_CHECK_INT_LT(a_int, b_int)`` |
+ +------------------------------------+-------------------------------------+
+ | ``PW_CHECK(a_ptr <= b_ptr)`` | ``PW_CHECK_PTR_LE(a_ptr, b_ptr)`` |
+ +------------------------------------+-------------------------------------+
+ | ``PW_CHECK(Temp() <= 10.0)`` | ``PW_CHECK_FLOAT_LE(Temp(), 10.0)`` |
+ +------------------------------------+-------------------------------------+
+ | ``PW_CHECK(Foo() == Status::OK)`` | ``PW_CHECK_OK(Foo())`` |
+ +------------------------------------+-------------------------------------+
.. cpp:function:: PW_CHECK_NOTNULL(ptr)
.. cpp:function:: PW_CHECK_NOTNULL(ptr, format, ...)
@@ -207,7 +214,7 @@
Foo* foo = GetTheFoo()
PW_CHECK_NOTNULL(foo);
- Bar* bar = GetSomeBar()
+ Bar* bar = GetSomeBar();
PW_CHECK_NOTNULL(bar, "Weirdly got NULL bar; state: %d", MyState());
.. cpp:function:: PW_CHECK_TYPE_OP(a, b)
@@ -348,6 +355,37 @@
| PW_DCHECK_FLOAT_NE | float | a != b | %f |
+--------------------+--------------+-----------+-----------------------+
+.. cpp:function:: PW_CHECK_OK(status)
+.. cpp:function:: PW_CHECK_OK(status, format, ...)
+.. cpp:function:: PW_DCHECK_OK(status)
+.. cpp:function:: PW_DCHECK_OK(status, format, ...)
+
+ Assert that ``status`` evaluates to ``pw::Status::OK`` (in C++) or
+ ``PW_STATUS_OK`` (in C). Optionally include a message with arguments to
+ report.
+
+ The ``DCHECK`` variants only run if ``NDEBUG`` is defined; otherwise, the
+ entire statement is removed (and the expression not evaluated).
+
+ .. code-block:: cpp
+
+ pw::Status operation_status = DoSomeOperation();
+ PW_CHECK_OK(operation_status);
+
+ // Any expression that evaluates to a pw::Status or pw_Status works.
+ PW_CHECK_OK(DoTheThing(), "System state: %s", SystemState());
+
+ // C works too.
+ pw_Status c_status = DoMoreThings();
+ PW_CHECK_OK(c_status, "System state: %s", SystemState());
+
+ .. note::
+
+ Using ``PW_CHECK_OK(status)`` instead of ``PW_CHECK(status == Status::OK)``
+ enables displaying an error message with a string version of the error
+ code; for example ``status == RESOURCE_EXHAUSTED`` instead of ``status ==
+ 5``.
+
----------------------------
Assert backend API reference
----------------------------
diff --git a/pw_assert/public/pw_assert/internal/assert_impl.h b/pw_assert/public/pw_assert/internal/assert_impl.h
index a03c1d1..7814340 100644
--- a/pw_assert/public/pw_assert/internal/assert_impl.h
+++ b/pw_assert/public/pw_assert/internal/assert_impl.h
@@ -128,6 +128,21 @@
// clang-format on
+// PW_CHECK - If condition evaluates to false, crash. Message optional.
+#define PW_CHECK_OK(status, ...) \
+ do { \
+ if (status != PW_STATUS_OK) { \
+ _PW_CHECK_OK_SELECT_MACRO(#status, \
+ pw_StatusString(status), \
+ PW_HAS_ARGS(__VA_ARGS__), \
+ __VA_ARGS__); \
+ } \
+ } while (0)
+
+#define PW_DCHECK_OK(...) \
+ if (PW_ASSERT_ENABLE_DCHECK) \
+ PW_CHECK_OK(__VA_ARGS__)
+
// =========================================================================
// Implementation for PW_CHECK
@@ -223,8 +238,6 @@
//
// The macro avoids evaluating the arguments multiple times at the cost of some
// macro complexity.
-//
-// TODO: Concat names with __LINE__; requires an extra layer of macros.
#define _PW_CHECK_BINARY_CMP_IMPL( \
argument_a, comparison_op, argument_b, type_decl, type_fmt, ...) \
do { \
@@ -242,14 +255,45 @@
} \
} while (0)
+// =========================================================================
+// Implementation for PW_CHECK_OK
+
+// TODO: Explain why we must expand another time.
+#define _PW_CHECK_OK_SELECT_MACRO( \
+ status_expr_str, status_value_str, has_args, ...) \
+ _PW_CHECK_OK_SELECT_MACRO_EXPANDED( \
+ status_expr_str, status_value_str, has_args, __VA_ARGS__)
+
+// Delegate to the macro
+#define _PW_CHECK_OK_SELECT_MACRO_EXPANDED( \
+ status_expr_str, status_value_str, has_args, ...) \
+ _PW_CHECK_OK_HAS_MSG_##has_args( \
+ status_expr_str, status_value_str, __VA_ARGS__)
+
+// PW_CHECK_OK version 1: No message or args
+#define _PW_CHECK_OK_HAS_MSG_0(status_expr_str, status_value_str, ignored_arg) \
+ PW_HANDLE_ASSERT_BINARY_COMPARE_FAILURE( \
+ status_expr_str, status_value_str, "==", "Status::OK", "OK", "%s", "")
+
+// PW_CHECK_OK version 2: With message (and maybe args)
+#define _PW_CHECK_OK_HAS_MSG_1(status_expr_str, status_value_str, ...) \
+ PW_HANDLE_ASSERT_BINARY_COMPARE_FAILURE(status_expr_str, \
+ status_value_str, \
+ "==", \
+ "Status::OK", \
+ "OK", \
+ "%s", \
+ __VA_ARGS__)
+
// Define short, usable names if requested. Note that the CHECK() macro will
// conflict with Google Log, which expects stream style logs.
-//
-// TODO(pwbug/17): Convert this to the config system when available.
#ifndef PW_ASSERT_USE_SHORT_NAMES
#define PW_ASSERT_USE_SHORT_NAMES 0
#endif
+// =========================================================================
+// Short name definitions (optional)
+
// clang-format off
#if PW_ASSERT_USE_SHORT_NAMES
@@ -281,6 +325,7 @@
#define CHECK_FLOAT_GT PW_CHECK_FLOAT_GT
#define CHECK_FLOAT_EQ PW_CHECK_FLOAT_EQ
#define CHECK_FLOAT_NE PW_CHECK_FLOAT_NE
+#define CHECK_OK PW_CHECK_OK
// Checks that are disabled if NDEBUG is not defined.
#define DCHECK PW_DCHECK
@@ -309,6 +354,7 @@
#define DCHECK_FLOAT_GT PW_DCHECK_FLOAT_GT
#define DCHECK_FLOAT_EQ PW_DCHECK_FLOAT_EQ
#define DCHECK_FLOAT_NE PW_DCHECK_FLOAT_NE
+#define DCHECK_OK PW_DCHECK_OK
#endif // PW_ASSERT_SHORT_NAMES
// clang-format on
diff --git a/pw_assert/pw_assert_test/fake_backend.h b/pw_assert/pw_assert_test/fake_backend.h
index c059056..ee7f3be 100644
--- a/pw_assert/pw_assert_test/fake_backend.h
+++ b/pw_assert/pw_assert_test/fake_backend.h
@@ -67,9 +67,13 @@
// clang-format off
// This is too hairy for clang format to handle and retain readability.
-#define PW_HANDLE_ASSERT_BINARY_COMPARE_FAILURE( \
- arg_a_str, arg_a_val, comparison_op_str, arg_b_str, arg_b_val, \
- type_fmt, message, ...) \
+#define PW_HANDLE_ASSERT_BINARY_COMPARE_FAILURE(arg_a_str, \
+ arg_a_val, \
+ comparison_op_str, \
+ arg_b_str, \
+ arg_b_val, \
+ type_fmt, \
+ message, ...) \
pw_CaptureAssert(__FILE__, \
__LINE__, \
__func__, \
diff --git a/pw_assert_basic/public/pw_assert_basic/assert_basic.h b/pw_assert_basic/public/pw_assert_basic/assert_basic.h
index fa27cbb..f2186c2 100644
--- a/pw_assert_basic/public/pw_assert_basic/assert_basic.h
+++ b/pw_assert_basic/public/pw_assert_basic/assert_basic.h
@@ -58,9 +58,13 @@
// clang-format off
// This is too hairy for clang format to handle and retain readability.
-#define PW_HANDLE_ASSERT_BINARY_COMPARE_FAILURE( \
- arg_a_str, arg_a_val, comparison_op_str, arg_b_str, arg_b_val, \
- type_fmt, message, ...) \
+#define PW_HANDLE_ASSERT_BINARY_COMPARE_FAILURE(arg_a_str, \
+ arg_a_val, \
+ comparison_op_str, \
+ arg_b_str, \
+ arg_b_val, \
+ type_fmt, \
+ message, ...) \
pw_Crash(__FILE__, \
__LINE__, \
__func__, \