summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--crawl-ref/.gitignore2
-rw-r--r--crawl-ref/source/acr.cc19
-rw-r--r--crawl-ref/source/chardump.cc9
-rw-r--r--crawl-ref/source/clua.cc6
-rw-r--r--crawl-ref/source/ctest.cc121
-rw-r--r--crawl-ref/source/ctest.h15
-rw-r--r--crawl-ref/source/debug.cc3
-rw-r--r--crawl-ref/source/dungeon.cc6
-rw-r--r--crawl-ref/source/dungeon.h2
-rw-r--r--crawl-ref/source/files.cc12
-rw-r--r--crawl-ref/source/files.h4
-rw-r--r--crawl-ref/source/initfile.cc18
-rw-r--r--crawl-ref/source/luadgn.cc89
-rw-r--r--crawl-ref/source/makefile.obj1
-rw-r--r--crawl-ref/source/makefile.unix3
-rw-r--r--crawl-ref/source/state.h2
-rw-r--r--crawl-ref/source/stuff.cc3
-rw-r--r--crawl-ref/source/test/los.lua68
18 files changed, 359 insertions, 24 deletions
diff --git a/crawl-ref/.gitignore b/crawl-ref/.gitignore
index 112510f8e1..33683b3191 100644
--- a/crawl-ref/.gitignore
+++ b/crawl-ref/.gitignore
@@ -7,7 +7,7 @@ morgue
*.rej
*.swp
core
-debug.map
+*.map
err.txt
map.dump
mapgen.log
diff --git a/crawl-ref/source/acr.cc b/crawl-ref/source/acr.cc
index 2da1b34de9..ad5b968a30 100644
--- a/crawl-ref/source/acr.cc
+++ b/crawl-ref/source/acr.cc
@@ -53,6 +53,7 @@ REVISION("$Rev$");
#include "cloud.h"
#include "clua.h"
#include "command.h"
+#include "ctest.h"
#include "crash.h"
#include "database.h"
#include "debug.h"
@@ -316,6 +317,10 @@ static void _show_commandline_options_help()
puts("");
puts("Arena options: (Stage a tournament between various monsters.)");
puts(" -arena \"<monster list> v <monster list> arena:<arena map>\"");
+#if DEBUG_DIAGNOSTICS
+ puts("");
+ puts(" -test run test cases in ./test");
+#endif
}
static void _wanderer_startup_message()
@@ -3508,6 +3513,20 @@ static bool _initialise(void)
}
#endif
+ if (crawl_state.test)
+ {
+#ifdef DEBUG_DIAGNOSTICS
+ crawl_tests::run_tests(true);
+ // Superfluous, just to make it clear that this is the end of
+ // the line.
+ end(0, false);
+#else
+ end(1, false, "Non-debug Crawl cannot run tests. "
+ "Please use a debug build");
+#endif
+ }
+
+
if (crawl_state.arena)
{
run_map_preludes();
diff --git a/crawl-ref/source/chardump.cc b/crawl-ref/source/chardump.cc
index 097932a3c0..be0b4d43aa 100644
--- a/crawl-ref/source/chardump.cc
+++ b/crawl-ref/source/chardump.cc
@@ -1207,7 +1207,14 @@ void dump_map(FILE *fp, bool debug)
for (int y = 0; y < GYM; ++y)
{
for (int x = 0; x < GXM; ++x)
- fputc(grid_character_at(coord_def(x,y)), fp);
+ {
+ if (you.pos() == coord_def(x, y))
+ fputc('@', fp);
+ else if (grd[x][y] == DNGN_FLOOR_SPECIAL)
+ fputc('?', fp);
+ else
+ fputc(grid_character_at(coord_def(x,y)), fp);
+ }
fputc('\n', fp);
}
}
diff --git a/crawl-ref/source/clua.cc b/crawl-ref/source/clua.cc
index d14df76573..de3a3f534c 100644
--- a/crawl-ref/source/clua.cc
+++ b/crawl-ref/source/clua.cc
@@ -810,10 +810,6 @@ LUARET1(you_taking_stairs, boolean,
current_delay_action() == DELAY_ASCENDING_STAIRS
|| current_delay_action() == DELAY_DESCENDING_STAIRS)
LUARET1(you_turns, number, you.num_turns)
-LUARET1(you_see_grid, boolean,
- see_grid(luaL_checkint(ls, 1), luaL_checkint(ls, 2)))
-LUARET1(you_see_grid_no_trans, boolean,
- see_grid_no_trans(luaL_checkint(ls, 1), luaL_checkint(ls, 2)))
LUARET1(you_can_smell, boolean, player_can_smell())
LUARET1(you_has_claws, number, you.has_claws(false))
@@ -928,8 +924,6 @@ static const struct luaL_reg you_lib[] =
{ "subdepth", you_subdepth },
{ "absdepth", you_absdepth },
- { "see_grid", you_see_grid },
- { "see_grid_no_trans", you_see_grid_no_trans },
{ "can_smell", you_can_smell },
{ "has_claws", you_has_claws },
diff --git a/crawl-ref/source/ctest.cc b/crawl-ref/source/ctest.cc
new file mode 100644
index 0000000000..48227254e2
--- /dev/null
+++ b/crawl-ref/source/ctest.cc
@@ -0,0 +1,121 @@
+/*
+ * File: ctest.cc
+ * Summary: Crawl Lua test cases
+ * Written by: Darshan Shaligram
+ *
+ * Modified for Crawl Reference by $Author$ on $Date$
+ *
+ * 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"
+
+#if DEBUG_DIAGNOSTICS
+
+#include "clua.h"
+#include "files.h"
+#include "luadgn.h"
+#include "maps.h"
+#include "stuff.h"
+
+#include <algorithm>
+#include <vector>
+
+namespace crawl_tests
+{
+ const std::string test_dir = "test";
+ const std::string test_player_name = "Superbug99";
+
+ int ntests = 0;
+ int nsuccess = 0;
+
+ typedef std::pair<std::string, std::string> file_error;
+ std::vector<file_error> failures;
+
+ void reset_test_data()
+ {
+ ntests = 0;
+ nsuccess = 0;
+ failures.clear();
+ // XXX: Good grief, you.your_name is still not a C++ string?!
+ strncpy(you.your_name, test_player_name.c_str(), kNameLen);
+ you.your_name[kNameLen - 1] = 0;
+ }
+
+ int crawl_begin_test(lua_State *ls)
+ {
+ mprf(MSGCH_PROMPT, "Starting tests: %s", luaL_checkstring(ls, 1));
+ lua_pushnumber(ls, ++ntests);
+ return (1);
+ }
+
+ int crawl_test_success(lua_State *ls)
+ {
+ mprf(MSGCH_PROMPT, "Test success: %s", luaL_checkstring(ls, 1));
+ lua_pushnumber(ls, ++nsuccess);
+ return (1);
+ }
+
+ static const struct luaL_reg crawl_test_lib[] =
+ {
+ { "begin_test", crawl_begin_test },
+ { "test_success", crawl_test_success },
+ { NULL, NULL }
+ };
+
+ void init_test_bindings()
+ {
+ lua_stack_cleaner clean(dlua);
+ luaL_openlib(dlua, "crawl", crawl_test_lib, 0);
+ }
+
+ void run_test(const std::string &file)
+ {
+ ++ntests;
+ const std::string path(catpath(test_dir, file));
+ dlua.execfile(path.c_str(), true, false);
+ if (dlua.error.empty())
+ ++nsuccess;
+ else
+ failures.push_back(file_error(file, dlua.error));
+ }
+
+ // Assumes curses has already been initialized.
+ bool run_tests(bool exit_on_complete)
+ {
+ run_map_preludes();
+ reset_test_data();
+
+ init_test_bindings();
+
+ // Get a list of Lua files in test. Order of execution of
+ // tests should be irrelevant.
+ const std::vector<std::string> tests(
+ get_dir_files_ext(test_dir, ".lua"));
+ std::for_each(tests.begin(), tests.end(),
+ run_test);
+
+ if (exit_on_complete)
+ {
+ cio_cleanup();
+ for (int i = 0, size = failures.size(); i < size; ++i)
+ {
+ const file_error &fe(failures[i]);
+ fprintf(stderr, "Test error: %s\n",
+ fe.second.c_str());
+ }
+ const int code = failures.empty() ? 0 : 1;
+ end(code, false, "%d tests, %d succeeded, %d failed",
+ ntests, nsuccess, failures.size());
+ }
+ return (failures.empty());
+ }
+}
+
+#endif // DEBUG_DIAGNOSTICS
diff --git a/crawl-ref/source/ctest.h b/crawl-ref/source/ctest.h
new file mode 100644
index 0000000000..0cc1d1ad55
--- /dev/null
+++ b/crawl-ref/source/ctest.h
@@ -0,0 +1,15 @@
+#ifndef CTEST_H
+#define CTEST_H
+
+#include "AppHdr.h"
+
+#ifdef DEBUG_DIAGNOSTICS
+
+namespace crawl_tests
+{
+ void run_tests(bool exit_on_complete);
+}
+
+#endif
+
+#endif
diff --git a/crawl-ref/source/debug.cc b/crawl-ref/source/debug.cc
index 4b47b21c88..c008f5c1fa 100644
--- a/crawl-ref/source/debug.cc
+++ b/crawl-ref/source/debug.cc
@@ -819,7 +819,8 @@ static void _wizard_go_to_level(const level_pos &pos)
#else
UNUSED(newlevel);
#endif
- save_game_state();
+ if (!crawl_state.test)
+ save_game_state();
new_level();
viewwindow(true, true);
diff --git a/crawl-ref/source/dungeon.cc b/crawl-ref/source/dungeon.cc
index 1abe8a5a99..30de547fe9 100644
--- a/crawl-ref/source/dungeon.cc
+++ b/crawl-ref/source/dungeon.cc
@@ -363,6 +363,12 @@ void level_clear_vault_memory()
dgn_Map_Mask.init(0);
}
+void dgn_flush_map_memory()
+{
+ you.uniq_map_tags.clear();
+ you.uniq_map_names.clear();
+}
+
static void _dgn_load_colour_grid()
{
dgn_colour_grid.reset(new dungeon_colour_grid);
diff --git a/crawl-ref/source/dungeon.h b/crawl-ref/source/dungeon.h
index 00d24662b6..cb9c4bdbda 100644
--- a/crawl-ref/source/dungeon.h
+++ b/crawl-ref/source/dungeon.h
@@ -332,6 +332,8 @@ void write_level_connectivity(writer &th);
bool builder(int level_number, int level_type);
+void dgn_flush_map_memory();
+
coord_def dgn_find_feature_marker(dungeon_feature_type feat);
// Set floor/wall colour based on the mons_alloc array. Used for
diff --git a/crawl-ref/source/files.cc b/crawl-ref/source/files.cc
index b5dc708b08..3650f8899d 100644
--- a/crawl-ref/source/files.cc
+++ b/crawl-ref/source/files.cc
@@ -225,7 +225,6 @@ bool get_dos_compatible_file_name(std::string *fname)
}
#endif
-
// Returns the names of all files in the given directory. Note that the
// filenames returned are relative to the directory.
std::vector<std::string> get_dir_files(const std::string &dirname)
@@ -268,6 +267,17 @@ std::vector<std::string> get_dir_files(const std::string &dirname)
return (files);
}
+std::vector<std::string> get_dir_files_ext(const std::string &dir,
+ const std::string &ext)
+{
+ const std::vector<std::string> allfiles(get_dir_files(dir));
+ std::vector<std::string> filtered;
+ for (int i = 0, size = allfiles.size(); i < size; ++i)
+ if (ends_with(allfiles[i], ext))
+ filtered.push_back(allfiles[i]);
+ return (filtered);
+}
+
std::string get_parent_directory(const std::string &filename)
{
std::string::size_type pos = filename.rfind(FILE_SEPARATOR);
diff --git a/crawl-ref/source/files.h b/crawl-ref/source/files.h
index e732e354ca..50da4cb0cc 100644
--- a/crawl-ref/source/files.h
+++ b/crawl-ref/source/files.h
@@ -39,6 +39,10 @@ bool is_absolute_path(const std::string &path);
bool is_read_safe_path(const std::string &path);
void assert_read_safe_path(const std::string &path) throw (std::string);
+std::vector<std::string> get_dir_files(const std::string &dir);
+std::vector<std::string> get_dir_files_ext(const std::string &dir,
+ const std::string &ext);
+
std::string datafile_path(std::string basename,
bool croak_on_fail = true,
bool test_base_path = false);
diff --git a/crawl-ref/source/initfile.cc b/crawl-ref/source/initfile.cc
index a342137e3b..247cde2076 100644
--- a/crawl-ref/source/initfile.cc
+++ b/crawl-ref/source/initfile.cc
@@ -3424,6 +3424,7 @@ enum commandline_option_type {
CLO_MACRO,
CLO_MAPSTAT,
CLO_ARENA,
+ CLO_TEST,
CLO_HELP,
CLO_NOPS
@@ -3432,7 +3433,7 @@ enum commandline_option_type {
static const char *cmd_ops[] = {
"scores", "name", "species", "job", "plain", "dir", "rc",
"rcdir", "tscores", "vscores", "scorefile", "morgue", "macro",
- "mapstat", "arena", "help"
+ "mapstat", "arena", "test", "help"
};
const int num_cmd_ops = CLO_NOPS;
@@ -3451,28 +3452,21 @@ std::string find_executable_path()
tempPath[0] = 0;
#if defined ( _MSC_VER )
-
int retval = GetModuleFileName ( NULL, tempPath, sizeof(tempPath) );
-
#elif defined ( __linux__ )
-
int retval = readlink ( "/proc/self/exe", tempPath, sizeof(tempPath) );
-
#elif defined ( __MACH__ )
-
strncpy ( tempPath, NXArgv[0], sizeof(tempPath) );
-
#else
-
// We don't know how to find the executable's path on this OS.
-
#endif
-
return std::string(tempPath);
}
bool parse_args( int argc, char **argv, bool rc_only )
{
+ COMPILE_CHECK(ARRAYSZ(cmd_ops) == CLO_NOPS, c1);
+
std::string exe_path = find_executable_path();
if (!exe_path.empty())
@@ -3615,6 +3609,10 @@ bool parse_args( int argc, char **argv, bool rc_only )
}
break;
+ case CLO_TEST:
+ crawl_state.test = true;
+ break;
+
case CLO_MACRO:
if (!next_is_param)
return (false);
diff --git a/crawl-ref/source/luadgn.cc b/crawl-ref/source/luadgn.cc
index a0a965b738..27ae6fa4ae 100644
--- a/crawl-ref/source/luadgn.cc
+++ b/crawl-ref/source/luadgn.cc
@@ -29,9 +29,12 @@ REVISION("$Rev$");
#include "mapdef.h"
#include "mapmark.h"
#include "maps.h"
+#include "message.h"
#include "misc.h"
#include "mon-util.h"
#include "monplace.h"
+#include "place.h"
+#include "spells3.h"
#include "spl-util.h"
#include "state.h"
#include "stuff.h"
@@ -461,6 +464,42 @@ static int dgn_depth(lua_State *ls)
return dgn_depth_proc(ls, map->depths, 2);
}
+// WARNING: This is a very low-level call.
+LUAFN(dgn_dbg_goto_place)
+{
+ try
+ {
+ const level_id id = level_id::parse_level_id(luaL_checkstring(ls, 1));
+ you.level_type = id.level_type;
+ if (id.level_type == LEVEL_DUNGEON)
+ {
+ you.where_are_you = static_cast<branch_type>(id.branch);
+ you.your_level = absdungeon_depth(id.branch, id.depth);
+ }
+ }
+ catch (const std::string &err)
+ {
+ luaL_error(ls, err.c_str());
+ }
+ return (0);
+}
+
+LUAFN(dgn_dbg_flush_map_memory)
+{
+ dgn_flush_map_memory();
+ init_level_connectivity();
+ return (0);
+}
+
+LUAFN(dgn_dbg_generate_level)
+{
+ no_messages mx;
+ env.show.init(0);
+ env.map.init(map_cell());
+ builder(you.your_level, you.level_type);
+ return (0);
+}
+
static int dgn_place(lua_State *ls)
{
MAP(ls, 1, map);
@@ -2934,7 +2973,7 @@ LUAFN(dgn_rtile)
#endif
}
-LUAFN(dgn_debug_dump_map)
+LUAFN(dgn_dbg_dump_map)
{
const int pos = lua_isuserdata(ls, 1) ? 2 : 1;
if (lua_isstring(ls, pos))
@@ -2944,6 +2983,11 @@ LUAFN(dgn_debug_dump_map)
static const struct luaL_reg dgn_lib[] =
{
+ { "dbg_goto_place", dgn_dbg_goto_place },
+ { "dbg_flush_map_memory", dgn_dbg_flush_map_memory },
+ { "dbg_generate_level", dgn_dbg_generate_level },
+ { "dbg_dump_map", dgn_dbg_dump_map },
+
{ "default_depth", dgn_default_depth },
{ "name", dgn_name },
{ "depth", dgn_depth },
@@ -3084,8 +3128,6 @@ static const struct luaL_reg dgn_lib[] =
{ "lev_floortile", dgn_lev_floortile },
{ "lev_rocktile", dgn_lev_rocktile },
- { "debug_dump_map", dgn_debug_dump_map },
-
{ NULL, NULL }
};
@@ -3103,6 +3145,12 @@ LUAFN(_crawl_milestone)
return (0);
}
+LUAFN(_crawl_redraw_view)
+{
+ viewwindow(true, false);
+ return (0);
+}
+
#ifdef UNIX
LUAFN(_crawl_millis)
{
@@ -3122,6 +3170,7 @@ static const struct luaL_reg crawl_lib[] =
{
{ "args", _crawl_args },
{ "mark_milestone", _crawl_milestone },
+ { "redraw_view", _crawl_redraw_view },
#ifdef UNIX
{ "millis", _crawl_millis },
#endif
@@ -3266,12 +3315,46 @@ LUARET1(you_x_pos, number, you.pos().x)
LUARET1(you_y_pos, number, you.pos().y)
LUARET2(you_pos, number, you.pos().x, you.pos().y)
+// see_grid should not be exposed to user scripts. The game should
+// never disclose grid coordinates to the player. Therefore we load it
+// only into the core Lua interpreter (dlua), never into the user
+// script interpreter (clua).
+LUARET1(you_see_grid, boolean,
+ see_grid(luaL_checkint(ls, 1), luaL_checkint(ls, 2)))
+LUARET1(you_see_grid_no_trans, boolean,
+ see_grid_no_trans(luaL_checkint(ls, 1), luaL_checkint(ls, 2)))
+
+LUAFN(you_moveto)
+{
+ const coord_def place(luaL_checkint(ls, 1), luaL_checkint(ls, 2));
+ ASSERT(map_bounds(place));
+ you.moveto(place);
+ return (0);
+}
+
+LUAFN(you_random_teleport)
+{
+ you_teleport_now(false, false);
+ return (0);
+}
+
+LUAFN(you_losight)
+{
+ calc_show_los();
+ return (0);
+}
+
static const struct luaL_reg you_lib[] =
{
{ "hear_pos", you_can_hear_pos },
{ "x_pos", you_x_pos },
{ "y_pos", you_y_pos },
{ "pos", you_pos },
+ { "moveto", you_moveto },
+ { "see_grid", you_see_grid },
+ { "see_grid_no_trans", you_see_grid_no_trans },
+ { "random_teleport", you_random_teleport },
+ { "losight", you_losight },
{ NULL, NULL }
};
diff --git a/crawl-ref/source/makefile.obj b/crawl-ref/source/makefile.obj
index 62d3018ebf..6c3b2f7c72 100644
--- a/crawl-ref/source/makefile.obj
+++ b/crawl-ref/source/makefile.obj
@@ -12,6 +12,7 @@ chardump.o \
cio.o \
cloud.o \
command.o \
+ctest.o \
database.o \
debug.o \
decks.o \
diff --git a/crawl-ref/source/makefile.unix b/crawl-ref/source/makefile.unix
index ab6135b042..9892a8a94a 100644
--- a/crawl-ref/source/makefile.unix
+++ b/crawl-ref/source/makefile.unix
@@ -151,6 +151,9 @@ PKG_EXCLUDES := $(PWD)/misc/src-pkg-excludes.lst
all: $(GAME)
+test: $(GAME)
+ $(PWD)/$(GAME) -test
+
##########################################################################
# Dependencies
diff --git a/crawl-ref/source/state.h b/crawl-ref/source/state.h
index 397bd510ef..d8cd566a29 100644
--- a/crawl-ref/source/state.h
+++ b/crawl-ref/source/state.h
@@ -59,6 +59,8 @@ struct game_state
bool arena_suspended; // Set if the arena has been temporarily
// suspended.
+ bool test; // Set if we want to run self-tests and exit.
+
bool unicode_ok; // Is unicode support available?
std::string (*glyph2strfn)(unsigned glyph);
diff --git a/crawl-ref/source/stuff.cc b/crawl-ref/source/stuff.cc
index e405c2bf73..d825f8e5b4 100644
--- a/crawl-ref/source/stuff.cc
+++ b/crawl-ref/source/stuff.cc
@@ -790,7 +790,8 @@ void end(int exit_code, bool print_error, const char *format, ...)
}
#if defined(WIN32CONSOLE) || defined(DOS) || defined(DGL_PAUSE_AFTER_ERROR)
- if (exit_code && !crawl_state.arena && !crawl_state.seen_hups)
+ if (exit_code && !crawl_state.arena
+ && !crawl_state.seen_hups && !crawl_state.test)
{
fprintf(stderr, "Hit Enter to continue...\n");
getchar();
diff --git a/crawl-ref/source/test/los.lua b/crawl-ref/source/test/los.lua
new file mode 100644
index 0000000000..6ff3a092a4
--- /dev/null
+++ b/crawl-ref/source/test/los.lua
@@ -0,0 +1,68 @@
+-- Vet LOS for symmetry.
+
+local FAILMAP = 'losfail.map'
+local checks = 0
+
+local function test_los()
+ -- Send the player to a random spot on the level.
+ you.random_teleport()
+
+ -- Forcibly redo LOS.
+ you.losight()
+
+ -- And draw the view to keep the watcher entertained.
+ crawl.redraw_view()
+
+ checks = checks + 1
+
+ local you_x, you_y = you.pos()
+
+ local visible_spots = { }
+ for y = -8, 8 do
+ for x = -8, 8 do
+ if x ~= 0 or y ~= 0 then
+ local px, py = x + you_x, y + you_y
+ if you.see_grid(px, py) then
+ table.insert(visible_spots, { px, py })
+ end
+ end
+ end
+ end
+
+ -- For each position in LOS, jump to that position and make sure we
+ -- can see the original spot.
+ for _, spot in ipairs(visible_spots) do
+ local x, y = unpack(spot)
+ you.moveto(x, y)
+ you.losight()
+ if not you.see_grid(you_x, you_y) then
+ crawl.redraw_view()
+ local this_p = dgn.point(x, y)
+ local you_p = dgn.point(you_x, you_y)
+ dgn.grid(you_x, you_y, "floor_special")
+ dgn.dbg_dump_map(FAILMAP)
+ assert(false,
+ "LOS asymmetry detected (iter #" .. checks .. "): " .. you_p ..
+ " sees " .. this_p .. ", but not vice versa." ..
+ " Map saved to " .. FAILMAP)
+ end
+ end
+end
+
+local function run_los_tests(depth, nlevels, tests_per_level)
+ local place = "D:" .. depth
+ crawl.mpr("Running LOS tests on " .. place)
+ dgn.dbg_goto_place(place)
+
+ for lev_i = 1, nlevels do
+ dgn.dbg_flush_map_memory()
+ dgn.dbg_generate_level()
+ for t_i = 1, tests_per_level do
+ test_los()
+ end
+ end
+end
+
+for depth = 1, 27 do
+ run_los_tests(depth, 10, 100)
+end \ No newline at end of file