pw_assert: Add real assert facade tests

This splits out the tests into backend compile tests and direct facade
tests, by directly depending on the facade implementation (rather than
going through the backend header redirection) and adding a fake
capturing assert backend that captures assert arguments.

Change-Id: Ie6d8c837d5abaa9f735f9e552791e939d6f2a245
diff --git a/pw_assert/BUILD b/pw_assert/BUILD
index 341de73..76b2f64 100644
--- a/pw_assert/BUILD
+++ b/pw_assert/BUILD
@@ -53,11 +53,25 @@
     ],
 )
 
+
 pw_cc_test(
-    name = "test",
+    name = "assert_facade_test",
     srcs = [
-        "assert_test.c",
-        "assert_test.cc",
+        "assert_facade_test.cc",
+        "fake_backend.cc",
+        "public/pw_assert/internal/assert_impl.h",
+        "pw_assert_test/fake_backend.h",
+    ],
+    deps = [
+        "//pw_unit_test",
+    ],
+)
+
+pw_cc_test(
+    name = "assert_backend_compile_test",
+    srcs = [
+        "assert_backend_compile_test.c",
+        "assert_backend_compile_test.cc",
     ],
     deps = [
         ":backend",
diff --git a/pw_assert/BUILD.gn b/pw_assert/BUILD.gn
index 83c3207..48da2f3 100644
--- a/pw_assert/BUILD.gn
+++ b/pw_assert/BUILD.gn
@@ -31,22 +31,34 @@
 }
 
 pw_test_group("tests") {
-  tests = []
+  tests = [ ":assert_facade_test" ]
   if (dir_pw_assert_backend != "") {
-    tests += [ ":assert_test" ]
+    tests += [ ":assert_backend_compile_test" ]
   }
 }
 
+# The assert facade test doesn't require a backend since a fake one is
+# provided.  However, since this doesn't depend on the backend it re-includes
+# the facade headers.
+pw_test("assert_facade_test") {
+  configs = [ ":default_config" ]  # For internal/assert_impl.h
+  sources = [
+    "assert_facade_test.cc",
+    "fake_backend.cc",
+    "public/pw_assert/internal/assert_impl.h",
+    "pw_assert_test/fake_backend.h",
+  ]
+}
+
 if (dir_pw_assert_backend != "") {
-  pw_test("assert_test") {
+  pw_test("assert_backend_compile_test") {
     deps = [
       ":pw_assert",
       dir_pw_assert_backend,
     ]
-
     sources = [
-      "assert_test.c",
-      "assert_test.cc",
+      "assert_backend_compile_test.c",
+      "assert_backend_compile_test.cc",
     ]
   }
 }
diff --git a/pw_assert/assert_test.c b/pw_assert/assert_backend_compile_test.c
similarity index 74%
rename from pw_assert/assert_test.c
rename to pw_assert/assert_backend_compile_test.c
index a309cf0..b1bbe26 100644
--- a/pw_assert/assert_test.c
+++ b/pw_assert/assert_backend_compile_test.c
@@ -12,20 +12,16 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-// This is "test" verifies that the assert backend:
+// This is a compile test that verifies that the assert macros compile in a C
+// context. They are not correctness checks.
 //
-// - Compiles as plain C
-//
-// Unfortunately, this doesn't really test the crashing functionality since
-// that is so backend dependent.
-//
-// NOTE: To run these tests for pw_assert_basic, you must modify two things:
-//
-//   (1) Set DISABLE_ASSERT_TEST_EXECUTION 1 in assert_test.cc (this file)
-//   (1) Set DISABLE_ASSERT_TEST_EXECUTION 1 in assert_test.c
-//   (2) Set PW_ASSERT_BASIC_DISABLE_NORETURN 1 in assert_basic.h
-//
-// This is obviously not a long term solution.
+// Note: These tests cannot be run with a normal assert backend, since they
+// will abort. However, the assert_basic backend supports non-aborting assert;
+// see the note in assert_backend_compile_test.cc.
+
+// The compile tests verifies that the short macros compile, so enable them.
+#undef PW_ASSERT_USE_SHORT_NAMES
+#define PW_ASSERT_USE_SHORT_NAMES 1
 
 #include "pw_assert/assert.h"
 
@@ -56,7 +52,7 @@
 
 static int Add3(int a, int b, int c) { return a + b + c; }
 
-void AssertTestsInC() {
+void AssertBackendCompileTestsInC() {
   {  // TEST(Crash, WithAndWithoutMessageArguments)
     MAYBE_SKIP_TEST;
     PW_CRASH(FAIL_IF_HIDDEN);
@@ -134,4 +130,32 @@
     PW_CHECK_INT_LE(Add3(1, 2, 3), Add3(1, 2, 3), "INT: " FAIL_IF_DISPLAYED);
     PW_CHECK_INT_LE(x_int, y_int, "INT: " FAIL_IF_DISPLAYED_ARGS, z);
   }
+
+  // Note: This requires enabling PW_ASSERT_USE_SHORT_NAMES 1 above.
+  {  // TEST(Check, ShortNamesWork) {
+    MAYBE_SKIP_TEST;
+
+    // Crash
+    CRASH(FAIL_IF_HIDDEN);
+    CRASH(FAIL_IF_HIDDEN_ARGS, z);
+
+    // Check
+    CHECK(1, FAIL_IF_DISPLAYED);
+    CHECK(1, FAIL_IF_DISPLAYED_ARGS, z);
+    CHECK(0, FAIL_IF_HIDDEN);
+    CHECK(0, FAIL_IF_HIDDEN_ARGS, z);
+
+    // Check with binary comparison
+    int x_int = 50;
+    int y_int = 66;
+
+    CHECK_INT_LE(Add3(1, 2, 3), y_int);
+    CHECK_INT_LE(x_int, Add3(1, 2, 3));
+
+    CHECK_INT_LE(Add3(1, 2, 3), y_int, FAIL_IF_DISPLAYED);
+    CHECK_INT_LE(x_int, Add3(1, 2, 3), FAIL_IF_DISPLAYED_ARGS, z);
+
+    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);
+  }
 }
diff --git a/pw_assert/assert_test.cc b/pw_assert/assert_backend_compile_test.cc
similarity index 76%
rename from pw_assert/assert_test.cc
rename to pw_assert/assert_backend_compile_test.cc
index 5532f2e..49666eb 100644
--- a/pw_assert/assert_test.cc
+++ b/pw_assert/assert_backend_compile_test.cc
@@ -12,11 +12,28 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-// This is mostly a compile test to verify that the log backend is able to
-// compile the constructs promised by the logging facade; and that when run,
-// there is no crash.
+// This series of "tests" is more a compile test to verify that the assert
+// backend is able to compile the constructs promised by the assert facade.
+// Truly testing the backend in a general way from the facade is impossible
+// since the device will go down when an assert triggers, and so that must be
+// handled inside the individual backends.
 //
-// TODO(pwbug/88): Add verification of the actually logged statements.
+// NOTE: While these tests are not intended to run, it *is* possible to run
+// them with the assert_basic backend, in a special mode where the assert
+// statements fall through instead of aborting.
+//
+// To run these "tests" for pw_assert_basic, you must modify two things:
+//
+//   (1) Set DISABLE_ASSERT_TEST_EXECUTION 0 in assert_backend_compile_test.cc
+//   (2) Set DISABLE_ASSERT_TEST_EXECUTION 0 in assert_backend_compile_test.c
+//   (3) Set PW_ASSERT_BASIC_DISABLE_NORETURN 1 in assert_basic.h
+//   (4) Compile and run the resulting binary, paying attention to the
+//       displayed error messages. If "FAIL IF DISPLAYED" is printed, the
+//       test has failed. If any "FAIL_IF_HIDDEN" asserts are not displayed,
+//       the test has failed. Obviously manually verifying these is a pain
+//       and so this is not a suitable test for production.
+//
+// TODO(pwbug/88): Add verification of the actually recorded asserts statements.
 
 // clang-format off
 #define PW_ASSERT_USE_SHORT_NAMES 1
@@ -128,45 +145,6 @@
   PW_CHECK_INT_LE(x_int, y_int, "INT: " FAIL_IF_DISPLAYED_ARGS, z);
 }
 
-// These are defined in assert_test.c, to test C compatibility.
-extern "C" {
-void AssertTestsInC();
-}  // extern "C"
-
-TEST(Check, AssertTestsInC) {
-  MAYBE_SKIP_TEST;
-  AssertTestsInC();
-}
-
-static int global_state_for_multi_evaluate_test;
-static int IncrementsGlobal() {
-  global_state_for_multi_evaluate_test++;
-  return 0;
-}
-
-// This test verifies that the binary CHECK_*(x,y) macros only
-// evaluate their arguments once.
-TEST(Check, BinaryOpOnlyEvaluatesOnce) {
-  MAYBE_SKIP_TEST;
-
-  global_state_for_multi_evaluate_test = 0;
-  PW_CHECK_INT_EQ(0, IncrementsGlobal());
-  EXPECT_EQ(global_state_for_multi_evaluate_test, 1);
-
-  global_state_for_multi_evaluate_test = 0;
-  PW_CHECK_INT_EQ(IncrementsGlobal(), IncrementsGlobal());
-  EXPECT_EQ(global_state_for_multi_evaluate_test, 2);
-
-  // Fails; should only evaluate IncrementGlobal() once.
-  global_state_for_multi_evaluate_test = 0;
-  PW_CHECK_INT_EQ(1, IncrementsGlobal());
-  EXPECT_EQ(global_state_for_multi_evaluate_test, 1);
-
-  global_state_for_multi_evaluate_test = 0;
-  PW_CHECK_INT_EQ(IncrementsGlobal(), 1 + IncrementsGlobal());
-  EXPECT_EQ(global_state_for_multi_evaluate_test, 2);
-}
-
 // Note: This requires enabling PW_ASSERT_USE_SHORT_NAMES 1 above.
 TEST(Check, ShortNamesWork) {
   MAYBE_SKIP_TEST;
@@ -194,4 +172,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);
 }
