BMC#6101 (Reopened): Reseting the order of test suites is not working.
[qa-tools:testrunner-ui.git] / src / dockmainwindow.cpp
1 #include "dockmainwindow.h"
2 #include "ui_dockmainwindow.h"
3
4 #include "testtreewidget.h"
5 #include "runnercontroller.h"
6 #include "settingsdialog.h"
7 #include "attachmentsdialog.h"
8 #include "filtersdialog.h"
9 #include "resultpromptwidget.h"
10 #include "testrunneruisettings.h"
11 #include "testmodel.h"
12 #include "xmlvalidatormessagehandler.h"
13 #include "mainstatuswidget.h"
14 #include "logwidget.h"
15 #include "stopdialog.h"
16
17 #include <QMenu>
18 #include <QMenuBar>
19 #include <QAction>
20 #include <QToolBar>
21 #include <QApplication>
22 #include <QIcon>
23 #include <QFileDialog>
24 #include <QDesktopWidget>
25 #include <QDebug>
26 #include <QMessageBox>
27 #include <QFileInfo>
28 #include <QCloseEvent>
29 #include <QUrl>
30 #include <QXmlSchemaValidator>
31 #include <QXmlSchema>
32 #include <QFileSystemWatcher>
33 #include <QTemporaryFile>
34
35 #ifdef VERSIONSTR
36 #define AS_STRING_(x) #x
37 #define AS_STRING(x) AS_STRING_(x)
38 const QString versionString = QString(AS_STRING(VERSIONSTR));
39 #else
40 const char *versionString = "unknown";
41 #endif
42
43
44 DockMainWindow::DockMainWindow(QWidget *parent) :
45         QMainWindow(parent),
46         resultPromptWidget(0),
47         fileWatcher(0),
48         reloadRequested(false),
49         errorInfoShown(false),
50         m_testModel(0),
51         m_tempDefFile(0),
52         m_tempResultFile(0),
53         m_tempLogFile(0),
54         ui(new Ui::DockMainWindow)
55 {
56     ui->setupUi(this);
57
58     createConnections();
59
60     testTreeWidget_p = new TestTreeWidget(this);
61     connect(testTreeWidget_p, SIGNAL(selectedTestItemChanged(TestItem*)),
62             ui->itemDetailsWidget, SLOT(setTestItem(TestItem*)));
63     connect(testTreeWidget_p, SIGNAL(selectedTestItemChanged(TestItem*)),
64             this, SLOT(refreshState()));
65
66     setCentralWidget(testTreeWidget_p);
67
68     setWindowIcon(QIcon(":/icons/run.png"));
69     setGeometry(50, 50, 1100, 800);
70     
71     initializeDockWidget(ui->statusDockWidget, ui->actionToggleStatusWindow, true);
72     initializeDockWidget(ui->detailsDockWidget, ui->actionToggleDetailsWindow, false);
73     initializeDockWidget(ui->logDockWidget, ui->actionToggleLogWindow, false);
74
75     fileChangeTimer.setSingleShot(true);
76     connect(&fileChangeTimer, SIGNAL(timeout()), this, SLOT(reloadFile()));
77 }
78
79 DockMainWindow::~DockMainWindow()
80 {
81     delete ui;
82 }
83
84 void DockMainWindow::setTestModel(TestModel *testModel)
85 {
86     if (m_testModel) {
87         disconnect(m_testModel, SIGNAL(manualResultsExpected(TestItem*)),
88                    this, SLOT(promptForManualResults(TestItem *)));
89         disconnect(m_testModel, SIGNAL(itemExecutionEnded(TestItem*)),
90                    this, SLOT(closeItemDetailWindow(TestItem*)));
91
92         disconnect(m_testModel, SIGNAL(stateChanged(TestState)), this, SLOT(refreshState()));
93         disconnect(m_testModel, SIGNAL(itemChanged()), this, SLOT(refreshState()));
94         disconnect(m_testModel, SIGNAL(newModelLoaded()), this, SLOT(refreshState()));
95     }
96
97     m_testModel = testModel;
98     testTreeWidget_p->setRootItem(m_testModel->testDefs().at(0));
99     ui->mainStatusWidget->setTestModel(testModel);
100     ui->logWidget->setTestModel(m_testModel);
101
102     if (m_testModel) {
103         connect(m_testModel, SIGNAL(manualResultsExpected(TestItem*)),
104                 this, SLOT(promptForManualResults(TestItem *)));
105         connect(m_testModel, SIGNAL(itemExecutionEnded(TestItem*)),
106                 this, SLOT(closeItemDetailWindow(TestItem*)));
107
108         connect(m_testModel, SIGNAL(stateChanged(TestState)), this, SLOT(refreshState()));
109         connect(m_testModel, SIGNAL(itemChanged()), this, SLOT(refreshState()));
110         connect(m_testModel, SIGNAL(newModelLoaded()), this, SLOT(refreshState()));
111     }
112
113     refreshState();
114 }
115
116 void DockMainWindow::setRunnerController(RunnerController *runnerController)
117 {
118     m_runnerController = runnerController;
119 }
120
121 void DockMainWindow::createConnections()
122 {
123     connect(ui->actionOpen_Test_Definition, SIGNAL(triggered()), this, SLOT(openFile()));
124     connect(ui->actionSave_Results, SIGNAL(triggered()), this, SLOT(saveResults()));
125     connect(ui->actionSave_Results_As, SIGNAL(triggered()), this, SLOT(saveResultsAs()));
126     connect(ui->actionClose, SIGNAL(triggered()), this, SLOT(closeFile()));
127     connect(ui->actionExit, SIGNAL(triggered()), this, SLOT(exitApplication()));
128
129     connect(ui->actionReset_Order, SIGNAL(triggered()), this, SLOT(resetOrder()));
130     connect(ui->actionEdit_with_Testplanner, SIGNAL(triggered()), this, SLOT(launchTestPlanner()));
131
132     connect(ui->actionRun_All_Tests, SIGNAL(triggered()), this, SLOT(runTests()));
133     connect(ui->actionRun_Selected_Tests, SIGNAL(triggered()), this, SLOT(runTests()));
134     connect(ui->actionReset_Results, SIGNAL(triggered()), this, SLOT(resetResults()));
135     connect(ui->actionPause, SIGNAL(triggered()), this, SLOT(showNotImplemented()));
136     connect(ui->actionStop, SIGNAL(triggered()), this, SLOT(requestStop()));
137     connect(ui->actionFilters, SIGNAL(triggered()), this, SLOT(openFiltersDialog()));
138     connect(ui->actionSettings, SIGNAL(triggered()), this, SLOT(openSettingsDialog()));
139
140     connect(ui->actionToggleStatusWindow, SIGNAL(toggled(bool)), ui->statusDockWidget, SLOT(setVisible(bool)));
141     connect(ui->actionToggleDetailsWindow, SIGNAL(toggled(bool)), ui->detailsDockWidget, SLOT(setVisible(bool)));
142     connect(ui->actionToggleLogWindow, SIGNAL(toggled(bool)), ui->logDockWidget, SLOT(setVisible(bool)));
143     
144     connect(ui->actionHelp, SIGNAL(triggered()), this, SLOT(showNotImplemented()));
145     connect(ui->actionAbout, SIGNAL(triggered()), this, SLOT(openAboutDialog()));
146 }
147
148 void DockMainWindow::openFile()
149 {
150     if (!showConfirmationBox("Confirm open"))
151         return;
152
153     QFileDialog dialog(this);
154     dialog.setDirectory(TestRunnerUiSettings::instance()->lastDefDirectory());
155     dialog.setNameFilter("XML files (*.xml)");
156
157     int dialogCode = dialog.exec();
158
159     QStringList list = dialog.selectedFiles();
160
161     if (list.isEmpty() || dialogCode == QDialog::Rejected)
162         return;
163
164     openFile(list[0]);
165 }
166
167 bool DockMainWindow::openFile(QString filePath)
168 {
169     reloadRequested = false;
170
171     QFileInfo fileInfo(filePath);
172     QString absFilePath = fileInfo.absoluteFilePath();
173
174     bool valid = validateTestDefinition(absFilePath);
175     if (!valid)
176         return false;
177
178     if (m_testModel->loadTestDef(absFilePath)) {
179         TestRunnerUiSettings::instance()->setLastDefDirectory(fileInfo.dir().absolutePath());
180
181         if (fileWatcher)
182             fileWatcher->deleteLater();
183         fileWatcher = new QFileSystemWatcher(this);
184         fileWatcher->addPath(absFilePath);
185         connect(fileWatcher, SIGNAL(fileChanged(QString)), this, SLOT(handleChangedDefFile(QString)));
186
187         testTreeWidget_p->setRootItem(m_testModel->testDefs().at(0));
188
189         return true;
190     }
191
192     return false;
193 }
194
195 void DockMainWindow::closeFile()
196 {
197     if (showConfirmationBox("Confirm close")) {
198         m_testModel->closeTestDef(m_testModel->testDefs().at(0));
199     }
200 }
201
202 void DockMainWindow::showDetails()
203 {
204     showDetails(testTreeWidget_p->selectedTestItem());
205 }
206
207 void DockMainWindow::showDetails(TestItem *item)
208 {
209     if (item) {
210         ui->itemDetailsWidget->setTestItem(item);
211         ui->detailsDockWidget->show();
212     } else {
213         ui->detailsDockWidget->hide();
214     }
215 }
216
217 void DockMainWindow::resetOrder()
218 {
219     m_testModel->resetTestOrder();
220 }
221
222 void DockMainWindow::launchTestPlanner()
223 {
224     QString testdef = m_testModel->testDefs().at(0)->definitionFile().absoluteFilePath();
225
226     QString planner = TestRunnerUiSettings::instance()->testPlannerPath();
227
228     if (!QFile::exists(planner)) {
229         showError(tr("Testplanner not found"), tr("File %1 does not exist").arg(planner));
230     } else if (!QFileInfo(planner).isExecutable()) {
231         showError(tr("Testplanner not executable"), tr("File %1 is not executable").arg(planner));
232     } else if (!QFile::exists(testdef)) {
233         showError(tr("Test definition not found"), tr("File %1 does not exist").arg(testdef));
234     } else {
235         QStringList args;
236         args << testdef;
237         // No parent
238         QProcess *process = new QProcess();
239         process->start(planner, args);
240     }
241 }
242
243 void DockMainWindow::runTests()
244 {
245     bool runSelectedOnly = (sender() == ui->actionRun_Selected_Tests);
246
247     if (showConfirmationBox("Confirm run")) {
248         
249         QString pid = QString::number(QCoreApplication::applicationPid());
250         QString defFileName = "/tmp/testrunner_def_" + pid + ".xml";
251         QString resultFileName = "/tmp/testrunner_result_" + pid + ".xml";
252         QString logFileName = "/tmp/testrunner_output_" + pid + ".log";
253         
254 /* Temp file handling not ready yet
255         if (m_tempDefFile)
256             delete m_tempDefFile;
257         if (m_tempResultFile)
258             delete m_tempResultFile;
259         if (m_tempLogFile)
260             delete m_tempLogFile;
261         m_tempDefFile = new QTemporaryFile("testrunner_def_XXXXXX.xml");
262         m_tempDefFile->open();
263         defFileName = m_tempDefFile->fileName();
264         m_tempResultFile = new QTemporaryFile("testrunner_result_XXXXXX.xml");
265         m_tempResultFile->open();
266         resultFileName = m_tempResultFile->fileName();
267         m_tempLogFile = new QTemporaryFile("testrunner_output_XXXXXX.log");
268         m_tempLogFile->open();
269         logFileName = m_tempLogFile->fileName();
270 */       
271         
272         m_testModel->resetResults(true);
273         m_testModel->setRunModeAll(!runSelectedOnly);
274         m_testModel->testDefs().at(0)->saveTempDefinitionFile(defFileName, runSelectedOnly);
275         m_runnerController->setInputFile(defFileName);
276         m_runnerController->setOutputFile(resultFileName);
277         m_testModel->setTestRunnerResultFile(resultFileName);
278         m_runnerController->setLogFile(logFileName);
279         m_runnerController->startRunnerProcess();
280         m_runnerController->setInputFile("");
281         ui->logWidget->setLogFile(logFileName);
282     }
283 }
284
285 bool DockMainWindow::saveResultsAs()
286 {
287     QFileDialog dialog(this, "Save Results as...");
288
289     dialog.setNameFilter("XML files (*.xml)");
290     dialog.setAcceptMode(QFileDialog::AcceptSave);
291
292     if (!m_testModel->testDefs().at(0)->resultFile().isFile()) {
293         dialog.setDefaultSuffix("xml");
294
295         QString defFile = m_testModel->testDefs().at(0)->definitionFile().absoluteFilePath();
296         int i = defFile.lastIndexOf(".");
297         QString resultFile = defFile.left(i) + "_result.xml";
298         dialog.selectFile(resultFile);
299     } else {
300         dialog.selectFile(m_testModel->testDefs().at(0)->resultFile().absoluteFilePath());
301     }
302
303     int dialogCode = dialog.exec();
304
305     QStringList list = dialog.selectedFiles();
306
307     if(list.isEmpty() || dialogCode == QDialog::Rejected)
308         return false;
309
310     QFileInfo fileInfo(list.at(0));
311
312     return m_testModel->saveResultsFile(fileInfo.absoluteFilePath(), true);
313 }
314
315 bool DockMainWindow::saveResults()
316 {
317     if (!m_testModel->testDefs().at(0)->resultFile().isFile())
318         return saveResultsAs();
319
320     return m_testModel->saveResultsFile(m_testModel->testDefs().at(0)->resultFile().absoluteFilePath(), true);
321 }
322
323 void DockMainWindow::exitApplication()
324 {
325     if (showConfirmationBox("Confirm Exit"))
326         qApp->exit();
327 }
328
329 void DockMainWindow::resetResults()
330 {
331     if (showConfirmationBox("Confirm reset"))
332         m_testModel->resetResults(true);
333 }
334
335 void DockMainWindow::refreshState()
336 {
337     TestState state = m_testModel->state();
338     
339     if (state == StateError)
340         showErrorInfo();
341     else 
342         errorInfoShown = false;
343
344     QString fileName = m_testModel->testDefs().at(0)->definitionFile().fileName();
345     setWindowTitle(tr("Testrunner UI")
346                    + ((fileName == "")? "": (" - " + fileName)));
347
348     ui->detailsDockWidget->setWindowTitle(ui->itemDetailsWidget->windowTitle());
349     
350     ui->actionOpen_Test_Definition->setEnabled(state & StateOpenEnabled);
351     ui->actionSave_Results->setEnabled(state & StateSaveEnabled);
352     ui->actionSave_Results_As->setEnabled(state & StateSaveAsEnabled);
353     ui->actionClose->setEnabled(state & StateCloseEnabled);
354     ui->actionExit->setEnabled(state & StateExitEnabled);
355
356     ui->actionReset_Order->setEnabled(state & StateDefEditEnabled); // TODO & order edited
357     ui->actionEdit_with_Testplanner->setEnabled(state & StateDefEditEnabled);
358
359     ui->actionRun_All_Tests->setEnabled((state & StateRunEnabled)
360                                && m_testModel->compositeResults().total > 0);
361     ui->actionRun_Selected_Tests->setEnabled((state & StateRunEnabled)
362                                     && m_testModel->compositeResults().selected > 0);
363     ui->actionReset_Results->setEnabled(state & StateResetEnabled);
364
365     ui->actionPause->setEnabled(state & StatePauseEnabled);
366
367     ui->actionStop->setVisible(state & StateStopEnabled);
368     ui->actionRun_Selected_Tests->setVisible(!(state & StateStopEnabled));
369
370     ui->actionFilters->setEnabled(state & StateDefEditEnabled);
371     ui->actionSettings->setEnabled(state & StateSettingsEnabled);
372
373     testTreeWidget_p->setEnabled(state != StateNotLoaded);
374
375     ui->statusDockWidget->setWindowTitle(
376             (state == StateNotLoaded)? tr("Test Definition not loaded"):
377             tr("Test Definition - %1").arg(m_testModel->testDefs().at(0)->name()));
378     
379     // this is a bit hack
380     if (reloadRequested && state == StateNotRun)
381         openFile(m_testModel->testDefs().at(0)->definitionFile().absoluteFilePath());
382 }
383
384 void DockMainWindow::openSettingsDialog()
385 {
386     (new SettingsDialog(this))->open();
387 }
388
389 void DockMainWindow::openAttachmentsDialog()
390 {
391     (new AttachmentsDialog(this))->open();
392 }
393
394 void DockMainWindow::openFiltersDialog()
395 {
396     FiltersDialog *dialog = new FiltersDialog(this);
397     dialog->setTestModel(m_testModel);
398     dialog->open();
399 }
400
401 void DockMainWindow::showLogWidget()
402 {
403     if (ui->logDockWidget->isHidden())
404         ui->logDockWidget->show();
405 }
406
407 void DockMainWindow::openAboutDialog()
408 {
409     QString aboutTrl = m_runnerController->aboutTestRunnerLite();
410     aboutTrl = aboutTrl.replace('\n', "<br>");
411     QMessageBox::about(this,
412                        "About Testrunner UI",
413                        QString("<b>Testrunner UI</b> version ") + QString(versionString)  + QString("<br>")
414                        + QString("a graphical user interface to testrunner-lite.<br><br>")
415                        + QString("For more info, see: http://gitorious.org/qa-tools/testrunner-ui<br><br>")
416                        + aboutTrl);
417 }
418
419 void DockMainWindow::showNotImplemented()
420 {
421     QMessageBox::information(this, "Not implemented", "This feature has not been implemented yet.");
422 }
423
424 bool DockMainWindow::showConfirmationBox(QString title)
425 {
426     if (!(m_testModel->state() & StateSaveEnabled))
427         return true;
428
429     QMessageBox::StandardButton selectedButton =
430             QMessageBox::warning(this,
431                                  title,
432                                  "Test results have not been saved.\nDo you want to save the results now?",
433                                  QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
434                                  QMessageBox::Yes);
435
436     if (selectedButton & QMessageBox::Yes)
437         return saveResults();
438     else
439         return (selectedButton & QMessageBox::No);
440
441 }
442
443 void DockMainWindow::showError(QString title, QString text)
444 {
445     QMessageBox::warning(this, title, text);
446 }
447
448 void DockMainWindow::closeEvent(QCloseEvent *event)
449 {
450     Q_UNUSED(event);
451     exitApplication();
452
453     // if execution reaches this point, the user canceled exit operation ->
454     // let's ignore the window close event.
455     event->ignore();
456 }
457
458 bool DockMainWindow::validateTestDefinition(QString filepath)
459 {
460     QUrl schemaUrl("file://" + TestRunnerUiSettings::instance()->testDefinitionSchemaPath());
461
462     QXmlSchema schema;
463     if (!schema.load(schemaUrl)) {
464         showError("Test definition schema could not be loaded",
465                   QString("Test definition schema in\n")
466                   + schemaUrl.toString()
467                   + QString("\ncould not be loaded"));
468         return false;
469     }
470
471     if (schema.isValid()) {
472         QFile file(filepath);
473         if (!file.open(QIODevice::ReadOnly)) {
474             showError("Could not open file",
475                       QString("File ")
476                       + filepath
477                       + QString("\n could not be opened for reading."));
478             return false;
479         }
480
481         QXmlSchemaValidator validator(schema);
482         XmlValidatorMessageHandler *msgHandler = new XmlValidatorMessageHandler();
483         validator.setMessageHandler(msgHandler);
484         if (!validator.validate(&file, QUrl::fromLocalFile(file.fileName()))) {
485             showError("Incorrect test defintion",
486                       QString("")
487                       + "<p>The opened file "
488                       + filepath
489                       + " does not contain a valid test definition.</p>"
490                       + "<i>Errors:</i>"
491                       + msgHandler->messages()
492                       + "<p><br>The applied test definition schema is\n"
493                       + schema.documentUri().toString()
494                       + "</p>");
495             return false;
496         }
497         file.close();
498         return true;
499     }
500     showError("Incorrect schema",
501               QString("Test definition schema is not valid: ")
502               + schema.documentUri().toString());
503     return false;
504 }
505
506 void DockMainWindow::promptForManualResults(TestItem *item)
507 {
508     if (item && item->itemType() == TestItem::Step) {
509         TestCase *testCase = static_cast<TestStep *>(item)->testCase();
510         if (resultPromptWidget && resultPromptWidget->testItem() != testCase) {
511             resultPromptWidget->deleteLater();
512             resultPromptWidget = 0;
513         }
514         if (!resultPromptWidget) {
515             resultPromptWidget = new ResultPromptWidget(this);
516             connect(resultPromptWidget, SIGNAL(destroyed()),
517                     this, SLOT(unrefItemDetailWindow()));
518             resultPromptWidget->setTestItem(testCase);
519         }
520         ui->detailsDockWidget->setEnabled(false);
521         resultPromptWidget->setWindowModality(Qt::ApplicationModal);
522         resultPromptWidget->show();
523     }
524 }
525
526 void DockMainWindow::closeItemDetailWindow(TestItem *item)
527 {
528     if (resultPromptWidget
529         && item
530         && item->itemType() == TestItem::Case
531         && item == resultPromptWidget->testItem()) {
532
533         ui->detailsDockWidget->setEnabled(true);
534
535         resultPromptWidget->close();
536         resultPromptWidget->deleteLater();
537         resultPromptWidget = 0;
538     }
539 }
540
541 void DockMainWindow::unrefItemDetailWindow()
542 {
543     ui->detailsDockWidget->setEnabled(true);
544
545     if (resultPromptWidget && sender() == resultPromptWidget) {
546         resultPromptWidget = 0;
547     }
548 }
549
550 void DockMainWindow::handleChangedDefFile(QString filePath)
551 {
552     Q_UNUSED(filePath);
553     // Let's combine several signals together before opening the file.
554     if (!fileChangeTimer.isActive())
555         fileChangeTimer.start(300);
556 }
557
558 void DockMainWindow::reloadFile()
559 {
560     reloadRequested = true;
561     if (ui->itemDetailsWidget->isVisible()) {
562         ui->itemDetailsWidget->setTestItem(0);
563     }
564     refreshState();
565 }
566
567 void DockMainWindow::storeDockWidgetGeometry()
568 {
569     // currently only visiblity is stored
570     QDockWidget *dock = static_cast<QDockWidget*>(sender());
571     TestRunnerUiSettings::instance()->setWidgetProperty(dock->objectName(), "visibility", dock->isVisible());
572 }
573
574 void DockMainWindow::initializeDockWidget(QDockWidget *dock, QAction *action, bool defaultVisibility)
575 {
576     TestRunnerUiSettings *settings = TestRunnerUiSettings::instance();
577
578     action->setChecked(settings->widgetProperty(dock->objectName(), "visibility", 
579                                                 defaultVisibility).toBool());
580
581     connect(dock, SIGNAL(visibilityChanged(bool)), this, SLOT(storeDockWidgetGeometry()));
582     connect(dock, SIGNAL(visibilityChanged(bool)), action, SLOT(setChecked(bool)));
583 }
584
585 void DockMainWindow::requestStop()
586 {
587     StopDialog *dialog = new StopDialog();
588     
589     connect(dialog, SIGNAL(stopRequested()), m_runnerController, SLOT(stopRunnerProcess()));
590     connect(dialog, SIGNAL(killRequested()), m_runnerController, SLOT(killRunnerProcess()));
591     connect(m_runnerController, SIGNAL(finished(int)), dialog, SLOT(close()));
592     
593     dialog->open();
594 }
595
596 void DockMainWindow::showErrorInfo()
597 {
598     if (errorInfoShown)
599         return;        
600
601     QMessageBox::warning(this,
602                          tr("Process Killed"), 
603                          tr("Testrunner-lite process was killed and did not produce a results file"),
604                          QMessageBox::Ok,
605                          QMessageBox::Ok);
606     
607     errorInfoShown = true;
608 }