Cleanup shm at startup in message_bridge_test

When we start forwarding messages from before connection, we need shm to
be clean.  Take this oppertunity to dedup some things.

Change-Id: Ic4caff28ba63e48ce5e74a28868deb5c808a984d
diff --git a/WORKSPACE b/WORKSPACE
index bcdbf4f..6c9949d 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -169,6 +169,7 @@
 http_archive(
     name = "arm_frc_linux_gnueabi_repo",
     build_file = "@//tools/cpp/arm-frc-linux-gnueabi:arm-frc-linux-gnueabi.BUILD",
+    patches = ["//debian:fts.patch"],
     sha256 = "043a5b047c2af9cf80d146d8327b588264c98a01e0f3f41e3564dd2bbbc95c0e",
     strip_prefix = "frc2020/roborio/",
     url = "https://www.frc971.org/Build-Dependencies/FRC-2020-Linux-Toolchain-7.3.0.tar.gz",
diff --git a/aos/network/BUILD b/aos/network/BUILD
index 98a271c..b142967 100644
--- a/aos/network/BUILD
+++ b/aos/network/BUILD
@@ -299,7 +299,7 @@
         ":message_bridge_test_client_config",
         ":message_bridge_test_server_config",
     ],
-    shard_count = 3,
+    shard_count = 4,
     deps = [
         ":message_bridge_client_lib",
         ":message_bridge_server_lib",
diff --git a/aos/network/message_bridge_test.cc b/aos/network/message_bridge_test.cc
index aa0a034..a3e7f63 100644
--- a/aos/network/message_bridge_test.cc
+++ b/aos/network/message_bridge_test.cc
@@ -9,6 +9,7 @@
 #include "aos/network/message_bridge_client_lib.h"
 #include "aos/network/message_bridge_server_lib.h"
 #include "aos/network/team_number.h"
+#include "aos/util/file.h"
 
 namespace aos {
 void SetShmBase(const std::string_view base);
@@ -18,17 +19,36 @@
 
 namespace chrono = std::chrono;
 
-void DoSetShmBase(const std::string_view node) {
+std::string ShmBase(const std::string_view node) {
   const char *tmpdir_c_str = getenv("TEST_TMPDIR");
   if (tmpdir_c_str != nullptr) {
-    aos::SetShmBase(absl::StrCat(tmpdir_c_str, "/", node));
+    return absl::StrCat(tmpdir_c_str, "/", node);
   } else {
-    aos::SetShmBase(absl::StrCat("/dev/shm/", node));
+    return absl::StrCat("/dev/shm/", node);
   }
 }
 
+void DoSetShmBase(const std::string_view node) {
+  aos::SetShmBase(ShmBase(node));
+}
+
+class MessageBridgeTest : public ::testing::Test {
+  public:
+   MessageBridgeTest()
+       : pi1_config(aos::configuration::ReadConfig(
+             "aos/network/message_bridge_test_server_config.json")),
+         pi2_config(aos::configuration::ReadConfig(
+             "aos/network/message_bridge_test_client_config.json")) {
+     util::UnlinkRecursive(ShmBase("pi1"));
+     util::UnlinkRecursive(ShmBase("pi2"));
+   }
+
+   aos::FlatbufferDetachedBuffer<aos::Configuration> pi1_config;
+   aos::FlatbufferDetachedBuffer<aos::Configuration> pi2_config;
+};
+
 // Test that we can send a ping message over sctp and receive it.
-TEST(MessageBridgeTest, PingPong) {
+TEST_F(MessageBridgeTest, PingPong) {
   // This is rather annoying to set up.  We need to start up a client and
   // server, on the same node, but get them to think that they are on different
   // nodes.
@@ -49,32 +69,25 @@
   // hope for the best.  We can be more generous in the future if we need to.
   //
   // We are faking the application names by passing in --application_name=foo
-  aos::FlatbufferDetachedBuffer<aos::Configuration> server_config =
-      aos::configuration::ReadConfig(
-          "aos/network/message_bridge_test_server_config.json");
-  aos::FlatbufferDetachedBuffer<aos::Configuration> pi2_config =
-      aos::configuration::ReadConfig(
-          "aos/network/message_bridge_test_client_config.json");
-
   DoSetShmBase("pi1");
   FLAGS_application_name = "pi1_message_bridge_server";
   // Force ourselves to be "raspberrypi" and allocate everything.
   FLAGS_override_hostname = "raspberrypi";
 
-  aos::ShmEventLoop pi1_server_event_loop(&server_config.message());
+  aos::ShmEventLoop pi1_server_event_loop(&pi1_config.message());
   MessageBridgeServer pi1_message_bridge_server(&pi1_server_event_loop);
 
   FLAGS_application_name = "pi1_message_bridge_client";
-  aos::ShmEventLoop pi1_client_event_loop(&server_config.message());
+  aos::ShmEventLoop pi1_client_event_loop(&pi1_config.message());
   MessageBridgeClient pi1_message_bridge_client(&pi1_client_event_loop);
 
   // And build the app which sends the pings.
   FLAGS_application_name = "ping";
-  aos::ShmEventLoop ping_event_loop(&server_config.message());
+  aos::ShmEventLoop ping_event_loop(&pi1_config.message());
   aos::Sender<examples::Ping> ping_sender =
       ping_event_loop.MakeSender<examples::Ping>("/test");
 
-  aos::ShmEventLoop pi1_test_event_loop(&server_config.message());
+  aos::ShmEventLoop pi1_test_event_loop(&pi1_config.message());
   aos::Fetcher<logger::MessageHeader> message_header_fetcher1 =
       pi1_test_event_loop.MakeFetcher<logger::MessageHeader>(
           "/pi1/aos/remote_timestamps/pi2");
@@ -399,7 +412,7 @@
 
 // Test that the client disconnecting triggers the server offsets on both sides
 // to clear.
-TEST(MessageBridgeTest, ClientRestart) {
+TEST_F(MessageBridgeTest, ClientRestart) {
   // This is rather annoying to set up.  We need to start up a client and
   // server, on the same node, but get them to think that they are on different
   // nodes.
@@ -414,27 +427,20 @@
   // hope for the best.  We can be more generous in the future if we need to.
   //
   // We are faking the application names by passing in --application_name=foo
-  aos::FlatbufferDetachedBuffer<aos::Configuration> server_config =
-      aos::configuration::ReadConfig(
-          "aos/network/message_bridge_test_server_config.json");
-  aos::FlatbufferDetachedBuffer<aos::Configuration> pi2_config =
-      aos::configuration::ReadConfig(
-          "aos/network/message_bridge_test_client_config.json");
-
   FLAGS_application_name = "pi1_message_bridge_server";
   // Force ourselves to be "raspberrypi" and allocate everything.
   FLAGS_override_hostname = "raspberrypi";
   DoSetShmBase("pi1");
-  aos::ShmEventLoop pi1_server_event_loop(&server_config.message());
+  aos::ShmEventLoop pi1_server_event_loop(&pi1_config.message());
   MessageBridgeServer pi1_message_bridge_server(&pi1_server_event_loop);
 
   FLAGS_application_name = "pi1_message_bridge_client";
-  aos::ShmEventLoop pi1_client_event_loop(&server_config.message());
+  aos::ShmEventLoop pi1_client_event_loop(&pi1_config.message());
   MessageBridgeClient pi1_message_bridge_client(&pi1_client_event_loop);
 
   // And build the app for testing.
   FLAGS_application_name = "test1";
-  aos::ShmEventLoop pi1_test_event_loop(&server_config.message());
+  aos::ShmEventLoop pi1_test_event_loop(&pi1_config.message());
   aos::Fetcher<ServerStatistics> pi1_server_statistics_fetcher =
       pi1_test_event_loop.MakeFetcher<ServerStatistics>("/pi1/aos");
 
@@ -607,7 +613,7 @@
 
 // Test that the server disconnecting triggers the server offsets on the other
 // side to clear, along with the other client.
-TEST(MessageBridgeTest, ServerRestart) {
+TEST_F(MessageBridgeTest, ServerRestart) {
   // This is rather annoying to set up.  We need to start up a client and
   // server, on the same node, but get them to think that they are on different
   // nodes.
@@ -622,27 +628,20 @@
   // hope for the best.  We can be more generous in the future if we need to.
   //
   // We are faking the application names by passing in --application_name=foo
-  aos::FlatbufferDetachedBuffer<aos::Configuration> server_config =
-      aos::configuration::ReadConfig(
-          "aos/network/message_bridge_test_server_config.json");
-  aos::FlatbufferDetachedBuffer<aos::Configuration> pi2_config =
-      aos::configuration::ReadConfig(
-          "aos/network/message_bridge_test_client_config.json");
-
   FLAGS_application_name = "pi1_message_bridge_server";
   // Force ourselves to be "raspberrypi" and allocate everything.
   FLAGS_override_hostname = "raspberrypi";
   DoSetShmBase("pi1");
-  aos::ShmEventLoop pi1_server_event_loop(&server_config.message());
+  aos::ShmEventLoop pi1_server_event_loop(&pi1_config.message());
   MessageBridgeServer pi1_message_bridge_server(&pi1_server_event_loop);
 
   FLAGS_application_name = "pi1_message_bridge_client";
-  aos::ShmEventLoop pi1_client_event_loop(&server_config.message());
+  aos::ShmEventLoop pi1_client_event_loop(&pi1_config.message());
   MessageBridgeClient pi1_message_bridge_client(&pi1_client_event_loop);
 
   // And build the app for testing.
   FLAGS_application_name = "test1";
-  aos::ShmEventLoop pi1_test_event_loop(&server_config.message());
+  aos::ShmEventLoop pi1_test_event_loop(&pi1_config.message());
   aos::Fetcher<ServerStatistics> pi1_server_statistics_fetcher =
       pi1_test_event_loop.MakeFetcher<ServerStatistics>("/pi1/aos");
   aos::Fetcher<ClientStatistics> pi1_client_statistics_fetcher =
diff --git a/aos/util/file.cc b/aos/util/file.cc
index afefa86..97500d1 100644
--- a/aos/util/file.cc
+++ b/aos/util/file.cc
@@ -1,6 +1,7 @@
 #include "aos/util/file.h"
 
 #include <fcntl.h>
+#include <fts.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
@@ -78,5 +79,64 @@
   return stat(path.data(), &buffer) == 0;
 }
 
+void UnlinkRecursive(std::string_view path) {
+  FTS *ftsp = NULL;
+  FTSENT *curr;
+
+  // Cast needed (in C) because fts_open() takes a "char * const *", instead
+  // of a "const char * const *", which is only allowed in C++. fts_open()
+  // does not modify the argument.
+  std::string p(path);
+  char *files[] = {const_cast<char *>(p.c_str()), NULL};
+
+  // FTS_NOCHDIR  - Avoid changing cwd, which could cause unexpected behavior
+  //                in multithreaded programs
+  // FTS_PHYSICAL - Don't follow symlinks. Prevents deletion of files outside
+  //                of the specified directory
+  // FTS_XDEV     - Don't cross filesystem boundaries
+  ftsp = fts_open(files, FTS_NOCHDIR | FTS_PHYSICAL | FTS_XDEV, NULL);
+  if (!ftsp) {
+    return;
+  }
+
+  while ((curr = fts_read(ftsp))) {
+    switch (curr->fts_info) {
+      case FTS_NS:
+      case FTS_DNR:
+      case FTS_ERR:
+        LOG(WARNING) << "Can't read " << curr->fts_accpath;
+        break;
+
+      case FTS_DC:
+      case FTS_DOT:
+      case FTS_NSOK:
+        // Not reached unless FTS_LOGICAL, FTS_SEEDOT, or FTS_NOSTAT were
+        // passed to fts_open()
+        break;
+
+      case FTS_D:
+        // Do nothing. Need depth-first search, so directories are deleted
+        // in FTS_DP
+        break;
+
+      case FTS_DP:
+      case FTS_F:
+      case FTS_SL:
+      case FTS_SLNONE:
+      case FTS_DEFAULT:
+        VLOG(1) << "Removing " << curr->fts_path;
+        if (remove(curr->fts_accpath) < 0) {
+          LOG(WARNING) << curr->fts_path
+                       << ": Failed to remove: " << strerror(curr->fts_errno);
+        }
+        break;
+    }
+  }
+
+  if (ftsp) {
+    fts_close(ftsp);
+  }
+}
+
 }  // namespace util
 }  // namespace aos
diff --git a/aos/util/file.h b/aos/util/file.h
index bf216bb..4d0ed2d 100644
--- a/aos/util/file.h
+++ b/aos/util/file.h
@@ -26,6 +26,10 @@
 
 bool PathExists(std::string_view path);
 
+// Recursively removes everything in the provided path.  Ignores any errors it
+// runs across.
+void UnlinkRecursive(std::string_view path);
+
 }  // namespace util
 }  // namespace aos
 
diff --git a/debian/fts.patch b/debian/fts.patch
new file mode 100644
index 0000000..7357940
--- /dev/null
+++ b/debian/fts.patch
@@ -0,0 +1,11 @@
+--- arm-frc2020-linux-gnueabi/usr/include/fts.h
++++ arm-frc2020-linux-gnueabi/usr/include/fts.h
+@@ -193,7 +193,7 @@ FTS *__REDIRECT (fts_open, (char * const *, int,
+ 				int (*)(const FTSENT **, const FTSENT **)),
+ 		     fts64_open);
+ FTSENT	*__REDIRECT (fts_read, (FTS *), fts64_read);
+-int	 __REDIRECT (fts_set, (FTS *, FTSENT *, int), fts64_set) __THROW;
++int	 __REDIRECT_NTH (fts_set, (FTS *, FTSENT *, int), fts64_set);
+ # else
+ #  define fts_children fts64_children
+ #  define fts_close fts64_close