/*------------------------------------------------------------------------- * 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 #include 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) { int64_t pos = 0; while (m_run && pos < (int64_t)m_caseList.size()) { int64_t 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 tmpBuf(FILEREADER_TMP_BUFFER_SIZE); int64_t numRead = 0; while (!m_buf->isCanceled()) { deFileResult result = deFile_read(m_file, &tmpBuf[0], (int64_t)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; } } // namespace posix 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(uint8_t *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); } } // namespace xs