1 /**
2  * This file is part of the KDE project
3  *
4  * Copyright (C) 2001,2003 Peter Kelly (pmk@post.com)
5  * Copyright (C) 2003,2004 Stephan Kulow (coolo@kde.org)
6  * Copyright (C) 2004 Dirk Mueller ( mueller@kde.org )
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public License
19  * along with this library; see the file COPYING.LIB.  If not, write to
20  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22  *
23  */
24 
25 #include "test_regression.h"
26 
27 #include <stdlib.h>
28 #include <sys/time.h>
29 #include <sys/resource.h>
30 #include <sys/types.h>
31 #include <pwd.h>
32 #include <signal.h>
33 #include <unistd.h>
34 
35 #include <QApplication>
36 #include <kacceleratormanager.h>
37 #include <kconfiggroup.h>
38 
39 #include <QImage>
40 #include <QFile>
41 #include <QEventLoop>
42 #include <stdio.h>
43 
44 #include "css/cssstyleselector.h"
45 #include <dom_string.h>
46 #include "rendering/render_style.h"
47 #include "rendering/render_layer.h"
48 #include "khtmldefaults.h"
49 #include <QProcess>
50 #include <QCommonStyle>
51 #include <QStyleOption>
52 
53 #include <dom/dom_node.h>
54 #include <dom/dom_element.h>
55 #include <dom/dom_text.h>
56 #include <dom/dom_xml.h>
57 
58 //We don't use the default fonts, though, but traditional testregression ones
59 #undef HTML_DEFAULT_VIEW_FONT
60 #undef HTML_DEFAULT_VIEW_FIXED_FONT
61 #undef HTML_DEFAULT_VIEW_SERIF_FONT
62 #undef HTML_DEFAULT_VIEW_SANSSERIF_FONT
63 #undef HTML_DEFAULT_VIEW_CURSIVE_FONT
64 #undef HTML_DEFAULT_VIEW_FANTASY_FONT
65 #define HTML_DEFAULT_VIEW_FONT "helvetica"
66 #define HTML_DEFAULT_VIEW_FIXED_FONT "courier"
67 #define HTML_DEFAULT_VIEW_SERIF_FONT "times"
68 #define HTML_DEFAULT_VIEW_SANSSERIF_FONT "helvetica"
69 #define HTML_DEFAULT_VIEW_CURSIVE_FONT "helvetica"
70 #define HTML_DEFAULT_VIEW_FANTASY_FONT "helvetica"
71 
72 #ifdef __GNUC__
73 #warning "Kill this at some point"
74 #endif
75 
76 struct PalInfo {
77     QPalette::ColorRole role;
78     quint32            color;
79 };
80 
81 PalInfo palInfo[] = {
82     {QPalette::WindowText, 0xff000000},
83     {QPalette::Button, 0xffc0c0c0},
84     {QPalette::Light, 0xffffffff},
85     {QPalette::Midlight, 0xffdfdfdf},
86     {QPalette::Dark, 0xff808080},
87     {QPalette::Mid, 0xffa0a0a4},
88     {QPalette::Text, 0xff000000},
89     {QPalette::BrightText, 0xffffffff},
90     {QPalette::ButtonText, 0xff000000},
91     {QPalette::Base, 0xffffffff},
92     {QPalette::Window, 0xffc0c0c0},
93     {QPalette::Shadow, 0xff000000},
94     {QPalette::Highlight, 0xff000080},
95     {QPalette::HighlightedText, 0xffffffff},
96     {QPalette::Link, 0xff0000ff},
97     {QPalette::LinkVisited, 0xffff00ff},
98     {QPalette::LinkVisited, 0}
99 };
100 
101 PalInfo disPalInfo[] = {
102     {QPalette::WindowText, 0xff808080},
103     {QPalette::Button, 0xffc0c0c0},
104     {QPalette::Light, 0xffffffff},
105     {QPalette::Midlight, 0xffdfdfdf},
106     {QPalette::Dark, 0xff808080},
107     {QPalette::Mid, 0xffa0a0a4},
108     {QPalette::Text, 0xff808080},
109     {QPalette::BrightText, 0xffffffff},
110     {QPalette::ButtonText, 0xff808080},
111     {QPalette::Base, 0xffc0c0c0},
112     {QPalette::Window, 0xffc0c0c0},
113     {QPalette::Shadow, 0xff000000},
114     {QPalette::Highlight, 0xff000080},
115     {QPalette::HighlightedText, 0xffffffff},
116     {QPalette::Link, 0xff0000ff},
117     {QPalette::LinkVisited, 0xffff00ff},
118     {QPalette::LinkVisited, 0}
119 };
120 
121 class TestStyle: public QCommonStyle // was QWindowsStyle in Qt4. TODO: Check if this draws everything we need in Qt5...
122 {
123 public:
TestStyle()124     TestStyle()
125     {}
126 
drawControl(ControlElement element,const QStyleOption * option,QPainter * painter,const QWidget * widget) const127     virtual void drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const
128     {
129         switch (element) {
130         case CE_ScrollBarSubLine:
131         case CE_ScrollBarAddLine:
132         case CE_ScrollBarSubPage:
133         case CE_ScrollBarAddPage:
134         case CE_ScrollBarFirst:
135         case CE_ScrollBarLast:
136         case CE_ScrollBarSlider: {
137             const QStyleOptionSlider *sbOpt = qstyleoption_cast<const QStyleOptionSlider *>(option);
138 
139             if (sbOpt->minimum == sbOpt->maximum) {
140                 const_cast<QStyleOptionSlider *>(sbOpt)->state ^= QStyle::State_Enabled;
141                 if (element == CE_ScrollBarSlider) {
142                     element = CE_ScrollBarAddPage;
143                 }
144             }
145 
146             if (element == CE_ScrollBarAddPage || element == CE_ScrollBarSubPage) {
147                 //Fun. in Qt4, the brush offset seems to be sensitive to window position??
148                 painter->setBrushOrigin(0, 1);
149             }
150             break;
151         }
152         default: //shaddup
153             break;
154         }
155 
156         QCommonStyle::drawControl(element, option, painter, widget);
157     }
158 
subControlRect(ComplexControl control,const QStyleOptionComplex * option,SubControl subControl,const QWidget * widget) const159     virtual QRect subControlRect(ComplexControl control, const QStyleOptionComplex *option,
160                                  SubControl subControl, const QWidget *widget) const
161     {
162         QRect rect = QCommonStyle::subControlRect(control, option, subControl, widget);
163 
164         switch (control) {
165         case CC_ComboBox:
166             if (subControl == SC_ComboBoxEditField) {
167                 return rect.translated(3, 0);
168             } else {
169                 return rect;
170             }
171         default:
172             return rect;
173         }
174     }
175 
sizeFromContents(ContentsType type,const QStyleOption * option,const QSize & contentsSize,const QWidget * widget) const176     virtual QSize sizeFromContents(ContentsType type, const QStyleOption *option, const QSize &contentsSize, const QWidget *widget) const
177     {
178         QSize size = QCommonStyle::sizeFromContents(type, option, contentsSize, widget);
179 
180         switch (type) {
181         case CT_PushButton:
182             return QSize(size.width(), size.height() - 1);
183         case CT_LineEdit:
184             if (widget && widget->parentWidget() && !qstricmp(widget->parentWidget()->metaObject()->className(), "KUrlRequester")) {
185                 return QSize(size.width() + 1, size.height());
186             }
187             return QSize(size.width() + 2, size.height() + 2);
188         case CT_ComboBox: {
189             const QStyleOptionComboBox *cbOpt = qstyleoption_cast<const QStyleOptionComboBox *>(option);
190             Q_UNUSED(cbOpt); // is 'cbOpt' needed at all here?
191             return QSize(qMax(43, size.width() + 6), size.height());
192         }
193         default:
194             return size;
195         }
196 
197     }
198 
pixelMetric(PixelMetric metric,const QStyleOption * option,const QWidget * widget) const199     virtual int pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const
200     {
201         if (metric == PM_ButtonMargin) {
202             return 7;
203         }
204         return QCommonStyle::pixelMetric(metric, option, widget);
205     }
206 
207 };
208 
209 const char *imageMissingIcon =
210     "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAzrAAAM6wHl1kTSAAAB2ElEQVQ4jZWTMWsiQRTHfxlDCln2iFhIClksRUIQC6t8BkurzTewEBGx2GKrICmsUsg218rBWV2xdQqLRVJYhUQkBCKLeN6iixB5d0WyYOKGcP9mmHm83/u/NzMHvOobcMz/6Tfw5/Btc+z7/oWIoJRCKQWwt0ZSSqHr+vddACLyZck44GFc8OnpiX6/z3Q6RSmFYRhUq1UMw9gDqF2AUorRaES73WYymVAsFsnlcnieR6PRwPO8dy3uAcIwxHEcADRNo1qt0mq16PV6ZDIZut0uYRh+Dri7u2O1WlGr1QCwLIsgCMhkMlQqFebzOePx+P1cdgG+7wNQKpWwbRsRodlsslgsOD8/R0R4fHyMdwBwcnKCiHBzc0M6neby8hIRoV6vMxwOERGy2eznDvL5PJqmMRgMmM1mpFIprq6uEBFs20bXdc7OzkgkEvvXGA2uVqth2zamaVIul9E0jeVyiVKKdrtNMplkvV7HAwDK5TLX19c4jsN4PEZEKBQKmKbJdrvl/v4e13UfgAXAwVueEYbhRdwzTiQSvLy8cHt7y+npKZ1O59myrB8RQH10EKcgCDg6OoqSf/L6keJbiNNms8F13QfLsn69Jf+NYlELGpD+grMAgo+H/wARELhn1VB8lwAAAABJRU5ErkJggg==";
211 //r 727816 of oxygen's image-missing, base64'd PNG
212 
213 #include <kio/job.h>
214 #include <kmainwindow.h>
215 #include <kconfig.h>
216 
217 #include <QColor>
218 #include <QCursor>
219 #include <QDir>
220 #include <QPushButton>
221 #include <QString>
222 #include <QTextStream>
223 #include <QFileInfo>
224 #include <QTimer>
225 #include <QStatusBar>
226 
227 #include "dom/dom2_range.h"
228 #include "dom/dom_exception.h"
229 #include "dom/html_document.h"
230 #include "html/htmltokenizer.h"
231 #include "khtml_part.h"
232 #include "khtmlpart_p.h"
233 #include <kparts/browserextension.h>
234 #include <qstandardpaths.h>
235 #include <qcommandlineparser.h>
236 #include <qcommandlineoption.h>
237 
238 #include "khtmlview.h"
239 #include "rendering/render_replaced.h"
240 #include "xml/dom_docimpl.h"
241 #include "html/html_baseimpl.h"
242 #include "dom/dom_doc.h"
243 #include "misc/loader.h"
244 #include "ecma/kjs_dom.h"
245 #include "ecma/kjs_window.h"
246 #include "ecma/kjs_proxy.h"
247 
248 using namespace khtml;
249 using namespace DOM;
250 using namespace KJS;
251 
252 static bool visual = false;
253 static pid_t xvfb;
254 
255 // -------------------------------------------------------------------------
256 
257 PartMonitor *PartMonitor::sm_highestMonitor = NULL;
258 
PartMonitor(KHTMLPart * _part)259 PartMonitor::PartMonitor(KHTMLPart *_part)
260 {
261     m_part = _part;
262     m_completed = false;
263     connect(m_part, SIGNAL(completed()), this, SLOT(partCompleted()));
264     m_timer_waits = 200;
265     m_timeout_timer = new QTimer(this);
266 }
267 
~PartMonitor()268 PartMonitor::~PartMonitor()
269 {
270     if (this == sm_highestMonitor) {
271         sm_highestMonitor = 0;
272     }
273     while (!m_eventLoopStack.isEmpty()) {
274         exitLoop();
275     }
276 }
277 
waitForCompletion()278 void PartMonitor::waitForCompletion()
279 {
280     if (!m_completed) {
281 
282         if (sm_highestMonitor) {
283             return;
284         }
285 
286         sm_highestMonitor = this;
287 
288         enterLoop();
289 
290         //connect(m_timeout_timer, SIGNAL(timeout()), this, SLOT(timeout()) );
291         //m_timeout_timer->stop();
292         //m_timeout_timer->start( visual ? 100 : 2, true );
293     }
294     QTimer::singleShot(0, this, SLOT(finishTimers()));
295     enterLoop();
296 }
297 
enterLoop()298 void PartMonitor::enterLoop()
299 {
300     if (m_eventLoopStack.isEmpty() || m_eventLoopStack.top()->isRunning()) {
301         m_eventLoopStack.push(new QEventLoop());
302     }
303     m_eventLoopStack.top()->exec();
304 }
305 
exitLoop()306 void PartMonitor::exitLoop()
307 {
308     while (!m_eventLoopStack.isEmpty() && !m_eventLoopStack.top()->isRunning()) {
309         delete m_eventLoopStack.pop();
310     }
311     if (!m_eventLoopStack.isEmpty()) {
312         m_eventLoopStack.top()->exit();
313     }
314 }
315 
timeout()316 void PartMonitor::timeout()
317 {
318     exitLoop();
319 }
320 
finishTimers()321 void PartMonitor::finishTimers()
322 {
323 
324     KJS::Window *w = KJS::Window::retrieveWindow(m_part);
325     --m_timer_waits;
326     if (m_timer_waits && ((w && w->winq->hasTimers()) || m_part->inProgress())) {
327         // wait a bit
328         QTimer::singleShot(10, this, SLOT(finishTimers()));
329         return;
330     }
331     exitLoop();
332 }
333 
partCompleted()334 void PartMonitor::partCompleted()
335 {
336     m_completed = true;
337     m_timeout_timer->stop();
338     connect(m_timeout_timer, SIGNAL(timeout()), this, SLOT(timeout()));
339     m_timeout_timer->setSingleShot(true);
340     m_timeout_timer->start(visual ? 100 : 2);
341     disconnect(m_part, SIGNAL(completed()), this, SLOT(partCompleted()));
342 }
343 
signal_handler(int)344 static void signal_handler(int)
345 {
346     printf("timeout - this should *NOT* happen, it is likely the part's completed() signal was not emitted - FIXME!!\n");
347     if (PartMonitor::sm_highestMonitor) {
348         PartMonitor::sm_highestMonitor->exitLoop();
349     } else {
350         abort();
351     }
352 }
353 // -------------------------------------------------------------------------
354 
RegTestObject(ExecState * exec,RegressionTest * _regTest)355 RegTestObject::RegTestObject(ExecState *exec, RegressionTest *_regTest)
356 {
357     m_regTest = _regTest;
358     putDirect("print", new RegTestFunction(exec, m_regTest, RegTestFunction::Print, 1), DontEnum);
359     putDirect("reportResult", new RegTestFunction(exec, m_regTest, RegTestFunction::ReportResult, 3), DontEnum);
360     putDirect("checkOutput", new RegTestFunction(exec, m_regTest, RegTestFunction::CheckOutput, 1), DontEnum);
361     // add "quit" for compatibility with the mozilla js shell
362     putDirect("quit", new RegTestFunction(exec, m_regTest, RegTestFunction::Quit, 1), DontEnum);
363 }
364 
RegTestFunction(ExecState *,RegressionTest * _regTest,int _id,int length)365 RegTestFunction::RegTestFunction(ExecState * /*exec*/, RegressionTest *_regTest, int _id, int length)
366 {
367     m_regTest = _regTest;
368     id = _id;
369     putDirect("length", length);
370 }
371 
implementsCall() const372 bool RegTestFunction::implementsCall() const
373 {
374     return true;
375 }
376 
callAsFunction(ExecState * exec,JSObject *,const List & args)377 JSValue *RegTestFunction::callAsFunction(ExecState *exec, JSObject * /*thisObj*/, const List &args)
378 {
379     JSValue *result = jsUndefined();
380     if (m_regTest->ignore_errors) {
381         return result;
382     }
383 
384     switch (id) {
385     case Print: {
386         UString str = args[0]->toString(exec);
387         if (str.qstring().toLower().indexOf("failed!") >= 0) {
388             m_regTest->saw_failure = true;
389         }
390         QString res = str.qstring().replace('\007', "");
391         m_regTest->m_currentOutput += res + "\n";   //krazy:exclude=duoblequote_chars DOM demands chars
392         break;
393     }
394     case ReportResult: {
395         bool passed = args[0]->toBoolean(exec);
396         QString description = args[1]->toString(exec).qstring();
397         if (args[1]->type() == UndefinedType || args[1]->type() == NullType) {
398             description.clear();
399         }
400         m_regTest->reportResult(passed, description);
401         if (!passed) {
402             m_regTest->saw_failure = true;
403         }
404         break;
405     }
406     case CheckOutput: {
407         DOM::DocumentImpl *docimpl = static_cast<DOM::DocumentImpl *>(m_regTest->m_part->document().handle());
408         if (docimpl && docimpl->view() && docimpl->renderer()) {
409             docimpl->updateRendering();
410         }
411         QString filename = args[0]->toString(exec).qstring();
412         filename = RegressionTest::curr->m_currentCategory + "/" + filename; //krazy:exclude=duoblequote_chars DOM demands chars
413         int failures = RegressionTest::NoFailure;
414         if (m_regTest->m_genOutput) {
415             if (!m_regTest->reportResult(m_regTest->checkOutput(filename + "-dom"),
416                                          "Script-generated " + filename + "-dom")) {
417                 failures |= RegressionTest::DomFailure;
418             }
419             if (!m_regTest->reportResult(m_regTest->checkOutput(filename + "-render"),
420                                          "Script-generated " + filename + "-render")) {
421                 failures |= RegressionTest::RenderFailure;
422             }
423         } else {
424             // compare with output file
425             if (!m_regTest->reportResult(m_regTest->checkOutput(filename + "-dom"), "DOM")) {
426                 failures |= RegressionTest::DomFailure;
427             }
428             if (!m_regTest->reportResult(m_regTest->checkOutput(filename + "-render"), "RENDER")) {
429                 failures |= RegressionTest::RenderFailure;
430             }
431         }
432         RegressionTest::curr->doFailureReport(filename, failures);
433         break;
434     }
435     case Quit:
436         m_regTest->reportResult(true,
437                                 "Called quit");
438         if (!m_regTest->saw_failure) {
439             m_regTest->ignore_errors = true;
440         }
441         break;
442     }
443 
444     return result;
445 }
446 
447 // -------------------------------------------------------------------------
448 
KHTMLPartObject(ExecState * exec,KHTMLPart * _part)449 KHTMLPartObject::KHTMLPartObject(ExecState *exec, KHTMLPart *_part)
450 {
451     m_part = _part;
452     putDirect("openPage", new KHTMLPartFunction(exec, m_part, KHTMLPartFunction::OpenPage, 1), DontEnum);
453     putDirect("openPageAsUrl", new KHTMLPartFunction(exec, m_part, KHTMLPartFunction::OpenPageAsUrl, 1), DontEnum);
454     putDirect("begin",     new KHTMLPartFunction(exec, m_part, KHTMLPartFunction::Begin, 1), DontEnum);
455     putDirect("write",    new KHTMLPartFunction(exec, m_part, KHTMLPartFunction::Write, 1), DontEnum);
456     putDirect("end",    new KHTMLPartFunction(exec, m_part, KHTMLPartFunction::End, 0), DontEnum);
457     putDirect("executeScript", new KHTMLPartFunction(exec, m_part, KHTMLPartFunction::ExecuteScript, 0), DontEnum);
458     putDirect("processEvents", new KHTMLPartFunction(exec, m_part, KHTMLPartFunction::ProcessEvents, 0), DontEnum);
459 }
460 
winGetter(KJS::ExecState *,KJS::JSObject *,const KJS::Identifier &,const KJS::PropertySlot & slot)461 KJS::JSValue *KHTMLPartObject::winGetter(KJS::ExecState *, KJS::JSObject *, const KJS::Identifier &, const KJS::PropertySlot &slot)
462 {
463     KHTMLPartObject *thisObj = static_cast<KHTMLPartObject *>(slot.slotBase());
464     return KJS::Window::retrieveWindow(thisObj->m_part);
465 }
466 
docGetter(KJS::ExecState * exec,KJS::JSObject *,const KJS::Identifier &,const KJS::PropertySlot & slot)467 KJS::JSValue *KHTMLPartObject::docGetter(KJS::ExecState *exec, KJS::JSObject *, const KJS::Identifier &, const KJS::PropertySlot &slot)
468 {
469     KHTMLPartObject *thisObj = static_cast<KHTMLPartObject *>(slot.slotBase());
470     return getDOMNode(exec, thisObj->m_part->document().handle());
471 }
472 
getOwnPropertySlot(KJS::ExecState * exec,const KJS::Identifier & propertyName,KJS::PropertySlot & slot)473 bool KHTMLPartObject::getOwnPropertySlot(KJS::ExecState *exec, const KJS::Identifier &propertyName, KJS::PropertySlot &slot)
474 {
475     if (propertyName == "document") {
476         slot.setCustom(this, docGetter);
477         return true;
478     } else if (propertyName == "window") {
479         slot.setCustom(this, winGetter);
480         return true;
481     }
482     return JSObject::getOwnPropertySlot(exec, propertyName, slot);
483 }
484 
KHTMLPartFunction(ExecState *,KHTMLPart * _part,int _id,int length)485 KHTMLPartFunction::KHTMLPartFunction(ExecState */*exec*/, KHTMLPart *_part, int _id, int length)
486 {
487     m_part = _part;
488     id = _id;
489     putDirect("length", length);
490 }
491 
implementsCall() const492 bool KHTMLPartFunction::implementsCall() const
493 {
494     return true;
495 }
496 
callAsFunction(ExecState * exec,JSObject *,const List & args)497 JSValue *KHTMLPartFunction::callAsFunction(ExecState *exec, JSObject */*thisObj*/, const List &args)
498 {
499     JSValue *result = jsUndefined();
500 
501     switch (id) {
502     case OpenPage: {
503         if (args[0]->type() == NullType || args[0]->type() == NullType) {
504             exec->setException(Error::create(exec, GeneralError, "No filename specified"));
505             return jsUndefined();
506         }
507 
508         QString filename = args[0]->toString(exec).qstring();
509         QString fullFilename = QFileInfo(RegressionTest::curr->m_currentBase + "/" + filename).absoluteFilePath(); //krazy:exclude=duoblequote_chars DOM demands chars
510         QUrl url = QUrl::fromLocalFile(fullFilename);
511         PartMonitor pm(m_part);
512         m_part->openUrl(url);
513         pm.waitForCompletion();
514         qApp->processEvents(QEventLoop::AllEvents);
515         break;
516     }
517     case OpenPageAsUrl: {
518         if (args[0]->type() == NullType || args[0]->type() == UndefinedType) {
519             exec->setException(Error::create(exec, GeneralError, "No filename specified"));
520             return jsUndefined();
521         }
522         if (args[1]->type() == NullType || args[1]->type() == UndefinedType) {
523             exec->setException(Error::create(exec, GeneralError, "No url specified"));
524             return jsUndefined();
525         }
526 
527         QString filename = args[0]->toString(exec).qstring();
528         QString url = args[1]->toString(exec).qstring();
529         QFile file(RegressionTest::curr->m_currentBase + "/" + filename); //krazy:exclude=duoblequote_chars DOM demands chars
530         if (!file.open(QIODevice::ReadOnly)) {
531             exec->setException(Error::create(exec, GeneralError,
532                                              qPrintable(QString("Error reading " + filename))));
533         } else {
534             QByteArray fileData;
535             QDataStream stream(&fileData, QIODevice::WriteOnly);
536             char buf[1024];
537             int bytesread;
538             while (!file.atEnd()) {
539                 bytesread = file.read(buf, 1024);
540                 stream.writeRawData(buf, bytesread);
541             }
542             file.close();
543             QString contents(fileData);
544             PartMonitor pm(m_part);
545             m_part->begin(QUrl(url));
546             m_part->write(contents);
547             m_part->end();
548             pm.waitForCompletion();
549         }
550         qApp->processEvents(QEventLoop::AllEvents);
551         break;
552     }
553     case Begin: {
554         QString url = args[0]->toString(exec).qstring();
555         m_part->begin(QUrl(url));
556         break;
557     }
558     case Write: {
559         QString str = args[0]->toString(exec).qstring();
560         m_part->write(str);
561         break;
562     }
563     case End: {
564         m_part->end();
565         qApp->processEvents(QEventLoop::AllEvents);
566         break;
567     }
568     case ExecuteScript: {
569         QString code = args[0]->toString(exec).qstring();
570         Completion comp;
571         KJSProxy *proxy = m_part->jScript();
572         proxy->evaluate("", 0, code, 0, &comp);
573         if (comp.complType() == Throw) {
574             exec->setException(comp.value());
575         }
576         qApp->processEvents(QEventLoop::AllEvents);
577         break;
578     }
579     case ProcessEvents: {
580         qApp->processEvents(QEventLoop::AllEvents);
581         break;
582     }
583     }
584 
585     return result;
586 }
587 
588 // -------------------------------------------------------------------------
589 
main(int argc,char * argv[])590 int main(int argc, char *argv[])
591 {
592     // forget about any settings
593     passwd *pw = getpwuid(getuid());
594     if (!pw) {
595         fprintf(stderr, "dang, I don't even know who I am.\n");
596         exit(1);
597     }
598 
599     QString kh("/var/tmp/%1_non_existant");
600     kh = kh.arg(pw->pw_name);
601     qputenv("XDG_CONFIG_HOME", kh.toLatin1());
602     qputenv("XDG_DATA_HOME", kh.toLatin1());
603     qputenv("LC_ALL", "C");
604     qputenv("LANG", "C");
605 
606     // We want KIO to be in the slave-forking mode since
607     // then it'll ask KProtocolInfo::exec for the binary to run,
608     // and we intercept that, limiting the I/O to file://
609     // and the magic data://. See Slave::createSlave in KIO's slave.cpp
610     qputenv("KDE_FORK_SLAVES", "true");
611     signal(SIGALRM, signal_handler);
612 
613     QApplication a(argc, argv);
614     // workaround various Qt crashes by always enforcing a TrueColor visual
615     QApplication::setColorSpec(QApplication::ManyColor);
616 
617     QCommandLineParser parser;
618     parser.addHelpOption(QCoreApplication::translate("main", "Regression tester for khtml"));
619     parser.addOption(QCommandLineOption(QStringList() << "b" << "base", QCoreApplication::translate("main", "Directory containing tests, basedir and output directories."), "base_dir"));
620     parser.addOption(QCommandLineOption(QStringList() << "d" << "debug", QCoreApplication::translate("main", "Do not suppress debug output")));
621     parser.addOption(QCommandLineOption(QStringList() << "g" << "genoutput", QCoreApplication::translate("main", "Regenerate baseline (instead of checking)")));
622     parser.addOption(QCommandLineOption(QStringList() << "s" << "noshow", QCoreApplication::translate("main", "Do not show the window while running tests")));
623     parser.addOption(QCommandLineOption(QStringList() << "t" << "test", QCoreApplication::translate("main", "Only run a single test. Multiple options allowed."), "filename"));
624     parser.addOption(QCommandLineOption(QStringList() << "js", QCoreApplication::translate("main", "Only run .js tests")));
625     parser.addOption(QCommandLineOption(QStringList() << "html", QCoreApplication::translate("main", "Only run .html tests")));
626     parser.addOption(QCommandLineOption(QStringList() << "noxvfb", QCoreApplication::translate("main", "Do not use Xvfb")));
627     parser.addOption(QCommandLineOption(QStringList() << "o" << "output", QCoreApplication::translate("main", "Put output in &lt;directory&gt; instead of &lt;base_dir&gt;/output"), "directory"));
628     parser.addOption(QCommandLineOption(QStringList() << "r" << "reference", QCoreApplication::translate("main", "Use &lt;directory&gt; as reference instead of &lt;base_dir&gt;/baseline"), "directory"));
629     parser.addOption(QCommandLineOption(QStringList() << "+[base_dir]", QCoreApplication::translate("main", "Directory containing tests, basedir and output directories. Only regarded if -b is not specified.")));
630     parser.addOption(QCommandLineOption(QStringList() << "+[testcases]", QCoreApplication::translate("main", "Relative path to testcase, or directory of testcases to be run (equivalent to -t).")));
631     parser.process(a);
632 
633     QString baseDir = parser.value("base");
634 
635     if (parser.remainingArguments().count() < 1 && baseDir.isEmpty()) {
636         parser.showHelp();
637         ::exit(1);
638     }
639 
640     int testcase_index = 0;
641     if (baseDir.isEmpty()) {
642         baseDir = parser.remainingArguments().at(testcase_index++);
643     }
644 
645     QFileInfo bdInfo(baseDir);
646     // font pathes passed to Xvfb must be absolute
647     if (bdInfo.isRelative()) {
648         baseDir = bdInfo.dir().absolutePath();
649     }
650 
651     const char *subdirs[] = {"tests", "baseline", "output", "resources"};
652     for (int i = 0; i < 3; i++) {
653         QFileInfo sourceDir(baseDir + QLatin1Char('/') + QLatin1String(subdirs[i]));
654         if (!sourceDir.exists() || !sourceDir.isDir()) {
655             fprintf(stderr, "ERROR: Source directory \"%s\": no such directory.\n", sourceDir.filePath().toLocal8Bit().data());
656             exit(1);
657         }
658     }
659 
660     if (parser.isSet("xvfb")) {
661         QString xvfbPath = QStandardPaths::findExecutable("Xvfb");
662         if (xvfbPath.isEmpty()) {
663             fprintf(stderr, "ERROR: We need Xvfb to be installed for reliable results\n");
664             exit(1);
665         }
666 
667         QByteArray xvfbPath8 = QFile::encodeName(xvfbPath);
668         QStringList fpaths;
669         fpaths.append(baseDir + "/resources");
670 
671         const char *const fontdirs[] = { "75dpi", "misc", "Type1" };
672         const char *const fontpaths[] =  {"/usr/share/fonts/", "/usr/X11/lib/X11/fonts/",
673                                           "/usr/lib/X11/fonts/", "/usr/share/fonts/X11/"
674                                          };
675 
676         for (size_t fp = 0; fp < sizeof(fontpaths) / sizeof(*fontpaths); ++fp)
677             for (size_t fd = 0; fd < sizeof(fontdirs) / sizeof(*fontdirs); ++fd)
678                 if (QFile::exists(QLatin1String(fontpaths[fp]) + QLatin1String(fontdirs[fd]))) {
679                     if (strcmp(fontdirs[fd], "Type1")) {
680                         fpaths.append(QLatin1String(fontpaths[fp]) + QLatin1String(fontdirs[fd]) + ":unscaled");
681                     } else {
682                         fpaths.append(QLatin1String(fontpaths[fp]) + QLatin1String(fontdirs[fd]));
683                     }
684                 }
685 
686         xvfb = fork();
687         if (!xvfb) {
688             QByteArray buffer = fpaths.join(",").toLatin1();
689             execl(xvfbPath8.data(), xvfbPath8.data(), "-once", "-dpi", "100", "-screen", "0",
690                   "1024x768x16", "-ac", "-fp", buffer.data(), ":47", (char *)NULL);
691         }
692 
693         qputenv("DISPLAY", ":47");
694     }
695 
696     a.setStyle(new TestStyle);
697     KConfig sc1("cryptodefaults", KConfig::SimpleConfig);
698     KConfigGroup grp = sc1.group("Warnings");
699     grp.writeEntry("OnUnencrypted",  false);
700     KSharedConfigPtr config = KSharedConfig::openConfig();
701     grp = config->group("Notification Messages");
702     grp.writeEntry("kjscupguard_alarmhandler", true);
703     grp.writeEntry("ReportJSErrors", false);
704     KConfig cfg("khtmlrc");
705     grp = cfg.group("HTML Settings");
706     grp.writeEntry("StandardFont", HTML_DEFAULT_VIEW_SANSSERIF_FONT);
707     grp.writeEntry("FixedFont", HTML_DEFAULT_VIEW_FIXED_FONT);
708     grp.writeEntry("SerifFont", HTML_DEFAULT_VIEW_SERIF_FONT);
709     grp.writeEntry("SansSerifFont", HTML_DEFAULT_VIEW_SANSSERIF_FONT);
710     grp.writeEntry("CursiveFont", HTML_DEFAULT_VIEW_CURSIVE_FONT);
711     grp.writeEntry("FantasyFont", HTML_DEFAULT_VIEW_FANTASY_FONT);
712     grp.writeEntry("MinimumFontSize", HTML_DEFAULT_MIN_FONT_SIZE);
713     grp.writeEntry("MediumFontSize", 10);
714     grp.writeEntry("Fonts", QStringList());
715     grp.writeEntry("DefaultEncoding", "");
716     grp = cfg.group("Java/JavaScript Settings");
717     grp.writeEntry("WindowOpenPolicy", (int)KHTMLSettings::KJSWindowOpenAllow);
718 
719     cfg.sync();
720     grp.sync();
721 
722     KJS::ScriptInterpreter::turnOffCPUGuard();
723 
724     QPalette pal = a.palette();
725     for (int c = 0; palInfo[c].color; ++c) {
726         pal.setColor(QPalette::Active,   palInfo[c].role, QColor(palInfo[c].color));
727         pal.setColor(QPalette::Inactive, palInfo[c].role, QColor(palInfo[c].color));
728         pal.setColor(QPalette::Disabled, palInfo[c].role, QColor(disPalInfo[c].color));
729     }
730     a.setPalette(pal);
731 
732     int rv = 1;
733 
734     bool outputDebug = parser.isSet("debug");
735 
736     KConfig dc("kdebugrc", KConfig::SimpleConfig);
737     static int areas[] = { 1000, 6000, 6005, 6010, 6020, 6030,
738                            6031, 6035, 6036, 6040, 6041, 6045,
739                            6050, 6060, 6061, 7000, 7006, 170,
740                            171, 7101, 7002, 7019, 7027, 7014,
741                            7011, 6070, 6080, 6090, 0
742                          };
743     for (int i = 0; areas[i]; ++i) {
744         grp = dc.group(QString::number(areas[i]));
745         grp.writeEntry("InfoOutput", outputDebug ? 2 : 4);
746         grp.sync();
747     }
748     dc.sync();
749 
750     kClearDebugConfig();
751 
752     // make sure the missing image icon is independent of the icon theme..
753     QByteArray brokenImData = QByteArray::fromBase64(imageMissingIcon);
754     QImage brokenIm;
755     brokenIm.loadFromData(brokenImData);
756     khtml::Cache::brokenPixmap = new QPixmap(QPixmap::fromImage(brokenIm));
757 
758     // create widgets
759     KMainWindow *toplevel = new KMainWindow();
760     KHTMLPart *part = new KHTMLPart(toplevel, 0, KHTMLPart::BrowserViewGUI);
761 
762     toplevel->setCentralWidget(part->widget());
763     KAcceleratorManager::setNoAccel(part->widget());
764     part->setJScriptEnabled(true);
765 
766     part->executeScript(DOM::Node(), ""); // force the part to create an interpreter
767     part->setJavaEnabled(false);
768     part->setPluginsEnabled(false);
769 
770     if (parser.isSet("show")) {
771         visual = true;
772     }
773 
774     //a.setTopWidget(part->widget());
775     if (visual) {
776         toplevel->show();
777     }
778 
779     // we're not interested
780     toplevel->statusBar()->hide();
781 
782     if (!getenv("KDE_DEBUG")) {
783         // set ulimits
784         rlimit vmem_limit = { 512 * 1024 * 1024, RLIM_INFINITY }; // 512Mb Memory should suffice
785         setrlimit(RLIMIT_AS, &vmem_limit);
786         rlimit stack_limit = { 8 * 1024 * 1024, RLIM_INFINITY }; // 8Mb Memory should suffice
787         setrlimit(RLIMIT_STACK, &stack_limit);
788     }
789 
790     // run the tests
791     RegressionTest *regressionTest = new RegressionTest(part,
792             baseDir,
793             parser.argument("output"),
794             parser.argument("reference"),
795             parser.isSet("genoutput"),
796             !parser.isSet("html"),
797             !parser.isSet("js"));
798     QObject::connect(part->browserExtension(), SIGNAL(openUrlRequest(QUrl,KParts::OpenUrlArguments,KParts::BrowserArguments)),
799                      regressionTest, SLOT(slotOpenURL(QUrl,KParts::OpenUrlArguments,KParts::BrowserArguments)));
800     QObject::connect(part->browserExtension(), SIGNAL(resizeTopLevelWidget(int,int)),
801                      regressionTest, SLOT(resizeTopLevelWidget(int,int)));
802 
803     bool result = false;
804     QStringList tests = parser.arguments("test");
805     // merge testcases specified on command line
806     for (; testcase_index < parser.remainingArguments().count(); testcase_index++) {
807         tests << parser.remainingArguments().at(testcase_index);
808     }
809     if (tests.count() > 0)
810         foreach (QString test, tests) {
811             result = regressionTest->runTests(test, true);
812             if (!result) {
813                 break;
814             }
815         }
816     else {
817         result = regressionTest->runTests();
818     }
819 
820     if (result) {
821         if (parser.isSet("genoutput")) {
822             printf("\nOutput generation completed.\n");
823         } else {
824             printf("\nTests completed.\n");
825             printf("Total:    %d\n",
826                    regressionTest->m_passes_work +
827                    regressionTest->m_passes_fail +
828                    regressionTest->m_failures_work +
829                    regressionTest->m_failures_fail +
830                    regressionTest->m_errors);
831             printf("Passes:   %d", regressionTest->m_passes_work);
832             if (regressionTest->m_passes_fail) {
833                 printf(" (%d unexpected passes)\n", regressionTest->m_passes_fail);
834             } else {
835                 printf("\n");
836             }
837             printf("Failures: %d", regressionTest->m_failures_work);
838             if (regressionTest->m_failures_fail) {
839                 printf(" (%d expected failures)\n", regressionTest->m_failures_fail);
840             } else {
841                 printf("\n");
842             }
843             if (regressionTest->m_errors) {
844                 printf("Errors:   %d\n", regressionTest->m_errors);
845             }
846 
847             QFile list(regressionTest->m_outputDir + "/links.html");
848             list.open(QIODevice::WriteOnly | QIODevice::Append);
849             QString link, cl;
850             link = QString("<hr>%1 failures. (%2 expected failures)")
851                    .arg(regressionTest->m_failures_work)
852                    .arg(regressionTest->m_failures_fail);
853             list.write(link.toLatin1(), link.length());
854             list.close();
855         }
856     }
857 
858     // Only return a 0 exit code if all tests were successful
859     if (regressionTest->m_failures_work == 0 && regressionTest->m_errors == 0) {
860         rv = 0;
861     }
862 
863     // cleanup
864     delete regressionTest;
865     delete part;
866     delete toplevel;
867 
868     khtml::Cache::clear();
869     khtml::CSSStyleSelector::clear();
870     khtml::RenderStyle::cleanup();
871 
872     kill(xvfb, SIGINT);
873 
874     return rv;
875 }
876 
877 // -------------------------------------------------------------------------
878 
879 RegressionTest *RegressionTest::curr = 0;
880 
RegressionTest(KHTMLPart * part,const QString & baseDir,const QString & outputDir,const QString & baselineDir,bool _genOutput,bool runJS,bool runHTML)881 RegressionTest::RegressionTest(KHTMLPart *part, const QString &baseDir, const QString &outputDir, const QString &baselineDir,
882                                bool _genOutput, bool runJS, bool runHTML)
883     : QObject(part)
884 {
885     m_part = part;
886 
887     m_baseDir = baseDir;
888     m_baseDir = m_baseDir.replace("//", "/");
889     if (m_baseDir.endsWith("/")) {    //krazy:exclude=duoblequote_chars DOM demands chars
890         m_baseDir = m_baseDir.left(m_baseDir.length() - 1);
891     }
892     if (outputDir.isEmpty()) {
893         m_outputDir = m_baseDir + "/output";
894     } else {
895         createMissingDirs(outputDir + "/"); //krazy:exclude=duoblequote_chars DOM demands chars
896         m_outputDir = outputDir;
897     }
898     m_baselineDir = baselineDir;
899     m_baselineDir = m_baselineDir.replace("//", "/");
900     if (m_baselineDir.endsWith("/")) {
901         m_baselineDir = m_baselineDir.left(m_baselineDir.length() - 1);
902     }
903     if (m_baselineDir.isEmpty()) {
904         m_baselineDir = m_baseDir + "/baseline";
905     } else {
906         createMissingDirs(m_baselineDir + "/");
907     }
908     m_genOutput = _genOutput;
909     m_runJS = runJS;
910     m_runHTML =  runHTML;
911     m_passes_work = m_passes_fail = 0;
912     m_failures_work = m_failures_fail = 0;
913     m_errors = 0;
914 
915     if (!m_genOutput) {
916         ::unlink(QFile::encodeName(m_outputDir + "/links.html"));
917     }
918     QFile f(m_outputDir + "/empty.html");
919     QString s;
920     f.open(QIODevice::WriteOnly | QIODevice::Truncate);
921     s = "<html><body>Follow the white rabbit";
922     f.write(s.toLatin1(), s.length());
923     f.close();
924     f.setFileName(m_outputDir + "/index.html");
925     f.open(QIODevice::WriteOnly | QIODevice::Truncate);
926     s = "<html><frameset cols=150,*><frame src=links.html><frame name=content src=empty.html>";
927     f.write(s.toLatin1(), s.length());
928     f.close();
929 
930     m_paintBuffer = 0;
931 
932     curr = this;
933     m_part->view()->setFrameStyle(QFrame::StyledPanel | QFrame::Plain);
934     resizeTopLevelWidget(800, 598);
935 }
936 
readListFile(const QString & filename)937 static QStringList readListFile(const QString &filename)
938 {
939     // Read ignore file for this directory
940     QString ignoreFilename = filename;
941     QFileInfo ignoreInfo(ignoreFilename);
942     QStringList ignoreFiles;
943     if (ignoreInfo.exists()) {
944         QFile ignoreFile(ignoreFilename);
945         if (!ignoreFile.open(QIODevice::ReadOnly)) {
946             fprintf(stderr, "Can't open %s\n", qPrintable(ignoreFilename));
947             exit(1);
948         }
949         QTextStream ignoreStream(&ignoreFile);
950         QString line;
951         while (!(line = ignoreStream.readLine()).isNull()) {
952             ignoreFiles.append(line);
953         }
954         ignoreFile.close();
955     }
956     return ignoreFiles;
957 }
958 
~RegressionTest()959 RegressionTest::~RegressionTest()
960 {
961     delete m_paintBuffer;
962 }
963 
runTests(QString relPath,bool mustExist,QStringList failureFileList)964 bool RegressionTest::runTests(QString relPath, bool mustExist, QStringList failureFileList)
965 {
966     m_currentOutput.clear();
967 
968     QString fullPath = m_baseDir + "/tests/" + relPath;
969 
970     if (!QFile(fullPath).exists()) {
971         fprintf(stderr, "%s: No such file or directory\n", qPrintable(relPath));
972         return false;
973     }
974 
975     QFileInfo info(fullPath);
976 
977     if (!info.exists() && mustExist) {
978         fprintf(stderr, "%s: No such file or directory\n", qPrintable(relPath));
979         return false;
980     }
981 
982     if (!info.isReadable() && mustExist) {
983         fprintf(stderr, "%s: Access denied\n", qPrintable(relPath));
984         return false;
985     }
986 
987     if (info.isDir()) {
988         QStringList ignoreFiles = readListFile(fullPath + "/ignore");
989         QStringList failureFiles = readListFile(fullPath + "/KNOWN_FAILURES");
990 
991         // Run each test in this directory, recusively
992         QDir sourceDir(m_baseDir + "/tests/" + relPath);
993         for (uint fileno = 0; fileno < sourceDir.count(); fileno++) {
994             QString filename = sourceDir[fileno];
995             QString relFilename = relPath.isEmpty() ? filename : relPath + "/" + filename; //krazy:exclude=duoblequote_chars DOM demands chars
996 
997             if (filename == "." || filename == ".." ||  ignoreFiles.contains(filename)) {
998                 continue;
999             }
1000 
1001             runTests(relFilename, false, failureFiles);
1002         }
1003     } else if (info.isFile()) {
1004 
1005         alarm(12);
1006 
1007         khtml::Cache::init();
1008 
1009         QString relativeDir = QFileInfo(relPath).path();
1010         QString filename = info.fileName();
1011         m_currentBase = m_baseDir + "/tests/" + relativeDir;
1012         m_currentCategory = relativeDir;
1013         m_currentTest = filename;
1014 
1015         if (failureFileList.isEmpty() && QFile(info.path() + "/KNOWN_FAILURES").exists()) {
1016             failureFileList = readListFile(info.path() + "/KNOWN_FAILURES");
1017         }
1018 
1019         int known_failure = NoFailure;
1020         if (failureFileList.contains(filename)) {
1021             known_failure |= AllFailure;
1022         }
1023         if (failureFileList.contains(filename + "-render")) {
1024             known_failure |= RenderFailure;
1025         }
1026         if (failureFileList.contains(filename + "-dump.png")) {
1027             known_failure |= PaintFailure;
1028         }
1029         if (failureFileList.contains(filename + "-dom")) {
1030             known_failure |= DomFailure;
1031         }
1032 
1033         m_known_failures = known_failure;
1034         if (filename.endsWith(".html") || filename.endsWith(".htm") || filename.endsWith(".xhtml") || filename.endsWith(".xml")) {
1035             if (relPath.startsWith("domts/") && !m_runJS) {
1036                 return true;
1037             }
1038             if (relPath.startsWith("ecma/") && !m_runJS) {
1039                 return true;
1040             }
1041             if (m_runHTML) {
1042                 testStaticFile(relPath);
1043             }
1044         } else if (filename.endsWith(".js")) {
1045             if (m_runJS) {
1046                 alarm(120);
1047                 testJSFile(relPath);
1048             }
1049         } else if (mustExist) {
1050             fprintf(stderr, "%s: Not a valid test file (must be .htm(l) or .js)\n", qPrintable(relPath));
1051             return false;
1052         }
1053     } else if (mustExist) {
1054         fprintf(stderr, "%s: Not a regular file\n", qPrintable(relPath));
1055         return false;
1056     }
1057 
1058     return true;
1059 }
1060 
getPartDOMOutput(QTextStream & outputStream,KHTMLPart * part,uint indent)1061 void RegressionTest::getPartDOMOutput(QTextStream &outputStream, KHTMLPart *part, uint indent)
1062 {
1063     DOM::Node node = part->document();
1064     while (!node.isNull()) {
1065         // process
1066 
1067         for (uint i = 0; i < indent; i++) {
1068             outputStream << "  ";
1069         }
1070 
1071         // Make doctype's visually different from elements
1072         if (node.nodeType() == DOM::Node::DOCUMENT_TYPE_NODE) {
1073             outputStream << "!doctype ";
1074         }
1075 
1076         outputStream << node.nodeName().string();
1077 
1078         switch (node.nodeType()) {
1079         case DOM::Node::ELEMENT_NODE: {
1080             // Sort strings to ensure consistent output
1081             QStringList attrNames;
1082             NamedNodeMap attrs = node.attributes();
1083             for (uint a = 0; a < attrs.length(); a++) {
1084                 attrNames.append(attrs.item(a).nodeName().string());
1085             }
1086             attrNames.sort();
1087 
1088             QStringList::iterator it;
1089             Element elem(node);
1090             for (it = attrNames.begin(); it != attrNames.end(); ++it) {
1091                 QString name = *it;
1092                 QString value = elem.getAttribute(*it).string();
1093                 outputStream << " " << name << "=\"" << value << "\"";
1094             }
1095             if (node.handle()->id() == ID_FRAME) {
1096                 outputStream << endl;
1097                 QString frameName = static_cast<DOM::HTMLFrameElementImpl *>(node.handle())->name.string();
1098                 KHTMLPart *frame = part->findFrame(frameName);
1099                 if (frame) {
1100                     getPartDOMOutput(outputStream, frame, indent);
1101                 } else {
1102                     outputStream << "(FRAME NOT FOUND)";
1103                 }
1104             }
1105             break;
1106         }
1107         case DOM::Node::ATTRIBUTE_NODE:
1108             // Should not be present in tree
1109             assert(false);
1110             break;
1111         case DOM::Node::TEXT_NODE:
1112             outputStream << " \"" << Text(node).data().string() << "\"";
1113             break;
1114         case DOM::Node::CDATA_SECTION_NODE:
1115             outputStream << " \"" << CDATASection(node).data().string() << "\"";
1116             break;
1117         case DOM::Node::ENTITY_REFERENCE_NODE:
1118             break;
1119         case DOM::Node::ENTITY_NODE:
1120             break;
1121         case DOM::Node::PROCESSING_INSTRUCTION_NODE:
1122             break;
1123         case DOM::Node::COMMENT_NODE:
1124             outputStream << " \"" << Comment(node).data().string() << "\"";
1125             break;
1126         case DOM::Node::DOCUMENT_NODE:
1127             break;
1128         case DOM::Node::DOCUMENT_TYPE_NODE:
1129             break;
1130         case DOM::Node::DOCUMENT_FRAGMENT_NODE:
1131             // Should not be present in tree
1132             assert(false);
1133             break;
1134         case DOM::Node::NOTATION_NODE:
1135             break;
1136         default:
1137             assert(false);
1138             break;
1139         }
1140 
1141         outputStream << endl;
1142 
1143         if (!node.firstChild().isNull()) {
1144             node = node.firstChild();
1145             indent++;
1146         } else if (!node.nextSibling().isNull()) {
1147             node = node.nextSibling();
1148         } else {
1149             while (!node.isNull() && node.nextSibling().isNull()) {
1150                 node = node.parentNode();
1151                 indent--;
1152             }
1153             if (!node.isNull()) {
1154                 node = node.nextSibling();
1155             }
1156         }
1157     }
1158 }
1159 
dumpRenderTree(QTextStream & outputStream,KHTMLPart * part)1160 void RegressionTest::dumpRenderTree(QTextStream &outputStream, KHTMLPart *part)
1161 {
1162     DOM::DocumentImpl *doc = static_cast<DocumentImpl *>(part->document().handle());
1163     if (!doc || !doc->renderer()) {
1164         return;
1165     }
1166     doc->renderer()->layer()->dump(outputStream);
1167 
1168     // Dump frames if any
1169     // Get list of names instead of frames() to sort the list alphabetically
1170     QStringList names = part->frameNames();
1171     names.sort();
1172     for (QStringList::iterator it = names.begin(); it != names.end(); ++it) {
1173         outputStream << "FRAME: " << (*it) << "\n";
1174         KHTMLPart *frame = part->findFrame((*it));
1175 //  Q_ASSERT( frame );
1176         if (frame) {
1177             dumpRenderTree(outputStream, frame);
1178         }
1179     }
1180 }
1181 
getPartOutput(OutputType type)1182 QString RegressionTest::getPartOutput(OutputType type)
1183 {
1184     // dump out the contents of the rendering & DOM trees
1185     QString dump;
1186     QTextStream outputStream(&dump, QIODevice::WriteOnly);
1187 
1188     if (type == RenderTree) {
1189         dumpRenderTree(outputStream, m_part);
1190     } else {
1191         assert(type == DOMTree);
1192         getPartDOMOutput(outputStream, m_part, 0);
1193     }
1194 
1195     dump.replace(m_baseDir + "/tests", QLatin1String("REGRESSION_SRCDIR"));
1196     return dump;
1197 }
1198 
renderToImage()1199 QImage RegressionTest::renderToImage()
1200 {
1201     int ew = m_part->view()->contentsWidth();
1202     int eh = m_part->view()->contentsHeight();
1203 
1204     if (ew * eh > 4000 * 4000) { // don't DoS us
1205         return QImage();
1206     }
1207 
1208     QImage img(ew, eh, QImage::Format_ARGB32);
1209     img.fill(0xff0000);
1210     if (!m_paintBuffer) {
1211         m_paintBuffer = new QPixmap(512, 128);
1212     }
1213 
1214     for (int py = 0; py < eh; py += 128) {
1215         for (int px = 0; px < ew; px += 512) {
1216             QPainter *tp = new QPainter;
1217             tp->begin(m_paintBuffer);
1218             tp->translate(-px, -py);
1219             tp->fillRect(px, py, 512, 128, Qt::magenta);
1220             m_part->document().handle()->renderer()->layer()->paint(tp, QRect(px, py, 512, 128));
1221             tp->end();
1222             delete tp;
1223 
1224             // now fill the chunk into our image
1225             QImage chunk = m_paintBuffer->toImage();
1226             assert(chunk.depth() == 32);
1227             for (int y = 0; y < 128 && py + y < eh; ++y) {
1228                 memcpy(img.scanLine(py + y) + px * 4, chunk.scanLine(y), qMin(512, ew - px) * 4);
1229             }
1230         }
1231     }
1232 
1233     assert(img.depth() == 32);
1234     return img;
1235 }
1236 
imageEqual(const QImage & lhsi,const QImage & rhsi)1237 bool RegressionTest::imageEqual(const QImage &lhsi, const QImage &rhsi)
1238 {
1239     if (lhsi.width() != rhsi.width() || lhsi.height() != rhsi.height()) {
1240         // qDebug() << "dimensions different " << lhsi.size() << " " << rhsi.size();
1241         return false;
1242     }
1243     int w = lhsi.width();
1244     int h = lhsi.height();
1245     int bytes = lhsi.bytesPerLine();
1246 
1247     const unsigned char *origLs = lhsi.bits();
1248     const unsigned char *origRs = rhsi.bits();
1249 
1250     for (int y = 0; y < h; ++y) {
1251         const QRgb *ls = (const QRgb *)(origLs + y * bytes);
1252         const QRgb *rs = (const QRgb *)(origRs + y * bytes);
1253         if (memcmp(ls, rs, bytes)) {
1254             for (int x = 0; x < w; ++x) {
1255                 QRgb l = ls[x];
1256                 QRgb r = rs[x];
1257                 if ((abs(qRed(l) - qRed(r)) < 20) &&
1258                         (abs(qGreen(l) - qGreen(r)) < 20) &&
1259                         (abs(qBlue(l) - qBlue(r)) < 20)) {
1260                     continue;
1261                 }
1262                 // qDebug() << "pixel (" << x << ", " << y << ") is different " << QColor(  lhsi.pixel (  x, y ) ) << " " << QColor(  rhsi.pixel (  x, y ) );
1263                 return false;
1264             }
1265         }
1266     }
1267 
1268     return true;
1269 }
1270 
createLink(const QString & test,int failures)1271 void RegressionTest::createLink(const QString &test, int failures)
1272 {
1273     createMissingDirs(m_outputDir + "/" + test + "-compare.html");   //krazy:exclude=duoblequote_chars DOM demands chars
1274 
1275     QFile list(m_outputDir + "/links.html");
1276     list.open(QIODevice::WriteOnly | QIODevice::Append);
1277     QString link;
1278     link = QString("<a href=\"%1\" target=\"content\" title=\"%2\">")
1279            .arg(test + "-compare.html")
1280            .arg(test);
1281     link += m_currentTest;
1282     link += "</a> [";
1283     if (failures & DomFailure) {
1284         link += "D";    //krazy:exclude=duoblequote_chars DOM demands chars
1285     }
1286     if (failures & RenderFailure) {
1287         link += "R";    //krazy:exclude=duoblequote_chars DOM demands chars
1288     }
1289     if (failures & PaintFailure) {
1290         link += "P";    //krazy:exclude=duoblequote_chars DOM demands chars
1291     }
1292     link += "]<br>\n";
1293     list.write(link.toLatin1(), link.length());
1294     list.close();
1295 }
1296 
doJavascriptReport(const QString & test)1297 void RegressionTest::doJavascriptReport(const QString &test)
1298 {
1299     QFile compare(m_outputDir + "/" + test + "-compare.html");   //krazy:exclude=duoblequote_chars DOM demands chars
1300     if (!compare.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
1301         qDebug() << "failed to open " << m_outputDir + "/" + test + "-compare.html";    //krazy:exclude=duoblequote_chars DOM demands chars
1302     }
1303     QString cl;
1304     cl = QString("<html><head><title>%1</title>").arg(test);
1305     cl += "<body><tt>";
1306     QString text = "\n" + m_currentOutput;  //krazy:exclude=duoblequote_chars DOM demands chars
1307     text.replace('<', "&lt;");
1308     text.replace('>', "&gt;");
1309     text.replace(QRegExp("\nFAILED"), "\n<span style='color: red'>FAILED</span>");
1310     text.replace(QRegExp("\nFAIL"), "\n<span style='color: red'>FAIL</span>");
1311     text.replace(QRegExp("\nPASSED"), "\n<span style='color: green'>PASSED</span>");
1312     text.replace(QRegExp("\nPASS"), "\n<span style='color: green'>PASS</span>");
1313     if (text.at(0) == '\n') {
1314         text = text.mid(1, text.length());
1315     }
1316     text.replace('\n', "<br>\n");
1317     cl += text;
1318     cl += "</tt></body></html>";
1319     compare.write(cl.toLatin1(), cl.length());
1320     compare.close();
1321 }
1322 
1323 /** returns the path in a way that is relatively reachable from base.
1324  * @param base base directory (must not include trailing slash)
1325  * @param path directory/file to be relatively reached by base
1326  * @return path with all elements replaced by .. and concerning path elements
1327  *  to be relatively reachable from base.
1328  */
makeRelativePath(const QString & base,const QString & path)1329 static QString makeRelativePath(const QString &base, const QString &path)
1330 {
1331     QString absBase = QFileInfo(base).absoluteFilePath();
1332     QString absPath = QFileInfo(path).absoluteFilePath();
1333 //     qDebug() << "absPath: \"" << absPath << "\"";
1334 //     qDebug() << "absBase: \"" << absBase << "\"";
1335 
1336     // walk up to common ancestor directory
1337     int pos = 0;
1338     do {
1339         pos++;
1340         int newpos = absBase.indexOf('/', pos);
1341         if (newpos == -1) {
1342             newpos = absBase.length();
1343         }
1344         QString cmpPathComp(absPath.unicode() + pos, newpos - pos);
1345         QString cmpBaseComp(absBase.unicode() + pos, newpos - pos);
1346 //         qDebug() << "cmpPathComp: \"" << cmpPathComp.string() << "\"";
1347 //         qDebug() << "cmpBaseComp: \"" << cmpBaseComp.string() << "\"";
1348 //         qDebug() << "pos: " << pos << " newpos: " << newpos;
1349         if (cmpPathComp != cmpBaseComp) {
1350             pos--;
1351             break;
1352         }
1353         pos = newpos;
1354     } while (pos < (int)absBase.length() && pos < (int)absPath.length());
1355     int basepos = pos < (int)absBase.length() ? pos + 1 : pos;
1356     int pathpos = pos < (int)absPath.length() ? pos + 1 : pos;
1357 
1358 //     qDebug() << "basepos " << basepos << " pathpos " << pathpos;
1359 
1360     QString rel;
1361     {
1362         QString relBase(absBase.unicode() + basepos, absBase.length() - basepos);
1363         QString relPath(absPath.unicode() + pathpos, absPath.length() - pathpos);
1364         // generate as many .. as there are path elements in relBase
1365         if (relBase.length() > 0) {
1366             for (int i = relBase.count('/'); i > 0; --i) {
1367                 rel += "../";
1368             }
1369             rel += "..";
1370             if (relPath.length() > 0) {
1371                 rel += "/";    //krazy:exclude=duoblequote_chars DOM demands chars
1372             }
1373         }
1374         rel += relPath;
1375     }
1376     return rel;
1377 }
1378 
getDiff(QString cmdLine)1379 static QString getDiff(QString cmdLine)
1380 {
1381     QProcess p;
1382     p.start(cmdLine, QIODevice::ReadOnly);
1383     p.waitForFinished();
1384     QString text = QString::fromLocal8Bit(p.readAllStandardOutput());
1385     text = text.replace('<', "&lt;");
1386     text = text.replace('>', "&gt;");
1387     return text;
1388 }
1389 
doFailureReport(const QString & test,int failures)1390 void RegressionTest::doFailureReport(const QString &test, int failures)
1391 {
1392     if (failures == NoFailure) {
1393         ::unlink(QFile::encodeName(m_outputDir + "/" + test + "-compare.html"));      //krazy:exclude=duoblequote_chars DOM demands chars
1394         return;
1395     }
1396 
1397     createLink(test, failures);
1398 
1399     if (failures & JSFailure) {
1400         doJavascriptReport(test);
1401         return; // no support for both kind
1402     }
1403 
1404     QFile compare(m_outputDir + "/" + test + "-compare.html");   //krazy:exclude=duoblequote_chars DOM demands chars
1405 
1406     QString testFile = QFileInfo(test).fileName();
1407 
1408     QString renderDiff;
1409     QString domDiff;
1410 
1411     QString relOutputDir = makeRelativePath(m_baseDir, m_outputDir);
1412     QString relBaselineDir = makeRelativePath(m_baseDir, m_baselineDir);
1413 
1414     // are blocking reads possible with K3Process?
1415     QString pwd = QDir::currentPath();
1416     chdir(QFile::encodeName(m_baseDir));
1417 
1418     if (failures & RenderFailure) {
1419         renderDiff += "<pre>";
1420         renderDiff += getDiff(QString::fromLatin1("diff -u %4/%1-render %3/%2-render")
1421                               .arg(test, test, relOutputDir, relBaselineDir));
1422         renderDiff += "</pre>";
1423     }
1424 
1425     if (failures & DomFailure) {
1426         domDiff += "<pre>";
1427         domDiff += getDiff(QString::fromLatin1("diff -u %4/%1-dom %3/%2-dom")
1428                            .arg(test, test, relOutputDir, relBaselineDir));
1429         domDiff += "</pre>";
1430     }
1431 
1432     chdir(QFile::encodeName(pwd));
1433 
1434     // create a relative path so that it works via web as well. ugly
1435     QString relpath = makeRelativePath(m_outputDir + "/"    //krazy:exclude=duoblequote_chars DOM demands chars
1436                                        + QFileInfo(test).path(), m_baseDir);
1437 
1438     compare.open(QIODevice::WriteOnly | QIODevice::Truncate);
1439     QString cl;
1440     cl = QString("<html><head><title>%1</title>").arg(test);
1441     cl += QString("<script>\n"
1442                   "var pics = new Array();\n"
1443                   "pics[0]=new Image();\n"
1444                   "pics[0].src = '%1';\n"
1445                   "pics[1]=new Image();\n"
1446                   "pics[1].src = '%2';\n"
1447                   "var doflicker = 1;\n"
1448                   "var t = 1;\n"
1449                   "var lastb=0;\n")
1450           .arg(relpath + "/" + relBaselineDir + "/" + test + "-dump.png")
1451           .arg(testFile + "-dump.png");
1452     cl += QString("function toggleVisible(visible) {\n"
1453                   "     document.getElementById('render').style.visibility= visible == 'render' ? 'visible' : 'hidden';\n"
1454                   "     document.getElementById('image').style.visibility= visible == 'image' ? 'visible' : 'hidden';\n"
1455                   "     document.getElementById('dom').style.visibility= visible == 'dom' ? 'visible' : 'hidden';\n"
1456                   "}\n"
1457                   "function show() { document.getElementById('image').src = pics[t].src; "
1458                   "document.getElementById('image').style.borderColor = t && !doflicker ? 'red' : 'gray';\n"
1459                   "toggleVisible('image');\n"
1460                   "}");
1461     cl += QString("function runSlideShow(){\n"
1462                   "   document.getElementById('image').src = pics[t].src;\n"
1463                   "   if (doflicker)\n"
1464                   "       t = 1 - t;\n"
1465                   "   setTimeout('runSlideShow()', 200);\n"
1466                   "}\n"
1467                   "function m(b) { if (b == lastb) return; document.getElementById('b'+b).className='buttondown';\n"
1468                   "                var e = document.getElementById('b'+lastb);\n"
1469                   "                 if(e) e.className='button';\n"
1470                   "                 lastb = b;\n"
1471                   "}\n"
1472                   "function showRender() { doflicker=0;toggleVisible('render')\n"
1473                   "}\n"
1474                   "function showDom() { doflicker=0;toggleVisible('dom')\n"
1475                   "}\n"
1476                   "</script>\n");
1477 
1478     cl += QString("<style>\n"
1479                   ".buttondown { cursor: pointer; padding: 0px 20px; color: white; background-color: blue; border: inset blue 2px;}\n"
1480                   ".button { cursor: pointer; padding: 0px 20px; color: black; background-color: white; border: outset blue 2px;}\n"
1481                   ".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"
1482                   "</style>\n");
1483 
1484     if (failures & PaintFailure) {
1485         cl += QString("<body onload=\"m(1); show(); runSlideShow();\"");
1486     } else if (failures & RenderFailure) {
1487         cl += QString("<body onload=\"m(4); toggleVisible('render');\"");
1488     } else {
1489         cl += QString("<body onload=\"m(5); toggleVisible('dom');\"");
1490     }
1491     cl += QString(" text=black bgcolor=gray>\n<h1>%3</h1>\n").arg(test);
1492     if (failures & PaintFailure)
1493         cl += QString("<span id='b1' class='buttondown' onclick=\"doflicker=1;show();m(1)\">FLICKER</span>&nbsp;\n"
1494                       "<span id='b2' class='button' onclick=\"doflicker=0;t=0;show();m(2)\">BASE</span>&nbsp;\n"
1495                       "<span id='b3' class='button' onclick=\"doflicker=0;t=1;show();m(3)\">OUT</span>&nbsp;\n");
1496     if (renderDiff.length()) {
1497         cl += "<span id='b4' class='button' onclick='showRender();m(4)'>R-DIFF</span>&nbsp;\n";
1498     }
1499     if (domDiff.length()) {
1500         cl += "<span id='b5' class='button' onclick='showDom();m(5);'>D-DIFF</span>&nbsp;\n";
1501     }
1502     // The test file always exists - except for checkOutput called from *.js files
1503     if (QFile::exists(m_baseDir + "/tests/" + test))
1504         cl += QString("<a class=button href=\"%1\">HTML</a>&nbsp;")
1505               .arg(relpath + "/tests/" + test);
1506 
1507     cl += QString("<hr>"
1508                   "<img style='border: solid 5px gray' src=\"%1\" id='image'>")
1509           .arg(relpath + "/" + relBaselineDir + "/" + test + "-dump.png");
1510 
1511     cl += "<div id='render' class='diff'>" + renderDiff + "</div>";
1512     cl += "<div id='dom' class='diff'>" + domDiff + "</div>";
1513 
1514     cl += "</body></html>";
1515     compare.write(cl.toLatin1(), cl.length());
1516     compare.close();
1517 }
1518 
testStaticFile(const QString & filename)1519 void RegressionTest::testStaticFile(const QString &filename)
1520 {
1521     qDebug("TESTING:%s", filename.toLatin1().data());
1522     resizeTopLevelWidget(800, 598);   // restore size
1523 
1524     // Set arguments
1525     KParts::OpenUrlArguments args;
1526     if (filename.endsWith(".html") || filename.endsWith(".htm")) {
1527         args.setMimeType("text/html");
1528     } else if (filename.endsWith(".xhtml")) {
1529         args.setMimeType("application/xhtml+xml");
1530     } else if (filename.endsWith(".xml")) {
1531         args.setMimeType("text/xml");
1532     }
1533     m_part->setArguments(args);
1534     // load page
1535     QUrl url = QUrl::fromLocalFile(QFileInfo(m_baseDir + "/tests/" + filename).absoluteFilePath());
1536     PartMonitor pm(m_part);
1537     m_part->openUrl(url);
1538     pm.waitForCompletion();
1539     m_part->closeUrl();
1540 
1541     if (filename.startsWith("domts/")) {
1542         QString functionname;
1543 
1544         KJS::Completion comp = m_part->jScriptInterpreter()->evaluate(filename, 0, "exposeTestFunctionNames();");
1545         /*
1546          *  Error handling
1547          */
1548         KJS::ExecState *exec = m_part->jScriptInterpreter()->globalExec();
1549         if (comp.complType() == ReturnValue || comp.complType() == Normal) {
1550             if (comp.value() && comp.value()->type() == ObjectType &&
1551                     comp.value()->toObject(exec)->className() == "Array") {
1552                 JSObject *argArrayObj = comp.value()->toObject(exec);
1553                 unsigned int length = argArrayObj->
1554                                       get(exec, "length")->
1555                                       toUInt32(exec);
1556                 if (length == 1) {
1557                     functionname = argArrayObj->get(exec, 0)->toString(exec).qstring();
1558                 }
1559             }
1560         }
1561         if (functionname.isNull()) {
1562             // qDebug() << "DOM " << filename << " doesn't expose 1 function name - ignoring";
1563             return;
1564         }
1565 
1566         KJS::Completion comp2 = m_part->jScriptInterpreter()->evaluate(filename, 0, QString("setUpPage(); " + functionname + "();"));
1567         bool success = (comp2.complType() == ReturnValue || comp2.complType() == Normal);
1568         QString description = "DOMTS";
1569         if (comp2.complType() == Throw) {
1570             KJS::JSValue  *val = comp2.value();
1571             KJS::JSObject *obj = val->toObject(exec);
1572             if (obj && obj->hasProperty(exec, "jsUnitMessage")) {
1573                 description = obj->get(exec, "jsUnitMessage")->toString(exec).qstring();
1574             } else {
1575                 description = comp2.value()->toString(exec).qstring();
1576             }
1577         }
1578         reportResult(success,  description);
1579 
1580         if (!success && !m_known_failures) {
1581             doFailureReport(filename, JSFailure);
1582         }
1583         return;
1584     }
1585 
1586     int back_known_failures = m_known_failures;
1587 
1588     if (m_genOutput) {
1589         if (m_known_failures & DomFailure) {
1590             m_known_failures = AllFailure;
1591         }
1592         reportResult(checkOutput(filename + "-dom"), "DOM");
1593         if (m_known_failures & RenderFailure) {
1594             m_known_failures = AllFailure;
1595         }
1596         reportResult(checkOutput(filename + "-render"), "RENDER");
1597         if (m_known_failures & PaintFailure) {
1598             m_known_failures = AllFailure;
1599         }
1600         renderToImage().save(m_baselineDir + "/" + filename + "-dump.png", "PNG", 60);
1601         printf("Generated %s\n", qPrintable(QString(m_baselineDir + "/" + filename + "-dump.png")));
1602         reportResult(true, "PAINT");
1603     } else {
1604         int failures = NoFailure;
1605 
1606         // compare with output file
1607         if (m_known_failures & DomFailure) {
1608             m_known_failures = AllFailure;
1609         }
1610         if (!reportResult(checkOutput(filename + "-dom"), "DOM")) {
1611             failures |= DomFailure;
1612         }
1613 
1614         if (m_known_failures & RenderFailure) {
1615             m_known_failures = AllFailure;
1616         }
1617         if (!reportResult(checkOutput(filename + "-render"), "RENDER")) {
1618             failures |= RenderFailure;
1619         }
1620 
1621         if (m_known_failures & PaintFailure) {
1622             m_known_failures = AllFailure;
1623         }
1624         if (!reportResult(checkPaintdump(filename), "PAINT")) {
1625             failures |= PaintFailure;
1626         }
1627 
1628         doFailureReport(filename, failures);
1629     }
1630 
1631     m_known_failures = back_known_failures;
1632 }
1633 
evalJS(ScriptInterpreter & interp,const QString & filename,bool report_result)1634 void RegressionTest::evalJS(ScriptInterpreter &interp, const QString &filename, bool report_result)
1635 {
1636     QString fullSourceName = filename;
1637     QFile sourceFile(fullSourceName);
1638 
1639     if (!sourceFile.open(QIODevice::ReadOnly)) {
1640         fprintf(stderr, "Error reading file %s\n", qPrintable(fullSourceName));
1641         exit(1);
1642     }
1643 
1644     QTextStream stream(&sourceFile);
1645     stream.setCodec("UTF-8");
1646     QString code = stream.readAll();
1647     sourceFile.close();
1648 
1649     saw_failure = false;
1650     ignore_errors = false;
1651     Completion c = interp.evaluate(filename, 0, UString(code));
1652 
1653     if (report_result && !ignore_errors) {
1654         bool expected_failure = filename.endsWith("-n.js");
1655         if (c.complType() == Throw) {
1656             ExecState *exec = interp.globalExec();
1657             QString errmsg = c.value()->toString(exec).qstring();
1658             if (!expected_failure) {
1659                 int line = -1;
1660                 JSObject *obj = c.value()->toObject(exec);
1661                 if (obj) {
1662                     line = obj->get(exec, "line")->toUInt32(exec);
1663                 }
1664                 printf("ERROR: %s (%s) at line:%d\n", qPrintable(filename), qPrintable(errmsg), line);
1665                 doFailureReport(m_currentCategory + "/" + m_currentTest, JSFailure);     //krazy:exclude=duoblequote_chars DOM demands chars
1666                 m_errors++;
1667             } else {
1668                 reportResult(true, QString("Expected Failure: %1").arg(errmsg));
1669             }
1670         } else if (saw_failure) {
1671             if (!expected_failure) {
1672                 doFailureReport(m_currentCategory + "/" + m_currentTest, JSFailure);    //krazy:exclude=duoblequote_chars DOM demands chars
1673             }
1674             reportResult(expected_failure, "saw 'failed!'");
1675         } else {
1676             reportResult(!expected_failure, "passed");
1677         }
1678     }
1679 }
1680 
1681 class GlobalImp : public JSGlobalObject
1682 {
1683 public:
className() const1684     virtual UString className() const
1685     {
1686         return "global";
1687     }
1688 };
1689 
testJSFile(const QString & filename)1690 void RegressionTest::testJSFile(const QString &filename)
1691 {
1692     qDebug("TEST JS:%s", filename.toLatin1().data());
1693     resizeTopLevelWidget(800, 598);   // restore size
1694 
1695     // create interpreter
1696     // note: this is different from the interpreter used by the part,
1697     // it contains regression test-specific objects & functions
1698     ProtectedPtr<JSGlobalObject> global(new GlobalImp());
1699     khtml::ChildFrame frame;
1700     frame.m_part = m_part;
1701     ScriptInterpreter interp(global, &frame);
1702     ExecState *exec = interp.globalExec();
1703 
1704     global->put(exec, "part", new KHTMLPartObject(exec, m_part));
1705     global->put(exec, "regtest", new RegTestObject(exec, this));
1706     global->put(exec, "debug", new RegTestFunction(exec, this, RegTestFunction::Print, 1));
1707     global->put(exec, "print", new RegTestFunction(exec, this, RegTestFunction::Print, 1));
1708 
1709     QStringList dirs = filename.split('/');
1710     // NOTE: the basename is of little interest here, but the last basedir change
1711     // isn't taken in account
1712     QString basedir =  m_baseDir + "/tests/";
1713     foreach (const QString &it, dirs) {
1714         if (! ::access(QFile::encodeName(basedir + "shell.js"), R_OK)) {
1715             evalJS(interp, basedir + "shell.js", false);
1716         }
1717         basedir += it + "/";    //krazy:exclude=duoblequote_chars DOM demands chars
1718     }
1719     evalJS(interp, m_baseDir + "/tests/" + filename, true);
1720 }
1721 
checkPaintdump(const QString & filename)1722 RegressionTest::CheckResult RegressionTest::checkPaintdump(const QString &filename)
1723 {
1724     QString againstFilename(filename + "-dump.png");
1725     QString absFilename = QFileInfo(m_baselineDir + "/" + againstFilename).absoluteFilePath();
1726     if (svnIgnored(absFilename)) {
1727         m_known_failures = NoFailure;
1728         return Ignored;
1729     }
1730     CheckResult result = Failure;
1731 
1732     QImage baseline;
1733     baseline.load(absFilename, "PNG");
1734     QImage output = renderToImage();
1735     if (!imageEqual(baseline, output)) {      //krazy:exclude=duoblequote_chars DOM demands chars
1736         QString outputFilename = m_outputDir + "/" + againstFilename;
1737         createMissingDirs(outputFilename);
1738 
1739         bool kf = false;
1740         if (m_known_failures & AllFailure) {
1741             kf = true;
1742         } else if (m_known_failures & PaintFailure) {
1743             kf = true;
1744         }
1745         if (kf) {
1746             outputFilename += "-KF";
1747         }
1748 
1749         output.save(outputFilename, "PNG", 60);
1750     } else {
1751         ::unlink(QFile::encodeName(m_outputDir + "/" + againstFilename));     //krazy:exclude=duoblequote_chars DOM demands chars
1752         result = Success;
1753     }
1754     return result;
1755 }
1756 
checkOutput(const QString & againstFilename)1757 RegressionTest::CheckResult RegressionTest::checkOutput(const QString &againstFilename)
1758 {
1759     QString absFilename = QFileInfo(m_baselineDir + "/" + againstFilename).absoluteFilePath();
1760     if (svnIgnored(absFilename)) {
1761         m_known_failures = NoFailure;
1762         return Ignored;
1763     }
1764 
1765     bool domOut = againstFilename.endsWith("-dom");
1766     QString data = getPartOutput(domOut ? DOMTree : RenderTree);
1767     data.remove(char(13));
1768 
1769     CheckResult result = Success;
1770 
1771     // compare result to existing file
1772     QString outputFilename = QFileInfo(m_outputDir + "/" + againstFilename).absoluteFilePath(); //krazy:exclude=duoblequote_chars DOM demands chars
1773     bool kf = false;
1774     if (m_known_failures & AllFailure) {
1775         kf = true;
1776     } else if (domOut && (m_known_failures & DomFailure)) {
1777         kf = true;
1778     } else if (!domOut && (m_known_failures & RenderFailure)) {
1779         kf = true;
1780     }
1781     if (kf) {
1782         outputFilename += "-KF";
1783     }
1784 
1785     if (m_genOutput) {
1786         outputFilename = absFilename;
1787     }
1788 
1789     QFile file(absFilename);
1790     if (file.open(QIODevice::ReadOnly)) {
1791         QTextStream stream(&file);
1792         stream.setCodec("UTF-8");
1793 
1794         QString fileData = stream.readAll();
1795 
1796         result = (fileData == data) ? Success : Failure;
1797         if (!m_genOutput && result == Success) {
1798             ::unlink(QFile::encodeName(outputFilename));
1799             return Success;
1800         }
1801     }
1802 
1803     // generate result file
1804     createMissingDirs(outputFilename);
1805     QFile file2(outputFilename);
1806     if (!file2.open(QIODevice::WriteOnly)) {
1807         fprintf(stderr, "Error writing to file %s\n", qPrintable(outputFilename));
1808         exit(1);
1809     }
1810 
1811     QTextStream stream2(&file2);
1812     stream2.setCodec("UTF-8");
1813     stream2 << data;
1814     if (m_genOutput) {
1815         printf("Generated %s\n", qPrintable(outputFilename));
1816     }
1817 
1818     return result;
1819 }
1820 
reportResult(CheckResult result,const QString & description)1821 bool RegressionTest::reportResult(CheckResult result, const QString &description)
1822 {
1823     if (result == Ignored) {
1824         //printf("IGNORED: ");
1825         //printDescription( description );
1826         return true; // no error
1827     } else {
1828         return reportResult(result == Success, description);
1829     }
1830 }
1831 
reportResult(bool passed,const QString & description)1832 bool RegressionTest::reportResult(bool passed, const QString &description)
1833 {
1834     if (m_genOutput) {
1835         return true;
1836     }
1837 
1838     if (passed) {
1839         if (m_known_failures & AllFailure) {
1840             printf("PASS (unexpected!): ");
1841             m_passes_fail++;
1842         } else {
1843             printf("PASS: ");
1844             m_passes_work++;
1845         }
1846     } else {
1847         if (m_known_failures & AllFailure) {
1848             printf("FAIL (known): ");
1849             m_failures_fail++;
1850             passed = true; // we knew about it
1851         } else {
1852             printf("FAIL: ");
1853             m_failures_work++;
1854         }
1855     }
1856 
1857     printDescription(description);
1858     return passed;
1859 }
1860 
printDescription(const QString & description)1861 void RegressionTest::printDescription(const QString &description)
1862 {
1863     if (!m_currentCategory.isEmpty()) {
1864         printf("%s/", qPrintable(m_currentCategory));
1865     }
1866 
1867     printf("%s", qPrintable(m_currentTest));
1868 
1869     if (!description.isEmpty()) {
1870         QString desc = description;
1871         desc.replace('\n', ' ');
1872         printf(" [%s]", qPrintable(desc));
1873     }
1874 
1875     printf("\n");
1876     fflush(stdout);
1877 }
1878 
createMissingDirs(const QString & filename)1879 void RegressionTest::createMissingDirs(const QString &filename)
1880 {
1881     QFileInfo dif(filename);
1882     QFileInfo dirInfo(dif.path());
1883     if (dirInfo.exists()) {
1884         return;
1885     }
1886 
1887     QStringList pathComponents;
1888     QFileInfo parentDir = dirInfo;
1889     pathComponents.prepend(parentDir.absoluteFilePath());
1890     while (!parentDir.exists()) {
1891         QString parentPath = parentDir.absoluteFilePath();
1892         int slashPos = parentPath.lastIndexOf('/');
1893         if (slashPos < 0) {
1894             break;
1895         }
1896         parentPath = parentPath.left(slashPos);
1897         pathComponents.prepend(parentPath);
1898         parentDir = QFileInfo(parentPath);
1899     }
1900     for (int pathno = 1; pathno < pathComponents.count(); pathno++) {
1901         if (!QFileInfo(pathComponents[pathno]).exists() &&
1902                 !QDir(pathComponents[pathno - 1]).mkdir(pathComponents[pathno])) {
1903             fprintf(stderr, "Error creating directory %s\n", qPrintable(pathComponents[pathno]));
1904             exit(1);
1905         }
1906     }
1907 }
1908 
slotOpenURL(const QUrl & url,const KParts::OpenUrlArguments & args,const KParts::BrowserArguments & browserArgs)1909 void RegressionTest::slotOpenURL(const QUrl &url, const KParts::OpenUrlArguments &args, const KParts::BrowserArguments &browserArgs)
1910 {
1911     m_part->setArguments(args);
1912     m_part->browserExtension()->setBrowserArguments(browserArgs);
1913 
1914     PartMonitor pm(m_part);
1915     m_part->openUrl(url);
1916     pm.waitForCompletion();
1917 }
1918 
svnIgnored(const QString & filename)1919 bool RegressionTest::svnIgnored(const QString &filename)
1920 {
1921     QFileInfo fi(filename);
1922     QString ignoreFilename = fi.path() + "/svnignore";
1923     QFile ignoreFile(ignoreFilename);
1924     if (!ignoreFile.open(QIODevice::ReadOnly)) {
1925         return false;
1926     }
1927 
1928     QTextStream ignoreStream(&ignoreFile);
1929     QString line;
1930     while (!(line = ignoreStream.readLine()).isNull()) {
1931         if (line == fi.fileName()) {
1932             return true;
1933         }
1934     }
1935     ignoreFile.close();
1936     return false;
1937 }
1938 
resizeTopLevelWidget(int w,int h)1939 void RegressionTest::resizeTopLevelWidget(int w, int h)
1940 {
1941     m_part->widget()->parentWidget()->resize(w, h);
1942     m_part->widget()->resize(w, h);
1943     // Since we're not visible, this doesn't have an immediate effect, QWidget posts the event
1944     QApplication::sendPostedEvents(0, QEvent::Resize);
1945 }
1946 
1947