Index: apps/moterm/moterm_driver_unittest.cc |
diff --git a/apps/moterm/moterm_driver_unittest.cc b/apps/moterm/moterm_driver_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..822204fa66bf8221fd9bc23501e4482a31793aec |
--- /dev/null |
+++ b/apps/moterm/moterm_driver_unittest.cc |
@@ -0,0 +1,283 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "apps/moterm/moterm_driver.h" |
+ |
+#include <deque> |
+#include <string> |
+#include <vector> |
+ |
+#include "base/macros.h" |
+#include "base/memory/weak_ptr.h" |
+#include "base/message_loop/message_loop.h" |
+#include "mojo/public/cpp/application/application_test_base.h" |
+#include "mojo/public/cpp/bindings/callback.h" |
+#include "mojo/public/cpp/bindings/type_converter.h" |
+#include "mojo/services/files/public/interfaces/file.mojom.h" |
+#include "mojo/services/files/public/interfaces/types.mojom.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+namespace { |
+ |
+// A test |MotermDriver::Client| that just records the notifications it |
+// receives. When |OnDestroyed()| gets called, it'll also quit the current |
+// message loop. |
+class TestClient : public MotermDriver::Client { |
+ public: |
+ struct Event { |
+ enum class Type { DATA_RECEIVED, CLOSED, DESTROYED }; |
+ |
+ Event(Type type) : type(type) {} |
+ Event(Type type, const std::string& data) : type(type), data(data) {} |
+ ~Event() {} |
+ |
+ Type type; |
+ std::string data; |
+ }; |
+ |
+ TestClient() {} |
+ ~TestClient() {} |
+ |
+ std::deque<Event>& events() { return events_; } |
+ |
+ private: |
+ // |MotermDriver::Client|: |
+ void OnDataReceived(const void* bytes, size_t num_bytes) override { |
+ events_.push_back( |
+ Event(Event::Type::DATA_RECEIVED, |
+ std::string(static_cast<const char*>(bytes), num_bytes))); |
+ } |
+ |
+ void OnClosed() override { events_.push_back(Event(Event::Type::CLOSED)); } |
+ |
+ void OnDestroyed() override { |
+ events_.push_back(Event(Event::Type::DESTROYED)); |
+ base::MessageLoop::current()->Quit(); |
+ } |
+ |
+ std::deque<Event> events_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(TestClient); |
+}; |
+ |
+using MotermDriverTest = mojo::test::ApplicationTestBase; |
+ |
+// This spins the message loop until the callback is run. Note that we can't |
+// just wait for the response message, since we need to give a chance for the |
+// driver to handle the message. |
+void FileWriteString(mojo::files::File* file, const std::string& s) { |
+ std::vector<uint8_t> bytes_to_write; |
+ for (size_t i = 0; i < s.size(); i++) |
+ bytes_to_write.push_back(static_cast<uint8_t>(s[i])); |
+ mojo::files::Error error = mojo::files::ERROR_INTERNAL; |
+ uint32_t num_bytes_written = 0; |
+ file->Write(mojo::Array<uint8_t>::From(bytes_to_write), 0, |
+ mojo::files::WHENCE_FROM_CURRENT, |
+ [&error, &num_bytes_written](mojo::files::Error e, uint32_t n) { |
+ error = e; |
+ num_bytes_written = n; |
+ base::MessageLoop::current()->Quit(); |
+ }); |
+ base::MessageLoop::current()->Run(); |
+ EXPECT_EQ(mojo::files::ERROR_OK, error); |
+ EXPECT_EQ(bytes_to_write.size(), num_bytes_written); |
+} |
+ |
+// (See the comments above |FileWriteString()|.) |
+void FileClose(mojo::files::File* file) { |
+ mojo::files::Error error = mojo::files::ERROR_INTERNAL; |
+ file->Close([&error](mojo::files::Error e) { |
+ error = e; |
+ base::MessageLoop::current()->Quit(); |
+ }); |
+ base::MessageLoop::current()->Run(); |
+ EXPECT_EQ(mojo::files::ERROR_OK, error); |
+} |
+ |
+// (See the comments above |FileWriteString()|.) |
+std::string FileReadString(mojo::files::File* file, uint32_t max_bytes) { |
+ mojo::files::Error error = mojo::files::ERROR_INTERNAL; |
+ mojo::Array<uint8_t> bytes_read; |
+ file->Read( |
+ 10, 0, mojo::files::WHENCE_FROM_CURRENT, |
+ [&error, &bytes_read](mojo::files::Error e, mojo::Array<uint8_t> b) { |
+ error = e; |
+ bytes_read = b.Pass(); |
+ base::MessageLoop::current()->Quit(); |
+ }); |
+ base::MessageLoop::current()->Run(); |
+ EXPECT_EQ(mojo::files::ERROR_OK, error); |
+ if (!bytes_read.size()) |
+ return std::string(); |
+ return std::string(reinterpret_cast<const char*>(&bytes_read[0]), |
+ bytes_read.size()); |
+} |
+ |
+TEST_F(MotermDriverTest, BasicWritesToTerminal) { |
+ TestClient client; |
+ mojo::files::FilePtr file; |
+ base::WeakPtr<MotermDriver> driver = |
+ MotermDriver::Create(&client, GetProxy(&file)); |
+ ASSERT_TRUE(driver); |
+ |
+ // Write a simple string. |
+ std::string hello("hello"); |
+ FileWriteString(file.get(), hello); |
+ // The client should have gotten the data. |
+ ASSERT_EQ(1u, client.events().size()); |
+ TestClient::Event event = client.events().front(); |
+ client.events().pop_front(); |
+ EXPECT_EQ(TestClient::Event::Type::DATA_RECEIVED, event.type); |
+ EXPECT_EQ(hello, event.data); |
+ |
+ // With the default settings, it should transform \n to \r\n. |
+ FileWriteString(file.get(), "world\n"); |
+ // The client should have gotten the data. |
+ ASSERT_EQ(1u, client.events().size()); |
+ event = client.events().front(); |
+ client.events().pop_front(); |
+ EXPECT_EQ(TestClient::Event::Type::DATA_RECEIVED, event.type); |
+ EXPECT_EQ(std::string("world\r\n"), event.data); |
+ |
+ FileClose(file.get()); |
+ // The client should have gotten the closed event. |
+ ASSERT_EQ(1u, client.events().size()); |
+ event = client.events().front(); |
+ client.events().pop_front(); |
+ EXPECT_EQ(TestClient::Event::Type::CLOSED, event.type); |
+ // The driver should still be alive. |
+ EXPECT_TRUE(driver); |
+ |
+ // Actually destroying the file (and spinning the message loop) should kill |
+ // the driver. |
+ file.reset(); |
+ base::MessageLoop::current()->Run(); |
+ EXPECT_FALSE(driver); |
+ // The client should have gotten the destroyed event. |
+ ASSERT_EQ(1u, client.events().size()); |
+ event = client.events().front(); |
+ client.events().pop_front(); |
+ EXPECT_EQ(TestClient::Event::Type::DESTROYED, event.type); |
+} |
+ |
+TEST_F(MotermDriverTest, BasicReadsFromTerminal) { |
+ TestClient client; |
+ mojo::files::FilePtr file; |
+ base::WeakPtr<MotermDriver> driver = |
+ MotermDriver::Create(&client, GetProxy(&file)); |
+ ASSERT_TRUE(driver); |
+ |
+ // Kick off two reads. They should be satisfied in order. (Don't use |
+ // |FileReadString()| here, since we want to queue up the reads before calling |
+ // |SendData()|.) |
+ mojo::files::Error error1 = mojo::files::ERROR_INTERNAL; |
+ mojo::Array<uint8_t> bytes_read1; |
+ file->Read( |
+ 4, 0, mojo::files::WHENCE_FROM_CURRENT, |
+ [&error1, &bytes_read1](mojo::files::Error e, mojo::Array<uint8_t> b) { |
+ error1 = e; |
+ bytes_read1 = b.Pass(); |
+ }); |
+ // Only quit the message loop on getting the response to the second. |
+ mojo::files::Error error2 = mojo::files::ERROR_INTERNAL; |
+ mojo::Array<uint8_t> bytes_read2; |
+ file->Read( |
+ 100, 0, mojo::files::WHENCE_FROM_CURRENT, |
+ [&error2, &bytes_read2](mojo::files::Error e, mojo::Array<uint8_t> b) { |
+ error2 = e; |
+ bytes_read2 = b.Pass(); |
+ base::MessageLoop::current()->Quit(); |
+ }); |
+ // They'll only be satisfied on input from the terminal. (We need to send a |
+ // \n (or a \r), since by default we start off in canonical mode.) |
+ std::string data("hello\nworld"); |
+ driver->SendData(data.data(), data.size()); |
+ base::MessageLoop::current()->Run(); |
+ EXPECT_EQ(mojo::files::ERROR_OK, error1); |
+ EXPECT_EQ(4u, bytes_read1.size()); |
+ EXPECT_EQ(std::string("hell"), |
+ std::string(reinterpret_cast<const char*>(&bytes_read1[0]), |
+ bytes_read1.size())); |
+ EXPECT_EQ(mojo::files::ERROR_OK, error2); |
+ EXPECT_EQ(2u, bytes_read2.size()); |
+ // (We shouldn't get the "world" yet.) |
+ EXPECT_EQ(std::string("o\n"), |
+ std::string(reinterpret_cast<const char*>(&bytes_read2[0]), |
+ bytes_read2.size())); |
+ |
+ // Now send a \n. |
+ driver->SendData("\n", 1); |
+ // And then do a read. |
+ std::string result = FileReadString(file.get(), 100); |
+ // It should now get the "world" (and the \n). |
+ EXPECT_EQ(std::string("world\n"), result); |
+ |
+ // A bunch of stuff should have been echoed to the terminal. (Don't worry |
+ // about what was "displayed" here -- we'll test that separately.) |
+ for (const auto& event : client.events()) |
+ EXPECT_EQ(TestClient::Event::Type::DATA_RECEIVED, event.type); |
+ client.events().clear(); |
+ |
+ file.reset(); |
+ base::MessageLoop::current()->Run(); |
+ EXPECT_FALSE(driver); |
+ // The client should have gotten the destroyed event. |
+ ASSERT_EQ(1u, client.events().size()); |
+ TestClient::Event event = client.events().front(); |
+ client.events().pop_front(); |
+ EXPECT_EQ(TestClient::Event::Type::DESTROYED, event.type); |
+} |
+ |
+TEST_F(MotermDriverTest, BasicLineEditing) { |
+ TestClient client; |
+ mojo::files::FilePtr file; |
+ base::WeakPtr<MotermDriver> driver = |
+ MotermDriver::Create(&client, GetProxy(&file)); |
+ ASSERT_TRUE(driver); |
+ |
+ // The default erase character is DEL (\x7f). (In case you're wondering, the |
+ // string literal is split to avoid an "out of range" hex constant.) |
+ std::string data( |
+ "abde\x7f\x7f" |
+ "cdef\n\x7f" |
+ "124\x7f" |
+ "345\r\nXYZ"); |
+ driver->SendData(data.data(), data.size()); |
+ std::string result = FileReadString(file.get(), 100); |
+ EXPECT_EQ(std::string("abcdef\n"), result); |
+ result = FileReadString(file.get(), 100); |
+ // By default, ICRNL is set, so \r gets transformed to \n. |
+ EXPECT_EQ(std::string("12345\n"), result); |
+ result = FileReadString(file.get(), 100); |
+ EXPECT_EQ(std::string("\n"), result); |
+ |
+ // A bunch of stuff should have been echoed to the terminal. The driver has |
+ // some discretion about how it splits up the stuff (into |
+ // |OnDataReceived()|s), so just concatenate the contents and verify that. |
+ std::string echoed; |
+ for (const auto& event : client.events()) { |
+ EXPECT_EQ(TestClient::Event::Type::DATA_RECEIVED, event.type); |
+ EXPECT_FALSE(event.data.empty()); |
+ echoed += event.data; |
+ } |
+ client.events().clear(); |
+ // Note: Erases should yield BS (\x08), space, BS (unless at the beginning of |
+ // a line). Note that the "XYZ" hasn't been sent to us yet, but should already |
+ // be echoed. |
+ EXPECT_EQ(std::string( |
+ "abde\x08 \x08\x08 \x08" |
+ "cdef\r\n124\x08 \x08" |
+ "345\r\n\r\nXYZ"), |
+ echoed); |
+ |
+ // Here, detach rather than doing destroying the other end. |
+ // TODO(vtl): Verify that the driver's end of the message pipe is closed |
+ // (e.g., by checking that |file| detects that its peer is closed). |
+ driver->Detach(); |
+ EXPECT_FALSE(driver); |
+ // The client shouldn't have received anything. |
+ EXPECT_TRUE(client.events().empty()); |
+} |
+ |
+} // namespace |