LayoutTestRelay should wait for WebKitTestRunnerApp installation to complete
[WebKit-https.git] / Tools / LayoutTestRelay / LayoutTestRelay / LTRelayController.m
1 /*
2  * Copyright (C) 2014 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "LTRelayController.h"
27
28 #import "CoreSimulatorSPI.h"
29 #import "LTPipeRelay.h"
30 #import <AppKit/AppKit.h>
31 #import <crt_externs.h>
32
33 @interface LTRelayController ()
34 @property (readonly, strong) dispatch_source_t standardInputDispatchSource;
35 @property (readonly, strong) NSFileHandle *standardOutput;
36 @property (readonly, strong) NSFileHandle *standardError;
37 @property (readonly, strong) NSString *ipcIdentifier;
38 @property (readonly, strong) NSString *appBundleIdentifier;
39 @property (readonly, strong) NSString *appPath;
40 @property (readonly, strong) NSUUID *deviceUDID;
41 @property (readonly, strong) NSArray *dumpToolArguments;
42 @property (readonly, strong) NSString *productDir;
43 @property (strong) SimDevice *device;
44 @property (strong) id<LTRelay> relay;
45 @property pid_t pid;
46 @property (strong) dispatch_source_t appDispatchSource;
47 @end
48
49 @implementation LTRelayController
50
51 - (id)initWithDevice:(SimDevice *)device productDir:(NSString *)productDir appPath:(NSString *)appPath deviceUDID:(NSUUID *)udid dumpToolArguments:(NSArray *)arguments
52 {
53     if ((self = [super init])) {
54         _device = device;
55         _productDir = productDir;
56         _appPath = appPath;
57         _appBundleIdentifier = [[NSBundle bundleWithPath:appPath] bundleIdentifier];
58         _deviceUDID = udid;
59         _dumpToolArguments = arguments;
60         _standardInputDispatchSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, STDIN_FILENO, 0, dispatch_get_main_queue());
61         _standardOutput = [NSFileHandle fileHandleWithStandardOutput];
62         _standardError = [NSFileHandle fileHandleWithStandardError];
63
64         dispatch_source_set_event_handler(_standardInputDispatchSource, ^{
65             uint8_t buffer[1024];
66             ssize_t len = read(STDIN_FILENO, buffer, sizeof(buffer));
67             if (len > 0) {
68                 @try {
69                     [[[self relay] outputStream] write:buffer maxLength:len];
70                 } @catch (NSException *e) {
71                     // Broken pipe - the dump tool crashed. Time to die.
72                     [self didCrashWithMessage:nil];
73                 }
74             } else {
75                 // EOF Received on the relay's standard input.
76                 [self finish];
77             }
78         });
79
80         _relay = [[LTPipeRelay alloc] initWithPrefix:[@"/tmp" stringByAppendingPathComponent:self.ipcIdentifier]];
81         [_relay setRelayDelegate:self];
82     }
83     return self;
84 }
85
86 - (NSString *)ipcIdentifier
87 {
88     return [NSString stringWithFormat:@"%@-%@", self.appBundleIdentifier, self.deviceUDID.UUIDString];
89 }
90
91 - (NSString *)processName
92 {
93     return [self.appBundleIdentifier componentsSeparatedByString:@"."].lastObject;
94 }
95
96 - (void)didReceiveStdoutData:(NSData *)data
97 {
98     @try {
99         [[self standardOutput] writeData:data];
100     } @catch (NSException *exception) {
101         // NSFileHandleOperationException
102         // Broken pipe - the test harness stopped listening to us,
103         // probably because we timed out or a run was canceled.
104         exit(EXIT_FAILURE);
105     }
106 }
107
108 - (void)didReceiveStderrData:(NSData *)data
109 {
110     @try {
111         [[self standardError] writeData:data];
112     } @catch (NSException *exception) {
113         // NSFileHandleOperationException
114         // Broken pipe - the test harness stopped listening to us,
115         // probably because we timed out or a run was canceled.
116         exit(EXIT_FAILURE);
117     }
118 }
119
120 - (void)didDisconnect
121 {
122     dispatch_suspend([self standardInputDispatchSource]);
123 }
124
125 - (void)didConnect
126 {
127     dispatch_resume([self standardInputDispatchSource]);
128 }
129
130 - (void)didCrashWithMessage:(NSString *)message
131 {
132     [[self relay] disconnect];
133     NSString *crashMessage = [NSString stringWithFormat:@"\n#CRASHED - %@ (pid %d)\n", [self processName], [self pid]];
134
135     if (message)
136         crashMessage = [crashMessage stringByAppendingFormat:@"%@\n", message];
137
138     [[self standardError] writeData:[crashMessage dataUsingEncoding:NSUTF8StringEncoding]];
139     [[self standardError] closeFile];
140     [[self standardOutput] closeFile];
141     exit(EXIT_FAILURE);
142 }
143
144 - (void)installApp
145 {
146     NSDictionary *installOptions = @{
147         (NSString *)kCFBundleIdentifierKey: self.appBundleIdentifier,
148         @"LSBlockUntilComplete": @YES,
149     };
150
151     NSError *error = nil;
152     BOOL installed = [self.device installApplication:[NSURL fileURLWithPath:self.appPath] withOptions:installOptions error:&error];
153     if (!installed) {
154         NSLog(@"Couldn't install %@: %@", self.appPath, error.description);
155         exit(EXIT_FAILURE);
156     }
157 }
158
159 - (void)killApp
160 {
161     dispatch_source_t dispatch = [self appDispatchSource];
162
163     if (dispatch)
164         dispatch_source_cancel(dispatch);
165
166     if ([self pid] > 1) {
167         kill(self.pid, SIGKILL);
168         [self setPid:-1];
169     }
170     [self setAppDispatchSource:nil];
171 }
172
173 - (NSDictionary *)_environmentVariables
174 {
175     static NSDictionary *environmentVariables;
176     static dispatch_once_t once;
177
178     dispatch_once(&once, ^{
179         NSString *productDirectory = [self productDir];
180
181         NSMutableDictionary *dictionary = [@{
182             @"IPC_IDENTIFIER": self.ipcIdentifier,
183             @"DYLD_FRAMEWORK_PATH": productDirectory,
184             @"__XPC_DYLD_FRAMEWORK_PATH": productDirectory,
185             @"DYLD_LIBRARY_PATH": productDirectory,
186             @"__XPC_DYLD_LIBRARY_PATH": productDirectory,
187         } mutableCopy];
188
189         for (NSString *keyName in @[@"DYLD_INSERT_LIBRARIES", @"MallocStackLogging", @"LOCAL_RESOURCE_ROOT", @"DUMPRENDERTREE_TEMP"]) {
190             const char* value = getenv([keyName UTF8String]);
191             if (value && strlen(value)) {
192                 NSString *nsValue = [NSString stringWithUTF8String:value];
193                 [dictionary setObject:nsValue forKey:keyName];
194                 [dictionary setObject:nsValue forKey:[@"__XPC_" stringByAppendingString:keyName]];
195             }
196         }
197
198         for (char** envp = *_NSGetEnviron(); *envp; envp++) {
199             const char* env = *envp;
200             if (!strncmp("JSC_", env, 4) || !strncmp("__XPC_JSC_", env, 10)) {
201                 const char* equal = strchr(env, '=');
202                 if (!equal) {
203                     NSLog(@"Missing '=' in env var '%s'", env);
204                     continue;
205                 }
206
207                 static const size_t maxKeyLength = 256;
208                 size_t keyLength = equal - env;
209                 if (keyLength >= maxKeyLength) {
210                     NSLog(@"Env var '%s' is too long", env);
211                     continue;
212                 }
213
214                 char key[maxKeyLength];
215                 strncpy(key, env, keyLength);
216                 key[keyLength] = '\0';
217                 const char* value = equal + 1;
218
219                 NSString *nsKey = [NSString stringWithUTF8String:key];
220                 NSString *nsValue = [NSString stringWithUTF8String:value];
221                 [dictionary setObject:nsValue forKey:nsKey];
222             }
223         }
224
225         environmentVariables = [dictionary copy];
226     });
227
228     return environmentVariables;
229 }
230
231 - (void)launchApp
232 {
233     NSDictionary *launchOptions = @{
234         kSimDeviceLaunchApplicationEnvironment: [self _environmentVariables],
235         kSimDeviceLaunchApplicationArguments: [self dumpToolArguments],
236     };
237
238     NSError *error;
239     pid_t pid = [self.device launchApplicationWithID:self.appBundleIdentifier options:launchOptions error:&error];
240
241     if (pid < 0) {
242         NSLog(@"Couldn't launch %@: %@", self.appBundleIdentifier, error.description);
243         exit(EXIT_FAILURE);
244     }
245
246     [self setPid:pid];
247
248     dispatch_queue_t queue = dispatch_get_main_queue();
249     dispatch_source_t simulatorAppExitDispatchSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, [self pid], DISPATCH_PROC_EXIT, queue);
250     [self setAppDispatchSource:simulatorAppExitDispatchSource];
251     dispatch_source_set_event_handler(simulatorAppExitDispatchSource, ^{
252         dispatch_source_cancel(simulatorAppExitDispatchSource);
253         [self didCrashWithMessage:nil];
254     });
255     dispatch_resume(simulatorAppExitDispatchSource);
256 }
257
258 - (void)start
259 {
260     [self installApp];
261     [[self relay] setup];
262     [self launchApp];
263     [[self relay] connect];
264 }
265
266 - (void)finish
267 {
268     [[self relay] disconnect];
269     [self killApp];
270     exit(EXIT_SUCCESS);
271 }
272
273 @end