backend: add two command line arguments to backend test (#9329)

`--headless_only`
Headless-only mode indicates that we will only use headless
swapchains.  This is particularly useful if we want to test in
a CI (continuous integration) environment.

`--ci`
Having CONTINUOUS_INTEGRATION as an OS will allow us to have
CI-only temporary exceptions. Though it's expected that some
exceptions will be unfixable and will remain as workarounds.
This commit is contained in:
Powei Feng
2025-10-16 11:54:38 -07:00
committed by GitHub
parent a068d3df79
commit fa02a7fa3b
9 changed files with 81 additions and 42 deletions

View File

@@ -23,13 +23,17 @@
namespace test {
Backend parseArgumentsForBackend(int argc, char* argv[]) {
Backend backend = Backend::OPENGL;
TestArguments parseArguments(int argc, char* argv[]) {
TestArguments arguments = {};
arguments.backend = Backend::OPENGL;
// The first colon in OPTSTR turns on silent error reporting. This is important, as the
// arguments may also contain gtest parameters we don't know about.
static constexpr const char* OPTSTR = ":a:";
static constexpr const char* OPTSTR = ":a:kc";
static const struct option OPTIONS[] = {
{ "api", required_argument, nullptr, 'a' },
{ "headless_only", no_argument, nullptr, 'k' },
{ "ci", no_argument, nullptr, 'c' },
{ nullptr, 0, nullptr, 0 } // termination of the option list
};
@@ -41,13 +45,13 @@ Backend parseArgumentsForBackend(int argc, char* argv[]) {
switch (opt) {
case 'a':
if (arg == "opengl") {
backend = Backend::OPENGL;
arguments.backend = Backend::OPENGL;
} else if (arg == "vulkan") {
backend = Backend::VULKAN;
arguments.backend = Backend::VULKAN;
} else if (arg == "metal") {
backend = Backend::METAL;
arguments.backend = Backend::METAL;
} else if (arg == "webgpu") {
backend = Backend::WEBGPU;
arguments.backend = Backend::WEBGPU;
} else {
std::cerr << "Unrecognized target API. Must be 'opengl'|'vulkan'|'metal'|'webgpu'."
<< std::endl
@@ -55,10 +59,16 @@ Backend parseArgumentsForBackend(int argc, char* argv[]) {
<< std::endl;
}
break;
case 'k':
arguments.headlessOnly = true;
break;
case 'c':
arguments.isContinuousIntegration = true;
break;
}
}
return backend;
return arguments;
}
} // namespace test

View File

@@ -41,6 +41,7 @@ enum class OperatingSystem: uint8_t {
LINUX = 2,
// Also represents iOS phones.
APPLE = 3,
CONTINUOUS_INTEGRATION = 4,
// TODO: When tests support windows add it here.
};
@@ -73,11 +74,17 @@ void initTests(Backend backend, OperatingSystem operatingSystem, bool isMobile,
*/
int runTests();
struct TestArguments {
Backend backend;
bool headlessOnly = false;
bool isContinuousIntegration = false;
};
/**
* A utility method that can be invoked by test runners to parse arguments.
* Looks through the provided command-line arguments and finds any -a <backend> arguments.
*/
Backend parseArgumentsForBackend(int argc, char* argv[]);
TestArguments parseArguments(int argc, char* argv[]);
} // namespace test

View File

@@ -43,7 +43,10 @@ std::array<test::Backend, 2> const VALID_BACKENDS{
}// namespace
int main(int argc, char* argv[]) {
auto backend = test::parseArgumentsForBackend(argc, argv);
const auto arguments = test::parseArguments(argc, argv);
const auto backend = arguments.backend;
// Note that Linux is headless-only.
if (!std::any_of(VALID_BACKENDS.begin(), VALID_BACKENDS.end(),
[backend](test::Backend validBackend) { return backend == validBackend; })) {
@@ -51,6 +54,8 @@ int main(int argc, char* argv[]) {
return 1;
}
test::initTests(backend, test::OperatingSystem::LINUX, false, argc, argv);
const auto operatingSystem = arguments.isContinuousIntegration ?
test::OperatingSystem::CONTINUOUS_INTEGRATION : test::OperatingSystem::LINUX;
test::initTests(backend, operatingSystem, false, argc, argv);
return test::runTests();
}

View File

@@ -32,29 +32,34 @@ test::NativeView getNativeView() {
@interface AppDelegate : NSObject <NSApplicationDelegate>
@property test::Backend backend;
@property bool headlessOnly;
@end
@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
NSView* view = [self createView];
if (self.backend == test::Backend::OPENGL) {
nativeView.ptr = (void*) view;
if (self.headlessOnly) {
nativeView.ptr = nullptr;
nativeView.width = test::WINDOW_WIDTH;
nativeView.height = test::WINDOW_HEIGHT;
} else {
NSView* view = [self createView];
switch (self.backend) {
case test::Backend::OPENGL:
nativeView.ptr = (void*) view;
break;
case test::Backend::METAL:
case test::Backend::VULKAN:
case test::Backend::WEBGPU:
case test::Backend::NOOP:
nativeView.ptr = (void*) view.layer;
break;
}
CGSize drawableSize = ((CAMetalLayer*) view.layer).drawableSize;
nativeView.width = static_cast<size_t>(drawableSize.width);
nativeView.height = static_cast<size_t>(drawableSize.height);
}
if (self.backend == test::Backend::METAL) {
nativeView.ptr = (void*) view.layer;
}
if (self.backend == test::Backend::VULKAN) {
nativeView.ptr = (void*) view.layer;
}
if (self.backend == test::Backend::WEBGPU) {
nativeView.ptr = (void*) view.layer;
}
CGSize drawableSize = ((CAMetalLayer*) view.layer).drawableSize;
nativeView.width = static_cast<size_t>(drawableSize.width);
nativeView.height = static_cast<size_t>(drawableSize.height);
exit(test::runTests());
}
@@ -100,11 +105,25 @@ test::NativeView getNativeView() {
@end
int main(int argc, char* argv[]) {
auto backend = test::parseArgumentsForBackend(argc, argv);
test::initTests(backend, test::OperatingSystem::APPLE, false, argc, argv);
AppDelegate* delegate = [AppDelegate new];
delegate.backend = backend;
const auto arguments = test::parseArguments(argc, argv);
const auto operatingSystem = arguments.isContinuousIntegration ?
test::OperatingSystem::CONTINUOUS_INTEGRATION : test::OperatingSystem::APPLE;
test::initTests(arguments.backend, operatingSystem, false, argc, argv);
NSApplication* app = [NSApplication sharedApplication];
AppDelegate* delegate = [AppDelegate new];
delegate.backend = arguments.backend;
delegate.headlessOnly = arguments.headlessOnly;
[app setDelegate:delegate];
if (arguments.headlessOnly) {
// In headless mode, we don't want to start the NSApplication event loop.
// Instead, we can manually "finish" launching the app, which will trigger the tests to run.
[app finishLaunching];
[delegate applicationDidFinishLaunching:nil];
// The line above calls exit(), so we should not reach here.
return 0;
}
[app run];
}

View File

@@ -158,7 +158,7 @@ TEST_F(BlitTest, ColorMagnify) {
constexpr int kNumLevels = 3;
// Create a SwapChain and make it current. We don't really use it so the res doesn't matter.
auto swapChain = mCleanup.add(api.createSwapChainHeadless(256, 256, 0));
auto swapChain = mCleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
// Create a source texture.
@@ -222,7 +222,7 @@ TEST_F(BlitTest, ColorMinify) {
constexpr int kNumLevels = 3;
// Create a SwapChain and make it current. We don't really use it so the res doesn't matter.
auto swapChain = mCleanup.add(api.createSwapChainHeadless(256, 256, 0));
auto swapChain = mCleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
// Create a source texture.
@@ -371,7 +371,7 @@ TEST_F(BlitTest, Blit2DTextureArray) {
constexpr int kDstTexLayer = 0;
// Create a SwapChain and make it current. We don't really use it so the res doesn't matter.
auto swapChain = mCleanup.add(api.createSwapChainHeadless(256, 256, 0));
auto swapChain = mCleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
// Create a source texture.
@@ -437,7 +437,7 @@ TEST_F(BlitTest, BlitRegion) {
constexpr int kDstLevel = 0;
// Create a SwapChain and make it current. We don't really use it so the res doesn't matter.
auto swapChain = mCleanup.add(api.createSwapChainHeadless(256, 256, 0));
auto swapChain = mCleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
// Create a source texture.

View File

@@ -103,7 +103,7 @@ TEST_F(BackendTest, FrameCompletedCallback) {
Cleanup cleanup(api);
// Create a SwapChain.
auto swapChain = cleanup.add(api.createSwapChainHeadless(256, 256, 0));
auto swapChain = cleanup.add(createSwapChain());
int callbackCountA = 0;
api.setFrameCompletedCallback(swapChain, nullptr,

View File

@@ -385,8 +385,7 @@ TEST_F(ReadPixelsTest, ReadPixelsPerformance) {
Cleanup cleanup(api);
// Create a platform-specific SwapChain and make it current.
auto swapChain = cleanup.add(
api.createSwapChainHeadless(renderTargetSize, renderTargetSize, 0));
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
Shader shader = SharedShaders::makeShader(api, cleanup, ShaderRequest{

View File

@@ -70,7 +70,7 @@ TEST_F(BackendTest, ScissorViewportRegion) {
// executeCommands().
{
// Create a SwapChain and make it current. We don't really use it so the res doesn't matter.
auto swapChain = cleanup.add(api.createSwapChainHeadless(256, 256, 0));
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
Shader shader = SharedShaders::makeShader(api, cleanup,
@@ -149,7 +149,7 @@ TEST_F(BackendTest, ScissorViewportEdgeCases) {
// executeCommands().
{
// Create a SwapChain and make it current. We don't really use it so the res doesn't matter.
auto swapChain = cleanup.add(api.createSwapChainHeadless(256, 256, 0));
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
Shader shader = SharedShaders::makeShader(api, cleanup, ShaderRequest{

View File

@@ -33,8 +33,7 @@ TEST_F(BackendTest, TestTemplate) {
auto& api = getDriverApi();
Cleanup cleanup(api);
auto swapChain =
cleanup.add(api.createSwapChainHeadless(kRenderTargetSize, kRenderTargetSize, 0));
auto swapChain = cleanup.add(createSwapChain());
api.makeCurrent(swapChain, swapChain);
RenderTargetHandle renderTarget = cleanup.add(api.createDefaultRenderTarget());