Promote RtcEventLogOutputFile to api/
Preparation for deleting PeerConnectionInterface::StartRtcEventLog
method with a PlatformFile argument.
Bug: webrtc:6463
Change-Id: Ia9fa1d99a3d87f3bf193e73382690b782ffea65c
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/135285
Commit-Queue: Niels Moller <nisse@webrtc.org>
Reviewed-by: Björn Terelius <terelius@webrtc.org>
Reviewed-by: Karl Wiberg <kwiberg@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#27879}
diff --git a/api/BUILD.gn b/api/BUILD.gn
index ec27409..2512bd4 100644
--- a/api/BUILD.gn
+++ b/api/BUILD.gn
@@ -348,6 +348,22 @@
]
}
+rtc_source_set("rtc_event_log_output_file") {
+ visibility = [ "*" ]
+ sources = [
+ "rtc_event_log_output_file.cc",
+ "rtc_event_log_output_file.h",
+ ]
+
+ deps = [
+ ":libjingle_logging_api",
+ "../logging:rtc_event_log_api",
+ "../rtc_base:checks",
+ "../rtc_base:rtc_base_approved",
+ "../rtc_base/system:file_wrapper",
+ ]
+}
+
rtc_source_set("ortc_api") {
visibility = [ "*" ]
sources = [
@@ -804,6 +820,7 @@
"array_view_unittest.cc",
"function_view_unittest.cc",
"rtc_error_unittest.cc",
+ "rtc_event_log_output_file_unittest.cc",
"rtp_parameters_unittest.cc",
"test/loopback_media_transport_unittest.cc",
]
@@ -813,13 +830,16 @@
":function_view",
":libjingle_peerconnection_api",
":loopback_media_transport",
+ ":rtc_event_log_output_file",
"../rtc_base:checks",
"../rtc_base:gunit_helpers",
"../rtc_base:rtc_base_approved",
+ "../test:fileutils",
"../test:test_support",
"task_queue:task_queue_default_factory_unittests",
"units:units_unittests",
"video:video_unittests",
+ "//third_party/abseil-cpp/absl/memory",
]
}
diff --git a/api/DEPS b/api/DEPS
index 34bdfe0..9c174a1 100644
--- a/api/DEPS
+++ b/api/DEPS
@@ -148,7 +148,12 @@
"rtc_error\.h": [
"+rtc_base/logging.h",
],
-
+ "rtc_event_log_output_file.h": [
+ # TODO(bugs.webrtc.org/6463): Delete this dependency.
+ "+rtc_base/platform_file.h",
+ # For private member and constructor.
+ "+rtc_base/system/file_wrapper.h",
+ ],
"rtp_receiver_interface\.h": [
"+rtc_base/ref_count.h",
],
diff --git a/api/rtc_event_log_output_file.cc b/api/rtc_event_log_output_file.cc
new file mode 100644
index 0000000..3845071
--- /dev/null
+++ b/api/rtc_event_log_output_file.cc
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <limits>
+#include <utility>
+
+#include "api/rtc_event_log_output_file.h"
+#include "logging/rtc_event_log/rtc_event_log.h"
+#include "rtc_base/checks.h"
+#include "rtc_base/logging.h"
+
+namespace webrtc {
+
+namespace {
+
+FileWrapper FileWrapperFromPlatformFile(rtc::PlatformFile platform_file) {
+ if (platform_file == rtc::kInvalidPlatformFileValue)
+ return FileWrapper();
+
+ return FileWrapper(rtc::FdopenPlatformFileForWriting(platform_file));
+}
+
+} // namespace
+
+// Together with the assumption of no single Write() would ever be called on
+// an input with length greater-than-or-equal-to (max(size_t) / 2), this
+// guarantees no overflow of the check for remaining file capacity in Write().
+// This does *not* apply to files with unlimited size.
+const size_t RtcEventLogOutputFile::kMaxReasonableFileSize =
+ std::numeric_limits<size_t>::max() / 2;
+
+RtcEventLogOutputFile::RtcEventLogOutputFile(const std::string& file_name)
+ : RtcEventLogOutputFile(FileWrapper::OpenWriteOnly(file_name),
+ RtcEventLog::kUnlimitedOutput) {}
+
+RtcEventLogOutputFile::RtcEventLogOutputFile(const std::string& file_name,
+ size_t max_size_bytes)
+
+ // Unlike plain fopen, FileWrapper takes care of filename utf8 ->
+ // wchar conversion on Windows.
+ : RtcEventLogOutputFile(FileWrapper::OpenWriteOnly(file_name),
+ max_size_bytes) {}
+
+RtcEventLogOutputFile::RtcEventLogOutputFile(FILE* file, size_t max_size_bytes)
+ : RtcEventLogOutputFile(FileWrapper(file), max_size_bytes) {}
+
+RtcEventLogOutputFile::RtcEventLogOutputFile(FileWrapper file,
+ size_t max_size_bytes)
+ : max_size_bytes_(max_size_bytes), file_(std::move(file)) {
+ RTC_CHECK_LE(max_size_bytes_, kMaxReasonableFileSize);
+ if (!file_.is_open()) {
+ RTC_LOG(LS_ERROR) << "Invalid file. WebRTC event log not started.";
+ }
+}
+
+RtcEventLogOutputFile::RtcEventLogOutputFile(rtc::PlatformFile platform_file,
+ size_t max_size_bytes)
+ : RtcEventLogOutputFile(FileWrapperFromPlatformFile(platform_file),
+ max_size_bytes) {}
+
+bool RtcEventLogOutputFile::IsActive() const {
+ return IsActiveInternal();
+}
+
+bool RtcEventLogOutputFile::Write(const std::string& output) {
+ RTC_DCHECK(IsActiveInternal());
+ // No single write may be so big, that it would risk overflowing the
+ // calculation of (written_bytes_ + output.length()).
+ RTC_DCHECK_LT(output.length(), kMaxReasonableFileSize);
+
+ if (max_size_bytes_ == RtcEventLog::kUnlimitedOutput ||
+ written_bytes_ + output.length() <= max_size_bytes_) {
+ if (file_.Write(output.c_str(), output.size())) {
+ written_bytes_ += output.size();
+ return true;
+ } else {
+ RTC_LOG(LS_ERROR) << "Write to WebRtcEventLog file failed.";
+ }
+ } else {
+ RTC_LOG(LS_VERBOSE) << "Max file size reached.";
+ }
+
+ // Failed, for one of above reasons. Close output file.
+ file_.Close();
+ return false;
+}
+
+// Internal non-virtual method.
+bool RtcEventLogOutputFile::IsActiveInternal() const {
+ return file_.is_open();
+}
+
+} // namespace webrtc
diff --git a/api/rtc_event_log_output_file.h b/api/rtc_event_log_output_file.h
new file mode 100644
index 0000000..e536dfc
--- /dev/null
+++ b/api/rtc_event_log_output_file.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef API_RTC_EVENT_LOG_OUTPUT_FILE_H_
+#define API_RTC_EVENT_LOG_OUTPUT_FILE_H_
+
+#include <stddef.h>
+#include <stdio.h>
+#include <string>
+
+#include "api/rtc_event_log_output.h"
+#include "rtc_base/platform_file.h" // Can't neatly forward PlatformFile.
+#include "rtc_base/system/file_wrapper.h"
+
+namespace webrtc {
+
+class RtcEventLogOutputFile final : public RtcEventLogOutput {
+ public:
+ static const size_t kMaxReasonableFileSize; // Explanation at declaration.
+
+ // Unlimited/limited-size output file (by filename).
+ explicit RtcEventLogOutputFile(const std::string& file_name);
+ RtcEventLogOutputFile(const std::string& file_name, size_t max_size_bytes);
+
+ // Limited-size output file (by FILE*). This class takes ownership
+ // of the FILE*, and closes it on destruction.
+ RtcEventLogOutputFile(FILE* file, size_t max_size_bytes);
+
+ // TODO(bugs.webrtc.org/6463): Deprecated, delete together with the
+ // corresponding PeerConnection::StartRtcEventLog override.
+ RtcEventLogOutputFile(rtc::PlatformFile file, size_t max_size_bytes);
+
+ ~RtcEventLogOutputFile() override = default;
+
+ bool IsActive() const override;
+
+ bool Write(const std::string& output) override;
+
+ private:
+ RtcEventLogOutputFile(FileWrapper file, size_t max_size_bytes);
+
+ // IsActive() can be called either from outside or from inside, but we don't
+ // want to incur the overhead of a virtual function call if called from inside
+ // some other function of this class.
+ inline bool IsActiveInternal() const;
+
+ // Maximum size, or zero for no limit.
+ const size_t max_size_bytes_;
+ size_t written_bytes_{0};
+ FileWrapper file_;
+};
+
+} // namespace webrtc
+
+#endif // API_RTC_EVENT_LOG_OUTPUT_FILE_H_
diff --git a/api/rtc_event_log_output_file_unittest.cc b/api/rtc_event_log_output_file_unittest.cc
new file mode 100644
index 0000000..bffda0c
--- /dev/null
+++ b/api/rtc_event_log_output_file_unittest.cc
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "api/rtc_event_log_output_file.h"
+
+#include <fstream>
+#include <iterator>
+#include <memory>
+#include <string>
+
+#include "absl/memory/memory.h"
+#include "rtc_base/checks.h"
+#include "test/gtest.h"
+#include "test/testsupport/file_utils.h"
+
+namespace webrtc {
+
+class RtcEventLogOutputFileTest : public ::testing::Test {
+ public:
+ RtcEventLogOutputFileTest() : output_file_name_(GetOutputFilePath()) {
+ // Ensure no leftovers from previous runs, which might not have terminated
+ // in an orderly fashion.
+ remove(output_file_name_.c_str());
+ }
+
+ ~RtcEventLogOutputFileTest() override { remove(output_file_name_.c_str()); }
+
+ protected:
+ std::string GetOutputFilePath() const {
+ auto test_info = ::testing::UnitTest::GetInstance()->current_test_info();
+ return test::OutputPath() + test_info->test_case_name() + test_info->name();
+ }
+
+ std::string GetOutputFileContents() const {
+ std::ifstream file(output_file_name_,
+ std::ios_base::in | std::ios_base::binary);
+ RTC_CHECK(file.is_open());
+ RTC_CHECK(file.good());
+ std::string file_str((std::istreambuf_iterator<char>(file)),
+ std::istreambuf_iterator<char>());
+ return file_str;
+ }
+
+ const std::string output_file_name_;
+};
+
+TEST_F(RtcEventLogOutputFileTest, NonDefectiveOutputsStartOutActive) {
+ auto output_file =
+ absl::make_unique<RtcEventLogOutputFile>(output_file_name_);
+ EXPECT_TRUE(output_file->IsActive());
+}
+
+TEST_F(RtcEventLogOutputFileTest, DefectiveOutputsStartOutInactive) {
+ const std::string illegal_filename = "/////////";
+ auto output_file = absl::make_unique<RtcEventLogOutputFile>(illegal_filename);
+ EXPECT_FALSE(output_file->IsActive());
+}
+
+// Sanity over opening a file (by filename) with an unlimited size.
+TEST_F(RtcEventLogOutputFileTest, UnlimitedOutputFile) {
+ const std::string output_str = "one two three";
+
+ auto output_file =
+ absl::make_unique<RtcEventLogOutputFile>(output_file_name_);
+ output_file->Write(output_str);
+ output_file.reset(); // Closing the file flushes the buffer to disk.
+
+ EXPECT_EQ(GetOutputFileContents(), output_str);
+}
+
+// Do not allow writing more bytes to the file than
+TEST_F(RtcEventLogOutputFileTest, LimitedOutputFileCappedToCapacity) {
+ // Fit two bytes, then the third should be rejected.
+ auto output_file =
+ absl::make_unique<RtcEventLogOutputFile>(output_file_name_, 2);
+
+ output_file->Write("1");
+ output_file->Write("2");
+ output_file->Write("3");
+ // Unsuccessful writes close the file; no need to delete the output to flush.
+
+ EXPECT_EQ(GetOutputFileContents(), "12");
+}
+
+// Make sure that calls to Write() either write everything to the file, or
+// nothing (short of underlying issues in the module that handles the file,
+// which would be beyond our control).
+TEST_F(RtcEventLogOutputFileTest, DoNotWritePartialLines) {
+ const std::string output_str_1 = "0123456789";
+ const std::string output_str_2 = "abcdefghij";
+
+ // Set a file size limit just shy of fitting the entire second line.
+ const size_t size_limit = output_str_1.length() + output_str_2.length() - 1;
+ auto output_file =
+ absl::make_unique<RtcEventLogOutputFile>(output_file_name_, size_limit);
+
+ output_file->Write(output_str_1);
+ output_file->Write(output_str_2);
+ // Unsuccessful writes close the file; no need to delete the output to flush.
+
+ EXPECT_EQ(GetOutputFileContents(), output_str_1);
+}
+
+TEST_F(RtcEventLogOutputFileTest, UnsuccessfulWriteReturnsFalse) {
+ auto output_file =
+ absl::make_unique<RtcEventLogOutputFile>(output_file_name_, 2);
+ EXPECT_FALSE(output_file->Write("abc"));
+}
+
+TEST_F(RtcEventLogOutputFileTest, SuccessfulWriteReturnsTrue) {
+ auto output_file =
+ absl::make_unique<RtcEventLogOutputFile>(output_file_name_, 3);
+ EXPECT_TRUE(output_file->Write("abc"));
+}
+
+// Even if capacity is reached, a successful write leaves the output active.
+TEST_F(RtcEventLogOutputFileTest, FileStillActiveAfterSuccessfulWrite) {
+ auto output_file =
+ absl::make_unique<RtcEventLogOutputFile>(output_file_name_, 3);
+ ASSERT_TRUE(output_file->Write("abc"));
+ EXPECT_TRUE(output_file->IsActive());
+}
+
+// Unsuccessful writes switch the output to inactive, even if capacity has
+// not yet been reached.
+TEST_F(RtcEventLogOutputFileTest, FileInactiveAfterUnsuccessfulWrite) {
+ auto output_file =
+ absl::make_unique<RtcEventLogOutputFile>(output_file_name_, 2);
+ ASSERT_FALSE(output_file->Write("abc"));
+ EXPECT_FALSE(output_file->IsActive());
+}
+
+TEST_F(RtcEventLogOutputFileTest, AllowReasonableFileSizeLimits) {
+ auto output_file = absl::make_unique<RtcEventLogOutputFile>(
+ output_file_name_, RtcEventLogOutputFile::kMaxReasonableFileSize);
+ EXPECT_TRUE(output_file->IsActive());
+}
+
+#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
+TEST_F(RtcEventLogOutputFileTest, WritingToInactiveFileForbidden) {
+ RtcEventLogOutputFile output_file(output_file_name_, 2);
+ ASSERT_FALSE(output_file.Write("abc"));
+ ASSERT_FALSE(output_file.IsActive());
+ EXPECT_DEATH(output_file.Write("abc"), "");
+}
+
+TEST_F(RtcEventLogOutputFileTest, DisallowUnreasonableFileSizeLimits) {
+ // Keeping in a temporary unique_ptr to make it clearer that the death is
+ // triggered by construction, not destruction.
+ std::unique_ptr<RtcEventLogOutputFile> output_file;
+ auto create_output_file = [&] {
+ const size_t unreasonable_size =
+ RtcEventLogOutputFile::kMaxReasonableFileSize + 1;
+ output_file = absl::make_unique<RtcEventLogOutputFile>(output_file_name_,
+ unreasonable_size);
+ };
+ EXPECT_DEATH(create_output_file(), "");
+}
+#endif
+
+} // namespace webrtc