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;
RecordStream(PrintStream out, PrintStream err)74         RecordStream(PrintStream out, PrintStream err) {
75             this.out = out;
76             this.err = err;
77         }
78 
79         @Override
write(int i)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 
startRecording()89         void startRecording() {
90             out.flush();
91             err.flush();
92             bos.reset();
93             record = true;
94         }
stopRecording()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
handleGetObject(String string)115         protected Object handleGetObject(String string) {
116             return map.get(string);
117         }
118 
119         @Override
getKeys()120         public Enumeration<String> getKeys() {
121             return Collections.enumeration(map.keySet());
122         }
123 
124     }
125 
126     public static class EmptyResourceBundle extends ResourceBundle {
127         @Override
handleGetObject(String string)128         protected Object handleGetObject(String string) {
129             return null;
130         }
131 
132         @Override
getKeys()133         public Enumeration<String> getKeys() {
134             return Collections.emptyEnumeration();
135         }
136 
137     }
138 
main(String[] args)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")
testGetLogger(String desc, String name, Module mod, Class<? extends Throwable> thrown)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")
testGetLocalizedLogger(String desc, String name, ResourceBundle bundle, Module mod, Class<? extends Throwable> thrown)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 
check(byte[] logged, String test, String desc, ResourceBundle bundle, String meth)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     };
getLoggerDataProvider()277     public static Object[][] getLoggerDataProvider() {
278         return GET_LOGGER;
279     }
getLocalizedLoggerDataProvider()280     public static Object[][] getLocalizedLoggerDataProvider() {
281         return GET_LOCALIZED_LOGGER;
282     }
283 }
284