[fzl] Make FdioCaller movable, add tests

Change-Id: Ic07d237e894317e2a7c12d7bf4b9759eba8e916a
diff --git a/zircon/system/ulib/fzl/include/lib/fzl/fdio.h b/zircon/system/ulib/fzl/include/lib/fzl/fdio.h
index 204d882..f2f3ff5 100644
--- a/zircon/system/ulib/fzl/include/lib/fzl/fdio.h
+++ b/zircon/system/ulib/fzl/include/lib/fzl/fdio.h
@@ -18,6 +18,8 @@
 //
 // FdioCaller consumes |fd|, but the same |fd| may be re-acquired by
 // calling "release()" on the FdioCaller object.
+//
+// This class is movable, but not copyable.
 class FdioCaller {
 public:
     FdioCaller() : io_(nullptr) {}
@@ -25,6 +27,18 @@
     explicit FdioCaller(fbl::unique_fd fd) :
         fd_(std::move(fd)), io_(fdio_unsafe_fd_to_io(fd_.get())) {}
 
+    FdioCaller& operator=(FdioCaller&& o) {
+        fd_ = std::move(o.fd_);
+        io_ = o.io_;
+        o.io_ = nullptr;
+        return *this;
+    }
+    FdioCaller(FdioCaller&& o) : fd_(std::move(o.fd_)), io_(o.io_) {
+        o.io_ = nullptr;
+    }
+    FdioCaller(const FdioCaller&) = delete;
+    FdioCaller& operator=(const FdioCaller&) = delete;
+
     ~FdioCaller() {
         release();
     }
@@ -64,11 +78,6 @@
         return fdio_unsafe_borrow_channel(io_);
     }
 
-    FdioCaller& operator=(FdioCaller&& o) = delete;
-    FdioCaller(FdioCaller&& o) = delete;
-    FdioCaller(const FdioCaller&) = delete;
-    FdioCaller& operator=(const FdioCaller&) = delete;
-
 private:
     fbl::unique_fd fd_;
     fdio_t* io_;
diff --git a/zircon/system/ulib/fzl/test/BUILD.gn b/zircon/system/ulib/fzl/test/BUILD.gn
index 3242b77..e91858e 100644
--- a/zircon/system/ulib/fzl/test/BUILD.gn
+++ b/zircon/system/ulib/fzl/test/BUILD.gn
@@ -31,6 +31,7 @@
     "$zx/system/ulib/fdio",
     "$zx/system/ulib/fzl",
     "$zx/system/ulib/memfs",
+    "$zx/system/ulib/sync",
     "$zx/system/ulib/unittest",
     "$zx/system/ulib/zircon",
     "$zx/system/ulib/zx",
diff --git a/zircon/system/ulib/fzl/test/fdio.cpp b/zircon/system/ulib/fzl/test/fdio.cpp
index 396bdc8..7dfdf2c 100644
--- a/zircon/system/ulib/fzl/test/fdio.cpp
+++ b/zircon/system/ulib/fzl/test/fdio.cpp
@@ -8,6 +8,7 @@
 #include <fbl/unique_fd.h>
 #include <fuchsia/io/c/fidl.h>
 #include <lib/async-loop/cpp/loop.h>
+#include <lib/fdio/fd.h>
 #include <lib/memfs/memfs.h>
 #include <lib/fzl/fdio.h>
 #include <unittest/unittest.h>
