/** * Copyright (c) 2012-2017, jcabi.com * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: 1) Redistributions of source code must retain the above * copyright notice, this list of conditions and the following * disclaimer. 2) Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. 3) Neither the name of the jcabi.com nor * the names of its contributors may be used to endorse or promote * products derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jcabi.aspects; import java.io.IOException; import java.io.StringWriter; import java.util.Collection; import java.util.LinkedList; import java.util.Random; import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; import org.apache.log4j.PatternLayout; import org.apache.log4j.SimpleLayout; import org.apache.log4j.WriterAppender; import org.hamcrest.Description; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.hamcrest.TypeSafeMatcher; import org.junit.Test; /** * Test case for {@link Loggable} annotation and its implementation. * @author Yegor Bugayenko (yegor@tpc2.com) * @version $Id: f5b5216026c71a910c717b06256018a3eac2c4ef $ */ @SuppressWarnings({ "PMD.TooManyMethods", "PMD.AvoidUsingShortType" }) public final class LoggableTest { /** * Foo toString result. */ private static final transient String RESULT = "some text"; /** * Loggable can log simple calls. * @throws Exception If something goes wrong */ @Test public void logsSimpleCall() throws Exception { new LoggableTest.Foo().revert("hello"); } /** * Loggable can ignore toString() methods. * @throws Exception If something goes wrong */ @Test public void ignoresToStringMethods() throws Exception { new LoggableTest.Foo().self(); } /** * Loggable can log static methods. * @throws Exception If something goes wrong */ @Test public void logsStaticMethods() throws Exception { LoggableTest.Foo.text(); } /** * Loggable can ignore inherited methods. * @throws Exception If something goes wrong */ @Test public void doesntLogInheritedMethods() throws Exception { new LoggableTest.Foo().parentText(); } /** * Loggable can ignore some exceptions. * @throws Exception If something goes wrong */ @Test(expected = IllegalStateException.class) public void ignoresSomeExceptions() throws Exception { new LoggableTest.Foo().doThrow(); } /** * Loggable can log duration with a specific time unit. * @throws Exception If something goes wrong */ @Test public void logsDurationWithSpecifiedTimeUnit() throws Exception { final StringWriter writer = new StringWriter(); org.apache.log4j.Logger.getRootLogger().addAppender( new WriterAppender(new SimpleLayout(), writer) ); LoggableTest.Foo.logsDurationInSeconds(); MatcherAssert.assertThat( writer.toString(), RegexContainsMatcher.contains("in \\d.\\d{3}") ); } /** * Loggable can log toString method. * @throws Exception If something goes wrong */ @Test public void logsToStringResult() throws Exception { final StringWriter writer = new StringWriter(); org.apache.log4j.Logger.getRootLogger().addAppender( new WriterAppender(new SimpleLayout(), writer) ); new LoggableTest.Foo().last("TEST"); MatcherAssert.assertThat( writer.toString(), RegexContainsMatcher.contains(LoggableTest.RESULT) ); } /** * Loggable can log byte array results. */ @Test public void logsByteArray() { final StringWriter writer = new StringWriter(); org.apache.log4j.Logger.getRootLogger().addAppender( new WriterAppender(new SimpleLayout(), writer) ); final byte[] result = new LoggableTest.Foo().logsByteArray(); MatcherAssert.assertThat( writer.toString(), Matchers.not( Matchers.containsString( ClassCastException.class.getSimpleName() ) ) ); final Collection<String> bytes = new LinkedList<String>(); for (final byte part : result) { bytes.add(Byte.toString(part)); } MatcherAssert.assertThat( writer.toString(), Matchers.stringContainsInOrder(bytes) ); } /** * Loggable can log short array results. */ @Test public void logsShortArray() { final StringWriter writer = new StringWriter(); org.apache.log4j.Logger.getRootLogger().addAppender( new WriterAppender(new SimpleLayout(), writer) ); final short[] result = new LoggableTest.Foo().logsShortArray(); MatcherAssert.assertThat( writer.toString(), Matchers.not( Matchers.containsString( ClassCastException.class.getSimpleName() ) ) ); final Collection<String> shorts = new LinkedList<String>(); for (final short part : result) { shorts.add(Short.toString(part)); } MatcherAssert.assertThat( writer.toString(), Matchers.stringContainsInOrder(shorts) ); } /** * Loggable can log methods that specify their own logger name. * @throws Exception If something goes wrong */ @Test public void logsWithExplicitLoggerName() throws Exception { final StringWriter writer = new StringWriter(); org.apache.log4j.Logger.getRootLogger().addAppender( new WriterAppender(new PatternLayout("%t %c: %m%n"), writer) ); LoggableTest.Foo.explicitLoggerName(); MatcherAssert.assertThat( // @checkstyle MultipleStringLiterals (2 lines) writer.toString(), Matchers.containsString("test-logger") ); } /** * Parent class, without logging. */ private static class Parent { /** * Get some text. * @return The text */ public String parentText() { return "some parent text"; } } /** * Dummy class, for tests above. */ @Loggable ( value = Loggable.DEBUG, prepend = true, limit = 1, unit = TimeUnit.MILLISECONDS ) private static final class Foo extends LoggableTest.Parent { @Override public String toString() { return LoggableTest.RESULT; } /** * Get self instance. * @return Self */ @Loggable(Loggable.INFO) public Foo self() { return this; } /** * Static method. * @return Some text * @throws Exception If terminated */ @Timeable(limit = 1, unit = TimeUnit.HOURS) public static String text() throws Exception { TimeUnit.SECONDS.sleep(2L); return LoggableTest.Foo.hiddenText(); } /** * Method annotated with Loggable specifying explicit logger name. * @return A String * @throws Exception If terminated */ @Loggable(value = Loggable.DEBUG, name = "test-logger", prepend = true) public static String explicitLoggerName() throws Exception { return LoggableTest.Foo.hiddenText(); } /** * Revert string. * @param text Some text * @return Reverted text */ @Timeable @Loggable(value = Loggable.INFO, trim = false) public String revert(final String text) { return new StringBuffer(text).reverse().toString(); } /** * Method with different time unit specification. * @return Some text * @throws Exception If terminated */ @Loggable(precision = Tv.THREE) public static String logsDurationInSeconds() throws Exception { TimeUnit.SECONDS.sleep(2); return LoggableTest.Foo.hiddenText(); } /** * Method returns byte array. * @return Byte array. */ @Loggable public byte[] logsByteArray() { final byte[] bytes = new byte[Tv.TEN]; new Random().nextBytes(bytes); return bytes; } /** * Method returns short array. * @return Byte array. */ @Loggable public short[] logsShortArray() { final short[] shorts = new short[Tv.TEN]; final Random random = new Random(); for (int idx = 0; idx < shorts.length; ++idx) { shorts[idx] = (short) random.nextInt(); } return shorts; } /** * Get last char. * @param text Text to get last char from. * @return Last char. */ @Loggable(value = Loggable.INFO, logThis = true) public String last(final String text) { return text.substring(text.length() - 1); } /** * Private static method. * @return Some text */ private static String hiddenText() { return "some static text"; } /** * Always throw. */ @Loggable(ignore = { IOException.class, RuntimeException.class }) private void doThrow() { throw new IllegalStateException(); } } /** * Matcher that checks if a string contains the given pattern. */ private static class RegexContainsMatcher extends TypeSafeMatcher<String> { /** * Regex to match against. */ private final transient Pattern pattern; /** * Ctor. * @param regex The regex pattern */ public RegexContainsMatcher(final String regex) { super(); this.pattern = Pattern.compile(regex); } @Override public boolean matchesSafely(final String str) { return this.pattern.matcher(str).find(); } @Override public void describeTo(final Description description) { description.appendText("matches regex="); } /** * Matcher for regex patterns. * @param regex The regex pattern * @return Matcher for containing regex pattern */ public static RegexContainsMatcher contains(final String regex) { return new RegexContainsMatcher(regex); } } }