fix tricky regression noticed by Vyacheslav Tokarev on Google Reader.
[kdelibs.git] / khtml / test_regression.cpp
blob4effa3ecca5cf623466d10cbd9d00bd87e0122f0
1 /**
2 * This file is part of the KDE project
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 )
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.
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.
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.
25 #include "test_regression.h"
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>
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>
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>
52 #include <dom/dom_node.h>
53 #include <dom/dom_element.h>
54 #include <dom/dom_text.h>
55 #include <dom/dom_xml.h>
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"
71 #ifdef __GNUC__
72 #warning "Kill this at some point"
73 #endif
77 struct PalInfo
79 QPalette::ColorRole role;
80 quint32 color;
83 PalInfo palInfo[] =
85 {QPalette::Foreground, 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::Background, 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}
104 PalInfo disPalInfo[] =
106 {QPalette::Foreground, 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::Background, 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}
127 class TestStyle: public QWindowsStyle
129 public:
130 TestStyle()
133 virtual void drawControl(ControlElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget) const
135 switch (element)
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:
145 const QStyleOptionSlider* sbOpt = qstyleoption_cast<const QStyleOptionSlider*>(option);
147 if (sbOpt->minimum == sbOpt->maximum)
149 const_cast<QStyleOptionSlider*>(sbOpt)->state ^= QStyle::State_Enabled;
150 if (element == CE_ScrollBarSlider)
151 element = CE_ScrollBarAddPage;
154 if (element == CE_ScrollBarAddPage || element == CE_ScrollBarSubPage)
156 //Fun. in Qt4, the brush offset seems to be sensitive to window position??
157 painter->setBrushOrigin(0,1);
159 break;
161 default: //shaddup
162 break;
165 QWindowsStyle::drawControl(element, option, painter, widget);
168 virtual QRect subControlRect(ComplexControl control, const QStyleOptionComplex* option,
169 SubControl subControl, const QWidget* widget) const
171 QRect rect = QWindowsStyle::subControlRect(control, option, subControl, widget);
173 switch (control)
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;
185 virtual QSize sizeFromContents(ContentsType type, const QStyleOption* option, const QSize& contentsSize, const QWidget* widget) const
187 QSize size = QWindowsStyle::sizeFromContents(type, option, contentsSize, widget);
189 switch (type)
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:
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());
203 default:
204 return size;
209 virtual int pixelMetric(PixelMetric metric, const QStyleOption* option, const QWidget* widget) const
211 if (metric == PM_ButtonMargin)
212 return 7;
213 return QWindowsStyle::pixelMetric(metric, option, widget);
218 const 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
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>
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>
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>
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"
260 using namespace khtml;
261 using namespace DOM;
262 using namespace KJS;
264 static bool visual = false;
265 static pid_t xvfb;
267 // -------------------------------------------------------------------------
269 PartMonitor *PartMonitor::sm_highestMonitor = NULL;
271 PartMonitor::PartMonitor(KHTMLPart *_part)
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);
280 PartMonitor::~PartMonitor()
282 if (this == sm_highestMonitor)
283 sm_highestMonitor = 0;
284 qDeleteAll(m_eventLoopStack);
288 void PartMonitor::waitForCompletion()
290 if (!m_completed) {
292 if (sm_highestMonitor)
293 return;
295 sm_highestMonitor = this;
297 enterLoop();
299 //connect(m_timeout_timer, SIGNAL(timeout()), this, SLOT( timeout() ) );
300 //m_timeout_timer->stop();
301 //m_timeout_timer->start( visual ? 100 : 2, true );
304 QTimer::singleShot( 0, this, SLOT( finishTimers() ) );
305 enterLoop();
308 void PartMonitor::enterLoop()
310 if (m_eventLoopStack.isEmpty() || m_eventLoopStack.top()->isRunning())
311 m_eventLoopStack.push(new QEventLoop());
312 m_eventLoopStack.top()->exec();
315 void PartMonitor::exitLoop()
317 while (!m_eventLoopStack.isEmpty() && !m_eventLoopStack.top()->isRunning())
318 delete m_eventLoopStack.pop();
319 if (!m_eventLoopStack.isEmpty())
320 m_eventLoopStack.top()->exit();
323 void PartMonitor::timeout()
325 exitLoop();
328 void PartMonitor::finishTimers()
330 KJS::Window *w = KJS::Window::retrieveWindow( m_part );
331 --m_timer_waits;
332 if ( m_timer_waits && (w && w->winq->hasTimers()) || m_part->inProgress()) {
333 // wait a bit
334 QTimer::singleShot( 10, this, SLOT(finishTimers() ) );
335 return;
337 exitLoop();
340 void PartMonitor::partCompleted()
342 m_completed = true;
343 m_timeout_timer->stop();
344 connect(m_timeout_timer, SIGNAL(timeout()),this, SLOT( timeout() ) );
345 m_timeout_timer->setSingleShot(true);
346 m_timeout_timer->start(visual ? 100 : 2);
347 disconnect(m_part,SIGNAL(completed()),this,SLOT(partCompleted()));
350 static void signal_handler( int )
352 printf( "timeout\n" );
353 abort();
355 // -------------------------------------------------------------------------
357 RegTestObject::RegTestObject(ExecState *exec, RegressionTest *_regTest)
359 m_regTest = _regTest;
360 putDirect("print",new RegTestFunction(exec,m_regTest,RegTestFunction::Print,1), DontEnum);
361 putDirect("reportResult",new RegTestFunction(exec,m_regTest,RegTestFunction::ReportResult,3), DontEnum);
362 putDirect("checkOutput",new RegTestFunction(exec,m_regTest,RegTestFunction::CheckOutput,1), DontEnum);
363 // add "quit" for compatibility with the mozilla js shell
364 putDirect("quit", new RegTestFunction(exec,m_regTest,RegTestFunction::Quit,1), DontEnum );
367 RegTestFunction::RegTestFunction(ExecState* /*exec*/, RegressionTest *_regTest, int _id, int length)
369 m_regTest = _regTest;
370 id = _id;
371 putDirect("length",length);
374 bool RegTestFunction::implementsCall() const
376 return true;
379 JSValue* RegTestFunction::callAsFunction(ExecState *exec, JSObject* /*thisObj*/, const List &args)
381 JSValue* result = jsUndefined();
382 if ( m_regTest->ignore_errors )
383 return result;
385 switch (id) {
386 case Print: {
387 UString str = args[0]->toString(exec);
388 if ( str.qstring().toLower().indexOf( "failed!" ) >= 0 )
389 m_regTest->saw_failure = true;
390 QString res = str.qstring().replace('\007', "");
391 m_regTest->m_currentOutput += res + "\n"; //krazy:exclude=duoblequote_chars DOM demands chars
392 break;
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 m_regTest->reportResult(passed,description);
400 if ( !passed )
401 m_regTest->saw_failure = true;
402 break;
404 case CheckOutput: {
405 DOM::DocumentImpl* docimpl = static_cast<DOM::DocumentImpl*>( m_regTest->m_part->document().handle() );
406 if ( docimpl && docimpl->view() && docimpl->renderer() )
408 docimpl->updateRendering();
410 QString filename = args[0]->toString(exec).qstring();
411 filename = RegressionTest::curr->m_currentCategory+"/"+filename; //krazy:exclude=duoblequote_chars DOM demands chars
412 int failures = RegressionTest::NoFailure;
413 if ( m_regTest->m_genOutput ) {
414 if ( !m_regTest->reportResult( m_regTest->checkOutput(filename+"-dom"),
415 "Script-generated " + filename + "-dom") )
416 failures |= RegressionTest::DomFailure;
417 if ( !m_regTest->reportResult( m_regTest->checkOutput(filename+"-render"),
418 "Script-generated " + filename + "-render") )
419 failures |= RegressionTest::RenderFailure;
420 } else {
421 // compare with output file
422 if ( !m_regTest->reportResult( m_regTest->checkOutput(filename+"-dom"), "DOM") )
423 failures |= RegressionTest::DomFailure;
424 if ( !m_regTest->reportResult( m_regTest->checkOutput(filename+"-render"), "RENDER") )
425 failures |= RegressionTest::RenderFailure;
427 RegressionTest::curr->doFailureReport( filename, failures );
428 break;
430 case Quit:
431 m_regTest->reportResult(true,
432 "Called quit" );
433 if ( !m_regTest->saw_failure )
434 m_regTest->ignore_errors = true;
435 break;
438 return result;
441 // -------------------------------------------------------------------------
443 KHTMLPartObject::KHTMLPartObject(ExecState *exec, KHTMLPart *_part)
445 m_part = _part;
446 putDirect("openPage", new KHTMLPartFunction(exec,m_part,KHTMLPartFunction::OpenPage,1), DontEnum);
447 putDirect("openPageAsUrl", new KHTMLPartFunction(exec,m_part,KHTMLPartFunction::OpenPageAsUrl,1), DontEnum);
448 putDirect("begin", new KHTMLPartFunction(exec,m_part,KHTMLPartFunction::Begin,1), DontEnum);
449 putDirect("write", new KHTMLPartFunction(exec,m_part,KHTMLPartFunction::Write,1), DontEnum);
450 putDirect("end", new KHTMLPartFunction(exec,m_part,KHTMLPartFunction::End,0), DontEnum);
451 putDirect("executeScript", new KHTMLPartFunction(exec,m_part,KHTMLPartFunction::ExecuteScript,0), DontEnum);
452 putDirect("processEvents", new KHTMLPartFunction(exec,m_part,KHTMLPartFunction::ProcessEvents,0), DontEnum);
455 KJS::JSValue *KHTMLPartObject::winGetter(KJS::ExecState *, KJS::JSObject*, const KJS::Identifier&, const KJS::PropertySlot& slot)
457 KHTMLPartObject* thisObj = static_cast<KHTMLPartObject*>(slot.slotBase());
458 return KJS::Window::retrieveWindow(thisObj->m_part);
461 KJS::JSValue *KHTMLPartObject::docGetter(KJS::ExecState *exec, KJS::JSObject*, const KJS::Identifier&, const KJS::PropertySlot& slot)
463 KHTMLPartObject* thisObj = static_cast<KHTMLPartObject*>(slot.slotBase());
464 return getDOMNode(exec, thisObj->m_part->document().handle());
468 bool KHTMLPartObject::getOwnPropertySlot(KJS::ExecState *exec, const KJS::Identifier& propertyName, KJS::PropertySlot& slot)
470 if (propertyName == "document") {
471 slot.setCustom(this, docGetter);
472 return true;
474 else if (propertyName == "window") {
475 slot.setCustom(this, winGetter);
476 return true;
478 return JSObject::getOwnPropertySlot(exec, propertyName, slot);
481 KHTMLPartFunction::KHTMLPartFunction(ExecState */*exec*/, KHTMLPart *_part, int _id, int length)
483 m_part = _part;
484 id = _id;
485 putDirect("length",length);
488 bool KHTMLPartFunction::implementsCall() const
490 return true;
493 JSValue* KHTMLPartFunction::callAsFunction(ExecState *exec, JSObject*/*thisObj*/, const List &args)
495 JSValue* result = jsUndefined();
497 switch (id) {
498 case OpenPage: {
499 if (args[0]->type() == NullType || args[0]->type() == NullType) {
500 exec->setException(Error::create(exec, GeneralError,"No filename specified"));
501 return jsUndefined();
504 QString filename = args[0]->toString(exec).qstring();
505 QString fullFilename = QFileInfo(RegressionTest::curr->m_currentBase+"/"+filename).absoluteFilePath(); //krazy:exclude=duoblequote_chars DOM demands chars
506 KUrl url;
507 url.setProtocol("file");
508 url.setPath(fullFilename);
509 PartMonitor pm(m_part);
510 m_part->openUrl(url);
511 pm.waitForCompletion();
512 kapp->processEvents(QEventLoop::AllEvents);
513 break;
515 case OpenPageAsUrl: {
516 if (args[0]->type() == NullType || args[0]->type() == UndefinedType) {
517 exec->setException(Error::create(exec, GeneralError,"No filename specified"));
518 return jsUndefined();
520 if (args[1]->type() == NullType || args[1]->type() == UndefinedType) {
521 exec->setException(Error::create(exec, GeneralError,"No url specified"));
522 return jsUndefined();
525 QString filename = args[0]->toString(exec).qstring();
526 QString url = args[1]->toString(exec).qstring();
527 QFile file(RegressionTest::curr->m_currentBase+"/"+filename); //krazy:exclude=duoblequote_chars DOM demands chars
528 if (!file.open(QIODevice::ReadOnly)) {
529 exec->setException(Error::create(exec, GeneralError,
530 qPrintable(QString("Error reading " + filename))));
532 else {
533 QByteArray fileData;
534 QDataStream stream(&fileData,QIODevice::WriteOnly);
535 char buf[1024];
536 int bytesread;
537 while (!file.atEnd()) {
538 bytesread = file.read(buf,1024);
539 stream.writeRawData(buf,bytesread);
541 file.close();
542 QString contents(fileData);
543 PartMonitor pm(m_part);
544 m_part->begin(KUrl( url ));
545 m_part->write(contents);
546 m_part->end();
547 pm.waitForCompletion();
549 kapp->processEvents(QEventLoop::AllEvents);
550 break;
552 case Begin: {
553 QString url = args[0]->toString(exec).qstring();
554 m_part->begin(KUrl( url ));
555 break;
557 case Write: {
558 QString str = args[0]->toString(exec).qstring();
559 m_part->write(str);
560 break;
562 case End: {
563 m_part->end();
564 kapp->processEvents(QEventLoop::AllEvents);
565 break;
567 case ExecuteScript: {
568 QString code = args[0]->toString(exec).qstring();
569 Completion comp;
570 KJSProxy *proxy = m_part->jScript();
571 proxy->evaluate("",0,code,0,&comp);
572 if (comp.complType() == Throw)
573 exec->setException(comp.value());
574 kapp->processEvents(QEventLoop::AllEvents);
575 break;
577 case ProcessEvents: {
578 kapp->processEvents(QEventLoop::AllEvents);
579 break;
583 return result;
586 // -------------------------------------------------------------------------
588 int main(int argc, char *argv[])
590 // forget about any settings
591 passwd* pw = getpwuid( getuid() );
592 if (!pw) {
593 fprintf(stderr, "dang, I don't even know who I am.\n");
594 exit(1);
597 QString kh("/var/tmp/%1_non_existant");
598 kh = kh.arg( pw->pw_name );
599 setenv( "KDEHOME", kh.toLatin1(), 1 );
600 setenv( "LC_ALL", "C", 1 );
601 setenv( "LANG", "C", 1 );
603 // We want KIO to be in the slave-forking mode since
604 // then it'll ask KProtocolInfo::exec for the binary to run,
605 // and we intercept that, limiting the I/O to file://
606 // and the magic data://. See Slave::createSlave in KIO's slave.cpp
607 setenv( "KDE_FORK_SLAVES", "true", 1);
608 signal( SIGALRM, signal_handler );
610 // workaround various Qt crashes by always enforcing a TrueColor visual
611 QApplication::setColorSpec( QApplication::ManyColor );
613 KCmdLineOptions options;
614 options.add("b");
615 options.add("base <base_dir>", ki18n("Directory containing tests, basedir and output directories."));
616 options.add("d");
617 options.add("debug", ki18n("Do not suppress debug output"));
618 options.add("g");
619 options.add("genoutput", ki18n("Regenerate baseline (instead of checking)"));
620 options.add("s");
621 options.add("noshow", ki18n("Do not show the window while running tests"));
622 options.add("t");
623 options.add("test <filename>", ki18n("Only run a single test. Multiple options allowed."));
624 options.add("js", ki18n("Only run .js tests"));
625 options.add("html", ki18n("Only run .html tests"));
626 options.add("noxvfb", ki18n("Do not use Xvfb"));
627 options.add("o");
628 options.add("output <directory>", ki18n("Put output in <directory> instead of <base_dir>/output"));
629 options.add("+[base_dir]", ki18n("Directory containing tests, basedir and output directories. Only regarded if -b is not specified."));
630 options.add("+[testcases]", ki18n("Relative path to testcase, or directory of testcases to be run (equivalent to -t)."));
632 KCmdLineArgs::init(argc, argv, "testregression", 0, ki18n("TestRegression"),
633 "1.0", ki18n("Regression tester for khtml"));
634 KCmdLineArgs::addCmdLineOptions(options);
636 KCmdLineArgs *args = KCmdLineArgs::parsedArgs( );
638 QString baseDir = args->getOption("base");
640 if ( args->count() < 1 && baseDir.isEmpty() ) {
641 KCmdLineArgs::usage();
642 ::exit( 1 );
645 int testcase_index = 0;
646 if (baseDir.isEmpty()) baseDir = args->arg(testcase_index++);
648 QFileInfo bdInfo(baseDir);
649 // font pathes passed to Xvfb must be absolute
650 if (bdInfo.isRelative())
651 baseDir = bdInfo.dir().absolutePath();
653 const char *subdirs[] = {"tests", "baseline", "output", "resources"};
654 for ( int i = 0; i < 3; i++ ) {
655 QFileInfo sourceDir(baseDir + QLatin1Char('/') + QLatin1String(subdirs[i]));
656 if ( !sourceDir.exists() || !sourceDir.isDir() ) {
657 fprintf(stderr,"ERROR: Source directory \"%s\": no such directory.\n", sourceDir.filePath().toLocal8Bit().data());
658 exit(1);
662 if (args->isSet("xvfb"))
664 QString xvfbPath = KStandardDirs::findExe("Xvfb");
665 if ( xvfbPath.isEmpty() ) {
666 fprintf( stderr, "ERROR: We need Xvfb to be installed for reliable results\n" );
667 exit( 1 );
670 QByteArray xvfbPath8 = QFile::encodeName(xvfbPath);
671 QStringList fpaths;
672 fpaths.append(baseDir+"/resources");
674 const char* const fontdirs[] = { "75dpi", "misc", "Type1" };
675 const char* const fontpaths[] = {"/usr/share/fonts/", "/usr/X11/lib/X11/fonts/",
676 "/usr/lib/X11/fonts/", "/usr/share/fonts/X11/" };
678 for (size_t fp=0; fp < sizeof(fontpaths)/sizeof(*fontpaths); ++fp)
679 for (size_t fd=0; fd < sizeof(fontdirs)/sizeof(*fontdirs); ++fd)
680 if (QFile::exists(QLatin1String(fontpaths[fp])+QLatin1String(fontdirs[fd])))
681 if (strcmp(fontdirs[fd] , "Type1"))
682 fpaths.append(QLatin1String(fontpaths[fp])+QLatin1String(fontdirs[fd])+":unscaled");
683 else
684 fpaths.append(QLatin1String(fontpaths[fp])+QLatin1String(fontdirs[fd]));
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 );
693 setenv( "DISPLAY", ":47", 1 );
696 KApplication a;
697 // a.disableAutoDcopRegistration();
698 a.setStyle( new TestStyle );
699 KConfig sc1( "cryptodefaults", KConfig::SimpleConfig );
700 KConfigGroup grp = sc1.group("Warnings");
701 grp.writeEntry( "OnUnencrypted", false );
702 KSharedConfigPtr config = KGlobal::mainComponent().config();
703 grp = config->group("Notification Messages" );
704 grp.writeEntry( "kjscupguard_alarmhandler", true );
705 grp.writeEntry("ReportJSErrors", false);
706 KConfig cfg( "khtmlrc" );
707 grp = cfg.group("HTML Settings");
708 grp.writeEntry( "StandardFont", HTML_DEFAULT_VIEW_SANSSERIF_FONT );
709 grp.writeEntry( "FixedFont", HTML_DEFAULT_VIEW_FIXED_FONT );
710 grp.writeEntry( "SerifFont", HTML_DEFAULT_VIEW_SERIF_FONT );
711 grp.writeEntry( "SansSerifFont", HTML_DEFAULT_VIEW_SANSSERIF_FONT );
712 grp.writeEntry( "CursiveFont", HTML_DEFAULT_VIEW_CURSIVE_FONT );
713 grp.writeEntry( "FantasyFont", HTML_DEFAULT_VIEW_FANTASY_FONT );
714 grp.writeEntry( "MinimumFontSize", HTML_DEFAULT_MIN_FONT_SIZE );
715 grp.writeEntry( "MediumFontSize", 10 );
716 grp.writeEntry( "Fonts", QStringList() );
717 grp.writeEntry( "DefaultEncoding", "" );
718 grp = cfg.group("Java/JavaScript Settings");
719 grp.writeEntry( "WindowOpenPolicy", (int)KHTMLSettings::KJSWindowOpenAllow);
721 cfg.sync();
722 grp.sync();
724 KJS::ScriptInterpreter::turnOffCPUGuard();
726 QPalette pal = a.palette();
727 for (int c = 0; palInfo[c].color; ++c)
729 pal.setColor(QPalette::Active, palInfo[c].role, QColor(palInfo[c].color));
730 pal.setColor(QPalette::Inactive, palInfo[c].role, QColor(palInfo[c].color));
731 pal.setColor(QPalette::Disabled, palInfo[c].role, QColor(disPalInfo[c].color));
733 a.setPalette(pal);
735 int rv = 1;
737 bool outputDebug = args->isSet( "debug" );
739 KConfig dc( "kdebugrc", KConfig::SimpleConfig );
740 static int areas[] = { 1000, 6000, 6005, 6010, 6020, 6030,
741 6031, 6035, 6036, 6040, 6041, 6045,
742 6050, 6060, 6061, 7000, 7006, 170,
743 171, 7101, 7002, 7019, 7027, 7014,
744 7011, 6070, 6080, 6090, 0};
745 for ( int i = 0; areas[i]; ++i ) {
746 grp = dc.group( QString::number( areas[i] ) );
747 grp.writeEntry( "InfoOutput", outputDebug ? 2 : 4 );
748 grp.sync();
750 dc.sync();
752 kClearDebugConfig();
754 // make sure the missing image icon is independent of the icon theme..
755 QByteArray brokenImData = QByteArray::fromBase64(imageMissingIcon);
756 QImage brokenIm;
757 brokenIm.loadFromData(brokenImData);
758 khtml::Cache::brokenPixmap = new QPixmap(QPixmap::fromImage(brokenIm));
760 // create widgets
761 KMainWindow *toplevel = new KMainWindow();
762 KHTMLPart *part = new KHTMLPart( toplevel, 0, KHTMLPart::BrowserViewGUI );
764 toplevel->setCentralWidget( part->widget() );
765 KAcceleratorManager::setNoAccel ( part->widget() );
766 part->setJScriptEnabled(true);
768 part->executeScript(DOM::Node(), ""); // force the part to create an interpreter
769 part->setJavaEnabled(false);
770 part->setPluginsEnabled(false);
772 if (args->isSet("show"))
773 visual = true;
775 a.setTopWidget(part->widget());
776 if ( visual )
777 toplevel->show();
779 // we're not interested
780 toplevel->statusBar()->hide();
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);
790 // run the tests
791 RegressionTest *regressionTest = new RegressionTest(part,
792 baseDir,
793 args->getOption("output"),
794 args->isSet("genoutput"),
795 !args->isSet( "html" ),
796 !args->isSet( "js" ));
797 QObject::connect(part->browserExtension(), SIGNAL(openUrlRequest(const KUrl&, const KParts::OpenUrlArguments&, const KParts::BrowserArguments&)),
798 regressionTest, SLOT(slotOpenURL(const KUrl&, const KParts::OpenUrlArguments&, const KParts::BrowserArguments&)));
799 QObject::connect(part->browserExtension(), SIGNAL(resizeTopLevelWidget( int, int )),
800 regressionTest, SLOT(resizeTopLevelWidget( int, int )));
802 bool result = false;
803 QStringList tests = args->getOptionList("test");
804 // merge testcases specified on command line
805 for (; testcase_index < args->count(); testcase_index++)
806 tests << args->arg(testcase_index);
807 if (tests.count() > 0)
808 foreach (QString test, tests) {
809 result = regressionTest->runTests(test,true);
810 if (!result) break;
812 else
813 result = regressionTest->runTests();
815 if (result) {
816 if (args->isSet("genoutput")) {
817 printf("\nOutput generation completed.\n");
819 else {
820 printf("\nTests completed.\n");
821 printf("Total: %d\n",
822 regressionTest->m_passes_work+
823 regressionTest->m_passes_fail+
824 regressionTest->m_failures_work+
825 regressionTest->m_failures_fail+
826 regressionTest->m_errors);
827 printf("Passes: %d",regressionTest->m_passes_work);
828 if ( regressionTest->m_passes_fail )
829 printf( " (%d unexpected passes)\n", regressionTest->m_passes_fail );
830 else
831 printf( "\n" );
832 printf("Failures: %d",regressionTest->m_failures_work);
833 if ( regressionTest->m_failures_fail )
834 printf( " (%d expected failures)\n", regressionTest->m_failures_fail );
835 else
836 printf( "\n" );
837 if ( regressionTest->m_errors )
838 printf("Errors: %d\n",regressionTest->m_errors);
840 QFile list( regressionTest->m_outputDir + "/links.html" );
841 list.open( QIODevice::WriteOnly|QIODevice::Append );
842 QString link, cl;
843 link = QString( "<hr>%1 failures. (%2 expected failures)" )
844 .arg(regressionTest->m_failures_work )
845 .arg( regressionTest->m_failures_fail );
846 list.write( link.toLatin1(), link.length() );
847 list.close();
851 // Only return a 0 exit code if all tests were successful
852 if (regressionTest->m_failures_work == 0 && regressionTest->m_errors == 0)
853 rv = 0;
855 // cleanup
856 delete regressionTest;
857 delete part;
858 delete toplevel;
860 khtml::Cache::clear();
861 khtml::CSSStyleSelector::clear();
862 khtml::RenderStyle::cleanup();
864 kill( xvfb, SIGINT );
866 return rv;
869 // -------------------------------------------------------------------------
871 RegressionTest *RegressionTest::curr = 0;
873 RegressionTest::RegressionTest(KHTMLPart *part, const QString &baseDir, const QString &outputDir,
874 bool _genOutput, bool runJS, bool runHTML)
875 : QObject(part)
877 m_part = part;
879 m_baseDir = baseDir;
880 m_baseDir = m_baseDir.replace( "//", "/" );
881 if ( m_baseDir.endsWith( "/" ) ) //krazy:exclude=duoblequote_chars DOM demands chars
882 m_baseDir = m_baseDir.left( m_baseDir.length() - 1 );
883 if (outputDir.isEmpty())
884 m_outputDir = m_baseDir + "/output";
885 else {
886 createMissingDirs(outputDir + "/"); //krazy:exclude=duoblequote_chars DOM demands chars
887 m_outputDir = outputDir;
889 m_genOutput = _genOutput;
890 m_runJS = runJS;
891 m_runHTML = runHTML;
892 m_passes_work = m_passes_fail = 0;
893 m_failures_work = m_failures_fail = 0;
894 m_errors = 0;
896 ::unlink( QFile::encodeName( m_outputDir + "/links.html" ) );
897 QFile f( m_outputDir + "/empty.html" );
898 QString s;
899 f.open( QIODevice::WriteOnly | QIODevice::Truncate );
900 s = "<html><body>Follow the white rabbit";
901 f.write( s.toLatin1(), s.length() );
902 f.close();
903 f.setFileName( m_outputDir + "/index.html" );
904 f.open( QIODevice::WriteOnly | QIODevice::Truncate );
905 s = "<html><frameset cols=150,*><frame src=links.html><frame name=content src=empty.html>";
906 f.write( s.toLatin1(), s.length() );
907 f.close();
909 m_paintBuffer = 0;
911 curr = this;
912 m_part->view()->setFrameStyle(QFrame::StyledPanel | QFrame::Plain);
913 resizeTopLevelWidget(800, 598 );
917 static QStringList readListFile( const QString &filename )
919 // Read ignore file for this directory
920 QString ignoreFilename = filename;
921 QFileInfo ignoreInfo(ignoreFilename);
922 QStringList ignoreFiles;
923 if (ignoreInfo.exists()) {
924 QFile ignoreFile(ignoreFilename);
925 if (!ignoreFile.open(QIODevice::ReadOnly)) {
926 fprintf(stderr,"Can't open %s\n",qPrintable(ignoreFilename));
927 exit(1);
929 QTextStream ignoreStream(&ignoreFile);
930 QString line;
931 while (!(line = ignoreStream.readLine()).isNull())
932 ignoreFiles.append(line);
933 ignoreFile.close();
935 return ignoreFiles;
938 RegressionTest::~RegressionTest()
940 delete m_paintBuffer;
943 bool RegressionTest::runTests(QString relPath, bool mustExist, QStringList failureFileList)
945 m_currentOutput.clear();
947 QString fullPath = m_baseDir + "/tests/" + relPath;
949 if (!QFile(fullPath).exists()) {
950 fprintf(stderr,"%s: No such file or directory\n",qPrintable(relPath));
951 return false;
954 QFileInfo info(fullPath);
956 if (!info.exists() && mustExist) {
957 fprintf(stderr,"%s: No such file or directory\n",qPrintable(relPath));
958 return false;
961 if (!info.isReadable() && mustExist) {
962 fprintf(stderr,"%s: Access denied\n",qPrintable(relPath));
963 return false;
966 if (info.isDir()) {
967 QStringList ignoreFiles = readListFile( fullPath + "/ignore" );
968 QStringList failureFiles = readListFile( fullPath + "/KNOWN_FAILURES" );
970 // Run each test in this directory, recusively
971 QDir sourceDir(m_baseDir + "/tests/"+relPath);
972 for (uint fileno = 0; fileno < sourceDir.count(); fileno++) {
973 QString filename = sourceDir[fileno];
974 QString relFilename = relPath.isEmpty() ? filename : relPath+"/"+filename; //krazy:exclude=duoblequote_chars DOM demands chars
976 if (filename == "." || filename == ".." || ignoreFiles.contains(filename) )
977 continue;
979 runTests(relFilename, false, failureFiles);
982 else if (info.isFile()) {
984 alarm( 400 );
986 khtml::Cache::init();
988 QString relativeDir = QFileInfo(relPath).path();
989 QString filename = info.fileName();
990 m_currentBase = m_baseDir + "/tests/"+relativeDir;
991 m_currentCategory = relativeDir;
992 m_currentTest = filename;
994 if (failureFileList.isEmpty() && QFile(info.path() + "/KNOWN_FAILURES").exists()) {
995 failureFileList = readListFile( info.path() + "/KNOWN_FAILURES" );
998 int known_failure = NoFailure;
999 if ( failureFileList.contains( filename ) )
1000 known_failure |= AllFailure;
1001 if ( failureFileList.contains ( filename + "-render" ) )
1002 known_failure |= RenderFailure;
1003 if ( failureFileList.contains ( filename + "-dump.png" ) )
1004 known_failure |= PaintFailure;
1005 if ( failureFileList.contains ( filename + "-dom" ) )
1006 known_failure |= DomFailure;
1008 m_known_failures = known_failure;
1009 if ( filename.endsWith(".html") || filename.endsWith( ".htm" ) || filename.endsWith( ".xhtml" ) || filename.endsWith( ".xml" ) ) {
1010 if ( relPath.startsWith( "domts/" ) && !m_runJS )
1011 return true;
1012 if ( relPath.startsWith( "ecma/" ) && !m_runJS )
1013 return true;
1014 if ( m_runHTML )
1015 testStaticFile(relPath);
1017 else if (filename.endsWith(".js")) {
1018 if ( m_runJS )
1019 testJSFile(relPath);
1021 else if (mustExist) {
1022 fprintf(stderr,"%s: Not a valid test file (must be .htm(l) or .js)\n",qPrintable(relPath));
1023 return false;
1025 } else if (mustExist) {
1026 fprintf(stderr,"%s: Not a regular file\n",qPrintable(relPath));
1027 return false;
1030 return true;
1033 void RegressionTest::getPartDOMOutput( QTextStream &outputStream, KHTMLPart* part, uint indent )
1035 DOM::Node node = part->document();
1036 while (!node.isNull()) {
1037 // process
1039 for (uint i = 0; i < indent; i++)
1040 outputStream << " ";
1042 // Make doctype's visually different from elements
1043 if (node.nodeType() == DOM::Node::DOCUMENT_TYPE_NODE)
1044 outputStream << "!doctype ";
1046 outputStream << node.nodeName().string();
1048 switch (node.nodeType()) {
1049 case DOM::Node::ELEMENT_NODE: {
1050 // Sort strings to ensure consistent output
1051 QStringList attrNames;
1052 NamedNodeMap attrs = node.attributes();
1053 for (uint a = 0; a < attrs.length(); a++)
1054 attrNames.append(attrs.item(a).nodeName().string());
1055 attrNames.sort();
1057 QStringList::iterator it;
1058 Element elem(node);
1059 for (it = attrNames.begin(); it != attrNames.end(); ++it) {
1060 QString name = *it;
1061 QString value = elem.getAttribute(*it).string();
1062 outputStream << " " << name << "=\"" << value << "\"";
1064 if ( node.handle()->id() == ID_FRAME ) {
1065 outputStream << endl;
1066 QString frameName = static_cast<DOM::HTMLFrameElementImpl *>( node.handle() )->name.string();
1067 KHTMLPart* frame = part->findFrame( frameName );
1068 Q_ASSERT( frame );
1069 if ( frame )
1070 getPartDOMOutput( outputStream, frame, indent );
1072 break;
1074 case DOM::Node::ATTRIBUTE_NODE:
1075 // Should not be present in tree
1076 assert(false);
1077 break;
1078 case DOM::Node::TEXT_NODE:
1079 outputStream << " \"" << Text(node).data().string() << "\"";
1080 break;
1081 case DOM::Node::CDATA_SECTION_NODE:
1082 outputStream << " \"" << CDATASection(node).data().string() << "\"";
1083 break;
1084 case DOM::Node::ENTITY_REFERENCE_NODE:
1085 break;
1086 case DOM::Node::ENTITY_NODE:
1087 break;
1088 case DOM::Node::PROCESSING_INSTRUCTION_NODE:
1089 break;
1090 case DOM::Node::COMMENT_NODE:
1091 outputStream << " \"" << Comment(node).data().string() << "\"";
1092 break;
1093 case DOM::Node::DOCUMENT_NODE:
1094 break;
1095 case DOM::Node::DOCUMENT_TYPE_NODE:
1096 break;
1097 case DOM::Node::DOCUMENT_FRAGMENT_NODE:
1098 // Should not be present in tree
1099 assert(false);
1100 break;
1101 case DOM::Node::NOTATION_NODE:
1102 break;
1103 default:
1104 assert(false);
1105 break;
1108 outputStream << endl;
1110 if (!node.firstChild().isNull()) {
1111 node = node.firstChild();
1112 indent++;
1114 else if (!node.nextSibling().isNull()) {
1115 node = node.nextSibling();
1117 else {
1118 while (!node.isNull() && node.nextSibling().isNull()) {
1119 node = node.parentNode();
1120 indent--;
1122 if (!node.isNull())
1123 node = node.nextSibling();
1128 void RegressionTest::dumpRenderTree( QTextStream &outputStream, KHTMLPart* part )
1130 DOM::DocumentImpl* doc = static_cast<DocumentImpl*>( part->document().handle() );
1131 if ( !doc || !doc->renderer() )
1132 return;
1133 doc->renderer()->layer()->dump( outputStream );
1135 // Dump frames if any
1136 // Get list of names instead of frames() to sort the list alphabetically
1137 QStringList names = part->frameNames();
1138 names.sort();
1139 for ( QStringList::iterator it = names.begin(); it != names.end(); ++it ) {
1140 outputStream << "FRAME: " << (*it) << "\n";
1141 KHTMLPart* frame = part->findFrame( (*it) );
1142 // Q_ASSERT( frame );
1143 if ( frame )
1144 dumpRenderTree( outputStream, frame );
1148 QString RegressionTest::getPartOutput( OutputType type)
1150 // dump out the contents of the rendering & DOM trees
1151 QString dump;
1152 QTextStream outputStream(&dump, QIODevice::WriteOnly);
1154 if ( type == RenderTree ) {
1155 dumpRenderTree( outputStream, m_part );
1156 } else {
1157 assert( type == DOMTree );
1158 getPartDOMOutput( outputStream, m_part, 0 );
1161 dump.replace( m_baseDir + "/tests", QLatin1String( "REGRESSION_SRCDIR" ) );
1162 return dump;
1165 QImage RegressionTest::renderToImage()
1167 int ew = m_part->view()->contentsWidth();
1168 int eh = m_part->view()->contentsHeight();
1170 if (ew * eh > 4000 * 4000) // don't DoS us
1171 return QImage();
1173 QImage img( ew, eh, QImage::Format_ARGB32 );
1174 img.fill( 0xff0000 );
1175 if (!m_paintBuffer )
1176 m_paintBuffer = new QPixmap( 512, 128 );
1178 for ( int py = 0; py < eh; py += 128 ) {
1179 for ( int px = 0; px < ew; px += 512 ) {
1180 QPainter* tp = new QPainter;
1181 tp->begin( m_paintBuffer );
1182 tp->translate( -px, -py );
1183 tp->fillRect(px, py, 512, 128, Qt::magenta);
1184 m_part->document().handle()->renderer()->layer()->paint( tp, QRect( px, py, 512, 128 ) );
1185 tp->end();
1186 delete tp;
1188 // now fill the chunk into our image
1189 QImage chunk = m_paintBuffer->toImage();
1190 assert( chunk.depth() == 32 );
1191 for ( int y = 0; y < 128 && py + y < eh; ++y )
1192 memcpy( img.scanLine( py+y ) + px*4, chunk.scanLine( y ), qMin( 512, ew-px )*4 );
1196 assert( img.depth() == 32 );
1197 return img;
1200 bool RegressionTest::imageEqual( const QImage &lhsi, const QImage &rhsi )
1202 if ( lhsi.width() != rhsi.width() || lhsi.height() != rhsi.height() ) {
1203 kDebug() << "dimensions different " << lhsi.size() << " " << rhsi.size();
1204 return false;
1206 int w = lhsi.width();
1207 int h = lhsi.height();
1208 int bytes = lhsi.bytesPerLine();
1210 const unsigned char* origLs = lhsi.bits();
1211 const unsigned char* origRs = rhsi.bits();
1213 for ( int y = 0; y < h; ++y )
1215 const QRgb* ls = (const QRgb*)(origLs + y * bytes);
1216 const QRgb* rs = (const QRgb*)(origRs + y * bytes);
1217 if ( memcmp( ls, rs, bytes ) ) {
1218 for ( int x = 0; x < w; ++x ) {
1219 QRgb l = ls[x];
1220 QRgb r = rs[x];
1221 if ( ( abs( qRed( l ) - qRed(r ) ) < 20 ) &&
1222 ( abs( qGreen( l ) - qGreen(r ) ) < 20 ) &&
1223 ( abs( qBlue( l ) - qBlue(r ) ) < 20 ) )
1224 continue;
1225 kDebug() << "pixel (" << x << ", " << y << ") is different " << QColor( lhsi.pixel ( x, y ) ) << " " << QColor( rhsi.pixel ( x, y ) );
1226 return false;
1231 return true;
1234 void RegressionTest::createLink( const QString& test, int failures )
1236 createMissingDirs( m_outputDir + "/" + test + "-compare.html" ); //krazy:exclude=duoblequote_chars DOM demands chars
1238 QFile list( m_outputDir + "/links.html" );
1239 list.open( QIODevice::WriteOnly|QIODevice::Append );
1240 QString link;
1241 link = QString( "<a href=\"%1\" target=\"content\" title=\"%2\">" )
1242 .arg( test + "-compare.html" )
1243 .arg( test );
1244 link += m_currentTest;
1245 link += "</a> [";
1246 if ( failures & DomFailure )
1247 link += "D"; //krazy:exclude=duoblequote_chars DOM demands chars
1248 if ( failures & RenderFailure )
1249 link += "R"; //krazy:exclude=duoblequote_chars DOM demands chars
1250 if ( failures & PaintFailure )
1251 link += "P"; //krazy:exclude=duoblequote_chars DOM demands chars
1252 link += "]<br>\n";
1253 list.write( link.toLatin1(), link.length() );
1254 list.close();
1257 void RegressionTest::doJavascriptReport( const QString &test )
1259 QFile compare( m_outputDir + "/" + test + "-compare.html" ); //krazy:exclude=duoblequote_chars DOM demands chars
1260 if ( !compare.open( QIODevice::WriteOnly|QIODevice::Truncate ) )
1261 kDebug() << "failed to open " << m_outputDir + "/" + test + "-compare.html"; //krazy:exclude=duoblequote_chars DOM demands chars
1262 QString cl;
1263 cl = QString( "<html><head><title>%1</title>" ).arg( test );
1264 cl += "<body><tt>";
1265 QString text = "\n" + m_currentOutput; //krazy:exclude=duoblequote_chars DOM demands chars
1266 text.replace( '<', "&lt;" );
1267 text.replace( '>', "&gt;" );
1268 text.replace( QRegExp( "\nFAILED" ), "\n<span style='color: red'>FAILED</span>" );
1269 text.replace( QRegExp( "\nFAIL" ), "\n<span style='color: red'>FAIL</span>" );
1270 text.replace( QRegExp( "\nPASSED" ), "\n<span style='color: green'>PASSED</span>" );
1271 text.replace( QRegExp( "\nPASS" ), "\n<span style='color: green'>PASS</span>" );
1272 if ( text.at( 0 ) == '\n' )
1273 text = text.mid( 1, text.length() );
1274 text.replace( '\n', "<br>\n" );
1275 cl += text;
1276 cl += "</tt></body></html>";
1277 compare.write( cl.toLatin1(), cl.length() );
1278 compare.close();
1281 /** returns the path in a way that is relatively reachable from base.
1282 * @param base base directory (must not include trailing slash)
1283 * @param path directory/file to be relatively reached by base
1284 * @return path with all elements replaced by .. and concerning path elements
1285 * to be relatively reachable from base.
1287 static QString makeRelativePath(const QString &base, const QString &path)
1289 QString absBase = QFileInfo(base).absoluteFilePath();
1290 QString absPath = QFileInfo(path).absoluteFilePath();
1291 // kDebug() << "absPath: \"" << absPath << "\"";
1292 // kDebug() << "absBase: \"" << absBase << "\"";
1294 // walk up to common ancestor directory
1295 int pos = 0;
1296 do {
1297 pos++;
1298 int newpos = absBase.indexOf('/', pos);
1299 if (newpos == -1) newpos = absBase.length();
1300 QString cmpPathComp(absPath.unicode() + pos, newpos - pos);
1301 QString cmpBaseComp(absBase.unicode() + pos, newpos - pos);
1302 // kDebug() << "cmpPathComp: \"" << cmpPathComp.string() << "\"";
1303 // kDebug() << "cmpBaseComp: \"" << cmpBaseComp.string() << "\"";
1304 // kDebug() << "pos: " << pos << " newpos: " << newpos;
1305 if (cmpPathComp != cmpBaseComp) { pos--; break; }
1306 pos = newpos;
1307 } while (pos < (int)absBase.length() && pos < (int)absPath.length());
1308 int basepos = pos < (int)absBase.length() ? pos + 1 : pos;
1309 int pathpos = pos < (int)absPath.length() ? pos + 1 : pos;
1311 // kDebug() << "basepos " << basepos << " pathpos " << pathpos;
1313 QString rel;
1315 QString relBase(absBase.unicode() + basepos, absBase.length() - basepos);
1316 QString relPath(absPath.unicode() + pathpos, absPath.length() - pathpos);
1317 // generate as many .. as there are path elements in relBase
1318 if (relBase.length() > 0) {
1319 for (int i = relBase.count('/'); i > 0; --i)
1320 rel += "../";
1321 rel += "..";
1322 if (relPath.length() > 0) rel += "/"; //krazy:exclude=duoblequote_chars DOM demands chars
1324 rel += relPath;
1326 return rel;
1329 static QString getDiff(QString cmdLine)
1331 QProcess p;
1332 p.start(cmdLine, QIODevice::ReadOnly);
1333 p.waitForFinished();
1334 QString text = QString::fromLocal8Bit(p.readAllStandardOutput());
1335 text = text.replace( '<', "&lt;" );
1336 text = text.replace( '>', "&gt;" );
1337 return text;
1340 void RegressionTest::doFailureReport( const QString& test, int failures )
1342 if ( failures == NoFailure ) {
1343 ::unlink( QFile::encodeName( m_outputDir + "/" + test + "-compare.html" ) ); //krazy:exclude=duoblequote_chars DOM demands chars
1344 return;
1347 createLink( test, failures );
1349 if ( failures & JSFailure ) {
1350 doJavascriptReport( test );
1351 return; // no support for both kind
1354 QFile compare( m_outputDir + "/" + test + "-compare.html" ); //krazy:exclude=duoblequote_chars DOM demands chars
1356 QString testFile = QFileInfo(test).fileName();
1358 QString renderDiff;
1359 QString domDiff;
1361 QString relOutputDir = makeRelativePath(m_baseDir, m_outputDir);
1363 // are blocking reads possible with K3Process?
1364 QString pwd = QDir::currentPath();
1365 chdir( QFile::encodeName( m_baseDir ) );
1367 if ( failures & RenderFailure ) {
1368 renderDiff += "<pre>";
1369 renderDiff += getDiff( QString::fromLatin1( "diff -u baseline/%1-render %3/%2-render" )
1370 .arg ( test, test, relOutputDir ) );
1371 renderDiff += "</pre>";
1374 if ( failures & DomFailure ) {
1375 domDiff += "<pre>";
1376 domDiff += getDiff( QString::fromLatin1( "diff -u baseline/%1-dom %3/%2-dom" )
1377 .arg ( test, test, relOutputDir ) );
1378 domDiff += "</pre>";
1381 chdir( QFile::encodeName( pwd ) );
1383 // create a relative path so that it works via web as well. ugly
1384 QString relpath = makeRelativePath(m_outputDir + "/" //krazy:exclude=duoblequote_chars DOM demands chars
1385 + QFileInfo(test).path(), m_baseDir);
1387 compare.open( QIODevice::WriteOnly|QIODevice::Truncate );
1388 QString cl;
1389 cl = QString( "<html><head><title>%1</title>" ).arg( test );
1390 cl += QString( "<script>\n"
1391 "var pics = new Array();\n"
1392 "pics[0]=new Image();\n"
1393 "pics[0].src = '%1';\n"
1394 "pics[1]=new Image();\n"
1395 "pics[1].src = '%2';\n"
1396 "var doflicker = 1;\n"
1397 "var t = 1;\n"
1398 "var lastb=0;\n" )
1399 .arg( relpath+"/baseline/"+test+"-dump.png" )
1400 .arg( testFile+"-dump.png" );
1401 cl += QString( "function toggleVisible(visible) {\n"
1402 " document.getElementById('render').style.visibility= visible == 'render' ? 'visible' : 'hidden';\n"
1403 " document.getElementById('image').style.visibility= visible == 'image' ? 'visible' : 'hidden';\n"
1404 " document.getElementById('dom').style.visibility= visible == 'dom' ? 'visible' : 'hidden';\n"
1405 "}\n"
1406 "function show() { document.getElementById('image').src = pics[t].src; "
1407 "document.getElementById('image').style.borderColor = t && !doflicker ? 'red' : 'gray';\n"
1408 "toggleVisible('image');\n"
1409 "}" );
1410 cl += QString ( "function runSlideShow(){\n"
1411 " document.getElementById('image').src = pics[t].src;\n"
1412 " if (doflicker)\n"
1413 " t = 1 - t;\n"
1414 " setTimeout('runSlideShow()', 200);\n"
1415 "}\n"
1416 "function m(b) { if (b == lastb) return; document.getElementById('b'+b).className='buttondown';\n"
1417 " var e = document.getElementById('b'+lastb);\n"
1418 " if(e) e.className='button';\n"
1419 " lastb = b;\n"
1420 "}\n"
1421 "function showRender() { doflicker=0;toggleVisible('render')\n"
1422 "}\n"
1423 "function showDom() { doflicker=0;toggleVisible('dom')\n"
1424 "}\n"
1425 "</script>\n");
1427 cl += QString ("<style>\n"
1428 ".buttondown { cursor: pointer; padding: 0px 20px; color: white; background-color: blue; border: inset blue 2px;}\n"
1429 ".button { cursor: pointer; padding: 0px 20px; color: black; background-color: white; border: outset blue 2px;}\n"
1430 ".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"
1431 "</style>\n" );
1433 if ( failures & PaintFailure )
1434 cl += QString( "<body onload=\"m(1); show(); runSlideShow();\"" );
1435 else if ( failures & RenderFailure )
1436 cl += QString( "<body onload=\"m(4); toggleVisible('render');\"" );
1437 else
1438 cl += QString( "<body onload=\"m(5); toggleVisible('dom');\"" );
1439 cl += QString(" text=black bgcolor=gray>\n<h1>%3</h1>\n" ).arg( test );
1440 if ( failures & PaintFailure )
1441 cl += QString ( "<span id='b1' class='buttondown' onclick=\"doflicker=1;show();m(1)\">FLICKER</span>&nbsp;\n"
1442 "<span id='b2' class='button' onclick=\"doflicker=0;t=0;show();m(2)\">BASE</span>&nbsp;\n"
1443 "<span id='b3' class='button' onclick=\"doflicker=0;t=1;show();m(3)\">OUT</span>&nbsp;\n" );
1444 if ( renderDiff.length() )
1445 cl += "<span id='b4' class='button' onclick='showRender();m(4)'>R-DIFF</span>&nbsp;\n";
1446 if ( domDiff.length() )
1447 cl += "<span id='b5' class='button' onclick='showDom();m(5);'>D-DIFF</span>&nbsp;\n";
1448 // The test file always exists - except for checkOutput called from *.js files
1449 if ( QFile::exists( m_baseDir + "/tests/"+ test ) )
1450 cl += QString( "<a class=button href=\"%1\">HTML</a>&nbsp;" )
1451 .arg( relpath+"/tests/"+test );
1453 cl += QString( "<hr>"
1454 "<img style='border: solid 5px gray' src=\"%1\" id='image'>" )
1455 .arg( relpath+"/baseline/"+test+"-dump.png" );
1457 cl += "<div id='render' class='diff'>" + renderDiff + "</div>";
1458 cl += "<div id='dom' class='diff'>" + domDiff + "</div>";
1460 cl += "</body></html>";
1461 compare.write( cl.toLatin1(), cl.length() );
1462 compare.close();
1465 void RegressionTest::testStaticFile(const QString & filename)
1467 qDebug("TESTING:%s", filename.toLatin1().data());
1468 resizeTopLevelWidget( 800, 598 ); // restore size
1470 // Set arguments
1471 KParts::OpenUrlArguments args;
1472 if (filename.endsWith(".html") || filename.endsWith(".htm")) args.setMimeType("text/html");
1473 else if (filename.endsWith(".xhtml")) args.setMimeType("application/xhtml+xml");
1474 else if (filename.endsWith(".xml")) args.setMimeType("text/xml");
1475 m_part->setArguments(args);
1476 // load page
1477 KUrl url;
1478 url.setProtocol("file");
1479 url.setPath(QFileInfo(m_baseDir + "/tests/"+filename).absoluteFilePath());
1480 PartMonitor pm(m_part);
1481 m_part->openUrl(url);
1482 pm.waitForCompletion();
1483 m_part->closeUrl();
1485 if ( filename.startsWith( "domts/" ) ) {
1486 QString functionname;
1488 KJS::Completion comp = m_part->jScriptInterpreter()->evaluate(filename, 0, "exposeTestFunctionNames();");
1490 * Error handling
1492 KJS::ExecState *exec = m_part->jScriptInterpreter()->globalExec();
1493 if ( comp.complType() == ReturnValue || comp.complType() == Normal )
1495 if (comp.value() && comp.value()->type() == ObjectType &&
1496 comp.value()->toObject(exec)->className() == "Array" )
1498 JSObject* argArrayObj = comp.value()->toObject(exec);
1499 unsigned int length = argArrayObj->
1500 get(exec, "length")->
1501 toUInt32(exec);
1502 if ( length == 1 )
1503 functionname = argArrayObj->get(exec, 0)->toString(exec).qstring();
1506 if ( functionname.isNull() ) {
1507 kDebug() << "DOM " << filename << " doesn't expose 1 function name - ignoring";
1508 return;
1511 KJS::Completion comp2 = m_part->jScriptInterpreter()->evaluate(filename, 0, "setUpPage(); " + functionname + "();" );
1512 bool success = ( comp2.complType() == ReturnValue || comp2.complType() == Normal );
1513 QString description = "DOMTS";
1514 if ( comp2.complType() == Throw ) {
1515 KJS::JSValue* val = comp2.value();
1516 KJS::JSObject* obj = val->toObject(exec);
1517 if ( obj && obj->hasProperty( exec, "jsUnitMessage" ) )
1518 description = obj->get( exec, "jsUnitMessage" )->toString( exec ).qstring();
1519 else
1520 description = comp2.value()->toString( exec ).qstring();
1522 reportResult( success, description );
1524 if (!success && !m_known_failures)
1525 doFailureReport( filename, JSFailure );
1526 return;
1529 int back_known_failures = m_known_failures;
1531 if ( m_genOutput ) {
1532 if ( m_known_failures & DomFailure)
1533 m_known_failures = AllFailure;
1534 reportResult( checkOutput(filename+"-dom"), "DOM" );
1535 if ( m_known_failures & RenderFailure )
1536 m_known_failures = AllFailure;
1537 reportResult( checkOutput(filename+"-render"), "RENDER" );
1538 if ( m_known_failures & PaintFailure )
1539 m_known_failures = AllFailure;
1540 renderToImage().save(m_baseDir + "/baseline/" + filename + "-dump.png","PNG", 60);
1541 printf("Generated %s\n", qPrintable(QString( m_baseDir + "/baseline/" + filename + "-dump.png" )) );
1542 reportResult( true, "PAINT" );
1543 } else {
1544 int failures = NoFailure;
1546 // compare with output file
1547 if ( m_known_failures & DomFailure)
1548 m_known_failures = AllFailure;
1549 if ( !reportResult( checkOutput(filename+"-dom"), "DOM" ) )
1550 failures |= DomFailure;
1552 if ( m_known_failures & RenderFailure )
1553 m_known_failures = AllFailure;
1554 if ( !reportResult( checkOutput(filename+"-render"), "RENDER" ) )
1555 failures |= RenderFailure;
1557 if ( m_known_failures & PaintFailure )
1558 m_known_failures = AllFailure;
1559 if (!reportResult( checkPaintdump(filename), "PAINT") )
1560 failures |= PaintFailure;
1562 doFailureReport(filename, failures );
1565 m_known_failures = back_known_failures;
1568 void RegressionTest::evalJS( ScriptInterpreter &interp, const QString &filename, bool report_result )
1570 QString fullSourceName = filename;
1571 QFile sourceFile(fullSourceName);
1573 if (!sourceFile.open(QIODevice::ReadOnly)) {
1574 fprintf(stderr,"Error reading file %s\n",qPrintable(fullSourceName));
1575 exit(1);
1578 QTextStream stream ( &sourceFile );
1579 stream.setCodec( "UTF-8" );
1580 QString code = stream.readAll();
1581 sourceFile.close();
1583 saw_failure = false;
1584 ignore_errors = false;
1585 Completion c = interp.evaluate(filename, 0, UString( code ) );
1587 if ( report_result && !ignore_errors) {
1588 bool expected_failure = filename.endsWith( "-n.js" );
1589 if (c.complType() == Throw) {
1590 ExecState* exec = interp.globalExec();
1591 QString errmsg = c.value()->toString(exec).qstring();
1592 if ( !expected_failure ) {
1593 int line = -1;
1594 JSObject* obj = c.value()->toObject(exec);
1595 if (obj)
1596 line = obj->get(exec, "line")->toUInt32(exec);
1597 printf( "ERROR: %s (%s) at line:%d\n",qPrintable(filename), qPrintable(errmsg), line);
1598 doFailureReport( m_currentCategory + "/" + m_currentTest, JSFailure ); //krazy:exclude=duoblequote_chars DOM demands chars
1599 m_errors++;
1600 } else {
1601 reportResult( true, QString( "Expected Failure: %1" ).arg( errmsg ) );
1603 } else if ( saw_failure ) {
1604 if ( !expected_failure )
1605 doFailureReport( m_currentCategory + "/" + m_currentTest, JSFailure ); //krazy:exclude=duoblequote_chars DOM demands chars
1606 reportResult( expected_failure, "saw 'failed!'" );
1607 } else {
1608 reportResult( !expected_failure, "passed" );
1613 class GlobalImp : public JSGlobalObject {
1614 public:
1615 virtual UString className() const { return "global"; }
1618 void RegressionTest::testJSFile(const QString & filename )
1620 qDebug("TEST JS:%s", filename.toLatin1().data());
1621 resizeTopLevelWidget( 800, 598 ); // restore size
1623 // create interpreter
1624 // note: this is different from the interpreter used by the part,
1625 // it contains regression test-specific objects & functions
1626 ProtectedPtr<JSGlobalObject> global(new GlobalImp());
1627 khtml::ChildFrame frame;
1628 frame.m_part = m_part;
1629 ScriptInterpreter interp(global,&frame);
1630 ExecState *exec = interp.globalExec();
1632 global->put(exec, "part", new KHTMLPartObject(exec,m_part));
1633 global->put(exec, "regtest", new RegTestObject(exec,this));
1634 global->put(exec, "debug", new RegTestFunction(exec,this,RegTestFunction::Print,1) );
1635 global->put(exec, "print", new RegTestFunction(exec,this,RegTestFunction::Print,1) );
1637 QStringList dirs = filename.split( '/' );
1638 // NOTE: the basename is of little interest here, but the last basedir change
1639 // isn't taken in account
1640 QString basedir = m_baseDir + "/tests/";
1641 for ( QStringList::ConstIterator it = dirs.begin(); it != dirs.end(); ++it )
1643 if ( ! ::access( QFile::encodeName( basedir + "shell.js" ), R_OK ) )
1644 evalJS( interp, basedir + "shell.js", false );
1645 basedir += *it + "/"; //krazy:exclude=duoblequote_chars DOM demands chars
1647 evalJS( interp, m_baseDir + "/tests/"+ filename, true );
1650 RegressionTest::CheckResult RegressionTest::checkPaintdump(const QString &filename)
1652 QString againstFilename( filename + "-dump.png" );
1653 QString absFilename = QFileInfo(m_baseDir + "/baseline/" + againstFilename).absoluteFilePath();
1654 if ( svnIgnored( absFilename ) ) {
1655 m_known_failures = NoFailure;
1656 return Ignored;
1658 CheckResult result = Failure;
1660 QImage baseline;
1661 baseline.load( absFilename, "PNG");
1662 QImage output = renderToImage();
1663 if ( !imageEqual( baseline, output ) ) { //krazy:exclude=duoblequote_chars DOM demands chars
1664 QString outputFilename = m_outputDir + "/" + againstFilename;
1665 createMissingDirs(outputFilename );
1667 bool kf = false;
1668 if ( m_known_failures & AllFailure )
1669 kf = true;
1670 else if ( m_known_failures & PaintFailure )
1671 kf = true;
1672 if ( kf )
1673 outputFilename += "-KF";
1675 output.save(outputFilename, "PNG", 60);
1677 else {
1678 ::unlink( QFile::encodeName( m_outputDir + "/" + againstFilename ) ); //krazy:exclude=duoblequote_chars DOM demands chars
1679 result = Success;
1681 return result;
1684 RegressionTest::CheckResult RegressionTest::checkOutput(const QString &againstFilename)
1686 QString absFilename = QFileInfo(m_baseDir + "/baseline/" + againstFilename).absoluteFilePath();
1687 if ( svnIgnored( absFilename ) ) {
1688 m_known_failures = NoFailure;
1689 return Ignored;
1692 bool domOut = againstFilename.endsWith( "-dom" );
1693 QString data = getPartOutput( domOut ? DOMTree : RenderTree );
1694 data.remove( char( 13 ) );
1696 CheckResult result = Success;
1698 // compare result to existing file
1699 QString outputFilename = QFileInfo(m_outputDir + "/" + againstFilename).absoluteFilePath(); //krazy:exclude=duoblequote_chars DOM demands chars
1700 bool kf = false;
1701 if ( m_known_failures & AllFailure )
1702 kf = true;
1703 else if ( domOut && ( m_known_failures & DomFailure ) )
1704 kf = true;
1705 else if ( !domOut && ( m_known_failures & RenderFailure ) )
1706 kf = true;
1707 if ( kf )
1708 outputFilename += "-KF";
1710 if ( m_genOutput )
1711 outputFilename = absFilename;
1713 QFile file(absFilename);
1714 if (file.open(QIODevice::ReadOnly)) {
1715 QTextStream stream ( &file );
1716 stream.setCodec( "UTF-8" );
1718 QString fileData = stream.readAll();
1720 result = ( fileData == data ) ? Success : Failure;
1721 if ( !m_genOutput && result == Success ) {
1722 ::unlink( QFile::encodeName( outputFilename ) );
1723 return Success;
1727 // generate result file
1728 createMissingDirs( outputFilename );
1729 QFile file2(outputFilename);
1730 if (!file2.open(QIODevice::WriteOnly)) {
1731 fprintf(stderr,"Error writing to file %s\n",qPrintable(outputFilename));
1732 exit(1);
1735 QTextStream stream2(&file2);
1736 stream2.setCodec( "UTF-8" );
1737 stream2 << data;
1738 if ( m_genOutput )
1739 printf("Generated %s\n", qPrintable(outputFilename));
1741 return result;
1744 bool RegressionTest::reportResult(CheckResult result, const QString & description)
1746 if ( result == Ignored ) {
1747 //printf("IGNORED: ");
1748 //printDescription( description );
1749 return true; // no error
1750 } else
1751 return reportResult( result == Success, description );
1754 bool RegressionTest::reportResult(bool passed, const QString & description)
1756 if (m_genOutput)
1757 return true;
1759 if (passed) {
1760 if ( m_known_failures & AllFailure ) {
1761 printf("PASS (unexpected!): ");
1762 m_passes_fail++;
1763 } else {
1764 printf("PASS: ");
1765 m_passes_work++;
1768 else {
1769 if ( m_known_failures & AllFailure ) {
1770 printf("FAIL (known): ");
1771 m_failures_fail++;
1772 passed = true; // we knew about it
1773 } else {
1774 printf("FAIL: ");
1775 m_failures_work++;
1779 printDescription( description );
1780 return passed;
1783 void RegressionTest::printDescription(const QString& description)
1785 if (!m_currentCategory.isEmpty())
1786 printf("%s/", qPrintable(m_currentCategory));
1788 printf("%s", qPrintable(m_currentTest));
1790 if (!description.isEmpty()) {
1791 QString desc = description;
1792 desc.replace( '\n', ' ' );
1793 printf(" [%s]", qPrintable(desc));
1796 printf("\n");
1797 fflush(stdout);
1800 void RegressionTest::createMissingDirs(const QString & filename)
1802 QFileInfo dif(filename);
1803 QFileInfo dirInfo( dif.path() );
1804 if (dirInfo.exists())
1805 return;
1807 QStringList pathComponents;
1808 QFileInfo parentDir = dirInfo;
1809 pathComponents.prepend(parentDir.absoluteFilePath());
1810 while (!parentDir.exists()) {
1811 QString parentPath = parentDir.absoluteFilePath();
1812 int slashPos = parentPath.lastIndexOf('/');
1813 if (slashPos < 0)
1814 break;
1815 parentPath = parentPath.left(slashPos);
1816 pathComponents.prepend(parentPath);
1817 parentDir = QFileInfo(parentPath);
1819 for (int pathno = 1; pathno < pathComponents.count(); pathno++) {
1820 if (!QFileInfo(pathComponents[pathno]).exists() &&
1821 !QDir(pathComponents[pathno-1]).mkdir(pathComponents[pathno])) {
1822 fprintf(stderr,"Error creating directory %s\n",qPrintable(pathComponents[pathno]));
1823 exit(1);
1828 void RegressionTest::slotOpenURL(const KUrl &url, const KParts::OpenUrlArguments& args, const KParts::BrowserArguments& browserArgs)
1830 m_part->setArguments(args);
1831 m_part->browserExtension()->setBrowserArguments(browserArgs);
1833 PartMonitor pm(m_part);
1834 m_part->openUrl(url);
1835 pm.waitForCompletion();
1838 bool RegressionTest::svnIgnored( const QString &filename )
1840 QFileInfo fi( filename );
1841 QString ignoreFilename = fi.path() + "/svnignore";
1842 QFile ignoreFile(ignoreFilename);
1843 if (!ignoreFile.open(QIODevice::ReadOnly))
1844 return false;
1846 QTextStream ignoreStream(&ignoreFile);
1847 QString line;
1848 while (!(line = ignoreStream.readLine()).isNull()) {
1849 if ( line == fi.fileName() )
1850 return true;
1852 ignoreFile.close();
1853 return false;
1856 void RegressionTest::resizeTopLevelWidget( int w, int h )
1858 m_part->widget()->parentWidget()->resize( w, h );
1859 m_part->widget()->resize( w, h );
1860 // Since we're not visible, this doesn't have an immediate effect, QWidget posts the event
1861 QApplication::sendPostedEvents( 0, QEvent::Resize );
1864 #include "test_regression.moc"