Warning: That file was not part of the compilation database. It may have many parsing errors.

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

Warning: That file was not part of the compilation database. It may have many parsing errors.