8 #include <boost/algorithm/string/case_conv.hpp>
10 #include <simgear/simgear_config.h>
12 #include "test_HTTP.hxx"
13 #include "HTTPRepository.hxx"
14 #include "HTTPClient.hxx"
16 #include <simgear/misc/strutils.hxx>
17 #include <simgear/misc/sg_hash.hxx>
18 #include <simgear/timing/timestamp.hxx>
19 #include <simgear/debug/logstream.hxx>
20 #include <simgear/misc/sg_dir.hxx>
21 #include <simgear/structure/exception.hxx>
22 #include <simgear/io/sg_file.hxx>
24 using namespace simgear;
26 std::string dataForFile(const std::string& parentName, const std::string& name, int revision)
28 std::ostringstream os;
29 // random content but which definitely depends on our tree location
31 for (int i=0; i<100; ++i) {
32 os << i << parentName << "_" << name << "_" << revision;
38 std::string hashForData(const std::string& d)
40 simgear::sha1nfo info;
42 sha1_write(&info, d.data(), d.size());
43 return strutils::encodeHex(sha1_result(&info), HASH_LENGTH);
49 TestRepoEntry(TestRepoEntry* parent, const std::string& name, bool isDir);
52 TestRepoEntry* parent;
55 std::string indexLine() const;
57 std::string hash() const;
59 std::vector<TestRepoEntry*> children;
61 size_t sizeInBytes() const
67 int revision; // for files
70 bool returnCorruptData;
72 void clearRequestCounts();
74 void setGetWillFail(bool b)
79 void setReturnCorruptData(bool d)
81 returnCorruptData = d;
84 std::string pathInRepo() const
86 return parent ? (parent->pathInRepo() + "/" + name) : name;
89 std::string data() const;
91 void defineFile(const std::string& path, int rev = 1)
93 string_list pathParts = strutils::split(path, "/");
94 if (pathParts.size() == 1) {
95 children.push_back(new TestRepoEntry(this, pathParts.front(), false));
96 children.back()->revision = rev;
99 TestRepoEntry* c = childEntry(pathParts.front());
101 // define a new directory child
102 c = new TestRepoEntry(this, pathParts.front(), true);
103 children.push_back(c);
106 size_t frontPartLength = pathParts.front().size();
107 c->defineFile(path.substr(frontPartLength + 1), rev);
111 TestRepoEntry* findEntry(const std::string& path)
117 string_list pathParts = strutils::split(path, "/");
118 TestRepoEntry* entry = childEntry(pathParts.front());
119 if (pathParts.size() == 1) {
120 return entry; // might be NULL
124 std::cerr << "bad path: " << path << std::endl;
128 size_t part0Length = pathParts.front().size() + 1;
129 return entry->findEntry(path.substr(part0Length));
132 TestRepoEntry* childEntry(const std::string& name) const
135 for (size_t i=0; i<children.size(); ++i) {
136 if (children[i]->name == name) {
144 void removeChild(const std::string& name)
146 std::vector<TestRepoEntry*>::iterator it;
147 for (it = children.begin(); it != children.end(); ++it) {
148 if ((*it)->name == name) {
154 std::cerr << "child not found:" << name << std::endl;
158 TestRepoEntry::TestRepoEntry(TestRepoEntry* pr, const std::string& nm, bool d) :
159 parent(pr), name(nm), isDir(d)
164 returnCorruptData = false;
167 TestRepoEntry::~TestRepoEntry()
169 for (size_t i=0; i<children.size(); ++i) {
174 std::string TestRepoEntry::data() const
177 std::ostringstream os;
179 os << "path:" << pathInRepo() << "\n";
180 for (size_t i=0; i<children.size(); ++i) {
181 os << children[i]->indexLine() << "\n";
185 return dataForFile(parent->name, name, revision);
189 std::string TestRepoEntry::indexLine() const
191 std::ostringstream os;
192 os << (isDir ? "d:" : "f:") << name << ":" << hash()
193 << ":" << sizeInBytes();
197 std::string TestRepoEntry::hash() const
199 simgear::sha1nfo info;
201 std::string d(data());
202 sha1_write(&info, d.data(), d.size());
203 return strutils::encodeHex(sha1_result(&info), HASH_LENGTH);
206 void TestRepoEntry::clearRequestCounts()
210 for (size_t i=0; i<children.size(); ++i) {
211 children[i]->clearRequestCounts();
216 TestRepoEntry* global_repo = NULL;
218 class TestRepositoryChannel : public TestServerChannel
222 virtual void processRequestHeaders()
225 if (path.find("/repo/") == 0) {
226 // std::cerr << "get for:" << path << std::endl;
228 std::string repoPath = path.substr(6);
229 bool lookingForDir = false;
230 std::string::size_type suffix = repoPath.find(".dirindex");
231 if (suffix != std::string::npos) {
232 lookingForDir = true;
234 // trim the preceeding '/' as well, for non-root dirs
238 repoPath = repoPath.substr(0, suffix);
241 TestRepoEntry* entry = global_repo->findEntry(repoPath);
243 sendErrorResponse(404, false, "unknown repo path:" + repoPath);
247 if (entry->isDir != lookingForDir) {
248 sendErrorResponse(404, false, "mismatched path type:" + repoPath);
252 if (entry->getWillFail) {
253 sendErrorResponse(404, false, "entry marked to fail explicitly:" + repoPath);
257 entry->requestCount++;
260 if (entry->returnCorruptData) {
261 content = dataForFile("!$£$!" + entry->parent->name,
262 "corrupt_" + entry->name,
265 content = entry->data();
269 d << "HTTP/1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
270 d << "Content-Length:" << content.size() << "\r\n";
271 d << "\r\n"; // final CRLF to terminate the headers
273 push(d.str().c_str());
275 sendErrorResponse(404, false, "");
280 std::string test_computeHashForPath(const SGPath& p)
283 return std::string();
286 char* buf = static_cast<char*>(alloca(1024 * 1024));
292 while ((readLen = f.read(buf, 1024 * 1024)) > 0) {
293 sha1_write(&info, buf, readLen);
296 std::string hashBytes((char*) sha1_result(&info), HASH_LENGTH);
297 return strutils::encodeHex(hashBytes);
300 void verifyFileState(const SGPath& fsRoot, const std::string& relPath)
302 TestRepoEntry* entry = global_repo->findEntry(relPath);
304 throw sg_error("Missing test repo entry", relPath);
310 throw sg_error("Missing file system entry", relPath);
313 std::string hashOnDisk = test_computeHashForPath(p);
314 if (hashOnDisk != entry->hash()) {
315 throw sg_error("Checksum mismatch", relPath);
319 void verifyRequestCount(const std::string& relPath, int count)
321 TestRepoEntry* entry = global_repo->findEntry(relPath);
323 throw sg_error("Missing test repo entry", relPath);
326 if (entry->requestCount != count) {
327 throw sg_exception("Bad request count", relPath);
331 void createFile(const SGPath& basePath, const std::string& relPath, int revision)
333 string_list comps = strutils::split(relPath, "/");
338 simgear::Dir d(p.dir());
341 std::string prName = comps.at(comps.size() - 2);
343 std::ofstream f(p.c_str(), std::ios::trunc | std::ios::out);
344 f << dataForFile(prName, comps.back(), revision);
348 TestServer<TestRepositoryChannel> testServer;
350 void waitForUpdateComplete(HTTP::Client* cl, HTTPRepository* repo)
352 SGTimeStamp start(SGTimeStamp::now());
353 while (start.elapsedMSec() < 10000) {
357 if (!repo->isDoingSync()) {
360 SGTimeStamp::sleepForMSec(15);
363 std::cerr << "timed out" << std::endl;
366 void testBasicClone(HTTP::Client* cl)
368 std::auto_ptr<HTTPRepository> repo;
369 SGPath p(simgear::Dir::current().path());
370 p.append("http_repo_basic");
374 repo.reset(new HTTPRepository(p, cl));
375 repo->setBaseUrl("http://localhost:2000/repo");
378 waitForUpdateComplete(cl, repo.get());
380 verifyFileState(p, "fileA");
381 verifyFileState(p, "dirA/subdirA/fileAAA");
382 verifyFileState(p, "dirC/subdirA/subsubA/fileCAAA");
384 global_repo->findEntry("fileA")->revision++;
385 global_repo->findEntry("dirB/subdirA/fileBAA")->revision++;
386 global_repo->defineFile("dirC/fileCA"); // new file
387 global_repo->findEntry("dirB/subdirA")->removeChild("fileBAB");
388 global_repo->findEntry("dirA")->removeChild("subdirA"); // remove a dir
393 waitForUpdateComplete(cl, repo.get());
395 verifyFileState(p, "fileA");
396 verifyFileState(p, "dirC/fileCA");
398 std::cout << "Passed test: basic clone and update" << std::endl;
401 void testModifyLocalFiles(HTTP::Client* cl)
403 std::auto_ptr<HTTPRepository> repo;
404 SGPath p(simgear::Dir::current().path());
405 p.append("http_repo_modify_local_2");
411 repo.reset(new HTTPRepository(p, cl));
412 repo->setBaseUrl("http://localhost:2000/repo");
415 waitForUpdateComplete(cl, repo.get());
416 verifyFileState(p, "dirB/subdirA/fileBAA");
419 modFile.append("dirB/subdirA/fileBAA");
421 std::ofstream of(modFile.c_str(), std::ios::out | std::ios::trunc);
422 of << "complete nonsense";
426 global_repo->clearRequestCounts();
428 waitForUpdateComplete(cl, repo.get());
429 verifyFileState(p, "dirB/subdirA/fileBAA");
430 verifyRequestCount("dirB", 0);
431 verifyRequestCount("dirB/subdirA", 0);
432 verifyRequestCount("dirB/subdirA/fileBAA", 1);
434 std::cout << "Passed test: identify and fix locally modified files" << std::endl;
437 void testNoChangesUpdate()
442 void testMergeExistingFileWithoutDownload(HTTP::Client* cl)
444 std::auto_ptr<HTTPRepository> repo;
445 SGPath p(simgear::Dir::current().path());
446 p.append("http_repo_merge_existing");
452 repo.reset(new HTTPRepository(p, cl));
453 repo->setBaseUrl("http://localhost:2000/repo");
455 createFile(p, "dirC/fileCB", 4); // should match
456 createFile(p, "dirC/fileCC", 3); // mismatch
458 global_repo->defineFile("dirC/fileCB", 4);
459 global_repo->defineFile("dirC/fileCC", 10);
462 createFile(p, "dirD/fileDA", 4);
463 createFile(p, "dirD/subdirDA/fileDAA", 6);
464 createFile(p, "dirD/subdirDB/fileDBA", 6);
466 global_repo->defineFile("dirD/fileDA", 4);
467 global_repo->defineFile("dirD/subdirDA/fileDAA", 6);
468 global_repo->defineFile("dirD/subdirDB/fileDBA", 6);
471 waitForUpdateComplete(cl, repo.get());
472 verifyFileState(p, "dirC/fileCB");
473 verifyFileState(p, "dirC/fileCC");
474 verifyRequestCount("dirC/fileCB", 0);
475 verifyRequestCount("dirC/fileCC", 1);
477 verifyRequestCount("dirD/fileDA", 0);
478 verifyRequestCount("dirD/subdirDA/fileDAA", 0);
479 verifyRequestCount("dirD/subdirDB/fileDBA", 0);
481 std::cout << "Passed test: merge existing files with matching hash" << std::endl;
484 void testLossOfLocalFiles(HTTP::Client* cl)
486 std::auto_ptr<HTTPRepository> repo;
487 SGPath p(simgear::Dir::current().path());
488 p.append("http_repo_lose_local");
494 repo.reset(new HTTPRepository(p, cl));
495 repo->setBaseUrl("http://localhost:2000/repo");
497 waitForUpdateComplete(cl, repo.get());
498 verifyFileState(p, "dirB/subdirA/fileBAA");
501 lostPath.append("dirB/subdirA");
502 simgear::Dir lpd(lostPath);
505 global_repo->clearRequestCounts();
508 waitForUpdateComplete(cl, repo.get());
509 verifyFileState(p, "dirB/subdirA/fileBAA");
511 verifyRequestCount("dirB", 0);
512 verifyRequestCount("dirB/subdirA", 1);
513 verifyRequestCount("dirB/subdirA/fileBAC", 1);
515 std::cout << "Passed test: lose and replace local files" << std::endl;
518 void testAbandonMissingFiles(HTTP::Client* cl)
520 std::auto_ptr<HTTPRepository> repo;
521 SGPath p(simgear::Dir::current().path());
522 p.append("http_repo_missing_files");
528 global_repo->defineFile("dirA/subdirE/fileAEA");
529 global_repo->findEntry("dirA/subdirE/fileAEA")->setGetWillFail(true);
531 repo.reset(new HTTPRepository(p, cl));
532 repo->setBaseUrl("http://localhost:2000/repo");
534 waitForUpdateComplete(cl, repo.get());
535 if (repo->failure() != AbstractRepository::REPO_PARTIAL_UPDATE) {
536 throw sg_exception("Bad result from missing files test");
539 global_repo->findEntry("dirA/subdirE/fileAEA")->setGetWillFail(false);
542 void testAbandonCorruptFiles(HTTP::Client* cl)
544 std::auto_ptr<HTTPRepository> repo;
545 SGPath p(simgear::Dir::current().path());
546 p.append("http_repo_corrupt_files");
552 global_repo->defineFile("dirB/subdirG/fileBGA");
553 global_repo->findEntry("dirB/subdirG/fileBGA")->setReturnCorruptData(true);
555 repo.reset(new HTTPRepository(p, cl));
556 repo->setBaseUrl("http://localhost:2000/repo");
558 waitForUpdateComplete(cl, repo.get());
559 if (repo->failure() != AbstractRepository::REPO_PARTIAL_UPDATE) {
560 throw sg_exception("Bad result from corrupt files test");
563 std::cout << "Passed test: detect corrupted download" << std::endl;
566 int main(int argc, char* argv[])
568 sglog().setLogLevels( SG_ALL, SG_INFO );
571 cl.setMaxConnections(1);
573 global_repo = new TestRepoEntry(NULL, "root", true);
574 global_repo->defineFile("fileA");
575 global_repo->defineFile("fileB");
576 global_repo->defineFile("dirA/fileAA");
577 global_repo->defineFile("dirA/fileAB");
578 global_repo->defineFile("dirA/fileAC");
579 global_repo->defineFile("dirA/subdirA/fileAAA");
580 global_repo->defineFile("dirA/subdirA/fileAAB");
581 global_repo->defineFile("dirB/subdirA/fileBAA");
582 global_repo->defineFile("dirB/subdirA/fileBAB");
583 global_repo->defineFile("dirB/subdirA/fileBAC");
584 global_repo->defineFile("dirC/subdirA/subsubA/fileCAAA");
588 testModifyLocalFiles(&cl);
590 testLossOfLocalFiles(&cl);
592 testMergeExistingFileWithoutDownload(&cl);
594 testAbandonMissingFiles(&cl);
596 testAbandonCorruptFiles(&cl);