Fixup some tests

// FREEBIE
This commit is contained in:
Michael Kirk 2018-03-26 12:15:32 -04:00
parent 448e59d7fc
commit 00e5e1b0d6
11 changed files with 31 additions and 280 deletions

14
Podfile
View File

@ -54,6 +54,7 @@ end
post_install do |installer|
# Disable some asserts when building for tests
set_building_for_tests_config(installer, 'SignalServiceKit')
disable_optimizations_for_tests(installer)
enable_extension_support_for_purelayout(installer)
end
@ -79,6 +80,19 @@ def set_building_for_tests_config(installer, target_name)
build_config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] = "#{existing_definitions} POD_CONFIGURATION_TEST=1 COCOAPODS=1 SSK_BUILDING_FOR_TESTS=1"
end
def disable_optimizations_for_tests(installer)
installer.pods_project.targets.each do | target|
build_config_name = "Test"
build_config = target.build_configurations.detect { |config| config.to_s == build_config_name }
if build_config == nil
throw "failed to find config: #{build_config_name} for target: #{target_name}"
end
# Allow accurate step-thru debugging while in tests
build_config.build_settings['GCC_OPTIMIZATION_LEVEL'] = '0'
end
end
# PureLayout by default makes use of UIApplication, and must be configured to be built for an extension.
def enable_extension_support_for_purelayout(installer)
installer.pods_project.targets.each do |target|

View File

@ -220,6 +220,6 @@ SPEC CHECKSUMS:
YapDatabase: 299a32de9d350d37a9ac5b0532609d87d5d2a5de
YYImage: 1e1b62a9997399593e4b9c4ecfbbabbf1d3f3b54
PODFILE CHECKSUM: 66db99df53e7593362ebb004bea5d2215ca00e8e
PODFILE CHECKSUM: c9f29fde957d145230abe7f07fc6e4b1ac4a21c3
COCOAPODS: 1.4.0

2
Pods

@ -1 +1 @@
Subproject commit 9ed49e411d7838bc31c11426a8cc0898dc302bf0
Subproject commit 804bb4d53938a96e5f689723eb692ed7f34b88a5

View File

@ -384,7 +384,6 @@
76C87F19181EFCE600C4ACAB /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76C87F18181EFCE600C4ACAB /* MediaPlayer.framework */; };
76EB054018170B33006006FC /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB03C318170B33006006FC /* AppDelegate.m */; };
76EB063A18170B33006006FC /* FunctionalUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04EB18170B33006006FC /* FunctionalUtil.m */; };
76EB063C18170B33006006FC /* NumberUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB04ED18170B33006006FC /* NumberUtil.m */; };
954AEE6A1DF33E01002E5410 /* ContactsPickerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 954AEE681DF33D32002E5410 /* ContactsPickerTest.swift */; };
A10FDF79184FB4BB007FF963 /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76C87F18181EFCE600C4ACAB /* MediaPlayer.framework */; };
A11CD70D17FA230600A2D1B1 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A11CD70C17FA230600A2D1B1 /* QuartzCore.framework */; };
@ -1007,8 +1006,6 @@
76EB03C318170B33006006FC /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
76EB04EA18170B33006006FC /* FunctionalUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FunctionalUtil.h; sourceTree = "<group>"; };
76EB04EB18170B33006006FC /* FunctionalUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FunctionalUtil.m; sourceTree = "<group>"; };
76EB04EC18170B33006006FC /* NumberUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NumberUtil.h; sourceTree = "<group>"; };
76EB04ED18170B33006006FC /* NumberUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NumberUtil.m; sourceTree = "<group>"; };
76EB052E18170B33006006FC /* ContactTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContactTableViewCell.h; sourceTree = "<group>"; };
76EB052F18170B33006006FC /* ContactTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ContactTableViewCell.m; sourceTree = "<group>"; };
8981C8F64D94D3C52EB67A2C /* Pods-SignalTests.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalTests.test.xcconfig"; path = "Pods/Target Support Files/Pods-SignalTests/Pods-SignalTests.test.xcconfig"; sourceTree = "<group>"; };
@ -1981,8 +1978,6 @@
76EB04EB18170B33006006FC /* FunctionalUtil.m */,
34B0796C1FCF46B000E248C2 /* MainAppContext.h */,
34B0796B1FCF46B000E248C2 /* MainAppContext.m */,
76EB04EC18170B33006006FC /* NumberUtil.h */,
76EB04ED18170B33006006FC /* NumberUtil.m */,
34D99C911F2937CC00D284D6 /* OWSAnalytics.swift */,
34A9105E1FFEB113000C4745 /* OWSBackup.h */,
34A9105F1FFEB114000C4745 /* OWSBackup.m */,
@ -2298,7 +2293,6 @@
346129A91FD1F0E000532771 /* OWSFormat.h in Headers */,
34480B551FD0A7A400BC14EF /* DebugLogger.h in Headers */,
344F248420069E9C00CFB4F4 /* CountryCodeViewController.h in Headers */,
346129CA1FD2072E00532771 /* UIImage+OWS.h in Headers */,
4503F1C4204711D300CEE724 /* OWS107LegacySounds.h in Headers */,
346129711FD1D74C00532771 /* SignalKeyingStorage.h in Headers */,
34612A011FD5F31400532771 /* OWS104CreateRecipientIdentities.h in Headers */,
@ -3167,7 +3161,6 @@
B6B9ECFC198B31BA00C620D3 /* PushManager.m in Sources */,
45DF5DF21DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift in Sources */,
458DE9D91DEE7B360071BB03 /* OWSWebRTCDataProtos.pb.m in Sources */,
76EB063C18170B33006006FC /* NumberUtil.m in Sources */,
451166C01FD86B98000739BA /* AccountManager.swift in Sources */,
34D2CCD220618B3000CB1A14 /* OWSBackupLazyRestoreJob.swift in Sources */,
3430FE181F7751D4000EC51B /* GiphyAPI.swift in Sources */,
@ -3700,6 +3693,7 @@
);
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "SignalShareExtension/SignalShareExtension-Prefix.pch";
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;

