summaryrefslogtreecommitdiffstats
path: root/libksieve/tests
diff options
context:
space:
mode:
Diffstat (limited to 'libksieve/tests')
-rw-r--r--libksieve/tests/Makefile.am13
-rw-r--r--libksieve/tests/lexertest.cpp484
-rw-r--r--libksieve/tests/parsertest.cpp667
3 files changed, 1164 insertions, 0 deletions
diff --git a/libksieve/tests/Makefile.am b/libksieve/tests/Makefile.am
new file mode 100644
index 000000000..36b538408
--- /dev/null
+++ b/libksieve/tests/Makefile.am
@@ -0,0 +1,13 @@
+
+INCLUDES = -I$(top_srcdir)/libksieve $(all_includes)
+LDADD = ../libksieve.la
+
+# test programs:
+check_PROGRAMS = \
+ lexertest \
+ parsertest
+
+TESTS = $(check_PROGRAMS)
+
+lexertest_SOURCES = lexertest.cpp
+parsertest_SOURCES = parsertest.cpp
diff --git a/libksieve/tests/lexertest.cpp b/libksieve/tests/lexertest.cpp
new file mode 100644
index 000000000..461499501
--- /dev/null
+++ b/libksieve/tests/lexertest.cpp
@@ -0,0 +1,484 @@
+/* -*- c++ -*-
+ tests/lexertest.cpp
+
+ This file is part of the testsuite of KSieve,
+ the KDE internet mail/usenet news message filtering library.
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ KSieve is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License, version 2, as
+ published by the Free Software Foundation.
+
+ KSieve is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+#include <config.h>
+#include <ksieve/lexer.h>
+using KSieve::Lexer;
+
+#include <ksieve/error.h>
+using KSieve::Error;
+
+#include <qcstring.h> // qstrlen
+#include <qstring.h>
+
+#include <iostream>
+using std::cout;
+using std::cerr;
+using std::endl;
+
+static const char * token2string( Lexer::Token t ) {
+ switch ( t ) {
+#define CASE(x) case Lexer::x: return #x
+ CASE( None );
+ CASE( HashComment );
+ CASE( BracketComment );
+ CASE( Identifier );
+ CASE( Tag );
+ CASE( Number );
+ CASE( MultiLineString );
+ CASE( QuotedString );
+ CASE( Special );
+ CASE( LineFeeds );
+ }
+ return "";
+#undef CASE
+}
+
+struct TestCase {
+ const char * name;
+ const char * string;
+ struct {
+ Lexer::Token token;
+ const char * result;
+ } expected[16]; // end with { None, 0 }
+ Error::Type expectedError;
+ int errorLine, errorCol;
+};
+
+static const TestCase testcases[] = {
+ //
+ // Whitespace:
+ //
+
+ { "Null script", 0,
+ { { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+
+ { "Empty script", "",
+ { { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+
+ { "Whitespace-only script", " \t\n\t \n",
+ { { Lexer::LineFeeds, "2" }, { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+
+ { "Lone CR", "\r",
+ { { Lexer::None, 0 } },
+ Error::CRWithoutLF, 0, 1
+ },
+
+ { "CR+Space", "\r ",
+ { { Lexer::None, 0 } },
+ Error::CRWithoutLF, 0, 1
+ },
+
+ { "CRLF alone", "\r\n",
+ { { Lexer::LineFeeds, "1" }, { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+
+ //
+ // hash comments:
+ //
+
+ { "Basic hash comment (no newline)", "#comment",
+ { { Lexer::HashComment, "comment" }, { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+
+ { "Basic hash comment (LF)", "#comment\n",
+ { { Lexer::HashComment, "comment" }, { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+
+ { "Basic hash comment (CRLF)", "#comment\r\n",
+ { { Lexer::HashComment, "comment" }, { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+
+ { "Basic hash comment (CR)", "#comment\r",
+ { { Lexer::HashComment, 0 } },
+ Error::CRWithoutLF, 0, 9
+ },
+
+ { "Non-UTF-8 in hash comment", "#\xA9 copyright",
+ { { Lexer::HashComment, 0 } },
+ Error::InvalidUTF8, 0, 12
+ },
+
+ //
+ // bracket comments:
+ //
+
+ { "Basic bracket comment", "/* comment */",
+ { { Lexer::BracketComment, " comment " }, { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+
+ { "Basic bracket comment - missing trailing slash", "/* comment *",
+ { { Lexer::BracketComment, 0 } },
+ Error::UnfinishedBracketComment, 0, 0
+ },
+
+ { "Basic bracket comment - missing trailing asterisk + slash", "/* comment ",
+ { { Lexer::BracketComment, 0 } },
+ Error::UnfinishedBracketComment, 0, 0
+ },
+
+ { "Basic bracket comment - missing leading slash", "* comment */",
+ { { Lexer::None, 0 } },
+ Error::IllegalCharacter, 0, 0
+ },
+
+ { "Basic bracket comment - missing leading asterisk + slash", "comment */",
+ { { Lexer::Identifier, "comment" }, { Lexer::None, 0 } },
+ Error::IllegalCharacter, 0, 8
+ },
+
+ { "Basic multiline bracket comment (LF)", "/* comment\ncomment */",
+ { { Lexer::BracketComment, " comment\ncomment " }, { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+
+ { "Basic multiline bracket comment (CRLF)", "/* comment\r\ncomment */",
+ { { Lexer::BracketComment, " comment\ncomment " }, { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+
+ { "Basic multiline bracket comment (CR)", "/* comment\rcomment */",
+ { { Lexer::BracketComment, 0 } },
+ Error::CRWithoutLF, 0, 11
+ },
+
+ { "Non-UTF-8 in bracket comment", "/*\xA9 copyright*/",
+ { { Lexer::BracketComment, 0 } },
+ Error::InvalidUTF8, 0, 14
+ },
+
+ //
+ // numbers:
+ //
+ { "Basic number 1", "1",
+ { { Lexer::Number, "1" }, { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+ { "Basic number 01", "01",
+ { { Lexer::Number, "01" }, { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+ { "Qualified number 1k", "1k",
+ { { Lexer::Number, "1k" }, { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+ { "Qualified number 1M", "1M",
+ { { Lexer::Number, "1M" }, { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+ { "Qualified number 1G", "1G",
+ { { Lexer::Number, "1G" }, { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+ //
+ // identifiers:
+ //
+ { "Basic identifier \"id\"", "id",
+ { { Lexer::Identifier, "id" }, { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+ { "Basic identifier \"_id\"", "_id",
+ { { Lexer::Identifier, "_id" }, { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+ //
+ // tags:
+ //
+ { "Basic tag \":tag\"", ":tag",
+ { { Lexer::Tag, "tag" }, { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+ { "Basic tag \":_tag\"", ":_tag",
+ { { Lexer::Tag, "_tag" }, { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+ //
+ // specials:
+ //
+ { "Basic special \"{}[]();,\"", "{}[]();,",
+ { { Lexer::Special, "{" }, { Lexer::Special, "}" },
+ { Lexer::Special, "[" }, { Lexer::Special, "]" },
+ { Lexer::Special, "(" }, { Lexer::Special, ")" },
+ { Lexer::Special, ";" }, { Lexer::Special, "," }, { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+ //
+ // quoted-string:
+ //
+ { "Basic quoted string \"foo\"", "\"foo\"",
+ { { Lexer::QuotedString, "foo" }, { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+ { "Basic quoted string, UTF-8", "\"foo\xC3\xB1" "foo\"", // fooäfoo
+ { { Lexer::QuotedString, "foo\xC3\xB1" "foo" }, { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+ { "Quoted string, escaped '\"'", "\"foo\\\"bar\"",
+ { { Lexer::QuotedString, "foo\"bar" }, { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+ { "Quoted string, escaped '\\'", "\"foo\\\\bar\"",
+ { { Lexer::QuotedString, "foo\\bar" }, { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+ { "Quoted string, excessive escapes", "\"\\fo\\o\"",
+ { { Lexer::QuotedString, "foo" }, { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+ { "Quoted string across lines (LF)", "\"foo\nbar\"",
+ { { Lexer::QuotedString, "foo\nbar" }, { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+ { "Quoted string across lines (CRLF)", "\"foo\r\nbar\"",
+ { { Lexer::QuotedString, "foo\nbar" }, { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+ //
+ // multiline strings:
+ //
+ { "Basic multiline string I (LF)", "text:\nfoo\n.",
+ { { Lexer::MultiLineString, "foo" /* "foo\n" ? */ }, { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+ { "Basic multiline string I (CRLF)", "text:\r\nfoo\r\n.",
+ { { Lexer::MultiLineString, "foo" /* "foo\n" ? */ }, { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+ { "Basic multiline string II (LF)", "text:\nfoo\n.\n",
+ { { Lexer::MultiLineString, "foo" /* "foo\n" ? */ }, { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+ { "Basic multiline string II (CRLF)", "text:\r\nfoo\r\n.\r\n",
+ { { Lexer::MultiLineString, "foo" /* "foo\n" ? */ }, { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+ { "Dotstuffed multiline string (LF)", "text:\n..foo\n.",
+ { { Lexer::MultiLineString, ".foo" /* ".foo\n" ? */ }, { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+ { "Dotstuffed multiline string (CRLF)", "text:\r\n..foo\r\n.",
+ { { Lexer::MultiLineString, ".foo" /* ".foo\n" ? */ }, { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+ { "Incompletely dotstuffed multiline string (LF)", "text:\n.foo\n.",
+ { { Lexer::MultiLineString, ".foo" /* ".foo\n" ? */ }, { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+ { "Incompletely dotstuffed multiline string (CRLF)", "text:\r\n.foo\r\n.",
+ { { Lexer::MultiLineString, ".foo" /* ".foo\n" ? */ }, { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+ { "Mutiline with a line with only one '.'","text:\r\nfoo\r\n..\r\nbar\r\n.",
+ { { Lexer::MultiLineString, "foo\n.\nbar" }, { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+
+
+ //
+ // Errors in single tokens:
+ //
+
+ //
+ // numbers:
+ //
+ { "Number, unknown qualifier", "100f",
+ { { Lexer::Number, "100" } },
+ Error::UnexpectedCharacter, 0, 3
+ },
+ { "Negative number", "-100",
+ { { Lexer::None, 0 } },
+ Error::IllegalCharacter, 0, 0
+ },
+ //
+ // identifiers:
+ //
+ { "Identifier, leading digits", "0id",
+ { { Lexer::Number, "0" } },
+ Error::UnexpectedCharacter, 0, 1
+ },
+ { "Identifier, embedded umlaut", "idäid",
+ { { Lexer::Identifier, "id" } },
+ Error::IllegalCharacter, 0, 2
+ },
+ //
+ // tags:
+ //
+ { "Lone ':' (at end)", ":",
+ { { Lexer::Tag, 0 } },
+ Error::UnexpectedCharacter, 0, 0
+ },
+ { "Lone ':' (in stream)", ": ",
+ { { Lexer::Tag, 0 } },
+ Error::UnexpectedCharacter, 0, 1
+ },
+ { "Tag, leading digits", ":0tag",
+ { { Lexer::Tag, 0 } },
+ Error::NoLeadingDigits, 0, 1
+ },
+ { "Tag, embedded umlaut", ":tagätag",
+ { { Lexer::Tag, "tag" } },
+ Error::IllegalCharacter, 0, 4
+ },
+ //
+ // specials: (none)
+ // quoted string:
+ //
+ { "Premature end of quoted string", "\"foo",
+ { { Lexer::QuotedString, "foo" } },
+ Error::PrematureEndOfQuotedString, 0, 0
+ },
+ { "Invalid UTF-8 in quoted string", "\"foo\xC0\xA0" "foo\"",
+ { { Lexer::QuotedString, "foo" } },
+ Error::InvalidUTF8, 0, 4
+ },
+
+ //
+ // Whitespace / token separation: valid
+ //
+
+ { "Two identifiers with linebreaks", "foo\nbar\n",
+ { { Lexer::Identifier, "foo" },
+ { Lexer::LineFeeds, "1" },
+ { Lexer::Identifier, "bar" },
+ { Lexer::LineFeeds, "1" },
+ { Lexer::None, 0 } },
+ Error::None, 0, 0
+ },
+
+ //
+ // Whitespace / token separation: invalid
+ //
+
+};
+
+static const int numTestCases = sizeof testcases / sizeof *testcases ;
+
+int main( int argc, char * argv[] ) {
+
+ if ( argc == 2 ) { // manual test
+
+ const char * scursor = argv[1];
+ const char * const send = argv[1] + qstrlen( argv[1] );
+
+ Lexer lexer( scursor, send );
+
+ cout << "Begin" << endl;
+ while ( !lexer.atEnd() ) {
+ QString result;
+ Lexer::Token token = lexer.nextToken( result );
+ if ( lexer.error() ) {
+ cout << "Error " << token2string( token ) << ": \""
+ << lexer.error().asString().latin1() << "\" at ("
+ << lexer.error().line() << "," << lexer.error().column()
+ << ")" << endl;
+ break;
+ } else
+ cout << "Got " << token2string( token ) << ": \""
+ << result.utf8().data() << "\" at ("
+ << lexer.line() << "," << lexer.column() << ")" << endl;
+ }
+ cout << "End" << endl;
+
+ } else if ( argc == 1 ) { // automated test
+ bool success = true;
+ for ( int i = 0 ; i < numTestCases ; ++i ) {
+ bool ok = true;
+ const TestCase & t = testcases[i];
+ const char * const send = t.string + qstrlen( t.string );
+ Lexer lexer( t.string, send, Lexer::IncludeComments );
+ cerr << t.name << ":";
+ for ( int j = 0 ; !lexer.atEnd() ; ++j ) {
+ QString result;
+ Lexer::Token token = lexer.nextToken( result );
+ Error error = lexer.error();
+ if ( t.expected[j].token != token ) {
+ ok = false;
+ cerr << " expected token " << token2string( t.expected[j].token )
+ << ", got " << token2string( token );
+ }
+ if ( QString::fromUtf8( t.expected[j].result ) != result ) {
+ ok = false;
+ if ( t.expected[j].result )
+ cerr << " expected string \"" << t.expected[j].result << "\"";
+ else
+ cerr << " expected null string";
+ if ( !result.utf8().isNull() )
+ cerr << ", got \"" << result.utf8().data() << "\"";
+ else
+ cerr << ", got null string";
+ }
+ if ( error && error.type() != t.expectedError ) {
+ ok = false;
+ cerr << " expected error #" << (int)t.expectedError
+ << ", got #" << (int)error.type();
+ }
+ if ( error && ( error.line() != t.errorLine || error.column() != t.errorCol ) ) {
+ ok = false;
+ cerr << " expected position (" << t.errorLine << "," << t.errorCol
+ << "), got (" << error.line() << "," << error.column() << ")";
+ }
+ if ( error )
+ goto ErrorOut;
+ if ( t.expected[j].token == Lexer::None &&
+ t.expected[j].result == 0 )
+ break;
+ }
+ if ( !lexer.atEnd() ) {
+ ok = false;
+ cerr << " premature end of expected token list";
+ }
+ ErrorOut:
+ if ( ok )
+ cerr << " ok";
+ cerr << endl;
+ if ( !ok )
+ success = false;
+ }
+ if ( !success )
+ return 1;
+ } else { // usage error
+ cerr << "usage: lexertest [ <string> ]" << endl;
+ exit( 1 );
+ }
+
+ return 0;
+}
diff --git a/libksieve/tests/parsertest.cpp b/libksieve/tests/parsertest.cpp
new file mode 100644
index 000000000..e2ea0fd39
--- /dev/null
+++ b/libksieve/tests/parsertest.cpp
@@ -0,0 +1,667 @@
+/* -*- c++ -*-
+ tests/parsertest.cpp
+
+ This file is part of the testsuite of KSieve,
+ the KDE internet mail/usenet news message filtering library.
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ KSieve is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License, version 2, as
+ published by the Free Software Foundation.
+
+ KSieve is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+#include <config.h>
+#include <ksieve/parser.h>
+using KSieve::Parser;
+
+#include <ksieve/error.h>
+#include <ksieve/scriptbuilder.h>
+
+#include <qcstring.h> // qstrlen
+#include <qstring.h>
+
+#include <iostream>
+using std::cout;
+using std::cerr;
+using std::endl;
+
+#include <cassert>
+
+enum BuilderMethod {
+ TaggedArgument,
+ StringArgument,
+ NumberArgument,
+ CommandStart,
+ CommandEnd,
+ TestStart,
+ TestEnd,
+ TestListStart,
+ TestListEnd,
+ BlockStart,
+ BlockEnd,
+ StringListArgumentStart,
+ StringListEntry,
+ StringListArgumentEnd,
+ HashComment,
+ BracketComment,
+ Error,
+ Finished
+};
+
+static const unsigned int MAX_RESPONSES = 100;
+
+struct TestCase {
+ const char * name;
+ const char * script;
+ struct Response {
+ BuilderMethod method;
+ const char * string;
+ bool boolean;
+ } responses[MAX_RESPONSES];
+} testCases[] = {
+
+ //
+ // single commands:
+ //
+
+ { "Null script",
+ 0,
+ { { Finished, 0, false } }
+ },
+
+ { "Empty script",
+ "",
+ { { Finished, 0, false } }
+ },
+
+ { "WS-only script",
+ " \t\n\r\n",
+ { { Finished, 0, false } }
+ },
+
+ { "Bare hash comment",
+ "#comment",
+ { { HashComment, "comment", false },
+ { Finished, 0, false } }
+ },
+
+ { "Bare bracket comment",
+ "/*comment*/",
+ { { BracketComment, "comment", false },
+ { Finished, 0, false } }
+ },
+
+ { "Bare command",
+ "command;",
+ { { CommandStart, "command", false },
+ { CommandEnd, 0, false },
+ { Finished, 0, false } }
+ },
+
+ { "Bare command - missing semicolon",
+ "command",
+ { { CommandStart, "command", false },
+ { Error, "MissingSemicolonOrBlock", false } }
+ },
+
+ { "surrounded by bracket comments",
+ "/*comment*/command/*comment*/;/*comment*/",
+ { { BracketComment, "comment", false },
+ { CommandStart, "command", false },
+ { BracketComment, "comment", false },
+ { CommandEnd, 0, false },
+ { BracketComment, "comment", false },
+ { Finished, 0, false } }
+ },
+
+ { "surrounded by hash comments",
+ "#comment\ncommand#comment\n;#comment",
+ { { HashComment, "comment", false },
+ { CommandStart, "command", false },
+ { HashComment, "comment", false },
+ { CommandEnd, 0, false },
+ { HashComment, "comment", false },
+ { Finished, 0, false } }
+ },
+
+ { "single tagged argument",
+ "command :tag;",
+ { { CommandStart, "command", false },
+ { TaggedArgument, "tag", false },
+ { CommandEnd, 0, false },
+ { Finished, 0, false } }
+ },
+
+ { "single tagged argument - missing semicolon",
+ "command :tag",
+ { { CommandStart, "command", false },
+ { TaggedArgument, "tag", false },
+ { Error, "MissingSemicolonOrBlock", false } }
+ },
+
+ { "single string argument - quoted string",
+ "command \"string\";",
+ { { CommandStart, "command", false },
+ { StringArgument, "string", false /*quoted*/ },
+ { CommandEnd, 0, false },
+ { Finished, 0, false } }
+ },
+
+ { "single string argument - multi-line string",
+ "command text:\nstring\n.\n;",
+ { { CommandStart, "command", false },
+ { StringArgument, "string", true /*multiline*/ },
+ { CommandEnd, 0, false },
+ { Finished, 0, false } }
+ },
+
+ { "single number argument - 100",
+ "command 100;",
+ { { CommandStart, "command", false },
+ { NumberArgument, "100 ", false },
+ { CommandEnd, 0, false },
+ { Finished, 0, false } }
+ },
+
+ { "single number argument - 100k",
+ "command 100k;",
+ { { CommandStart, "command", false },
+ { NumberArgument, "102400k", false },
+ { CommandEnd, 0, false },
+ { Finished, 0, false } }
+ },
+
+ { "single number argument - 100M",
+ "command 100M;",
+ { { CommandStart, "command", false },
+ { NumberArgument, "104857600M", false },
+ { CommandEnd, 0, false },
+ { Finished, 0, false } }
+ },
+
+ { "single number argument - 2G",
+ "command 2G;",
+ { { CommandStart, "command", false },
+ { NumberArgument, "2147483648G", false },
+ { CommandEnd, 0, false },
+ { Finished, 0, false } }
+ },
+
+#if SIZEOF_UNSIGNED_LONG == 8
+# define ULONG_MAX_STRING "18446744073709551615"
+# define ULONG_MAXP1_STRING "18446744073709551616"
+#elif SIZEOF_UNSIGNED_LONG == 4
+# define ULONG_MAX_STRING "4294967295"
+# define ULONG_MAXP1_STRING "4G"
+#else
+# error sizeof( unsigned long ) != 4 && sizeof( unsigned long ) != 8 ???
+#endif
+
+ { "single number argument - ULONG_MAX + 1",
+ "command " ULONG_MAXP1_STRING ";",
+ { { CommandStart, "command", false },
+ { Error, "NumberOutOfRange", false } }
+ },
+
+ { "single number argument - ULONG_MAX",
+ "command " ULONG_MAX_STRING ";",
+ { { CommandStart, "command", false },
+ { NumberArgument, ULONG_MAX_STRING " ", false },
+ { CommandEnd, 0, false },
+ { Finished, 0, false } }
+ },
+
+ { "single one-element string list argument - quoted string",
+ "command [\"string\"];",
+ { { CommandStart, "command", false },
+ { StringListArgumentStart, 0, false },
+ { StringListEntry, "string", false /*quoted*/ },
+ { StringListArgumentEnd, 0, false },
+ { CommandEnd, 0, false },
+ { Finished, 0, false } }
+ },
+
+ { "single one-element string list argument - multi-line string",
+ "command [text:\nstring\n.\n];",
+ { { CommandStart, "command", false },
+ { StringListArgumentStart, 0, false },
+ { StringListEntry, "string", true /*multiline*/ },
+ { StringListArgumentEnd, 0, false },
+ { CommandEnd, 0, false },
+ { Finished, 0, false } }
+ },
+
+ { "single two-element string list argument - quoted strings",
+ "command [\"string\",\"string\"];",
+ { { CommandStart, "command", false },
+ { StringListArgumentStart, 0, false },
+ { StringListEntry, "string", false /*quoted*/ },
+ { StringListEntry, "string", false /*quoted*/ },
+ { StringListArgumentEnd, 0, false },
+ { CommandEnd, 0, false },
+ { Finished, 0, false } }
+ },
+
+ { "single two-element string list argument - multi-line strings",
+ "command [text:\nstring\n.\n,text:\nstring\n.\n];",
+ { { CommandStart, "command", false },
+ { StringListArgumentStart, 0, false },
+ { StringListEntry, "string", true /*multiline*/ },
+ { StringListEntry, "string", true /*multiline*/ },
+ { StringListArgumentEnd, 0, false },
+ { CommandEnd, 0, false },
+ { Finished, 0, false } }
+ },
+
+ { "single two-element string list argument - quoted + multi-line strings",
+ "command [\"string\",text:\nstring\n.\n];",
+ { { CommandStart, "command", false },
+ { StringListArgumentStart, 0, false },
+ { StringListEntry, "string", false /*quoted*/ },
+ { StringListEntry, "string", true /*multiline*/ },
+ { StringListArgumentEnd, 0, false },
+ { CommandEnd, 0, false },
+ { Finished, 0, false } }
+ },
+
+ { "single two-element string list argument - multi-line + quoted strings",
+ "command [text:\nstring\n.\n,\"string\"];",
+ { { CommandStart, "command", false },
+ { StringListArgumentStart, 0, false },
+ { StringListEntry, "string", true /*multiline*/ },
+ { StringListEntry, "string", false /*quoted*/ },
+ { StringListArgumentEnd, 0, false },
+ { CommandEnd, 0, false },
+ { Finished, 0, false } }
+ },
+
+ { "single bare test argument",
+ "command test;",
+ { { CommandStart, "command", false },
+ { TestStart, "test", false },
+ { TestEnd, 0, false },
+ { CommandEnd, 0, false },
+ { Finished, 0, false } }
+ },
+
+ { "one-element test list argument",
+ "command(test);",
+ { { CommandStart, "command", false },
+ { TestListStart, 0, false },
+ { TestStart, "test", false },
+ { TestEnd, 0, false },
+ { TestListEnd, 0, false },
+ { CommandEnd, 0, false },
+ { Finished, 0, false } }
+ },
+
+ { "two-element test list argument",
+ "command(test,test);",
+ { { CommandStart, "command", false },
+ { TestListStart, 0, false },
+ { TestStart, "test", false },
+ { TestEnd, 0, false },
+ { TestStart, "test", false },
+ { TestEnd, 0, false },
+ { TestListEnd, 0, false },
+ { CommandEnd, 0, false },
+ { Finished, 0, false } }
+ },
+
+ { "zero-element block",
+ "command{}",
+ { { CommandStart, "command", false },
+ { BlockStart, 0, false },
+ { BlockEnd, 0, false },
+ { CommandEnd, 0, false },
+ { Finished, 0, false } }
+ },
+
+ { "one-element block",
+ "command{command;}",
+ { { CommandStart, "command", false },
+ { BlockStart, 0, false },
+ { CommandStart, "command", false },
+ { CommandEnd, 0, false },
+ { BlockEnd, 0, false },
+ { CommandEnd, 0, false },
+ { Finished, 0, false } }
+ },
+
+ { "two-element block",
+ "command{command;command;}",
+ { { CommandStart, "command", false },
+ { BlockStart, 0, false },
+ { CommandStart, "command", false },
+ { CommandEnd, 0, false },
+ { CommandStart, "command", false },
+ { CommandEnd, 0, false },
+ { BlockEnd, 0, false },
+ { CommandEnd, 0, false },
+ { Finished, 0, false } }
+ },
+
+ { "command with a test with a test with a test",
+ "command test test test;",
+ { { CommandStart, "command", false },
+ { TestStart, "test", false },
+ { TestStart, "test", false },
+ { TestStart, "test", false },
+ { TestEnd, 0, false },
+ { TestEnd, 0, false },
+ { TestEnd, 0, false },
+ { CommandEnd, 0, false },
+ { Finished, 0, false } }
+ },
+
+};
+
+static const int numTestCases = sizeof testCases / sizeof *testCases ;
+
+// Prints out the parse tree in XML-like format. For visual inspection
+// (manual tests).
+class PrintingScriptBuilder : public KSieve::ScriptBuilder {
+public:
+ PrintingScriptBuilder()
+ : KSieve::ScriptBuilder(), indent( 0 )
+ {
+ write( "<script type=\"application/sieve\">" );
+ ++indent;
+ }
+ virtual ~PrintingScriptBuilder() {}
+
+ void taggedArgument( const QString & tag ) {
+ write( "tag", tag );
+ }
+ void stringArgument( const QString & string, bool multiLine, const QString & /*fixme*/ ) {
+ write( multiLine ? "string type=\"multiline\"" : "string type=\"quoted\"", string );
+ }
+ void numberArgument( unsigned long number, char quantifier ) {
+ const QString txt = "number" + ( quantifier ? QString(" quantifier=\"%1\"").arg( quantifier ) : QString::null ) ;
+ write( txt.latin1(), QString::number( number ) );
+ }
+ void commandStart( const QString & identifier ) {
+ write( "<command>" );
+ ++indent;
+ write( "identifier", identifier );
+ }
+ void commandEnd() {
+ --indent;
+ write( "</command>" );
+ }
+ void testStart( const QString & identifier ) {
+ write( "<test>" );
+ ++indent;
+ write( "identifier", identifier );
+ }
+ void testEnd() {
+ --indent;
+ write( "</test>" );
+ }
+ void testListStart() {
+ write( "<testlist>" );
+ ++indent;
+ }
+ void testListEnd() {
+ --indent;
+ write( "</testlist>" );
+ }
+ void blockStart() {
+ write( "<block>" );
+ ++indent;
+ }
+ void blockEnd() {
+ --indent;
+ write( "</block>" );
+ }
+ void stringListArgumentStart() {
+ write( "<stringlist>" );
+ ++indent;
+ }
+ void stringListArgumentEnd() {
+ --indent;
+ write( "</stringlist>" );
+ }
+ void stringListEntry( const QString & string, bool multiline, const QString & hashComment ) {
+ stringArgument( string, multiline, hashComment );
+ }
+ void hashComment( const QString & comment ) {
+ write( "comment type=\"hash\"", comment );
+ }
+ void bracketComment( const QString & comment ) {
+ write( "comment type=\"bracket\"", comment );
+ }
+
+ void lineFeed() {
+ write( "<crlf/>" );
+ }
+
+ void error( const KSieve::Error & error ) {
+ indent = 0;
+ write( ("Error: " + error.asString()).latin1() );
+ }
+ void finished() {
+ --indent;
+ write( "</script>" );
+ }
+private:
+ int indent;
+ void write( const char * msg ) {
+ for ( int i = 2*indent ; i > 0 ; --i )
+ cout << " ";
+ cout << msg << endl;
+ }
+ void write( const QCString & key, const QString & value ) {
+ if ( value.isEmpty() ) {
+ write( "<" + key + "/>" );
+ return;
+ }
+ write( "<" + key + ">" );
+ ++indent;
+ write( value.utf8().data() );
+ --indent;
+ write( "</" + key + ">" );
+ }
+};
+
+
+// verifes that methods get called with expected arguments (and in
+// expected sequence) as specified by the TestCase. For automated
+// tests.
+class VerifyingScriptBuilder : public KSieve::ScriptBuilder {
+public:
+ VerifyingScriptBuilder( const TestCase & testCase )
+ : KSieve::ScriptBuilder(),
+ mNextResponse( 0 ), mTestCase( testCase ), mOk( true )
+ {
+ }
+ virtual ~VerifyingScriptBuilder() {}
+
+ bool ok() const { return mOk; }
+
+ void taggedArgument( const QString & tag ) {
+ checkIs( TaggedArgument );
+ checkEquals( tag );
+ ++mNextResponse;
+ }
+ void stringArgument( const QString & string, bool multiline, const QString & /*fixme*/ ) {
+ checkIs( StringArgument );
+ checkEquals( string );
+ checkEquals( multiline );
+ ++mNextResponse;
+ }
+ void numberArgument( unsigned long number, char quantifier ) {
+ checkIs( NumberArgument );
+ checkEquals( QString::number( number ) + ( quantifier ? quantifier : ' ' ) );
+ ++mNextResponse;
+ }
+ void commandStart( const QString & identifier ) {
+ checkIs( CommandStart );
+ checkEquals( identifier );
+ ++mNextResponse;
+ }
+ void commandEnd() {
+ checkIs( CommandEnd );
+ ++mNextResponse;
+ }
+ void testStart( const QString & identifier ) {
+ checkIs( TestStart );
+ checkEquals( identifier );
+ ++mNextResponse;
+ }
+ void testEnd() {
+ checkIs( TestEnd );
+ ++mNextResponse;
+ }
+ void testListStart() {
+ checkIs( TestListStart );
+ ++mNextResponse;
+ }
+ void testListEnd() {
+ checkIs( TestListEnd );
+ ++mNextResponse;
+ }
+ void blockStart() {
+ checkIs( BlockStart );
+ ++mNextResponse;
+ }
+ void blockEnd() {
+ checkIs( BlockEnd );
+ ++mNextResponse;
+ }
+ void stringListArgumentStart() {
+ checkIs( StringListArgumentStart );
+ ++mNextResponse;
+ }
+ void stringListEntry( const QString & string, bool multiLine, const QString & /*fixme*/ ) {
+ checkIs( StringListEntry );
+ checkEquals( string );
+ checkEquals( multiLine );
+ ++mNextResponse;
+ }
+ void stringListArgumentEnd() {
+ checkIs( StringListArgumentEnd );
+ ++mNextResponse;
+ }
+ void hashComment( const QString & comment ) {
+ checkIs( HashComment );
+ checkEquals( comment );
+ ++mNextResponse;
+ }
+ void bracketComment( const QString & comment ) {
+ checkIs( BracketComment );
+ checkEquals( comment );
+ ++mNextResponse;
+ }
+ void lineFeed() {
+ // FIXME
+ }
+ void error( const KSieve::Error & error ) {
+ checkIs( Error );
+ checkEquals( QString( KSieve::Error::typeToString( error.type() ) ) );
+ ++mNextResponse;
+ }
+ void finished() {
+ checkIs( Finished );
+ //++mNextResponse (no!)
+ }
+
+private:
+ const TestCase::Response & currentResponse() const {
+ assert( mNextResponse <= MAX_RESPONSES );
+ return mTestCase.responses[mNextResponse];
+ }
+
+ void checkIs( BuilderMethod m ) {
+ if ( currentResponse().method != m ) {
+ cerr << " expected method " << (int)currentResponse().method
+ << ", got " << (int)m;
+ mOk = false;
+ }
+ }
+
+ void checkEquals( const QString & s ) {
+ if ( s != QString::fromUtf8( currentResponse().string ) ) {
+ cerr << " expected string arg \""
+ << ( currentResponse().string ? currentResponse().string : "<null>" )
+ << "\", got \"" << ( s.isNull() ? "<null>" : s.utf8().data() ) << "\"";
+ mOk = false;
+ }
+ }
+ void checkEquals( bool b ) {
+ if ( b != currentResponse().boolean ) {
+ cerr << " expected boolean arg <" << currentResponse().boolean
+ << ">, got <" << b << ">";
+ mOk = false;
+ }
+ }
+
+ unsigned int mNextResponse;
+ const TestCase & mTestCase;
+ bool mOk;
+};
+
+
+int main( int argc, char * argv[] ) {
+
+ if ( argc == 2 ) { // manual test
+
+ const char * scursor = argv[1];
+ const char * const send = argv[1] + qstrlen( argv[1] );
+
+ Parser parser( scursor, send );
+ PrintingScriptBuilder psb;
+ parser.setScriptBuilder( &psb );
+ if ( parser.parse() )
+ cout << "ok" << endl;
+ else
+ cout << "bad" << endl;
+
+
+ } else if ( argc == 1 ) { // automated test
+ bool success = true;
+ for ( int i = 0 ; i < numTestCases ; ++i ) {
+ const TestCase & t = testCases[i];
+ cerr << t.name << ":";
+ VerifyingScriptBuilder v( t );
+ Parser p( t.script, t.script + qstrlen( t.script ) );
+ p.setScriptBuilder( &v );
+ const bool ok = p.parse();
+ if ( v.ok() )
+ if ( ok )
+ cerr << " ok";
+ else
+ cerr << " xfail";
+ else
+ success = false;
+ cerr << endl;
+ }
+ if ( !success )
+ exit( 1 );
+
+ } else { // usage error
+ cerr << "usage: parsertest [ <string> ]" << endl;
+ exit( 1 );
+ }
+
+ return 0;
+}