blob: 5685bfb7ef359b7285942eceba9583d6ab9a477f [file] [log] [blame]
/*
* Copyright © 2020 Valve Corporation
*
* SPDX-License-Identifier: MIT
*/
#include "aco_ir.h"
#include <llvm-c/Target.h>
#include "framework.h"
#include <getopt.h>
#include <map>
#include <set>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <string>
#include <unistd.h>
#include <vector>
static const char* help_message =
"Usage: %s [-h] [-l --list] [--no-check] [TEST [TEST ...]]\n"
"\n"
"Run ACO unit test(s). If TEST is not provided, all tests are run.\n"
"\n"
"positional arguments:\n"
" TEST Run TEST. If TEST ends with a '.', run tests with names\n"
" starting with TEST. The test variant (after the '/') can\n"
" be omitted to run all variants\n"
"\n"
"optional arguments:\n"
" -h, --help Show this help message and exit.\n"
" -l --list List unit tests.\n"
" --no-check Print test output instead of checking it.\n";
std::map<std::string, TestDef> *tests = NULL;
FILE* output = NULL;
static TestDef current_test;
static unsigned tests_written = 0;
static FILE* checker_stdin = NULL;
static char* checker_stdin_data = NULL;
static size_t checker_stdin_size = 0;
static char* output_data = NULL;
static size_t output_size = 0;
static size_t output_offset = 0;
static char current_variant[64] = {0};
static std::set<std::string>* variant_filter = NULL;
bool test_failed = false;
bool test_skipped = false;
static char fail_message[256] = {0};
void
write_test()
{
if (!checker_stdin) {
/* not entirely correct, but shouldn't matter */
tests_written++;
return;
}
fflush(output);
if (output_offset == output_size && !test_skipped && !test_failed)
return;
char* data = output_data + output_offset;
uint32_t size = output_size - output_offset;
fwrite("test", 1, 4, checker_stdin);
fwrite(current_test.name, 1, strlen(current_test.name) + 1, checker_stdin);
fwrite(current_variant, 1, strlen(current_variant) + 1, checker_stdin);
fwrite(current_test.source_file, 1, strlen(current_test.source_file) + 1, checker_stdin);
if (test_failed || test_skipped) {
const char* res = test_failed ? "failed" : "skipped";
fwrite("\x01", 1, 1, checker_stdin);
fwrite(res, 1, strlen(res) + 1, checker_stdin);
fwrite(fail_message, 1, strlen(fail_message) + 1, checker_stdin);
} else {
fwrite("\x00", 1, 1, checker_stdin);
}
fwrite(&size, 4, 1, checker_stdin);
fwrite(data, 1, size, checker_stdin);
tests_written++;
output_offset += size;
}
bool
set_variant(const char* name)
{
if (variant_filter && !variant_filter->count(name))
return false;
write_test();
test_failed = false;
test_skipped = false;
strncpy(current_variant, name, sizeof(current_variant) - 1);
printf("Running '%s/%s'\n", current_test.name, name);
return true;
}
void
fail_test(const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
test_failed = true;
vsnprintf(fail_message, sizeof(fail_message), fmt, args);
va_end(args);
}
void
skip_test(const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
test_skipped = true;
vsnprintf(fail_message, sizeof(fail_message), fmt, args);
va_end(args);
}
void
run_test(TestDef def)
{
current_test = def;
output_data = NULL;
output_size = 0;
output_offset = 0;
test_failed = false;
test_skipped = false;
memset(current_variant, 0, sizeof(current_variant));
if (checker_stdin)
output = open_memstream(&output_data, &output_size);
else
output = stdout;
current_test.func();
write_test();
if (checker_stdin)
fclose(output);
free(output_data);
}
int
check_output(char** argv)
{
fflush(stdout);
fflush(stderr);
fclose(checker_stdin);
int stdin_pipe[2];
pipe(stdin_pipe);
pid_t child_pid = fork();
if (child_pid == -1) {
fprintf(stderr, "%s: fork() failed: %s\n", argv[0], strerror(errno));
return 99;
} else if (child_pid != 0) {
/* Evaluate test output externally using Python */
dup2(stdin_pipe[0], STDIN_FILENO);
close(stdin_pipe[0]);
close(stdin_pipe[1]);
execlp(ACO_TEST_PYTHON_BIN, ACO_TEST_PYTHON_BIN, ACO_TEST_SOURCE_DIR "/check_output.py",
NULL);
fprintf(stderr, "%s: execlp() failed: %s\n", argv[0], strerror(errno));
return 99;
} else {
/* Feed input data to the Python process. Writing large streams to
* stdin will block eventually, so this is done in a forked process
* to let the test checker process chunks of data as they arrive */
write(stdin_pipe[1], checker_stdin_data, checker_stdin_size);
close(stdin_pipe[0]);
close(stdin_pipe[1]);
_exit(0);
}
}
bool
match_test(std::string name, std::string pattern)
{
if (name.length() < pattern.length())
return false;
if (pattern.back() == '.')
name.resize(pattern.length());
return name == pattern;
}
int
main(int argc, char** argv)
{
int print_help = 0;
int do_list = 0;
int do_check = 1;
const struct option opts[] = {{"help", no_argument, &print_help, 1},
{"list", no_argument, &do_list, 1},
{"no-check", no_argument, &do_check, 0},
{NULL, 0, NULL, 0}};
int c;
while ((c = getopt_long(argc, argv, "hl", opts, NULL)) != -1) {
switch (c) {
case 'h': print_help = 1; break;
case 'l': do_list = 1; break;
case 0: break;
case '?':
default: fprintf(stderr, "%s: Invalid argument\n", argv[0]); return 99;
}
}
if (print_help) {
fprintf(stderr, help_message, argv[0]);
return 99;
}
if (!tests)
tests = new std::map<std::string, TestDef>;
if (do_list) {
for (auto test : *tests)
printf("%s\n", test.first.c_str());
return 99;
}
std::vector<std::pair<std::string, std::string>> names;
for (int i = optind; i < argc; i++) {
std::string name = argv[i];
std::string variant;
size_t pos = name.find('/');
if (pos != std::string::npos) {
variant = name.substr(pos + 1);
name = name.substr(0, pos);
}
names.emplace_back(std::pair<std::string, std::string>(name, variant));
}
if (do_check)
checker_stdin = open_memstream(&checker_stdin_data, &checker_stdin_size);
LLVMInitializeAMDGPUTargetInfo();
LLVMInitializeAMDGPUTarget();
LLVMInitializeAMDGPUTargetMC();
LLVMInitializeAMDGPUDisassembler();
aco::init();
for (auto pair : *tests) {
bool found = names.empty();
bool all_variants = names.empty();
std::set<std::string> variants;
for (const std::pair<std::string, std::string>& name : names) {
if (match_test(pair.first, name.first)) {
found = true;
if (name.second.empty())
all_variants = true;
else
variants.insert(name.second);
}
}
if (found) {
variant_filter = all_variants ? NULL : &variants;
printf("Running '%s'\n", pair.first.c_str());
run_test(pair.second);
}
}
if (!tests_written) {
fprintf(stderr, "%s: No matching tests\n", argv[0]);
return 99;
}
if (checker_stdin) {
printf("\n");
return check_output(argv);
} else {
printf("Tests ran\n");
return 99;
}
}