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"
29 #include <sys/resource.h>
30 #include <sys/types.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>
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"
72 #warning "Kill this at some point"
79 QPalette::ColorRole role
;
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
133 virtual void drawControl(ControlElement element
, const QStyleOption
* option
, QPainter
* painter
, const QWidget
* widget
) const
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);
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
);
176 if (subControl
== SC_ComboBoxEditField
)
177 return rect
.translated(3,0);
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
);
192 return QSize(size
.width(), size
.height() - 1);
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);
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());
209 virtual int pixelMetric(PixelMetric metric
, const QStyleOption
* option
, const QWidget
* widget
) const
211 if (metric
== PM_ButtonMargin
)
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
223 #include <kcmdlineargs.h>
225 #include <kmainwindow.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
;
264 static bool visual
= false;
267 // -------------------------------------------------------------------------
269 PartMonitor
*PartMonitor::sm_highestMonitor
= NULL
;
271 PartMonitor::PartMonitor(KHTMLPart
*_part
)
275 connect(m_part
,SIGNAL(completed()),this,SLOT(partCompleted()));
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()
292 if (sm_highestMonitor
)
295 sm_highestMonitor
= this;
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() ) );
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()
328 void PartMonitor::finishTimers()
330 KJS::Window
*w
= KJS::Window::retrieveWindow( m_part
);
332 if ( m_timer_waits
&& (w
&& w
->winq
->hasTimers()) || m_part
->inProgress()) {
334 QTimer::singleShot( 10, this, SLOT(finishTimers() ) );
340 void PartMonitor::partCompleted()
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" );
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
;
371 putDirect("length",length
);
374 bool RegTestFunction::implementsCall() const
379 JSValue
* RegTestFunction::callAsFunction(ExecState
*exec
, JSObject
* /*thisObj*/, const List
&args
)
381 JSValue
* result
= jsUndefined();
382 if ( m_regTest
->ignore_errors
)
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
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
)
399 m_regTest
->reportResult(passed
,description
);
401 m_regTest
->saw_failure
= true;
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
;
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
);
431 m_regTest
->reportResult(true,
433 if ( !m_regTest
->saw_failure
)
434 m_regTest
->ignore_errors
= true;
441 // -------------------------------------------------------------------------
443 KHTMLPartObject::KHTMLPartObject(ExecState
*exec
, KHTMLPart
*_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
);
474 else if (propertyName
== "window") {
475 slot
.setCustom(this, winGetter
);
478 return JSObject::getOwnPropertySlot(exec
, propertyName
, slot
);
481 KHTMLPartFunction::KHTMLPartFunction(ExecState */
*exec*/
, KHTMLPart
*_part
, int _id
, int length
)
485 putDirect("length",length
);
488 bool KHTMLPartFunction::implementsCall() const
493 JSValue
* KHTMLPartFunction::callAsFunction(ExecState
*exec
, JSObject*/
*thisObj*/
, const List
&args
)
495 JSValue
* result
= jsUndefined();
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
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
);
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
))));
534 QDataStream
stream(&fileData
,QIODevice::WriteOnly
);
537 while (!file
.atEnd()) {
538 bytesread
= file
.read(buf
,1024);
539 stream
.writeRawData(buf
,bytesread
);
542 QString
contents(fileData
);
543 PartMonitor
pm(m_part
);
544 m_part
->begin(KUrl( url
));
545 m_part
->write(contents
);
547 pm
.waitForCompletion();
549 kapp
->processEvents(QEventLoop::AllEvents
);
553 QString url
= args
[0]->toString(exec
).qstring();
554 m_part
->begin(KUrl( url
));
558 QString str
= args
[0]->toString(exec
).qstring();
564 kapp
->processEvents(QEventLoop::AllEvents
);
567 case ExecuteScript
: {
568 QString code
= args
[0]->toString(exec
).qstring();
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
);
577 case ProcessEvents
: {
578 kapp
->processEvents(QEventLoop::AllEvents
);
586 // -------------------------------------------------------------------------
588 int main(int argc
, char *argv
[])
590 // forget about any settings
591 passwd
* pw
= getpwuid( getuid() );
593 fprintf(stderr
, "dang, I don't even know who I am.\n");
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
;
615 options
.add("base <base_dir>", ki18n("Directory containing tests, basedir and output directories."));
617 options
.add("debug", ki18n("Do not suppress debug output"));
619 options
.add("genoutput", ki18n("Regenerate baseline (instead of checking)"));
621 options
.add("noshow", ki18n("Do not show the window while running tests"));
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"));
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();
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());
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" );
670 QByteArray xvfbPath8
= QFile::encodeName(xvfbPath
);
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");
684 fpaths
.append(QLatin1String(fontpaths
[fp
])+QLatin1String(fontdirs
[fd
]));
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 );
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
);
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
));
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 );
754 // make sure the missing image icon is independent of the icon theme..
755 QByteArray brokenImData
= QByteArray::fromBase64(imageMissingIcon
);
757 brokenIm
.loadFromData(brokenImData
);
758 khtml::Cache::brokenPixmap
= new QPixmap(QPixmap::fromImage(brokenIm
));
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"))
775 a
.setTopWidget(part
->widget());
779 // we're not interested
780 toplevel
->statusBar()->hide();
782 if (!getenv("KDE_DEBUG")) {
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
);
791 RegressionTest
*regressionTest
= new RegressionTest(part
,
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 )));
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);
813 result
= regressionTest
->runTests();
816 if (args
->isSet("genoutput")) {
817 printf("\nOutput generation completed.\n");
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
);
832 printf("Failures: %d",regressionTest
->m_failures_work
);
833 if ( regressionTest
->m_failures_fail
)
834 printf( " (%d expected failures)\n", regressionTest
->m_failures_fail
);
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
);
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() );
851 // Only return a 0 exit code if all tests were successful
852 if (regressionTest
->m_failures_work
== 0 && regressionTest
->m_errors
== 0)
856 delete regressionTest
;
860 khtml::Cache::clear();
861 khtml::CSSStyleSelector::clear();
862 khtml::RenderStyle::cleanup();
864 kill( xvfb
, SIGINT
);
869 // -------------------------------------------------------------------------
871 RegressionTest
*RegressionTest::curr
= 0;
873 RegressionTest::RegressionTest(KHTMLPart
*part
, const QString
&baseDir
, const QString
&outputDir
,
874 bool _genOutput
, bool runJS
, bool runHTML
)
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";
886 createMissingDirs(outputDir
+ "/"); //krazy:exclude=duoblequote_chars DOM demands chars
887 m_outputDir
= outputDir
;
889 m_genOutput
= _genOutput
;
892 m_passes_work
= m_passes_fail
= 0;
893 m_failures_work
= m_failures_fail
= 0;
896 ::unlink( QFile::encodeName( m_outputDir
+ "/links.html" ) );
897 QFile
f( m_outputDir
+ "/empty.html" );
899 f
.open( QIODevice::WriteOnly
| QIODevice::Truncate
);
900 s
= "<html><body>Follow the white rabbit";
901 f
.write( s
.toLatin1(), s
.length() );
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() );
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
));
929 QTextStream
ignoreStream(&ignoreFile
);
931 while (!(line
= ignoreStream
.readLine()).isNull())
932 ignoreFiles
.append(line
);
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
));
954 QFileInfo
info(fullPath
);
956 if (!info
.exists() && mustExist
) {
957 fprintf(stderr
,"%s: No such file or directory\n",qPrintable(relPath
));
961 if (!info
.isReadable() && mustExist
) {
962 fprintf(stderr
,"%s: Access denied\n",qPrintable(relPath
));
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
) )
979 runTests(relFilename
, false, failureFiles
);
982 else if (info
.isFile()) {
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
)
1012 if ( relPath
.startsWith( "ecma/" ) && !m_runJS
)
1015 testStaticFile(relPath
);
1017 else if (filename
.endsWith(".js")) {
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
));
1025 } else if (mustExist
) {
1026 fprintf(stderr
,"%s: Not a regular file\n",qPrintable(relPath
));
1033 void RegressionTest::getPartDOMOutput( QTextStream
&outputStream
, KHTMLPart
* part
, uint indent
)
1035 DOM::Node node
= part
->document();
1036 while (!node
.isNull()) {
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());
1057 QStringList::iterator it
;
1059 for (it
= attrNames
.begin(); it
!= attrNames
.end(); ++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
);
1070 getPartDOMOutput( outputStream
, frame
, indent
);
1074 case DOM::Node::ATTRIBUTE_NODE
:
1075 // Should not be present in tree
1078 case DOM::Node::TEXT_NODE
:
1079 outputStream
<< " \"" << Text(node
).data().string() << "\"";
1081 case DOM::Node::CDATA_SECTION_NODE
:
1082 outputStream
<< " \"" << CDATASection(node
).data().string() << "\"";
1084 case DOM::Node::ENTITY_REFERENCE_NODE
:
1086 case DOM::Node::ENTITY_NODE
:
1088 case DOM::Node::PROCESSING_INSTRUCTION_NODE
:
1090 case DOM::Node::COMMENT_NODE
:
1091 outputStream
<< " \"" << Comment(node
).data().string() << "\"";
1093 case DOM::Node::DOCUMENT_NODE
:
1095 case DOM::Node::DOCUMENT_TYPE_NODE
:
1097 case DOM::Node::DOCUMENT_FRAGMENT_NODE
:
1098 // Should not be present in tree
1101 case DOM::Node::NOTATION_NODE
:
1108 outputStream
<< endl
;
1110 if (!node
.firstChild().isNull()) {
1111 node
= node
.firstChild();
1114 else if (!node
.nextSibling().isNull()) {
1115 node
= node
.nextSibling();
1118 while (!node
.isNull() && node
.nextSibling().isNull()) {
1119 node
= node
.parentNode();
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() )
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();
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 );
1144 dumpRenderTree( outputStream
, frame
);
1148 QString
RegressionTest::getPartOutput( OutputType type
)
1150 // dump out the contents of the rendering & DOM trees
1152 QTextStream
outputStream(&dump
, QIODevice::WriteOnly
);
1154 if ( type
== RenderTree
) {
1155 dumpRenderTree( outputStream
, m_part
);
1157 assert( type
== DOMTree
);
1158 getPartDOMOutput( outputStream
, m_part
, 0 );
1161 dump
.replace( m_baseDir
+ "/tests", QLatin1String( "REGRESSION_SRCDIR" ) );
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
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 ) );
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 );
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();
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
) {
1221 if ( ( abs( qRed( l
) - qRed(r
) ) < 20 ) &&
1222 ( abs( qGreen( l
) - qGreen(r
) ) < 20 ) &&
1223 ( abs( qBlue( l
) - qBlue(r
) ) < 20 ) )
1225 kDebug() << "pixel (" << x
<< ", " << y
<< ") is different " << QColor( lhsi
.pixel ( x
, y
) ) << " " << QColor( rhsi
.pixel ( x
, y
) );
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
);
1241 link
= QString( "<a href=\"%1\" target=\"content\" title=\"%2\">" )
1242 .arg( test
+ "-compare.html" )
1244 link
+= m_currentTest
;
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
1253 list
.write( link
.toLatin1(), link
.length() );
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
1263 cl
= QString( "<html><head><title>%1</title>" ).arg( test
);
1265 QString text
= "\n" + m_currentOutput
; //krazy:exclude=duoblequote_chars DOM demands chars
1266 text
.replace( '<', "<" );
1267 text
.replace( '>', ">" );
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" );
1276 cl
+= "</tt></body></html>";
1277 compare
.write( cl
.toLatin1(), cl
.length() );
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
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; }
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;
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
)
1322 if (relPath
.length() > 0) rel
+= "/"; //krazy:exclude=duoblequote_chars DOM demands chars
1329 static QString
getDiff(QString cmdLine
)
1332 p
.start(cmdLine
, QIODevice::ReadOnly
);
1333 p
.waitForFinished();
1334 QString text
= QString::fromLocal8Bit(p
.readAllStandardOutput());
1335 text
= text
.replace( '<', "<" );
1336 text
= text
.replace( '>', ">" );
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
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();
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
) {
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
);
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"
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"
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"
1410 cl
+= QString ( "function runSlideShow(){\n"
1411 " document.getElementById('image').src = pics[t].src;\n"
1414 " setTimeout('runSlideShow()', 200);\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"
1421 "function showRender() { doflicker=0;toggleVisible('render')\n"
1423 "function showDom() { doflicker=0;toggleVisible('dom')\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"
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');\"" );
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> \n"
1442 "<span id='b2' class='button' onclick=\"doflicker=0;t=0;show();m(2)\">BASE</span> \n"
1443 "<span id='b3' class='button' onclick=\"doflicker=0;t=1;show();m(3)\">OUT</span> \n" );
1444 if ( renderDiff
.length() )
1445 cl
+= "<span id='b4' class='button' onclick='showRender();m(4)'>R-DIFF</span> \n";
1446 if ( domDiff
.length() )
1447 cl
+= "<span id='b5' class='button' onclick='showDom();m(5);'>D-DIFF</span> \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> " )
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() );
1465 void RegressionTest::testStaticFile(const QString
& filename
)
1467 qDebug("TESTING:%s", filename
.toLatin1().data());
1468 resizeTopLevelWidget( 800, 598 ); // restore size
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
);
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();
1485 if ( filename
.startsWith( "domts/" ) ) {
1486 QString functionname
;
1488 KJS::Completion comp
= m_part
->jScriptInterpreter()->evaluate(filename
, 0, "exposeTestFunctionNames();");
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")->
1503 functionname
= argArrayObj
->get(exec
, 0)->toString(exec
).qstring();
1506 if ( functionname
.isNull() ) {
1507 kDebug() << "DOM " << filename
<< " doesn't expose 1 function name - ignoring";
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();
1520 description
= comp2
.value()->toString( exec
).qstring();
1522 reportResult( success
, description
);
1524 if (!success
&& !m_known_failures
)
1525 doFailureReport( filename
, JSFailure
);
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" );
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
));
1578 QTextStream
stream ( &sourceFile
);
1579 stream
.setCodec( "UTF-8" );
1580 QString code
= stream
.readAll();
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
) {
1594 JSObject
* obj
= c
.value()->toObject(exec
);
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
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!'" );
1608 reportResult( !expected_failure
, "passed" );
1613 class GlobalImp
: public JSGlobalObject
{
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
;
1658 CheckResult result
= Failure
;
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
);
1668 if ( m_known_failures
& AllFailure
)
1670 else if ( m_known_failures
& PaintFailure
)
1673 outputFilename
+= "-KF";
1675 output
.save(outputFilename
, "PNG", 60);
1678 ::unlink( QFile::encodeName( m_outputDir
+ "/" + againstFilename
) ); //krazy:exclude=duoblequote_chars DOM demands chars
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
;
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
1701 if ( m_known_failures
& AllFailure
)
1703 else if ( domOut
&& ( m_known_failures
& DomFailure
) )
1705 else if ( !domOut
&& ( m_known_failures
& RenderFailure
) )
1708 outputFilename
+= "-KF";
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
) );
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
));
1735 QTextStream
stream2(&file2
);
1736 stream2
.setCodec( "UTF-8" );
1739 printf("Generated %s\n", qPrintable(outputFilename
));
1744 bool RegressionTest::reportResult(CheckResult result
, const QString
& description
)
1746 if ( result
== Ignored
) {
1747 //printf("IGNORED: ");
1748 //printDescription( description );
1749 return true; // no error
1751 return reportResult( result
== Success
, description
);
1754 bool RegressionTest::reportResult(bool passed
, const QString
& description
)
1760 if ( m_known_failures
& AllFailure
) {
1761 printf("PASS (unexpected!): ");
1769 if ( m_known_failures
& AllFailure
) {
1770 printf("FAIL (known): ");
1772 passed
= true; // we knew about it
1779 printDescription( description
);
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
));
1800 void RegressionTest::createMissingDirs(const QString
& filename
)
1802 QFileInfo
dif(filename
);
1803 QFileInfo
dirInfo( dif
.path() );
1804 if (dirInfo
.exists())
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('/');
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
]));
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
))
1846 QTextStream
ignoreStream(&ignoreFile
);
1848 while (!(line
= ignoreStream
.readLine()).isNull()) {
1849 if ( line
== fi
.fileName() )
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"