File: test_sqllogictest.cpp

package info (click to toggle)
duckdb 1.5.1-3
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 299,196 kB
  • sloc: cpp: 865,414; ansic: 57,292; python: 18,871; sql: 12,663; lisp: 11,751; yacc: 7,412; lex: 1,682; sh: 747; makefile: 564
file content (225 lines) | stat: -rw-r--r-- 9,296 bytes parent folder | download | duplicates (3)
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