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