Skip to content

Commit fd9b882

Browse files
authored
Create testing infrastructure that simplifies catching exceptions in dispatch_queues (#2226)
* Apply updated clang-format * Add a custom assert and use it instead of NSAssert * Define a shared unit test test class and change unit tests to use it * Add the GDLAssertHelper to be used by GDLAsserts * Change copyright year, style, and only define the assert macro body if !defined(NS_BLOCK_ASSERTIONS) * Remove rvm specification from travis (#2227)
1 parent 4ffae2c commit fd9b882

19 files changed

+246
-33
lines changed

.travis.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ cache:
55
- bundler
66
- cocoapods
77

8-
rvm: 2.3.1
9-
108
jobs:
119
include:
1210
- stage: checks
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2019 Google
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "GDLAssert.h"
18+
19+
GDLAssertionBlock GDLAssertionBlockToRunInsteadOfNSAssert(void) {
20+
// This class is only compiled in by unit tests, and this should fail quickly in optimized builds.
21+
Class GDLAssertClass = NSClassFromString(@"GDLAssertHelper");
22+
if (__builtin_expect(!!GDLAssertClass, 0)) {
23+
SEL assertionBlockSEL = NSSelectorFromString(@"assertionBlock");
24+
if (assertionBlockSEL) {
25+
IMP assertionBlockIMP = [GDLAssertClass methodForSelector:assertionBlockSEL];
26+
if (assertionBlockIMP) {
27+
GDLAssertionBlock assertionBlock =
28+
((GDLAssertionBlock(*)(id, SEL))assertionBlockIMP)(GDLAssertClass, assertionBlockSEL);
29+
if (assertionBlock) {
30+
return assertionBlock;
31+
}
32+
}
33+
}
34+
}
35+
return NULL;
36+
}

GoogleDataLogger/GoogleDataLogger/Classes/GDLLogEvent.m

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@
1616

1717
#import <GoogleDataLogger/GDLLogEvent.h>
1818

19+
#import "GDLAssert.h"
1920
#import "GDLLogEvent_Private.h"
2021

2122
@implementation GDLLogEvent
2223

2324
- (instancetype)initWithLogMapID:(NSString *)logMapID logTarget:(NSInteger)logTarget {
24-
NSAssert(logMapID.length > 0, @"Please give a valid log map ID");
25-
NSAssert(logTarget > 0, @"A log target cannot be negative or 0");
25+
GDLAssert(logMapID.length > 0, @"Please give a valid log map ID");
26+
GDLAssert(logTarget > 0, @"A log target cannot be negative or 0");
2627
self = [super init];
2728
if (self) {
2829
_logMapID = logMapID;

GoogleDataLogger/GoogleDataLogger/Classes/GDLLogStorage.m

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
#import <GoogleDataLogger/GDLLogPrioritizer.h>
2121

22+
#import "GDLAssert.h"
2223
#import "GDLConsoleLogger.h"
2324
#import "GDLLogEvent_Private.h"
2425
#import "GDLRegistrar_Private.h"
@@ -75,13 +76,13 @@ - (void)storeLog:(GDLLogEvent *)log {
7576
// Check that a log prioritizer is available for this logTarget.
7677
id<GDLLogPrioritizer> logPrioritizer =
7778
[GDLRegistrar sharedInstance].logTargetToPrioritizer[@(logTarget)];
78-
NSAssert(logPrioritizer, @"There's no scorer registered for the given logTarget.");
79+
GDLAssert(logPrioritizer, @"There's no scorer registered for the given logTarget.");
7980

8081
// Write the extension bytes to disk, get a filename.
81-
NSAssert(shortLivedLog.extensionBytes, @"The log should have been serialized to bytes");
82-
NSAssert(shortLivedLog.extension == nil, @"The original log proto should be removed");
83-
NSURL *logFile =
84-
[self saveLogProtoToDisk:shortLivedLog.extensionBytes logHash:shortLivedLog.hash];
82+
GDLAssert(shortLivedLog.extensionBytes, @"The log should have been serialized to bytes");
83+
GDLAssert(shortLivedLog.extension == nil, @"The original log proto should be removed");
84+
NSURL *logFile = [self saveLogProtoToDisk:shortLivedLog.extensionBytes
85+
logHash:shortLivedLog.hash];
8586

8687
// Add log to tracking collections.
8788
[self addLogToTrackingCollections:shortLivedLog logFile:logFile];
@@ -111,12 +112,12 @@ - (void)removeLog:(NSNumber *)logHash logTarget:(NSNumber *)logTarget {
111112
// Remove from disk, first and foremost.
112113
NSError *error;
113114
[[NSFileManager defaultManager] removeItemAtURL:logFile error:&error];
114-
NSAssert(error == nil, @"There was an error removing a logFile: %@", error);
115+
GDLAssert(error == nil, @"There was an error removing a logFile: %@", error);
115116

116117
// Remove from the tracking collections.
117118
[self.logHashToLogFile removeObjectForKey:logHash];
118119
NSMutableSet<NSURL *> *logFiles = self.logTargetToLogFileSet[logTarget];
119-
NSAssert(logFiles, @"There wasn't a logSet for this logTarget.");
120+
GDLAssert(logFiles, @"There wasn't a logSet for this logTarget.");
120121
[logFiles removeObject:logFile];
121122
// It's fine to not remove the set if it's empty.
122123
});
@@ -195,8 +196,8 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder {
195196
GDLLogStorage *sharedInstance = [self.class sharedInstance];
196197
dispatch_sync(sharedInstance.storageQueue, ^{
197198
Class NSMutableDictionaryClass = [NSMutableDictionary class];
198-
sharedInstance->_logHashToLogFile =
199-
[aDecoder decodeObjectOfClass:NSMutableDictionaryClass forKey:kGDLLogHashToLogFileKey];
199+
sharedInstance->_logHashToLogFile = [aDecoder decodeObjectOfClass:NSMutableDictionaryClass
200+
forKey:kGDLLogHashToLogFileKey];
200201
sharedInstance->_logTargetToLogFileSet =
201202
[aDecoder decodeObjectOfClass:NSMutableDictionaryClass forKey:kGDLLogTargetToLogSetKey];
202203
});

GoogleDataLogger/GoogleDataLogger/Classes/GDLLogWriter.m

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
#import <GoogleDataLogger/GDLLogTransformer.h>
2121

22+
#import "GDLAssert.h"
2223
#import "GDLConsoleLogger.h"
2324
#import "GDLLogEvent_Private.h"
2425
#import "GDLLogStorage.h"
@@ -46,7 +47,7 @@ - (instancetype)init {
4647

4748
- (void)writeLog:(GDLLogEvent *)log
4849
afterApplyingTransformers:(NSArray<id<GDLLogTransformer>> *)logTransformers {
49-
NSAssert(log, @"You can't write a nil log");
50+
GDLAssert(log, @"You can't write a nil log");
5051
dispatch_async(_logWritingQueue, ^{
5152
GDLLogEvent *transformedLog = log;
5253
for (id<GDLLogTransformer> transformer in logTransformers) {

GoogleDataLogger/GoogleDataLogger/Classes/GDLLogger.m

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#import "GDLLogger.h"
1818

19+
#import "GDLAssert.h"
1920
#import "GDLLogEvent.h"
2021
#import "GDLLogWriter.h"
2122

@@ -39,8 +40,8 @@ - (instancetype)initWithLogMapID:(NSString *)logMapID
3940
logTarget:(NSInteger)logTarget {
4041
self = [super init];
4142
if (self) {
42-
NSAssert(logMapID.length > 0, @"A log mapping ID cannot be nil or empty");
43-
NSAssert(logTarget > 0, @"A log target cannot be negative or 0");
43+
GDLAssert(logMapID.length > 0, @"A log mapping ID cannot be nil or empty");
44+
GDLAssert(logTarget > 0, @"A log target cannot be negative or 0");
4445
_logMapID = logMapID;
4546
_logTransformers = logTransformers;
4647
_logTarget = logTarget;
@@ -49,13 +50,13 @@ - (instancetype)initWithLogMapID:(NSString *)logMapID
4950
}
5051

5152
- (void)logTelemetryEvent:(GDLLogEvent *)logEvent {
52-
NSAssert(logEvent, @"You can't log a nil event");
53+
GDLAssert(logEvent, @"You can't log a nil event");
5354
GDLLogEvent *copiedLog = [logEvent copy];
5455
[[GDLLogWriter sharedInstance] writeLog:copiedLog afterApplyingTransformers:_logTransformers];
5556
}
5657

5758
- (void)logDataEvent:(GDLLogEvent *)logEvent {
58-
NSAssert(logEvent, @"You can't log a nil event");
59+
GDLAssert(logEvent, @"You can't log a nil event");
5960
GDLLogEvent *copiedLog = [logEvent copy];
6061
[[GDLLogWriter sharedInstance] writeLog:copiedLog afterApplyingTransformers:_logTransformers];
6162
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2019 Google
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#import <Foundation/Foundation.h>
18+
19+
/** A block type that could be run instead of NSAssert. No return type, no params. */
20+
typedef void (^GDLAssertionBlock)(void);
21+
22+
/** Returns the result of executing a soft-linked method present in unit tests that allows a block
23+
* to be run in lieu of a call to NSAssert. This helps ameliorate issues with catching exceptions
24+
* that occur on a dispatch_queue.
25+
*
26+
* @return A block that can be run instead of calling NSAssert, or nil.
27+
*/
28+
FOUNDATION_EXTERN GDLAssertionBlock _Nullable GDLAssertionBlockToRunInsteadOfNSAssert(void);
29+
30+
#if !defined(NS_BLOCK_ASSERTIONS)
31+
32+
/** Asserts using NSAssert, unless a block was specified to be run instead.
33+
*
34+
* @param condition The condition you'd expect to be YES.
35+
*/
36+
#define GDLAssert(condition, ...) \
37+
do { \
38+
if (__builtin_expect(!(condition), 0)) { \
39+
GDLAssertionBlock assertionBlock = GDLAssertionBlockToRunInsteadOfNSAssert(); \
40+
if (assertionBlock) { \
41+
assertionBlock(); \
42+
} else { \
43+
NSAssert(condition, __VA_ARGS__); \
44+
} \
45+
} \
46+
} while (0);
47+
48+
#else
49+
50+
#define GDLAssert(condition, ...) \
51+
do { \
52+
} while (0);
53+
54+
#endif // !defined(NS_BLOCK_ASSERTIONS)

GoogleDataLogger/GoogleDataLogger/DependencyWrappers/GDLConsoleLogger.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
#import "GULLogger.h"
1818

19+
#import "GDLAssert.h"
20+
1921
/** The console logger prefix. */
2022
static GULLoggerService kGDLConsoleLogger = @"[GoogleDataLogger]";
2123

@@ -64,4 +66,4 @@ FOUNDATION_EXTERN void GDLLogWarning(GDLMessageCode messageCode,
6466
#define GDLLogError(MESSAGE_CODE, MESSAGE_FORMAT, ...) \
6567
GULLogError(kGDLConsoleLogger, YES, GDLMessageCodeEnumToString(MESSAGE_CODE), MESSAGE_FORMAT, \
6668
__VA_ARGS__); \
67-
NSAssert(NO, MESSAGE_FORMAT, __VA_ARGS__);
69+
GDLAssert(NO, MESSAGE_FORMAT, __VA_ARGS__);

GoogleDataLogger/Tests/GDLClockTest.m

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
1414
* limitations under the License.
1515
*/
1616

17-
#import <XCTest/XCTest.h>
17+
#import "GDLTestCase.h"
1818

1919
#import "GDLClock.h"
2020

21-
@interface GDLClockTest : XCTestCase
21+
@interface GDLClockTest : GDLTestCase
2222

2323
@end
2424

GoogleDataLogger/Tests/GDLLogEventTest.m

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@
1414
* limitations under the License.
1515
*/
1616

17-
#import <XCTest/XCTest.h>
17+
#import "GDLTestCase.h"
1818

1919
#import <GoogleDataLogger/GDLLogEvent.h>
2020

2121
#import "GDLLogEvent_Private.h"
2222

23-
@interface GDLLogEventTest : XCTestCase
23+
@interface GDLLogEventTest : GDLTestCase
2424

2525
@end
2626

GoogleDataLogger/Tests/GDLLogStorageTest.m

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
#import <XCTest/XCTest.h>
17+
#import "GDLTestCase.h"
1818

1919
#import <GoogleDataLogger/GDLLogEvent.h>
2020

@@ -32,7 +32,7 @@
3232

3333
static NSInteger logTarget = 1337;
3434

35-
@interface GDLLogStorageTest : XCTestCase
35+
@interface GDLLogStorageTest : GDLTestCase
3636

3737
/** The test backend implementation. */
3838
@property(nullable, nonatomic) GDLTestBackend *testBackend;

GoogleDataLogger/Tests/GDLLogUploaderTest.m

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
1414
* limitations under the License.
1515
*/
1616

17-
#import <XCTest/XCTest.h>
17+
#import "GDLTestCase.h"
1818

1919
#import "GDLUploader.h"
2020

21-
@interface GDLUploaderTest : XCTestCase
21+
@interface GDLUploaderTest : GDLTestCase
2222

2323
@end
2424

GoogleDataLogger/Tests/GDLLogWriterTest.m

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
#import <XCTest/XCTest.h>
17+
#import "GDLTestCase.h"
1818

1919
#import <GoogleDataLogger/GDLLogTransformer.h>
2020

@@ -47,7 +47,7 @@ - (GDLLogEvent *)transform:(GDLLogEvent *)logEvent {
4747

4848
@end
4949

50-
@interface GDLLogWriterTest : XCTestCase
50+
@interface GDLLogWriterTest : GDLTestCase
5151

5252
@end
5353

GoogleDataLogger/Tests/GDLLoggerTest.m

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@
1414
* limitations under the License.
1515
*/
1616

17-
#import <XCTest/XCTest.h>
17+
#import "GDLTestCase.h"
1818

1919
#import <GoogleDataLogger/GDLLogEvent.h>
2020
#import <GoogleDataLogger/GDLLogger.h>
2121

2222
#import "GDLLogExtensionTesterClasses.h"
2323

24-
@interface GDLLoggerTest : XCTestCase
24+
@interface GDLLoggerTest : GDLTestCase
2525

2626
@end
2727

GoogleDataLogger/Tests/GDLRegistrarTest.m

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
1414
* limitations under the License.
1515
*/
1616

17-
#import <XCTest/XCTest.h>
17+
#import "GDLTestCase.h"
1818

1919
#import <GoogleDataLogger/GDLRegistrar.h>
2020

21-
@interface GDLRegistrarTest : XCTestCase
21+
@interface GDLRegistrarTest : GDLTestCase
2222

2323
@end
2424

GoogleDataLogger/Tests/GDLTestCase.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2019 Google
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#import <XCTest/XCTest.h>
18+
19+
#import "GDLAssertHelper.h"
20+
21+
NS_ASSUME_NONNULL_BEGIN
22+
23+
/** This class defines shared testing infrastructure across all unit tests in GoogleDataLogger. */
24+
@interface GDLTestCase : XCTestCase
25+
26+
@end
27+
28+
NS_ASSUME_NONNULL_END

0 commit comments

Comments
 (0)