/* * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. * */ #include "precompiled.hpp" /////////////// Unit tests /////////////// #ifndef PRODUCT #include "logging/log.hpp" #include "logging/logConfiguration.hpp" #include "logging/logMessage.hpp" #include "logging/logMessageBuffer.hpp" #include "logging/logOutput.hpp" #include "memory/resourceArea.hpp" static bool file_contains_substrings_in_order(const char* filename, const char* substrs[]) { FILE* fp = fopen(filename, "r"); assert(fp, "File read error"); char buffer[1024]; for (int i = 0; substrs[i] != NULL; i++) { assert(sizeof(buffer) > strlen(substrs[i]), "insufficient buffer"); bool found = false; while (fgets(buffer, sizeof(buffer), fp) != NULL) { if (strstr(buffer, substrs[i]) != NULL) { found = true; break; } } if (!found) { return false; } } fclose(fp); return true; } static bool file_contains_substring(const char* filename, const char* substr) { const char* strs[] = {substr, NULL}; return file_contains_substrings_in_order(filename, strs); } void Test_log_length() { remove("loglengthoutput.txt"); // Write long message to output file ResourceMark rm; LogHandle(logging) log; bool success = LogConfiguration::parse_log_arguments("loglengthoutput.txt", "logging=trace", NULL, NULL, log.error_stream()); assert(success, "test unable to configure logging"); log.trace("01:1234567890-" "02:1234567890-" "03:1234567890-" "04:1234567890-" "05:1234567890-" "06:1234567890-" "07:1234567890-" "08:1234567890-" "09:1234567890-" "10:1234567890-" "11:1234567890-" "12:1234567890-" "13:1234567890-" "14:1234567890-" "15:1234567890-" "16:1234567890-" "17:1234567890-" "18:1234567890-" "19:1234567890-" "20:1234567890-" "21:1234567890-" "22:1234567890-" "23:1234567890-" "24:1234567890-" "25:1234567890-" "26:1234567890-" "27:1234567890-" "28:1234567890-" "29:1234567890-" "30:1234567890-" "31:1234567890-" "32:1234567890-" "33:1234567890-" "34:1234567890-" "35:1234567890-" "36:1234567890-" "37:1234567890-"); LogConfiguration::parse_log_arguments("loglengthoutput.txt", "all=off", NULL, NULL, log.error_stream()); // Look for end of message in output file assert(file_contains_substring("loglengthoutput.txt", "37:1234567890-"), "logging print size error"); remove("loglengthoutput.txt"); } #define assert_str_eq(s1, s2) \ assert(strcmp(s1, s2) == 0, "Expected '%s' to equal '%s'", s1, s2) #define assert_char_in(c, s) \ assert(strchr(s, c) != NULL, "Expected '%s' to contain character '%c'", s, c) #define assert_char_not_in(c, s) \ assert(strchr(s, c) == NULL, "Expected '%s' to *not* contain character '%c'", s, c) void Test_configure_stdout() { ResourceMark rm; LogHandle(logging) log; LogOutput* stdoutput = LogOutput::Stdout; // Save current stdout config and clear it char* saved_config = os::strdup_check_oom(stdoutput->config_string()); LogConfiguration::parse_log_arguments("stdout", "all=off", NULL, NULL, log.error_stream()); // Enable 'logging=info', verifying it has been set LogConfiguration::configure_stdout(LogLevel::Info, true, LOG_TAGS(logging)); assert_str_eq("logging=info,", stdoutput->config_string()); assert(log_is_enabled(Info, logging), "logging was not properly enabled"); // Enable 'gc=debug' (no wildcard), verifying no other tags are enabled LogConfiguration::configure_stdout(LogLevel::Debug, true, LOG_TAGS(gc)); // No '+' character means only single tags are enabled, and no combinations assert_char_not_in('+', stdoutput->config_string()); assert(log_is_enabled(Debug, gc), "logging was not properly enabled"); // Enable 'gc*=trace' (with wildcard), verifying at least one tag combination is enabled (gc+...) LogConfiguration::configure_stdout(LogLevel::Trace, false, LOG_TAGS(gc)); assert_char_in('+', stdoutput->config_string()); assert(log_is_enabled(Trace, gc), "logging was not properly enabled"); // Disable 'gc*' and 'logging', verifying all logging is properly disabled LogConfiguration::configure_stdout(LogLevel::Off, false, LOG_TAGS(gc)); LogConfiguration::configure_stdout(LogLevel::Off, true, LOG_TAGS(logging)); assert_str_eq("all=off", stdoutput->config_string()); // Restore saved configuration LogConfiguration::parse_log_arguments("stdout", saved_config, NULL, NULL, log.error_stream()); os::free(saved_config); } class LogMessageTest { private: static LogHandle(logging) _log; static const char* _level_filename[]; static void test_level_inclusion(); static void test_long_message(); static void test_message_with_many_lines(); static void test_line_order(); static void test_prefixing(); static void test_scoped_messages(); public: static void test(); }; const char* LogMessageTest::_level_filename[] = { NULL, // LogLevel::Off #define LOG_LEVEL(name, printname) "multiline-" #printname ".log", LOG_LEVEL_LIST #undef LOG_LEVEL }; void Test_multiline_logging() { LogMessageTest::test(); } void LogMessageTest::test() { ResourceMark rm; for (int i = 0; i < LogLevel::Count; i++) { char buf[32]; // Attempt to remove possibly pre-existing log files remove(_level_filename[i]); jio_snprintf(buf, sizeof(buf), "logging=%s", LogLevel::name(static_cast(i))); bool success = LogConfiguration::parse_log_arguments(_level_filename[i], buf, NULL, NULL, _log.error_stream()); assert(success, "unable to configure logging to file '%s'", _level_filename[i]); } test_level_inclusion(); test_line_order(); test_long_message(); test_message_with_many_lines(); test_prefixing(); test_scoped_messages(); // Stop logging to the files and remove them. for (int i = 0; i < LogLevel::Count; i++) { LogConfiguration::parse_log_arguments(_level_filename[i], "all=off", NULL, NULL, _log.error_stream()); remove(_level_filename[i]); } } // Verify that messages with multiple levels are written // to outputs configured for all the corresponding levels void LogMessageTest::test_level_inclusion() { const size_t message_count = 10; LogMessageBuffer msg[message_count]; struct { int message_number; LogLevelType level; } lines[] = { { 0, LogLevel::Error }, { 1, LogLevel::Info }, { 2, LogLevel::Info }, { 2, LogLevel::Debug }, { 3, LogLevel::Info }, { 3, LogLevel::Warning }, { 4, LogLevel::Debug }, { 4, LogLevel::Warning }, { 5, LogLevel::Trace }, { 5, LogLevel::Debug }, { 6, LogLevel::Warning }, { 6, LogLevel::Error }, { 7, LogLevel::Trace }, { 7, LogLevel::Info }, { 7, LogLevel::Debug }, { 8, LogLevel::Trace }, { 8, LogLevel::Debug }, { 8, LogLevel::Info }, { 8, LogLevel::Warning }, { 8, LogLevel::Error}, { 9, LogLevel::Trace } }; // Fill in messages with the above lines for (size_t i = 0; i < ARRAY_SIZE(lines); i++) { switch (lines[i].level) { #define LOG_LEVEL(name, printname) \ case LogLevel::name: \ msg[lines[i].message_number].printname("msg[%d]: " #printname, lines[i].message_number); \ break; LOG_LEVEL_LIST #undef LOG_LEVEL } } for (size_t i = 0; i < message_count; i++) { _log.write(msg[i]); } // Verify that lines are written to the expected log files for (size_t i = 0; i < ARRAY_SIZE(lines); i++) { char expected[256]; jio_snprintf(expected, sizeof(expected), "msg[%d]: %s", lines[i].message_number, LogLevel::name(lines[i].level)); for (int level = lines[i].level; level > 0; level--) { assert(file_contains_substring(_level_filename[level], expected), "line #" SIZE_FORMAT " missing from log file '%s'", i, _level_filename[level]); } for (int level = lines[i].level + 1; level < LogLevel::Count; level++) { assert(!file_contains_substring(_level_filename[level], expected), "line #" SIZE_FORMAT " erroneously included in log file '%s'", i, _level_filename[level]); } } } // Verify that messages are logged in the order they are added to the log message void LogMessageTest::test_line_order() { LogMessageBuffer msg; msg.info("info line").error("error line").trace("trace line") .error("another error").warning("warning line").debug("debug line"); _log.write(msg); const char* expected[] = { "info line", "error line", "trace line", "another error", "warning line", "debug line", NULL }; assert(file_contains_substrings_in_order(_level_filename[LogLevel::Trace], expected), "output missing or in incorrect order"); } void LogMessageTest::test_long_message() { // Write 10K bytes worth of log data LogMessageBuffer msg; const size_t size = 10 * K; const char* start_marker = "#start#"; const char* end_marker = "#the end#"; char* data = NEW_C_HEAP_ARRAY(char, size, mtLogging); // fill buffer with start_marker...some data...end_marker sprintf(data, "%s", start_marker); for (size_t i = strlen(start_marker); i < size; i++) { data[i] = '0' + (i % 10); } sprintf(data + size - strlen(end_marker) - 1, "%s", end_marker); msg.trace("%s", data); // Adds a newline, making the message exactly 10K in length. _log.write(msg); const char* expected[] = { start_marker, "0123456789", end_marker, NULL }; assert(file_contains_substrings_in_order(_level_filename[LogLevel::Trace], expected), "unable to print long line"); FREE_C_HEAP_ARRAY(char, data); } void LogMessageTest::test_message_with_many_lines() { const size_t lines = 100; const size_t line_length = 16; LogMessageBuffer msg; for (size_t i = 0; i < lines; i++) { msg.info("Line #" SIZE_FORMAT, i); } _log.write(msg); char expected_lines_data[lines][line_length]; const char* expected_lines[lines + 1]; for (size_t i = 0; i < lines; i++) { jio_snprintf(&expected_lines_data[i][0], line_length, "Line #" SIZE_FORMAT, i); expected_lines[i] = expected_lines_data[i]; } expected_lines[lines] = NULL; assert(file_contains_substrings_in_order(_level_filename[LogLevel::Trace], expected_lines), "couldn't find all lines in multiline message"); } static size_t dummy_prefixer(char* buf, size_t len) { static int i = 0; const char* prefix = "some prefix: "; const size_t prefix_len = strlen(prefix); if (len < prefix_len) { return prefix_len; } jio_snprintf(buf, len, "%s", prefix); return prefix_len; } void LogMessageTest::test_prefixing() { LogMessageBuffer msg; msg.set_prefix(dummy_prefixer); for (int i = 0; i < 3; i++) { msg.info("test %d", i); } msg.set_prefix(NULL); msg.info("test 3"); _log.write(msg); const char* expected[] = { "] some prefix: test 0", "] some prefix: test 1", "] some prefix: test 2", "] test 3", NULL }; assert(file_contains_substrings_in_order(_level_filename[LogLevel::Trace], expected), "error in prefixed output"); } void LogMessageTest::test_scoped_messages() { { LogMessage(logging) msg; msg.info("scoped info"); } assert(file_contains_substring(_level_filename[LogLevel::Info], "scoped info"), "missing output from scoped log message"); } #endif // PRODUCT