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