/*------------------------------------------------------------------------- * OpenGL Conformance Test Suite * ----------------------------- * * Copyright (c) 2016 Google Inc. * Copyright (c) 2016 The Khronos Group Inc. * * 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 CTS runner. */ /*-------------------------------------------------------------------*/ #include "glcTestRunner.hpp" #include "deFilePath.hpp" #include "deStringUtil.hpp" #include "deUniquePtr.hpp" #include "glcConfigList.hpp" #include "qpXmlWriter.h" #include "tcuApp.hpp" #include "tcuCommandLine.hpp" #include "tcuTestLog.hpp" #include "tcuTestSessionExecutor.hpp" #include namespace glcts { using std::vector; using std::string; // RunSession class RunSession { public: RunSession(tcu::Platform& platform, tcu::Archive& archive, const int numArgs, const char* const* args) : m_cmdLine(numArgs, args) , m_log(m_cmdLine.getLogFileName(), m_cmdLine.getLogFlags()) , m_app(platform, archive, m_log, m_cmdLine) { } inline bool iterate(void) { return m_app.iterate(); } inline const tcu::TestRunStatus& getResult(void) const { return m_app.getResult(); } private: tcu::CommandLine m_cmdLine; tcu::TestLog m_log; tcu::App m_app; }; static void appendConfigArgs(const Config& config, std::vector& args, const char* fboConfig) { if (fboConfig != NULL) { args.push_back(string("--deqp-gl-config-name=") + fboConfig); args.push_back("--deqp-surface-type=fbo"); } if (config.type != CONFIGTYPE_DEFAULT) { // \todo [2013-05-06 pyry] Test all surface types for some configs? if (fboConfig == NULL) { if (config.surfaceTypes & SURFACETYPE_WINDOW) args.push_back("--deqp-surface-type=window"); else if (config.surfaceTypes & SURFACETYPE_PBUFFER) args.push_back("--deqp-surface-type=pbuffer"); else if (config.surfaceTypes & SURFACETYPE_PIXMAP) args.push_back("--deqp-surface-type=pixmap"); } args.push_back(string("--deqp-gl-config-id=") + de::toString(config.id)); if (config.type == CONFIGTYPE_EGL) args.push_back("--deqp-gl-context-type=egl"); else if (config.type == CONFIGTYPE_WGL) args.push_back("--deqp-gl-context-type=wgl"); } } typedef struct configInfo { deInt32 redBits; deInt32 greenBits; deInt32 blueBits; deInt32 alphaBits; deInt32 depthBits; deInt32 stencilBits; deInt32 samples; } configInfo; static configInfo parseConfigBitsFromName(const char* configName) { configInfo cfgInfo; static const struct { const char* name; int redBits; int greenBits; int blueBits; int alphaBits; } colorCfgs[] = { { "rgba8888", 8, 8, 8, 8 }, { "rgb565", 5, 6, 5, 0 }, }; for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(colorCfgs); ndx++) { if (!strncmp(configName, colorCfgs[ndx].name, strlen(colorCfgs[ndx].name))) { cfgInfo.redBits = colorCfgs[ndx].redBits; cfgInfo.greenBits = colorCfgs[ndx].greenBits; cfgInfo.blueBits = colorCfgs[ndx].blueBits; cfgInfo.alphaBits = colorCfgs[ndx].alphaBits; configName += strlen(colorCfgs[ndx].name); break; } } static const struct { const char* name; int depthBits; } depthCfgs[] = { { "d0", 0 }, { "d24", 24 }, }; for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(depthCfgs); ndx++) { if (!strncmp(configName, depthCfgs[ndx].name, strlen(depthCfgs[ndx].name))) { cfgInfo.depthBits = depthCfgs[ndx].depthBits; configName += strlen(depthCfgs[ndx].name); break; } } static const struct { const char* name; int stencilBits; } stencilCfgs[] = { { "s0", 0 }, { "s8", 8 }, }; for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(stencilCfgs); ndx++) { if (!strncmp(configName, stencilCfgs[ndx].name, strlen(stencilCfgs[ndx].name))) { cfgInfo.stencilBits = stencilCfgs[ndx].stencilBits; configName += strlen(stencilCfgs[ndx].name); break; } } static const struct { const char* name; int samples; } multiSampleCfgs[] = { { "ms0", 0 }, { "ms4", 4 }, }; for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(multiSampleCfgs); ndx++) { if (!strncmp(configName, multiSampleCfgs[ndx].name, strlen(multiSampleCfgs[ndx].name))) { cfgInfo.samples = multiSampleCfgs[ndx].samples; configName += strlen(multiSampleCfgs[ndx].name); break; } } return cfgInfo; } static const char* getApiName(glu::ApiType apiType) { if (apiType == glu::ApiType::es(2, 0)) return "gles2"; else if (apiType == glu::ApiType::es(3, 0)) return "gles3"; else if (apiType == glu::ApiType::es(3, 1)) return "gles31"; else if (apiType == glu::ApiType::es(3, 2)) return "gles32"; else if (apiType == glu::ApiType::core(3, 0)) return "gl30"; else if (apiType == glu::ApiType::core(3, 1)) return "gl31"; else if (apiType == glu::ApiType::core(3, 2)) return "gl32"; else if (apiType == glu::ApiType::core(3, 3)) return "gl33"; else if (apiType == glu::ApiType::core(4, 0)) return "gl40"; else if (apiType == glu::ApiType::core(4, 1)) return "gl41"; else if (apiType == glu::ApiType::core(4, 2)) return "gl42"; else if (apiType == glu::ApiType::core(4, 3)) return "gl43"; else if (apiType == glu::ApiType::core(4, 4)) return "gl44"; else if (apiType == glu::ApiType::core(4, 5)) return "gl45"; else if (apiType == glu::ApiType::core(4, 6)) return "gl46"; else throw std::runtime_error("Unknown context type"); } static const string getCaseListFileOption(const char* mustpassDir, const char* apiName, const char* mustpassName) { #if DE_OS == DE_OS_ANDROID const string case_list_option = "--deqp-caselist-resource="; #else const string case_list_option = "--deqp-caselist-file="; #endif return case_list_option + mustpassDir + apiName + "-" + mustpassName + ".txt"; } static const string getLogFileName(const char* apiName, const char* configName, const int iterId, const int runId, const int width, const int height, const int seed) { string res = string("config-") + apiName + "-" + configName + "-cfg-" + de::toString(iterId) + "-run-" + de::toString(runId) + "-width-" + de::toString(width) + "-height-" + de::toString(height); if (seed != -1) { res += "-seed-" + de::toString(seed); } res += ".qpa"; return res; } static void getBaseOptions(std::vector& args, const char* mustpassDir, const char* apiName, const char* configName, const char* screenRotation, int width, int height) { args.push_back(getCaseListFileOption(mustpassDir, apiName, configName)); args.push_back(string("--deqp-screen-rotation=") + screenRotation); args.push_back(string("--deqp-surface-width=") + de::toString(width)); args.push_back(string("--deqp-surface-height=") + de::toString(height)); args.push_back("--deqp-watchdog=disable"); } static bool isGLConfigCompatible(configInfo cfgInfo, const AOSPConfig& config) { return cfgInfo.redBits == config.redBits && cfgInfo.greenBits == config.greenBits && cfgInfo.blueBits == config.blueBits && cfgInfo.alphaBits == config.alphaBits && cfgInfo.depthBits == config.depthBits && cfgInfo.stencilBits == config.stencilBits && cfgInfo.samples == config.samples; } static void getTestRunsForAOSPEGL(vector& runs, const ConfigList& configs) { #include "glcAospMustpassEgl.hpp" for (int i = 0; i < DE_LENGTH_OF_ARRAY(aosp_mustpass_egl_first_cfg); ++i) { configInfo cfgInfo = parseConfigBitsFromName(aosp_mustpass_egl_first_cfg[i].glConfigName); vector::const_iterator cfgIter; for (cfgIter = configs.aospConfigs.begin(); cfgIter != configs.aospConfigs.end(); ++cfgIter) { // find first compatible config if ((*cfgIter).type == CONFIGTYPE_EGL && isGLConfigCompatible(cfgInfo, *cfgIter)) { break; } } if (cfgIter == configs.aospConfigs.end()) { // No suitable configuration found. Skipping EGL tests continue; } const char* apiName = "egl"; const int width = aosp_mustpass_egl_first_cfg[i].surfaceWidth; const int height = aosp_mustpass_egl_first_cfg[i].surfaceHeight; TestRunParams params; params.logFilename = getLogFileName(apiName, aosp_mustpass_egl_first_cfg[i].configName, 1, i, width, height, -1); getBaseOptions(params.args, mustpassDir, apiName, aosp_mustpass_egl_first_cfg[i].configName, aosp_mustpass_egl_first_cfg[i].screenRotation, width, height); params.args.push_back(string("--deqp-gl-config-name=") + string(aosp_mustpass_egl_first_cfg[i].glConfigName)); runs.push_back(params); } } static void getTestRunsForAOSPES(vector& runs, const ConfigList& configs, const glu::ApiType apiType) { #include "glcAospMustpassEs.hpp" for (int i = 0; i < DE_LENGTH_OF_ARRAY(aosp_mustpass_es_first_cfg); ++i) { if (!glu::contextSupports(glu::ContextType(apiType), aosp_mustpass_es_first_cfg[i].apiType)) continue; configInfo cfgInfo = parseConfigBitsFromName(aosp_mustpass_es_first_cfg[i].glConfigName); vector::const_iterator cfgIter; for (cfgIter = configs.aospConfigs.begin(); cfgIter != configs.aospConfigs.end(); ++cfgIter) { // find first compatible config if (isGLConfigCompatible(cfgInfo, *cfgIter)) { break; } } if (cfgIter == configs.aospConfigs.end()) { TCU_FAIL(("No suitable configuration found for GL config " + de::toString(aosp_mustpass_es_first_cfg[i].glConfigName)) .c_str()); return; } const char* apiName = getApiName(aosp_mustpass_es_first_cfg[i].apiType); const int width = aosp_mustpass_es_first_cfg[i].surfaceWidth; const int height = aosp_mustpass_es_first_cfg[i].surfaceHeight; TestRunParams params; params.logFilename = getLogFileName(apiName, aosp_mustpass_es_first_cfg[i].configName, 1, i, width, height, -1); getBaseOptions(params.args, mustpassDir, apiName, aosp_mustpass_es_first_cfg[i].configName, aosp_mustpass_es_first_cfg[i].screenRotation, width, height); params.args.push_back(string("--deqp-gl-config-name=") + string(aosp_mustpass_es_first_cfg[i].glConfigName)); //set surface type if ((*cfgIter).surfaceTypes & SURFACETYPE_WINDOW) params.args.push_back("--deqp-surface-type=window"); else if ((*cfgIter).surfaceTypes & SURFACETYPE_PBUFFER) params.args.push_back("--deqp-surface-type=pbuffer"); else if ((*cfgIter).surfaceTypes & SURFACETYPE_PIXMAP) params.args.push_back("--deqp-surface-type=pixmap"); runs.push_back(params); } } static void getTestRunsForNoContext(glu::ApiType type, vector& runs, const ConfigList& configs, const RunParams* runParams, const int numRunParams, const char* mustpassDir) { vector::const_iterator cfgIter = configs.configs.begin(); for (int i = 0; i < numRunParams; ++i) { if (!glu::contextSupports(glu::ContextType(type), runParams[i].apiType)) continue; const char* apiName = getApiName(runParams[i].apiType); const int width = runParams[i].surfaceWidth; const int height = runParams[i].surfaceHeight; const int seed = runParams[i].baseSeed; TestRunParams params; params.logFilename = getLogFileName(apiName, runParams[i].configName, 1, i, width, height, seed); getBaseOptions(params.args, mustpassDir, apiName, runParams[i].configName, runParams[i].screenRotation, width, height); params.args.push_back(string("--deqp-base-seed=") + de::toString(seed)); appendConfigArgs(*cfgIter, params.args, runParams[i].fboConfig); runs.push_back(params); } } static void getTestRunsForNoContextES(glu::ApiType type, vector& runs, const ConfigList& configs) { #include "glcKhronosMustpassEsNocontext.hpp" getTestRunsForNoContext(type, runs, configs, khronos_mustpass_es_nocontext_first_cfg, DE_LENGTH_OF_ARRAY(khronos_mustpass_es_nocontext_first_cfg), mustpassDir); } static void getTestRunsForES(glu::ApiType type, const ConfigList& configs, vector& runs) { getTestRunsForAOSPEGL(runs, configs); getTestRunsForAOSPES(runs, configs, type); getTestRunsForNoContextES(type, runs, configs); #include "glcKhronosMustpassEs.hpp" for (vector::const_iterator cfgIter = configs.configs.begin(); cfgIter != configs.configs.end(); ++cfgIter) { const bool isFirst = cfgIter == configs.configs.begin(); const int numRunParams = isFirst ? DE_LENGTH_OF_ARRAY(khronos_mustpass_es_first_cfg) : DE_LENGTH_OF_ARRAY(khronos_mustpass_es_other_cfg); const RunParams* runParams = isFirst ? khronos_mustpass_es_first_cfg : khronos_mustpass_es_other_cfg; for (int runNdx = 0; runNdx < numRunParams; runNdx++) { if (!glu::contextSupports(glu::ContextType(type), runParams[runNdx].apiType)) continue; const char* apiName = getApiName(runParams[runNdx].apiType); const int width = runParams[runNdx].surfaceWidth; const int height = runParams[runNdx].surfaceHeight; const int seed = runParams[runNdx].baseSeed; TestRunParams params; params.logFilename = getLogFileName(apiName, runParams[runNdx].configName, cfgIter->id, runNdx, width, height, seed); getBaseOptions(params.args, mustpassDir, apiName, runParams[runNdx].configName, runParams[runNdx].screenRotation, width, height); params.args.push_back(string("--deqp-base-seed=") + de::toString(seed)); appendConfigArgs(*cfgIter, params.args, runParams[runNdx].fboConfig); runs.push_back(params); } } } static void getTestRunsForNoContextGL(glu::ApiType type, vector& runs, const ConfigList& configs) { #include "glcKhronosMustpassGlNocontext.hpp" getTestRunsForNoContext(type, runs, configs, khronos_mustpass_gl_nocontext_first_cfg, DE_LENGTH_OF_ARRAY(khronos_mustpass_gl_nocontext_first_cfg), mustpassDir); } static void getTestRunsForGL(glu::ApiType type, const ConfigList& configs, vector& runs) { getTestRunsForNoContextGL(type, runs, configs); #include "glcKhronosMustpassGl.hpp" for (vector::const_iterator cfgIter = configs.configs.begin(); cfgIter != configs.configs.end(); ++cfgIter) { const bool isFirst = cfgIter == configs.configs.begin(); const int numRunParams = isFirst ? DE_LENGTH_OF_ARRAY(khronos_mustpass_gl_first_cfg) : DE_LENGTH_OF_ARRAY(khronos_mustpass_gl_other_cfg); const RunParams* runParams = isFirst ? khronos_mustpass_gl_first_cfg : khronos_mustpass_gl_other_cfg; for (int runNdx = 0; runNdx < numRunParams; runNdx++) { if (type != runParams[runNdx].apiType) continue; const char* apiName = getApiName(runParams[runNdx].apiType); const int width = runParams[runNdx].surfaceWidth; const int height = runParams[runNdx].surfaceHeight; const int seed = runParams[runNdx].baseSeed; TestRunParams params; params.logFilename = getLogFileName(apiName, runParams[runNdx].configName, cfgIter->id, runNdx, width, height, seed); getBaseOptions(params.args, mustpassDir, apiName, runParams[runNdx].configName, runParams[runNdx].screenRotation, width, height); params.args.push_back(string("--deqp-base-seed=") + de::toString(seed)); appendConfigArgs(*cfgIter, params.args, runParams[runNdx].fboConfig); runs.push_back(params); } } } static void getTestRunParams(glu::ApiType type, const ConfigList& configs, vector& runs) { switch (type.getProfile()) { case glu::PROFILE_CORE: getTestRunsForGL(type, configs, runs); break; case glu::PROFILE_ES: getTestRunsForES(type, configs, runs); break; default: throw std::runtime_error("Unknown context type"); } } struct FileDeleter { void operator()(FILE* file) const { if (file) fclose(file); } }; struct XmlWriterDeleter { void operator()(qpXmlWriter* writer) const { if (writer) qpXmlWriter_destroy(writer); } }; static const char* getRunTypeName(glu::ApiType type) { if (type == glu::ApiType::es(2, 0)) return "es2"; else if (type == glu::ApiType::es(3, 0)) return "es3"; else if (type == glu::ApiType::es(3, 1)) return "es31"; else if (type == glu::ApiType::es(3, 2)) return "es32"; else if (type == glu::ApiType::core(3, 0)) return "gl30"; else if (type == glu::ApiType::core(3, 1)) return "gl31"; else if (type == glu::ApiType::core(3, 2)) return "gl32"; else if (type == glu::ApiType::core(3, 3)) return "gl33"; else if (type == glu::ApiType::core(4, 0)) return "gl40"; else if (type == glu::ApiType::core(4, 1)) return "gl41"; else if (type == glu::ApiType::core(4, 2)) return "gl42"; else if (type == glu::ApiType::core(4, 3)) return "gl43"; else if (type == glu::ApiType::core(4, 4)) return "gl44"; else if (type == glu::ApiType::core(4, 5)) return "gl45"; else if (type == glu::ApiType::core(4, 6)) return "gl46"; else return DE_NULL; } #define XML_CHECK(X) \ if (!(X)) \ throw tcu::Exception("Writing XML failed") static void writeRunSummary(const TestRunSummary& summary, const char* filename) { de::UniquePtr out(fopen(filename, "wb")); if (!out) throw tcu::Exception(string("Failed to open ") + filename); de::UniquePtr writer(qpXmlWriter_createFileWriter(out.get(), DE_FALSE, DE_FALSE)); if (!writer) throw std::bad_alloc(); XML_CHECK(qpXmlWriter_startDocument(writer.get())); { qpXmlAttribute attribs[2]; attribs[0] = qpSetStringAttrib("Type", getRunTypeName(summary.runType)); attribs[1] = qpSetBoolAttrib("Conformant", summary.isConformant ? DE_TRUE : DE_FALSE); XML_CHECK(qpXmlWriter_startElement(writer.get(), "Summary", DE_LENGTH_OF_ARRAY(attribs), attribs)); } // Config run { qpXmlAttribute attribs[1]; attribs[0] = qpSetStringAttrib("FileName", summary.configLogFilename.c_str()); XML_CHECK(qpXmlWriter_startElement(writer.get(), "Configs", DE_LENGTH_OF_ARRAY(attribs), attribs) && qpXmlWriter_endElement(writer.get(), "Configs")); } // Record test run parameters (log filename & command line). for (vector::const_iterator runIter = summary.runParams.begin(); runIter != summary.runParams.end(); ++runIter) { string cmdLine; qpXmlAttribute attribs[2]; for (vector::const_iterator argIter = runIter->args.begin(); argIter != runIter->args.end(); ++argIter) { if (argIter != runIter->args.begin()) cmdLine += " "; cmdLine += *argIter; } attribs[0] = qpSetStringAttrib("FileName", runIter->logFilename.c_str()); attribs[1] = qpSetStringAttrib("CmdLine", cmdLine.c_str()); XML_CHECK(qpXmlWriter_startElement(writer.get(), "TestRun", DE_LENGTH_OF_ARRAY(attribs), attribs) && qpXmlWriter_endElement(writer.get(), "TestRun")); } XML_CHECK(qpXmlWriter_endElement(writer.get(), "Summary")); XML_CHECK(qpXmlWriter_endDocument(writer.get())); } #undef XML_CHECK TestRunner::TestRunner(tcu::Platform& platform, tcu::Archive& archive, const char* logDirPath, glu::ApiType type, deUint32 flags) : m_platform(platform) , m_archive(archive) , m_logDirPath(logDirPath) , m_type(type) , m_flags(flags) , m_iterState(ITERATE_INIT) , m_curSession(DE_NULL) , m_sessionsExecuted(0) , m_sessionsPassed(0) , m_sessionsFailed(0) { } TestRunner::~TestRunner(void) { delete m_curSession; } bool TestRunner::iterate(void) { switch (m_iterState) { case ITERATE_INIT: init(); m_iterState = (m_sessionIter != m_runSessions.end()) ? ITERATE_INIT_SESSION : ITERATE_DEINIT; return true; case ITERATE_DEINIT: deinit(); m_iterState = ITERATE_INIT; return false; case ITERATE_INIT_SESSION: DE_ASSERT(m_sessionIter != m_runSessions.end()); initSession(*m_sessionIter); if (m_flags & PRINT_SUMMARY) m_iterState = ITERATE_DEINIT_SESSION; else m_iterState = ITERATE_ITERATE_SESSION; return true; case ITERATE_DEINIT_SESSION: deinitSession(); ++m_sessionIter; m_iterState = (m_sessionIter != m_runSessions.end()) ? ITERATE_INIT_SESSION : ITERATE_DEINIT; return true; case ITERATE_ITERATE_SESSION: if (!iterateSession()) m_iterState = ITERATE_DEINIT_SESSION; return true; default: DE_ASSERT(false); return false; } } void TestRunner::init(void) { DE_ASSERT(m_runSessions.empty() && m_summary.runParams.empty()); tcu::print("Running %s conformance\n", glu::getApiTypeDescription(m_type)); m_summary.runType = m_type; // Get list of configs to test. ConfigList configList; getDefaultConfigList(m_platform, m_type, configList); tcu::print(" found %d compatible and %d excluded configs\n", (int)configList.configs.size(), (int)configList.excludedConfigs.size()); // Config list run. { const char* configLogFilename = "configs.qpa"; TestRunParams configRun; configRun.logFilename = configLogFilename; configRun.args.push_back("--deqp-case=CTS-Configs.*"); m_runSessions.push_back(configRun); m_summary.configLogFilename = configLogFilename; } // Conformance test type specific runs getTestRunParams(m_type, configList, m_runSessions); // Record run params for summary. for (std::vector::const_iterator runIter = m_runSessions.begin() + 1; runIter != m_runSessions.end(); ++runIter) m_summary.runParams.push_back(*runIter); // Session iterator m_sessionIter = m_runSessions.begin(); } void TestRunner::deinit(void) { // Print out totals. bool isConformant = m_sessionsExecuted == m_sessionsPassed; DE_ASSERT(m_sessionsExecuted == m_sessionsPassed + m_sessionsFailed); tcu::print("\n%d/%d sessions passed, conformance test %s\n", m_sessionsPassed, m_sessionsExecuted, isConformant ? "PASSED" : "FAILED"); m_summary.isConformant = isConformant; // Write out summary. writeRunSummary(m_summary, de::FilePath::join(m_logDirPath, "cts-run-summary.xml").getPath()); m_runSessions.clear(); m_summary.clear(); } void TestRunner::initSession(const TestRunParams& runParams) { DE_ASSERT(!m_curSession); tcu::print("\n Test run %d / %d\n", (int)(m_sessionIter - m_runSessions.begin() + 1), (int)m_runSessions.size()); // Compute final args for run. vector args(runParams.args); args.push_back(string("--deqp-log-filename=") + de::FilePath::join(m_logDirPath, runParams.logFilename).getPath()); if (!(m_flags & VERBOSE_IMAGES)) args.push_back("--deqp-log-images=disable"); if (!(m_flags & VERBOSE_SHADERS)) args.push_back("--deqp-log-shader-sources=disable"); std::ostringstream ostr; std::ostream_iterator out_it(ostr, ", "); std::copy(args.begin(), args.end(), out_it); tcu::print("\n Config: %s \n\n", ostr.str().c_str()); // Translate to argc, argv vector argv; argv.push_back("cts-runner"); // Dummy binary name for (vector::const_iterator i = args.begin(); i != args.end(); i++) argv.push_back(i->c_str()); // Create session m_curSession = new RunSession(m_platform, m_archive, (int)argv.size(), &argv[0]); } void TestRunner::deinitSession(void) { DE_ASSERT(m_curSession); // Collect results. // \note NotSupported is treated as pass. const tcu::TestRunStatus& result = m_curSession->getResult(); bool isOk = result.numExecuted == (result.numPassed + result.numNotSupported + result.numWarnings) && result.isComplete; DE_ASSERT(result.numExecuted == result.numPassed + result.numFailed + result.numNotSupported + result.numWarnings); m_sessionsExecuted += 1; (isOk ? m_sessionsPassed : m_sessionsFailed) += 1; delete m_curSession; m_curSession = DE_NULL; } inline bool TestRunner::iterateSession(void) { return m_curSession->iterate(); } } // glcts