Path: blob/master/test/jdk/java/lang/System/LoggerFinder/internal/api/LoggerFinderAPITest.java
41161 views
/*1* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223/*24* @test25* @bug 814036426* @author danielfuchs27* @summary JDK implementation specific unit test for JDK internal artifacts.28* Tests the consistency of the LoggerFinder and JDK extensions.29* @modules java.base/sun.util.logging30* java.base/jdk.internal.logger31* java.logging32* @run main LoggerFinderAPITest33*/343536import java.lang.reflect.Method;37import java.lang.reflect.Modifier;38import java.util.ArrayList;39import java.util.Arrays;40import java.util.Collection;41import java.util.Collections;42import java.util.Enumeration;43import java.util.HashMap;44import java.util.LinkedHashMap;45import java.util.LinkedHashSet;46import java.util.List;47import java.util.Map;48import java.util.ResourceBundle;49import java.util.function.Supplier;50import java.util.logging.ConsoleHandler;51import java.util.logging.Handler;52import java.util.logging.LogRecord;53import java.util.logging.Logger;54import java.util.regex.Matcher;55import java.util.regex.Pattern;56import java.util.stream.Collectors;57import java.util.stream.Stream;58import sun.util.logging.PlatformLogger;5960public class LoggerFinderAPITest {6162static final Class<java.lang.System.Logger> spiLoggerClass63= java.lang.System.Logger.class;64static final Class<java.lang.System.Logger> jdkLoggerClass65= java.lang.System.Logger.class;66static final Class<sun.util.logging.PlatformLogger.Bridge> bridgeLoggerClass67= sun.util.logging.PlatformLogger.Bridge.class;68static final Class<java.util.logging.Logger> julLoggerClass69= java.util.logging.Logger.class;70static final Class<sun.util.logging.PlatformLogger.Bridge> julLogProducerClass71= PlatformLogger.Bridge.class;72static final Pattern julLogNames = Pattern.compile(73"^((log(p|rb)?)|severe|warning|info|config|fine|finer|finest|isLoggable)$");74static final Collection<Method> julLoggerIgnores;75static {76List<Method> ignores = new ArrayList<>();77try {78ignores.add(julLoggerClass.getDeclaredMethod("log", LogRecord.class));79} catch (NoSuchMethodException | SecurityException ex) {80throw new ExceptionInInitializerError(ex);81}82julLoggerIgnores = Collections.unmodifiableList(ignores);83}84858687// Don't require LoggerBridge to have a body for those methods88interface LoggerBridgeMethodsWithNoBody extends89PlatformLogger.Bridge, java.lang.System.Logger {9091@Override92public default String getName() {93throw new UnsupportedOperationException("Not supported yet.");94}9596@Override97public default boolean isLoggable(PlatformLogger.Level level) {98throw new UnsupportedOperationException("Not supported yet.");99}100101@Override102public default void log(sun.util.logging.PlatformLogger.Level level,103String msg, Throwable thrown) {104}105@Override106public default void log(sun.util.logging.PlatformLogger.Level level,107Throwable thrown, Supplier<String> msgSupplier) {108}109@Override110public default void log(sun.util.logging.PlatformLogger.Level level,111Supplier<String> msgSupplier) {112}113@Override114public default void log(sun.util.logging.PlatformLogger.Level level, String msg) {115}116@Override117public default void log(sun.util.logging.PlatformLogger.Level level,118String format, Object... params) {119}120@Override121public default void logrb(sun.util.logging.PlatformLogger.Level level,122ResourceBundle bundle, String key, Throwable thrown) {123}124@Override125public default void logrb(sun.util.logging.PlatformLogger.Level level,126ResourceBundle bundle, String format, Object... params) {127}128129@Override130public default void logrb(PlatformLogger.Level level,131String sourceClass, String sourceMethod,132ResourceBundle bundle, String msg, Throwable thrown) {133}134135@Override136public default void logrb(PlatformLogger.Level level, String sourceClass,137String sourceMethod, ResourceBundle bundle, String msg,138Object... params) {139}140141@Override142public default void logp(PlatformLogger.Level level, String sourceClass,143String sourceMethod, Supplier<String> msgSupplier) {144}145146@Override147public default void logp(PlatformLogger.Level level, String sourceClass,148String sourceMethod, String msg, Object... params) {149}150151@Override152public default void logp(PlatformLogger.Level level, String sourceClass,153String sourceMethod, String msg, Throwable thrown) {154}155156@Override157public default void logp(PlatformLogger.Level level, String sourceClass,158String sourceMethod, String msg) {159}160161@Override162public default void logp(PlatformLogger.Level level, String sourceClass,163String sourceMethod, Throwable thrown,164Supplier<String> msgSupplier) {165}166167static boolean requiresDefaultBodyFor(Method m) {168try {169Method m2 = LoggerBridgeMethodsWithNoBody.class170.getDeclaredMethod(m.getName(),171m.getParameterTypes());172return !m2.isDefault();173} catch (NoSuchMethodException x) {174return true;175}176}177}178179final boolean warnDuplicateMappings;180public LoggerFinderAPITest(boolean verbose) {181this.warnDuplicateMappings = verbose;182for (Handler h : Logger.getLogger("").getHandlers()) {183if (h instanceof ConsoleHandler) {184Logger.getLogger("").removeHandler(h);185}186}187Logger.getLogger("").addHandler( new Handler() {188@Override189public void publish(LogRecord record) {190StringBuilder builder = new StringBuilder();191builder.append("GOT LogRecord: ")192.append(record.getLevel().getLocalizedName())193.append(": [").append(record.getLoggerName())194.append("] ").append(record.getSourceClassName())195.append('.')196.append(record.getSourceMethodName()).append(" -> ")197.append(record.getMessage())198.append(' ')199.append(record.getParameters() == null ? ""200: Arrays.toString(record.getParameters()))201;202System.out.println(builder);203if (record.getThrown() != null) {204record.getThrown().printStackTrace(System.out);205}206}207@Override public void flush() {}208@Override public void close() {}209});210}211212public Stream<Method> getJulLogMethodStream(Class<?> loggerClass) {213214return Stream.of(loggerClass.getMethods()).filter((x) -> {215final Matcher m = julLogNames.matcher(x.getName());216return m.matches() ? x.getAnnotation(Deprecated.class) == null : false;217});218}219220/**221* Tells whether a method invocation of 'origin' can be transformed in a222* method invocation of 'target'.223* This method only look at the parameter signatures, it doesn't look at224* the name, nor does it look at the return types.225* <p>226* Example:227* <ul>228* <li>java.util.logging.Logger.log(Level, String, Object) can be invoked as<br>229java.util.logging.spi.Logger.log(Level, String, Object...) because the230last parameter in 'target' is a varargs.</li>231* <li>java.util.logging.Logger.log(Level, String) can also be invoked as<br>232java.util.logging.spi.Logger.log(Level, String, Object...) for the233same reason.</li>234* </ul>235* <p>236* The algorithm is tailored for our needs: when the last parameter in the237* target is a vararg, and when origin & target have the same number of238* parameters, then we consider that the types of the last parameter *must*239* match.240* <p>241* Similarly - we do not consider that o(X x, Y y, Y y) matches t(X x, Y... y)242* although strictly speaking, it should...243*244* @param origin The method in the original class245* @param target The correspondent candidate in the target class246* @return true if a method invocation of 'origin' can be transformed in a247* method invocation of 'target'.248*/249public boolean canBeInvokedAs(Method origin, Method target,250Map<Class<?>,Class<?>> substitutes) {251final Class<?>[] xParams = target.getParameterTypes();252final Class<?>[] mParams = Stream.of(origin.getParameterTypes())253.map((x) -> substitutes.getOrDefault(x, x))254.collect(Collectors.toList()).toArray(new Class<?>[0]);255if (Arrays.deepEquals(xParams, mParams)) return true;256if (target.isVarArgs()) {257if (xParams.length == mParams.length) {258if (xParams[xParams.length-1].isArray()) {259return mParams[mParams.length -1].equals(260xParams[xParams.length -1].getComponentType());261}262} else if (xParams.length == mParams.length + 1) {263return Arrays.deepEquals(264Arrays.copyOfRange(xParams, 0, xParams.length-1), mParams);265}266}267return false;268}269270/**271* Look whether {@code otherClass} has a public method similar to m272* @param m273* @param otherClass274* @return275*/276public Stream<Method> findInvokable(Method m, Class<?> otherClass) {277final Map<Class<?>,Class<?>> substitues =278Collections.singletonMap(java.util.logging.Level.class,279sun.util.logging.PlatformLogger.Level.class);280return Stream.of(otherClass.getMethods())281.filter((x) -> m.getName().equals(x.getName()))282.filter((x) -> canBeInvokedAs(m, x, substitues));283}284285/**286* Test that the concrete Logger implementation passed as parameter287* overrides all the methods defined by its interface.288* @param julLogger A concrete implementation of System.Logger289* whose backend is a JUL Logger.290*/291StringBuilder testDefaultJULLogger(java.lang.System.Logger julLogger) {292final StringBuilder errors = new StringBuilder();293if (!bridgeLoggerClass.isInstance(julLogger)) {294final String errorMsg =295"Logger returned by LoggerFactory.getLogger(\"foo\") is not a "296+ bridgeLoggerClass + "\n\t" + julLogger;297System.err.println(errorMsg);298errors.append(errorMsg).append('\n');299}300final Class<? extends java.lang.System.Logger> xClass = julLogger.getClass();301List<Method> notOverridden =302Stream.of(bridgeLoggerClass.getDeclaredMethods()).filter((m) -> {303try {304Method x = xClass.getDeclaredMethod(m.getName(), m.getParameterTypes());305return x == null;306} catch (NoSuchMethodException ex) {307return !Modifier.isStatic(m.getModifiers());308}309}).collect(Collectors.toList());310notOverridden.stream().filter((x) -> {311boolean shouldOverride = true;312try {313final Method m = xClass.getMethod(x.getName(), x.getParameterTypes());314Method m2 = null;315try {316m2 = jdkLoggerClass.getDeclaredMethod(x.getName(), x.getParameterTypes());317} catch (Exception e) {318319}320shouldOverride = m.isDefault() || m2 == null;321} catch (Exception e) {322// should override.323}324return shouldOverride;325}).forEach(x -> {326final String errorMsg = xClass.getName() + " should override\n\t" + x.toString();327System.err.println(errorMsg);328errors.append(errorMsg).append('\n');329});330if (notOverridden.isEmpty()) {331System.out.println(xClass + " overrides all methods from " + bridgeLoggerClass);332}333return errors;334}335336public static class ResourceBundeParam extends ResourceBundle {337Map<String, String> map = Collections.synchronizedMap(new LinkedHashMap<>());338@Override339protected Object handleGetObject(String key) {340map.putIfAbsent(key, "${"+key+"}");341return map.get(key);342}343344@Override345public Enumeration<String> getKeys() {346return Collections.enumeration(new LinkedHashSet<>(map.keySet()));347}348349}350351final ResourceBundle bundleParam =352ResourceBundle.getBundle(ResourceBundeParam.class.getName());353354public static class ResourceBundeLocalized extends ResourceBundle {355Map<String, String> map = Collections.synchronizedMap(new LinkedHashMap<>());356@Override357protected Object handleGetObject(String key) {358map.putIfAbsent(key, "Localized:${"+key+"}");359return map.get(key);360}361362@Override363public Enumeration<String> getKeys() {364return Collections.enumeration(new LinkedHashSet<>(map.keySet()));365}366367}368369final static ResourceBundle bundleLocalized =370ResourceBundle.getBundle(ResourceBundeLocalized.class.getName());371372final Map<Class<?>, Object> params = new HashMap<>();373{374params.put(String.class, "TestString");375params.put(sun.util.logging.PlatformLogger.Level.class, sun.util.logging.PlatformLogger.Level.WARNING);376params.put(java.lang.System.Logger.Level.class, java.lang.System.Logger.Level.WARNING);377params.put(ResourceBundle.class, bundleParam);378params.put(Throwable.class, new Throwable("TestThrowable (Please ignore it!)"));379params.put(Object[].class, new Object[] {"One", "Two"});380params.put(Object.class, new Object() {381@Override public String toString() { return "I am an object!"; }382});383}384385public Object[] getParamsFor(Method m) {386final Object[] res = new Object[m.getParameterCount()];387final Class<?>[] sig = m.getParameterTypes();388if (res.length == 0) {389return res;390}391for (int i=0; i<res.length; i++) {392Object p = params.get(sig[i]);393if (p == null && sig[i].equals(Supplier.class)) {394final String msg = "SuppliedMsg["+i+"]";395p = (Supplier<String>) () -> msg;396}397if (p instanceof String) {398res[i] = String.valueOf(p)+"["+i+"]";399} else {400res[i] = p;401}402}403return res;404}405406public void invokeOn(java.lang.System.Logger logger, Method m) {407Object[] p = getParamsFor(m);408try {409m.invoke(logger, p);410} catch (Exception e) {411throw new RuntimeException("Failed to invoke "+m.toString(), e);412}413}414415public void testAllJdkExtensionMethods(java.lang.System.Logger logger) {416Stream.of(jdkLoggerClass.getDeclaredMethods())417.filter(m -> !Modifier.isStatic(m.getModifiers()))418.forEach((m) -> invokeOn(logger, m));419}420421public void testAllAPIMethods(java.lang.System.Logger logger) {422Stream.of(spiLoggerClass.getDeclaredMethods())423.filter(m -> !Modifier.isStatic(m.getModifiers()))424.forEach((m) -> invokeOn(logger, m));425}426427public void testAllBridgeMethods(java.lang.System.Logger logger) {428Stream.of(bridgeLoggerClass.getDeclaredMethods())429.filter(m -> !Modifier.isStatic(m.getModifiers()))430.forEach((m) -> invokeOn(logger, m));431}432433public void testAllLogProducerMethods(java.lang.System.Logger logger) {434Stream.of(julLogProducerClass.getDeclaredMethods())435.filter(m -> !Modifier.isStatic(m.getModifiers()))436.forEach((m) -> invokeOn(logger, m));437}438439public StringBuilder testGetLoggerOverriddenOnSpi() {440final StringBuilder errors = new StringBuilder();441Stream.of(jdkLoggerClass.getDeclaredMethods())442.filter(m -> Modifier.isStatic(m.getModifiers()))443.filter(m -> Modifier.isPublic(m.getModifiers()))444.filter(m -> !m.getName().equals("getLoggerFinder"))445.filter(m -> {446try {447final Method x = bridgeLoggerClass.getDeclaredMethod(m.getName(), m.getParameterTypes());448return x == null;449} catch (NoSuchMethodException ex) {450return true;451}452}).forEach(m -> {453final String errorMsg = bridgeLoggerClass.getName() + " should override\n\t" + m.toString();454System.err.println(errorMsg);455errors.append(errorMsg).append('\n');456});457if (errors.length() == 0) {458System.out.println(bridgeLoggerClass + " overrides all static methods from " + jdkLoggerClass);459} else {460if (errors.length() > 0) throw new RuntimeException(errors.toString());461}462return errors;463}464465public static void main(String argv[]) throws Exception {466final LoggerFinderAPITest test = new LoggerFinderAPITest(false);467final StringBuilder errors = new StringBuilder();468errors.append(test.testGetLoggerOverriddenOnSpi());469java.lang.System.Logger julLogger =470java.lang.System.LoggerFinder.getLoggerFinder()471.getLogger("foo", LoggerFinderAPITest.class.getModule());472errors.append(test.testDefaultJULLogger(julLogger));473if (errors.length() > 0) throw new RuntimeException(errors.toString());474java.lang.System.Logger julSystemLogger =475java.lang.System.LoggerFinder.getLoggerFinder()476.getLogger("bar", Thread.class.getModule());477errors.append(test.testDefaultJULLogger(julSystemLogger));478if (errors.length() > 0) throw new RuntimeException(errors.toString());479java.lang.System.Logger julLocalizedLogger =480(java.lang.System.Logger)481System.getLogger("baz", bundleLocalized);482java.lang.System.Logger julLocalizedSystemLogger =483java.lang.System.LoggerFinder.getLoggerFinder()484.getLocalizedLogger("oof", bundleLocalized, Thread.class.getModule());485final String error = errors.toString();486if (!error.isEmpty()) throw new RuntimeException(error);487for (java.lang.System.Logger logger : new java.lang.System.Logger[] {488julLogger, julSystemLogger, julLocalizedLogger, julLocalizedSystemLogger489}) {490test.testAllJdkExtensionMethods(logger);491test.testAllAPIMethods(logger);492test.testAllBridgeMethods(logger);493test.testAllLogProducerMethods(logger);494}495}496497}498499500