mirror of
https://github.com/TextureGroup/Texture.git
synced 2026-04-07 19:17:39 +00:00
* [ASDataController] Avoid flushing editing queue before starting data controller pipeline Step 1 of the pipeline is on main thread and needs pendingMap which was updated by the end of step 1 of the run. And step 1 operates on data source index space. So it should be ok to start it ASAP -- without waiting for any running work on the background editing queue. One potential race condition after this change is that it's possible for main thread to query the data source (step 1) while the editing queue consumes the last change set (step 3). However, as long as the client's each nodeBlock and the node's layout code capture/reference an individual model object (as opposed to the whole data set) -- which is what clients are supposed to do anyways, then everything should be fine. The benefit of this diff is that the pipeline will be able to accept many more change sets within a short time window, for example when clients submit a burst of separate small updates. I tested this diff against our test suite several times and smoke tested it in our code base without any issues. * Wrap in an experiment * Enable experiment in tests * Minor change
165 lines
5.9 KiB
Plaintext
165 lines
5.9 KiB
Plaintext
//
|
|
// ASTableViewThrashTests.mm
|
|
// Texture
|
|
//
|
|
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
|
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
|
|
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
|
|
#import <XCTest/XCTest.h>
|
|
#import <AsyncDisplayKit/AsyncDisplayKit.h>
|
|
#import <AsyncDisplayKit/ASTableViewInternal.h>
|
|
#import <AsyncDisplayKit/ASTableView+Undeprecated.h>
|
|
#import <stdatomic.h>
|
|
|
|
#import "ASTestCase.h"
|
|
#import "ASThrashUtility.h"
|
|
|
|
@interface ASTableViewThrashTests: ASTestCase
|
|
@end
|
|
|
|
@implementation ASTableViewThrashTests
|
|
{
|
|
// The current update, which will be logged in case of a failure.
|
|
ASThrashUpdate *_update;
|
|
BOOL _failed;
|
|
}
|
|
|
|
#pragma mark Overrides
|
|
|
|
- (void)setUp
|
|
{
|
|
[super setUp];
|
|
ASConfiguration *config = [ASConfiguration new];
|
|
config.experimentalFeatures = ASExperimentalOptimizeDataControllerPipeline;
|
|
[ASConfigurationManager test_resetWithConfiguration:config];
|
|
}
|
|
|
|
- (void)tearDown
|
|
{
|
|
[super tearDown];
|
|
if (_failed && _update != nil) {
|
|
NSLog(@"Failed update %@: %@", _update, _update.logFriendlyBase64Representation);
|
|
}
|
|
_failed = NO;
|
|
_update = nil;
|
|
}
|
|
|
|
// NOTE: Despite the documentation, this is not always called if an exception is caught.
|
|
- (void)recordFailureWithDescription:(NSString *)description inFile:(NSString *)filePath atLine:(NSUInteger)lineNumber expected:(BOOL)expected
|
|
{
|
|
_failed = YES;
|
|
[super recordFailureWithDescription:description inFile:filePath atLine:lineNumber expected:expected];
|
|
}
|
|
|
|
#pragma mark Test Methods
|
|
|
|
// Disabled temporarily due to issue where cell nodes are not marked invisible before deallocation.
|
|
- (void)testInitialDataRead
|
|
{
|
|
ASThrashDataSource *ds = [[ASThrashDataSource alloc] initTableViewDataSourceWithData:[ASThrashTestSection sectionsWithCount:kInitialSectionCount]];
|
|
[self verifyDataSource:ds];
|
|
}
|
|
|
|
/// Replays the Base64 representation of an ASThrashUpdate from "ASThrashTestRecordedCase" file
|
|
- (void)testRecordedThrashCase
|
|
{
|
|
NSURL *caseURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"ASThrashTestRecordedCase" withExtension:nil subdirectory:@"TestResources"];
|
|
NSString *base64 = [NSString stringWithContentsOfURL:caseURL encoding:NSUTF8StringEncoding error:NULL];
|
|
|
|
_update = [ASThrashUpdate thrashUpdateWithBase64String:base64];
|
|
if (_update == nil) {
|
|
return;
|
|
}
|
|
|
|
ASThrashDataSource *ds = [[ASThrashDataSource alloc] initTableViewDataSourceWithData:_update.oldData];
|
|
ds.tableView.test_enableSuperUpdateCallLogging = YES;
|
|
[self applyUpdate:_update toDataSource:ds];
|
|
[self verifyDataSource:ds];
|
|
}
|
|
|
|
// Disabled temporarily due to issue where cell nodes are not marked invisible before deallocation.
|
|
- (void)testThrashingWildly
|
|
{
|
|
for (NSInteger i = 0; i < kThrashingIterationCount; i++) {
|
|
[self setUp];
|
|
@autoreleasepool {
|
|
NSArray *sections = [ASThrashTestSection sectionsWithCount:kInitialSectionCount];
|
|
_update = [[ASThrashUpdate alloc] initWithData:sections];
|
|
ASThrashDataSource *ds = [[ASThrashDataSource alloc] initTableViewDataSourceWithData:sections];
|
|
|
|
[self applyUpdate:_update toDataSource:ds];
|
|
[self verifyDataSource:ds];
|
|
[self expectationForPredicate:[ds predicateForDeallocatedHierarchy] evaluatedWithObject:(id)kCFNull handler:nil];
|
|
}
|
|
[self waitForExpectationsWithTimeout:3 handler:nil];
|
|
|
|
[self tearDown];
|
|
}
|
|
}
|
|
|
|
#pragma mark Helpers
|
|
|
|
- (void)applyUpdate:(ASThrashUpdate *)update toDataSource:(ASThrashDataSource *)dataSource
|
|
{
|
|
TableView *tableView = dataSource.tableView;
|
|
|
|
[tableView beginUpdates];
|
|
dataSource.data = update.data;
|
|
|
|
[tableView insertSections:update.insertedSectionIndexes withRowAnimation:UITableViewRowAnimationNone];
|
|
|
|
[tableView deleteSections:update.deletedSectionIndexes withRowAnimation:UITableViewRowAnimationNone];
|
|
|
|
[tableView reloadSections:update.replacedSectionIndexes withRowAnimation:UITableViewRowAnimationNone];
|
|
|
|
[update.insertedItemIndexes enumerateObjectsUsingBlock:^(NSMutableIndexSet * _Nonnull indexes, NSUInteger idx, BOOL * _Nonnull stop) {
|
|
NSArray *indexPaths = [indexes indexPathsInSection:idx];
|
|
[tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
|
|
}];
|
|
|
|
[update.deletedItemIndexes enumerateObjectsUsingBlock:^(NSMutableIndexSet * _Nonnull indexes, NSUInteger sec, BOOL * _Nonnull stop) {
|
|
NSArray *indexPaths = [indexes indexPathsInSection:sec];
|
|
[tableView deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
|
|
}];
|
|
|
|
[update.replacedItemIndexes enumerateObjectsUsingBlock:^(NSMutableIndexSet * _Nonnull indexes, NSUInteger sec, BOOL * _Nonnull stop) {
|
|
NSArray *indexPaths = [indexes indexPathsInSection:sec];
|
|
[tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
|
|
}];
|
|
@try {
|
|
[tableView endUpdatesAnimated:NO completion:nil];
|
|
#if !USE_UIKIT_REFERENCE
|
|
[tableView waitUntilAllUpdatesAreCommitted];
|
|
#endif
|
|
} @catch (NSException *exception) {
|
|
_failed = YES;
|
|
@throw exception;
|
|
}
|
|
}
|
|
|
|
- (void)verifyDataSource:(ASThrashDataSource *)ds
|
|
{
|
|
TableView *tableView = ds.tableView;
|
|
NSArray <ASThrashTestSection *> *data = [ds data];
|
|
XCTAssertEqual(data.count, tableView.numberOfSections);
|
|
for (NSInteger i = 0; i < tableView.numberOfSections; i++) {
|
|
XCTAssertEqual([tableView numberOfRowsInSection:i], data[i].items.count);
|
|
XCTAssertEqual([tableView rectForHeaderInSection:i].size.height, data[i].headerHeight);
|
|
|
|
for (NSInteger j = 0; j < [tableView numberOfRowsInSection:i]; j++) {
|
|
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:j inSection:i];
|
|
ASThrashTestItem *item = data[i].items[j];
|
|
#if USE_UIKIT_REFERENCE
|
|
XCTAssertEqual([tableView rectForRowAtIndexPath:indexPath].size.height, item.rowHeight);
|
|
#else
|
|
ASThrashTestNode *node = (ASThrashTestNode *)[tableView nodeForRowAtIndexPath:indexPath];
|
|
XCTAssertEqualObjects(node.item, item, @"Wrong node at index path %@", indexPath);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
@end
|