blob: 068361ffae2377bcf44cba28324a0f4cef51106d [file] [log] [blame]
// Copyright 2017 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 "content/browser/webrtc/webrtc_event_log_manager.h"
#include <algorithm>
#include <memory>
#include <numeric>
#include <string>
#include <vector>
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/memory/ptr_util.h"
#include "base/optional.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/test/gtest_util.h"
#include "base/test/simple_test_clock.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#if defined(OS_WIN)
#define IntToStringType base::IntToString16
#else
#define IntToStringType base::IntToString
#endif
namespace content {
using ::testing::_;
using ::testing::Invoke;
using ::testing::NiceMock;
using ::testing::StrictMock;
using PeerConnectionKey = WebRtcEventLogPeerConnectionKey;
namespace {
class MockWebRtcLocalEventLogsObserver : public WebRtcLocalEventLogsObserver {
public:
~MockWebRtcLocalEventLogsObserver() override = default;
MOCK_METHOD2(OnLocalLogStarted, void(PeerConnectionKey, base::FilePath));
MOCK_METHOD1(OnLocalLogStopped, void(PeerConnectionKey));
};
auto SaveKeyAndFilePathTo(base::Optional<PeerConnectionKey>* key_output,
base::Optional<base::FilePath>* file_path_output) {
return [key_output, file_path_output](PeerConnectionKey key,
base::FilePath file_path) {
*key_output = key;
*file_path_output = file_path;
};
}
} // namespace
class WebRtcEventLogManagerTest : public ::testing::Test {
protected:
WebRtcEventLogManagerTest()
: run_loop_(std::make_unique<base::RunLoop>()),
manager_(new WebRtcEventLogManager) {
EXPECT_TRUE(
base::CreateNewTempDirectory(FILE_PATH_LITERAL(""), &base_dir_));
if (base_dir_.empty()) {
EXPECT_TRUE(false);
return;
}
base_path_ = base_dir_.Append(FILE_PATH_LITERAL("webrtc_event_log"));
}
~WebRtcEventLogManagerTest() override {
DestroyUnitUnderTest();
if (!base_dir_.empty()) {
EXPECT_TRUE(base::DeleteFile(base_dir_, true));
}
}
void DestroyUnitUnderTest() {
if (manager_ != nullptr) {
SetLocalLogsObserver(nullptr);
delete manager_; // Raw pointer; see definition for rationale.
manager_ = nullptr;
}
}
void WaitForReply() {
run_loop_->Run();
run_loop_.reset(new base::RunLoop); // Allow re-blocking.
}
void VoidReply() { run_loop_->QuitWhenIdle(); }
base::OnceClosure VoidReplyClosure() {
return base::BindOnce(&WebRtcEventLogManagerTest::VoidReply,
base::Unretained(this));
}
void BoolReply(bool* output, bool value) {
*output = value;
run_loop_->QuitWhenIdle();
}
base::OnceCallback<void(bool)> BoolReplyClosure(bool* output) {
return base::BindOnce(&WebRtcEventLogManagerTest::BoolReply,
base::Unretained(this), output);
}
bool PeerConnectionAdded(int render_process_id, int lid) {
bool result;
manager_->PeerConnectionAdded(render_process_id, lid,
BoolReplyClosure(&result));
WaitForReply();
return result;
}
bool PeerConnectionRemoved(int render_process_id, int lid) {
bool result;
manager_->PeerConnectionRemoved(render_process_id, lid,
BoolReplyClosure(&result));
WaitForReply();
return result;
}
bool EnableLocalLogging(
size_t max_size_bytes = kWebRtcEventLogManagerUnlimitedFileSize) {
return EnableLocalLogging(base_path_, max_size_bytes);
}
bool EnableLocalLogging(
base::FilePath base_path,
size_t max_size_bytes = kWebRtcEventLogManagerUnlimitedFileSize) {
bool result;
manager_->EnableLocalLogging(base_path, max_size_bytes,
BoolReplyClosure(&result));
WaitForReply();
return result;
}
bool DisableLocalLogging() {
bool result;
manager_->DisableLocalLogging(BoolReplyClosure(&result));
WaitForReply();
return result;
}
void SetLocalLogsObserver(WebRtcLocalEventLogsObserver* observer) {
manager_->SetLocalLogsObserver(observer, VoidReplyClosure());
WaitForReply();
}
bool OnWebRtcEventLogWrite(int render_process_id,
int lid,
const std::string& output) {
bool result;
manager_->OnWebRtcEventLogWrite(render_process_id, lid, output,
BoolReplyClosure(&result));
WaitForReply();
return result;
}
void FreezeClockAt(const base::Time::Exploded& frozen_time_exploded) {
base::Time frozen_time;
ASSERT_TRUE(
base::Time::FromLocalExploded(frozen_time_exploded, &frozen_time));
frozen_clock_.SetNow(frozen_time);
manager_->InjectClockForTesting(&frozen_clock_);
}
// Common default values.
static constexpr int kRenderProcessId = 23;
static constexpr int kPeerConnectionId = 478;
// Testing utilities.
content::TestBrowserThreadBundle test_browser_thread_bundle_;
std::unique_ptr<base::RunLoop> run_loop_; // New instance allows reblocking.
base::SimpleTestClock frozen_clock_;
// Unit under test. (Cannot be a unique_ptr because of its private ctor/dtor.)
WebRtcEventLogManager* manager_;
base::FilePath base_dir_; // The directory where we'll save log files.
base::FilePath base_path_; // base_dir_ + log files' name prefix.
};
TEST_F(WebRtcEventLogManagerTest, LocalLogPeerConnectionAddedReturnsTrue) {
EXPECT_TRUE(PeerConnectionAdded(kRenderProcessId, kPeerConnectionId));
}
TEST_F(WebRtcEventLogManagerTest,
LocalLogPeerConnectionAddedReturnsFalseIfAlreadyAdded) {
ASSERT_TRUE(PeerConnectionAdded(kRenderProcessId, kPeerConnectionId));
EXPECT_FALSE(PeerConnectionAdded(kRenderProcessId, kPeerConnectionId));
}
TEST_F(WebRtcEventLogManagerTest, LocalLogPeerConnectionRemovedReturnsTrue) {
ASSERT_TRUE(PeerConnectionAdded(kRenderProcessId, kPeerConnectionId));
EXPECT_TRUE(PeerConnectionRemoved(kRenderProcessId, kPeerConnectionId));
}
TEST_F(WebRtcEventLogManagerTest,
LocalLogPeerConnectionRemovedReturnsFalseIfNeverAdded) {
EXPECT_FALSE(PeerConnectionRemoved(kRenderProcessId, kPeerConnectionId));
}
TEST_F(WebRtcEventLogManagerTest,
LocalLogPeerConnectionRemovedReturnsFalseIfAlreadyRemoved) {
ASSERT_TRUE(PeerConnectionAdded(kRenderProcessId, kPeerConnectionId));
ASSERT_TRUE(PeerConnectionRemoved(kRenderProcessId, kPeerConnectionId));
EXPECT_FALSE(PeerConnectionRemoved(kRenderProcessId, kPeerConnectionId));
}
TEST_F(WebRtcEventLogManagerTest, LocalLogEnableLocalLoggingReturnsTrue) {
EXPECT_TRUE(EnableLocalLogging());
}
TEST_F(WebRtcEventLogManagerTest,
LocalLogEnableLocalLoggingReturnsFalseIfCalledWhenAlreadyEnabled) {
ASSERT_TRUE(EnableLocalLogging());
EXPECT_FALSE(EnableLocalLogging());
}
TEST_F(WebRtcEventLogManagerTest, LocalLogDisableLocalLoggingReturnsTrue) {
ASSERT_TRUE(EnableLocalLogging());
EXPECT_TRUE(DisableLocalLogging());
}
TEST_F(WebRtcEventLogManagerTest,
LocalLogDisableLocalLoggingReturnsIfNeverEnabled) {
EXPECT_FALSE(DisableLocalLogging());
}
TEST_F(WebRtcEventLogManagerTest,
LocalLogDisableLocalLoggingReturnsIfAlreadyDisabled) {
ASSERT_TRUE(EnableLocalLogging());
ASSERT_TRUE(DisableLocalLogging());
EXPECT_FALSE(DisableLocalLogging());
}
TEST_F(WebRtcEventLogManagerTest,
OnWebRtcEventLogWriteReturnsFalseWhenAllLoggingDisabled) {
// Note that EnableLocalLogging() wasn't called.
ASSERT_TRUE(PeerConnectionAdded(kRenderProcessId, kPeerConnectionId));
ASSERT_FALSE(
OnWebRtcEventLogWrite(kRenderProcessId, kPeerConnectionId, "log"));
}
TEST_F(WebRtcEventLogManagerTest,
OnWebRtcEventLogWriteReturnsFalseForUnknownPeerConnection) {
ASSERT_TRUE(EnableLocalLogging());
// Note that PeerConnectionAdded() wasn't called.
ASSERT_FALSE(
OnWebRtcEventLogWrite(kRenderProcessId, kPeerConnectionId, "log"));
}
TEST_F(WebRtcEventLogManagerTest,
OnWebRtcEventLogWriteReturnsTrueWhenPeerConnectionKnownLocalLoggingOn) {
ASSERT_TRUE(EnableLocalLogging());
ASSERT_TRUE(PeerConnectionAdded(kRenderProcessId, kPeerConnectionId));
ASSERT_TRUE(
OnWebRtcEventLogWrite(kRenderProcessId, kPeerConnectionId, "log"));
}
TEST_F(WebRtcEventLogManagerTest,
OnLocalLogStartedNotCalledIfLocalLoggingEnabledWithoutPeerConnections) {
MockWebRtcLocalEventLogsObserver observer;
EXPECT_CALL(observer, OnLocalLogStarted(_, _)).Times(0);
EXPECT_CALL(observer, OnLocalLogStopped(_)).Times(0);
SetLocalLogsObserver(&observer);
ASSERT_TRUE(EnableLocalLogging());
}
TEST_F(WebRtcEventLogManagerTest,
OnLocalLogStartedCalledForPeerConnectionAddedAndLocalLoggingEnabled) {
MockWebRtcLocalEventLogsObserver observer;
PeerConnectionKey peer_connection(kRenderProcessId, kPeerConnectionId);
EXPECT_CALL(observer, OnLocalLogStarted(peer_connection, _)).Times(1);
SetLocalLogsObserver(&observer);
ASSERT_TRUE(PeerConnectionAdded(kRenderProcessId, kPeerConnectionId));
ASSERT_TRUE(EnableLocalLogging());
}
TEST_F(WebRtcEventLogManagerTest,
OnLocalLogStartedCalledForLocalLoggingEnabledAndPeerConnectionAdded) {
MockWebRtcLocalEventLogsObserver observer;
PeerConnectionKey peer_connection(kRenderProcessId, kPeerConnectionId);
EXPECT_CALL(observer, OnLocalLogStarted(peer_connection, _)).Times(1);
SetLocalLogsObserver(&observer);
ASSERT_TRUE(EnableLocalLogging());
ASSERT_TRUE(PeerConnectionAdded(kRenderProcessId, kPeerConnectionId));
}
TEST_F(WebRtcEventLogManagerTest,
OnLocalLogStoppedCalledAfterLocalLoggingDisabled) {
NiceMock<MockWebRtcLocalEventLogsObserver> observer;
PeerConnectionKey peer_connection(kRenderProcessId, kPeerConnectionId);
EXPECT_CALL(observer, OnLocalLogStopped(peer_connection)).Times(1);
SetLocalLogsObserver(&observer);
ASSERT_TRUE(PeerConnectionAdded(kRenderProcessId, kPeerConnectionId));
ASSERT_TRUE(EnableLocalLogging());
ASSERT_TRUE(DisableLocalLogging());
}
TEST_F(WebRtcEventLogManagerTest,
OnLocalLogStoppedCalledAfterPeerConnectionRemoved) {
NiceMock<MockWebRtcLocalEventLogsObserver> observer;
PeerConnectionKey peer_connection(kRenderProcessId, kPeerConnectionId);
EXPECT_CALL(observer, OnLocalLogStopped(peer_connection)).Times(1);
SetLocalLogsObserver(&observer);
ASSERT_TRUE(PeerConnectionAdded(kRenderProcessId, kPeerConnectionId));
ASSERT_TRUE(EnableLocalLogging());
ASSERT_TRUE(PeerConnectionRemoved(kRenderProcessId, kPeerConnectionId));
}
TEST_F(WebRtcEventLogManagerTest, RemovedLocalLogsObserverReceivesNoCalls) {
StrictMock<MockWebRtcLocalEventLogsObserver> observer;
EXPECT_CALL(observer, OnLocalLogStarted(_, _)).Times(0);
EXPECT_CALL(observer, OnLocalLogStopped(_)).Times(0);
SetLocalLogsObserver(&observer);
SetLocalLogsObserver(nullptr);
ASSERT_TRUE(EnableLocalLogging());
ASSERT_TRUE(PeerConnectionAdded(kRenderProcessId, kPeerConnectionId));
ASSERT_TRUE(PeerConnectionRemoved(kRenderProcessId, kPeerConnectionId));
}
TEST_F(WebRtcEventLogManagerTest, LocalLogCreatesEmptyFileWhenStarted) {
NiceMock<MockWebRtcLocalEventLogsObserver> observer;
SetLocalLogsObserver(&observer);
base::Optional<PeerConnectionKey> key;
base::Optional<base::FilePath> file_path;
ON_CALL(observer, OnLocalLogStarted(_, _))
.WillByDefault(Invoke(SaveKeyAndFilePathTo(&key, &file_path)));
ASSERT_TRUE(EnableLocalLogging());
ASSERT_TRUE(PeerConnectionAdded(kRenderProcessId, kPeerConnectionId));
ASSERT_TRUE(key);
ASSERT_EQ(*key, PeerConnectionKey(kRenderProcessId, kPeerConnectionId));
ASSERT_TRUE(file_path);
ASSERT_FALSE(file_path->empty());
// Make sure the file would be closed, so that we could safely read it.
ASSERT_TRUE(PeerConnectionRemoved(kRenderProcessId, kPeerConnectionId));
std::string file_contents;
ASSERT_TRUE(base::ReadFileToString(*file_path, &file_contents));
EXPECT_EQ(file_contents, "");
}
TEST_F(WebRtcEventLogManagerTest, LocalLogCreateAndWriteToFile) {
NiceMock<MockWebRtcLocalEventLogsObserver> observer;
SetLocalLogsObserver(&observer);
base::Optional<PeerConnectionKey> key;
base::Optional<base::FilePath> file_path;
ON_CALL(observer, OnLocalLogStarted(_, _))
.WillByDefault(Invoke(SaveKeyAndFilePathTo(&key, &file_path)));
ASSERT_TRUE(EnableLocalLogging());
ASSERT_TRUE(PeerConnectionAdded(kRenderProcessId, kPeerConnectionId));
ASSERT_TRUE(key);
ASSERT_EQ(*key, PeerConnectionKey(kRenderProcessId, kPeerConnectionId));
ASSERT_TRUE(file_path);
ASSERT_FALSE(file_path->empty());
const std::string log = "To strive, to seek, to find, and not to yield.";
ASSERT_TRUE(OnWebRtcEventLogWrite(kRenderProcessId, kPeerConnectionId, log));
// Make sure the file would be closed, so that we could safely read it.
ASSERT_TRUE(PeerConnectionRemoved(kRenderProcessId, kPeerConnectionId));
std::string file_contents;
ASSERT_TRUE(base::ReadFileToString(*file_path, &file_contents));
EXPECT_EQ(file_contents, file_contents);
}
TEST_F(WebRtcEventLogManagerTest, LocalLogMultipleWritesToSameFile) {
NiceMock<MockWebRtcLocalEventLogsObserver> observer;
SetLocalLogsObserver(&observer);
base::Optional<PeerConnectionKey> key;
base::Optional<base::FilePath> file_path;
ON_CALL(observer, OnLocalLogStarted(_, _))
.WillByDefault(Invoke(SaveKeyAndFilePathTo(&key, &file_path)));
ASSERT_TRUE(EnableLocalLogging());
ASSERT_TRUE(PeerConnectionAdded(kRenderProcessId, kPeerConnectionId));
ASSERT_TRUE(key);
ASSERT_EQ(*key, PeerConnectionKey(kRenderProcessId, kPeerConnectionId));
ASSERT_TRUE(file_path);
ASSERT_FALSE(file_path->empty());
const std::string logs[] = {"Old age hath yet his honour and his toil;",
"Death closes all: but something ere the end,",
"Some work of noble note, may yet be done,",
"Not unbecoming men that strove with Gods."};
for (const std::string& log : logs) {
ASSERT_TRUE(
OnWebRtcEventLogWrite(kRenderProcessId, kPeerConnectionId, log));
}
// Make sure the file would be closed, so that we could safely read it.
ASSERT_TRUE(PeerConnectionRemoved(kRenderProcessId, kPeerConnectionId));
std::string file_contents;
ASSERT_TRUE(base::ReadFileToString(*file_path, &file_contents));
EXPECT_EQ(file_contents,
std::accumulate(std::begin(logs), std::end(logs), std::string()));
}
TEST_F(WebRtcEventLogManagerTest, LocalLogFileSizeLimitNotExceeded) {
NiceMock<MockWebRtcLocalEventLogsObserver> observer;
SetLocalLogsObserver(&observer);
base::Optional<PeerConnectionKey> key;
base::Optional<base::FilePath> file_path;
ON_CALL(observer, OnLocalLogStarted(_, _))
.WillByDefault(Invoke(SaveKeyAndFilePathTo(&key, &file_path)));
const std::string log = "There lies the port; the vessel puffs her sail:";
const size_t file_size_limit_bytes = log.length() / 2;
ASSERT_TRUE(EnableLocalLogging(file_size_limit_bytes));
ASSERT_TRUE(PeerConnectionAdded(kRenderProcessId, kPeerConnectionId));
ASSERT_TRUE(key);
ASSERT_EQ(*key, PeerConnectionKey(kRenderProcessId, kPeerConnectionId));
ASSERT_TRUE(file_path);
ASSERT_FALSE(file_path->empty());
// A failure is reported, because not everything could be written. The file
// will also be closed.
const auto pc = PeerConnectionKey(kRenderProcessId, kPeerConnectionId);
EXPECT_CALL(observer, OnLocalLogStopped(pc)).Times(1);
ASSERT_FALSE(OnWebRtcEventLogWrite(kRenderProcessId, kPeerConnectionId, log));
// Additional calls to Write() have no effect.
ASSERT_FALSE(
OnWebRtcEventLogWrite(kRenderProcessId, kPeerConnectionId, "ignored"));
std::string file_contents;
ASSERT_TRUE(base::ReadFileToString(*file_path, &file_contents));
EXPECT_EQ(file_contents, log.substr(0, file_size_limit_bytes));
}
TEST_F(WebRtcEventLogManagerTest, LocalLogSanityOverUnlimitedFileSizes) {
NiceMock<MockWebRtcLocalEventLogsObserver> observer;
SetLocalLogsObserver(&observer);
base::Optional<PeerConnectionKey> key;
base::Optional<base::FilePath> file_path;
ON_CALL(observer, OnLocalLogStarted(_, _))
.WillByDefault(Invoke(SaveKeyAndFilePathTo(&key, &file_path)));
ASSERT_TRUE(EnableLocalLogging(kWebRtcEventLogManagerUnlimitedFileSize));
ASSERT_TRUE(PeerConnectionAdded(kRenderProcessId, kPeerConnectionId));
ASSERT_TRUE(key);
ASSERT_EQ(*key, PeerConnectionKey(kRenderProcessId, kPeerConnectionId));
ASSERT_TRUE(file_path);
ASSERT_FALSE(file_path->empty());
const std::string log1 = "Who let the dogs out?";
const std::string log2 = "Woof, woof, woof, woof, woof!";
ASSERT_TRUE(OnWebRtcEventLogWrite(kRenderProcessId, kPeerConnectionId, log1));
ASSERT_TRUE(OnWebRtcEventLogWrite(kRenderProcessId, kPeerConnectionId, log2));
// Make sure the file would be closed, so that we could safely read it.
ASSERT_TRUE(PeerConnectionRemoved(kRenderProcessId, kPeerConnectionId));
std::string file_contents;
ASSERT_TRUE(base::ReadFileToString(*file_path, &file_contents));
EXPECT_EQ(file_contents, log1 + log2);
}
TEST_F(WebRtcEventLogManagerTest, LocalLogNoWriteAfterLogStopped) {
NiceMock<MockWebRtcLocalEventLogsObserver> observer;
SetLocalLogsObserver(&observer);
base::Optional<PeerConnectionKey> key;
base::Optional<base::FilePath> file_path;
ON_CALL(observer, OnLocalLogStarted(_, _))
.WillByDefault(Invoke(SaveKeyAndFilePathTo(&key, &file_path)));
ASSERT_TRUE(EnableLocalLogging());
ASSERT_TRUE(PeerConnectionAdded(kRenderProcessId, kPeerConnectionId));
ASSERT_TRUE(key);
ASSERT_EQ(*key, PeerConnectionKey(kRenderProcessId, kPeerConnectionId));
ASSERT_TRUE(file_path);
ASSERT_FALSE(file_path->empty());
const std::string log_before = "log_before_stop";
ASSERT_TRUE(
OnWebRtcEventLogWrite(kRenderProcessId, kPeerConnectionId, log_before));
const auto pc = PeerConnectionKey(kRenderProcessId, kPeerConnectionId);
EXPECT_CALL(observer, OnLocalLogStopped(pc)).Times(1);
ASSERT_TRUE(PeerConnectionRemoved(kRenderProcessId, kPeerConnectionId));
const std::string log_after = "log_after_stop";
ASSERT_FALSE(
OnWebRtcEventLogWrite(kRenderProcessId, kPeerConnectionId, log_after));
std::string file_contents;
ASSERT_TRUE(base::ReadFileToString(*file_path, &file_contents));
EXPECT_EQ(file_contents, log_before);
}
TEST_F(WebRtcEventLogManagerTest, LocalLogOnlyWritesTheLogsAfterStarted) {
NiceMock<MockWebRtcLocalEventLogsObserver> observer;
SetLocalLogsObserver(&observer);
// Calls to Write() before the log was started are ignored.
EXPECT_CALL(observer, OnLocalLogStarted(_, _)).Times(0);
const std::string log1 = "The lights begin to twinkle from the rocks:";
ASSERT_FALSE(
OnWebRtcEventLogWrite(kRenderProcessId, kPeerConnectionId, log1));
ASSERT_TRUE(base::IsDirectoryEmpty(base_dir_));
base::Optional<PeerConnectionKey> key;
base::Optional<base::FilePath> file_path;
EXPECT_CALL(observer, OnLocalLogStarted(_, _))
.Times(1)
.WillOnce(Invoke(SaveKeyAndFilePathTo(&key, &file_path)));
ASSERT_TRUE(EnableLocalLogging());
ASSERT_TRUE(PeerConnectionAdded(kRenderProcessId, kPeerConnectionId));
ASSERT_TRUE(key);
ASSERT_EQ(*key, PeerConnectionKey(kRenderProcessId, kPeerConnectionId));
ASSERT_TRUE(file_path);
ASSERT_FALSE(file_path->empty());
// Calls after the log started have an effect. The calls to Write() from
// before the log started are not remembered.
const std::string log2 = "The long day wanes: the slow moon climbs: the deep";
ASSERT_TRUE(OnWebRtcEventLogWrite(kRenderProcessId, kPeerConnectionId, log2));
// Make sure the file would be closed, so that we could safely read it.
ASSERT_TRUE(PeerConnectionRemoved(kRenderProcessId, kPeerConnectionId));
std::string file_contents;
ASSERT_TRUE(base::ReadFileToString(*file_path, &file_contents));
EXPECT_EQ(file_contents, log2);
}
// Note: This test also covers the scenario LocalLogExistingFilesNotOverwritten,
// which is therefore not explicitly tested.
TEST_F(WebRtcEventLogManagerTest, LocalLoggingRestartCreatesNewFile) {
NiceMock<MockWebRtcLocalEventLogsObserver> observer;
SetLocalLogsObserver(&observer);
const std::vector<std::string> logs = {"<setup>", "<punchline>", "<encore>"};
std::vector<base::Optional<PeerConnectionKey>> keys(logs.size());
std::vector<base::Optional<base::FilePath>> file_paths(logs.size());
ASSERT_TRUE(PeerConnectionAdded(kRenderProcessId, kPeerConnectionId));
for (size_t i = 0; i < logs.size(); i++) {
ON_CALL(observer, OnLocalLogStarted(_, _))
.WillByDefault(Invoke(SaveKeyAndFilePathTo(&keys[i], &file_paths[i])));
ASSERT_TRUE(EnableLocalLogging());
ASSERT_TRUE(keys[i]);
ASSERT_EQ(*keys[i], PeerConnectionKey(kRenderProcessId, kPeerConnectionId));
ASSERT_TRUE(file_paths[i]);
ASSERT_FALSE(file_paths[i]->empty());
ASSERT_TRUE(
OnWebRtcEventLogWrite(kRenderProcessId, kPeerConnectionId, logs[i]));
ASSERT_TRUE(DisableLocalLogging());
}
for (size_t i = 0; i < logs.size(); i++) {
std::string file_contents;
ASSERT_TRUE(base::ReadFileToString(*file_paths[i], &file_contents));
EXPECT_EQ(file_contents, logs[i]);
}
}
TEST_F(WebRtcEventLogManagerTest, LocalLogMultipleActiveFiles) {
NiceMock<MockWebRtcLocalEventLogsObserver> observer;
SetLocalLogsObserver(&observer);
ASSERT_TRUE(EnableLocalLogging());
const std::vector<base::Optional<PeerConnectionKey>> keys = {
base::Optional<PeerConnectionKey>({1, 2}),
base::Optional<PeerConnectionKey>({2, 1}),
base::Optional<PeerConnectionKey>({3, 4})};
std::vector<base::Optional<base::FilePath>> file_paths(keys.size());
for (size_t i = 0; i < keys.size(); i++) {
base::Optional<PeerConnectionKey> key;
ON_CALL(observer, OnLocalLogStarted(_, _))
.WillByDefault(Invoke(SaveKeyAndFilePathTo(&key, &file_paths[i])));
ASSERT_TRUE(PeerConnectionAdded(keys[i]->render_process_id, keys[i]->lid));
ASSERT_TRUE(key);
ASSERT_EQ(key, keys[i]);
ASSERT_TRUE(file_paths[i]);
ASSERT_FALSE(file_paths[i]->empty());
}
std::vector<std::string> logs;
for (size_t i = 0; i < keys.size(); i++) {
logs.emplace_back(std::to_string(kRenderProcessId) +
std::to_string(kPeerConnectionId));
ASSERT_TRUE(OnWebRtcEventLogWrite(keys[i]->render_process_id, keys[i]->lid,
logs[i]));
}
// Make sure the file woulds be closed, so that we could safely read them.
ASSERT_TRUE(DisableLocalLogging());
for (size_t i = 0; i < keys.size(); i++) {
std::string file_contents;
ASSERT_TRUE(base::ReadFileToString(*file_paths[i], &file_contents));
EXPECT_EQ(file_contents, logs[i]);
}
}
TEST_F(WebRtcEventLogManagerTest, LocalLogLimitActiveLocalLogFiles) {
NiceMock<MockWebRtcLocalEventLogsObserver> observer;
SetLocalLogsObserver(&observer);
ASSERT_TRUE(EnableLocalLogging());
const int kMaxLocalLogFiles =
static_cast<int>(kMaxNumberLocalWebRtcEventLogFiles);
int i;
for (i = 0; i < kMaxLocalLogFiles; i++) {
EXPECT_CALL(observer, OnLocalLogStarted(PeerConnectionKey(i, i), _))
.Times(1);
ASSERT_TRUE(PeerConnectionAdded(i, i));
}
EXPECT_CALL(observer, OnLocalLogStarted(_, _)).Times(0);
ASSERT_TRUE(PeerConnectionAdded(i, i));
}
// When a log reaches its maximum size limit, it is closed, and no longer
// counted towards the limit.
TEST_F(WebRtcEventLogManagerTest, LocalLogFilledLogNotCountedTowardsFileLimit) {
NiceMock<MockWebRtcLocalEventLogsObserver> observer;
SetLocalLogsObserver(&observer);
const std::string log = "very_short_log";
ASSERT_TRUE(EnableLocalLogging(log.size()));
const int kMaxLocalLogFiles =
static_cast<int>(kMaxNumberLocalWebRtcEventLogFiles);
int i;
for (i = 0; i < kMaxLocalLogFiles - 1; i++) {
EXPECT_CALL(observer, OnLocalLogStarted(PeerConnectionKey(i, i), _))
.Times(1);
ASSERT_TRUE(PeerConnectionAdded(i, i));
}
// Last allowed log (we've started kMaxLocalLogFiles - 1 so far).
EXPECT_CALL(observer, OnLocalLogStarted(PeerConnectionKey(i, i), _)).Times(1);
ASSERT_TRUE(PeerConnectionAdded(i, i));
// By writing to it, we fill it and end up closing it, allowing an additional
// log to be written.
EXPECT_TRUE(OnWebRtcEventLogWrite(i, i, log));
// We now have room for one additional log.
++i;
EXPECT_CALL(observer, OnLocalLogStarted(PeerConnectionKey(i, i), _)).Times(1);
ASSERT_TRUE(PeerConnectionAdded(i, i));
}
TEST_F(WebRtcEventLogManagerTest,
LocalLogForRemovedPeerConnectionFilledLogNotCountedTowardsFileLimit) {
NiceMock<MockWebRtcLocalEventLogsObserver> observer;
SetLocalLogsObserver(&observer);
ASSERT_TRUE(EnableLocalLogging());
const int kMaxLocalLogFiles =
static_cast<int>(kMaxNumberLocalWebRtcEventLogFiles);
int i;
for (i = 0; i < kMaxLocalLogFiles - 1; i++) {
EXPECT_CALL(observer, OnLocalLogStarted(PeerConnectionKey(i, i), _))
.Times(1);
ASSERT_TRUE(PeerConnectionAdded(i, i));
}
// Last allowed log (we've started kMaxLocalLogFiles - 1 so far).
EXPECT_CALL(observer, OnLocalLogStarted(PeerConnectionKey(i, i), _)).Times(1);
ASSERT_TRUE(PeerConnectionAdded(i, i));
// By removing this peer connection, we allowi an additional log.
EXPECT_CALL(observer, OnLocalLogStopped(PeerConnectionKey(i, i))).Times(1);
ASSERT_TRUE(PeerConnectionRemoved(i, i));
// We now have room for one additional log.
++i;
EXPECT_CALL(observer, OnLocalLogStarted(PeerConnectionKey(i, i), _)).Times(1);
ASSERT_TRUE(PeerConnectionAdded(i, i));
}
TEST_F(WebRtcEventLogManagerTest, LocalLogSanityDestructorWithActiveFile) {
// We don't actually test that the file was closed, because this behavior
// is not supported - WebRtcEventLogManager's destructor may never be called,
// except for in unit-tests.
ASSERT_TRUE(PeerConnectionAdded(kRenderProcessId, kPeerConnectionId));
ASSERT_TRUE(EnableLocalLogging());
DestroyUnitUnderTest(); // Explicitly show destruction does not crash.
}
TEST_F(WebRtcEventLogManagerTest, LocalLogIllegalPath) {
StrictMock<MockWebRtcLocalEventLogsObserver> observer;
SetLocalLogsObserver(&observer);
// Since the log file won't be properly opened, these will not be called.
EXPECT_CALL(observer, OnLocalLogStarted(_, _)).Times(0);
EXPECT_CALL(observer, OnLocalLogStopped(_)).Times(0);
ASSERT_TRUE(PeerConnectionAdded(kRenderProcessId, kPeerConnectionId));
// See the documentation of the function for why |true| is expected despite
// the path being illegal.
const base::FilePath illegal_path(FILE_PATH_LITERAL(":!@#$%|`^&*\\/"));
EXPECT_TRUE(EnableLocalLogging(illegal_path));
EXPECT_TRUE(base::IsDirectoryEmpty(base_dir_));
}
#if defined(OS_POSIX) && !defined(OS_FUCHSIA)
TEST_F(WebRtcEventLogManagerTest, LocalLogLegalPathWithoutPermissionsSanity) {
// Remove write permissions from the entire directory.
int permissions;
ASSERT_TRUE(base::GetPosixFilePermissions(base_dir_, &permissions));
constexpr int write_permissions = base::FILE_PERMISSION_WRITE_BY_USER |
base::FILE_PERMISSION_WRITE_BY_GROUP |
base::FILE_PERMISSION_WRITE_BY_OTHERS;
permissions &= ~write_permissions;
ASSERT_TRUE(base::SetPosixFilePermissions(base_dir_, permissions));
StrictMock<MockWebRtcLocalEventLogsObserver> observer;
SetLocalLogsObserver(&observer);
// Since the log file won't be properly opened, these will not be called.
EXPECT_CALL(observer, OnLocalLogStarted(_, _)).Times(0);
EXPECT_CALL(observer, OnLocalLogStopped(_)).Times(0);
ASSERT_TRUE(PeerConnectionAdded(kRenderProcessId, kPeerConnectionId));
// See the documentation of the function for why |true| is expected despite
// the path being illegal.
EXPECT_TRUE(EnableLocalLogging(base_path_));
EXPECT_TRUE(base::IsDirectoryEmpty(base_dir_));
// Write() has no effect (but is handled gracefully).
EXPECT_FALSE(OnWebRtcEventLogWrite(kRenderProcessId, kPeerConnectionId,
"Why did the chicken cross the road?"));
EXPECT_TRUE(base::IsDirectoryEmpty(base_dir_));
// Logging was enabled, even if it had no effect because of the lacking
// permissions; therefore, the operation of disabling it makes sense.
EXPECT_TRUE(DisableLocalLogging());
EXPECT_TRUE(base::IsDirectoryEmpty(base_dir_));
}
#endif // defined(OS_POSIX) && !defined(OS_FUCHSIA)
TEST_F(WebRtcEventLogManagerTest, LocalLogEmptyStringHandledGracefully) {
NiceMock<MockWebRtcLocalEventLogsObserver> observer;
SetLocalLogsObserver(&observer);
// By writing a log after the empty string, we show that no odd behavior is
// encountered, such as closing the file (an actual bug from WebRTC).
const std::vector<std::string> logs = {"<setup>", "", "<encore>"};
std::vector<base::Optional<PeerConnectionKey>> keys(logs.size());
std::vector<base::Optional<base::FilePath>> file_paths(logs.size());
ASSERT_TRUE(PeerConnectionAdded(kRenderProcessId, kPeerConnectionId));
for (size_t i = 0; i < logs.size(); i++) {
ON_CALL(observer, OnLocalLogStarted(_, _))
.WillByDefault(Invoke(SaveKeyAndFilePathTo(&keys[i], &file_paths[i])));
ASSERT_TRUE(EnableLocalLogging());
ASSERT_TRUE(keys[i]);
ASSERT_EQ(*keys[i], PeerConnectionKey(kRenderProcessId, kPeerConnectionId));
ASSERT_TRUE(file_paths[i]);
ASSERT_FALSE(file_paths[i]->empty());
ASSERT_TRUE(
OnWebRtcEventLogWrite(kRenderProcessId, kPeerConnectionId, logs[i]));
ASSERT_TRUE(DisableLocalLogging());
}
for (size_t i = 0; i < logs.size(); i++) {
std::string file_contents;
ASSERT_TRUE(base::ReadFileToString(*file_paths[i], &file_contents));
EXPECT_EQ(file_contents, logs[i]);
}
}
// The logs would typically be binary. However, the other tests only cover ASCII
// characters, for readability. This test shows that this is not a problem.
TEST_F(WebRtcEventLogManagerTest, LocalLogAllPossibleCharacters) {
NiceMock<MockWebRtcLocalEventLogsObserver> observer;
SetLocalLogsObserver(&observer);
base::Optional<PeerConnectionKey> key;
base::Optional<base::FilePath> file_path;
ON_CALL(observer, OnLocalLogStarted(_, _))
.WillByDefault(Invoke(SaveKeyAndFilePathTo(&key, &file_path)));
ASSERT_TRUE(EnableLocalLogging());
ASSERT_TRUE(PeerConnectionAdded(kRenderProcessId, kPeerConnectionId));
ASSERT_TRUE(key);
ASSERT_EQ(*key, PeerConnectionKey(kRenderProcessId, kPeerConnectionId));
ASSERT_TRUE(file_path);
ASSERT_FALSE(file_path->empty());
std::string all_chars;
for (size_t i = 0; i < 256; i++) {
all_chars += static_cast<uint8_t>(i);
}
ASSERT_TRUE(
OnWebRtcEventLogWrite(kRenderProcessId, kPeerConnectionId, all_chars));
// Make sure the file would be closed, so that we could safely read it.
ASSERT_TRUE(PeerConnectionRemoved(kRenderProcessId, kPeerConnectionId));
std::string file_contents;
ASSERT_TRUE(base::ReadFileToString(*file_path, &file_contents));
EXPECT_EQ(file_contents, file_contents);
}
TEST_F(WebRtcEventLogManagerTest, LocalLogFilenameMatchesExpectedFormat) {
using StringType = base::FilePath::StringType;
NiceMock<MockWebRtcLocalEventLogsObserver> observer;
SetLocalLogsObserver(&observer);
base::Optional<PeerConnectionKey> key;
base::Optional<base::FilePath> file_path;
ON_CALL(observer, OnLocalLogStarted(_, _))
.WillByDefault(Invoke(SaveKeyAndFilePathTo(&key, &file_path)));
const base::Time::Exploded frozen_time_exploded{
2017, // Four digit year "2007"
9, // 1-based month (values 1 = January, etc.)
3, // 0-based day of week (0 = Sunday, etc.)
6, // 1-based day of month (1-31)
10, // Hour within the current day (0-23)
43, // Minute within the current hour (0-59)
29, // Second within the current minute.
0 // Milliseconds within the current second (0-999)
};
ASSERT_TRUE(frozen_time_exploded.HasValidValues());
FreezeClockAt(frozen_time_exploded);
const StringType user_defined = FILE_PATH_LITERAL("user_defined");
const base::FilePath base_path = base_dir_.Append(user_defined);
ASSERT_TRUE(EnableLocalLogging(base_path));
ASSERT_TRUE(PeerConnectionAdded(kRenderProcessId, kPeerConnectionId));
ASSERT_TRUE(key);
ASSERT_EQ(*key, PeerConnectionKey(kRenderProcessId, kPeerConnectionId));
ASSERT_TRUE(file_path);
ASSERT_FALSE(file_path->empty());
// [user_defined]_[date]_[time]_[pid]_[lid].log
const StringType date = FILE_PATH_LITERAL("20170906");
const StringType time = FILE_PATH_LITERAL("1043");
base::FilePath expected_path = base_path;
expected_path = base_path.InsertBeforeExtension(
FILE_PATH_LITERAL("_") + date + FILE_PATH_LITERAL("_") + time +
FILE_PATH_LITERAL("_") + IntToStringType(kRenderProcessId) +
FILE_PATH_LITERAL("_") + IntToStringType(kPeerConnectionId));
expected_path = expected_path.AddExtension(FILE_PATH_LITERAL("log"));
EXPECT_EQ(file_path, expected_path);
}
TEST_F(WebRtcEventLogManagerTest,
LocalLogFilenameMatchesExpectedFormatRepeatedFilename) {
using StringType = base::FilePath::StringType;
NiceMock<MockWebRtcLocalEventLogsObserver> observer;
SetLocalLogsObserver(&observer);
base::Optional<PeerConnectionKey> key;
base::Optional<base::FilePath> file_path_1;
base::Optional<base::FilePath> file_path_2;
EXPECT_CALL(observer, OnLocalLogStarted(_, _))
.WillOnce(Invoke(SaveKeyAndFilePathTo(&key, &file_path_1)))
.WillOnce(Invoke(SaveKeyAndFilePathTo(&key, &file_path_2)));
const base::Time::Exploded frozen_time_exploded{
2017, // Four digit year "2007"
9, // 1-based month (values 1 = January, etc.)
3, // 0-based day of week (0 = Sunday, etc.)
6, // 1-based day of month (1-31)
10, // Hour within the current day (0-23)
43, // Minute within the current hour (0-59)
29, // Second within the current minute.
0 // Milliseconds within the current second (0-999)
};
ASSERT_TRUE(frozen_time_exploded.HasValidValues());
FreezeClockAt(frozen_time_exploded);
const StringType user_defined_portion = FILE_PATH_LITERAL("user_defined");
const base::FilePath base_path = base_dir_.Append(user_defined_portion);
ASSERT_TRUE(EnableLocalLogging(base_path));
ASSERT_TRUE(PeerConnectionAdded(kRenderProcessId, kPeerConnectionId));
ASSERT_TRUE(key);
ASSERT_EQ(*key, PeerConnectionKey(kRenderProcessId, kPeerConnectionId));
ASSERT_TRUE(file_path_1);
ASSERT_FALSE(file_path_1->empty());
// [user_defined]_[date]_[time]_[pid]_[lid].log
const StringType date = FILE_PATH_LITERAL("20170906");
const StringType time = FILE_PATH_LITERAL("1043");
base::FilePath expected_path_1 = base_path;
expected_path_1 = base_path.InsertBeforeExtension(
FILE_PATH_LITERAL("_") + date + FILE_PATH_LITERAL("_") + time +
FILE_PATH_LITERAL("_") + IntToStringType(kRenderProcessId) +
FILE_PATH_LITERAL("_") + IntToStringType(kPeerConnectionId));
expected_path_1 = expected_path_1.AddExtension(FILE_PATH_LITERAL("log"));
ASSERT_EQ(file_path_1, expected_path_1);
ASSERT_TRUE(DisableLocalLogging());
ASSERT_TRUE(EnableLocalLogging(base_path));
ASSERT_TRUE(key);
ASSERT_EQ(*key, PeerConnectionKey(kRenderProcessId, kPeerConnectionId));
ASSERT_TRUE(file_path_2);
ASSERT_FALSE(file_path_2->empty());
const base::FilePath expected_path_2 =
expected_path_1.InsertBeforeExtension(FILE_PATH_LITERAL(" (1)"));
// Focus of the test - starting the same log again produces a new file,
// with an expected new filename.
ASSERT_EQ(file_path_2, expected_path_2);
}
} // namespace content