1 /*
2 * Copyright 2015 The WebRTC Project Authors. All rights reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11 #include "webrtc/base/arraysize.h"
12 #include "webrtc/base/checks.h"
13 #include "webrtc/base/filerotatingstream.h"
14 #include "webrtc/base/fileutils.h"
15 #include "webrtc/base/gunit.h"
16 #include "webrtc/base/pathutils.h"
17
18 namespace rtc {
19
20 class FileRotatingStreamTest : public ::testing::Test {
21 protected:
22 static const char* kFilePrefix;
23 static const size_t kMaxFileSize;
24
Init(const std::string & dir_name,const std::string & file_prefix,size_t max_file_size,size_t num_log_files)25 void Init(const std::string& dir_name,
26 const std::string& file_prefix,
27 size_t max_file_size,
28 size_t num_log_files) {
29 Pathname test_path;
30 ASSERT_TRUE(Filesystem::GetAppTempFolder(&test_path));
31 // Append per-test output path in order to run within gtest parallel.
32 test_path.AppendFolder(dir_name);
33 ASSERT_TRUE(Filesystem::CreateFolder(test_path));
34 dir_path_ = test_path.pathname();
35 ASSERT_TRUE(dir_path_.size());
36 stream_.reset(new FileRotatingStream(dir_path_, file_prefix, max_file_size,
37 num_log_files));
38 }
39
TearDown()40 void TearDown() override {
41 stream_.reset();
42 if (dir_path_.size() && Filesystem::IsFolder(dir_path_) &&
43 Filesystem::IsTemporaryPath(dir_path_)) {
44 Filesystem::DeleteFolderAndContents(dir_path_);
45 }
46 }
47
48 // Writes the data to the stream and flushes it.
WriteAndFlush(const void * data,const size_t data_len)49 void WriteAndFlush(const void* data, const size_t data_len) {
50 EXPECT_EQ(SR_SUCCESS, stream_->WriteAll(data, data_len, nullptr, nullptr));
51 EXPECT_TRUE(stream_->Flush());
52 }
53
54 // Checks that the stream reads in the expected contents and then returns an
55 // end of stream result.
VerifyStreamRead(const char * expected_contents,const size_t expected_length,const std::string & dir_path,const char * file_prefix)56 void VerifyStreamRead(const char* expected_contents,
57 const size_t expected_length,
58 const std::string& dir_path,
59 const char* file_prefix) {
60 scoped_ptr<FileRotatingStream> stream;
61 stream.reset(new FileRotatingStream(dir_path, file_prefix));
62 ASSERT_TRUE(stream->Open());
63 size_t read = 0;
64 size_t stream_size = 0;
65 EXPECT_TRUE(stream->GetSize(&stream_size));
66 scoped_ptr<uint8_t[]> buffer(new uint8_t[expected_length]);
67 EXPECT_EQ(SR_SUCCESS,
68 stream->ReadAll(buffer.get(), expected_length, &read, nullptr));
69 EXPECT_EQ(0, memcmp(expected_contents, buffer.get(), expected_length));
70 EXPECT_EQ(SR_EOS, stream->ReadAll(buffer.get(), 1, nullptr, nullptr));
71 EXPECT_EQ(stream_size, read);
72 }
73
VerifyFileContents(const char * expected_contents,const size_t expected_length,const std::string & file_path)74 void VerifyFileContents(const char* expected_contents,
75 const size_t expected_length,
76 const std::string& file_path) {
77 scoped_ptr<uint8_t[]> buffer(new uint8_t[expected_length]);
78 scoped_ptr<FileStream> stream(Filesystem::OpenFile(file_path, "r"));
79 EXPECT_TRUE(stream);
80 if (!stream) {
81 return;
82 }
83 EXPECT_EQ(rtc::SR_SUCCESS,
84 stream->ReadAll(buffer.get(), expected_length, nullptr, nullptr));
85 EXPECT_EQ(0, memcmp(expected_contents, buffer.get(), expected_length));
86 size_t file_size = 0;
87 EXPECT_TRUE(stream->GetSize(&file_size));
88 EXPECT_EQ(file_size, expected_length);
89 }
90
91 scoped_ptr<FileRotatingStream> stream_;
92 std::string dir_path_;
93 };
94
95 const char* FileRotatingStreamTest::kFilePrefix = "FileRotatingStreamTest";
96 const size_t FileRotatingStreamTest::kMaxFileSize = 2;
97
98 // Tests that stream state is correct before and after Open / Close.
TEST_F(FileRotatingStreamTest,State)99 TEST_F(FileRotatingStreamTest, State) {
100 Init("FileRotatingStreamTestState", kFilePrefix, kMaxFileSize, 3);
101
102 EXPECT_EQ(SS_CLOSED, stream_->GetState());
103 ASSERT_TRUE(stream_->Open());
104 EXPECT_EQ(SS_OPEN, stream_->GetState());
105 stream_->Close();
106 EXPECT_EQ(SS_CLOSED, stream_->GetState());
107 }
108
109 // Tests that nothing is written to file when data of length zero is written.
TEST_F(FileRotatingStreamTest,EmptyWrite)110 TEST_F(FileRotatingStreamTest, EmptyWrite) {
111 Init("FileRotatingStreamTestEmptyWrite", kFilePrefix, kMaxFileSize, 3);
112
113 ASSERT_TRUE(stream_->Open());
114 WriteAndFlush("a", 0);
115
116 std::string logfile_path = stream_->GetFilePath(0);
117 scoped_ptr<FileStream> stream(Filesystem::OpenFile(logfile_path, "r"));
118 size_t file_size = 0;
119 EXPECT_TRUE(stream->GetSize(&file_size));
120 EXPECT_EQ(0u, file_size);
121 }
122
123 // Tests that a write operation followed by a read returns the expected data
124 // and writes to the expected files.
TEST_F(FileRotatingStreamTest,WriteAndRead)125 TEST_F(FileRotatingStreamTest, WriteAndRead) {
126 Init("FileRotatingStreamTestWriteAndRead", kFilePrefix, kMaxFileSize, 3);
127
128 ASSERT_TRUE(stream_->Open());
129 // The test is set up to create three log files of length 2. Write and check
130 // contents.
131 std::string messages[3] = {"aa", "bb", "cc"};
132 for (size_t i = 0; i < arraysize(messages); ++i) {
133 const std::string& message = messages[i];
134 WriteAndFlush(message.c_str(), message.size());
135 // Since the max log size is 2, we will be causing rotation. Read from the
136 // next file.
137 VerifyFileContents(message.c_str(), message.size(),
138 stream_->GetFilePath(1));
139 }
140 // Check that exactly three files exist.
141 for (size_t i = 0; i < arraysize(messages); ++i) {
142 EXPECT_TRUE(Filesystem::IsFile(stream_->GetFilePath(i)));
143 }
144 std::string message("d");
145 WriteAndFlush(message.c_str(), message.size());
146 for (size_t i = 0; i < arraysize(messages); ++i) {
147 EXPECT_TRUE(Filesystem::IsFile(stream_->GetFilePath(i)));
148 }
149 // TODO(tkchin): Maybe check all the files in the dir.
150
151 // Reopen for read.
152 std::string expected_contents("bbccd");
153 VerifyStreamRead(expected_contents.c_str(), expected_contents.size(),
154 dir_path_, kFilePrefix);
155 }
156
157 // Tests that writing data greater than the total capacity of the files
158 // overwrites the files correctly and is read correctly after.
TEST_F(FileRotatingStreamTest,WriteOverflowAndRead)159 TEST_F(FileRotatingStreamTest, WriteOverflowAndRead) {
160 Init("FileRotatingStreamTestWriteOverflowAndRead", kFilePrefix, kMaxFileSize,
161 3);
162 ASSERT_TRUE(stream_->Open());
163 // This should cause overflow across all three files, such that the first file
164 // we wrote to also gets overwritten.
165 std::string message("foobarbaz");
166 WriteAndFlush(message.c_str(), message.size());
167 std::string expected_file_contents("z");
168 VerifyFileContents(expected_file_contents.c_str(),
169 expected_file_contents.size(), stream_->GetFilePath(0));
170 std::string expected_stream_contents("arbaz");
171 VerifyStreamRead(expected_stream_contents.c_str(),
172 expected_stream_contents.size(), dir_path_, kFilePrefix);
173 }
174
175 // Tests that the returned file paths have the right folder and prefix.
TEST_F(FileRotatingStreamTest,GetFilePath)176 TEST_F(FileRotatingStreamTest, GetFilePath) {
177 Init("FileRotatingStreamTestGetFilePath", kFilePrefix, kMaxFileSize, 20);
178 for (auto i = 0; i < 20; ++i) {
179 Pathname path(stream_->GetFilePath(i));
180 EXPECT_EQ(0, path.folder().compare(dir_path_));
181 EXPECT_EQ(0, path.filename().compare(0, strlen(kFilePrefix), kFilePrefix));
182 }
183 }
184
185 class CallSessionFileRotatingStreamTest : public ::testing::Test {
186 protected:
Init(const std::string & dir_name,size_t max_total_log_size)187 void Init(const std::string& dir_name, size_t max_total_log_size) {
188 Pathname test_path;
189 ASSERT_TRUE(Filesystem::GetAppTempFolder(&test_path));
190 // Append per-test output path in order to run within gtest parallel.
191 test_path.AppendFolder(dir_name);
192 ASSERT_TRUE(Filesystem::CreateFolder(test_path));
193 dir_path_ = test_path.pathname();
194 ASSERT_TRUE(dir_path_.size());
195 stream_.reset(
196 new CallSessionFileRotatingStream(dir_path_, max_total_log_size));
197 }
198
TearDown()199 virtual void TearDown() {
200 stream_.reset();
201 if (dir_path_.size() && Filesystem::IsFolder(dir_path_) &&
202 Filesystem::IsTemporaryPath(dir_path_)) {
203 Filesystem::DeleteFolderAndContents(dir_path_);
204 }
205 }
206
207 // Writes the data to the stream and flushes it.
WriteAndFlush(const void * data,const size_t data_len)208 void WriteAndFlush(const void* data, const size_t data_len) {
209 EXPECT_EQ(SR_SUCCESS, stream_->WriteAll(data, data_len, nullptr, nullptr));
210 EXPECT_TRUE(stream_->Flush());
211 }
212
213 // Checks that the stream reads in the expected contents and then returns an
214 // end of stream result.
VerifyStreamRead(const char * expected_contents,const size_t expected_length,const std::string & dir_path)215 void VerifyStreamRead(const char* expected_contents,
216 const size_t expected_length,
217 const std::string& dir_path) {
218 scoped_ptr<CallSessionFileRotatingStream> stream(
219 new CallSessionFileRotatingStream(dir_path));
220 ASSERT_TRUE(stream->Open());
221 size_t read = 0;
222 size_t stream_size = 0;
223 EXPECT_TRUE(stream->GetSize(&stream_size));
224 scoped_ptr<uint8_t[]> buffer(new uint8_t[expected_length]);
225 EXPECT_EQ(SR_SUCCESS,
226 stream->ReadAll(buffer.get(), expected_length, &read, nullptr));
227 EXPECT_EQ(0, memcmp(expected_contents, buffer.get(), expected_length));
228 EXPECT_EQ(SR_EOS, stream->ReadAll(buffer.get(), 1, nullptr, nullptr));
229 EXPECT_EQ(stream_size, read);
230 }
231
232 scoped_ptr<CallSessionFileRotatingStream> stream_;
233 std::string dir_path_;
234 };
235
236 // Tests that writing and reading to a stream with the smallest possible
237 // capacity works.
TEST_F(CallSessionFileRotatingStreamTest,WriteAndReadSmallest)238 TEST_F(CallSessionFileRotatingStreamTest, WriteAndReadSmallest) {
239 Init("CallSessionFileRotatingStreamTestWriteAndReadSmallest", 4);
240
241 ASSERT_TRUE(stream_->Open());
242 std::string message("abcde");
243 WriteAndFlush(message.c_str(), message.size());
244 std::string expected_contents("abe");
245 VerifyStreamRead(expected_contents.c_str(), expected_contents.size(),
246 dir_path_);
247 }
248
249 // Tests that writing and reading to a stream with capacity lesser than 4MB
250 // behaves correctly.
TEST_F(CallSessionFileRotatingStreamTest,WriteAndReadSmall)251 TEST_F(CallSessionFileRotatingStreamTest, WriteAndReadSmall) {
252 Init("CallSessionFileRotatingStreamTestWriteAndReadSmall", 8);
253
254 ASSERT_TRUE(stream_->Open());
255 std::string message("123456789");
256 WriteAndFlush(message.c_str(), message.size());
257 std::string expected_contents("1234789");
258 VerifyStreamRead(expected_contents.c_str(), expected_contents.size(),
259 dir_path_);
260 }
261
262 // Tests that writing and reading to a stream with capacity greater than 4MB
263 // behaves correctly.
TEST_F(CallSessionFileRotatingStreamTest,WriteAndReadLarge)264 TEST_F(CallSessionFileRotatingStreamTest, WriteAndReadLarge) {
265 Init("CallSessionFileRotatingStreamTestWriteAndReadLarge", 6 * 1024 * 1024);
266
267 ASSERT_TRUE(stream_->Open());
268 const size_t buffer_size = 1024 * 1024;
269 scoped_ptr<uint8_t[]> buffer(new uint8_t[buffer_size]);
270 for (int i = 0; i < 8; i++) {
271 memset(buffer.get(), i, buffer_size);
272 EXPECT_EQ(SR_SUCCESS,
273 stream_->WriteAll(buffer.get(), buffer_size, nullptr, nullptr));
274 }
275
276 stream_.reset(new CallSessionFileRotatingStream(dir_path_));
277 ASSERT_TRUE(stream_->Open());
278 scoped_ptr<uint8_t[]> expected_buffer(new uint8_t[buffer_size]);
279 int expected_vals[] = {0, 1, 2, 6, 7};
280 for (size_t i = 0; i < arraysize(expected_vals); ++i) {
281 memset(expected_buffer.get(), expected_vals[i], buffer_size);
282 EXPECT_EQ(SR_SUCCESS,
283 stream_->ReadAll(buffer.get(), buffer_size, nullptr, nullptr));
284 EXPECT_EQ(0, memcmp(buffer.get(), expected_buffer.get(), buffer_size));
285 }
286 EXPECT_EQ(SR_EOS, stream_->ReadAll(buffer.get(), 1, nullptr, nullptr));
287 }
288
289 // Tests that writing and reading to a stream where only the first file is
290 // written to behaves correctly.
TEST_F(CallSessionFileRotatingStreamTest,WriteAndReadFirstHalf)291 TEST_F(CallSessionFileRotatingStreamTest, WriteAndReadFirstHalf) {
292 Init("CallSessionFileRotatingStreamTestWriteAndReadFirstHalf",
293 6 * 1024 * 1024);
294 ASSERT_TRUE(stream_->Open());
295 const size_t buffer_size = 1024 * 1024;
296 scoped_ptr<uint8_t[]> buffer(new uint8_t[buffer_size]);
297 for (int i = 0; i < 2; i++) {
298 memset(buffer.get(), i, buffer_size);
299 EXPECT_EQ(SR_SUCCESS,
300 stream_->WriteAll(buffer.get(), buffer_size, nullptr, nullptr));
301 }
302
303 stream_.reset(new CallSessionFileRotatingStream(dir_path_));
304 ASSERT_TRUE(stream_->Open());
305 scoped_ptr<uint8_t[]> expected_buffer(new uint8_t[buffer_size]);
306 int expected_vals[] = {0, 1};
307 for (size_t i = 0; i < arraysize(expected_vals); ++i) {
308 memset(expected_buffer.get(), expected_vals[i], buffer_size);
309 EXPECT_EQ(SR_SUCCESS,
310 stream_->ReadAll(buffer.get(), buffer_size, nullptr, nullptr));
311 EXPECT_EQ(0, memcmp(buffer.get(), expected_buffer.get(), buffer_size));
312 }
313 EXPECT_EQ(SR_EOS, stream_->ReadAll(buffer.get(), 1, nullptr, nullptr));
314 }
315
316 } // namespace rtc
317