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 "testconfiguration.h"
27 
28 #include "itestframework.h"
29 #include "testoutputreader.h"
30 #include "testrunconfiguration.h"
31 
32 #include <cpptools/cppmodelmanager.h>
33 #include <cpptools/projectinfo.h>
34 
35 #include <projectexplorer/buildconfiguration.h>
36 #include <projectexplorer/buildsystem.h>
37 #include <projectexplorer/buildtargetinfo.h>
38 #include <projectexplorer/deploymentdata.h>
39 #include <projectexplorer/environmentaspect.h>
40 #include <projectexplorer/kitinformation.h>
41 #include <projectexplorer/runconfiguration.h>
42 #include <projectexplorer/session.h>
43 #include <projectexplorer/target.h>
44 
45 #include <QLoggingCategory>
46 
47 static Q_LOGGING_CATEGORY(LOG, "qtc.autotest.testconfiguration", QtWarningMsg)
48 
49 using namespace ProjectExplorer;
50 using namespace Utils;
51 
52 namespace Autotest {
53 
54 
ITestConfiguration(Autotest::ITestBase * testBase)55 ITestConfiguration::ITestConfiguration(Autotest::ITestBase *testBase)
56     : m_testBase(testBase)
57 {
58 }
59 
setWorkingDirectory(const QString & workingDirectory)60 void ITestConfiguration::setWorkingDirectory(const QString &workingDirectory)
61 {
62     m_runnable.workingDirectory = workingDirectory;
63 }
64 
workingDirectory() const65 Utils::FilePath ITestConfiguration::workingDirectory() const
66 {
67     if (!m_runnable.workingDirectory.isEmpty()) {
68         const QFileInfo info(m_runnable.workingDirectory);
69         if (info.isDir()) // ensure wanted working dir does exist
70             return Utils::FilePath::fromString(info.absoluteFilePath());
71     }
72 
73     const Utils::FilePath executable = executableFilePath();
74     return executable.isEmpty() ? executable : executable.absolutePath();
75 }
76 
hasExecutable() const77 bool ITestConfiguration::hasExecutable() const
78 {
79     return !m_runnable.executable.isEmpty();
80 }
81 
executableFilePath() const82 Utils::FilePath ITestConfiguration::executableFilePath() const
83 {
84     if (!hasExecutable())
85         return {};
86 
87     if (m_runnable.executable.isExecutableFile() && m_runnable.executable.path() != ".") {
88         return m_runnable.executable.absoluteFilePath();
89     } else if (m_runnable.executable.path() == "."){
90         QString fullCommandFileName = m_runnable.executable.toString();
91         // TODO: check if we can use searchInPath() from Utils::Environment
92         const QStringList &pathList = m_runnable.environment.toProcessEnvironment().value("PATH")
93                 .split(Utils::HostOsInfo::pathListSeparator());
94 
95         for (const QString &path : pathList) {
96             QString filePath(path + QDir::separator() + fullCommandFileName);
97             if (QFileInfo(filePath).isExecutable())
98                 return m_runnable.executable.absoluteFilePath();
99         }
100     }
101     return {};
102 }
103 
filteredEnvironment(const Environment & original) const104 Environment ITestConfiguration::filteredEnvironment(const Environment &original) const
105 {
106     return original;
107 }
108 
TestConfiguration(ITestFramework * framework)109 TestConfiguration::TestConfiguration(ITestFramework *framework)
110     : ITestConfiguration(framework)
111 {
112 }
113 
~TestConfiguration()114 TestConfiguration::~TestConfiguration()
115 {
116     m_testCases.clear();
117 }
118 
isLocal(Target * target)119 static bool isLocal(Target *target)
120 {
121     Kit *kit = target ? target->kit() : nullptr;
122     return DeviceTypeKitAspect::deviceTypeId(kit) == ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE;
123 }
124 
ensureExeEnding(const FilePath & file)125 static FilePath ensureExeEnding(const FilePath &file)
126 {
127     if (!HostOsInfo::isWindowsHost() || file.isEmpty() || file.toString().toLower().endsWith(".exe"))
128         return file;
129     return FilePath::fromString(HostOsInfo::withExecutableSuffix(file.toString()));
130 }
131 
completeTestInformation(ProjectExplorer::RunConfiguration * rc,TestRunMode runMode)132 void TestConfiguration::completeTestInformation(ProjectExplorer::RunConfiguration *rc,
133                                                 TestRunMode runMode)
134 {
135     QTC_ASSERT(rc, return);
136     QTC_ASSERT(project(), return);
137 
138     if (hasExecutable()) {
139         qCDebug(LOG) << "Executable has been set already - not completing configuration again.";
140         return;
141     }
142     Project *startupProject = SessionManager::startupProject();
143     if (!startupProject || startupProject != project())
144         return;
145 
146     Target *target = startupProject->activeTarget();
147     if (!target)
148         return;
149 
150     if (!target->runConfigurations().contains(rc))
151         return;
152 
153     m_runnable = rc->runnable();
154     setDisplayName(rc->displayName());
155 
156     BuildTargetInfo targetInfo = rc->buildTargetInfo();
157     if (!targetInfo.targetFilePath.isEmpty())
158         m_runnable.executable = ensureExeEnding(targetInfo.targetFilePath);
159 
160     Utils::FilePath buildBase;
161     if (auto buildConfig = target->activeBuildConfiguration()) {
162         buildBase = buildConfig->buildDirectory();
163         const QString projBase = startupProject->projectDirectory().toString();
164         if (m_projectFile.startsWith(projBase))
165             m_buildDir = (buildBase / m_projectFile.toString().mid(projBase.length())).absolutePath();
166     }
167     if (runMode == TestRunMode::Debug || runMode == TestRunMode::DebugWithoutDeploy)
168         m_runConfig = new Internal::TestRunConfiguration(rc->target(), this);
169 }
170 
completeTestInformation(TestRunMode runMode)171 void TestConfiguration::completeTestInformation(TestRunMode runMode)
172 {
173     QTC_ASSERT(!m_projectFile.isEmpty(), return);
174     QTC_ASSERT(!m_buildTargets.isEmpty(), return);
175     QTC_ASSERT(project(), return);
176 
177     if (m_origRunConfig) {
178         qCDebug(LOG) << "Using run configuration specified by user or found by first call";
179         completeTestInformation(m_origRunConfig, runMode);
180         if (hasExecutable()) {
181             qCDebug(LOG) << "Completed.\nRunnable:" << m_runnable.executable
182                          << "\nArgs:" << m_runnable.commandLineArguments
183                          << "\nWorking directory:" << m_runnable.workingDirectory;
184             return;
185         }
186         qCDebug(LOG) << "Failed to complete - using 'normal' way.";
187     }
188     Project *startupProject = SessionManager::startupProject();
189     if (!startupProject || startupProject != project()) {
190         setProject(nullptr);
191         return;
192     }
193 
194     Target *target = startupProject->activeTarget();
195     if (!target)
196         return;
197     qCDebug(LOG) << "ActiveTargetName\n    " << target->displayName();
198     if (const auto kit = target->kit())
199         qCDebug(LOG) << "SupportedPlatforms\n    " << kit->supportedPlatforms();
200 
201     const QSet<QString> buildSystemTargets = m_buildTargets;
202     qCDebug(LOG) << "BuildSystemTargets\n    " << buildSystemTargets;
203     const QList<BuildTargetInfo> buildTargets
204             = Utils::filtered(target->buildSystem()->applicationTargets(),
205                               [&buildSystemTargets](const BuildTargetInfo &bti) {
206         return buildSystemTargets.contains(bti.buildKey);
207     });
208     if (buildTargets.size() > 1 )  // there are multiple executables with the same build target
209         return;                    // let the user decide which one to run
210 
211     const BuildTargetInfo targetInfo = buildTargets.size() ? buildTargets.first()
212                                                            : BuildTargetInfo();
213 
214     // we might end up with an empty targetFilePath - e.g. when having a library we just link to
215     // there would be no BuildTargetInfo that could match
216     if (targetInfo.targetFilePath.isEmpty()) {
217         qCDebug(LOG) << "BuildTargetInfos";
218         // if there is only one build target just use it (but be honest that we're deducing)
219         m_deducedConfiguration = true;
220         m_deducedFrom = targetInfo.buildKey;
221     }
222 
223     const FilePath localExecutable = ensureExeEnding(targetInfo.targetFilePath);
224     if (localExecutable.isEmpty())
225         return;
226 
227     Utils::FilePath buildBase;
228     if (auto buildConfig = target->activeBuildConfiguration()) {
229         buildBase = buildConfig->buildDirectory();
230         const QString projBase = startupProject->projectDirectory().toString();
231         if (m_projectFile.startsWith(projBase))
232             m_buildDir = (buildBase / m_projectFile.toString().mid(projBase.length())).absolutePath();
233     }
234 
235     // deployment information should get taken into account, but it pretty much seems as if
236     // each build system uses it differently
237     const DeploymentData &deployData = target->deploymentData();
238     const DeployableFile deploy = deployData.deployableForLocalFile(localExecutable);
239     // we might have a deployable executable
240     const FilePath deployedExecutable = ensureExeEnding((deploy.isValid() && deploy.isExecutable())
241             ? FilePath::fromString(QDir::cleanPath(deploy.remoteFilePath())) : localExecutable);
242 
243     qCDebug(LOG) << " LocalExecutable" << localExecutable;
244     qCDebug(LOG) << " DeployedExecutable" << deployedExecutable;
245     qCDebug(LOG) << "Iterating run configurations - prefer active over others";
246     QList<RunConfiguration *> runConfigurations = target->runConfigurations();
247     runConfigurations.removeOne(target->activeRunConfiguration());
248     runConfigurations.prepend(target->activeRunConfiguration());
249     for (RunConfiguration *runConfig : qAsConst(runConfigurations)) {
250         qCDebug(LOG) << "RunConfiguration" << runConfig->id();
251         if (!isLocal(target)) { // TODO add device support
252             qCDebug(LOG) << " Skipped as not being local";
253             continue;
254         }
255 
256         const Runnable runnable = runConfig->runnable();
257         // not the best approach - but depending on the build system and whether the executables
258         // are going to get installed or not we have to soften the condition...
259         const FilePath currentExecutable = ensureExeEnding(runnable.executable);
260         const QString currentBST = runConfig->buildKey();
261         qCDebug(LOG) << " CurrentExecutable" << currentExecutable;
262         qCDebug(LOG) << " BST of RunConfig" << currentBST;
263         if ((localExecutable == currentExecutable)
264                 || (deployedExecutable == currentExecutable)
265                 || (buildSystemTargets.contains(currentBST))) {
266             qCDebug(LOG) << "  Using this RunConfig.";
267             m_origRunConfig = runConfig;
268             m_runnable = runnable;
269             m_runnable.executable = currentExecutable;
270             setDisplayName(runConfig->displayName());
271             if (runMode == TestRunMode::Debug || runMode == TestRunMode::DebugWithoutDeploy)
272                 m_runConfig = new Internal::TestRunConfiguration(target, this);
273             break;
274         }
275     }
276 
277     // RunConfiguration for this target could be explicitly removed or not created at all
278     // or we might have end up using the (wrong) path of a locally installed executable
279     // for this case try the original executable path of the BuildTargetInfo (the executable
280     // before installation) to have at least something to execute
281     if (!hasExecutable() && !localExecutable.isEmpty())
282         m_runnable.executable = localExecutable;
283     if (displayName().isEmpty() && hasExecutable()) {
284         qCDebug(LOG) << "   Fallback";
285         // we failed to find a valid runconfiguration - but we've got the executable already
286         if (auto rc = target->activeRunConfiguration()) {
287             if (isLocal(target)) { // FIXME for now only Desktop support
288                 const Runnable runnable = rc->runnable();
289                 m_runnable.environment = runnable.environment;
290                 m_deducedConfiguration = true;
291                 m_deducedFrom = rc->displayName();
292                 if (runMode == TestRunMode::Debug)
293                     m_runConfig = new Internal::TestRunConfiguration(rc->target(), this);
294             } else {
295                 qCDebug(LOG) << "not using the fallback as the current active run configuration "
296                                 "appears to be non-Desktop";
297             }
298         }
299     }
300 
301     if (displayName().isEmpty()) // happens e.g. when deducing the TestConfiguration or error
302         setDisplayName(*buildSystemTargets.begin());
303 }
304 
305 /**
306  * @brief sets the test cases for this test configuration.
307  *
308  * Watch out for special handling of test configurations, because this method also
309  * updates the test case count to the current size of \a testCases.
310  *
311  * @param testCases list of names of the test functions / test cases
312  */
setTestCases(const QStringList & testCases)313 void TestConfiguration::setTestCases(const QStringList &testCases)
314 {
315     m_testCases.clear();
316     m_testCases << testCases;
317     setTestCaseCount(m_testCases.size());
318 }
319 
setExecutableFile(const QString & executableFile)320 void TestConfiguration::setExecutableFile(const QString &executableFile)
321 {
322     m_runnable.executable = Utils::FilePath::fromString(executableFile);
323 }
324 
setProjectFile(const Utils::FilePath & projectFile)325 void TestConfiguration::setProjectFile(const Utils::FilePath &projectFile)
326 {
327     m_projectFile = projectFile;
328 }
329 
setBuildDirectory(const FilePath & buildDirectory)330 void TestConfiguration::setBuildDirectory(const FilePath &buildDirectory)
331 {
332     m_buildDir = buildDirectory;
333 }
334 
setInternalTarget(const QString & target)335 void TestConfiguration::setInternalTarget(const QString &target)
336 {
337     m_buildTargets.clear();
338     m_buildTargets.insert(target);
339 }
340 
setInternalTargets(const QSet<QString> & targets)341 void TestConfiguration::setInternalTargets(const QSet<QString> &targets)
342 {
343     m_buildTargets = targets;
344 }
345 
setOriginalRunConfiguration(RunConfiguration * runConfig)346 void TestConfiguration::setOriginalRunConfiguration(RunConfiguration *runConfig)
347 {
348     m_origRunConfig = runConfig;
349 }
350 
isDebugRunMode() const351 bool DebuggableTestConfiguration::isDebugRunMode() const
352 {
353     return m_runMode == TestRunMode::Debug || m_runMode == TestRunMode::DebugWithoutDeploy;
354 }
355 
framework() const356 ITestFramework *TestConfiguration::framework() const
357 {
358     return static_cast<ITestFramework *>(testBase());
359 }
360 
361 } // namespace Autotest
362