/*-------------------------------------------------------------------------
* drawElements Quality Program Execution Server
* ---------------------------------------------
*
* Copyright 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*//*!
* \file
* \brief TestProcess implementation for Unix-like systems.
*//*--------------------------------------------------------------------*/
#include "xsPosixTestProcess.hpp"
#include "deFilePath.hpp"
#include "deClock.h"
#include <string.h>
#include <stdio.h>
using std::string;
using std::vector;
namespace xs
{
namespace posix
{
CaseListWriter::CaseListWriter (void)
: m_file (DE_NULL)
, m_run (false)
{
}
CaseListWriter::~CaseListWriter (void)
{
}
void CaseListWriter::start (const char* caseList, deFile* dst)
{
DE_ASSERT(!isStarted());
m_file = dst;
m_run = true;
int caseListSize = (int)strlen(caseList)+1;
m_caseList.resize(caseListSize);
std::copy(caseList, caseList+caseListSize, m_caseList.begin());
// Set to non-blocking mode.
if (!deFile_setFlags(m_file, DE_FILE_NONBLOCKING))
XS_FAIL("Failed to set non-blocking mode");
de::Thread::start();
}
void CaseListWriter::run (void)
{
deInt64 pos = 0;
while (m_run && pos < (deInt64)m_caseList.size())
{
deInt64 numWritten = 0;
deFileResult result = deFile_write(m_file, &m_caseList[0] + pos, m_caseList.size()-pos, &numWritten);
if (result == DE_FILERESULT_SUCCESS)
pos += numWritten;
else if (result == DE_FILERESULT_WOULD_BLOCK)
deSleep(1); // Yield.
else
break; // Error.
}
}
void CaseListWriter::stop (void)
{
if (!isStarted())
return; // Nothing to do.
m_run = false;
// Join thread.
join();
m_file = DE_NULL;
}
PipeReader::PipeReader (ThreadedByteBuffer* dst)
: m_file (DE_NULL)
, m_buf (dst)
{
}
PipeReader::~PipeReader (void)
{
}
void PipeReader::start (deFile* file)
{
DE_ASSERT(!isStarted());
// Set to non-blocking mode.
if (!deFile_setFlags(file, DE_FILE_NONBLOCKING))
XS_FAIL("Failed to set non-blocking mode");
m_file = file;
de::Thread::start();
}
void PipeReader::run (void)
{
std::vector<deUint8> tmpBuf (FILEREADER_TMP_BUFFER_SIZE);
deInt64 numRead = 0;
while (!m_buf->isCanceled())
{
deFileResult result = deFile_read(m_file, &tmpBuf[0], (deInt64)tmpBuf.size(), &numRead);
if (result == DE_FILERESULT_SUCCESS)
{
// Write to buffer.
try
{
m_buf->write((int)numRead, &tmpBuf[0]);
m_buf->flush();
}
catch (const ThreadedByteBuffer::CanceledException&)
{
// Canceled.
break;
}
}
else if (result == DE_FILERESULT_END_OF_FILE ||
result == DE_FILERESULT_WOULD_BLOCK)
{
// Wait for more data.
deSleep(FILEREADER_IDLE_SLEEP);
}
else
break; // Error.
}
}
void PipeReader::stop (void)
{
if (!isStarted())
return; // Nothing to do.
// Buffer must be in canceled state or otherwise stopping reader might block.
DE_ASSERT(m_buf->isCanceled());
// Join thread.
join();
m_file = DE_NULL;
}
} // unix
PosixTestProcess::PosixTestProcess (void)
: m_process (DE_NULL)
, m_processStartTime (0)
, m_infoBuffer (INFO_BUFFER_BLOCK_SIZE, INFO_BUFFER_NUM_BLOCKS)
, m_stdOutReader (&m_infoBuffer)
, m_stdErrReader (&m_infoBuffer)
, m_logReader (LOG_BUFFER_BLOCK_SIZE, LOG_BUFFER_NUM_BLOCKS)
{
}
PosixTestProcess::~PosixTestProcess (void)
{
delete m_process;
}
void PosixTestProcess::start (const char* name, const char* params, const char* workingDir, const char* caseList)
{
bool hasCaseList = strlen(caseList) > 0;
XS_CHECK(!m_process);
de::FilePath logFilePath = de::FilePath::join(workingDir, "TestResults.qpa");
m_logFileName = logFilePath.getPath();
// Remove old file if such exists.
if (deFileExists(m_logFileName.c_str()))
{
if (!deDeleteFile(m_logFileName.c_str()) || deFileExists(m_logFileName.c_str()))
throw TestProcessException(string("Failed to remove '") + m_logFileName + "'");
}
// Construct command line.
string cmdLine = de::FilePath(name).isAbsolutePath() ? name : de::FilePath::join(workingDir, name).getPath();
cmdLine += string(" --deqp-log-filename=") + logFilePath.getBaseName();
if (hasCaseList)
cmdLine += " --deqp-stdin-caselist";
if (strlen(params) > 0)
cmdLine += string(" ") + params;
DE_ASSERT(!m_process);
m_process = new de::Process();
try
{
m_process->start(cmdLine.c_str(), strlen(workingDir) > 0 ? workingDir : DE_NULL);
}
catch (const de::ProcessError& e)
{
delete m_process;
m_process = DE_NULL;
throw TestProcessException(e.what());
}
m_processStartTime = deGetMicroseconds();
// Create stdout & stderr readers.
if (m_process->getStdOut())
m_stdOutReader.start(m_process->getStdOut());
if (m_process->getStdErr())
m_stdErrReader.start(m_process->getStdErr());
// Start case list writer.
if (hasCaseList)
{
deFile* dst = m_process->getStdIn();
if (dst)
m_caseListWriter.start(caseList, dst);
else
{
cleanup();
throw TestProcessException("Failed to write case list");
}
}
}
void PosixTestProcess::terminate (void)
{
if (m_process)
{
try
{
m_process->kill();
}
catch (const std::exception& e)
{
printf("PosixTestProcess::terminate(): Failed to kill process: %s\n", e.what());
}
}
}
void PosixTestProcess::cleanup (void)
{
m_caseListWriter.stop();
m_logReader.stop();
// \note Info buffer must be canceled before stopping pipe readers.
m_infoBuffer.cancel();
m_stdErrReader.stop();
m_stdOutReader.stop();
// Reset info buffer.
m_infoBuffer.clear();
if (m_process)
{
try
{
if (m_process->isRunning())
{
m_process->kill();
m_process->waitForFinish();
}
}
catch (const de::ProcessError& e)
{
printf("PosixTestProcess::stop(): Failed to kill process: %s\n", e.what());
}
delete m_process;
m_process = DE_NULL;
}
}
bool PosixTestProcess::isRunning (void)
{
if (m_process)
return m_process->isRunning();
else
return false;
}
int PosixTestProcess::getExitCode (void) const
{
if (m_process)
return m_process->getExitCode();
else
return -1;
}
int PosixTestProcess::readTestLog (deUint8* dst, int numBytes)
{
if (!m_logReader.isRunning())
{
if (deGetMicroseconds() - m_processStartTime > LOG_FILE_TIMEOUT*1000)
{
// Timeout, kill process.
terminate();
return 0; // \todo [2013-08-13 pyry] Throw exception?
}
if (!deFileExists(m_logFileName.c_str()))
return 0;
// Start reader.
m_logReader.start(m_logFileName.c_str());
}
DE_ASSERT(m_logReader.isRunning());
return m_logReader.read(dst, numBytes);
}
} // xs