Partial revert of r203811 - this key is not needed.
[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     };
149
150     NSError *error = nil;
151     BOOL installed = [self.device installApplication:[NSURL fileURLWithPath:self.appPath] withOptions:installOptions error:&error];
152     if (!installed) {
153         NSLog(@"Couldn't install %@: %@", self.appPath, error.description);
154         exit(EXIT_FAILURE);
155     }
156 }
157
158 - (void)killApp
159 {
160     dispatch_source_t dispatch = [self appDispatchSource];
161
162     if (dispatch)
163         dispatch_source_cancel(dispatch);
164
165     if ([self pid] > 1) {
166         kill(self.pid, SIGKILL);
167         [self setPid:-1];
168     }
169     [self setAppDispatchSource:nil];
170 }
171
172 - (NSDictionary *)_environmentVariables
173 {
174     static NSDictionary *environmentVariables;
175     static dispatch_once_t once;
176
177     dispatch_once(&once, ^{
178         NSString *productDirectory = [self productDir];
179
180         NSMutableDictionary *dictionary = [@{
181             @"IPC_IDENTIFIER": self.ipcIdentifier,
182             @"DYLD_FRAMEWORK_PATH": productDirectory,
183             @"__XPC_DYLD_FRAMEWORK_PATH": productDirectory,
184             @"DYLD_LIBRARY_PATH": productDirectory,
185             @"__XPC_DYLD_LIBRARY_PATH": productDirectory,
186         } mutableCopy];
187
188         for (NSString *keyName in @[@"DYLD_INSERT_LIBRARIES", @"MallocStackLogging", @"LOCAL_RESOURCE_ROOT", @"DUMPRENDERTREE_TEMP"]) {
189             const char* value = getenv([keyName UTF8String]);
190             if (value && strlen(value)) {
191                 NSString *nsValue = [NSString stringWithUTF8String:value];
192                 [dictionary setObject:nsValue forKey:keyName];
193                 [dictionary setObject:nsValue forKey:[@"__XPC_" stringByAppendingString:keyName]];
194             }
195         }
196
197         for (char** envp = *_NSGetEnviron(); *envp; envp++) {
198             const char* env = *envp;
199             if (!strncmp("JSC_", env, 4) || !strncmp("__XPC_JSC_", env, 10)) {
200                 const char* equal = strchr(env, '=');
201                 if (!equal) {
202                     NSLog(@"Missing '=' in env var '%s'", env);
203                     continue;
204                 }
205
206                 static const size_t maxKeyLength = 256;
207                 size_t keyLength = equal - env;
208                 if (keyLength >= maxKeyLength) {
209                     NSLog(@"Env var '%s' is too long", env);
210                     continue;
211                 }
212
213                 char key[maxKeyLength];
214                 strncpy(key, env, keyLength);
215                 key[keyLength] = '\0';
216                 const char* value = equal + 1;
217
218                 NSString *nsKey = [NSString stringWithUTF8String:key];
219                 NSString *nsValue = [NSString stringWithUTF8String:value];
220                 [dictionary setObject:nsValue forKey:nsKey];
221             }
222         }
223
224         environmentVariables = [dictionary copy];
225     });
226
227     return environmentVariables;
228 }
229
230 - (void)launchApp
231 {
232     NSDictionary *launchOptions = @{
233         kSimDeviceLaunchApplicationEnvironment: [self _environmentVariables],
234         kSimDeviceLaunchApplicationArguments: [self dumpToolArguments],
235     };
236
237     NSError *error;
238     pid_t pid = [self.device launchApplicationWithID:self.appBundleIdentifier options:launchOptions error:&error];
239
240     if (pid < 0) {
241         NSLog(@"Couldn't launch %@: %@", self.appBundleIdentifier, error.description);
242         exit(EXIT_FAILURE);
243     }
244
245     [self setPid:pid];
246
247     dispatch_queue_t queue = dispatch_get_main_queue();
248     dispatch_source_t simulatorAppExitDispatchSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, [self pid], DISPATCH_PROC_EXIT, queue);
249     [self setAppDispatchSource:simulatorAppExitDispatchSource];
250     dispatch_source_set_event_handler(simulatorAppExitDispatchSource, ^{
251         dispatch_source_cancel(simulatorAppExitDispatchSource);
252         [self didCrashWithMessage:nil];
253     });
254     dispatch_resume(simulatorAppExitDispatchSource);
255 }
256
257 - (void)start
258 {
259     [self installApp];
260     [[self relay] setup];
261     [self launchApp];
262     [[self relay] connect];
263 }
264
265 - (void)finish
266 {
267     [[self relay] disconnect];
268     [self killApp];
269     exit(EXIT_SUCCESS);
270 }
271
272 @end