7
\$\begingroup\$

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;
    }
};
\$\endgroup\$
7
\$\begingroup\$

Coverage

Your tests don't cover

  • the case where there is no completion because the last separator is called on an entry that is not a table

  • the case where __index is not a table

This hints at a general problem: It seems that you are doing sunny day tests only.

Non selfcontained source

It seems you did not post the whole code because this does not work as is. (Even after "including" the code from the other question.) This opens the door for problems that occur because the answerers did not create the same environment as you have there.

Missing includes

Your test code does not include

  • the necessary lua libraries for functions (or macros) like luaL_dostring.
  • the test runner needed for actually running the test (I used cppunit/ui/text/TestRunner.h)

Naming

  • L (looks like a macro, too generic (although probably idiomatic in Lua context))-> luaState
  • script -> expression (at least you did never pass a script but only expression to execute)
  • blankExpressionAutocompletsWithAllGlobals (typo) -> blankExpressionAutocompletesWithAllGlobals
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.