1/*
2 * Copyright (c) 2016, 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 */
23import java.io.ByteArrayOutputStream;
24import java.io.IOException;
25import java.io.PrintStream;
26import java.io.UncheckedIOException;
27import java.util.Collections;
28import java.util.Enumeration;
29import java.util.ResourceBundle;
30import java.util.concurrent.ConcurrentHashMap;
31import java.util.concurrent.atomic.AtomicBoolean;
32import java.util.concurrent.atomic.AtomicLong;
33import java.util.function.Supplier;
34import java.lang.System.Logger;
35import java.lang.System.Logger.Level;
36import java.util.EnumSet;
37import java.util.HashMap;
38import java.util.Locale;
39import java.util.Map;
40import jdk.internal.logger.SimpleConsoleLogger;
41import jdk.internal.logger.SurrogateLogger;
42import sun.util.logging.PlatformLogger;
43
44/**
45 * @test
46 * @bug     8140364
47 * @summary JDK implementation specific unit test for SimpleConsoleLogger.
48 *          Tests the behavior of SimpleConsoleLogger.
49 * @modules java.base/sun.util.logging
50 *          java.base/jdk.internal.logger
51 * @build SimpleConsoleLoggerTest
52 * @run  main/othervm SimpleConsoleLoggerTest
53 * @run  main/othervm -Djdk.system.logger.level=OFF SimpleConsoleLoggerTest
54 * @run  main/othervm -Djdk.system.logger.level=ERROR SimpleConsoleLoggerTest
55 * @run  main/othervm -Djdk.system.logger.level=WARNING SimpleConsoleLoggerTest
56 * @run  main/othervm -Djdk.system.logger.level=INFO SimpleConsoleLoggerTest
57 * @run  main/othervm -Djdk.system.logger.level=DEBUG SimpleConsoleLoggerTest
58 * @run  main/othervm -Djdk.system.logger.level=TRACE SimpleConsoleLoggerTest
59 * @run  main/othervm -Djdk.system.logger.level=ALL SimpleConsoleLoggerTest
60 * @run  main/othervm -Djdk.system.logger.level=WOMBAT SimpleConsoleLoggerTest
61 * @run  main/othervm -Djdk.system.logger.level SimpleConsoleLoggerTest
62 * @run  main/othervm -Djdk.system.logger.level=FINEST SimpleConsoleLoggerTest
63 * @run  main/othervm -Djdk.system.logger.level=DEBUG -Djava.util.logging.SimpleFormatter.format=++++_%2$s%n%4$s:_%5$s%6$s%n SimpleConsoleLoggerTest
64 * @run  main/othervm -Djdk.system.logger.level=DEBUG -Djdk.system.logger.format=++++_%2$s%n%4$s:_%5$s%6$s%n SimpleConsoleLoggerTest
65 *
66 * @author danielfuchs
67 */
68public class SimpleConsoleLoggerTest {
69
70    static final RuntimePermission LOGGERFINDER_PERMISSION =
71                new RuntimePermission("loggerFinder");
72    final static boolean VERBOSE = false;
73
74    public static class MyBundle extends ResourceBundle {
75
76        final ConcurrentHashMap<String,String> map = new ConcurrentHashMap<>();
77
78        @Override
79        protected Object handleGetObject(String key) {
80            if (key.contains(" (translated)")) {
81                throw new RuntimeException("Unexpected key: " + key);
82            }
83            return map.computeIfAbsent(key, k -> k.toUpperCase(Locale.ROOT) + " (translated)");
84        }
85
86        @Override
87        public Enumeration<String> getKeys() {
88            return Collections.enumeration(map.keySet());
89        }
90
91    }
92    public static class MyLoggerBundle extends MyBundle {
93
94    }
95
96
97    static class ErrorStream extends PrintStream {
98
99        static AtomicBoolean forward = new AtomicBoolean();
100        ByteArrayOutputStream out;
101        String saved = "";
102        public ErrorStream(ByteArrayOutputStream out) {
103            super(out);
104            this.out = out;
105        }
106
107        @Override
108        public void write(int b) {
109            super.write(b);
110            if (forward.get()) err.write(b);
111        }
112
113        @Override
114        public void write(byte[] b) throws IOException {
115            super.write(b);
116            if (forward.get()) err.write(b);
117        }
118
119        @Override
120        public void write(byte[] buf, int off, int len) {
121            super.write(buf, off, len);
122            if (forward.get()) err.write(buf, off, len);
123        }
124
125        public String peek() {
126            flush();
127            return out.toString();
128        }
129
130        public String drain() {
131            flush();
132            String res = out.toString();
133            out.reset();
134            return res;
135        }
136
137        public void store() {
138            flush();
139            saved = out.toString();
140            out.reset();
141        }
142
143        public void restore() {
144            out.reset();
145            try {
146                out.write(saved.getBytes());
147            } catch(IOException io) {
148                throw new UncheckedIOException(io);
149            }
150        }
151
152        static final PrintStream err = System.err;
153        static final ErrorStream errorStream = new ErrorStream(new ByteArrayOutputStream());
154    }
155
156    private static StringBuilder appendProperty(StringBuilder b, String name) {
157        String value = System.getProperty(name);
158        if (value == null) return b;
159        return b.append(name).append("=").append(value).append('\n');
160    }
161
162    public static void main(String[] args) {
163        Locale.setDefault(Locale.ENGLISH);
164        System.setErr(ErrorStream.errorStream);
165        try {
166            test(args);
167        } finally {
168            try {
169                System.setErr(ErrorStream.err);
170            } catch (Error | RuntimeException x) {
171                x.printStackTrace(ErrorStream.err);
172            }
173        }
174    }
175
176
177    public static void test(String[] args) {
178
179        ErrorStream.errorStream.restore();
180        String l = System.getProperty("jdk.system.logger.level");
181        String f = System.getProperty("jdk.system.logger.format");
182        String jf = System.getProperty("java.util.logging.SimpleFormatter.format");
183        System.out.println("Running test: "
184            + "\n\tjdk.system.logger.level=\"" + l + "\""
185            + "\n\tjdk.system.logger.format=\"" + f + "\""
186            + "\n\tjava.util.logging.SimpleFormatter.format=\"" + jf + "\"");
187
188        test(l,f,jf);
189        System.out.println("\nPASSED: tested " + SEQUENCER.get() + " test cases");
190    }
191
192    static final AtomicLong SEQUENCER = new AtomicLong();
193    public static void test(String defaultLevel, String defaultFormat, String julFormat) {
194
195        final Map<Logger, String> loggerDescMap = new HashMap<>();
196
197        SimpleConsoleLogger simple = SimpleConsoleLogger.makeSimpleLogger("test.logger");
198        loggerDescMap.put(simple, "SimpleConsoleLogger.makeSimpleLogger(\"test.logger\")");
199        SimpleConsoleLogger temporary = SurrogateLogger.makeSurrogateLogger("test.logger");
200        loggerDescMap.put(temporary, "SurrogateLogger.makeSimpleLogger(\"test.logger\")");
201
202        Level level;
203        try {
204            level = defaultLevel == null ? null : Level.valueOf(defaultLevel);
205        } catch (IllegalArgumentException ex) {
206            level = null;
207        }
208        testLogger(loggerDescMap, simple, level, false, defaultFormat);
209        testLogger(loggerDescMap, temporary, null, true, julFormat);
210    }
211
212    public static class Foo {
213
214    }
215
216    static void verbose(String msg) {
217       if (VERBOSE) {
218           System.out.println(msg);
219       }
220    }
221
222    static String getName(Level level, boolean usePlatformLevel) {
223        if (usePlatformLevel) {
224            return PlatformLogger.toPlatformLevel(level).name();
225        } else {
226            return level.getName();
227        }
228    }
229
230    // Calls the 8 methods defined on Logger and verify the
231    // parameters received by the underlying TestProvider.LoggerImpl
232    // logger.
233    private static void testLogger(Map<Logger, String> loggerDescMap,
234            SimpleConsoleLogger simple,
235            Level defaultLevel,
236            boolean usePlatformLevel,
237            String defaultFormat) {
238
239        System.out.println("Testing " + loggerDescMap.get(simple) + " [" + simple +"]");
240
241        String formatStrMarker = defaultFormat == null ? ""
242                : defaultFormat.startsWith("++++") ? "++++" : "";
243        String unexpectedMarker = defaultFormat == null ? "++++"
244                : defaultFormat.startsWith("++++") ? "????" : "++++";
245        String formatStrSpec = defaultFormat == null ? "[date]"
246                : defaultFormat.startsWith("++++") ? "++++" : "????";
247        String sep = defaultFormat == null ? ": " : ":_";
248        String sepw = defaultFormat == null ? " " : "_";
249
250        Foo foo = new Foo();
251        String fooMsg = foo.toString();
252        for (Level loggerLevel : defaultLevel == null
253                ? EnumSet.of(Level.INFO) : EnumSet.of(defaultLevel)) {
254            for (Level messageLevel : Level.values()) {
255                ErrorStream.errorStream.drain();
256                String desc = "logger.log(messageLevel, foo): loggerLevel="
257                        + loggerLevel+", messageLevel="+messageLevel;
258                SEQUENCER.incrementAndGet();
259                simple.log(messageLevel, foo);
260                if (loggerLevel == Level.OFF || messageLevel == Level.OFF
261                    || messageLevel.compareTo(loggerLevel) < 0) {
262                    if (!ErrorStream.errorStream.peek().isEmpty()) {
263                        throw new RuntimeException("unexpected event in queue for "
264                                + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
265                    }
266                } else {
267                    String logged = ErrorStream.errorStream.drain();
268                    String expected = getName(messageLevel, usePlatformLevel) + sep + fooMsg;
269                    if (!logged.contains("SimpleConsoleLoggerTest testLogger")
270                        || !logged.contains(formatStrMarker)
271                        || logged.contains(unexpectedMarker)
272                        || !logged.contains(expected)) {
273                        throw new RuntimeException("mismatch for " + desc
274                                + "\n\texpected:" + "\n<<<<\n"
275                                + formatStrSpec + sepw + "SimpleConsoleLoggerTest testLogger\n"
276                                + expected
277                                + "\n>>>>"
278                                + "\n\t  actual:"
279                                + "\n<<<<\n" + logged + ">>>>\n");
280                    } else {
281                        verbose("Got expected results for "
282                                + desc + "\n<<<<\n" + logged + ">>>>\n");
283                    }
284                }
285            }
286        }
287
288        String msg = "blah";
289        for (Level loggerLevel : defaultLevel == null
290                ? EnumSet.of(Level.INFO) : EnumSet.of(defaultLevel)) {
291            for (Level messageLevel : Level.values()) {
292                String desc = "logger.log(messageLevel, \"blah\"): loggerLevel="
293                        + loggerLevel+", messageLevel="+messageLevel;
294                SEQUENCER.incrementAndGet();
295                simple.log(messageLevel, msg);
296                if (loggerLevel == Level.OFF || messageLevel == Level.OFF
297                    || messageLevel.compareTo(loggerLevel) < 0) {
298                    if (!ErrorStream.errorStream.peek().isEmpty()) {
299                        throw new RuntimeException("unexpected event in queue for "
300                                + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
301                    }
302                } else {
303                    String logged = ErrorStream.errorStream.drain();
304                    String expected = getName(messageLevel, usePlatformLevel) + sep + msg;
305                    if (!logged.contains("SimpleConsoleLoggerTest testLogger")
306                        || !logged.contains(formatStrMarker)
307                        || logged.contains(unexpectedMarker)
308                        || !logged.contains(expected)) {
309                        throw new RuntimeException("mismatch for " + desc
310                                + "\n\texpected:" + "\n<<<<\n"
311                                + formatStrSpec + sepw + "SimpleConsoleLoggerTest testLogger\n"
312                                + expected
313                                + "\n>>>>"
314                                + "\n\t  actual:"
315                                + "\n<<<<\n" + logged + ">>>>\n");
316                    } else {
317                        verbose("Got expected results for "
318                                + desc + "\n<<<<\n" + logged + ">>>>\n");
319                    }
320                }
321            }
322        }
323
324        Supplier<String> fooSupplier = new Supplier<String>() {
325            @Override
326            public String get() {
327                return this.toString();
328            }
329        };
330
331        for (Level loggerLevel : defaultLevel == null
332                ? EnumSet.of(Level.INFO) : EnumSet.of(defaultLevel)) {
333            for (Level messageLevel : Level.values()) {
334                String desc = "logger.log(messageLevel, fooSupplier): loggerLevel="
335                        + loggerLevel+", messageLevel="+messageLevel;
336                SEQUENCER.incrementAndGet();
337                simple.log(messageLevel, fooSupplier);
338                if (loggerLevel == Level.OFF || messageLevel == Level.OFF
339                    || messageLevel.compareTo(loggerLevel) < 0) {
340                    if (!ErrorStream.errorStream.peek().isEmpty()) {
341                        throw new RuntimeException("unexpected event in queue for "
342                                + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
343                    }
344                } else {
345                    String logged = ErrorStream.errorStream.drain();
346                    String expected = getName(messageLevel, usePlatformLevel) + sep + fooSupplier.get();
347                    if (!logged.contains("SimpleConsoleLoggerTest testLogger")
348                        || !logged.contains(formatStrMarker)
349                        || logged.contains(unexpectedMarker)
350                        || !logged.contains(expected)) {
351                        throw new RuntimeException("mismatch for " + desc
352                                + "\n\texpected:" + "\n<<<<\n"
353                                + formatStrSpec + sepw + "SimpleConsoleLoggerTest testLogger\n"
354                                + expected
355                                + "\n>>>>"
356                                + "\n\t  actual:"
357                                + "\n<<<<\n" + logged + ">>>>\n");
358                    } else {
359                        verbose("Got expected results for "
360                                + desc + "\n<<<<\n" + logged + ">>>>\n");
361                    }
362                }
363            }
364        }
365
366
367        String format = "two params [{1} {2}]";
368        Object arg1 = foo;
369        Object arg2 = msg;
370        for (Level loggerLevel : defaultLevel == null
371                ? EnumSet.of(Level.INFO) : EnumSet.of(defaultLevel)) {
372            for (Level messageLevel : Level.values()) {
373                String desc = "logger.log(messageLevel, format, params...): loggerLevel="
374                        + loggerLevel+", messageLevel="+messageLevel;
375                SEQUENCER.incrementAndGet();
376                simple.log(messageLevel, format, foo, msg);
377                if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
378                    if (!ErrorStream.errorStream.peek().isEmpty()) {
379                        throw new RuntimeException("unexpected event in queue for "
380                                + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
381                    }
382                } else {
383                    String logged = ErrorStream.errorStream.drain();
384                    String msgFormat = format;
385                    String text = java.text.MessageFormat.format(msgFormat, foo, msg);
386                    String expected = getName(messageLevel, usePlatformLevel) + sep + text;
387                    if (!logged.contains("SimpleConsoleLoggerTest testLogger")
388                        || !logged.contains(formatStrMarker)
389                        || !logged.contains(expected)) {
390                        throw new RuntimeException("mismatch for " + desc
391                                + "\n\texpected:" + "\n<<<<\n"
392                                + formatStrSpec + sepw + "SimpleConsoleLoggerTest testLogger\n"
393                                + expected
394                                + "\n>>>>"
395                                + "\n\t  actual:"
396                                + "\n<<<<\n" + logged + ">>>>\n");
397                    } else {
398                        verbose("Got expected results for "
399                                + desc + "\n<<<<\n" + logged + ">>>>\n");
400                    }
401                }
402            }
403        }
404
405        Throwable thrown = new Exception("OK: log me!");
406        for (Level loggerLevel : defaultLevel == null
407                ? EnumSet.of(Level.INFO) : EnumSet.of(defaultLevel)) {
408            for (Level messageLevel : Level.values()) {
409                String desc = "logger.log(messageLevel, \"blah\", thrown): loggerLevel="
410                        + loggerLevel+", messageLevel="+messageLevel;
411                SEQUENCER.incrementAndGet();
412                simple.log(messageLevel, msg, thrown);
413                if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
414                    if (!ErrorStream.errorStream.peek().isEmpty()) {
415                        throw new RuntimeException("unexpected event in queue for "
416                                + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
417                    }
418                } else {
419                    String logged = ErrorStream.errorStream.drain();
420                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
421                    thrown.printStackTrace(new PrintStream(baos));
422                    String text = baos.toString();
423                    String expected = getName(messageLevel, usePlatformLevel) + sep + msg;
424                    if (!logged.contains("SimpleConsoleLoggerTest testLogger")
425                        || !logged.contains(formatStrMarker)
426                        || !logged.contains(expected)
427                        || logged.contains(unexpectedMarker)
428                        || !logged.contains(text)) {
429                        throw new RuntimeException("mismatch for " + desc
430                                + "\n\texpected:" + "\n<<<<\n"
431                                + formatStrSpec + sepw + "SimpleConsoleLoggerTest testLogger\n"
432                                + msg +"\n"
433                                + text
434                                + ">>>>"
435                                + "\n\t  actual:"
436                                + "\n<<<<\n" + logged + ">>>>\n");
437                    } else {
438                        verbose("Got expected results for "
439                                + desc + "\n<<<<\n" + logged + ">>>>\n");
440                    }
441                }
442            }
443        }
444
445
446        for (Level loggerLevel : defaultLevel == null
447                ? EnumSet.of(Level.INFO) : EnumSet.of(defaultLevel)) {
448            for (Level messageLevel : Level.values()) {
449                String desc = "logger.log(messageLevel, thrown, fooSupplier): loggerLevel="
450                        + loggerLevel+", messageLevel="+messageLevel;
451                SEQUENCER.incrementAndGet();
452                simple.log(messageLevel, fooSupplier, thrown);
453                if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
454                    if (!ErrorStream.errorStream.peek().isEmpty()) {
455                        throw new RuntimeException("unexpected event in queue for "
456                                + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
457                    }
458                } else {
459                    String logged = ErrorStream.errorStream.drain();
460                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
461                    thrown.printStackTrace(new PrintStream(baos));
462                    String text = baos.toString();
463                    String expected = getName(messageLevel, usePlatformLevel) + sep + fooSupplier.get();
464                    if (!logged.contains("SimpleConsoleLoggerTest testLogger")
465                        || !logged.contains(formatStrMarker)
466                        || !logged.contains(expected)
467                        || logged.contains(unexpectedMarker)
468                        || !logged.contains(text)) {
469                        throw new RuntimeException("mismatch for " + desc
470                                + "\n\texpected:" + "\n<<<<\n"
471                                + formatStrSpec + sepw + "SimpleConsoleLoggerTest testLogger\n"
472                                + expected +"\n"
473                                + text
474                                + ">>>>"
475                                + "\n\t  actual:"
476                                + "\n<<<<\n" + logged + ">>>>\n");
477                    } else {
478                        verbose("Got expected results for "
479                                + desc + "\n<<<<\n" + logged + ">>>>\n");
480                    }
481                }
482            }
483        }
484
485        ResourceBundle bundle = ResourceBundle.getBundle(MyBundle.class.getName());
486        for (Level loggerLevel : defaultLevel == null
487                ? EnumSet.of(Level.INFO) : EnumSet.of(defaultLevel)) {
488            for (Level messageLevel : Level.values()) {
489                String desc = "logger.log(messageLevel, bundle, format, params...): loggerLevel="
490                        + loggerLevel+", messageLevel="+messageLevel;
491                SEQUENCER.incrementAndGet();
492                simple.log(messageLevel, bundle, format, foo, msg);
493                if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
494                    if (!ErrorStream.errorStream.peek().isEmpty()) {
495                        throw new RuntimeException("unexpected event in queue for "
496                                + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
497                    }
498                } else {
499                    String logged = ErrorStream.errorStream.drain();
500                    String text = java.text.MessageFormat.format(bundle.getString(format), foo, msg);
501                    if (!logged.contains("SimpleConsoleLoggerTest testLogger")
502                        || !logged.contains(formatStrMarker)
503                        || logged.contains(unexpectedMarker)
504                        || !logged.contains(getName(messageLevel, usePlatformLevel) + sep + text)) {
505                        throw new RuntimeException("mismatch for " + desc
506                                + "\n\texpected:" + "\n<<<<\n"
507                                + formatStrSpec + sepw + "SimpleConsoleLoggerTest testLogger\n"
508                                + getName(messageLevel, usePlatformLevel) + " " + text
509                                + "\n>>>>"
510                                + "\n\t  actual:"
511                                + "\n<<<<\n" + logged + ">>>>\n");
512                    } else {
513                        verbose("Got expected results for "
514                                + desc + "\n<<<<\n" + logged + ">>>>\n");
515                    }
516                }
517            }
518        }
519
520        for (Level loggerLevel : defaultLevel == null
521                ? EnumSet.of(Level.INFO) : EnumSet.of(defaultLevel)) {
522            for (Level messageLevel : Level.values()) {
523                String desc = "logger.log(messageLevel, bundle, \"blah\", thrown): loggerLevel="
524                        + loggerLevel+", messageLevel="+messageLevel;
525                SEQUENCER.incrementAndGet();
526                simple.log(messageLevel, bundle, msg, thrown);
527                if (loggerLevel == Level.OFF || messageLevel == Level.OFF || messageLevel.compareTo(loggerLevel) < 0) {
528                    if (!ErrorStream.errorStream.peek().isEmpty()) {
529                        throw new RuntimeException("unexpected event in queue for "
530                                + desc +": " + "\n\t" + ErrorStream.errorStream.drain());
531                    }
532                } else {
533                    String logged = ErrorStream.errorStream.drain();
534                    String textMsg = bundle.getString(msg);
535                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
536                    thrown.printStackTrace(new PrintStream(baos));
537                    String text = baos.toString();
538                    String expected = getName(messageLevel, usePlatformLevel) + sep + textMsg;
539                    if (!logged.contains("SimpleConsoleLoggerTest testLogger")
540                        || !logged.contains(formatStrMarker)
541                        || !logged.contains(expected)
542                        || logged.contains(unexpectedMarker)
543                        || !logged.contains(text)) {
544                        throw new RuntimeException("mismatch for " + desc
545                                + "\n\texpected:" + "\n<<<<\n"
546                                + formatStrSpec + sepw + "SimpleConsoleLoggerTest testLogger\n"
547                                + expected +"\n"
548                                + text
549                                + ">>>>"
550                                + "\n\t  actual:"
551                                + "\n<<<<\n" + logged + ">>>>\n");
552                    } else {
553                        verbose("Got expected results for "
554                                + desc + "\n<<<<\n" + logged + ">>>>\n");
555                    }
556                }
557            }
558        }
559
560    }
561}
562