diff --git a/luprex/cpp/core/luasnap.cpp b/luprex/cpp/core/luasnap.cpp index cffddf2b..5ac8f4a9 100644 --- a/luprex/cpp/core/luasnap.cpp +++ b/luprex/cpp/core/luasnap.cpp @@ -27,17 +27,6 @@ public: LuaSnap::LuaSnap() { state_ = LuaCoreStack::newstate(eng::l_alloc); LuaExtStack LS(state_); - - // Create the persist table and the unpersist table. - // - // These tables need to contain all C functions. Whenever - // the source module inserts a C function into the lua environment, - // it also needs to register the C function in these tables. - // - // I don't think anything else needs to go in these tables. - // - LS.rawset(LuaRegistry, "persist", LuaNewTable); - LS.rawset(LuaRegistry, "unpersist", LuaNewTable); } LuaSnap::~LuaSnap() { diff --git a/luprex/cpp/core/luastack.cpp b/luprex/cpp/core/luastack.cpp index 4c9c31e1..1a41d496 100644 --- a/luprex/cpp/core/luastack.cpp +++ b/luprex/cpp/core/luastack.cpp @@ -66,19 +66,22 @@ lua_State *LuaCoreStack::newstate (lua_Alloc allocf) { assert(L != nullptr); lua_atpanic(L, &panicf); - // We want all states to have a classes table and - // a tangibles table so that LS.makeclass and LS.maketan - // work out-of-the-box. + // We want all states to have a classes table, a tangibles table, + // and persist/unpersist tables so that LS.makeclass, LS.maketan, + // and eris work out-of-the-box. + LuaVar globtab; + LuaExtStack LS(L, globtab); + LS.rawset(LuaRegistry, "classes", LuaNewTable); + LS.rawset(LuaRegistry, "classnames", LuaNewTable); + LS.rawset(LuaRegistry, "tangibles", LuaNewTable); + LS.rawset(LuaRegistry, "persist", LuaNewTable); + LS.rawset(LuaRegistry, "unpersist", LuaNewTable); + + // Tag the registry and global environment with their tabletypes. + LS.settabletype(LuaRegistry, LUA_TT_REGISTRY); + LS.getglobaltable(globtab); + LS.settabletype(globtab, LUA_TT_GLOBALENV); - lua_pushstring(L, "classes"); - lua_newtable(L); - lua_rawset(L, LUA_REGISTRYINDEX); - lua_pushstring(L, "classnames"); - lua_newtable(L); - lua_rawset(L, LUA_REGISTRYINDEX); - lua_pushstring(L, "tangibles"); - lua_newtable(L); - lua_rawset(L, LUA_REGISTRYINDEX); return L; } diff --git a/luprex/cpp/core/streambuffer.hpp b/luprex/cpp/core/streambuffer.hpp index cad543ad..61000b2f 100644 --- a/luprex/cpp/core/streambuffer.hpp +++ b/luprex/cpp/core/streambuffer.hpp @@ -262,6 +262,8 @@ public: class StreamBuffer : public eng::nevernew, public BaseBuffer { public: using BaseBuffer::BaseBuffer; + StreamBuffer(const StreamBuffer &) = delete; + StreamBuffer &operator=(const StreamBuffer &) = delete; void write_hashvalue(const util::HashValue &h) { write_uint64(h.first); diff --git a/luprex/cpp/core/world-core.cpp b/luprex/cpp/core/world-core.cpp index fabd02cd..1a34617c 100644 --- a/luprex/cpp/core/world-core.cpp +++ b/luprex/cpp/core/world-core.cpp @@ -116,8 +116,8 @@ World::World(WorldType wt) { } // Prepare to manipulate the lua state. - LuaVar world, globtab; - LuaExtStack LS(state(), world, globtab); + LuaVar world; + LuaExtStack LS(state(), world); // Put the world pointer into the lua registry. World::store_global_pointer(state(), this); @@ -125,14 +125,6 @@ World::World(WorldType wt) { // Clear the lthread state. clear_lthread_state(); - // Set the tabletype of the registry. - LS.settabletype(LuaRegistry, LUA_TT_REGISTRY); - - // Set the tabletype of the global environment. - LS.getglobaltable(globtab); - LS.settabletype(globtab, LUA_TT_GLOBALENV); - - // Create the globaldb in the registry. LS.rawset(LuaRegistry, "globaldb", LuaNewTable); diff --git a/luprex/cpp/core/world-difftab.cpp b/luprex/cpp/core/world-difftab.cpp index 07818fe9..d4982b9d 100644 --- a/luprex/cpp/core/world-difftab.cpp +++ b/luprex/cpp/core/world-difftab.cpp @@ -8,13 +8,8 @@ // World::patch_numbered_tables // World::patch_tangible_databases // -// It also contains these unit testing support routines: -// -// table.diffcompare -// table.diffapply -// // This file also contains all the support code needed to implement -// this stuff. +// this stuff, plus the unit tests (unittests_difftab). // //////////////////////////////////////////////////////////////////// @@ -142,52 +137,6 @@ static void transmit_value(LuaCoreStack &MLS, LuaSlot mval, LuaSlot mtnmap, Stre } } -static void transmit_value_debug_string(StreamBuffer *sb, eng::ostringstream &oss) { - int kind = sb->read_uint8(); - switch (kind) { - case LUA_TBOOLEAN: { - bool b = sb->read_bool(); - oss << (b ? "true":"false"); - return; - } - case LUA_TNUMBER: { - oss << sb->read_double(); - return; - } - case LUA_TSTRING: { - oss << sb->read_string(); - return; - } - case LUA_TLIGHTUSERDATA: { - LuaToken token(sb->read_uint64()); - oss << "[" << token.str() << "]"; - return; - } - case LUA_TT_GENERAL: { - oss << "table " << sb->read_int32(); - return; - } - case LUA_TT_CLASS: { - oss << "class " << sb->read_string(); - return; - } - case LUA_TT_TANGIBLE: { - oss << "tan " << sb->read_int64(); - return; - } - case LUA_TT_GLOBALENV: { - oss << "globals"; - return; - } - case LUA_TNIL: { - oss << "nil"; - return; - } - default: - assert(false); // Should not get here. - } -} - static bool diff_tables(LuaCoreStack &SLS0, LuaSlot stnmap, LuaSlot stab, LuaCoreStack &MLS0, LuaSlot mtnmap, LuaSlot mtab, bool cmeta, StreamBuffer *sb) { @@ -240,24 +189,6 @@ static bool diff_tables(LuaCoreStack &SLS0, LuaSlot stnmap, LuaSlot stab, return (nupdates > 0); } -static eng::string diff_tables_debug_string(StreamBuffer *sb) { - eng::vector sorted; - eng::ostringstream oss; - int ndiffs = sb->read_int32(); - for (int i = 0; i < ndiffs; i++) { - transmit_value_debug_string(sb, oss); - oss << "="; - transmit_value_debug_string(sb, oss); - sorted.push_back(oss.str()); - oss.str(""); - } - std::sort(sorted.begin(), sorted.end()); - for (const eng::string &s : sorted) { - oss << s << ";"; - } - return oss.str(); -} - static void set_transmitted_value(LuaCoreStack &LS, LuaSlot tangibles, LuaSlot ntmap, LuaSlot target, StreamBuffer *sb, const char *dbinfo, DebugCollector *dbc) { int kind = sb->read_uint8(); switch (kind) { @@ -441,82 +372,302 @@ void World::diff_tangible_databases(const IdVector &basis, lua_State *master, St sb->overwrite_int32(write_count_after, nmodified); } -LuaDefine(table_diffcompare, "mtnmap,mtab,stnmap,stab", "for unit testing only") { - LuaArg mtnmap, mtab, mstnmap, mstab; - LuaRet dbgstring; - LuaVar tthread; - LuaDefStack MLS(L, mtnmap, mtab, mstnmap, mstab, dbgstring, tthread); - // Check the arguments. - MLS.cktable(mtnmap, "mtnmap"); - MLS.cktable(mstnmap, "mstnmap"); - MLS.cktable(mtab, "mtab"); - MLS.cktable(mstab, "mstab"); +//////////////////////////////////////////////////////////////////// +// +// Unit Testing Framework for Table Diff +// +// The function test_diffcompare creates a standalone lua environment, +// evaluates two lua expressions to build a master table and a synch +// table, runs diff_tables, and returns the result as a debug string. +// +//////////////////////////////////////////////////////////////////// - // Create a temporary thread to be the 'synch model'. We'll use the - // existing thread as the 'master model'. Move two tables to the synch thread. - lua_State *synch = lua_newthread(L); - lua_replace(L, tthread.index()); - lua_pushvalue(L, mstnmap.index()); - lua_pushvalue(L, mstab.index()); - lua_xmove(L, synch, 2); - LuaArg stnmap,stab; - LuaDefStack SLS(synch, stnmap, stab); +#include "source.hpp" +#include "traceback.hpp" +#include "pprint.hpp" +#include "wrap-sstream.hpp" - // Call tablecmp_diff. - StreamBuffer sb; - diff_tables(SLS, stnmap, stab, MLS, mtnmap, mtab, true, &sb); +class DiffTester { + lua_State *master_L_ = nullptr; + lua_State *synch_L_ = nullptr; + lua_State *caller_; + SourceDB master_sdb_; + SourceDB synch_sdb_; - // Convert the output to a debug string. - MLS.set(dbgstring, diff_tables_debug_string(&sb)); - return MLS.result(); -} - -LuaDefine(table_diffapply, "mtnmap,mtab,mstab", "for unit testing only") { - LuaArg mtnmap, mtab, mstab; - LuaRet eql, eqlstr, rtab; - LuaVar tthread, tangibles, mntmap, key, val; - LuaDefStack MLS(L, mtnmap, mtab, mstab, eql, eqlstr, rtab, tthread, tangibles, mntmap, key, val); - // Check the arguments. - MLS.cktable(mtnmap, "mtnmap"); - MLS.cktable(mtab, "mtab"); - MLS.cktable(mstab, "mstab"); - - // Get the tangibles map. - MLS.rawget(tangibles, LuaRegistry, "tangibles"); - - // Invert the tnmap to make the ntmap. - MLS.set(mntmap, LuaNewTable); - MLS.set(key, LuaNil); - while (MLS.next(mtnmap, key, val)) { - MLS.rawset(mntmap, val, key); + static void transmit_value_debug_string(StreamBuffer *sb, eng::ostringstream &oss) { + int kind = sb->read_uint8(); + switch (kind) { + case LUA_TBOOLEAN: { + bool b = sb->read_bool(); + oss << (b ? "true":"false"); + return; + } + case LUA_TNUMBER: { + oss << sb->read_double(); + return; + } + case LUA_TSTRING: { + oss << sb->read_string(); + return; + } + case LUA_TLIGHTUSERDATA: { + LuaToken token(sb->read_uint64()); + oss << "[" << token.str() << "]"; + return; + } + case LUA_TT_GENERAL: { + oss << "tab" << sb->read_int32(); + return; + } + case LUA_TT_CLASS: { + oss << "class " << sb->read_string(); + return; + } + case LUA_TT_TANGIBLE: { + oss << "tan " << sb->read_int64(); + return; + } + case LUA_TT_GLOBALENV: { + oss << "globals"; + return; + } + case LUA_TNIL: { + oss << "nil"; + return; + } + default: + assert(false); // Should not get here. + } } - // Create a temporary thread to be the 'synch model'. We'll use the - // existing thread as the 'master model'. - lua_State *synch = lua_newthread(L); - lua_replace(L, tthread.index()); - lua_pushvalue(L, mtnmap.index()); - lua_pushvalue(L, mstab.index()); - lua_xmove(L, synch, 2); - LuaArg stnmap, stab; - LuaDefStack SLS(synch, stnmap, stab); - - // Call diff_tables and patch_tables - StreamBuffer sb; - diff_tables(SLS, stnmap, stab, MLS, mtnmap, mtab, true, &sb); - - patch_table(MLS, tangibles, mntmap, mstab, &sb, nullptr); - - bool eq = table_equal(MLS, mstab, mtab); - MLS.set(eql, eq); - if (eq) { - MLS.set(eqlstr, "tables equal"); - } else { - MLS.set(eqlstr, "tables were supposed to be equal"); + static eng::string diff_tables_debug_string(StreamBuffer *sb) { + eng::vector sorted; + eng::ostringstream oss; + int ndiffs = sb->read_int32(); + for (int i = 0; i < ndiffs; i++) { + transmit_value_debug_string(sb, oss); + oss << "="; + transmit_value_debug_string(sb, oss); + sorted.push_back(oss.str()); + oss.str(""); + } + std::sort(sorted.begin(), sorted.end()); + for (const eng::string &s : sorted) { + oss << s << ";"; + } + return oss.str(); } - MLS.set(rtab, stab); - return MLS.result(); +public: + DiffTester(lua_State *caller) : caller_(caller) { + SourceDB::register_lua_builtins(); + master_L_ = LuaCoreStack::newstate(eng::l_alloc); + synch_L_ = LuaCoreStack::newstate(eng::l_alloc); + master_sdb_.init(master_L_); + synch_sdb_.init(synch_L_); + } + + ~DiffTester() { + if (master_L_) lua_close(master_L_); + if (synch_L_) lua_close(synch_L_); + } + + // Create stock test tables and their maps in a single lua environment. + // Sets up globals tab1-tab9 and populates the tnmap and ntmap. + void create_stock_tables(LuaCoreStack &LS, LuaSlot tnmap, LuaSlot ntmap) { + lua_State *L = LS.state(); + LuaVar globtab; + LuaExtStack LS2(L, globtab); + LS.set(tnmap, LuaNewTable); + LS.set(ntmap, LuaNewTable); + LS.getglobaltable(globtab); + for (int i = 1; i <= 9; i++) { + LuaVar tabi; + LuaExtStack LS3(L, tabi); + LS.set(tabi, LuaNewTable); + LS.rawset(tabi, 1, util::ss("tab", i)); + LS.rawset(globtab, util::ss("tab", i), tabi); + LS.rawset(tnmap, tabi, i); + LS.rawset(ntmap, i, tabi); + } + } + + // Pretty-print a lua value to a string. + eng::string pprint(LuaCoreStack &LS, LuaSlot val) { + std::ostringstream oss; + PrettyPrint::Indented().print(LS, val, &oss); + return eng::string(oss.str()); + } + + // Evaluate a lua expression and store the result in the given slot. + void eval(LuaCoreStack &LS, LuaSlot dest, const char *expr, const char *name) { + lua_State *L = LS.state(); + LuaVar closure; + LuaExtStack LS2(L, closure); + eng::string err = LS2.load(closure, util::ss("return ", expr), name); + LuaAssert(caller_, err.empty()); + lua_pushvalue(L, closure.index()); + err = traceback_pcall(L, 0, 1); + LuaAssert(caller_, err.empty()); + lua_replace(L, dest.index()); + } + + eng::string diffcompare(const char *master_expr, const char *synch_expr) { + assert(lua_gettop(master_L_) == 0); + assert(lua_gettop(synch_L_) == 0); + + LuaVar mtnmap, mntmap, mtab; + LuaExtStack MLS(master_L_, mtnmap, mntmap, mtab); + LuaVar stnmap, sntmap, stab; + LuaExtStack SLS(synch_L_, stnmap, sntmap, stab); + + create_stock_tables(MLS, mtnmap, mntmap); + create_stock_tables(SLS, stnmap, sntmap); + eval(MLS, mtab, master_expr, "master"); + eval(SLS, stab, synch_expr, "synch"); + + // Run diff_tables. + StreamBuffer sb; + diff_tables(SLS, stnmap, stab, MLS, mtnmap, mtab, true, &sb); + + return diff_tables_debug_string(&sb); + } + + bool diffapply(const char *master_expr, const char *synch_expr, bool verbose = false) { + assert(lua_gettop(master_L_) == 0); + assert(lua_gettop(synch_L_) == 0); + + LuaVar mtnmap, mntmap, mtab; + LuaExtStack MLS(master_L_, mtnmap, mntmap, mtab); + LuaVar stnmap, sntmap, stab, tangibles; + LuaExtStack SLS(synch_L_, stnmap, sntmap, stab, tangibles); + + create_stock_tables(MLS, mtnmap, mntmap); + create_stock_tables(SLS, stnmap, sntmap); + eval(MLS, mtab, master_expr, "master"); + eval(SLS, stab, synch_expr, "synch"); + + // Get the tangibles map from the synch environment. + SLS.rawget(tangibles, LuaRegistry, "tangibles"); + + // Diff and patch. + StreamBuffer sb; + diff_tables(SLS, stnmap, stab, MLS, mtnmap, mtab, true, &sb); + if (verbose) { + StreamBuffer sb_copy(sb.view()); + eng::string diff_str = diff_tables_debug_string(&sb_copy); + printf("diffapply: master_expr = %s\n", master_expr); + printf("diffapply: synch_expr = %s\n", synch_expr); + printf("diffapply: diff = %s\n", diff_str.c_str()); + } + patch_table(SLS, tangibles, sntmap, stab, &sb, nullptr); + + // Check equality by pretty-printing both tables and comparing. + eng::string master_str = pprint(MLS, mtab); + eng::string synch_str = pprint(SLS, stab); + if (verbose) { + printf("diffapply: master pprint (%d chars) = %s\n", (int)master_str.size(), master_str.c_str()); + printf("diffapply: synch pprint (%d chars) = %s\n", (int)synch_str.size(), synch_str.c_str()); + } + return master_str == synch_str; + } +}; + +LuaDefine(unittests_difftab, "", "unit tests for table diff") { + DiffTester dt(L); + + // No differences in these simple-valued tables. + LuaAssertStrEq(L, dt.diffcompare("{a=true}", "{a=true}"), ""); + LuaAssertStrEq(L, dt.diffcompare("{a=5}", "{a=5}"), ""); + LuaAssertStrEq(L, dt.diffcompare("{a='foo'}", "{a='foo'}"), ""); + + // Test transmission of missing simple values. + LuaAssertStrEq(L, dt.diffcompare("{a=true}", "{}"), "a=true;"); + LuaAssertStrEq(L, dt.diffcompare("{a=5}", "{}"), "a=5;"); + LuaAssertStrEq(L, dt.diffcompare("{a='foo'}", "{}"), "a=foo;"); + + // Test the replacement of simple values. + LuaAssertStrEq(L, dt.diffcompare("{a=true}", "{a=false}"), "a=true;"); + LuaAssertStrEq(L, dt.diffcompare("{a=5}", "{a=4}"), "a=5;"); + LuaAssertStrEq(L, dt.diffcompare("{a='foo'}", "{a='bar'}"), "a=foo;"); + + // Test the clearing of values. + LuaAssertStrEq(L, dt.diffcompare("{}", "{a=true}"), "a=nil;"); + LuaAssertStrEq(L, dt.diffcompare("{}", "{a=5}"), "a=nil;"); + LuaAssertStrEq(L, dt.diffcompare("{}", "{a='foo'}"), "a=nil;"); + + // Try boolean keys. + LuaAssertStrEq(L, dt.diffcompare("{[true]=3}", "{}"), "true=3;"); + LuaAssertStrEq(L, dt.diffcompare("{}", "{[true]=3}"), "true=nil;"); + + // Try number keys. + LuaAssertStrEq(L, dt.diffcompare("{[7]=3}", "{}"), "7=3;"); + LuaAssertStrEq(L, dt.diffcompare("{}", "{[7]=3}"), "7=nil;"); + + // Try a table with multiple keys. + LuaAssertStrEq(L, dt.diffcompare("{a=4, b=5, c=6}", "{b=5, c=7, d=8}"), "a=4;c=6;d=nil;"); + + // Nonsortable keys should be ignored (no diffs). + LuaAssertStrEq(L, dt.diffcompare("{[{}]=3}", "{}"), ""); + + // Numbered tables: matching pairs produce no diff. + LuaAssertStrEq(L, dt.diffcompare("{a=tab1}", "{a=tab1}"), ""); + + // Numbered tables: missing in synch. + LuaAssertStrEq(L, dt.diffcompare("{a=tab1}", "{}"), "a=tab1;"); + + // Numbered tables: wrong table number. + LuaAssertStrEq(L, dt.diffcompare("{a=tab1}", "{a=tab2}"), "a=tab1;"); + + // Numbered tables: replaced by simple value. + LuaAssertStrEq(L, dt.diffcompare("{a=3}", "{a=tab1}"), "a=3;"); + + // Numbered tables: cleared. + LuaAssertStrEq(L, dt.diffcompare("{}", "{a=tab1}"), "a=nil;"); + + // Unnumbered tables are forced to nil. + LuaAssertStrEq(L, dt.diffcompare("{a={}}", "{}"), ""); + LuaAssertStrEq(L, dt.diffcompare("{a={}}", "{a=3}"), "a=nil;"); + + // Class values. + LuaAssertStrEq(L, dt.diffcompare("{a=deque}", "{}"), "a=class deque;"); + + // Global environment. + LuaAssertStrEq(L, dt.diffcompare("{a=_G}", "{}"), "a=globals;"); + + // Metatable: set, match, clear. + LuaAssertStrEq(L, dt.diffcompare("setmetatable({}, tab1)", "{}"), "nil=tab1;"); + LuaAssertStrEq(L, dt.diffcompare("setmetatable({}, tab1)", "setmetatable({}, tab1)"), ""); + LuaAssertStrEq(L, dt.diffcompare("{}", "setmetatable({}, tab1)"), "nil=nil;"); + + // Diff-apply: verify simple values. + LuaAssert(L, dt.diffapply("{a=1}", "{}")); + LuaAssert(L, dt.diffapply("{[true]='foo'}", "{}")); + LuaAssert(L, dt.diffapply("{[3]=false}", "{}")); + + // Diff-apply: multiple simple values. + LuaAssert(L, dt.diffapply("{a=1, b=2, c=3}", "{}")); + + // Diff-apply: remove or replace wrong values. + LuaAssert(L, dt.diffapply("{a=1, b=2}", "{b=3, c=4}")); + + // Diff-apply: table containing a numbered table. + LuaAssert(L, dt.diffapply("{a=tab1, b=tab2}", "{}")); + + // Diff-apply: table containing a class. + LuaAssert(L, dt.diffapply("{a=deque, b=table}", "{}")); + + // Diff-apply: table containing the global environment. + LuaAssert(L, dt.diffapply("{a=_G}", "{}")); + + // Diff-apply: unnumbered tables are forced to nil. + LuaAssert(L, !dt.diffapply("{a={}}", "{a=3}")); + + // Diff-apply: set metatable. + LuaAssert(L, dt.diffapply("setmetatable({}, tab1)", "{}")); + + // Diff-apply: clear metatable. + LuaAssert(L, dt.diffapply("{}", "setmetatable({}, tab1)")); + + return 0; } - - diff --git a/luprex/lua/control.lst b/luprex/lua/control.lst index a92c13fb..fc2e8c43 100644 --- a/luprex/lua/control.lst +++ b/luprex/lua/control.lst @@ -4,6 +4,5 @@ # ut-table.lua -ut-tablecmp.lua hotkeys.lua login.lua diff --git a/luprex/lua/ut-tablecmp.lua b/luprex/lua/ut-tablecmp.lua deleted file mode 100644 index 5699b104..00000000 --- a/luprex/lua/ut-tablecmp.lua +++ /dev/null @@ -1,135 +0,0 @@ - --- the tdc function calculates diffs, and returns those --- diffs as a human-readable string. -local tdc = table.diffcompare - --- the tda function calculates diffs, applies the diffs to the second --- table, and then returns true if the second table equals the first. -local tda = table.diffapply - -function unittests.diffcompare() - local mtab = nil - local rtab = nil - - -- No differences in these simple-valued tables. - assert(tdc({}, {a=true}, {}, {a=true}) == "") - assert(tdc({}, {a=5}, {}, {a=5}) == ""); - assert(tdc({}, {a="foo"}, {}, {a="foo"}) == "") - - -- Test transmission of missing simple values. - assert(tdc({}, {a=true}, {}, {}) == "a=true;") - assert(tdc({}, {a=5}, {}, {}) == "a=5;"); - assert(tdc({}, {a="foo"}, {}, {}) == "a=foo;") - - -- Test the replacement of simple values. - assert(tdc({}, {a=true}, {}, {a=false}) == "a=true;") - assert(tdc({}, {a=5}, {}, {a=4}) == "a=5;"); - assert(tdc({}, {a="foo"}, {}, {a="bar"}) == "a=foo;") - - -- Test the clearing of values. - assert(tdc({}, {}, {}, {a=true}) == "a=nil;") - assert(tdc({}, {}, {}, {a=5}) == "a=nil;"); - assert(tdc({}, {}, {}, {a="foo"}) == "a=nil;") - - -- Try boolean keys. - assert(tdc({}, {[true]=3}, {}, {}) == "true=3;") - assert(tdc({}, {}, {}, {[true]=3}) == "true=nil;") - - -- Try number keys. - assert(tdc({}, {[7]=3}, {}, {}) == "7=3;") - assert(tdc({}, {}, {}, {[7]=3}) == "7=nil;") - - -- Try a table with multiple keys. - assert(tdc({}, {a=4, b=5, c=6}, {}, {b=5, c=7, d=8}) == "a=4;c=6;d=nil;") - - -- Nonsortable keys should be ignored (no diffs). - assert(tdc({}, {[{}]=3}, {}, {}) == "") - - -- Try a table containing a class. - assert(tdc({}, {a=deque}, {}, {}) == "a=class deque;") - - -- Try a table containing a pointer to the global environment. - assert(tdc({}, {a=_G}, {}, {}) == "a=globals;") - - -- Set up some numbered tables for tests involving such. - local mtab10 = {} - local stab10 = {} - local mtnmap = {} - local stnmap = {} - mtnmap[mtab10] = 10 - stnmap[stab10] = 10 - - -- confirm that numbered tables are being transmitted. - assert(tdc(mtnmap, {a=mtab10}, stnmap, {}) == "a=table 10;") - assert(tdc(mtnmap, {a=mtab10}, stnmap, {a=stab10}) == "") - assert(tdc(mtnmap, {a=3}, stnmap, {a=stab10}) == "a=3;") - assert(tdc(mtnmap, {}, stnmap, {a=stab10}) == "a=nil;") - - -- confirm that unnumbered tables are forced to nil. - assert(tdc(mtnmap, {a={}}, stnmap, {}) == "") - assert(tdc(mtnmap, {a={}}, stnmap, {a=3}) == "a=nil;") - - -- transmit a correction to the metatable. - mtab={} - stab={} - setmetatable(mtab, mtab10) - setmetatable(stab, stab10) - assert(tdc(mtnmap, mtab, stnmap, {}) == "nil=table 10;") - assert(tdc(mtnmap, mtab, stnmap, stab) == "") - assert(tdc(mtnmap, {}, stnmap, stab) == "nil=nil;") - - -- we're not going to test tangibles - -- creating tangibles here is too difficult. -end - -function unittests.diffapply() - local tab10={id=tab10} - local tab11={id=tab11} - local tnmap={} - tnmap[tab10] = 10 - tnmap[tab11] = 11 - local mtab=nil - local stab=nil - - -- verify some simple values. - assert(tda(tnmap, {a=1}, {})) - assert(tda(tnmap, {[true]="foo"}, {})) - assert(tda(tnmap, {[3]=false}, {})) - - -- verify a table with multiple simple values. - assert(tda(tnmap, {a=1, b=2, c=3}, {})) - - -- verify that it can remove or replace wrong values. - assert(tda(tnmap, {a=1,b=2}, {b=3,c=4})) - - -- verify a table containing another table. - assert(tda(tnmap, {a=tab10, b=tab11}, {})) - - -- verify a table containing a class. - assert(tda(tnmap, {a=deque, b=table}, {})) - - -- verify a table containing the global environment. - assert(tda(tnmap, {a=_G}, {})) - - -- Unnumbered tables should be forced to NIL. - rtab={a=3} - assert(not tda({}, {a={}}, rtab)) - assert(rtab.a == nil) - - -- transmit a correction to the metatable - mtab={} - rtab={} - setmetatable(mtab, tab10) - assert(tda(tnmap, mtab, rtab)) - assert(getmetatable(rtab) == tab10) - - -- transmit a clearing of the metatable - mtab={} - rtab={} - setmetatable(rtab, tab10) - assert(tda(tnmap, mtab, rtab)) - assert(getmetatable(rtab) == nil) - - -- don't test tangibles. -end -