// QueryTest.cpp #include #include #include #include #include #include "QueryTest.h" #include #include #include #include #include #include #include #include #include #include #include #include #include // Query class Query : public BQuery { public: #if TEST_R5 status_t PushValue(int32 value) { PushInt32(value); return B_OK; } status_t PushValue(uint32 value) { PushUInt32(value); return B_OK; } status_t PushValue(int64 value) { PushInt64(value); return B_OK; } status_t PushValue(uint64 value) { PushUInt64(value); return B_OK; } status_t PushValue(float value) { PushFloat(value); return B_OK; } status_t PushValue(double value) { PushDouble(value); return B_OK; } status_t PushValue(const BString value, bool caseInsensitive = false) { PushString(value.String(), caseInsensitive); return B_OK; } status_t PushAttr(const char *attribute) { BQuery::PushAttr(attribute); return B_OK; } status_t PushOp(query_op op) { BQuery::PushOp(op); return B_OK; } #else status_t PushValue(int32 value) { return PushInt32(value); } status_t PushValue(uint32 value) { return PushUInt32(value); } status_t PushValue(int64 value) { return PushInt64(value); } status_t PushValue(uint64 value) { return PushUInt64(value); } status_t PushValue(float value) { return PushFloat(value); } status_t PushValue(double value) { return PushDouble(value); } status_t PushValue(const BString value, bool caseInsensitive = false) { return PushString(value.String(), caseInsensitive); } #endif }; // PredicateNode class PredicateNode { public: virtual ~PredicateNode() {} virtual status_t push(Query &query) const = 0; virtual BString toString() const = 0; }; // ValueNode template class ValueNode : public PredicateNode { public: ValueNode(ValueType v) : value(v) {} virtual ~ValueNode() {} virtual status_t push(Query &query) const { return query.PushValue(value); } virtual BString toString() const { return BString() << value; } ValueType value; }; // float specialization template<> BString ValueNode::toString() const { char buffer[32]; sprintf(buffer, "0x%08lx", *(int32*)&value); return BString() << buffer; } // double specialization template<> BString ValueNode::toString() const { char buffer[32]; sprintf(buffer, "0x%016Lx", *(int64*)&value); return BString() << buffer; } // StringNode class StringNode : public PredicateNode { public: StringNode(BString v, bool caseInsensitive = false) : value(v), caseInsensitive(caseInsensitive) { } virtual ~StringNode() {} virtual status_t push(Query &query) const { return query.PushValue(value, caseInsensitive); } virtual BString toString() const { BString escaped; if (caseInsensitive) { const char *str = value.String(); int32 len = value.Length(); for (int32 i = 0; i < len; i++) { char c = str[i]; if (isalpha(c)) { int lower = tolower(c); int upper = toupper(c); if (lower < 0 || upper < 0) escaped << c; else escaped << "[" << (char)lower << (char)upper << "]"; } else escaped << c; } } else escaped = value; escaped.CharacterEscape("\"\\'", '\\'); return BString("\"") << escaped << "\""; } BString value; bool caseInsensitive; }; // DateNode class DateNode : public PredicateNode { public: DateNode(BString v) : value(v) {} virtual ~DateNode() {} virtual status_t push(Query &query) const { return query.PushDate(value.String()); } virtual BString toString() const { BString escaped(value); escaped.CharacterEscape("%\"\\'", '\\'); return BString("%") << escaped << "%"; } BString value; }; // AttributeNode class AttributeNode : public PredicateNode { public: AttributeNode(BString v) : value(v) {} virtual ~AttributeNode() {} virtual status_t push(Query &query) const { return query.PushAttr(value.String()); } virtual BString toString() const { return value; } BString value; }; // short hands typedef ValueNode Int32Node; typedef ValueNode UInt32Node; typedef ValueNode Int64Node; typedef ValueNode UInt64Node; typedef ValueNode FloatNode; typedef ValueNode DoubleNode; // ListNode class ListNode : public PredicateNode { public: ListNode(PredicateNode *child1 = NULL, PredicateNode *child2 = NULL, PredicateNode *child3 = NULL, PredicateNode *child4 = NULL, PredicateNode *child5 = NULL, PredicateNode *child6 = NULL) : children() { addChild(child1); addChild(child2); addChild(child3); addChild(child4); addChild(child5); addChild(child6); } virtual ~ListNode() { for (int32 i = 0; PredicateNode *child = childAt(i); i++) delete child; } virtual status_t push(Query &query) const { status_t error = B_OK; for (int32 i = 0; PredicateNode *child = childAt(i); i++) { error = child->push(query); if (error != B_OK) break; } return error; } virtual BString toString() const { return BString("INVALID"); } ListNode &addChild(PredicateNode *child) { if (child) children.AddItem(child); return *this; } PredicateNode *childAt(int32 index) const { return (PredicateNode*)children.ItemAt(index); } BObjectList children; }; // OpNode class OpNode : public ListNode { public: OpNode(query_op op, PredicateNode *left, PredicateNode *right = NULL) : ListNode(left, right), op(op) {} virtual ~OpNode() { } virtual status_t push(Query &query) const { status_t error = ListNode::push(query); if (error == B_OK) error = query.PushOp(op); return error; } virtual BString toString() const { PredicateNode *left = childAt(0); PredicateNode *right = childAt(1); if (!left) return "INVALID ARGS"; BString result; BString leftString = left->toString(); BString rightString; if (right) rightString = right->toString(); switch (op) { case B_INVALID_OP: result = "INVALID"; break; case B_EQ: result << "(" << leftString << "==" << rightString << ")"; break; case B_GT: result << "(" << leftString << ">" << rightString << ")"; break; case B_GE: result << "(" << leftString << ">=" << rightString << ")"; break; case B_LT: result << "(" << leftString << "<" << rightString << ")"; break; case B_LE: result << "(" << leftString << "<=" << rightString << ")"; break; case B_NE: result << "(" << leftString << "!=" << rightString << ")"; break; case B_CONTAINS: { StringNode *strNode = dynamic_cast(right); if (strNode) { rightString = StringNode(BString("*") << strNode->value << "*").toString(); } result << "(" << leftString << "==" << rightString << ")"; break; } case B_BEGINS_WITH: { StringNode *strNode = dynamic_cast(right); if (strNode) { rightString = StringNode(BString(strNode->value) << "*") .toString(); } result << "(" << leftString << "==" << rightString << ")"; break; } case B_ENDS_WITH: { StringNode *strNode = dynamic_cast(right); if (strNode) { rightString = StringNode(BString("*") << strNode->value) .toString(); } result << "(" << leftString << "==" << rightString << ")"; break; } case B_AND: result << "(" << leftString << "&&" << rightString << ")"; break; case B_OR: result << "(" << leftString << "||" << rightString << ")"; break; case B_NOT: result << "(" << "!" << leftString << ")"; break; case _B_RESERVED_OP_: result = "RESERVED"; break; } return result; } query_op op; }; // QueryTestEntry class QueryTestEntry { public: QueryTestEntry(string path, node_flavor kind, const QueryTestEntry *linkTarget = NULL) : path(path), cpath(NULL), kind(kind), linkToPath(), clinkToPath(NULL), directory(-1), node(-1), name() { cpath = this->path.c_str(); if (linkTarget) linkToPath = linkTarget->path; clinkToPath = this->linkToPath.c_str(); } string operator+(string leaf) const { return path + "/" + leaf; } string path; const char *cpath; node_flavor kind; string linkToPath; const char *clinkToPath; ino_t directory; ino_t node; string name; }; static const char *testVolumeImage = "/tmp/query-test-image"; static const char *testMountPoint = "/non-existing-mount-point"; // the test entry hierarchy: // mountPoint // + dir1 // + subdir11 // + subdir12 // + file11 // + file12 // + link11 // + dir2 // + subdir21 // + subdir22 // + subdir23 // + file21 // + file22 // + link21 // + dir3 // + subdir31 // + subdir32 // + file31 // + file32 // + link31 // + file1 // + file2 // + file3 // + link1 // + link2 // + link3 static QueryTestEntry mountPoint(testMountPoint, B_DIRECTORY_NODE); static QueryTestEntry dir1(mountPoint + "dir1", B_DIRECTORY_NODE); static QueryTestEntry subdir11(dir1 + "subdir11", B_DIRECTORY_NODE); static QueryTestEntry subdir12(dir1 + "subdir12", B_DIRECTORY_NODE); static QueryTestEntry file11(dir1 + "file11", B_FILE_NODE); static QueryTestEntry file12(dir1 + "file12", B_FILE_NODE); static QueryTestEntry link11(dir1 + "link11", B_SYMLINK_NODE, &file11); static QueryTestEntry dir2(mountPoint + "dir2", B_DIRECTORY_NODE); static QueryTestEntry subdir21(dir2 + "subdir21", B_DIRECTORY_NODE); static QueryTestEntry subdir22(dir2 + "subdir22", B_DIRECTORY_NODE); static QueryTestEntry subdir23(dir2 + "subdir23", B_DIRECTORY_NODE); static QueryTestEntry file21(dir2 + "file21", B_FILE_NODE); static QueryTestEntry file22(dir2 + "file22", B_FILE_NODE); static QueryTestEntry link21(dir2 + "link21", B_SYMLINK_NODE, &file12); static QueryTestEntry dir3(mountPoint + "dir3", B_DIRECTORY_NODE); static QueryTestEntry subdir31(dir3 + "subdir31", B_DIRECTORY_NODE); static QueryTestEntry subdir32(dir3 + "subdir32", B_DIRECTORY_NODE); static QueryTestEntry file31(dir3 + "file31", B_FILE_NODE); static QueryTestEntry file32(dir3 + "file32", B_FILE_NODE); static QueryTestEntry link31(dir3 + "link31", B_SYMLINK_NODE, &file22); static QueryTestEntry file1(mountPoint + "file1", B_FILE_NODE); static QueryTestEntry file2(mountPoint + "file2", B_FILE_NODE); static QueryTestEntry file3(mountPoint + "file3", B_FILE_NODE); static QueryTestEntry link1(mountPoint + "link1", B_SYMLINK_NODE, &file1); static QueryTestEntry link2(mountPoint + "link2", B_SYMLINK_NODE, &file2); static QueryTestEntry link3(mountPoint + "link3", B_SYMLINK_NODE, &file3); static QueryTestEntry *allTestEntries[] = { &dir1, &subdir11, &subdir12, &file11, &file12, &link11, &dir2, &subdir21, &subdir22, &subdir23, &file21, &file22, &link21, &dir3, &subdir31, &subdir32, &file31, &file32, &link31, &file1, &file2, &file3, &link1, &link2, &link3 }; static const int32 allTestEntryCount = sizeof(allTestEntries) / sizeof(QueryTestEntry*); // create_test_entries void create_test_entries(QueryTestEntry **testEntries, int32 count) { // create the command line string cmdLine("true"); for (int32 i = 0; i < count; i++) { const QueryTestEntry *entry = testEntries[i]; switch (entry->kind) { case B_DIRECTORY_NODE: cmdLine += " ; mkdir " + entry->path; break; case B_FILE_NODE: cmdLine += " ; touch " + entry->path; break; case B_SYMLINK_NODE: cmdLine += " ; ln -s " + entry->linkToPath + " " + entry->path; break; case B_ANY_NODE: default: printf("WARNING: invalid node kind\n"); break; } } BasicTest::execCommand(cmdLine); } // delete_test_entries void delete_test_entries(QueryTestEntry **testEntries, int32 count) { // create the command line string cmdLine("true"); for (int32 i = 0; i < count; i++) { const QueryTestEntry *entry = testEntries[i]; switch (entry->kind) { case B_DIRECTORY_NODE: case B_FILE_NODE: case B_SYMLINK_NODE: cmdLine += " ; rm -rf " + entry->path; break; case B_ANY_NODE: default: printf("WARNING: invalid node kind\n"); break; } } BasicTest::execCommand(cmdLine); } // QueryTest // Suite CppUnit::Test* QueryTest::Suite() { CppUnit::TestSuite *suite = new CppUnit::TestSuite(); typedef CppUnit::TestCaller TC; suite->addTest( new TC("BQuery::Predicate Test", &QueryTest::PredicateTest) ); suite->addTest( new TC("BQuery::Parameter Test", &QueryTest::ParameterTest) ); suite->addTest( new TC("BQuery::Fetch Test", &QueryTest::FetchTest) ); suite->addTest( new TC("BQuery::Live Test", &QueryTest::LiveTest) ); return suite; } // setUp void QueryTest::setUp() { BasicTest::setUp(); fApplication = new BTestApp("application/x-vnd.obos.query-test"); if (fApplication->Init() != B_OK) { fprintf(stderr, "Failed to initialize application.\n"); delete fApplication; fApplication = NULL; } fVolumeCreated = false; } // tearDown void QueryTest::tearDown() { BasicTest::tearDown(); if (fApplication) { fApplication->Terminate(); delete fApplication; fApplication = NULL; } if (fVolumeCreated) { deleteVolume(testVolumeImage, testMountPoint); fVolumeCreated = false; } } // TestPredicate static void TestPredicate(const PredicateNode &predicateNode, status_t pushResult = B_OK, status_t getResult = B_OK) { BString predicateString = predicateNode.toString().String(); //printf("predicate: `%s'\n", predicateString.String()); // GetPredicate(BString *) { Query query; // CPPUNIT_ASSERT( predicateNode.push(query) == pushResult ); status_t error = predicateNode.push(query); if (error != pushResult) { printf("predicate: `%s'\n", predicateString.String()); printf("error: %lx vs %lx\n", error, pushResult); } CPPUNIT_ASSERT( error == pushResult ); if (pushResult == B_OK) { BString predicate; // CPPUNIT_ASSERT( query.GetPredicate(&predicate) == getResult ); error = query.GetPredicate(&predicate); if (error != getResult) { printf("predicate: `%s'\n", predicateString.String()); printf("error: %lx vs %lx\n", error, getResult); } CPPUNIT_ASSERT( error == getResult ); if (getResult == B_OK) { CPPUNIT_ASSERT( (int32)query.PredicateLength() == predicateString.Length() + 1 ); CPPUNIT_ASSERT( predicateString == predicate ); } } } // GetPredicate(char *, size_t) { Query query; CPPUNIT_ASSERT( predicateNode.push(query) == pushResult ); if (pushResult == B_OK) { char buffer[1024]; CPPUNIT_ASSERT( query.GetPredicate(buffer, sizeof(buffer)) == getResult ); if (getResult == B_OK) CPPUNIT_ASSERT( predicateString == buffer ); } } // PredicateLength() { Query query; CPPUNIT_ASSERT( predicateNode.push(query) == pushResult ); if (pushResult == B_OK) { size_t expectedLength = (getResult == B_OK ? predicateString.Length() + 1 : 0); CPPUNIT_ASSERT( query.PredicateLength() == expectedLength ); } } // SetPredicate() { Query query; CPPUNIT_ASSERT( query.SetPredicate(predicateString.String()) == B_OK ); CPPUNIT_ASSERT( (int32)query.PredicateLength() == predicateString.Length() + 1 ); BString predicate; CPPUNIT_ASSERT( query.GetPredicate(&predicate) == B_OK ); CPPUNIT_ASSERT( predicateString == predicate ); } } // TestOperator static void TestOperator(query_op op) { // well formed TestPredicate(OpNode(op, new AttributeNode("attribute"), new Int32Node(42) )); TestPredicate(OpNode(op, new AttributeNode("attribute"), new StringNode("some string") )); TestPredicate(OpNode(op, new AttributeNode("attribute"), new DateNode("22 May 2002") )); // ill formed TestPredicate(OpNode(op, new AttributeNode("attribute"), NULL), B_OK, B_NO_INIT); // R5: crashs when pushing B_CONTAINS/B_BEGINS/ENDS_WITH on an empty stack #if TEST_R5 if (op < B_CONTAINS || op > B_ENDS_WITH) #endif TestPredicate(OpNode(op, NULL, NULL), B_OK, B_NO_INIT); TestPredicate(OpNode(op, new AttributeNode("attribute"), new DateNode("22 May 2002") ).addChild(new Int32Node(42)), B_OK, B_NO_INIT); } // PredicateTest void QueryTest::PredicateTest() { // tests: // * Push*() // * Set/GetPredicate(), PredicateLength() // empty predicate NextSubTest(); char buffer[1024]; { Query query; BString predicate; CPPUNIT_ASSERT( query.GetPredicate(&predicate) == B_NO_INIT ); } { Query query; CPPUNIT_ASSERT( query.GetPredicate(buffer, sizeof(buffer)) == B_NO_INIT ); } // one element predicates NextSubTest(); TestPredicate(Int32Node(42)); TestPredicate(UInt32Node(42)); TestPredicate(Int64Node(42)); // R5: buggy PushUInt64() implementation. #if !TEST_R5 TestPredicate(UInt64Node(42)); #endif TestPredicate(FloatNode(42)); TestPredicate(DoubleNode(42)); TestPredicate(StringNode("some \" chars ' to \\ be ( escaped ) or " "% not!")); TestPredicate(StringNode("some \" chars ' to \\ be ( escaped ) or " "% not!", true)); TestPredicate(DateNode("+15 min")); TestPredicate(DateNode("22 May 2002")); TestPredicate(DateNode("tomorrow")); TestPredicate(DateNode("17:57")); TestPredicate(DateNode("invalid date"), B_BAD_VALUE); TestPredicate(AttributeNode("some attribute")); // operators NextSubTest(); TestOperator(B_EQ); TestOperator(B_GT); TestOperator(B_GE); TestOperator(B_LT); TestOperator(B_LE); TestOperator(B_NE); TestOperator(B_CONTAINS); TestOperator(B_BEGINS_WITH); TestOperator(B_ENDS_WITH); TestOperator(B_AND); TestOperator(B_OR); { // B_NOT TestPredicate(OpNode(B_NOT, new AttributeNode("attribute"))); TestPredicate(OpNode(B_NOT, new Int32Node(42))); TestPredicate(OpNode(B_NOT, new StringNode("some string"))); TestPredicate(OpNode(B_NOT, new StringNode("some string", true))); TestPredicate(OpNode(B_NOT, new DateNode("22 May 2002"))); TestPredicate(OpNode(B_NOT, NULL), B_OK, B_NO_INIT); } // well formed, legal predicate NextSubTest(); TestPredicate(OpNode(B_AND, new OpNode(B_CONTAINS, new AttributeNode("attribute"), new StringNode("hello") ), new OpNode(B_OR, new OpNode(B_NOT, new OpNode(B_EQ, new AttributeNode("attribute2"), new UInt32Node(7) ), NULL ), new OpNode(B_GE, new AttributeNode("attribute3"), new DateNode("20 May 2002") ) ) )); // well formed, illegal predicate NextSubTest(); TestPredicate(OpNode(B_EQ, new StringNode("hello"), new OpNode(B_LE, new OpNode(B_NOT, new Int32Node(17), NULL ), new DateNode("20 May 2002") ) )); // ill formed predicates // Some have already been tested in TestOperator, so we only test a few // special ones. NextSubTest(); TestPredicate(ListNode(new Int32Node(42), new StringNode("hello!")), B_OK, B_NO_INIT); TestPredicate(OpNode(B_EQ, new StringNode("hello"), new OpNode(B_NOT, NULL) ), B_OK, B_NO_INIT); // precedence Push*() over SetPredicate() NextSubTest(); { Query query; OpNode predicate1(B_CONTAINS, new AttributeNode("attribute"), new StringNode("hello") ); StringNode predicate2("I'm the loser. :ยด-("); CPPUNIT_ASSERT( predicate1.push(query) == B_OK ); CPPUNIT_ASSERT( query.SetPredicate(predicate2.toString().String()) == B_OK ); BString predicate; CPPUNIT_ASSERT( query.GetPredicate(&predicate) == B_OK ); CPPUNIT_ASSERT( predicate == predicate1.toString() ); } // GetPredicate() clears the stack NextSubTest(); { Query query; OpNode predicate1(B_CONTAINS, new AttributeNode("attribute"), new StringNode("hello") ); StringNode predicate2("I'm the winner. :-)"); CPPUNIT_ASSERT( predicate1.push(query) == B_OK ); BString predicate; CPPUNIT_ASSERT( query.GetPredicate(&predicate) == B_OK ); CPPUNIT_ASSERT( predicate == predicate1.toString() ); CPPUNIT_ASSERT( query.SetPredicate(predicate2.toString().String()) == B_OK ); CPPUNIT_ASSERT( query.GetPredicate(&predicate) == B_OK ); CPPUNIT_ASSERT( predicate == predicate2.toString() ); } // PredicateLength() clears the stack NextSubTest(); { Query query; OpNode predicate1(B_CONTAINS, new AttributeNode("attribute"), new StringNode("hello") ); StringNode predicate2("I'm the winner. :-)"); CPPUNIT_ASSERT( predicate1.push(query) == B_OK ); CPPUNIT_ASSERT( (int32)query.PredicateLength() == predicate1.toString().Length() + 1 ); CPPUNIT_ASSERT( query.SetPredicate(predicate2.toString().String()) == B_OK ); BString predicate; CPPUNIT_ASSERT( query.GetPredicate(&predicate) == B_OK ); CPPUNIT_ASSERT( predicate == predicate2.toString() ); } // SetPredicate(), Push*() fail after Fetch() NextSubTest(); { Query query; CPPUNIT_ASSERT( query.SetPredicate("name=\"ThisShouldNotExist\"") == B_OK ); BVolume volume(dev_for_path("/boot")); CPPUNIT_ASSERT( volume.InitCheck() == B_OK ); CPPUNIT_ASSERT( query.SetVolume(&volume) == B_OK ); CPPUNIT_ASSERT( query.Fetch() == B_OK ); CPPUNIT_ASSERT( query.SetPredicate("name=\"ThisShouldNotExistEither\"") == B_NOT_ALLOWED ); // R5: Push*()ing a new predicate does work, though it doesn't make any sense #if TEST_R5 CPPUNIT_ASSERT( query.PushDate("20 May 2002") == B_OK ); CPPUNIT_ASSERT( query.PushValue((int32)42) == B_OK ); CPPUNIT_ASSERT( query.PushValue((uint32)42) == B_OK ); CPPUNIT_ASSERT( query.PushValue((int64)42) == B_OK ); CPPUNIT_ASSERT( query.PushValue((uint64)42) == B_OK ); CPPUNIT_ASSERT( query.PushValue((float)42) == B_OK ); CPPUNIT_ASSERT( query.PushValue((double)42) == B_OK ); CPPUNIT_ASSERT( query.PushValue("hello") == B_OK ); CPPUNIT_ASSERT( query.PushAttr("attribute") == B_OK ); CPPUNIT_ASSERT( query.PushOp(B_EQ) == B_OK ); #else CPPUNIT_ASSERT( query.PushDate("20 May 2002") == B_NOT_ALLOWED ); CPPUNIT_ASSERT( query.PushValue((int32)42) == B_NOT_ALLOWED ); CPPUNIT_ASSERT( query.PushValue((uint32)42) == B_NOT_ALLOWED ); CPPUNIT_ASSERT( query.PushValue((int64)42) == B_NOT_ALLOWED ); CPPUNIT_ASSERT( query.PushValue((uint64)42) == B_NOT_ALLOWED ); CPPUNIT_ASSERT( query.PushValue((float)42) == B_NOT_ALLOWED ); CPPUNIT_ASSERT( query.PushValue((double)42) == B_NOT_ALLOWED ); CPPUNIT_ASSERT( query.PushValue("hello") == B_NOT_ALLOWED ); CPPUNIT_ASSERT( query.PushAttr("attribute") == B_NOT_ALLOWED ); CPPUNIT_ASSERT( query.PushOp(B_EQ) == B_NOT_ALLOWED ); #endif } // SetPredicate(): bad args // R5: crashes when passing NULL to Set/GetPredicate() #if !TEST_R5 NextSubTest(); { Query query; CPPUNIT_ASSERT( query.SetPredicate(NULL) == B_BAD_VALUE ); CPPUNIT_ASSERT( query.SetPredicate("hello") == B_OK ); CPPUNIT_ASSERT( query.GetPredicate(NULL) == B_BAD_VALUE ); CPPUNIT_ASSERT( query.GetPredicate(NULL, 10) == B_BAD_VALUE ); } #endif } // ParameterTest void QueryTest::ParameterTest() { // tests: // * SetVolume, TargetDevice() // * SetTarget(), IsLive() // SetVolume(), TargetDevice() // uninitialized BQuery NextSubTest(); { BQuery query; CPPUNIT_ASSERT( query.TargetDevice() == B_ERROR ); } // NULL volume // R5: crashs when passing a NULL BVolume #if !TEST_R5 NextSubTest(); { BQuery query; CPPUNIT_ASSERT( query.SetVolume(NULL) == B_BAD_VALUE ); CPPUNIT_ASSERT( query.TargetDevice() == B_ERROR ); } #endif // invalid volume NextSubTest(); { BQuery query; BVolume volume(-2); CPPUNIT_ASSERT( volume.InitCheck() == B_BAD_VALUE ); CPPUNIT_ASSERT( query.SetVolume(&volume) == B_OK ); CPPUNIT_ASSERT( query.TargetDevice() == B_ERROR ); } // valid volume NextSubTest(); { BQuery query; dev_t device = dev_for_path("/boot"); BVolume volume(device); CPPUNIT_ASSERT( volume.InitCheck() == B_OK ); CPPUNIT_ASSERT( query.SetVolume(&volume) == B_OK ); CPPUNIT_ASSERT( query.TargetDevice() == device ); } // SetTarget(), IsLive() // uninitialized BQuery NextSubTest(); { BQuery query; CPPUNIT_ASSERT( query.IsLive() == false ); } // uninitialized BMessenger NextSubTest(); { BQuery query; BMessenger messenger; CPPUNIT_ASSERT( messenger.IsValid() == false ); CPPUNIT_ASSERT( query.SetTarget(messenger) == B_BAD_VALUE ); CPPUNIT_ASSERT( query.IsLive() == false ); } // valid BMessenger NextSubTest(); { BQuery query; BMessenger messenger(&fApplication->Handler()); CPPUNIT_ASSERT( messenger.IsValid() == true ); CPPUNIT_ASSERT( query.SetTarget(messenger) == B_OK ); CPPUNIT_ASSERT( query.IsLive() == true ); } // SetVolume/Target() fail after Fetch() NextSubTest(); { Query query; CPPUNIT_ASSERT( query.SetPredicate("name=\"ThisShouldNotExist\"") == B_OK ); BVolume volume(dev_for_path("/boot")); CPPUNIT_ASSERT( volume.InitCheck() == B_OK ); CPPUNIT_ASSERT( query.SetVolume(&volume) == B_OK ); CPPUNIT_ASSERT( query.Fetch() == B_OK ); CPPUNIT_ASSERT( query.SetVolume(&volume) == B_NOT_ALLOWED ); BMessenger messenger(&fApplication->Handler()); CPPUNIT_ASSERT( messenger.IsValid() == true ); CPPUNIT_ASSERT( query.SetTarget(messenger) == B_NOT_ALLOWED ); } // Fetch() fails without a valid volume set NextSubTest(); { Query query; CPPUNIT_ASSERT( query.SetPredicate("name=\"ThisShouldNotExist\"") == B_OK ); CPPUNIT_ASSERT( query.Fetch() == B_NO_INIT ); } } // TestFetchPredicateInit static void TestFetchPredicateInit(Query &query, TestSet &testSet, const char *mountPoint, const char *predicate, QueryTestEntry **entries, int32 entryCount) { // init the query CPPUNIT_ASSERT( query.SetPredicate(predicate) == B_OK ); BVolume volume(dev_for_path(mountPoint)); CPPUNIT_ASSERT( volume.InitCheck() == B_OK ); CPPUNIT_ASSERT( query.SetVolume(&volume) == B_OK ); CPPUNIT_ASSERT( query.Fetch() == B_OK ); // init the test set testSet.clear(); for (int32 i = 0; i < entryCount; i++) testSet.add(entries[i]->path); } // TestFetchPredicate static void TestFetchPredicate(const char *mountPoint, const char *predicate, QueryTestEntry **entries, int32 entryCount) { // GetNextEntry() { Query query; TestSet testSet; TestFetchPredicateInit(query, testSet, mountPoint, predicate, entries, entryCount); BEntry entry; while (query.GetNextEntry(&entry) == B_OK) { // Haiku supports rewinding queries, R5 does not. #ifdef TEST_R5 CPPUNIT_ASSERT( query.Rewind() == B_ERROR ); #endif CPPUNIT_ASSERT( query.CountEntries() == B_ERROR ); BPath path; CPPUNIT_ASSERT( entry.InitCheck() == B_OK ); CPPUNIT_ASSERT( entry.GetPath(&path) == B_OK ); CPPUNIT_ASSERT( testSet.test(path.Path()) == true ); } CPPUNIT_ASSERT( testSet.testDone() == true ); CPPUNIT_ASSERT( query.GetNextEntry(&entry) == B_ENTRY_NOT_FOUND ); } // GetNextRef() { Query query; TestSet testSet; TestFetchPredicateInit(query, testSet, mountPoint, predicate, entries, entryCount); entry_ref ref; while (query.GetNextRef(&ref) == B_OK) { // Haiku supports rewinding queries, R5 does not. #ifdef TEST_R5 CPPUNIT_ASSERT( query.Rewind() == B_ERROR ); #endif CPPUNIT_ASSERT( query.CountEntries() == B_ERROR ); BPath path(&ref); CPPUNIT_ASSERT( path.InitCheck() == B_OK ); CPPUNIT_ASSERT( testSet.test(path.Path()) == true ); } CPPUNIT_ASSERT( testSet.testDone() == true ); CPPUNIT_ASSERT( query.GetNextRef(&ref) == B_ENTRY_NOT_FOUND ); } // GetNextDirents() { Query query; TestSet testSet; TestFetchPredicateInit(query, testSet, mountPoint, predicate, entries, entryCount); size_t bufSize = (sizeof(dirent) + B_FILE_NAME_LENGTH) * 10; char buffer[bufSize]; dirent *ents = (dirent *)buffer; while (query.GetNextDirents(ents, bufSize, 1) == 1) { // Haiku supports rewinding queries, R5 does not. #ifdef TEST_R5 CPPUNIT_ASSERT( query.Rewind() == B_ERROR ); #endif CPPUNIT_ASSERT( query.CountEntries() == B_ERROR ); entry_ref ref(ents->d_pdev, ents->d_pino, ents->d_name); BPath path(&ref); CPPUNIT_ASSERT( path.InitCheck() == B_OK ); CPPUNIT_ASSERT( testSet.test(path.Path()) == true ); } CPPUNIT_ASSERT( testSet.testDone() == true ); CPPUNIT_ASSERT( query.GetNextDirents(ents, bufSize, 1) == 0 ); } // interleaving use of the different methods { Query query; TestSet testSet; TestFetchPredicateInit(query, testSet, mountPoint, predicate, entries, entryCount); size_t bufSize = (sizeof(dirent) + B_FILE_NAME_LENGTH) * 10; char buffer[bufSize]; dirent *ents = (dirent *)buffer; entry_ref ref; BEntry entry; while (query.GetNextDirents(ents, bufSize, 1) == 1) { // Haiku supports rewinding queries, R5 does not. #ifdef TEST_R5 CPPUNIT_ASSERT( query.Rewind() == B_ERROR ); #endif CPPUNIT_ASSERT( query.CountEntries() == B_ERROR ); entry_ref entref(ents->d_pdev, ents->d_pino, ents->d_name); BPath entpath(&entref); CPPUNIT_ASSERT( entpath.InitCheck() == B_OK ); CPPUNIT_ASSERT( testSet.test(entpath.Path()) == true ); if (query.GetNextRef(&ref) == B_OK) { BPath refpath(&ref); CPPUNIT_ASSERT( refpath.InitCheck() == B_OK ); CPPUNIT_ASSERT( testSet.test(refpath.Path()) == true ); } if (query.GetNextEntry(&entry) == B_OK) { BPath path; CPPUNIT_ASSERT( entry.InitCheck() == B_OK ); CPPUNIT_ASSERT( entry.GetPath(&path) == B_OK ); CPPUNIT_ASSERT( testSet.test(path.Path()) == true ); } } CPPUNIT_ASSERT( query.GetNextEntry(&entry) == B_ENTRY_NOT_FOUND ); CPPUNIT_ASSERT( query.GetNextRef(&ref) == B_ENTRY_NOT_FOUND ); CPPUNIT_ASSERT( query.GetNextDirents(ents, bufSize, 1) == 0 ); } } // FetchTest void QueryTest::FetchTest() { // tests: // * Clear()/Fetch() // * BEntryList interface // Fetch() // uninitialized BQuery NextSubTest(); { Query query; CPPUNIT_ASSERT( query.Fetch() == B_NO_INIT ); } // incompletely initialized BQuery (no predicate) NextSubTest(); { Query query; BVolume volume(dev_for_path("/boot")); CPPUNIT_ASSERT( volume.InitCheck() == B_OK ); CPPUNIT_ASSERT( query.SetVolume(&volume) == B_OK ); CPPUNIT_ASSERT( query.Fetch() == B_NO_INIT ); } // incompletely initialized BQuery (no volume) NextSubTest(); { Query query; CPPUNIT_ASSERT( query.SetPredicate("name=\"ThisShouldNotExist\"") == B_OK ); CPPUNIT_ASSERT( query.Fetch() == B_NO_INIT ); } // incompletely initialized BQuery (invalid predicate) NextSubTest(); { Query query; CPPUNIT_ASSERT( query.SetPredicate("name=\"ThisShouldNotExist\"&&") == B_OK ); BVolume volume(dev_for_path("/boot")); CPPUNIT_ASSERT( volume.InitCheck() == B_OK ); CPPUNIT_ASSERT( query.SetVolume(&volume) == B_OK ); CPPUNIT_ASSERT( query.Fetch() == B_BAD_VALUE ); } // initialized BQuery, Fetch() twice NextSubTest(); { Query query; CPPUNIT_ASSERT( query.SetPredicate("name=\"ThisShouldNotExist\"") == B_OK ); BVolume volume(dev_for_path("/boot")); CPPUNIT_ASSERT( volume.InitCheck() == B_OK ); CPPUNIT_ASSERT( query.SetVolume(&volume) == B_OK ); CPPUNIT_ASSERT( query.Fetch() == B_OK ); CPPUNIT_ASSERT( query.Fetch() == B_NOT_ALLOWED ); } // initialized BQuery, successful Fetch(), different predicates createVolume(testVolumeImage, testMountPoint, 2); fVolumeCreated = true; create_test_entries(allTestEntries, allTestEntryCount); // ... all files NextSubTest(); { QueryTestEntry *entries[] = { &file11, &file12, &file21, &file22, &file31, &file32, &file1, &file2, &file3 }; const int32 entryCount = sizeof(entries) / sizeof(QueryTestEntry*); TestFetchPredicate(testMountPoint, "name=\"file*\"", entries, entryCount); } // ... all entries containing a "l" NextSubTest(); { QueryTestEntry *entries[] = { &file11, &file12, &link11, &file21, &file22, &link21, &file31, &file32, &link31, &file1, &file2, &file3, &link1, &link2, &link3 }; const int32 entryCount = sizeof(entries) / sizeof(QueryTestEntry*); TestFetchPredicate(testMountPoint, "name=\"*l*\"", entries, entryCount); } // ... all entries ending on "2" NextSubTest(); { QueryTestEntry *entries[] = { &subdir12, &file12, &dir2, &subdir22, &file22, &subdir32, &file32, &file2, &link2 }; const int32 entryCount = sizeof(entries) / sizeof(QueryTestEntry*); TestFetchPredicate(testMountPoint, "name=\"*2\"", entries, entryCount); } // Clear() // uninitialized BQuery NextSubTest(); { Query query; CPPUNIT_ASSERT( query.Clear() == B_OK ); } // initialized BQuery, Fetch(), Clear(), Fetch() NextSubTest(); { Query query; CPPUNIT_ASSERT( query.SetPredicate("name=\"ThisShouldNotExist\"") == B_OK ); BVolume volume(dev_for_path("/boot")); CPPUNIT_ASSERT( volume.InitCheck() == B_OK ); CPPUNIT_ASSERT( query.SetVolume(&volume) == B_OK ); CPPUNIT_ASSERT( query.Fetch() == B_OK ); CPPUNIT_ASSERT( query.Clear() == B_OK ); CPPUNIT_ASSERT( query.Fetch() == B_NO_INIT ); } // initialized BQuery, Fetch(), Clear(), re-init, Fetch() NextSubTest(); { Query query; CPPUNIT_ASSERT( query.SetPredicate("name=\"ThisShouldNotExist\"") == B_OK ); BVolume volume(dev_for_path("/boot")); CPPUNIT_ASSERT( volume.InitCheck() == B_OK ); CPPUNIT_ASSERT( query.SetVolume(&volume) == B_OK ); CPPUNIT_ASSERT( query.Fetch() == B_OK ); CPPUNIT_ASSERT( query.Clear() == B_OK ); CPPUNIT_ASSERT( query.SetPredicate("name=\"ThisShouldNotExist\"") == B_OK ); CPPUNIT_ASSERT( volume.SetTo(dev_for_path("/boot")) == B_OK ); CPPUNIT_ASSERT( query.SetVolume(&volume) == B_OK ); CPPUNIT_ASSERT( query.Fetch() == B_OK ); } // BEntryList interface: // empty queries NextSubTest(); { Query query; CPPUNIT_ASSERT( query.SetPredicate("name=\"ThisShouldNotExist\"") == B_OK ); BVolume volume(dev_for_path("/boot")); CPPUNIT_ASSERT( volume.InitCheck() == B_OK ); CPPUNIT_ASSERT( query.SetVolume(&volume) == B_OK ); CPPUNIT_ASSERT( query.Fetch() == B_OK ); BEntry entry; entry_ref ref; size_t bufSize = (sizeof(dirent) + B_FILE_NAME_LENGTH) * 10; char buffer[bufSize]; dirent *ents = (dirent *)buffer; CPPUNIT_ASSERT( query.GetNextEntry(&entry) == B_ENTRY_NOT_FOUND ); CPPUNIT_ASSERT( query.GetNextRef(&ref) == B_ENTRY_NOT_FOUND ); CPPUNIT_ASSERT( query.GetNextDirents(ents, bufSize, 1) == 0 ); } // uninitialized queries NextSubTest(); { Query query; BEntry entry; entry_ref ref; size_t bufSize = (sizeof(dirent) + B_FILE_NAME_LENGTH) * 10; char buffer[bufSize]; dirent *ents = (dirent *)buffer; CPPUNIT_ASSERT( query.GetNextEntry(&entry) == B_FILE_ERROR ); CPPUNIT_ASSERT( query.GetNextRef(&ref) == B_FILE_ERROR ); CPPUNIT_ASSERT( query.GetNextDirents(ents, bufSize, 1) == B_FILE_ERROR ); } // bad args NextSubTest(); { Query query; CPPUNIT_ASSERT( query.SetPredicate("name=\"ThisShouldNotExist\"") == B_OK ); BVolume volume(dev_for_path("/boot")); CPPUNIT_ASSERT( volume.InitCheck() == B_OK ); CPPUNIT_ASSERT( query.SetVolume(&volume) == B_OK ); CPPUNIT_ASSERT( query.Fetch() == B_OK ); size_t bufSize = (sizeof(dirent) + B_FILE_NAME_LENGTH) * 10; // R5: crashs when passing a NULL BEntry or entry_ref #if !TEST_R5 CPPUNIT_ASSERT( query.GetNextEntry(NULL) == B_BAD_VALUE ); CPPUNIT_ASSERT( query.GetNextRef(NULL) == B_BAD_VALUE ); #endif CPPUNIT_ASSERT( equals(query.GetNextDirents(NULL, bufSize, 1), B_BAD_ADDRESS, B_BAD_VALUE) ); } } // AddLiveEntries void QueryTest::AddLiveEntries(QueryTestEntry **entries, int32 entryCount, QueryTestEntry **queryEntries, int32 queryEntryCount) { create_test_entries(entries, entryCount); for (int32 i = 0; i < entryCount; i++) { QueryTestEntry *entry = entries[i]; BNode node(entry->cpath); CPPUNIT_ASSERT( node.InitCheck() == B_OK ); node_ref nref; CPPUNIT_ASSERT( node.GetNodeRef(&nref) == B_OK ); entry->node = nref.node; entry_ref ref; CPPUNIT_ASSERT( get_ref_for_path(entry->cpath, &ref) == B_OK ); entry->directory = ref.directory; entry->name = ref.name; } CheckUpdateMessages(B_ENTRY_CREATED, queryEntries, queryEntryCount); } // RemoveLiveEntries void QueryTest::RemoveLiveEntries(QueryTestEntry **entries, int32 entryCount, QueryTestEntry **queryEntries, int32 queryEntryCount) { delete_test_entries(entries, entryCount); CheckUpdateMessages(B_ENTRY_REMOVED, queryEntries, queryEntryCount); for (int32 i = 0; i < entryCount; i++) { QueryTestEntry *entry = entries[i]; entry->directory = -1; entry->node = -1; entry->name = ""; } } // CheckUpdateMessages void QueryTest::CheckUpdateMessages(uint32 opcode, QueryTestEntry **entries, int32 entryCount) { // wait for the messages snooze(100000); if (fApplication) { BMessageQueue &queue = fApplication->Handler().Queue(); CPPUNIT_ASSERT( queue.Lock() ); try { int32 entryNum = 0; while (BMessage *_message = queue.NextMessage()) { BMessage message(*_message); delete _message; CPPUNIT_ASSERT( entryNum < entryCount ); QueryTestEntry *entry = entries[entryNum]; CPPUNIT_ASSERT( message.what == B_QUERY_UPDATE ); uint32 msgOpcode; CPPUNIT_ASSERT( message.FindInt32("opcode", (int32*)&msgOpcode) == B_OK ); CPPUNIT_ASSERT( msgOpcode == opcode ); dev_t device; CPPUNIT_ASSERT( message.FindInt32("device", &device) == B_OK ); CPPUNIT_ASSERT( device == dev_for_path(testMountPoint) ); ino_t directory; CPPUNIT_ASSERT( message.FindInt64("directory", &directory) == B_OK ); CPPUNIT_ASSERT( directory == entry->directory ); ino_t node; CPPUNIT_ASSERT( message.FindInt64("node", &node) == B_OK ); CPPUNIT_ASSERT( node == entry->node ); if (opcode == B_ENTRY_CREATED) { const char *name; CPPUNIT_ASSERT( message.FindString("name", &name) == B_OK ); CPPUNIT_ASSERT( entry->name == name ); } entryNum++; } CPPUNIT_ASSERT( entryNum == entryCount ); } catch (CppUnit::Exception exception) { queue.Unlock(); throw exception; } queue.Unlock(); } } // LiveTest void QueryTest::LiveTest() { // tests: // * live queries CPPUNIT_ASSERT( fApplication != NULL ); createVolume(testVolumeImage, testMountPoint, 2); fVolumeCreated = true; create_test_entries(allTestEntries, allTestEntryCount); BMessenger target(&fApplication->Handler()); // empty query, add some files, remove some files NextSubTest(); { Query query; CPPUNIT_ASSERT( query.SetPredicate("name=\"*Argh\"") == B_OK ); BVolume volume(dev_for_path(testMountPoint)); CPPUNIT_ASSERT( volume.InitCheck() == B_OK ); CPPUNIT_ASSERT( query.SetVolume(&volume) == B_OK ); CPPUNIT_ASSERT( query.SetTarget(target) == B_OK ); CPPUNIT_ASSERT( query.Fetch() == B_OK ); BEntry entry; CPPUNIT_ASSERT( query.GetNextEntry(&entry) == B_ENTRY_NOT_FOUND ); // the test entries QueryTestEntry testDir1(dir1 + "testDirArgh", B_DIRECTORY_NODE); QueryTestEntry testDir2(dir1 + "testDir2", B_DIRECTORY_NODE); QueryTestEntry testFile1(subdir21 + "testFileArgh", B_FILE_NODE); QueryTestEntry testFile2(subdir21 + "testFile2", B_FILE_NODE); QueryTestEntry testLink1(subdir32 + "testLinkArgh", B_SYMLINK_NODE, &file11); QueryTestEntry testLink2(subdir32 + "testLink2", B_SYMLINK_NODE, &file11); QueryTestEntry *entries[] = { &testDir1, &testDir2, &testFile1, &testFile2, &testLink1, &testLink2 }; int32 entryCount = sizeof(entries) / sizeof(QueryTestEntry*); QueryTestEntry *queryEntries[] = { &testDir1, &testFile1, &testLink1 }; int32 queryEntryCount = sizeof(queryEntries) / sizeof(QueryTestEntry*); AddLiveEntries(entries, entryCount, queryEntries, queryEntryCount); RemoveLiveEntries(entries, entryCount, queryEntries, queryEntryCount); } // non-empty query, add some files, remove some files NextSubTest(); { Query query; TestSet testSet; CPPUNIT_ASSERT( query.SetTarget(target) == B_OK ); QueryTestEntry *initialEntries[] = { &file11, &file12, &file21, &file22, &file31, &file32, &file1, &file2, &file3 }; int32 initialEntryCount = sizeof(initialEntries) / sizeof(QueryTestEntry*); TestFetchPredicateInit(query, testSet, testMountPoint, "name=\"*ile*\"", initialEntries, initialEntryCount); BEntry entry; while (query.GetNextEntry(&entry) == B_OK) { BPath path; CPPUNIT_ASSERT( entry.InitCheck() == B_OK ); CPPUNIT_ASSERT( entry.GetPath(&path) == B_OK ); CPPUNIT_ASSERT( testSet.test(path.Path()) == true ); } CPPUNIT_ASSERT( testSet.testDone() == true ); CPPUNIT_ASSERT( query.GetNextEntry(&entry) == B_ENTRY_NOT_FOUND ); // the test entries QueryTestEntry testDir1(dir1 + "testDir1", B_DIRECTORY_NODE); QueryTestEntry testDir2(dir1 + "testDir2", B_DIRECTORY_NODE); QueryTestEntry testFile1(subdir21 + "testFile1", B_FILE_NODE); QueryTestEntry testFile2(subdir21 + "testFile2", B_FILE_NODE); QueryTestEntry testLink1(subdir32 + "testLink1", B_SYMLINK_NODE, &file11); QueryTestEntry testLink2(subdir32 + "testLink2", B_SYMLINK_NODE, &file11); QueryTestEntry testFile3(subdir32 + "testFile3", B_FILE_NODE); QueryTestEntry *entries[] = { &testDir1, &testDir2, &testFile1, &testFile2, &testLink1, &testLink2, &testFile3 }; int32 entryCount = sizeof(entries) / sizeof(QueryTestEntry*); QueryTestEntry *queryEntries[] = { &testFile1, &testFile2, &testFile3 }; int32 queryEntryCount = sizeof(queryEntries) / sizeof(QueryTestEntry*); AddLiveEntries(entries, entryCount, queryEntries, queryEntryCount); RemoveLiveEntries(entries, entryCount, queryEntries, queryEntryCount); } }