diff options
Diffstat (limited to 'libksieve/tests')
-rw-r--r-- | libksieve/tests/Makefile.am | 13 | ||||
-rw-r--r-- | libksieve/tests/lexertest.cpp | 484 | ||||
-rw-r--r-- | libksieve/tests/parsertest.cpp | 667 |
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; +} |