blob: 05db50b0336f2f52aa58ba33fd70a849b6698ab9 [file] [log] [blame]
//
// Copyright 2015 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// angle_deqp_gtest:
// dEQP and GoogleTest integration logic. Calls through to the random
// order executor.
#include <stdint.h>
#include <array>
#include <fstream>
#include <gtest/gtest.h>
#include "angle_deqp_libtester.h"
#include "common/Optional.h"
#include "common/angleutils.h"
#include "common/debug.h"
#include "common/platform.h"
#include "common/string_utils.h"
#include "common/system_utils.h"
#include "platform/Platform.h"
#include "tests/test_expectations/GPUTestConfig.h"
#include "tests/test_expectations/GPUTestExpectationsParser.h"
#include "util/test_utils.h"
namespace angle
{
namespace
{
bool gGlobalError = false;
bool gExpectError = false;
constexpr char kInfoTag[] = "*RESULT";
// Stored as globals to work around a Clang bug. http://crbug.com/951458
std::vector<std::string> gUnexpectedFailed;
std::vector<std::string> gUnexpectedPasses;
void HandlePlatformError(PlatformMethods *platform, const char *errorMessage)
{
if (!gExpectError)
{
FAIL() << errorMessage;
}
gGlobalError = true;
}
std::string DrawElementsToGoogleTestName(const std::string &dEQPName)
{
std::string gTestName = dEQPName.substr(dEQPName.find('.') + 1);
std::replace(gTestName.begin(), gTestName.end(), '.', '_');
// Occurs in some luminance tests
gTestName.erase(std::remove(gTestName.begin(), gTestName.end(), '-'), gTestName.end());
return gTestName;
}
const char *gCaseListSearchPaths[] = {
"/../../sdcard/chromium_tests_root/third_party/angle/third_party/VK-GL-CTS/src",
"/../../third_party/VK-GL-CTS/src",
"/../../third_party/angle/third_party/VK-GL-CTS/src",
};
const char *gTestExpectationsSearchPaths[] = {
"/../../src/tests/deqp_support/",
"/../../third_party/angle/src/tests/deqp_support/",
"/deqp_support/",
"/../../sdcard/chromium_tests_root/third_party/angle/src/tests/deqp_support/",
};
#define OPENGL_CTS_DIR(PATH) "/external/openglcts/data/mustpass/gles/" PATH
const char *gCaseListFiles[] = {OPENGL_CTS_DIR("aosp_mustpass/master/gles2-master.txt"),
OPENGL_CTS_DIR("aosp_mustpass/master/gles3-master.txt"),
OPENGL_CTS_DIR("aosp_mustpass/master/gles31-master.txt"),
"/android/cts/master/egl-master.txt",
OPENGL_CTS_DIR("khronos_mustpass/master/gles2-khr-master.txt"),
OPENGL_CTS_DIR("khronos_mustpass/master/gles3-khr-master.txt"),
OPENGL_CTS_DIR("khronos_mustpass/master/gles31-khr-master.txt")};
#undef OPENGL_CTS_DIR
const char *gTestExpectationsFiles[] = {
"deqp_gles2_test_expectations.txt", "deqp_gles3_test_expectations.txt",
"deqp_gles31_test_expectations.txt", "deqp_egl_test_expectations.txt",
"deqp_khr_gles2_test_expectations.txt", "deqp_khr_gles3_test_expectations.txt",
"deqp_khr_gles31_test_expectations.txt",
};
using APIInfo = std::pair<const char *, GPUTestConfig::API>;
constexpr APIInfo kEGLDisplayAPIs[] = {
{"angle-d3d9", GPUTestConfig::kAPID3D9},
{"angle-d3d11", GPUTestConfig::kAPID3D11},
{"angle-gl", GPUTestConfig::kAPIGLDesktop},
{"angle-gles", GPUTestConfig::kAPIGLES},
{"angle-null", GPUTestConfig::kAPIUnknown},
{"angle-vulkan", GPUTestConfig::kAPIVulkan},
{"angle-swiftshader", GPUTestConfig::kAPISwiftShader},
};
constexpr char kdEQPEGLString[] = "--deqp-egl-display-type=";
constexpr char kANGLEEGLString[] = "--use-angle=";
constexpr char kdEQPCaseString[] = "--deqp-case=";
std::array<char, 500> gCaseStringBuffer;
const APIInfo *gInitAPI = nullptr;
constexpr const char *gdEQPEGLConfigNameString = "--deqp-gl-config-name=";
// Default the config to RGBA8
const char *gEGLConfigName = "rgba8888d24s8";
// Returns the default API for a platform.
const char *GetDefaultAPIName()
{
#if defined(ANGLE_PLATFORM_WINDOWS)
return "angle-d3d11";
#elif defined(ANGLE_PLATFORM_APPLE) || defined(ANGLE_PLATFORM_LINUX)
return "angle-gl";
#elif defined(ANGLE_PLATFORM_ANDROID)
return "angle-gles";
#else
# error Unknown platform.
#endif
}
const APIInfo *FindAPIInfo(const std::string &arg)
{
for (auto &displayAPI : kEGLDisplayAPIs)
{
if (arg == displayAPI.first)
{
return &displayAPI;
}
}
return nullptr;
}
const APIInfo *GetDefaultAPIInfo()
{
const APIInfo *defaultInfo = FindAPIInfo(GetDefaultAPIName());
ASSERT(defaultInfo);
return defaultInfo;
}
std::string GetTestStatLine(const std::string &key, const std::string &value)
{
return std::string(kInfoTag) + ": " + key + ": " + value + "\n";
}
// During the CaseList initialization we cannot use the GTEST FAIL macro to quit the program because
// the initialization is called outside of tests the first time.
void Die()
{
exit(EXIT_FAILURE);
}
Optional<std::string> FindFileFromPaths(const char *paths[],
size_t numPaths,
const std::string &exeDir,
const std::string &searchFile)
{
for (size_t pathIndex = 0; pathIndex < numPaths; ++pathIndex)
{
const char *testPath = paths[pathIndex];
std::stringstream pathStringStream;
pathStringStream << exeDir << testPath << searchFile;
std::string path = pathStringStream.str();
std::ifstream inFile(path.c_str());
if (!inFile.fail())
{
inFile.close();
return Optional<std::string>(path);
}
}
return Optional<std::string>::Invalid();
}
Optional<std::string> FindCaseListPath(const std::string &exeDir, size_t testModuleIndex)
{
return FindFileFromPaths(gCaseListSearchPaths, ArraySize(gCaseListSearchPaths), exeDir,
gCaseListFiles[testModuleIndex]);
}
Optional<std::string> FindTestExpectationsPath(const std::string &exeDir, size_t testModuleIndex)
{
return FindFileFromPaths(gTestExpectationsSearchPaths, ArraySize(gTestExpectationsSearchPaths),
exeDir, gTestExpectationsFiles[testModuleIndex]);
}
class dEQPCaseList
{
public:
dEQPCaseList(size_t testModuleIndex);
struct CaseInfo
{
CaseInfo(const std::string &dEQPName, const std::string &gTestName, int expectation)
: mDEQPName(dEQPName), mGTestName(gTestName), mExpectation(expectation)
{}
std::string mDEQPName;
std::string mGTestName;
int mExpectation;
};
void initialize();
const CaseInfo &getCaseInfo(size_t caseIndex) const
{
ASSERT(mInitialized);
ASSERT(caseIndex < mCaseInfoList.size());
return mCaseInfoList[caseIndex];
}
size_t numCases() const
{
ASSERT(mInitialized);
return mCaseInfoList.size();
}
private:
std::vector<CaseInfo> mCaseInfoList;
GPUTestExpectationsParser mTestExpectationsParser;
size_t mTestModuleIndex;
bool mInitialized = false;
};
dEQPCaseList::dEQPCaseList(size_t testModuleIndex) : mTestModuleIndex(testModuleIndex) {}
void dEQPCaseList::initialize()
{
if (mInitialized)
{
return;
}
mInitialized = true;
std::string exeDir = GetExecutableDirectory();
Optional<std::string> caseListPath = FindCaseListPath(exeDir, mTestModuleIndex);
if (!caseListPath.valid())
{
std::cerr << "Failed to find case list file." << std::endl;
Die();
}
Optional<std::string> testExpectationsPath = FindTestExpectationsPath(exeDir, mTestModuleIndex);
if (!testExpectationsPath.valid())
{
std::cerr << "Failed to find test expectations file." << std::endl;
Die();
}
GPUTestConfig::API api = GetDefaultAPIInfo()->second;
// Set the API from the command line, or using the default platform API.
if (gInitAPI)
{
api = gInitAPI->second;
}
GPUTestConfig testConfig = GPUTestConfig(api);
#if !defined(ANGLE_PLATFORM_ANDROID)
// Note: These prints mess up parsing of test list when running on Android.
std::cout << "Using test config with:" << std::endl;
for (uint32_t condition : testConfig.getConditions())
{
const char *name = GetConditionName(condition);
if (name != nullptr)
{
std::cout << " " << name << std::endl;
}
}
#endif
if (!mTestExpectationsParser.loadTestExpectationsFromFile(testConfig,
testExpectationsPath.value()))
{
std::stringstream errorMsgStream;
for (const auto &message : mTestExpectationsParser.getErrorMessages())
{
errorMsgStream << std::endl << " " << message;
}
std::cerr << "Failed to load test expectations." << errorMsgStream.str() << std::endl;
Die();
}
std::ifstream caseListStream(caseListPath.value());
if (caseListStream.fail())
{
std::cerr << "Failed to load the case list." << std::endl;
Die();
}
while (!caseListStream.eof())
{
std::string inString;
std::getline(caseListStream, inString);
std::string dEQPName = TrimString(inString, kWhitespaceASCII);
if (dEQPName.empty())
continue;
std::string gTestName = DrawElementsToGoogleTestName(dEQPName);
if (gTestName.empty())
continue;
int expectation = mTestExpectationsParser.getTestExpectation(dEQPName);
mCaseInfoList.push_back(CaseInfo(dEQPName, gTestName, expectation));
}
std::stringstream unusedMsgStream;
bool anyUnused = false;
for (const auto &message : mTestExpectationsParser.getUnusedExpectationsMessages())
{
anyUnused = true;
unusedMsgStream << std::endl << " " << message;
}
if (anyUnused)
{
std::cerr << "Failed to validate test expectations." << unusedMsgStream.str() << std::endl;
Die();
}
}
template <size_t TestModuleIndex>
class dEQPTest : public testing::TestWithParam<size_t>
{
public:
static testing::internal::ParamGenerator<size_t> GetTestingRange()
{
return testing::Range<size_t>(0, GetCaseList().numCases());
}
static std::string GetCaseGTestName(size_t caseIndex)
{
const auto &caseInfo = GetCaseList().getCaseInfo(caseIndex);
return caseInfo.mGTestName;
}
static const dEQPCaseList &GetCaseList()
{
static dEQPCaseList sCaseList(TestModuleIndex);
sCaseList.initialize();
return sCaseList;
}
static void SetUpTestCase();
static void TearDownTestCase();
protected:
void runTest() const
{
if (sTestExceptionCount > 1)
{
std::cout << "Too many exceptions, skipping all remaining tests." << std::endl;
return;
}
const auto &caseInfo = GetCaseList().getCaseInfo(GetParam());
std::cout << caseInfo.mDEQPName << std::endl;
// Tests that crash exit the harness before collecting the result. To tally the number of
// crashed tests we track how many tests we "tried" to run.
sTestCount++;
if (caseInfo.mExpectation == GPUTestExpectationsParser::kGpuTestSkip)
{
sSkippedTestCount++;
std::cout << "Test skipped.\n";
return;
}
gExpectError = (caseInfo.mExpectation != GPUTestExpectationsParser::kGpuTestPass);
TestResult result = deqp_libtester_run(caseInfo.mDEQPName.c_str());
bool testSucceeded = countTestResultAndReturnSuccess(result);
// Check the global error flag for unexpected platform errors.
if (gGlobalError)
{
testSucceeded = false;
gGlobalError = false;
}
if (caseInfo.mExpectation == GPUTestExpectationsParser::kGpuTestPass)
{
EXPECT_TRUE(testSucceeded);
if (!testSucceeded)
{
gUnexpectedFailed.push_back(caseInfo.mDEQPName);
}
}
else if (testSucceeded)
{
std::cout << "Test expected to fail but passed!" << std::endl;
gUnexpectedPasses.push_back(caseInfo.mDEQPName);
}
}
bool countTestResultAndReturnSuccess(TestResult result) const
{
switch (result)
{
case TestResult::Pass:
sPassedTestCount++;
return true;
case TestResult::Fail:
sFailedTestCount++;
return false;
case TestResult::NotSupported:
sNotSupportedTestCount++;
return true;
case TestResult::Exception:
sTestExceptionCount++;
return false;
default:
std::cerr << "Unexpected test result code: " << static_cast<int>(result) << "\n";
return false;
}
}
static void PrintTestStats()
{
uint32_t crashedCount =
sTestCount - (sPassedTestCount + sFailedTestCount + sNotSupportedTestCount +
sTestExceptionCount + sSkippedTestCount);
std::cout << GetTestStatLine("Total", std::to_string(sTestCount));
std::cout << GetTestStatLine("Passed", std::to_string(sPassedTestCount));
std::cout << GetTestStatLine("Failed", std::to_string(sFailedTestCount));
std::cout << GetTestStatLine("Skipped", std::to_string(sSkippedTestCount));
std::cout << GetTestStatLine("Not Supported", std::to_string(sNotSupportedTestCount));
std::cout << GetTestStatLine("Exception", std::to_string(sTestExceptionCount));
std::cout << GetTestStatLine("Crashed", std::to_string(crashedCount));
if (!gUnexpectedPasses.empty())
{
std::cout << GetTestStatLine("Unexpected Passed Count",
std::to_string(gUnexpectedPasses.size()));
for (const std::string &testName : gUnexpectedPasses)
{
std::cout << GetTestStatLine("Unexpected Passed Tests", testName);
}
}
if (!gUnexpectedFailed.empty())
{
std::cout << GetTestStatLine("Unexpected Failed Count",
std::to_string(gUnexpectedFailed.size()));
for (const std::string &testName : gUnexpectedFailed)
{
std::cout << GetTestStatLine("Unexpected Failed Tests", testName);
}
}
}
static uint32_t sTestCount;
static uint32_t sPassedTestCount;
static uint32_t sFailedTestCount;
static uint32_t sTestExceptionCount;
static uint32_t sNotSupportedTestCount;
static uint32_t sSkippedTestCount;
};
template <size_t TestModuleIndex>
uint32_t dEQPTest<TestModuleIndex>::sTestCount = 0;
template <size_t TestModuleIndex>
uint32_t dEQPTest<TestModuleIndex>::sPassedTestCount = 0;
template <size_t TestModuleIndex>
uint32_t dEQPTest<TestModuleIndex>::sFailedTestCount = 0;
template <size_t TestModuleIndex>
uint32_t dEQPTest<TestModuleIndex>::sTestExceptionCount = 0;
template <size_t TestModuleIndex>
uint32_t dEQPTest<TestModuleIndex>::sNotSupportedTestCount = 0;
template <size_t TestModuleIndex>
uint32_t dEQPTest<TestModuleIndex>::sSkippedTestCount = 0;
// static
template <size_t TestModuleIndex>
void dEQPTest<TestModuleIndex>::SetUpTestCase()
{
sPassedTestCount = 0;
sFailedTestCount = 0;
sNotSupportedTestCount = 0;
sTestExceptionCount = 0;
sTestCount = 0;
sSkippedTestCount = 0;
gUnexpectedPasses.clear();
gUnexpectedFailed.clear();
std::vector<const char *> argv;
// Reserve one argument for the binary name.
argv.push_back("");
// Add init api.
const char *targetApi = gInitAPI ? gInitAPI->first : GetDefaultAPIName();
std::string apiArgString = std::string(kdEQPEGLString) + targetApi;
argv.push_back(apiArgString.c_str());
// Add config name
const char *targetConfigName = gEGLConfigName;
std::string configArgString = std::string(gdEQPEGLConfigNameString) + targetConfigName;
argv.push_back(configArgString.c_str());
// Init the platform.
if (!deqp_libtester_init_platform(static_cast<int>(argv.size()), argv.data(),
reinterpret_cast<void *>(&HandlePlatformError)))
{
std::cout << "Aborting test due to dEQP initialization error." << std::endl;
exit(1);
}
}
// static
template <size_t TestModuleIndex>
void dEQPTest<TestModuleIndex>::TearDownTestCase()
{
PrintTestStats();
deqp_libtester_shutdown_platform();
}
#define ANGLE_INSTANTIATE_DEQP_TEST_CASE(API, N) \
class dEQP : public dEQPTest<N> \
{}; \
TEST_P(dEQP, API) { runTest(); } \
\
INSTANTIATE_TEST_SUITE_P(, dEQP, dEQP::GetTestingRange(), \
[](const testing::TestParamInfo<size_t> &info) { \
return dEQP::GetCaseGTestName(info.param); \
})
#ifdef ANGLE_DEQP_GLES2_TESTS
ANGLE_INSTANTIATE_DEQP_TEST_CASE(GLES2, 0);
#endif
#ifdef ANGLE_DEQP_GLES3_TESTS
ANGLE_INSTANTIATE_DEQP_TEST_CASE(GLES3, 1);
#endif
#ifdef ANGLE_DEQP_GLES31_TESTS
ANGLE_INSTANTIATE_DEQP_TEST_CASE(GLES31, 2);
#endif
#ifdef ANGLE_DEQP_EGL_TESTS
ANGLE_INSTANTIATE_DEQP_TEST_CASE(EGL, 3);
#endif
#ifdef ANGLE_DEQP_KHR_GLES2_TESTS
ANGLE_INSTANTIATE_DEQP_TEST_CASE(KHR_GLES2, 4);
#endif
#ifdef ANGLE_DEQP_KHR_GLES3_TESTS
ANGLE_INSTANTIATE_DEQP_TEST_CASE(KHR_GLES3, 5);
#endif
#ifdef ANGLE_DEQP_KHR_GLES31_TESTS
ANGLE_INSTANTIATE_DEQP_TEST_CASE(KHR_GLES31, 6);
#endif
void HandleDisplayType(const char *displayTypeString)
{
std::stringstream argStream;
if (gInitAPI)
{
std::cout << "Cannot specify two EGL displays!" << std::endl;
exit(1);
}
if (strncmp(displayTypeString, "angle-", strlen("angle-")) != 0)
{
argStream << "angle-";
}
argStream << displayTypeString;
std::string arg = argStream.str();
gInitAPI = FindAPIInfo(arg);
if (!gInitAPI)
{
std::cout << "Unknown ANGLE back-end API: " << displayTypeString << std::endl;
exit(1);
}
}
void HandleEGLConfigName(const char *configNameString)
{
gEGLConfigName = configNameString;
}
// The --deqp-case flag takes a case expression that is parsed into a --gtest_filter. It converts
// the "dEQP" style names (functional.thing.*) into "GoogleTest" style names (functional_thing_*).
// Currently it does not handle multiple tests and multiple filters in different arguments.
void HandleCaseName(const char *caseString, int *argc, int argIndex, char **argv)
{
std::string googleTestName = DrawElementsToGoogleTestName(caseString);
gCaseStringBuffer.fill(0);
int bytesWritten = snprintf(gCaseStringBuffer.data(), gCaseStringBuffer.size() - 1,
"--gtest_filter=*%s", googleTestName.c_str());
if (bytesWritten <= 0 || static_cast<size_t>(bytesWritten) >= gCaseStringBuffer.size() - 1)
{
std::cout << "Error parsing test case string: " << caseString;
exit(1);
}
argv[argIndex] = gCaseStringBuffer.data();
}
void DeleteArg(int *argc, int argIndex, char **argv)
{
(*argc)--;
for (int moveIndex = argIndex; moveIndex < *argc; ++moveIndex)
{
argv[moveIndex] = argv[moveIndex + 1];
}
}
} // anonymous namespace
// Called from main() to process command-line arguments.
void InitTestHarness(int *argc, char **argv)
{
int argIndex = 0;
while (argIndex < *argc)
{
if (strncmp(argv[argIndex], kdEQPEGLString, strlen(kdEQPEGLString)) == 0)
{
HandleDisplayType(argv[argIndex] + strlen(kdEQPEGLString));
DeleteArg(argc, argIndex, argv);
}
else if (strncmp(argv[argIndex], kANGLEEGLString, strlen(kANGLEEGLString)) == 0)
{
HandleDisplayType(argv[argIndex] + strlen(kANGLEEGLString));
DeleteArg(argc, argIndex, argv);
}
else if (strncmp(argv[argIndex], gdEQPEGLConfigNameString,
strlen(gdEQPEGLConfigNameString)) == 0)
{
HandleEGLConfigName(argv[argIndex] + strlen(gdEQPEGLConfigNameString));
DeleteArg(argc, argIndex, argv);
}
else if (strncmp(argv[argIndex], kdEQPCaseString, strlen(kdEQPCaseString)) == 0)
{
HandleCaseName(argv[argIndex] + strlen(kdEQPCaseString), argc, argIndex, argv);
argIndex++;
}
else
{
argIndex++;
}
}
}
} // namespace angle