View File

@ -37,10 +37,9 @@
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
buildConfiguration = "Test"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "NO">
<Testables>
<TestableReference
@ -77,7 +76,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"

View File

@ -426,7 +426,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
if (self.messageCellType == OWSMessageCellType_Unknown) {
// Messages of unknown type (including messages with missing attachments)
// are rendered like empty text messages, but without any interactivity.
DDLogWarn(@"%@ Treating unknown message as empty text message: %@", self.logTag, message.description);
DDLogWarn(@"%@ Treating unknown message as empty text message: %@ %llu", self.logTag, message.class, message.timestamp);
self.messageCellType = OWSMessageCellType_TextMessage;
self.displayableBodyText = [[DisplayableText alloc] initWithFullText:@"" displayText:@"" isTextTruncated:NO];
}

View File

@ -1,41 +0,0 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
@interface NumberUtil : NSObject
+ (int16_t)congruentDifferenceMod2ToThe16From:(uint16_t)s1 to:(uint16_t)s2;
+ (int8_t)signOfInt32:(int32_t)value;
+ (int8_t)signOfDouble:(double)value;
+ (NSUInteger)largestIntegerThatIsAtMost:(NSUInteger)value andIsAMultipleOf:(NSUInteger)multiple;
+ (NSUInteger)smallestIntegerThatIsAtLeast:(NSUInteger)value andIsAMultipleOf:(NSUInteger)multiple;
+ (double)clamp:(double)value toMin:(double)min andMax:(double)max;
+ (NSUInteger)from:(NSUInteger)value subtractWithSaturationAtZero:(NSUInteger)minusDelta;
+ (uint8_t)uint8FromLowUInt4:(uint8_t)low4UInt4 andHighUInt4:(uint8_t)highUInt4;
+ (uint8_t)lowUInt4OfUint8:(uint8_t)value;
+ (uint8_t)highUInt4OfUint8:(uint8_t)value;
+ (NSUInteger)assertConvertIntToNSUInteger:(int)value;
+ (NSInteger)assertConvertUnsignedIntToNSInteger:(unsigned int)value;
+ (int)assertConvertNSUIntegerToInt:(NSUInteger)value;
@end
@interface NSNumber (NumberUtil)
- (bool)hasUnsignedIntegerValue;
- (bool)hasUnsignedLongLongValue;
- (bool)hasLongLongValue;
@end

View File

