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