tests/auto/xmlpatternsxqts/lib/TestBaseLine.cpp
changeset 0 1918ee327afb
child 4 3b1da2848fc7
equal deleted inserted replaced
-1:000000000000 0:1918ee327afb
       
     1 /****************************************************************************
       
     2 **
       
     3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
       
     4 ** All rights reserved.
       
     5 ** Contact: Nokia Corporation (qt-info@nokia.com)
       
     6 **
       
     7 ** This file is part of the test suite of the Qt Toolkit.
       
     8 **
       
     9 ** $QT_BEGIN_LICENSE:LGPL$
       
    10 ** No Commercial Usage
       
    11 ** This file contains pre-release code and may not be distributed.
       
    12 ** You may use this file in accordance with the terms and conditions
       
    13 ** contained in the Technology Preview License Agreement accompanying
       
    14 ** this package.
       
    15 **
       
    16 ** GNU Lesser General Public License Usage
       
    17 ** Alternatively, this file may be used under the terms of the GNU Lesser
       
    18 ** General Public License version 2.1 as published by the Free Software
       
    19 ** Foundation and appearing in the file LICENSE.LGPL included in the
       
    20 ** packaging of this file.  Please review the following information to
       
    21 ** ensure the GNU Lesser General Public License version 2.1 requirements
       
    22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
       
    23 **
       
    24 ** In addition, as a special exception, Nokia gives you certain additional
       
    25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
       
    26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
       
    27 **
       
    28 ** If you have questions regarding the use of this file, please contact
       
    29 ** Nokia at qt-info@nokia.com.
       
    30 **
       
    31 **
       
    32 **
       
    33 **
       
    34 **
       
    35 **
       
    36 **
       
    37 **
       
    38 ** $QT_END_LICENSE$
       
    39 **
       
    40 ****************************************************************************/
       
    41 
       
    42 #include <QDomDocument>
       
    43 #include <QFile>
       
    44 #include <QFileInfo>
       
    45 #include <QRegExp>
       
    46 #include <QtDebug>
       
    47 #include <QUrl>
       
    48 #include <QXmlAttributes>
       
    49 #include <QXmlSimpleReader>
       
    50 
       
    51 #include "qdebug_p.h"
       
    52 #include "XMLWriter.h"
       
    53 
       
    54 #include "TestBaseLine.h"
       
    55 
       
    56 using namespace QPatternistSDK;
       
    57 using namespace QPatternist;
       
    58 
       
    59 Q_GLOBAL_STATIC_WITH_ARGS(QRegExp, errorRegExp, (QLatin1String("[A-Z]{4}[0-9]{4}")))
       
    60 
       
    61 TestBaseLine::TestBaseLine(const Type t) : m_type(t)
       
    62 {
       
    63     Q_ASSERT(errorRegExp()->isValid());
       
    64 }
       
    65 
       
    66 TestResult::Status TestBaseLine::scan(const QString &serialized,
       
    67                                       const TestBaseLine::List &lines)
       
    68 {
       
    69     Q_ASSERT_X(lines.count() >= 1, Q_FUNC_INFO,
       
    70                "At least one base line must be passed, otherwise there's nothing "
       
    71                "to compare to.");
       
    72 
       
    73     const TestBaseLine::List::const_iterator end(lines.constEnd());
       
    74     TestBaseLine::List::const_iterator it(lines.constBegin());
       
    75     for(; it != end; ++it)
       
    76     {
       
    77         const TestResult::Status retval((*it)->verify(serialized));
       
    78 
       
    79         if(retval == TestResult::Pass || retval == TestResult::NotTested)
       
    80             return retval;
       
    81     }
       
    82 
       
    83     return TestResult::Fail;
       
    84 }
       
    85 
       
    86 TestResult::Status TestBaseLine::scanErrors(const ErrorHandler::Message::List &errors,
       
    87                                             const TestBaseLine::List &lines)
       
    88 {
       
    89     pDebug() << "TestBaseLine::scanErrors()";
       
    90 
       
    91     /* 1. Find the first error in @p errors that's a Patternist
       
    92      * error(not warning and not from Qt) and extract the error code. */
       
    93     QString errorCode;
       
    94 
       
    95     const ErrorHandler::Message::List::const_iterator end(errors.constEnd());
       
    96     ErrorHandler::Message::List::const_iterator it(errors.constBegin());
       
    97     for(; it != end; ++it)
       
    98     {
       
    99         if((*it).type() != QtFatalMsg)
       
   100             continue;
       
   101 
       
   102         errorCode = QUrl((*it).identifier()).fragment();
       
   103 
       
   104         pDebug() << "ERR:" << (*it).description();
       
   105         /* This is hackish. We have no way of determining whether a Message
       
   106          * is actually issued from Patternist, so we try to narrow it down like this. */
       
   107         if(errorRegExp()->exactMatch(errorCode))
       
   108             break; /* It's an error code. */
       
   109         else
       
   110             errorCode.clear();
       
   111     }
       
   112 
       
   113     pDebug() << "Got error code: " << errorCode;
       
   114     /* 2. Loop through @p lines, and for the first base line
       
   115      * which is of type ExpectedError and which matches @p errorCode
       
   116      * return Pass, otherwise Fail. */
       
   117     const TestBaseLine::List::const_iterator blend(lines.constEnd());
       
   118     TestBaseLine::List::const_iterator blit(lines.constBegin());
       
   119     for(; blit != blend; ++blit)
       
   120     {
       
   121         const Type t = (*blit)->type();
       
   122 
       
   123         if(t == TestBaseLine::ExpectedError)
       
   124         {
       
   125             const QString d((*blit)->details());
       
   126             if(d == errorCode || d == QChar::fromLatin1('*'))
       
   127                 return TestResult::Pass;
       
   128         }
       
   129     }
       
   130 
       
   131     return TestResult::Fail;
       
   132 }
       
   133 
       
   134 void TestBaseLine::toXML(XMLWriter &receiver) const
       
   135 {
       
   136     switch(m_type)
       
   137     {
       
   138         case XML: /* Fallthrough. */
       
   139         case Fragment: /* Fallthrough. */
       
   140         case SchemaIsValid: /* Fallthrough. */
       
   141         case Text:
       
   142         {
       
   143             QXmlAttributes inspectAtts;
       
   144             inspectAtts.append(QLatin1String("role"), QString(),
       
   145                                QLatin1String("role"), QLatin1String("principal"));
       
   146             inspectAtts.append(QLatin1String("compare"), QString(),
       
   147                                QLatin1String("compare"), displayName(m_type));
       
   148             receiver.startElement(QLatin1String("output-file"), inspectAtts);
       
   149             receiver.characters(m_details);
       
   150             receiver.endElement(QLatin1String("output-file"));
       
   151             return;
       
   152         }
       
   153         case Ignore:
       
   154         {
       
   155             Q_ASSERT_X(false, Q_FUNC_INFO, "Serializing 'Ignore' is not implemented.");
       
   156             return;
       
   157         }
       
   158         case Inspect:
       
   159         {
       
   160             QXmlAttributes inspectAtts;
       
   161             inspectAtts.append(QLatin1String("role"), QString(),
       
   162                                QLatin1String("role"), QLatin1String("principal"));
       
   163             inspectAtts.append(QLatin1String("compare"), QString(),
       
   164                                QLatin1String("compare"), QLatin1String("Inspect"));
       
   165             receiver.startElement(QLatin1String("output-file"), inspectAtts);
       
   166             receiver.characters(m_details);
       
   167             receiver.endElement(QLatin1String("output-file"));
       
   168             return;
       
   169         }
       
   170         case ExpectedError:
       
   171         {
       
   172             receiver.startElement(QLatin1String("expected-error"));
       
   173             receiver.characters(m_details);
       
   174             receiver.endElement(QLatin1String("expected-error"));
       
   175             return;
       
   176         }
       
   177     }
       
   178 }
       
   179 
       
   180 bool TestBaseLine::isChildrenDeepEqual(const QDomNodeList &cl1, const QDomNodeList &cl2)
       
   181 {
       
   182     const unsigned int len = cl1.length();
       
   183 
       
   184     if(len == cl2.length())
       
   185     {
       
   186         for(unsigned int i = 0; i < len; ++i)
       
   187         {
       
   188             if(!isDeepEqual(cl1.at(i), cl2.at(i)))
       
   189                 return false;
       
   190         }
       
   191 
       
   192         return true;
       
   193     }
       
   194     else
       
   195         return false;
       
   196 }
       
   197 
       
   198 bool TestBaseLine::isAttributesEqual(const QDomNamedNodeMap &cl1, const QDomNamedNodeMap &cl2)
       
   199 {
       
   200     const unsigned int len = cl1.length();
       
   201     pDebug() << "LEN:" << len;
       
   202 
       
   203     if(len == cl2.length())
       
   204     {
       
   205         for(unsigned int i1 = 0; i1 < len; ++i1)
       
   206         {
       
   207             const QDomNode attr1(cl1.item(i1));
       
   208             Q_ASSERT(!attr1.isNull());
       
   209 
       
   210             /* This is set if attr1 cannot be found at all in cl2. */
       
   211             bool earlyExit = false;
       
   212 
       
   213             for(unsigned int i2 = 0; i2 < len; ++i2)
       
   214             {
       
   215                 const QDomNode attr2(cl2.item(i2));
       
   216                 Q_ASSERT(!attr2.isNull());
       
   217                 pDebug() << "ATTR1:" << attr1.localName() << attr1.namespaceURI() << attr1.prefix() << attr1.nodeName();
       
   218                 pDebug() << "ATTR2:" << attr2.localName() << attr2.namespaceURI() << attr2.prefix() << attr2.nodeName();
       
   219 
       
   220                 if(attr1.localName() == attr2.localName()       &&
       
   221                    attr1.namespaceURI() == attr2.namespaceURI() &&
       
   222                    attr1.prefix() == attr2.prefix()             &&
       
   223                    attr1.nodeName() == attr2.nodeName()         && /* Yes, needed in addition to all the other. */
       
   224                    attr1.nodeValue() == attr2.nodeValue())
       
   225                 {
       
   226                     earlyExit = true;
       
   227                     break;
       
   228                 }
       
   229             }
       
   230 
       
   231             if(!earlyExit)
       
   232             {
       
   233                 /* An attribute was found that doesn't exist in the other list so exit. */
       
   234                 return false;
       
   235             }
       
   236         }
       
   237 
       
   238         return true;
       
   239     }
       
   240     else
       
   241         return false;
       
   242 }
       
   243 
       
   244 bool TestBaseLine::isDeepEqual(const QDomNode &n1, const QDomNode &n2)
       
   245 {
       
   246     if(n1.nodeType() != n2.nodeType())
       
   247         return false;
       
   248 
       
   249     switch(n1.nodeType())
       
   250     {
       
   251         case QDomNode::CommentNode:
       
   252         /* Fallthrough. */
       
   253         case QDomNode::TextNode:
       
   254         {
       
   255             return static_cast<const QDomCharacterData &>(n1).data() ==
       
   256                    static_cast<const QDomCharacterData &>(n2).data();
       
   257         }
       
   258         case QDomNode::ProcessingInstructionNode:
       
   259         {
       
   260             return n1.nodeName() == n2.nodeName() &&
       
   261                    n1.nodeValue() == n2.nodeValue();
       
   262         }
       
   263         case QDomNode::DocumentNode:
       
   264             return isChildrenDeepEqual(n1.childNodes(), n2.childNodes());
       
   265         case QDomNode::ElementNode:
       
   266         {
       
   267             return n1.localName() == n2.localName()                     &&
       
   268                    n1.namespaceURI() == n2.namespaceURI()               &&
       
   269                    n1.nodeName() == n2.nodeName()                       && /* Yes, this one is needed in addition to localName(). */
       
   270                    isAttributesEqual(n1.attributes(), n2.attributes())  &&
       
   271                    isChildrenDeepEqual(n1.childNodes(), n2.childNodes());
       
   272         }
       
   273         /* Fallthrough all these. */
       
   274         case QDomNode::EntityReferenceNode:
       
   275         case QDomNode::CDATASectionNode:
       
   276         case QDomNode::EntityNode:
       
   277         case QDomNode::DocumentTypeNode:
       
   278         case QDomNode::DocumentFragmentNode:
       
   279         case QDomNode::NotationNode:
       
   280         case QDomNode::BaseNode:
       
   281         case QDomNode::CharacterDataNode:
       
   282         {
       
   283             Q_ASSERT_X(false, Q_FUNC_INFO,
       
   284                        "An unsupported node type was encountered.");
       
   285             return false;
       
   286         }
       
   287         case QDomNode::AttributeNode:
       
   288         {
       
   289             Q_ASSERT_X(false, Q_FUNC_INFO,
       
   290                        "This should never happen. QDom doesn't allow us to compare DOM attributes "
       
   291                        "properly.");
       
   292             return false;
       
   293         }
       
   294         default:
       
   295         {
       
   296             Q_ASSERT_X(false, Q_FUNC_INFO, "Unhandled QDom::NodeType value.");
       
   297             return false;
       
   298         }
       
   299     }
       
   300 }
       
   301 
       
   302 TestResult::Status TestBaseLine::verify(const QString &serializedInput) const
       
   303 {
       
   304     switch(m_type)
       
   305     {
       
   306         case SchemaIsValid:
       
   307         /* Fall through. */
       
   308         case Text:
       
   309         {
       
   310             if(serializedInput == details())
       
   311                 return TestResult::Pass;
       
   312             else
       
   313                 return TestResult::Fail;
       
   314         }
       
   315         case Fragment:
       
   316         /* Fall through. */
       
   317         case XML:
       
   318         {
       
   319             /* Read the baseline and the serialized input into two QDomDocuments, and compare
       
   320              * them deeply. We wrap fragments in a root node such that it is well-formed XML.
       
   321              */
       
   322 
       
   323             QDomDocument output;
       
   324             {
       
   325                 /* The reason we put things into a QByteArray and then parse it through QXmlSimpleReader, is that
       
   326                  * QDomDocument does whitespace stripping when calling setContent(QString). In other words,
       
   327                  * this workarounds a bug. */
       
   328 
       
   329                 QXmlInputSource source;
       
   330                 source.setData((m_type == XML ? serializedInput : QLatin1String("<r>") +
       
   331                                                                   serializedInput +
       
   332                                                                   QLatin1String("</r>")).toUtf8());
       
   333 
       
   334                 QString outputReadingError;
       
   335 
       
   336                 QXmlSimpleReader reader;
       
   337                 reader.setFeature(QLatin1String("http://xml.org/sax/features/namespace-prefixes"), true);
       
   338 
       
   339                 const bool success = output.setContent(&source,
       
   340                                                        &reader,
       
   341                                                        &outputReadingError);
       
   342 
       
   343                 if(!success)
       
   344                     return TestResult::Fail;
       
   345 
       
   346                 Q_ASSERT(success);
       
   347             }
       
   348 
       
   349             QDomDocument baseline;
       
   350             {
       
   351                 QXmlInputSource source;
       
   352                 source.setData((m_type == XML ? details() : QLatin1String("<r>") +
       
   353                                                             details() +
       
   354                                                             QLatin1String("</r>")).toUtf8());
       
   355                 QString baselineReadingError;
       
   356 
       
   357                 QXmlSimpleReader reader;
       
   358                 reader.setFeature(QLatin1String("http://xml.org/sax/features/namespace-prefixes"), true);
       
   359 
       
   360                 const bool success = baseline.setContent(&source,
       
   361                                                          &reader,
       
   362                                                          &baselineReadingError);
       
   363 
       
   364                 if(!success)
       
   365                     return TestResult::Fail;
       
   366 
       
   367                 /* This piece of code workaround a bug in QDom, which treats XML prologs as processing
       
   368                  * instructions and make them available in the tree as so. */
       
   369                 if(m_type == XML)
       
   370                 {
       
   371                     /* $doc/r/node() */
       
   372                     const QDomNodeList children(baseline.childNodes());
       
   373                     const int len = children.length();
       
   374 
       
   375                     for(int i = 0; i < len; ++i)
       
   376                     {
       
   377                         const QDomNode &child = children.at(i);
       
   378                         if(child.isProcessingInstruction() && child.nodeName() == QLatin1String("xml"))
       
   379                         {
       
   380                             baseline.removeChild(child);
       
   381                             break;
       
   382                         }
       
   383                     }
       
   384                 }
       
   385 
       
   386                 Q_ASSERT_X(baselineReadingError.isNull(), Q_FUNC_INFO,
       
   387                            qPrintable((QLatin1String("Reading the baseline failed: ") + baselineReadingError)));
       
   388             }
       
   389 
       
   390             if(isDeepEqual(output, baseline))
       
   391                 return TestResult::Pass;
       
   392             else
       
   393             {
       
   394                 pDebug() << "FAILURE:" << output.toString() << "is NOT IDENTICAL to(baseline):" << baseline.toString();
       
   395                 return TestResult::Fail;
       
   396             }
       
   397         }
       
   398         case Ignore:
       
   399             return TestResult::Pass;
       
   400         case Inspect:
       
   401             return TestResult::NotTested;
       
   402         case ExpectedError:
       
   403         {
       
   404             /* This function is only called for Text/XML/Fragment tests. */
       
   405             return TestResult::Fail;
       
   406         }
       
   407     }
       
   408     Q_ASSERT(false);
       
   409     return TestResult::Fail;
       
   410 }
       
   411 
       
   412 TestBaseLine::Type TestBaseLine::identifierFromString(const QString &string)
       
   413 {
       
   414     /* "html-output: Using an ad hoc tool, it must assert that the document obeys the HTML
       
   415      * Output Method as defined in the Serialization specification and section
       
   416      * 20 of the XSLT 2.0 specification." We treat it as XML for now, same with
       
   417      * xhtml-output. */
       
   418     if(string.compare(QLatin1String("XML"), Qt::CaseInsensitive) == 0 ||
       
   419        string == QLatin1String("html-output") ||
       
   420        string == QLatin1String("xml-output") ||
       
   421        string == QLatin1String("xhtml-output"))
       
   422         return XML;
       
   423     else if(string == QLatin1String("Fragment") || string == QLatin1String("xml-frag"))
       
   424         return Fragment;
       
   425     else if(string.compare(QLatin1String("Text"), Qt::CaseInsensitive) == 0)
       
   426         return Text;
       
   427     else if(string == QLatin1String("Ignore"))
       
   428         return Ignore;
       
   429     else if(string.compare(QLatin1String("Inspect"), Qt::CaseInsensitive) == 0)
       
   430         return Inspect;
       
   431     else
       
   432     {
       
   433         Q_ASSERT_X(false, Q_FUNC_INFO,
       
   434                    qPrintable(QString::fromLatin1("Invalid string representation for a comparation type: %1").arg(string)));
       
   435 
       
   436         return Ignore; /* Silence GCC. */
       
   437     }
       
   438 }
       
   439 
       
   440 QString TestBaseLine::displayName(const Type id)
       
   441 {
       
   442     switch(id)
       
   443     {
       
   444         case XML:
       
   445             return QLatin1String("XML");
       
   446         case Fragment:
       
   447             return QLatin1String("Fragment");
       
   448         case Text:
       
   449             return QLatin1String("Text");
       
   450         case Ignore:
       
   451             return QLatin1String("Ignore");
       
   452         case Inspect:
       
   453             return QLatin1String("Inspect");
       
   454         case ExpectedError:
       
   455             return QLatin1String("ExpectedError");
       
   456         case SchemaIsValid:
       
   457             return QLatin1String("SchemaIsValid");
       
   458     }
       
   459 
       
   460     Q_ASSERT(false);
       
   461     return QString();
       
   462 }
       
   463 
       
   464 QString TestBaseLine::details() const
       
   465 {
       
   466     if(m_type == Ignore) /* We're an error code. */
       
   467         return QString();
       
   468     if(m_type == ExpectedError) /* We're an error code. */
       
   469         return m_details;
       
   470     if(m_type == SchemaIsValid) /* We're a schema validation information . */
       
   471         return m_details;
       
   472 
       
   473     if(m_details.isEmpty())
       
   474         return m_details;
       
   475 
       
   476     /* m_details is a file name, we open it and return the result. */
       
   477     QFile file(QUrl(m_details).toLocalFile());
       
   478 
       
   479     QString retval;
       
   480     if(!file.exists())
       
   481         retval = QString::fromLatin1("%1 does not exist.").arg(file.fileName());
       
   482     else if(!QFileInfo(file.fileName()).isFile())
       
   483         retval = QString::fromLatin1("%1 is not a file, cannot display it.").arg(file.fileName());
       
   484     else if(!file.open(QIODevice::ReadOnly | QIODevice::Text))
       
   485         retval = QString::fromLatin1("Could not open %1. Likely a permission error.").arg(file.fileName());
       
   486 
       
   487     if(retval.isNull())
       
   488     {
       
   489         /* Scary, we assume the query/baseline is in UTF-8. */
       
   490         return QString::fromUtf8(file.readAll());
       
   491     }
       
   492     else
       
   493     {
       
   494         /* We had a file error. */
       
   495         retval.prepend(QLatin1String("Test-suite harness error: "));
       
   496         qCritical() << retval;
       
   497         return retval;
       
   498     }
       
   499 }
       
   500 
       
   501 TestBaseLine::Type TestBaseLine::type() const
       
   502 {
       
   503     return m_type;
       
   504 }
       
   505 
       
   506 void TestBaseLine::setDetails(const QString &detailsP)
       
   507 {
       
   508     m_details = detailsP;
       
   509 }
       
   510 
       
   511 // vim: et:ts=4:sw=4:sts=4