← Back to team overview

widelands-dev team mailing list archive

[Merge] lp:~widelands-dev/widelands/regression_testing into lp:widelands

 

SirVer has proposed merging lp:~widelands-dev/widelands/regression_testing into lp:widelands.

Requested reviews:
  Widelands Developers (widelands-dev)

For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/regression_testing/+merge/178000
-- 
https://code.launchpad.net/~widelands-dev/widelands/regression_testing/+merge/178000
Your team Widelands Developers is requested to review the proposed merge of lp:~widelands-dev/widelands/regression_testing into lp:widelands.
=== modified file '.bzrignore'
--- .bzrignore	2013-02-05 17:57:25 +0000
+++ .bzrignore	2013-09-20 19:50:25 +0000
@@ -25,3 +25,6 @@
 
 # Local KDevelop settings files
 **/.kdev_include_paths
+
+# Python compiled files.
+*.pyc

=== modified file 'doc/sphinx/source/introduction.rst'
--- doc/sphinx/source/introduction.rst	2012-06-20 09:07:10 +0000
+++ doc/sphinx/source/introduction.rst	2013-09-20 19:50:25 +0000
@@ -162,3 +162,27 @@
 game state via the debug console will not work. It is very useful
 for debugging scenarios though. 
 
+Regression testing infrastructure
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The `test` directory in the repository contains the regression testing
+infrastructure. Each python file contains one test class, the test suite can
+be started through the `regression_test.py` script in the root directory.
+
+Each test starts Widelands using either the `--editor`, `--loadgame` or
+`--scenario` switch and additionally, the `--script` switch can be supplied to
+run a Lua script directly after the game is ready to take commands.
+
+A test can therefore consist of either
+
+    - a map containing scripting (e.g `test_lua_api_in_editor.py`),
+    - a savegame with an additional Lua script (`test_compatibility*.py`), or
+    - a map file with an additional Lua script (no example yet).
+
+The tests are run as standard python tests, so they should check that the
+stdout of Widelands contains bits that proof that the test should pass (e.g.
+by using the lunit test suite runner and assert_all_lunit_tests_passed()).
+
+
+
+

=== modified file 'doc/sphinx/source/manual_testing.rst'
--- doc/sphinx/source/manual_testing.rst	2012-06-20 09:59:07 +0000
+++ doc/sphinx/source/manual_testing.rst	2013-09-20 19:50:25 +0000
@@ -1,47 +1,13 @@
 Manual Testing
 ==============
 
-The ``test`` directory contains various directories with testing
+The ``manual_test`` directory contains various directories with testing
 data/scripts/programs that can be used to manually exercise some subpart of
 widelands. These tests are not integrated into any kind of automated test
 suite but can be run on a as-needed basis by individual programmers. 
 
 The following directories are currently available:
 
-``compatibility/``
-   This directory contains old savegame states from earlier builds. You can load
-   them manualle by using the ``--loadgame`` switch on the commandline.
-
-``lua/``
-   This directory contains two maps that contain a complete test suite for most
-   Lua functionality in the game. You can run the test suite by using:
-
-   .. code-block:: bash
-   
-      widelands --scenario=test/lua/ts.wmf  or
-      widelands --editor=test/lua/ts.wmf
-
-   Both should immediately exit Widelands, the test suite will have printed
-   its result to stdout. If a test fails, Widelands will not exit however and
-   you should investigate stdout more carefully.
-
-   The other test map in this directory tests persistence (saving/loading) of
-   Lua objects. You launch it using
-
-   .. code-block:: bash
-   
-      widelands --scenario=test/lua/persistence.wmf
-
-   This will immediately save a game as ``lua_persistence.wgf``, then proceed to close Widelands.
-   You can now load this savestate using
-
-   .. code-block:: bash
-   
-      widelands --loadgame=/home/sirver/.widelands/save/lua_persistence.wgf
-
-   The test suite will then be run and check if everything was loaded alright.
-   Output will be printed to stdout again and Widelands should exit.
-   
 ``richtext/``
    This contains a test suite for the rich text rendering engine that is
    contained in ``src/graphic/text/``. The test stuff consists of a thin

=== renamed directory 'test' => 'manual_test'
=== added file 'regression_test.py'
--- regression_test.py	1970-01-01 00:00:00 +0000
+++ regression_test.py	2013-09-20 19:50:25 +0000
@@ -0,0 +1,64 @@
+#!/usr/bin/env python
+# encoding: utf-8
+
+from glob import glob
+import argparse
+import os
+import re
+import unittest
+
+import test
+
+def parse_args():
+    p = argparse.ArgumentParser(description=
+        "Run the regression tests suite."
+    )
+
+    p.add_argument("-r", "--regexp", type=str,
+        help = "Run only the tests from the files which filename matches."
+    )
+    p.add_argument("-n", "--nonrandom", action="store_true", default = False,
+        help = "Do not randomize the directories for the tests. This is useful "
+        "if you want to run a test more often than once and not reopen stdout.txt "
+        "in your editor."
+    )
+    p.add_argument("-b", "--binary", type=str,
+        help = "Run this binary as Widelands. Otherwise some default paths are searched."
+    )
+
+    args = p.parse_args()
+
+    if args.binary is None:
+        potential_binaries = (
+            glob("widelands") +
+            glob("src/widelands") +
+            glob("../*/src/widelands") +
+            glob("../*/src/widelands")
+        )
+        if not potential_binaries:
+            p.error("No widelands binary found. Please specify with -b.")
+        args.binary = potential_binaries[0]
+
+    return args
+
+
+def main():
+    args = parse_args()
+
+    test.WidelandsTestCase.path_to_widelands_binary = args.binary
+    print "Using '%s' binary." % args.binary
+    test.WidelandsTestCase.do_use_random_directory = not args.nonrandom
+
+    all_files = [os.path.basename(filename) for filename in glob("test/test_*.py") ]
+    if args.regexp:
+        all_files = [filename for filename in all_files if re.search(args.regexp, filename) ]
+
+    all_modules = [ "test.%s" % filename[:-3] for filename in all_files ]
+
+    test_loader = unittest.TestLoader()
+    all_tests = test_loader.loadTestsFromNames(all_modules)
+
+    unittest.TextTestRunner(verbosity=2).run(all_tests)
+
+if __name__ == '__main__':
+    main()

=== renamed file 'test/lua/ts.wmf/scripting/lunit.lua' => 'scripting/lunit.lua'
--- test/lua/ts.wmf/scripting/lunit.lua	2010-02-01 12:47:37 +0000
+++ scripting/lunit.lua	2013-09-20 19:50:25 +0000
@@ -9,7 +9,7 @@
 
     Copyright (c) 2004 Michael Roth <mroth@xxxxxxxxx>
 
-    Permission is hereby granted, free of charge, to any person 
+    Permission is hereby granted, free of charge, to any person
     obtaining a copy of this software and associated documentation
     files (the "Software"), to deal in the Software without restriction,
     including without limitation the rights to use, copy, modify, merge,
@@ -17,7 +17,7 @@
     and to permit persons to whom the Software is furnished to do so,
     subject to the following conditions:
 
-    The above copyright notice and this permission notice shall be 
+    The above copyright notice and this permission notice shall be
     included in all copies or substantial portions of the Software.
 
     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
@@ -415,7 +415,7 @@
     table.insert(arg, 1, name)
     name = "Anonymous Testcase"
   end
-  
+
   local tc = TestCase(name)
   for index, test in ipairs(arg) do
     tc["Test #"..tostring(index)] = test
@@ -433,11 +433,11 @@
 ----------------------------------
 
 function run()
-  
+
   ---------------------------
   -- Initialize statistics --
   ---------------------------
-  
+
   stats.testcases = 0	-- Total number of Test Cases
   stats.tests = 0	-- Total number of all Tests in all Test Cases
   stats.run = 0		-- Number of Tests run
@@ -445,51 +445,51 @@
   stats.failed = 0	-- Number of Tests failed
   stats.passed = 0	-- Number of Test passed
   stats.assertions = 0	-- Number of all assertions made in all Test in all Test Cases
-  
+
   --------------------------------
   -- Count Test Cases and Tests --
   --------------------------------
-  
+
   stats.testcases = table.getn(testcases)
-  
+
   for _, tc in ipairs(testcases) do
     stats_inc("tests" , table.getn(tc.__lunit_tests))
   end
-  
+
   ------------------
   -- Print Header --
   ------------------
-  
+
   print()
   print("#### Test Suite with "..stats.tests.." Tests in "..stats.testcases.." Test Cases loaded.")
-  
+
   ------------------------
   -- Run all Test Cases --
   ------------------------
