/**
* 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);
}
}
}