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 &currentResponse() 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 }