summaryrefslogtreecommitdiffstats
path: root/khtml/test_regression.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'khtml/test_regression.cpp')
-rw-r--r--khtml/test_regression.cpp1644
1 files changed, 1644 insertions, 0 deletions
diff --git a/khtml/test_regression.cpp b/khtml/test_regression.cpp
new file mode 100644
index 000000000..2e67b614e
--- /dev/null
+++ b/khtml/test_regression.cpp
@@ -0,0 +1,1644 @@
+/**
+ * This file is part of the KDE project
+ *
+ * Copyright (C) 2001,2003 Peter Kelly (pmk@post.com)
+ * Copyright (C) 2003,2004 Stephan Kulow (coolo@kde.org)
+ * Copyright (C) 2004 Dirk Mueller ( mueller@kde.org )
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include <stdlib.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <signal.h>
+
+#include <kapplication.h>
+#include <kstandarddirs.h>
+#include <qimage.h>
+#include <qfile.h>
+#include "test_regression.h"
+#include <unistd.h>
+#include <stdio.h>
+
+#include "css/cssstyleselector.h"
+#include <dom_string.h>
+#include "rendering/render_style.h"
+#include "rendering/render_layer.h"
+#include "khtmldefaults.h"
+
+//We don't use the default fonts, though, but traditional testregression ones
+#undef HTML_DEFAULT_VIEW_FONT
+#undef HTML_DEFAULT_VIEW_FIXED_FONT
+#undef HTML_DEFAULT_VIEW_SERIF_FONT
+#undef HTML_DEFAULT_VIEW_SANSSERIF_FONT
+#undef HTML_DEFAULT_VIEW_CURSIVE_FONT
+#undef HTML_DEFAULT_VIEW_FANTASY_FONT
+#define HTML_DEFAULT_VIEW_FONT "helvetica"
+#define HTML_DEFAULT_VIEW_FIXED_FONT "courier"
+#define HTML_DEFAULT_VIEW_SERIF_FONT "times"
+#define HTML_DEFAULT_VIEW_SANSSERIF_FONT "helvetica"
+#define HTML_DEFAULT_VIEW_CURSIVE_FONT "helvetica"
+#define HTML_DEFAULT_VIEW_FANTASY_FONT "helvetica"
+
+
+#include <kaction.h>
+#include <kcmdlineargs.h>
+#include "khtml_factory.h"
+#include <kio/job.h>
+#include <kmainwindow.h>
+#include <ksimpleconfig.h>
+#include <kglobalsettings.h>
+
+#include <qcolor.h>
+#include <qcursor.h>
+#include <qdir.h>
+#include <qobject.h>
+#include <qpushbutton.h>
+#include <qscrollview.h>
+#include <qstring.h>
+#include <qtextstream.h>
+#include <qvaluelist.h>
+#include <qwidget.h>
+#include <qfileinfo.h>
+#include <qtimer.h>
+#include <kstatusbar.h>
+#include <qfileinfo.h>
+
+#include "misc/decoder.h"
+#include "dom/dom2_range.h"
+#include "dom/dom_exception.h"
+#include "dom/html_document.h"
+#include "html/htmltokenizer.h"
+#include "khtml_part.h"
+#include "khtmlpart_p.h"
+#include <kparts/browserextension.h>
+
+#include "khtmlview.h"
+#include "rendering/render_replaced.h"
+#include "xml/dom_docimpl.h"
+#include "html/html_baseimpl.h"
+#include "dom/dom_doc.h"
+#include "misc/loader.h"
+#include "ecma/kjs_binding.h"
+#include "ecma/kjs_dom.h"
+#include "ecma/kjs_window.h"
+#include "ecma/kjs_binding.h"
+#include "ecma/kjs_proxy.h"
+
+using namespace khtml;
+using namespace DOM;
+using namespace KJS;
+
+static bool visual = false;
+static pid_t xvfb;
+
+// -------------------------------------------------------------------------
+
+PartMonitor *PartMonitor::sm_highestMonitor = NULL;
+
+PartMonitor::PartMonitor(KHTMLPart *_part)
+{
+ m_part = _part;
+ m_completed = false;
+ connect(m_part,SIGNAL(completed()),this,SLOT(partCompleted()));
+ m_timer_waits = 200;
+ m_timeout_timer = new QTimer(this);
+}
+
+PartMonitor::~PartMonitor()
+{
+ if (this == sm_highestMonitor)
+ sm_highestMonitor = 0;
+}
+
+
+void PartMonitor::waitForCompletion()
+{
+ if (!m_completed) {
+
+ if (sm_highestMonitor)
+ return;
+
+ sm_highestMonitor = this;
+
+ kapp->enter_loop();
+
+ //connect(m_timeout_timer, SIGNAL(timeout()), this, SLOT( timeout() ) );
+ //m_timeout_timer->stop();
+ //m_timeout_timer->start( visual ? 100 : 2, true );
+ }
+
+ QTimer::singleShot( 0, this, SLOT( finishTimers() ) );
+ kapp->enter_loop();
+}
+
+void PartMonitor::timeout()
+{
+ kapp->exit_loop();
+}
+
+void PartMonitor::finishTimers()
+{
+ KJS::Window *w = KJS::Window::retrieveWindow( m_part );
+ --m_timer_waits;
+ if ( m_timer_waits && (w && w->winq->hasTimers()) || m_part->inProgress()) {
+ // wait a bit
+ QTimer::singleShot( 10, this, SLOT(finishTimers() ) );
+ return;
+ }
+ kapp->exit_loop();
+}
+
+void PartMonitor::partCompleted()
+{
+ m_completed = true;
+ RenderWidget::flushWidgetResizes();
+ m_timeout_timer->stop();
+ connect(m_timeout_timer, SIGNAL(timeout()),this, SLOT( timeout() ) );
+ m_timeout_timer->start( visual ? 100 : 2, true );
+ disconnect(m_part,SIGNAL(completed()),this,SLOT(partCompleted()));
+}
+
+static void signal_handler( int )
+{
+ printf( "timeout\n" );
+ abort();
+}
+// -------------------------------------------------------------------------
+
+RegTestObject::RegTestObject(ExecState *exec, RegressionTest *_regTest)
+{
+ m_regTest = _regTest;
+ putDirect("print",new RegTestFunction(exec,m_regTest,RegTestFunction::Print,1), DontEnum);
+ putDirect("reportResult",new RegTestFunction(exec,m_regTest,RegTestFunction::ReportResult,3), DontEnum);
+ putDirect("checkOutput",new RegTestFunction(exec,m_regTest,RegTestFunction::CheckOutput,1), DontEnum);
+ // add "quit" for compatibility with the mozilla js shell
+ putDirect("quit", new RegTestFunction(exec,m_regTest,RegTestFunction::Quit,1), DontEnum );
+}
+
+RegTestFunction::RegTestFunction(ExecState* /*exec*/, RegressionTest *_regTest, int _id, int length)
+{
+ m_regTest = _regTest;
+ id = _id;
+ putDirect("length",length);
+}
+
+bool RegTestFunction::implementsCall() const
+{
+ return true;
+}
+
+Value RegTestFunction::call(ExecState *exec, Object &/*thisObj*/, const List &args)
+{
+ Value result = Undefined();
+ if ( m_regTest->ignore_errors )
+ return result;
+
+ switch (id) {
+ case Print: {
+ UString str = args[0].toString(exec);
+ if ( str.qstring().lower().find( "failed!" ) >= 0 )
+ m_regTest->saw_failure = true;
+ QString res = str.qstring().replace('\007', "");
+ m_regTest->m_currentOutput += res + "\n";
+ break;
+ }
+ case ReportResult: {
+ bool passed = args[0].toBoolean(exec);
+ QString description = args[1].toString(exec).qstring();
+ if (args[1].isA(UndefinedType) || args[1].isA(NullType))
+ description = QString::null;
+ m_regTest->reportResult(passed,description);
+ if ( !passed )
+ m_regTest->saw_failure = true;
+ break;
+ }
+ case CheckOutput: {
+ DOM::DocumentImpl* docimpl = static_cast<DOM::DocumentImpl*>( m_regTest->m_part->document().handle() );
+ if ( docimpl && docimpl->view() && docimpl->renderer() )
+ {
+ docimpl->updateRendering();
+ docimpl->view()->layout();
+ }
+ QString filename = args[0].toString(exec).qstring();
+ filename = RegressionTest::curr->m_currentCategory+"/"+filename;
+ int failures = RegressionTest::NoFailure;
+ if ( m_regTest->m_genOutput ) {
+ if ( !m_regTest->reportResult( m_regTest->checkOutput(filename+"-dom"),
+ "Script-generated " + filename + "-dom") )
+ failures |= RegressionTest::DomFailure;
+ if ( !m_regTest->reportResult( m_regTest->checkOutput(filename+"-render"),
+ "Script-generated " + filename + "-render") )
+ failures |= RegressionTest::RenderFailure;
+ } else {
+ // compare with output file
+ if ( !m_regTest->reportResult( m_regTest->checkOutput(filename+"-dom"), "DOM") )
+ failures |= RegressionTest::DomFailure;
+ if ( !m_regTest->reportResult( m_regTest->checkOutput(filename+"-render"), "RENDER") )
+ failures |= RegressionTest::RenderFailure;
+ }
+ RegressionTest::curr->doFailureReport( filename, failures );
+ break;
+ }
+ case Quit:
+ m_regTest->reportResult(true,
+ "Called quit" );
+ if ( !m_regTest->saw_failure )
+ m_regTest->ignore_errors = true;
+ break;
+ }
+
+ return result;
+}
+
+// -------------------------------------------------------------------------
+
+KHTMLPartObject::KHTMLPartObject(ExecState *exec, KHTMLPart *_part)
+{
+ m_part = _part;
+ putDirect("openPage", new KHTMLPartFunction(exec,m_part,KHTMLPartFunction::OpenPage,1), DontEnum);
+ putDirect("openPageAsUrl", new KHTMLPartFunction(exec,m_part,KHTMLPartFunction::OpenPageAsUrl,1), DontEnum);
+ putDirect("begin", new KHTMLPartFunction(exec,m_part,KHTMLPartFunction::Begin,1), DontEnum);
+ putDirect("write", new KHTMLPartFunction(exec,m_part,KHTMLPartFunction::Write,1), DontEnum);
+ putDirect("end", new KHTMLPartFunction(exec,m_part,KHTMLPartFunction::End,0), DontEnum);
+ putDirect("executeScript", new KHTMLPartFunction(exec,m_part,KHTMLPartFunction::ExecuteScript,0), DontEnum);
+ putDirect("processEvents", new KHTMLPartFunction(exec,m_part,KHTMLPartFunction::ProcessEvents,0), DontEnum);
+}
+
+Value KHTMLPartObject::get(ExecState *exec, const Identifier &propertyName) const
+{
+ if (propertyName == "document")
+ return getDOMNode(exec,m_part->document());
+ else if (propertyName == "window")
+ return Object(KJS::Window::retrieveWindow(m_part));
+ else
+ return ObjectImp::get(exec,propertyName);
+}
+
+KHTMLPartFunction::KHTMLPartFunction(ExecState */*exec*/, KHTMLPart *_part, int _id, int length)
+{
+ m_part = _part;
+ id = _id;
+ putDirect("length",length);
+}
+
+bool KHTMLPartFunction::implementsCall() const
+{
+ return true;
+}
+
+Value KHTMLPartFunction::call(ExecState *exec, Object &/*thisObj*/, const List &args)
+{
+ Value result = Undefined();
+
+ switch (id) {
+ case OpenPage: {
+ if (args[0].type() == NullType || args[0].type() == NullType) {
+ exec->setException(Error::create(exec, GeneralError,"No filename specified"));
+ return Undefined();
+ }
+
+ QString filename = args[0].toString(exec).qstring();
+ QString fullFilename = QFileInfo(RegressionTest::curr->m_currentBase+"/"+filename).absFilePath();
+ KURL url;
+ url.setProtocol("file");
+ url.setPath(fullFilename);
+ PartMonitor pm(m_part);
+ m_part->openURL(url);
+ pm.waitForCompletion();
+ kapp->processEvents(60000);
+ break;
+ }
+ case OpenPageAsUrl: {
+ if (args[0].type() == NullType || args[0].type() == UndefinedType) {
+ exec->setException(Error::create(exec, GeneralError,"No filename specified"));
+ return Undefined();
+ }
+ if (args[1].type() == NullType || args[1].type() == UndefinedType) {
+ exec->setException(Error::create(exec, GeneralError,"No url specified"));
+ return Undefined();
+ }
+
+ QString filename = args[0].toString(exec).qstring();
+ QString url = args[1].toString(exec).qstring();
+ QFile file(RegressionTest::curr->m_currentBase+"/"+filename);
+ if (!file.open(IO_ReadOnly)) {
+ exec->setException(Error::create(exec, GeneralError,
+ QString("Error reading " + filename).latin1()));
+ }
+ else {
+ QByteArray fileData;
+ QDataStream stream(fileData,IO_WriteOnly);
+ char buf[1024];
+ int bytesread;
+ while (!file.atEnd()) {
+ bytesread = file.readBlock(buf,1024);
+ stream.writeRawBytes(buf,bytesread);
+ }
+ file.close();
+ QString contents(fileData);
+ PartMonitor pm(m_part);
+ m_part->begin(KURL( url ));
+ m_part->write(contents);
+ m_part->end();
+ pm.waitForCompletion();
+ }
+ kapp->processEvents(60000);
+ break;
+ }
+ case Begin: {
+ QString url = args[0].toString(exec).qstring();
+ m_part->begin(KURL( url ));
+ break;
+ }
+ case Write: {
+ QString str = args[0].toString(exec).qstring();
+ m_part->write(str);
+ break;
+ }
+ case End: {
+ m_part->end();
+ kapp->processEvents(60000);
+ break;
+ }
+ case ExecuteScript: {
+ QString code = args[0].toString(exec).qstring();
+ Completion comp;
+ KJSProxy *proxy = m_part->jScript();
+ proxy->evaluate("",0,code,0,&comp);
+ if (comp.complType() == Throw)
+ exec->setException(comp.value());
+ kapp->processEvents(60000);
+ break;
+ }
+ case ProcessEvents: {
+ kapp->processEvents(60000);
+ break;
+ }
+ }
+
+ return result;
+}
+
+// -------------------------------------------------------------------------
+
+static KCmdLineOptions options[] =
+{
+ { "b", 0, 0 },
+ { "base <base_dir>", "Directory containing tests, basedir and output directories.", 0},
+ { "d", 0, 0 },
+ { "debug", "Do not supress debug output", 0},
+ { "g", 0, 0 } ,
+ { "genoutput", "Regenerate baseline (instead of checking)", 0 } ,
+ { "s", 0, 0 } ,
+ { "show", "Show the window while running tests", 0 } ,
+ { "t", 0, 0 } ,
+ { "test <filename>", "Only run a single test. Multiple options allowed.", 0 } ,
+ { "js", "Only run .js tests", 0 },
+ { "html", "Only run .html tests", 0},
+ { "noxvfb", "Do not use Xvfb", 0},
+ { "o", 0, 0 },
+ { "output <directory>", "Put output in <directory> instead of <base_dir>/output", 0 } ,
+ { "+[base_dir]", "Directory containing tests,basedir and output directories. Only regarded if -b is not specified.", 0 } ,
+ { "+[testcases]", "Relative path to testcase, or directory of testcases to be run (equivalent to -t).", 0 } ,
+ KCmdLineLastOption
+};
+
+static bool existsDir(QCString dir)
+{
+ struct stat st;
+
+ return (!stat(dir.data(), &st) && S_ISDIR(st.st_mode));
+}
+
+int main(int argc, char *argv[])
+{
+ // forget about any settings
+ passwd* pw = getpwuid( getuid() );
+ if (!pw) {
+ fprintf(stderr, "dang, I don't even know who I am.\n");
+ exit(1);
+ }
+
+ QString kh("/var/tmp/%1_non_existant");
+ kh = kh.arg( pw->pw_name );
+ setenv( "KDEHOME", kh.latin1(), 1 );
+ setenv( "LC_ALL", "C", 1 );
+ setenv( "LANG", "C", 1 );
+
+ signal( SIGALRM, signal_handler );
+
+ // workaround various Qt crashes by always enforcing a TrueColor visual
+ QApplication::setColorSpec( QApplication::ManyColor );
+
+ KCmdLineArgs::init(argc, argv, "testregression", "TestRegression",
+ "Regression tester for khtml", "1.0");
+ KCmdLineArgs::addCmdLineOptions(options);
+
+ KCmdLineArgs *args = KCmdLineArgs::parsedArgs( );
+
+ QCString baseDir = args->getOption("base");
+
+ if ( args->count() < 1 && baseDir.isEmpty() ) {
+ KCmdLineArgs::usage();
+ ::exit( 1 );
+ }
+
+ int testcase_index = 0;
+ if (baseDir.isEmpty()) baseDir = args->arg(testcase_index++);
+
+ QFileInfo bdInfo(baseDir);
+ baseDir = QFile::encodeName(bdInfo.absFilePath());
+
+ const char *subdirs[] = {"tests", "baseline", "output", "resources"};
+ for ( int i = 0; i < 3; i++ ) {
+ QFileInfo sourceDir(QFile::encodeName( baseDir ) + "/" + subdirs[i]);
+ if ( !sourceDir.exists() || !sourceDir.isDir() ) {
+ fprintf(stderr,"ERROR: Source directory \"%s/%s\": no such directory.\n", (const char *)baseDir, subdirs[i]);
+ exit(1);
+ }
+ }
+
+ if (args->isSet("xvfb"))
+ {
+ QString xvfbPath = KStandardDirs::findExe("Xvfb");
+ if ( xvfbPath.isEmpty() ) {
+ fprintf( stderr, "ERROR: We need Xvfb to be installed for reliable results\n" );
+ exit( 1 );
+ }
+
+ QCString xvfbPath8 = QFile::encodeName(xvfbPath);
+ QStringList fpaths;
+ fpaths.append(baseDir+"/resources");
+
+ const char* const fontdirs[] = { "75dpi", "misc", "Type1" };
+ const char* const fontpaths[] = {"/usr/share/fonts/", "/usr/X11/lib/X11/fonts/",
+ "/usr/lib/X11/fonts/", "/usr/share/fonts/X11/" };
+
+ for (size_t fp=0; fp < sizeof(fontpaths)/sizeof(*fontpaths); ++fp)
+ for (size_t fd=0; fd < sizeof(fontdirs)/sizeof(*fontdirs); ++fd)
+ if (existsDir(QCString(fontpaths[fp])+QCString(fontdirs[fd])))
+ if (strcmp(fontdirs[fd] , "Type1"))
+ fpaths.append(QCString(fontpaths[fp])+QCString(fontdirs[fd])+":unscaled");
+ else
+ fpaths.append(QCString(fontpaths[fp])+QCString(fontdirs[fd]));
+
+ xvfb = fork();
+ if ( !xvfb ) {
+ QCString buffer = fpaths.join(",").latin1();
+ execl( xvfbPath8.data(), xvfbPath8.data(), "-once", "-dpi", "100", "-screen", "0",
+ "1024x768x16", "-ac", "-fp", buffer.data(), ":47", (char*)NULL );
+ }
+
+ setenv( "DISPLAY", ":47", 1 );
+ }
+
+ KApplication a;
+ a.disableAutoDcopRegistration();
+ a.setStyle( "windows" );
+ KSimpleConfig sc1( "cryptodefaults" );
+ sc1.setGroup( "Warnings" );
+ sc1.writeEntry( "OnUnencrypted", false );
+ a.config()->setGroup( "Notification Messages" );
+ a.config()->writeEntry( "kjscupguard_alarmhandler", true );
+ a.config()->setGroup("HTML Settings");
+ a.config()->writeEntry("ReportJSErrors", false);
+ KConfig cfg( "khtmlrc" );
+ cfg.setGroup("HTML Settings");
+ cfg.writeEntry( "StandardFont", HTML_DEFAULT_VIEW_SANSSERIF_FONT );
+ cfg.writeEntry( "FixedFont", HTML_DEFAULT_VIEW_FIXED_FONT );
+ cfg.writeEntry( "SerifFont", HTML_DEFAULT_VIEW_SERIF_FONT );
+ cfg.writeEntry( "SansSerifFont", HTML_DEFAULT_VIEW_SANSSERIF_FONT );
+ cfg.writeEntry( "CursiveFont", HTML_DEFAULT_VIEW_CURSIVE_FONT );
+ cfg.writeEntry( "FantasyFont", HTML_DEFAULT_VIEW_FANTASY_FONT );
+ cfg.writeEntry( "MinimumFontSize", HTML_DEFAULT_MIN_FONT_SIZE );
+ cfg.writeEntry( "MediumFontSize", 10 );
+ cfg.writeEntry( "Fonts", QStringList() );
+ cfg.writeEntry( "DefaultEncoding", "" );
+ cfg.setGroup("Java/JavaScript Settings");
+ cfg.writeEntry( "WindowOpenPolicy", KHTMLSettings::KJSWindowOpenAllow);
+
+ cfg.sync();
+
+ int rv = 1;
+
+ if ( !args->isSet( "debug" ) ) {
+ KSimpleConfig dc( "kdebugrc" );
+ static int areas[] = { 1000, 6000, 6005, 6010, 6020, 6030,
+ 6031, 6035, 6036, 6040, 6041, 6045,
+ 6050, 6060, 6061, 7000, 7006, 170,
+ 171, 7101, 7002, 7019, 7027, 7014,
+ 7011, 6070, 6080, 6090, 0};
+ for ( int i = 0; areas[i]; ++i ) {
+ dc.setGroup( QString::number( areas[i] ) );
+ dc.writeEntry( "InfoOutput", 4 );
+ }
+ dc.sync();
+
+ kdClearDebugConfig();
+ }
+
+ // create widgets
+ KHTMLFactory *fac = new KHTMLFactory();
+ KMainWindow *toplevel = new KMainWindow();
+ KHTMLPart *part = new KHTMLPart( toplevel, 0, toplevel, 0, KHTMLPart::BrowserViewGUI );
+
+ toplevel->setCentralWidget( part->widget() );
+ part->setJScriptEnabled(true);
+
+ part->executeScript(DOM::Node(), ""); // force the part to create an interpreter
+// part->setJavaEnabled(true);
+
+ if (args->isSet("show"))
+ visual = true;
+
+ a.setTopWidget(part->widget());
+ a.setMainWidget( toplevel );
+ if ( visual )
+ toplevel->show();
+
+ // we're not interested
+ toplevel->statusBar()->hide();
+
+ if (!getenv("KDE_DEBUG")) {
+ // set ulimits
+ rlimit vmem_limit = { 256*1024*1024, RLIM_INFINITY }; // 256Mb Memory should suffice
+ setrlimit(RLIMIT_AS, &vmem_limit);
+ rlimit stack_limit = { 8*1024*1024, RLIM_INFINITY }; // 8Mb Memory should suffice
+ setrlimit(RLIMIT_STACK, &stack_limit);
+ }
+
+ // run the tests
+ RegressionTest *regressionTest = new RegressionTest(part,
+ baseDir,
+ args->getOption("output"),
+ args->isSet("genoutput"),
+ !args->isSet( "html" ),
+ !args->isSet( "js" ));
+ QObject::connect(part->browserExtension(), SIGNAL(openURLRequest(const KURL &, const KParts::URLArgs &)),
+ regressionTest, SLOT(slotOpenURL(const KURL&, const KParts::URLArgs &)));
+ QObject::connect(part->browserExtension(), SIGNAL(resizeTopLevelWidget( int, int )),
+ regressionTest, SLOT(resizeTopLevelWidget( int, int )));
+
+ bool result = false;
+ QCStringList tests = args->getOptionList("test");
+ // merge testcases specified on command line
+ for (; testcase_index < args->count(); testcase_index++)
+ tests << args->arg(testcase_index);
+ if (tests.count() > 0)
+ for (QValueListConstIterator<QCString> it = tests.begin(); it != tests.end(); ++it) {
+ result = regressionTest->runTests(*it,true);
+ if (!result) break;
+ }
+ else
+ result = regressionTest->runTests();
+
+ if (result) {
+ if (args->isSet("genoutput")) {
+ printf("\nOutput generation completed.\n");
+ }
+ else {
+ printf("\nTests completed.\n");
+ printf("Total: %d\n",
+ regressionTest->m_passes_work+
+ regressionTest->m_passes_fail+
+ regressionTest->m_failures_work+
+ regressionTest->m_failures_fail+
+ regressionTest->m_errors);
+ printf("Passes: %d",regressionTest->m_passes_work);
+ if ( regressionTest->m_passes_fail )
+ printf( " (%d unexpected passes)\n", regressionTest->m_passes_fail );
+ else
+ printf( "\n" );
+ printf("Failures: %d",regressionTest->m_failures_work);
+ if ( regressionTest->m_failures_fail )
+ printf( " (%d expected failures)\n", regressionTest->m_failures_fail );
+ else
+ printf( "\n" );
+ if ( regressionTest->m_errors )
+ printf("Errors: %d\n",regressionTest->m_errors);
+
+ QFile list( regressionTest->m_outputDir + "/links.html" );
+ list.open( IO_WriteOnly|IO_Append );
+ QString link, cl;
+ link = QString( "<hr>%1 failures. (%2 expected failures)" )
+ .arg(regressionTest->m_failures_work )
+ .arg( regressionTest->m_failures_fail );
+ list.writeBlock( link.latin1(), link.length() );
+ list.close();
+ }
+ }
+
+ // Only return a 0 exit code if all tests were successful
+ if (regressionTest->m_failures_work == 0 && regressionTest->m_errors == 0)
+ rv = 0;
+
+ // cleanup
+ delete regressionTest;
+ delete part;
+ delete toplevel;
+ delete fac;
+
+ khtml::Cache::clear();
+ khtml::CSSStyleSelector::clear();
+ khtml::RenderStyle::cleanup();
+
+ kill( xvfb, SIGINT );
+
+ return rv;
+}
+
+// -------------------------------------------------------------------------
+
+RegressionTest *RegressionTest::curr = 0;
+
+RegressionTest::RegressionTest(KHTMLPart *part, const QString &baseDir, const QString &outputDir,
+ bool _genOutput, bool runJS, bool runHTML)
+ : QObject(part)
+{
+ m_part = part;
+ m_baseDir = baseDir;
+ m_baseDir = m_baseDir.replace( "//", "/" );
+ if ( m_baseDir.endsWith( "/" ) )
+ m_baseDir = m_baseDir.left( m_baseDir.length() - 1 );
+ if (outputDir.isEmpty())
+ m_outputDir = m_baseDir + "/output";
+ else {
+ createMissingDirs(outputDir + "/");
+ m_outputDir = outputDir;
+ }
+ m_genOutput = _genOutput;
+ m_runJS = runJS;
+ m_runHTML = runHTML;
+ m_passes_work = m_passes_fail = 0;
+ m_failures_work = m_failures_fail = 0;
+ m_errors = 0;
+
+ ::unlink( QFile::encodeName( m_outputDir + "/links.html" ) );
+ QFile f( m_outputDir + "/empty.html" );
+ QString s;
+ f.open( IO_WriteOnly | IO_Truncate );
+ s = "<html><body>Follow the white rabbit";
+ f.writeBlock( s.latin1(), s.length() );
+ f.close();
+ f.setName( m_outputDir + "/index.html" );
+ f.open( IO_WriteOnly | IO_Truncate );
+ s = "<html><frameset cols=150,*><frame src=links.html><frame name=content src=empty.html>";
+ f.writeBlock( s.latin1(), s.length() );
+ f.close();
+
+ m_paintBuffer = 0;
+
+ curr = this;
+}
+
+#include <qobjectlist.h>
+
+static QStringList readListFile( const QString &filename )
+{
+ // Read ignore file for this directory
+ QString ignoreFilename = filename;
+ QFileInfo ignoreInfo(ignoreFilename);
+ QStringList ignoreFiles;
+ if (ignoreInfo.exists()) {
+ QFile ignoreFile(ignoreFilename);
+ if (!ignoreFile.open(IO_ReadOnly)) {
+ fprintf(stderr,"Can't open %s\n",ignoreFilename.latin1());
+ exit(1);
+ }
+ QTextStream ignoreStream(&ignoreFile);
+ QString line;
+ while (!(line = ignoreStream.readLine()).isNull())
+ ignoreFiles.append(line);
+ ignoreFile.close();
+ }
+ return ignoreFiles;
+}
+
+RegressionTest::~RegressionTest()
+{
+ delete m_paintBuffer;
+}
+
+bool RegressionTest::runTests(QString relPath, bool mustExist, int known_failure)
+{
+ m_currentOutput = QString::null;
+
+ if (!QFile(m_baseDir + "/tests/"+relPath).exists()) {
+ fprintf(stderr,"%s: No such file or directory\n",relPath.latin1());
+ return false;
+ }
+
+ QString fullPath = m_baseDir + "/tests/"+relPath;
+ QFileInfo info(fullPath);
+
+ if (!info.exists() && mustExist) {
+ fprintf(stderr,"%s: No such file or directory\n",relPath.latin1());
+ return false;
+ }
+
+ if (!info.isReadable() && mustExist) {
+ fprintf(stderr,"%s: Access denied\n",relPath.latin1());
+ return false;
+ }
+
+ if (info.isDir()) {
+ QStringList ignoreFiles = readListFile( m_baseDir + "/tests/"+relPath+"/ignore" );
+ QStringList failureFiles = readListFile( m_baseDir + "/tests/"+relPath+"/KNOWN_FAILURES" );
+
+ // Run each test in this directory, recusively
+ QDir sourceDir(m_baseDir + "/tests/"+relPath);
+ for (uint fileno = 0; fileno < sourceDir.count(); fileno++) {
+ QString filename = sourceDir[fileno];
+ QString relFilename = relPath.isEmpty() ? filename : relPath+"/"+filename;
+
+ if (filename == "." || filename == ".." || ignoreFiles.contains(filename) )
+ continue;
+ int failure_type = NoFailure;
+ if ( failureFiles.contains( filename ) )
+ failure_type |= AllFailure;
+ if ( failureFiles.contains ( filename + "-render" ) )
+ failure_type |= RenderFailure;
+ if ( failureFiles.contains ( filename + "-dump.png" ) )
+ failure_type |= PaintFailure;
+ if ( failureFiles.contains ( filename + "-dom" ) )
+ failure_type |= DomFailure;
+ runTests(relFilename, false, failure_type );
+ }
+ }
+ else if (info.isFile()) {
+
+ alarm( 400 );
+
+ khtml::Cache::init();
+
+ QString relativeDir = QFileInfo(relPath).dirPath();
+ QString filename = info.fileName();
+ m_currentBase = m_baseDir + "/tests/"+relativeDir;
+ m_currentCategory = relativeDir;
+ m_currentTest = filename;
+ m_known_failures = known_failure;
+ if ( filename.endsWith(".html") || filename.endsWith( ".htm" ) || filename.endsWith( ".xhtml" ) || filename.endsWith( ".xml" ) ) {
+ if ( relPath.startsWith( "domts/" ) && !m_runJS )
+ return true;
+ if ( relPath.startsWith( "ecma/" ) && !m_runJS )
+ return true;
+ if ( m_runHTML )
+ testStaticFile(relPath);
+ }
+ else if (filename.endsWith(".js")) {
+ if ( m_runJS )
+ testJSFile(relPath);
+ }
+ else if (mustExist) {
+ fprintf(stderr,"%s: Not a valid test file (must be .htm(l) or .js)\n",relPath.latin1());
+ return false;
+ }
+ } else if (mustExist) {
+ fprintf(stderr,"%s: Not a regular file\n",relPath.latin1());
+ return false;
+ }
+
+ return true;
+}
+
+void RegressionTest::getPartDOMOutput( QTextStream &outputStream, KHTMLPart* part, uint indent )
+{
+ Node node = part->document();
+ while (!node.isNull()) {
+ // process
+
+ for (uint i = 0; i < indent; i++)
+ outputStream << " ";
+ outputStream << node.nodeName().string();
+
+ switch (node.nodeType()) {
+ case Node::ELEMENT_NODE: {
+ // Sort strings to ensure consistent output
+ QStringList attrNames;
+ NamedNodeMap attrs = node.attributes();
+ for (uint a = 0; a < attrs.length(); a++)
+ attrNames.append(attrs.item(a).nodeName().string());
+ attrNames.sort();
+
+ QStringList::iterator it;
+ Element elem(node);
+ for (it = attrNames.begin(); it != attrNames.end(); ++it) {
+ QString name = *it;
+ QString value = elem.getAttribute(*it).string();
+ outputStream << " " << name << "=\"" << value << "\"";
+ }
+ if ( node.handle()->id() == ID_FRAME ) {
+ outputStream << endl;
+ QString frameName = static_cast<DOM::HTMLFrameElementImpl *>( node.handle() )->name.string();
+ KHTMLPart* frame = part->findFrame( frameName );
+ Q_ASSERT( frame );
+ if ( frame )
+ getPartDOMOutput( outputStream, frame, indent );
+ }
+ break;
+ }
+ case Node::ATTRIBUTE_NODE:
+ // Should not be present in tree
+ assert(false);
+ break;
+ case Node::TEXT_NODE:
+ outputStream << " \"" << Text(node).data().string() << "\"";
+ break;
+ case Node::CDATA_SECTION_NODE:
+ outputStream << " \"" << CDATASection(node).data().string() << "\"";
+ break;
+ case Node::ENTITY_REFERENCE_NODE:
+ break;
+ case Node::ENTITY_NODE:
+ break;
+ case Node::PROCESSING_INSTRUCTION_NODE:
+ break;
+ case Node::COMMENT_NODE:
+ outputStream << " \"" << Comment(node).data().string() << "\"";
+ break;
+ case Node::DOCUMENT_NODE:
+ break;
+ case Node::DOCUMENT_TYPE_NODE:
+ break;
+ case Node::DOCUMENT_FRAGMENT_NODE:
+ // Should not be present in tree
+ assert(false);
+ break;
+ case Node::NOTATION_NODE:
+ break;
+ default:
+ assert(false);
+ break;
+ }
+
+ outputStream << endl;
+
+ if (!node.firstChild().isNull()) {
+ node = node.firstChild();
+ indent++;
+ }
+ else if (!node.nextSibling().isNull()) {
+ node = node.nextSibling();
+ }
+ else {
+ while (!node.isNull() && node.nextSibling().isNull()) {
+ node = node.parentNode();
+ indent--;
+ }
+ if (!node.isNull())
+ node = node.nextSibling();
+ }
+ }
+}
+
+void RegressionTest::dumpRenderTree( QTextStream &outputStream, KHTMLPart* part )
+{
+ DOM::DocumentImpl* doc = static_cast<DocumentImpl*>( part->document().handle() );
+ if ( !doc || !doc->renderer() )
+ return;
+ doc->renderer()->layer()->dump( outputStream );
+
+ // Dump frames if any
+ // Get list of names instead of frames() to sort the list alphabetically
+ QStringList names = part->frameNames();
+ names.sort();
+ for ( QStringList::iterator it = names.begin(); it != names.end(); ++it ) {
+ outputStream << "FRAME: " << (*it) << "\n";
+ KHTMLPart* frame = part->findFrame( (*it) );
+ Q_ASSERT( frame );
+ if ( frame )
+ dumpRenderTree( outputStream, frame );
+ }
+}
+
+QString RegressionTest::getPartOutput( OutputType type)
+{
+ // dump out the contents of the rendering & DOM trees
+ QString dump;
+ QTextStream outputStream(dump,IO_WriteOnly);
+
+ if ( type == RenderTree ) {
+ dumpRenderTree( outputStream, m_part );
+ } else {
+ assert( type == DOMTree );
+ getPartDOMOutput( outputStream, m_part, 0 );
+ }
+
+ dump.replace( m_baseDir + "/tests", QString::fromLatin1( "REGRESSION_SRCDIR" ) );
+ return dump;
+}
+
+QImage RegressionTest::renderToImage()
+{
+ int ew = m_part->view()->contentsWidth();
+ int eh = m_part->view()->contentsHeight();
+
+ if (ew * eh > 4000 * 4000) // don't DoS us
+ return QImage();
+
+ QImage img( ew, eh, 32 );
+ img.fill( 0xff0000 );
+ if (!m_paintBuffer )
+ m_paintBuffer = new QPixmap( 512, 128, -1, QPixmap::MemoryOptim );
+
+ for ( int py = 0; py < eh; py += 128 ) {
+ for ( int px = 0; px < ew; px += 512 ) {
+ QPainter* tp = new QPainter;
+ tp->begin( m_paintBuffer );
+ tp->translate( -px, -py );
+ tp->fillRect(px, py, 512, 128, Qt::magenta);
+ m_part->document().handle()->renderer()->layer()->paint( tp, QRect( px, py, 512, 128 ) );
+ tp->end();
+ delete tp;
+
+ // now fill the chunk into our image
+ QImage chunk = m_paintBuffer->convertToImage();
+ assert( chunk.depth() == 32 );
+ for ( int y = 0; y < 128 && py + y < eh; ++y )
+ memcpy( img.scanLine( py+y ) + px*4, chunk.scanLine( y ), kMin( 512, ew-px )*4 );
+ }
+ }
+
+ assert( img.depth() == 32 );
+ return img;
+}
+
+bool RegressionTest::imageEqual( const QImage &lhsi, const QImage &rhsi )
+{
+ if ( lhsi.width() != rhsi.width() || lhsi.height() != rhsi.height() ) {
+ kdDebug() << "dimensions different " << lhsi.size() << " " << rhsi.size() << endl;
+ return false;
+ }
+ int w = lhsi.width();
+ int h = lhsi.height();
+ int bytes = lhsi.bytesPerLine();
+
+ for ( int y = 0; y < h; ++y )
+ {
+ QRgb* ls = ( QRgb* ) lhsi.scanLine( y );
+ QRgb* rs = ( QRgb* ) rhsi.scanLine( y );
+ if ( memcmp( ls, rs, bytes ) ) {
+ for ( int x = 0; x < w; ++x ) {
+ QRgb l = ls[x];
+ QRgb r = rs[x];
+ if ( ( abs( qRed( l ) - qRed(r ) ) < 20 ) &&
+ ( abs( qGreen( l ) - qGreen(r ) ) < 20 ) &&
+ ( abs( qBlue( l ) - qBlue(r ) ) < 20 ) )
+ continue;
+ kdDebug() << "pixel (" << x << ", " << y << ") is different " << QColor( lhsi.pixel ( x, y ) ) << " " << QColor( rhsi.pixel ( x, y ) ) << endl;
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+void RegressionTest::createLink( const QString& test, int failures )
+{
+ createMissingDirs( m_outputDir + "/" + test + "-compare.html" );
+
+ QFile list( m_outputDir + "/links.html" );
+ list.open( IO_WriteOnly|IO_Append );
+ QString link;
+ link = QString( "<a href=\"%1\" target=\"content\" title=\"%2\">" )
+ .arg( test + "-compare.html" )
+ .arg( test );
+ link += m_currentTest;
+ link += "</a> [";
+ if ( failures & DomFailure )
+ link += "D";
+ if ( failures & RenderFailure )
+ link += "R";
+ if ( failures & PaintFailure )
+ link += "P";
+ link += "]<br>\n";
+ list.writeBlock( link.latin1(), link.length() );
+ list.close();
+}
+
+void RegressionTest::doJavascriptReport( const QString &test )
+{
+ QFile compare( m_outputDir + "/" + test + "-compare.html" );
+ if ( !compare.open( IO_WriteOnly|IO_Truncate ) )
+ kdDebug() << "failed to open " << m_outputDir + "/" + test + "-compare.html" << endl;
+ QString cl;
+ cl = QString( "<html><head><title>%1</title>" ).arg( test );
+ cl += "<body><tt>";
+ QString text = "\n" + m_currentOutput;
+ text.replace( '<', "&lt;" );
+ text.replace( '>', "&gt;" );
+ text.replace( QRegExp( "\nFAILED" ), "\n<span style='color: red'>FAILED</span>" );
+ text.replace( QRegExp( "\nFAIL" ), "\n<span style='color: red'>FAIL</span>" );
+ text.replace( QRegExp( "\nPASSED" ), "\n<span style='color: green'>PASSED</span>" );
+ text.replace( QRegExp( "\nPASS" ), "\n<span style='color: green'>PASS</span>" );
+ if ( text.at( 0 ) == '\n' )
+ text = text.mid( 1, text.length() );
+ text.replace( '\n', "<br>\n" );
+ cl += text;
+ cl += "</tt></body></html>";
+ compare.writeBlock( cl.latin1(), cl.length() );
+ compare.close();
+}
+
+/** returns the path in a way that is relatively reachable from base.
+ * @param base base directory (must not include trailing slash)
+ * @param path directory/file to be relatively reached by base
+ * @return path with all elements replaced by .. and concerning path elements
+ * to be relatively reachable from base.
+ */
+static QString makeRelativePath(const QString &base, const QString &path)
+{
+ QString absBase = QFileInfo(base).absFilePath();
+ QString absPath = QFileInfo(path).absFilePath();
+// kdDebug() << "absPath: \"" << absPath << "\"" << endl;
+// kdDebug() << "absBase: \"" << absBase << "\"" << endl;
+
+ // walk up to common ancestor directory
+ int pos = 0;
+ do {
+ pos++;
+ int newpos = absBase.find('/', pos);
+ if (newpos == -1) newpos = absBase.length();
+ QConstString cmpPathComp(absPath.unicode() + pos, newpos - pos);
+ QConstString cmpBaseComp(absBase.unicode() + pos, newpos - pos);
+// kdDebug() << "cmpPathComp: \"" << cmpPathComp.string() << "\"" << endl;
+// kdDebug() << "cmpBaseComp: \"" << cmpBaseComp.string() << "\"" << endl;
+// kdDebug() << "pos: " << pos << " newpos: " << newpos << endl;
+ if (cmpPathComp.string() != cmpBaseComp.string()) { pos--; break; }
+ pos = newpos;
+ } while (pos < (int)absBase.length() && pos < (int)absPath.length());
+ int basepos = pos < (int)absBase.length() ? pos + 1 : pos;
+ int pathpos = pos < (int)absPath.length() ? pos + 1 : pos;
+
+// kdDebug() << "basepos " << basepos << " pathpos " << pathpos << endl;
+
+ QString rel;
+ {
+ QConstString relBase(absBase.unicode() + basepos, absBase.length() - basepos);
+ QConstString relPath(absPath.unicode() + pathpos, absPath.length() - pathpos);
+ // generate as many .. as there are path elements in relBase
+ if (relBase.string().length() > 0) {
+ for (int i = relBase.string().contains('/'); i > 0; --i)
+ rel += "../";
+ rel += "..";
+ if (relPath.string().length() > 0) rel += "/";
+ }
+ rel += relPath.string();
+ }
+ return rel;
+}
+
+void RegressionTest::doFailureReport( const QString& test, int failures )
+{
+ if ( failures == NoFailure ) {
+ ::unlink( QFile::encodeName( m_outputDir + "/" + test + "-compare.html" ) );
+ return;
+ }
+
+ createLink( test, failures );
+
+ if ( failures & JSFailure ) {
+ doJavascriptReport( test );
+ return; // no support for both kind
+ }
+
+ QFile compare( m_outputDir + "/" + test + "-compare.html" );
+
+ QString testFile = QFileInfo(test).fileName();
+
+ QString renderDiff;
+ QString domDiff;
+
+ QString relOutputDir = makeRelativePath(m_baseDir, m_outputDir);
+
+ // are blocking reads possible with KProcess?
+ char pwd[PATH_MAX];
+ (void) getcwd( pwd, PATH_MAX );
+ chdir( QFile::encodeName( m_baseDir ) );
+
+ if ( failures & RenderFailure ) {
+ renderDiff += "<pre>";
+ FILE *pipe = popen( QString::fromLatin1( "diff -u baseline/%1-render %3/%2-render" )
+ .arg ( test, test, relOutputDir ).latin1(), "r" );
+ QTextIStream *is = new QTextIStream( pipe );
+ for ( int line = 0; line < 100 && !is->eof(); ++line ) {
+ QString line = is->readLine();
+ line = line.replace( '<', "&lt;" );
+ line = line.replace( '>', "&gt;" );
+ renderDiff += line + "\n";
+ }
+ delete is;
+ pclose( pipe );
+ renderDiff += "</pre>";
+ }
+
+ if ( failures & DomFailure ) {
+ domDiff += "<pre>";
+ FILE *pipe = popen( QString::fromLatin1( "diff -u baseline/%1-dom %3/%2-dom" )
+ .arg ( test, test, relOutputDir ).latin1(), "r" );
+ QTextIStream *is = new QTextIStream( pipe );
+ for ( int line = 0; line < 100 && !is->eof(); ++line ) {
+ QString line = is->readLine();
+ line = line.replace( '<', "&lt;" );
+ line = line.replace( '>', "&gt;" );
+ domDiff += line + "\n";
+ }
+ delete is;
+ pclose( pipe );
+ domDiff += "</pre>";
+ }
+
+ chdir( pwd );
+
+ // create a relative path so that it works via web as well. ugly
+ QString relpath = makeRelativePath(m_outputDir + "/"
+ + QFileInfo(test).dirPath(), m_baseDir);
+
+ compare.open( IO_WriteOnly|IO_Truncate );
+ QString cl;
+ cl = QString( "<html><head><title>%1</title>" ).arg( test );
+ cl += QString( "<script>\n"
+ "var pics = new Array();\n"
+ "pics[0]=new Image();\n"
+ "pics[0].src = '%1';\n"
+ "pics[1]=new Image();\n"
+ "pics[1].src = '%2';\n"
+ "var doflicker = 1;\n"
+ "var t = 1;\n"
+ "var lastb=0;\n" )
+ .arg( relpath+"/baseline/"+test+"-dump.png" )
+ .arg( testFile+"-dump.png" );
+ cl += QString( "function toggleVisible(visible) {\n"
+ " document.getElementById('render').style.visibility= visible == 'render' ? 'visible' : 'hidden';\n"
+ " document.getElementById('image').style.visibility= visible == 'image' ? 'visible' : 'hidden';\n"
+ " document.getElementById('dom').style.visibility= visible == 'dom' ? 'visible' : 'hidden';\n"
+ "}\n"
+ "function show() { document.getElementById('image').src = pics[t].src; "
+ "document.getElementById('image').style.borderColor = t && !doflicker ? 'red' : 'gray';\n"
+ "toggleVisible('image');\n"
+ "}" );
+ cl += QString ( "function runSlideShow(){\n"
+ " document.getElementById('image').src = pics[t].src;\n"
+ " if (doflicker)\n"
+ " t = 1 - t;\n"
+ " setTimeout('runSlideShow()', 200);\n"
+ "}\n"
+ "function m(b) { if (b == lastb) return; document.getElementById('b'+b).className='buttondown';\n"
+ " var e = document.getElementById('b'+lastb);\n"
+ " if(e) e.className='button';\n"
+ " lastb = b;\n"
+ "}\n"
+ "function showRender() { doflicker=0;toggleVisible('render')\n"
+ "}\n"
+ "function showDom() { doflicker=0;toggleVisible('dom')\n"
+ "}\n"
+ "</script>\n");
+
+ cl += QString ("<style>\n"
+ ".buttondown { cursor: pointer; padding: 0px 20px; color: white; background-color: blue; border: inset blue 2px;}\n"
+ ".button { cursor: pointer; padding: 0px 20px; color: black; background-color: white; border: outset blue 2px;}\n"
+ ".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"
+ "</style>\n" );
+
+ if ( failures & PaintFailure )
+ cl += QString( "<body onload=\"m(1); show(); runSlideShow();\"" );
+ else if ( failures & RenderFailure )
+ cl += QString( "<body onload=\"m(4); toggleVisible('render');\"" );
+ else
+ cl += QString( "<body onload=\"m(5); toggleVisible('dom');\"" );
+ cl += QString(" text=black bgcolor=gray>\n<h1>%3</h1>\n" ).arg( test );
+ if ( failures & PaintFailure )
+ cl += QString ( "<span id='b1' class='buttondown' onclick=\"doflicker=1;show();m(1)\">FLICKER</span>&nbsp;\n"
+ "<span id='b2' class='button' onclick=\"doflicker=0;t=0;show();m(2)\">BASE</span>&nbsp;\n"
+ "<span id='b3' class='button' onclick=\"doflicker=0;t=1;show();m(3)\">OUT</span>&nbsp;\n" );
+ if ( renderDiff.length() )
+ cl += "<span id='b4' class='button' onclick='showRender();m(4)'>R-DIFF</span>&nbsp;\n";
+ if ( domDiff.length() )
+ cl += "<span id='b5' class='button' onclick='showDom();m(5);'>D-DIFF</span>&nbsp;\n";
+ // The test file always exists - except for checkOutput called from *.js files
+ if ( QFile::exists( m_baseDir + "/tests/"+ test ) )
+ cl += QString( "<a class=button href=\"%1\">HTML</a>&nbsp;" )
+ .arg( relpath+"/tests/"+test );
+
+ cl += QString( "<hr>"
+ "<img style='border: solid 5px gray' src=\"%1\" id='image'>" )
+ .arg( relpath+"/baseline/"+test+"-dump.png" );
+
+ cl += "<div id='render' class='diff'>" + renderDiff + "</div>";
+ cl += "<div id='dom' class='diff'>" + domDiff + "</div>";
+
+ cl += "</body></html>";
+ compare.writeBlock( cl.latin1(), cl.length() );
+ compare.close();
+}
+
+void RegressionTest::testStaticFile(const QString & filename)
+{
+ qApp->mainWidget()->resize( 800, 600); // restore size
+
+ // Set arguments
+ KParts::URLArgs args;
+ if (filename.endsWith(".html") || filename.endsWith(".htm")) args.serviceType = "text/html";
+ else if (filename.endsWith(".xhtml")) args.serviceType = "application/xhtml+xml";
+ else if (filename.endsWith(".xml")) args.serviceType = "text/xml";
+ m_part->browserExtension()->setURLArgs(args);
+ // load page
+ KURL url;
+ url.setProtocol("file");
+ url.setPath(QFileInfo(m_baseDir + "/tests/"+filename).absFilePath());
+ PartMonitor pm(m_part);
+ m_part->openURL(url);
+ pm.waitForCompletion();
+ m_part->closeURL();
+
+ if ( filename.startsWith( "domts/" ) ) {
+ QString functionname;
+
+ KJS::Completion comp = m_part->jScriptInterpreter()->evaluate("exposeTestFunctionNames();");
+ /*
+ * Error handling
+ */
+ KJS::ExecState *exec = m_part->jScriptInterpreter()->globalExec();
+ if ( comp.complType() == ReturnValue || comp.complType() == Normal )
+ {
+ if (comp.value().isValid() && comp.value().isA(ObjectType) &&
+ (Object::dynamicCast(comp.value()).className() == "Array" ) )
+ {
+ Object argArrayObj = Object::dynamicCast(comp.value());
+ unsigned int length = argArrayObj.
+ get(exec,lengthPropertyName).
+ toUInt32(exec);
+ if ( length == 1 )
+ functionname = argArrayObj.get(exec, 0).toString(exec).qstring();
+ }
+ }
+ if ( functionname.isNull() ) {
+ kdDebug() << "DOM " << filename << " doesn't expose 1 function name - ignoring" << endl;
+ return;
+ }
+
+ KJS::Completion comp2 = m_part->jScriptInterpreter()->evaluate("setUpPage(); " + functionname + "();" );
+ bool success = ( comp2.complType() == ReturnValue || comp2.complType() == Normal );
+ QString description = "DOMTS";
+ if ( comp2.complType() == Throw ) {
+ KJS::Value val = comp2.value();
+ KJS::Object obj = Object::dynamicCast(val);
+ if ( obj.isValid() && obj.hasProperty( exec, "jsUnitMessage" ) )
+ description = obj.get( exec, "jsUnitMessage" ).toString( exec ).qstring();
+ else
+ description = comp2.value().toString( exec ).qstring();
+ }
+ reportResult( success, description );
+
+ if (!success && !m_known_failures)
+ doFailureReport( filename, JSFailure );
+ return;
+ }
+
+ int back_known_failures = m_known_failures;
+
+ if ( m_genOutput ) {
+ if ( m_known_failures & DomFailure)
+ m_known_failures = AllFailure;
+ reportResult( checkOutput(filename+"-dom"), "DOM" );
+ if ( m_known_failures & RenderFailure )
+ m_known_failures = AllFailure;
+ reportResult( checkOutput(filename+"-render"), "RENDER" );
+ if ( m_known_failures & PaintFailure )
+ m_known_failures = AllFailure;
+ renderToImage().save(m_baseDir + "/baseline/" + filename + "-dump.png","PNG", 60);
+ printf("Generated %s\n", QString( m_baseDir + "/baseline/" + filename + "-dump.png" ).latin1() );
+ reportResult( true, "PAINT" );
+ } else {
+ int failures = NoFailure;
+
+ // compare with output file
+ if ( m_known_failures & DomFailure)
+ m_known_failures = AllFailure;
+ if ( !reportResult( checkOutput(filename+"-dom"), "DOM" ) )
+ failures |= DomFailure;
+
+ if ( m_known_failures & RenderFailure )
+ m_known_failures = AllFailure;
+ if ( !reportResult( checkOutput(filename+"-render"), "RENDER" ) )
+ failures |= RenderFailure;
+
+ if ( m_known_failures & PaintFailure )
+ m_known_failures = AllFailure;
+ if (!reportResult( checkPaintdump(filename), "PAINT") )
+ failures |= PaintFailure;
+
+ doFailureReport(filename, failures );
+ }
+
+ m_known_failures = back_known_failures;
+}
+
+void RegressionTest::evalJS( ScriptInterpreter &interp, const QString &filename, bool report_result )
+{
+ QString fullSourceName = filename;
+ QFile sourceFile(fullSourceName);
+
+ if (!sourceFile.open(IO_ReadOnly)) {
+ fprintf(stderr,"Error reading file %s\n",fullSourceName.latin1());
+ exit(1);
+ }
+
+ QTextStream stream ( &sourceFile );
+ stream.setEncoding( QTextStream::UnicodeUTF8 );
+ QString code = stream.read();
+ sourceFile.close();
+
+ saw_failure = false;
+ ignore_errors = false;
+ Completion c = interp.evaluate(UString( code ) );
+
+ if ( report_result && !ignore_errors) {
+ bool expected_failure = filename.endsWith( "-n.js" );
+ if (c.complType() == Throw) {
+ QString errmsg = c.value().toString(interp.globalExec()).qstring();
+ if ( !expected_failure ) {
+ printf( "ERROR: %s (%s)\n",filename.latin1(), errmsg.latin1());
+ m_errors++;
+ } else {
+ reportResult( true, QString( "Expected Failure: %1" ).arg( errmsg ) );
+ }
+ } else if ( saw_failure ) {
+ if ( !expected_failure )
+ doFailureReport( m_currentCategory + "/" + m_currentTest, JSFailure );
+ reportResult( expected_failure, "saw 'failed!'" );
+ } else {
+ reportResult( !expected_failure, "passed" );
+ }
+ }
+}
+
+class GlobalImp : public ObjectImp {
+public:
+ virtual UString className() const { return "global"; }
+};
+
+void RegressionTest::testJSFile(const QString & filename )
+{
+ qApp->mainWidget()->resize( 800, 600); // restore size
+
+ // create interpreter
+ // note: this is different from the interpreter used by the part,
+ // it contains regression test-specific objects & functions
+ Object global(new GlobalImp());
+ khtml::ChildFrame frame;
+ frame.m_part = m_part;
+ ScriptInterpreter interp(global,&frame);
+ ExecState *exec = interp.globalExec();
+
+ global.put(exec, "part", Object(new KHTMLPartObject(exec,m_part)));
+ global.put(exec, "regtest", Object(new RegTestObject(exec,this)));
+ global.put(exec, "debug", Object(new RegTestFunction(exec,this,RegTestFunction::Print,1) ) );
+ global.put(exec, "print", Object(new RegTestFunction(exec,this,RegTestFunction::Print,1) ) );
+
+ QStringList dirs = QStringList::split( '/', filename );
+ // NOTE: the basename is of little interest here, but the last basedir change
+ // isn't taken in account
+ QString basedir = m_baseDir + "/tests/";
+ for ( QStringList::ConstIterator it = dirs.begin(); it != dirs.end(); ++it )
+ {
+ if ( ! ::access( QFile::encodeName( basedir + "shell.js" ), R_OK ) )
+ evalJS( interp, basedir + "shell.js", false );
+ basedir += *it + "/";
+ }
+ evalJS( interp, m_baseDir + "/tests/"+ filename, true );
+}
+
+RegressionTest::CheckResult RegressionTest::checkPaintdump(const QString &filename)
+{
+ QString againstFilename( filename + "-dump.png" );
+ QString absFilename = QFileInfo(m_baseDir + "/baseline/" + againstFilename).absFilePath();
+ if ( svnIgnored( absFilename ) ) {
+ m_known_failures = NoFailure;
+ return Ignored;
+ }
+ CheckResult result = Failure;
+
+ QImage baseline;
+ baseline.load( absFilename, "PNG");
+ QImage output = renderToImage();
+ if ( !imageEqual( baseline, output ) ) {
+ QString outputFilename = m_outputDir + "/" + againstFilename;
+ createMissingDirs(outputFilename );
+
+ bool kf = false;
+ if ( m_known_failures & AllFailure )
+ kf = true;
+ else if ( m_known_failures & PaintFailure )
+ kf = true;
+ if ( kf )
+ outputFilename += "-KF";
+
+ output.save(outputFilename, "PNG", 60);
+ }
+ else {
+ ::unlink( QFile::encodeName( m_outputDir + "/" + againstFilename ) );
+ result = Success;
+ }
+ return result;
+}
+
+RegressionTest::CheckResult RegressionTest::checkOutput(const QString &againstFilename)
+{
+ QString absFilename = QFileInfo(m_baseDir + "/baseline/" + againstFilename).absFilePath();
+ if ( svnIgnored( absFilename ) ) {
+ m_known_failures = NoFailure;
+ return Ignored;
+ }
+
+ bool domOut = againstFilename.endsWith( "-dom" );
+ QString data = getPartOutput( domOut ? DOMTree : RenderTree );
+ data.remove( char( 13 ) );
+
+ CheckResult result = Success;
+
+ // compare result to existing file
+ QString outputFilename = QFileInfo(m_outputDir + "/" + againstFilename).absFilePath();
+ bool kf = false;
+ if ( m_known_failures & AllFailure )
+ kf = true;
+ else if ( domOut && ( m_known_failures & DomFailure ) )
+ kf = true;
+ else if ( !domOut && ( m_known_failures & RenderFailure ) )
+ kf = true;
+ if ( kf )
+ outputFilename += "-KF";
+
+ if ( m_genOutput )
+ outputFilename = absFilename;
+
+ QFile file(absFilename);
+ if (file.open(IO_ReadOnly)) {
+ QTextStream stream ( &file );
+ stream.setEncoding( QTextStream::UnicodeUTF8 );
+
+ QString fileData = stream.read();
+
+ result = ( fileData == data ) ? Success : Failure;
+ if ( !m_genOutput && result == Success ) {
+ ::unlink( QFile::encodeName( outputFilename ) );
+ return Success;
+ }
+ }
+
+ // generate result file
+ createMissingDirs( outputFilename );
+ QFile file2(outputFilename);
+ if (!file2.open(IO_WriteOnly)) {
+ fprintf(stderr,"Error writing to file %s\n",outputFilename.latin1());
+ exit(1);
+ }
+
+ QTextStream stream2(&file2);
+ stream2.setEncoding( QTextStream::UnicodeUTF8 );
+ stream2 << data;
+ if ( m_genOutput )
+ printf("Generated %s\n", outputFilename.latin1());
+
+ return result;
+}
+
+bool RegressionTest::reportResult(CheckResult result, const QString & description)
+{
+ if ( result == Ignored ) {
+ //printf("IGNORED: ");
+ //printDescription( description );
+ return true; // no error
+ } else
+ return reportResult( result == Success, description );
+}
+
+bool RegressionTest::reportResult(bool passed, const QString & description)
+{
+ if (m_genOutput)
+ return true;
+
+ if (passed) {
+ if ( m_known_failures & AllFailure ) {
+ printf("PASS (unexpected!): ");
+ m_passes_fail++;
+ } else {
+ printf("PASS: ");
+ m_passes_work++;
+ }
+ }
+ else {
+ if ( m_known_failures & AllFailure ) {
+ printf("FAIL (known): ");
+ m_failures_fail++;
+ passed = true; // we knew about it
+ } else {
+ printf("FAIL: ");
+ m_failures_work++;
+ }
+ }
+
+ printDescription( description );
+ return passed;
+}
+
+void RegressionTest::printDescription(const QString& description)
+{
+ if (!m_currentCategory.isEmpty())
+ printf("%s/", m_currentCategory.latin1());
+
+ printf("%s", m_currentTest.latin1());
+
+ if (!description.isEmpty()) {
+ QString desc = description;
+ desc.replace( '\n', ' ' );
+ printf(" [%s]", desc.latin1());
+ }
+
+ printf("\n");
+ fflush(stdout);
+}
+
+void RegressionTest::createMissingDirs(const QString & filename)
+{
+ QFileInfo dif(filename);
+ QFileInfo dirInfo( dif.dirPath() );
+ if (dirInfo.exists())
+ return;
+
+ QStringList pathComponents;
+ QFileInfo parentDir = dirInfo;
+ pathComponents.prepend(parentDir.absFilePath());
+ while (!parentDir.exists()) {
+ QString parentPath = parentDir.absFilePath();
+ int slashPos = parentPath.findRev('/');
+ if (slashPos < 0)
+ break;
+ parentPath = parentPath.left(slashPos);
+ pathComponents.prepend(parentPath);
+ parentDir = QFileInfo(parentPath);
+ }
+ for (uint pathno = 1; pathno < pathComponents.count(); pathno++) {
+ if (!QFileInfo(pathComponents[pathno]).exists() &&
+ !QDir(pathComponents[pathno-1]).mkdir(pathComponents[pathno])) {
+ fprintf(stderr,"Error creating directory %s\n",pathComponents[pathno].latin1());
+ exit(1);
+ }
+ }
+}
+
+void RegressionTest::slotOpenURL(const KURL &url, const KParts::URLArgs &args)
+{
+ m_part->browserExtension()->setURLArgs( args );
+
+ PartMonitor pm(m_part);
+ m_part->openURL(url);
+ pm.waitForCompletion();
+}
+
+bool RegressionTest::svnIgnored( const QString &filename )
+{
+ QFileInfo fi( filename );
+ QString ignoreFilename = fi.dirPath() + "/svnignore";
+ QFile ignoreFile(ignoreFilename);
+ if (!ignoreFile.open(IO_ReadOnly))
+ return false;
+
+ QTextStream ignoreStream(&ignoreFile);
+ QString line;
+ while (!(line = ignoreStream.readLine()).isNull()) {
+ if ( line == fi.fileName() )
+ return true;
+ }
+ ignoreFile.close();
+ return false;
+}
+
+void RegressionTest::resizeTopLevelWidget( int w, int h )
+{
+ qApp->mainWidget()->resize( w, h );
+ // Since we're not visible, this doesn't have an immediate effect, QWidget posts the event
+ QApplication::sendPostedEvents( 0, QEvent::Resize );
+}
+
+#include "test_regression.moc"