/***************************************************************************** * Open LiteSpeed is an open source HTTP server. * * Copyright (C) 2013 - 2015 LiteSpeed Technologies, Inc. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see http://www.gnu.org/licenses/. * *****************************************************************************/ #ifdef RUN_TEST #include #include #include #include #include "unittest-cpp/UnitTest++.h" // #define RADIXTREE_DEBUG // #define RADIXTREE_PRINTTREE #define RT_DOREGTEST #define RT_DOWCTEST #define RTTEST_NOCONTEXT (1 << 0) #define RTTEST_GLOBALPOOL (1 << 1) #define RTTEST_MERGE (1 << 2) const char *pInputs[] = { "/home/one/user/dir", "/home/two/user/dir", "/home/one/client/", "/home/one/client/dir", "/home/one", "/home/one/client1", "/home/one/client2", "/home/one/client3", "/home/one/client4", "/home/one/client5", "/home/one/client6", "/home/one/client7", "/home/one/client8", "/home/one/client9", "/home/one/client10", "/home/one/client11", "/home/one/client12", "/home/one/client13", "/home/one/client14", "/home/one/client15", "/home/one/client16", "/home/one/client17", "/home/one/client18", "/home/one/client19", "/not/starting/with/slash", "/not", "/home/one/two/three/four/five/six/seven/eight/nine/ten/" }; const int iInputLen = 27; const char *pWCTest[] = { "/home/user*", "/home/test*", "/home/newbegin*", "/home/end*", "/home/*diff", "/home/in*middle", "/home/username/", "/home/testing", "/home/test/wherewillthisgo", "/home/testing/where/this/will/go", "/home/*", "/home/*/has/children", "/home/*/has/multi", "/home/*/should/these/be/found/", "/home/*/multi/layer*", "/home/*/to/be.sure", "/home/one/*" }; const int iWCLen = 17; // How got to this value: // 1 - /home/* added obj to /home/test // 9 - /home/*/to/be.sure added 9 objects, and was not deleted at the end. // - 4 - When erasing, erased the original objects as well. const int iMergeIncr = 1 + 9 - 4; const char *pWCFind[] = { "/home/usermatch/", "/home/testmatch/", "/home/newbeginning", "/home/ending", "/home/blah/to/be.sure", "/home/updating", "/home/one/match" }; const int iWCFindLen = 7; static int test_for_each(void *pObj, const char *pKey, int iKeyLen) { #ifdef RADIXTREE_DEBUG printf("%.*s, %p\n", iKeyLen, pKey, pObj); #endif return 0; } static int test_for_each2(void *pObj, void *pUData, const char *pKey, int iKeyLen) { #ifdef RADIXTREE_DEBUG int *p = (int *)pUData; printf("%.*s, %p %d\n", iKeyLen, pKey, pObj, ++(*p)); #endif return 0; } void doTest(RadixTree *pTree, char **pDynamicInputs, int count) { int i; RadixTree tree2; #ifdef RADIXTREE_PRINTTREE pTree->printTree(); #endif for (i = 0; i < count; ++i) { #ifdef RADIXTREE_DEBUG printf("Testing add %s\n", pDynamicInputs[i]); #endif CHECK(pTree->insert(pDynamicInputs[i], strlen(pDynamicInputs[i]), pTree) != NULL); #ifdef RADIXTREE_PRINTTREE pTree->printTree(); #endif } for (i = 0; i < count; i += 2) { #ifdef RADIXTREE_DEBUG printf("Testing update %s\n", pDynamicInputs[i]); #endif CHECK(pTree->update(pDynamicInputs[i], strlen(pDynamicInputs[i]), &tree2) == pTree); #ifdef RADIXTREE_PRINTTREE pTree->printTree(); #endif } for (i = 0; i < count; i += 2) { #ifdef RADIXTREE_DEBUG printf("Testing find %s\n", pDynamicInputs[i]); #endif CHECK(pTree->find(pDynamicInputs[i], strlen(pDynamicInputs[i])) == &tree2); #ifdef RADIXTREE_PRINTTREE pTree->printTree(); #endif } for (i = 1; i < count; i += 2) { #ifdef RADIXTREE_DEBUG printf("Testing find %s\n", pDynamicInputs[i]); #endif CHECK(pTree->find(pDynamicInputs[i], strlen(pDynamicInputs[i])) == pTree); } const char *pBegin = pDynamicInputs[26]; const char *pNext = (const char *)memchr(pBegin + 1, '/', strlen(pBegin) - 1); void *pOut, *data = pTree->bestMatch(pDynamicInputs[4], strlen(pDynamicInputs[4])); CHECK((pOut = pTree->bestMatch(pBegin, pNext - pBegin)) == NULL); #ifdef RADIXTREE_DEBUG printf("Should be null: %p\n", pOut); #endif if (pTree->getNoContext() == 0) { while ((pNext = (const char *)memchr(pNext + 1, '/', strlen(pNext) - 1)) != NULL) { CHECK((pOut = pTree->bestMatch(pBegin, pNext - pBegin)) == data); #ifdef RADIXTREE_DEBUG printf("Should match: %p %p\n", pOut, data); #endif } } else { pNext = (const char *)memchr(pNext + 1, '/', strlen(pNext) - 1); CHECK((pOut = pTree->bestMatch(pBegin, pNext - pBegin)) == data); while ((pNext = (const char *)memchr(pNext + 1, '/', strlen(pNext) - 2)) != NULL) { CHECK((pOut = pTree->bestMatch(pBegin, pNext - pBegin)) == NULL); #ifdef RADIXTREE_DEBUG printf("Should be NULL: %p\n", pOut); #endif } CHECK((pOut = pTree->bestMatch(pBegin, strlen(pBegin))) == &tree2); } CHECK(pTree->for_each(test_for_each) == count); int p = 0; CHECK(pTree->for_each2(test_for_each2, &p) == count); } void doWCTest(RadixTree *pTree) { int i, count = iWCLen + iInputLen; RadixTree tree2; RadixTree tree3; #ifdef RADIXTREE_PRINTTREE pTree->printTree(); #endif for (i = 0; i < iWCLen; ++i) { #ifdef RADIXTREE_DEBUG printf("Testing add %s\n", pWCTest[i]); #endif CHECK(pTree->insert(pWCTest[i], strlen(pWCTest[i]), pTree) != NULL); #ifdef RADIXTREE_PRINTTREE pTree->printTree(); #endif } for (i = 0; i < iInputLen; ++i) { #ifdef RADIXTREE_DEBUG printf("Testing add %s\n", pInputs[i]); #endif CHECK(pTree->insert(pInputs[i], strlen(pInputs[i]), pTree) != NULL); #ifdef RADIXTREE_PRINTTREE pTree->printTree(); #endif } for (i = 0; i < iInputLen; i += 2) { #ifdef RADIXTREE_DEBUG printf("Testing update %s\n", pInputs[i]); #endif CHECK(pTree->update(pInputs[i], strlen(pInputs[i]), &tree2) == pTree); #ifdef RADIXTREE_PRINTTREE pTree->printTree(); #endif } for (i = 0; i < iInputLen; i += 2) { #ifdef RADIXTREE_DEBUG printf("Testing find %s\n", pInputs[i]); #endif CHECK(pTree->find(pInputs[i], strlen(pInputs[i])) == &tree2); #ifdef RADIXTREE_PRINTTREE pTree->printTree(); #endif } for (i = 1; i < iInputLen; i += 2) { #ifdef RADIXTREE_DEBUG printf("Testing find %s\n", pInputs[i]); #endif CHECK(pTree->find(pInputs[i], strlen(pInputs[i])) == pTree); } for (i = 0; i < iWCFindLen; ++i) { #ifdef RADIXTREE_DEBUG printf("Testing find %s\n", pWCFind[i]); #endif CHECK(pTree->find(pWCFind[i], strlen(pWCFind[i])) == pTree); } for (i = 0; i < iWCFindLen; ++i) { #ifdef RADIXTREE_DEBUG printf("Testing update %s\n", pWCFind[i]); #endif CHECK(pTree->update(pWCFind[i], strlen(pWCFind[i]), &tree2) == NULL); } for (i = 0; i < iWCLen; ++i) { #ifdef RADIXTREE_DEBUG printf("Testing update %s\n", pWCTest[i]); #endif CHECK(pTree->update(pWCTest[i], strlen(pWCTest[i]), &tree3) == pTree); #ifdef RADIXTREE_PRINTTREE pTree->printTree(); #endif } for (i = 0; i < iWCFindLen - 1; ++i) { #ifdef RADIXTREE_DEBUG printf("Testing bestMatch %s\n", pWCFind[i]); #endif CHECK(pTree->bestMatch(pWCFind[i], strlen(pWCFind[i])) == &tree3); } for (i = 0; i < iInputLen; i += 2) { #ifdef RADIXTREE_DEBUG printf("Testing bestMatch %s\n", pInputs[i]); #endif CHECK(pTree->bestMatch(pInputs[i], strlen(pInputs[i])) == &tree2); #ifdef RADIXTREE_PRINTTREE pTree->printTree(); #endif } const char *pFindFail = "/home/undiff/has/no/children"; #ifdef RADIXTREE_DEBUG printf("Testing find %s\n", pFindFail); #endif if (pTree->getNoContext()) CHECK(pTree->find(pFindFail, strlen(pFindFail)) != NULL); else CHECK(pTree->find(pFindFail, strlen(pFindFail)) == NULL); if (pTree->getUseMerge()) { const char *pEraseFail[] = { "/home/testing/has/children", "/home/testing/has/multi", "/home/*diff/multi/layer*", "/home/username/should/these/be/found" }; const int iEraseFailCnt = 4; for (i = 0; i < iEraseFailCnt; ++i) { #ifdef RADIXTREE_DEBUG printf("Testing find and erase %s\n", pEraseFail[i]); #endif CHECK(pTree->find(pEraseFail[i], strlen(pEraseFail[i])) != NULL); CHECK(pTree->erase(pEraseFail[i], strlen(pEraseFail[i])) == NULL); } #ifdef RADIXTREE_DEBUG printf("Testing erase %s\n", pWCTest[11]); #endif CHECK(pTree->erase(pWCTest[11], strlen(pWCTest[11])) != NULL); #ifdef RADIXTREE_DEBUG printf("Testing erase %s\n", pWCTest[12]); #endif CHECK(pTree->erase(pWCTest[12], strlen(pWCTest[12])) != NULL); #ifdef RADIXTREE_DEBUG printf("Testing erase %s\n", pWCTest[13]); #endif CHECK(pTree->erase(pWCTest[13], strlen(pWCTest[13])) != NULL); #ifdef RADIXTREE_DEBUG printf("Testing erase %s\n", pWCTest[14]); #endif CHECK(pTree->erase(pWCTest[14], strlen(pWCTest[14])) != NULL); for (i = 0; i < iEraseFailCnt; ++i) { #ifdef RADIXTREE_DEBUG printf("Testing find %s\n", pEraseFail[i]); #endif CHECK(pTree->find(pEraseFail[i], strlen(pEraseFail[i])) == NULL); } if (pTree->getNoContext() != 0) count -= iEraseFailCnt; else count += iMergeIncr; } CHECK(pTree->for_each(test_for_each) == count); int p = 0; CHECK(pTree->for_each2(test_for_each2, &p) == count); } void setupTest(char **pDynamicInputs, int count) { int i, iNumFlags = 1 << 2; RadixTree *pContTree, *pPtrTree; #ifdef RT_DOREGTEST for (i = 0; i < iNumFlags; ++i) { pContTree = new RadixTree(RTMODE_CONTIGUOUS); pPtrTree = new RadixTree(RTMODE_POINTER); if ((i & RTTEST_NOCONTEXT) != 0) { pContTree->setNoContext(); pPtrTree->setNoContext(); } else { pContTree->setRootLabel("/", 1); pPtrTree->setRootLabel("/", 1); } if ((i & RTTEST_GLOBALPOOL) != 0) { pContTree->setUseGlobalPool(); pPtrTree->setUseGlobalPool(); } printf("CONTIGUOUS TEST: NoContext: %d, GlobalPool: %d\n", pContTree->getNoContext() != 0, pContTree->getUseGlobalPool() != 0); doTest(pContTree, pDynamicInputs, count); printf("END CONTIGUOUS TEST\n"); printf("POINTER TEST: NoContext: %d, GlobalPool: %d\n", pPtrTree->getNoContext() != 0, pPtrTree->getUseGlobalPool() != 0); doTest(pPtrTree, pDynamicInputs, count); printf("END POINTER TEST\n"); delete pContTree; delete pPtrTree; } #endif #ifdef RT_DOWCTEST iNumFlags = 1 << 3; for (i = 0; i < iNumFlags; ++i) { pContTree = new RadixTree(RTMODE_CONTIGUOUS); pPtrTree = new RadixTree(RTMODE_POINTER); pContTree->setUseWildCard(); pPtrTree->setUseWildCard(); if ((i & RTTEST_NOCONTEXT) != 0) { pContTree->setNoContext(); pPtrTree->setNoContext(); } else { pContTree->setRootLabel("/", 1); pPtrTree->setRootLabel("/", 1); } if ((i & RTTEST_GLOBALPOOL) != 0) { pContTree->setUseGlobalPool(); pPtrTree->setUseGlobalPool(); } if ((i & RTTEST_MERGE) != 0) { pContTree->setUseMerge(); pPtrTree->setUseMerge(); } printf("CONTIGUOUS TEST: NoContext: %d, GlobalPool: %d, Merge: %d\n", pContTree->getNoContext() != 0, pContTree->getUseGlobalPool() != 0, pContTree->getUseMerge()); doWCTest(pContTree); printf("END CONTIGUOUS TEST\n"); printf("POINTER TEST: NoContext: %d, GlobalPool: %d, Merge: %d\n", pPtrTree->getNoContext() != 0, pPtrTree->getUseGlobalPool() != 0, pPtrTree->getUseMerge()); doWCTest(pPtrTree); printf("END POINTER TEST\n"); delete pContTree; delete pPtrTree; } #endif } TEST(radixtreetest) { printf("Start Radix Tree Test!\n"); int i, count = iInputLen, iDynamicCount = iInputLen; char **pDynamicInputs = (char **)malloc(sizeof(char *)*iDynamicCount); for (i = 0; i < iDynamicCount; ++i) pDynamicInputs[i] = strdup(pInputs[i]); setupTest(pDynamicInputs, count); for (i = 0; i < iDynamicCount; ++i) free(pDynamicInputs[i]); free(pDynamicInputs); printf("End Radix Tree Test!\n"); } #endif