1 /* 2 * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 import java.io.ByteArrayOutputStream; 24 import java.io.IOException; 25 import java.io.OutputStream; 26 import java.io.PrintStream; 27 import java.nio.file.Files; 28 import java.nio.file.Path; 29 import java.nio.file.Paths; 30 import java.nio.file.StandardCopyOption; 31 import java.util.Collections; 32 import java.util.Enumeration; 33 import java.util.List; 34 import java.util.Objects; 35 import java.util.concurrent.ConcurrentHashMap; 36 import java.util.concurrent.ConcurrentMap; 37 import java.util.logging.Handler; 38 import java.util.logging.Level; 39 import java.util.logging.LogManager; 40 import java.util.logging.Logger; 41 import java.util.stream.Collectors; 42 import java.util.stream.Stream; 43 44 /** 45 * @test 46 * @bug 8191033 47 * @build custom.DotHandler custom.Handler 48 * @run main/othervm -Dlogging.properties=badlogging.properties -Dclz=1custom.DotHandler BadRootLoggerHandlers CUSTOM 49 * @run main/othervm -Dlogging.properties=badlogging.properties -Dclz=1custom.DotHandler BadRootLoggerHandlers DEFAULT 50 * @run main/othervm -Dlogging.properties=badglobal.properties -Dclz=1custom.GlobalHandler BadRootLoggerHandlers CUSTOM 51 * @run main/othervm -Dlogging.properties=badglobal.properties -Dclz=1custom.GlobalHandler BadRootLoggerHandlers DEFAULT 52 * @run main/othervm/java.security.policy==test.policy -Dlogging.properties=badlogging.properties -Dclz=1custom.DotHandler BadRootLoggerHandlers CUSTOM 53 * @run main/othervm/java.security.policy==test.policy -Dlogging.properties=badlogging.properties -Dclz=1custom.DotHandler BadRootLoggerHandlers DEFAULT 54 * @run main/othervm/java.security.policy==test.policy -Dlogging.properties=badglobal.properties -Dclz=1custom.GlobalHandler BadRootLoggerHandlers CUSTOM 55 * @run main/othervm/java.security.policy==test.policy -Dlogging.properties=badglobal.properties -Dclz=1custom.GlobalHandler BadRootLoggerHandlers DEFAULT 56 * @author danielfuchs 57 */ 58 public class BadRootLoggerHandlers { 59 60 public static final Path SRC_DIR = 61 Paths.get(System.getProperty("test.src", "src")); 62 public static final Path USER_DIR = 63 Paths.get(System.getProperty("user.dir", ".")); 64 public static final Path CONFIG_FILE = Paths.get( 65 Objects.requireNonNull(System.getProperty("logging.properties"))); 66 public static final String BAD_HANDLER_NAME = 67 Objects.requireNonNull(System.getProperty("clz")); 68 69 static enum TESTS { CUSTOM, DEFAULT} 70 public static final class CustomLogManager extends LogManager { 71 final ConcurrentMap<String, Logger> loggers = new ConcurrentHashMap<>(); 72 @Override 73 public boolean addLogger(Logger logger) { 74 return loggers.putIfAbsent(logger.getName(), logger) == null; 75 } 76 77 @Override 78 public Enumeration<String> getLoggerNames() { 79 return Collections.enumeration(loggers.keySet()); 80 } 81 82 @Override 83 public Logger getLogger(String name) { 84 return loggers.get(name); 85 } 86 } 87 88 public static class SystemErr extends OutputStream { 89 final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 90 final OutputStream wrapped; 91 public SystemErr(OutputStream out) { 92 this.wrapped = out; 93 } 94 95 @Override 96 public void write(int b) throws IOException { 97 baos.write(b); 98 wrapped.write(b); 99 } 100 101 public void close() throws IOException { 102 flush(); 103 super.close(); 104 } 105 106 public void flush() throws IOException { 107 super.flush(); 108 wrapped.flush(); 109 } 110 111 } 112 113 // Uncomment this to run the test on Java 8. Java 8 does not have 114 // List.of(...) 115 // static final class List { 116 // static <T> java.util.List<T> of(T... items) { 117 // return Collections.unmodifiableList(Arrays.asList(items)); 118 // } 119 // } 120 121 public static void main(String[] args) throws IOException { 122 Path initialProps = SRC_DIR.resolve(CONFIG_FILE); 123 Path loggingProps = USER_DIR.resolve(CONFIG_FILE); 124 if (args.length != 1) { 125 throw new IllegalArgumentException("expected (only) one of " + List.of(TESTS.values())); 126 } 127 128 TESTS test = TESTS.valueOf(args[0]); 129 System.setProperty("java.util.logging.config.file", loggingProps.toString()); 130 if (test == TESTS.CUSTOM) { 131 System.setProperty("java.util.logging.manager", CustomLogManager.class.getName()); 132 } 133 134 Files.copy(initialProps, loggingProps, StandardCopyOption.REPLACE_EXISTING); 135 loggingProps.toFile().setWritable(true); 136 137 SystemErr err = new SystemErr(System.err); 138 System.setErr(new PrintStream(err)); 139 140 System.out.println("Root level is: " + Logger.getLogger("").getLevel()); 141 if (Logger.getLogger("").getLevel() != Level.INFO) { 142 throw new RuntimeException("Expected root level INFO, got: " 143 + Logger.getLogger("").getLevel()); 144 } 145 146 Class<? extends LogManager> logManagerClass = 147 LogManager.getLogManager().getClass(); 148 Class<? extends LogManager> expectedClass = 149 test == TESTS.CUSTOM ? CustomLogManager.class : LogManager.class; 150 if (logManagerClass != expectedClass) { 151 throw new RuntimeException("Bad class for log manager: " + logManagerClass 152 + " expected " + expectedClass + " for " + test); 153 } 154 155 if (test == TESTS.DEFAULT) { 156 // Verify that we have two handlers. One was configured with 157 // handlers=custom.Handler, the other with 158 // .handlers=custom.DotHandler 159 // Verify that exactly one of the two handlers is a custom.Handler 160 // Verify that exactly one of the two handlers is a custom.DotHandler 161 // Verify that the two handlers have an id of '1' 162 checkHandlers(Logger.getLogger(""), 163 Logger.getLogger("").getHandlers(), 164 1L, 165 custom.Handler.class, 166 custom.DotHandler.class); 167 } else { 168 // Verify that we have one handler, configured with 169 // handlers=custom.Handler. 170 // Verify that it is a custom.Handler 171 // Verify that the handler have an id of '1' 172 checkHandlers(Logger.getLogger(""), 173 Logger.getLogger("").getHandlers(), 174 1L, 175 custom.Handler.class); 176 177 } 178 179 // DEFAULT: The log message "hi" should appear twice on the console. 180 // CUSTOM: The log message "hi" should appear twice on the console. 181 // We don't check that. This is just for log analysis in case 182 // of test failure. 183 Logger.getAnonymousLogger().info("hi (" + test +")"); 184 185 // Change the root logger level to FINE in the properties file 186 // and reload the configuration. 187 Files.write(loggingProps, 188 Files.lines(initialProps) 189 .map((s) -> s.replace("INFO", "FINE")) 190 .collect(Collectors.toList())); 191 LogManager.getLogManager().readConfiguration(); 192 193 System.out.println("Root level is: " + Logger.getLogger("").getLevel()); 194 if (Logger.getLogger("").getLevel() != Level.FINE) { 195 throw new RuntimeException("Expected root level FINE, got: " 196 + Logger.getLogger("").getLevel()); 197 } 198 199 // Verify that we have now only one handler, configured with 200 // handlers=custom.Handler, and that the other configured with 201 // .handlers=custom.DotHandler was ignored. 202 // Verify that the handler is a custom.Handler 203 // Verify that the handler has an id of '2' 204 checkHandlers(Logger.getLogger(""), 205 Logger.getLogger("").getHandlers(), 206 2L, 207 custom.Handler.class); 208 209 // The log message "there" should appear only once on the console. 210 // We don't check that. This is just for log analysis in case 211 // of test failure. 212 Logger.getAnonymousLogger().info("there!"); 213 214 // Change the root logger level to FINER in the properties file 215 // and reload the configuration. 216 Files.write(loggingProps, 217 Files.lines(initialProps) 218 .map((s) -> s.replace("INFO", "FINER")) 219 .collect(Collectors.toList())); 220 LogManager.getLogManager().readConfiguration(); 221 222 System.out.println("Root level is: " + Logger.getLogger("").getLevel()); 223 if (Logger.getLogger("").getLevel() != Level.FINER) { 224 throw new RuntimeException("Expected root level FINER, got: " 225 + Logger.getLogger("").getLevel()); 226 } 227 228 // Verify that we have only one handler, configured with 229 // handlers=custom.Handler, and that the other configured with 230 // .handlers=custom.DotHandler was ignored. 231 // Verify that the handler is a custom.Handler 232 // Verify that the handler has an id of '3' 233 checkHandlers(Logger.getLogger(""), 234 Logger.getLogger("").getHandlers(), 235 3L, 236 custom.Handler.class); 237 238 // The log message "done" should appear only once on the console. 239 // We don't check that. This is just for log analysis in case 240 // of test failure. 241 Logger.getAnonymousLogger().info("done!"); 242 243 byte[] errBytes = err.baos.toByteArray(); 244 String errText = new String(errBytes); 245 switch(test) { 246 case CUSTOM: 247 if (errText.contains("java.lang.ClassNotFoundException: " 248 + BAD_HANDLER_NAME)) { 249 throw new RuntimeException("Error message found on System.err"); 250 } 251 System.out.println("OK: ClassNotFoundException error message not found for " + test); 252 break; 253 case DEFAULT: 254 if (!errText.contains("java.lang.ClassNotFoundException: " 255 + BAD_HANDLER_NAME)) { 256 throw new RuntimeException("Error message not found on System.err"); 257 } 258 System.err.println("OK: ClassNotFoundException error message found for " + test); 259 break; 260 default: 261 throw new InternalError("unknown test case: " + test); 262 } 263 } 264 265 static void checkHandlers(Logger logger, Handler[] handlers, Long expectedID, Class<?>... clz) { 266 // Verify that we have the expected number of handlers. 267 if (Stream.of(handlers).count() != clz.length) { 268 throw new RuntimeException("Expected " + clz.length + " handlers, got: " 269 + List.of(logger.getHandlers())); 270 } 271 for (Class<?> cl : clz) { 272 // Verify that the handlers are of the expected class. 273 // For each class, we should have exactly one handler 274 // of that class. 275 if (Stream.of(handlers) 276 .map(Object::getClass) 277 .filter(cl::equals) 278 .count() != 1) { 279 throw new RuntimeException("Expected one " + cl +", got: " 280 + List.of(logger.getHandlers())); 281 } 282 } 283 // Verify that all handlers have the expected ID 284 if (Stream.of(logger.getHandlers()) 285 .map(BadRootLoggerHandlers::getId) 286 .filter(expectedID::equals) 287 .count() != clz.length) { 288 throw new RuntimeException("Expected ids to be " + expectedID + ", got: " 289 + List.of(logger.getHandlers())); 290 } 291 } 292 293 static long getId(Handler h) { 294 if (h instanceof custom.Handler) { 295 return ((custom.Handler)h).id; 296 } 297 if (h instanceof custom.DotHandler) { 298 return ((custom.DotHandler)h).id; 299 } 300 if (h instanceof custom.GlobalHandler) { 301 return ((custom.GlobalHandler)h).id; 302 } 303 return -1; 304 } 305 }