|  | // Copyright (c) 2013 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 "components/net_log/net_export_file_writer.h" | 
|  |  | 
|  | #include <stdint.h> | 
|  |  | 
|  | #include <memory> | 
|  |  | 
|  | #include "base/command_line.h" | 
|  | #include "base/compiler_specific.h" | 
|  | #include "base/files/file_path.h" | 
|  | #include "base/files/file_util.h" | 
|  | #include "base/files/scoped_file.h" | 
|  | #include "base/files/scoped_temp_dir.h" | 
|  | #include "base/json/json_reader.h" | 
|  | #include "base/json/json_string_value_serializer.h" | 
|  | #include "base/synchronization/waitable_event.h" | 
|  | #include "base/test/scoped_task_environment.h" | 
|  | #include "base/values.h" | 
|  | #include "build/build_config.h" | 
|  | #include "mojo/public/cpp/bindings/strong_binding.h" | 
|  | #include "net/base/network_change_notifier.h" | 
|  | #include "net/base/test_completion_callback.h" | 
|  | #include "net/log/net_log_capture_mode.h" | 
|  | #include "net/log/net_log_event_type.h" | 
|  | #include "net/test/embedded_test_server/default_handlers.h" | 
|  | #include "net/test/embedded_test_server/embedded_test_server.h" | 
|  | #include "net/test/embedded_test_server/http_request.h" | 
|  | #include "net/test/embedded_test_server/http_response.h" | 
|  | #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" | 
|  | #include "services/network/network_context.h" | 
|  | #include "services/network/network_service.h" | 
|  | #include "services/network/public/cpp/resource_request.h" | 
|  | #include "services/network/public/cpp/simple_url_loader.h" | 
|  | #include "services/network/test/test_network_context.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | const char kChannelString[] = "SomeChannel"; | 
|  | const size_t kMaxLogSizeBytes = 100 * 1024 * 1024;  // 100MiB | 
|  |  | 
|  | // Keep this in sync with kLogRelativePath defined in net_export_file_writer.cc. | 
|  | base::FilePath::CharType kLogRelativePath[] = | 
|  | FILE_PATH_LITERAL("net-export/chrome-net-export-log.json"); | 
|  |  | 
|  | const char kCaptureModeDefaultString[] = "STRIP_PRIVATE_DATA"; | 
|  | const char kCaptureModeIncludeCookiesAndCredentialsString[] = "NORMAL"; | 
|  | const char kCaptureModeIncludeSocketBytesString[] = "LOG_BYTES"; | 
|  |  | 
|  | const char kStateUninitializedString[] = "UNINITIALIZED"; | 
|  | const char kStateInitializingString[] = "INITIALIZING"; | 
|  | const char kStateNotLoggingString[] = "NOT_LOGGING"; | 
|  | const char kStateStartingLogString[] = "STARTING_LOG"; | 
|  | const char kStateLoggingString[] = "LOGGING"; | 
|  | const char kStateStoppingLogString[] = "STOPPING_LOG"; | 
|  | }  // namespace | 
|  |  | 
|  | namespace net_log { | 
|  |  | 
|  | class FakeNetLogExporter : public network::mojom::NetLogExporter { | 
|  | public: | 
|  | FakeNetLogExporter() {} | 
|  | ~FakeNetLogExporter() override {} | 
|  |  | 
|  | void Start(base::File destination, | 
|  | base::Value extra_constants, | 
|  | network::mojom::NetLogCaptureMode capture_mode, | 
|  | uint64_t max_file_size, | 
|  | StartCallback callback) override { | 
|  | std::move(callback).Run(net::OK); | 
|  | } | 
|  |  | 
|  | void Stop(base::Value polled_values, StopCallback callback) override { | 
|  | std::move(callback).Run(net::OK); | 
|  | } | 
|  | }; | 
|  |  | 
|  | class FakeNetworkContext : public network::TestNetworkContext { | 
|  | public: | 
|  | void CreateNetLogExporter( | 
|  | network::mojom::NetLogExporterRequest request) override { | 
|  | binding_ = mojo::StrongBinding<network::mojom::NetLogExporter>::Create( | 
|  | std::make_unique<FakeNetLogExporter>(), std::move(request)); | 
|  | } | 
|  |  | 
|  | void Disconnect() { binding_->Close(); } | 
|  |  | 
|  | private: | 
|  | mojo::StrongBindingPtr<network::mojom::NetLogExporter> binding_; | 
|  | }; | 
|  |  | 
|  | // Sets |path| to |path_to_return| and always returns true. This function is | 
|  | // used to override NetExportFileWriter's usual getter for the default log base | 
|  | // directory. | 
|  | bool SetPathToGivenAndReturnTrue(const base::FilePath& path_to_return, | 
|  | base::FilePath* path) { | 
|  | *path = path_to_return; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // Checks the "state" string of a NetExportFileWriter state. | 
|  | WARN_UNUSED_RESULT ::testing::AssertionResult VerifyState( | 
|  | std::unique_ptr<base::DictionaryValue> state, | 
|  | const std::string& expected_state_string) { | 
|  | std::string actual_state_string; | 
|  | if (!state->GetString("state", &actual_state_string)) { | 
|  | return ::testing::AssertionFailure() | 
|  | << "State is missing \"state\" string."; | 
|  | } | 
|  | if (actual_state_string != expected_state_string) { | 
|  | return ::testing::AssertionFailure() | 
|  | << "\"state\" string of state does not match expected." << std::endl | 
|  | << "    Actual: " << actual_state_string << std::endl | 
|  | << "  Expected: " << expected_state_string; | 
|  | } | 
|  | return ::testing::AssertionSuccess(); | 
|  | } | 
|  |  | 
|  | // Checks all fields of a NetExportFileWriter state except possibly the | 
|  | // "captureMode" string; that field is only checked if | 
|  | // |expected_log_capture_mode_known| is true. | 
|  | WARN_UNUSED_RESULT ::testing::AssertionResult VerifyState( | 
|  | std::unique_ptr<base::DictionaryValue> state, | 
|  | const std::string& expected_state_string, | 
|  | bool expected_log_exists, | 
|  | bool expected_log_capture_mode_known, | 
|  | const std::string& expected_log_capture_mode_string) { | 
|  | base::DictionaryValue expected_state; | 
|  | expected_state.SetString("state", expected_state_string); | 
|  | expected_state.SetBoolean("logExists", expected_log_exists); | 
|  | expected_state.SetBoolean("logCaptureModeKnown", | 
|  | expected_log_capture_mode_known); | 
|  | if (expected_log_capture_mode_known) { | 
|  | expected_state.SetString("captureMode", expected_log_capture_mode_string); | 
|  | } else { | 
|  | state->Remove("captureMode", nullptr); | 
|  | } | 
|  |  | 
|  | // Remove "file" field which is only added in debug mode. | 
|  | state->Remove("file", nullptr); | 
|  |  | 
|  | std::string expected_state_json_string; | 
|  | JSONStringValueSerializer expected_state_serializer( | 
|  | &expected_state_json_string); | 
|  | expected_state_serializer.Serialize(expected_state); | 
|  |  | 
|  | std::string actual_state_json_string; | 
|  | JSONStringValueSerializer actual_state_serializer(&actual_state_json_string); | 
|  | actual_state_serializer.Serialize(*state); | 
|  |  | 
|  | if (actual_state_json_string != expected_state_json_string) { | 
|  | return ::testing::AssertionFailure() | 
|  | << "State (possibly excluding \"captureMode\") does not match " | 
|  | "expected." | 
|  | << std::endl | 
|  | << "    Actual: " << actual_state_json_string << std::endl | 
|  | << "  Expected: " << expected_state_json_string; | 
|  | } | 
|  | return ::testing::AssertionSuccess(); | 
|  | } | 
|  |  | 
|  | WARN_UNUSED_RESULT ::testing::AssertionResult ReadCompleteLogFile( | 
|  | const base::FilePath& log_path, | 
|  | std::unique_ptr<base::DictionaryValue>* root) { | 
|  | DCHECK(!log_path.empty()); | 
|  |  | 
|  | if (!base::PathExists(log_path)) { | 
|  | return ::testing::AssertionFailure() | 
|  | << log_path.value() << " does not exist."; | 
|  | } | 
|  | // Parse log file contents into a dictionary | 
|  | std::string log_string; | 
|  | if (!base::ReadFileToString(log_path, &log_string)) { | 
|  | return ::testing::AssertionFailure() | 
|  | << log_path.value() << " could not be read."; | 
|  | } | 
|  | *root = base::DictionaryValue::From(base::JSONReader::Read(log_string)); | 
|  | if (!*root) { | 
|  | return ::testing::AssertionFailure() | 
|  | << "Contents of " << log_path.value() | 
|  | << " do not form valid JSON dictionary."; | 
|  | } | 
|  | // Make sure the "constants" section exists | 
|  | base::DictionaryValue* constants; | 
|  | if (!(*root)->GetDictionary("constants", &constants)) { | 
|  | root->reset(); | 
|  | return ::testing::AssertionFailure() | 
|  | << log_path.value() << " is missing constants."; | 
|  | } | 
|  | // Make sure the "events" section exists | 
|  | base::ListValue* events; | 
|  | if (!(*root)->GetList("events", &events)) { | 
|  | root->reset(); | 
|  | return ::testing::AssertionFailure() | 
|  | << log_path.value() << " is missing events list."; | 
|  | } | 
|  | return ::testing::AssertionSuccess(); | 
|  | } | 
|  |  | 
|  | // An implementation of NetExportFileWriter::StateObserver that allows waiting | 
|  | // until it's notified of a new state. | 
|  | class TestStateObserver : public NetExportFileWriter::StateObserver { | 
|  | public: | 
|  | // NetExportFileWriter::StateObserver implementation | 
|  | void OnNewState(const base::DictionaryValue& state) override { | 
|  | test_closure_.closure().Run(); | 
|  | result_state_ = state.CreateDeepCopy(); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<base::DictionaryValue> WaitForNewState() { | 
|  | test_closure_.WaitForResult(); | 
|  | DCHECK(result_state_); | 
|  | return std::move(result_state_); | 
|  | } | 
|  |  | 
|  | private: | 
|  | net::TestClosure test_closure_; | 
|  | std::unique_ptr<base::DictionaryValue> result_state_; | 
|  | }; | 
|  |  | 
|  | // A class that wraps around TestClosure. Provides the ability to wait on a | 
|  | // file path callback and retrieve the result. | 
|  | class TestFilePathCallback { | 
|  | public: | 
|  | TestFilePathCallback() | 
|  | : callback_(base::Bind(&TestFilePathCallback::SetResultThenNotify, | 
|  | base::Unretained(this))) {} | 
|  |  | 
|  | const base::Callback<void(const base::FilePath&)>& callback() const { | 
|  | return callback_; | 
|  | } | 
|  |  | 
|  | const base::FilePath& WaitForResult() { | 
|  | test_closure_.WaitForResult(); | 
|  | return result_; | 
|  | } | 
|  |  | 
|  | private: | 
|  | void SetResultThenNotify(const base::FilePath& result) { | 
|  | result_ = result; | 
|  | test_closure_.closure().Run(); | 
|  | } | 
|  |  | 
|  | net::TestClosure test_closure_; | 
|  | base::FilePath result_; | 
|  | base::Callback<void(const base::FilePath&)> callback_; | 
|  | }; | 
|  |  | 
|  | class NetExportFileWriterTest : public ::testing::Test { | 
|  | public: | 
|  | NetExportFileWriterTest() | 
|  | : scoped_task_environment_( | 
|  | base::test::ScopedTaskEnvironment::MainThreadType::IO), | 
|  | network_change_notifier_(net::NetworkChangeNotifier::CreateMock()), | 
|  | network_service_(network::NetworkService::CreateForTesting()) {} | 
|  |  | 
|  | // ::testing::Test implementation | 
|  | void SetUp() override { | 
|  | ASSERT_TRUE(log_temp_dir_.CreateUniqueTempDir()); | 
|  | network::mojom::NetworkContextParamsPtr params = | 
|  | network::mojom::NetworkContextParams::New(); | 
|  | // Use a fixed proxy config, to avoid dependencies on local network | 
|  | // configuration. | 
|  | params->initial_proxy_config = | 
|  | net::ProxyConfigWithAnnotation::CreateDirect(); | 
|  | network_context_ = std::make_unique<network::NetworkContext>( | 
|  | network_service_.get(), mojo::MakeRequest(&network_context_ptr_), | 
|  | std::move(params)); | 
|  |  | 
|  | // Override |file_writer_|'s default-log-base-directory-getter to | 
|  | // a getter that returns the temp dir created for the test. | 
|  | file_writer_.SetDefaultLogBaseDirectoryGetterForTest( | 
|  | base::Bind(&SetPathToGivenAndReturnTrue, log_temp_dir_.GetPath())); | 
|  |  | 
|  | default_log_path_ = log_temp_dir_.GetPath().Append(kLogRelativePath); | 
|  |  | 
|  | file_writer_.AddObserver(&test_state_observer_); | 
|  |  | 
|  | ASSERT_TRUE(VerifyState(file_writer_.GetState(), kStateUninitializedString, | 
|  | false, false, "")); | 
|  | } | 
|  |  | 
|  | // ::testing::Test implementation | 
|  | void TearDown() override { | 
|  | file_writer_.RemoveObserver(&test_state_observer_); | 
|  | ASSERT_TRUE(log_temp_dir_.Delete()); | 
|  | } | 
|  |  | 
|  | base::FilePath FileWriterGetFilePathToCompletedLog() { | 
|  | TestFilePathCallback test_callback; | 
|  | file_writer_.GetFilePathToCompletedLog(test_callback.callback()); | 
|  | return test_callback.WaitForResult(); | 
|  | } | 
|  |  | 
|  | WARN_UNUSED_RESULT ::testing::AssertionResult InitializeThenVerifyNewState( | 
|  | bool expected_initialize_success, | 
|  | bool expected_log_exists) { | 
|  | file_writer_.Initialize(); | 
|  | std::unique_ptr<base::DictionaryValue> state = | 
|  | test_state_observer_.WaitForNewState(); | 
|  | ::testing::AssertionResult result = | 
|  | VerifyState(std::move(state), kStateInitializingString); | 
|  | if (!result) { | 
|  | return ::testing::AssertionFailure() | 
|  | << "First state after Initialize() does not match expected:" | 
|  | << std::endl | 
|  | << result.message(); | 
|  | } | 
|  |  | 
|  | state = test_state_observer_.WaitForNewState(); | 
|  | result = | 
|  | VerifyState(std::move(state), | 
|  | expected_initialize_success ? kStateNotLoggingString | 
|  | : kStateUninitializedString, | 
|  | expected_log_exists, false, ""); | 
|  | if (!result) { | 
|  | return ::testing::AssertionFailure() | 
|  | << "Second state after Initialize() does not match expected:" | 
|  | << std::endl | 
|  | << result.message(); | 
|  | } | 
|  |  | 
|  | return ::testing::AssertionSuccess(); | 
|  | } | 
|  |  | 
|  | // If |custom_log_path| is empty path, |file_writer_| will use its | 
|  | // default log path, which is cached in |default_log_path_|. | 
|  | WARN_UNUSED_RESULT::testing::AssertionResult StartThenVerifyNewState( | 
|  | const base::FilePath& custom_log_path, | 
|  | net::NetLogCaptureMode capture_mode, | 
|  | const std::string& expected_capture_mode_string, | 
|  | network::mojom::NetworkContext* network_context) { | 
|  | file_writer_.StartNetLog(custom_log_path, capture_mode, kMaxLogSizeBytes, | 
|  | base::CommandLine::StringType(), kChannelString, | 
|  | network_context); | 
|  | std::unique_ptr<base::DictionaryValue> state = | 
|  | test_state_observer_.WaitForNewState(); | 
|  | ::testing::AssertionResult result = | 
|  | VerifyState(std::move(state), kStateStartingLogString); | 
|  | if (!result) { | 
|  | return ::testing::AssertionFailure() | 
|  | << "First state after StartNetLog() does not match expected:" | 
|  | << std::endl | 
|  | << result.message(); | 
|  | } | 
|  |  | 
|  | state = test_state_observer_.WaitForNewState(); | 
|  | result = VerifyState(std::move(state), kStateLoggingString, true, true, | 
|  | expected_capture_mode_string); | 
|  | if (!result) { | 
|  | return ::testing::AssertionFailure() | 
|  | << "Second state after StartNetLog() does not match expected:" | 
|  | << std::endl | 
|  | << result.message(); | 
|  | } | 
|  |  | 
|  | // Make sure GetFilePath() returns empty path when logging. | 
|  | base::FilePath actual_log_path = FileWriterGetFilePathToCompletedLog(); | 
|  | if (!actual_log_path.empty()) { | 
|  | return ::testing::AssertionFailure() | 
|  | << "GetFilePath() should return empty path after logging starts." | 
|  | << " Actual: " << ::testing::PrintToString(actual_log_path); | 
|  | } | 
|  |  | 
|  | return ::testing::AssertionSuccess(); | 
|  | } | 
|  |  | 
|  | // If |custom_log_path| is empty path, it's assumed the log file with be at | 
|  | // |default_log_path_|. | 
|  | WARN_UNUSED_RESULT ::testing::AssertionResult StopThenVerifyNewStateAndFile( | 
|  | const base::FilePath& custom_log_path, | 
|  | std::unique_ptr<base::DictionaryValue> polled_data, | 
|  | const std::string& expected_capture_mode_string) { | 
|  | file_writer_.StopNetLog(std::move(polled_data)); | 
|  | std::unique_ptr<base::DictionaryValue> state = | 
|  | test_state_observer_.WaitForNewState(); | 
|  | ::testing::AssertionResult result = | 
|  | VerifyState(std::move(state), kStateStoppingLogString); | 
|  | if (!result) { | 
|  | return ::testing::AssertionFailure() | 
|  | << "First state after StopNetLog() does not match expected:" | 
|  | << std::endl | 
|  | << result.message(); | 
|  | } | 
|  |  | 
|  | state = test_state_observer_.WaitForNewState(); | 
|  | result = VerifyState(std::move(state), kStateNotLoggingString, true, true, | 
|  | expected_capture_mode_string); | 
|  | if (!result) { | 
|  | return ::testing::AssertionFailure() | 
|  | << "Second state after StopNetLog() does not match expected:" | 
|  | << std::endl | 
|  | << result.message(); | 
|  | } | 
|  |  | 
|  | // Make sure GetFilePath() returns the expected path. | 
|  | const base::FilePath& expected_log_path = | 
|  | custom_log_path.empty() ? default_log_path_ : custom_log_path; | 
|  | base::FilePath actual_log_path = FileWriterGetFilePathToCompletedLog(); | 
|  | if (actual_log_path != expected_log_path) { | 
|  | return ::testing::AssertionFailure() | 
|  | << "GetFilePath() returned incorrect path after logging stopped." | 
|  | << std::endl | 
|  | << "    Actual: " << ::testing::PrintToString(actual_log_path) | 
|  | << std::endl | 
|  | << "  Expected: " << ::testing::PrintToString(expected_log_path); | 
|  | } | 
|  |  | 
|  | // Make sure the generated log file is valid. | 
|  | std::unique_ptr<base::DictionaryValue> root; | 
|  | result = ReadCompleteLogFile(expected_log_path, &root); | 
|  | if (!result) { | 
|  | return ::testing::AssertionFailure() | 
|  | << "Log file after logging stopped is not valid:" << std::endl | 
|  | << result.message(); | 
|  | } | 
|  |  | 
|  | return ::testing::AssertionSuccess(); | 
|  | } | 
|  |  | 
|  | net::NetLog* net_log() { return network_service_->net_log(); } | 
|  |  | 
|  | NetExportFileWriter* file_writer() { return &file_writer_; } | 
|  |  | 
|  | const base::FilePath& GetLogTempDirPath() const { | 
|  | return log_temp_dir_.GetPath(); | 
|  | } | 
|  |  | 
|  | const base::FilePath& default_log_path() const { return default_log_path_; } | 
|  |  | 
|  | TestStateObserver* test_state_observer() { return &test_state_observer_; } | 
|  |  | 
|  | network::mojom::NetworkContext* network_context() { | 
|  | return network_context_ptr_.get(); | 
|  | } | 
|  |  | 
|  | private: | 
|  | base::test::ScopedTaskEnvironment scoped_task_environment_; | 
|  | // Use a mock NetworkChangeNotifier so the real one can't add any logging. | 
|  | std::unique_ptr<net::NetworkChangeNotifier> network_change_notifier_; | 
|  | std::unique_ptr<network::NetworkService> network_service_; | 
|  |  | 
|  | network::mojom::NetworkContextPtr network_context_ptr_; | 
|  | std::unique_ptr<network::NetworkContext> network_context_; | 
|  |  | 
|  | // |file_writer_| is initialized after |network_context_ptr_| since | 
|  | // |file_writer_| destructor can talk to mojo objects owned by | 
|  | // |network_context_|. | 
|  | NetExportFileWriter file_writer_; | 
|  |  | 
|  | base::ScopedTempDir log_temp_dir_; | 
|  |  | 
|  | // The default log path that |file_writer_| will use is cached here. | 
|  | base::FilePath default_log_path_; | 
|  |  | 
|  | TestStateObserver test_state_observer_; | 
|  | }; | 
|  |  | 
|  | TEST_F(NetExportFileWriterTest, InitFail) { | 
|  | // Override file_writer_'s default log base directory getter to always | 
|  | // fail. | 
|  | file_writer()->SetDefaultLogBaseDirectoryGetterForTest( | 
|  | base::Bind([](base::FilePath* path) -> bool { return false; })); | 
|  |  | 
|  | // Initialization should fail due to the override. | 
|  | ASSERT_TRUE(InitializeThenVerifyNewState(false, false)); | 
|  |  | 
|  | // NetExportFileWriter::GetFilePath() should return empty path if | 
|  | // uninitialized. | 
|  | EXPECT_TRUE(FileWriterGetFilePathToCompletedLog().empty()); | 
|  | } | 
|  |  | 
|  | TEST_F(NetExportFileWriterTest, InitWithoutExistingLog) { | 
|  | ASSERT_TRUE(InitializeThenVerifyNewState(true, false)); | 
|  |  | 
|  | // NetExportFileWriter::GetFilePathToCompletedLog() should return empty path | 
|  | // when no log file exists. | 
|  | EXPECT_TRUE(FileWriterGetFilePathToCompletedLog().empty()); | 
|  | } | 
|  |  | 
|  | TEST_F(NetExportFileWriterTest, InitWithExistingLog) { | 
|  | // Create and close an empty log file to simulate existence of a previous log | 
|  | // file. | 
|  | ASSERT_TRUE( | 
|  | base::CreateDirectoryAndGetError(default_log_path().DirName(), nullptr)); | 
|  | base::ScopedFILE empty_file(base::OpenFile(default_log_path(), "w")); | 
|  | ASSERT_TRUE(empty_file.get()); | 
|  | empty_file.reset(); | 
|  |  | 
|  | ASSERT_TRUE(InitializeThenVerifyNewState(true, true)); | 
|  |  | 
|  | EXPECT_EQ(default_log_path(), FileWriterGetFilePathToCompletedLog()); | 
|  | } | 
|  |  | 
|  | TEST_F(NetExportFileWriterTest, StartAndStopWithAllCaptureModes) { | 
|  | const net::NetLogCaptureMode capture_modes[3] = { | 
|  | net::NetLogCaptureMode::Default(), | 
|  | net::NetLogCaptureMode::IncludeCookiesAndCredentials(), | 
|  | net::NetLogCaptureMode::IncludeSocketBytes()}; | 
|  |  | 
|  | const std::string capture_mode_strings[3] = { | 
|  | kCaptureModeDefaultString, kCaptureModeIncludeCookiesAndCredentialsString, | 
|  | kCaptureModeIncludeSocketBytesString}; | 
|  |  | 
|  | ASSERT_TRUE(InitializeThenVerifyNewState(true, false)); | 
|  |  | 
|  | // For each capture mode, start and stop |file_writer_| in that mode. | 
|  | for (int i = 0; i < 3; ++i) { | 
|  | // StartNetLog(), should result in state change. | 
|  | ASSERT_TRUE(StartThenVerifyNewState(base::FilePath(), capture_modes[i], | 
|  | capture_mode_strings[i], | 
|  | network_context())); | 
|  |  | 
|  | // Calling StartNetLog() again should be a no-op. Try doing StartNetLog() | 
|  | // with various capture modes; they should all be ignored and result in no | 
|  | // state change. | 
|  | file_writer()->StartNetLog( | 
|  | base::FilePath(), capture_modes[i], kMaxLogSizeBytes, | 
|  | base::CommandLine::StringType(), kChannelString, network_context()); | 
|  | file_writer()->StartNetLog( | 
|  | base::FilePath(), capture_modes[(i + 1) % 3], kMaxLogSizeBytes, | 
|  | base::CommandLine::StringType(), kChannelString, network_context()); | 
|  | file_writer()->StartNetLog( | 
|  | base::FilePath(), capture_modes[(i + 2) % 3], kMaxLogSizeBytes, | 
|  | base::CommandLine::StringType(), kChannelString, network_context()); | 
|  |  | 
|  | // StopNetLog(), should result in state change. The capture mode should | 
|  | // match that of the first StartNetLog() call (called by | 
|  | // StartThenVerifyNewState()). | 
|  | ASSERT_TRUE(StopThenVerifyNewStateAndFile(base::FilePath(), nullptr, | 
|  | capture_mode_strings[i])); | 
|  |  | 
|  | // Stopping a second time should be a no-op. | 
|  | file_writer()->StopNetLog(nullptr); | 
|  | } | 
|  |  | 
|  | // Start and stop one more time just to make sure the last StopNetLog() call | 
|  | // was properly ignored and left |file_writer_| in a valid state. | 
|  | ASSERT_TRUE(StartThenVerifyNewState(base::FilePath(), capture_modes[0], | 
|  | capture_mode_strings[0], | 
|  | network_context())); | 
|  |  | 
|  | ASSERT_TRUE(StopThenVerifyNewStateAndFile(base::FilePath(), nullptr, | 
|  | capture_mode_strings[0])); | 
|  | } | 
|  |  | 
|  | // Verify the file sizes after two consecutive starts/stops are the same (even | 
|  | // if some junk data is added in between). | 
|  | TEST_F(NetExportFileWriterTest, StartClearsFile) { | 
|  | ASSERT_TRUE(InitializeThenVerifyNewState(true, false)); | 
|  |  | 
|  | ASSERT_TRUE(StartThenVerifyNewState( | 
|  | base::FilePath(), net::NetLogCaptureMode::Default(), | 
|  | kCaptureModeDefaultString, network_context())); | 
|  |  | 
|  | ASSERT_TRUE(StopThenVerifyNewStateAndFile(base::FilePath(), nullptr, | 
|  | kCaptureModeDefaultString)); | 
|  |  | 
|  | int64_t stop_file_size; | 
|  | EXPECT_TRUE(base::GetFileSize(default_log_path(), &stop_file_size)); | 
|  |  | 
|  | // Add some junk at the end of the file. | 
|  | std::string junk_data("Hello"); | 
|  | EXPECT_TRUE(base::AppendToFile(default_log_path(), junk_data.c_str(), | 
|  | junk_data.size())); | 
|  |  | 
|  | int64_t junk_file_size; | 
|  | EXPECT_TRUE(base::GetFileSize(default_log_path(), &junk_file_size)); | 
|  | EXPECT_GT(junk_file_size, stop_file_size); | 
|  |  | 
|  | // Start and stop again and make sure the file is back to the size it was | 
|  | // before adding the junk data. | 
|  | ASSERT_TRUE(StartThenVerifyNewState( | 
|  | base::FilePath(), net::NetLogCaptureMode::Default(), | 
|  | kCaptureModeDefaultString, network_context())); | 
|  |  | 
|  | ASSERT_TRUE(StopThenVerifyNewStateAndFile(base::FilePath(), nullptr, | 
|  | kCaptureModeDefaultString)); | 
|  |  | 
|  | int64_t new_stop_file_size; | 
|  | EXPECT_TRUE(base::GetFileSize(default_log_path(), &new_stop_file_size)); | 
|  |  | 
|  | EXPECT_EQ(stop_file_size, new_stop_file_size); | 
|  | } | 
|  |  | 
|  | // Adds an event to the log file, then checks that the file is larger than | 
|  | // the file created without that event. | 
|  | TEST_F(NetExportFileWriterTest, AddEvent) { | 
|  | ASSERT_TRUE(InitializeThenVerifyNewState(true, false)); | 
|  |  | 
|  | ASSERT_TRUE(StartThenVerifyNewState( | 
|  | base::FilePath(), net::NetLogCaptureMode::Default(), | 
|  | kCaptureModeDefaultString, network_context())); | 
|  |  | 
|  | ASSERT_TRUE(StopThenVerifyNewStateAndFile(base::FilePath(), nullptr, | 
|  | kCaptureModeDefaultString)); | 
|  |  | 
|  | // Get file size without the event. | 
|  | int64_t stop_file_size; | 
|  | EXPECT_TRUE(base::GetFileSize(default_log_path(), &stop_file_size)); | 
|  |  | 
|  | ASSERT_TRUE(StartThenVerifyNewState( | 
|  | base::FilePath(), net::NetLogCaptureMode::Default(), | 
|  | kCaptureModeDefaultString, network_context())); | 
|  |  | 
|  | net_log()->AddGlobalEntry(net::NetLogEventType::CANCELLED); | 
|  |  | 
|  | ASSERT_TRUE(StopThenVerifyNewStateAndFile(base::FilePath(), nullptr, | 
|  | kCaptureModeDefaultString)); | 
|  |  | 
|  | // Get file size after adding the event and make sure it's larger than before. | 
|  | int64_t new_stop_file_size; | 
|  | EXPECT_TRUE(base::GetFileSize(default_log_path(), &new_stop_file_size)); | 
|  | EXPECT_GE(new_stop_file_size, stop_file_size); | 
|  | } | 
|  |  | 
|  | // Using a custom path to make sure logging can still occur when the path has | 
|  | // changed. | 
|  | TEST_F(NetExportFileWriterTest, AddEventCustomPath) { | 
|  | ASSERT_TRUE(InitializeThenVerifyNewState(true, false)); | 
|  |  | 
|  | base::FilePath::CharType kCustomRelativePath[] = | 
|  | FILE_PATH_LITERAL("custom/custom/chrome-net-export-log.json"); | 
|  | base::FilePath custom_log_path = | 
|  | GetLogTempDirPath().Append(kCustomRelativePath); | 
|  | EXPECT_TRUE( | 
|  | base::CreateDirectoryAndGetError(custom_log_path.DirName(), nullptr)); | 
|  |  | 
|  | ASSERT_TRUE(StartThenVerifyNewState( | 
|  | custom_log_path, net::NetLogCaptureMode::Default(), | 
|  | kCaptureModeDefaultString, network_context())); | 
|  |  | 
|  | ASSERT_TRUE(StopThenVerifyNewStateAndFile(custom_log_path, nullptr, | 
|  | kCaptureModeDefaultString)); | 
|  |  | 
|  | // Get file size without the event. | 
|  | int64_t stop_file_size; | 
|  | EXPECT_TRUE(base::GetFileSize(custom_log_path, &stop_file_size)); | 
|  |  | 
|  | ASSERT_TRUE(StartThenVerifyNewState( | 
|  | custom_log_path, net::NetLogCaptureMode::Default(), | 
|  | kCaptureModeDefaultString, network_context())); | 
|  |  | 
|  | net_log()->AddGlobalEntry(net::NetLogEventType::CANCELLED); | 
|  |  | 
|  | ASSERT_TRUE(StopThenVerifyNewStateAndFile(custom_log_path, nullptr, | 
|  | kCaptureModeDefaultString)); | 
|  |  | 
|  | // Get file size after adding the event and make sure it's larger than before. | 
|  | int64_t new_stop_file_size; | 
|  | EXPECT_TRUE(base::GetFileSize(custom_log_path, &new_stop_file_size)); | 
|  | EXPECT_GE(new_stop_file_size, stop_file_size); | 
|  | } | 
|  |  | 
|  | TEST_F(NetExportFileWriterTest, StopWithPolledData) { | 
|  | ASSERT_TRUE(InitializeThenVerifyNewState(true, false)); | 
|  |  | 
|  | // Create dummy polled data | 
|  | const char kDummyPolledDataPath[] = "dummy_path"; | 
|  | const char kDummyPolledDataString[] = "dummy_info"; | 
|  | std::unique_ptr<base::DictionaryValue> dummy_polled_data = | 
|  | std::make_unique<base::DictionaryValue>(); | 
|  | dummy_polled_data->SetString(kDummyPolledDataPath, kDummyPolledDataString); | 
|  |  | 
|  | ASSERT_TRUE(StartThenVerifyNewState( | 
|  | base::FilePath(), net::NetLogCaptureMode::Default(), | 
|  | kCaptureModeDefaultString, network_context())); | 
|  |  | 
|  | ASSERT_TRUE(StopThenVerifyNewStateAndFile(base::FilePath(), | 
|  | std::move(dummy_polled_data), | 
|  | kCaptureModeDefaultString)); | 
|  |  | 
|  | // Read polledData from log file. | 
|  | std::unique_ptr<base::DictionaryValue> root; | 
|  | ASSERT_TRUE(ReadCompleteLogFile(default_log_path(), &root)); | 
|  | base::DictionaryValue* polled_data; | 
|  | ASSERT_TRUE(root->GetDictionary("polledData", &polled_data)); | 
|  |  | 
|  | // Check that it contains the field from the polled data that was passed in. | 
|  | std::string dummy_string; | 
|  | ASSERT_TRUE(polled_data->GetString(kDummyPolledDataPath, &dummy_string)); | 
|  | EXPECT_EQ(kDummyPolledDataString, dummy_string); | 
|  |  | 
|  | // Check that it also contains something from net::GetNetInfo. | 
|  | base::DictionaryValue* http_cache_info; | 
|  | ASSERT_TRUE(polled_data->GetDictionary("httpCacheInfo", &http_cache_info)); | 
|  | } | 
|  |  | 
|  | // Test with requests in flight. This is done by going through a sequence of a | 
|  | // redirect --- at which point the log is started --- and then a fetch of | 
|  | // destination that's blocked on an event in EmbeddedTestServer. | 
|  | TEST_F(NetExportFileWriterTest, StartWithNetworkContextActive) { | 
|  | net::EmbeddedTestServer test_server; | 
|  | net::test_server::RegisterDefaultHandlers(&test_server); | 
|  |  | 
|  | base::WaitableEvent block_fetch( | 
|  | base::WaitableEvent::ResetPolicy::MANUAL, | 
|  | base::WaitableEvent::InitialState::NOT_SIGNALED); | 
|  |  | 
|  | test_server.RegisterRequestHandler(base::BindRepeating( | 
|  | [](base::WaitableEvent* block_fetch, | 
|  | const net::test_server::HttpRequest& request) | 
|  | -> std::unique_ptr<net::test_server::HttpResponse> { | 
|  | if (request.relative_url == "/block") | 
|  | block_fetch->Wait(); | 
|  | return nullptr; | 
|  | }, | 
|  | &block_fetch)); | 
|  |  | 
|  | ASSERT_TRUE(test_server.Start()); | 
|  |  | 
|  | ASSERT_TRUE(InitializeThenVerifyNewState(true, false)); | 
|  |  | 
|  | network::mojom::URLLoaderFactoryPtr url_loader_factory; | 
|  | auto url_loader_factory_params = | 
|  | network::mojom::URLLoaderFactoryParams::New(); | 
|  | url_loader_factory_params->process_id = network::mojom::kBrowserProcessId; | 
|  | url_loader_factory_params->is_corb_enabled = false; | 
|  | network_context()->CreateURLLoaderFactory( | 
|  | mojo::MakeRequest(&url_loader_factory), | 
|  | std::move(url_loader_factory_params)); | 
|  |  | 
|  | const char kRedirectURL[] = "/server-redirect?/block"; | 
|  | std::unique_ptr<network::ResourceRequest> request = | 
|  | std::make_unique<network::ResourceRequest>(); | 
|  | request->url = test_server.GetURL(kRedirectURL); | 
|  |  | 
|  | std::unique_ptr<network::SimpleURLLoader> simple_loader = | 
|  | network::SimpleURLLoader::Create(std::move(request), | 
|  | TRAFFIC_ANNOTATION_FOR_TESTS); | 
|  | base::RunLoop run_loop, run_loop2; | 
|  | simple_loader->SetOnRedirectCallback(base::BindRepeating( | 
|  | [](base::RepeatingClosure notify_log, | 
|  | const net::RedirectInfo& redirect_info, | 
|  | const network::ResourceResponseHead& response_head, | 
|  | std::vector<std::string>* to_be_removed_headers) { notify_log.Run(); }, | 
|  | run_loop.QuitClosure())); | 
|  | simple_loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie( | 
|  | url_loader_factory.get(), | 
|  | base::BindOnce( | 
|  | [](base::OnceClosure quit_closure, | 
|  | std::unique_ptr<std::string> response_body) { | 
|  | std::move(quit_closure).Run(); | 
|  | }, | 
|  | run_loop2.QuitClosure())); | 
|  |  | 
|  | // Wait for fetch to get some bytes accross. It will not be the entire | 
|  | // thing since the post-redirect URL will get blocked by the custom handler. | 
|  | run_loop.Run(); | 
|  | ASSERT_TRUE(StartThenVerifyNewState( | 
|  | base::FilePath(), net::NetLogCaptureMode::Default(), | 
|  | kCaptureModeDefaultString, network_context())); | 
|  |  | 
|  | ASSERT_TRUE(StopThenVerifyNewStateAndFile(base::FilePath(), nullptr, | 
|  | kCaptureModeDefaultString)); | 
|  | // Read events from log file. | 
|  | std::unique_ptr<base::DictionaryValue> root; | 
|  | ASSERT_TRUE(ReadCompleteLogFile(default_log_path(), &root)); | 
|  | base::ListValue* events; | 
|  | ASSERT_TRUE(root->GetList("events", &events)); | 
|  |  | 
|  | // Check there is at least one event as a result of the ongoing request. | 
|  | ASSERT_GE(events->GetSize(), 1u); | 
|  |  | 
|  | // Check the URL in the params of the first event. | 
|  | base::DictionaryValue* event; | 
|  | EXPECT_TRUE(events->GetDictionary(0, &event)); | 
|  | base::DictionaryValue* event_params; | 
|  | EXPECT_TRUE(event->GetDictionary("params", &event_params)); | 
|  | std::string event_url; | 
|  | EXPECT_TRUE(event_params->GetString("url", &event_url)); | 
|  | EXPECT_EQ(test_server.GetURL(kRedirectURL), event_url); | 
|  |  | 
|  | block_fetch.Signal(); | 
|  | run_loop2.Run(); | 
|  | } | 
|  |  | 
|  | TEST_F(NetExportFileWriterTest, ReceiveStartWhileInitializing) { | 
|  | // Trigger initialization of |file_writer_|. | 
|  | file_writer()->Initialize(); | 
|  |  | 
|  | // Before running the main message loop, tell |file_writer_| to start | 
|  | // logging. Not running the main message loop prevents the initialization | 
|  | // process from completing, so this ensures that StartNetLog() is received | 
|  | // before |file_writer_| finishes initialization, which means this | 
|  | // should be a no-op. | 
|  | file_writer()->StartNetLog( | 
|  | base::FilePath(), net::NetLogCaptureMode::Default(), kMaxLogSizeBytes, | 
|  | base::CommandLine::StringType(), kChannelString, network_context()); | 
|  |  | 
|  | // Now run the main message loop. Make sure StartNetLog() was ignored by | 
|  | // checking that the next two states are "initializing" followed by | 
|  | // "not-logging". | 
|  | std::unique_ptr<base::DictionaryValue> state = | 
|  | test_state_observer()->WaitForNewState(); | 
|  | ASSERT_TRUE(VerifyState(std::move(state), kStateInitializingString)); | 
|  | state = test_state_observer()->WaitForNewState(); | 
|  | ASSERT_TRUE( | 
|  | VerifyState(std::move(state), kStateNotLoggingString, false, false, "")); | 
|  | } | 
|  |  | 
|  | TEST_F(NetExportFileWriterTest, ReceiveStartWhileStoppingLog) { | 
|  | ASSERT_TRUE(InitializeThenVerifyNewState(true, false)); | 
|  |  | 
|  | // Call StartNetLog() on |file_writer_| and wait for the state change. | 
|  | ASSERT_TRUE(StartThenVerifyNewState( | 
|  | base::FilePath(), net::NetLogCaptureMode::IncludeSocketBytes(), | 
|  | kCaptureModeIncludeSocketBytesString, network_context())); | 
|  |  | 
|  | // Tell |file_writer_| to stop logging. | 
|  | file_writer()->StopNetLog(nullptr); | 
|  |  | 
|  | // Before running the main message loop, tell |file_writer_| to start | 
|  | // logging. Not running the main message loop prevents the stopping process | 
|  | // from completing, so this ensures StartNetLog() is received before | 
|  | // |file_writer_| finishes stopping, which means this should be a | 
|  | // no-op. | 
|  | file_writer()->StartNetLog( | 
|  | base::FilePath(), net::NetLogCaptureMode::Default(), kMaxLogSizeBytes, | 
|  | base::CommandLine::StringType(), kChannelString, network_context()); | 
|  |  | 
|  | // Now run the main message loop. Make sure the last StartNetLog() was | 
|  | // ignored by checking that the next two states are "stopping-log" followed by | 
|  | // "not-logging". Also make sure the capture mode matches that of the first | 
|  | // StartNetLog() call (called by StartThenVerifyState()). | 
|  | std::unique_ptr<base::DictionaryValue> state = | 
|  | test_state_observer()->WaitForNewState(); | 
|  | ASSERT_TRUE(VerifyState(std::move(state), kStateStoppingLogString)); | 
|  | state = test_state_observer()->WaitForNewState(); | 
|  | ASSERT_TRUE(VerifyState(std::move(state), kStateNotLoggingString, true, true, | 
|  | kCaptureModeIncludeSocketBytesString)); | 
|  | } | 
|  |  | 
|  | TEST_F(NetExportFileWriterTest, HandleCrash) { | 
|  | FakeNetworkContext fake_network_context; | 
|  |  | 
|  | ASSERT_TRUE(InitializeThenVerifyNewState(true, false)); | 
|  | ASSERT_TRUE(StartThenVerifyNewState( | 
|  | base::FilePath(), net::NetLogCaptureMode::IncludeSocketBytes(), | 
|  | kCaptureModeIncludeSocketBytesString, &fake_network_context)); | 
|  |  | 
|  | // Break the pipe, as if network service crashed. | 
|  | fake_network_context.Disconnect(); | 
|  |  | 
|  | std::unique_ptr<base::DictionaryValue> state = | 
|  | test_state_observer()->WaitForNewState(); | 
|  | ASSERT_TRUE(VerifyState(std::move(state), kStateNotLoggingString)); | 
|  | } | 
|  |  | 
|  | }  // namespace net_log |