| 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
|
|
|