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 <directory> instead of <base_dir>/output"), "directory")); 0623 parser.addOption(QCommandLineOption(QStringList() << "r" << "reference", QCoreApplication::translate("main", "Use <directory> as reference instead of <base_dir>/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('<', "<"); 1303 text.replace('>', ">"); 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('<', "<"); 1381 text = text.replace('>', ">"); 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> \n" 1489 "<span id='b2' class='button' onclick=\"doflicker=0;t=0;show();m(2)\">BASE</span> \n" 1490 "<span id='b3' class='button' onclick=\"doflicker=0;t=1;show();m(3)\">OUT</span> \n"); 1491 if (renderDiff.length()) { 1492 cl += "<span id='b4' class='button' onclick='showRender();m(4)'>R-DIFF</span> \n"; 1493 } 1494 if (domDiff.length()) { 1495 cl += "<span id='b5' class='button' onclick='showDom();m(5);'>D-DIFF</span> \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> ") 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"