OLD | NEW |
| (Empty) |
1 // Copyright 2016 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 "blimp/test/fake_engine/fake_engine.h" | |
6 | |
7 #include <fcntl.h> | |
8 #include <sys/socket.h> | |
9 #include <sys/un.h> | |
10 | |
11 #include "base/command_line.h" | |
12 #include "base/files/file_path.h" | |
13 #include "base/files/file_util.h" | |
14 #include "base/synchronization/waitable_event.h" | |
15 #include "base/test/launcher/unit_test_launcher.h" | |
16 #include "base/test/test_suite.h" | |
17 #include "blimp/test/fake_engine/proto/engine.grpc.pb.h" | |
18 #include "blimp/test/fake_engine/proto/lifetime.grpc.pb.h" | |
19 #include "blimp/test/fake_engine/proto/logging.grpc.pb.h" | |
20 #include "testing/gmock/include/gmock/gmock.h" | |
21 #include "testing/gtest/include/gtest/gtest.h" | |
22 #include "third_party/grpc/include/grpc++/create_channel_posix.h" | |
23 #include "third_party/grpc/include/grpc++/grpc++.h" | |
24 #include "third_party/grpc/include/grpc++/server_posix.h" | |
25 | |
26 using ::testing::_; | |
27 | |
28 namespace blimp { | |
29 | |
30 namespace { | |
31 | |
32 bool SocketPair(int* fd1, int* fd2) { | |
33 int pipe_fds[2]; | |
34 if (socketpair(AF_UNIX, SOCK_STREAM, 0, pipe_fds) != 0) { | |
35 PLOG(ERROR) << "socketpair()"; | |
36 return false; | |
37 } | |
38 | |
39 // Set both ends to be non-blocking. | |
40 if (fcntl(pipe_fds[0], F_SETFL, O_NONBLOCK) == -1 || | |
41 fcntl(pipe_fds[1], F_SETFL, O_NONBLOCK) == -1) { | |
42 PLOG(ERROR) << "fcntl(O_NONBLOCK)"; | |
43 if (IGNORE_EINTR(close(pipe_fds[0])) < 0) | |
44 PLOG(ERROR) << "close"; | |
45 if (IGNORE_EINTR(close(pipe_fds[1])) < 0) | |
46 PLOG(ERROR) << "close"; | |
47 return false; | |
48 } | |
49 | |
50 *fd1 = pipe_fds[0]; | |
51 *fd2 = pipe_fds[1]; | |
52 | |
53 return true; | |
54 } | |
55 | |
56 class MockLifetimeService : public Lifetime::Service { | |
57 public: | |
58 MOCK_METHOD3(EngineReady, grpc::Status( | |
59 grpc::ServerContext*, const EngineReadyRequest*, EngineReadyResponse*)); | |
60 }; | |
61 | |
62 class MockLoggingService : public Logging::Service { | |
63 public: | |
64 MOCK_METHOD3(Log, grpc::Status( | |
65 grpc::ServerContext*, const LogRequest*, LogResponse*)); | |
66 }; | |
67 | |
68 // The FakeEngineAppTest suite sets up a simple interface resembling the | |
69 // Service, which is required by the Fake Engine. This allows testing whether | |
70 // the Fake Engine serves its purpose of simulating the real Engine as spawned | |
71 // by the Service. | |
72 class FakeEngineAppTest : public testing::Test { | |
73 public: | |
74 FakeEngineAppTest() | |
75 : engine_ready_(base::WaitableEvent::ResetPolicy::MANUAL, | |
76 base::WaitableEvent::InitialState::NOT_SIGNALED) {} | |
77 | |
78 protected: | |
79 void SetUp() override { | |
80 CHECK(SocketPair(&rendering_server_connect_fd_, &engine_listen_fd_)); | |
81 CHECK(SocketPair(&rendering_server_listen_fd_, &engine_connect_fd_)); | |
82 | |
83 grpc::ServerBuilder builder; | |
84 builder.RegisterService(&mock_lifetime_service_); | |
85 builder.RegisterService(&mock_logging_service_); | |
86 grpc_server_ = builder.BuildAndStart(); | |
87 | |
88 // TODO(xyzzyz): Remove fcntl when https://github.com/grpc/grpc/pull/8051 is | |
89 // merged and added to Chromium. | |
90 int flags = fcntl(rendering_server_listen_fd_, F_GETFL, 0); | |
91 CHECK_EQ(0, fcntl(rendering_server_listen_fd_, F_SETFL, | |
92 flags | O_NONBLOCK)); | |
93 | |
94 grpc::AddInsecureChannelFromFd(grpc_server_.get(), | |
95 rendering_server_listen_fd_); | |
96 } | |
97 | |
98 // This function spawns a Fake Engine as a child process, and sets up the file | |
99 // descriptor mappings for it, resembling the mappings in the Service | |
100 // environment. It also sets up the handler for the EngineReady function that | |
101 // will be called by the Fake Engine upon startup. | |
102 base::Process SpawnEngine() { | |
103 // Find the path of the Engine. | |
104 base::CommandLine current_cmd = *base::CommandLine::ForCurrentProcess(); | |
105 base::FilePath current_program_path = base::MakeAbsoluteFilePath( | |
106 current_cmd.GetProgram()); | |
107 base::FilePath current_dir = current_program_path.DirName(); | |
108 base::FilePath fake_engine_path = current_dir.Append("fake_engine_app"); | |
109 | |
110 base::CommandLine engine_cmd(fake_engine_path); | |
111 | |
112 base::LaunchOptions options; | |
113 base::FileHandleMappingVector fds_to_remap = { | |
114 { engine_listen_fd_, kEngineListenFd }, | |
115 { engine_connect_fd_, kRenderingServerListenFd } | |
116 }; | |
117 options.fds_to_remap = &fds_to_remap; | |
118 | |
119 ON_CALL(mock_lifetime_service_, EngineReady(_, _, _)) | |
120 .WillByDefault( | |
121 testing::Invoke(this, &FakeEngineAppTest::EngineReadyHandler)); | |
122 return base::LaunchProcess(engine_cmd, options); | |
123 } | |
124 | |
125 grpc::Status EngineReadyHandler( | |
126 grpc::ServerContext*, const EngineReadyRequest*, EngineReadyResponse*) { | |
127 engine_ready_.Signal(); | |
128 return grpc::Status::OK; | |
129 } | |
130 | |
131 bool WaitForEngineReady(base::TimeDelta timeout) { | |
132 return engine_ready_.TimedWait(timeout); | |
133 } | |
134 | |
135 std::unique_ptr<Engine::Stub> GetEngineStub() { | |
136 CHECK(!engine_channel_); | |
137 engine_channel_ = grpc::CreateInsecureChannelFromFd( | |
138 "fake_engine", rendering_server_connect_fd_); | |
139 CHECK(engine_channel_); | |
140 return Engine::NewStub(engine_channel_); | |
141 } | |
142 | |
143 private: | |
144 int engine_listen_fd_; | |
145 int rendering_server_connect_fd_; | |
146 | |
147 int rendering_server_listen_fd_; | |
148 int engine_connect_fd_; | |
149 | |
150 std::unique_ptr<grpc::Server> grpc_server_; | |
151 MockLifetimeService mock_lifetime_service_; | |
152 MockLoggingService mock_logging_service_; | |
153 | |
154 std::shared_ptr<grpc::Channel> engine_channel_; | |
155 base::WaitableEvent engine_ready_; | |
156 | |
157 DISALLOW_COPY_AND_ASSIGN(FakeEngineAppTest); | |
158 }; | |
159 | |
160 | |
161 TEST_F(FakeEngineAppTest, Basic) { | |
162 base::Process engine_process = SpawnEngine(); | |
163 EXPECT_TRUE(WaitForEngineReady(base::TimeDelta::FromSeconds(5))); | |
164 | |
165 std::unique_ptr<Engine::Stub> engine_stub = GetEngineStub(); | |
166 { | |
167 CheckHealthRequest request; | |
168 CheckHealthResponse response; | |
169 | |
170 grpc::ClientContext context; | |
171 grpc::Status status = engine_stub->CheckHealth( | |
172 &context, request, &response); | |
173 EXPECT_TRUE(status.ok()); | |
174 EXPECT_EQ(response.status(), CheckHealthResponse::OK); | |
175 } | |
176 | |
177 { | |
178 ShutDownRequest request; | |
179 ShutDownResponse response; | |
180 | |
181 grpc::ClientContext context; | |
182 grpc::CompletionQueue cq; | |
183 // We do async call, as the Fake Engine doesn't send any reply to the | |
184 // ShutDown RPC, so it makes no sense to wait for it. | |
185 auto rpc = engine_stub->AsyncShutDown(&context, request, &cq); | |
186 | |
187 EXPECT_TRUE(engine_process.WaitForExitWithTimeout( | |
188 base::TimeDelta::FromSeconds(5), nullptr /* exit_code */)); | |
189 } | |
190 } | |
191 | |
192 class FakeEngineTestSuite : public base::TestSuite { | |
193 public: | |
194 FakeEngineTestSuite(int argc, char** argv) : base::TestSuite(argc, argv) {} | |
195 | |
196 protected: | |
197 void Initialize() override { | |
198 base::TestSuite::Initialize(); | |
199 } | |
200 | |
201 private: | |
202 DISALLOW_COPY_AND_ASSIGN(FakeEngineTestSuite); | |
203 }; | |
204 | |
205 } // namespace | |
206 | |
207 } // namespace blimp | |
208 | |
209 int main(int argc, char** argv) { | |
210 blimp::FakeEngineTestSuite test_suite(argc, argv); | |
211 return base::LaunchUnitTests( | |
212 argc, argv, | |
213 base::Bind(&base::TestSuite::Run, base::Unretained(&test_suite))); | |
214 } | |
OLD | NEW |