Merge lp:~thumper/nux/rolling-file-appender into lp:nux
- rolling-file-appender
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Mirco Müller |
Approved revision: | 411 |
Merged at revision: | 393 |
Proposed branch: | lp:~thumper/nux/rolling-file-appender |
Merge into: | lp:nux |
Diff against target: |
1118 lines (+987/-6) 14 files modified
.bzrignore (+4/-2) Nux/Makefile.am (+1/-1) NuxCore/AsyncFileWriter.cpp (+246/-0) NuxCore/AsyncFileWriter.h (+63/-0) NuxCore/Makefile.am (+4/-0) NuxCore/RollingFileAppender.cpp (+231/-0) NuxCore/RollingFileAppender.h (+48/-0) configure.ac (+1/-1) tests/Helpers.cpp (+52/-0) tests/Helpers.h (+63/-0) tests/Makefile.am (+7/-2) tests/test_async_file_writer.cpp (+110/-0) tests/test_main.cpp (+10/-0) tests/test_rolling_file_appender.cpp (+147/-0) |
To merge this branch: | bzr merge lp:~thumper/nux/rolling-file-appender |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Neil J. Patel (community) | Approve | ||
Mirco Müller (community) | Approve | ||
Review via email: mp+65314@code.launchpad.net |
Commit message
Description of the change
This branch adds a RollingFileAppe
intended to be used with the logging code.
The RollingFileAppender is constructed with a full path filename, and
optionally a number of backup files and maximum log size.
Every time the stream is flushed, it checks the size of the file, and if
necessary, it closes the existing file, rolls the backups[1], and opens a new
file.
This change brings in the boost filesystem shared object library. It is 80k,
but I believe that it simplifies our filesystem handling and should be used.
The tests are found in tests/test_
fixture that has setup and teardown methods that make sure that the temp
directory doesn't exist prior to running the tests, and to clean up after
itself.
[1] Rolling means deleting the last log file if we have reached our limit on
the number of backup files, and renaming logfile.x to logfile.x+1, and finally
renaming logfile to logfile.1.
- 398. By Tim Penhey
-
Found that I can simplify at least some of the tests.
Tim Penhey (thumper) wrote : | # |
On Thu, 30 Jun 2011 13:11:12 you wrote:
> Review: Needs Fixing
> Trying to compile nux with this branch merged led to this error...
This branch is going to have hacked somewhat for gio stuff :) So is the remove
NFileName branch
- 399. By Tim Penhey
-
Initial declaration for the async file writer.
- 400. By Tim Penhey
-
Make the rolling file appender not depend on boost filesystem. Damn tests don't link though.
- 401. By Tim Penhey
-
Make the tests pass with the gio based rolling file appender.
- 402. By Tim Penhey
-
Framework of the async file writer and a failing test.
- 403. By Tim Penhey
-
Closer to working code now...
- 404. By Tim Penhey
-
Trying to figure out why it isn't creating the file.
- 405. By Tim Penhey
-
Tests should work, but they don't always.
- 406. By Tim Penhey
-
Async writer now seems to work.
- 407. By Tim Penhey
-
More ignores :(
- 408. By Tim Penhey
-
Merge trunk.
- 409. By Tim Penhey
-
Change the RollingFileAppender so it writes to the files asynchronously.
- 410. By Tim Penhey
-
Emit a signal when the files are rolled. Primarily for testing, but you never know.
- 411. By Tim Penhey
-
Update the rolling file appender tests.
Tim Penhey (thumper) wrote : | # |
Hi Guys,
This is finally ready to have another look. I've kept the boost filesystem use, but only for the test suite. It allows for a secondary way to confirm that the files and directories and correct in the tests.
The RollingFileAppender now writes the log files out asynchronously using gio which is handled by the AsyncFileWriter class.
Due to the async nature, it was a PITA to test. I've added comments explaining the weirdness in the tests.
Mirco Müller (macslow) wrote : | # |
After going through this again (this early in the morning) my brain hurts... but approved now :)
Neil J. Patel (njpatel) wrote : | # |
Looks good and works well, nice one!
Preview Diff
1 | === modified file '.bzrignore' |
2 | --- .bzrignore 2011-06-30 13:26:34 +0000 |
3 | +++ .bzrignore 2011-07-15 04:02:32 +0000 |
4 | @@ -58,5 +58,7 @@ |
5 | tests/gtest-nux-core |
6 | tests/test-nux |
7 | tests/test-nux-results.xml |
8 | - |
9 | - |
10 | +tests/relative |
11 | +Logs |
12 | +**/nux.log |
13 | +**/nux.log.* |
14 | |
15 | === modified file 'Nux/Makefile.am' |
16 | --- Nux/Makefile.am 2011-06-30 13:26:34 +0000 |
17 | +++ Nux/Makefile.am 2011-07-15 04:02:32 +0000 |
18 | @@ -23,7 +23,7 @@ |
19 | $(NUX_LIBS) |
20 | |
21 | libnux_@NUX_API_VERSION@_la_LDFLAGS = \ |
22 | - $(NUX_LT_LDFLAGS) |
23 | + $(NUX_LT_LDFLAGS) -lboost_filesystem |
24 | |
25 | source_cpp = \ |
26 | $(srcdir)/PropertyItem/CheckBoxProperty.cpp \ |
27 | |
28 | === added file 'NuxCore/AsyncFileWriter.cpp' |
29 | --- NuxCore/AsyncFileWriter.cpp 1970-01-01 00:00:00 +0000 |
30 | +++ NuxCore/AsyncFileWriter.cpp 2011-07-15 04:02:32 +0000 |
31 | @@ -0,0 +1,246 @@ |
32 | +// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- |
33 | +/* |
34 | + * Copyright 2011 Inalogic® Inc. |
35 | + * |
36 | + * This program is free software: you can redistribute it and/or modify it |
37 | + * under the terms of the GNU Lesser General Public License, as |
38 | + * published by the Free Software Foundation; either version 2.1 or 3.0 |
39 | + * of the License. |
40 | + * |
41 | + * This program is distributed in the hope that it will be useful, but |
42 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
43 | + * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR |
44 | + * PURPOSE. See the applicable version of the GNU Lesser General Public |
45 | + * License for more details. |
46 | + * |
47 | + * You should have received a copy of both the GNU Lesser General Public |
48 | + * License along with this program. If not, see <http://www.gnu.org/licenses/> |
49 | + * |
50 | + * Authored by: Tim Penhey <tim.penhey@canonical.com> |
51 | + * |
52 | + */ |
53 | + |
54 | +#include "AsyncFileWriter.h" |
55 | + |
56 | +#include <sstream> |
57 | + |
58 | +#include <gio/gio.h> |
59 | + |
60 | +#include <iostream> |
61 | + |
62 | +namespace nux |
63 | +{ |
64 | + |
65 | +/** |
66 | + * CAUTION: right now this class is not thread aware. It assumes that all the |
67 | + * write calls are coming from a single thread. Perhaps we need to fix this? |
68 | + */ |
69 | +class AsyncFileWriter::Impl |
70 | +{ |
71 | +public: |
72 | + Impl(AsyncFileWriter* owner, std::string const& filename); |
73 | + ~Impl(); |
74 | + |
75 | + void Write(std::string const& data); |
76 | + void Close(); |
77 | + |
78 | + void ProcessAsync(); |
79 | + |
80 | + static void AppendAsyncCallback(GFile* source, GAsyncResult* res, Impl* impl); |
81 | + static void WriteAsyncCallback(GOutputStream* source, GAsyncResult* res, Impl* impl); |
82 | + static void CloseAsyncCallback(GOutputStream* source, GAsyncResult* res, Impl* impl); |
83 | + |
84 | + AsyncFileWriter* owner_; |
85 | + GCancellable* cancel_; |
86 | + GFile* file_; |
87 | + GFileOutputStream* output_stream_; |
88 | + bool close_pending_; |
89 | + bool pending_async_call_; |
90 | + |
91 | + std::stringstream pending_content_; |
92 | + std::string data_to_write_; |
93 | +}; |
94 | + |
95 | + |
96 | +AsyncFileWriter::Impl::Impl(AsyncFileWriter* owner, std::string const& filename) |
97 | + : owner_(owner) |
98 | + , cancel_(g_cancellable_new()) |
99 | + , file_(g_file_new_for_path(filename.c_str())) |
100 | + , output_stream_(0) |
101 | + , close_pending_(false) |
102 | + , pending_async_call_(true) |
103 | +{ |
104 | + g_file_append_to_async(file_, |
105 | + G_FILE_CREATE_NONE, |
106 | + G_PRIORITY_DEFAULT, |
107 | + cancel_, |
108 | + (GAsyncReadyCallback)&AsyncFileWriter::Impl::AppendAsyncCallback, |
109 | + this); |
110 | +} |
111 | + |
112 | +AsyncFileWriter::Impl::~Impl() |
113 | +{ |
114 | + if (pending_async_call_) |
115 | + { |
116 | + g_cancellable_cancel(cancel_); |
117 | + } |
118 | + // make sure the file is closed. |
119 | + if (output_stream_) |
120 | + { |
121 | + // If we had an output stream, sync write any pending content. |
122 | + if (pending_content_.tellp()) |
123 | + { |
124 | + std::string data(pending_content_.str()); |
125 | + gsize bytes_written; |
126 | + g_output_stream_write_all((GOutputStream*)output_stream_, |
127 | + data.c_str(), |
128 | + data.size(), |
129 | + &bytes_written, |
130 | + NULL, NULL); |
131 | + } |
132 | + owner_->closed.emit(); |
133 | + g_object_unref(output_stream_); |
134 | + } |
135 | + |
136 | + g_object_unref(file_); |
137 | + g_object_unref(cancel_); |
138 | +} |
139 | + |
140 | +void AsyncFileWriter::Impl::AppendAsyncCallback(GFile* source, |
141 | + GAsyncResult* res, |
142 | + Impl* impl) |
143 | +{ |
144 | + GError* error = NULL; |
145 | + GFileOutputStream* stream = g_file_append_to_finish(source, res, &error); |
146 | + if (error) { |
147 | + // Cancelled callbacks call back, but have a cancelled error code. |
148 | + if (error->code != G_IO_ERROR_CANCELLED) { |
149 | + std::cerr << error->message << "\n"; |
150 | + } |
151 | + g_error_free(error); |
152 | + return; |
153 | + } |
154 | + impl->output_stream_ = stream; |
155 | + impl->pending_async_call_ = false; |
156 | + impl->owner_->opened.emit(); |
157 | + impl->ProcessAsync(); |
158 | +} |
159 | + |
160 | +void AsyncFileWriter::Impl::Write(std::string const& data) |
161 | +{ |
162 | + if (close_pending_) return; |
163 | + // TODO: lock the pending_content_ access |
164 | + pending_content_ << data; |
165 | + ProcessAsync(); |
166 | +} |
167 | + |
168 | +void AsyncFileWriter::Impl::ProcessAsync() |
169 | +{ |
170 | + if (output_stream_ == NULL || pending_async_call_) return; |
171 | + |
172 | + if (pending_content_.tellp()) |
173 | + { |
174 | + // TODO: lock the pending_content_ access |
175 | + data_to_write_ = pending_content_.str(); |
176 | + g_output_stream_write_async((GOutputStream*)output_stream_, |
177 | + data_to_write_.c_str(), |
178 | + data_to_write_.size(), |
179 | + G_PRIORITY_DEFAULT, |
180 | + cancel_, |
181 | + (GAsyncReadyCallback)&AsyncFileWriter::Impl::WriteAsyncCallback, |
182 | + this); |
183 | + pending_async_call_ = true; |
184 | + } |
185 | + else if (close_pending_) |
186 | + { |
187 | + g_output_stream_close_async((GOutputStream*)output_stream_, |
188 | + G_PRIORITY_DEFAULT, |
189 | + cancel_, |
190 | + (GAsyncReadyCallback)&AsyncFileWriter::Impl::CloseAsyncCallback, |
191 | + this); |
192 | + pending_async_call_ = true; |
193 | + } |
194 | +} |
195 | + |
196 | +void AsyncFileWriter::Impl::WriteAsyncCallback(GOutputStream* source, |
197 | + GAsyncResult* res, |
198 | + Impl* impl) |
199 | +{ |
200 | + GError* error = NULL; |
201 | + gssize g_bytes_written = g_output_stream_write_finish(source, res, &error); |
202 | + if (error) { |
203 | + // Cancelled callbacks call back, but have a cancelled error code. |
204 | + if (error->code != G_IO_ERROR_CANCELLED) { |
205 | + std::cerr << error->message << "\n"; |
206 | + } |
207 | + g_error_free(error); |
208 | + return; |
209 | + } |
210 | + // g_bytes_written is signed from gio, but only negative if there is an error. |
211 | + // The error should be set too if there was an error, so no negative bytes |
212 | + // written get past here. |
213 | + std::size_t bytes_written = g_bytes_written; |
214 | + impl->pending_async_call_ = false; |
215 | + // TODO: lock the pending_content_ access |
216 | + std::string data = impl->pending_content_.str(); |
217 | + if (bytes_written >= data.size()) { |
218 | + // There should be no reason why bytes_written should be greater than the |
219 | + // number of bytes in the stream, but this is paranoia. |
220 | + impl->pending_content_.str(""); |
221 | + } else { |
222 | + impl->pending_content_.str(data.substr(bytes_written)); |
223 | + } |
224 | + impl->ProcessAsync(); |
225 | +} |
226 | + |
227 | +void AsyncFileWriter::Impl::Close() |
228 | +{ |
229 | + close_pending_ = true; |
230 | + ProcessAsync(); |
231 | +} |
232 | + |
233 | +void AsyncFileWriter::Impl::CloseAsyncCallback(GOutputStream* source, |
234 | + GAsyncResult* res, |
235 | + Impl* impl) |
236 | +{ |
237 | + GError* error = NULL; |
238 | + g_output_stream_close_finish(source, res, &error); |
239 | + if (error) { |
240 | + // Cancelled callbacks call back, but have a cancelled error code. |
241 | + if (error->code != G_IO_ERROR_CANCELLED) { |
242 | + std::cerr << error->message << "\n"; |
243 | + } |
244 | + g_error_free(error); |
245 | + return; |
246 | + } |
247 | + g_object_unref(impl->output_stream_); |
248 | + impl->output_stream_ = 0; |
249 | + impl->owner_->closed.emit(); |
250 | +} |
251 | + |
252 | +AsyncFileWriter::AsyncFileWriter(std::string const& filename) |
253 | + : pimpl(new Impl(this, filename)) |
254 | +{} |
255 | + |
256 | +AsyncFileWriter::~AsyncFileWriter() |
257 | +{ |
258 | + delete pimpl; |
259 | +} |
260 | + |
261 | +void AsyncFileWriter::Write(std::string const& data) |
262 | +{ |
263 | + pimpl->Write(data); |
264 | +} |
265 | + |
266 | +void AsyncFileWriter::Close() |
267 | +{ |
268 | + pimpl->Close(); |
269 | +} |
270 | + |
271 | +bool AsyncFileWriter::IsClosing() const |
272 | +{ |
273 | + return pimpl->close_pending_; |
274 | +} |
275 | + |
276 | + |
277 | +} // namespace nux |
278 | |
279 | === added file 'NuxCore/AsyncFileWriter.h' |
280 | --- NuxCore/AsyncFileWriter.h 1970-01-01 00:00:00 +0000 |
281 | +++ NuxCore/AsyncFileWriter.h 2011-07-15 04:02:32 +0000 |
282 | @@ -0,0 +1,63 @@ |
283 | +// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- |
284 | +/* |
285 | + * Copyright 2011 Inalogic® Inc. |
286 | + * |
287 | + * This program is free software: you can redistribute it and/or modify it |
288 | + * under the terms of the GNU Lesser General Public License, as |
289 | + * published by the Free Software Foundation; either version 2.1 or 3.0 |
290 | + * of the License. |
291 | + * |
292 | + * This program is distributed in the hope that it will be useful, but |
293 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
294 | + * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR |
295 | + * PURPOSE. See the applicable version of the GNU Lesser General Public |
296 | + * License for more details. |
297 | + * |
298 | + * You should have received a copy of both the GNU Lesser General Public |
299 | + * License along with this program. If not, see <http://www.gnu.org/licenses/> |
300 | + * |
301 | + * Authored by: Tim Penhey <tim.penhey@canonical.com> |
302 | + * |
303 | + */ |
304 | +#ifndef NUXCORE_ASYNC_FILE_WRITER_H |
305 | +#define NUXCORE_ASYNC_FILE_WRITER_H |
306 | + |
307 | +#include <string> |
308 | +#include <sigc++/sigc++.h> |
309 | + |
310 | +namespace nux |
311 | +{ |
312 | + |
313 | +/** |
314 | + * Write to a file asynchronously. |
315 | + * |
316 | + * This uses the GIO async functions, and as such depend on the gobject main |
317 | + * loop. |
318 | + */ |
319 | +class AsyncFileWriter |
320 | +{ |
321 | +public: |
322 | + AsyncFileWriter(std::string const& filename); |
323 | + // Destructor kills any pending async requests, and close the file |
324 | + // synchronously if it is open. |
325 | + ~AsyncFileWriter(); |
326 | + |
327 | + // Queue the data for writing. It'll happen some time. |
328 | + void Write(std::string const& data); |
329 | + // Close the file asynchronously. When the file is closed, the closed |
330 | + // signal is emitted. |
331 | + void Close(); |
332 | + |
333 | + bool IsClosing() const; |
334 | + |
335 | + sigc::signal<void> opened; |
336 | + sigc::signal<void> closed; |
337 | + |
338 | +private: |
339 | + class Impl; |
340 | + Impl* pimpl; |
341 | +}; |
342 | + |
343 | +} |
344 | + |
345 | +#endif |
346 | |
347 | === modified file 'NuxCore/Makefile.am' |
348 | --- NuxCore/Makefile.am 2011-06-20 22:57:47 +0000 |
349 | +++ NuxCore/Makefile.am 2011-07-15 04:02:32 +0000 |
350 | @@ -25,6 +25,7 @@ |
351 | $(NUX_LT_LDFLAGS) |
352 | |
353 | source_cpp = \ |
354 | + $(srcdir)/AsyncFileWriter.cpp \ |
355 | $(srcdir)/TextString.cpp \ |
356 | $(srcdir)/TimeFunctions.cpp \ |
357 | $(srcdir)/Template.cpp \ |
358 | @@ -80,6 +81,7 @@ |
359 | $(srcdir)/Math/Vector3.cpp \ |
360 | $(srcdir)/Math/Line3D.cpp \ |
361 | $(srcdir)/Math/Vector2.cpp \ |
362 | + $(srcdir)/RollingFileAppender.cpp \ |
363 | $(srcdir)/CRC32.cpp \ |
364 | $(srcdir)/InitiallyUnownedObject.cpp |
365 | |
366 | @@ -126,6 +128,7 @@ |
367 | $(srcdir)/SmartPtr/GenericSmartPointer.h |
368 | |
369 | source_h = \ |
370 | + $(srcdir)/AsyncFileWriter.h \ |
371 | $(srcdir)/SystemTypes.h \ |
372 | $(srcdir)/Point.h \ |
373 | $(srcdir)/CPU.h \ |
374 | @@ -141,6 +144,7 @@ |
375 | $(srcdir)/Colors.h \ |
376 | $(srcdir)/Logger.h \ |
377 | $(srcdir)/LoggingWriter.h \ |
378 | + $(srcdir)/RollingFileAppender.h \ |
379 | $(srcdir)/Memory.h \ |
380 | $(srcdir)/Error.h \ |
381 | $(srcdir)/SystemGNU.h \ |
382 | |
383 | === added file 'NuxCore/RollingFileAppender.cpp' |
384 | --- NuxCore/RollingFileAppender.cpp 1970-01-01 00:00:00 +0000 |
385 | +++ NuxCore/RollingFileAppender.cpp 2011-07-15 04:02:32 +0000 |
386 | @@ -0,0 +1,231 @@ |
387 | +// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- |
388 | +/* |
389 | + * Copyright 2011 Inalogic® Inc. |
390 | + * |
391 | + * This program is free software: you can redistribute it and/or modify it |
392 | + * under the terms of the GNU Lesser General Public License, as |
393 | + * published by the Free Software Foundation; either version 2.1 or 3.0 |
394 | + * of the License. |
395 | + * |
396 | + * This program is distributed in the hope that it will be useful, but |
397 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
398 | + * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR |
399 | + * PURPOSE. See the applicable version of the GNU Lesser General Public |
400 | + * License for more details. |
401 | + * |
402 | + * You should have received a copy of both the GNU Lesser General Public |
403 | + * License along with this program. If not, see <http://www.gnu.org/licenses/> |
404 | + * |
405 | + * Authored by: Tim Penhey <tim.penhey@canonical.com> |
406 | + * |
407 | + */ |
408 | + |
409 | +#include "RollingFileAppender.h" |
410 | + |
411 | +#include <fstream> |
412 | +#include <memory> |
413 | +#include <sstream> |
414 | +#include <stdexcept> |
415 | + |
416 | +#include <gio/gio.h> |
417 | + |
418 | +#include <boost/lexical_cast.hpp> |
419 | + |
420 | +#include "AsyncFileWriter.h" |
421 | + |
422 | +namespace nux { |
423 | +namespace logging { |
424 | + |
425 | +namespace { |
426 | + |
427 | +std::string backup_path(std::string const& path, unsigned number) |
428 | +{ |
429 | + if (number == 0) |
430 | + return path; |
431 | + |
432 | + std::ostringstream sout; |
433 | + sout << path << "." << number; |
434 | + return sout.str(); |
435 | +} |
436 | + |
437 | +// Change the implementation to string buf |
438 | +// Have a GFile representing the file on disk |
439 | +// g_file_append gives a GFileOutputStream (subclass of GOutputStream) |
440 | +// g_output_stream_write_async (only have one in progress at a time) |
441 | +// g_output_stream_flush_async (probably don't want a pending async) |
442 | +// keep our own count of the written bytes. |
443 | +// Tests don't have a g_object main loop, so we have another way... |
444 | +// while (g_main_context_pending(g_main_context_get_thread_default())) { |
445 | +// g_main_context_iteration(g_main_context_get_thread_default()); |
446 | +// } |
447 | +class RollingFileStreamBuffer : public std::stringbuf |
448 | +{ |
449 | +public: |
450 | + RollingFileStreamBuffer(std::string const& filename, |
451 | + unsigned number_of_backup_files, |
452 | + unsigned long long max_log_size, |
453 | + sigc::signal<void>& files_rolled); |
454 | + ~RollingFileStreamBuffer(); |
455 | +protected: |
456 | + virtual int sync(); |
457 | +private: |
458 | + void AsyncWriterClosed(); |
459 | + void RotateFiles(); |
460 | + |
461 | + std::shared_ptr<AsyncFileWriter> writer_; |
462 | + GFile* log_file_; |
463 | + std::string filename_; |
464 | + unsigned number_of_backup_files_; |
465 | + unsigned long long max_log_size_; |
466 | + unsigned long long bytes_written_; |
467 | + sigc::connection writer_closed_; |
468 | + sigc::signal<void>& files_rolled_; |
469 | +}; |
470 | + |
471 | +RollingFileStreamBuffer::RollingFileStreamBuffer(std::string const& filename, |
472 | + unsigned number_of_backup_files, |
473 | + unsigned long long max_log_size, |
474 | + sigc::signal<void>& files_rolled) |
475 | + : filename_(filename) |
476 | + , number_of_backup_files_(number_of_backup_files) |
477 | + , max_log_size_(max_log_size) |
478 | + , bytes_written_(0) |
479 | + , files_rolled_(files_rolled) |
480 | +{ |
481 | + // Make sure that the filename starts with a '/' for a full path. |
482 | + if (filename.empty() || filename[0] != '/') { |
483 | + std::string error_msg = "\"" + filename + "\" is not a full path"; |
484 | + throw std::runtime_error(error_msg.c_str()); |
485 | + } |
486 | + // Looks to see if our filename exists. |
487 | + if (g_file_test(filename.c_str(), G_FILE_TEST_EXISTS)) { |
488 | + // The filename needs to be a regular file. |
489 | + if (!g_file_test(filename.c_str(), G_FILE_TEST_IS_REGULAR)) { |
490 | + std::string error_msg = filename + " is not a regular file"; |
491 | + throw std::runtime_error(error_msg.c_str()); |
492 | + } |
493 | + // Rotate the files. |
494 | + RotateFiles(); |
495 | + } else { |
496 | + GFile* log_file = g_file_new_for_path(filename.c_str()); |
497 | + GFile* log_dir = g_file_get_parent(log_file); |
498 | + if (log_dir) { |
499 | + g_file_make_directory_with_parents(log_dir, NULL, NULL); |
500 | + g_object_unref(log_dir); |
501 | + g_object_unref(log_file); |
502 | + } |
503 | + else { |
504 | + g_object_unref(log_file); |
505 | + std::string error_msg = "Can't get parent for " + filename; |
506 | + throw std::runtime_error(error_msg.c_str()); |
507 | + } |
508 | + } |
509 | + // Now open the filename. |
510 | + writer_.reset(new AsyncFileWriter(filename)); |
511 | + log_file_ = g_file_new_for_path(filename_.c_str()); |
512 | +} |
513 | + |
514 | +RollingFileStreamBuffer::~RollingFileStreamBuffer() |
515 | +{ |
516 | + // We don't want notification when the writer closes now. |
517 | + if (writer_closed_.connected()) |
518 | + writer_closed_.disconnect(); |
519 | + g_object_unref(log_file_); |
520 | +} |
521 | + |
522 | +void RollingFileStreamBuffer::RotateFiles() |
523 | +{ |
524 | + // If we aren't keeping backups, no rolling needed. |
525 | + if (number_of_backup_files_ == 0) |
526 | + return; |
527 | + |
528 | + unsigned backup = number_of_backup_files_; |
529 | + std::string last_log(backup_path(filename_, backup)); |
530 | + if (g_file_test(last_log.c_str(), G_FILE_TEST_EXISTS)) { |
531 | + // Attempt to remove it. |
532 | + GFile* logfile = g_file_new_for_path(last_log.c_str()); |
533 | + g_file_delete(logfile, NULL, NULL); |
534 | + g_object_unref(logfile); |
535 | + } |
536 | + // Move the previous files out. |
537 | + while (backup > 0) { |
538 | + std::string prev_log(backup_path(filename_, --backup)); |
539 | + if (g_file_test(prev_log.c_str(), G_FILE_TEST_EXISTS)) { |
540 | + GFile* dest = g_file_new_for_path(last_log.c_str()); |
541 | + GFile* src = g_file_new_for_path(prev_log.c_str()); |
542 | + // We don't really care if there are errors for now. |
543 | + g_file_move(src, dest, G_FILE_COPY_NONE, NULL, NULL, NULL, NULL); |
544 | + g_object_unref(src); |
545 | + g_object_unref(dest); |
546 | + } |
547 | + last_log = prev_log; |
548 | + } |
549 | +} |
550 | + |
551 | +void RollingFileStreamBuffer::AsyncWriterClosed() |
552 | +{ |
553 | + // Rotate the files and open a new file writer. |
554 | + RotateFiles(); |
555 | + writer_.reset(new AsyncFileWriter(filename_)); |
556 | + bytes_written_ = 0; |
557 | + // We emit the files_rolled_ here and not in the RotateFiles method as the |
558 | + // RotateFiles is called from the constructor, which has a reference to the |
559 | + // files_rolled signal from the parent stream. If this is emitted due |
560 | + // rotating the files in the contructor, we get a seg fault due to trying to |
561 | + // use the signal before it is constructed. |
562 | + files_rolled_.emit(); |
563 | +} |
564 | + |
565 | +int RollingFileStreamBuffer::sync() |
566 | +{ |
567 | + // If the async file writer is in the middle of closing, there is nothing we can do. |
568 | + if (writer_->IsClosing()) |
569 | + return 0; |
570 | + |
571 | + std::string message = str(); |
572 | + // reset the stream |
573 | + str(""); |
574 | + |
575 | + std::size_t message_size = message.size(); |
576 | + if (message_size > 0) |
577 | + { |
578 | + bytes_written_ += message_size; |
579 | + writer_->Write(message); |
580 | + if (bytes_written_ > max_log_size_) |
581 | + { |
582 | + // Close the writer and once it is closed, rotate the files and open a new file. |
583 | + writer_closed_ = writer_->closed.connect( |
584 | + sigc::mem_fun(this, &RollingFileStreamBuffer::AsyncWriterClosed)); |
585 | + writer_->Close(); |
586 | + } |
587 | + } |
588 | + return 0; // success |
589 | +} |
590 | + |
591 | +} // anon namespace |
592 | + |
593 | +RollingFileAppender::RollingFileAppender(std::string const& filename) |
594 | + : std::ostream(new RollingFileStreamBuffer(filename, 5, 1e7, files_rolled)) |
595 | +{ |
596 | +} |
597 | + |
598 | +RollingFileAppender::RollingFileAppender(std::string const& filename, |
599 | + unsigned number_of_backup_files, |
600 | + unsigned long long max_log_size) |
601 | + : std::ostream(new RollingFileStreamBuffer(filename, |
602 | + number_of_backup_files, |
603 | + max_log_size, |
604 | + files_rolled)) |
605 | +{ |
606 | +} |
607 | + |
608 | +RollingFileAppender::~RollingFileAppender() |
609 | +{ |
610 | + rdbuf()->pubsync(); |
611 | + std::streambuf* buff = rdbuf(0); |
612 | + delete buff; |
613 | +} |
614 | + |
615 | + |
616 | +} // namespace logging |
617 | +} // namespace nux |
618 | |
619 | === added file 'NuxCore/RollingFileAppender.h' |
620 | --- NuxCore/RollingFileAppender.h 1970-01-01 00:00:00 +0000 |
621 | +++ NuxCore/RollingFileAppender.h 2011-07-15 04:02:32 +0000 |
622 | @@ -0,0 +1,48 @@ |
623 | +// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- |
624 | +/* |
625 | + * Copyright 2011 Inalogic® Inc. |
626 | + * |
627 | + * This program is free software: you can redistribute it and/or modify it |
628 | + * under the terms of the GNU Lesser General Public License, as |
629 | + * published by the Free Software Foundation; either version 2.1 or 3.0 |
630 | + * of the License. |
631 | + * |
632 | + * This program is distributed in the hope that it will be useful, but |
633 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
634 | + * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR |
635 | + * PURPOSE. See the applicable version of the GNU Lesser General Public |
636 | + * License for more details. |
637 | + * |
638 | + * You should have received a copy of both the GNU Lesser General Public |
639 | + * License along with this program. If not, see <http://www.gnu.org/licenses/> |
640 | + * |
641 | + * Authored by: Tim Penhey <tim.penhey@canonical.com> |
642 | + * |
643 | + */ |
644 | +#ifndef NUX_CORE_ROLLING_FILE_APPENDER_H |
645 | +#define NUX_CORE_ROLLING_FILE_APPENDER_H |
646 | + |
647 | +#include <ostream> |
648 | +#include <string> |
649 | + |
650 | +#include <sigc++/sigc++.h> |
651 | + |
652 | +namespace nux { |
653 | +namespace logging { |
654 | + |
655 | +class RollingFileAppender : public std::ostream |
656 | +{ |
657 | +public: |
658 | + RollingFileAppender(std::string const& filename); |
659 | + RollingFileAppender(std::string const& filename, |
660 | + unsigned number_of_backup_files, |
661 | + unsigned long long max_log_size); |
662 | + ~RollingFileAppender(); |
663 | + |
664 | + sigc::signal<void> files_rolled; |
665 | +}; |
666 | + |
667 | +} |
668 | +} |
669 | + |
670 | +#endif |
671 | |
672 | === modified file 'configure.ac' |
673 | --- configure.ac 2011-07-13 10:22:48 +0000 |
674 | +++ configure.ac 2011-07-15 04:02:32 +0000 |
675 | @@ -110,7 +110,7 @@ |
676 | |
677 | dnl =========================================================================== |
678 | |
679 | -PKG_CHECK_MODULES(NUX_CORE, glib-2.0 >= 2.25.14 gthread-2.0 sigc++-2.0) |
680 | +PKG_CHECK_MODULES(NUX_CORE, glib-2.0 >= 2.25.14 gthread-2.0 sigc++-2.0 gio-2.0) |
681 | AC_SUBST(NUX_CORE_CFLAGS) |
682 | AC_SUBST(NUX_CORE_LIBS) |
683 | |
684 | |
685 | === added file 'tests/Helpers.cpp' |
686 | --- tests/Helpers.cpp 1970-01-01 00:00:00 +0000 |
687 | +++ tests/Helpers.cpp 2011-07-15 04:02:32 +0000 |
688 | @@ -0,0 +1,52 @@ |
689 | +// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- |
690 | +/* |
691 | + * Copyright 2011 Inalogic® Inc. |
692 | + * |
693 | + * This program is free software: you can redistribute it and/or modify it |
694 | + * under the terms of the GNU Lesser General Public License, as |
695 | + * published by the Free Software Foundation; either version 2.1 or 3.0 |
696 | + * of the License. |
697 | + * |
698 | + * This program is distributed in the hope that it will be useful, but |
699 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
700 | + * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR |
701 | + * PURPOSE. See the applicable version of the GNU Lesser General Public |
702 | + * License for more details. |
703 | + * |
704 | + * You should have received a copy of both the GNU Lesser General Public |
705 | + * License along with this program. If not, see <http://www.gnu.org/licenses/> |
706 | + * |
707 | + * Authored by: Tim Penhey <tim.penhey@canonical.com> |
708 | + * |
709 | + */ |
710 | + |
711 | +#include "Helpers.h" |
712 | + |
713 | +#include <fstream> |
714 | +#include <stdexcept> |
715 | + |
716 | +namespace nux |
717 | +{ |
718 | +namespace testing |
719 | +{ |
720 | + |
721 | +std::string ReadFile(std::string const& filename) |
722 | +{ |
723 | + std::ifstream input(filename.c_str()); |
724 | + if (input.bad()) |
725 | + throw std::runtime_error("bad file"); |
726 | + return std::string((std::istreambuf_iterator<char>(input)), |
727 | + std::istreambuf_iterator<char>()); |
728 | +} |
729 | + |
730 | +void PumpGObjectMainLoop() |
731 | +{ |
732 | + GMainContext* context(g_main_context_get_thread_default()); |
733 | + while (g_main_context_pending(context)) { |
734 | + g_main_context_iteration(context, false); |
735 | + } |
736 | +} |
737 | + |
738 | + |
739 | +} |
740 | +} |
741 | |
742 | === added file 'tests/Helpers.h' |
743 | --- tests/Helpers.h 1970-01-01 00:00:00 +0000 |
744 | +++ tests/Helpers.h 2011-07-15 04:02:32 +0000 |
745 | @@ -0,0 +1,63 @@ |
746 | +// -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*- |
747 | +/* |
748 | + * Copyright 2011 Inalogic® Inc. |
749 | + * |
750 | + * This program is free software: you can redistribute it and/or modify it |
751 | + * under the terms of the GNU Lesser General Public License, as |
752 | + * published by the Free Software Foundation; either version 2.1 or 3.0 |
753 | + * of the License. |
754 | + * |
755 | + * This program is distributed in the hope that it will be useful, but |
756 | + * WITHOUT ANY WARRANTY; without even the implied warranties of |
757 | + * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR |
758 | + * PURPOSE. See the applicable version of the GNU Lesser General Public |
759 | + * License for more details. |
760 | + * |
761 | + * You should have received a copy of both the GNU Lesser General Public |
762 | + * License along with this program. If not, see <http://www.gnu.org/licenses/> |
763 | + * |
764 | + * Authored by: Tim Penhey <tim.penhey@canonical.com> |
765 | + * |
766 | + */ |
767 | +#ifndef NUX_TESTS_FILE_HELPERS_H |
768 | +#define NUX_TESTS_FILE_HELPERS_H |
769 | + |
770 | +#include <string> |
771 | +#include <glib.h> |
772 | +#include <sigc++/sigc++.h> |
773 | + |
774 | + |
775 | +namespace nux |
776 | +{ |
777 | +namespace testing |
778 | +{ |
779 | + |
780 | +std::string ReadFile(std::string const& filename); |
781 | +void PumpGObjectMainLoop(); |
782 | + |
783 | +class TestCallback |
784 | +{ |
785 | +public: |
786 | + TestCallback() : happened(false) {} |
787 | + |
788 | + sigc::slot<void> sigc_callback() { |
789 | + return sigc::mem_fun(this, &TestCallback::callback); |
790 | + } |
791 | + |
792 | + static gboolean glib_callback(gpointer data) { |
793 | + TestCallback* test = reinterpret_cast<TestCallback*>(data); |
794 | + test->callback(); |
795 | + return FALSE; |
796 | + } |
797 | + void callback() { |
798 | + happened = true; |
799 | + } |
800 | + |
801 | + bool happened; |
802 | +}; |
803 | + |
804 | + |
805 | +} |
806 | +} |
807 | + |
808 | +#endif |
809 | |
810 | === modified file 'tests/Makefile.am' |
811 | --- tests/Makefile.am 2011-06-24 14:49:09 +0000 |
812 | +++ tests/Makefile.am 2011-07-15 04:02:32 +0000 |
813 | @@ -33,8 +33,12 @@ |
814 | $(NUX_LIBS) |
815 | |
816 | gtest_nux_core_SOURCES = \ |
817 | + Helpers.cpp \ |
818 | + test_main.cpp \ |
819 | + test_async_file_writer.cpp \ |
820 | test_logger.cpp \ |
821 | - test_properties.cpp |
822 | + test_properties.cpp \ |
823 | + test_rolling_file_appender.cpp |
824 | |
825 | gtest_nux_core_CPPFLAGS = \ |
826 | -I$(srcdir) \ |
827 | @@ -55,7 +59,8 @@ |
828 | $(NUX_LIBS) |
829 | |
830 | gtest_nux_core_LDFLAGS = \ |
831 | - -lpthread -lgtest -lgtest_main -lgmock |
832 | + -lpthread -lgtest -lgmock \ |
833 | + -lboost_filesystem -lboost_system |
834 | |
835 | |
836 | #run make test as part of make check |
837 | |
838 | === added file 'tests/test_async_file_writer.cpp' |
839 | --- tests/test_async_file_writer.cpp 1970-01-01 00:00:00 +0000 |
840 | +++ tests/test_async_file_writer.cpp 2011-07-15 04:02:32 +0000 |
841 | @@ -0,0 +1,110 @@ |
842 | +#include <string> |
843 | +#include <fstream> |
844 | + |
845 | +#include <iostream> |
846 | + |
847 | +#include <gmock/gmock.h> |
848 | + |
849 | +#include <boost/filesystem.hpp> |
850 | + |
851 | +#include <glib.h> |
852 | + |
853 | +#include "NuxCore/AsyncFileWriter.h" |
854 | + |
855 | +#include "Helpers.h" |
856 | + |
857 | +namespace bf = boost::filesystem; |
858 | +using namespace testing; |
859 | +using namespace nux::testing; |
860 | + |
861 | +namespace { |
862 | + |
863 | +const std::string TEST_ROOT("/tmp/nux-test-cases"); |
864 | + |
865 | + |
866 | +class TestAsyncfileWriter : public ::testing::Test |
867 | +{ |
868 | +protected: |
869 | + virtual void SetUp() { |
870 | + // Make sure that the tests start with and empty TEST_ROOT. |
871 | + bf::remove_all(TEST_ROOT); |
872 | + bf::create_directories(TEST_ROOT); |
873 | + } |
874 | + |
875 | + virtual void TearDown() { |
876 | + // Delete the unity test directory |
877 | + bf::remove_all(TEST_ROOT); |
878 | + } |
879 | + |
880 | + bool WaitForOpen(nux::AsyncFileWriter& writer, unsigned timeout = 5) { |
881 | + TestCallback opened; |
882 | + TestCallback timed_out; |
883 | + g_timeout_add_seconds(timeout, &TestCallback::glib_callback, &timed_out); |
884 | + writer.opened.connect(opened.sigc_callback()); |
885 | + |
886 | + while (!opened.happened && !timed_out.happened) { |
887 | + PumpGObjectMainLoop(); |
888 | + } |
889 | + return opened.happened; |
890 | + } |
891 | + |
892 | + bool WaitForClose(nux::AsyncFileWriter& writer, unsigned timeout = 5) { |
893 | + TestCallback closed; |
894 | + TestCallback timed_out; |
895 | + g_timeout_add_seconds(timeout, &TestCallback::glib_callback, &timed_out); |
896 | + writer.closed.connect(closed.sigc_callback()); |
897 | + |
898 | + while (!closed.happened && !timed_out.happened) { |
899 | + PumpGObjectMainLoop(); |
900 | + } |
901 | + return closed.happened; |
902 | + } |
903 | + |
904 | +}; |
905 | + |
906 | +TEST_F(TestAsyncfileWriter, TestConstructor) { |
907 | + std::string filename(TEST_ROOT + "/empty-file"); |
908 | + { |
909 | + nux::AsyncFileWriter writer(filename); |
910 | + bool opened = WaitForOpen(writer); |
911 | + EXPECT_TRUE(opened); |
912 | + } |
913 | + EXPECT_TRUE(bf::exists(filename)); |
914 | + EXPECT_THAT(ReadFile(filename), Eq("")); |
915 | +} |
916 | + |
917 | +TEST_F(TestAsyncfileWriter, TestWrites) { |
918 | + std::string filename(TEST_ROOT + "/write-file"); |
919 | + std::string data(200, 'x'); |
920 | + { |
921 | + nux::AsyncFileWriter writer(filename); |
922 | + writer.Write(data); |
923 | + writer.Close(); |
924 | + bool closed = WaitForClose(writer); |
925 | + EXPECT_TRUE(closed); |
926 | + } |
927 | + EXPECT_THAT(ReadFile(filename), Eq(data)); |
928 | +} |
929 | + |
930 | +TEST_F(TestAsyncfileWriter, TestWriteLots) { |
931 | + std::string filename(TEST_ROOT + "/lots-file"); |
932 | + std::string data(200, 'x'); |
933 | + const int loop_count = 1000; |
934 | + { |
935 | + nux::AsyncFileWriter writer(filename); |
936 | + for (int i = 0; i < loop_count; ++i) { |
937 | + writer.Write(data); |
938 | + } |
939 | + writer.Close(); |
940 | + bool closed = WaitForClose(writer); |
941 | + EXPECT_TRUE(closed); |
942 | + } |
943 | + std::string file_content = ReadFile(filename); |
944 | + EXPECT_THAT(file_content.size(), Eq(data.size() * loop_count)); |
945 | + // They are all x's. |
946 | + EXPECT_THAT(file_content, MatchesRegex("^x+$")); |
947 | +} |
948 | + |
949 | + |
950 | + |
951 | +} // anon namespace |
952 | |
953 | === added file 'tests/test_main.cpp' |
954 | --- tests/test_main.cpp 1970-01-01 00:00:00 +0000 |
955 | +++ tests/test_main.cpp 2011-07-15 04:02:32 +0000 |
956 | @@ -0,0 +1,10 @@ |
957 | +#include <gtest/gtest.h> |
958 | +#include <glib-object.h> |
959 | + |
960 | +int main(int argc, char **argv) |
961 | +{ |
962 | + ::testing::InitGoogleTest(&argc, argv); |
963 | + g_type_init(); |
964 | + |
965 | + return RUN_ALL_TESTS(); |
966 | +} |
967 | |
968 | === added file 'tests/test_rolling_file_appender.cpp' |
969 | --- tests/test_rolling_file_appender.cpp 1970-01-01 00:00:00 +0000 |
970 | +++ tests/test_rolling_file_appender.cpp 2011-07-15 04:02:32 +0000 |
971 | @@ -0,0 +1,147 @@ |
972 | +#include <gtest/gtest.h> |
973 | +#include <gmock/gmock.h> |
974 | + |
975 | +#include <fstream> |
976 | +#include <streambuf> |
977 | +#include <string> |
978 | + |
979 | +#include <boost/filesystem.hpp> |
980 | +#include <boost/filesystem/fstream.hpp> |
981 | + |
982 | +#include "NuxCore/RollingFileAppender.h" |
983 | +#include "Helpers.h" |
984 | + |
985 | +namespace bf = boost::filesystem; |
986 | + |
987 | +using namespace nux::logging; |
988 | +using namespace nux::testing; |
989 | +using namespace testing; |
990 | + |
991 | +namespace { |
992 | + |
993 | +/** |
994 | + * Due to the asynchronous manner in which the rolling file appender writes |
995 | + * the data to disk, it is incredibly hard to test in a simple unit test as |
996 | + * the underlying file on the file system isn't created synchronously, nor is |
997 | + * the output written synchronously. |
998 | + * |
999 | + * A files_rolled event exists on the RollingFileAppender so we can at least |
1000 | + * wait for that signal and check the underlying files on the file system at |
1001 | + * that stage. This does mean that we aren't testing the smaller chunks of |
1002 | + * how it works, but more only the complete system, which I guess has to be |
1003 | + * good enough for this case. |
1004 | + */ |
1005 | + |
1006 | +const std::string TEST_ROOT("/tmp/nux-test-cases"); |
1007 | + |
1008 | +class TestRollingFileAppender : public ::testing::Test |
1009 | +{ |
1010 | +protected: |
1011 | + virtual void SetUp() { |
1012 | + // Make sure that the tests start with nothing there. |
1013 | + bf::remove_all(TEST_ROOT); |
1014 | + } |
1015 | + |
1016 | + virtual void TearDown() { |
1017 | + // Delete the unity test directory |
1018 | + bf::remove_all(TEST_ROOT); |
1019 | + } |
1020 | + |
1021 | + bool WaitForRoll(RollingFileAppender& appender, unsigned timeout = 5) { |
1022 | + TestCallback rolled; |
1023 | + TestCallback timed_out; |
1024 | + g_timeout_add_seconds(timeout, &TestCallback::glib_callback, &timed_out); |
1025 | + appender.files_rolled.connect(rolled.sigc_callback()); |
1026 | + |
1027 | + while (!rolled.happened && !timed_out.happened) { |
1028 | + PumpGObjectMainLoop(); |
1029 | + } |
1030 | + return rolled.happened; |
1031 | + } |
1032 | + |
1033 | +}; |
1034 | + |
1035 | +TEST_F(TestRollingFileAppender, NoTestRoot) { |
1036 | + // The test root should not exist. |
1037 | + EXPECT_FALSE(bf::exists(TEST_ROOT)); |
1038 | +} |
1039 | + |
1040 | +TEST_F(TestRollingFileAppender, TestLogFileRollsAtFlush) { |
1041 | + |
1042 | + std::string logfile = TEST_ROOT + "/nux.log"; |
1043 | + unsigned max_log_size = 20; // roll every 20 characters |
1044 | + RollingFileAppender output(logfile, 5, max_log_size); |
1045 | + |
1046 | + output << "Testing the rolling of the logfile" << std::endl; |
1047 | + WaitForRoll(output); |
1048 | + output << "Long line greater than max_log_size" << std::endl; |
1049 | + WaitForRoll(output); |
1050 | + |
1051 | + // Since the log files are rolled on flush, if the last thing written out |
1052 | + // takes the filesize greater than the max_log_size, the log files are |
1053 | + // rolled and the current file being appended to is now empty. |
1054 | + EXPECT_THAT(ReadFile(logfile + ".1"), |
1055 | + Eq("Long line greater than max_log_size\n")); |
1056 | + EXPECT_THAT(ReadFile(logfile + ".2"), |
1057 | + Eq("Testing the rolling of the logfile\n")); |
1058 | +} |
1059 | + |
1060 | +TEST_F(TestRollingFileAppender, TestExistingLogFileMoved) { |
1061 | + |
1062 | + std::string logfile = TEST_ROOT + "/nux.log"; |
1063 | + { |
1064 | + bf::create_directories(bf::path(logfile).parent_path()); |
1065 | + std::ofstream output(logfile); |
1066 | + output << "Existing file."; |
1067 | + } |
1068 | + EXPECT_TRUE(bf::exists(logfile)); |
1069 | + |
1070 | + RollingFileAppender output(logfile); |
1071 | + |
1072 | + EXPECT_THAT(ReadFile(logfile + ".1"), |
1073 | + Eq("Existing file.")); |
1074 | +} |
1075 | + |
1076 | +TEST_F(TestRollingFileAppender, TestDeletingOld) { |
1077 | + |
1078 | + std::string logfile = TEST_ROOT + "/nux.log"; |
1079 | + // Two backups, size 20 bytes. |
1080 | + RollingFileAppender output(logfile, 2, 20); |
1081 | + |
1082 | + // Due to the asynchronous manner in which the output is sent to the |
1083 | + // underlying file, we explicitly wait for the roll here. Otherwise we may |
1084 | + // just send all the logging lines to one file then it would roll. |
1085 | + output << "Oldest line should be deleted." << std::endl; |
1086 | + WaitForRoll(output); |
1087 | + output << "This line will be in the last backup." << std::endl; |
1088 | + WaitForRoll(output); |
1089 | + output << "This is backup number 1." << std::endl; |
1090 | + WaitForRoll(output); |
1091 | + |
1092 | + EXPECT_THAT(ReadFile(logfile + ".1"), |
1093 | + Eq("This is backup number 1.\n")); |
1094 | + EXPECT_THAT(ReadFile(logfile + ".2"), |
1095 | + Eq("This line will be in the last backup.\n")); |
1096 | + EXPECT_FALSE(bf::exists(logfile + ".3")); |
1097 | +} |
1098 | + |
1099 | +TEST_F(TestRollingFileAppender, TestFullPathNeeded) { |
1100 | + EXPECT_THROW(RollingFileAppender("nux.log"), std::runtime_error); |
1101 | + EXPECT_THROW(RollingFileAppender("relative/nux.log"), std::runtime_error); |
1102 | +} |
1103 | + |
1104 | +TEST_F(TestRollingFileAppender, TestFileNeeded) { |
1105 | + // For some obscure reason, EXPECT_THROW won't accept: |
1106 | + // RollingFileAppender(logfile) |
1107 | + // as its first arg. |
1108 | + std::string directory_path = TEST_ROOT + "/somedir"; |
1109 | + bf::create_directories(directory_path); |
1110 | + EXPECT_THROW(RollingFileAppender appender(directory_path), std::runtime_error); |
1111 | + |
1112 | + std::string symlink_path = TEST_ROOT + "/somelink"; |
1113 | + bf::create_symlink(directory_path, symlink_path); |
1114 | + EXPECT_THROW(RollingFileAppender appender(symlink_path), std::runtime_error); |
1115 | +} |
1116 | + |
1117 | + |
1118 | +} // anon namespace |
Trying to compile nux with this branch merged led to this error...
make[2]: Betrete Verzeichnis '/tmp/bla/ nux/examples' .libs/libnux- core-1. 0.so: undefined reference to `boost: :filesystem: :detail: :remove_ api(std: :basic_ string< char, std::char_ traits< char>, std::allocator< char> > const&)' .libs/libnux- core-1. 0.so: undefined reference to `boost: :system: :get_system_ category( )' .libs/libnux- core-1. 0.so: undefined reference to `boost: :filesystem: :detail: :status_ api(std: :basic_ string< char, std::char_ traits< char>, std::allocator< char> > const&, boost:: system: :error_ code&)' .libs/libnux- core-1. 0.so: undefined reference to `boost: :filesystem: :detail: :file_size_ api(std: :basic_ string< char, std::char_ traits< char>, std::allocator< char> > const&)' .libs/libnux- core-1. 0.so: undefined reference to `boost: :filesystem: :detail: :create_ directory_ api(std: :basic_ string< char, std::char_ traits< char>, std::allocator< char> > const&)' .libs/libnux- core-1. 0.so: undefined reference to `boost: :system: :get_generic_ category( )' .libs/libnux- core-1. 0.so: undefined reference to `boost: :filesystem: :detail: :symlink_ status_ api(std: :basic_ string< char, std::char_ traits< char>, std::allocator< char> > const&, boost:: system: :error_ code&)' .libs/libnux- core-1. 0.so: undefined reference to `boost: :filesystem: :detail: :rename_ api(std: :basic_ string< char, std::char_ traits< char>, std::allocator< char> > const&, std::basic_ string< char, std::char_ traits< char>, std::allocator< char> > const&)'
CXXLD button
../NuxCore/
../NuxCore/
../NuxCore/
../NuxCore/
../NuxCore/
../NuxCore/
../NuxCore/
../NuxCore/
collect2: ld returned 1 exit status