I wrote a CppUnit test suite to unit-test the code that I posted here which is a single routine that proposes how a Lua expression could be auto-completed.
What do you think? I appreciate all the comments.
#include <boost/format.hpp>
#include <cppunit/extensions/HelperMacros.h>
class Tests : public CppUnit::TestFixture
{
private:
lua_State* L;
protected:
void execute (const std::string& script)
{
luaL_dostring(L, script.c_str());
}
void assertSize (const std::vector<std::string>& candidates, size_t size)
{
CPPUNIT_ASSERT_EQUAL_MESSAGE(
boost::str(boost::format("The candidate list size is %d, but should be %d.") % candidates.size() % size),
size, candidates.size()
);
}
void assertContains (const std::vector<std::string>& candidates, const std::string& value)
{
CPPUNIT_ASSERT_MESSAGE(
boost::str(boost::format("Candidate list doesn't contain '%s'.") % value),
std::find(candidates.begin(), candidates.end(), value) != candidates.end()
);
}
public:
void setUp()
{
L = luaL_newstate();
luaL_openlibs(L);
}
void tearDown()
{
CPPUNIT_ASSERT_EQUAL_MESSAGE("The stack is not empty.", 0, lua_gettop(L));
lua_close(L);
}
void globalNamesAreAutocompleted()
{
execute("resources = { textures = { 'texture1', 'texture2', 'texture3' } }");
execute("resolution = { sizeX = 2560, sizeY = 1440 }");
auto candidates = buildAutoCompleteCandidates(L, "reso");
assertSize(candidates, 2);
assertContains(candidates, "resources");
assertContains(candidates, "resolution");
}
void valuesWithinTablesAreAutocompleted()
{
execute("europe = { countries = { France = { cities = { Paris = { LeMarais = {}, LesHalls = {}, Montparnasse = {}, Montmartre = {} }}}}}");
auto candidates = buildAutoCompleteCandidates(L, "europe.countries['France'].cities['Paris'].Mont");
assertSize(candidates, 2);
assertContains(candidates, "europe.countries['France'].cities['Paris'].Montparnasse");
assertContains(candidates, "europe.countries['France'].cities['Paris'].Montmartre");
}
void metatableIndexTableIsFollowed()
{
execute("metaValues = { metaValue = 'AAA' }");
execute("values = setmetatable({ value = 'aaa' }, { __index = metaValues })");
auto candidates = buildAutoCompleteCandidates(L, "values.");
assertSize(candidates, 2);
assertContains(candidates, "values.metaValue");
assertContains(candidates, "values.value");
}
void metatableIndexTableChainIsFollowed()
{
execute("metaValues1 = { valueA = 'alpha' }");
execute("metaValues2 = { valueB = 'beta' }");
execute("metaValues3 = { valueC = 'kappa' }");
execute("setmetatable(metaValues3, { __index = metaValues2 })");
execute("setmetatable(metaValues2, { __index = metaValues1 })");
execute("values = { value = 'theta' }");
execute("setmetatable(values, { __index = metaValues3 })");
auto candidates = buildAutoCompleteCandidates(L, "values.");
assertSize(candidates, 4);
assertContains(candidates, "values.value");
assertContains(candidates, "values.valueA");
assertContains(candidates, "values.valueB");
assertContains(candidates, "values.valueC");
}
void metatableLoopBreaks()
{
execute("loopTable1 = { valueA = 'alpha' }");
execute("loopTable2 = { valueB = 'beta' }");
execute("loopTable3 = { valueC = 'kappa' }");
execute("setmetatable(loopTable1, { __index = loopTable2 })");
execute("setmetatable(loopTable2, { __index = loopTable3 })");
execute("setmetatable(loopTable3, { __index = loopTable1 })");
auto candidates = buildAutoCompleteCandidates(L, "loopTable1.");
assertSize(candidates, 3);
assertContains(candidates, "loopTable1.valueA");
assertContains(candidates, "loopTable1.valueB");
assertContains(candidates, "loopTable1.valueC");
}
void blankExpressionAutocompletsWithAllGlobals()
{
execute("star = 'Alpha Centauri'");
execute("spaceship = 'Millenium Falcon'");
execute("planets = 'Tarsonis'");
auto candidates = buildAutoCompleteCandidates(L, "");
CPPUNIT_ASSERT(candidates.size() > 25);
assertContains(candidates, "star");
assertContains(candidates, "spaceship");
assertContains(candidates, "planets");
assertContains(candidates, "io");
assertContains(candidates, "table");
assertContains(candidates, "print");
}
void colonReturnsFunctionValues()
{
execute("calculator = { sin = math.sin, cos = math.cos, pi = 3.14, e = 2.7182 }");
auto candidates = buildAutoCompleteCandidates(L, "calculator:");
assertSize(candidates, 2);
assertContains(candidates, "calculator:sin");
assertContains(candidates, "calculator:cos");
}
static CppUnit::Test* suite()
{
CppUnit::TestSuite* suite = new CppUnit::TestSuite("AutocompleteSuite");
suite->addTest(new CppUnit::TestCaller<Tests>("Global names are autocompleted", &Tests::globalNamesAreAutocompleted));
suite->addTest(new CppUnit::TestCaller<Tests>("Values within tables are autocompleted", &Tests::valuesWithinTablesAreAutocompleted));
suite->addTest(new CppUnit::TestCaller<Tests>("Metatable index table is followed", &Tests::metatableIndexTableIsFollowed));
suite->addTest(new CppUnit::TestCaller<Tests>("Metatable index table chain is followed", &Tests::metatableIndexTableChainIsFollowed));
suite->addTest(new CppUnit::TestCaller<Tests>("Metatable loop breaks", &Tests::metatableLoopBreaks));
suite->addTest(new CppUnit::TestCaller<Tests>("Blank expression autocompletes with all globals", &Tests::blankExpressionAutocompletsWithAllGlobals));
suite->addTest(new CppUnit::TestCaller<Tests>("Colon returns function values", &Tests::colonReturnsFunctionValues));
return suite;
}
};