1 /*
   2  * Copyright (c) 2017, 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 
  24 import java.io.ByteArrayOutputStream;
  25 import java.io.IOException;
  26 import java.io.OutputStream;
  27 import java.io.PrintStream;
  28 import java.lang.System.Logger;
  29 import java.lang.System.Logger.Level;
  30 import java.lang.System.LoggerFinder;
  31 import java.util.Collections;
  32 import java.util.Enumeration;
  33 import java.util.Locale;
  34 import java.util.Map;
  35 import java.util.Objects;
  36 import java.util.ResourceBundle;
  37 // Can't use testng because testng requires java.logging
  38 //import org.testng.annotations.Test;
  39 
  40 /**
  41  * @test
  42  * @bug 8177835 8179222
  43  * @summary Checks that the DefaultLoggerFinder and LoggingProviderImpl
  44  *          implementations of the System.LoggerFinder conform to the
  45  *          LoggerFinder specification, in particular with respect to
  46  *          throwing NullPointerException. The test uses --limit-module
  47  *          to force the selection of one or the other.
  48  * @author danielfuchs
  49  * @requires !vm.graal.enabled
  50  * @build LoggerFinderAPI
  51  * @run main/othervm --limit-modules java.base,java.logging
  52  *          -Djava.util.logging.SimpleFormatter.format=LOG-%4$s:-[%2$s]-%5$s%6$s%n
  53  *          LoggerFinderAPI
  54  * @run main/othervm -Djdk.system.logger.format=LOG-%4$s:-[%2$s]-%5$s%6$s%n
  55  *          --limit-modules java.base
  56  *          LoggerFinderAPI
  57  */
  58 public class LoggerFinderAPI {
  59 
  60     // Simplified log format string. No white space for main/othervm line
  61     static final String TEST_FORMAT = "LOG-%4$s:-[%2$s]-%5$s%6$s%n";
  62     static final String JDK_FORMAT_PROP_KEY = "jdk.system.logger.format";
  63     static final String JUL_FORMAT_PROP_KEY =
  64         "java.util.logging.SimpleFormatter.format";
  65     static final String MESSAGE = "{0} with {1}: PASSED";
  66     static final String LOCALIZED = "[localized] ";
  67 
  68     static class RecordStream extends OutputStream {
  69         static final Object LOCK = new Object[0];
  70         final PrintStream out;
  71         final PrintStream err;
  72         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
  73         boolean record;
  74         RecordStream(PrintStream out, PrintStream err) {
  75             this.out = out;
  76             this.err = err;
  77         }
  78 
  79         @Override
  80         public void write(int i) throws IOException {
  81             if (record) {
  82                 bos.write(i);
  83                 out.write(i);
  84             } else {
  85                 err.write(i);
  86             }
  87         }
  88 
  89         void startRecording() {
  90             out.flush();
  91             err.flush();
  92             bos.reset();
  93             record = true;
  94         }
  95         byte[] stopRecording() {
  96             out.flush();
  97             err.flush();
  98             record = false;
  99             return bos.toByteArray();
 100         }
 101     }
 102 
 103     static final PrintStream ERR = System.err;
 104     static final PrintStream OUT = System.out;
 105     static final RecordStream LOG_STREAM = new RecordStream(OUT, ERR);
 106     static {
 107         Locale.setDefault(Locale.US);
 108         PrintStream perr = new PrintStream(LOG_STREAM);
 109         System.setErr(perr);
 110     }
 111 
 112     public static class MyResourceBundle extends ResourceBundle {
 113         final Map<String, String> map = Map.of(MESSAGE, LOCALIZED + MESSAGE);
 114         @Override
 115         protected Object handleGetObject(String string) {
 116             return map.get(string);
 117         }
 118 
 119         @Override
 120         public Enumeration<String> getKeys() {
 121             return Collections.enumeration(map.keySet());
 122         }
 123 
 124     }
 125 
 126     public static class EmptyResourceBundle extends ResourceBundle {
 127         @Override
 128         protected Object handleGetObject(String string) {
 129             return null;
 130         }
 131 
 132         @Override
 133         public Enumeration<String> getKeys() {
 134             return Collections.emptyEnumeration();
 135         }
 136 
 137     }
 138 
 139     public static void main(String[] args) {
 140         // Set on the command line, to ensure that the test will fail if
 141         // the 'wrong' provider gets selected.
 142         // System.setProperty(JDK_FORMAT_PROP_KEY, TEST_FORMAT);
 143         // System.setProperty(JUL_FORMAT_PROP_KEY, TEST_FORMAT);
 144         LoggerFinder finder = System.LoggerFinder.getLoggerFinder();
 145         System.out.println("LoggerFinder is " + finder.getClass().getName());
 146 
 147         LoggerFinderAPI apiTest = new LoggerFinderAPI();
 148         for (Object[] params : getLoggerDataProvider()) {
 149             @SuppressWarnings("unchecked")
 150             Class<? extends Throwable> throwableClass  =
 151                     Throwable.class.getClass().cast(params[3]);
 152             apiTest.testGetLogger((String)params[0],
 153                                   (String)params[1],
 154                                   (Module)params[2],
 155                                   throwableClass);
 156         }
 157         for (Object[] params : getLocalizedLoggerDataProvider()) {
 158             @SuppressWarnings("unchecked")
 159             Class<? extends Throwable> throwableClass =
 160                     Throwable.class.getClass().cast(params[4]);
 161             apiTest.testGetLocalizedLogger((String)params[0],
 162                                   (String)params[1],
 163                                   (ResourceBundle)params[2],
 164                                   (Module)params[3],
 165                                   throwableClass);
 166         }
 167     }
 168 
 169     //Can't use testng because testng requires java.logging
 170     //@Test(dataProvider = "testGetLoggerDataProvider")
 171     void testGetLogger(String desc, String name, Module mod, Class<? extends Throwable> thrown) {
 172         try {
 173             LoggerFinder finder = System.LoggerFinder.getLoggerFinder();
 174             Logger logger = finder.getLogger(name, mod);
 175             if (thrown != null) {
 176                 throw new AssertionError("Exception " + thrown.getName()
 177                         + " not thrown for "
 178                         + "LoggerFinder.getLogger"
 179                         + " with " + desc);
 180             }
 181             // Make sure we don't fail if tests are run in parallel
 182             synchronized(RecordStream.LOCK) {
 183                 LOG_STREAM.startRecording();
 184                 byte[] logged = null;
 185                 try {
 186                     logger.log(Level.INFO, "{0} with {1}: PASSED",
 187                                "LoggerFinder.getLogger",
 188                                desc);
 189                 } finally {
 190                     logged = LOG_STREAM.stopRecording();
 191                 }
 192                 check(logged, "testGetLogger", desc, null,
 193                       "LoggerFinder.getLogger");
 194             }
 195         } catch (Throwable x) {
 196             if (thrown != null && thrown.isInstance(x)) {
 197                 System.out.printf("Got expected exception for %s with %s: %s\n",
 198                         "LoggerFinder.getLogger", desc, String.valueOf(x));
 199             } else throw x;
 200         }
 201     }
 202 
 203     //Can't use testng because testng requires java.logging
 204     //@Test(dataProvider = "getLocalizedLoggerDataProvider")
 205     void testGetLocalizedLogger(String desc, String name, ResourceBundle bundle,
 206                                 Module mod, Class<? extends Throwable> thrown) {
 207         try {
 208             LoggerFinder finder = System.LoggerFinder.getLoggerFinder();
 209             Logger logger = finder.getLocalizedLogger(name, bundle, mod);
 210             if (thrown != null) {
 211                 throw new AssertionError("Exception " + thrown.getName()
 212                         + " not thrown for "
 213                         + "LoggerFinder.getLocalizedLogger"
 214                         + " with " + desc);
 215             }
 216             // Make sure we don't fail if tests are run in parallel
 217             synchronized(RecordStream.LOCK) {
 218                 LOG_STREAM.startRecording();
 219                 byte[] logged = null;
 220                 try {
 221                     logger.log(Level.INFO, MESSAGE,
 222                               "LoggerFinder.getLocalizedLogger",
 223                               desc);
 224                 } finally {
 225                    logged = LOG_STREAM.stopRecording();
 226                 }
 227                 check(logged, "testGetLocalizedLogger", desc, bundle,
 228                       "LoggerFinder.getLocalizedLogger");
 229             }
 230         } catch (Throwable x) {
 231             if (thrown != null && thrown.isInstance(x)) {
 232                 System.out.printf("Got expected exception for %s with %s: %s\n",
 233                         "LoggerFinder.getLocalizedLogger", desc, String.valueOf(x));
 234             } else throw x;
 235         }
 236     }
 237 
 238     private void check(byte[] logged, String test, String desc,
 239                        ResourceBundle bundle, String meth) {
 240         String msg = new String(logged);
 241         String localizedPrefix =
 242                 ((bundle==null || bundle==EMPTY_BUNDLE)?"":LOCALIZED);
 243         String expected = String.format(TEST_FORMAT, null,
 244                 "LoggerFinderAPI " + test, null, Level.INFO.name(),
 245                 localizedPrefix + meth + " with " + desc + ": PASSED",
 246                 "");
 247         if (!Objects.equals(msg, expected)) {
 248             throw new AssertionError("Expected log message not found: "
 249                                      + "\n\texpected:  " + expected
 250                                      + "\n\tretrieved: " + msg);
 251         }
 252     }
 253 
 254 
 255     static final Module MODULE = LoggerFinderAPI.class.getModule();
 256     static final ResourceBundle BUNDLE = new MyResourceBundle();
 257     static final ResourceBundle EMPTY_BUNDLE = new EmptyResourceBundle();
 258     static final Object[][] GET_LOGGER = {
 259         {"null name", null, MODULE , NullPointerException.class},
 260         {"null module", "foo", null, NullPointerException.class},
 261         {"null name and module", null, null, NullPointerException.class},
 262         {"non null name and module", "foo", MODULE, null},
 263     };
 264     static final Object[][] GET_LOCALIZED_LOGGER = {
 265         {"null name", null, BUNDLE, MODULE , NullPointerException.class},
 266         {"null module", "foo", BUNDLE, null, NullPointerException.class},
 267         {"null name and module, non null bundle", null, BUNDLE, null, NullPointerException.class},
 268         {"non null name, module, and bundle", "foo", BUNDLE, MODULE, null},
 269         {"null name and bundle", null, null, MODULE , NullPointerException.class},
 270         {"null module and bundle", "foo", null, null, NullPointerException.class},
 271         {"null name and module and bundle", null, null, null, NullPointerException.class},
 272         {"non null name and module, null bundle", "foo", null, MODULE, null},
 273         // tests that MissingResourceBundle is not propagated to the caller of
 274         // logger.log() if the key is not found in the resource bundle
 275         {"non null name, module, and empty bundle", "foo", EMPTY_BUNDLE, MODULE, null},
 276     };
 277     public static Object[][] getLoggerDataProvider() {
 278         return GET_LOGGER;
 279     }
 280     public static Object[][] getLocalizedLoggerDataProvider() {
 281         return GET_LOCALIZED_LOGGER;
 282     }
 283 }