@ -1,115 +0,0 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
//
#import "NumberUtil.h"
@implementation NumberUtil
+ (int16_t)congruentDifferenceMod2ToThe16From:(uint16_t)s1 to:(uint16_t)s2 {
int32_t d = (int32_t)(s2 - s1);
if (d > INT16_MAX)
d -= 1 << 16;
return (int16_t)d;
}
+ (int8_t)signOfInt32:(int32_t)value {
if (value < 0)
return -1;
if (value > 0)
return +1;
return 0;
}
+ (int8_t)signOfDouble:(double)value {
if (value < 0)
return -1;
if (value > 0)
return +1;
return 0;
}
+ (NSUInteger)largestIntegerThatIsAtMost:(NSUInteger)value andIsAMultipleOf:(NSUInteger)multiple {
OWSAssert(multiple != 0);
NSUInteger d = value / multiple;
d *= multiple;
if (d > value)
d -= multiple;
return d;
}
+ (NSUInteger)smallestIntegerThatIsAtLeast:(NSUInteger)value andIsAMultipleOf:(NSUInteger)multiple {
OWSAssert(multiple != 0);
NSUInteger d = value / multiple;
d *= multiple;
if (d < value)
d += multiple;
return d;
}
+ (double)clamp:(double)value toMin:(double)min andMax:(double)max {
OWSAssert(min <= max);
if (isnan(value)) {
return max;
}
if (value < min) {
return min;
}
if (value > max) {
return max;
}
return value;
}
+ (NSUInteger)from:(NSUInteger)value subtractWithSaturationAtZero:(NSUInteger)minusDelta {
return value - MIN(value, minusDelta);
}
+ (uint8_t)uint8FromLowUInt4:(uint8_t)low4UInt4 andHighUInt4:(uint8_t)highUInt4 {
OWSAssert(low4UInt4 < 0x10);
OWSAssert(highUInt4 < 0x10);
return low4UInt4 | (uint8_t)(highUInt4 << 4);
}
+ (uint8_t)lowUInt4OfUint8:(uint8_t)value {
return value & 0xF;
}
+ (uint8_t)highUInt4OfUint8:(uint8_t)value {
return value >> 4;
}
+ (NSUInteger)assertConvertIntToNSUInteger:(int)value {
OWSAssert(0 <= value);
return (NSUInteger)value;
}
+ (NSInteger)assertConvertUnsignedIntToNSInteger:(unsigned int)value {
// uint is a subset of NSInteger(long) bounds check is always true
return (NSInteger)value;
}
+ (int)assertConvertNSUIntegerToInt:(NSUInteger)value {
OWSAssert(value <= INT32_MAX);
return (int)value;
}
@end
@implementation NSNumber (NumberUtil)
- (bool)hasUnsignedIntegerValue {
return [self isEqual:@([self unsignedIntegerValue])];
}
- (bool)hasUnsignedLongLongValue {
return [self isEqual:@([self unsignedLongLongValue])];
}
- (bool)hasLongLongValue {
return [self isEqual:@([self longLongValue])];
}
@end

View File

@ -10,16 +10,6 @@
#import <XCTest/XCTest.h>
#import <YapDatabase/YapDatabaseConnection.h>
@interface ConversationViewItem (Testing)
- (SEL)copyActionSelector;
- (SEL)saveActionSelector;
- (SEL)shareActionSelector;
- (SEL)deleteActionSelector;
- (SEL)metadataActionSelector;
@end
@interface ConversationViewItemTest : XCTestCase
@end
@ -107,66 +97,6 @@
return [self viewItemWithAttachmentMimetype:@"audio/mp3" filename:@"test-mp3.mp3"];
}
- (void)testCanPerformEditingActionWithNonMediaMessage
{
ConversationViewItem *viewItem = self.textViewItem;
XCTAssertTrue([viewItem canPerformAction:viewItem.copyActionSelector]);
XCTAssertFalse([viewItem canPerformAction:viewItem.saveActionSelector]);
XCTAssertTrue([viewItem canPerformAction:viewItem.shareActionSelector]);
XCTAssertTrue([viewItem canPerformAction:viewItem.deleteActionSelector]);
XCTAssertTrue([viewItem canPerformAction:viewItem.metadataActionSelector]);
XCTAssertFalse([viewItem canPerformAction:@selector(unknownAction:)]);
}
- (void)testCanPerformEditingActionWithPhotoMessage
{
ConversationViewItem *viewItem = self.stillImageViewItem;
XCTAssertTrue([viewItem canPerformAction:viewItem.copyActionSelector]);
XCTAssertTrue([viewItem canPerformAction:viewItem.saveActionSelector]);
XCTAssertTrue([viewItem canPerformAction:viewItem.shareActionSelector]);
XCTAssertTrue([viewItem canPerformAction:viewItem.deleteActionSelector]);
XCTAssertTrue([viewItem canPerformAction:viewItem.metadataActionSelector]);
XCTAssertFalse([viewItem canPerformAction:@selector(unknownAction:)]);
}
- (void)testCanPerformEditingActionWithAnimatedMessage
{
ConversationViewItem *viewItem = self.animatedImageViewItem;
XCTAssertTrue([viewItem canPerformAction:viewItem.copyActionSelector]);
XCTAssertTrue([viewItem canPerformAction:viewItem.saveActionSelector]);
XCTAssertTrue([viewItem canPerformAction:viewItem.shareActionSelector]);
XCTAssertTrue([viewItem canPerformAction:viewItem.deleteActionSelector]);
XCTAssertTrue([viewItem canPerformAction:viewItem.metadataActionSelector]);
XCTAssertFalse([viewItem canPerformAction:@selector(unknownAction:)]);
}
- (void)testCanPerformEditingActionWithVideoMessage
{
ConversationViewItem *viewItem = self.videoViewItem;
XCTAssertTrue([viewItem canPerformAction:viewItem.copyActionSelector]);
XCTAssertTrue([viewItem canPerformAction:viewItem.saveActionSelector]);
XCTAssertTrue([viewItem canPerformAction:viewItem.shareActionSelector]);
XCTAssertTrue([viewItem canPerformAction:viewItem.deleteActionSelector]);
XCTAssertTrue([viewItem canPerformAction:viewItem.metadataActionSelector]);
XCTAssertFalse([viewItem canPerformAction:@selector(unknownAction:)]);
}
- (void)testCanPerformEditingActionWithAudioMessage
{
ConversationViewItem *viewItem = self.audioViewItem;
XCTAssertTrue([viewItem canPerformAction:viewItem.copyActionSelector]);
XCTAssertFalse([viewItem canPerformAction:viewItem.saveActionSelector]);
XCTAssertTrue([viewItem canPerformAction:viewItem.shareActionSelector]);
XCTAssertTrue([viewItem canPerformAction:viewItem.deleteActionSelector]);
XCTAssertTrue([viewItem canPerformAction:viewItem.metadataActionSelector]);
XCTAssertFalse([viewItem canPerformAction:@selector(unknownAction:)]);
}
// Test Delete
- (void)testPerformDeleteEditingActionWithNonMediaMessage

