#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <Autolock.h>
#include <Entry.h>
#include <Messenger.h>
#include <String.h>
#include <TestShell.h>
#include <TestUtils.h>
#include <cppunit/TestAssert.h>
#include "AppRunner.h"
static const char *kAppRunnerTeamPort = "app runner team port";
AppRunner::AppRunner(bool requestQuitOnDestruction)
: fOutputLock(),
fRemotePort(-1),
fOutput(),
fReader(-1),
fTeam(-1),
fRef(),
fMessenger(),
fRequestQuitOnDestruction(requestQuitOnDestruction)
{
}
AppRunner::~AppRunner()
{
if (fRequestQuitOnDestruction)
WaitFor(true);
if (fReader >= 0) {
int32 result;
wait_for_thread(fReader, &result);
}
}
status_t
AppRunner::Run(const char *command, const char *args, bool findCommand)
{
status_t error = (HasQuitted() ? B_OK : B_ERROR);
BString appPath;
if (findCommand) {
find_test_app(command, &appPath);
command = appPath.String();
}
get_ref_for_path(command, &fRef);
BString cmdLine(command);
if (args) {
cmdLine += " ";
cmdLine += args;
}
bool teamPortLocked = false;
if (error == B_OK) {
teamPortLocked = _LockTeamPort();
if (!teamPortLocked)
error = B_ERROR;
}
if (error == B_OK) {
cmdLine += " &";
if (system(cmdLine.String()) != 0)
error = errno;
}
if (error == B_OK) {
fRemotePort = _ReadPortID(fMessenger);
if (fRemotePort < 0)
error = fRemotePort;
}
if (teamPortLocked)
_UnlockTeamPort();
if (error == B_OK) {
port_info info;
error = get_port_info(fRemotePort, &info);
fTeam = info.team;
}
if (error == B_OK) {
fReader = spawn_thread(&_ReaderEntry, "AppRunner reader",
B_NORMAL_PRIORITY, (void*)this);
if (fReader >= 0)
error = resume_thread(fReader);
else
error = fReader;
}
if (error != B_OK) {
if (fReader >= 0) {
kill_thread(fReader);
fReader = -1;
}
}
return error;
}
bool
AppRunner::HasQuitted()
{
port_info info;
return (get_port_info(fRemotePort, &info) != B_OK);
}
void
AppRunner::WaitFor(bool requestQuit)
{
if (!HasQuitted() && requestQuit)
RequestQuit();
while (!HasQuitted())
snooze(10000);
}
team_id
AppRunner::Team()
{
return fTeam;
}
port_id
AppRunner::AppLooperPort()
{
struct messenger_hack {
port_id fPort;
int32 fHandlerToken;
team_id fTeam;
int32 extra0;
int32 extra1;
bool fPreferredTarget;
bool extra2;
bool extra3;
bool extra4;
};
return ((messenger_hack*)&fMessenger)->fPort;
}
status_t
AppRunner::GetRef(entry_ref *ref)
{
status_t error = (ref ? B_OK : B_ERROR);
if (error == B_OK)
*ref = fRef;
return error;
}
status_t
AppRunner::RequestQuit()
{
status_t error = B_OK;
if (fTeam >= 0) {
BMessenger messenger(fMessenger);
error = messenger.SendMessage(B_QUIT_REQUESTED);
} else
error = fTeam;
return error;
}
status_t
AppRunner::GetOutput(BString *buffer)
{
status_t error = (buffer ? B_OK : B_BAD_VALUE);
if (error == B_OK) {
BAutolock locker(fOutputLock);
size_t size = fOutput.BufferLength();
const void *output = fOutput.Buffer();
if (size > 0)
buffer->SetTo((const char*)output, size);
else
*buffer = "";
}
return error;
}
ssize_t
AppRunner::ReadOutput(void *buffer, size_t size)
{
BAutolock locker(fOutputLock);
return fOutput.Read(buffer, size);
}
ssize_t
AppRunner::ReadOutputAt(off_t position, void *buffer, size_t size)
{
BAutolock locker(fOutputLock);
return fOutput.ReadAt(position, buffer, size);
}
int32
AppRunner::_ReaderEntry(void *data)
{
int32 result = 0;
if (AppRunner *me = (AppRunner*)data)
result = me->_ReaderLoop();
return result;
}
int32
AppRunner::_ReaderLoop()
{
char buffer[10240];
port_id port = fRemotePort;
status_t error = B_OK;
while (error == B_OK) {
int32 code;
ssize_t bytes = read_port(port, &code, buffer, sizeof(buffer) - 1);
if (bytes > 0) {
BAutolock locker(fOutputLock);
off_t oldPosition = fOutput.Seek(0, SEEK_END);
fOutput.Write(buffer, bytes);
fOutput.Seek(oldPosition, SEEK_SET);
} else if (bytes < 0)
error = bytes;
}
fRemotePort = -1;
return 0;
}
bool
AppRunner::_LockTeamPort()
{
bool result = fTeamPortLock.Lock();
if (result && fTeamPort < 0) {
fTeamPort = create_port(5, kAppRunnerTeamPort);
if (fTeamPort < 0) {
fTeamPortLock.Unlock();
result = false;
}
}
return result;
}
void
AppRunner::_UnlockTeamPort()
{
fTeamPortLock.Unlock();
}
port_id
AppRunner::_ReadPortID(BMessenger &messenger)
{
port_id port = -1;
ssize_t size = read_port(fTeamPort, &port, &messenger, sizeof(BMessenger));
if (size < 0)
port = size;
return port;
}
port_id AppRunner::fTeamPort = -1;
BLocker AppRunner::fTeamPortLock;
status_t
find_test_app(const char *testApp, BString *path)
{
status_t error = (testApp && path ? B_OK : B_BAD_VALUE);
if (error == B_OK) {
*path = BTestShell::GlobalTestDir();
path->CharacterEscape(" \t\n!\"'`$&()?*+{}[]<>|", '\\');
*path += "/";
*path += testApp;
#ifdef TEST_R5
*path += "_r5";
#endif
}
return error;
}
status_t
find_test_app(const char *testApp, entry_ref *ref)
{
status_t error = (testApp && ref ? B_OK : B_BAD_VALUE);
BString path;
if (error == B_OK)
error = find_test_app(testApp, &path);
if (error == B_OK)
error = get_ref_for_path(path.String(), ref);
return error;
}