blob: ce580f8693dfae34f989ccf8f9c4a21b67cd0186 [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 "base/at_exit.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "mojo/data_pipe_utils/data_pipe_utils.h"
#include "mojo/message_pump/message_pump_mojo.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "mojo/services/network/interfaces/network_service.mojom.h"
#include "shell/crash/crash_upload.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace breakpad {
namespace {
class MockURLLoader : public mojo::URLLoader {
public:
MockURLLoader() : called_(false) {}
~MockURLLoader() override {}
bool called() const { return called_; }
std::string GetLastContent() {
std::string result;
mojo::common::BlockingCopyToString(body_.Pass(), &result);
return result;
}
private:
// Overriden from mojo::NetworkService.
void Start(mojo::URLRequestPtr request,
const StartCallback& callback) override {
called_ = true;
mojo::URLResponsePtr response = mojo::URLResponse::New();
response->status_code = 200;
body_ = request->body[0].Pass();
callback.Run(response.Pass());
}
void FollowRedirect(const FollowRedirectCallback& callback) override {}
void QueryStatus(const QueryStatusCallback& callback) override {}
bool called_;
mojo::ScopedDataPipeConsumerHandle body_;
};
class MockNetworkService : public mojo::NetworkService {
public:
MockNetworkService() : binding_(&mock_url_loader_) {}
~MockNetworkService() override {}
MockURLLoader* url_loader() { return &mock_url_loader_; }
private:
// Overriden from mojo::NetworkService.
void CreateURLLoader(
mojo::InterfaceRequest<mojo::URLLoader> loader) override {
binding_.Bind(loader.Pass());
}
void GetCookieStore(
mojo::InterfaceRequest<mojo::CookieStore> cookie_store) override {}
void CreateWebSocket(
mojo::InterfaceRequest<mojo::WebSocket> socket) override {}
void CreateTCPBoundSocket(
mojo::NetAddressPtr local_address,
mojo::InterfaceRequest<mojo::TCPBoundSocket> bound_socket,
const CreateTCPBoundSocketCallback& callback) override {}
void CreateTCPConnectedSocket(
mojo::NetAddressPtr remote_address,
mojo::ScopedDataPipeConsumerHandle send_stream,
mojo::ScopedDataPipeProducerHandle receive_stream,
mojo::InterfaceRequest<mojo::TCPConnectedSocket> client_socket,
const CreateTCPConnectedSocketCallback& callback) override {}
void CreateUDPSocket(
mojo::InterfaceRequest<mojo::UDPSocket> socket) override {}
void CreateHttpServer(
mojo::NetAddressPtr local_address,
mojo::InterfaceHandle<mojo::HttpServerDelegate> delegate,
const CreateHttpServerCallback& callback) override {}
void RegisterURLLoaderInterceptor(
mojo::InterfaceHandle<mojo::URLLoaderInterceptorFactory> factory)
override {}
void CreateHostResolver(
mojo::InterfaceRequest<mojo::HostResolver> host_resolver) override {}
MockURLLoader mock_url_loader_;
mojo::Binding<mojo::URLLoader> binding_;
};
std::string GetDumpContent(const base::FilePath& path) {
return "--" + path.BaseName().value() + "\r\n";
}
void CreateValidDump(const base::FilePath& path, const base::Time& time) {
std::string content = GetDumpContent(path);
base::WriteFile(path, content.data(), content.size());
base::TouchFile(path, time, time);
}
int CountFiles(const base::FilePath& path) {
base::FileEnumerator files(path, false, base::FileEnumerator::FILES);
int result = 0;
for (base::FilePath file = files.Next(); !file.empty(); file = files.Next())
++result;
return result;
}
base::FilePath GetSentinelPath(const base::FilePath& path) {
return path.Append("upload.sentinel");
}
class CrashUploadTest : public testing::Test {
public:
CrashUploadTest()
: loop_(mojo::common::MessagePumpMojo::Create()),
binding_(&mock_network_service_, GetProxy(&network_service_)) {}
void SetUp() override { ASSERT_TRUE(dumps_dir_.CreateUniqueTempDir()); }
void RebindNetworkService() {
binding_.Close();
binding_.Bind(GetProxy(&network_service_));
}
protected:
base::ShadowingAtExitManager at_exit_;
base::MessageLoop loop_;
MockNetworkService mock_network_service_;
mojo::NetworkServicePtr network_service_;
mojo::Binding<mojo::NetworkService> binding_;
base::ScopedTempDir dumps_dir_;
};
// Tests that reports can be uploaded and that only the most recent report is
// uploaded.
TEST_F(CrashUploadTest, UploadToServer) {
for (int i = 0; i < 3; ++i) {
CreateValidDump(dumps_dir_.path().Append(base::StringPrintf("%d.dmp", i)),
base::Time::Now() - base::TimeDelta::FromHours(i));
}
UploadCrashes(dumps_dir_.path(), loop_.task_runner().get(),
network_service_.Pass());
loop_.PostTask(FROM_HERE, base::MessageLoop::QuitClosure());
base::RunLoop().Run();
EXPECT_TRUE(mock_network_service_.url_loader()->called());
EXPECT_TRUE(base::PathExists(GetSentinelPath(dumps_dir_.path())));
EXPECT_EQ(GetDumpContent(dumps_dir_.path().Append("0.dmp")),
mock_network_service_.url_loader()->GetLastContent());
EXPECT_EQ(1, CountFiles(dumps_dir_.path()));
}
// Tests that nothing happen when there is nothing to upload.
TEST_F(CrashUploadTest, NothingToUpload) {
UploadCrashes(dumps_dir_.path(), loop_.task_runner().get(),
network_service_.Pass());
loop_.PostTask(FROM_HERE, base::MessageLoop::QuitClosure());
base::RunLoop().Run();
EXPECT_FALSE(mock_network_service_.url_loader()->called());
EXPECT_EQ(0, CountFiles(dumps_dir_.path()));
}
// Tests that spurious reports are deleted but none are uploaded when there is a
// recent sentinel file. Check that the report is uploaded when the sentinel is
// old enough.
TEST_F(CrashUploadTest, UploadGuardedBySentinel) {
for (int i = 0; i < 3; ++i) {
CreateValidDump(dumps_dir_.path().Append(base::StringPrintf("%d.dmp", i)),
base::Time::Now() - base::TimeDelta::FromHours(i));
}
base::FilePath sentinel = GetSentinelPath(dumps_dir_.path());
base::WriteFile(sentinel, nullptr, 0);
base::TouchFile(sentinel,
base::Time::Now() - base::TimeDelta::FromMinutes(30),
base::Time::Now() - base::TimeDelta::FromMinutes(30));
UploadCrashes(dumps_dir_.path(), loop_.task_runner().get(),
network_service_.Pass());
loop_.PostTask(FROM_HERE, base::MessageLoop::QuitClosure());
base::RunLoop().Run();
EXPECT_FALSE(mock_network_service_.url_loader()->called());
EXPECT_TRUE(base::PathExists(GetSentinelPath(dumps_dir_.path())));
EXPECT_EQ(2, CountFiles(dumps_dir_.path()));
// Send the sentinel in the past.
base::TouchFile(sentinel,
base::Time::Now() - base::TimeDelta::FromMinutes(90),
base::Time::Now() - base::TimeDelta::FromMinutes(90));
RebindNetworkService();
UploadCrashes(dumps_dir_.path(), loop_.task_runner().get(),
network_service_.Pass());
loop_.PostTask(FROM_HERE, base::MessageLoop::QuitClosure());
base::RunLoop().Run();
EXPECT_TRUE(mock_network_service_.url_loader()->called());
EXPECT_TRUE(base::PathExists(GetSentinelPath(dumps_dir_.path())));
EXPECT_EQ(GetDumpContent(dumps_dir_.path().Append("0.dmp")),
mock_network_service_.url_loader()->GetLastContent());
EXPECT_EQ(1, CountFiles(dumps_dir_.path()));
}
} // namespace
} // namespace breakpad