-  
+
   for _, tc in ipairs(testcases) do
     run_testcase(tc)
   end
-  
+
   ------------------
   -- Print Footer --
   ------------------
-  
+
   print()
   print("#### Test Suite finished.")
-  
+
   local msg_assertions = stats.assertions.." Assertions checked. "
   local msg_passed     = stats.passed == stats.tests and "All Tests passed" or  stats.passed.." Tests passed"
   local msg_failed     = stats.failed > 0 and ", "..stats.failed.." failed" or ""
   local msg_run	       = stats.notrun > 0 and ", "..stats.notrun.." not run" or ""
-  
+
   print()
   print(msg_assertions..msg_passed..msg_failed..msg_run.."!")
-  
+
   -----------------
   -- Return code --
   -----------------
-  
+
   if stats.passed == stats.tests then
     return 0
   else
@@ -505,17 +505,17 @@
 -----------------------------
 
 function run_testcase(tc)
-  
+
   orig_assert(is_table(tc))
   orig_assert(is_table(tc.__lunit_tests))
   orig_assert(is_string(tc.__lunit_name))
   orig_assert(is_nil(tc.__lunit_setup) or is_function(tc.__lunit_setup))
   orig_assert(is_nil(tc.__lunit_teardown) or is_function(tc.__lunit_teardown))
-  
+
   --------------------------------------------
   -- Protected call to a Test Case function --
   --------------------------------------------
-  
+
   local function call(errprefix, func)
     orig_assert(is_string(errprefix))
     orig_assert(is_function(func))
@@ -526,23 +526,23 @@
     end
     return ok
   end
-  
+
   ------------------------------------
   -- Calls setup() on the Test Case --
   ------------------------------------
-  
+
   local function setup()
-    if tc.__lunit_setup then 
+    if tc.__lunit_setup then
       return call("ERROR: setup() failed", tc.__lunit_setup)
     else
       return true
     end
   end
-  
+
   ------------------------------------------
   -- Calls a single Test on the Test Case --
   ------------------------------------------
-  
+
   local function run(testname)
     orig_assert(is_string(testname))
     orig_assert(is_function(tc[testname]))
@@ -554,24 +554,24 @@
     end
     return ok
   end
-  
+
   ---------------------------------------
   -- Calls teardown() on the Test Case --
   ---------------------------------------
-  
+
   local function teardown()
      if tc.__lunit_teardown then
        call("WARNING: teardown() failed", tc.__lunit_teardown)
      end
   end
-  
+
   ---------------------------------
   -- Run all Tests on a TestCase --
   ---------------------------------
-  
+
   print()
   print("#### Running '"..tc.__lunit_name.."' ("..table.getn(tc.__lunit_tests).." Tests)...")
-  
+
   for _, testname in ipairs(tc.__lunit_tests) do
     if setup() then
       run(testname)
@@ -582,7 +582,7 @@
       stats_inc("notrun")
     end
   end
-  
+
 end
 
 
@@ -593,24 +593,24 @@
 ---------------------
 
 function import(name)
-  
+
   do_assert(is_string(name), "lunit.import() expects a single string as argument")
-  
+
   local user_env = getfenv(2)
-  
+
   --------------------------------------------------
   -- Installs a specific function in the user env --
   --------------------------------------------------
-  
+
   local function install(funcname)
     user_env[funcname] = P[funcname]
   end
-  
-  
+
+
   ----------------------------------------------------------
   -- Install functions matching a pattern in the user env --
   ----------------------------------------------------------
-  
+
   local function install_pattern(pattern)
     for funcname, _ in pairs(P) do
       if string.find(funcname, pattern) then
@@ -618,23 +618,23 @@
       end
     end
   end
-  
+
   ------------------------------------------------------------
   -- Installs assert() and all assert_xxx() in the user env --
   ------------------------------------------------------------
-  
+
   local function install_asserts()
     install_pattern("^assert.*")
   end
-  
+
   -------------------------------------------
   -- Installs all is_xxx() in the user env --
   -------------------------------------------
-  
+
   local function install_tests()
     install_pattern("^is_.+")
   end
-  
+
   if name == "asserts" or name == "assertions" then
     install_asserts()
   elseif name == "tests" or name == "checks" then
@@ -672,7 +672,7 @@
 
 
 --------------------------------------------------
--- Increments a counter in the statistics table --  
+-- Increments a counter in the statistics table --
 --------------------------------------------------
 
 function stats_inc(varname, value)

=== modified file 'src/editor/editorinteractive.cc'
--- src/editor/editorinteractive.cc	2013-07-26 20:19:36 +0000
+++ src/editor/editorinteractive.cc	2013-09-20 19:50:25 +0000
@@ -578,11 +578,7 @@
 }
 
 
-/**
- * Public static method to create an instance of the editor
- * and run it. This takes care of all the setup and teardown.
- */
-void Editor_Interactive::run_editor(const std::string & filename) {
+void Editor_Interactive::run_editor(const std::string & filename, const std::string& script_to_run) {
 	Widelands::Editor_Game_Base editor(0);
 	Editor_Interactive eia(editor);
 	editor.set_ibase(&eia); // TODO get rid of this
@@ -623,6 +619,10 @@
 		eia.select_tool(eia.tools.increase_height, Editor_Tool::First);
 		editor.postload();
 		eia.start();
+
+		if (!script_to_run.empty()) {
+			eia.egbase().lua().run_script(*g_fs, script_to_run, "commandline");
+		}
 	}
 	eia.run();
 
@@ -631,4 +631,3 @@
 	g_gr->flush_animations();
 	g_anim.flush();
 }
-

=== modified file 'src/editor/editorinteractive.h'
--- src/editor/editorinteractive.h	2013-07-26 20:19:36 +0000
+++ src/editor/editorinteractive.h	2013-09-20 19:50:25 +0000
@@ -46,7 +46,9 @@
 struct Editor_Interactive : public Interactive_Base {
 	friend struct Editor_Tool_Menu;
 
-	static void run_editor(const std::string & filename);
+	// Runs the Editor via the commandline --editor flag. Will load 'filename' as a
+	// map and run 'script_to_run' directly after all initialization is done.
+	static void run_editor(const std::string & filename, const std::string& script_to_run);
 
 private:
 	Editor_Interactive(Widelands::Editor_Game_Base &);

=== modified file 'src/io/filesystem/disk_filesystem.cc'
--- src/io/filesystem/disk_filesystem.cc	2013-07-30 09:23:23 +0000
+++ src/io/filesystem/disk_filesystem.cc	2013-09-20 19:50:25 +0000
@@ -20,7 +20,7 @@
 #include "io/filesystem/disk_filesystem.h"
 
 #include <cassert>
-#include <cerrno>
+#include <cerrno>
 
 #include <sys/stat.h>
 
@@ -201,8 +201,6 @@
 FileSystem * RealFSImpl::MakeSubFileSystem(const std::string & path) {
 	FileSystemPath fspath(FS_CanonicalizeName(path));
 	assert(fspath.m_exists); //TODO: throw an exception instead
-	//printf("RealFSImpl MakeSubFileSystem path %s fullname %s\n",
-	//path.c_str(), fspath.c_str());
 
 	if (fspath.m_isDirectory)
 		return new RealFSImpl   (fspath);

=== modified file 'src/io/filesystem/layered_filesystem.cc'
--- src/io/filesystem/layered_filesystem.cc	2013-07-26 20:19:36 +0000
+++ src/io/filesystem/layered_filesystem.cc	2013-09-20 19:50:25 +0000
@@ -406,7 +406,6 @@
 		if ((*it)->IsWritable() and (*it)->FileExists(dirname))
 			return (*it)->MakeSubFileSystem(dirname);
 
-	printf("dirname %s\n", dirname.c_str());
 	throw wexception("LayeredFileSystem: unable to create sub filesystem");
 }
 
@@ -425,7 +424,6 @@
 		if ((*it)->IsWritable() and not (*it)->FileExists(dirname))
 			return (*it)->CreateSubFileSystem(dirname, type);
 
-	printf("dirname %s\n", dirname.c_str());
 	throw wexception("LayeredFileSystem: unable to create sub filesystem");
 }
 

=== modified file 'src/logic/game.cc'
--- src/logic/game.cc	2013-08-20 16:17:50 +0000
+++ src/logic/game.cc	2013-09-20 19:50:25 +0000
@@ -214,13 +214,13 @@
 }
 
 
