diff --git a/logginginterceptor/.gitignore b/logginginterceptor/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/logginginterceptor/.gitignore @@ -0,0 +1 @@ +/build diff --git a/logginginterceptor/build.gradle b/logginginterceptor/build.gradle new file mode 100644 index 00000000..68b450ab --- /dev/null +++ b/logginginterceptor/build.gradle @@ -0,0 +1,55 @@ +buildscript { + repositories { + jcenter() + } + dependencies { + classpath "com.novoda:bintray-release:$bintrayVersion" + } +} + +apply plugin: 'com.android.library' +apply plugin: 'bintray-release' +apply plugin: 'jacoco-android' + +android { + compileSdkVersion COMPILE_SDK_VERSION + buildToolsVersion BUILD_TOOLS_VERSION + + defaultConfig { + minSdkVersion MIN_SDK_VERSION + targetSdkVersion TARGET_SDK_VERSION + versionCode VERSION_CODE + versionName VERSION_NAME + } + buildTypes { + release { + minifyEnabled false + } + debug { + // output coverage with ./gradlew clean build createDebugCoverageReport + testCoverageEnabled true + } + } + lintOptions { + abortOnError false + } +} + +dependencies { + compile project(':thirtyinch') + provided "com.android.support:support-annotations:$supportLibraryVersion" + + testCompile "junit:junit:$junitVersion" + testCompile "org.mockito:mockito-core:$mockitoVersion" + testCompile "org.assertj:assertj-core:$assertjVersion" +} + +publish { + userOrg = 'passsy' + groupId = 'net.grandcentrix.thirtyinch' + artifactId = 'thirtyinch-logginginterceptor' + uploadName = 'ThirtyInch' + publishVersion = VERSION_NAME + //description = '' + website = 'https://github.com/grandcentrix/ThirtyInch' +} \ No newline at end of file diff --git a/logginginterceptor/src/main/AndroidManifest.xml b/logginginterceptor/src/main/AndroidManifest.xml new file mode 100644 index 00000000..e1be1291 --- /dev/null +++ b/logginginterceptor/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/logginginterceptor/src/main/java/net/grandcentrix/thirtyinch/logginginterceptor/LoggingInterceptor.java b/logginginterceptor/src/main/java/net/grandcentrix/thirtyinch/logginginterceptor/LoggingInterceptor.java new file mode 100644 index 00000000..98e78cd2 --- /dev/null +++ b/logginginterceptor/src/main/java/net/grandcentrix/thirtyinch/logginginterceptor/LoggingInterceptor.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2017 grandcentrix GmbH + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.grandcentrix.thirtyinch.logginginterceptor; + +import net.grandcentrix.thirtyinch.BindViewInterceptor; +import net.grandcentrix.thirtyinch.TiLog; +import net.grandcentrix.thirtyinch.TiView; +import net.grandcentrix.thirtyinch.util.AbstractInvocationHandler; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Log; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.List; + +import static net.grandcentrix.thirtyinch.util.AnnotationUtil.getInterfaceOfClassExtendingGivenInterface; + +/** + * Logs all methods calls and parameters to the bound view interface. + */ +public class LoggingInterceptor implements BindViewInterceptor { + + private final static class MethodLoggingInvocationHandler extends AbstractInvocationHandler { + + /** + * limit each argument instead of the complete string. This should limit the overall + * output to a reasonable length while showing all params + */ + private static final int MAX_LENGTH_OF_PARAM = 240; + + private TiLog.Logger mLogger; + + private final V mView; + + private MethodLoggingInvocationHandler(V view, @NonNull TiLog.Logger logger) { + mView = view; + mLogger = logger; + } + + @Override + public String toString() { + return "MethodLoggingProxy@" + Integer.toHexString(this.hashCode()) + "-" + mView + .toString(); + } + + @Override + protected Object handleInvocation(final Object proxy, final Method method, + final Object[] args) + throws Throwable { + + try { + mLogger.log(Log.VERBOSE, TAG, toString(method, args)); + return method.invoke(mView, args); + } catch (InvocationTargetException e) { + throw e.getCause(); + } + } + + private static String parseParams(Object[] methodParams, int maxLenOfParam) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < methodParams.length; i++) { + final Object param = methodParams[i]; + + final String paramString; + if (param instanceof List) { + final int size = ((List) param).size(); + final String stringPresentation = String.valueOf(param); + paramString = "{" + param.getClass().getSimpleName() + + "[" + size + "]" + + "@" + Integer.toHexString(param.hashCode()) + "}" + + " " + stringPresentation; + } else if (param instanceof Object[]) { + final Object[] args = ((Object[]) param); + final int size = args.length; + + final StringBuilder sb = new StringBuilder(); + sb.append("["); + for (int j = 0; j < args.length; j++) { + sb.append(String.valueOf(args[j])); + if (j + 1 < args.length) { + sb.append(", "); + } + } + sb.append("]"); + + paramString = "{" + param.getClass().getSimpleName() + + "[" + size + "]" + + "@" + Integer.toHexString(param.hashCode()) + "}" + + " " + sb; + } else { + paramString = String.valueOf(param); + } + + if (paramString.length() <= maxLenOfParam) { + builder.append(paramString); + } else { + final String shortParam = paramString.substring(0, + Math.min(paramString.length(), maxLenOfParam)); + builder.append(shortParam); + // trim remaining whitespace at the end before appending ellipsis + builder = new StringBuilder(builder.toString().trim()); + builder.append("…"); + } + builder.append(", "); + } + + // remove last ", " + int length = builder.length(); + builder.delete(length - 2, length); + + return builder.toString(); + } + + private static String toString(@NonNull final Method method, + @Nullable final Object[] args) { + final StringBuilder sb = new StringBuilder(method.getName()); + sb.append("("); + if (args != null && args.length > 0) { + final String paramsString = parseParams(args, MAX_LENGTH_OF_PARAM); + sb.append(paramsString); + } + sb.append(")"); + return sb.toString(); + } + } + + private static final String TAG = LoggingInterceptor.class.getSimpleName(); + + private final TiLog.Logger mLogger; + + /** + * Logs all view interface method invocations to {@link TiLog}. You may have to enable + * logging from {@link TiLog} or set your own logger with {@link LoggingInterceptor#LoggingInterceptor(TiLog.Logger)} + */ + public LoggingInterceptor() { + this(TiLog.TI_LOG); + } + + /** + * Logs all view interface method invocations to the provided {@link TiLog.Logger} interface + * + * @param logger custom logger, {@link TiLog#LOGCAT} or {@link TiLog#NOOP} to disable logging. + */ + public LoggingInterceptor(@Nullable final TiLog.Logger logger) { + if (logger == null) { + mLogger = TiLog.NOOP; + } else { + mLogger = logger; + } + } + + @Override + public V intercept(final V view) { + if (mLogger != TiLog.NOOP) { + final V wrapped = wrap(view); + TiLog.v(TAG, "wrapping View " + view + " in " + wrapped); + return wrapped; + } + return view; + } + + @SuppressWarnings("unchecked") + private V wrap(final V view) { + + Class foundInterfaceClass = getInterfaceOfClassExtendingGivenInterface( + view.getClass(), TiView.class); + if (foundInterfaceClass == null) { + throw new IllegalStateException("the interface extending TiView could not be found"); + } + + final V wrappedView = (V) Proxy.newProxyInstance( + foundInterfaceClass.getClassLoader(), new Class[]{foundInterfaceClass}, + new MethodLoggingInvocationHandler<>(view, mLogger)); + + return wrappedView; + } +} \ No newline at end of file diff --git a/logginginterceptor/src/test/java/net/grandcentrix/thirtyinch/logginginterceptor/LoggingInterceptorTest.java b/logginginterceptor/src/test/java/net/grandcentrix/thirtyinch/logginginterceptor/LoggingInterceptorTest.java new file mode 100644 index 00000000..4895f4b9 --- /dev/null +++ b/logginginterceptor/src/test/java/net/grandcentrix/thirtyinch/logginginterceptor/LoggingInterceptorTest.java @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2017 grandcentrix GmbH + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.grandcentrix.thirtyinch.logginginterceptor; + +import net.grandcentrix.thirtyinch.TiLog; +import net.grandcentrix.thirtyinch.TiView; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.ArgumentCaptor; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.assertj.core.api.Java6Assertions.fail; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +@RunWith(JUnit4.class) +public class LoggingInterceptorTest { + + private class BaseActivity implements MyView { + + } + + private class MyActivity extends BaseActivity { + + } + + private class TestViewImpl implements TestView { + + @Override + public void doSomething() { + // stub + } + + @Override + public void singleArg(final Object arg) { + // stub + } + + @Override + public void throwUnexpected() { + throw new RuntimeException("Unexpected"); + } + + @Override + public void twoArgs(final Object arg1, final Object arg2) { + // stub + } + + @Override + public void varargs(final Object... args) { + // stub + } + } + + private interface MyView extends TiView { + + } + + private interface TestView extends TiView { + + void doSomething(); + + void singleArg(Object arg); + + void throwUnexpected(); + + void twoArgs(Object arg1, Object arg2); + + void varargs(Object... args); + + } + + @Test + public void testCropLongParams() throws Exception { + + final TiLog.Logger logger = mock(TiLog.Logger.class); + final LoggingInterceptor loggingInterceptor = new LoggingInterceptor(logger); + final TestView view = loggingInterceptor.intercept(new TestViewImpl()); + + final ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(String.class); + + String maxArg = ""; + while (maxArg.length() < 240) { + maxArg += "0123456789"; + } + + view.twoArgs(maxArg + "too long", "B"); + verify(logger).log(anyInt(), anyString(), msgCaptor.capture()); + + assertThat(msgCaptor.getValue()) + .doesNotContain("too long") + .isEqualTo("twoArgs(" + maxArg + "…, B)"); + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + @Test + public void testDontLogObjectInvocations() throws Exception { + + final TiLog.Logger logger = mock(TiLog.Logger.class); + final LoggingInterceptor loggingInterceptor = new LoggingInterceptor(logger); + final TestView view = loggingInterceptor.intercept(new TestViewImpl()); + + view.hashCode(); + view.toString(); + view.getClass(); + verify(logger, never()).log(anyInt(), anyString(), anyString()); + } + + @Test + public void testFindTiViewInterfaceInComplexStructure() throws Exception { + final LoggingInterceptor interceptor = new LoggingInterceptor(); + final TiView interceptView = interceptor.intercept(new MyActivity()); + assertThat(interceptView) + .isInstanceOf(TiView.class) + .isInstanceOf(MyView.class) + .isNotInstanceOf(MyActivity.class) + .isNotInstanceOf(BaseActivity.class); + } + + @Test + public void testLogArray() throws Exception { + + final TiLog.Logger logger = mock(TiLog.Logger.class); + final LoggingInterceptor loggingInterceptor = new LoggingInterceptor(logger); + final TestView view = loggingInterceptor.intercept(new TestViewImpl()); + + final ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(String.class); + + final String[] array = new String[]{"Buenos Aires", "Córdoba", "La Plata"}; + view.twoArgs(array, "B"); + verify(logger).log(anyInt(), anyString(), msgCaptor.capture()); + + assertThat(msgCaptor.getValue()) + .matches("twoArgs\\(\\{String\\[\\]\\[3\\]@[\\da-f]{1,8}\\} \\" + + "[Buenos Aires, Córdoba, La Plata\\], B\\)"); + } + + @Test + public void testLogEmptyList() throws Exception { + + final TiLog.Logger logger = mock(TiLog.Logger.class); + final LoggingInterceptor loggingInterceptor = new LoggingInterceptor(logger); + final TestView view = loggingInterceptor.intercept(new TestViewImpl()); + + final ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(String.class); + + view.twoArgs(new ArrayList(), "B"); + verify(logger).log(anyInt(), anyString(), msgCaptor.capture()); + + assertThat(msgCaptor.getValue()) + .matches("twoArgs\\(" + + "\\{ArrayList\\[0\\]@[\\da-f]{1,8}\\} \\[\\], " + + "B" + + "\\)"); + } + + @Test + public void testLogLists() throws Exception { + + final TiLog.Logger logger = mock(TiLog.Logger.class); + final LoggingInterceptor loggingInterceptor = new LoggingInterceptor(logger); + final TestView view = loggingInterceptor.intercept(new TestViewImpl()); + + final ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(String.class); + + final List list = Arrays.asList("Buenos Aires", "Córdoba", "La Plata"); + view.twoArgs(list, "B"); + verify(logger).log(anyInt(), anyString(), msgCaptor.capture()); + + assertThat(msgCaptor.getValue()) + .matches("twoArgs\\(" + + "\\{ArrayList\\[3\\]@[\\da-f]{1,8}\\} \\[Buenos Aires, Córdoba, La Plata\\], " + + "B" + + "\\)"); + } + + @Test + public void testLogMultipleArguments() throws Exception { + + final TiLog.Logger logger = mock(TiLog.Logger.class); + final LoggingInterceptor loggingInterceptor = new LoggingInterceptor(logger); + final TestView view = loggingInterceptor.intercept(new TestViewImpl()); + + final ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(String.class); + + view.twoArgs("A", "B"); + verify(logger).log(anyInt(), anyString(), msgCaptor.capture()); + + assertThat(msgCaptor.getValue()).isEqualTo("twoArgs(A, B)"); + } + + @Test + public void testLogNull() throws Exception { + + final TiLog.Logger logger = mock(TiLog.Logger.class); + final LoggingInterceptor loggingInterceptor = new LoggingInterceptor(logger); + final TestView view = loggingInterceptor.intercept(new TestViewImpl()); + + final ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(String.class); + + view.singleArg(null); + verify(logger).log(anyInt(), anyString(), msgCaptor.capture()); + + assertThat(msgCaptor.getValue()) + .isEqualTo("singleArg(null)"); + } + + @Test + public void testLogNullVarargs() throws Exception { + + final TiLog.Logger logger = mock(TiLog.Logger.class); + final LoggingInterceptor loggingInterceptor = new LoggingInterceptor(logger); + final TestView view = loggingInterceptor.intercept(new TestViewImpl()); + + final ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(String.class); + + view.varargs((Object[]) null); + verify(logger).log(anyInt(), anyString(), msgCaptor.capture()); + + assertThat(msgCaptor.getValue()) + .isEqualTo("varargs(null)"); + } + + @Test + public void testLogVarargs() throws Exception { + + final TiLog.Logger logger = mock(TiLog.Logger.class); + final LoggingInterceptor loggingInterceptor = new LoggingInterceptor(logger); + final TestView view = loggingInterceptor.intercept(new TestViewImpl()); + + final ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(String.class); + + view.varargs("Buenos Aires", "Córdoba", "La Plata"); + verify(logger).log(anyInt(), anyString(), msgCaptor.capture()); + + assertThat(msgCaptor.getValue()) + .matches("varargs\\(\\{Object\\[\\]\\[3\\]@[\\da-f]{1,8}\\} \\" + + "[Buenos Aires, Córdoba, La Plata\\]\\)"); + } + + @Test + public void testLogVoidMethods() throws Exception { + + final TiLog.Logger logger = mock(TiLog.Logger.class); + final LoggingInterceptor loggingInterceptor = new LoggingInterceptor(logger); + final TestView view = loggingInterceptor.intercept(new TestViewImpl()); + + final ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(String.class); + + view.doSomething(); + verify(logger).log(anyInt(), anyString(), msgCaptor.capture()); + + assertThat(msgCaptor.getValue()).isEqualTo("doSomething()"); + } + + @Test + public void testLoggerNoop_dontWrap() throws Exception { + final LoggingInterceptor interceptor = new LoggingInterceptor(TiLog.NOOP); + final TiView view = mock(TiView.class); + final TiView interceptView = interceptor.intercept(view); + assertThat(interceptView).isEqualTo(view); + } + + @Test + public void testLoggerNull_dontWrap() throws Exception { + final LoggingInterceptor interceptor = new LoggingInterceptor(null); + final TiView view = mock(TiView.class); + final TiView interceptView = interceptor.intercept(view); + assertThat(interceptView).isEqualTo(view); + } + + @Test + public void testLoggingDisabled_wrap() throws Exception { + final LoggingInterceptor interceptor = new LoggingInterceptor(); + final TiView view = mock(TiView.class); + final TiView interceptView = interceptor.intercept(view); + assertThat(interceptView).isNotEqualTo(view).isNotSameAs(view); + } + + @Test + public void testReportErrorsCorrectly() throws Exception { + + final TiLog.Logger logger = mock(TiLog.Logger.class); + final LoggingInterceptor loggingInterceptor = new LoggingInterceptor(logger); + final TestView view = loggingInterceptor.intercept(new TestViewImpl()); + + final ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(String.class); + + try { + view.throwUnexpected(); + fail("did not throw"); + } catch (RuntimeException e) { + assertThat(e).hasMessage("Unexpected"); + } + verify(logger).log(anyInt(), anyString(), msgCaptor.capture()); + + // make sure logging happened before the method was called + assertThat(msgCaptor.getValue()).isEqualTo("throwUnexpected()"); + } +} \ No newline at end of file diff --git a/sample/build.gradle b/sample/build.gradle index 452c1a25..762e013b 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -38,5 +38,6 @@ dependencies { compile project(':thirtyinch') compile project(':rx') + compile project(':logginginterceptor') testCompile "junit:junit:$junitVersion" } diff --git a/sample/src/main/java/net/grandcentrix/thirtyinch/sample/HelloWorldActivity.java b/sample/src/main/java/net/grandcentrix/thirtyinch/sample/HelloWorldActivity.java index 5e62f821..be7d3a99 100644 --- a/sample/src/main/java/net/grandcentrix/thirtyinch/sample/HelloWorldActivity.java +++ b/sample/src/main/java/net/grandcentrix/thirtyinch/sample/HelloWorldActivity.java @@ -19,6 +19,7 @@ import com.jakewharton.rxbinding.view.RxView; import net.grandcentrix.thirtyinch.TiActivity; +import net.grandcentrix.thirtyinch.logginginterceptor.LoggingInterceptor; import net.grandcentrix.thirtyinch.sample.fragmentlifecycle.FragmentLifecycleActivity; import net.grandcentrix.thirtyinch.sample.fragmentlifecycle.viewpager.LifecycleViewPagerActivity; @@ -42,6 +43,10 @@ public class HelloWorldActivity extends TiActivity onButtonClicked() { return RxView.clicks(mButton); diff --git a/settings.gradle b/settings.gradle index 8588ceb2..f0bb3c73 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,2 @@ -include ':thirtyinch', ':plugin', ':sample', ':rx', ':test', ':plugin-test', ':rx2' +include ':thirtyinch', ':plugin', ':sample', ':rx', ':test', ':plugin-test', ':rx2', + ':logginginterceptor' diff --git a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/TiLog.java b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/TiLog.java index 1dbf9c89..702ed911 100644 --- a/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/TiLog.java +++ b/thirtyinch/src/main/java/net/grandcentrix/thirtyinch/TiLog.java @@ -54,8 +54,28 @@ public void log(final int level, final String tag, final String msg) { } }; + /** + * no-op version, doesn't log + */ + public static Logger NOOP = new Logger() { + @Override + public void log(final int level, final String tag, final String msg) { + // no-op + } + }; + private static Logger logger; + /** + * forward log to {@link TiLog} for logging + */ + public static Logger TI_LOG = new Logger() { + @Override + public void log(final int level, final String tag, final String msg) { + TiLog.log(level, tag, msg); + } + }; + public static void d(final String tag, final String msg) { if (logger != null) { logger.log(Log.DEBUG, tag, msg); @@ -74,6 +94,12 @@ public static void i(final String tag, final String msg) { } } + public static void log(final int level, final String tag, final String msg) { + if (logger != null) { + logger.log(level, tag, msg); + } + } + /** * set a custom logger, {@code null} to disable logging *

@@ -99,6 +125,10 @@ public static void i(final String tag, final String msg) { * */ public static void setLogger(@Nullable final Logger logger) { + if (logger == TI_LOG) { + throw new IllegalArgumentException( + "Recursion warning: You can't use TI_LOG as Logger for TiLog"); + } TiLog.logger = logger; } diff --git a/thirtyinch/src/test/java/net/grandcentrix/thirtyinch/TiLogTest.java b/thirtyinch/src/test/java/net/grandcentrix/thirtyinch/TiLogTest.java new file mode 100644 index 00000000..db4cb514 --- /dev/null +++ b/thirtyinch/src/test/java/net/grandcentrix/thirtyinch/TiLogTest.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2017 grandcentrix GmbH + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.grandcentrix.thirtyinch; + +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import android.util.Log; + +import static junit.framework.Assert.fail; +import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class TiLogTest { + + @Test + public void dontCrashForNullLogger() throws Exception { + TiLog.setLogger(null); + TiLog.v("tag", "msg"); + TiLog.i("tag", "msg"); + TiLog.d("tag", "msg"); + TiLog.e("tag", "msg"); + TiLog.w("tag", "msg"); + TiLog.log(Log.VERBOSE, "tag", "msg"); + } + + @Test + public void logDToLogger() throws Exception { + final TiLog.Logger logger = mock(TiLog.Logger.class); + TiLog.setLogger(logger); + final ArgumentCaptor levelCaptor = ArgumentCaptor.forClass(Integer.class); + final ArgumentCaptor tagCaptor = ArgumentCaptor.forClass(String.class); + final ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(String.class); + + TiLog.d("tag", "msg"); + verify(logger).log(levelCaptor.capture(), tagCaptor.capture(), msgCaptor.capture()); + + assertThat(levelCaptor.getValue()).isEqualTo(Log.DEBUG); + assertThat(tagCaptor.getValue()).isEqualTo("tag"); + assertThat(msgCaptor.getValue()).isEqualTo("msg"); + } + + @Test + public void logEToLogger() throws Exception { + final TiLog.Logger logger = mock(TiLog.Logger.class); + TiLog.setLogger(logger); + final ArgumentCaptor levelCaptor = ArgumentCaptor.forClass(Integer.class); + final ArgumentCaptor tagCaptor = ArgumentCaptor.forClass(String.class); + final ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(String.class); + + TiLog.e("tag", "msg"); + verify(logger).log(levelCaptor.capture(), tagCaptor.capture(), msgCaptor.capture()); + + assertThat(levelCaptor.getValue()).isEqualTo(Log.ERROR); + assertThat(tagCaptor.getValue()).isEqualTo("tag"); + assertThat(msgCaptor.getValue()).isEqualTo("msg"); + } + + @Test + public void logIToLogger() throws Exception { + final TiLog.Logger logger = mock(TiLog.Logger.class); + TiLog.setLogger(logger); + final ArgumentCaptor levelCaptor = ArgumentCaptor.forClass(Integer.class); + final ArgumentCaptor tagCaptor = ArgumentCaptor.forClass(String.class); + final ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(String.class); + + TiLog.i("tag", "msg"); + verify(logger).log(levelCaptor.capture(), tagCaptor.capture(), msgCaptor.capture()); + + assertThat(levelCaptor.getValue()).isEqualTo(Log.INFO); + assertThat(tagCaptor.getValue()).isEqualTo("tag"); + assertThat(msgCaptor.getValue()).isEqualTo("msg"); + } + + @Test + public void logVToLogger() throws Exception { + final TiLog.Logger logger = mock(TiLog.Logger.class); + TiLog.setLogger(logger); + final ArgumentCaptor levelCaptor = ArgumentCaptor.forClass(Integer.class); + final ArgumentCaptor tagCaptor = ArgumentCaptor.forClass(String.class); + final ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(String.class); + + TiLog.v("tag", "msg"); + verify(logger).log(levelCaptor.capture(), tagCaptor.capture(), msgCaptor.capture()); + + assertThat(levelCaptor.getValue()).isEqualTo(Log.VERBOSE); + assertThat(tagCaptor.getValue()).isEqualTo("tag"); + assertThat(msgCaptor.getValue()).isEqualTo("msg"); + } + + @Test + public void logWToLogger() throws Exception { + final TiLog.Logger logger = mock(TiLog.Logger.class); + TiLog.setLogger(logger); + final ArgumentCaptor levelCaptor = ArgumentCaptor.forClass(Integer.class); + final ArgumentCaptor tagCaptor = ArgumentCaptor.forClass(String.class); + final ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(String.class); + + TiLog.w("tag", "msg"); + verify(logger).log(levelCaptor.capture(), tagCaptor.capture(), msgCaptor.capture()); + + assertThat(levelCaptor.getValue()).isEqualTo(Log.WARN); + assertThat(tagCaptor.getValue()).isEqualTo("tag"); + assertThat(msgCaptor.getValue()).isEqualTo("msg"); + } + + @Test + public void loglogDToLogger() throws Exception { + final TiLog.Logger logger = mock(TiLog.Logger.class); + TiLog.setLogger(logger); + final ArgumentCaptor levelCaptor = ArgumentCaptor.forClass(Integer.class); + final ArgumentCaptor tagCaptor = ArgumentCaptor.forClass(String.class); + final ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(String.class); + + TiLog.log(Log.DEBUG, "tag", "msg"); + verify(logger).log(levelCaptor.capture(), tagCaptor.capture(), msgCaptor.capture()); + + assertThat(levelCaptor.getValue()).isEqualTo(Log.DEBUG); + assertThat(tagCaptor.getValue()).isEqualTo("tag"); + assertThat(msgCaptor.getValue()).isEqualTo("msg"); + } + + @Test + public void loglogVToLogger() throws Exception { + final TiLog.Logger logger = mock(TiLog.Logger.class); + TiLog.setLogger(logger); + final ArgumentCaptor levelCaptor = ArgumentCaptor.forClass(Integer.class); + final ArgumentCaptor tagCaptor = ArgumentCaptor.forClass(String.class); + final ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(String.class); + + TiLog.log(Log.VERBOSE, "tag", "msg"); + verify(logger).log(levelCaptor.capture(), tagCaptor.capture(), msgCaptor.capture()); + + assertThat(levelCaptor.getValue()).isEqualTo(Log.VERBOSE); + assertThat(tagCaptor.getValue()).isEqualTo("tag"); + assertThat(msgCaptor.getValue()).isEqualTo("msg"); + } + + @Test + public void preventSettingRecursiveLogger() throws Exception { + try { + TiLog.setLogger(TiLog.TI_LOG); + fail("did not throw"); + } catch (Exception e) { + assertThat(e).hasMessageContaining("Recursion"); + } + } +} \ No newline at end of file