diff --git a/.hgignore b/.hgignore new file mode 100644 index 0000000..3c1d9a7 --- /dev/null +++ b/.hgignore @@ -0,0 +1,4 @@ +syntax: glob +g2log-sinks/* +3rdParty/* +g2log/build/* diff --git a/g2log/CMakeLists.txt b/g2log/CMakeLists.txt index 6f0f17e..c2643c9 100644 --- a/g2log/CMakeLists.txt +++ b/g2log/CMakeLists.txt @@ -127,7 +127,7 @@ ENDIF(MSVC) # before it can be "cmake'd" and compiled --- leaving it as OFF for now # ============================================================================ # 1. a simple test example 'g2log-FATAL-example' - option (USE_SIMPLE_EXAMPLE "Simple (fatal-crash) example " OFF) + option (USE_SIMPLE_EXAMPLE "Simple (fatal-crash) example " ON) # 2. performance test (average + worst case) for KjellKod's g2log option (USE_G2LOG_PERFORMANCE "g2log performance test" OFF) @@ -225,7 +225,7 @@ ENDIF(MSVC) # and this: http://stackoverflow.com/questions/2257464/google-test-and-visual-studio-2010-rc - SET(tests_to_run test_filechange test_io test_configuration test_sink) + SET(tests_to_run test_filechange test_io test_configuration test_sink_concept test_sink) SET(helper ${DIR_UNIT_TEST}/testing_helpers.h ${DIR_UNIT_TEST}/testing_helpers.cpp) include_directories(${DIR_UNIT_TEST}) diff --git a/g2log/src/crashhandler_unix.cpp b/g2log/src/crashhandler_unix.cpp index c688caa..2a3b210 100644 --- a/g2log/src/crashhandler_unix.cpp +++ b/g2log/src/crashhandler_unix.cpp @@ -152,7 +152,7 @@ std::string signalName(int signal_number) // --- If LOG(FATAL) or CHECK(false) the signal_number will be SIGABRT void exitWithDefaultSignalHandler(int signal_number) { - std::cerr << "Exiting - FATAL SIGNAL: " << signal_number << " " << std::flush; + std::cerr << "\nExiting - FATAL SIGNAL: " << signal_number << " " << std::flush; struct sigaction action; memset(&action, 0, sizeof(action)); // sigemptyset(&action.sa_mask); diff --git a/g2log/src/g2filesinkhelper.ipp b/g2log/src/g2filesinkhelper.ipp new file mode 100644 index 0000000..3a5e292 --- /dev/null +++ b/g2log/src/g2filesinkhelper.ipp @@ -0,0 +1,124 @@ +#ifndef G2_FILESINK_HELPER_IPP_ +#define G2_FILESINK_HELPER_IPP_ + + +#include +#include +#include +#include +#include +#include +#include + +//#include +//#include +//#include +//#include +//#include + + +namespace g2 { + namespace internal { + + + static const std::string file_name_time_formatted = "%Y%m%d-%H%M%S"; + + // check for filename validity - filename should not be part of PATH + + bool isValidFilename(const std::string& prefix_filename) { + std::string illegal_characters("/,|<>:#$%{}()[]\'\"^!?+* "); + size_t pos = prefix_filename.find_first_of(illegal_characters, 0); + if (pos != std::string::npos) { + std::cerr << "Illegal character [" << prefix_filename.at(pos) << "] in logname prefix: " << "[" << prefix_filename << "]" << std::endl; + return false; + } else if (prefix_filename.empty()) { + std::cerr << "Empty filename prefix is not allowed" << std::endl; + return false; + } + + return true; + } + + std::string prefixSanityFix(std::string prefix) { + prefix.erase(std::remove_if(prefix.begin(), prefix.end(), ::isspace), prefix.end()); + prefix.erase(std::remove(prefix.begin(), prefix.end(), '/'), prefix.end()); + prefix.erase(std::remove(prefix.begin(), prefix.end(), '\\'), prefix.end()); + prefix.erase(std::remove(prefix.begin(), prefix.end(), '.'), prefix.end()); + if (!isValidFilename(prefix)) { + return + { + }; + } + return prefix; + } + + std::string pathSanityFix(std::string path, std::string file_name) { + // Unify the delimeters,. maybe sketchy solution but it seems to work + // on at least win7 + ubuntu. All bets are off for older windows + std::replace(path.begin(), path.end(), '\\', '/'); + + // clean up in case of multiples + auto contains_end = [&](std::string & in) -> bool { + size_t size = in.size(); + if (!size) return false; + char end = in[size - 1]; + return (end == '/' || end == ' '); + }; + + while (contains_end(path)) { + path.erase(path.size() - 1); + } + + if (!path.empty()) { + path.insert(path.end(), '/'); + } + + path.insert(path.size(), file_name); + return path; + } + + std::string header() { + std::ostringstream ss_entry; + // Day Month Date Time Year: is written as "%a %b %d %H:%M:%S %Y" and formatted output as : Wed Sep 19 08:28:16 2012 + ss_entry << "\t\tg2log created log file at: " << g2::localtime_formatted(g2::systemtime_now(), "%a %b %d %H:%M:%S %Y") << "\n"; + ss_entry << "\t\tLOG format: [YYYY/MM/DD hh:mm:ss.uuu* LEVEL FILE:LINE] message\n\n"; // TODO: if(header) + return ss_entry.str(); + } + + std::string createLogFileName(const std::string& verified_prefix) { + std::stringstream oss_name; + oss_name << verified_prefix << ".g2log."; + oss_name << g2::localtime_formatted(g2::systemtime_now(), file_name_time_formatted); + oss_name << ".log"; + return oss_name.str(); + } + + bool openLogFile(const std::string& complete_file_with_path, std::ofstream& outstream) { + std::ios_base::openmode mode = std::ios_base::out; // for clarity: it's really overkill since it's an ofstream + mode |= std::ios_base::trunc; + outstream.open(complete_file_with_path, mode); + if (!outstream.is_open()) { + std::ostringstream ss_error; + ss_error << "FILE ERROR: could not open log file:[" << complete_file_with_path << "]"; + ss_error << "\n\t\t std::ios_base state = " << outstream.rdstate(); + std::cerr << ss_error.str().c_str() << std::endl; + outstream.close(); + return false; + } + return true; + } + + std::unique_ptr createLogFile(const std::string& file_with_full_path) { + std::unique_ptr out(new std::ofstream); + std::ofstream & stream(*(out.get())); + bool success_with_open_file = openLogFile(file_with_full_path, stream); + if (false == success_with_open_file) { + out.release(); + } + return out; + } + + + } +} +#endif // G2_FILESINK_HELPER_IPP_ \ No newline at end of file diff --git a/g2log/test_example/main.cpp b/g2log/test_example/main.cpp index 0e562f6..9827db1 100644 --- a/g2log/test_example/main.cpp +++ b/g2log/test_example/main.cpp @@ -43,10 +43,16 @@ int main(int argc, char** argv) double pi_d = 3.1415926535897932384626433832795; float pi_f = 3.1415926535897932384626433832795f; - - g2LogWorker logger(argv[0], path_to_log_file); - g2::initializeLogging(&logger); - std::future log_file_name = logger.logFileName(); + +// auto logger = g2LogWorker::createWithNoSink(); +// auto sinkptr = std2::make_unique(argv[0], path_to_log_file); +// auto log_handle = logger->addSink(std::move(singkptr), &g2::g2FileSink::fileWrite); +// g2::initializeLogging(logger.get()); + auto logger_n_handle = g2LogWorker::createWithDefaultFileSink(argv[0], path_to_log_file); + g2::initializeLogging(logger_n_handle.first.get()); + + + std::future log_file_name = logger_n_handle.second->call(&g2::g2FileSink::fileName); std::cout << "*** This is an example of g2log " << std::endl; std::cout << "*** It WILL exit by a FATAL trigger in the end" << std::endl; std::cout << "*** Please see the generated log and compare to " << std::endl; diff --git a/g2log/test_unit/test_filechange.cpp b/g2log/test_unit/test_filechange.cpp index 1721013..06bdae5 100644 --- a/g2log/test_unit/test_filechange.cpp +++ b/g2log/test_unit/test_filechange.cpp @@ -19,7 +19,7 @@ #include "g2logworker.h" #include "testing_helpers.h" -using namespace testing_helper__cleaner; +using namespace testing_helpers; namespace { // anonymous @@ -28,9 +28,7 @@ namespace { // anonymous g2LogWorker* g_logger_ptr = nullptr; g2::SinkHandle* g_filesink_handler = nullptr; LogFileCleaner* g_cleaner_ptr = nullptr; - - - + bool isTextAvailableInContent(const std::string &total_text, std::string msg_to_find) { std::string content(total_text); size_t location = content.find(msg_to_find); @@ -64,20 +62,19 @@ namespace { // anonymous return add_count; } - std::string setLogName(std::string new_file_to_create) { + std::string setLogName(std::string new_file_to_create) { auto future_new_log = g_filesink_handler->call(&g2::g2FileSink::changeLogFile, new_file_to_create); auto new_log = future_new_log.get(); if (!new_log.empty()) g_cleaner_ptr->addLogToClean(new_log); return new_log; } - + std::string getLogName() { - return g_filesink_handler->call(&g2::g2FileSink::fileName).get(); + return g_filesink_handler->call(&g2::g2FileSink::fileName).get(); } } // anonymous - TEST(TestOf_GetFileName, Expecting_ValidLogFile) { LOG(INFO) << "test_filechange, Retrieving file name: "; @@ -86,14 +83,14 @@ TEST(TestOf_GetFileName, Expecting_ValidLogFile) { } TEST(TestOf_ChangingLogFile, Expecting_NewLogFileUsed) { - auto old_log = g_filesink_handler->call(&g2::g2FileSink::fileName).get(); + auto old_log = getLogName(); std::string name = setLogNameAndAddCount(name_path_1); auto new_log = setLogName(name); ASSERT_NE(old_log, new_log); } TEST(TestOf_ManyThreadsChangingLogFileName, Expecting_EqualNumberLogsCreated) { - auto old_log = g_filesink_handler->call(&g2::g2FileSink::fileName).get(); + auto old_log = g_filesink_handler->call(&g2::g2FileSink::fileName).get(); if (!old_log.empty()) g_cleaner_ptr->addLogToClean(old_log); LOG(INFO) << "SoManyThreadsAllDoingChangeFileName"; @@ -113,32 +110,36 @@ TEST(TestOf_ManyThreadsChangingLogFileName, Expecting_EqualNumberLogsCreated) { TEST(TestOf_IllegalLogFileName, Expecting_NoChangeToOriginalFileName) { std::string original = getLogName(); - std::cerr << "Below WILL print 'FiLE ERROR'. This is part of the testing and perfectly OK" << std::endl; - std::cerr << "****" << std::endl; auto perhaps_a_name = setLogName("XY:/"); // does not exist ASSERT_TRUE(perhaps_a_name.empty()); - std::cerr << "****" << std::endl; std::string post_illegal = getLogName(); ASSERT_STREQ(original.c_str(), post_illegal.c_str()); } + int main(int argc, char *argv[]) { LogFileCleaner cleaner; g_cleaner_ptr = &cleaner; int return_value = 1; - + std::stringstream cerrDump; + std::string last_log_file; { - auto pair = g2LogWorker::createWithDefaultFileSink("ReplaceLogFile", name_path_2); + + testing_helpers::ScopedOut scopedCerr(std::cerr, &cerrDump); + + auto pair = g2LogWorker::createWithDefaultFileSink("ReplaceLogFile", name_path_1); //g2LogWorker logger("ReplaceLogFile", name_path_2); - testing::InitGoogleTest(&argc, argv); g_logger_ptr = pair.first.get(); // ugly but fine for this test g_filesink_handler = pair.second.get(); - g2::initializeLogging(g_logger_ptr); - cleaner.addLogToClean(g_filesink_handler->call(&g2::g2FileSink::fileName).get()); + LOG(INFO) << "test_filechange demo*" << std::endl; + + testing::InitGoogleTest(&argc, argv); return_value = RUN_ALL_TESTS(); + last_log_file = g_filesink_handler->call(&g2::g2FileSink::fileName).get(); + std::cout << "log file at: " << last_log_file << std::endl; //g2::shutDownLogging(); } std::cout << "FINISHED WITH THE TESTING" << std::endl; diff --git a/g2log/test_unit/test_io.cpp b/g2log/test_unit/test_io.cpp index ec1d6cc..6dd22d5 100644 --- a/g2log/test_unit/test_io.cpp +++ b/g2log/test_unit/test_io.cpp @@ -30,21 +30,17 @@ namespace { std::ifstream in; in.open(filename.c_str(), std::ios_base::in); if (!in.is_open()) { - return ""; // error just return empty string - test will 'fault' + return {}; // error just return empty string - test will 'fault' } std::ostringstream oss; oss << in.rdbuf(); - std::string content(oss.str()); - return content; + return oss.str(); } } // end anonymous namespace - - - - +using namespace testing_helpers; // LOG TEST(IO_RestoreLogger, Expecting_Fine_To_ShutDownMultipleTimes) { diff --git a/g2log/test_unit/test_sink_concept.cpp b/g2log/test_unit/test_sink_concept.cpp new file mode 100644 index 0000000..32371dd --- /dev/null +++ b/g2log/test_unit/test_sink_concept.cpp @@ -0,0 +1,142 @@ +#include + +#include +#include +#include +#include +#include + +#include "testing_helpers.h" +#include "std2_make_unique.hpp" +#include "g2sink.h" +#include "g2sinkwrapper.h" +#include "g2sinkhandle.h" +#include "g2logmessage.hpp" + + +using namespace std; +using namespace std2; +using namespace testing_helpers; + +class CoutSink { + stringstream buffer; + unique_ptr scope_ptr; + + CoutSink() : scope_ptr(std2::make_unique(std::cout, &buffer)) { } +public: + void clear() { buffer.str(""); } + std::string string() { return buffer.str(); } + void save(g2::internal::LogEntry msg) { std::cout << msg; } + + virtual ~CoutSink() final { } + + static std::unique_ptr createSink() + { return std::unique_ptr(new CoutSink); } +}; + + +namespace { + typedef std::shared_ptr SinkWrapperPtr; + typedef g2::internal::LogEntry LogEntry; +} + +namespace g2 { + + class Worker { + std::vector _container; // should be hidden in a pimple with a bg active object + std::unique_ptr _bg; + + void bgSave(LogEntry msg) { + for (auto& sink : _container) { + sink->send(msg); + } + } + + public: + + Worker() : _bg { + kjellkod::Active::createActive() + } + { + } + + ~Worker() { + _bg->send([this] { _container.clear(); }); + } + + void save(LogEntry msg) { + _bg->send([this, msg] { bgSave(msg); }); + } // will this be copied? + //this is guaranteed to work std::bind(&Worker::bgSave, this, msg)); } + + template + std::unique_ptr< SinkHandle > addSink(std::unique_ptr unique, DefaultLogCall call) { + auto shared = std::shared_ptr(unique.release()); + auto sink = std::make_shared < internal::Sink > (shared, call); + auto add_sink_call = [this, sink] { _container.push_back(sink); + + }; + auto wait_result = g2::spawn_task(add_sink_call, _bg.get()); + wait_result.wait(); + + auto handle = std2::make_unique< SinkHandle >(sink); + return handle; + } + }; + +} // g2 + + + + using namespace g2; + using namespace g2::internal; + + TEST(Sink, CreateHandle) { + Worker worker; + auto handle = worker.addSink(CoutSink::createSink(), &CoutSink::save); + ASSERT_NE(nullptr, handle.get()); + } + + TEST(Sink, OneSink__VerifyMsgIn) { + Worker worker; + auto handle = worker.addSink(CoutSink::createSink(), &CoutSink::save); + worker.save("Hello World!"); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + auto output = handle->call(&CoutSink::string); + ASSERT_EQ("Hello World!", output.get()); + } + + struct StringSink { + std::string raw; + void append(LogEntry entry) { raw.append(entry); } + std::string string(){return raw; } + }; + + + TEST(Sink, DualSink__VerifyMsgIn) { + Worker worker; + auto h1 = worker.addSink(CoutSink::createSink(), &CoutSink::save); + auto h2 = worker.addSink(std2::make_unique(), &StringSink::append); + worker.save("Hello World!"); + + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + auto first = h1->call(&CoutSink::string); + auto second = h2->call(&StringSink::string); + + + ASSERT_EQ("Hello World!", first.get()); + ASSERT_EQ("Hello World!", second.get()); + } + + + + TEST(Sink, DeletedSink__Exptect_badweak_ptr___exception) { + auto worker = std2::make_unique(); + auto h1 = worker->addSink(CoutSink::createSink(), &CoutSink::save); + worker->save("Hello World!"); + worker.reset(); + + auto first = h1->call(&CoutSink::string); + EXPECT_THROW(first.get(), std::bad_weak_ptr); +}