Files correlati : cg0.exe cg0700a.msk cg0700b.msk cg3.exe cg4.exe Bug : Commento: Merge 1.0 libraries
512 lines
18 KiB
C++
512 lines
18 KiB
C++
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2015 The Qt Company Ltd.
|
|
** Contact: http://www.qt.io/licensing/
|
|
**
|
|
** This file is part of the test suite of the Qt Toolkit.
|
|
**
|
|
** $QT_BEGIN_LICENSE:LGPL$
|
|
** Commercial License Usage
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
** accordance with the commercial license agreement provided with the
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
** and conditions see http://www.qt.io/terms-conditions. For further
|
|
** information use the contact form at http://www.qt.io/contact-us.
|
|
**
|
|
** GNU Lesser General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
|
** General Public License version 2.1 or version 3 as published by the Free
|
|
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
|
|
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
|
|
** following information to ensure the GNU Lesser General Public License
|
|
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
|
|
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
|
**
|
|
** As a special exception, The Qt Company gives you certain additional
|
|
** rights. These rights are described in The Qt Company LGPL Exception
|
|
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
|
**
|
|
** GNU General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
** General Public License version 3.0 as published by the Free Software
|
|
** Foundation and appearing in the file LICENSE.GPL included in the
|
|
** packaging of this file. Please review the following information to
|
|
** ensure the GNU General Public License version 3.0 requirements will be
|
|
** met: http://www.gnu.org/copyleft/gpl.html.
|
|
**
|
|
** $QT_END_LICENSE$
|
|
**
|
|
****************************************************************************/
|
|
|
|
#include <QDomDocument>
|
|
#include <QFile>
|
|
#include <QFileInfo>
|
|
#include <QRegExp>
|
|
#include <QtDebug>
|
|
#include <QUrl>
|
|
#include <QXmlAttributes>
|
|
#include <QXmlSimpleReader>
|
|
|
|
#include "qdebug_p.h"
|
|
#include "XMLWriter.h"
|
|
|
|
#include "TestBaseLine.h"
|
|
|
|
using namespace QPatternistSDK;
|
|
using namespace QPatternist;
|
|
|
|
Q_GLOBAL_STATIC_WITH_ARGS(QRegExp, errorRegExp, (QLatin1String("[A-Z]{4}[0-9]{4}")))
|
|
|
|
TestBaseLine::TestBaseLine(const Type t) : m_type(t)
|
|
{
|
|
Q_ASSERT(errorRegExp()->isValid());
|
|
}
|
|
|
|
TestResult::Status TestBaseLine::scan(const QString &serialized,
|
|
const TestBaseLine::List &lines)
|
|
{
|
|
Q_ASSERT_X(lines.count() >= 1, Q_FUNC_INFO,
|
|
"At least one base line must be passed, otherwise there's nothing "
|
|
"to compare to.");
|
|
|
|
const TestBaseLine::List::const_iterator end(lines.constEnd());
|
|
TestBaseLine::List::const_iterator it(lines.constBegin());
|
|
for(; it != end; ++it)
|
|
{
|
|
const TestResult::Status retval((*it)->verify(serialized));
|
|
|
|
if(retval == TestResult::Pass || retval == TestResult::NotTested)
|
|
return retval;
|
|
}
|
|
|
|
return TestResult::Fail;
|
|
}
|
|
|
|
TestResult::Status TestBaseLine::scanErrors(const ErrorHandler::Message::List &errors,
|
|
const TestBaseLine::List &lines)
|
|
{
|
|
pDebug() << "TestBaseLine::scanErrors()";
|
|
|
|
/* 1. Find the first error in @p errors that's a Patternist
|
|
* error(not warning and not from Qt) and extract the error code. */
|
|
QString errorCode;
|
|
|
|
const ErrorHandler::Message::List::const_iterator end(errors.constEnd());
|
|
ErrorHandler::Message::List::const_iterator it(errors.constBegin());
|
|
for(; it != end; ++it)
|
|
{
|
|
if((*it).type() != QtFatalMsg)
|
|
continue;
|
|
|
|
errorCode = QUrl((*it).identifier()).fragment();
|
|
|
|
pDebug() << "ERR:" << (*it).description();
|
|
/* This is hackish. We have no way of determining whether a Message
|
|
* is actually issued from Patternist, so we try to narrow it down like this. */
|
|
if(errorRegExp()->exactMatch(errorCode))
|
|
break; /* It's an error code. */
|
|
else
|
|
errorCode.clear();
|
|
}
|
|
|
|
pDebug() << "Got error code: " << errorCode;
|
|
/* 2. Loop through @p lines, and for the first base line
|
|
* which is of type ExpectedError and which matches @p errorCode
|
|
* return Pass, otherwise Fail. */
|
|
const TestBaseLine::List::const_iterator blend(lines.constEnd());
|
|
TestBaseLine::List::const_iterator blit(lines.constBegin());
|
|
for(; blit != blend; ++blit)
|
|
{
|
|
const Type t = (*blit)->type();
|
|
|
|
if(t == TestBaseLine::ExpectedError)
|
|
{
|
|
const QString d((*blit)->details());
|
|
if(d == errorCode || d == QChar::fromLatin1('*'))
|
|
return TestResult::Pass;
|
|
}
|
|
}
|
|
|
|
return TestResult::Fail;
|
|
}
|
|
|
|
void TestBaseLine::toXML(XMLWriter &receiver) const
|
|
{
|
|
switch(m_type)
|
|
{
|
|
case XML: /* Fallthrough. */
|
|
case Fragment: /* Fallthrough. */
|
|
case SchemaIsValid: /* Fallthrough. */
|
|
case Text:
|
|
{
|
|
QXmlAttributes inspectAtts;
|
|
inspectAtts.append(QLatin1String("role"), QString(),
|
|
QLatin1String("role"), QLatin1String("principal"));
|
|
inspectAtts.append(QLatin1String("compare"), QString(),
|
|
QLatin1String("compare"), displayName(m_type));
|
|
receiver.startElement(QLatin1String("output-file"), inspectAtts);
|
|
receiver.characters(m_details);
|
|
receiver.endElement(QLatin1String("output-file"));
|
|
return;
|
|
}
|
|
case Ignore:
|
|
{
|
|
Q_ASSERT_X(false, Q_FUNC_INFO, "Serializing 'Ignore' is not implemented.");
|
|
return;
|
|
}
|
|
case Inspect:
|
|
{
|
|
QXmlAttributes inspectAtts;
|
|
inspectAtts.append(QLatin1String("role"), QString(),
|
|
QLatin1String("role"), QLatin1String("principal"));
|
|
inspectAtts.append(QLatin1String("compare"), QString(),
|
|
QLatin1String("compare"), QLatin1String("Inspect"));
|
|
receiver.startElement(QLatin1String("output-file"), inspectAtts);
|
|
receiver.characters(m_details);
|
|
receiver.endElement(QLatin1String("output-file"));
|
|
return;
|
|
}
|
|
case ExpectedError:
|
|
{
|
|
receiver.startElement(QLatin1String("expected-error"));
|
|
receiver.characters(m_details);
|
|
receiver.endElement(QLatin1String("expected-error"));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool TestBaseLine::isChildrenDeepEqual(const QDomNodeList &cl1, const QDomNodeList &cl2)
|
|
{
|
|
const unsigned int len = cl1.length();
|
|
|
|
if(len == cl2.length())
|
|
{
|
|
for(unsigned int i = 0; i < len; ++i)
|
|
{
|
|
if(!isDeepEqual(cl1.at(i), cl2.at(i)))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
bool TestBaseLine::isAttributesEqual(const QDomNamedNodeMap &cl1, const QDomNamedNodeMap &cl2)
|
|
{
|
|
const unsigned int len = cl1.length();
|
|
pDebug() << "LEN:" << len;
|
|
|
|
if(len == cl2.length())
|
|
{
|
|
for(unsigned int i1 = 0; i1 < len; ++i1)
|
|
{
|
|
const QDomNode attr1(cl1.item(i1));
|
|
Q_ASSERT(!attr1.isNull());
|
|
|
|
/* This is set if attr1 cannot be found at all in cl2. */
|
|
bool earlyExit = false;
|
|
|
|
for(unsigned int i2 = 0; i2 < len; ++i2)
|
|
{
|
|
const QDomNode attr2(cl2.item(i2));
|
|
Q_ASSERT(!attr2.isNull());
|
|
pDebug() << "ATTR1:" << attr1.localName() << attr1.namespaceURI() << attr1.prefix() << attr1.nodeName();
|
|
pDebug() << "ATTR2:" << attr2.localName() << attr2.namespaceURI() << attr2.prefix() << attr2.nodeName();
|
|
|
|
if(attr1.localName() == attr2.localName() &&
|
|
attr1.namespaceURI() == attr2.namespaceURI() &&
|
|
attr1.prefix() == attr2.prefix() &&
|
|
attr1.nodeName() == attr2.nodeName() && /* Yes, needed in addition to all the other. */
|
|
attr1.nodeValue() == attr2.nodeValue())
|
|
{
|
|
earlyExit = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!earlyExit)
|
|
{
|
|
/* An attribute was found that doesn't exist in the other list so exit. */
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
bool TestBaseLine::isDeepEqual(const QDomNode &n1, const QDomNode &n2)
|
|
{
|
|
if(n1.nodeType() != n2.nodeType())
|
|
return false;
|
|
|
|
switch(n1.nodeType())
|
|
{
|
|
case QDomNode::CommentNode:
|
|
/* Fallthrough. */
|
|
case QDomNode::TextNode:
|
|
{
|
|
return static_cast<const QDomCharacterData &>(n1).data() ==
|
|
static_cast<const QDomCharacterData &>(n2).data();
|
|
}
|
|
case QDomNode::ProcessingInstructionNode:
|
|
{
|
|
return n1.nodeName() == n2.nodeName() &&
|
|
n1.nodeValue() == n2.nodeValue();
|
|
}
|
|
case QDomNode::DocumentNode:
|
|
return isChildrenDeepEqual(n1.childNodes(), n2.childNodes());
|
|
case QDomNode::ElementNode:
|
|
{
|
|
return n1.localName() == n2.localName() &&
|
|
n1.namespaceURI() == n2.namespaceURI() &&
|
|
n1.nodeName() == n2.nodeName() && /* Yes, this one is needed in addition to localName(). */
|
|
isAttributesEqual(n1.attributes(), n2.attributes()) &&
|
|
isChildrenDeepEqual(n1.childNodes(), n2.childNodes());
|
|
}
|
|
/* Fallthrough all these. */
|
|
case QDomNode::EntityReferenceNode:
|
|
case QDomNode::CDATASectionNode:
|
|
case QDomNode::EntityNode:
|
|
case QDomNode::DocumentTypeNode:
|
|
case QDomNode::DocumentFragmentNode:
|
|
case QDomNode::NotationNode:
|
|
case QDomNode::BaseNode:
|
|
case QDomNode::CharacterDataNode:
|
|
{
|
|
Q_ASSERT_X(false, Q_FUNC_INFO,
|
|
"An unsupported node type was encountered.");
|
|
return false;
|
|
}
|
|
case QDomNode::AttributeNode:
|
|
{
|
|
Q_ASSERT_X(false, Q_FUNC_INFO,
|
|
"This should never happen. QDom doesn't allow us to compare DOM attributes "
|
|
"properly.");
|
|
return false;
|
|
}
|
|
default:
|
|
{
|
|
Q_ASSERT_X(false, Q_FUNC_INFO, "Unhandled QDom::NodeType value.");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
TestResult::Status TestBaseLine::verify(const QString &serializedInput) const
|
|
{
|
|
switch(m_type)
|
|
{
|
|
case SchemaIsValid:
|
|
/* Fall through. */
|
|
case Text:
|
|
{
|
|
if(serializedInput == details())
|
|
return TestResult::Pass;
|
|
else
|
|
return TestResult::Fail;
|
|
}
|
|
case Fragment:
|
|
/* Fall through. */
|
|
case XML:
|
|
{
|
|
/* Read the baseline and the serialized input into two QDomDocuments, and compare
|
|
* them deeply. We wrap fragments in a root node such that it is well-formed XML.
|
|
*/
|
|
|
|
QDomDocument output;
|
|
{
|
|
/* The reason we put things into a QByteArray and then parse it through QXmlSimpleReader, is that
|
|
* QDomDocument does whitespace stripping when calling setContent(QString). In other words,
|
|
* this workarounds a bug. */
|
|
|
|
QXmlInputSource source;
|
|
source.setData((m_type == XML ? serializedInput : QLatin1String("<r>") +
|
|
serializedInput +
|
|
QLatin1String("</r>")).toUtf8());
|
|
|
|
QString outputReadingError;
|
|
|
|
QXmlSimpleReader reader;
|
|
reader.setFeature(QLatin1String("http://xml.org/sax/features/namespace-prefixes"), true);
|
|
|
|
const bool success = output.setContent(&source,
|
|
&reader,
|
|
&outputReadingError);
|
|
|
|
if(!success)
|
|
return TestResult::Fail;
|
|
|
|
Q_ASSERT(success);
|
|
}
|
|
|
|
QDomDocument baseline;
|
|
{
|
|
QXmlInputSource source;
|
|
source.setData((m_type == XML ? details() : QLatin1String("<r>") +
|
|
details() +
|
|
QLatin1String("</r>")).toUtf8());
|
|
QString baselineReadingError;
|
|
|
|
QXmlSimpleReader reader;
|
|
reader.setFeature(QLatin1String("http://xml.org/sax/features/namespace-prefixes"), true);
|
|
|
|
const bool success = baseline.setContent(&source,
|
|
&reader,
|
|
&baselineReadingError);
|
|
|
|
if(!success)
|
|
return TestResult::Fail;
|
|
|
|
/* This piece of code workaround a bug in QDom, which treats XML prologs as processing
|
|
* instructions and make them available in the tree as so. */
|
|
if(m_type == XML)
|
|
{
|
|
/* $doc/r/node() */
|
|
const QDomNodeList children(baseline.childNodes());
|
|
const int len = children.length();
|
|
|
|
for(int i = 0; i < len; ++i)
|
|
{
|
|
const QDomNode &child = children.at(i);
|
|
if(child.isProcessingInstruction() && child.nodeName() == QLatin1String("xml"))
|
|
{
|
|
baseline.removeChild(child);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
Q_ASSERT_X(baselineReadingError.isNull(), Q_FUNC_INFO,
|
|
qPrintable((QLatin1String("Reading the baseline failed: ") + baselineReadingError)));
|
|
}
|
|
|
|
if(isDeepEqual(output, baseline))
|
|
return TestResult::Pass;
|
|
else
|
|
{
|
|
pDebug() << "FAILURE:" << output.toString() << "is NOT IDENTICAL to(baseline):" << baseline.toString();
|
|
return TestResult::Fail;
|
|
}
|
|
}
|
|
case Ignore:
|
|
return TestResult::Pass;
|
|
case Inspect:
|
|
return TestResult::NotTested;
|
|
case ExpectedError:
|
|
{
|
|
/* This function is only called for Text/XML/Fragment tests. */
|
|
return TestResult::Fail;
|
|
}
|
|
}
|
|
Q_ASSERT(false);
|
|
return TestResult::Fail;
|
|
}
|
|
|
|
TestBaseLine::Type TestBaseLine::identifierFromString(const QString &string)
|
|
{
|
|
/* "html-output: Using an ad hoc tool, it must assert that the document obeys the HTML
|
|
* Output Method as defined in the Serialization specification and section
|
|
* 20 of the XSLT 2.0 specification." We treat it as XML for now, same with
|
|
* xhtml-output. */
|
|
if(string.compare(QLatin1String("XML"), Qt::CaseInsensitive) == 0 ||
|
|
string == QLatin1String("html-output") ||
|
|
string == QLatin1String("xml-output") ||
|
|
string == QLatin1String("xhtml-output"))
|
|
return XML;
|
|
else if(string == QLatin1String("Fragment") || string == QLatin1String("xml-frag"))
|
|
return Fragment;
|
|
else if(string.compare(QLatin1String("Text"), Qt::CaseInsensitive) == 0)
|
|
return Text;
|
|
else if(string == QLatin1String("Ignore"))
|
|
return Ignore;
|
|
else if(string.compare(QLatin1String("Inspect"), Qt::CaseInsensitive) == 0)
|
|
return Inspect;
|
|
else
|
|
{
|
|
Q_ASSERT_X(false, Q_FUNC_INFO,
|
|
qPrintable(QString::fromLatin1("Invalid string representation for a comparation type: %1").arg(string)));
|
|
|
|
return Ignore; /* Silence GCC. */
|
|
}
|
|
}
|
|
|
|
QString TestBaseLine::displayName(const Type id)
|
|
{
|
|
switch(id)
|
|
{
|
|
case XML:
|
|
return QLatin1String("XML");
|
|
case Fragment:
|
|
return QLatin1String("Fragment");
|
|
case Text:
|
|
return QLatin1String("Text");
|
|
case Ignore:
|
|
return QLatin1String("Ignore");
|
|
case Inspect:
|
|
return QLatin1String("Inspect");
|
|
case ExpectedError:
|
|
return QLatin1String("ExpectedError");
|
|
case SchemaIsValid:
|
|
return QLatin1String("SchemaIsValid");
|
|
}
|
|
|
|
Q_ASSERT(false);
|
|
return QString();
|
|
}
|
|
|
|
QString TestBaseLine::details() const
|
|
{
|
|
if(m_type == Ignore) /* We're an error code. */
|
|
return QString();
|
|
if(m_type == ExpectedError) /* We're an error code. */
|
|
return m_details;
|
|
if(m_type == SchemaIsValid) /* We're a schema validation information . */
|
|
return m_details;
|
|
|
|
if(m_details.isEmpty())
|
|
return m_details;
|
|
|
|
/* m_details is a file name, we open it and return the result. */
|
|
QFile file(QUrl(m_details).toLocalFile());
|
|
|
|
QString retval;
|
|
if(!file.exists())
|
|
retval = QString::fromLatin1("%1 does not exist.").arg(file.fileName());
|
|
else if(!QFileInfo(file.fileName()).isFile())
|
|
retval = QString::fromLatin1("%1 is not a file, cannot display it.").arg(file.fileName());
|
|
else if(!file.open(QIODevice::ReadOnly | QIODevice::Text))
|
|
retval = QString::fromLatin1("Could not open %1. Likely a permission error.").arg(file.fileName());
|
|
|
|
if(retval.isNull())
|
|
{
|
|
/* Scary, we assume the query/baseline is in UTF-8. */
|
|
return QString::fromUtf8(file.readAll());
|
|
}
|
|
else
|
|
{
|
|
/* We had a file error. */
|
|
retval.prepend(QLatin1String("Test-suite harness error: "));
|
|
qCritical() << retval;
|
|
return retval;
|
|
}
|
|
}
|
|
|
|
TestBaseLine::Type TestBaseLine::type() const
|
|
{
|
|
return m_type;
|
|
}
|
|
|
|
void TestBaseLine::setDetails(const QString &detailsP)
|
|
{
|
|
m_details = detailsP;
|
|
}
|
|
|
|
// vim: et:ts=4:sw=4:sts=4
|