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