+
+// These are defined in assert_test.c, to test C compatibility.
+extern "C" void AssertBackendCompileTestsInC();
+
+TEST(Check, AssertBackendCompileTestsInC) {
+  MAYBE_SKIP_TEST;
+  AssertBackendCompileTestsInC();
+}
+
 }  // namespace
diff --git a/pw_assert/assert_facade_test.cc b/pw_assert/assert_facade_test.cc
new file mode 100644
index 0000000..5f643d0
--- /dev/null
+++ b/pw_assert/assert_facade_test.cc
@@ -0,0 +1,298 @@
+// Copyright 2020 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.
+
+// This test directly verifies the facade logic, by leveraging a fake backend
+// that captures arguments and returns rather than aborting execution.
+
+#include "pw_assert_test/fake_backend.h"
+
+// This directly includes the assert facade implementation header rather than
+// going through the backend header indirection mechanism, to prevent the real
+// assert backend from triggering.
+//
+// clang-format off
+#define PW_ASSERT_USE_SHORT_NAMES 1
+#include "pw_assert/internal/assert_impl.h"
+// clang-format on
+
+#include "gtest/gtest.h"
+
+namespace {
+
+#define EXPECT_MESSAGE(expected_assert_message)                        \
+  do {                                                                 \
+    EXPECT_STREQ(pw_captured_assert.message, expected_assert_message); \
+  } while (0)
+
+class AssertFail : public ::testing::Test {
+ protected:
+  void SetUp() override { pw_captured_assert.triggered = 0; }
+  void TearDown() override { EXPECT_EQ(pw_captured_assert.triggered, 1); }
+};
+
+class AssertPass : public ::testing::Test {
+ protected:
+  void SetUp() override { pw_captured_assert.triggered = 0; }
+  void TearDown() override { EXPECT_EQ(pw_captured_assert.triggered, 0); }
+};
+
+// PW_CRASH(...)
+TEST_F(AssertFail, CrashMessageNoArguments) {
+  PW_CRASH("Goodbye");
+  EXPECT_MESSAGE("Goodbye");
+}
+TEST_F(AssertFail, CrashMessageWithArguments) {
+  PW_CRASH("Goodbye cruel %s", "world");
+  EXPECT_MESSAGE("Goodbye cruel world");
+}
+
+// PW_CHECK(...) - No message
+TEST_F(AssertPass, CheckNoMessage) { PW_CHECK(true); }
+TEST_F(AssertFail, CheckNoMessage) {
+  PW_CHECK(false);
+  EXPECT_MESSAGE("Check failed: false. ");
+}
+TEST_F(AssertPass, CheckNoMessageComplexExpression) { PW_CHECK(2 == 2); }
+TEST_F(AssertFail, CheckNoMessageComplexExpression) {
+  PW_CHECK(1 == 2);
+  EXPECT_MESSAGE("Check failed: 1 == 2. ");
+}
+
+// PW_CHECK(..., msg) - With message; with and without arguments.
+TEST_F(AssertPass, CheckMessageNoArguments) { PW_CHECK(true, "Hello"); }
+TEST_F(AssertFail, CheckMessageNoArguments) {
+  PW_CHECK(false, "Hello");
+  EXPECT_MESSAGE("Check failed: false. Hello");
+}
+TEST_F(AssertPass, CheckMessageWithArguments) { PW_CHECK(true, "Hello %d", 5); }
+TEST_F(AssertFail, CheckMessageWithArguments) {
+  PW_CHECK(false, "Hello %d", 5);
+  EXPECT_MESSAGE("Check failed: false. Hello 5");
+}
+
+// PW_CHECK_INT_*(...)
+// Binary checks with ints, comparisons: <, <=, =, >, >=.
+
+// Test message formatting separate from the triggering.
+// Only test formatting for the type once.
+TEST_F(AssertFail, IntLessThanNoMessageNoArguments) {
+  PW_CHECK_INT_LT(5, -2);
+  EXPECT_MESSAGE("Check failed: 5 (=5) < -2 (=-2). ");
+}
+TEST_F(AssertFail, IntLessThanMessageNoArguments) {
+  PW_CHECK_INT_LT(5, -2, "msg");
+  EXPECT_MESSAGE("Check failed: 5 (=5) < -2 (=-2). msg");
+}
+TEST_F(AssertFail, IntLessThanMessageArguments) {
+  PW_CHECK_INT_LT(5, -2, "msg: %d", 6);
+  EXPECT_MESSAGE("Check failed: 5 (=5) < -2 (=-2). msg: 6");
+}
+
+// Test comparison boundaries.
+
+// INT <
+TEST_F(AssertPass, IntLt1) { PW_CHECK_INT_LT(-1, 2); }
+TEST_F(AssertPass, IntLt2) { PW_CHECK_INT_LT(1, 2); }
+TEST_F(AssertFail, IntLt3) { PW_CHECK_INT_LT(-1, -2); }
+TEST_F(AssertFail, IntLt4) { PW_CHECK_INT_LT(1, 1); }
+
+// INT <=
+TEST_F(AssertPass, IntLe1) { PW_CHECK_INT_LE(-1, 2); }
+TEST_F(AssertPass, IntLe2) { PW_CHECK_INT_LE(1, 2); }
+TEST_F(AssertFail, IntLe3) { PW_CHECK_INT_LE(-1, -2); }
+TEST_F(AssertPass, IntLe4) { PW_CHECK_INT_LE(1, 1); }
+
+// INT ==
+TEST_F(AssertFail, IntEq1) { PW_CHECK_INT_EQ(-1, 2); }
+TEST_F(AssertFail, IntEq2) { PW_CHECK_INT_EQ(1, 2); }
+TEST_F(AssertFail, IntEq3) { PW_CHECK_INT_EQ(-1, -2); }
+TEST_F(AssertPass, IntEq4) { PW_CHECK_INT_EQ(1, 1); }
+
+// INT >
+TEST_F(AssertFail, IntGt1) { PW_CHECK_INT_GT(-1, 2); }
+TEST_F(AssertFail, IntGt2) { PW_CHECK_INT_GT(1, 2); }
+TEST_F(AssertPass, IntGt3) { PW_CHECK_INT_GT(-1, -2); }
+TEST_F(AssertFail, IntGt4) { PW_CHECK_INT_GT(1, 1); }
+
+// INT >=
+TEST_F(AssertFail, IntGe1) { PW_CHECK_INT_GE(-1, 2); }
+TEST_F(AssertFail, IntGe2) { PW_CHECK_INT_GE(1, 2); }
+TEST_F(AssertPass, IntGe3) { PW_CHECK_INT_GE(-1, -2); }
+TEST_F(AssertPass, IntGe4) { PW_CHECK_INT_GE(1, 1); }
+
+// PW_CHECK_UINT_*(...)
+// Binary checks with uints, comparisons: <, <=, =, >, >=.
+
+// Test message formatting separate from the triggering.
+// Only test formatting for the type once.
+TEST_F(AssertFail, UintLessThanNoMessageNoArguments) {
+  PW_CHECK_UINT_LT(5, 2);
+  EXPECT_MESSAGE("Check failed: 5 (=5) < 2 (=2). ");
+}
+TEST_F(AssertFail, UintLessThanMessageNoArguments) {
+  PW_CHECK_UINT_LT(5, 2, "msg");
+  EXPECT_MESSAGE("Check failed: 5 (=5) < 2 (=2). msg");
+}
+TEST_F(AssertFail, UintLessThanMessageArguments) {
+  PW_CHECK_UINT_LT(5, 2, "msg: %d", 6);
+  EXPECT_MESSAGE("Check failed: 5 (=5) < 2 (=2). msg: 6");
+}
+
+// Test comparison boundaries.
+
+// UINT <
+TEST_F(AssertPass, UintLt1) { PW_CHECK_UINT_LT(1, 2); }
+TEST_F(AssertFail, UintLt2) { PW_CHECK_UINT_LT(2, 2); }
+TEST_F(AssertFail, UintLt3) { PW_CHECK_UINT_LT(2, 1); }
+
+// UINT <=
+TEST_F(AssertPass, UintLe1) { PW_CHECK_UINT_LE(1, 2); }
+TEST_F(AssertPass, UintLe2) { PW_CHECK_UINT_LE(2, 2); }
+TEST_F(AssertFail, UintLe3) { PW_CHECK_UINT_LE(2, 1); }
+
+// UINT ==
+TEST_F(AssertFail, UintEq1) { PW_CHECK_UINT_EQ(1, 2); }
+TEST_F(AssertPass, UintEq2) { PW_CHECK_UINT_EQ(2, 2); }
+TEST_F(AssertFail, UintEq3) { PW_CHECK_UINT_EQ(2, 1); }
+
+// UINT >
+TEST_F(AssertFail, UintGt1) { PW_CHECK_UINT_GT(1, 2); }
+TEST_F(AssertFail, UintGt2) { PW_CHECK_UINT_GT(2, 2); }
+TEST_F(AssertPass, UintGt3) { PW_CHECK_UINT_GT(2, 1); }
+
+// UINT >=
+TEST_F(AssertFail, UintGe1) { PW_CHECK_UINT_GE(1, 2); }
+TEST_F(AssertPass, UintGe2) { PW_CHECK_UINT_GE(2, 2); }
+TEST_F(AssertPass, UintGe3) { PW_CHECK_UINT_GE(2, 1); }
+
+// PW_CHECK_FLOAT_*(...)
+// Binary checks with floats, comparisons: <, <=, =, >, >=.
+
+// Test message formatting separate from the triggering.
+// Only test formatting for the type once.
+TEST_F(AssertFail, FloatLessThanNoMessageNoArguments) {
+  PW_CHECK_FLOAT_LT(5.2, 2.3);
+  EXPECT_MESSAGE("Check failed: 5.2 (=5.200000) < 2.3 (=2.300000). ");
+}
+TEST_F(AssertFail, FloatLessThanMessageNoArguments) {
+  PW_CHECK_FLOAT_LT(5.2, 2.3, "msg");
+  EXPECT_MESSAGE("Check failed: 5.2 (=5.200000) < 2.3 (=2.300000). msg");
+}
+TEST_F(AssertFail, FloatLessThanMessageArguments) {
+  PW_CHECK_FLOAT_LT(5.2, 2.3, "msg: %d", 6);
+  EXPECT_MESSAGE("Check failed: 5.2 (=5.200000) < 2.3 (=2.300000). msg: 6");
+}
+
+// Test comparison boundaries.
+// Note: The below example numbers all round to integer 1, to detect accidental
+// integer conversions in the asserts.
+
+// FLOAT <
+TEST_F(AssertPass, FloatLt1) { PW_CHECK_FLOAT_LT(1.1, 1.2); }
+TEST_F(AssertFail, FloatLt2) { PW_CHECK_FLOAT_LT(1.2, 1.2); }
+TEST_F(AssertFail, FloatLt3) { PW_CHECK_FLOAT_LT(1.2, 1.1); }
+
+// FLOAT <=
+TEST_F(AssertPass, FloatLe1) { PW_CHECK_FLOAT_LE(1.1, 1.2); }
+TEST_F(AssertPass, FloatLe2) { PW_CHECK_FLOAT_LE(1.2, 1.2); }
+TEST_F(AssertFail, FloatLe3) { PW_CHECK_FLOAT_LE(1.2, 1.1); }
+
+// FLOAT ==
+TEST_F(AssertFail, FloatEq1) { PW_CHECK_FLOAT_EQ(1.1, 1.2); }
+TEST_F(AssertPass, FloatEq2) { PW_CHECK_FLOAT_EQ(1.2, 1.2); }
+TEST_F(AssertFail, FloatEq3) { PW_CHECK_FLOAT_EQ(1.2, 1.1); }
+
+// FLOAT >
+TEST_F(AssertFail, FloatGt1) { PW_CHECK_FLOAT_GT(1.1, 1.2); }
+TEST_F(AssertFail, FloatGt2) { PW_CHECK_FLOAT_GT(1.2, 1.2); }
+TEST_F(AssertPass, FloatGt3) { PW_CHECK_FLOAT_GT(1.2, 1.1); }
+
+// FLOAT >=
+TEST_F(AssertFail, FloatGe1) { PW_CHECK_FLOAT_GE(1.1, 1.2); }
+TEST_F(AssertPass, FloatGe2) { PW_CHECK_FLOAT_GE(1.2, 1.2); }
+TEST_F(AssertPass, FloatGe3) { PW_CHECK_FLOAT_GE(1.2, 1.1); }
+
+// Nested comma handling.
+static int Add3(int a, int b, int c) { return a + b + c; }
+
+TEST_F(AssertFail, CommaHandlingLeftSide) {
+  PW_CHECK_INT_EQ(Add3(1, 2, 3), 4);
+  EXPECT_MESSAGE("Check failed: Add3(1, 2, 3) (=6) == 4 (=4). ");
+}
+TEST_F(AssertFail, CommaHandlingRightSide) {
+  PW_CHECK_INT_EQ(4, Add3(1, 2, 3));
+  EXPECT_MESSAGE("Check failed: 4 (=4) == Add3(1, 2, 3) (=6). ");
+}
+
+// Verify that the CHECK_*(x,y) macros only evaluate their arguments once.
+static int global_state_for_multi_evaluate_test;
+static int IncrementsGlobal() {
+  global_state_for_multi_evaluate_test++;
+  return 0;
+}
+
+TEST(AssertPass, CheckSingleSideEffectingCall) {
+  global_state_for_multi_evaluate_test = 0;
+  PW_CHECK(IncrementsGlobal() == 0);
+  EXPECT_EQ(global_state_for_multi_evaluate_test, 1);
+}
+TEST(AssertFail, CheckSingleSideEffectingCall) {
+  global_state_for_multi_evaluate_test = 0;
+  PW_CHECK(IncrementsGlobal() == 1);
+  EXPECT_EQ(global_state_for_multi_evaluate_test, 1);
+}
+TEST(AssertPass, BinaryOpSingleSideEffectingCall) {
+  global_state_for_multi_evaluate_test = 0;
+  PW_CHECK_INT_EQ(0, IncrementsGlobal());
+  EXPECT_EQ(global_state_for_multi_evaluate_test, 1);
+}
+TEST(AssertPass, BinaryOpTwoSideEffectingCalls) {
+  global_state_for_multi_evaluate_test = 0;
+  PW_CHECK_INT_EQ(IncrementsGlobal(), IncrementsGlobal());
+  EXPECT_EQ(global_state_for_multi_evaluate_test, 2);
+}
+TEST(AssertFail, BinaryOpSingleSideEffectingCall) {
+  global_state_for_multi_evaluate_test = 0;
+  PW_CHECK_INT_EQ(12314, IncrementsGlobal());
+  EXPECT_EQ(global_state_for_multi_evaluate_test, 1);
+}
+TEST(AssertFail, BinaryOpTwoSideEffectingCalls) {
+  global_state_for_multi_evaluate_test = 0;
+  PW_CHECK_INT_EQ(IncrementsGlobal() + 10, IncrementsGlobal());
+  EXPECT_EQ(global_state_for_multi_evaluate_test, 2);
+}
+
+// Note: This requires enabling PW_ASSERT_USE_SHORT_NAMES 1 above.
+TEST(Check, ShortNamesWork) {
+  // Crash
+  CRASH("msg");
+  CRASH("msg: %d", 5);
+
+  // Check
+  CHECK(true);
+  CHECK(true, "msg");
+  CHECK(true, "msg: %d", 5);
+  CHECK(false);
+  CHECK(false, "msg");
+  CHECK(false, "msg: %d", 5);
+
+  // Check with binary comparison
+  CHECK_INT_LE(1, 2);
+  CHECK_INT_LE(1, 2, "msg");
+  CHECK_INT_LE(1, 2, "msg: %d", 5);
+}
+
+// TODO: Figure out how to run some of these tests is C.
+
+}  // namespace
diff --git a/pw_assert/fake_backend.cc b/pw_assert/fake_backend.cc
new file mode 100644
index 0000000..b81c106
--- /dev/null
+++ b/pw_assert/fake_backend.cc
@@ -0,0 +1,67 @@
+// Copyright 2020 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_assert_test/fake_backend.h"
+
+#include <cstring>
+
+#include "pw_span/span.h"
+#include "pw_string/string_builder.h"
+
+// Global that's publicly accessible to read captured assert contents.
+struct pw_CapturedAssert pw_captured_assert;
+
+bool IsDirSeparator(char c) { return c == '/' || c == '\\'; }
+
+const char* GetFileBasename(const char* filename) {
+  int length = std::strlen(filename);
+  if (length == 0) {
+    return filename;
+  }
+
+  // Start on the last character, find the parent directory.
+  const char* basename = filename + std::strlen(filename) - 1;
+  while (basename != filename && !IsDirSeparator(*basename)) {
+    basename--;
+  }
+  if (IsDirSeparator(*basename)) {
+    basename++;
+  }
+  return basename;
+}
+
+void pw_CaptureAssert(const char* file_name,
+                      int line_number,
+                      const char* function_name,
+                      const char* message,
+                      ...) {
+  // Triggered
+  pw_captured_assert.triggered = 1;
+
+  // Filename
+  pw_captured_assert.file_name = file_name;
+
+  // Line number
+  pw_captured_assert.line_number = line_number;
+
+  // Function name
+  pw_captured_assert.function_name = function_name;
+
+  // Message
+  pw::StringBuilder builder(pw_captured_assert.message);
+  va_list args;
+  va_start(args, message);
+  builder.FormatVaList(message, args);
+  va_end(args);
+}
diff --git a/pw_assert/pw_assert_test/fake_backend.h b/pw_assert/pw_assert_test/fake_backend.h
new file mode 100644
index 0000000..c059056
--- /dev/null
+++ b/pw_assert/pw_assert_test/fake_backend.h
@@ -0,0 +1,82 @@
+// Copyright 2020 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 <stdbool.h>
+
+#include "pw_preprocessor/compiler.h"
+#include "pw_preprocessor/util.h"
+
+PW_EXTERN_C_START
+
+struct pw_CapturedAssert {
+  // The capturing assert handler will set triggered = 1 upon entry, enabling
+  // detecting if an assert triggered by setting it to 0 before; for example
+  //
+  //   captured_assert_arguments.triggered = 0;
+  //   PW_CHECK_NE(1, 2, "Ruh roh!");
+  //   EXPECT_TRUE(captured_assert_arguments.triggered);
+  //
+  int triggered;
+  const char* file_name;
+  int line_number;
+  const char* function_name;
+  char message[150];
+};
+
+// The assert handler pushes arguments into this global, then returns.
+extern struct pw_CapturedAssert pw_captured_assert;
+
+// Crash, including a message with the listed attributes.
+void pw_CaptureAssert(const char* file_name,
+                      int line_number,
+                      const char* function_name,
+                      const char* message,
+                      ...) PW_PRINTF_FORMAT(4, 5);
+
+PW_EXTERN_C_END
+
+// Capture the crash attributes for testing.
+#define PW_HANDLE_CRASH(...) \
+  pw_CaptureAssert(__FILE__, __LINE__, __func__ PW_COMMA_ARGS(__VA_ARGS__))
+
+// Capture the crash attributes for testing, including the condition string.
+#define PW_HANDLE_ASSERT_FAILURE(condition_string, message, ...) \
+  pw_CaptureAssert(__FILE__,                                     \
+                   __LINE__,                                     \
+                   __func__,                                     \
+                   "Check failed: " condition_string             \
+                   ". " message PW_COMMA_ARGS(__VA_ARGS__))
+
+// Sample assert failure message produced by the below implementation:
+//
+//   Check failed: current_sensor (=610) < new_sensor (=50): More details!
+//
+// Putting the value next to the operand makes the string easier to read.
+
+// 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, ...)                                        \
+  pw_CaptureAssert(__FILE__,                                       \
+                   __LINE__,                                       \
+                   __func__,                                       \
+                   "Check failed: "                                \
+                       arg_a_str " (=" type_fmt ") "               \
+                       comparison_op_str " "                       \
+                       arg_b_str " (=" type_fmt ")"                \
+                       ". " message,                               \
+                   arg_a_val, arg_b_val PW_COMMA_ARGS(__VA_ARGS__))
+// clang-format on