--- /dev/null 2015-10-09 21:42:22.000000000 +0200 +++ new/jdk/test/java/lang/System/LoggerFinder/internal/api/LoggerFinderAPITest.java 2015-10-09 21:42:22.000000000 +0200 @@ -0,0 +1,498 @@ +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8046565 + * @author danielfuchs + * @summary JDK implementation specific unit test for JDK internal artifacts. + * Tests the consistency of the LoggerFinder and JDK extensions. + * @modules java.base/sun.util.logging + * java.base/sun.util.logger + * @run main LoggerFinderAPITest + */ + + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.function.Supplier; +import java.util.logging.ConsoleHandler; +import java.util.logging.Handler; +import java.util.logging.LogRecord; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import sun.util.logging.PlatformLogger; +import sun.util.logging.PlatformLoggerBridge; + +public class LoggerFinderAPITest { + + static final Class spiLoggerClass + = java.lang.System.Logger.class; + static final Class jdkLoggerClass + = java.lang.System.Logger.class; + static final Class bridgeLoggerClass + = sun.util.logging.PlatformLoggerBridge.class; + static final Class julLoggerClass + = java.util.logging.Logger.class; + static final Class julLogProducerClass + = PlatformLoggerBridge.class; + static final Pattern julLogNames = Pattern.compile( + "^((log(p|rb)?)|severe|warning|info|config|fine|finer|finest|isLoggable)$"); + static final Collection julLoggerIgnores; + static { + List ignores = new ArrayList<>(); + try { + ignores.add(julLoggerClass.getDeclaredMethod("log", LogRecord.class)); + } catch (NoSuchMethodException | SecurityException ex) { + throw new ExceptionInInitializerError(ex); + } + julLoggerIgnores = Collections.unmodifiableList(ignores); + } + + + + // Don't require LoggerBridge to have a body for those methods + interface LoggerBridgeMethodsWithNoBody extends + PlatformLoggerBridge, java.lang.System.Logger { + + @Override + public default String getName() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public default boolean isLoggable(PlatformLogger.Level level) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public default void log(sun.util.logging.PlatformLogger.Level level, + String msg, Throwable thrown) { + } + @Override + public default void log(sun.util.logging.PlatformLogger.Level level, + Throwable thrown, Supplier msgSupplier) { + } + @Override + public default void log(sun.util.logging.PlatformLogger.Level level, + Supplier msgSupplier) { + } + @Override + public default void log(sun.util.logging.PlatformLogger.Level level, String msg) { + } + @Override + public default void log(sun.util.logging.PlatformLogger.Level level, + String format, Object... params) { + } + @Override + public default void logrb(sun.util.logging.PlatformLogger.Level level, + ResourceBundle bundle, String key, Throwable thrown) { + } + @Override + public default void logrb(sun.util.logging.PlatformLogger.Level level, + ResourceBundle bundle, String format, Object... params) { + } + + @Override + public default void logrb(PlatformLogger.Level level, + String sourceClass, String sourceMethod, + ResourceBundle bundle, String msg, Throwable thrown) { + } + + @Override + public default void logrb(PlatformLogger.Level level, String sourceClass, + String sourceMethod, ResourceBundle bundle, String msg, + Object... params) { + } + + @Override + public default void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, Supplier msgSupplier) { + } + + @Override + public default void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg, Object... params) { + } + + @Override + public default void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg, Throwable thrown) { + } + + @Override + public default void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, String msg) { + } + + @Override + public default void logp(PlatformLogger.Level level, String sourceClass, + String sourceMethod, Throwable thrown, + Supplier msgSupplier) { + } + + static boolean requiresDefaultBodyFor(Method m) { + try { + Method m2 = LoggerBridgeMethodsWithNoBody.class + .getDeclaredMethod(m.getName(), + m.getParameterTypes()); + return !m2.isDefault(); + } catch (NoSuchMethodException x) { + return true; + } + } + } + + final boolean warnDuplicateMappings; + public LoggerFinderAPITest(boolean verbose) { + this.warnDuplicateMappings = verbose; + for (Handler h : Logger.getLogger("").getHandlers()) { + if (h instanceof ConsoleHandler) { + Logger.getLogger("").removeHandler(h); + } + } + Logger.getLogger("").addHandler( new Handler() { + @Override + public void publish(LogRecord record) { + StringBuilder builder = new StringBuilder(); + builder.append("GOT LogRecord: ") + .append(record.getLevel().getLocalizedName()) + .append(": [").append(record.getLoggerName()) + .append("] ").append(record.getSourceClassName()) + .append('.') + .append(record.getSourceMethodName()).append(" -> ") + .append(record.getMessage()) + .append(' ') + .append(record.getParameters() == null ? "" + : Arrays.toString(record.getParameters())) + ; + System.out.println(builder); + if (record.getThrown() != null) { + record.getThrown().printStackTrace(System.out); + } + } + @Override public void flush() {} + @Override public void close() {} + }); + } + + public Stream getJulLogMethodStream(Class loggerClass) { + + return Stream.of(loggerClass.getMethods()).filter((x) -> { + final Matcher m = julLogNames.matcher(x.getName()); + return m.matches() ? x.getAnnotation(Deprecated.class) == null : false; + }); + } + + /** + * Tells whether a method invocation of 'origin' can be transformed in a + * method invocation of 'target'. + * This method only look at the parameter signatures, it doesn't look at + * the name, nor does it look at the return types. + *

+ * Example: + *

    + *
  • java.util.logging.Logger.log(Level, String, Object) can be invoked as
    + java.util.logging.spi.Logger.log(Level, String, Object...) because the + last parameter in 'target' is a varargs.
  • + *
  • java.util.logging.Logger.log(Level, String) can also be invoked as
    + java.util.logging.spi.Logger.log(Level, String, Object...) for the + same reason.
  • + *
+ *

+ * The algorithm is tailored for our needs: when the last parameter in the + * target is a vararg, and when origin & target have the same number of + * parameters, then we consider that the types of the last parameter *must* + * match. + *

+ * Similarly - we do not consider that o(X x, Y y, Y y) matches t(X x, Y... y) + * although strictly speaking, it should... + * + * @param origin The method in the original class + * @param target The correspondent candidate in the target class + * @return true if a method invocation of 'origin' can be transformed in a + * method invocation of 'target'. + */ + public boolean canBeInvokedAs(Method origin, Method target, + Map,Class> substitutes) { + final Class[] xParams = target.getParameterTypes(); + final Class[] mParams = Stream.of(origin.getParameterTypes()) + .map((x) -> substitutes.getOrDefault(x, x)) + .collect(Collectors.toList()).toArray(new Class[0]); + if (Arrays.deepEquals(xParams, mParams)) return true; + if (target.isVarArgs()) { + if (xParams.length == mParams.length) { + if (xParams[xParams.length-1].isArray()) { + return mParams[mParams.length -1].equals( + xParams[xParams.length -1].getComponentType()); + } + } else if (xParams.length == mParams.length + 1) { + return Arrays.deepEquals( + Arrays.copyOfRange(xParams, 0, xParams.length-1), mParams); + } + } + return false; + } + + /** + * Look whether {@code otherClass} has a public method similar to m + * @param m + * @param otherClass + * @return + */ + public Stream findInvokable(Method m, Class otherClass) { + final Map,Class> substitues = + Collections.singletonMap(java.util.logging.Level.class, + sun.util.logging.PlatformLogger.Level.class); + return Stream.of(otherClass.getMethods()) + .filter((x) -> m.getName().equals(x.getName())) + .filter((x) -> canBeInvokedAs(m, x, substitues)); + } + + /** + * Test that the concrete Logger implementation passed as parameter + * overrides all the methods defined by its interface. + * @param julLogger A concrete implementation of System.Logger + * whose backend is a JUL Logger. + */ + StringBuilder testDefaultJULLogger(java.lang.System.Logger julLogger) { + final StringBuilder errors = new StringBuilder(); + if (!bridgeLoggerClass.isInstance(julLogger)) { + final String errorMsg = + "Logger returned by LoggerFactory.getLogger(\"foo\") is not a " + + bridgeLoggerClass + "\n\t" + julLogger; + System.err.println(errorMsg); + errors.append(errorMsg).append('\n'); + } + final Class xClass = julLogger.getClass(); + List notOverridden = + Stream.of(bridgeLoggerClass.getDeclaredMethods()).filter((m) -> { + try { + Method x = xClass.getDeclaredMethod(m.getName(), m.getParameterTypes()); + return x == null; + } catch (NoSuchMethodException ex) { + return !Modifier.isStatic(m.getModifiers()); + } + }).collect(Collectors.toList()); + notOverridden.stream().filter((x) -> { + boolean shouldOverride = true; + try { + final Method m = xClass.getMethod(x.getName(), x.getParameterTypes()); + Method m2 = null; + try { + m2 = jdkLoggerClass.getDeclaredMethod(x.getName(), x.getParameterTypes()); + } catch (Exception e) { + + } + shouldOverride = m.isDefault() || m2 == null; + } catch (Exception e) { + // should override. + } + return shouldOverride; + }).forEach(x -> { + final String errorMsg = xClass.getName() + " should override\n\t" + x.toString(); + System.err.println(errorMsg); + errors.append(errorMsg).append('\n'); + }); + if (notOverridden.isEmpty()) { + System.out.println(xClass + " overrides all methods from " + bridgeLoggerClass); + } + return errors; + } + + public static class ResourceBundeParam extends ResourceBundle { + Map map = Collections.synchronizedMap(new LinkedHashMap<>()); + @Override + protected Object handleGetObject(String key) { + map.putIfAbsent(key, "${"+key+"}"); + return map.get(key); + } + + @Override + public Enumeration getKeys() { + return Collections.enumeration(new LinkedHashSet<>(map.keySet())); + } + + } + + final ResourceBundle bundleParam = + ResourceBundle.getBundle(ResourceBundeParam.class.getName()); + + public static class ResourceBundeLocalized extends ResourceBundle { + Map map = Collections.synchronizedMap(new LinkedHashMap<>()); + @Override + protected Object handleGetObject(String key) { + map.putIfAbsent(key, "Localized:${"+key+"}"); + return map.get(key); + } + + @Override + public Enumeration getKeys() { + return Collections.enumeration(new LinkedHashSet<>(map.keySet())); + } + + } + + final static ResourceBundle bundleLocalized = + ResourceBundle.getBundle(ResourceBundeLocalized.class.getName()); + + final Map, Object> params = new HashMap<>(); + { + params.put(String.class, "TestString"); + params.put(sun.util.logging.PlatformLogger.Level.class, sun.util.logging.PlatformLogger.Level.WARNING); + params.put(java.lang.System.Logger.Level.class, java.lang.System.Logger.Level.WARNING); + params.put(ResourceBundle.class, bundleParam); + params.put(Throwable.class, new Throwable("TestThrowable (Please ignore it!)")); + params.put(Object[].class, new Object[] {"One", "Two"}); + params.put(Object.class, new Object() { + @Override public String toString() { return "I am an object!"; } + }); + } + + public Object[] getParamsFor(Method m) { + final Object[] res = new Object[m.getParameterCount()]; + final Class[] sig = m.getParameterTypes(); + if (res.length == 0) { + return res; + } + for (int i=0; i) () -> msg; + } + if (p instanceof String) { + res[i] = String.valueOf(p)+"["+i+"]"; + } else { + res[i] = p; + } + } + return res; + } + + public void invokeOn(java.lang.System.Logger logger, Method m) { + Object[] p = getParamsFor(m); + try { + m.invoke(logger, p); + } catch (Exception e) { + throw new RuntimeException("Failed to invoke "+m.toString(), e); + } + } + + public void testAllJdkExtensionMethods(java.lang.System.Logger logger) { + Stream.of(jdkLoggerClass.getDeclaredMethods()) + .filter(m -> !Modifier.isStatic(m.getModifiers())) + .forEach((m) -> invokeOn(logger, m)); + } + + public void testAllAPIMethods(java.lang.System.Logger logger) { + Stream.of(spiLoggerClass.getDeclaredMethods()) + .filter(m -> !Modifier.isStatic(m.getModifiers())) + .forEach((m) -> invokeOn(logger, m)); + } + + public void testAllBridgeMethods(java.lang.System.Logger logger) { + Stream.of(bridgeLoggerClass.getDeclaredMethods()) + .filter(m -> !Modifier.isStatic(m.getModifiers())) + .forEach((m) -> invokeOn(logger, m)); + } + + public void testAllLogProducerMethods(java.lang.System.Logger logger) { + Stream.of(julLogProducerClass.getDeclaredMethods()) + .filter(m -> !Modifier.isStatic(m.getModifiers())) + .forEach((m) -> invokeOn(logger, m)); + } + + public StringBuilder testGetLoggerOverriddenOnSpi() { + final StringBuilder errors = new StringBuilder(); + Stream.of(jdkLoggerClass.getDeclaredMethods()) + .filter(m -> Modifier.isStatic(m.getModifiers())) + .filter(m -> Modifier.isPublic(m.getModifiers())) + .filter(m -> !m.getName().equals("getLoggerFinder")) + .filter(m -> { + try { + final Method x = bridgeLoggerClass.getDeclaredMethod(m.getName(), m.getParameterTypes()); + return x == null; + } catch (NoSuchMethodException ex) { + return true; + } + }).forEach(m -> { + final String errorMsg = bridgeLoggerClass.getName() + " should override\n\t" + m.toString(); + System.err.println(errorMsg); + errors.append(errorMsg).append('\n'); + }); + if (errors.length() == 0) { + System.out.println(bridgeLoggerClass + " overrides all static methods from " + jdkLoggerClass); + } else { + if (errors.length() > 0) throw new RuntimeException(errors.toString()); + } + return errors; + } + + public static void main(String argv[]) throws Exception { + final LoggerFinderAPITest test = new LoggerFinderAPITest(false); + final StringBuilder errors = new StringBuilder(); + errors.append(test.testGetLoggerOverriddenOnSpi()); + java.lang.System.Logger julLogger = + java.lang.System.LoggerFinder.getLoggerFinder() + .getLogger("foo", LoggerFinderAPITest.class); + errors.append(test.testDefaultJULLogger(julLogger)); + if (errors.length() > 0) throw new RuntimeException(errors.toString()); + java.lang.System.Logger julSystemLogger = + java.lang.System.LoggerFinder.getLoggerFinder() + .getLogger("bar", Thread.class); + errors.append(test.testDefaultJULLogger(julSystemLogger)); + if (errors.length() > 0) throw new RuntimeException(errors.toString()); + java.lang.System.Logger julLocalizedLogger = + (java.lang.System.Logger) + System.getLogger("baz", bundleLocalized); + java.lang.System.Logger julLocalizedSystemLogger = + java.lang.System.LoggerFinder.getLoggerFinder() + .getLocalizedLogger("oof", bundleLocalized, Thread.class); + final String error = errors.toString(); + if (!error.isEmpty()) throw new RuntimeException(error); + for (java.lang.System.Logger logger : new java.lang.System.Logger[] { + julLogger, julSystemLogger, julLocalizedLogger, julLocalizedSystemLogger + }) { + test.testAllJdkExtensionMethods(logger); + test.testAllAPIMethods(logger); + test.testAllBridgeMethods(logger); + test.testAllLogProducerMethods(logger); + } + } + +}