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 }