View File

@ -64,7 +64,7 @@ class DisplayableTextTest: XCTestCase {
XCTAssertEqual("Příliš žluťoučký kůň úpěl ďábelské ódy.".glyphCount, 39)
// Excessive diacritics
XCTAssertEqual("H҉̸̧͘͠A͢͞V̛̛I̴̸N͏̕͏G҉̵͜͏͢ ̧̧́T̶̛͘͡R̸̵̨̢̀O̷̡U͡҉B̶̛͢͞L̸̸͘͢͟É̸ ̸̛͘͏R͟È͠͞A̸͝Ḑ̕͘͜I̵͘҉͜͞N̷̡̢͠G̴͘͠ ͟͞T͏̢́͡È̀X̕҉̢̀T̢͠?̕͏̢͘͢".glyphCount, 115)
XCTAssertEqual("H҉̸̧͘͠A͢͞V̛̛I̴̸N͏̕͏G҉̵͜͏͢ ̧̧́T̶̛͘͡R̸̵̨̢̀O̷̡U͡҉B̶̛͢͞L̸̸͘͢͟É̸ ̸̛͘͏R͟È͠͞A̸͝Ḑ̕͘͜I̵͘҉͜͞N̷̡̢͠G̴͘͠ ͟͞T͏̢́͡È̀X̕҉̢̀T̢͠?̕͏̢͘͢".glyphCount, 109)
XCTAssertEqual("L̷̳͔̲͝Ģ̵̮̯̤̩̙͍̬̟͉̹̘̹͍͈̮̦̰̣͟͝O̶̴̮̻̮̗͘͡!̴̷̟͓͓".glyphCount, 43)
}

View File

