1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
|
#include "catch.hpp"
#include "duckdb.hpp"
#include "duckdb/common/string_util.hpp"
#include "duckdb/main/extension/generated_extension_loader.hpp"
#include "duckdb/parser/parser.hpp"
#include "sqllogic_test_runner.hpp"
#include "test_helpers.hpp"
#include "test_config.hpp"
#include <functional>
#include <string>
#include <system_error>
#include <vector>
using namespace duckdb;
using namespace std;
// code below traverses the test directory and makes individual test cases out
// of each script
static void listFiles(FileSystem &fs, const string &path, std::function<void(const string &)> cb) {
fs.ListFiles(path, [&](string fname, bool is_dir) {
string full_path = fs.JoinPath(path, fname);
if (is_dir) {
// recurse into directory
listFiles(fs, full_path, cb);
} else {
cb(full_path);
}
});
}
static bool endsWith(const string &mainStr, const string &toMatch) {
return (mainStr.size() >= toMatch.size() &&
mainStr.compare(mainStr.size() - toMatch.size(), toMatch.size(), toMatch) == 0);
}
template <bool AUTO_SWITCH_TEST_DIR = false>
static void testRunner() {
// this is an ugly hack that uses the test case name to pass the script file
// name if someone has a better idea...
const auto name = Catch::getResultCapture().getCurrentTestName();
const auto test_dir_path = TestDirectoryPath(); // can vary between tests, and does IO
auto &test_config = TestConfiguration::Get();
string initial_dbpath = test_config.GetInitialDBPath();
test_config.ProcessPath(initial_dbpath, name);
if (!initial_dbpath.empty()) {
auto test_path = StringUtil::Replace(initial_dbpath, test_dir_path, string());
test_path = StringUtil::Replace(test_path, "\\", "/");
auto components = StringUtil::Split(test_path, "/");
components.pop_back();
string total_path = test_dir_path;
for (auto &component : components) {
if (component.empty()) {
continue;
}
total_path = TestJoinPath(total_path, component);
TestCreateDirectory(total_path);
}
}
SQLLogicTestRunner runner(std::move(initial_dbpath));
runner.output_sql = Catch::getCurrentContext().getConfig()->outputSQL();
string prev_directory;
// We assume the test working dir for extensions to be one dir above the test/sql. Note that this is very hacky.
// however for now it suffices: we use it to run tests from out-of-tree extensions that are based on the extension
// template which adheres to this convention.
if (AUTO_SWITCH_TEST_DIR) {
prev_directory = TestGetCurrentDirectory();
std::size_t found = name.rfind("/test/sql");
if (found == std::string::npos) {
throw InvalidInputException("Failed to auto detect working dir for test '" + name +
"' because a non-standard path was used!");
}
auto test_working_dir = name.substr(0, found);
test_config.ChangeWorkingDirectory(test_working_dir);
}
// setup this test runner with Config-based env, then override with ephemerals (only WORKING_DIR at this point)
for (auto &kv : test_config.GetTestEnvMap()) {
runner.environment_variables[kv.first] = kv.second;
}
// Per runner vars
runner.environment_variables["WORKING_DIR"] = TestGetCurrentDirectory();
runner.environment_variables["TEST_NAME"] = name;
runner.environment_variables["TEST_NAME__NO_SLASH"] = StringUtil::Replace(name, "/", "_");
ErrorData error;
try {
runner.ExecuteFile(name);
} catch (std::exception &ex) {
error = ErrorData(ex);
}
if (AUTO_SWITCH_TEST_DIR) {
test_config.ChangeWorkingDirectory(prev_directory);
}
auto on_cleanup = test_config.OnCleanupCommand();
if (!on_cleanup.empty()) {
// perform clean-up if any is defined
try {
if (!runner.con) {
runner.Reconnect();
}
auto res = runner.con->Query(on_cleanup);
if (res->HasError()) {
res->GetErrorObject().Throw();
}
} catch (std::exception &ex) {
string cleanup_failure = "Error while running clean-up routine:\n";
ErrorData error(ex);
cleanup_failure += error.Message();
FAIL(cleanup_failure);
}
}
// clear test directory after running tests
ClearTestDirectory();
if (error.HasError()) {
FAIL(error.Message());
}
}
static string ParseGroupFromPath(string file) {
string extension = "";
if (file.find(".test_slow") != std::string::npos) {
// "slow" in the name indicates a slow test (i.e. only run as part of allunit)
extension = "[.]";
}
if (file.find(".test_coverage") != std::string::npos) {
// "coverage" in the name indicates a coverage test (i.e. only run as part of coverage)
return "[coverage][.]";
}
// move backwards to the last slash
int group_begin = -1, group_end = -1;
for (idx_t i = file.size(); i > 0; i--) {
if (file[i - 1] == '/' || file[i - 1] == '\\') {
if (group_end == -1) {
group_end = i - 1;
} else {
group_begin = i;
return "[" + file.substr(group_begin, group_end - group_begin) + "]" + extension;
}
}
}
if (group_end == -1) {
return "[" + file + "]" + extension;
}
return "[" + file.substr(0, group_end) + "]" + extension;
}
namespace duckdb {
void RegisterSqllogictests() {
vector<string> excludes = {
// tested separately
"test/select1.test", "test/select2.test", "test/select3.test", "test/select4.test",
// feature not supported
"evidence/slt_lang_replace.test", // INSERT OR REPLACE
"evidence/slt_lang_reindex.test", // REINDEX
"evidence/slt_lang_update.test", // Multiple assignments to same column "x" in update
"evidence/slt_lang_createtrigger.test", // TRIGGER
"evidence/slt_lang_droptrigger.test", // TRIGGER
// no + for varchar columns
"test/index/random/10/slt_good_14.test", "test/index/random/10/slt_good_1.test",
"test/index/random/10/slt_good_0.test", "test/index/random/10/slt_good_12.test",
"test/index/random/10/slt_good_6.test", "test/index/random/10/slt_good_13.test",
"test/index/random/10/slt_good_5.test", "test/index/random/10/slt_good_10.test",
"test/index/random/10/slt_good_11.test", "test/index/random/10/slt_good_4.test",
"test/index/random/10/slt_good_8.test", "test/index/random/10/slt_good_3.test",
"test/index/random/10/slt_good_2.test", "test/index/random/100/slt_good_1.test",
"test/index/random/100/slt_good_0.test", "test/index/random/1000/slt_good_0.test",
"test/index/random/1000/slt_good_7.test", "test/index/random/1000/slt_good_6.test",
"test/index/random/1000/slt_good_5.test", "test/index/random/1000/slt_good_8.test",
// overflow in 32-bit integer multiplication (sqlite does automatic upcasting)
"test/random/aggregates/slt_good_96.test", "test/random/aggregates/slt_good_75.test",
"test/random/aggregates/slt_good_64.test", "test/random/aggregates/slt_good_9.test",
"test/random/aggregates/slt_good_110.test", "test/random/aggregates/slt_good_101.test",
"test/random/expr/slt_good_55.test", "test/random/expr/slt_good_115.test", "test/random/expr/slt_good_103.test",
"test/random/expr/slt_good_80.test", "test/random/expr/slt_good_75.test", "test/random/expr/slt_good_42.test",
"test/random/expr/slt_good_49.test", "test/random/expr/slt_good_24.test", "test/random/expr/slt_good_30.test",
"test/random/expr/slt_good_8.test", "test/random/expr/slt_good_61.test",
// dependencies between tables/views prevent dropping in DuckDB without CASCADE
"test/index/view/1000/slt_good_0.test", "test/index/view/100/slt_good_0.test",
"test/index/view/100/slt_good_5.test", "test/index/view/100/slt_good_1.test",
"test/index/view/100/slt_good_3.test", "test/index/view/100/slt_good_4.test",
"test/index/view/100/slt_good_2.test", "test/index/view/10000/slt_good_0.test",
"test/index/view/10/slt_good_5.test", "test/index/view/10/slt_good_7.test",
"test/index/view/10/slt_good_1.test", "test/index/view/10/slt_good_3.test",
"test/index/view/10/slt_good_4.test", "test/index/view/10/slt_good_6.test",
"test/index/view/10/slt_good_2.test",
// strange error in hash comparison, results appear correct...
"test/index/random/10/slt_good_7.test", "test/index/random/10/slt_good_9.test"};
duckdb::unique_ptr<FileSystem> fs = FileSystem::CreateLocal();
listFiles(*fs, fs->JoinPath(fs->JoinPath("third_party", "sqllogictest"), "test"), [&](const string &path) {
if (endsWith(path, ".test")) {
for (auto &excl : excludes) {
if (path.find(excl) != string::npos) {
return;
}
}
REGISTER_TEST_CASE(testRunner, StringUtil::Replace(path, "\\", "/"), "[sqlitelogic][.]");
}
});
listFiles(*fs, "test", [&](const string &path) {
if (endsWith(path, ".test") || endsWith(path, ".test_slow") || endsWith(path, ".test_coverage")) {
// parse the name / group from the test
REGISTER_TEST_CASE(testRunner<false>, StringUtil::Replace(path, "\\", "/"), ParseGroupFromPath(path));
}
});
for (const auto &extension_test_path : ExtensionHelper::LoadedExtensionTestPaths()) {
listFiles(*fs, extension_test_path, [&](const string &path) {
if (endsWith(path, ".test") || endsWith(path, ".test_slow") || endsWith(path, ".test_coverage")) {
auto fun = testRunner<true>;
REGISTER_TEST_CASE(fun, StringUtil::Replace(path, "\\", "/"), ParseGroupFromPath(path));
}
});
}
}
} // namespace duckdb
|