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