OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "apps/moterm/moterm_driver.h" |
| 6 |
| 7 #include <deque> |
| 8 #include <string> |
| 9 #include <vector> |
| 10 |
| 11 #include "base/macros.h" |
| 12 #include "base/memory/weak_ptr.h" |
| 13 #include "base/message_loop/message_loop.h" |
| 14 #include "mojo/public/cpp/application/application_test_base.h" |
| 15 #include "mojo/public/cpp/bindings/callback.h" |
| 16 #include "mojo/public/cpp/bindings/type_converter.h" |
| 17 #include "mojo/services/files/public/interfaces/file.mojom.h" |
| 18 #include "mojo/services/files/public/interfaces/types.mojom.h" |
| 19 #include "testing/gtest/include/gtest/gtest.h" |
| 20 |
| 21 namespace { |
| 22 |
| 23 // A test |MotermDriver::Client| that just records the notifications it |
| 24 // receives. When |OnDestroyed()| gets called, it'll also quit the current |
| 25 // message loop. |
| 26 class TestClient : public MotermDriver::Client { |
| 27 public: |
| 28 struct Event { |
| 29 enum class Type { DATA_RECEIVED, CLOSED, DESTROYED }; |
| 30 |
| 31 Event(Type type) : type(type) {} |
| 32 Event(Type type, const std::string& data) : type(type), data(data) {} |
| 33 ~Event() {} |
| 34 |
| 35 Type type; |
| 36 std::string data; |
| 37 }; |
| 38 |
| 39 TestClient() {} |
| 40 ~TestClient() {} |
| 41 |
| 42 std::deque<Event>& events() { return events_; } |
| 43 |
| 44 private: |
| 45 // |MotermDriver::Client|: |
| 46 void OnDataReceived(const void* bytes, size_t num_bytes) override { |
| 47 events_.push_back( |
| 48 Event(Event::Type::DATA_RECEIVED, |
| 49 std::string(static_cast<const char*>(bytes), num_bytes))); |
| 50 } |
| 51 |
| 52 void OnClosed() override { events_.push_back(Event(Event::Type::CLOSED)); } |
| 53 |
| 54 void OnDestroyed() override { |
| 55 events_.push_back(Event(Event::Type::DESTROYED)); |
| 56 base::MessageLoop::current()->Quit(); |
| 57 } |
| 58 |
| 59 std::deque<Event> events_; |
| 60 |
| 61 DISALLOW_COPY_AND_ASSIGN(TestClient); |
| 62 }; |
| 63 |
| 64 using MotermDriverTest = mojo::test::ApplicationTestBase; |
| 65 |
| 66 // This spins the message loop until the callback is run. Note that we can't |
| 67 // just wait for the response message, since we need to give a chance for the |
| 68 // driver to handle the message. |
| 69 void FileWriteString(mojo::files::File* file, const std::string& s) { |
| 70 std::vector<uint8_t> bytes_to_write; |
| 71 for (size_t i = 0; i < s.size(); i++) |
| 72 bytes_to_write.push_back(static_cast<uint8_t>(s[i])); |
| 73 mojo::files::Error error = mojo::files::ERROR_INTERNAL; |
| 74 uint32_t num_bytes_written = 0; |
| 75 file->Write(mojo::Array<uint8_t>::From(bytes_to_write), 0, |
| 76 mojo::files::WHENCE_FROM_CURRENT, |
| 77 [&error, &num_bytes_written](mojo::files::Error e, uint32_t n) { |
| 78 error = e; |
| 79 num_bytes_written = n; |
| 80 base::MessageLoop::current()->Quit(); |
| 81 }); |
| 82 base::MessageLoop::current()->Run(); |
| 83 EXPECT_EQ(mojo::files::ERROR_OK, error); |
| 84 EXPECT_EQ(bytes_to_write.size(), num_bytes_written); |
| 85 } |
| 86 |
| 87 // (See the comments above |FileWriteString()|.) |
| 88 void FileClose(mojo::files::File* file) { |
| 89 mojo::files::Error error = mojo::files::ERROR_INTERNAL; |
| 90 file->Close([&error](mojo::files::Error e) { |
| 91 error = e; |
| 92 base::MessageLoop::current()->Quit(); |
| 93 }); |
| 94 base::MessageLoop::current()->Run(); |
| 95 EXPECT_EQ(mojo::files::ERROR_OK, error); |
| 96 } |
| 97 |
| 98 // (See the comments above |FileWriteString()|.) |
| 99 std::string FileReadString(mojo::files::File* file, uint32_t max_bytes) { |
| 100 mojo::files::Error error = mojo::files::ERROR_INTERNAL; |
| 101 mojo::Array<uint8_t> bytes_read; |
| 102 file->Read( |
| 103 10, 0, mojo::files::WHENCE_FROM_CURRENT, |
| 104 [&error, &bytes_read](mojo::files::Error e, mojo::Array<uint8_t> b) { |
| 105 error = e; |
| 106 bytes_read = b.Pass(); |
| 107 base::MessageLoop::current()->Quit(); |
| 108 }); |
| 109 base::MessageLoop::current()->Run(); |
| 110 EXPECT_EQ(mojo::files::ERROR_OK, error); |
| 111 if (!bytes_read.size()) |
| 112 return std::string(); |
| 113 return std::string(reinterpret_cast<const char*>(&bytes_read[0]), |
| 114 bytes_read.size()); |
| 115 } |
| 116 |
| 117 TEST_F(MotermDriverTest, BasicWritesToTerminal) { |
| 118 TestClient client; |
| 119 mojo::files::FilePtr file; |
| 120 base::WeakPtr<MotermDriver> driver = |
| 121 MotermDriver::Create(&client, GetProxy(&file)); |
| 122 ASSERT_TRUE(driver); |
| 123 |
| 124 // Write a simple string. |
| 125 std::string hello("hello"); |
| 126 FileWriteString(file.get(), hello); |
| 127 // The client should have gotten the data. |
| 128 ASSERT_EQ(1u, client.events().size()); |
| 129 TestClient::Event event = client.events().front(); |
| 130 client.events().pop_front(); |
| 131 EXPECT_EQ(TestClient::Event::Type::DATA_RECEIVED, event.type); |
| 132 EXPECT_EQ(hello, event.data); |
| 133 |
| 134 // With the default settings, it should transform \n to \r\n. |
| 135 FileWriteString(file.get(), "world\n"); |
| 136 // The client should have gotten the data. |
| 137 ASSERT_EQ(1u, client.events().size()); |
| 138 event = client.events().front(); |
| 139 client.events().pop_front(); |
| 140 EXPECT_EQ(TestClient::Event::Type::DATA_RECEIVED, event.type); |
| 141 EXPECT_EQ(std::string("world\r\n"), event.data); |
| 142 |
| 143 FileClose(file.get()); |
| 144 // The client should have gotten the closed event. |
| 145 ASSERT_EQ(1u, client.events().size()); |
| 146 event = client.events().front(); |
| 147 client.events().pop_front(); |
| 148 EXPECT_EQ(TestClient::Event::Type::CLOSED, event.type); |
| 149 // The driver should still be alive. |
| 150 EXPECT_TRUE(driver); |
| 151 |
| 152 // Actually destroying the file (and spinning the message loop) should kill |
| 153 // the driver. |
| 154 file.reset(); |
| 155 base::MessageLoop::current()->Run(); |
| 156 EXPECT_FALSE(driver); |
| 157 // The client should have gotten the destroyed event. |
| 158 ASSERT_EQ(1u, client.events().size()); |
| 159 event = client.events().front(); |
| 160 client.events().pop_front(); |
| 161 EXPECT_EQ(TestClient::Event::Type::DESTROYED, event.type); |
| 162 } |
| 163 |
| 164 TEST_F(MotermDriverTest, BasicReadsFromTerminal) { |
| 165 TestClient client; |
| 166 mojo::files::FilePtr file; |
| 167 base::WeakPtr<MotermDriver> driver = |
| 168 MotermDriver::Create(&client, GetProxy(&file)); |
| 169 ASSERT_TRUE(driver); |
| 170 |
| 171 // Kick off two reads. They should be satisfied in order. (Don't use |
| 172 // |FileReadString()| here, since we want to queue up the reads before calling |
| 173 // |SendData()|.) |
| 174 mojo::files::Error error1 = mojo::files::ERROR_INTERNAL; |
| 175 mojo::Array<uint8_t> bytes_read1; |
| 176 file->Read( |
| 177 4, 0, mojo::files::WHENCE_FROM_CURRENT, |
| 178 [&error1, &bytes_read1](mojo::files::Error e, mojo::Array<uint8_t> b) { |
| 179 error1 = e; |
| 180 bytes_read1 = b.Pass(); |
| 181 }); |
| 182 // Only quit the message loop on getting the response to the second. |
| 183 mojo::files::Error error2 = mojo::files::ERROR_INTERNAL; |
| 184 mojo::Array<uint8_t> bytes_read2; |
| 185 file->Read( |
| 186 100, 0, mojo::files::WHENCE_FROM_CURRENT, |
| 187 [&error2, &bytes_read2](mojo::files::Error e, mojo::Array<uint8_t> b) { |
| 188 error2 = e; |
| 189 bytes_read2 = b.Pass(); |
| 190 base::MessageLoop::current()->Quit(); |
| 191 }); |
| 192 // They'll only be satisfied on input from the terminal. (We need to send a |
| 193 // \n (or a \r), since by default we start off in canonical mode.) |
| 194 std::string data("hello\nworld"); |
| 195 driver->SendData(data.data(), data.size()); |
| 196 base::MessageLoop::current()->Run(); |
| 197 EXPECT_EQ(mojo::files::ERROR_OK, error1); |
| 198 EXPECT_EQ(4u, bytes_read1.size()); |
| 199 EXPECT_EQ(std::string("hell"), |
| 200 std::string(reinterpret_cast<const char*>(&bytes_read1[0]), |
| 201 bytes_read1.size())); |
| 202 EXPECT_EQ(mojo::files::ERROR_OK, error2); |
| 203 EXPECT_EQ(2u, bytes_read2.size()); |
| 204 // (We shouldn't get the "world" yet.) |
| 205 EXPECT_EQ(std::string("o\n"), |
| 206 std::string(reinterpret_cast<const char*>(&bytes_read2[0]), |
| 207 bytes_read2.size())); |
| 208 |
| 209 // Now send a \n. |
| 210 driver->SendData("\n", 1); |
| 211 // And then do a read. |
| 212 std::string result = FileReadString(file.get(), 100); |
| 213 // It should now get the "world" (and the \n). |
| 214 EXPECT_EQ(std::string("world\n"), result); |
| 215 |
| 216 // A bunch of stuff should have been echoed to the terminal. (Don't worry |
| 217 // about what was "displayed" here -- we'll test that separately.) |
| 218 for (const auto& event : client.events()) |
| 219 EXPECT_EQ(TestClient::Event::Type::DATA_RECEIVED, event.type); |
| 220 client.events().clear(); |
| 221 |
| 222 file.reset(); |
| 223 base::MessageLoop::current()->Run(); |
| 224 EXPECT_FALSE(driver); |
| 225 // The client should have gotten the destroyed event. |
| 226 ASSERT_EQ(1u, client.events().size()); |
| 227 TestClient::Event event = client.events().front(); |
| 228 client.events().pop_front(); |
| 229 EXPECT_EQ(TestClient::Event::Type::DESTROYED, event.type); |
| 230 } |
| 231 |
| 232 TEST_F(MotermDriverTest, BasicLineEditing) { |
| 233 TestClient client; |
| 234 mojo::files::FilePtr file; |
| 235 base::WeakPtr<MotermDriver> driver = |
| 236 MotermDriver::Create(&client, GetProxy(&file)); |
| 237 ASSERT_TRUE(driver); |
| 238 |
| 239 // The default erase character is DEL (\x7f). (In case you're wondering, the |
| 240 // string literal is split to avoid an "out of range" hex constant.) |
| 241 std::string data( |
| 242 "abde\x7f\x7f" |
| 243 "cdef\n\x7f" |
| 244 "124\x7f" |
| 245 "345\r\nXYZ"); |
| 246 driver->SendData(data.data(), data.size()); |
| 247 std::string result = FileReadString(file.get(), 100); |
| 248 EXPECT_EQ(std::string("abcdef\n"), result); |
| 249 result = FileReadString(file.get(), 100); |
| 250 // By default, ICRNL is set, so \r gets transformed to \n. |
| 251 EXPECT_EQ(std::string("12345\n"), result); |
| 252 result = FileReadString(file.get(), 100); |
| 253 EXPECT_EQ(std::string("\n"), result); |
| 254 |
| 255 // A bunch of stuff should have been echoed to the terminal. The driver has |
| 256 // some discretion about how it splits up the stuff (into |
| 257 // |OnDataReceived()|s), so just concatenate the contents and verify that. |
| 258 std::string echoed; |
| 259 for (const auto& event : client.events()) { |
| 260 EXPECT_EQ(TestClient::Event::Type::DATA_RECEIVED, event.type); |
| 261 EXPECT_FALSE(event.data.empty()); |
| 262 echoed += event.data; |
| 263 } |
| 264 client.events().clear(); |
| 265 // Note: Erases should yield BS (\x08), space, BS (unless at the beginning of |
| 266 // a line). Note that the "XYZ" hasn't been sent to us yet, but should already |
| 267 // be echoed. |
| 268 EXPECT_EQ(std::string( |
| 269 "abde\x08 \x08\x08 \x08" |
| 270 "cdef\r\n124\x08 \x08" |
| 271 "345\r\n\r\nXYZ"), |
| 272 echoed); |
| 273 |
| 274 // Here, detach rather than doing destroying the other end. |
| 275 // TODO(vtl): Verify that the driver's end of the message pipe is closed |
| 276 // (e.g., by checking that |file| detects that its peer is closed). |
| 277 driver->Detach(); |
| 278 EXPECT_FALSE(driver); |
| 279 // The client shouldn't have received anything. |
| 280 EXPECT_TRUE(client.events().empty()); |
| 281 } |
| 282 |
| 283 } // namespace |
OLD | NEW |