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