8165600: Convert internal logging tests to GTest
authormlarsson
Thu, 08 Sep 2016 15:57:57 +0200
changeset 41680 bb5ec2c00465
parent 41679 6008faaa396d
child 41681 fc7925b0d519
8165600: Convert internal logging tests to GTest Reviewed-by: rehn, rprotacio
hotspot/src/share/vm/logging/log.cpp
hotspot/src/share/vm/utilities/internalVMTests.cpp
hotspot/test/native/logging/test_log.cpp
hotspot/test/native/logging/test_logConfiguration.cpp
--- a/hotspot/src/share/vm/logging/log.cpp	Thu Sep 08 15:51:33 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,399 +0,0 @@
-/*
- * Copyright (c) 2015, 2016, 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 "gc/shared/gcTraceTime.inline.hpp"
-#include "logging/log.hpp"
-#include "logging/logConfiguration.hpp"
-#include "logging/logFileOutput.hpp"
-#include "logging/logMessage.hpp"
-#include "logging/logMessageBuffer.hpp"
-#include "logging/logOutput.hpp"
-#include "logging/logTagLevelExpression.hpp"
-#include "logging/logTagSet.hpp"
-#include "logging/logTagSetDescriptions.hpp"
-#include "logging/logStream.inline.hpp"
-#include "memory/resourceArea.hpp"
-
-#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)
-
-class TestLogFile {
- private:
-  char file_name[256];
-
-  void set_name(const char* test_name) {
-    const char* tmpdir = os::get_temp_directory();
-    int pos = jio_snprintf(file_name, sizeof(file_name), "%s%svmtest.%s.%d.log", tmpdir, os::file_separator(), test_name, os::current_process_id());
-    assert(pos > 0, "too small log file name buffer");
-    assert((size_t)pos < sizeof(file_name), "too small log file name buffer");
-  }
-
- public:
-  TestLogFile(const char* test_name) {
-    set_name(test_name);
-    remove(name());
-  }
-
-  ~TestLogFile() {
-    remove(name());
-  }
-
-  const char* name() {
-    return file_name;
-  }
-};
-
-class TestLogSavedConfig {
- private:
-  char* _saved_config;
-  char* _new_output;
-  Log(logging) _log;
- public:
-  TestLogSavedConfig(const char* apply_output = NULL, const char* apply_setting = NULL) : _new_output(0) {
-    ResourceMark rm;
-    _saved_config = os::strdup_check_oom(LogOutput::Stdout->config_string());
-    bool success = LogConfiguration::parse_log_arguments("stdout", "all=off", NULL, NULL, _log.error_stream());
-    assert(success, "test unable to turn all off");
-
-    if (apply_output) {
-      _new_output = os::strdup_check_oom(apply_output);
-      bool success = LogConfiguration::parse_log_arguments(_new_output, apply_setting,  NULL, NULL, _log.error_stream());
-      assert(success, "test unable to apply test log configuration");
-    }
-  }
-
-  ~TestLogSavedConfig() {
-    ResourceMark rm;
-    if (_new_output) {
-      bool success = LogConfiguration::parse_log_arguments(_new_output, "all=off", NULL, NULL, _log.error_stream());
-      assert(success, "test unable to turn all off");
-      os::free(_new_output);
-    }
-
-    bool success = LogConfiguration::parse_log_arguments("stdout", _saved_config, NULL, NULL, _log.error_stream());
-    assert(success, "test unable to restore log configuration");
-    os::free(_saved_config);
-  }
-};
-
-void Test_configure_stdout() {
-  LogOutput* stdoutput = LogOutput::Stdout;
-  TestLogSavedConfig tlsc;
-
-  // 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());
-}
-
-static int Test_logconfiguration_subscribe_triggered = 0;
-
-static void Test_logconfiguration_subscribe_helper() {
-  Test_logconfiguration_subscribe_triggered++;
-}
-
-void Test_logconfiguration_subscribe() {
-  ResourceMark rm;
-  Log(logging) log;
-
-  TestLogSavedConfig log_cfg("stdout", "logging*=trace");
-
-  LogConfiguration::register_update_listener(&Test_logconfiguration_subscribe_helper);
-
-  LogConfiguration::parse_log_arguments("stdout", "logging=trace", NULL, NULL, log.error_stream());
-  assert(Test_logconfiguration_subscribe_triggered == 1, "subscription not triggered (1)");
-
-  LogConfiguration::configure_stdout(LogLevel::Debug, true, LOG_TAGS(gc));
-  assert(Test_logconfiguration_subscribe_triggered == 2, "subscription not triggered (2)");
-
-  LogConfiguration::disable_logging();
-  assert(Test_logconfiguration_subscribe_triggered == 3, "subscription not triggered (3)");
-}
-
-#define LOG_PREFIX_STR "THE_PREFIX "
-#define LOG_LINE_STR "a log line"
-
-size_t Test_log_prefix_prefixer(char* buf, size_t len) {
-  int ret = jio_snprintf(buf, len, LOG_PREFIX_STR);
-  assert(ret > 0, "Failed to print prefix. Log buffer too small?");
-  return (size_t) ret;
-}
-
-void Test_log_prefix() {
-  TestLogFile log_file("log_prefix");
-  TestLogSavedConfig log_cfg(log_file.name(), "logging+test=trace");
-
-  log_trace(logging, test)(LOG_LINE_STR);
-
-  FILE* fp = fopen(log_file.name(), "r");
-  assert(fp, "File read error");
-  char output[1024];
-  if (fgets(output, 1024, fp) != NULL) {
-    assert(strstr(output, LOG_PREFIX_STR LOG_LINE_STR), "logging prefix error");
-  }
-  fclose(fp);
-}
-
-void Test_log_big() {
-  char big_msg[4096] = {0};
-  char Xchar = '~';
-
-  TestLogFile log_file("log_big");
-  TestLogSavedConfig log_cfg(log_file.name(), "logging+test=trace");
-
-  memset(big_msg, Xchar, sizeof(big_msg) - 1);
-
-  log_trace(logging, test)("%s", big_msg);
-
-  FILE* fp = fopen(log_file.name(), "r");
-  assert(fp, "File read error");
-  char output[sizeof(big_msg)+128 /*decorators*/ ];
-  if (fgets(output, sizeof(output), fp) != NULL) {
-    assert(strstr(output, LOG_PREFIX_STR), "logging prefix error");
-    size_t count = 0;
-    for (size_t ps = 0 ; output[ps + count] != '\0'; output[ps + count] == Xchar ? count++ : ps++);
-    assert(count == (sizeof(big_msg) - 1) , "logging msg error");
-  }
-  fclose(fp);
-}
-
-#define Test_logtarget_string_literal "First line"
-
-
-static void Test_logtarget_on() {
-  TestLogFile log_file("log_target");
-  TestLogSavedConfig tlsc(log_file.name(), "gc=debug");
-
-  LogTarget(Debug, gc) log;
-
-  assert(log.is_enabled(), "assert");
-
-  // Log the line and expect it to be available in the output file.
-  log.print(Test_logtarget_string_literal);
-
-  FILE* fp = fopen(log_file.name(), "r");
-  assert(fp != NULL, "File read error");
-
-  char output[256 /* Large enough buffer */];
-  char* res = fgets(output, sizeof(output), fp);
-  assert(res != NULL, "assert");
-
-  assert(strstr(output, Test_logtarget_string_literal) != NULL, "log line missing");
-
-  fclose(fp);
-}
-
-static void Test_logtarget_off() {
-  TestLogFile log_file("log_target");
-  TestLogSavedConfig tlsc(log_file.name(), "gc=info");
-
-  LogTarget(Debug, gc) log;
-
-  if (log.is_enabled()) {
-    // The log config could have been redirected gc=debug to a file. If gc=debug
-    // is enabled, we can only test that the LogTarget returns the same value
-    // as the log_is_enabled function. The rest of the test will be ignored.
-    assert(log.is_enabled() == log_is_enabled(Debug, gc), "assert");
-    log_warning(logging)("This test doesn't support runs with -Xlog");
-    return;
-  }
-
-  // Try to log, but expect this to be filtered out.
-  log.print(Test_logtarget_string_literal);
-
-  // Log a dummy line so that fgets doesn't return NULL because the file is empty.
-  log_info(gc)("Dummy line");
-
-  FILE* fp = fopen(log_file.name(), "r");
-  assert(fp != NULL, "File read error");
-
-  char output[256 /* Large enough buffer */];
-  char* res = fgets(output, sizeof(output), fp);
-  assert(res != NULL, "assert");
-
-  assert(strstr(output, Test_logtarget_string_literal) == NULL, "log line not missing");
-
-  fclose(fp);
-}
-
-void Test_logtarget() {
-  Test_logtarget_on();
-  Test_logtarget_off();
-}
-
-void Test_loghandle_on() {
-  TestLogFile log_file("log_handle");
-  TestLogSavedConfig tlsc(log_file.name(), "gc=debug");
-
-  Log(gc) log;
-  LogHandle log_handle(log);
-
-  assert(log_handle.is_debug(), "assert");
-
-  // Try to log through a LogHandle.
-  log_handle.debug("%d workers", 3);
-
-  FILE* fp = fopen(log_file.name(), "r");
-  assert(fp, "File read error");
-
-  char output[256 /* Large enough buffer */];
-  char* res = fgets(output, sizeof(output), fp);
-  assert(res != NULL, "assert");
-
-  assert(strstr(output, "3 workers") != NULL, "log line missing");
-
-  fclose(fp);
-}
-
-void Test_loghandle_off() {
-  TestLogFile log_file("log_handle");
-  TestLogSavedConfig tlsc(log_file.name(), "gc=info");
-
-  Log(gc) log;
-  LogHandle log_handle(log);
-
-  if (log_handle.is_debug()) {
-    // The log config could have been redirected gc=debug to a file. If gc=debug
-    // is enabled, we can only test that the LogTarget returns the same value
-    // as the log_is_enabled function. The rest of the test will be ignored.
-    assert(log_handle.is_debug() == log_is_enabled(Debug, gc), "assert");
-    log_warning(logging)("This test doesn't support runs with -Xlog");
-    return;
-  }
-
-  // Try to log through a LogHandle. Should fail, since only info is turned on.
-  log_handle.debug("%d workers", 3);
-
-  // Log a dummy line so that fgets doesn't return NULL because the file is empty.
-  log_info(gc)("Dummy line");
-
-  FILE* fp = fopen(log_file.name(), "r");
-  assert(fp, "File read error");
-
-  char output[256 /* Large enough buffer */];
-  char* res = fgets(output, sizeof(output), fp);
-  assert(res != NULL, "assert");
-
-  assert(strstr(output, "3 workers") == NULL, "log line missing");
-
-  fclose(fp);
-}
-
-void Test_loghandle() {
-  Test_loghandle_on();
-  Test_loghandle_off();
-}
-
-static void Test_logtargethandle_on() {
-  TestLogFile log_file("log_handle");
-  TestLogSavedConfig tlsc(log_file.name(), "gc=debug");
-
-  LogTarget(Debug, gc) log;
-  LogTargetHandle log_handle(log);
-
-  assert(log_handle.is_enabled(), "assert");
-
-  // Try to log through a LogHandle.
-  log_handle.print("%d workers", 3);
-
-  FILE* fp = fopen(log_file.name(), "r");
-  assert(fp, "File read error");
-
-  char output[256 /* Large enough buffer */];
-  char* res = fgets(output, sizeof(output), fp);
-  assert(res != NULL, "assert");
-
-  assert(strstr(output, "3 workers") != NULL, "log line missing");
-
-  fclose(fp);
-}
-
-static void Test_logtargethandle_off() {
-  TestLogFile log_file("log_handle");
-  TestLogSavedConfig tlsc(log_file.name(), "gc=info");
-
-  LogTarget(Debug, gc) log;
-  LogTargetHandle log_handle(log);
-
-  if (log_handle.is_enabled()) {
-    // The log config could have been redirected gc=debug to a file. If gc=debug
-    // is enabled, we can only test that the LogTarget returns the same value
-    // as the log_is_enabled function. The rest of the test will be ignored.
-    assert(log_handle.is_enabled() == log_is_enabled(Debug, gc), "assert");
-    log_warning(logging)("This test doesn't support runs with -Xlog");
-    return;
-  }
-
-  // Try to log through a LogHandle. Should fail, since only info is turned on.
-  log_handle.print("%d workers", 3);
-
-  // Log a dummy line so that fgets doesn't return NULL because the file is empty.
-  log_info(gc)("Dummy line");
-
-  FILE* fp = fopen(log_file.name(), "r");
-  assert(fp, "File read error");
-
-  char output[256 /* Large enough buffer */];
-  char* res = fgets(output, sizeof(output), fp);
-  assert(res != NULL, "assert");
-
-  assert(strstr(output, "3 workers") == NULL, "log line missing");
-
-  fclose(fp);
-}
-
-void Test_logtargethandle() {
-  Test_logtargethandle_on();
-  Test_logtargethandle_off();
-}
-
-#endif // PRODUCT
--- a/hotspot/src/share/vm/utilities/internalVMTests.cpp	Thu Sep 08 15:51:33 2016 +0200
+++ b/hotspot/src/share/vm/utilities/internalVMTests.cpp	Thu Sep 08 15:57:57 2016 +0200
@@ -59,13 +59,6 @@
   run_unit_test(TestOldSize_test);
   run_unit_test(TestBitMap_test);
   run_unit_test(ObjectMonitor_test);
