Files
react-native/RNTester/RNTesterIntegrationTests/RCTLoggingTests.m
T
Moti Zilberman 2dadb9e2b0 Move React error message formatting into ExceptionsManager
Summary:
# Context

In https://github.com/facebook/react/pull/16141 we imported `ReactFiberErrorDialog` unchanged from React. That implementation was not idempotent: if passed the same error instance multiple times, it would amend its `message` property every time, eventually leading to bloat and low-signal logs.

The message bloat problem is most evident when rendering multiple `lazy()` components that expose the same Error reference to React (e.g. due to some cache that vends the same rejected Promise multiple times).

More broadly, there's a need for structured, machine-readable logging to replace stringly-typed interfaces in both the production and development use cases.

# This diff

* We leave the user-supplied `message` field intact and instead do all the formatting inside `ExceptionsManager`. To avoid needless complexity, this **doesn't** always have the exact same output as the old code (but it does come close). See tests for the specifics.
* The only mutation we do on React-captured error instances is setting the `componentStack` expando property. This replaces any previously-captured component stack rather than adding to it, and so doesn't create bloat.
* We also report the exception fields `componentStack`, unformatted `message` (as `originalMessage`) and `name` directly to `NativeExceptionsManager` for future use.

Reviewed By: cpojer

Differential Revision: D16331228

fbshipit-source-id: 7b0539c2c83c7dd4e56db8508afcf367931ac71d
2019-07-31 02:34:15 -07:00

122 lines
4.3 KiB
Objective-C

/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
#import <XCTest/XCTest.h>
#import <React/RCTAssert.h>
#import <React/RCTBridge.h>
#import <React/RCTLog.h>
@interface RCTLoggingTests : XCTestCase
@end
@implementation RCTLoggingTests
{
RCTBridge *_bridge;
dispatch_semaphore_t _logSem;
RCTLogLevel _lastLogLevel;
RCTLogSource _lastLogSource;
NSString *_lastLogMessage;
}
- (void)setUp
{
NSURL *scriptURL;
if (getenv("CI_USE_PACKAGER")) {
NSString *bundlePrefix = [[[NSBundle mainBundle] infoDictionary] valueForKey:@"RN_BUNDLE_PREFIX"];
NSString *app = @"IntegrationTests/IntegrationTestsApp";
scriptURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://localhost:8081/%@%@.bundle?platform=ios&dev=true", bundlePrefix, app]];
} else {
scriptURL = [[NSBundle bundleForClass:[RCTBridge class]] URLForResource:@"main" withExtension:@"jsbundle"];
}
RCTAssert(scriptURL != nil, @"No scriptURL set");
_bridge = [[RCTBridge alloc] initWithBundleURL:scriptURL moduleProvider:NULL launchOptions:nil];
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:60];
while (date.timeIntervalSinceNow > 0 && _bridge.loading) {
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
}
XCTAssertFalse(_bridge.loading);
_logSem = dispatch_semaphore_create(0);
}
- (void)tearDown
{
[_bridge invalidate];
_bridge = nil;
RCTSetLogFunction(RCTDefaultLogFunction);
}
- (void)testLogging
{
// First console log call will fire after 2.0 sec, to allow for any initial log messages
// that might come in (seeing this in tvOS)
[_bridge enqueueJSCall:@"LoggingTestModule.logToConsoleAfterWait" args:@[@"Invoking console.log",@2000]];
// Spin native layer for 1.9 sec
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.9]];
// Now set the log function to signal the semaphore
RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, __unused NSString *fileName, __unused NSNumber *lineNumber, NSString *message) {
if (source == RCTLogSourceJavaScript) {
self->_lastLogLevel = level;
self->_lastLogSource = source;
self->_lastLogMessage = message;
dispatch_semaphore_signal(self->_logSem);
}
});
// Wait for console log to signal the semaphore
dispatch_semaphore_wait(_logSem, DISPATCH_TIME_FOREVER);
XCTAssertEqual(_lastLogLevel, RCTLogLevelInfo);
XCTAssertEqual(_lastLogSource, RCTLogSourceJavaScript);
XCTAssertEqualObjects(_lastLogMessage, @"Invoking console.log");
[_bridge enqueueJSCall:@"LoggingTestModule.warning" args:@[@"Generating warning"]];
dispatch_semaphore_wait(_logSem, DISPATCH_TIME_FOREVER);
XCTAssertEqual(_lastLogLevel, RCTLogLevelWarning);
XCTAssertEqual(_lastLogSource, RCTLogSourceJavaScript);
XCTAssertEqualObjects(_lastLogMessage, @"Warning: Generating warning");
[_bridge enqueueJSCall:@"LoggingTestModule.invariant" args:@[@"Invariant failed"]];
dispatch_semaphore_wait(_logSem, DISPATCH_TIME_FOREVER);
XCTAssertEqual(_lastLogLevel, RCTLogLevelError);
XCTAssertEqual(_lastLogSource, RCTLogSourceJavaScript);
XCTAssertEqualObjects(_lastLogMessage, @"Invariant Violation: Invariant failed");
[_bridge enqueueJSCall:@"LoggingTestModule.logErrorToConsole" args:@[@"Invoking console.error"]];
dispatch_semaphore_wait(_logSem, DISPATCH_TIME_FOREVER);
// For local bundles, we'll first get a warning about symbolication
if ([_bridge.bundleURL isFileURL]) {
dispatch_semaphore_wait(_logSem, DISPATCH_TIME_FOREVER);
}
XCTAssertEqual(_lastLogLevel, RCTLogLevelError);
XCTAssertEqual(_lastLogSource, RCTLogSourceJavaScript);
XCTAssertEqualObjects(_lastLogMessage, @"Invoking console.error");
[_bridge enqueueJSCall:@"LoggingTestModule.throwError" args:@[@"Throwing an error"]];
dispatch_semaphore_wait(_logSem, DISPATCH_TIME_FOREVER);
// For local bundles, we'll first get a warning about symbolication
if ([_bridge.bundleURL isFileURL]) {
dispatch_semaphore_wait(_logSem, DISPATCH_TIME_FOREVER);
}
XCTAssertEqual(_lastLogLevel, RCTLogLevelError);
XCTAssertEqual(_lastLogSource, RCTLogSourceJavaScript);
XCTAssertEqualObjects(_lastLogMessage, @"Error: Throwing an error");
}
@end