File indexing completed on 2024-05-12 05:22:54
0001 /* -*- c++ -*- 0002 tests/parsertest.cpp 0003 0004 This file is part of the testsuite of KSieve, 0005 the KDE internet mail/usenet news message filtering library. 0006 SPDX-FileCopyrightText: 2003 Marc Mutz <mutz@kde.org> 0007 0008 SPDX-License-Identifier: GPL-2.0-only 0009 */ 0010 0011 #include "parser.h" 0012 #include <config-libksieve.h> // SIZEOF_UNSIGNED_LONG 0013 using KSieve::Parser; 0014 0015 #include "error.h" 0016 #include "scriptbuilder.h" 0017 0018 #include <QString> 0019 0020 #include <cstdlib> 0021 #include <iostream> 0022 0023 using std::cerr; 0024 using std::cout; 0025 using std::endl; 0026 0027 #include <cassert> 0028 0029 enum BuilderMethod { 0030 TaggedArgument, 0031 StringArgument, 0032 NumberArgument, 0033 CommandStart, 0034 CommandEnd, 0035 TestStart, 0036 TestEnd, 0037 TestListStart, 0038 TestListEnd, 0039 BlockStart, 0040 BlockEnd, 0041 StringListArgumentStart, 0042 StringListEntry, 0043 StringListArgumentEnd, 0044 HashComment, 0045 BracketComment, 0046 Error, 0047 Finished 0048 }; 0049 0050 static const unsigned int MAX_RESPONSES = 100; 0051 0052 static struct TestCase { 0053 const char *name; 0054 const char *script; 0055 struct Response { 0056 BuilderMethod method; 0057 const char *string; 0058 bool boolean; 0059 } responses[MAX_RESPONSES]; 0060 } testCases[] = { 0061 // 0062 // single commands: 0063 // 0064 0065 {"Null script", nullptr, {{Finished, nullptr, false}}}, 0066 0067 {"Empty script", "", {{Finished, nullptr, false}}}, 0068 0069 {"WS-only script", " \t\n\r\n", {{Finished, nullptr, false}}}, 0070 0071 {"Bare hash comment", "#comment", {{HashComment, "comment", false}, {Finished, nullptr, false}}}, 0072 0073 {"Bare bracket comment", "/*comment*/", {{BracketComment, "comment", false}, {Finished, nullptr, false}}}, 0074 0075 {"Bare command", "command;", {{CommandStart, "command", false}, {CommandEnd, nullptr, false}, {Finished, nullptr, false}}}, 0076 0077 {"Bare command - missing semicolon", "command", {{CommandStart, "command", false}, {Error, "MissingSemicolonOrBlock", false}}}, 0078 0079 {"surrounded by bracket comments", 0080 "/*comment*/command/*comment*/;/*comment*/", 0081 {{BracketComment, "comment", false}, 0082 {CommandStart, "command", false}, 0083 {BracketComment, "comment", false}, 0084 {CommandEnd, nullptr, false}, 0085 {BracketComment, "comment", false}, 0086 {Finished, nullptr, false}}}, 0087 0088 {"surrounded by hash comments", 0089 "#comment\ncommand#comment\n;#comment", 0090 {{HashComment, "comment", false}, 0091 {CommandStart, "command", false}, 0092 {HashComment, "comment", false}, 0093 {CommandEnd, nullptr, false}, 0094 {HashComment, "comment", false}, 0095 {Finished, nullptr, false}}}, 0096 0097 {"single tagged argument", 0098 "command :tag;", 0099 {{CommandStart, "command", false}, {TaggedArgument, "tag", false}, {CommandEnd, nullptr, false}, {Finished, nullptr, false}}}, 0100 0101 {"single tagged argument - missing semicolon", 0102 "command :tag", 0103 {{CommandStart, "command", false}, {TaggedArgument, "tag", false}, {Error, "MissingSemicolonOrBlock", false}}}, 0104 0105 {"single string argument - quoted string", 0106 "command \"string\";", 0107 {{CommandStart, "command", false}, {StringArgument, "string", false /*quoted*/}, {CommandEnd, nullptr, false}, {Finished, nullptr, false}}}, 0108 0109 {"single string argument - multi-line string", 0110 "command text:\nstring\n.\n;", 0111 {{CommandStart, "command", false}, {StringArgument, "string", true /*multiline*/}, {CommandEnd, nullptr, false}, {Finished, nullptr, false}}}, 0112 0113 {"single number argument - 100", 0114 "command 100;", 0115 {{CommandStart, "command", false}, {NumberArgument, "100 ", false}, {CommandEnd, nullptr, false}, {Finished, nullptr, false}}}, 0116 0117 {"single number argument - 100k", 0118 "command 100k;", 0119 {{CommandStart, "command", false}, {NumberArgument, "102400k", false}, {CommandEnd, nullptr, false}, {Finished, nullptr, false}}}, 0120 0121 {"single number argument - 100M", 0122 "command 100M;", 0123 {{CommandStart, "command", false}, {NumberArgument, "104857600M", false}, {CommandEnd, nullptr, false}, {Finished, nullptr, false}}}, 0124 0125 {"single number argument - 2G", 0126 "command 2G;", 0127 {{CommandStart, "command", false}, {NumberArgument, "2147483648G", false}, {CommandEnd, nullptr, false}, {Finished, nullptr, false}}}, 0128 0129 #if SIZEOF_UNSIGNED_LONG == 8 0130 #define ULONG_MAX_STRING "18446744073709551615" 0131 #define ULONG_MAXP1_STRING "18446744073709551616" 0132 #elif SIZEOF_UNSIGNED_LONG == 4 0133 #define ULONG_MAX_STRING "4294967295" 0134 #define ULONG_MAXP1_STRING "4G" 0135 #else 0136 #error sizeof( unsigned long ) != 4 && sizeof( unsigned long ) != 8 ??? 0137 #endif 0138 0139 {"single number argument - ULONG_MAX + 1", "command " ULONG_MAXP1_STRING ";", {{CommandStart, "command", false}, {Error, "NumberOutOfRange", false}}}, 0140 0141 {"single number argument - ULONG_MAX", 0142 "command " ULONG_MAX_STRING ";", 0143 {{CommandStart, "command", false}, {NumberArgument, ULONG_MAX_STRING " ", false}, {CommandEnd, nullptr, false}, {Finished, nullptr, false}}}, 0144 0145 {"single one-element string list argument - quoted string", 0146 "command [\"string\"];", 0147 {{CommandStart, "command", false}, 0148 {StringListArgumentStart, nullptr, false}, 0149 {StringListEntry, "string", false /*quoted*/}, 0150 {StringListArgumentEnd, nullptr, false}, 0151 {CommandEnd, nullptr, false}, 0152 {Finished, nullptr, false}}}, 0153 0154 {"single one-element string list argument - multi-line string", 0155 "command [text:\nstring\n.\n];", 0156 {{CommandStart, "command", false}, 0157 {StringListArgumentStart, nullptr, false}, 0158 {StringListEntry, "string", true /*multiline*/}, 0159 {StringListArgumentEnd, nullptr, false}, 0160 {CommandEnd, nullptr, false}, 0161 {Finished, nullptr, false}}}, 0162 0163 {"single two-element string list argument - quoted strings", 0164 R"(command ["string","string"];)", 0165 {{CommandStart, "command", false}, 0166 {StringListArgumentStart, nullptr, false}, 0167 {StringListEntry, "string", false /*quoted*/}, 0168 {StringListEntry, "string", false /*quoted*/}, 0169 {StringListArgumentEnd, nullptr, false}, 0170 {CommandEnd, nullptr, false}, 0171 {Finished, nullptr, false}}}, 0172 0173 {"single two-element string list argument - multi-line strings", 0174 "command [text:\nstring\n.\n,text:\nstring\n.\n];", 0175 {{CommandStart, "command", false}, 0176 {StringListArgumentStart, nullptr, false}, 0177 {StringListEntry, "string", true /*multiline*/}, 0178 {StringListEntry, "string", true /*multiline*/}, 0179 {StringListArgumentEnd, nullptr, false}, 0180 {CommandEnd, nullptr, false}, 0181 {Finished, nullptr, false}}}, 0182 0183 {"single two-element string list argument - quoted + multi-line strings", 0184 "command [\"string\",text:\nstring\n.\n];", 0185 {{CommandStart, "command", false}, 0186 {StringListArgumentStart, nullptr, false}, 0187 {StringListEntry, "string", false /*quoted*/}, 0188 {StringListEntry, "string", true /*multiline*/}, 0189 {StringListArgumentEnd, nullptr, false}, 0190 {CommandEnd, nullptr, false}, 0191 {Finished, nullptr, false}}}, 0192 0193 {"single two-element string list argument - multi-line + quoted strings", 0194 "command [text:\nstring\n.\n,\"string\"];", 0195 {{CommandStart, "command", false}, 0196 {StringListArgumentStart, nullptr, false}, 0197 {StringListEntry, "string", true /*multiline*/}, 0198 {StringListEntry, "string", false /*quoted*/}, 0199 {StringListArgumentEnd, nullptr, false}, 0200 {CommandEnd, nullptr, false}, 0201 {Finished, nullptr, false}}}, 0202 0203 {"single bare test argument", 0204 "command test;", 0205 {{CommandStart, "command", false}, {TestStart, "test", false}, {TestEnd, nullptr, false}, {CommandEnd, nullptr, false}, {Finished, nullptr, false}}}, 0206 0207 {"one-element test list argument", 0208 "command(test);", 0209 {{CommandStart, "command", false}, 0210 {TestListStart, nullptr, false}, 0211 {TestStart, "test", false}, 0212 {TestEnd, nullptr, false}, 0213 {TestListEnd, nullptr, false}, 0214 {CommandEnd, nullptr, false}, 0215 {Finished, nullptr, false}}}, 0216 0217 {"two-element test list argument", 0218 "command(test,test);", 0219 {{CommandStart, "command", false}, 0220 {TestListStart, nullptr, false}, 0221 {TestStart, "test", false}, 0222 {TestEnd, nullptr, false}, 0223 {TestStart, "test", false}, 0224 {TestEnd, nullptr, false}, 0225 {TestListEnd, nullptr, false}, 0226 {CommandEnd, nullptr, false}, 0227 {Finished, nullptr, false}}}, 0228 0229 {"zero-element block", 0230 "command{}", 0231 {{CommandStart, "command", false}, {BlockStart, nullptr, false}, {BlockEnd, nullptr, false}, {CommandEnd, nullptr, false}, {Finished, nullptr, false}}}, 0232 0233 {"one-element block", 0234 "command{command;}", 0235 {{CommandStart, "command", false}, 0236 {BlockStart, nullptr, false}, 0237 {CommandStart, "command", false}, 0238 {CommandEnd, nullptr, false}, 0239 {BlockEnd, nullptr, false}, 0240 {CommandEnd, nullptr, false}, 0241 {Finished, nullptr, false}}}, 0242 0243 {"two-element block", 0244 "command{command;command;}", 0245 {{CommandStart, "command", false}, 0246 {BlockStart, nullptr, false}, 0247 {CommandStart, "command", false}, 0248 {CommandEnd, nullptr, false}, 0249 {CommandStart, "command", false}, 0250 {CommandEnd, nullptr, false}, 0251 {BlockEnd, nullptr, false}, 0252 {CommandEnd, nullptr, false}, 0253 {Finished, nullptr, false}}}, 0254 0255 {"command with a test with a test with a test", 0256 "command test test test;", 0257 {{CommandStart, "command", false}, 0258 {TestStart, "test", false}, 0259 {TestStart, "test", false}, 0260 {TestStart, "test", false}, 0261 {TestEnd, nullptr, false}, 0262 {TestEnd, nullptr, false}, 0263 {TestEnd, nullptr, false}, 0264 {CommandEnd, nullptr, false}, 0265 {Finished, nullptr, false}}}, 0266 }; 0267 0268 static const int numTestCases = sizeof testCases / sizeof *testCases; 0269 0270 // Prints out the parse tree in XML-like format. For visual inspection 0271 // (manual tests). 0272 class PrintingScriptBuilder : public KSieve::ScriptBuilder 0273 { 0274 public: 0275 PrintingScriptBuilder() 0276 : KSieve::ScriptBuilder() 0277 , indent(0) 0278 { 0279 write("<script type=\"application/sieve\">"); 0280 ++indent; 0281 } 0282 0283 ~PrintingScriptBuilder() override = default; 0284 0285 void taggedArgument(const QString &tag) override 0286 { 0287 write("tag", tag); 0288 } 0289 0290 void stringArgument(const QString &string, bool multiLine, const QString & /*fixme*/) override 0291 { 0292 write(multiLine ? "string type=\"multiline\"" : "string type=\"quoted\"", string); 0293 } 0294 0295 void numberArgument(unsigned long number, char quantifier) override 0296 { 0297 const QString txt = QLatin1StringView("number") + (quantifier ? QStringLiteral(" quantifier=\"%1\"").arg(quantifier) : QString()); 0298 write(txt.toLatin1(), QString::number(number)); 0299 } 0300 0301 void commandStart(const QString &identifier, int lineNumber) override 0302 { 0303 Q_UNUSED(lineNumber) 0304 write("<command>"); 0305 ++indent; 0306 write("identifier", identifier); 0307 } 0308 0309 void commandEnd(int lineNumber) override 0310 { 0311 Q_UNUSED(lineNumber) 0312 --indent; 0313 write("</command>"); 0314 } 0315 0316 void testStart(const QString &identifier) override 0317 { 0318 write("<test>"); 0319 ++indent; 0320 write("identifier", identifier); 0321 } 0322 0323 void testEnd() override 0324 { 0325 --indent; 0326 write("</test>"); 0327 } 0328 0329 void testListStart() override 0330 { 0331 write("<testlist>"); 0332 ++indent; 0333 } 0334 0335 void testListEnd() override 0336 { 0337 --indent; 0338 write("</testlist>"); 0339 } 0340 0341 void blockStart(int lineNumber) override 0342 { 0343 Q_UNUSED(lineNumber) 0344 write("<block>"); 0345 ++indent; 0346 } 0347 0348 void blockEnd(int lineNumber) override 0349 { 0350 Q_UNUSED(lineNumber) 0351 --indent; 0352 write("</block>"); 0353 } 0354 0355 void stringListArgumentStart() override 0356 { 0357 write("<stringlist>"); 0358 ++indent; 0359 } 0360 0361 void stringListArgumentEnd() override 0362 { 0363 --indent; 0364 write("</stringlist>"); 0365 } 0366 0367 void stringListEntry(const QString &string, bool multiline, const QString &hashComment) override 0368 { 0369 stringArgument(string, multiline, hashComment); 0370 } 0371 0372 void hashComment(const QString &comment) override 0373 { 0374 write("comment type=\"hash\"", comment); 0375 } 0376 0377 void bracketComment(const QString &comment) override 0378 { 0379 write("comment type=\"bracket\"", comment); 0380 } 0381 0382 void lineFeed() override 0383 { 0384 write("<crlf/>"); 0385 } 0386 0387 void error(const KSieve::Error &error) override 0388 { 0389 indent = 0; 0390 write((QStringLiteral("Error: ") + error.asString()).toLatin1().constData()); 0391 } 0392 0393 void finished() override 0394 { 0395 --indent; 0396 write("</script>"); 0397 } 0398 0399 private: 0400 int indent; 0401 void write(const char *msg) 0402 { 0403 for (int i = 2 * indent; i > 0; --i) { 0404 cout << " "; 0405 } 0406 cout << msg << endl; 0407 } 0408 0409 void write(const QByteArray &key, const QString &value) 0410 { 0411 if (value.isEmpty()) { 0412 write(QByteArray(QByteArray("<") + key + QByteArray("/>")).constData()); 0413 return; 0414 } 0415 write(QByteArray(QByteArray("<") + key + QByteArray(">")).constData()); 0416 ++indent; 0417 write(value.toUtf8().data()); 0418 --indent; 0419 write(QByteArray(QByteArray("</") + key + QByteArray(">")).constData()); 0420 } 0421 }; 0422 0423 // verifies that methods get called with expected arguments (and in 0424 // expected sequence) as specified by the TestCase. For automated 0425 // tests. 0426 class VerifyingScriptBuilder : public KSieve::ScriptBuilder 0427 { 0428 public: 0429 VerifyingScriptBuilder(const TestCase &testCase) 0430 : KSieve::ScriptBuilder() 0431 , mNextResponse(0) 0432 , mTestCase(testCase) 0433 , mOk(true) 0434 { 0435 } 0436 0437 ~VerifyingScriptBuilder() override = default; 0438 0439 [[nodiscard]] bool ok() const 0440 { 0441 return mOk; 0442 } 0443 0444 void taggedArgument(const QString &tag) override 0445 { 0446 checkIs(TaggedArgument); 0447 checkEquals(tag); 0448 ++mNextResponse; 0449 } 0450 0451 void stringArgument(const QString &string, bool multiline, const QString & /*fixme*/) override 0452 { 0453 checkIs(StringArgument); 0454 checkEquals(string); 0455 checkEquals(multiline); 0456 ++mNextResponse; 0457 } 0458 0459 void numberArgument(unsigned long number, char quantifier) override 0460 { 0461 checkIs(NumberArgument); 0462 checkEquals(QString::number(number) + QLatin1Char(quantifier ? quantifier : ' ')); 0463 ++mNextResponse; 0464 } 0465 0466 void commandStart(const QString &identifier, int lineNumber) override 0467 { 0468 Q_UNUSED(lineNumber) 0469 checkIs(CommandStart); 0470 checkEquals(identifier); 0471 ++mNextResponse; 0472 } 0473 0474 void commandEnd(int lineNumber) override 0475 { 0476 Q_UNUSED(lineNumber) 0477 checkIs(CommandEnd); 0478 ++mNextResponse; 0479 } 0480 0481 void testStart(const QString &identifier) override 0482 { 0483 checkIs(TestStart); 0484 checkEquals(identifier); 0485 ++mNextResponse; 0486 } 0487 0488 void testEnd() override 0489 { 0490 checkIs(TestEnd); 0491 ++mNextResponse; 0492 } 0493 0494 void testListStart() override 0495 { 0496 checkIs(TestListStart); 0497 ++mNextResponse; 0498 } 0499 0500 void testListEnd() override 0501 { 0502 checkIs(TestListEnd); 0503 ++mNextResponse; 0504 } 0505 0506 void blockStart(int lineNumber) override 0507 { 0508 Q_UNUSED(lineNumber) 0509 checkIs(BlockStart); 0510 ++mNextResponse; 0511 } 0512 0513 void blockEnd(int lineNumber) override 0514 { 0515 Q_UNUSED(lineNumber) 0516 checkIs(BlockEnd); 0517 ++mNextResponse; 0518 } 0519 0520 void stringListArgumentStart() override 0521 { 0522 checkIs(StringListArgumentStart); 0523 ++mNextResponse; 0524 } 0525 0526 void stringListEntry(const QString &string, bool multiLine, const QString & /*fixme*/) override 0527 { 0528 checkIs(StringListEntry); 0529 checkEquals(string); 0530 checkEquals(multiLine); 0531 ++mNextResponse; 0532 } 0533 0534 void stringListArgumentEnd() override 0535 { 0536 checkIs(StringListArgumentEnd); 0537 ++mNextResponse; 0538 } 0539 0540 void hashComment(const QString &comment) override 0541 { 0542 checkIs(HashComment); 0543 checkEquals(comment); 0544 ++mNextResponse; 0545 } 0546 0547 void bracketComment(const QString &comment) override 0548 { 0549 checkIs(BracketComment); 0550 checkEquals(comment); 0551 ++mNextResponse; 0552 } 0553 0554 void lineFeed() override 0555 { 0556 // FIXME 0557 } 0558 0559 void error(const KSieve::Error &error) override 0560 { 0561 checkIs(Error); 0562 checkEquals(QString::fromLatin1(KSieve::Error::typeToString(error.type()))); 0563 ++mNextResponse; 0564 } 0565 0566 void finished() override 0567 { 0568 checkIs(Finished); 0569 //++mNextResponse (no!) 0570 } 0571 0572 private: 0573 Q_DISABLE_COPY(VerifyingScriptBuilder) 0574 [[nodiscard]] const TestCase::Response ¤tResponse() const 0575 { 0576 assert(mNextResponse <= MAX_RESPONSES); 0577 return mTestCase.responses[mNextResponse]; 0578 } 0579 0580 void checkIs(BuilderMethod m) 0581 { 0582 if (currentResponse().method != m) { 0583 cerr << " expected method " << (int)currentResponse().method << ", got " << (int)m; 0584 mOk = false; 0585 } 0586 } 0587 0588 void checkEquals(const QString &s) 0589 { 0590 if (s != QString::fromUtf8(currentResponse().string)) { 0591 cerr << " expected string arg \"" << (currentResponse().string ? currentResponse().string : "<null>") << "\", got \"" 0592 << (s.isNull() ? "<null>" : s.toUtf8().data()) << "\""; 0593 mOk = false; 0594 } 0595 } 0596 0597 void checkEquals(bool b) 0598 { 0599 if (b != currentResponse().boolean) { 0600 cerr << " expected boolean arg <" << currentResponse().boolean << ">, got <" << b << ">"; 0601 mOk = false; 0602 } 0603 } 0604 0605 unsigned int mNextResponse; 0606 const TestCase &mTestCase; 0607 bool mOk; 0608 }; 0609 0610 int main(int argc, char *argv[]) 0611 { 0612 if (argc == 2) { // manual test 0613 const char *scursor = argv[1]; 0614 const char *const send = argv[1] + qstrlen(argv[1]); 0615 0616 Parser parser(scursor, send); 0617 PrintingScriptBuilder psb; 0618 parser.setScriptBuilder(&psb); 0619 if (parser.parse()) { 0620 cout << "ok" << endl; 0621 } else { 0622 cout << "bad" << endl; 0623 } 0624 } else if (argc == 1) { // automated test 0625 bool success = true; 0626 for (int i = 0; i < numTestCases; ++i) { 0627 const TestCase &t = testCases[i]; 0628 cerr << t.name << ":"; 0629 VerifyingScriptBuilder v(t); 0630 Parser p(t.script, t.script + qstrlen(t.script)); 0631 p.setScriptBuilder(&v); 0632 const bool ok = p.parse(); 0633 if (v.ok()) { 0634 if (ok) { 0635 cerr << " ok"; 0636 } else { 0637 cerr << " xfail"; 0638 } 0639 } else { 0640 success = false; 0641 } 0642 cerr << endl; 0643 } 0644 if (!success) { 0645 exit(1); 0646 } 0647 } else { // usage error 0648 cerr << "usage: parsertest [ <string> ]" << endl; 0649 exit(1); 0650 } 0651 0652 return 0; 0653 }