-  run_unit_test(Test_logtarget);
-  run_unit_test(Test_loghandle);
-  run_unit_test(Test_logtargethandle);
-  run_unit_test(Test_configure_stdout);
-  run_unit_test(Test_logconfiguration_subscribe);
-  run_unit_test(Test_log_prefix);
-  run_unit_test(Test_log_big);
   run_unit_test(DirectivesParser_test);
 #if INCLUDE_VM_STRUCTS
   run_unit_test(VMStructs_test);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/native/logging/test_log.cpp	Thu Sep 08 15:57:57 2016 +0200
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2015, 2016, 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"
+#include "gc/shared/gcTraceTime.inline.hpp"
+#include "logTestFixture.hpp"
+#include "logTestUtils.inline.hpp"
+#include "logging/log.hpp"
+#include "unittest.hpp"
+
+class LogTest : public LogTestFixture {
+};
+
+#define LOG_PREFIX_STR "THE_PREFIX "
+#define LOG_LINE_STR "a log line"
+
+size_t Test_log_prefix_prefixer(char* buf, size_t len) {
+  int ret = jio_snprintf(buf, len, LOG_PREFIX_STR);
+  assert(ret > 0, "Failed to print prefix. Log buffer too small?");
+  return (size_t) ret;
+}
+
+#ifdef ASSERT // 'test' tag is debug only
+TEST_F(LogTest, prefix) {
+  set_log_config(TestLogFileName, "logging+test=trace");
+  log_trace(logging, test)(LOG_LINE_STR);
+  EXPECT_TRUE(file_contains_substring(TestLogFileName, LOG_PREFIX_STR LOG_LINE_STR));
+}
+#endif
+
+TEST_F(LogTest, large_message) {
+  char big_msg[4096] = {0};
+  char Xchar = '~';
+
+  set_log_config(TestLogFileName, "logging=trace");
+
+  memset(big_msg, Xchar, sizeof(big_msg) - 1);
+  log_trace(logging)("%s", big_msg);
+
+  ResourceMark rm;
+  FILE* fp = fopen(TestLogFileName, "r");
+  ASSERT_NE((void*)NULL, fp);
+  char* output = read_line(fp);
+  fclose(fp);
+
+  size_t count = 0;
+  for (size_t ps = 0 ; output[ps + count] != '\0'; output[ps + count] == Xchar ? count++ : ps++);
+  EXPECT_EQ(sizeof(big_msg) - 1, count);
+}
+
+TEST_F(LogTest, enabled_logtarget) {
+  set_log_config(TestLogFileName, "gc=debug");
+
+  LogTarget(Debug, gc) log;
+  EXPECT_TRUE(log.is_enabled());
+
+  // Log the line and expect it to be available in the output file.
+  log.print(LOG_TEST_STRING_LITERAL);
+
+  EXPECT_TRUE(file_contains_substring(TestLogFileName, LOG_TEST_STRING_LITERAL));
+}
+
+TEST_F(LogTest, disabled_logtarget) {
+  set_log_config(TestLogFileName, "gc=info");
+
+  LogTarget(Debug, gc) log;
+  EXPECT_FALSE(log.is_enabled());
+
+  // Try to log, but expect this to be filtered out.
+  log.print(LOG_TEST_STRING_LITERAL);
+
+  // Log a dummy line so that fgets doesn't return NULL because the file is empty.
+  log_info(gc)("Dummy line");
+
+  EXPECT_FALSE(file_contains_substring(TestLogFileName, LOG_TEST_STRING_LITERAL));
+}
+
+TEST_F(LogTest, enabled_loghandle) {
+  set_log_config(TestLogFileName, "gc=debug");
+
+  Log(gc) log;
+  LogHandle log_handle(log);
+
+  EXPECT_TRUE(log_handle.is_debug());
+
+  // Try to log through a LogHandle.
+  log_handle.debug("%d workers", 3);
+
+  EXPECT_TRUE(file_contains_substring(TestLogFileName, "3 workers"));
+}
+
+TEST_F(LogTest, disabled_loghandle) {
+  set_log_config(TestLogFileName, "gc=info");
+
+  Log(gc) log;
+  LogHandle log_handle(log);
+
+  EXPECT_FALSE(log_handle.is_debug());
+
+  // Try to log through a LogHandle.
+  log_handle.debug("%d workers", 3);
+
+  // Log a dummy line so that fgets doesn't return NULL because the file is empty.
+  log_info(gc)("Dummy line");
+
+  EXPECT_FALSE(file_contains_substring(TestLogFileName, "3 workers"));
+}
+
+TEST_F(LogTest, enabled_logtargethandle) {
+  set_log_config(TestLogFileName, "gc=debug");
+
+  LogTarget(Debug, gc) log;
+  LogTargetHandle log_handle(log);
+
+  EXPECT_TRUE(log_handle.is_enabled());
+
+  // Try to log through a LogHandle.
+  log_handle.print("%d workers", 3);
+
+  EXPECT_TRUE(file_contains_substring(TestLogFileName, "3 workers"));
+}
+
+TEST_F(LogTest, disabled_logtargethandle) {
+  set_log_config(TestLogFileName, "gc=info");
+
+  LogTarget(Debug, gc) log;
+  LogTargetHandle log_handle(log);
+
+  EXPECT_FALSE(log_handle.is_enabled());
+
+  // Try to log through a LogHandle.
+  log_handle.print("%d workers", 3);
+
+  // Log a dummy line so that fgets doesn't return NULL because the file is empty.
+  log_info(gc)("Dummy line");
+
+  EXPECT_FALSE(file_contains_substring(TestLogFileName, "3 workers"));
+}
--- a/hotspot/test/native/logging/test_logConfiguration.cpp	Thu Sep 08 15:51:33 2016 +0200
+++ b/hotspot/test/native/logging/test_logConfiguration.cpp	Thu Sep 08 15:57:57 2016 +0200
@@ -296,6 +296,81 @@
   }
 }
 
