diff --git a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/BUCK b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/BUCK index 30cd0dbe4..cc5c99ee1 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/BUCK @@ -32,4 +32,3 @@ android_library( 'PUBLIC', ], ) - diff --git a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/FallbackJSBundleLoader.java b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/FallbackJSBundleLoader.java index ee86feedc..ea2ff3274 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/FallbackJSBundleLoader.java +++ b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/FallbackJSBundleLoader.java @@ -14,6 +14,8 @@ import java.util.List; import java.util.ListIterator; import java.util.Stack; +import com.facebook.common.logging.FLog; + /** * FallbackJSBundleLoader * @@ -24,6 +26,7 @@ import java.util.Stack; public final class FallbackJSBundleLoader extends JSBundleLoader { /* package */ static final String RECOVERABLE = "facebook::react::Recoverable"; + /* package */ static final String TAG = "FallbackJSBundleLoader"; // Loaders to delegate to, with the preferred one at the top. private Stack mLoaders; @@ -60,7 +63,7 @@ public final class FallbackJSBundleLoader extends JSBundleLoader { mLoaders.pop(); mRecoveredErrors.add(e); - // TODO (t14839302): Report a soft error for each swallowed exception. + FLog.wtf(TAG, "Falling back from recoverable error", e); } } } diff --git a/ReactAndroid/src/test/java/com/facebook/common/logging/BUCK b/ReactAndroid/src/test/java/com/facebook/common/logging/BUCK new file mode 100644 index 000000000..50139dd81 --- /dev/null +++ b/ReactAndroid/src/test/java/com/facebook/common/logging/BUCK @@ -0,0 +1,16 @@ +android_library( + name = 'logging', + srcs = glob(['**/*.java']), + + exported_deps = [ + react_native_dep('libraries/fbcore/src/main/java/com/facebook/common/logging:logging'), + ], + + deps = [ + react_native_dep('third-party/java/jsr-305:jsr-305'), + ], + + visibility = [ + 'PUBLIC', + ], +) diff --git a/ReactAndroid/src/test/java/com/facebook/common/logging/FakeLoggingDelegate.java b/ReactAndroid/src/test/java/com/facebook/common/logging/FakeLoggingDelegate.java new file mode 100644 index 000000000..80ebb0cee --- /dev/null +++ b/ReactAndroid/src/test/java/com/facebook/common/logging/FakeLoggingDelegate.java @@ -0,0 +1,151 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.common.logging; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; + +import javax.annotation.Nullable; + +public final class FakeLoggingDelegate implements LoggingDelegate { + + public static final class LogLine { + public final int priority; + public final String tag; + public final String msg; + public final @Nullable Throwable tr; + + private LogLine( + int priority, + String tag, + String msg, + @Nullable Throwable tr) { + + this.priority = priority; + this.tag = tag; + this.msg = msg; + this.tr = tr; + } + } + + public static final int ASSERT = FLog.ASSERT; + public static final int DEBUG = FLog.DEBUG; + public static final int ERROR = FLog.ERROR; + public static final int INFO = FLog.INFO; + public static final int VERBOSE = FLog.VERBOSE; + public static final int WARN = FLog.WARN; + + /** + * There is no log level for Terrible Failures (we emit them at the Error + * Log-level), but to test that WTF errors are being logged, we are making up + * a new log level here, guaranteed to be larger than any of the other log + * levels. + */ + public static final int WTF = + 1 + Collections.max(Arrays.asList(ASSERT, DEBUG, ERROR, INFO, VERBOSE, WARN)); + + private int mMinLogLevel = FLog.VERBOSE; + private final ArrayList mLogs = new ArrayList<>(); + + /** Test Harness */ + + private static boolean matchLogQuery( + int priority, + String tag, + @Nullable String throwMsg, + LogLine line) { + return priority == line.priority + && tag.equals(line.tag) + && (throwMsg == null || throwMsg.equals(line.tr.getMessage())); + } + + public boolean logContains(int priority, String tag, String throwMsg) { + for (FakeLoggingDelegate.LogLine line : mLogs) { + if (matchLogQuery(priority, tag, throwMsg, line)) { + return true; + } + } + + return false; + } + + /** LoggingDelegate API */ + + public int getMinimumLoggingLevel() { + return mMinLogLevel; + } + + public void setMinimumLoggingLevel(int level) { + mMinLogLevel = level; + } + + public boolean isLoggable(int level) { + return level >= mMinLogLevel; + } + + private void logImpl(int priority, String tag, String msg, Throwable tr) { + if (isLoggable(priority)) { + mLogs.add(new LogLine(priority, tag, msg, tr)); + } + } + + public void log(int priority, String tag, String msg) { + logImpl(priority, tag, msg, null); + } + + public void d(String tag, String msg, Throwable tr) { + logImpl(DEBUG, tag, msg, tr); + } + + public void d(String tag, String msg) { + logImpl(DEBUG, tag, msg, null); + } + + public void e(String tag, String msg, Throwable tr) { + logImpl(ERROR, tag, msg, tr); + } + + public void e(String tag, String msg) { + logImpl(ERROR, tag, msg, null); + } + + public void i(String tag, String msg, Throwable tr) { + logImpl(INFO, tag, msg, tr); + } + + public void i(String tag, String msg) { + logImpl(INFO, tag, msg, null); + } + + public void v(String tag, String msg, Throwable tr) { + logImpl(VERBOSE, tag, msg, tr); + } + + public void v(String tag, String msg) { + logImpl(VERBOSE, tag, msg, null); + } + + public void w(String tag, String msg, Throwable tr) { + logImpl(WARN, tag, msg, tr); + } + + public void w(String tag, String msg) { + logImpl(WARN, tag, msg, null); + } + + public void wtf(String tag, String msg, Throwable tr) { + logImpl(WTF, tag, msg, tr); + } + + public void wtf(String tag, String msg) { + logImpl(WTF, tag, msg, null); + } +} diff --git a/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/BUCK b/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/BUCK index 0a5a55086..e7f39e896 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/BUCK +++ b/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/BUCK @@ -11,6 +11,7 @@ rn_robolectric_test( react_native_dep('third-party/java/mockito:mockito'), react_native_dep('third-party/java/robolectric3/robolectric:robolectric'), react_native_target('java/com/facebook/react/cxxbridge:bridge'), + react_native_tests_target('java/com/facebook/common/logging:logging'), ], visibility = [ 'PUBLIC' diff --git a/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/FallbackJSBundleLoaderTest.java b/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/FallbackJSBundleLoaderTest.java index 45c0fd1e8..bb7ae0151 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/FallbackJSBundleLoaderTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/cxxbridge/FallbackJSBundleLoaderTest.java @@ -15,6 +15,10 @@ import java.util.Arrays; import org.junit.Before; import org.junit.Test; +import com.facebook.common.logging.FLog; +import com.facebook.common.logging.FakeLoggingDelegate; +import com.facebook.common.logging.LoggingDelegate; + import static org.fest.assertions.api.Assertions.assertThat; import static org.fest.assertions.api.Assertions.fail; @@ -34,6 +38,14 @@ public class FallbackJSBundleLoaderTest { UNRECOVERABLE = prefix.replace(first, (char) (first + 1)); } + private FakeLoggingDelegate mLoggingDelegate; + + @Before + public void setup() { + mLoggingDelegate = new FakeLoggingDelegate(); + FLog.setLoggingDelegate(mLoggingDelegate); + } + @Test public void firstLoaderSucceeds() { JSBundleLoader delegates[] = new JSBundleLoader[] { @@ -48,6 +60,12 @@ public class FallbackJSBundleLoaderTest { verify(delegates[0], times(1)).loadScript(null); verify(delegates[1], never()).loadScript(null); + + assertThat(mLoggingDelegate.logContains( + FakeLoggingDelegate.WTF, + FallbackJSBundleLoader.TAG, + null)) + .isFalse(); } @Test @@ -66,6 +84,12 @@ public class FallbackJSBundleLoaderTest { verify(delegates[0], times(1)).loadScript(null); verify(delegates[1], times(1)).loadScript(null); verify(delegates[2], never()).loadScript(null); + + assertThat(mLoggingDelegate.logContains( + FakeLoggingDelegate.WTF, + FallbackJSBundleLoader.TAG, + recoverableMsg("error1"))) + .isTrue(); } @Test @@ -98,6 +122,18 @@ public class FallbackJSBundleLoaderTest { verify(delegates[0], times(1)).loadScript(null); verify(delegates[1], times(1)).loadScript(null); + + assertThat(mLoggingDelegate.logContains( + FakeLoggingDelegate.WTF, + FallbackJSBundleLoader.TAG, + recoverableMsg("error1"))) + .isTrue(); + + assertThat(mLoggingDelegate.logContains( + FakeLoggingDelegate.WTF, + FallbackJSBundleLoader.TAG, + recoverableMsg("error2"))) + .isTrue(); } @Test @@ -119,6 +155,12 @@ public class FallbackJSBundleLoaderTest { verify(delegates[0], times(1)).loadScript(null); verify(delegates[1], never()).loadScript(null); + + assertThat(mLoggingDelegate.logContains( + FakeLoggingDelegate.WTF, + FallbackJSBundleLoader.TAG, + null)) + .isFalse(); } private static JSBundleLoader successfulLoader(String url) {