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 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263
|
/**
* @file
* @brief Crawl Lua test cases
*
* ctest runs Lua tests found in the test directory. The intent here
* is to test parts of Crawl that can be easily tested from within Crawl
* itself (such as LOS). As a side-effect, writing Lua bindings to support
* tests will expand the available Lua bindings. :-)
*
* Tests will run only with Crawl built in its source tree without
* DATA_DIR_PATH set.
**/
#include "AppHdr.h"
#include "ctest.h"
#include <algorithm>
#include <vector>
#include "clua.h"
#include "cluautil.h"
#include "coordit.h"
#include "dlua.h"
#include "end.h"
#include "errors.h"
#include "files.h"
#include "fixedp.h"
#include "item-name.h"
#include "jobs.h"
#include "libutil.h"
#include "mapdef.h"
#include "maps.h"
#include "message.h"
#include "mon-pick.h"
#include "mon-place.h"
#include "mon-util.h"
#include "ng-init.h"
#include "state.h"
#include "stringutil.h"
#include "xom.h"
static const string test_dir = "test";
static const string script_dir = "scripts";
static const char *activity = "test";
static int ntests = 0;
static int nsuccess = 0;
typedef pair<string, string> file_error;
static vector<file_error> failures;
static void _reset_test_data()
{
ntests = 0;
nsuccess = 0;
failures.clear();
you.your_name = "Superbug99";
you.species = SP_HUMAN;
you.char_class = JOB_FIGHTER;
}
static int crawl_begin_test(lua_State *ls)
{
mprf(MSGCH_PROMPT, "Starting %s: %s",
activity,
luaL_checkstring(ls, 1));
lua_pushinteger(ls, ++ntests);
return 1;
}
static int crawl_test_success(lua_State *ls)
{
if (!crawl_state.script)
mprf(MSGCH_PROMPT, "Test success: %s", luaL_checkstring(ls, 1));
lua_pushinteger(ls, ++nsuccess);
return 1;
}
static int crawl_script_args(lua_State *ls)
{
return clua_stringtable(ls, crawl_state.script_args);
}
static const struct luaL_Reg crawl_test_lib[] =
{
{ "begin_test", crawl_begin_test },
{ "test_success", crawl_test_success },
{ "script_args", crawl_script_args },
{ nullptr, nullptr }
};
static void _init_test_bindings()
{
lua_stack_cleaner clean(dlua);
if (lua_getglobal(dlua, "crawl") == LUA_TNIL) {
lua_pop(dlua, 1);
lua_newtable(dlua);
}
luaL_setfuncs(dlua, crawl_test_lib, 0);
lua_setglobal(dlua, "crawl");
dlua.execfile("dlua/test.lua", true, true);
initialise_branch_depths();
initialise_item_descriptions();
}
static bool _is_test_selected(const string &testname)
{
if (crawl_state.test_list)
{
ASSERT(ends_with(testname, ".lua"));
printf("%s\n", testname.substr(0, testname.length() - 4).c_str());
return false;
}
if (crawl_state.tests_selected.empty() && !starts_with(testname, "big/"))
return true;
for (const string& phrase : crawl_state.tests_selected)
{
if (testname == phrase || testname == phrase + ".lua")
return true;
}
return false;
}
static void run_test(const string &file)
{
if (!_is_test_selected(file))
return;
// halt immediately if there are HUPs. TODO: interrupt tests?
if (crawl_state.seen_hups)
end(0);
++ntests;
if (!crawl_state.script)
fprintf(stderr, "Running test #%d: '%s'.\n", ntests, file.c_str());
mprf(MSGCH_DIAGNOSTICS, "Running %s %d: %s",
activity, ntests, file.c_str());
flush_prev_message();
// XXX: We should probably reset more things between tests
you.position.reset();
you.on_current_level = true;
const string path(catpath(crawl_state.script? script_dir : test_dir, file));
dlua.execfile(path.c_str(), true, false);
if (dlua.error.empty())
++nsuccess;
else
failures.emplace_back(file, dlua.error);
}
#ifdef DEBUG_TESTS
static bool _has_test(const string& test)
{
if (crawl_state.script)
return false;
if (crawl_state.tests_selected.empty())
return true;
return crawl_state.tests_selected[0].find(test) != string::npos;
}
static void _run_test(const string &name, void (*func)())
{
// halt immediately if there are HUPs. TODO: interrupt tests?
if (crawl_state.seen_hups)
end(0);
if (crawl_state.test_list)
return (void)printf("%s\n", name.c_str());
if (!_has_test(name))
return;
if (!crawl_state.script)
fprintf(stderr, "Running test #%d: '%s'.\n", ntests, name.c_str());
++ntests;
try
{
(*func)();
++nsuccess;
}
catch (const ext_fail_exception &E)
{
failures.emplace_back(name, E.what());
}
}
#endif
// Assumes curses has already been initialized.
void run_tests()
{
if (crawl_state.script)
activity = "script";
flush_prev_message();
run_map_global_preludes();
run_map_local_preludes();
_reset_test_data();
_init_test_bindings();
#ifdef DEBUG_TESTS
if (!crawl_state.script)
{
_run_test("makeitem", makeitem_tests);
_run_test("mon-pick", debug_monpick);
_run_test("mon-data", debug_mondata);
_run_test("mon-spell", debug_monspells);
_run_test("coordit", coordit_tests);
_run_test("makename", make_name_tests);
_run_test("job-data", debug_jobdata);
_run_test("mon-bands", debug_bands);
_run_test("xom-data", validate_xom_events);
_run_test("maybe-bool", maybe_bool::test_cases);
_run_test("fixedp", fixedp<>::test_cases);
}
#else
ASSERT(crawl_state.script);
#endif
// Get a list of Lua files in test.
{
const string &dir = crawl_state.script ? script_dir : test_dir;
vector<string> tests = get_dir_files_recursive(dir, ".lua");
// Make the order consistent from one run to the next, for
// reproducibility.
sort(begin(tests), end(tests));
for_each(tests.begin(), tests.end(), run_test);
if (failures.empty() && !ntests && crawl_state.script)
{
failures.emplace_back("Script setup",
"No scripts found matching "
+ comma_separated_line(crawl_state.tests_selected.begin(),
crawl_state.tests_selected.end(),
", ", ", "));
}
}
#ifdef DEBUG_TAG_PROFILING
tag_profile_out();
#endif
if (crawl_state.test_list)
end(0);
cio_cleanup();
for (const file_error &fe : failures)
fprintf(stderr, "%s error: %s\n", activity, fe.second.c_str());
const int code = failures.empty() ? 0 : 1;
// scripts are responsible for printing their own errors
if (crawl_state.script && ntests == 1)
end(code, false);
else
{
end(code, false, "%d %ss, %d succeeded, %d failed",
ntests, activity, nsuccess, (int)failures.size());
}
}
|