#include #include #include #include #include #include "data.h" #include "episodelistview.h" #include "pl.h" #include "util.h" #include "win.h" extern CfgA& g_cfg; struct Test { const char* name = {0}; char error[64] = {0}; Test(const char* name) : name(name) {} }; #define CAT(a, b) a##b #define APPLY(a, ...) a(__VA_ARGS__) #define TESTS struct APPLY(CAT, tests_, __COUNTER__) #define TEST(id) }; struct id : public Test { id() : Test(#id) #define FAIL(...) do { Sprintf(error, __VA_ARGS__); return; } while (0) template struct Defer { Defer(F dtor) : dtor(dtor) {} ~Defer() { dtor(); } F dtor; }; #define DEFER(x) Defer APPLY(CAT, defer_, __COUNTER__) {[=](){x;}} struct InternetFile { InternetFile(const wchar_t* url); ~InternetFile(); DWORD Read(void* buf, DWORD cb); HINTERNET hi; HINTERNET hiUrl; }; InternetFile::InternetFile(const wchar_t* url) { hi = InternetOpen(L"Episode Browser", INTERNET_OPEN_TYPE_DIRECT, nullptr, nullptr, /*INTERNET_FLAG_ASYNC*/0); if (!hi) throw Win32Error{}; hiUrl = InternetOpenUrl(hi, url, nullptr, 0, INTERNET_FLAG_NO_UI, 0); if (!hiUrl) { InternetCloseHandle(hi); throw Win32Error{}; } } InternetFile::~InternetFile() { InternetCloseHandle(hiUrl); InternetCloseHandle(hi); } DWORD InternetFile::Read(void* buf, DWORD cb) { DWORD cbRead; if (InternetReadFile(hiUrl, buf, cb, &cbRead)) return cbRead; else throw Win32Error{}; } TESTS { TEST(StrcpyWithSmallerDestination) { char src[15], dst[10]; Sprintf(src, "abcdefghijklmn"); Strcpy(dst, src); if (dst[8] != 'i') FAIL("dst[8] is not 'i', but '%c'", dst[8]); if (dst[9] != 0) FAIL("dst is not NUL-terminated"); } TEST(EpisodeDataFromWeb) { ElvDataA e; DlvDataA d; FromWeb(10, e, d); if (wcscmp(e.title, L"Pro Soccer Player Blackmail Case") != 0) FAIL("title is not correct"); if (wcscmp(d.date, L"March 11, 1996") != 0) FAIL("date is not correct"); } TEST(EpisodeDataFromProlog) { ElvDataA e; DlvDataA d; FromProlog(10, e); FromProlog(10, d); if (wcscmp(e.title, L"Pro Soccer Player Blackmail Case") != 0) FAIL("title is not correct"); if (wcscmp(d.date, L"March 11, 1996") != 0) FAIL("date is not correct"); } TEST(IO) { ElvDataA e1_0, e2_0; FromProlog(6, e1_0); FromProlog(10, e2_0); /* Write two ElvDataA structs to disk. */ { FileView fv{L"tmp.dat", 2}; memcpy(fv+0, &e1_0, sizeof(e1_0)); memcpy(fv+1, &e2_0, sizeof(e2_0)); } /* Read first ElvDataA struct from disk using * ElvDataA-typed FileView. */ { FileView fv{L"tmp.dat", 2}; const ElvDataA& e1 = *fv; if (e1.version != 'a') FAIL("version is different"); if (e1_0.rating != e1.rating) FAIL("rating is different (%d/%d)", e1_0.rating, e1.rating); if (e1_0.bWatched != e1.bWatched) FAIL("bWatched is different"); if (e1_0.bTVOriginal != e1.bTVOriginal) FAIL("bTVOriginal is different"); if (wcscmp(e1_0.sRating, e1.sRating) != 0) FAIL("sRating is different"); if (wcscmp(e1_0.siEp, e1.siEp) != 0) FAIL("siEp is different"); if (wcscmp(e1_0.title, e1.title) != 0) FAIL("title is different"); } /* Read second ElvDataA struct from disk using * unsigned char-typed FileView. */ { FileView fv{L"tmp.dat", 2*sizeof(ElvDataA)}; ElvDataA& e2 = *reinterpret_cast(&fv.At(sizeof(ElvDataA))); if (e2_0.rating != e2.rating) FAIL("rating is different (%d/%d)", e2_0.rating, e2.rating); if (e2_0.bWatched != e2.bWatched) FAIL("bWatched is different"); if (e2_0.bTVOriginal != e2.bTVOriginal) FAIL("bTVOriginal is different"); if (wcscmp(e2_0.sRating, e2.sRating) != 0) FAIL("sRating is different"); if (wcscmp(e2_0.siEp, e2.siEp) != 0) FAIL("siEp is different"); if (wcscmp(e2_0.title, e2.title) != 0) FAIL("title is different"); } //DeleteFile(L"tmp.dat"); } TEST(MigrateElvDataFromPrologToDisk) { int cEp; if (!Pl("episode_data","episode_count",&cEp)) return; { FileView fv{L"tmp.dat", g_cfg.cEp+128u}; ElvDataA* p = fv; for (int iEp = 1; iEp <= cEp; iEp++) { ElvDataA e; FromProlog(iEp, e); memcpy(p, &e, sizeof(e)); p++; } } { FileView fv{L"tmp.dat", g_cfg.cEp+128u}; ElvDataA& e = fv.At(9); if (wcscmp(e.title, L"Pro Soccer Player Blackmail Case") != 0) FAIL("title is not correct"); } //DeleteFile(L"tmp.dat"); } TEST(SampleConfigurationToDisk) { CfgA cfg_0; { FileView fv{L"tmp.dat", 1}; Wcscpy(cfg_0.url, L"https://animixplay.to/v1/detective-conan/ep"); memcpy(fv, &cfg_0, sizeof(cfg_0)); } { FileView fv{L"tmp.dat", 1}; const CfgA& cfg = fv.At(0); if (cfg_0.bViewWatched != cfg.bViewWatched) FAIL("bViewWatched is different"); if (wcscmp(cfg_0.url, cfg.url) != 0) FAIL("url is not correct"); } //DeleteFile(L"tmp.dat"); } // TEST(MigrateCfg) // { // FileView fva{L"cfga.dat", 1}; // FileView fvb = FileView::Initialized(L"cfgb.dat", 1); // fvb->bViewWatched = fva->bViewWatched; // fvb->bViewTVOriginal = fva->bViewTVOriginal; // fvb->iSortCol = fva->iSortCol; // fvb->iFocus = fva->iFocus; // fvb->heightDlv = fva->heightDlv; // Wcscpy(fvb->limitScreenwriter, fva->limitScreenwriter); // Wcscpy(fvb->root, fva->root); // Wcscpy(fvb->glob, fva->glob); // Wcscpy(fvb->url, fva->url); // } TEST(MigrateDlvDataFromPrologToDisk) { int cEp; if (!Pl("episode_data","episode_count",&cEp)) return; { FileView fv{L"tmp.dat", g_cfg.cEp+128u}; DlvDataA* p = fv; for (int iEp = 1; iEp <= cEp; iEp++) { DlvDataA d; FromProlog(iEp, d); memcpy(p, &d, sizeof(d)); p++; } } { FileView fv{L"tmp.dat", g_cfg.cEp+128u}; DlvDataA& e = fv.At(9); if (wcscmp(e.date, L"March 11, 1996") != 0) FAIL("date is not correct"); } //DeleteFile(L"tmp.dat"); } TEST(DownloadData) { WcharPtr title, wiki, date, source, hint; int i = 1053; /* This is slow. */ while(Pl("episode_data","fetch_episode_data",++i,&title,&wiki,&date,&source,&hint)) { extern FileView g_fvElv; extern FileView g_fvDlv; ElvDataA& e = g_fvElv.At(i-1); Wcscpy(e.title, title); if (!e.siEp[0]) Swprintf(e.siEp, L"%d", i); DlvDataA& d = g_fvDlv.At(i-1); Wcscpy(d.wiki, wiki); Wcscpy(d.date, date); Wcscpy(d.source, source); Wcscpy(d.hint, hint); } extern CfgA& g_cfg; g_cfg.cEp = i; } TEST(XML) { LIBXML_TEST_VERSION; InternetFile inf{L"https://www.detectiveconanworld.com/wiki/Anime"}; char buf[1024]; htmlParserCtxtPtr ctxt = htmlCreatePushParserCtxt(nullptr, nullptr, buf, sizeof(buf), "https://www.detectiveconanworld.com/wiki/Anime", XML_CHAR_ENCODING_UTF8); DEFER(xmlFreeParserCtxt(ctxt)); htmlCtxtUseOptions(ctxt, HTML_PARSE_RECOVER|HTML_PARSE_NOERROR|HTML_PARSE_NOWARNING); while (DWORD cbRead = inf.Read(&buf, sizeof(buf))) { if (!htmlParseChunk(ctxt, buf, cbRead, 0)) FAIL(xmlGetLastError()->message); } htmlParseChunk(ctxt, buf, 0, 1); /* Terminate. */ htmlDocPtr doc = ctxt->myDoc; if (!doc) FAIL(xmlGetLastError()->message); DEFER(xmlFreeDoc(doc)); /* Needed? */ xmlXPathContextPtr xpathCtx = xmlXPathNewContext(doc); if (!xpathCtx) FAIL(xmlGetLastError()->message); DEFER(xmlXPathFreeContext(xpathCtx)); xmlXPathObjectPtr xpathObj = xmlXPathEvalExpression( reinterpret_cast("//tr/td[@style='background:#f2fde9;']"), xpathCtx); if (!xpathObj) FAIL(xmlGetLastError()->message); DEFER(xmlXPathFreeObject(xpathObj)); xmlNodeSetPtr nodes; int cNodes; nodes = xpathObj->nodesetval; cNodes = nodes? nodes->nodeNr: 0; printf("%d nodes\n", cNodes); // for (int i = 0; i < cNodes; i++) { // xmlNodePtr node = nodes->nodeTab[i]; // printf("node \"%s\": type %d\n", node->name, node->type); // } } }; int RunTests() { const Test tests[] = { //StrcpyWithSmallerDestination{}, //EpisodeDataFromWeb{}, //EpisodeDataFromProlog{}, //IO{}, //MigrateElvDataFromPrologToDisk{}, //SampleConfigurationToDisk{}, //MigrateCfg{} //MigrateDlvDataFromPrologToDisk{}, //DownloadData{}, XML{}, //ImportElvData{}, //ImportDlvData{}, }; printf("Results (%llu tests):\n", sizeof(tests)/sizeof(*tests)); int cFailed = 0; for (const Test& t : tests) { const char* msg; if (t.error[0]) { msg = t.error; cFailed++; } else msg = const_cast("SUCCESS"); printf("%s: %s\n", t.name, msg); } printf("%d tests failed.\n", cFailed); return cFailed; }