diff --git a/ReactCommon/jserrorhandler/BUCK b/ReactCommon/jserrorhandler/BUCK index 9edd6729f64..da94ec52fa6 100644 --- a/ReactCommon/jserrorhandler/BUCK +++ b/ReactCommon/jserrorhandler/BUCK @@ -1,4 +1,4 @@ -load("//tools/build_defs/oss:rn_defs.bzl", "ANDROID", "APPLE", "CXX", "react_native_xplat_target", "rn_xplat_cxx_library") +load("//tools/build_defs/oss:rn_defs.bzl", "ANDROID", "APPLE", "CXX", "fb_xplat_cxx_test", "react_native_xplat_target", "rn_xplat_cxx_library") # TODO: Expolre merging this module into venice so we don't to load this library seperately rn_xplat_cxx_library( @@ -18,6 +18,7 @@ rn_xplat_cxx_library( "-DLOG_TAG=\"ReactNative\"", "-DWITH_FBSYSTRACE=1", ], + tests = [":tests"], visibility = [ "PUBLIC", ], @@ -28,3 +29,23 @@ rn_xplat_cxx_library( react_native_xplat_target("react/renderer/mapbuffer:mapbuffer"), ], ) + +fb_xplat_cxx_test( + name = "tests", + srcs = glob(["tests/**/*.cpp"]), + headers = glob(["tests/**/*.h"]), + compiler_flags = [ + "-fexceptions", + "-frtti", + "-std=c++17", + "-Wall", + ], + contacts = ["oncall+react_native@xmail.facebook.com"], + platforms = (ANDROID, APPLE, CXX), + deps = [ + "//xplat/hermes/API:HermesAPI", + "//xplat/third-party/gmock:gtest", + react_native_xplat_target("jserrorhandler:jserrorhandler"), + react_native_xplat_target("react/renderer/mapbuffer:mapbuffer"), + ], +) diff --git a/ReactCommon/jserrorhandler/tests/JsErrorHandlerTest.cpp b/ReactCommon/jserrorhandler/tests/JsErrorHandlerTest.cpp new file mode 100644 index 00000000000..44af20161b5 --- /dev/null +++ b/ReactCommon/jserrorhandler/tests/JsErrorHandlerTest.cpp @@ -0,0 +1,226 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace facebook::react { + +class JsErrorHandlerTest : public testing::Test { + void SetUp() override { + runtime_ = facebook::hermes::makeHermesRuntime(); + } + + protected: + MapBuffer parseErrorWithStackString(std::string stack, bool isFatal) { + std::optional result; + JsErrorHandler handler{ + [&](MapBuffer errorMap) -> void { result = std::move(errorMap); }}; + jsi::JSError error{*runtime_, "test message", stack}; + handler.handleJsError(error, isFatal); + EXPECT_TRUE(result.has_value()); + return std::move(*result); + } + + private: + std::unique_ptr runtime_; +}; + +TEST_F(JsErrorHandlerTest, testSimpleJscSourceStack) { + auto errorMap = parseErrorWithStackString( + "http://path/to/file.js:47:22\n" + "foo@http://path/to/file.js:52:15\n" + "bar@http://path/to/file.js:108:23", + /* isFatal */ false); + + EXPECT_FALSE(errorMap.getBool(JSErrorHandlerKey::kIsFatal)); + + auto frames = errorMap.getMapBufferList(JSErrorHandlerKey::kAllStackFrames); + EXPECT_EQ(frames.size(), 3); + + EXPECT_EQ( + frames[0].getString(JSErrorHandlerKey::kFrameFileName), + "http://path/to/file.js"); + EXPECT_EQ(frames[0].getInt(JSErrorHandlerKey::kFrameLineNumber), 47); + // TODO: This is the wrong column number. JSC's columns are 1-based while ours + // are 0-based. + EXPECT_EQ(frames[0].getInt(JSErrorHandlerKey::kFrameColumnNumber), 22); + EXPECT_EQ(frames[0].getString(JSErrorHandlerKey::kFrameMethodName), ""); + + EXPECT_EQ( + frames[1].getString(JSErrorHandlerKey::kFrameFileName), + "http://path/to/file.js"); + EXPECT_EQ(frames[1].getInt(JSErrorHandlerKey::kFrameLineNumber), 52); + // TODO: This is the wrong column number. JSC's columns are 1-based while ours + // are 0-based. + EXPECT_EQ(frames[1].getInt(JSErrorHandlerKey::kFrameColumnNumber), 15); + EXPECT_EQ(frames[1].getString(JSErrorHandlerKey::kFrameMethodName), "foo"); + + EXPECT_EQ( + frames[2].getString(JSErrorHandlerKey::kFrameFileName), + "http://path/to/file.js"); + EXPECT_EQ(frames[2].getInt(JSErrorHandlerKey::kFrameLineNumber), 108); + // TODO: This is the wrong column number. JSC's columns are 1-based while ours + // are 0-based. + EXPECT_EQ(frames[2].getInt(JSErrorHandlerKey::kFrameColumnNumber), 23); + EXPECT_EQ(frames[2].getString(JSErrorHandlerKey::kFrameMethodName), "bar"); +} + +TEST_F(JsErrorHandlerTest, testSimpleHermesBytecodeStack) { + auto errorMap = parseErrorWithStackString( + " at global (address at unknown:1:9)\n" + " at foo$bar (address at /js/foo.hbc:10:1234)", + /* isFatal */ false); + + EXPECT_FALSE(errorMap.getBool(JSErrorHandlerKey::kIsFatal)); + + auto frames = errorMap.getMapBufferList(JSErrorHandlerKey::kAllStackFrames); + EXPECT_EQ(frames.size(), 2); + + // TODO: This is the wrong source URL. + EXPECT_EQ( + frames[0].getString(JSErrorHandlerKey::kFrameFileName), + "address at unknown"); + EXPECT_EQ(frames[0].getInt(JSErrorHandlerKey::kFrameLineNumber), 1); + EXPECT_EQ(frames[0].getInt(JSErrorHandlerKey::kFrameColumnNumber), 9); + EXPECT_EQ(frames[0].getString(JSErrorHandlerKey::kFrameMethodName), "global"); + + // TODO: This is the wrong source URL. + EXPECT_EQ( + frames[1].getString(JSErrorHandlerKey::kFrameFileName), + "address at /js/foo.hbc"); + EXPECT_EQ(frames[1].getInt(JSErrorHandlerKey::kFrameLineNumber), 10); + EXPECT_EQ(frames[1].getInt(JSErrorHandlerKey::kFrameColumnNumber), 1234); + EXPECT_EQ( + frames[1].getString(JSErrorHandlerKey::kFrameMethodName), "foo$bar"); +} + +TEST_F(JsErrorHandlerTest, testSimpleHermesSourceStack) { + auto errorMap = parseErrorWithStackString( + " at global (unknown:1:9)\n" + " at foo$bar (/js/foo.js:10:1234)", + /* isFatal */ false); + + EXPECT_FALSE(errorMap.getBool(JSErrorHandlerKey::kIsFatal)); + + auto frames = errorMap.getMapBufferList(JSErrorHandlerKey::kAllStackFrames); + EXPECT_EQ(frames.size(), 2); + + EXPECT_EQ(frames[0].getString(JSErrorHandlerKey::kFrameFileName), "unknown"); + EXPECT_EQ(frames[0].getInt(JSErrorHandlerKey::kFrameLineNumber), 1); + // TODO: This is the wrong column number. Hermes's columns are 1-based (for + // non-bytecode locations) while ours are 0-based. + EXPECT_EQ(frames[0].getInt(JSErrorHandlerKey::kFrameColumnNumber), 9); + EXPECT_EQ(frames[0].getString(JSErrorHandlerKey::kFrameMethodName), "global"); + + EXPECT_EQ( + frames[1].getString(JSErrorHandlerKey::kFrameFileName), "/js/foo.js"); + EXPECT_EQ(frames[1].getInt(JSErrorHandlerKey::kFrameLineNumber), 10); + // TODO: This is the wrong column number. Hermes's columns are 1-based (for + // non-bytecode locations) while ours are 0-based. + EXPECT_EQ(frames[1].getInt(JSErrorHandlerKey::kFrameColumnNumber), 1234); + EXPECT_EQ( + frames[1].getString(JSErrorHandlerKey::kFrameMethodName), "foo$bar"); +} + +TEST_F(JsErrorHandlerTest, testHermesSourceStackWithSkippedFrames) { + auto errorMap = parseErrorWithStackString( + " at global (unknown:1:9)\n" + " ... skipping 50 frames\n" + " at foo$bar (/js/foo.js:10:1234)", + /* isFatal */ false); + + EXPECT_FALSE(errorMap.getBool(JSErrorHandlerKey::kIsFatal)); + + auto frames = errorMap.getMapBufferList(JSErrorHandlerKey::kAllStackFrames); + EXPECT_EQ(frames.size(), 2); + + EXPECT_EQ(frames[0].getString(JSErrorHandlerKey::kFrameFileName), "unknown"); + EXPECT_EQ(frames[0].getInt(JSErrorHandlerKey::kFrameLineNumber), 1); + // TODO: This is the wrong column number. Hermes's columns are 1-based (for + // non-bytecode locations) while ours are 0-based. + EXPECT_EQ(frames[0].getInt(JSErrorHandlerKey::kFrameColumnNumber), 9); + EXPECT_EQ(frames[0].getString(JSErrorHandlerKey::kFrameMethodName), "global"); + + EXPECT_EQ( + frames[1].getString(JSErrorHandlerKey::kFrameFileName), "/js/foo.js"); + EXPECT_EQ(frames[1].getInt(JSErrorHandlerKey::kFrameLineNumber), 10); + // TODO: This is the wrong column number. Hermes's columns are 1-based (for + // non-bytecode locations) while ours are 0-based. + EXPECT_EQ(frames[1].getInt(JSErrorHandlerKey::kFrameColumnNumber), 1234); + EXPECT_EQ( + frames[1].getString(JSErrorHandlerKey::kFrameMethodName), "foo$bar"); +} + +TEST_F(JsErrorHandlerTest, testHermesSourceStackWithNativeFrame) { + auto errorMap = parseErrorWithStackString( + " at global (unknown:1:9)\n" + " at apply (native)\n" + " at foo$bar (/js/foo.js:10:1234)", + /* isFatal */ false); + + EXPECT_FALSE(errorMap.getBool(JSErrorHandlerKey::kIsFatal)); + + auto frames = errorMap.getMapBufferList(JSErrorHandlerKey::kAllStackFrames); + EXPECT_EQ(frames.size(), 2); + + EXPECT_EQ(frames[0].getString(JSErrorHandlerKey::kFrameFileName), "unknown"); + EXPECT_EQ(frames[0].getInt(JSErrorHandlerKey::kFrameLineNumber), 1); + // TODO: This is the wrong column number. Hermes's columns are 1-based (for + // non-bytecode locations) while ours are 0-based. + EXPECT_EQ(frames[0].getInt(JSErrorHandlerKey::kFrameColumnNumber), 9); + EXPECT_EQ(frames[0].getString(JSErrorHandlerKey::kFrameMethodName), "global"); + + EXPECT_EQ( + frames[1].getString(JSErrorHandlerKey::kFrameFileName), "/js/foo.js"); + EXPECT_EQ(frames[1].getInt(JSErrorHandlerKey::kFrameLineNumber), 10); + // TODO: This is the wrong column number. Hermes's columns are 1-based (for + // non-bytecode locations) while ours are 0-based. + EXPECT_EQ(frames[1].getInt(JSErrorHandlerKey::kFrameColumnNumber), 1234); + EXPECT_EQ( + frames[1].getString(JSErrorHandlerKey::kFrameMethodName), "foo$bar"); +} + +TEST_F(JsErrorHandlerTest, testJsxSourceStackWithEval) { + auto errorMap = parseErrorWithStackString( + "eval code\n" + "eval@[native code]\n" + "foo@http://path/to/file.js:58:21\n" + "bar@http://path/to/file.js:109:91", + /* isFatal */ false); + + EXPECT_FALSE(errorMap.getBool(JSErrorHandlerKey::kIsFatal)); + + auto frames = errorMap.getMapBufferList(JSErrorHandlerKey::kAllStackFrames); + EXPECT_EQ(frames.size(), 2); + + EXPECT_EQ( + frames[0].getString(JSErrorHandlerKey::kFrameFileName), + "http://path/to/file.js"); + EXPECT_EQ(frames[0].getInt(JSErrorHandlerKey::kFrameLineNumber), 58); + // TODO: This is the wrong column number. JSC's columns are 1-based while ours + // are 0-based. + EXPECT_EQ(frames[0].getInt(JSErrorHandlerKey::kFrameColumnNumber), 21); + EXPECT_EQ(frames[0].getString(JSErrorHandlerKey::kFrameMethodName), "foo"); + + EXPECT_EQ( + frames[1].getString(JSErrorHandlerKey::kFrameFileName), + "http://path/to/file.js"); + EXPECT_EQ(frames[1].getInt(JSErrorHandlerKey::kFrameLineNumber), 109); + // TODO: This is the wrong column number. JSC's columns are 1-based while ours + // are 0-based. + EXPECT_EQ(frames[1].getInt(JSErrorHandlerKey::kFrameColumnNumber), 91); + EXPECT_EQ(frames[1].getString(JSErrorHandlerKey::kFrameMethodName), "bar"); +} +} // namespace facebook::react