ScopedBoundPort
authorYedidya Feldblum <yfeldblum@fb.com>
Tue, 16 Aug 2016 02:39:44 +0000 (19:39 -0700)
committerFacebook Github Bot <facebook-github-bot-bot@fb.com>
Tue, 16 Aug 2016 02:53:26 +0000 (19:53 -0700)
Summary:
[Folly] `ScopedBoundPort`.

An RAII class for `bind`ing, but not `listen`ing, an ephemral port.

Useful when there is a need to test server-down cases - in such cases, a guaranteed-not-listening port is required.

Extracted from a unit-test in Thrift; switches the Thrift test to use this extracted implementation instead.

Reviewed By: djwatson

Differential Revision: D3714293

fbshipit-source-id: 649fba1a0b7f0519b8297a3183d03c5dde23ddc6

folly/Makefile.am
folly/io/async/AsyncServerSocket.h
folly/io/async/test/ScopedBoundPort.cpp [new file with mode: 0644]
folly/io/async/test/ScopedBoundPort.h [new file with mode: 0644]

index 328f96a38b456015546eab9f87f98870b95fa9f1..116ef90e0c3e85dadc38b305caae7807890ec5d7 100644 (file)
@@ -227,6 +227,7 @@ nobase_follyinclude_HEADERS = \
        io/async/test/MockAsyncTransport.h \
        io/async/test/MockAsyncUDPSocket.h \
        io/async/test/MockTimeoutManager.h \
+       io/async/test/ScopedBoundPort.h \
        io/async/test/SocketPair.h \
        io/async/test/TimeUtil.h \
        io/async/test/UndelayedDestruction.h \
@@ -415,6 +416,7 @@ libfolly_la_SOURCES = \
        io/async/SSLContext.cpp \
        io/async/ScopedEventBaseThread.cpp \
        io/async/HHWheelTimer.cpp \
+       io/async/test/ScopedBoundPort.cpp \
        io/async/test/SocketPair.cpp \
        io/async/test/TimeUtil.cpp \
        io/async/ssl/OpenSSLUtils.cpp \
index d2f5002b45a50fd0d8fa0cea61c3ef4fe3d27656..83ef058c866347ec6d6543eaa7dd61852765521c 100644 (file)
@@ -351,6 +351,17 @@ class AsyncServerSocket : public DelayedDestruction
    */
   void getAddress(SocketAddress* addressReturn) const;
 
+  /**
+   * Get the local address to which the socket is bound.
+   *
+   * Throws TTransportException on error.
+   */
+  SocketAddress getAddress() const {
+    SocketAddress ret;
+    getAddress(&ret);
+    return ret;
+  }
+
   /**
    * Get all the local addresses to which the socket is bound.
    *
diff --git a/folly/io/async/test/ScopedBoundPort.cpp b/folly/io/async/test/ScopedBoundPort.cpp
new file mode 100644 (file)
index 0000000..b3ba946
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2016 Facebook, Inc.
+ *
+ * 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
+ *
+ *   http://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 <folly/io/async/test/ScopedBoundPort.h>
+
+#include <folly/Memory.h>
+#include <folly/io/async/AsyncServerSocket.h>
+#include <folly/io/async/ScopedEventBaseThread.h>
+
+namespace folly {
+
+ScopedBoundPort::ScopedBoundPort(IPAddress host) {
+  ebth_ = folly::make_unique<ScopedEventBaseThread>();
+  ebth_->getEventBase()->runInEventBaseThreadAndWait([&] {
+    sock_ = AsyncServerSocket::newSocket(ebth_->getEventBase());
+    sock_->bind(SocketAddress(host, 0));
+  });
+}
+
+ScopedBoundPort::~ScopedBoundPort() {
+  ebth_->getEventBase()->runInEventBaseThread([sock = std::move(sock_)]{});
+}
+
+SocketAddress ScopedBoundPort::getAddress() const {
+  return sock_->getAddress();
+}
+}
diff --git a/folly/io/async/test/ScopedBoundPort.h b/folly/io/async/test/ScopedBoundPort.h
new file mode 100644 (file)
index 0000000..8dc90ed
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2016 Facebook, Inc.
+ *
+ * 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
+ *
+ *   http://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 <memory>
+#include <utility>
+
+#include <folly/IPAddress.h>
+#include <folly/IPAddressV6.h>
+#include <folly/SocketAddress.h>
+
+namespace folly {
+
+class AsyncServerSocket;
+class ScopedEventBaseThread;
+
+/***
+ *  ScopedBoundPort
+ *
+ *  Binds to an ephemeral port in the ctor but does not listen. Unbinds from the
+ *  port in the dtor.
+ *
+ *  While an instance is in scope, we know at least one port which is guaranteed
+ *  not to be listening - the port the instance binds but does not listen.
+ *
+ *  Useful for testing server-down cases.
+ *
+ *  Example:
+ *
+ *    TEST(MyClient, WhenTheServerIsDown_ThrowsServerDownException) {
+ *      folly::ScopedBoundPort bound;
+ *      MyClient client(bound.getAddress(), 100ms);
+ *      EXPECT_THROW(client.getData(), ServerDownException);
+ *    }
+ */
+class ScopedBoundPort {
+ public:
+  explicit ScopedBoundPort(IPAddress host = IPAddressV6("::1"));
+  ~ScopedBoundPort();
+  SocketAddress getAddress() const;
+
+ private:
+  std::unique_ptr<ScopedEventBaseThread> ebth_;
+  std::shared_ptr<AsyncServerSocket> sock_;
+};
+}