-bool Game::run_splayer_scenario_direct(char const * const mapname) {
+bool Game::run_splayer_scenario_direct(char const * const mapname, const std::string& script_to_run) {
 	assert(!get_map());
 
 	set_map(new Map);
 
 	std::unique_ptr<Map_Loader> maploader(map().get_correct_loader(mapname));
-	if (not maploader.get())
+	if (!maploader)
 		throw wexception("could not load \"%s\"", mapname);
 	UI::ProgressWindow loaderUI;
 
@@ -257,7 +257,7 @@
 
 	set_game_controller(GameController::createSinglePlayer(*this, true, 1));
 	try {
-		bool const result = run(&loaderUI, NewSPScenario);
+		bool const result = run(&loaderUI, NewSPScenario, script_to_run, false);
 		delete m_ctrl;
 		m_ctrl = 0;
 		return result;
@@ -351,7 +351,7 @@
  * Initialize the savegame based on the given settings.
  * At return the game is at the same state like a map loaded with Game::init()
  * Only difference is, that players are already initialized.
- * run(loaderUI, true) takes care about this difference.
+ * run() takes care about this difference.
  *
  * \note loaderUI can be nullptr, if this is run as dedicated server.
  */
@@ -380,13 +380,7 @@
 	}
 }
 
-
-/**
- * Load a game
- * Returns false if the user cancels the dialog. Otherwise returns the result
- * of running the game.
- */
-bool Game::run_load_game(std::string filename) {
+bool Game::run_load_game(std::string filename, const std::string& script_to_run) {
 	UI::ProgressWindow loaderUI;
 	std::vector<std::string> tipstext;
 	tipstext.push_back("general_game");
@@ -420,7 +414,7 @@
 
 	set_game_controller(GameController::createSinglePlayer(*this, true, player_nr));
 	try {
-		bool const result = run(&loaderUI, Loaded);
+		bool const result = run(&loaderUI, Loaded, script_to_run, false);
 		delete m_ctrl;
 		m_ctrl = 0;
 		return result;
@@ -473,7 +467,7 @@
  */
 bool Game::run
 	(UI::ProgressWindow * loader_ui, Start_Game_Type const start_game_type,
-	 bool replay)
+	 const std::string& script_to_run, bool replay)
 {
 	m_replay = replay;
 	postload();
@@ -536,6 +530,12 @@
 		enqueue_command(new Cmd_CalculateStatistics(get_gametime() + 1));
 	}
 
+	if (!script_to_run.empty() && (start_game_type == NewSPScenario || start_game_type == Loaded)) {
+		const std::string registered_script =
+			lua().register_script(*g_fs, "commandline", script_to_run);
+		enqueue_command(new Cmd_LuaScript(get_gametime() + 1, "commandline", registered_script));
+	}
+
 	if (m_writereplay || m_writesyncstream) {
 		// Derive a replay filename from the current time
 		std::string fname(REPLAY_DIR);

=== modified file 'src/logic/game.h'
--- src/logic/game.h	2013-08-20 16:17:50 +0000
+++ src/logic/game.h	2013-09-20 19:50:25 +0000
@@ -102,10 +102,18 @@
 	void save_syncstream(bool save);
 	void init_newgame (UI::ProgressWindow *, const GameSettings &);
 	void init_savegame(UI::ProgressWindow *, const GameSettings &);
-	bool run_splayer_scenario_direct(char const * mapname);
-	bool run_load_game (std::string filename);
 	enum Start_Game_Type {NewSPScenario, NewNonScenario, Loaded, NewMPScenario};
-	bool run(UI::ProgressWindow * loader_ui, Start_Game_Type, bool replay = false);
+	bool run(UI::ProgressWindow * loader_ui, Start_Game_Type, const std::string& script_to_run, bool replay);
+
+	// Run a single player scenario directly via --scenario on the cmdline. Will
+	// run the 'script_to_run' after any init scripts of the map.
+	// Returns the result of run().
+	bool run_splayer_scenario_direct(char const * mapname, const std::string& script_to_run);
+
+	// Run a single player loaded game directly via --loadgame on the cmdline. Will
+	// run the 'script_to_run' directly after the game was loaded.
+	// Returns the result of run().
+	bool run_load_game (std::string filename, const std::string& script_to_run);
 
 	virtual void postload();
 

=== modified file 'src/logic/map.cc'
--- src/logic/map.cc	2013-09-13 12:46:44 +0000
+++ src/logic/map.cc	2013-09-20 19:50:25 +0000
@@ -22,6 +22,8 @@
 #include <algorithm>
 #include <cstdio>
 
+#include <boost/algorithm/string/predicate.hpp>
+
 #include "build_info.h"
 #include "economy/flag.h"
 #include "economy/road.h"
@@ -1686,16 +1688,10 @@
 	}
 }
 
-/**
- * Returns the correct initialized loader for the given mapfile
-*/
-Map_Loader * Map::get_correct_loader(char const * const filename) {
-	Map_Loader * result = 0;
+Map_Loader * Map::get_correct_loader(const std::string& filename) {
+	Map_Loader * result = nullptr;
 
-	if
-		(!
-		 strcasecmp
-		 	(filename + (strlen(filename) - strlen(WLMF_SUFFIX)), WLMF_SUFFIX))
+	if (boost::algorithm::ends_with(filename, WLMF_SUFFIX)) {
 		try {
 			result = new WL_Map_Loader(*g_fs->MakeSubFileSystem(filename), this);
 		} catch (...) {
@@ -1703,16 +1699,13 @@
 			//  format)
 			//  TODO: catchall hides real errors! Replace with more specific code
 		}
-	else if
-		(!
-		 strcasecmp
-		 	(filename + (strlen(filename) - strlen(S2MF_SUFFIX)), S2MF_SUFFIX)
-		 |
-		 !
-		 strcasecmp
-		 	(filename + (strlen(filename) - strlen(S2MF_SUFFIX2)), S2MF_SUFFIX2))
+	} else if
+		(boost::algorithm::ends_with(filename, S2MF_SUFFIX) ||
+		 boost::algorithm::ends_with(filename, S2MF_SUFFIX2))
+	{
 		//  It is a S2 Map file. Load it as such.
-		result = new S2_Map_Loader(filename, *this);
+		result = new S2_Map_Loader(filename.c_str(), *this);
+	}
 
 	return result;
 }

=== modified file 'src/logic/map.h'
--- src/logic/map.h	2013-09-14 19:10:06 +0000
+++ src/logic/map.h	2013-09-20 19:50:25 +0000
@@ -156,8 +156,9 @@
 	const Overlay_Manager & overlay_manager() const {return *m_overlay_manager;}
 	Overlay_Manager       & overlay_manager()       {return *m_overlay_manager;}
 
-	//  for loading
-	Map_Loader * get_correct_loader(char const *);
+	/// Returns the correct initialized loader for the given mapfile
+	Map_Loader* get_correct_loader(const std::string& filename);
+
 	void cleanup();
 
 	void create_empty_map // for editor

=== modified file 'src/logic/tribe.cc'
--- src/logic/tribe.cc	2013-08-02 14:20:21 +0000
+++ src/logic/tribe.cc	2013-09-20 19:50:25 +0000
@@ -294,7 +294,7 @@
 			// Register Lua scripts
 			if (g_fs->IsDirectory(path + "scripting")) {
 				std::unique_ptr<FileSystem> sub_fs(g_fs->MakeSubFileSystem(path));
-				egbase.lua().register_scripts(*sub_fs, "tribe_" + tribename);
+				egbase.lua().register_scripts(*sub_fs, "tribe_" + tribename, "scripting");
 			}
 
 			// Read initializations -- all scripts are initializations currently

=== modified file 'src/map_io/widelands_map_scripting_data_packet.cc'
--- src/map_io/widelands_map_scripting_data_packet.cc	2013-07-26 20:19:36 +0000
+++ src/map_io/widelands_map_scripting_data_packet.cc	2013-09-20 19:50:25 +0000
@@ -43,7 +43,7 @@
 throw (_wexception)
 {
 	if (not is_normal_game) { // Only load scripting stuff if this is a scenario
-		egbase.lua().register_scripts(fs, "map");
+		egbase.lua().register_scripts(fs, "map", "scripting");
 	}
 
 	// Always try to load the global State: even in a normal game, some lua

=== modified file 'src/network/netclient.cc'
--- src/network/netclient.cc	2013-08-20 16:17:50 +0000
+++ src/network/netclient.cc	2013-09-20 19:50:25 +0000
@@ -225,7 +225,8 @@
 			 d->settings.savegame ?
 			 Widelands::Game::Loaded
 			 : d->settings.scenario ?
-			 Widelands::Game::NewMPScenario : Widelands::Game::NewNonScenario);
+			 Widelands::Game::NewMPScenario : Widelands::Game::NewNonScenario,
+			 "", false);
 
 		// if this is an internet game, tell the metaserver that the game is done.
 		if (m_internet)
@@ -909,7 +910,7 @@
 			std::string path = "tribes/" + info.name;
 			if (g_fs->IsDirectory(path)) {
 				std::unique_ptr<FileSystem> sub_fs(g_fs->MakeSubFileSystem(path));
-				lua->register_scripts(*sub_fs, "tribe_" + info.name);
+				lua->register_scripts(*sub_fs, "tribe_" + info.name, "scripting");
 			}
 
 			for (uint8_t j = packet.Unsigned8(); j; --j) {

=== modified file 'src/network/nethost.cc'
--- src/network/nethost.cc	2013-08-21 08:31:27 +0000
+++ src/network/nethost.cc	2013-09-20 19:50:25 +0000
@@ -895,7 +895,9 @@
 		game.run
 			(loaderUI.get(),
 			 d->settings.savegame ? Widelands::Game::Loaded : d->settings.scenario ?
-			 Widelands::Game::NewMPScenario : Widelands::Game::NewNonScenario);
+			 Widelands::Game::NewMPScenario : Widelands::Game::NewNonScenario,
+			 "",
+			 false);
 
 		delete tips;
 
@@ -3001,4 +3003,3 @@
 		("NetHost::report_result(%d, %d, %s)\n",
 		 player->player_number(), result, info.c_str());
 }
-

=== modified file 'src/scripting/lua_root.cc'
--- src/scripting/lua_root.cc	2013-07-26 20:19:36 +0000
+++ src/scripting/lua_root.cc	2013-09-20 19:50:25 +0000
@@ -116,7 +116,7 @@
 	.. attribute:: desired_speed
 
 	(RW) Sets the desired speed of the game in ms per real second, so a speed of
-	1000 means the game runs at 1x speed. Note that this will not work in
+	2000 means the game runs at 2x speed. Note that this will not work in
 	network games as expected.
 */
 // UNTESTED
@@ -293,4 +293,3 @@
 }
 
 };
