Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(5)

Unified Diff: apps/moterm/moterm_driver_unittest.cc

Issue 1128333002: Moterm part 2: Add MotermDriver, a terminal "driver". (Closed) Base URL: https://github.com/domokit/mojo.git@moterm_model
Patch Set: rebased again Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « apps/moterm/moterm_driver.cc ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
« no previous file with comments | « apps/moterm/moterm_driver.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698