@ -3,17 +3,17 @@
//
#import "UtilTest.h"
#import "NumberUtil.h"
#import "TestUtil.h"
#import <SignalMessaging/NSString+OWS.h>
#import <SignalServiceKit/NSDate+OWS.h>
#import <SignalServiceKit/NSObject+OWS.h>
#import <SignalServiceKit/NSString+SSK.h>
@interface NSString (OWS_Test)
- (NSString *)removeAllCharactersIn:(NSCharacterSet *)characterSet;
- (NSString *)filterUnsafeCharacters;
- (NSString *)filterUnsafeFilenameCharacters;
@end
@ -21,35 +21,6 @@
@implementation UtilTest
-(void) testFloorMultiple {
test([NumberUtil largestIntegerThatIsAtMost:0 andIsAMultipleOf:20] == 0);
test([NumberUtil largestIntegerThatIsAtMost:1 andIsAMultipleOf:20] == 0);
test([NumberUtil largestIntegerThatIsAtMost:5 andIsAMultipleOf:20] == 0);
test([NumberUtil largestIntegerThatIsAtMost:15 andIsAMultipleOf:20] == 0);
test([NumberUtil largestIntegerThatIsAtMost:19 andIsAMultipleOf:20] == 0);
test([NumberUtil largestIntegerThatIsAtMost:20 andIsAMultipleOf:20] == 20);
test([NumberUtil largestIntegerThatIsAtMost:21 andIsAMultipleOf:20] == 20);
}
-(void) testCeilingMultiple {
test([NumberUtil smallestIntegerThatIsAtLeast:0 andIsAMultipleOf:20] == 0);
test([NumberUtil smallestIntegerThatIsAtLeast:1 andIsAMultipleOf:20] == 20);
test([NumberUtil smallestIntegerThatIsAtLeast:5 andIsAMultipleOf:20] == 20);
test([NumberUtil smallestIntegerThatIsAtLeast:15 andIsAMultipleOf:20] == 20);
test([NumberUtil smallestIntegerThatIsAtLeast:19 andIsAMultipleOf:20] == 20);
test([NumberUtil smallestIntegerThatIsAtLeast:20 andIsAMultipleOf:20] == 20);
test([NumberUtil smallestIntegerThatIsAtLeast:21 andIsAMultipleOf:20] == 40);
}
-(void) testCongruentDifferenceMod2ToThe16 {
test([NumberUtil congruentDifferenceMod2ToThe16From:1 to:0xFFFF] == -2);
test([NumberUtil congruentDifferenceMod2ToThe16From:1 to:10] == 9);
test([NumberUtil congruentDifferenceMod2ToThe16From:0xFFFF to:1] == 2);
test([NumberUtil congruentDifferenceMod2ToThe16From:0 to:0x8000] == -0x8000);
test([NumberUtil congruentDifferenceMod2ToThe16From:0x8000 to:0] == -0x8000);
test([NumberUtil congruentDifferenceMod2ToThe16From:0 to:0] == 0);
}
-(void) testRemoveAllCharactersIn {
testThrows([@"" removeAllCharactersIn:nil]);
@ -73,16 +44,16 @@
test([@"(555) 235-7111".digitsOnly isEqual:@"5552357111"]);
}
- (void)testFilterUnsafeCharacters
- (void)testfilterUnsafeFilenameCharacters
{
XCTAssertEqualObjects(@"1".filterUnsafeCharacters, @"1");
XCTAssertEqualObjects(@"alice\u202Dbob".filterUnsafeCharacters, @"alice\uFFFDbob");
XCTAssertEqualObjects(@"\u202Dalicebob".filterUnsafeCharacters, @"\uFFFDalicebob");
XCTAssertEqualObjects(@"alicebob\u202D".filterUnsafeCharacters, @"alicebob\uFFFD");
XCTAssertEqualObjects(@"alice\u202Ebob".filterUnsafeCharacters, @"alice\uFFFDbob");
XCTAssertEqualObjects(@"\u202Ealicebob".filterUnsafeCharacters, @"\uFFFDalicebob");
XCTAssertEqualObjects(@"alicebob\u202E".filterUnsafeCharacters, @"alicebob\uFFFD");
XCTAssertEqualObjects(@"alice\u202Dbobalice\u202Ebob".filterUnsafeCharacters, @"alice\uFFFDbobalice\uFFFDbob");
XCTAssertEqualObjects(@"1".filterUnsafeFilenameCharacters, @"1");
XCTAssertEqualObjects(@"alice\u202Dbob".filterUnsafeFilenameCharacters, @"alice\uFFFDbob");
XCTAssertEqualObjects(@"\u202Dalicebob".filterUnsafeFilenameCharacters, @"\uFFFDalicebob");
XCTAssertEqualObjects(@"alicebob\u202D".filterUnsafeFilenameCharacters, @"alicebob\uFFFD");
XCTAssertEqualObjects(@"alice\u202Ebob".filterUnsafeFilenameCharacters, @"alice\uFFFDbob");
XCTAssertEqualObjects(@"\u202Ealicebob".filterUnsafeFilenameCharacters, @"\uFFFDalicebob");
XCTAssertEqualObjects(@"alicebob\u202E".filterUnsafeFilenameCharacters, @"alicebob\uFFFD");
XCTAssertEqualObjects(@"alice\u202Dbobalice\u202Ebob".filterUnsafeFilenameCharacters, @"alice\uFFFDbobalice\uFFFDbob");
}
- (void)testDateComparison