-

=== modified file 'src/scripting/scripting.cc'
--- src/scripting/scripting.cc	2013-07-26 20:19:36 +0000
+++ src/scripting/scripting.cc	2013-09-20 19:50:25 +0000
@@ -115,8 +115,6 @@
 	 */
 	private:
 		int m_check_for_errors(int);
-		std::string m_register_script
-			(FileSystem & fs, std::string path, std::string ns);
 		bool m_filename_to_short(const std::string &);
 		bool m_is_lua_file(const std::string &);
 
@@ -128,7 +126,9 @@
 		virtual const std::string & get_last_error() const {return m_last_error;}
 
 		virtual void register_scripts
-			(FileSystem &, std::string, std::string = "scripting");
+			(FileSystem &, const std::string&, const std::string&);
+		virtual std::string register_script
+			(FileSystem &, const std::string&, const std::string&);
 		virtual ScriptContainer & get_scripts_for(std::string ns) {
 			return m_scripts[ns];
 		}
@@ -152,27 +152,6 @@
 	return rv;
 }
 
-std::string LuaInterface_Impl::m_register_script
-	(FileSystem & fs, std::string path, std::string ns)
-{
-		size_t length;
-		void * input_data = fs.Load(path, length);
-
-		std::string data(static_cast<char *>(input_data));
-		std::string name = path.substr(0, path.size() - 4); // strips '.lua'
-
-		// make sure the input_data is freed
-		free(input_data);
-
-		size_t pos = name.find_last_of("/\\");
-		if (pos != std::string::npos)  name = name.substr(pos + 1);
-
-		log("Registering script: (%s,%s)\n", ns.c_str(), name.c_str());
-		m_scripts[ns][name] = data;
-
-		return name;
-}
-
 bool LuaInterface_Impl::m_filename_to_short(const std::string & s) {
 	return s.size() < 4;
 }
@@ -235,20 +214,41 @@
 	// Now our own
 	LuaGlobals::luaopen_globals(m_L);
 
-	register_scripts(*g_fs, "aux");
+	register_scripts(*g_fs, "aux", "scripting");
 }
 
 LuaInterface_Impl::~LuaInterface_Impl() {
 	lua_close(m_L);
 }
 
+std::string LuaInterface_Impl::register_script
+	(FileSystem & fs, const std::string& ns, const std::string& path)
+{
+		size_t length;
+		void * input_data = fs.Load(path, length);
+
+		std::string data(static_cast<char *>(input_data));
+		std::string name = path.substr(0, path.size() - 4); // strips '.lua'
+
+		// make sure the input_data is freed
+		free(input_data);
+
+		size_t pos = name.find_last_of("/\\");
+		if (pos != std::string::npos)  name = name.substr(pos + 1);
+
+		log("Registering script: (%s,%s)\n", ns.c_str(), name.c_str());
+		m_scripts[ns][name] = data;
+
+		return name;
+}
+
 void LuaInterface_Impl::register_scripts
-	(FileSystem & fs, std::string ns, std::string subdir)
+	(FileSystem & fs, const std::string& ns, const std::string& subdir)
 {
 	filenameset_t scripting_files;
 
 	// Theoretically, we should be able to use fs.FindFiles(*.lua) here,
-	// but since FindFiles doesn't support Globbing in Zips and most
+	// but since FindFiles doesn't support globbing in Zips and most
 	// saved maps/games are zip, we have to work around this issue.
 	fs.FindFiles(subdir, "*", &scripting_files);
 
@@ -259,7 +259,7 @@
 		if (m_filename_to_short(*i) or not m_is_lua_file(*i))
 			continue;
 
-		m_register_script(fs, *i, ns);
+		register_script(fs, ns, *i);
 	}
 }
 
@@ -275,7 +275,7 @@
 	if (not m_scripts.count(ns))
 		delete_ns = true;
 
-	std::string name = m_register_script(fs, path, ns);
+	const std::string name = register_script(fs, ns, path);
 
 	std::unique_ptr<LuaTable> rv = run_script(ns, name);
 
@@ -561,5 +561,3 @@
 LuaInterface * create_LuaInterface() {
 	return new LuaInterface_Impl();
 }
-
-

=== modified file 'src/scripting/scripting.h'
--- src/scripting/scripting.h	2013-07-26 19:16:51 +0000
+++ src/scripting/scripting.h	2013-09-20 19:50:25 +0000
@@ -95,8 +95,15 @@
 	virtual void interpret_string(std::string) = 0;
 	virtual const std::string & get_last_error() const = 0;
 
+	// Register all Lua files in the directory "subdir" in "fs" under the
+	// namespace "ns".
 	virtual void register_scripts
-		(FileSystem &, std::string, std::string = "scripting") = 0;
+		(FileSystem & fs, const std::string& ns, const std::string& subdir) = 0;
+
+	// Register the Lua file "filename" in "fs" under the namespace "ns". Returns
+	// the name the script was registered, usually $(basename filename).
+	virtual std::string register_script
+		(FileSystem & fs, const std::string& ns, const std::string& filename) = 0;
 	virtual ScriptContainer & get_scripts_for(std::string) = 0;
 
 	virtual std::unique_ptr<LuaTable> run_script(std::string, std::string) = 0;

