Skip to content

Commit 57101b2

Browse files
GDTTransformer: fix background tasks handling (#6457)
* GDTCORTransformer: background task tests * GDTCORTransformer: background tasks handling fix * Copyright * Changelog * Comments * rand() -> arc4random() * GDTCORTransformer tests for default initializer * Fix default initializer * ./scripts/style.sh * Pass weak application reference to the blocks
1 parent 15198dc commit 57101b2

File tree

7 files changed

+185
-34
lines changed

7 files changed

+185
-34
lines changed

GoogleDataTransport/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# v7.4.0
22
- Limit disk space consumed by GoogleDataTransport to store events. (#6365)
3+
- Fix `GDTTransformer` background task handling. (#6258)
34

45
# v7.1.1
56
- Use `NSTimeZone` instead of `CFTimeZone` to get time zone offset respecting daylight. (#6246)

GoogleDataTransport/GDTCORLibrary/GDTCORTransformer.m

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,15 @@ + (instancetype)sharedInstance {
3939
}
4040

4141
- (instancetype)init {
42+
return [self initWithApplication:[GDTCORApplication sharedApplication]];
43+
}
44+
45+
- (instancetype)initWithApplication:(id<GDTCORApplicationProtocol>)application {
4246
self = [super init];
4347
if (self) {
4448
_eventWritingQueue =
4549
dispatch_queue_create("com.google.GDTCORTransformer", DISPATCH_QUEUE_SERIAL);
50+
_application = application;
4651
}
4752
return self;
4853
}
@@ -51,45 +56,47 @@ - (void)transformEvent:(GDTCOREvent *)event
5156
withTransformers:(NSArray<id<GDTCOREventTransformer>> *)transformers
5257
onComplete:(void (^_Nullable)(BOOL wasWritten, NSError *_Nullable error))completion {
5358
GDTCORAssert(event, @"You can't write a nil event");
54-
BOOL hadOriginalCompletion = completion != nil;
55-
if (!completion) {
56-
completion = ^(BOOL wasWritten, NSError *_Nullable error) {
57-
};
58-
}
5959

6060
__block GDTCORBackgroundIdentifier bgID = GDTCORBackgroundIdentifierInvalid;
61-
bgID = [[GDTCORApplication sharedApplication]
62-
beginBackgroundTaskWithName:@"GDTTransformer"
63-
expirationHandler:^{
64-
[[GDTCORApplication sharedApplication] endBackgroundTask:bgID];
65-
bgID = GDTCORBackgroundIdentifierInvalid;
66-
}];
61+
__auto_type __weak weakApplication = self.application;
62+
bgID = [self.application beginBackgroundTaskWithName:@"GDTTransformer"
63+
expirationHandler:^{
64+
[weakApplication endBackgroundTask:bgID];
65+
bgID = GDTCORBackgroundIdentifierInvalid;
66+
}];
67+
68+
__auto_type completionWrapper = ^(BOOL wasWritten, NSError *_Nullable error) {
69+
if (completion) {
70+
completion(wasWritten, error);
71+
}
72+
73+
// The work is done, cancel the background task if it's valid.
74+
[weakApplication endBackgroundTask:bgID];
75+
bgID = GDTCORBackgroundIdentifierInvalid;
76+
};
77+
6778
dispatch_async(_eventWritingQueue, ^{
6879
GDTCOREvent *transformedEvent = event;
6980
for (id<GDTCOREventTransformer> transformer in transformers) {
7081
if ([transformer respondsToSelector:@selector(transform:)]) {
7182
GDTCORLogDebug(@"Applying a transformer to event %@", event);
7283
transformedEvent = [transformer transform:transformedEvent];
7384
if (!transformedEvent) {
74-
completion(NO, nil);
85+
completionWrapper(NO, nil);
7586
return;
7687
}
7788
} else {
7889
GDTCORLogError(GDTCORMCETransformerDoesntImplementTransform,
7990
@"Transformer doesn't implement transform: %@", transformer);
80-
completion(NO, nil);
91+
completionWrapper(NO, nil);
8192
return;
8293
}
8394
}
8495

8596
id<GDTCORStorageProtocol> storage =
8697
[GDTCORRegistrar sharedInstance].targetToStorage[@(event.target)];
8798

88-
[storage storeEvent:transformedEvent onComplete:hadOriginalCompletion ? completion : nil];
89-
90-
// The work is done, cancel the background task if it's valid.
91-
[[GDTCORApplication sharedApplication] endBackgroundTask:bgID];
92-
bgID = GDTCORBackgroundIdentifierInvalid;
99+
[storage storeEvent:transformedEvent onComplete:completionWrapper];
93100
});
94101
}
95102

GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransformer_Private.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,22 @@
1616

1717
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransformer.h"
1818

19+
@protocol GDTCORApplicationProtocol;
20+
1921
NS_ASSUME_NONNULL_BEGIN
2022

2123
@interface GDTCORTransformer ()
2224

2325
/** The queue on which all work will occur. */
2426
@property(nonatomic) dispatch_queue_t eventWritingQueue;
2527

28+
/** The application instance that is used to begin/end background tasks. */
29+
@property(nonatomic, readonly) id<GDTCORApplicationProtocol> application;
30+
31+
/** The internal initializer. Should be used in tests only to create an instance with a
32+
* particular(fake) application instance. */
33+
- (instancetype)initWithApplication:(id<GDTCORApplicationProtocol>)application;
34+
2635
@end
2736

2837
NS_ASSUME_NONNULL_END

GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORPlatform.h

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -186,18 +186,13 @@ FOUNDATION_EXPORT const GDTCORBackgroundIdentifier GDTCORBackgroundIdentifierInv
186186

187187
@end
188188

189-
/** A cross-platform application class. */
190-
@interface GDTCORApplication : NSObject <GDTCORApplicationDelegate>
189+
@protocol GDTCORApplicationProtocol <NSObject>
190+
191+
@required
191192

192193
/** Flag to determine if the application is running in the background. */
193194
@property(atomic, readonly) BOOL isRunningInBackground;
194195

195-
/** Creates and/or returns the shared application instance.
196-
*
197-
* @return The shared application instance.
198-
*/
199-
+ (nullable GDTCORApplication *)sharedApplication;
200-
201196
/** Creates a background task with the returned identifier if on a suitable platform.
202197
*
203198
* @name name The name of the task, useful for debugging which background tasks are running.
@@ -216,4 +211,15 @@ FOUNDATION_EXPORT const GDTCORBackgroundIdentifier GDTCORBackgroundIdentifierInv
216211

217212
@end
218213

214+
/** A cross-platform application class. */
215+
@interface GDTCORApplication : NSObject <GDTCORApplicationProtocol, GDTCORApplicationDelegate>
216+
217+
/** Creates and/or returns the shared application instance.
218+
*
219+
* @return The shared application instance.
220+
*/
221+
+ (nullable GDTCORApplication *)sharedApplication;
222+
223+
@end
224+
219225
NS_ASSUME_NONNULL_END
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2020 Google LLC
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+
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORPlatform.h"
20+
21+
NS_ASSUME_NONNULL_BEGIN
22+
23+
typedef GDTCORBackgroundIdentifier (^GDTCORFakeBeginBackgroundTaskHandler)(
24+
NSString *name, dispatch_block_t handler);
25+
typedef void (^GDTCORFakeEndBackgroundTaskHandler)(GDTCORBackgroundIdentifier);
26+
27+
@interface GDTCORApplicationFake : NSObject <GDTCORApplicationProtocol>
28+
29+
@property(nonatomic, copy, nullable) GDTCORFakeBeginBackgroundTaskHandler beginTaskHandler;
30+
@property(nonatomic, copy, nullable) GDTCORFakeEndBackgroundTaskHandler endTaskHandler;
31+
32+
@end
33+
34+
NS_ASSUME_NONNULL_END
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2020 Google LLC
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 "GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORApplicationFake.h"
18+
19+
@implementation GDTCORApplicationFake
20+
21+
@synthesize isRunningInBackground;
22+
23+
- (GDTCORBackgroundIdentifier)beginBackgroundTaskWithName:(NSString *)name
24+
expirationHandler:(void (^__nullable)(void))handler {
25+
return self.beginTaskHandler(name, handler);
26+
}
27+
28+
- (void)endBackgroundTask:(GDTCORBackgroundIdentifier)bgID {
29+
self.endTaskHandler(bgID);
30+
}
31+
32+
@end

GoogleDataTransport/GDTCORTests/Unit/GDTCORTransformerTest.m

Lines changed: 70 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@
2929

3030
#import "GoogleDataTransport/GDTCORTests/Common/Categories/GDTCORRegistrar+Testing.h"
3131

32+
#import "GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORApplicationFake.h"
3233
#import "GoogleDataTransport/GDTCORTests/Common/Fakes/GDTCORStorageFake.h"
3334

3435
@interface GDTCORTransformerTestNilingTransformer : NSObject <GDTCOREventTransformer>
35-
3636
@end
3737

3838
@implementation GDTCORTransformerTestNilingTransformer
@@ -44,7 +44,6 @@ - (GDTCOREvent *)transform:(GDTCOREvent *)eventEvent {
4444
@end
4545

4646
@interface GDTCORTransformerTestNewEventTransformer : NSObject <GDTCOREventTransformer>
47-
4847
@end
4948

5049
@implementation GDTCORTransformerTestNewEventTransformer
@@ -57,50 +56,73 @@ - (GDTCOREvent *)transform:(GDTCOREvent *)eventEvent {
5756

5857
@interface GDTCORTransformerTest : GDTCORTestCase
5958

59+
@property(nonatomic) GDTCORApplicationFake *fakeApplication;
60+
@property(nonatomic) GDTCORTransformer *transformer;
61+
6062
@end
6163

6264
@implementation GDTCORTransformerTest
6365

6466
- (void)setUp {
6567
[super setUp];
66-
dispatch_sync([GDTCORTransformer sharedInstance].eventWritingQueue, ^{
68+
69+
self.fakeApplication = [[GDTCORApplicationFake alloc] init];
70+
71+
self.transformer = [[GDTCORTransformer alloc] initWithApplication:self.fakeApplication];
72+
dispatch_sync(self.transformer.eventWritingQueue, ^{
6773
[[GDTCORRegistrar sharedInstance] registerStorage:[[GDTCORStorageFake alloc] init]
6874
target:kGDTCORTargetTest];
6975
});
7076
}
7177

7278
- (void)tearDown {
7379
[super tearDown];
74-
dispatch_sync([GDTCORTransformer sharedInstance].eventWritingQueue, ^{
80+
dispatch_sync(self.transformer.eventWritingQueue, ^{
7581
[[GDTCORRegistrar sharedInstance] reset];
7682
});
83+
self.transformer = nil;
84+
85+
self.fakeApplication.beginTaskHandler = nil;
86+
self.fakeApplication = nil;
7787
}
7888

7989
/** Tests the default initializer. */
8090
- (void)testInit {
81-
XCTAssertNotNil([[GDTCORTransformer alloc] init]);
91+
GDTCORTransformer *transformer = [[GDTCORTransformer alloc] init];
92+
XCTAssertNotNil(transformer);
93+
XCTAssertEqualObjects(transformer.application, [GDTCORApplication sharedApplication]);
8294
}
8395

8496
/** Tests the pointer equality of result of the -sharedInstance method. */
8597
- (void)testSharedInstance {
8698
XCTAssertEqual([GDTCORTransformer sharedInstance], [GDTCORTransformer sharedInstance]);
99+
XCTAssertEqualObjects([GDTCORTransformer sharedInstance].application,
100+
[GDTCORApplication sharedApplication]);
87101
}
88102

89103
/** Tests writing a event without a transformer. */
90104
- (void)testWriteEventWithoutTransformers {
91-
GDTCORTransformer *transformer = [GDTCORTransformer sharedInstance];
105+
__auto_type bgTaskExpectations =
106+
[self expectationsBackgroundTaskBeginAndEndWithName:@"GDTTransformer"];
107+
108+
GDTCORTransformer *transformer = self.transformer;
92109
GDTCOREvent *event = [[GDTCOREvent alloc] initWithMappingID:@"1" target:kGDTCORTargetTest];
93110
event.dataObject = [[GDTCORDataObjectTesterSimple alloc] init];
94111
XCTAssertNoThrow([transformer transformEvent:event
95112
withTransformers:nil
96113
onComplete:^(BOOL wasWritten, NSError *_Nullable error) {
97114
XCTAssertTrue(wasWritten);
98115
}]);
116+
117+
[self waitForExpectations:bgTaskExpectations timeout:0.5];
99118
}
100119

101120
/** Tests writing a event with a transformer that nils out the event. */
102121
- (void)testWriteEventWithTransformersThatNilTheEvent {
103-
GDTCORTransformer *transformer = [GDTCORTransformer sharedInstance];
122+
__auto_type bgTaskExpectations =
123+
[self expectationsBackgroundTaskBeginAndEndWithName:@"GDTTransformer"];
124+
125+
GDTCORTransformer *transformer = self.transformer;
104126
GDTCOREvent *event = [[GDTCOREvent alloc] initWithMappingID:@"2" target:kGDTCORTargetTest];
105127
event.dataObject = [[GDTCORDataObjectTesterSimple alloc] init];
106128
NSArray<id<GDTCOREventTransformer>> *transformers =
@@ -110,11 +132,16 @@ - (void)testWriteEventWithTransformersThatNilTheEvent {
110132
onComplete:^(BOOL wasWritten, NSError *_Nullable error) {
111133
XCTAssertFalse(wasWritten);
112134
}]);
135+
136+
[self waitForExpectations:bgTaskExpectations timeout:0.5];
113137
}
114138

115139
/** Tests writing a event with a transformer that creates a new event. */
116140
- (void)testWriteEventWithTransformersThatCreateANewEvent {
117-
GDTCORTransformer *transformer = [GDTCORTransformer sharedInstance];
141+
__auto_type bgTaskExpectations =
142+
[self expectationsBackgroundTaskBeginAndEndWithName:@"GDTTransformer"];
143+
144+
GDTCORTransformer *transformer = self.transformer;
118145
GDTCOREvent *event = [[GDTCOREvent alloc] initWithMappingID:@"2" target:kGDTCORTargetTest];
119146
event.dataObject = [[GDTCORDataObjectTesterSimple alloc] init];
120147
NSArray<id<GDTCOREventTransformer>> *transformers =
@@ -125,6 +152,41 @@ - (void)testWriteEventWithTransformersThatCreateANewEvent {
125152
XCTAssertTrue(wasWritten);
126153
XCTAssertNil(error);
127154
}]);
155+
156+
[self waitForExpectations:bgTaskExpectations timeout:0.5];
157+
}
158+
159+
#pragma mark - Helpers
160+
161+
/** Sets GDTCORApplicationFake handlers to expect the begin and the end of a background task with
162+
* the specified name.
163+
* @return An array with the task begin and end XCTestExpectation.
164+
*/
165+
- (NSArray<XCTestExpectation *> *)expectationsBackgroundTaskBeginAndEndWithName:
166+
(NSString *)expectedName {
167+
XCTestExpectation *beginExpectation = [self expectationWithDescription:@"Background task begin"];
168+
XCTestExpectation *endExpectation = [self expectationWithDescription:@"Background task end"];
169+
170+
GDTCORBackgroundIdentifier taskID = arc4random();
171+
172+
__auto_type __weak weakSelf = self;
173+
174+
self.fakeApplication.beginTaskHandler =
175+
^GDTCORBackgroundIdentifier(NSString *_Nonnull name, dispatch_block_t _Nonnull handler) {
176+
__auto_type self = weakSelf;
177+
XCTAssertEqualObjects(expectedName, name);
178+
179+
[beginExpectation fulfill];
180+
return taskID;
181+
};
182+
183+
self.fakeApplication.endTaskHandler = ^(GDTCORBackgroundIdentifier endTaskID) {
184+
__auto_type self = weakSelf;
185+
XCTAssert(endTaskID == taskID);
186+
[endExpectation fulfill];
187+
};
188+
189+
return @[ beginExpectation, endExpectation ];
128190
}
129191

130192
@end

0 commit comments

Comments
 (0)