1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25
26 #include "autotestplugin.h"
27
28 #include "autotestconstants.h"
29 #include "autotesticons.h"
30 #include "projectsettingswidget.h"
31 #include "testcodeparser.h"
32 #include "testframeworkmanager.h"
33 #include "testnavigationwidget.h"
34 #include "testprojectsettings.h"
35 #include "testresultspane.h"
36 #include "testrunner.h"
37 #include "testsettings.h"
38 #include "testsettingspage.h"
39 #include "testtreeitem.h"
40 #include "testtreemodel.h"
41 #include "testtreeview.h"
42
43 #include "boost/boosttestframework.h"
44 #include "catch/catchframework.h"
45 #include "ctest/ctesttool.h"
46 #include "gtest/gtestframework.h"
47 #include "qtest/qttestframework.h"
48 #include "quick/quicktestframework.h"
49
50 #include <coreplugin/actionmanager/actioncontainer.h>
51 #include <coreplugin/actionmanager/actionmanager.h>
52 #include <coreplugin/actionmanager/command.h>
53 #include <coreplugin/coreconstants.h>
54 #include <coreplugin/icontext.h>
55 #include <coreplugin/icore.h>
56 #include <coreplugin/messagemanager.h>
57 #include <cppeditor/cppeditorconstants.h>
58 #include <extensionsystem/pluginmanager.h>
59 #include <projectexplorer/buildmanager.h>
60 #include <projectexplorer/project.h>
61 #include <projectexplorer/projectexplorer.h>
62 #include <projectexplorer/projectexplorericons.h>
63 #include <projectexplorer/projectpanelfactory.h>
64 #include <projectexplorer/runcontrol.h>
65 #include <projectexplorer/session.h>
66 #include <projectexplorer/target.h>
67 #include <texteditor/textdocument.h>
68 #include <texteditor/texteditor.h>
69 #include <utils/textutils.h>
70 #include <utils/utilsicons.h>
71
72 #include <QAction>
73 #include <QList>
74 #include <QMainWindow>
75 #include <QMap>
76 #include <QMenu>
77 #include <QMessageBox>
78 #include <QTextCursor>
79
80 #ifdef WITH_TESTS
81 #include "autotestunittests.h"
82 #include "loadprojectscenario.h"
83 #endif
84
85 using namespace Core;
86
87 namespace Autotest {
88 namespace Internal {
89
90 class AutotestPluginPrivate : public QObject
91 {
92 Q_OBJECT
93 public:
94 AutotestPluginPrivate();
95 ~AutotestPluginPrivate() override;
96
97 TestNavigationWidgetFactory m_navigationWidgetFactory;
98 TestResultsPane *m_resultsPane = nullptr;
99 QMap<QString, ChoicePair> m_runconfigCache;
100
101 void initializeMenuEntries();
102 void onRunAllTriggered();
103 void onRunSelectedTriggered();
104 void onRunFailedTriggered();
105 void onRunFileTriggered();
106 void onRunUnderCursorTriggered(TestRunMode mode);
107
108 TestSettings m_settings;
109 TestSettingsPage m_testSettingPage{&m_settings};
110
111 TestCodeParser m_testCodeParser;
112 TestTreeModel m_testTreeModel{&m_testCodeParser};
113 TestRunner m_testRunner;
114 TestFrameworkManager m_frameworkManager;
115 #ifdef WITH_TESTS
116 LoadProjectScenario m_loadProjectScenario{&m_testTreeModel};
117 #endif
118 };
119
120 static AutotestPluginPrivate *dd = nullptr;
121 static QHash<ProjectExplorer::Project *, TestProjectSettings *> s_projectSettings;
122
AutotestPlugin()123 AutotestPlugin::AutotestPlugin()
124 {
125 // needed to be used in QueuedConnection connects
126 qRegisterMetaType<TestResult>();
127 qRegisterMetaType<TestTreeItem *>();
128 qRegisterMetaType<TestCodeLocationAndType>();
129 // warm up meta type system to be able to read Qt::CheckState with persistent settings
130 qRegisterMetaType<Qt::CheckState>();
131 }
132
~AutotestPlugin()133 AutotestPlugin::~AutotestPlugin()
134 {
135 delete dd;
136 dd = nullptr;
137 }
138
AutotestPluginPrivate()139 AutotestPluginPrivate::AutotestPluginPrivate()
140 {
141 dd = this; // Needed as the code below access it via the static plugin interface
142 initializeMenuEntries();
143 m_frameworkManager.registerTestFramework(new QtTestFramework);
144 m_frameworkManager.registerTestFramework(new QuickTestFramework);
145 m_frameworkManager.registerTestFramework(new GTestFramework);
146 m_frameworkManager.registerTestFramework(new BoostTestFramework);
147 m_frameworkManager.registerTestFramework(new CatchFramework);
148
149 m_frameworkManager.registerTestTool(new CTestTool);
150
151 m_frameworkManager.synchronizeSettings(ICore::settings());
152 m_resultsPane = TestResultsPane::instance();
153
154 auto panelFactory = new ProjectExplorer::ProjectPanelFactory();
155 panelFactory->setPriority(666);
156 // panelFactory->setIcon(); // TODO ?
157 panelFactory->setDisplayName(tr("Testing"));
158 panelFactory->setCreateWidgetFunction([](ProjectExplorer::Project *project) {
159 return new ProjectTestSettingsWidget(project);
160 });
161 ProjectExplorer::ProjectPanelFactory::registerFactory(panelFactory);
162
163 TestFrameworkManager::activateFrameworksAndToolsFromSettings(&m_settings);
164 m_testTreeModel.synchronizeTestFrameworks();
165 m_testTreeModel.synchronizeTestTools();
166
167 auto sessionManager = ProjectExplorer::SessionManager::instance();
168 connect(sessionManager, &ProjectExplorer::SessionManager::startupProjectChanged,
169 this, [this] { m_runconfigCache.clear(); });
170
171 connect(sessionManager, &ProjectExplorer::SessionManager::aboutToRemoveProject,
172 this, [] (ProjectExplorer::Project *project) {
173 auto it = s_projectSettings.find(project);
174 if (it != s_projectSettings.end()) {
175 delete it.value();
176 s_projectSettings.erase(it);
177 }
178 });
179 }
180
~AutotestPluginPrivate()181 AutotestPluginPrivate::~AutotestPluginPrivate()
182 {
183 if (!s_projectSettings.isEmpty()) {
184 qDeleteAll(s_projectSettings);
185 s_projectSettings.clear();
186 }
187
188 delete m_resultsPane;
189 }
190
settings()191 TestSettings *AutotestPlugin::settings()
192 {
193 return &dd->m_settings;
194 }
195
projectSettings(ProjectExplorer::Project * project)196 TestProjectSettings *AutotestPlugin::projectSettings(ProjectExplorer::Project *project)
197 {
198 auto &settings = s_projectSettings[project];
199 if (!settings)
200 settings = new TestProjectSettings(project);
201
202 return settings;
203 }
204
initializeMenuEntries()205 void AutotestPluginPrivate::initializeMenuEntries()
206 {
207 ActionContainer *menu = ActionManager::createMenu(Constants::MENU_ID);
208 menu->menu()->setTitle(tr("&Tests"));
209 menu->setOnAllDisabledBehavior(ActionContainer::Show);
210
211 QAction *action = new QAction(tr("Run &All Tests"), this);
212 action->setIcon(Utils::Icons::RUN_SMALL.icon());
213 action->setToolTip(tr("Run All Tests"));
214 Command *command = ActionManager::registerAction(action, Constants::ACTION_RUN_ALL_ID);
215 command->setDefaultKeySequence(
216 QKeySequence(useMacShortcuts ? tr("Ctrl+Meta+T, Ctrl+Meta+A") : tr("Alt+Shift+T,Alt+A")));
217 connect(action, &QAction::triggered, this, &AutotestPluginPrivate::onRunAllTriggered);
218 action->setEnabled(false);
219 menu->addAction(command);
220
221 action = new QAction(tr("&Run Selected Tests"), this);
222 action->setIcon(Utils::Icons::RUN_SELECTED.icon());
223 action->setToolTip(tr("Run Selected Tests"));
224 command = ActionManager::registerAction(action, Constants::ACTION_RUN_SELECTED_ID);
225 command->setDefaultKeySequence(
226 QKeySequence(useMacShortcuts ? tr("Ctrl+Meta+T, Ctrl+Meta+R") : tr("Alt+Shift+T,Alt+R")));
227 connect(action, &QAction::triggered, this, &AutotestPluginPrivate::onRunSelectedTriggered);
228 action->setEnabled(false);
229 menu->addAction(command);
230
231 action = new QAction(tr("Run &Failed Tests"), this);
232 action->setIcon(Icons::RUN_FAILED.icon());
233 action->setToolTip(tr("Run Failed Tests"));
234 command = ActionManager::registerAction(action, Constants::ACTION_RUN_FAILED_ID);
235 command->setDefaultKeySequence(
236 useMacShortcuts ? tr("Ctrl+Meta+T, Ctrl+Meta+F") : tr("Alt+Shift+T,Alt+F"));
237 connect(action, &QAction::triggered, this, &AutotestPluginPrivate::onRunFailedTriggered);
238 action->setEnabled(false);
239 menu->addAction(command);
240
241 action = new QAction(tr("Run Tests for &Current File"), this);
242 action->setIcon(Utils::Icons::RUN_FILE.icon());
243 action->setToolTip(tr("Run Tests for Current File"));
244 command = ActionManager::registerAction(action, Constants::ACTION_RUN_FILE_ID);
245 command->setDefaultKeySequence(
246 QKeySequence(useMacShortcuts ? tr("Ctrl+Meta+T, Ctrl+Meta+C") : tr("Alt+Shift+T,Alt+C")));
247 connect(action, &QAction::triggered, this, &AutotestPluginPrivate::onRunFileTriggered);
248 action->setEnabled(false);
249 menu->addAction(command);
250
251 action = new QAction(tr("Re&scan Tests"), this);
252 command = ActionManager::registerAction(action, Constants::ACTION_SCAN_ID);
253 command->setDefaultKeySequence(
254 QKeySequence(useMacShortcuts ? tr("Ctrl+Meta+T, Ctrl+Meta+S") : tr("Alt+Shift+T,Alt+S")));
255
256 connect(action, &QAction::triggered, this, [] { dd->m_testCodeParser.updateTestTree(); });
257 menu->addAction(command);
258
259 ActionContainer *toolsMenu = ActionManager::actionContainer(Core::Constants::M_TOOLS);
260 toolsMenu->addMenu(menu);
261 using namespace ProjectExplorer;
262 connect(BuildManager::instance(), &BuildManager::buildStateChanged,
263 this, &AutotestPlugin::updateMenuItemsEnabledState);
264 connect(BuildManager::instance(), &BuildManager::buildQueueFinished,
265 this, &AutotestPlugin::updateMenuItemsEnabledState);
266 connect(ProjectExplorerPlugin::instance(), &ProjectExplorerPlugin::runActionsUpdated,
267 this, &AutotestPlugin::updateMenuItemsEnabledState);
268 connect(&dd->m_testTreeModel, &TestTreeModel::testTreeModelChanged,
269 this, &AutotestPlugin::updateMenuItemsEnabledState);
270 }
271
initialize(const QStringList & arguments,QString * errorString)272 bool AutotestPlugin::initialize(const QStringList &arguments, QString *errorString)
273 {
274 Q_UNUSED(arguments)
275 Q_UNUSED(errorString)
276
277 dd = new AutotestPluginPrivate;
278 #ifdef WITH_TESTS
279 ExtensionSystem::PluginManager::registerScenario("TestStringTable",
280 [] { return dd->m_loadProjectScenario(); });
281 ExtensionSystem::PluginManager::registerScenario("TestModelManagerInterface",
282 [] { return dd->m_loadProjectScenario(); });
283 #endif
284 return true;
285 }
286
extensionsInitialized()287 void AutotestPlugin::extensionsInitialized()
288 {
289 ActionContainer *contextMenu = ActionManager::actionContainer(CppEditor::Constants::M_CONTEXT);
290 if (!contextMenu) // if QC is started without CppEditor plugin
291 return;
292
293 QAction *action = new QAction(tr("&Run Test Under Cursor"), this);
294 action->setEnabled(false);
295 action->setIcon(Utils::Icons::RUN_SMALL.icon());
296
297 Command *command = ActionManager::registerAction(action, Constants::ACTION_RUN_UCURSOR);
298 connect(action, &QAction::triggered,
299 std::bind(&AutotestPluginPrivate::onRunUnderCursorTriggered, dd, TestRunMode::Run));
300 contextMenu->addSeparator();
301 contextMenu->addAction(command);
302
303 action = new QAction(tr("&Debug Test Under Cursor"), this);
304 action->setEnabled(false);
305 action->setIcon(ProjectExplorer::Icons::DEBUG_START_SMALL.icon());
306
307 command = ActionManager::registerAction(action, Constants::ACTION_RUN_DBG_UCURSOR);
308 connect(action, &QAction::triggered,
309 std::bind(&AutotestPluginPrivate::onRunUnderCursorTriggered, dd, TestRunMode::Debug));
310 contextMenu->addAction(command);
311 contextMenu->addSeparator();
312 }
313
aboutToShutdown()314 ExtensionSystem::IPlugin::ShutdownFlag AutotestPlugin::aboutToShutdown()
315 {
316 dd->m_testCodeParser.aboutToShutdown();
317 dd->m_testTreeModel.disconnect();
318 return SynchronousShutdown;
319 }
320
onRunAllTriggered()321 void AutotestPluginPrivate::onRunAllTriggered()
322 {
323 m_testRunner.setSelectedTests(m_testTreeModel.getAllTestCases());
324 m_testRunner.prepareToRunTests(TestRunMode::Run);
325 }
326
onRunSelectedTriggered()327 void AutotestPluginPrivate::onRunSelectedTriggered()
328 {
329 m_testRunner.setSelectedTests(m_testTreeModel.getSelectedTests());
330 m_testRunner.prepareToRunTests(TestRunMode::Run);
331 }
332
onRunFailedTriggered()333 void AutotestPluginPrivate::onRunFailedTriggered()
334 {
335 const QList<ITestConfiguration *> failed = m_testTreeModel.getFailedTests();
336 if (failed.isEmpty()) // the framework might not be able to provide them
337 return;
338 m_testRunner.setSelectedTests(failed);
339 m_testRunner.prepareToRunTests(TestRunMode::Run);
340 }
341
onRunFileTriggered()342 void AutotestPluginPrivate::onRunFileTriggered()
343 {
344 const IDocument *document = EditorManager::currentDocument();
345 if (!document)
346 return;
347
348 const Utils::FilePath &fileName = document->filePath();
349 if (fileName.isEmpty())
350 return;
351
352 const QList<ITestConfiguration *> tests = m_testTreeModel.getTestsForFile(fileName);
353 if (tests.isEmpty())
354 return;
355
356 m_testRunner.setSelectedTests(tests);
357 m_testRunner.prepareToRunTests(TestRunMode::Run);
358 }
359
testItemsToTestConfigurations(const QList<ITestTreeItem * > & items,TestRunMode mode)360 static QList<ITestConfiguration *> testItemsToTestConfigurations(const QList<ITestTreeItem *> &items,
361 TestRunMode mode)
362 {
363 QList<ITestConfiguration *> configs;
364 for (const ITestTreeItem * item : items) {
365 if (ITestConfiguration *currentConfig = item->asConfiguration(mode))
366 configs << currentConfig;
367 }
368 return configs;
369 }
370
onRunUnderCursorTriggered(TestRunMode mode)371 void AutotestPluginPrivate::onRunUnderCursorTriggered(TestRunMode mode)
372 {
373 TextEditor::BaseTextEditor *currentEditor = TextEditor::BaseTextEditor::currentTextEditor();
374 QTextCursor cursor = currentEditor->editorWidget()->textCursor();
375 cursor.select(QTextCursor::WordUnderCursor);
376 const QString text = cursor.selectedText();
377 if (text.isEmpty())
378 return; // Do not trigger when no name under cursor
379
380 const QList<ITestTreeItem *> testsItems = m_testTreeModel.testItemsByName(text);
381 if (testsItems.isEmpty())
382 return; // Wrong location triggered
383
384 // check whether we have been triggered on a test function definition
385 const int line = currentEditor->currentLine();
386 const Utils::FilePath &filePath = currentEditor->textDocument()->filePath();
387 const QList<ITestTreeItem *> filteredItems = Utils::filtered(testsItems, [&](ITestTreeItem *it){
388 return it->line() == line && it->filePath() == filePath;
389 });
390
391 const QList<ITestConfiguration *> testsToRun = testItemsToTestConfigurations(
392 filteredItems.size() == 1 ? filteredItems : testsItems, mode);
393
394 if (testsToRun.isEmpty()) {
395 MessageManager::writeFlashing(tr("Selected test was not found (%1).").arg(text));
396 return;
397 }
398
399 m_testRunner.setSelectedTests(testsToRun);
400 m_testRunner.prepareToRunTests(mode);
401 }
402
updateMenuItemsEnabledState()403 void AutotestPlugin::updateMenuItemsEnabledState()
404 {
405 const ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
406 const ProjectExplorer::Target *target = project ? project->activeTarget() : nullptr;
407 const bool canScan = !dd->m_testRunner.isTestRunning()
408 && dd->m_testCodeParser.state() == TestCodeParser::Idle;
409 const bool hasTests = dd->m_testTreeModel.hasTests();
410 // avoid expensive call to PE::canRunStartupProject() - limit to minimum necessary checks
411 const bool canRun = hasTests && canScan
412 && project && !project->needsConfiguration()
413 && target && target->activeRunConfiguration()
414 && !ProjectExplorer::BuildManager::isBuilding();
415 const bool canRunFailed = canRun && dd->m_testTreeModel.hasFailedTests();
416
417 ActionManager::command(Constants::ACTION_RUN_ALL_ID)->action()->setEnabled(canRun);
418 ActionManager::command(Constants::ACTION_RUN_SELECTED_ID)->action()->setEnabled(canRun);
419 ActionManager::command(Constants::ACTION_RUN_FAILED_ID)->action()->setEnabled(canRunFailed);
420 ActionManager::command(Constants::ACTION_RUN_FILE_ID)->action()->setEnabled(canRun);
421 ActionManager::command(Constants::ACTION_SCAN_ID)->action()->setEnabled(canScan);
422
423 ActionContainer *contextMenu = ActionManager::actionContainer(CppEditor::Constants::M_CONTEXT);
424 if (!contextMenu)
425 return; // When no context menu, actions do not exists
426
427 ActionManager::command(Constants::ACTION_RUN_UCURSOR)->action()->setEnabled(canRun);
428 ActionManager::command(Constants::ACTION_RUN_DBG_UCURSOR)->action()->setEnabled(canRun);
429 }
430
cacheRunConfigChoice(const QString & buildTargetKey,const ChoicePair & choice)431 void AutotestPlugin::cacheRunConfigChoice(const QString &buildTargetKey, const ChoicePair &choice)
432 {
433 if (dd)
434 dd->m_runconfigCache.insert(buildTargetKey, choice);
435 }
436
cachedChoiceFor(const QString & buildTargetKey)437 ChoicePair AutotestPlugin::cachedChoiceFor(const QString &buildTargetKey)
438 {
439 return dd ? dd->m_runconfigCache.value(buildTargetKey) : ChoicePair();
440 }
441
clearChoiceCache()442 void AutotestPlugin::clearChoiceCache()
443 {
444 if (dd)
445 dd->m_runconfigCache.clear();
446 }
447
popupResultsPane()448 void AutotestPlugin::popupResultsPane()
449 {
450 if (dd)
451 dd->m_resultsPane->popup(Core::IOutputPane::NoModeSwitch);
452 }
453
createTestObjects() const454 QVector<QObject *> AutotestPlugin::createTestObjects() const
455 {
456 QVector<QObject *> tests;
457 #ifdef WITH_TESTS
458 tests << new AutoTestUnitTests(&dd->m_testTreeModel);
459 #endif
460 return tests;
461 }
462
matches(const ProjectExplorer::RunConfiguration * rc) const463 bool ChoicePair::matches(const ProjectExplorer::RunConfiguration *rc) const
464 {
465 return rc && rc->displayName() == displayName && rc->runnable().executable.toString() == executable;
466 }
467
468 } // Internal
469 } // Autotest
470
471 #include "autotestplugin.moc"
472