blob: 692e75fbddf061663f266b0d0b69970d21288a1e [file] [log] [blame]
// 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/interfaces/file.mojom.h"
#include "mojo/services/files/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