1 : // Copyright 2014 Google Inc. All Rights Reserved.
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // http://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 :
15 : #include "syzygy/kasko/reporter.h"
16 :
17 : #include <Windows.h> // NOLINT
18 : #include <Dbgeng.h>
19 : #include <Rpc.h>
20 :
21 : #include <string>
22 :
23 : #include "base/base_switches.h"
24 : #include "base/bind.h"
25 : #include "base/command_line.h"
26 : #include "base/files/file_path.h"
27 : #include "base/files/scoped_temp_dir.h"
28 : #include "base/memory/scoped_ptr.h"
29 : #include "base/process/kill.h"
30 : #include "base/process/launch.h"
31 : #include "base/strings/string16.h"
32 : #include "base/strings/string_number_conversions.h"
33 : #include "base/strings/utf_string_conversions.h"
34 : #include "base/synchronization/waitable_event.h"
35 : #include "base/test/multiprocess_test.h"
36 : #include "base/time/time.h"
37 : #include "gtest/gtest.h"
38 : #include "syzygy/common/rpc/helpers.h"
39 : #include "syzygy/kasko/kasko_rpc.h"
40 : #include "syzygy/kasko/testing/minidump_unittest_helpers.h"
41 : #include "syzygy/kasko/testing/test_server.h"
42 : #include "syzygy/kasko/testing/upload_observer.h"
43 : #include "testing/multiprocess_func_list.h"
44 :
45 : // The test server will respond to POSTs to /crash by writing all parameters to
46 : // a report directory. Each file in the directory has the name of a parameter
47 : // and the parameter value as its contents.
48 : //
49 : // This test instantiates a reporter process, points it at a test server, and
50 : // then monitors the server's "incoming" director for new files named
51 : // Reporter::kMinidumpUploadFilePart.
52 : //
53 : // These tests are flaky on the bots. They appear to occasionally hang.
54 : // Presumably there is some kind of race condition.
55 : // TODO(erikwright): Debug these on the bots, add additional tracing, or do
56 : // whatever's necessary to diagnose and deflake these tests.
57 : namespace kasko {
58 :
59 : namespace {
60 :
61 : const char kCrashKey1Name[] = "foo";
62 : const char kCrashKey1Value[] = "bar";
63 : const char kCrashKey2Name[] = "hello";
64 : const char kCrashKey2Value[] = "world";
65 :
66 : const char kEndpointSwitch[] = "endpoint";
67 : const char kReadyEventSwitch[] = "ready-event";
68 :
69 : // Signals an event named by kReadyEventSwitch, then blocks indefinitely.
70 E : MULTIPROCESS_TEST_MAIN(ReporterTestBlockingProcess) {
71 : // Read the caller-supplied parameters.
72 E : base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
73 : base::string16 ready_event_name =
74 E : base::ASCIIToUTF16(cmd_line->GetSwitchValueASCII(kReadyEventSwitch));
75 : base::WaitableEvent ready_event(
76 E : ::OpenEvent(EVENT_MODIFY_STATE, FALSE, ready_event_name.c_str()));
77 E : ready_event.Signal();
78 E : ::Sleep(INFINITE);
79 E : return 0;
80 E : }
81 :
82 : // Invokes SendDiagnosticReport via the RPC endpoint named by kEndpointSwitch.
83 E : MULTIPROCESS_TEST_MAIN(ReporterTestClientProcess) {
84 : // Read the caller-supplied parameters.
85 E : base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
86 : base::string16 endpoint =
87 E : base::ASCIIToUTF16(cmd_line->GetSwitchValueASCII(kEndpointSwitch));
88 :
89 E : common::rpc::ScopedRpcBinding rpc_binding;
90 E : if (!rpc_binding.Open(L"ncalrpc", endpoint))
91 i : return 1;
92 :
93 : CrashKey crash_keys[] = {
94 : {reinterpret_cast<const signed char*>(kCrashKey1Name),
95 : reinterpret_cast<const signed char*>(kCrashKey1Value)},
96 : {reinterpret_cast<const signed char*>(kCrashKey2Name),
97 E : reinterpret_cast<const signed char*>(kCrashKey2Value)}};
98 :
99 E : std::string protobuf = "protobuf";
100 :
101 : common::rpc::RpcStatus status = common::rpc::InvokeRpc(
102 : KaskoClient_SendDiagnosticReport, rpc_binding.Get(), NULL, 0, SMALL_DUMP,
103 : protobuf.length(), reinterpret_cast<const signed char*>(protobuf.c_str()),
104 E : arraysize(crash_keys), crash_keys);
105 E : if (status.exception_occurred || !status.succeeded())
106 i : return 1;
107 E : return 0;
108 E : }
109 :
110 : // Invokes |instance|->SendReportForProcess() using |child_process|.
111 : void InvokeSendReportForProcess(Reporter* instance,
112 E : base::ProcessHandle child_process) {
113 E : std::map<base::string16, base::string16> crash_keys_in;
114 : crash_keys_in[base::UTF8ToUTF16(kCrashKey1Name)] =
115 E : base::UTF8ToUTF16(kCrashKey1Value);
116 : crash_keys_in[base::UTF8ToUTF16(kCrashKey2Name)] =
117 E : base::UTF8ToUTF16(kCrashKey2Value);
118 :
119 E : instance->SendReportForProcess(child_process, SMALL_DUMP_TYPE, crash_keys_in);
120 E : }
121 :
122 : // Verifies that the uploaded minidump is plausibly a dump of this test process.
123 : void ValidateMinidump(IDebugClient4* debug_client,
124 : IDebugControl* debug_control,
125 E : IDebugSymbols* debug_symbols) {
126 : ASSERT_HRESULT_SUCCEEDED(
127 E : debug_symbols->GetModuleByModuleName("kasko_unittests", 0, NULL, NULL));
128 E : }
129 :
130 : } // namespace
131 :
132 : class ReporterTest : public ::testing::Test {
133 : public:
134 E : ReporterTest()
135 : : test_instance_key_(base::UintToString(base::GetCurrentProcId())) {}
136 :
137 E : virtual void SetUp() override {
138 E : LOG(INFO) << "Starting server.";
139 E : ASSERT_TRUE(server_.Start());
140 E : LOG(INFO) << "Server started.";
141 E : ASSERT_TRUE(temp_directory_.CreateUniqueTempDir());
142 E : LOG(INFO) << "Temp directory: " << temp_directory_.path().value();
143 E : }
144 :
145 : protected:
146 : // Launches a child process that will invoke SendDiagnosticReport using the
147 : // RPC endpoint returned by endpoint().
148 E : void InvokeRpcFromChildProcess() {
149 : base::CommandLine client_command_line =
150 E : base::GetMultiProcessTestChildBaseCommandLine();
151 : client_command_line.AppendSwitchASCII(switches::kTestChildProcess,
152 E : "ReporterTestClientProcess");
153 : client_command_line.AppendSwitchASCII(kEndpointSwitch,
154 E : base::UTF16ToASCII(endpoint()));
155 : base::ProcessHandle client_process;
156 E : ASSERT_TRUE(base::LaunchProcess(client_command_line, base::LaunchOptions(),
157 : &client_process));
158 :
159 E : int exit_code = 0;
160 E : ASSERT_TRUE(base::WaitForExitCode(client_process, &exit_code));
161 E : ASSERT_EQ(0, exit_code);
162 E : }
163 :
164 : // Launches a child process and passes its handle to |callback|. Then kills
165 : // the child process.
166 : void DoWithChildProcess(
167 E : const base::Callback<void(base::ProcessHandle)>& callback) {
168 E : std::string ready_event_name = "reporter_test_ready_" + test_instance_key_;
169 : base::WaitableEvent ready_event(::CreateEvent(
170 E : NULL, FALSE, FALSE, base::ASCIIToUTF16(ready_event_name).c_str()));
171 :
172 : base::CommandLine child_command_line =
173 E : base::GetMultiProcessTestChildBaseCommandLine();
174 : child_command_line.AppendSwitchASCII(switches::kTestChildProcess,
175 E : "ReporterTestBlockingProcess");
176 E : child_command_line.AppendSwitchASCII(kReadyEventSwitch, ready_event_name);
177 : base::ProcessHandle child_process;
178 E : ASSERT_TRUE(base::LaunchProcess(child_command_line, base::LaunchOptions(),
179 : &child_process));
180 E : base::win::ScopedHandle scoped_child_process(child_process);
181 E : ready_event.Wait();
182 E : callback.Run(child_process);
183 E : ASSERT_TRUE(base::KillProcess(child_process, 0, true));
184 E : }
185 :
186 E : uint16_t server_port() { return server_.port(); }
187 :
188 E : base::string16 endpoint() {
189 E : return base::ASCIIToUTF16("reporter_test_endpoint_" + test_instance_key_);
190 E : }
191 :
192 : // This directory is intentionally non-existant to verify that the reporter
193 : // creates the target directory as needed.
194 E : base::FilePath data_directory() {
195 E : return temp_directory_.path().Append(L"Crash Reports");
196 E : }
197 :
198 : // This directory is intentionally non-existant to verify that the reporter
199 : // creates the target directory as needed.
200 E : base::FilePath permanent_failure_directory() {
201 E : return temp_directory_.path().Append(L"Permanent Failure");
202 E : }
203 :
204 E : base::FilePath upload_directory() { return server_.incoming_directory(); }
205 :
206 : private:
207 : testing::TestServer server_;
208 : base::ScopedTempDir temp_directory_;
209 : std::string test_instance_key_;
210 :
211 : DISALLOW_COPY_AND_ASSIGN(ReporterTest);
212 : };
213 :
214 E : TEST_F(ReporterTest, BasicTest) {
215 : scoped_ptr<Reporter> instance(Reporter::Create(
216 : endpoint(),
217 : L"http://127.0.0.1:" + base::UintToString16(server_port()) + L"/crash",
218 : data_directory(), permanent_failure_directory(),
219 : base::TimeDelta::FromMilliseconds(1),
220 E : base::TimeDelta::FromMilliseconds(1)));
221 :
222 E : ASSERT_TRUE(instance);
223 :
224 : testing::UploadObserver upload_observer(upload_directory(),
225 E : permanent_failure_directory());
226 :
227 E : ASSERT_NO_FATAL_FAILURE(InvokeRpcFromChildProcess());
228 :
229 E : base::FilePath minidump_path;
230 E : std::map<std::string, std::string> crash_keys;
231 E : bool upload_success = false;
232 :
233 E : upload_observer.WaitForUpload(&minidump_path, &crash_keys, &upload_success);
234 :
235 E : ASSERT_TRUE(upload_success);
236 : EXPECT_HRESULT_SUCCEEDED(
237 E : testing::VisitMinidump(minidump_path, base::Bind(&ValidateMinidump)));
238 :
239 E : Reporter::Shutdown(instance.Pass());
240 E : }
241 :
242 E : TEST_F(ReporterTest, SendReportForProcessTest) {
243 : scoped_ptr<Reporter> instance(Reporter::Create(
244 : endpoint(),
245 : L"http://127.0.0.1:" + base::UintToString16(server_port()) + L"/crash",
246 : data_directory(), permanent_failure_directory(),
247 : base::TimeDelta::FromMilliseconds(1),
248 E : base::TimeDelta::FromMilliseconds(1)));
249 :
250 E : ASSERT_TRUE(instance);
251 :
252 : testing::UploadObserver upload_observer(upload_directory(),
253 E : permanent_failure_directory());
254 :
255 : ASSERT_NO_FATAL_FAILURE(DoWithChildProcess(base::Bind(
256 E : &InvokeSendReportForProcess, base::Unretained(instance.get()))));
257 :
258 E : base::FilePath minidump_path;
259 E : std::map<std::string, std::string> crash_keys;
260 E : bool upload_success = false;
261 E : upload_observer.WaitForUpload(&minidump_path, &crash_keys, &upload_success);
262 :
263 E : ASSERT_TRUE(upload_success);
264 : EXPECT_HRESULT_SUCCEEDED(
265 E : testing::VisitMinidump(minidump_path, base::Bind(&ValidateMinidump)));
266 :
267 E : Reporter::Shutdown(instance.Pass());
268 E : }
269 :
270 E : TEST_F(ReporterTest, PermanentFailureTest) {
271 : scoped_ptr<Reporter> instance(Reporter::Create(
272 : endpoint(),
273 : L"http://127.0.0.1:" + base::UintToString16(server_port()) +
274 : L"/crash_failure",
275 : data_directory(), permanent_failure_directory(),
276 : base::TimeDelta::FromMilliseconds(1),
277 E : base::TimeDelta::FromMilliseconds(1)));
278 :
279 E : ASSERT_TRUE(instance);
280 :
281 : testing::UploadObserver upload_observer(upload_directory(),
282 E : permanent_failure_directory());
283 :
284 E : ASSERT_NO_FATAL_FAILURE(InvokeRpcFromChildProcess());
285 :
286 E : base::FilePath minidump_path;
287 E : std::map<std::string, std::string> crash_keys;
288 E : bool upload_success = false;
289 :
290 E : upload_observer.WaitForUpload(&minidump_path, &crash_keys, &upload_success);
291 :
292 E : ASSERT_FALSE(upload_success);
293 : EXPECT_HRESULT_SUCCEEDED(
294 E : testing::VisitMinidump(minidump_path, base::Bind(&ValidateMinidump)));
295 :
296 E : Reporter::Shutdown(instance.Pass());
297 E : }
298 :
299 : } // namespace kasko
|