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 <memory>
22 : #include <string>
23 :
24 : #include "base/base_switches.h"
25 : #include "base/bind.h"
26 : #include "base/command_line.h"
27 : #include "base/files/file_path.h"
28 : #include "base/files/scoped_temp_dir.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/minidump_request.h"
41 : #include "syzygy/kasko/version.h"
42 : #include "syzygy/kasko/testing/minidump_unittest_helpers.h"
43 : #include "syzygy/kasko/testing/test_server.h"
44 : #include "syzygy/kasko/testing/upload_observer.h"
45 : #include "testing/multiprocess_func_list.h"
46 :
47 : // The test server will respond to POSTs to /crash by writing all parameters to
48 : // a report directory. Each file in the directory has the name of a parameter
49 : // and the parameter value as its contents.
50 : //
51 : // This test instantiates a reporter process, points it at a test server, and
52 : // then monitors the server's "incoming" director for new files named
53 : // Reporter::kMinidumpUploadFilePart.
54 : //
55 : // These tests are flaky on the bots. They appear to occasionally hang.
56 : // Presumably there is some kind of race condition.
57 : // TODO(erikwright): Debug these on the bots, add additional tracing, or do
58 : // whatever's necessary to diagnose and deflake these tests.
59 m : namespace kasko {
60 :
61 m : namespace {
62 :
63 m : const base::char16 kCrashKey1Name[] = L"foo";
64 m : const base::char16 kCrashKey1Value[] = L"bar";
65 m : const base::char16 kCrashKey2Name[] = L"hello";
66 m : const base::char16 kCrashKey2Value[] = L"world";
67 :
68 m : const char kEndpointSwitch[] = "endpoint";
69 m : const char kReadyEventSwitch[] = "ready-event";
70 :
71 : // Signals an event named by kReadyEventSwitch, then blocks indefinitely.
72 m : MULTIPROCESS_TEST_MAIN(ReporterTestBlockingProcess) {
73 : // Read the caller-supplied parameters.
74 m : base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
75 m : base::string16 ready_event_name =
76 m : base::ASCIIToUTF16(cmd_line->GetSwitchValueASCII(kReadyEventSwitch));
77 m : base::WaitableEvent ready_event(base::win::ScopedHandle(
78 m : ::OpenEvent(EVENT_MODIFY_STATE, FALSE, ready_event_name.c_str())));
79 m : ready_event.Signal();
80 m : ::Sleep(INFINITE);
81 m : return 0;
82 m : }
83 :
84 : // Invokes SendDiagnosticReport via the RPC endpoint named by kEndpointSwitch.
85 m : MULTIPROCESS_TEST_MAIN(ReporterTestClientProcess) {
86 : // Read the caller-supplied parameters.
87 m : base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
88 m : base::string16 endpoint =
89 m : base::ASCIIToUTF16(cmd_line->GetSwitchValueASCII(kEndpointSwitch));
90 m : common::rpc::ScopedRpcBinding rpc_binding;
91 m : if (!rpc_binding.Open(L"ncalrpc", endpoint)) {
92 m : PLOG(ERROR) << "ScopedRpcBinding::Open";
93 m : return 1;
94 m : }
95 :
96 m : CrashKey crash_keys[] = {{kCrashKey1Name, kCrashKey1Value},
97 m : {kCrashKey2Name, kCrashKey2Value}};
98 m : ::MinidumpRequest rpc_request = {0, 0, SMALL_DUMP,
99 m : 0, nullptr, arraysize(crash_keys),
100 m : crash_keys, 0, nullptr};
101 :
102 m : common::rpc::RpcStatus status = common::rpc::InvokeRpc(
103 m : KaskoClient_SendDiagnosticReport, rpc_binding.Get(), rpc_request);
104 m : if (status.exception_occurred || !status.succeeded()) {
105 m : PLOG(ERROR) << "InvokeRpc";
106 m : return 1;
107 m : }
108 m : return 0;
109 m : }
110 :
111 : // Invokes |instance|->SendReportForProcess() using |child_process|.
112 m : void InvokeSendReportForProcess(Reporter* instance,
113 m : base::ProcessHandle child_process) {
114 m : MinidumpRequest request;
115 m : request.crash_keys.push_back(
116 m : MinidumpRequest::CrashKey(kCrashKey1Name, kCrashKey1Value));
117 m : request.crash_keys.push_back(
118 m : MinidumpRequest::CrashKey(kCrashKey2Name, kCrashKey2Value));
119 :
120 m : instance->SendReportForProcess(child_process, 0, request);
121 m : }
122 :
123 : // Verifies that the uploaded minidump is plausibly a dump of this test process.
124 m : void ValidateMinidump(IDebugClient4* debug_client,
125 m : IDebugControl* debug_control,
126 m : IDebugSymbols* debug_symbols) {
127 m : ASSERT_HRESULT_SUCCEEDED(
128 m : debug_symbols->GetModuleByModuleName("kasko_unittests", 0, NULL, NULL));
129 m : }
130 :
131 m : void OnUpload(base::string16* report_id_out,
132 m : const base::string16& report_id,
133 m : const base::FilePath& minidump_path,
134 m : const std::map<base::string16, base::string16>& crash_keys) {
135 m : *report_id_out = report_id;
136 m : ASSERT_FALSE(report_id.empty());
137 m : ASSERT_FALSE(minidump_path.empty());
138 m : }
139 :
140 m : } // namespace
141 :
142 m : class ReporterTest : public ::testing::Test {
143 m : public:
144 m : ReporterTest()
145 m : : test_instance_key_(base::UintToString(base::GetCurrentProcId())) {}
146 :
147 m : void SetUp() override {
148 m : ASSERT_TRUE(server_.Start());
149 m : ASSERT_TRUE(temp_directory_.CreateUniqueTempDir());
150 m : }
151 :
152 m : protected:
153 : // Launches a child process that will invoke SendDiagnosticReport using the
154 : // RPC endpoint returned by endpoint().
155 m : void InvokeRpcFromChildProcess() {
156 m : base::CommandLine client_command_line =
157 m : base::GetMultiProcessTestChildBaseCommandLine();
158 m : client_command_line.AppendSwitchASCII(switches::kTestChildProcess,
159 m : "ReporterTestClientProcess");
160 m : client_command_line.AppendSwitchASCII(kEndpointSwitch,
161 m : base::UTF16ToASCII(endpoint()));
162 m : base::Process client_process = base::LaunchProcess(client_command_line,
163 m : base::LaunchOptions());
164 m : ASSERT_TRUE(client_process.IsValid());
165 :
166 m : int exit_code = 0;
167 m : ASSERT_TRUE(client_process.WaitForExit(&exit_code));
168 m : ASSERT_EQ(0, exit_code);
169 m : }
170 :
171 : // Launches a child process and passes its handle to |callback|. Then kills
172 : // the child process.
173 m : void DoWithChildProcess(
174 m : const base::Callback<void(base::ProcessHandle)>& callback) {
175 m : std::string ready_event_name = "reporter_test_ready_" + test_instance_key_;
176 m : base::WaitableEvent ready_event(base::win::ScopedHandle(::CreateEvent(
177 m : NULL, FALSE, FALSE, base::ASCIIToUTF16(ready_event_name).c_str())));
178 :
179 m : base::CommandLine child_command_line =
180 m : base::GetMultiProcessTestChildBaseCommandLine();
181 m : child_command_line.AppendSwitchASCII(switches::kTestChildProcess,
182 m : "ReporterTestBlockingProcess");
183 m : child_command_line.AppendSwitchASCII(kReadyEventSwitch, ready_event_name);
184 m : base::Process child_process = base::LaunchProcess(child_command_line,
185 m : base::LaunchOptions());
186 m : ASSERT_TRUE(child_process.IsValid());
187 m : ready_event.Wait();
188 m : callback.Run(child_process.Handle());
189 m : ASSERT_TRUE(child_process.Terminate(0, true));
190 m : }
191 :
192 m : uint16_t server_port() { return server_.port(); }
193 :
194 m : base::string16 endpoint() {
195 m : return base::ASCIIToUTF16("reporter_test_endpoint_" + test_instance_key_);
196 m : }
197 :
198 : // This directory is intentionally non-existant to verify that the reporter
199 : // creates the target directory as needed.
200 m : base::FilePath data_directory() {
201 m : return temp_directory_.path().Append(L"Crash Reports");
202 m : }
203 :
204 : // This directory is intentionally non-existant to verify that the reporter
205 : // creates the target directory as needed.
206 m : base::FilePath permanent_failure_directory() {
207 m : return temp_directory_.path().Append(L"Permanent Failure");
208 m : }
209 :
210 m : base::FilePath upload_directory() { return server_.incoming_directory(); }
211 :
212 m : private:
213 m : testing::TestServer server_;
214 m : base::ScopedTempDir temp_directory_;
215 m : std::string test_instance_key_;
216 :
217 m : DISALLOW_COPY_AND_ASSIGN(ReporterTest);
218 m : };
219 :
220 m : TEST_F(ReporterTest, BasicTest) {
221 m : base::string16 report_id;
222 m : std::unique_ptr<Reporter> instance(Reporter::Create(
223 m : endpoint(),
224 m : L"http://127.0.0.1:" + base::UintToString16(server_port()) + L"/crash",
225 m : data_directory(), permanent_failure_directory(),
226 m : base::TimeDelta::FromMilliseconds(1),
227 m : base::TimeDelta::FromMilliseconds(1),
228 m : base::Bind(&OnUpload, base::Unretained(&report_id))));
229 :
230 m : ASSERT_TRUE(instance);
231 :
232 m : testing::UploadObserver upload_observer(upload_directory(),
233 m : permanent_failure_directory());
234 :
235 m : ASSERT_NO_FATAL_FAILURE(InvokeRpcFromChildProcess());
236 :
237 m : base::FilePath minidump_path;
238 m : std::map<std::string, std::string> crash_keys;
239 m : bool upload_success = false;
240 :
241 m : upload_observer.WaitForUpload(&minidump_path, &crash_keys, &upload_success);
242 :
243 m : EXPECT_TRUE(upload_success);
244 m : EXPECT_HRESULT_SUCCEEDED(
245 m : testing::VisitMinidump(minidump_path, base::Bind(&ValidateMinidump)));
246 m : Reporter::Shutdown(std::move(instance));
247 :
248 m : auto entry = crash_keys.find(base::UTF16ToASCII(kCrashKey1Name));
249 m : ASSERT_NE(entry, crash_keys.end());
250 m : ASSERT_EQ(base::UTF16ToASCII(kCrashKey1Value), entry->second);
251 m : entry = crash_keys.find(base::UTF16ToASCII(kCrashKey2Name));
252 m : ASSERT_NE(entry, crash_keys.end());
253 m : ASSERT_EQ(base::UTF16ToASCII(kCrashKey2Value), entry->second);
254 m : entry =
255 m : crash_keys.find(base::UTF16ToASCII(Reporter::kKaskoUploadedByVersion));
256 m : ASSERT_NE(entry, crash_keys.end());
257 m : ASSERT_EQ(KASKO_VERSION_STRING, entry->second);
258 m : entry =
259 m : crash_keys.find(base::UTF16ToASCII(Reporter::kKaskoGeneratedByVersion));
260 m : ASSERT_NE(entry, crash_keys.end());
261 m : ASSERT_EQ(KASKO_VERSION_STRING, entry->second);
262 :
263 m : ASSERT_FALSE(report_id.empty());
264 m : }
265 :
266 m : TEST_F(ReporterTest, NoCallback) {
267 m : std::unique_ptr<Reporter> instance(Reporter::Create(
268 m : endpoint(),
269 m : L"http://127.0.0.1:" + base::UintToString16(server_port()) + L"/crash",
270 m : data_directory(), permanent_failure_directory(),
271 m : base::TimeDelta::FromMilliseconds(1),
272 m : base::TimeDelta::FromMilliseconds(1), Reporter::OnUploadCallback()));
273 :
274 m : ASSERT_TRUE(instance);
275 :
276 m : testing::UploadObserver upload_observer(upload_directory(),
277 m : permanent_failure_directory());
278 :
279 m : ASSERT_NO_FATAL_FAILURE(InvokeRpcFromChildProcess());
280 :
281 m : base::FilePath minidump_path;
282 m : std::map<std::string, std::string> crash_keys;
283 m : bool upload_success = false;
284 :
285 m : upload_observer.WaitForUpload(&minidump_path, &crash_keys, &upload_success);
286 :
287 m : EXPECT_TRUE(upload_success);
288 m : EXPECT_HRESULT_SUCCEEDED(
289 m : testing::VisitMinidump(minidump_path, base::Bind(&ValidateMinidump)));
290 :
291 m : Reporter::Shutdown(std::move(instance));
292 m : }
293 :
294 m : TEST_F(ReporterTest, SendReportForProcessTest) {
295 m : base::string16 report_id;
296 m : std::unique_ptr<Reporter> instance(Reporter::Create(
297 m : endpoint(),
298 m : L"http://127.0.0.1:" + base::UintToString16(server_port()) + L"/crash",
299 m : data_directory(), permanent_failure_directory(),
300 m : base::TimeDelta::FromMilliseconds(1),
301 m : base::TimeDelta::FromMilliseconds(1),
302 m : base::Bind(&OnUpload, base::Unretained(&report_id))));
303 :
304 m : ASSERT_TRUE(instance);
305 :
306 m : testing::UploadObserver upload_observer(upload_directory(),
307 m : permanent_failure_directory());
308 :
309 m : ASSERT_NO_FATAL_FAILURE(DoWithChildProcess(base::Bind(
310 m : &InvokeSendReportForProcess, base::Unretained(instance.get()))));
311 :
312 m : base::FilePath minidump_path;
313 m : std::map<std::string, std::string> crash_keys;
314 m : bool upload_success = false;
315 m : upload_observer.WaitForUpload(&minidump_path, &crash_keys, &upload_success);
316 :
317 m : EXPECT_TRUE(upload_success);
318 m : EXPECT_HRESULT_SUCCEEDED(
319 m : testing::VisitMinidump(minidump_path, base::Bind(&ValidateMinidump)));
320 :
321 m : Reporter::Shutdown(std::move(instance));
322 :
323 m : ASSERT_FALSE(report_id.empty());
324 m : }
325 :
326 m : TEST_F(ReporterTest, PermanentFailureTest) {
327 m : base::string16 report_id;
328 m : std::unique_ptr<Reporter> instance(Reporter::Create(
329 m : endpoint(), L"http://127.0.0.1:" + base::UintToString16(server_port()) +
330 m : L"/crash_failure",
331 m : data_directory(), permanent_failure_directory(),
332 m : base::TimeDelta::FromMilliseconds(1),
333 m : base::TimeDelta::FromMilliseconds(1),
334 m : base::Bind(&OnUpload, base::Unretained(&report_id))));
335 :
336 m : ASSERT_TRUE(instance);
337 :
338 m : testing::UploadObserver upload_observer(upload_directory(),
339 m : permanent_failure_directory());
340 :
341 m : ASSERT_NO_FATAL_FAILURE(InvokeRpcFromChildProcess());
342 :
343 m : base::FilePath minidump_path;
344 m : std::map<std::string, std::string> crash_keys;
345 m : bool upload_success = false;
346 :
347 m : upload_observer.WaitForUpload(&minidump_path, &crash_keys, &upload_success);
348 :
349 m : EXPECT_FALSE(upload_success);
350 m : EXPECT_HRESULT_SUCCEEDED(
351 m : testing::VisitMinidump(minidump_path, base::Bind(&ValidateMinidump)));
352 :
353 m : Reporter::Shutdown(std::move(instance));
354 :
355 m : ASSERT_TRUE(report_id.empty());
356 m : }
357 :
358 m : } // namespace kasko
|