@@ -16,48 +17,77 @@
 
 namespace {
 
-bool fdio_call_io() {
-    BEGIN_TEST;
-
-    // Create a Memfs filesystem.
-    async::Loop loop(&kAsyncLoopConfigNoAttachToThread);
-    ASSERT_EQ(loop.StartThread(), ZX_OK);
-    ASSERT_EQ(memfs_install_at(loop.dispatcher(), "/my-tmp"), ZX_OK);
-    fbl::unique_fd dir(open("/my-tmp", O_DIRECTORY | O_RDONLY));
-    ASSERT_TRUE(dir);
-
-    // Open a file within the filesystem.
-    fbl::unique_fd fd(openat(dir.get(), "my-file", O_CREAT | O_RDWR));
-    ASSERT_TRUE(fd);
-
-    // Try some filesystem operations natively:
-    fzl::FdioCaller caller(std::move(fd));
-    ASSERT_TRUE(caller);
+bool TryFilesystemOperations(const fzl::FdioCaller& caller) {
+    BEGIN_HELPER;
 
     const char* golden = "foobar";
     zx_status_t status;
     uint64_t actual;
-    ASSERT_EQ(fuchsia_io_FileWrite(caller.borrow_channel(),
-                                   reinterpret_cast<const uint8_t*>(golden),
-                                   strlen(golden), &status, &actual),
+    ASSERT_EQ(fuchsia_io_FileWriteAt(caller.borrow_channel(),
+                                     reinterpret_cast<const uint8_t*>(golden),
+                                     strlen(golden), 0, &status, &actual),
               ZX_OK);
     ASSERT_EQ(status, ZX_OK);
     ASSERT_EQ(actual, strlen(golden));
 
-    ASSERT_EQ(fuchsia_io_FileSeek(caller.borrow_channel(), 0L, fuchsia_io_SeekOrigin_START,
-                                  &status, &actual),
-              ZX_OK);
-    ASSERT_EQ(status, ZX_OK);
-    ASSERT_EQ(actual, 0);
-
     char buf[256];
-    ASSERT_EQ(fuchsia_io_FileRead(caller.borrow_channel(), static_cast<uint64_t>(sizeof(buf)),
+    ASSERT_EQ(fuchsia_io_FileReadAt(caller.borrow_channel(), static_cast<uint64_t>(sizeof(buf)), 0,
                                   &status, reinterpret_cast<uint8_t*>(buf), sizeof(buf), &actual),
               ZX_OK);
     ASSERT_EQ(status, ZX_OK);
     ASSERT_EQ(actual, strlen(golden));
     ASSERT_EQ(memcmp(buf, golden, strlen(golden)), 0);
 
+    END_HELPER;
+}
+
+class Harness {
+public:
+    Harness() {}
+
+    ~Harness() {
+        if (memfs_) {
+            sync_completion_t unmounted;
+            memfs_free_filesystem(memfs_, &unmounted);
+            sync_completion_wait(&unmounted, ZX_SEC(3));
+        }
+    }
+
+    bool Setup() {
+        BEGIN_HELPER;
+        ASSERT_EQ(loop_.StartThread(), ZX_OK);
+        zx_handle_t root;
+        ASSERT_EQ(memfs_create_filesystem(loop_.dispatcher(), &memfs_, &root), ZX_OK);
+        int fd;
+        ASSERT_EQ(fdio_fd_create(root, &fd), ZX_OK);
+        fbl::unique_fd dir(fd);
+        ASSERT_TRUE(dir);
+        fd_.reset(openat(dir.get(), "my-file", O_CREAT | O_RDWR));
+        ASSERT_TRUE(fd_);
+
+        END_HELPER;
+    }
+
+    fbl::unique_fd fd() { return std::move(fd_); }
+
+private:
+    async::Loop loop_ = async::Loop(&kAsyncLoopConfigNoAttachToThread);
+    memfs_filesystem_t* memfs_ = nullptr;
+    fbl::unique_fd fd_;
+};
+
+bool FdioCallerFile() {
+    BEGIN_TEST;
+
+    Harness harness;
+    ASSERT_TRUE(harness.Setup());
+    auto fd = harness.fd();
+
+    // Try some filesystem operations.
+    fzl::FdioCaller caller(std::move(fd));
+    ASSERT_TRUE(caller);
+    ASSERT_TRUE(TryFilesystemOperations(caller));
+
     // Re-acquire the underlying fd.
     fd = caller.release();
     ASSERT_EQ(close(fd.release()), 0);
@@ -65,9 +95,43 @@
     END_TEST;
 }
 
+bool FdioCallerMoveAssignment() {
+    BEGIN_TEST;
+
+    Harness harness;
+    ASSERT_TRUE(harness.Setup());
+    auto fd = harness.fd();
+
+    fzl::FdioCaller caller(std::move(fd));
+    fzl::FdioCaller move_assignment_caller = std::move(caller);
+    ASSERT_TRUE(move_assignment_caller);
+    ASSERT_FALSE(caller);
+    ASSERT_TRUE(TryFilesystemOperations(move_assignment_caller));
+
+    END_TEST;
+}
+
+bool FdioCallerMoveConstructor() {
+    BEGIN_TEST;
+
+    Harness harness;
+    ASSERT_TRUE(harness.Setup());
+    auto fd = harness.fd();
+
+    fzl::FdioCaller caller(std::move(fd));
+    fzl::FdioCaller move_ctor_caller(std::move(caller));
+    ASSERT_TRUE(move_ctor_caller);
+    ASSERT_FALSE(caller);
+    ASSERT_TRUE(TryFilesystemOperations(move_ctor_caller));
+
+    END_TEST;
+}
+
 }  // namespace
 
-BEGIN_TEST_CASE(fdio_call_tests)
-RUN_TEST(fdio_call_io)
-END_TEST_CASE(fdio_call_tests)
+BEGIN_TEST_CASE(FdioCallTests)
+RUN_TEST(FdioCallerFile)
+RUN_TEST(FdioCallerMoveAssignment)
+RUN_TEST(FdioCallerMoveConstructor)
+END_TEST_CASE(FdioCallTests)