+TEST_F(LogConfigurationTest, configure_stdout) {
+  // Start out with all logging disabled
+  LogConfiguration::disable_logging();
+
+  // Enable 'logging=info', verifying it has been set
+  LogConfiguration::configure_stdout(LogLevel::Info, true, LOG_TAGS(logging));
+  EXPECT_TRUE(log_is_enabled(Info, logging));
+  EXPECT_FALSE(log_is_enabled(Debug, logging));
+  EXPECT_FALSE(log_is_enabled(Info, gc));
+  LogTagSet* logging_ts = &LogTagSetMapping<LOG_TAGS(logging)>::tagset();
+  EXPECT_EQ(LogLevel::Info, logging_ts->level_for(LogOutput::Stdout));
+
+  // Enable 'gc=debug' (no wildcard), verifying no other tags are enabled
+  LogConfiguration::configure_stdout(LogLevel::Debug, true, LOG_TAGS(gc));
+  EXPECT_TRUE(log_is_enabled(Debug, gc));
+  EXPECT_TRUE(log_is_enabled(Info, logging));
+  EXPECT_FALSE(log_is_enabled(Debug, gc, heap));
+  for (LogTagSet* ts = LogTagSet::first(); ts != NULL; ts = ts->next()) {
+    if (ts->contains(PREFIX_LOG_TAG(gc))) {
+      if (ts->ntags() == 1) {
+        EXPECT_EQ(LogLevel::Debug, ts->level_for(LogOutput::Stdout));
+      } else {
+        EXPECT_EQ(LogLevel::Off, ts->level_for(LogOutput::Stdout));
+      }
+    }
+  }
+
+  // Enable 'gc*=trace' (with wildcard), verifying that all tag combinations with gc are enabled (gc+...)
+  LogConfiguration::configure_stdout(LogLevel::Trace, false, LOG_TAGS(gc));
+  EXPECT_TRUE(log_is_enabled(Trace, gc));
+  EXPECT_TRUE(log_is_enabled(Trace, gc, heap));
+  for (LogTagSet* ts = LogTagSet::first(); ts != NULL; ts = ts->next()) {
+    if (ts->contains(PREFIX_LOG_TAG(gc))) {
+      EXPECT_EQ(LogLevel::Trace, ts->level_for(LogOutput::Stdout));
+    } else if (ts == logging_ts) {
+      // Previous setting for 'logging' should remain
+      EXPECT_EQ(LogLevel::Info, ts->level_for(LogOutput::Stdout));
+    } else {
+      EXPECT_EQ(LogLevel::Off, ts->level_for(LogOutput::Stdout));
+    }
+  }
+
+  // Disable 'gc*' and 'logging', verifying all logging is properly disabled
+  LogConfiguration::configure_stdout(LogLevel::Off, true, LOG_TAGS(logging));
+  EXPECT_FALSE(log_is_enabled(Error, logging));
+  LogConfiguration::configure_stdout(LogLevel::Off, false, LOG_TAGS(gc));
+  EXPECT_FALSE(log_is_enabled(Error, gc));
+  EXPECT_FALSE(log_is_enabled(Error, gc, heap));
+  for (LogTagSet* ts = LogTagSet::first(); ts != NULL; ts = ts->next()) {
+    EXPECT_EQ(LogLevel::Off, ts->level_for(LogOutput::Stdout));
+  }
+}
+
+static int Test_logconfiguration_subscribe_triggered = 0;
+static void Test_logconfiguration_subscribe_helper() {
+  Test_logconfiguration_subscribe_triggered++;
+}
+
+TEST_F(LogConfigurationTest, subscribe) {
+  ResourceMark rm;
+  Log(logging) log;
+  set_log_config("stdout", "logging*=trace");
+
+  LogConfiguration::register_update_listener(&Test_logconfiguration_subscribe_helper);
+
+  LogConfiguration::parse_log_arguments("stdout", "logging=trace", NULL, NULL, log.error_stream());
+  ASSERT_EQ(1, Test_logconfiguration_subscribe_triggered);
+
+  LogConfiguration::configure_stdout(LogLevel::Debug, true, LOG_TAGS(gc));
+  ASSERT_EQ(2, Test_logconfiguration_subscribe_triggered);
+
+  LogConfiguration::disable_logging();
+  ASSERT_EQ(3, Test_logconfiguration_subscribe_triggered);
+}
+
 TEST_VM_F(LogConfigurationTest, parse_invalid_tagset) {
   static const char* invalid_tagset = "logging+start+exit+safepoint+gc"; // Must not exist for test to function.