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) 2006 Nikolas Zimmermann <zimmermann@kde.org> |
5 | * |
6 | * This library is free software; you can redistribute it and/or |
7 | * modify it under the terms of the GNU Library General Public |
8 | * License as published by the Free Software Foundation; either |
9 | * version 2 of the License, or (at your option) any later version. |
10 | * |
11 | * This library is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 | * Library General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU Library General Public License |
17 | * along with this library; see the file COPYING.LIB. If not, write to |
18 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
19 | * Boston, MA 02110-1301, USA. |
20 | * |
21 | */ |
22 | |
23 | #include <assert.h> |
24 | #include <signal.h> |
25 | |
26 | #include <QtCore/QFile> |
27 | #include <QtCore/QTimer> |
28 | #include <QtCore/QProcess> |
29 | #include <QtCore/QFileInfo> |
30 | #include <QtCore/QTextStream> |
31 | #include <QtGui/QMainWindow> |
32 | |
33 | #include <kiconloader.h> |
34 | #include <kmessagebox.h> |
35 | #include <kconfig.h> |
36 | #include <kfiledialog.h> |
37 | |
38 | #include "test_regression_gui_window.moc" |
39 | |
40 | // Taken from QUrl |
41 | #define Q_HAS_FLAG(a, b) ( ((a) & (b)) == (b) ) |
42 | #define Q_SET_FLAG(a, b) { (a) |= (b); } |
43 | #define Q_UNSET_FLAG(a, b) { (a) &= ~(b); } |
44 | |
45 | TestRegressionWindow::TestRegressionWindow(QWidget *parent) |
46 | : QMainWindow(parent), m_flags(None), m_runCounter(0), m_testCounter(0), m_totalTests(0), |
47 | m_totalTestsJS(0), m_totalTestsDOMTS(0), m_lastResult(Unknown), |
48 | m_browserPart(0), m_activeProcess(0), m_activeTreeItem(0), |
49 | m_suspended(false), m_justProcessingQueue(false) |
50 | { |
51 | m_ui.setupUi(this); |
52 | |
53 | // Setup actions/connections |
54 | connect(m_ui.actionOnly_run_JS_tests, SIGNAL(toggled(bool)), SLOT(toggleJSTests(bool))); |
55 | connect(m_ui.actionOnly_run_HTML_tests, SIGNAL(toggled(bool)), SLOT(toggleHTMLTests(bool))); |
56 | connect(m_ui.actionDo_not_suppress_debug_output, SIGNAL(toggled(bool)), SLOT(toggleDebugOutput(bool))); |
57 | connect(m_ui.actionDo_not_use_Xvfb, SIGNAL(toggled(bool)), SLOT(toggleNoXvfbUse(bool))); |
58 | connect(m_ui.actionSpecify_tests_directory, SIGNAL(triggered(bool)), SLOT(setTestsDirectory())); |
59 | connect(m_ui.actionSpecify_khtml_directory, SIGNAL(triggered(bool)), SLOT(setKHTMLDirectory())); |
60 | connect(m_ui.actionSpecify_output_directory, SIGNAL(triggered(bool)), SLOT(setOutputDirectory())); |
61 | connect(m_ui.actionRun_tests, SIGNAL(triggered(bool)), SLOT(runTests())); |
62 | |
63 | connect(m_ui.pauseContinueButton, SIGNAL(clicked(bool)), SLOT(pauseContinueButtonClicked())); |
64 | connect(m_ui.saveLogButton, SIGNAL(clicked(bool)), SLOT(saveLogButtonClicked())); |
65 | |
66 | connect(m_ui.treeWidget, SIGNAL(customContextMenuRequested(QPoint)), |
67 | this, SLOT(treeWidgetContextMenuRequested(QPoint))); |
68 | |
69 | // Setup actions' default state |
70 | m_ui.progressBar->setValue(0); |
71 | m_ui.textEdit->setReadOnly(true); |
72 | m_ui.actionRun_tests->setEnabled(false); |
73 | m_ui.pauseContinueButton->setEnabled(false); |
74 | |
75 | m_ui.treeWidget->headerItem()->setTextAlignment(0, Qt::AlignLeft); |
76 | m_ui.treeWidget->headerItem()->setText(0, i18n("Available Tests: 0")); |
77 | |
78 | // Load default values for tests directory/khtml directory... |
79 | KConfig config("testregressiongui", KConfig::SimpleConfig); |
80 | KConfigGroup grp = config.group("<default>"); |
81 | |
82 | m_testsUrl = KUrl::fromPath(grp.readPathEntry("TestsDirectory", QString())); |
83 | m_khtmlUrl = KUrl::fromPath(grp.readPathEntry("KHTMLDirectory", QString())); |
84 | |
85 | initTestsDirectory(); |
86 | |
87 | // Init early visible items in the text edit... |
88 | initLegend(); |
89 | initOutputBrowser(); |
90 | } |
91 | |
92 | TestRegressionWindow::~TestRegressionWindow() |
93 | { |
94 | if(m_activeProcess) |
95 | { |
96 | m_activeProcess->kill(); |
97 | |
98 | /* This leads to: |
99 | * QProcess object destroyed while process is still running. |
100 | * Any idea why?? |
101 | delete m_activeProcess; |
102 | */ |
103 | |
104 | m_activeProcess = 0; |
105 | } |
106 | } |
107 | |
108 | void TestRegressionWindow::toggleJSTests(bool checked) |
109 | { |
110 | if(checked) |
111 | { |
112 | Q_SET_FLAG(m_flags, JSTests) |
113 | Q_UNSET_FLAG(m_flags, HTMLTests) |
114 | |
115 | m_ui.actionOnly_run_HTML_tests->setChecked(false); |
116 | } |
117 | else |
118 | Q_UNSET_FLAG(m_flags, JSTests) |
119 | |
120 | // Eventually update progress bar range... |
121 | updateProgressBarRange(); |
122 | } |
123 | |
124 | void TestRegressionWindow::toggleHTMLTests(bool checked) |
125 | { |
126 | if(checked) |
127 | { |
128 | Q_SET_FLAG(m_flags, HTMLTests) |
129 | Q_UNSET_FLAG(m_flags, JSTests) |
130 | |
131 | m_ui.actionOnly_run_JS_tests->setChecked(false); |
132 | } |
133 | else |
134 | Q_UNSET_FLAG(m_flags, HTMLTests) |
135 | |
136 | // Eventually update progress bar range... |
137 | updateProgressBarRange(); |
138 | } |
139 | |
140 | void TestRegressionWindow::toggleDebugOutput(bool checked) |
141 | { |
142 | if(checked) |
143 | Q_SET_FLAG(m_flags, DebugOutput) |
144 | else |
145 | Q_UNSET_FLAG(m_flags, DebugOutput) |
146 | } |
147 | |
148 | void TestRegressionWindow::toggleNoXvfbUse(bool checked) |
149 | { |
150 | if(checked) |
151 | Q_SET_FLAG(m_flags, NoXvfbUse) |
152 | else |
153 | Q_UNSET_FLAG(m_flags, NoXvfbUse) |
154 | } |
155 | |
156 | void TestRegressionWindow::setTestsDirectory() |
157 | { |
158 | m_testsUrl = KFileDialog::getExistingDirectory(); |
159 | |
160 | initTestsDirectory(); |
161 | loadOutputHTML(); |
162 | } |
163 | |
164 | void TestRegressionWindow::setOutputDirectory() |
165 | { |
166 | m_outputUrl = KFileDialog::getExistingDirectory(); |
167 | loadOutputHTML(); |
168 | } |
169 | |
170 | void TestRegressionWindow::initTestsDirectory() |
171 | { |
172 | bool okay = !m_testsUrl.isEmpty(); |
173 | if(okay) |
174 | { |
175 | const char *subdirs[] = { "tests", "baseline", "output", "resources"}; |
176 | for(int i = 0; i <= 3; i++) |
177 | { |
178 | QFileInfo sourceDir(m_testsUrl.path() + "/"+ subdirs[i]); //krazy:exclude=duoblequote_chars DOM demands chars |
179 | if(!sourceDir.exists() || !sourceDir.isDir()) |
180 | { |
181 | KMessageBox::error(0, i18n("Please choose a valid 'khtmltests/regression/' directory.")); |
182 | |
183 | okay = false; |
184 | m_testsUrl = KUrl(); |
185 | break; |
186 | } |
187 | } |
188 | } |
189 | |
190 | if(okay) |
191 | { |
192 | // Clean up... |
193 | m_itemMap.clear(); |
194 | m_ignoreMap.clear(); |
195 | m_failureMap.clear(); |
196 | m_directoryMap.clear(); |
197 | |
198 | m_ui.treeWidget->clear(); |
199 | |
200 | if(!m_khtmlUrl.isEmpty()) |
201 | m_ui.actionRun_tests->setEnabled(true); |
202 | |
203 | // Initialize map (to prevent assert below)... |
204 | m_directoryMap.insert(QString(), QStringList()); |
205 | |
206 | // Setup root tree widget item... |
207 | (void) new QTreeWidgetItem(m_ui.treeWidget, QStringList(m_testsUrl.path() + "/tests")); |
208 | |
209 | // Check for ignore & failure file in root directory... |
210 | QString ignoreFile = m_testsUrl.path() + "/tests/ignore"; |
211 | QString failureFile = m_testsUrl.path() + "/tests/KNOWN_FAILURES"; |
212 | |
213 | QStringList ignoreFileList = readListFile(ignoreFile); |
214 | QStringList failureFileList = readListFile(failureFile); |
215 | |
216 | if(!ignoreFileList.isEmpty()) |
217 | m_ignoreMap.insert(QString(), ignoreFileList); |
218 | |
219 | if(!failureFileList.isEmpty()) |
220 | m_failureMap.insert(QString(), failureFileList); |
221 | |
222 | // Remember directory... |
223 | KConfig config("testregressiongui", KConfig::SimpleConfig); |
224 | KConfigGroup grp = config.group("<default>"); |
225 | grp.writePathEntry("TestsDirectory", m_testsUrl.path()); |
226 | |
227 | // Start listing directory... |
228 | KUrl listUrl = m_testsUrl; listUrl.addPath("tests"); |
229 | KIO::ListJob *job = KIO::listRecursive(listUrl, KIO::HideProgressInfo, false /* no hidden files */); |
230 | |
231 | connect(job, SIGNAL(result(KJob*)), SLOT(directoryListingFinished(KJob*))); |
232 | |
233 | connect(job, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), |
234 | this, SLOT(directoryListingResult(KIO::Job*,KIO::UDSEntryList))); |
235 | } |
236 | } |
237 | |
238 | void TestRegressionWindow::setKHTMLDirectory() |
239 | { |
240 | m_khtmlUrl = KFileDialog::getExistingDirectory(); |
241 | |
242 | if(!m_khtmlUrl.isEmpty()) |
243 | { |
244 | const char *subdirs[] = { "css", "dom", "xml", "html"}; // That's enough ;-) |
245 | for(int i = 0; i <= 3; i++) |
246 | { |
247 | QFileInfo sourceDir(m_khtmlUrl.path() + "/"+ subdirs[i]); //krazy:exclude=duoblequote_chars DOM demands chars |
248 | if(!sourceDir.exists() || !sourceDir.isDir()) |
249 | { |
250 | KMessageBox::error(0, i18n("Please choose a valid 'khtml/' build directory.")); |
251 | |
252 | m_khtmlUrl = KUrl(); |
253 | break; |
254 | } |
255 | } |
256 | |
257 | // Remember directory... |
258 | KConfig config("testregressiongui", KConfig::SimpleConfig); |
259 | KConfigGroup grp = config.group("<default>"); |
260 | grp.writePathEntry("KHTMLDirectory", m_khtmlUrl.path()); |
261 | |
262 | if(!m_testsUrl.isEmpty() && !m_khtmlUrl.isEmpty()) |
263 | m_ui.actionRun_tests->setEnabled(true); |
264 | } |
265 | } |
266 | |
267 | void TestRegressionWindow::directoryListingResult(KIO::Job *, const KIO::UDSEntryList &list) |
268 | { |
269 | KIO::UDSEntryList::ConstIterator it = list.constBegin(); |
270 | const KIO::UDSEntryList::ConstIterator end = list.constEnd(); |
271 | |
272 | for(; it != end; ++it) |
273 | { |
274 | const KIO::UDSEntry &entry = *it; |
275 | |
276 | QString name = entry.stringValue(KIO::UDSEntry::UDS_NAME); |
277 | if(entry.isDir()) // Create new map entry... |
278 | { |
279 | assert(m_directoryMap.constFind(name) == m_directoryMap.constEnd()); |
280 | m_directoryMap.insert(name, QStringList()); |
281 | |
282 | QString ignoreFile = m_testsUrl.path() + "/tests/"+ name + "/ignore"; |
283 | QString failureFile = m_testsUrl.path() + "/tests/"+ name + "/KNOWN_FAILURES"; |
284 | |
285 | QStringList ignoreFileList = readListFile(ignoreFile); |
286 | QStringList failureFileList = readListFile(failureFile); |
287 | |
288 | if(!ignoreFileList.isEmpty()) |
289 | m_ignoreMap.insert(name, ignoreFileList); |
290 | |
291 | if(!failureFileList.isEmpty()) |
292 | m_failureMap.insert(name, failureFileList); |
293 | } |
294 | else if(name.endsWith(".html") || name.endsWith( ".htm") || |
295 | name.endsWith(".xhtml") || name.endsWith( ".xml") || name.endsWith( ".js")) |
296 | { |
297 | int lastSlashPos = name.lastIndexOf( |
298 | |
299 | QString cachedDirectory = (lastSlashPos > 0 ? name.mid(0, lastSlashPos) : QString()); |
300 | QString cachedFilename = name.mid(lastSlashPos + 1); |
301 | |
302 | assert(m_directoryMap.constFind(cachedDirectory) != m_directoryMap.constEnd()); |
303 | m_directoryMap[cachedDirectory].append(cachedFilename); |
304 | } |
305 | } |
306 | } |
307 | |
308 | void TestRegressionWindow::directoryListingFinished(KJob *) |
309 | { |
310 | QTreeWidgetItem *topLevelItem = m_ui.treeWidget->topLevelItem(0); |
311 | |
312 | // Gather a lot of statistics... |
313 | unsigned long availableDomFiles = 0; |
314 | unsigned long availableDumpFiles = 0; |
315 | unsigned long availableRenderFiles = 0; |
316 | |
317 | unsigned long ignoredJSTests = 0; |
318 | unsigned long availableJSTests = 0; |
319 | |
320 | unsigned long ignoredXMLTests = 0; |
321 | unsigned long availableXMLTests = 0; |
322 | |
323 | unsigned long ignoredHTMLTests = 0; |
324 | unsigned long availableHTMLTests = 0; |
325 | |
326 | unsigned long ignoredDOMTSTests = 0; |
327 | unsigned long availableDOMTSTests = 0; |
328 | |
329 | // Start the actual data processing... |
330 | QMap<QString, QStringList>::const_iterator it = m_directoryMap.constBegin(); |
331 | const QMap<QString, QStringList>::const_iterator end = m_directoryMap.constEnd(); |
332 | |
333 | for(; it != end; ++it) |
334 | { |
335 | QString directory = it.key(); |
336 | QStringList filenames = it.value(); |
337 | |
338 | if(filenames.isEmpty()) // Do not add empty directories at all... |
339 | continue; |
340 | |
341 | bool hasIgnores = (m_ignoreMap.constFind(directory) != m_directoryMap.constEnd()); |
342 | bool hasFailures = (m_failureMap.constFind(directory) != m_failureMap.constEnd()); |
343 | |
344 | // Extract parent directory... |
345 | int position = directory.lastIndexOf( |
346 | |
347 | QString parentDirectory = directory.mid(0, (position == -1 ? 0 : position)); |
348 | QString parentDirectoryItem = directory.mid(position + 1); |
349 | |
350 | bool hasParentIgnores = (m_ignoreMap.constFind(parentDirectory) != m_directoryMap.constEnd()); |
351 | bool hasParentFailures = (m_failureMap.constFind(parentDirectory) != m_failureMap.constEnd()); |
352 | |
353 | // Sort in ascending order... |
354 | filenames.sort(); |
355 | |
356 | QStringList::const_iterator it2 = filenames.constBegin(); |
357 | const QStringList::const_iterator end2 = filenames.constEnd(); |
358 | |
359 | // Create new tree widget item for the active directory... |
360 | QTreeWidgetItem *parent = topLevelItem; |
361 | |
362 | if(!directory.isEmpty()) |
363 | { |
364 | parent = new QTreeWidgetItem(topLevelItem, QStringList(directory)); |
365 | |
366 | // Directory is completely ignored, mark it 'yellow'... |
367 | if(hasParentIgnores && m_ignoreMap[parentDirectory].contains(parentDirectoryItem)) |
368 | parent->setIcon(0, m_ignorePixmap); |
369 | |
370 | // Directory is completely known to fail, mark it 'red'... |
371 | if(hasParentFailures && m_failureMap[parentDirectory].contains(parentDirectoryItem)) |
372 | parent->setIcon(0, m_failKnownPixmap); |
373 | } |
374 | |
375 | // Add all contained files as new items below 'parent'... |
376 | for(; it2 != end2; ++it2) |
377 | { |
378 | QString test = (*it2); |
379 | QString cacheName = directory + "/"+ test; //krazy:exclude=duoblequote_chars DOM demands chars |
380 | |
381 | QTreeWidgetItem *testItem = new QTreeWidgetItem(parent, QStringList(KUrl(test).path())); |
382 | |
383 | // Remember name <-> item pair... |
384 | assert(m_itemMap.contains(cacheName)); |
385 | m_itemMap.insert(cacheName, testItem); |
386 | |
387 | bool ignore = (hasIgnores && m_ignoreMap[directory].contains(test)); |
388 | bool ignoreParent = (hasParentIgnores && m_ignoreMap[parentDirectory].contains(parentDirectoryItem)); |
389 | |
390 | bool failure = (hasFailures && m_failureMap[directory].contains(test)); |
391 | |
392 | // Check baseline directory for this test... |
393 | QString baseLinePath = m_testsUrl.path() + "/baseline/"+ cacheName; |
394 | |
395 | bool dom[9], render[9]; |
396 | for(unsigned int i = 0; i < 9; ++i) |
397 | { |
398 | if(i == 0) |
399 | { |
400 | dom[i] = (QFileInfo(baseLinePath + "-dom").exists()); |
401 | render[i] = (QFileInfo(baseLinePath + "-render").exists()); |
402 | } |
403 | else |
404 | { |
405 | dom[i] = (QFileInfo(baseLinePath + "-"+ QString::number(i) + "-dom").exists()); //krazy:exclude=duoblequote_chars DOM demands chars |
406 | render[i] = (QFileInfo(baseLinePath + "-"+ QString::number(i) + "-render").exists()); //krazy:exclude=duoblequote_chars DOM demands chars |
407 | } |
408 | } |
409 | |
410 | bool dump = (QFileInfo(baseLinePath + "-dump.png").exists()); |
411 | |
412 | // Ignored tests are marked 'yellow'... |
413 | if(ignore) |
414 | testItem->setIcon(0, m_ignorePixmap); |
415 | |
416 | // Tests, known to fail, are marked 'red'... |
417 | if(failure) |
418 | testItem->setIcon(0, m_failKnownPixmap); |
419 | |
420 | // Detect whether the tests has no corresponding baseline items... |
421 | if(!ignore && !failure) |
422 | { |
423 | if(!dom[0] && !dump && !render && !cacheName.endsWith(".js") && !cacheName.startsWith( "domts")) |
424 | { |
425 | // See if parent directory is completely ignored... |
426 | if(!ignoreParent) |
427 | testItem->setIcon(0, m_noBaselinePixmap); |
428 | } |
429 | } |
430 | |
431 | // Update statistics... |
432 | if(dump) |
433 | availableDumpFiles++; |
434 | |
435 | for(unsigned i = 0; i < 9; ++i) |
436 | { |
437 | if(dom[i]) |
438 | availableDomFiles++; |
439 | |
440 | if(render[i]) |
441 | availableRenderFiles++; |
442 | } |
443 | |
444 | // Count DOM Testsuite files separated... (these have no baseline items!) |
445 | if(cacheName.startsWith("domts")) |
446 | { |
447 | // See if parent directory is completely ignored... |
448 | if(ignore || ignoreParent) |
449 | ignoredDOMTSTests++; |
450 | else |
451 | availableDOMTSTests++; |
452 | } |
453 | |
454 | if(cacheName.endsWith(".html") || cacheName.endsWith( ".htm") || cacheName.endsWith( ".xhtml")) |
455 | { |
456 | if(ignore || ignoreParent) |
457 | ignoredHTMLTests++; |
458 | else |
459 | availableHTMLTests++; |
460 | } |
461 | else if(cacheName.endsWith(".xml")) |
462 | { |
463 | if(ignore || ignoreParent) |
464 | ignoredXMLTests++; |
465 | else |
466 | availableXMLTests++; |
467 | } |
468 | else if(cacheName.endsWith(".js")) |
469 | { |
470 | unsigned long containedTests = 0; |
471 | |
472 | // Try hard to _ESTIMATE_ the number of tests... |
473 | // I really meant estimate, no way to calculate it perfectly. |
474 | |
475 | QString jsFilePath = m_testsUrl.path() + "/tests/"+ cacheName; |
476 | assert(QFileInfo(jsFilePath).exists() == true); |
477 | |
478 | QStringList fileList = readListFile(jsFilePath); |
479 | QString fileContent = fileList.join(""); |
480 | |
481 | // #1 -> Check js file for the 'reportResult' calls... |
482 | containedTests = fileContent.count("reportResult"); |
483 | |
484 | // #2 -> Check js file for 'openPage' calls... |
485 | containedTests += fileContent.count("openPage"); |
486 | |
487 | // #3 -> Check js file for 'checkOutput' calls... |
488 | containedTests += fileContent.count("checkOutput"); |
489 | |
490 | // #4 -> Fallback for ie. mozilla/ecma files... |
491 | if(containedTests == 0) // Doesn't use 'reportResult' scheme... |
492 | containedTests++; |
493 | |
494 | if(ignore || ignoreParent) |
495 | ignoredJSTests += containedTests; |
496 | else |
497 | availableJSTests += containedTests; |
498 | } |
499 | } |
500 | } |
501 | |
502 | // Now we can calculate all ignored/available tests... |
503 | unsigned long ignoredTests = ignoredJSTests + ignoredXMLTests + ignoredHTMLTests; |
504 | unsigned long availableTests = availableJSTests + availableXMLTests + availableHTMLTests; |
505 | |
506 | // This estimates the number of total tests, depending on the mode... |
507 | m_totalTests = availableDomFiles + availableDumpFiles + availableRenderFiles + |
508 | availableDOMTSTests + availableJSTests; |
509 | |
510 | m_totalTestsJS = availableJSTests; |
511 | m_totalTestsDOMTS = availableDOMTSTests; |
512 | |
513 | // Update progress bar range... |
514 | updateProgressBarRange(); |
515 | |
516 | QString statistics = QString("<body><table border='0' align='center' cellspacing='15'>") + |
517 | QString("<tr valign='top'><td colspan='3'><center><b>Statistics</b></center></td></tr>") + |
518 | QString("<tr valign='middle'><td>JS Tests</td><td>"+ QString::number(availableJSTests) + "</td><td>("+ QString::number(ignoredJSTests) + " ignored)</td></tr>") + |
519 | QString("<tr valign='middle'><td>XML Tests</td><td>"+ QString::number(availableXMLTests) + "</td><td>("+ QString::number(ignoredXMLTests) + " ignored)</td></tr>") + |
520 | QString("<tr valign='middle'><td>HTML Tests</td><td>"+ QString::number(availableHTMLTests) + "</td><td>("+ QString::number(ignoredHTMLTests) + " ignored)</td></tr>") + |
521 | QString("</table></body>"); |
522 | |
523 | // Go to end... |
524 | QTextCursor cursor = m_ui.textEdit->textCursor(); |
525 | cursor.movePosition(QTextCursor::End); |
526 | m_ui.textEdit->setTextCursor(cursor); |
527 | |
528 | // Insert statistics... |
529 | m_ui.textEdit->insertHtml(statistics); |
530 | |
531 | // Update treeview... |
532 | m_ui.treeWidget->headerItem()->setText(0, i18n("Available Tests: %1 (ignored: %2)", availableTests, ignoredTests)); |
533 | } |
534 | |
535 | void TestRegressionWindow::updateProgressBarRange() const |
536 | { |
537 | if(m_totalTests != 0 && m_totalTestsJS != 0) |
538 | { |
539 | unsigned long totalTests = m_totalTests; |
540 | |
541 | if(Q_HAS_FLAG(m_flags, JSTests)) |
542 | totalTests = m_totalTestsJS; |
543 | else if(Q_HAS_FLAG(m_flags, HTMLTests)) |
544 | { |
545 | totalTests -= m_totalTestsJS; |
546 | totalTests -= m_totalTestsDOMTS; |
547 | } |
548 | |
549 | m_ui.progressBar->setRange(0, totalTests); |
550 | } |
551 | } |
552 | |
553 | void TestRegressionWindow::pauseContinueButtonClicked() |
554 | { |
555 | assert(m_activeProcess != 0); |
556 | |
557 | if(!m_suspended) |
558 | { |
559 | // Suspend process |
560 | kill(m_activeProcess->pid(), SIGSTOP); |
561 | |
562 | m_suspended = true; |
563 | m_ui.pauseContinueButton->setText(i18n("Continue")); |
564 | } |
565 | else |
566 | { |
567 | // Continue process |
568 | kill(m_activeProcess->pid(), SIGCONT); |
569 | |
570 | m_suspended = false; |
571 | m_ui.pauseContinueButton->setText(i18n("Pause")); |
572 | } |
573 | } |
574 | |
575 | void TestRegressionWindow::saveLogButtonClicked() |
576 | { |
577 | assert(m_activeProcess == 0); |
578 | m_saveLogUrl = KFileDialog::getExistingDirectory(); |
579 | |
580 | QString fileName = m_saveLogUrl.path() + "/logOutput.html"; |
581 | if(QFileInfo(fileName).exists()) |
582 | { |
583 | // Remove file if already existent... |
584 | QFile file(fileName); |
585 | if(!file.remove()) |
586 | { |
587 | kError() << " Can't remove "<< fileName << endl; |
588 | exit(1); |
589 | } |
590 | } |
591 | } |
592 | |
593 | void TestRegressionWindow::runTests() |
594 | { |
595 | // Run in all-in-one mode... |
596 | m_runCounter = 0; |
597 | m_testCounter = 0; |
598 | |
599 | initRegressionTesting(QString()); |
600 | } |
601 | |
602 | void TestRegressionWindow::runSingleTest() |
603 | { |
604 | assert(m_activeTreeItem != 0); |
605 | |
606 | QString testFileName = pathFromItem(m_activeTreeItem); |
607 | |
608 | // Run in single-test mode... |
609 | m_runCounter = 0; |
610 | m_testCounter = -1; |
611 | |
612 | initRegressionTesting(testFileName); |
613 | } |
614 | |
615 | void TestRegressionWindow::initRegressionTesting(const QString &testFileName) |
616 | { |
617 | assert(m_activeProcess == 0); |
618 | |
619 | m_activeProcess = new QProcess(); |
620 | m_activeProcess->setReadChannelMode(QProcess::MergedChannels); |
621 | |
622 | QStringList environment = QProcess::systemEnvironment(); |
623 | environment << "KDE_DEBUG=false"; // No Dr. Konqi please! |
624 | |
625 | QString program = m_khtmlUrl.path() + "/.libs/testregression"; |
626 | QString program2 = m_khtmlUrl.path() + "/testregression"; // with CMake, it's in $buildir/bin |
627 | |
628 | if(!QFileInfo(program).exists()) |
629 | { |
630 | if(!QFileInfo(program2).exists()) |
631 | { |
632 | KMessageBox::error(0, i18n("Cannot find testregression executable.")); |
633 | return; |
634 | } |
635 | else |
636 | { |
637 | program = program2; |
638 | } |
639 | } |
640 | |
641 | QStringList arguments; |
642 | arguments << "--base"<< m_testsUrl.path(); |
643 | |
644 | if(!m_outputUrl.isEmpty()) |
645 | arguments << "--output"<< m_outputUrl.path(); |
646 | if(!testFileName.isEmpty()) |
647 | arguments << "--test"<< testFileName; |
648 | |
649 | if(Q_HAS_FLAG(m_flags, JSTests)) |
650 | arguments << "--js"; |
651 | if(Q_HAS_FLAG(m_flags, HTMLTests)) |
652 | arguments << "--html"; |
653 | if(Q_HAS_FLAG(m_flags, DebugOutput)) |
654 | arguments << "--debug"; |
655 | if(Q_HAS_FLAG(m_flags, NoXvfbUse)) |
656 | arguments << "--noxvfb"; |
657 | |
658 | connect(m_activeProcess, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(testerExited(int,QProcess::ExitStatus))); |
659 | connect(m_activeProcess, SIGNAL(readyReadStandardOutput()), SLOT(testerReceivedData())); |
660 | |
661 | // Clear processing queue before starting... |
662 | m_processingQueue.clear(); |
663 | |
664 | // Clean up gui... |
665 | m_ui.textEdit->clear(); |
666 | m_ui.progressBar->reset(); |
667 | m_ui.saveLogButton->setEnabled(false); |
668 | m_ui.actionRun_tests->setEnabled(false); |
669 | m_ui.pauseContinueButton->setEnabled(true); |
670 | |
671 | // Start regression testing process... |
672 | m_activeProcess->setEnvironment(environment); |
673 | m_activeProcess->start(program, arguments, QIODevice::ReadOnly); |
674 | } |
675 | |
676 | void TestRegressionWindow::initOutputBrowser() |
677 | { |
678 | assert(m_browserPart == 0); |
679 | m_browserPart = new KHTMLPart(m_ui.secondTab, m_ui.secondTab, KHTMLPart::BrowserViewGUI); |
680 | |
681 | // Setup vertical layout for the browser widget... |
682 | QVBoxLayout *layout = new QVBoxLayout(); |
683 | layout->addWidget(m_browserPart->widget()); |
684 | m_ui.secondTab->setLayout(layout); |
685 | |
686 | m_browserPart->setJavaEnabled(true); |
687 | m_browserPart->setJScriptEnabled(true); |
688 | m_browserPart->setPluginsEnabled(true); |
689 | m_browserPart->setURLCursor(QCursor(Qt::PointingHandCursor)); |
690 | |
691 | m_browserPart->widget()->show(); |
692 | |
693 | // Check if there is already an output/index.html present... |
694 | loadOutputHTML(); |
695 | } |
696 | |
697 | void TestRegressionWindow::loadOutputHTML() const |
698 | { |
699 | if(m_testsUrl.isEmpty()) |
700 | return; |
701 | |
702 | QString fileName = m_testsUrl.path() + "/output/index.html"; |
703 | if(!m_outputUrl.isEmpty()) |
704 | fileName = m_outputUrl.path() + "/index.html"; |
705 | |
706 | QFileInfo indexHtml(fileName); |
707 | if(indexHtml.exists()) |
708 | { |
709 | m_browserPart->openUrl(KUrl::fromPath(fileName)); |
710 | m_ui.tabWidget->setTabEnabled(1, true); |
711 | } |
712 | else |
713 | m_ui.tabWidget->setTabEnabled(1, false); |
714 | } |
715 | |
716 | void TestRegressionWindow::updateItemStatus(TestResult result, QTreeWidgetItem *item, const QString &testFileName) |
717 | { |
718 | if(!item) |
719 | return; |
720 | |
721 | // Ensure item is visible... |
722 | QTreeWidgetItem *parent = item; |
723 | while(parent != 0) |
724 | { |
725 | m_ui.treeWidget->setItemExpanded(parent, true); |
726 | parent = parent->parent(); |
727 | } |
728 | |
729 | m_ui.treeWidget->scrollToItem(item); |
730 | |
731 | bool updateIcon = true; |
732 | if(m_lastName == testFileName && !m_lastName.isEmpty()) |
733 | { |
734 | if(m_lastResult == result) |
735 | updateIcon = false; |
736 | else if((m_lastResult == Pass || m_lastResult == PassUnexpected) && |
737 | (result == Fail || result == FailKnown || result == Crash)) |
738 | { |
739 | // If one part of the test (render/dom/paint) passed, |
740 | // and the current part fails, update to 'failed' icon... |
741 | updateIcon = true; |
742 | } |
743 | else if((m_lastResult == Fail || m_lastResult == FailKnown || m_lastResult == Crash) && |
744 | (result == Pass || result == PassUnexpected)) |
745 | { |
746 | // If one part of the test (render/dom/paint) failed, |
747 | // and the current part passes, don't update to 'passed' icon... |
748 | updateIcon = false; |
749 | } |
750 | } |
751 | |
752 | // Update icon, if necessary... |
753 | if(updateIcon) |
754 | { |
755 | if(result == Crash) |
756 | item->setIcon(0, m_crashPixmap); |
757 | else if(result == Fail) |
758 | item->setIcon(0, m_failPixmap); |
759 | else if(result == FailKnown) |
760 | item->setIcon(0, m_failKnownPixmap); |
761 | else if(result == Pass) |
762 | item->setIcon(0, m_passPixmap); |
763 | else if(result == PassUnexpected) |
764 | item->setIcon(0, m_passUnexpectedPixmap); |
765 | else // Unhandled state... |
766 | assert(false); |
767 | } |
768 | |
769 | // Remember test & result... |
770 | m_lastResult = result; |
771 | m_lastName = testFileName; |
772 | m_activeTreeItem = item; |
773 | } |
774 | |
775 | void TestRegressionWindow::initLegend() |
776 | { |
777 | // Init pixmaps... |
778 | m_failPixmap = QPixmap(":/test/pics/fail.xpm"); |
779 | m_failKnownPixmap = QPixmap(":/test/pics/failKnown.xpm"); |
780 | m_passPixmap = QPixmap(":/test/pics/pass.xpm"); |
781 | m_passUnexpectedPixmap = QPixmap(":/test/pics/passUnexpected.xpm"); |
782 | m_ignorePixmap = QPixmap(":/test/pics/ignore.xpm"); |
783 | m_crashPixmap = QPixmap(":/test/pics/crash.xpm"); |
784 | m_noBaselinePixmap = QPixmap(":/test/pics/noBaseline.xpm"); |
785 | |
786 | QString legend = QLatin1String("<body><center><font size='8'>Welcome to the khtml<br/>") + |
787 | QLatin1String("regression testing tool!</font></center><br/><br/>") + |
788 | QLatin1String("<table border='0' align='center' cellspacing='15'>") + |
789 | QLatin1String("<tr valign='top'><td colspan='2'><center><b>Legend</b></center></td></tr>") + |
790 | QLatin1String("<tr valign='middle'><td>Pass</td><td><img src=':/test/pics/pass.xpm'></td></tr>") + |
791 | QLatin1String("<tr valign='middle'><td>Pass unexpected</td><td><img src=':/test/pics/passUnexpected.xpm'></td></tr>") + |
792 | QLatin1String("<tr valign='middle'><td>Fail</td><td><img src=':/test/pics/fail.xpm'></td></tr>") + |
793 | QLatin1String("<tr valign='middle'><td>Fail known</td><td><img src=':/test/pics/failKnown.xpm'></td></tr>") + |
794 | QLatin1String("<tr valign='middle'><td>Ignore</td><td><img src=':/test/pics/ignore.xpm'></td></tr>") + |
795 | QLatin1String("<tr valign='middle'><td>Baseline missing</td><td><img src=':/test/pics/noBaseline.xpm'></td></tr>") + |
796 | QLatin1String("<tr valign='middle'><td>Crash</td><td><img src=':/test/pics/crash.xpm'></td></tr>") + |
797 | QLatin1String("</table></body>"); |
798 | |
799 | m_ui.textEdit->setHtml(legend); |
800 | } |
801 | |
802 | void TestRegressionWindow::testerExited(int /* exitCode */, QProcess::ExitStatus exitStatus) |
803 | { |
804 | assert(m_activeProcess != 0); |
805 | assert(m_activeTreeItem != 0); |
806 | |
807 | if(exitStatus == QProcess::CrashExit) // Special case: crash! |
808 | { |
809 | QTreeWidgetItem *useItem = m_activeTreeItem; |
810 | |
811 | if(m_testCounter >= 0 || m_runCounter > 0) // Single-tests mode invoked on a directory OR All-test-mode |
812 | { |
813 | QTreeWidgetItem *parent = useItem->parent(); |
814 | assert(parent != 0); |
815 | |
816 | useItem = parent->child(parent->indexOfChild(useItem) + 1); |
817 | assert(useItem != 0); |
818 | } |
819 | |
820 | // Reflect crashed test... |
821 | updateItemStatus(Crash, useItem, QString()); |
822 | } |
823 | |
824 | if(m_testCounter >= 0) // All-tests mode |
825 | m_ui.progressBar->setValue(m_ui.progressBar->maximum()); |
826 | |
827 | // Eventually save log output... |
828 | if(!m_saveLogUrl.isEmpty()) |
829 | { |
830 | // We should close our written log with </body></html>. |
831 | m_processingQueue.enqueue(QString::fromLatin1("\n</body>\n</html>")); |
832 | |
833 | if(!m_justProcessingQueue) |
834 | { |
835 | m_justProcessingQueue = true; |
836 | QTimer::singleShot(50, this, SLOT(processQueue())); |
837 | } |
838 | } |
839 | |
840 | // Cleanup gui... |
841 | m_ui.saveLogButton->setEnabled(true); |
842 | m_ui.actionRun_tests->setEnabled(true); |
843 | m_ui.pauseContinueButton->setEnabled(false); |
844 | |
845 | // Check if there is already an output/index.html present... |
846 | loadOutputHTML(); |
847 | |
848 | // Cleanup data.. |
849 | delete m_activeProcess; |
850 | m_activeProcess = 0; |
851 | |
852 | m_runCounter = 0; |
853 | m_testCounter = 0; |
854 | m_activeTreeItem = 0; |
855 | } |
856 | |
857 | void TestRegressionWindow::testerReceivedData() |
858 | { |
859 | assert(m_activeProcess != 0); |
860 | |
861 | QString data(m_activeProcess->readAllStandardOutput()); |
862 | QStringList list = data.split( |
863 | |
864 | QStringList::const_iterator it = list.constBegin(); |
865 | const QStringList::const_iterator end = list.constEnd(); |
866 | |
867 | for(; it != end; ++it) |
868 | { |
869 | QString temp = *it; |
870 | if(!temp.isEmpty()) |
871 | m_processingQueue.enqueue(temp); |
872 | } |
873 | |
874 | if(!m_justProcessingQueue) |
875 | { |
876 | m_justProcessingQueue = true; |
877 | QTimer::singleShot(50, this, SLOT(processQueue())); |
878 | } |
879 | } |
880 | |
881 | void TestRegressionWindow::processQueue() |
882 | { |
883 | while(!m_processingQueue.isEmpty()) |
884 | { |
885 | QString data = m_processingQueue.dequeue(); |
886 | TestResult result = Unknown; |
887 | |
888 | QString cacheName = extractTestNameFromData(data, result); |
889 | |
890 | if(result != Unknown) // Yes, we're dealing with a test result... |
891 | { |
892 | if(cacheName.isEmpty()) // Make sure everything is alright! |
893 | { |
894 | kError() << "Couldn't extract cacheName from data=\""<< data << "\"! Ignoring!"<< endl; |
895 | continue; |
896 | } |
897 | } |
898 | |
899 | parseRegressionTestingOutput(data, result, cacheName); |
900 | } |
901 | |
902 | m_justProcessingQueue = false; |
903 | } |
904 | |
905 | void TestRegressionWindow::addToIgnores() |
906 | { |
907 | assert(m_activeTreeItem != 0); |
908 | |
909 | QString treeItemText = pathFromItem(m_activeTreeItem); |
910 | |
911 | // Extract directory/file name... |
912 | int position = treeItemText.lastIndexOf( |
913 | |
914 | QString directory = treeItemText.mid(0, (position == -1 ? 0 : position)); |
915 | QString fileName = treeItemText.mid(position + 1); |
916 | |
917 | // Read corresponding ignore file.. |
918 | QString ignoreFile = m_testsUrl.path() + "/tests/"+ directory + "/ignore"; |
919 | QStringList ignoreFileList = readListFile(ignoreFile); |
920 | |
921 | if(!ignoreFileList.contains(fileName)) |
922 | ignoreFileList.append(fileName); |
923 | |
924 | // Commit changes... |
925 | writeListFile(ignoreFile, ignoreFileList); |
926 | |
927 | // Reset icon status... |
928 | m_activeTreeItem->setIcon(0, m_ignorePixmap); |
929 | } |
930 | |
931 | void TestRegressionWindow::removeFromIgnores() |
932 | { |
933 | assert(m_activeTreeItem != 0); |
934 | |
935 | QString treeItemText = pathFromItem(m_activeTreeItem); |
936 | |
937 | // Extract directory/file name... |
938 | int position = treeItemText.lastIndexOf( |
939 | |
940 | QString directory = treeItemText.mid(0, (position == -1 ? 0 : position)); |
941 | QString fileName = treeItemText.mid(position + 1); |
942 | |
943 | // Read corresponding ignore file.. |
944 | QString ignoreFile = m_testsUrl.path() + "/tests/"+ directory + "/ignore"; |
945 | QStringList ignoreFileList = readListFile(ignoreFile); |
946 | |
947 | if(ignoreFileList.contains(fileName)) |
948 | ignoreFileList.removeAll(fileName); |
949 | |
950 | // Commit changes... |
951 | writeListFile(ignoreFile, ignoreFileList); |
952 | |
953 | // Reset icon status... |
954 | m_activeTreeItem->setIcon(0, QPixmap()); |
955 | } |
956 | |
957 | QString TestRegressionWindow::pathFromItem(const QTreeWidgetItem *item) const |
958 | { |
959 | QString path = item->text(0); |
960 | |
961 | QTreeWidgetItem *parent = item->parent(); |
962 | while(parent != 0) |
963 | { |
964 | if(parent->parent() != 0) |
965 | path.prepend(parent->text(0) + "/"); //krazy:exclude=duoblequote_chars DOM demands chars |
966 | |
967 | parent = parent->parent(); |
968 | } |
969 | |
970 | return path; |
971 | } |
972 | |
973 | QString TestRegressionWindow::extractTestNameFromData(QString &data, TestResult &result) const |
974 | { |
975 | if(data.indexOf("PASS") >= 0 || data.indexOf( "FAIL") >= 0) |
976 | { |
977 | // Name extraction regexps... |
978 | QString bracesSelector("[0-9a-zA-Z-_<>\\* +-,.:!?$'\"=/\\[\\]\\(\\)]*"); |
979 | |
980 | QRegExp expPass("PASS: ("+ bracesSelector + ")"); //krazy:exclude=duoblequote_chars DOM demands chars |
981 | QRegExp expPassUnexpected("PASS \\(unexpected!\\): ("+ bracesSelector + ")"); //krazy:exclude=duoblequote_chars DOM demands chars |
982 | |
983 | QRegExp expFail("FAIL: ("+ bracesSelector + ")"); //krazy:exclude=duoblequote_chars DOM demands chars |
984 | QRegExp expFailKnown("FAIL \\(known\\): ("+ bracesSelector + ")"); //krazy:exclude=duoblequote_chars DOM demands chars |
985 | |
986 | // Extract name of test... (while using regexps as rare as possible!) |
987 | int pos = -1; |
988 | QString test; |
989 | |
990 | QRegExp cleanTest(" \\["+ bracesSelector + "\\]"); |
991 | |
992 | pos = expPass.indexIn(data); |
993 | if(pos > -1) { test = expPass.cap(1); result = Pass; } |
994 | |
995 | if(result == Unknown) |
996 | { |
997 | pos = expPassUnexpected.indexIn(data); |
998 | if(pos > -1) { test = expPassUnexpected.cap(1); result = PassUnexpected; } |
999 | } |
1000 | |
1001 | if(result == Unknown) |
1002 | { |
1003 | pos = expFail.indexIn(data); |
1004 | if(pos > -1) { test = expFail.cap(1); result = Fail; } |
1005 | } |
1006 | |
1007 | if(result == Unknown) |
1008 | { |
1009 | pos = expFailKnown.indexIn(data); |
1010 | if(pos > -1) { test = expFailKnown.cap(1); result = FailKnown; } |
1011 | } |
1012 | |
1013 | if(!test.isEmpty() && result != Unknown) // Got information about test... |
1014 | { |
1015 | // Clean up first, so we only get the file name... |
1016 | test.replace(cleanTest, QString()); |
1017 | |
1018 | // Extract cached directory/filename pair... |
1019 | int lastSlashPos = test.lastIndexOf( |
1020 | |
1021 | QString cachedDirectory = (lastSlashPos > 0 ? test.mid(0, lastSlashPos) : QString()); |
1022 | QString cachedFilename = test.mid(lastSlashPos + 1); |
1023 | |
1024 | if(cachedDirectory == ".") // Handle cases like "./empty.html" |
1025 | cachedDirectory.clear(); |
1026 | |
1027 | assert(m_directoryMap.constFind(cachedDirectory) != m_directoryMap.constEnd()); |
1028 | |
1029 | QString cacheName = cachedDirectory + "/"+ cachedFilename; //krazy:exclude=duoblequote_chars DOM demands chars |
1030 | if(m_itemMap.constFind(cacheName) != m_itemMap.constEnd()) |
1031 | { |
1032 | // Highlight test... |
1033 | data.replace(expPass, "<b><font color='green'>PASS:\t\\1</font></b>"); |
1034 | data.replace(expPassUnexpected, "<b><font color='green'>PASS (unexpected!):\t\\1</font></b>"); |
1035 | data.replace(expFail, "<b><font color='red'>FAIL:\t\\1</font></b>"); |
1036 | data.replace(expFailKnown, "<b><font color='red'>FAIL (known):\t\\1</font></b>"); |
1037 | |
1038 | return cacheName; |
1039 | } |
1040 | } |
1041 | } |
1042 | |
1043 | return QString(); |
1044 | } |
1045 | |
1046 | void TestRegressionWindow::parseRegressionTestingOutput(QString data, TestResult result, const QString &cacheName) |
1047 | { |
1048 | if(!cacheName.isEmpty()) |
1049 | { |
1050 | if(m_testCounter >= 0) // Only increment in all-tests mode... |
1051 | m_testCounter++; |
1052 | |
1053 | m_runCounter++; // Always increment... |
1054 | |
1055 | // Update the icon... |
1056 | updateItemStatus(result, m_itemMap[cacheName], cacheName); |
1057 | } |
1058 | |
1059 | // Apply some nice formatting for the statistics... |
1060 | if(data.indexOf("Total") >= 0 || data.indexOf( "Passes") >= 0 || data.indexOf( "Tests completed") >= 0 || |
1061 | data.indexOf("Errors:") >= 0 || data.indexOf( "Failures:") >= 0) |
1062 | { |
1063 | QRegExp expTotal("Total: ([0-9]*)"); |
1064 | QRegExp expPasses("Passes: ([0-9 a-z\\(\\)]*)"); |
1065 | QRegExp expErrors("Errors: ([0-9 a-z]*)"); |
1066 | QRegExp expFailures("Failures: ([0-9 a-z\\(\\)]*)"); |
1067 | |
1068 | data.replace("Tests completed.", "<br><center><h2>Tests completed.</h2></center>"); |
1069 | data.replace(expTotal, "<b><font size='4'>Total: \\1</font></b>"); |
1070 | data.replace(expPasses, "<b><font size='4' color='green'>Passes: \\1</font></b>"); |
1071 | data.replace(expErrors, "<b><font size='4' color='blue'>Errors: \\1</font></b>"); |
1072 | data.replace(expFailures, "<b><font size='4' color='red'>Failures: \\1</font></b>"); |
1073 | } |
1074 | |
1075 | if(!data.contains("</body>\n</html>")) // Don't put <br> behind </html>! |
1076 | data.append("<br>"); |
1077 | |
1078 | // Update text edit... |
1079 | updateLogOutput(data); |
1080 | |
1081 | // Update progressbar... |
1082 | if(m_testCounter > 0) |
1083 | m_ui.progressBar->setValue(m_testCounter); |
1084 | } |
1085 | |
1086 | void TestRegressionWindow::treeWidgetContextMenuRequested(const QPoint &pos) |
1087 | { |
1088 | if((m_testCounter == -1 && m_activeProcess) || (m_testCounter > 0 && m_activeProcess) || |
1089 | m_testsUrl.isEmpty() || m_khtmlUrl.isEmpty()) // Still processing/not ready yet... |
1090 | { |
1091 | return; |
1092 | } |
1093 | |
1094 | QTreeWidgetItem *item = m_ui.treeWidget->itemAt(pos); |
1095 | if(item && item != m_ui.treeWidget->topLevelItem(0)) |
1096 | { |
1097 | m_activeTreeItem = item; |
1098 | |
1099 | // Build & show popup menu... |
1100 | QMenu menu(m_ui.treeWidget); |
1101 | |
1102 | menu.addAction(SmallIcon("media-playback-start"), i18n( "Run test..."), this, SLOT(runSingleTest())); |
1103 | menu.addSeparator(); |
1104 | menu.addAction(SmallIcon("list-add"), i18n( "Add to ignores..."), this, SLOT(addToIgnores())); |
1105 | menu.addAction(SmallIcon("dialog-cancel"), i18n( "Remove from ignores..."), this, SLOT(removeFromIgnores())); |
1106 | |
1107 | if(!menu.exec(m_ui.treeWidget->mapToGlobal(pos))) |
1108 | m_activeTreeItem = 0; // Needs reset... |
1109 | } |
1110 | } |
1111 | |
1112 | void TestRegressionWindow::updateLogOutput(const QString &data) |
1113 | { |
1114 | QTextCursor cursor = m_ui.textEdit->textCursor(); |
1115 | |
1116 | // Append 'data'... |
1117 | m_ui.textEdit->insertHtml(data); |
1118 | |
1119 | // Keep a maximum of 100 lines in the log... |
1120 | const int maxLogLines = 100; |
1121 | |
1122 | long logLines = countLogLines(); |
1123 | if(logLines > maxLogLines) |
1124 | { |
1125 | cursor.movePosition(QTextCursor::Start); |
1126 | cursor.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor, logLines - maxLogLines); |
1127 | cursor.removeSelectedText(); |
1128 | } |
1129 | |
1130 | // Go to end... |
1131 | cursor.movePosition(QTextCursor::End); |
1132 | m_ui.textEdit->setTextCursor(cursor); |
1133 | |
1134 | // Eventually save log output... |
1135 | if(!m_saveLogUrl.isEmpty()) |
1136 | { |
1137 | QString fileName = m_saveLogUrl.path() + "/logOutput.html"; |
1138 | QIODevice::OpenMode fileFlags = QIODevice::WriteOnly; |
1139 | |
1140 | bool fileExists = QFileInfo(fileName).exists(); |
1141 | if(fileExists) |
1142 | fileFlags |= QIODevice::Append; |
1143 | |
1144 | QFile file(fileName); |
1145 | if(!file.open(fileFlags)) |
1146 | { |
1147 | kError() << " Can't open "<< fileName << endl; |
1148 | exit(1); |
1149 | } |
1150 | |
1151 | if(!fileExists) |
1152 | file.write(QString::fromLatin1("<html>\n<body>\n").toLatin1()); |
1153 | |
1154 | file.write(QString(data + "\n").toLatin1()); //krazy:exclude=duoblequote_chars DOM demands chars |
1155 | file.close(); |
1156 | |
1157 | // Reset save log url, if we reached the end... |
1158 | if(data.contains("</body>\n</html>")) |
1159 | m_saveLogUrl = KUrl(); |
1160 | } |
1161 | } |
1162 | |
1163 | unsigned long TestRegressionWindow::countLogLines() const |
1164 | { |
1165 | QTextCursor cursor = m_ui.textEdit->textCursor(); |
1166 | cursor.movePosition(QTextCursor::Start); |
1167 | |
1168 | unsigned long lines = 0; |
1169 | while(cursor.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor)) |
1170 | lines++; |
1171 | |
1172 | return lines; |
1173 | } |
1174 | |
1175 | QStringList TestRegressionWindow::readListFile(const QString &fileName) const |
1176 | { |
1177 | QStringList files; |
1178 | |
1179 | QFileInfo fileInfo(fileName); |
1180 | if(fileInfo.exists()) |
1181 | { |
1182 | QFile file(fileName); |
1183 | if(!file.open(QIODevice::ReadOnly)) |
1184 | { |
1185 | kError() << " Can't open "<< fileName << endl; |
1186 | exit(1); |
1187 | } |
1188 | |
1189 | QString line; |
1190 | |
1191 | QTextStream fileStream(&file); |
1192 | while(!(line = fileStream.readLine()).isNull()) |
1193 | files.append(line); |
1194 | |
1195 | file.close(); |
1196 | } |
1197 | |
1198 | return files; |
1199 | } |
1200 | |
1201 | void TestRegressionWindow::writeListFile(const QString &fileName, const QStringList &content) const |
1202 | { |
1203 | QFile file(fileName); |
1204 | if(!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) |
1205 | { |
1206 | kError() << " Can't open "<< fileName << endl; |
1207 | exit(1); |
1208 | } |
1209 | |
1210 | file.write(content.join("\n").toLatin1()); |
1211 | file.close(); |
1212 | } |
1213 | |
1214 | // vim:ts=4:tw=4:noet |
1215 |
Warning: That file was not part of the compilation database. It may have many parsing errors.