=== modified file 'src/wlapplication.cc'
--- src/wlapplication.cc	2013-09-15 10:17:49 +0000
+++ src/wlapplication.cc	2013-09-20 19:50:25 +0000
@@ -353,13 +353,13 @@
 {
 	if (m_game_type == EDITOR) {
 		g_sound_handler.start_music("ingame");
-		Editor_Interactive::run_editor(m_filename);
+		Editor_Interactive::run_editor(m_filename, m_script_to_run);
 	} else if (m_game_type == REPLAY)   {
 		replay();
 	} else if (m_game_type == LOADGAME) {
 		Widelands::Game game;
 		try {
-			game.run_load_game(m_filename.c_str());
+			game.run_load_game(m_filename.c_str(), m_script_to_run);
 		} catch (const Widelands::game_data_error & e) {
 			log("Game not loaded: Game data error: %s\n", e.what());
 		} catch (const std::exception & e) {
@@ -370,7 +370,7 @@
 	} else if (m_game_type == SCENARIO) {
 		Widelands::Game game;
 		try {
-			game.run_splayer_scenario_direct(m_filename.c_str());
+			game.run_splayer_scenario_direct(m_filename.c_str(), m_script_to_run);
 		} catch (const Widelands::game_data_error & e) {
 			log("Scenario not started: Game data error: %s\n", e.what());
 		} catch (const std::exception & e) {
@@ -1272,8 +1272,16 @@
 		m_game_type = INTERNET;
 		m_commandline.erase("dedicated");
 	}
-	//Note: it should be possible to record and playback at the same time,
-	//but why would you?
+	if (m_commandline.count("script")) {
+		m_script_to_run = m_commandline["script"];
+		if (m_script_to_run.empty())
+			throw wexception("empty value of command line parameter --script");
+		if (*m_script_to_run.rbegin() == '/')
+			m_script_to_run.erase(m_script_to_run.size() - 1);
+		m_commandline.erase("script");
+	}
+
+	// TODO(sirver): this framework has not been useful in a long time. Kill it.
 	if (m_commandline.count("record")) {
 		if (m_commandline["record"].empty())
 			throw Parameter_error("ERROR: --record needs a filename!");
@@ -1375,6 +1383,9 @@
 			 " --scenario=FILENAME  Directly starts the map FILENAME as scenario\n"
 			 "                      map.\n"
 			 " --loadgame=FILENAME  Directly loads the savegame FILENAME.\n")
+		<< _
+			(" --script=FILENAME    Run the given Lua script after initialization.\n"
+			 "                      Only valid with --scenario, --loadgame, or --editor.\n")
 		<< _(" --dedicated=FILENAME Starts a dedicated server with FILENAME as map\n")
 		<<
 		_
@@ -1555,7 +1566,7 @@
 				{
 					Widelands::Game game;
 					try {
-						game.run_splayer_scenario_direct("campaigns/tutorial01.wmf");
+						game.run_splayer_scenario_direct("campaigns/tutorial01.wmf", "");
 					} catch (const std::exception & e) {
 						log("Fata exception: %s\n", e.what());
 						emergency_save(game);
@@ -1759,7 +1770,7 @@
 		case Fullscreen_Menu_Editor::Back:
 			return;
 		case Fullscreen_Menu_Editor::New_Map:
-			Editor_Interactive::run_editor(m_filename);
+			Editor_Interactive::run_editor(m_filename, m_script_to_run);
 			return;
 		case Fullscreen_Menu_Editor::Load_Map: {
 			std::string filename;
@@ -1770,7 +1781,7 @@
 
 				filename = emsm.get_map();
 			}
-			Editor_Interactive::run_editor(filename.c_str());
+			Editor_Interactive::run_editor(filename.c_str(), "");
 			return;
 		}
 		default:
@@ -2000,9 +2011,9 @@
 		return false;
 	if (code == 2) { // scenario
 		try {
-			game.run_splayer_scenario_direct(sp.getMap().c_str());
+			game.run_splayer_scenario_direct(sp.getMap().c_str(), "");
 		} catch (const std::exception & e) {
-			log("Fata exception: %s\n", e.what());
+			log("Fatal exception: %s\n", e.what());
 			emergency_save(game);
 			throw;
 		}
@@ -2030,7 +2041,7 @@
 
 			game.set_game_controller(ctrl.get());
 			game.init_newgame(&loaderUI, sp.settings());
-			game.run(&loaderUI, Widelands::Game::NewNonScenario);
+			game.run(&loaderUI, Widelands::Game::NewNonScenario, "", false);
 		} catch (const std::exception & e) {
 			log("Fata exception: %s\n", e.what());
 			emergency_save(game);
@@ -2060,7 +2071,7 @@
 		return false;
 
 	try {
-		if (game.run_load_game(filename))
+		if (game.run_load_game(filename, ""))
 			return true;
 	} catch (const std::exception & e) {
 		log("Fata exception: %s\n", e.what());
@@ -2104,7 +2115,7 @@
 	try {
 		// Load selected campaign-map-file
 		if (filename.size())
-			return game.run_splayer_scenario_direct(filename.c_str());
+			return game.run_splayer_scenario_direct(filename.c_str(), "");
 	} catch (const std::exception & e) {
 		log("Fata exception: %s\n", e.what());
 		emergency_save(game);
@@ -2228,7 +2239,7 @@
 
 		game.save_handler().set_allow_saving(false);
 
-		game.run(&loaderUI, Widelands::Game::Loaded, true);
+		game.run(&loaderUI, Widelands::Game::Loaded, "", true);
 	} catch (const std::exception & e) {
 		log("Fatal Exception: %s\n", e.what());
 		emergency_save(game);

=== modified file 'src/wlapplication.h'
--- src/wlapplication.h	2013-07-25 21:05:20 +0000
+++ src/wlapplication.h	2013-09-20 19:50:25 +0000
@@ -260,6 +260,10 @@
 
 	std::string m_filename;
 
+	/// Script to be run after the game was started with --editor,
+	/// --scenario or --loadgame.
+	std::string m_script_to_run;
+
 	//Log all output to this file if set, otherwise use cout
 	std::string m_logfile;
 

=== added directory 'test'
=== added file 'test/__init__.py'
--- test/__init__.py	1970-01-01 00:00:00 +0000
+++ test/__init__.py	2013-09-20 19:50:25 +0000
@@ -0,0 +1,69 @@
+#!/usr/bin/env python
+# encoding: utf-8
+
+"""
+Classes and utilities for Widelands Regression testing.
+"""
+
+import os
+import re
+import shutil
+import subprocess
+import tempfile
+import unittest
+
+class WidelandsTestCase(unittest.TestCase):
+    do_use_random_directory = True
+    path_to_widelands_binary = None
+
+    def setUp(self):
+        if self.do_use_random_directory:
+            self.run_dir = tempfile.mkdtemp(prefix="widelands_regression_test")
+        else:
+            self.run_dir = os.path.join(tempfile.gettempdir(), "widelands_regression_test", self.__class__.__name__)
+            if os.path.exists(self.run_dir):
+                shutil.rmtree(self.run_dir)
+            os.makedirs(self.run_dir)
+        self.widelands_returncode = 0
+
+    def run(self, result=None):
+        self.currentResult = result # remember result for use in tearDown
+        unittest.TestCase.run(self, result)
+
+    def tearDown(self):
+        if self.currentResult.wasSuccessful():
+            shutil.rmtree(self.run_dir)
+
+    def run_widelands(self, **kwargs):
+        """Runs widelands with the arguments given, catching stdout and stderr in files."""
+
+        stdout_filename = os.path.join(self.run_dir, "stdout.txt")
+        stderr_filename = os.path.join(self.run_dir, "stderr.txt")
+
+        with open(stdout_filename, 'a') as stdout_file, open(stderr_filename, 'a') as stderr_file:
+            args = [self.path_to_widelands_binary, '--verbose=true',
+                    '--datadir=.', '--homedir=%s' % self.run_dir,
+                    '--disable_fx=true', '--disable_music=true' ]
+            args += [ "--%s=%s" % (key, value) for key, value in kwargs.iteritems() ]
+            stdout_file.write("---- TestRunner: Starting Widelands: %s\n\n" % args)
+
+            widelands = subprocess.Popen(
+                    args, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+            (self.stdout, self.stderr) = widelands.communicate()
+
+            stderr_file.write(self.stderr)
+            stdout_file.write(self.stdout)
+
+            stdout_file.write("\n\n---- TestRunner: Widelands terminated.\n\n")
+
+            self.widelands_returncode = widelands.returncode
+
+    def assert_all_lunit_tests_passed(self):
+        success = (
+            self.widelands_returncode == 0 and
+            re.search("All Tests passed.", self.stdout, re.M) is not None
+        )
+
+        self.assertTrue(success,
+            "Not all tests pass. Analyze the files in %s to see why this test case failed." % self.run_dir
+        )

=== removed directory 'test/compatibility'
=== removed file 'test/compatibility/README'
--- test/compatibility/README	2010-11-07 10:30:44 +0000
+++ test/compatibility/README	1970-01-01 00:00:00 +0000
@@ -1,11 +0,0 @@
-This directory contains savegame files of older official releases for
-archiving and testing purposes.
-
-For every official release, the goal is to retain savegame files that
-- contain every tribe
-- contain every world
-- exercise all features of the game
-
-For example, there should be savegame files that have an on-going battle,
-and in general, savegame files should be of a decent sized game with several
-players.
\ No newline at end of file

=== removed directory 'test/lua'
=== removed file 'test/lua/persistence.wmf/scripting/lunit.lua'
--- test/lua/persistence.wmf/scripting/lunit.lua	2010-02-01 12:47:37 +0000
+++ test/lua/persistence.wmf/scripting/lunit.lua	1970-01-01 00:00:00 +0000
@@ -1,688 +0,0 @@
-
---[[--------------------------------------------------------------------------
-
-    This file is part of lunit 0.3 (alpha).
-
-    For Details about lunit look at: http://www.nessie.de/mroth/lunit/
-
-    Author: Michael Roth <mroth@xxxxxxxxx>
-
-    Copyright (c) 2004 Michael Roth <mroth@xxxxxxxxx>
-
-    Permission is hereby granted, free of charge, to any person 
-    obtaining a copy of this software and associated documentation
-    files (the "Software"), to deal in the Software without restriction,
-    including without limitation the rights to use, copy, modify, merge,
-    publish, distribute, sublicense, and/or sell copies of the Software,
-    and to permit persons to whom the Software is furnished to do so,
-    subject to the following conditions:
-
-    The above copyright notice and this permission notice shall be 
-    included in all copies or substantial portions of the Software.
-
-    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
---]]--------------------------------------------------------------------------
-
-
-
-
------------------------
--- Intialize package --
------------------------
-
-local P = { }
-lunit = P
-
--- Import
-local type = type
-local print = print
-local ipairs = ipairs
-local pairs = pairs
-local string = string
-local table = table
-local pcall = pcall
-local xpcall = xpcall
-local traceback = debug.traceback
-local error = error
-local setmetatable = setmetatable
-local rawset = rawset
-local orig_assert = assert
-local getfenv = getfenv
-local setfenv = setfenv
-local tostring = tostring
-
-
--- Start package scope
-setfenv(1, P)
-
-
-
-
---------------------------------
--- Private data and functions --
---------------------------------
-
-local run_testcase
-local do_assert, check_msg
-local stats = { }
-local testcases = { }
-local stats_inc, tc_mt
-
-
-
-
---------------------------
--- Type check functions --
---------------------------
-
-function is_nil(x)
-  return type(x) == "nil"
-end
-
-function is_boolean(x)
-  return type(x) == "boolean"
-end
-
-function is_number(x)
-  return type(x) == "number"
-end
-
-function is_string(x)
-  return type(x) == "string"
-end
-
-function is_table(x)
-  return type(x) == "table"
-end
-
-function is_function(x)
-  return type(x) == "function"
-end
-
-function is_thread(x)
-  return type(x) == "thread"
-end
-
-function is_userdata(x)
-  return type(x) == "userdata"
-end
-
-
-
-
-----------------------
--- Assert functions --
-----------------------
-
-function assert(assertion, msg)
-  stats_inc("assertions")
-  check_msg("assert", msg)
-  do_assert(not not assertion, "assertion failed (was: "..tostring(assertion)..")", msg)		-- (convert assertion to bool)
-  return assertion
-end
-
-
-function assert_fail(msg)
-  stats_inc("assertions")
-  check_msg("assert_fail", msg)
-  do_assert(false, "failure", msg)
-end
-
-
-function assert_true(actual, msg)
-  stats_inc("assertions")
-  check_msg("assert_true", msg)
-  do_assert(is_boolean(actual), "true expected but was a "..type(actual), msg)
-  do_assert(actual == true, "true expected but was false", msg)
-  return actual
-end
-
-
-function assert_false(actual, msg)
-  stats_inc("assertions")
-  check_msg("assert_false", msg)
-  do_assert(is_boolean(actual), "false expected but was a "..type(actual), msg)
-  do_assert(actual == false, "false expected but was true", msg)
-  return actual
-end
-
-
-function assert_equal(expected, actual, msg)
-  stats_inc("assertions")
-  check_msg("assert_equal", msg)
-  do_assert(expected == actual, "expected '"..tostring(expected).."' but was '"..tostring(actual).."'", msg)
-  return actual
-end
-
-
-function assert_not_equal(unexpected, actual, msg)
-  stats_inc("assertions")
-  check_msg("assert_not_equal", msg)
-  do_assert(unexpected ~= actual, "'"..tostring(expected).."' not expected but was one", msg)
-  return actual
-end
-
-
-function assert_match(pattern, actual, msg)
-  stats_inc("assertions")
-  check_msg("assert_match", msg)
-  do_assert(is_string(pattern), "assert_match expects the pattern as a string")
-  do_assert(is_string(actual), "expected a string to match pattern '"..pattern.."' but was a '"..type(actual).."'", msg)
-  do_assert(not not string.find(actual, pattern), "expected '"..actual.."' to match pattern '"..pattern.."' but doesn't", msg)
-  return actual
-end
-
-
-function assert_not_match(pattern, actual, msg)
-  stats_inc("assertions")
-  check_msg("assert_not_match", msg)
-  do_assert(is_string(actual), "expected a string to not match pattern '"..pattern.."' but was a '"..type(actual).."'", msg)
-  do_assert(string.find(actual, pattern) == nil, "expected '"..actual.."' to not match pattern '"..pattern.."' but it does", msg)
-  return actual
-end
-
-
-function assert_nil(actual, msg)
-  stats_inc("assertions")
-  check_msg("assert_nil", msg)
-  do_assert(is_nil(actual), "nil expected but was a "..type(actual), msg)
-  return actual
-end
-
-
-function assert_not_nil(actual, msg)
-  stats_inc("assertions")
-  check_msg("assert_not_nil", msg)
-  do_assert(not is_nil(actual), "nil not expected but was one", msg)
-  return actual
-end
-
-
-function assert_boolean(actual, msg)
-  stats_inc("assertions")
-  check_msg("assert_boolean", msg)
-  do_assert(is_boolean(actual), "boolean expected but was a "..type(actual), msg)
-  return actual
-end
-
-
-function assert_not_boolean(actual, msg)
-  stats_inc("assertions")
-  check_msg("assert_not_boolean", msg)
-  do_assert(not is_boolean(actual), "boolean not expected but was one", msg)
-  return actual
-end
-
-
-function assert_number(actual, msg)
-  stats_inc("assertions")
-  check_msg("assert_number", msg)
-  do_assert(is_number(actual), "number expected but was a "..type(actual), msg)
-  return actual
-end
-
-
-function assert_not_number(actual, msg)
-  stats_inc("assertions")
-  check_msg("assert_not_number", msg)
-  do_assert(not is_number(actual), "number not expected but was one", msg)
-  return actual
-end
-
-
-function assert_string(actual, msg)
-  stats_inc("assertions")
-  check_msg("assert_string", msg)
-  do_assert(is_string(actual), "string expected but was a "..type(actual), msg)
-  return actual
-end
-
-
-function assert_not_string(actual, msg)
-  stats_inc("assertions")
-  check_msg("assert_not_string", msg)
-  do_assert(not is_string(actual), "string not expected but was one", msg)
-  return actual
-end
-
-
-function assert_table(actual, msg)
-  stats_inc("assertions")
-  check_msg("assert_table", msg)
-  do_assert(is_table(actual), "table expected but was a "..type(actual), msg)
-  return actual
-end
-
-
-function assert_not_table(actual, msg)
-  stats_inc("assertions")
-  check_msg("assert_not_table", msg)
-  do_assert(not is_table(actual), "table not expected but was one", msg)
-  return actual
-end
-
-
-function assert_function(actual, msg)
-  stats_inc("assertions")
-  check_msg("assert_function", msg)
-  do_assert(is_function(actual), "function expected but was a "..type(actual), msg)
-  return actual
-end
-
-
-function assert_not_function(actual, msg)
-  stats_inc("assertions")
-  check_msg("assert_not_function", msg)
-  do_assert(not is_function(actual), "function not expected but was one", msg)
-  return actual
-end
-
-
-function assert_thread(actual, msg)
-  stats_inc("assertions")
-  check_msg("assert_thread", msg)
-  do_assert(is_thread(actual), "thread expected but was a "..type(actual), msg)
-  return actual
-end
-
-
-function assert_not_thread(actual, msg)
-  stats_inc("assertions")
-  check_msg("assert_not_thread", msg)
-  do_assert(not is_thread(actual), "thread not expected but was one", msg)
-  return actual
-end
-
-
-function assert_userdata(actual, msg)
-  stats_inc("assertions")
-  check_msg("assert_userdata", msg)
-  do_assert(is_userdata(actual), "userdata expected but was a "..type(actual), msg)
-  return actual
-end
-
-
-function assert_not_userdata(actual, msg)
-  stats_inc("assertions")
-  check_msg("assert_not_userdata", msg)
-  do_assert(not is_userdata(actual), "userdata not expected but was one", msg)
-  return actual
-end
-
-
-function assert_error(msg, func)
-  stats_inc("assertions")
-  if is_nil(func) then func, msg = msg, nil end
-  check_msg("assert_error", msg)
-  do_assert(is_function(func), "assert_error expects a function as the last argument but it was a "..type(func))
-  local ok, errmsg = pcall(func)
-  do_assert(ok == false, "error expected but no error occurred", msg)
-end
-
-
-function assert_pass(msg, func)
-  stats_inc("assertions")
-  if is_nil(func) then func, msg = msg, nil end
-  check_msg("assert_pass", msg)
-  do_assert(is_function(func), "assert_pass expects a function as the last argument but it was a "..type(func))
-  local ok, errmsg = pcall(func)
-  if not ok then do_assert(ok == true, "no error expected but error was: "..errmsg, msg) end
-end
-
-
-
-
------------------------------------------------------------
--- Assert implementation that assumes it was called from --
--- lunit code which was called directly from user code.  --
------------------------------------------------------------
-
-function do_assert(assertion, base_msg, user_msg)
-  orig_assert(is_boolean(assertion))
-  orig_assert(is_string(base_msg))
-  orig_assert(is_string(user_msg) or is_nil(user_msg))
-  if not assertion then
-    if user_msg then
-      error(base_msg..": "..user_msg, 3)
-    else
-      error(base_msg.."!", 3)
-    end
-  end
-end
-
--------------------------------------------
--- Checks the msg argument in assert_xxx --
--------------------------------------------
-
-function check_msg(name, msg)
-  orig_assert(is_string(name))
-  if not (is_nil(msg) or is_string(msg)) then
-    error("lunit."..name.."() expects the optional message as a string but it was a "..type(msg).."!" ,3)
-  end
-end
-
-
-
-
--------------------------------------
--- Creates a new TestCase 'Object' --
--------------------------------------
-
-function TestCase(name)
-  do_assert(is_string(name), "lunit.TestCase() needs a string as an argument")
-  local tc = {
-    __lunit_name = name;
-    __lunit_setup = nil;
-    __lunit_tests = { };
-    __lunit_teardown = nil;
-  }
-  setmetatable(tc, tc_mt)
-  table.insert(testcases, tc)
-  return tc
-end
-
-tc_mt = {
-  __newindex = function(tc, key, value)
-    rawset(tc, key, value)
-    if is_string(key) and is_function(value) then
-      local name = string.lower(key)
-      if string.find(name, "^test") or string.find(name, "test$") then
-        table.insert(tc.__lunit_tests, key)
-      elseif name == "setup" then
-        tc.__lunit_setup = value
-      elseif name == "teardown" then
-        tc.__lunit_teardown = value
-      end
-    end
-  end
-}
-
-
-
------------------------------------------
--- Wrap Functions in a TestCase object --
------------------------------------------
-
-function wrap(name, ...)
-  if is_function(name) then
-    table.insert(arg, 1, name)
-    name = "Anonymous Testcase"
-  end
-  
-  local tc = TestCase(name)
-  for index, test in ipairs(arg) do
-    tc["Test #"..tostring(index)] = test
-  end
-  return tc
-end
-
-
-
-
-
-
-----------------------------------
--- Runs the complete Test Suite --
-----------------------------------
-
-function run()
-  
-  ---------------------------
-  -- Initialize statistics --
-  ---------------------------
-  
-  stats.testcases = 0	-- Total number of Test Cases
-  stats.tests = 0	-- Total number of all Tests in all Test Cases
-  stats.run = 0		-- Number of Tests run
-  stats.notrun = 0	-- Number of Tests not run
-  stats.failed = 0	-- Number of Tests failed
-  stats.passed = 0	-- Number of Test passed
-  stats.assertions = 0	-- Number of all assertions made in all Test in all Test Cases
-  
-  --------------------------------
-  -- Count Test Cases and Tests --
-  --------------------------------
-  
-  stats.testcases = table.getn(testcases)
-  
-  for _, tc in ipairs(testcases) do
-    stats_inc("tests" , table.getn(tc.__lunit_tests))
-  end
-  
-  ------------------
-  -- Print Header --
-  ------------------
-  
-  print()
-  print("#### Test Suite with "..stats.tests.." Tests in "..stats.testcases.." Test Cases loaded.")
-  
-  ------------------------
-  -- Run all Test Cases --
-  ------------------------
-  
-  for _, tc in ipairs(testcases) do
-    run_testcase(tc)
-  end
-  
-  ------------------
-  -- Print Footer --
-  ------------------
-  
-  print()
-  print("#### Test Suite finished.")
-  
-  local msg_assertions = stats.assertions.." Assertions checked. "
-  local msg_passed     = stats.passed == stats.tests and "All Tests passed" or  stats.passed.." Tests passed"
-  local msg_failed     = stats.failed > 0 and ", "..stats.failed.." failed" or ""
-  local msg_run	       = stats.notrun > 0 and ", "..stats.notrun.." not run" or ""
-  
-  print()
-  print(msg_assertions..msg_passed..msg_failed..msg_run.."!")
-  
-  -----------------
-  -- Return code --
-  -----------------
-  
-  if stats.passed == stats.tests then
-    return 0
-  else
-    return 1
-  end
-end
-
-
-
-
------------------------------
--- Runs a single Test Case --
------------------------------
-
-function run_testcase(tc)
-  
-  orig_assert(is_table(tc))
-  orig_assert(is_table(tc.__lunit_tests))
-  orig_assert(is_string(tc.__lunit_name))
-  orig_assert(is_nil(tc.__lunit_setup) or is_function(tc.__lunit_setup))
-  orig_assert(is_nil(tc.__lunit_teardown) or is_function(tc.__lunit_teardown))
-  
-  --------------------------------------------
-  -- Protected call to a Test Case function --
-  --------------------------------------------
-  
-  local function call(errprefix, func)
-    orig_assert(is_string(errprefix))
-    orig_assert(is_function(func))
-    local ok, errmsg = xpcall(function() func(tc) end, traceback)
-    if not ok then
-      print()
-      print(errprefix..": "..errmsg)
-    end
-    return ok
-  end
-  
-  ------------------------------------
-  -- Calls setup() on the Test Case --
-  ------------------------------------
-  
-  local function setup()
-    if tc.__lunit_setup then 
-      return call("ERROR: setup() failed", tc.__lunit_setup)
-    else
-      return true
-    end
-  end
-  
-  ------------------------------------------
-  -- Calls a single Test on the Test Case --
-  ------------------------------------------
-  
-  local function run(testname)
-    orig_assert(is_string(testname))
-    orig_assert(is_function(tc[testname]))
-    local ok = call("FAIL: "..testname, tc[testname])
-    if not ok then
-      stats_inc("failed")
-    else
-      stats_inc("passed")
-    end
-    return ok
-  end
-  
-  ---------------------------------------
-  -- Calls teardown() on the Test Case --
-  ---------------------------------------
-  
-  local function teardown()
-     if tc.__lunit_teardown then
-       call("WARNING: teardown() failed", tc.__lunit_teardown)
-     end
-  end
-  
-  ---------------------------------
-  -- Run all Tests on a TestCase --
-  ---------------------------------
-  
-  print()
-  print("#### Running '"..tc.__lunit_name.."' ("..table.getn(tc.__lunit_tests).." Tests)...")
-  
-  for _, testname in ipairs(tc.__lunit_tests) do
-    if setup() then
-      run(testname)
-      stats_inc("run")
-      teardown()
-    else
-      print("WARN: Skipping '"..testname.."'...")
-      stats_inc("notrun")
-    end
-  end
-  
-end
-
-
-
-
----------------------
--- Import function --
----------------------
-
-function import(name)
-  
-  do_assert(is_string(name), "lunit.import() expects a single string as argument")
-  
-  local user_env = getfenv(2)
-  
-  --------------------------------------------------
-  -- Installs a specific function in the user env --
-  --------------------------------------------------
-  
-  local function install(funcname)
-    user_env[funcname] = P[funcname]
-  end
-  
-  
-  ----------------------------------------------------------
-  -- Install functions matching a pattern in the user env --
-  ----------------------------------------------------------
-  
-  local function install_pattern(pattern)
-    for funcname, _ in pairs(P) do
-      if string.find(funcname, pattern) then
-        install(funcname)
-      end
-    end
-  end
-  
-  ------------------------------------------------------------
-  -- Installs assert() and all assert_xxx() in the user env --
-  ------------------------------------------------------------
-  
-  local function install_asserts()
-    install_pattern("^assert.*")
-  end
-  
-  -------------------------------------------
-  -- Installs all is_xxx() in the user env --
-  -------------------------------------------
-  
-  local function install_tests()
-    install_pattern("^is_.+")
-  end
-  
-  if name == "asserts" or name == "assertions" then
-    install_asserts()
-  elseif name == "tests" or name == "checks" then
-    install_tests()
-  elseif name == "all" then
-    install_asserts()
-    install_tests()
-    install("TestCase")
-  elseif string.find(name, "^assert.*") and P[name] then
-    install(name)
-  elseif string.find(name, "^is_.+") and P[name] then
-    install(name)
-  elseif name == "TestCase" then
-    install("TestCase")
-  else
-    error("luniit.import(): invalid function '"..name.."' to import", 2)
-  end
-end
-
-
-
-
---------------------------------------------------
--- Installs a private environment on the caller --
---------------------------------------------------
-
-function setprivfenv()
-  local new_env = { }
-  local new_env_mt = { __index = getfenv(2) }
-  setmetatable(new_env, new_env_mt)
-  setfenv(2, new_env)
-end
-
-
-
-
---------------------------------------------------
--- Increments a counter in the statistics table --  
---------------------------------------------------
-
-function stats_inc(varname, value)
-  orig_assert(is_table(stats))
-  orig_assert(is_string(varname))
-  orig_assert(is_nil(value) or is_number(value))
-  if not stats[varname] then return end
-  stats[varname] = stats[varname] + (value or 1)
-end
-
-
-
-

=== added directory 'test/maps'
=== renamed directory 'test/lua/persistence.wmf' => 'test/maps/lua_persistence.wmf'
=== modified file 'test/maps/lua_persistence.wmf/scripting/init.lua'
--- test/lua/persistence.wmf/scripting/init.lua	2013-08-01 13:30:53 +0000
+++ test/maps/lua_persistence.wmf/scripting/init.lua	2013-09-20 19:50:25 +0000
@@ -70,7 +70,7 @@
 
    -- Attention, lunit contains code that can not be persisted (c functions),
    -- so it must be imported after reload.
-   use("map", "lunit")
+   use("aux", "lunit")
    lunit.import "assertions"
 
    print("###################### CHECKING FOR CORRECT PERSISTENCE")
@@ -133,7 +133,7 @@
    assert_equal(false, mapview.statistics)
    assert_equal(true, mapview.census)
 
-   print("################### ALL TEST PASS!")
+   print("# All Tests passed.")
 
    wl.ui.MapView():close()
 end

=== renamed directory 'test/lua/ts.wmf' => 'test/maps/lua_testsuite.wmf'
=== modified file 'test/maps/lua_testsuite.wmf/scripting/common_init.lua'
--- test/lua/ts.wmf/scripting/common_init.lua	2010-03-28 14:18:19 +0000
+++ test/maps/lua_testsuite.wmf/scripting/common_init.lua	2013-09-20 19:50:25 +0000
@@ -1,12 +1,12 @@
-function include(s) 
+function include(s)
    use("map", s)
 end
 
-include "lunit"
+use("aux", "lunit")
 lunit.import "assertions"
 
 -- ============
--- Basic tests 
+-- Basic tests
 -- ============
 use_test = lunit.TestCase("Use test")
 function use_test:test_use_invalid_name()

=== modified file 'test/maps/lua_testsuite.wmf/scripting/init.lua'
--- test/lua/ts.wmf/scripting/init.lua	2013-07-21 08:12:41 +0000
+++ test/maps/lua_testsuite.wmf/scripting/init.lua	2013-09-20 19:50:25 +0000
@@ -60,10 +60,5 @@
 -- ============
 -- Test Runner
 -- ============
-rv = lunit:run()
-if rv == 0 then -- No errors in the testsuite. Exit.
-   wl.ui.MapView():close()
-elseif not wl.editor then
-   player1.see_all = true
-end
-
+lunit:run()
+wl.ui.MapView():close()

=== modified file 'test/maps/lua_testsuite.wmf/scripting/test_map.lua'
--- test/lua/ts.wmf/scripting/test_map.lua	2011-02-21 20:36:10 +0000
+++ test/maps/lua_testsuite.wmf/scripting/test_map.lua	2013-09-20 19:50:25 +0000
@@ -1,5 +1,5 @@
 -- ================================
--- Test functionality in wl.map.Map 
+-- Test functionality in wl.map.Map
 -- ================================
 
 test_map = lunit.TestCase("Map functions test")

=== added directory 'test/save'
=== renamed file 'test/compatibility/Build16.wgf' => 'test/save/Build16.wgf'
=== renamed file 'test/compatibility/Build17.wgf' => 'test/save/Build17.wgf'
=== renamed file 'test/compatibility/build15-bigeconomy.wgf' => 'test/save/build15-bigeconomy.wgf'
=== added directory 'test/scripts'
=== added file 'test/scripts/exit_after_100_seconds.lua'
--- test/scripts/exit_after_100_seconds.lua	1970-01-01 00:00:00 +0000
+++ test/scripts/exit_after_100_seconds.lua	2013-09-20 19:50:25 +0000
@@ -0,0 +1,13 @@
+use("aux", "coroutine")
+
+run(
+function ()
+   use("aux", "lunit")
+
+   wl.Game().desired_speed = 10000
+   sleep(100 * 1000)
+
+   lunit:run()
+   wl.ui.MapView():close()
+end
+)

=== added file 'test/test_compatibility_build15.py'
--- test/test_compatibility_build15.py	1970-01-01 00:00:00 +0000
+++ test/test_compatibility_build15.py	2013-09-20 19:50:25 +0000
@@ -0,0 +1,12 @@
+#!/usr/bin/env python
+# encoding: utf-8
+
+from test import WidelandsTestCase
+
+class CompatibilityBuild15(WidelandsTestCase):
+    def runTest(self):
+        self.run_widelands(
+            loadgame="test/save/build15-bigeconomy.wgf",
+            script="test/scripts/exit_after_100_seconds.lua"
+        )
+        self.assert_all_lunit_tests_passed()

=== added file 'test/test_compatibility_build16.py'
--- test/test_compatibility_build16.py	1970-01-01 00:00:00 +0000
+++ test/test_compatibility_build16.py	2013-09-20 19:50:25 +0000
@@ -0,0 +1,12 @@
+#!/usr/bin/env python
+# encoding: utf-8
+
+from test import WidelandsTestCase
+
+class CompatibilityBuild16(WidelandsTestCase):
+    def runTest(self):
+        self.run_widelands(
+            loadgame="test/save/Build16.wgf",
+            script="test/scripts/exit_after_100_seconds.lua"
+        )
+        self.assert_all_lunit_tests_passed()

=== added file 'test/test_compatibility_build17.py'
--- test/test_compatibility_build17.py	1970-01-01 00:00:00 +0000
+++ test/test_compatibility_build17.py	2013-09-20 19:50:25 +0000
@@ -0,0 +1,12 @@
+#!/usr/bin/env python
+# encoding: utf-8
+
+from test import WidelandsTestCase
+
+class CompatibilityBuild17(WidelandsTestCase):
+    def runTest(self):
+        self.run_widelands(
+            loadgame="test/save/Build17.wgf",
+            script="test/scripts/exit_after_100_seconds.lua"
+        )
+        self.assert_all_lunit_tests_passed()

=== added file 'test/test_lua_api_in_editor.py'
--- test/test_lua_api_in_editor.py	1970-01-01 00:00:00 +0000
+++ test/test_lua_api_in_editor.py	2013-09-20 19:50:25 +0000
@@ -0,0 +1,9 @@
+#!/usr/bin/env python
+# encoding: utf-8
+
+from test import WidelandsTestCase
+
+class LuaTestsuiteInEditor(WidelandsTestCase):
+    def runTest(self):
+        self.run_widelands(editor="test/maps/lua_testsuite.wmf")
+        self.assert_all_lunit_tests_passed()

=== added file 'test/test_lua_api_in_game.py'
--- test/test_lua_api_in_game.py	1970-01-01 00:00:00 +0000
+++ test/test_lua_api_in_game.py	2013-09-20 19:50:25 +0000
@@ -0,0 +1,9 @@
+#!/usr/bin/env python
+# encoding: utf-8
+
+from test import WidelandsTestCase
+
+class LuaTestsuiteInGame(WidelandsTestCase):
+    def runTest(self):
+        self.run_widelands(scenario="test/maps/lua_testsuite.wmf")
+        self.assert_all_lunit_tests_passed()

=== added file 'test/test_lua_persistence.py'
--- test/test_lua_persistence.py	1970-01-01 00:00:00 +0000
+++ test/test_lua_persistence.py	2013-09-20 19:50:25 +0000
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+# encoding: utf-8
+
+from test import WidelandsTestCase
+
+class LuaPersistence(WidelandsTestCase):
+    def runTest(self):
+        self.run_widelands(scenario="test/maps/lua_persistence.wmf")
+        self.run_widelands(loadgame=self.run_dir + "/save/lua_persistence.wgf")
+        self.assert_all_lunit_tests_passed()


References