| /*------------------------------------------------------------------------- |
| * 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) |
| { |
| 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<uint8_t> 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 |