xref: /haiku/src/tests/kits/app/common/AppRunner.cpp (revision 7d6915b4d08ffe728cd38af02843d5e98ddfe0db)
1 // AppRunner.cpp
2 
3 #include <errno.h>
4 #include <stdlib.h>
5 #include <unistd.h>
6 
7 #include <Autolock.h>
8 #include <Entry.h>
9 #include <Messenger.h>
10 #include <String.h>
11 
12 #include <TestShell.h>
13 #include <TestUtils.h>
14 #include <cppunit/TestAssert.h>
15 
16 #include "AppRunner.h"
17 
18 static const char *kAppRunnerTeamPort = "app runner team port";
19 
20 // constructor
21 AppRunner::AppRunner(bool requestQuitOnDestruction)
22 		 : fOutputLock(),
23 		   fRemotePort(-1),
24 		   fOutput(),
25 		   fReader(-1),
26 		   fTeam(-1),
27 		   fRef(),
28 		   fMessenger(),
29 		   fRequestQuitOnDestruction(requestQuitOnDestruction)
30 {
31 }
32 
33 // destructor
34 AppRunner::~AppRunner()
35 {
36 	if (fRequestQuitOnDestruction)
37 		WaitFor(true);
38 	if (fReader >= 0) {
39 		int32 result;
40 		wait_for_thread(fReader, &result);
41 	}
42 }
43 
44 // Run
45 status_t
46 AppRunner::Run(const char *command, const char *args, bool findCommand)
47 {
48 	status_t error = (HasQuitted() ? B_OK : B_ERROR);
49 	// get the app path
50 	BString appPath;
51 	if (findCommand) {
52 		find_test_app(command, &appPath);
53 		command = appPath.String();
54 	}
55 	get_ref_for_path(command, &fRef);
56 	// add args, i.e. compose the command line
57 	BString cmdLine(command);
58 	if (args) {
59 		cmdLine += " ";
60 		cmdLine += args;
61 	}
62 	// lock the team port
63 	bool teamPortLocked = false;
64 	if (error == B_OK) {
65 		teamPortLocked = _LockTeamPort();
66 		if (!teamPortLocked)
67 			error = B_ERROR;
68 	}
69 	// run the command
70 	if (error == B_OK) {
71 		cmdLine += " &";
72 		if (system(cmdLine.String()) != 0)
73 			error = errno;
74 	}
75 	// read the port ID
76 	if (error == B_OK) {
77 		fRemotePort = _ReadPortID(fMessenger);
78 		if (fRemotePort < 0)
79 			error = fRemotePort;
80 	}
81 	// unlock the team port
82 	if (teamPortLocked)
83 		_UnlockTeamPort();
84 	// get the team ID
85 	if (error == B_OK) {
86 		port_info info;
87 		error = get_port_info(fRemotePort, &info);
88 		fTeam = info.team;
89 	}
90 	// spawn the reader thread
91 	if (error == B_OK) {
92 		fReader = spawn_thread(&_ReaderEntry, "AppRunner reader",
93 							   B_NORMAL_PRIORITY, (void*)this);
94 		if (fReader >= 0)
95 			error = resume_thread(fReader);
96 		else
97 			error = fReader;
98 	}
99 	// cleanup on error
100 	if (error != B_OK) {
101 		if (fReader >= 0) {
102 			kill_thread(fReader);
103 			fReader = -1;
104 		}
105 	}
106 	return error;
107 }
108 
109 // HasQuitted
110 bool
111 AppRunner::HasQuitted()
112 {
113 	port_info info;
114 	return (get_port_info(fRemotePort, &info) != B_OK);
115 }
116 
117 // WaitFor
118 void
119 AppRunner::WaitFor(bool requestQuit)
120 {
121 	if (!HasQuitted() && requestQuit)
122 		RequestQuit();
123 	while (!HasQuitted())
124 		snooze(10000);
125 }
126 
127 // Team
128 team_id
129 AppRunner::Team()
130 {
131 	return fTeam;
132 }
133 
134 // AppLooperPort
135 port_id
136 AppRunner::AppLooperPort()
137 {
138 	struct messenger_hack {
139 		port_id	fPort;
140 		int32	fHandlerToken;
141 		team_id	fTeam;
142 		int32	extra0;
143 		int32	extra1;
144 		bool	fPreferredTarget;
145 		bool	extra2;
146 		bool	extra3;
147 		bool	extra4;
148 	};
149 	return ((messenger_hack*)&fMessenger)->fPort;
150 }
151 
152 // GetRef
153 status_t
154 AppRunner::GetRef(entry_ref *ref)
155 {
156 	status_t error = (ref ? B_OK : B_ERROR);
157 	if (error == B_OK)
158 		*ref = fRef;
159 	return error;
160 }
161 
162 // RequestQuit
163 status_t
164 AppRunner::RequestQuit()
165 {
166 	status_t error = B_OK;
167 	if (fTeam >= 0) {
168 		BMessenger messenger(fMessenger);
169 		error = messenger.SendMessage(B_QUIT_REQUESTED);
170 	} else
171 		error = fTeam;
172 	return error;
173 }
174 
175 // GetOutput
176 status_t
177 AppRunner::GetOutput(BString *buffer)
178 {
179 	status_t error = (buffer ? B_OK : B_BAD_VALUE);
180 	if (error == B_OK) {
181 		BAutolock locker(fOutputLock);
182 		size_t size = fOutput.BufferLength();
183 		const void *output = fOutput.Buffer();
184 		if (size > 0)
185 			buffer->SetTo((const char*)output, size);
186 		else
187 			*buffer = "";
188 	}
189 	return error;
190 }
191 
192 // ReadOutput
193 ssize_t
194 AppRunner::ReadOutput(void *buffer, size_t size)
195 {
196 	BAutolock locker(fOutputLock);
197 	return fOutput.Read(buffer, size);
198 }
199 
200 // ReadOutputAt
201 ssize_t
202 AppRunner::ReadOutputAt(off_t position, void *buffer, size_t size)
203 {
204 	BAutolock locker(fOutputLock);
205 	return fOutput.ReadAt(position, buffer, size);
206 }
207 
208 // _ReaderEntry
209 int32
210 AppRunner::_ReaderEntry(void *data)
211 {
212 	int32 result = 0;
213 	if (AppRunner *me = (AppRunner*)data)
214 		result = me->_ReaderLoop();
215 	return result;
216 }
217 
218 // _ReaderLoop
219 int32
220 AppRunner::_ReaderLoop()
221 {
222 	char buffer[10240];
223 	port_id port = fRemotePort;
224 	status_t error = B_OK;
225 	while (error == B_OK) {
226 		int32 code;
227 		ssize_t bytes = read_port(port, &code, buffer, sizeof(buffer) - 1);
228 		if (bytes > 0) {
229 			// write the buffer
230 			BAutolock locker(fOutputLock);
231 			off_t oldPosition = fOutput.Seek(0, SEEK_END);
232 			fOutput.Write(buffer, bytes);
233 			fOutput.Seek(oldPosition, SEEK_SET);
234 		} else if (bytes < 0)
235 			error = bytes;
236 	}
237 	fRemotePort = -1;
238 	return 0;
239 }
240 
241 // _LockTeamPort
242 bool
243 AppRunner::_LockTeamPort()
244 {
245 	bool result = fTeamPortLock.Lock();
246 	// lazy port creation
247 	if (result && fTeamPort < 0) {
248 		fTeamPort = create_port(5, kAppRunnerTeamPort);
249 		if (fTeamPort < 0) {
250 			fTeamPortLock.Unlock();
251 			result = false;
252 		}
253 	}
254 	return result;
255 }
256 
257 // _UnlockTeamPort
258 void
259 AppRunner::_UnlockTeamPort()
260 {
261 	fTeamPortLock.Unlock();
262 }
263 
264 // _ReadPortID
265 port_id
266 AppRunner::_ReadPortID(BMessenger &messenger)
267 {
268 	port_id port = -1;
269 	ssize_t size = read_port(fTeamPort, &port, &messenger, sizeof(BMessenger));
270 	if (size < 0)
271 		port = size;
272 	return port;
273 }
274 
275 
276 // fTeamPort
277 port_id	AppRunner::fTeamPort = -1;
278 
279 // fTeamPortLock
280 BLocker	AppRunner::fTeamPortLock;
281 
282 
283 // find_test_app
284 status_t
285 find_test_app(const char *testApp, BString *path)
286 {
287 	status_t error = (testApp && path ? B_OK : B_BAD_VALUE);
288 	if (error == B_OK) {
289 		*path = BTestShell::GlobalTestDir();
290 		path->CharacterEscape(" \t\n!\"'`$&()?*+{}[]<>|", '\\');
291 		*path += "/";
292 		*path += testApp;
293 		#ifdef TEST_R5
294 			*path += "_r5";
295 		#endif
296 	}
297 	return error;
298 }
299 
300 // find_test_app
301 status_t
302 find_test_app(const char *testApp, entry_ref *ref)
303 {
304 	status_t error = (testApp && ref ? B_OK : B_BAD_VALUE);
305 	BString path;
306 	if (error == B_OK)
307 		error = find_test_app(testApp, &path);
308 	if (error == B_OK)
309 		error = get_ref_for_path(path.String(), ref);
310 	return error;
311 }
312 
313