File indexing completed on 2024-04-28 15:25:13

0001 /**
0002  * This file is part of the KDE project
0003  *
0004  * Copyright (C) 2001,2003 Peter Kelly (pmk@post.com)
0005  * Copyright (C) 2003,2004 Stephan Kulow (coolo@kde.org)
0006  * Copyright (C) 2004 Dirk Mueller ( mueller@kde.org )
0007  *
0008  * This library is free software; you can redistribute it and/or
0009  * modify it under the terms of the GNU Library General Public
0010  * License as published by the Free Software Foundation; either
0011  * version 2 of the License, or (at your option) any later version.
0012  *
0013  * This library is distributed in the hope that it will be useful,
0014  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0015  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0016  * Library General Public License for more details.
0017  *
0018  * You should have received a copy of the GNU Library General Public License
0019  * along with this library; see the file COPYING.LIB.  If not, write to
0020  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0021  * Boston, MA 02110-1301, USA.
0022  *
0023  */
0024 
0025 #include "test_regression.h"
0026 
0027 #include <stdlib.h>
0028 #include <sys/time.h>
0029 #include <sys/resource.h>
0030 #include <sys/types.h>
0031 #include <pwd.h>
0032 #include <signal.h>
0033 #include <unistd.h>
0034 
0035 #include <QApplication>
0036 #include <kacceleratormanager.h>
0037 #include <kconfiggroup.h>
0038 
0039 #include <QImage>
0040 #include <QFile>
0041 #include <QEventLoop>
0042 #include <stdio.h>
0043 
0044 #include "css/cssstyleselector.h"
0045 #include <dom_string.h>
0046 #include "rendering/render_style.h"
0047 #include "rendering/render_layer.h"
0048 #include "khtmldefaults.h"
0049 #include <QProcess>
0050 #include <QCommonStyle>
0051 #include <QStyleOption>
0052 
0053 #include <dom/dom_node.h>
0054 #include <dom/dom_element.h>
0055 #include <dom/dom_text.h>
0056 #include <dom/dom_xml.h>
0057 
0058 //We don't use the default fonts, though, but traditional testregression ones
0059 #undef HTML_DEFAULT_VIEW_FONT
0060 #undef HTML_DEFAULT_VIEW_FIXED_FONT
0061 #undef HTML_DEFAULT_VIEW_SERIF_FONT
0062 #undef HTML_DEFAULT_VIEW_SANSSERIF_FONT
0063 #undef HTML_DEFAULT_VIEW_CURSIVE_FONT
0064 #undef HTML_DEFAULT_VIEW_FANTASY_FONT
0065 #define HTML_DEFAULT_VIEW_FONT "helvetica"
0066 #define HTML_DEFAULT_VIEW_FIXED_FONT "courier"
0067 #define HTML_DEFAULT_VIEW_SERIF_FONT "times"
0068 #define HTML_DEFAULT_VIEW_SANSSERIF_FONT "helvetica"
0069 #define HTML_DEFAULT_VIEW_CURSIVE_FONT "helvetica"
0070 #define HTML_DEFAULT_VIEW_FANTASY_FONT "helvetica"
0071 
0072 #ifdef __GNUC__
0073 #warning "Kill this at some point"
0074 #endif
0075 
0076 struct PalInfo {
0077     QPalette::ColorRole role;
0078     quint32            color;
0079 };
0080 
0081 PalInfo palInfo[] = {
0082     {QPalette::WindowText, 0xff000000},
0083     {QPalette::Button, 0xffc0c0c0},
0084     {QPalette::Light, 0xffffffff},
0085     {QPalette::Midlight, 0xffdfdfdf},
0086     {QPalette::Dark, 0xff808080},
0087     {QPalette::Mid, 0xffa0a0a4},
0088     {QPalette::Text, 0xff000000},
0089     {QPalette::BrightText, 0xffffffff},
0090     {QPalette::ButtonText, 0xff000000},
0091     {QPalette::Base, 0xffffffff},
0092     {QPalette::Window, 0xffc0c0c0},
0093     {QPalette::Shadow, 0xff000000},
0094     {QPalette::Highlight, 0xff000080},
0095     {QPalette::HighlightedText, 0xffffffff},
0096     {QPalette::Link, 0xff0000ff},
0097     {QPalette::LinkVisited, 0xffff00ff},
0098     {QPalette::LinkVisited, 0}
0099 };
0100 
0101 PalInfo disPalInfo[] = {
0102     {QPalette::WindowText, 0xff808080},
0103     {QPalette::Button, 0xffc0c0c0},
0104     {QPalette::Light, 0xffffffff},
0105     {QPalette::Midlight, 0xffdfdfdf},
0106     {QPalette::Dark, 0xff808080},
0107     {QPalette::Mid, 0xffa0a0a4},
0108     {QPalette::Text, 0xff808080},
0109     {QPalette::BrightText, 0xffffffff},
0110     {QPalette::ButtonText, 0xff808080},
0111     {QPalette::Base, 0xffc0c0c0},
0112     {QPalette::Window, 0xffc0c0c0},
0113     {QPalette::Shadow, 0xff000000},
0114     {QPalette::Highlight, 0xff000080},
0115     {QPalette::HighlightedText, 0xffffffff},
0116     {QPalette::Link, 0xff0000ff},
0117     {QPalette::LinkVisited, 0xffff00ff},
0118     {QPalette::LinkVisited, 0}
0119 };
0120 
0121 class TestStyle: public QCommonStyle // was QWindowsStyle in Qt4. TODO: Check if this draws everything we need in Qt5...
0122 {
0123 public:
0124     TestStyle()
0125     {}
0126 
0127     virtual void drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const
0128     {
0129         switch (element) {
0130         case CE_ScrollBarSubLine:
0131         case CE_ScrollBarAddLine:
0132         case CE_ScrollBarSubPage:
0133         case CE_ScrollBarAddPage:
0134         case CE_ScrollBarFirst:
0135         case CE_ScrollBarLast:
0136         case CE_ScrollBarSlider: {
0137             const QStyleOptionSlider *sbOpt = qstyleoption_cast<const QStyleOptionSlider *>(option);
0138 
0139             if (sbOpt->minimum == sbOpt->maximum) {
0140                 const_cast<QStyleOptionSlider *>(sbOpt)->state ^= QStyle::State_Enabled;
0141                 if (element == CE_ScrollBarSlider) {
0142                     element = CE_ScrollBarAddPage;
0143                 }
0144             }
0145 
0146             if (element == CE_ScrollBarAddPage || element == CE_ScrollBarSubPage) {
0147                 //Fun. in Qt4, the brush offset seems to be sensitive to window position??
0148                 painter->setBrushOrigin(0, 1);
0149             }
0150             break;
0151         }
0152         default: //shaddup
0153             break;
0154         }
0155 
0156         QCommonStyle::drawControl(element, option, painter, widget);
0157     }
0158 
0159     virtual QRect subControlRect(ComplexControl control, const QStyleOptionComplex *option,
0160                                  SubControl subControl, const QWidget *widget) const
0161     {
0162         QRect rect = QCommonStyle::subControlRect(control, option, subControl, widget);
0163 
0164         switch (control) {
0165         case CC_ComboBox:
0166             if (subControl == SC_ComboBoxEditField) {
0167                 return rect.translated(3, 0);
0168             } else {
0169                 return rect;
0170             }
0171         default:
0172             return rect;
0173         }
0174     }
0175 
0176     virtual QSize sizeFromContents(ContentsType type, const QStyleOption *option, const QSize &contentsSize, const QWidget *widget) const
0177     {
0178         QSize size = QCommonStyle::sizeFromContents(type, option, contentsSize, widget);
0179 
0180         switch (type) {
0181         case CT_PushButton:
0182             return QSize(size.width(), size.height() - 1);
0183         case CT_LineEdit:
0184             if (widget && widget->parentWidget() && !qstricmp(widget->parentWidget()->metaObject()->className(), "KUrlRequester")) {
0185                 return QSize(size.width() + 1, size.height());
0186             }
0187             return QSize(size.width() + 2, size.height() + 2);
0188         case CT_ComboBox: {
0189             const QStyleOptionComboBox *cbOpt = qstyleoption_cast<const QStyleOptionComboBox *>(option);
0190             Q_UNUSED(cbOpt); // is 'cbOpt' needed at all here?
0191             return QSize(qMax(43, size.width() + 6), size.height());
0192         }
0193         default:
0194             return size;
0195         }
0196 
0197     }
0198 
0199     virtual int pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const
0200     {
0201         if (metric == PM_ButtonMargin) {
0202             return 7;
0203         }
0204         return QCommonStyle::pixelMetric(metric, option, widget);
0205     }
0206 
0207 };
0208 
0209 const char *imageMissingIcon =
0210     "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAzrAAAM6wHl1kTSAAAB2ElEQVQ4jZWTMWsiQRTHfxlDCln2iFhIClksRUIQC6t8BkurzTewEBGx2GKrICmsUsg218rBWV2xdQqLRVJYhUQkBCKLeN6iixB5d0WyYOKGcP9mmHm83/u/NzMHvOobcMz/6Tfw5/Btc+z7/oWIoJRCKQWwt0ZSSqHr+vddACLyZck44GFc8OnpiX6/z3Q6RSmFYRhUq1UMw9gDqF2AUorRaES73WYymVAsFsnlcnieR6PRwPO8dy3uAcIwxHEcADRNo1qt0mq16PV6ZDIZut0uYRh+Dri7u2O1WlGr1QCwLIsgCMhkMlQqFebzOePx+P1cdgG+7wNQKpWwbRsRodlsslgsOD8/R0R4fHyMdwBwcnKCiHBzc0M6neby8hIRoV6vMxwOERGy2eznDvL5PJqmMRgMmM1mpFIprq6uEBFs20bXdc7OzkgkEvvXGA2uVqth2zamaVIul9E0jeVyiVKKdrtNMplkvV7HAwDK5TLX19c4jsN4PEZEKBQKmKbJdrvl/v4e13UfgAXAwVueEYbhRdwzTiQSvLy8cHt7y+npKZ1O59myrB8RQH10EKcgCDg6OoqSf/L6keJbiNNms8F13QfLsn69Jf+NYlELGpD+grMAgo+H/wARELhn1VB8lwAAAABJRU5ErkJggg==";
0211 //r 727816 of oxygen's image-missing, base64'd PNG
0212 
0213 #include <kio/job.h>
0214 #include <kmainwindow.h>
0215 #include <kconfig.h>
0216 
0217 #include <QColor>
0218 #include <QCursor>
0219 #include <QDir>
0220 #include <QPushButton>
0221 #include <QString>
0222 #include <QTextStream>
0223 #include <QFileInfo>
0224 #include <QTimer>
0225 #include <QStatusBar>
0226 
0227 #include "dom/dom2_range.h"
0228 #include "dom/dom_exception.h"
0229 #include "dom/html_document.h"
0230 #include "html/htmltokenizer.h"
0231 #include "khtml_part.h"
0232 #include "khtmlpart_p.h"
0233 #include <kparts/browserextension.h>
0234 #include <qstandardpaths.h>
0235 #include <qcommandlineparser.h>
0236 #include <qcommandlineoption.h>
0237 
0238 #include "khtmlview.h"
0239 #include "rendering/render_replaced.h"
0240 #include "xml/dom_docimpl.h"
0241 #include "html/html_baseimpl.h"
0242 #include "dom/dom_doc.h"
0243 #include "misc/loader.h"
0244 #include "ecma/kjs_dom.h"
0245 #include "ecma/kjs_window.h"
0246 #include "ecma/kjs_proxy.h"
0247 
0248 using namespace khtml;
0249 using namespace DOM;
0250 using namespace KJS;
0251 
0252 static bool visual = false;
0253 static pid_t xvfb;
0254 
0255 // -------------------------------------------------------------------------
0256 
0257 PartMonitor *PartMonitor::sm_highestMonitor = NULL;
0258 
0259 PartMonitor::PartMonitor(KHTMLPart *_part)
0260 {
0261     m_part = _part;
0262     m_completed = false;
0263     connect(m_part, SIGNAL(completed()), this, SLOT(partCompleted()));
0264     m_timer_waits = 200;
0265     m_timeout_timer = new QTimer(this);
0266 }
0267 
0268 PartMonitor::~PartMonitor()
0269 {
0270     if (this == sm_highestMonitor) {
0271         sm_highestMonitor = 0;
0272     }
0273     while (!m_eventLoopStack.isEmpty()) {
0274         exitLoop();
0275     }
0276 }
0277 
0278 void PartMonitor::waitForCompletion()
0279 {
0280     if (!m_completed) {
0281 
0282         if (sm_highestMonitor) {
0283             return;
0284         }
0285 
0286         sm_highestMonitor = this;
0287 
0288         enterLoop();
0289 
0290         //connect(m_timeout_timer, SIGNAL(timeout()), this, SLOT(timeout()) );
0291         //m_timeout_timer->stop();
0292         //m_timeout_timer->start( visual ? 100 : 2, true );
0293     }
0294     QTimer::singleShot(0, this, SLOT(finishTimers()));
0295     enterLoop();
0296 }
0297 
0298 void PartMonitor::enterLoop()
0299 {
0300     if (m_eventLoopStack.isEmpty() || m_eventLoopStack.top()->isRunning()) {
0301         m_eventLoopStack.push(new QEventLoop());
0302     }
0303     m_eventLoopStack.top()->exec();
0304 }
0305 
0306 void PartMonitor::exitLoop()
0307 {
0308     while (!m_eventLoopStack.isEmpty() && !m_eventLoopStack.top()->isRunning()) {
0309         delete m_eventLoopStack.pop();
0310     }
0311     if (!m_eventLoopStack.isEmpty()) {
0312         m_eventLoopStack.top()->exit();
0313     }
0314 }
0315 
0316 void PartMonitor::timeout()
0317 {
0318     exitLoop();
0319 }
0320 
0321 void PartMonitor::finishTimers()
0322 {
0323 
0324     KJS::Window *w = KJS::Window::retrieveWindow(m_part);
0325     --m_timer_waits;
0326     if (m_timer_waits && ((w && w->winq->hasTimers()) || m_part->inProgress())) {
0327         // wait a bit
0328         QTimer::singleShot(10, this, SLOT(finishTimers()));
0329         return;
0330     }
0331     exitLoop();
0332 }
0333 
0334 void PartMonitor::partCompleted()
0335 {
0336     m_completed = true;
0337     m_timeout_timer->stop();
0338     connect(m_timeout_timer, SIGNAL(timeout()), this, SLOT(timeout()));
0339     m_timeout_timer->setSingleShot(true);
0340     m_timeout_timer->start(visual ? 100 : 2);
0341     disconnect(m_part, SIGNAL(completed()), this, SLOT(partCompleted()));
0342 }
0343 
0344 static void signal_handler(int)
0345 {
0346     printf("timeout - this should *NOT* happen, it is likely the part's completed() signal was not emitted - FIXME!!\n");
0347     if (PartMonitor::sm_highestMonitor) {
0348         PartMonitor::sm_highestMonitor->exitLoop();
0349     } else {
0350         abort();
0351     }
0352 }
0353 // -------------------------------------------------------------------------
0354 
0355 RegTestObject::RegTestObject(ExecState *exec, RegressionTest *_regTest)
0356 {
0357     m_regTest = _regTest;
0358     putDirect("print", new RegTestFunction(exec, m_regTest, RegTestFunction::Print, 1), DontEnum);
0359     putDirect("reportResult", new RegTestFunction(exec, m_regTest, RegTestFunction::ReportResult, 3), DontEnum);
0360     putDirect("checkOutput", new RegTestFunction(exec, m_regTest, RegTestFunction::CheckOutput, 1), DontEnum);
0361     // add "quit" for compatibility with the mozilla js shell
0362     putDirect("quit", new RegTestFunction(exec, m_regTest, RegTestFunction::Quit, 1), DontEnum);
0363 }
0364 
0365 RegTestFunction::RegTestFunction(ExecState * /*exec*/, RegressionTest *_regTest, int _id, int length)
0366 {
0367     m_regTest = _regTest;
0368     id = _id;
0369     putDirect("length", length);
0370 }
0371 
0372 bool RegTestFunction::implementsCall() const
0373 {
0374     return true;
0375 }
0376 
0377 JSValue *RegTestFunction::callAsFunction(ExecState *exec, JSObject * /*thisObj*/, const List &args)
0378 {
0379     JSValue *result = jsUndefined();
0380     if (m_regTest->ignore_errors) {
0381         return result;
0382     }
0383 
0384     switch (id) {
0385     case Print: {
0386         UString str = args[0]->toString(exec);
0387         if (str.qstring().toLower().indexOf("failed!") >= 0) {
0388             m_regTest->saw_failure = true;
0389         }
0390         QString res = str.qstring().replace('\007', "");
0391         m_regTest->m_currentOutput += res + "\n";   //krazy:exclude=duoblequote_chars DOM demands chars
0392         break;
0393     }
0394     case ReportResult: {
0395         bool passed = args[0]->toBoolean(exec);
0396         QString description = args[1]->toString(exec).qstring();
0397         if (args[1]->type() == UndefinedType || args[1]->type() == NullType) {
0398             description.clear();
0399         }
0400         m_regTest->reportResult(passed, description);
0401         if (!passed) {
0402             m_regTest->saw_failure = true;
0403         }
0404         break;
0405     }
0406     case CheckOutput: {
0407         DOM::DocumentImpl *docimpl = static_cast<DOM::DocumentImpl *>(m_regTest->m_part->document().handle());
0408         if (docimpl && docimpl->view() && docimpl->renderer()) {
0409             docimpl->updateRendering();
0410         }
0411         QString filename = args[0]->toString(exec).qstring();
0412         filename = RegressionTest::curr->m_currentCategory + "/" + filename; //krazy:exclude=duoblequote_chars DOM demands chars
0413         int failures = RegressionTest::NoFailure;
0414         if (m_regTest->m_genOutput) {
0415             if (!m_regTest->reportResult(m_regTest->checkOutput(filename + "-dom"),
0416                                          "Script-generated " + filename + "-dom")) {
0417                 failures |= RegressionTest::DomFailure;
0418             }
0419             if (!m_regTest->reportResult(m_regTest->checkOutput(filename + "-render"),
0420                                          "Script-generated " + filename + "-render")) {
0421                 failures |= RegressionTest::RenderFailure;
0422             }
0423         } else {
0424             // compare with output file
0425             if (!m_regTest->reportResult(m_regTest->checkOutput(filename + "-dom"), "DOM")) {
0426                 failures |= RegressionTest::DomFailure;
0427             }
0428             if (!m_regTest->reportResult(m_regTest->checkOutput(filename + "-render"), "RENDER")) {
0429                 failures |= RegressionTest::RenderFailure;
0430             }
0431         }
0432         RegressionTest::curr->doFailureReport(filename, failures);
0433         break;
0434     }
0435     case Quit:
0436         m_regTest->reportResult(true,
0437                                 "Called quit");
0438         if (!m_regTest->saw_failure) {
0439             m_regTest->ignore_errors = true;
0440         }
0441         break;
0442     }
0443 
0444     return result;
0445 }
0446 
0447 // -------------------------------------------------------------------------
0448 
0449 KHTMLPartObject::KHTMLPartObject(ExecState *exec, KHTMLPart *_part)
0450 {
0451     m_part = _part;
0452     putDirect("openPage", new KHTMLPartFunction(exec, m_part, KHTMLPartFunction::OpenPage, 1), DontEnum);
0453     putDirect("openPageAsUrl", new KHTMLPartFunction(exec, m_part, KHTMLPartFunction::OpenPageAsUrl, 1), DontEnum);
0454     putDirect("begin",     new KHTMLPartFunction(exec, m_part, KHTMLPartFunction::Begin, 1), DontEnum);
0455     putDirect("write",    new KHTMLPartFunction(exec, m_part, KHTMLPartFunction::Write, 1), DontEnum);
0456     putDirect("end",    new KHTMLPartFunction(exec, m_part, KHTMLPartFunction::End, 0), DontEnum);
0457     putDirect("executeScript", new KHTMLPartFunction(exec, m_part, KHTMLPartFunction::ExecuteScript, 0), DontEnum);
0458     putDirect("processEvents", new KHTMLPartFunction(exec, m_part, KHTMLPartFunction::ProcessEvents, 0), DontEnum);
0459 }
0460 
0461 KJS::JSValue *KHTMLPartObject::winGetter(KJS::ExecState *, KJS::JSObject *, const KJS::Identifier &, const KJS::PropertySlot &slot)
0462 {
0463     KHTMLPartObject *thisObj = static_cast<KHTMLPartObject *>(slot.slotBase());
0464     return KJS::Window::retrieveWindow(thisObj->m_part);
0465 }
0466 
0467 KJS::JSValue *KHTMLPartObject::docGetter(KJS::ExecState *exec, KJS::JSObject *, const KJS::Identifier &, const KJS::PropertySlot &slot)
0468 {
0469     KHTMLPartObject *thisObj = static_cast<KHTMLPartObject *>(slot.slotBase());
0470     return getDOMNode(exec, thisObj->m_part->document().handle());
0471 }
0472 
0473 bool KHTMLPartObject::getOwnPropertySlot(KJS::ExecState *exec, const KJS::Identifier &propertyName, KJS::PropertySlot &slot)
0474 {
0475     if (propertyName == "document") {
0476         slot.setCustom(this, docGetter);
0477         return true;
0478     } else if (propertyName == "window") {
0479         slot.setCustom(this, winGetter);
0480         return true;
0481     }
0482     return JSObject::getOwnPropertySlot(exec, propertyName, slot);
0483 }
0484 
0485 KHTMLPartFunction::KHTMLPartFunction(ExecState */*exec*/, KHTMLPart *_part, int _id, int length)
0486 {
0487     m_part = _part;
0488     id = _id;
0489     putDirect("length", length);
0490 }
0491 
0492 bool KHTMLPartFunction::implementsCall() const
0493 {
0494     return true;
0495 }
0496 
0497 JSValue *KHTMLPartFunction::callAsFunction(ExecState *exec, JSObject */*thisObj*/, const List &args)
0498 {
0499     JSValue *result = jsUndefined();
0500 
0501     switch (id) {
0502     case OpenPage: {
0503         if (args[0]->type() == NullType || args[0]->type() == NullType) {
0504             exec->setException(Error::create(exec, GeneralError, "No filename specified"));
0505             return jsUndefined();
0506         }
0507 
0508         QString filename = args[0]->toString(exec).qstring();
0509         QString fullFilename = QFileInfo(RegressionTest::curr->m_currentBase + "/" + filename).absoluteFilePath(); //krazy:exclude=duoblequote_chars DOM demands chars
0510         QUrl url = QUrl::fromLocalFile(fullFilename);
0511         PartMonitor pm(m_part);
0512         m_part->openUrl(url);
0513         pm.waitForCompletion();
0514         qApp->processEvents(QEventLoop::AllEvents);
0515         break;
0516     }
0517     case OpenPageAsUrl: {
0518         if (args[0]->type() == NullType || args[0]->type() == UndefinedType) {
0519             exec->setException(Error::create(exec, GeneralError, "No filename specified"));
0520             return jsUndefined();
0521         }
0522         if (args[1]->type() == NullType || args[1]->type() == UndefinedType) {
0523             exec->setException(Error::create(exec, GeneralError, "No url specified"));
0524             return jsUndefined();
0525         }
0526 
0527         QString filename = args[0]->toString(exec).qstring();
0528         QString url = args[1]->toString(exec).qstring();
0529         QFile file(RegressionTest::curr->m_currentBase + "/" + filename); //krazy:exclude=duoblequote_chars DOM demands chars
0530         if (!file.open(QIODevice::ReadOnly)) {
0531             exec->setException(Error::create(exec, GeneralError,
0532                                              qPrintable(QString("Error reading " + filename))));
0533         } else {
0534             QByteArray fileData;
0535             QDataStream stream(&fileData, QIODevice::WriteOnly);
0536             char buf[1024];
0537             int bytesread;
0538             while (!file.atEnd()) {
0539                 bytesread = file.read(buf, 1024);
0540                 stream.writeRawData(buf, bytesread);
0541             }
0542             file.close();
0543             QString contents(fileData);
0544             PartMonitor pm(m_part);
0545             m_part->begin(QUrl(url));
0546             m_part->write(contents);
0547             m_part->end();
0548             pm.waitForCompletion();
0549         }
0550         qApp->processEvents(QEventLoop::AllEvents);
0551         break;
0552     }
0553     case Begin: {
0554         QString url = args[0]->toString(exec).qstring();
0555         m_part->begin(QUrl(url));
0556         break;
0557     }
0558     case Write: {
0559         QString str = args[0]->toString(exec).qstring();
0560         m_part->write(str);
0561         break;
0562     }
0563     case End: {
0564         m_part->end();
0565         qApp->processEvents(QEventLoop::AllEvents);
0566         break;
0567     }
0568     case ExecuteScript: {
0569         QString code = args[0]->toString(exec).qstring();
0570         Completion comp;
0571         KJSProxy *proxy = m_part->jScript();
0572         proxy->evaluate("", 0, code, 0, &comp);
0573         if (comp.complType() == Throw) {
0574             exec->setException(comp.value());
0575         }
0576         qApp->processEvents(QEventLoop::AllEvents);
0577         break;
0578     }
0579     case ProcessEvents: {
0580         qApp->processEvents(QEventLoop::AllEvents);
0581         break;
0582     }
0583     }
0584 
0585     return result;
0586 }
0587 
0588 // -------------------------------------------------------------------------
0589 
0590 int main(int argc, char *argv[])
0591 {
0592     // forget about any settings
0593     passwd *pw = getpwuid(getuid());
0594     if (!pw) {
0595         fprintf(stderr, "dang, I don't even know who I am.\n");
0596         exit(1);
0597     }
0598 
0599     QString kh("/var/tmp/%1_non_existant");
0600     kh = kh.arg(pw->pw_name);
0601     qputenv("XDG_CONFIG_HOME", kh.toLatin1());
0602     qputenv("XDG_DATA_HOME", kh.toLatin1());
0603     qputenv("LC_ALL", "C");
0604     qputenv("LANG", "C");
0605 
0606     signal(SIGALRM, signal_handler);
0607 
0608     QApplication a(argc, argv);
0609     // workaround various Qt crashes by always enforcing a TrueColor visual
0610     QApplication::setColorSpec(QApplication::ManyColor);
0611 
0612     QCommandLineParser parser;
0613     parser.addHelpOption(QCoreApplication::translate("main", "Regression tester for khtml"));
0614     parser.addOption(QCommandLineOption(QStringList() << "b" << "base", QCoreApplication::translate("main", "Directory containing tests, basedir and output directories."), "base_dir"));
0615     parser.addOption(QCommandLineOption(QStringList() << "d" << "debug", QCoreApplication::translate("main", "Do not suppress debug output")));
0616     parser.addOption(QCommandLineOption(QStringList() << "g" << "genoutput", QCoreApplication::translate("main", "Regenerate baseline (instead of checking)")));
0617     parser.addOption(QCommandLineOption(QStringList() << "s" << "noshow", QCoreApplication::translate("main", "Do not show the window while running tests")));
0618     parser.addOption(QCommandLineOption(QStringList() << "t" << "test", QCoreApplication::translate("main", "Only run a single test. Multiple options allowed."), "filename"));
0619     parser.addOption(QCommandLineOption(QStringList() << "js", QCoreApplication::translate("main", "Only run .js tests")));
0620     parser.addOption(QCommandLineOption(QStringList() << "html", QCoreApplication::translate("main", "Only run .html tests")));
0621     parser.addOption(QCommandLineOption(QStringList() << "noxvfb", QCoreApplication::translate("main", "Do not use Xvfb")));
0622     parser.addOption(QCommandLineOption(QStringList() << "o" << "output", QCoreApplication::translate("main", "Put output in &lt;directory&gt; instead of &lt;base_dir&gt;/output"), "directory"));
0623     parser.addOption(QCommandLineOption(QStringList() << "r" << "reference", QCoreApplication::translate("main", "Use &lt;directory&gt; as reference instead of &lt;base_dir&gt;/baseline"), "directory"));
0624     parser.addOption(QCommandLineOption(QStringList() << "+[base_dir]", QCoreApplication::translate("main", "Directory containing tests, basedir and output directories. Only regarded if -b is not specified.")));
0625     parser.addOption(QCommandLineOption(QStringList() << "+[testcases]", QCoreApplication::translate("main", "Relative path to testcase, or directory of testcases to be run (equivalent to -t).")));
0626     parser.process(a);
0627 
0628     QString baseDir = parser.value("base");
0629 
0630     if (parser.remainingArguments().count() < 1 && baseDir.isEmpty()) {
0631         parser.showHelp();
0632         ::exit(1);
0633     }
0634 
0635     int testcase_index = 0;
0636     if (baseDir.isEmpty()) {
0637         baseDir = parser.remainingArguments().at(testcase_index++);
0638     }
0639 
0640     QFileInfo bdInfo(baseDir);
0641     // font pathes passed to Xvfb must be absolute
0642     if (bdInfo.isRelative()) {
0643         baseDir = bdInfo.dir().absolutePath();
0644     }
0645 
0646     const char *subdirs[] = {"tests", "baseline", "output", "resources"};
0647     for (int i = 0; i < 3; i++) {
0648         QFileInfo sourceDir(baseDir + QLatin1Char('/') + QLatin1String(subdirs[i]));
0649         if (!sourceDir.exists() || !sourceDir.isDir()) {
0650             fprintf(stderr, "ERROR: Source directory \"%s\": no such directory.\n", sourceDir.filePath().toLocal8Bit().data());
0651             exit(1);
0652         }
0653     }
0654 
0655     if (parser.isSet("xvfb")) {
0656         QString xvfbPath = QStandardPaths::findExecutable("Xvfb");
0657         if (xvfbPath.isEmpty()) {
0658             fprintf(stderr, "ERROR: We need Xvfb to be installed for reliable results\n");
0659             exit(1);
0660         }
0661 
0662         QByteArray xvfbPath8 = QFile::encodeName(xvfbPath);
0663         QStringList fpaths;
0664         fpaths.append(baseDir + "/resources");
0665 
0666         const char *const fontdirs[] = { "75dpi", "misc", "Type1" };
0667         const char *const fontpaths[] =  {"/usr/share/fonts/", "/usr/X11/lib/X11/fonts/",
0668                                           "/usr/lib/X11/fonts/", "/usr/share/fonts/X11/"
0669                                          };
0670 
0671         for (size_t fp = 0; fp < sizeof(fontpaths) / sizeof(*fontpaths); ++fp)
0672             for (size_t fd = 0; fd < sizeof(fontdirs) / sizeof(*fontdirs); ++fd)
0673                 if (QFile::exists(QLatin1String(fontpaths[fp]) + QLatin1String(fontdirs[fd]))) {
0674                     if (strcmp(fontdirs[fd], "Type1")) {
0675                         fpaths.append(QLatin1String(fontpaths[fp]) + QLatin1String(fontdirs[fd]) + ":unscaled");
0676                     } else {
0677                         fpaths.append(QLatin1String(fontpaths[fp]) + QLatin1String(fontdirs[fd]));
0678                     }
0679                 }
0680 
0681         xvfb = fork();
0682         if (!xvfb) {
0683             QByteArray buffer = fpaths.join(",").toLatin1();
0684             execl(xvfbPath8.data(), xvfbPath8.data(), "-once", "-dpi", "100", "-screen", "0",
0685                   "1024x768x16", "-ac", "-fp", buffer.data(), ":47", (char *)NULL);
0686         }
0687 
0688         qputenv("DISPLAY", ":47");
0689     }
0690 
0691     a.setStyle(new TestStyle);
0692     KConfig sc1("cryptodefaults", KConfig::SimpleConfig);
0693     KConfigGroup grp = sc1.group("Warnings");
0694     grp.writeEntry("OnUnencrypted",  false);
0695     KSharedConfigPtr config = KSharedConfig::openConfig();
0696     grp = config->group("Notification Messages");
0697     grp.writeEntry("kjscupguard_alarmhandler", true);
0698     grp.writeEntry("ReportJSErrors", false);
0699     KConfig cfg("khtmlrc");
0700     grp = cfg.group("HTML Settings");
0701     grp.writeEntry("StandardFont", HTML_DEFAULT_VIEW_SANSSERIF_FONT);
0702     grp.writeEntry("FixedFont", HTML_DEFAULT_VIEW_FIXED_FONT);
0703     grp.writeEntry("SerifFont", HTML_DEFAULT_VIEW_SERIF_FONT);
0704     grp.writeEntry("SansSerifFont", HTML_DEFAULT_VIEW_SANSSERIF_FONT);
0705     grp.writeEntry("CursiveFont", HTML_DEFAULT_VIEW_CURSIVE_FONT);
0706     grp.writeEntry("FantasyFont", HTML_DEFAULT_VIEW_FANTASY_FONT);
0707     grp.writeEntry("MinimumFontSize", HTML_DEFAULT_MIN_FONT_SIZE);
0708     grp.writeEntry("MediumFontSize", 10);
0709     grp.writeEntry("Fonts", QStringList());
0710     grp.writeEntry("DefaultEncoding", "");
0711     grp = cfg.group("Java/JavaScript Settings");
0712     grp.writeEntry("WindowOpenPolicy", (int)KHTMLSettings::KJSWindowOpenAllow);
0713 
0714     cfg.sync();
0715     grp.sync();
0716 
0717     KJS::ScriptInterpreter::turnOffCPUGuard();
0718 
0719     QPalette pal = a.palette();
0720     for (int c = 0; palInfo[c].color; ++c) {
0721         pal.setColor(QPalette::Active,   palInfo[c].role, QColor(palInfo[c].color));
0722         pal.setColor(QPalette::Inactive, palInfo[c].role, QColor(palInfo[c].color));
0723         pal.setColor(QPalette::Disabled, palInfo[c].role, QColor(disPalInfo[c].color));
0724     }
0725     a.setPalette(pal);
0726 
0727     int rv = 1;
0728 
0729     bool outputDebug = parser.isSet("debug");
0730 
0731     KConfig dc("kdebugrc", KConfig::SimpleConfig);
0732     static int areas[] = { 1000, 6000, 6005, 6010, 6020, 6030,
0733                            6031, 6035, 6036, 6040, 6041, 6045,
0734                            6050, 6060, 6061, 7000, 7006, 170,
0735                            171, 7101, 7002, 7019, 7027, 7014,
0736                            7011, 6070, 6080, 6090, 0
0737                          };
0738     for (int i = 0; areas[i]; ++i) {
0739         grp = dc.group(QString::number(areas[i]));
0740         grp.writeEntry("InfoOutput", outputDebug ? 2 : 4);
0741         grp.sync();
0742     }
0743     dc.sync();
0744 
0745     kClearDebugConfig();
0746 
0747     // make sure the missing image icon is independent of the icon theme..
0748     QByteArray brokenImData = QByteArray::fromBase64(imageMissingIcon);
0749     QImage brokenIm;
0750     brokenIm.loadFromData(brokenImData);
0751     khtml::Cache::brokenPixmap = new QPixmap(QPixmap::fromImage(brokenIm));
0752 
0753     // create widgets
0754     KMainWindow *toplevel = new KMainWindow();
0755     KHTMLPart *part = new KHTMLPart(toplevel, 0, KHTMLPart::BrowserViewGUI);
0756 
0757     toplevel->setCentralWidget(part->widget());
0758     KAcceleratorManager::setNoAccel(part->widget());
0759     part->setJScriptEnabled(true);
0760 
0761     part->executeScript(DOM::Node(), ""); // force the part to create an interpreter
0762     part->setJavaEnabled(false);
0763     part->setPluginsEnabled(false);
0764 
0765     if (parser.isSet("show")) {
0766         visual = true;
0767     }
0768 
0769     //a.setTopWidget(part->widget());
0770     if (visual) {
0771         toplevel->show();
0772     }
0773 
0774     // we're not interested
0775     toplevel->statusBar()->hide();
0776 
0777     if (!getenv("KDE_DEBUG")) {
0778         // set ulimits
0779         rlimit vmem_limit = { 512 * 1024 * 1024, RLIM_INFINITY }; // 512Mb Memory should suffice
0780         setrlimit(RLIMIT_AS, &vmem_limit);
0781         rlimit stack_limit = { 8 * 1024 * 1024, RLIM_INFINITY }; // 8Mb Memory should suffice
0782         setrlimit(RLIMIT_STACK, &stack_limit);
0783     }
0784 
0785     // run the tests
0786     RegressionTest *regressionTest = new RegressionTest(part,
0787             baseDir,
0788             parser.argument("output"),
0789             parser.argument("reference"),
0790             parser.isSet("genoutput"),
0791             !parser.isSet("html"),
0792             !parser.isSet("js"));
0793     QObject::connect(part->browserExtension(), SIGNAL(openUrlRequest(QUrl,KParts::OpenUrlArguments,KParts::BrowserArguments)),
0794                      regressionTest, SLOT(slotOpenURL(QUrl,KParts::OpenUrlArguments,KParts::BrowserArguments)));
0795     QObject::connect(part->browserExtension(), SIGNAL(resizeTopLevelWidget(int,int)),
0796                      regressionTest, SLOT(resizeTopLevelWidget(int,int)));
0797 
0798     bool result = false;
0799     QStringList tests = parser.arguments("test");
0800     // merge testcases specified on command line
0801     for (; testcase_index < parser.remainingArguments().count(); testcase_index++) {
0802         tests << parser.remainingArguments().at(testcase_index);
0803     }
0804     if (tests.count() > 0)
0805         foreach (QString test, tests) {
0806             result = regressionTest->runTests(test, true);
0807             if (!result) {
0808                 break;
0809             }
0810         }
0811     else {
0812         result = regressionTest->runTests();
0813     }
0814 
0815     if (result) {
0816         if (parser.isSet("genoutput")) {
0817             printf("\nOutput generation completed.\n");
0818         } else {
0819             printf("\nTests completed.\n");
0820             printf("Total:    %d\n",
0821                    regressionTest->m_passes_work +
0822                    regressionTest->m_passes_fail +
0823                    regressionTest->m_failures_work +
0824                    regressionTest->m_failures_fail +
0825                    regressionTest->m_errors);
0826             printf("Passes:   %d", regressionTest->m_passes_work);
0827             if (regressionTest->m_passes_fail) {
0828                 printf(" (%d unexpected passes)\n", regressionTest->m_passes_fail);
0829             } else {
0830                 printf("\n");
0831             }
0832             printf("Failures: %d", regressionTest->m_failures_work);
0833             if (regressionTest->m_failures_fail) {
0834                 printf(" (%d expected failures)\n", regressionTest->m_failures_fail);
0835             } else {
0836                 printf("\n");
0837             }
0838             if (regressionTest->m_errors) {
0839                 printf("Errors:   %d\n", regressionTest->m_errors);
0840             }
0841 
0842             QFile list(regressionTest->m_outputDir + "/links.html");
0843             list.open(QIODevice::WriteOnly | QIODevice::Append);
0844             QString link, cl;
0845             link = QString("<hr>%1 failures. (%2 expected failures)")
0846                    .arg(regressionTest->m_failures_work)
0847                    .arg(regressionTest->m_failures_fail);
0848             list.write(link.toLatin1(), link.length());
0849             list.close();
0850         }
0851     }
0852 
0853     // Only return a 0 exit code if all tests were successful
0854     if (regressionTest->m_failures_work == 0 && regressionTest->m_errors == 0) {
0855         rv = 0;
0856     }
0857 
0858     // cleanup
0859     delete regressionTest;
0860     delete part;
0861     delete toplevel;
0862 
0863     khtml::Cache::clear();
0864     khtml::CSSStyleSelector::clear();
0865     khtml::RenderStyle::cleanup();
0866 
0867     kill(xvfb, SIGINT);
0868 
0869     return rv;
0870 }
0871 
0872 // -------------------------------------------------------------------------
0873 
0874 RegressionTest *RegressionTest::curr = 0;
0875 
0876 RegressionTest::RegressionTest(KHTMLPart *part, const QString &baseDir, const QString &outputDir, const QString &baselineDir,
0877                                bool _genOutput, bool runJS, bool runHTML)
0878     : QObject(part)
0879 {
0880     m_part = part;
0881 
0882     m_baseDir = baseDir;
0883     m_baseDir = m_baseDir.replace("//", "/");
0884     if (m_baseDir.endsWith("/")) {    //krazy:exclude=duoblequote_chars DOM demands chars
0885         m_baseDir = m_baseDir.left(m_baseDir.length() - 1);
0886     }
0887     if (outputDir.isEmpty()) {
0888         m_outputDir = m_baseDir + "/output";
0889     } else {
0890         createMissingDirs(outputDir + "/"); //krazy:exclude=duoblequote_chars DOM demands chars
0891         m_outputDir = outputDir;
0892     }
0893     m_baselineDir = baselineDir;
0894     m_baselineDir = m_baselineDir.replace("//", "/");
0895     if (m_baselineDir.endsWith("/")) {
0896         m_baselineDir = m_baselineDir.left(m_baselineDir.length() - 1);
0897     }
0898     if (m_baselineDir.isEmpty()) {
0899         m_baselineDir = m_baseDir + "/baseline";
0900     } else {
0901         createMissingDirs(m_baselineDir + "/");
0902     }
0903     m_genOutput = _genOutput;
0904     m_runJS = runJS;
0905     m_runHTML =  runHTML;
0906     m_passes_work = m_passes_fail = 0;
0907     m_failures_work = m_failures_fail = 0;
0908     m_errors = 0;
0909 
0910     if (!m_genOutput) {
0911         ::unlink(QFile::encodeName(m_outputDir + "/links.html"));
0912     }
0913     QFile f(m_outputDir + "/empty.html");
0914     QString s;
0915     f.open(QIODevice::WriteOnly | QIODevice::Truncate);
0916     s = "<html><body>Follow the white rabbit";
0917     f.write(s.toLatin1(), s.length());
0918     f.close();
0919     f.setFileName(m_outputDir + "/index.html");
0920     f.open(QIODevice::WriteOnly | QIODevice::Truncate);
0921     s = "<html><frameset cols=150,*><frame src=links.html><frame name=content src=empty.html>";
0922     f.write(s.toLatin1(), s.length());
0923     f.close();
0924 
0925     m_paintBuffer = 0;
0926 
0927     curr = this;
0928     m_part->view()->setFrameStyle(QFrame::StyledPanel | QFrame::Plain);
0929     resizeTopLevelWidget(800, 598);
0930 }
0931 
0932 static QStringList readListFile(const QString &filename)
0933 {
0934     // Read ignore file for this directory
0935     QString ignoreFilename = filename;
0936     QFileInfo ignoreInfo(ignoreFilename);
0937     QStringList ignoreFiles;
0938     if (ignoreInfo.exists()) {
0939         QFile ignoreFile(ignoreFilename);
0940         if (!ignoreFile.open(QIODevice::ReadOnly)) {
0941             fprintf(stderr, "Can't open %s\n", qPrintable(ignoreFilename));
0942             exit(1);
0943         }
0944         QTextStream ignoreStream(&ignoreFile);
0945         QString line;
0946         while (!(line = ignoreStream.readLine()).isNull()) {
0947             ignoreFiles.append(line);
0948         }
0949         ignoreFile.close();
0950     }
0951     return ignoreFiles;
0952 }
0953 
0954 RegressionTest::~RegressionTest()
0955 {
0956     delete m_paintBuffer;
0957 }
0958 
0959 bool RegressionTest::runTests(QString relPath, bool mustExist, QStringList failureFileList)
0960 {
0961     m_currentOutput.clear();
0962 
0963     QString fullPath = m_baseDir + "/tests/" + relPath;
0964 
0965     if (!QFile(fullPath).exists()) {
0966         fprintf(stderr, "%s: No such file or directory\n", qPrintable(relPath));
0967         return false;
0968     }
0969 
0970     QFileInfo info(fullPath);
0971 
0972     if (!info.exists() && mustExist) {
0973         fprintf(stderr, "%s: No such file or directory\n", qPrintable(relPath));
0974         return false;
0975     }
0976 
0977     if (!info.isReadable() && mustExist) {
0978         fprintf(stderr, "%s: Access denied\n", qPrintable(relPath));
0979         return false;
0980     }
0981 
0982     if (info.isDir()) {
0983         QStringList ignoreFiles = readListFile(fullPath + "/ignore");
0984         QStringList failureFiles = readListFile(fullPath + "/KNOWN_FAILURES");
0985 
0986         // Run each test in this directory, recusively
0987         QDir sourceDir(m_baseDir + "/tests/" + relPath);
0988         for (uint fileno = 0; fileno < sourceDir.count(); fileno++) {
0989             QString filename = sourceDir[fileno];
0990             QString relFilename = relPath.isEmpty() ? filename : relPath + "/" + filename; //krazy:exclude=duoblequote_chars DOM demands chars
0991 
0992             if (filename == "." || filename == ".." ||  ignoreFiles.contains(filename)) {
0993                 continue;
0994             }
0995 
0996             runTests(relFilename, false, failureFiles);
0997         }
0998     } else if (info.isFile()) {
0999 
1000         alarm(12);
1001 
1002         khtml::Cache::init();
1003 
1004         QString relativeDir = QFileInfo(relPath).path();
1005         QString filename = info.fileName();
1006         m_currentBase = m_baseDir + "/tests/" + relativeDir;
1007         m_currentCategory = relativeDir;
1008         m_currentTest = filename;
1009 
1010         if (failureFileList.isEmpty() && QFile(info.path() + "/KNOWN_FAILURES").exists()) {
1011             failureFileList = readListFile(info.path() + "/KNOWN_FAILURES");
1012         }
1013 
1014         int known_failure = NoFailure;
1015         if (failureFileList.contains(filename)) {
1016             known_failure |= AllFailure;
1017         }
1018         if (failureFileList.contains(filename + "-render")) {
1019             known_failure |= RenderFailure;
1020         }
1021         if (failureFileList.contains(filename + "-dump.png")) {
1022             known_failure |= PaintFailure;
1023         }
1024         if (failureFileList.contains(filename + "-dom")) {
1025             known_failure |= DomFailure;
1026         }
1027 
1028         m_known_failures = known_failure;
1029         if (filename.endsWith(".html") || filename.endsWith(".htm") || filename.endsWith(".xhtml") || filename.endsWith(".xml")) {
1030             if (relPath.startsWith("domts/") && !m_runJS) {
1031                 return true;
1032             }
1033             if (relPath.startsWith("ecma/") && !m_runJS) {
1034                 return true;
1035             }
1036             if (m_runHTML) {
1037                 testStaticFile(relPath);
1038             }
1039         } else if (filename.endsWith(".js")) {
1040             if (m_runJS) {
1041                 alarm(120);
1042                 testJSFile(relPath);
1043             }
1044         } else if (mustExist) {
1045             fprintf(stderr, "%s: Not a valid test file (must be .htm(l) or .js)\n", qPrintable(relPath));
1046             return false;
1047         }
1048     } else if (mustExist) {
1049         fprintf(stderr, "%s: Not a regular file\n", qPrintable(relPath));
1050         return false;
1051     }
1052 
1053     return true;
1054 }
1055 
1056 void RegressionTest::getPartDOMOutput(QTextStream &outputStream, KHTMLPart *part, uint indent)
1057 {
1058     DOM::Node node = part->document();
1059     while (!node.isNull()) {
1060         // process
1061 
1062         for (uint i = 0; i < indent; i++) {
1063             outputStream << "  ";
1064         }
1065 
1066         // Make doctype's visually different from elements
1067         if (node.nodeType() == DOM::Node::DOCUMENT_TYPE_NODE) {
1068             outputStream << "!doctype ";
1069         }
1070 
1071         outputStream << node.nodeName().string();
1072 
1073         switch (node.nodeType()) {
1074         case DOM::Node::ELEMENT_NODE: {
1075             // Sort strings to ensure consistent output
1076             QStringList attrNames;
1077             NamedNodeMap attrs = node.attributes();
1078             for (uint a = 0; a < attrs.length(); a++) {
1079                 attrNames.append(attrs.item(a).nodeName().string());
1080             }
1081             attrNames.sort();
1082 
1083             QStringList::iterator it;
1084             Element elem(node);
1085             for (it = attrNames.begin(); it != attrNames.end(); ++it) {
1086                 QString name = *it;
1087                 QString value = elem.getAttribute(*it).string();
1088                 outputStream << " " << name << "=\"" << value << "\"";
1089             }
1090             if (node.handle()->id() == ID_FRAME) {
1091                 outputStream << endl;
1092                 QString frameName = static_cast<DOM::HTMLFrameElementImpl *>(node.handle())->name.string();
1093                 KHTMLPart *frame = part->findFrame(frameName);
1094                 if (frame) {
1095                     getPartDOMOutput(outputStream, frame, indent);
1096                 } else {
1097                     outputStream << "(FRAME NOT FOUND)";
1098                 }
1099             }
1100             break;
1101         }
1102         case DOM::Node::ATTRIBUTE_NODE:
1103             // Should not be present in tree
1104             assert(false);
1105             break;
1106         case DOM::Node::TEXT_NODE:
1107             outputStream << " \"" << Text(node).data().string() << "\"";
1108             break;
1109         case DOM::Node::CDATA_SECTION_NODE:
1110             outputStream << " \"" << CDATASection(node).data().string() << "\"";
1111             break;
1112         case DOM::Node::ENTITY_REFERENCE_NODE:
1113             break;
1114         case DOM::Node::ENTITY_NODE:
1115             break;
1116         case DOM::Node::PROCESSING_INSTRUCTION_NODE:
1117             break;
1118         case DOM::Node::COMMENT_NODE:
1119             outputStream << " \"" << Comment(node).data().string() << "\"";
1120             break;
1121         case DOM::Node::DOCUMENT_NODE:
1122             break;
1123         case DOM::Node::DOCUMENT_TYPE_NODE:
1124             break;
1125         case DOM::Node::DOCUMENT_FRAGMENT_NODE:
1126             // Should not be present in tree
1127             assert(false);
1128             break;
1129         case DOM::Node::NOTATION_NODE:
1130             break;
1131         default:
1132             assert(false);
1133             break;
1134         }
1135 
1136         outputStream << endl;
1137 
1138         if (!node.firstChild().isNull()) {
1139             node = node.firstChild();
1140             indent++;
1141         } else if (!node.nextSibling().isNull()) {
1142             node = node.nextSibling();
1143         } else {
1144             while (!node.isNull() && node.nextSibling().isNull()) {
1145                 node = node.parentNode();
1146                 indent--;
1147             }
1148             if (!node.isNull()) {
1149                 node = node.nextSibling();
1150             }
1151         }
1152     }
1153 }
1154 
1155 void RegressionTest::dumpRenderTree(QTextStream &outputStream, KHTMLPart *part)
1156 {
1157     DOM::DocumentImpl *doc = static_cast<DocumentImpl *>(part->document().handle());
1158     if (!doc || !doc->renderer()) {
1159         return;
1160     }
1161     doc->renderer()->layer()->dump(outputStream);
1162 
1163     // Dump frames if any
1164     // Get list of names instead of frames() to sort the list alphabetically
1165     QStringList names = part->frameNames();
1166     names.sort();
1167     for (QStringList::iterator it = names.begin(); it != names.end(); ++it) {
1168         outputStream << "FRAME: " << (*it) << "\n";
1169         KHTMLPart *frame = part->findFrame((*it));
1170 //  Q_ASSERT( frame );
1171         if (frame) {
1172             dumpRenderTree(outputStream, frame);
1173         }
1174     }
1175 }
1176 
1177 QString RegressionTest::getPartOutput(OutputType type)
1178 {
1179     // dump out the contents of the rendering & DOM trees
1180     QString dump;
1181     QTextStream outputStream(&dump, QIODevice::WriteOnly);
1182 
1183     if (type == RenderTree) {
1184         dumpRenderTree(outputStream, m_part);
1185     } else {
1186         assert(type == DOMTree);
1187         getPartDOMOutput(outputStream, m_part, 0);
1188     }
1189 
1190     dump.replace(m_baseDir + "/tests", QLatin1String("REGRESSION_SRCDIR"));
1191     return dump;
1192 }
1193 
1194 QImage RegressionTest::renderToImage()
1195 {
1196     int ew = m_part->view()->contentsWidth();
1197     int eh = m_part->view()->contentsHeight();
1198 
1199     if (ew * eh > 4000 * 4000) { // don't DoS us
1200         return QImage();
1201     }
1202 
1203     QImage img(ew, eh, QImage::Format_ARGB32);
1204     img.fill(0xff0000);
1205     if (!m_paintBuffer) {
1206         m_paintBuffer = new QPixmap(512, 128);
1207     }
1208 
1209     for (int py = 0; py < eh; py += 128) {
1210         for (int px = 0; px < ew; px += 512) {
1211             QPainter *tp = new QPainter;
1212             tp->begin(m_paintBuffer);
1213             tp->translate(-px, -py);
1214             tp->fillRect(px, py, 512, 128, Qt::magenta);
1215             m_part->document().handle()->renderer()->layer()->paint(tp, QRect(px, py, 512, 128));
1216             tp->end();
1217             delete tp;
1218 
1219             // now fill the chunk into our image
1220             QImage chunk = m_paintBuffer->toImage();
1221             assert(chunk.depth() == 32);
1222             for (int y = 0; y < 128 && py + y < eh; ++y) {
1223                 memcpy(img.scanLine(py + y) + px * 4, chunk.scanLine(y), qMin(512, ew - px) * 4);
1224             }
1225         }
1226     }
1227 
1228     assert(img.depth() == 32);
1229     return img;
1230 }
1231 
1232 bool RegressionTest::imageEqual(const QImage &lhsi, const QImage &rhsi)
1233 {
1234     if (lhsi.width() != rhsi.width() || lhsi.height() != rhsi.height()) {
1235         // qDebug() << "dimensions different " << lhsi.size() << " " << rhsi.size();
1236         return false;
1237     }
1238     int w = lhsi.width();
1239     int h = lhsi.height();
1240     int bytes = lhsi.bytesPerLine();
1241 
1242     const unsigned char *origLs = lhsi.bits();
1243     const unsigned char *origRs = rhsi.bits();
1244 
1245     for (int y = 0; y < h; ++y) {
1246         const QRgb *ls = (const QRgb *)(origLs + y * bytes);
1247         const QRgb *rs = (const QRgb *)(origRs + y * bytes);
1248         if (memcmp(ls, rs, bytes)) {
1249             for (int x = 0; x < w; ++x) {
1250                 QRgb l = ls[x];
1251                 QRgb r = rs[x];
1252                 if ((abs(qRed(l) - qRed(r)) < 20) &&
1253                         (abs(qGreen(l) - qGreen(r)) < 20) &&
1254                         (abs(qBlue(l) - qBlue(r)) < 20)) {
1255                     continue;
1256                 }
1257                 // qDebug() << "pixel (" << x << ", " << y << ") is different " << QColor(  lhsi.pixel (  x, y ) ) << " " << QColor(  rhsi.pixel (  x, y ) );
1258                 return false;
1259             }
1260         }
1261     }
1262 
1263     return true;
1264 }
1265 
1266 void RegressionTest::createLink(const QString &test, int failures)
1267 {
1268     createMissingDirs(m_outputDir + "/" + test + "-compare.html");   //krazy:exclude=duoblequote_chars DOM demands chars
1269 
1270     QFile list(m_outputDir + "/links.html");
1271     list.open(QIODevice::WriteOnly | QIODevice::Append);
1272     QString link;
1273     link = QString("<a href=\"%1\" target=\"content\" title=\"%2\">")
1274            .arg(test + "-compare.html")
1275            .arg(test);
1276     link += m_currentTest;
1277     link += "</a> [";
1278     if (failures & DomFailure) {
1279         link += "D";    //krazy:exclude=duoblequote_chars DOM demands chars
1280     }
1281     if (failures & RenderFailure) {
1282         link += "R";    //krazy:exclude=duoblequote_chars DOM demands chars
1283     }
1284     if (failures & PaintFailure) {
1285         link += "P";    //krazy:exclude=duoblequote_chars DOM demands chars
1286     }
1287     link += "]<br>\n";
1288     list.write(link.toLatin1(), link.length());
1289     list.close();
1290 }
1291 
1292 void RegressionTest::doJavascriptReport(const QString &test)
1293 {
1294     QFile compare(m_outputDir + "/" + test + "-compare.html");   //krazy:exclude=duoblequote_chars DOM demands chars
1295     if (!compare.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
1296         qDebug() << "failed to open " << m_outputDir + "/" + test + "-compare.html";    //krazy:exclude=duoblequote_chars DOM demands chars
1297     }
1298     QString cl;
1299     cl = QString("<html><head><title>%1</title>").arg(test);
1300     cl += "<body><tt>";
1301     QString text = "\n" + m_currentOutput;  //krazy:exclude=duoblequote_chars DOM demands chars
1302     text.replace('<', "&lt;");
1303     text.replace('>', "&gt;");
1304     text.replace(QRegExp("\nFAILED"), "\n<span style='color: red'>FAILED</span>");
1305     text.replace(QRegExp("\nFAIL"), "\n<span style='color: red'>FAIL</span>");
1306     text.replace(QRegExp("\nPASSED"), "\n<span style='color: green'>PASSED</span>");
1307     text.replace(QRegExp("\nPASS"), "\n<span style='color: green'>PASS</span>");
1308     if (text.at(0) == '\n') {
1309         text = text.mid(1, text.length());
1310     }
1311     text.replace('\n', "<br>\n");
1312     cl += text;
1313     cl += "</tt></body></html>";
1314     compare.write(cl.toLatin1(), cl.length());
1315     compare.close();
1316 }
1317 
1318 /** returns the path in a way that is relatively reachable from base.
1319  * @param base base directory (must not include trailing slash)
1320  * @param path directory/file to be relatively reached by base
1321  * @return path with all elements replaced by .. and concerning path elements
1322  *  to be relatively reachable from base.
1323  */
1324 static QString makeRelativePath(const QString &base, const QString &path)
1325 {
1326     QString absBase = QFileInfo(base).absoluteFilePath();
1327     QString absPath = QFileInfo(path).absoluteFilePath();
1328 //     qDebug() << "absPath: \"" << absPath << "\"";
1329 //     qDebug() << "absBase: \"" << absBase << "\"";
1330 
1331     // walk up to common ancestor directory
1332     int pos = 0;
1333     do {
1334         pos++;
1335         int newpos = absBase.indexOf('/', pos);
1336         if (newpos == -1) {
1337             newpos = absBase.length();
1338         }
1339         QString cmpPathComp(absPath.unicode() + pos, newpos - pos);
1340         QString cmpBaseComp(absBase.unicode() + pos, newpos - pos);
1341 //         qDebug() << "cmpPathComp: \"" << cmpPathComp.string() << "\"";
1342 //         qDebug() << "cmpBaseComp: \"" << cmpBaseComp.string() << "\"";
1343 //         qDebug() << "pos: " << pos << " newpos: " << newpos;
1344         if (cmpPathComp != cmpBaseComp) {
1345             pos--;
1346             break;
1347         }
1348         pos = newpos;
1349     } while (pos < (int)absBase.length() && pos < (int)absPath.length());
1350     int basepos = pos < (int)absBase.length() ? pos + 1 : pos;
1351     int pathpos = pos < (int)absPath.length() ? pos + 1 : pos;
1352 
1353 //     qDebug() << "basepos " << basepos << " pathpos " << pathpos;
1354 
1355     QString rel;
1356     {
1357         QString relBase(absBase.unicode() + basepos, absBase.length() - basepos);
1358         QString relPath(absPath.unicode() + pathpos, absPath.length() - pathpos);
1359         // generate as many .. as there are path elements in relBase
1360         if (relBase.length() > 0) {
1361             for (int i = relBase.count('/'); i > 0; --i) {
1362                 rel += "../";
1363             }
1364             rel += "..";
1365             if (relPath.length() > 0) {
1366                 rel += "/";    //krazy:exclude=duoblequote_chars DOM demands chars
1367             }
1368         }
1369         rel += relPath;
1370     }
1371     return rel;
1372 }
1373 
1374 static QString getDiff(QString cmdLine)
1375 {
1376     QProcess p;
1377     p.start(cmdLine, QIODevice::ReadOnly);
1378     p.waitForFinished();
1379     QString text = QString::fromLocal8Bit(p.readAllStandardOutput());
1380     text = text.replace('<', "&lt;");
1381     text = text.replace('>', "&gt;");
1382     return text;
1383 }
1384 
1385 void RegressionTest::doFailureReport(const QString &test, int failures)
1386 {
1387     if (failures == NoFailure) {
1388         ::unlink(QFile::encodeName(m_outputDir + "/" + test + "-compare.html"));      //krazy:exclude=duoblequote_chars DOM demands chars
1389         return;
1390     }
1391 
1392     createLink(test, failures);
1393 
1394     if (failures & JSFailure) {
1395         doJavascriptReport(test);
1396         return; // no support for both kind
1397     }
1398 
1399     QFile compare(m_outputDir + "/" + test + "-compare.html");   //krazy:exclude=duoblequote_chars DOM demands chars
1400 
1401     QString testFile = QFileInfo(test).fileName();
1402 
1403     QString renderDiff;
1404     QString domDiff;
1405 
1406     QString relOutputDir = makeRelativePath(m_baseDir, m_outputDir);
1407     QString relBaselineDir = makeRelativePath(m_baseDir, m_baselineDir);
1408 
1409     // are blocking reads possible with K3Process?
1410     QString pwd = QDir::currentPath();
1411     chdir(QFile::encodeName(m_baseDir));
1412 
1413     if (failures & RenderFailure) {
1414         renderDiff += "<pre>";
1415         renderDiff += getDiff(QString::fromLatin1("diff -u %4/%1-render %3/%2-render")
1416                               .arg(test, test, relOutputDir, relBaselineDir));
1417         renderDiff += "</pre>";
1418     }
1419 
1420     if (failures & DomFailure) {
1421         domDiff += "<pre>";
1422         domDiff += getDiff(QString::fromLatin1("diff -u %4/%1-dom %3/%2-dom")
1423                            .arg(test, test, relOutputDir, relBaselineDir));
1424         domDiff += "</pre>";
1425     }
1426 
1427     chdir(QFile::encodeName(pwd));
1428 
1429     // create a relative path so that it works via web as well. ugly
1430     QString relpath = makeRelativePath(m_outputDir + "/"    //krazy:exclude=duoblequote_chars DOM demands chars
1431                                        + QFileInfo(test).path(), m_baseDir);
1432 
1433     compare.open(QIODevice::WriteOnly | QIODevice::Truncate);
1434     QString cl;
1435     cl = QString("<html><head><title>%1</title>").arg(test);
1436     cl += QString("<script>\n"
1437                   "var pics = new Array();\n"
1438                   "pics[0]=new Image();\n"
1439                   "pics[0].src = '%1';\n"
1440                   "pics[1]=new Image();\n"
1441                   "pics[1].src = '%2';\n"
1442                   "var doflicker = 1;\n"
1443                   "var t = 1;\n"
1444                   "var lastb=0;\n")
1445           .arg(relpath + "/" + relBaselineDir + "/" + test + "-dump.png")
1446           .arg(testFile + "-dump.png");
1447     cl += QString("function toggleVisible(visible) {\n"
1448                   "     document.getElementById('render').style.visibility= visible == 'render' ? 'visible' : 'hidden';\n"
1449                   "     document.getElementById('image').style.visibility= visible == 'image' ? 'visible' : 'hidden';\n"
1450                   "     document.getElementById('dom').style.visibility= visible == 'dom' ? 'visible' : 'hidden';\n"
1451                   "}\n"
1452                   "function show() { document.getElementById('image').src = pics[t].src; "
1453                   "document.getElementById('image').style.borderColor = t && !doflicker ? 'red' : 'gray';\n"
1454                   "toggleVisible('image');\n"
1455                   "}");
1456     cl += QString("function runSlideShow(){\n"
1457                   "   document.getElementById('image').src = pics[t].src;\n"
1458                   "   if (doflicker)\n"
1459                   "       t = 1 - t;\n"
1460                   "   setTimeout('runSlideShow()', 200);\n"
1461                   "}\n"
1462                   "function m(b) { if (b == lastb) return; document.getElementById('b'+b).className='buttondown';\n"
1463                   "                var e = document.getElementById('b'+lastb);\n"
1464                   "                 if(e) e.className='button';\n"
1465                   "                 lastb = b;\n"
1466                   "}\n"
1467                   "function showRender() { doflicker=0;toggleVisible('render')\n"
1468                   "}\n"
1469                   "function showDom() { doflicker=0;toggleVisible('dom')\n"
1470                   "}\n"
1471                   "</script>\n");
1472 
1473     cl += QString("<style>\n"
1474                   ".buttondown { cursor: pointer; padding: 0px 20px; color: white; background-color: blue; border: inset blue 2px;}\n"
1475                   ".button { cursor: pointer; padding: 0px 20px; color: black; background-color: white; border: outset blue 2px;}\n"
1476                   ".diff { position: absolute; left: 10px; top: 100px; visibility: hidden; border: 1px black solid; background-color: white; color: black; /* width: 800; height: 600; overflow: scroll; */ }\n"
1477                   "</style>\n");
1478 
1479     if (failures & PaintFailure) {
1480         cl += QString("<body onload=\"m(1); show(); runSlideShow();\"");
1481     } else if (failures & RenderFailure) {
1482         cl += QString("<body onload=\"m(4); toggleVisible('render');\"");
1483     } else {
1484         cl += QString("<body onload=\"m(5); toggleVisible('dom');\"");
1485     }
1486     cl += QString(" text=black bgcolor=gray>\n<h1>%3</h1>\n").arg(test);
1487     if (failures & PaintFailure)
1488         cl += QString("<span id='b1' class='buttondown' onclick=\"doflicker=1;show();m(1)\">FLICKER</span>&nbsp;\n"
1489                       "<span id='b2' class='button' onclick=\"doflicker=0;t=0;show();m(2)\">BASE</span>&nbsp;\n"
1490                       "<span id='b3' class='button' onclick=\"doflicker=0;t=1;show();m(3)\">OUT</span>&nbsp;\n");
1491     if (renderDiff.length()) {
1492         cl += "<span id='b4' class='button' onclick='showRender();m(4)'>R-DIFF</span>&nbsp;\n";
1493     }
1494     if (domDiff.length()) {
1495         cl += "<span id='b5' class='button' onclick='showDom();m(5);'>D-DIFF</span>&nbsp;\n";
1496     }
1497     // The test file always exists - except for checkOutput called from *.js files
1498     if (QFile::exists(m_baseDir + "/tests/" + test))
1499         cl += QString("<a class=button href=\"%1\">HTML</a>&nbsp;")
1500               .arg(relpath + "/tests/" + test);
1501 
1502     cl += QString("<hr>"
1503                   "<img style='border: solid 5px gray' src=\"%1\" id='image'>")
1504           .arg(relpath + "/" + relBaselineDir + "/" + test + "-dump.png");
1505 
1506     cl += "<div id='render' class='diff'>" + renderDiff + "</div>";
1507     cl += "<div id='dom' class='diff'>" + domDiff + "</div>";
1508 
1509     cl += "</body></html>";
1510     compare.write(cl.toLatin1(), cl.length());
1511     compare.close();
1512 }
1513 
1514 void RegressionTest::testStaticFile(const QString &filename)
1515 {
1516     qDebug("TESTING:%s", filename.toLatin1().data());
1517     resizeTopLevelWidget(800, 598);   // restore size
1518 
1519     // Set arguments
1520     KParts::OpenUrlArguments args;
1521     if (filename.endsWith(".html") || filename.endsWith(".htm")) {
1522         args.setMimeType("text/html");
1523     } else if (filename.endsWith(".xhtml")) {
1524         args.setMimeType("application/xhtml+xml");
1525     } else if (filename.endsWith(".xml")) {
1526         args.setMimeType("text/xml");
1527     }
1528     m_part->setArguments(args);
1529     // load page
1530     QUrl url = QUrl::fromLocalFile(QFileInfo(m_baseDir + "/tests/" + filename).absoluteFilePath());
1531     PartMonitor pm(m_part);
1532     m_part->openUrl(url);
1533     pm.waitForCompletion();
1534     m_part->closeUrl();
1535 
1536     if (filename.startsWith("domts/")) {
1537         QString functionname;
1538 
1539         KJS::Completion comp = m_part->jScriptInterpreter()->evaluate(filename, 0, "exposeTestFunctionNames();");
1540         /*
1541          *  Error handling
1542          */
1543         KJS::ExecState *exec = m_part->jScriptInterpreter()->globalExec();
1544         if (comp.complType() == ReturnValue || comp.complType() == Normal) {
1545             if (comp.value() && comp.value()->type() == ObjectType &&
1546                     comp.value()->toObject(exec)->className() == "Array") {
1547                 JSObject *argArrayObj = comp.value()->toObject(exec);
1548                 unsigned int length = argArrayObj->
1549                                       get(exec, "length")->
1550                                       toUInt32(exec);
1551                 if (length == 1) {
1552                     functionname = argArrayObj->get(exec, 0)->toString(exec).qstring();
1553                 }
1554             }
1555         }
1556         if (functionname.isNull()) {
1557             // qDebug() << "DOM " << filename << " doesn't expose 1 function name - ignoring";
1558             return;
1559         }
1560 
1561         KJS::Completion comp2 = m_part->jScriptInterpreter()->evaluate(filename, 0, QString("setUpPage(); " + functionname + "();"));
1562         bool success = (comp2.complType() == ReturnValue || comp2.complType() == Normal);
1563         QString description = "DOMTS";
1564         if (comp2.complType() == Throw) {
1565             KJS::JSValue  *val = comp2.value();
1566             KJS::JSObject *obj = val->toObject(exec);
1567             if (obj && obj->hasProperty(exec, "jsUnitMessage")) {
1568                 description = obj->get(exec, "jsUnitMessage")->toString(exec).qstring();
1569             } else {
1570                 description = comp2.value()->toString(exec).qstring();
1571             }
1572         }
1573         reportResult(success,  description);
1574 
1575         if (!success && !m_known_failures) {
1576             doFailureReport(filename, JSFailure);
1577         }
1578         return;
1579     }
1580 
1581     int back_known_failures = m_known_failures;
1582 
1583     if (m_genOutput) {
1584         if (m_known_failures & DomFailure) {
1585             m_known_failures = AllFailure;
1586         }
1587         reportResult(checkOutput(filename + "-dom"), "DOM");
1588         if (m_known_failures & RenderFailure) {
1589             m_known_failures = AllFailure;
1590         }
1591         reportResult(checkOutput(filename + "-render"), "RENDER");
1592         if (m_known_failures & PaintFailure) {
1593             m_known_failures = AllFailure;
1594         }
1595         renderToImage().save(m_baselineDir + "/" + filename + "-dump.png", "PNG", 60);
1596         printf("Generated %s\n", qPrintable(QString(m_baselineDir + "/" + filename + "-dump.png")));
1597         reportResult(true, "PAINT");
1598     } else {
1599         int failures = NoFailure;
1600 
1601         // compare with output file
1602         if (m_known_failures & DomFailure) {
1603             m_known_failures = AllFailure;
1604         }
1605         if (!reportResult(checkOutput(filename + "-dom"), "DOM")) {
1606             failures |= DomFailure;
1607         }
1608 
1609         if (m_known_failures & RenderFailure) {
1610             m_known_failures = AllFailure;
1611         }
1612         if (!reportResult(checkOutput(filename + "-render"), "RENDER")) {
1613             failures |= RenderFailure;
1614         }
1615 
1616         if (m_known_failures & PaintFailure) {
1617             m_known_failures = AllFailure;
1618         }
1619         if (!reportResult(checkPaintdump(filename), "PAINT")) {
1620             failures |= PaintFailure;
1621         }
1622 
1623         doFailureReport(filename, failures);
1624     }
1625 
1626     m_known_failures = back_known_failures;
1627 }
1628 
1629 void RegressionTest::evalJS(ScriptInterpreter &interp, const QString &filename, bool report_result)
1630 {
1631     QString fullSourceName = filename;
1632     QFile sourceFile(fullSourceName);
1633 
1634     if (!sourceFile.open(QIODevice::ReadOnly)) {
1635         fprintf(stderr, "Error reading file %s\n", qPrintable(fullSourceName));
1636         exit(1);
1637     }
1638 
1639     QTextStream stream(&sourceFile);
1640     stream.setCodec("UTF-8");
1641     QString code = stream.readAll();
1642     sourceFile.close();
1643 
1644     saw_failure = false;
1645     ignore_errors = false;
1646     Completion c = interp.evaluate(filename, 0, UString(code));
1647 
1648     if (report_result && !ignore_errors) {
1649         bool expected_failure = filename.endsWith("-n.js");
1650         if (c.complType() == Throw) {
1651             ExecState *exec = interp.globalExec();
1652             QString errmsg = c.value()->toString(exec).qstring();
1653             if (!expected_failure) {
1654                 int line = -1;
1655                 JSObject *obj = c.value()->toObject(exec);
1656                 if (obj) {
1657                     line = obj->get(exec, "line")->toUInt32(exec);
1658                 }
1659                 printf("ERROR: %s (%s) at line:%d\n", qPrintable(filename), qPrintable(errmsg), line);
1660                 doFailureReport(m_currentCategory + "/" + m_currentTest, JSFailure);     //krazy:exclude=duoblequote_chars DOM demands chars
1661                 m_errors++;
1662             } else {
1663                 reportResult(true, QString("Expected Failure: %1").arg(errmsg));
1664             }
1665         } else if (saw_failure) {
1666             if (!expected_failure) {
1667                 doFailureReport(m_currentCategory + "/" + m_currentTest, JSFailure);    //krazy:exclude=duoblequote_chars DOM demands chars
1668             }
1669             reportResult(expected_failure, "saw 'failed!'");
1670         } else {
1671             reportResult(!expected_failure, "passed");
1672         }
1673     }
1674 }
1675 
1676 class GlobalImp : public JSGlobalObject
1677 {
1678 public:
1679     virtual UString className() const
1680     {
1681         return "global";
1682     }
1683 };
1684 
1685 void RegressionTest::testJSFile(const QString &filename)
1686 {
1687     qDebug("TEST JS:%s", filename.toLatin1().data());
1688     resizeTopLevelWidget(800, 598);   // restore size
1689 
1690     // create interpreter
1691     // note: this is different from the interpreter used by the part,
1692     // it contains regression test-specific objects & functions
1693     ProtectedPtr<JSGlobalObject> global(new GlobalImp());
1694     khtml::ChildFrame frame;
1695     frame.m_part = m_part;
1696     ScriptInterpreter interp(global, &frame);
1697     ExecState *exec = interp.globalExec();
1698 
1699     global->put(exec, "part", new KHTMLPartObject(exec, m_part));
1700     global->put(exec, "regtest", new RegTestObject(exec, this));
1701     global->put(exec, "debug", new RegTestFunction(exec, this, RegTestFunction::Print, 1));
1702     global->put(exec, "print", new RegTestFunction(exec, this, RegTestFunction::Print, 1));
1703 
1704     QStringList dirs = filename.split('/');
1705     // NOTE: the basename is of little interest here, but the last basedir change
1706     // isn't taken in account
1707     QString basedir =  m_baseDir + "/tests/";
1708     foreach (const QString &it, dirs) {
1709         if (! ::access(QFile::encodeName(basedir + "shell.js"), R_OK)) {
1710             evalJS(interp, basedir + "shell.js", false);
1711         }
1712         basedir += it + "/";    //krazy:exclude=duoblequote_chars DOM demands chars
1713     }
1714     evalJS(interp, m_baseDir + "/tests/" + filename, true);
1715 }
1716 
1717 RegressionTest::CheckResult RegressionTest::checkPaintdump(const QString &filename)
1718 {
1719     QString againstFilename(filename + "-dump.png");
1720     QString absFilename = QFileInfo(m_baselineDir + "/" + againstFilename).absoluteFilePath();
1721     if (svnIgnored(absFilename)) {
1722         m_known_failures = NoFailure;
1723         return Ignored;
1724     }
1725     CheckResult result = Failure;
1726 
1727     QImage baseline;
1728     baseline.load(absFilename, "PNG");
1729     QImage output = renderToImage();
1730     if (!imageEqual(baseline, output)) {      //krazy:exclude=duoblequote_chars DOM demands chars
1731         QString outputFilename = m_outputDir + "/" + againstFilename;
1732         createMissingDirs(outputFilename);
1733 
1734         bool kf = false;
1735         if (m_known_failures & AllFailure) {
1736             kf = true;
1737         } else if (m_known_failures & PaintFailure) {
1738             kf = true;
1739         }
1740         if (kf) {
1741             outputFilename += "-KF";
1742         }
1743 
1744         output.save(outputFilename, "PNG", 60);
1745     } else {
1746         ::unlink(QFile::encodeName(m_outputDir + "/" + againstFilename));     //krazy:exclude=duoblequote_chars DOM demands chars
1747         result = Success;
1748     }
1749     return result;
1750 }
1751 
1752 RegressionTest::CheckResult RegressionTest::checkOutput(const QString &againstFilename)
1753 {
1754     QString absFilename = QFileInfo(m_baselineDir + "/" + againstFilename).absoluteFilePath();
1755     if (svnIgnored(absFilename)) {
1756         m_known_failures = NoFailure;
1757         return Ignored;
1758     }
1759 
1760     bool domOut = againstFilename.endsWith("-dom");
1761     QString data = getPartOutput(domOut ? DOMTree : RenderTree);
1762     data.remove(char(13));
1763 
1764     CheckResult result = Success;
1765 
1766     // compare result to existing file
1767     QString outputFilename = QFileInfo(m_outputDir + "/" + againstFilename).absoluteFilePath(); //krazy:exclude=duoblequote_chars DOM demands chars
1768     bool kf = false;
1769     if (m_known_failures & AllFailure) {
1770         kf = true;
1771     } else if (domOut && (m_known_failures & DomFailure)) {
1772         kf = true;
1773     } else if (!domOut && (m_known_failures & RenderFailure)) {
1774         kf = true;
1775     }
1776     if (kf) {
1777         outputFilename += "-KF";
1778     }
1779 
1780     if (m_genOutput) {
1781         outputFilename = absFilename;
1782     }
1783 
1784     QFile file(absFilename);
1785     if (file.open(QIODevice::ReadOnly)) {
1786         QTextStream stream(&file);
1787         stream.setCodec("UTF-8");
1788 
1789         QString fileData = stream.readAll();
1790 
1791         result = (fileData == data) ? Success : Failure;
1792         if (!m_genOutput && result == Success) {
1793             ::unlink(QFile::encodeName(outputFilename));
1794             return Success;
1795         }
1796     }
1797 
1798     // generate result file
1799     createMissingDirs(outputFilename);
1800     QFile file2(outputFilename);
1801     if (!file2.open(QIODevice::WriteOnly)) {
1802         fprintf(stderr, "Error writing to file %s\n", qPrintable(outputFilename));
1803         exit(1);
1804     }
1805 
1806     QTextStream stream2(&file2);
1807     stream2.setCodec("UTF-8");
1808     stream2 << data;
1809     if (m_genOutput) {
1810         printf("Generated %s\n", qPrintable(outputFilename));
1811     }
1812 
1813     return result;
1814 }
1815 
1816 bool RegressionTest::reportResult(CheckResult result, const QString &description)
1817 {
1818     if (result == Ignored) {
1819         //printf("IGNORED: ");
1820         //printDescription( description );
1821         return true; // no error
1822     } else {
1823         return reportResult(result == Success, description);
1824     }
1825 }
1826 
1827 bool RegressionTest::reportResult(bool passed, const QString &description)
1828 {
1829     if (m_genOutput) {
1830         return true;
1831     }
1832 
1833     if (passed) {
1834         if (m_known_failures & AllFailure) {
1835             printf("PASS (unexpected!): ");
1836             m_passes_fail++;
1837         } else {
1838             printf("PASS: ");
1839             m_passes_work++;
1840         }
1841     } else {
1842         if (m_known_failures & AllFailure) {
1843             printf("FAIL (known): ");
1844             m_failures_fail++;
1845             passed = true; // we knew about it
1846         } else {
1847             printf("FAIL: ");
1848             m_failures_work++;
1849         }
1850     }
1851 
1852     printDescription(description);
1853     return passed;
1854 }
1855 
1856 void RegressionTest::printDescription(const QString &description)
1857 {
1858     if (!m_currentCategory.isEmpty()) {
1859         printf("%s/", qPrintable(m_currentCategory));
1860     }
1861 
1862     printf("%s", qPrintable(m_currentTest));
1863 
1864     if (!description.isEmpty()) {
1865         QString desc = description;
1866         desc.replace('\n', ' ');
1867         printf(" [%s]", qPrintable(desc));
1868     }
1869 
1870     printf("\n");
1871     fflush(stdout);
1872 }
1873 
1874 void RegressionTest::createMissingDirs(const QString &filename)
1875 {
1876     QFileInfo dif(filename);
1877     QFileInfo dirInfo(dif.path());
1878     if (dirInfo.exists()) {
1879         return;
1880     }
1881 
1882     QStringList pathComponents;
1883     QFileInfo parentDir = dirInfo;
1884     pathComponents.prepend(parentDir.absoluteFilePath());
1885     while (!parentDir.exists()) {
1886         QString parentPath = parentDir.absoluteFilePath();
1887         int slashPos = parentPath.lastIndexOf('/');
1888         if (slashPos < 0) {
1889             break;
1890         }
1891         parentPath = parentPath.left(slashPos);
1892         pathComponents.prepend(parentPath);
1893         parentDir = QFileInfo(parentPath);
1894     }
1895     for (int pathno = 1; pathno < pathComponents.count(); pathno++) {
1896         if (!QFileInfo(pathComponents[pathno]).exists() &&
1897                 !QDir(pathComponents[pathno - 1]).mkdir(pathComponents[pathno])) {
1898             fprintf(stderr, "Error creating directory %s\n", qPrintable(pathComponents[pathno]));
1899             exit(1);
1900         }
1901     }
1902 }
1903 
1904 void RegressionTest::slotOpenURL(const QUrl &url, const KParts::OpenUrlArguments &args, const KParts::BrowserArguments &browserArgs)
1905 {
1906     m_part->setArguments(args);
1907     m_part->browserExtension()->setBrowserArguments(browserArgs);
1908 
1909     PartMonitor pm(m_part);
1910     m_part->openUrl(url);
1911     pm.waitForCompletion();
1912 }
1913 
1914 bool RegressionTest::svnIgnored(const QString &filename)
1915 {
1916     QFileInfo fi(filename);
1917     QString ignoreFilename = fi.path() + "/svnignore";
1918     QFile ignoreFile(ignoreFilename);
1919     if (!ignoreFile.open(QIODevice::ReadOnly)) {
1920         return false;
1921     }
1922 
1923     QTextStream ignoreStream(&ignoreFile);
1924     QString line;
1925     while (!(line = ignoreStream.readLine()).isNull()) {
1926         if (line == fi.fileName()) {
1927             return true;
1928         }
1929     }
1930     ignoreFile.close();
1931     return false;
1932 }
1933 
1934 void RegressionTest::resizeTopLevelWidget(int w, int h)
1935 {
1936     m_part->widget()->parentWidget()->resize(w, h);
1937     m_part->widget()->resize(w, h);
1938     // Since we're not visible, this doesn't have an immediate effect, QWidget posts the event
1939     QApplication::sendPostedEvents(0, QEvent::Resize);
1940 }
1941 
1942 #include "moc_test_regression.cpp"