1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
|
/*
* Copyright (c) 2014, 2017, 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.
*/
import java.io.PrintStream;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.security.AllPermission;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.Policy;
import java.security.ProtectionDomain;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import jdk.internal.logger.BootstrapLogger;
import jdk.internal.logger.LazyLoggers;
/*
* @test
* @bug 8140364 8189291
* @author danielfuchs
* @summary JDK implementation specific unit test for JDK internal artifacts.
Tests the behavior of bootstrap loggers (and SimpleConsoleLoggers
* too).
* @modules java.base/jdk.internal.logger:+open
* java.logging
* @build BootstrapLoggerUtils LogStream
* @run main/othervm BootstrapLoggerTest NO_SECURITY
* @run main/othervm BootstrapLoggerTest SECURE
* @run main/othervm/timeout=120 BootstrapLoggerTest SECURE_AND_WAIT
*/
public class BootstrapLoggerTest {
static final Policy DEFAULT_POLICY = Policy.getPolicy();
static final Method isAlive;
static final Field logManagerInitialized;
static {
try {
// private reflection hook that allows us to test whether
// the BootstrapExecutor is alive.
isAlive = BootstrapLogger.class
.getDeclaredMethod("isAlive");
isAlive.setAccessible(true);
// private reflection hook that allows us to test whether the LogManager
// has initialized and registered with the BootstrapLogger class
logManagerInitialized = BootstrapLogger.class
.getDeclaredField("logManagerConfigured");
logManagerInitialized.setAccessible(true);
} catch (Exception ex) {
throw new ExceptionInInitializerError(ex);
}
}
static enum TestCase {
NO_SECURITY, SECURE, SECURE_AND_WAIT
}
public static void main(String[] args) throws Exception {
if (args == null || args.length == 0) {
args = new String[] { TestCase.SECURE_AND_WAIT.name() };
}
if (args.length > 1) throw new RuntimeException("Only one argument allowed");
TestCase test = TestCase.valueOf(args[0]);
System.err.println("Testing: " + test);
// private reflection hook that allows us to simulate a non booted VM
final AtomicBoolean vmBooted = new AtomicBoolean(false);
BootstrapLoggerUtils.setBootedHook(() -> vmBooted.get());
// We replace System.err to check the messages that have been logged
// by the JUL ConsoleHandler and default SimpleConsoleLogger
// implementaion
final LogStream err = new LogStream();
System.setErr(new PrintStream(err));
if (BootstrapLogger.isBooted()) {
throw new RuntimeException("VM should not be booted!");
}
Logger logger = LazyLoggers.getLogger("foo.bar", Thread.class.getModule());
if (test != TestCase.NO_SECURITY) {
LogStream.err.println("Setting security manager");
Policy.setPolicy(new SimplePolicy());
System.setSecurityManager(new SecurityManager());
}
Level[] levels = {Level.INFO, Level.WARNING, Level.INFO};
int index = 0;
logger.log(levels[index], "Early message #" + (index+1)); index++;
logger.log(levels[index], "Early message #" + (index+1)); index++;
LogStream.err.println("VM Booted: " + vmBooted.get());
LogStream.err.println("LogManager initialized: " + logManagerInitialized.get(null));
logger.log(levels[index], "Early message #" + (index+1)); index++;
if (err.drain().contains("Early message")) {
// We're expecting that logger will be a LazyLogger wrapping a
// BootstrapLogger. The Bootstrap logger will stack the log messages
// it receives until the VM is booted.
// Since our private hook pretend that the VM is not booted yet,
// the logged messages shouldn't have reached System.err yet.
throw new RuntimeException("Early message logged while VM is not booted!");
}
// Now pretend that the VM is booted. Nothing should happen yet, until
// we try to log a new message.
vmBooted.getAndSet(true);
LogStream.err.println("VM Booted: " + vmBooted.get());
LogStream.err.println("LogManager initialized: " + logManagerInitialized.get(null));
if (!BootstrapLogger.isBooted()) {
throw new RuntimeException("VM should now be booted!");
}
if (((Boolean)logManagerInitialized.get(null)).booleanValue()) {
throw new RuntimeException("LogManager shouldn't be initialized yet!");
}
// Logging a message should cause the BootstrapLogger to replace itself
// by a 'real' logger in the LazyLogger. But since the LogManager isn't
// initialized yet, this should be a SimpleConsoleLogger...
logger.log(Level.INFO, "LOG#4: VM now booted: {0}", vmBooted.get());
logger.log(Level.DEBUG, "LOG#5: hi!");
SimplePolicy.allowAll.set(Boolean.TRUE);
WeakReference<Thread> threadRef = null;
ReferenceQueue<Thread> queue = new ReferenceQueue<>();
try {
Set<Thread> set = Thread.getAllStackTraces().keySet().stream()
.filter((t) -> t.getName().startsWith("BootstrapMessageLoggerTask-"))
.collect(Collectors.toSet());
set.stream().forEach(t -> LogStream.err.println("Found: " + t));
if (set.size() > 1) {
throw new RuntimeException("Too many bootsrap threads found");
}
Optional<Thread> t = set.stream().findFirst();
if (t.isPresent()) {
threadRef = new WeakReference<>(t.get(), queue);
}
} finally{
SimplePolicy.allowAll.set(Boolean.FALSE);
}
if (!BootstrapLogger.isBooted()) {
throw new RuntimeException("VM should still be booted!");
}
if (((Boolean)logManagerInitialized.get(null)).booleanValue()) {
throw new RuntimeException("LogManager shouldn't be initialized yet!");
}
// Now check that the early messages we had printed before the VM was
// booted have appeared on System.err...
String afterBoot = err.drain();
for (int i=0; i<levels.length; i++) {
String m = levels[i].getName()+": Early message #"+(i+1);
if (!afterBoot.contains(m)) {
throw new RuntimeException("System.err does not contain: "+m);
}
}
// check that the message logged *after* the VM was booted also printed.
if (!afterBoot.contains("INFO: LOG#4")) {
throw new RuntimeException("System.err does not contain: "
+ "INFO: LOG#4");
}
// check that the debug message was not printed.
if (afterBoot.contains("LOG#5")) {
throw new RuntimeException("System.err contain: " + "LOG#5");
}
LogStream.err.println("VM Booted: " + vmBooted.get());
LogStream.err.println("LogManager initialized: " + logManagerInitialized.get(null));
if (!BootstrapLogger.isBooted()) {
throw new RuntimeException("VM should still be booted!");
}
if (((Boolean)logManagerInitialized.get(null)).booleanValue()) {
throw new RuntimeException("LogManager shouldn't be initialized yet!");
}
// Now we're going to use reflection to access JUL, and change
// the level of the "foo" logger.
// We're using reflection so that the test can also run in
// configurations where java.util.logging is not present.
boolean hasJUL = false;
SimplePolicy.allowAll.set(Boolean.TRUE);
try {
Class<?> loggerClass = Class.forName("java.util.logging.Logger");
Class<?> levelClass = Class.forName("java.util.logging.Level");
Class<?> handlerClass = Class.forName("java.util.logging.Handler");
// java.util.logging.Logger.getLogger("foo")
// .setLevel(java.util.logging.Level.FINEST);
Object fooLogger = loggerClass.getMethod("getLogger", String.class)
.invoke(null, "foo");
loggerClass.getMethod("setLevel", levelClass)
.invoke(fooLogger, levelClass.getField("FINEST").get(null));
// java.util.logging.Logger.getLogger("").getHandlers()[0]
// .setLevel(java.util.logging.Level.ALL);
Object rootLogger = loggerClass.getMethod("getLogger", String.class)
.invoke(null, "");
Object handlers = loggerClass.getMethod("getHandlers").
invoke(rootLogger);
handlerClass.getMethod("setLevel", levelClass)
.invoke(Array.get(handlers, 0), levelClass.getField("ALL")
.get(null));
hasJUL = true;
} catch (ClassNotFoundException x) {
LogStream.err.println("JUL is not present: class " + x.getMessage()
+ " not found");
hasJUL = false;
} finally {
SimplePolicy.allowAll.set(Boolean.FALSE);
}
logger.log(Level.DEBUG, "hi now!");
String debug = err.drain();
if (hasJUL) {
if (!((Boolean)logManagerInitialized.get(null)).booleanValue()) {
throw new RuntimeException("LogManager should be initialized now!");
}
if (!debug.contains("FINE: hi now!")) {
throw new RuntimeException("System.err does not contain: "
+ "FINE: hi now!");
}
} else {
if (debug.contains("hi now!")) {
throw new RuntimeException("System.err contains: " + "hi now!");
}
if (((Boolean)logManagerInitialized.get(null)).booleanValue()) {
throw new RuntimeException("LogManager shouldn't be initialized yet!");
}
Logger baz = System.getLogger("foo.bar.baz");
if (((Boolean)logManagerInitialized.get(null)).booleanValue()) {
throw new RuntimeException("LogManager shouldn't be initialized yet!");
}
}
Logger bazbaz = null;
SimplePolicy.allowAll.set(Boolean.TRUE);
try {
bazbaz = java.lang.System.LoggerFinder
.getLoggerFinder().getLogger("foo.bar.baz.baz", BootstrapLoggerTest.class.getModule());
} finally {
SimplePolicy.allowAll.set(Boolean.FALSE);
}
if (!((Boolean)logManagerInitialized.get(null)).booleanValue()) {
throw new RuntimeException("LogManager should be initialized now!");
}
Logger bazbaz2 = System.getLogger("foo.bar.baz.baz");
if (bazbaz2.getClass() != bazbaz.getClass()) {
throw new RuntimeException("bazbaz2.class != bazbaz.class ["
+ bazbaz2.getClass() + " != "
+ bazbaz.getClass() + "]");
}
if (hasJUL != bazbaz2.getClass().getName()
.equals("sun.util.logging.internal.LoggingProviderImpl$JULWrapper")) {
throw new RuntimeException("Unexpected class for bazbaz: "
+ bazbaz.getClass().getName()
+ "\n\t expected: "
+ "sun.util.logging.internal.LoggingProviderImpl$JULWrapper");
}
// Now we're going to check that the thread of the BootstrapLogger
// executor terminates, and that the Executor is GC'ed after that.
// This will involve a bit of waiting, hence the timeout=120 in
// the @run line.
// If this test fails in timeout - we could envisage skipping this part,
// or adding some System property to configure the keep alive delay
// of the executor.
SimplePolicy.allowAll.set(Boolean.TRUE);
try {
Stream<Thread> stream = Thread.getAllStackTraces().keySet().stream();
stream.filter((t) -> t.getName().startsWith("BootstrapMessageLoggerTask-"))
.forEach(t -> LogStream.err.println(t));
stream = null;
if (threadRef != null && test == TestCase.SECURE_AND_WAIT) {
Thread t = threadRef.get();
if (t != null) {
if (!(Boolean)isAlive.invoke(null)) {
throw new RuntimeException("Executor already terminated");
} else {
LogStream.err.println("Executor still alive as expected.");
}
LogStream.err.println("Waiting for " + t.getName() + " to terminate (join)");
t.join(60_000);
t = null;
}
LogStream.err.println("Calling System.gc()");
System.gc();
LogStream.err.println("Waiting for BootstrapMessageLoggerTask to be gc'ed");
while (queue.remove(1000) == null) {
LogStream.err.println("Calling System.gc()");
System.gc();
}
// Call the reference here to make sure threadRef will not be
// eagerly garbage collected before the thread it references.
// otherwise, it might not be enqueued, resulting in the
// queue.remove() call above to always return null....
if (threadRef.get() != null) {
throw new RuntimeException("Reference should have been cleared");
}
LogStream.err.println("BootstrapMessageLoggerTask has been gc'ed");
// Wait for the executor to be gc'ed...
for (int i=0; i<10; i++) {
LogStream.err.println("Calling System.gc()");
System.gc();
if (!(Boolean)isAlive.invoke(null)) break;
// It would be unexpected that we reach here...
Thread.sleep(1000);
}
if ((Boolean)isAlive.invoke(null)) {
throw new RuntimeException("Executor still alive");
} else {
LogStream.err.println("Executor terminated as expected.");
}
} else {
LogStream.err.println("Not checking executor termination for " + test);
}
} finally {
SimplePolicy.allowAll.set(Boolean.FALSE);
}
LogStream.err.println(test.name() + ": PASSED");
}
final static class SimplePolicy extends Policy {
static final ThreadLocal<Boolean> allowAll = new ThreadLocal<Boolean>() {
@Override
protected Boolean initialValue() {
return Boolean.FALSE;
}
};
Permissions getPermissions() {
Permissions perms = new Permissions();
if (allowAll.get()) {
perms.add(new AllPermission());
}
return perms;
}
@Override
public boolean implies(ProtectionDomain domain, Permission permission) {
return getPermissions(domain).implies(permission) ||
DEFAULT_POLICY.implies(domain, permission);
}
@Override
public PermissionCollection getPermissions(CodeSource codesource) {
return getPermissions();
}
@Override
public PermissionCollection getPermissions(ProtectionDomain domain) {
return getPermissions();
}
}
}
|