1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the test suite of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | |
30 | #include "externaltests.h" |
31 | |
32 | #include <QtCore/QTemporaryFile> |
33 | #include <QtCore/QTemporaryDir> |
34 | #if QT_CONFIG(process) |
35 | # include <QtCore/QProcess> |
36 | #endif |
37 | #include <QtCore/QByteArray> |
38 | #include <QtCore/QString> |
39 | #include <QtCore/QFileInfo> |
40 | #include <QtCore/QDir> |
41 | #include <QtCore/QDirIterator> |
42 | #include <QtCore/QDateTime> |
43 | #include <QtCore/QDebug> |
44 | #include <QtCore/QLibraryInfo> |
45 | #include <QtCore/QThread> |
46 | |
47 | #ifndef DEFAULT_MAKESPEC |
48 | # error DEFAULT_MAKESPEC not defined |
49 | #endif |
50 | |
51 | #ifdef Q_OS_UNIX |
52 | # include <fcntl.h> |
53 | # include <unistd.h> |
54 | #endif |
55 | |
56 | enum { |
57 | QMakeTimeout = 300000, // 5 minutes |
58 | CompileTimeout = 600000, // 10 minutes |
59 | RunTimeout = 300000 // 5 minutes |
60 | }; |
61 | |
62 | static QString makespec() |
63 | { |
64 | static const char default_makespec[] = DEFAULT_MAKESPEC; |
65 | if (default_makespec[0] == '/') |
66 | return QString::fromLatin1(str: default_makespec); |
67 | |
68 | const char *p; |
69 | for (p = default_makespec + sizeof(default_makespec) - 1; p >= default_makespec; --p) |
70 | if (*p == '/' || *p == '\\') |
71 | break; |
72 | |
73 | return QString::fromLatin1(str: p + 1); |
74 | } |
75 | |
76 | QT_BEGIN_NAMESPACE |
77 | namespace QTest { |
78 | #if QT_CONFIG(process) |
79 | static void ensureStopped(QProcess &process) |
80 | { |
81 | if (process.state() == QProcess::Running) { |
82 | process.terminate(); |
83 | QThread::msleep(20); |
84 | if (process.state() == QProcess::Running) |
85 | process.kill(); |
86 | } |
87 | } |
88 | |
89 | class QExternalProcess: public QProcess |
90 | { |
91 | protected: |
92 | #ifdef Q_OS_UNIX |
93 | void setupChildProcess() |
94 | { |
95 | // run in user code |
96 | QProcess::setupChildProcess(); |
97 | |
98 | if (processChannelMode() == ForwardedChannels) { |
99 | // reopen /dev/tty into stdin |
100 | int fd = ::open(file: "/dev/tty" , O_RDONLY); |
101 | if (fd == -1) |
102 | return; |
103 | ::dup2(fd: fd, fd2: 0); |
104 | ::close(fd: fd); |
105 | } |
106 | } |
107 | #endif |
108 | }; |
109 | #endif // QT_CONFIG(process) |
110 | |
111 | class QExternalTestPrivate |
112 | { |
113 | public: |
114 | QExternalTestPrivate() |
115 | : qtModules(QExternalTest::QtCore | QExternalTest::QtGui | QExternalTest::QtTest), |
116 | appType(QExternalTest::AutoApplication), |
117 | temporaryDir(0), exitCode(-1) |
118 | { |
119 | } |
120 | ~QExternalTestPrivate() |
121 | { |
122 | clear(); |
123 | } |
124 | |
125 | enum Target { Compile, Link, Run }; |
126 | |
127 | QList<QByteArray> qmakeLines; |
128 | QStringList ; |
129 | QByteArray ; |
130 | QExternalTest::QtModules qtModules; |
131 | QExternalTest::ApplicationType appType; |
132 | |
133 | QString temporaryDirPath; |
134 | QTemporaryDir *temporaryDir; |
135 | QByteArray sourceCode; |
136 | QByteArray std_out; |
137 | QByteArray std_err; |
138 | int exitCode; |
139 | QExternalTest::Stage failedStage; |
140 | |
141 | void clear(); |
142 | bool tryCompile(const QByteArray &body); |
143 | bool tryLink(const QByteArray &body); |
144 | bool tryRun(const QByteArray &body); |
145 | |
146 | private: |
147 | void removeTemporaryDirectory(); |
148 | bool createTemporaryDirectory(); |
149 | bool prepareSourceCode(const QByteArray &body); |
150 | bool createProjectFile(); |
151 | bool runQmake(); |
152 | bool runMake(Target target, int timeout); |
153 | bool commonSetup(const QByteArray &body); |
154 | }; |
155 | |
156 | QExternalTest::QExternalTest() |
157 | : d(new QExternalTestPrivate) |
158 | { |
159 | } |
160 | |
161 | QExternalTest::~QExternalTest() |
162 | { |
163 | delete d; |
164 | } |
165 | |
166 | QList<QByteArray> QExternalTest::qmakeSettings() const |
167 | { |
168 | return d->qmakeLines; |
169 | } |
170 | |
171 | void QExternalTest::setQmakeSettings(const QList<QByteArray> &settings) |
172 | { |
173 | d->qmakeLines = settings; |
174 | } |
175 | |
176 | QExternalTest::QtModules QExternalTest::qtModules() const |
177 | { |
178 | return d->qtModules; |
179 | } |
180 | |
181 | void QExternalTest::setQtModules(QtModules modules) |
182 | { |
183 | d->qtModules = modules; |
184 | } |
185 | |
186 | QExternalTest::ApplicationType QExternalTest::applicationType() const |
187 | { |
188 | return d->appType; |
189 | } |
190 | |
191 | void QExternalTest::setApplicationType(ApplicationType type) |
192 | { |
193 | d->appType = type; |
194 | } |
195 | |
196 | QStringList QExternalTest::() const |
197 | { |
198 | return d->extraProgramSources; |
199 | } |
200 | |
201 | void QExternalTest::(const QStringList &) |
202 | { |
203 | d->extraProgramSources = extra; |
204 | } |
205 | |
206 | QByteArray QExternalTest::() const |
207 | { |
208 | return d->programHeader; |
209 | } |
210 | |
211 | void QExternalTest::(const QByteArray &) |
212 | { |
213 | d->programHeader = header; |
214 | } |
215 | |
216 | bool QExternalTest::tryCompile(const QByteArray &body) |
217 | { |
218 | return d->tryCompile(body) && d->exitCode == 0; |
219 | } |
220 | |
221 | bool QExternalTest::tryLink(const QByteArray &body) |
222 | { |
223 | return d->tryLink(body) && d->exitCode == 0; |
224 | } |
225 | |
226 | bool QExternalTest::tryRun(const QByteArray &body) |
227 | { |
228 | return d->tryRun(body) && d->exitCode == 0; |
229 | } |
230 | |
231 | bool QExternalTest::tryCompileFail(const QByteArray &body) |
232 | { |
233 | return d->tryCompile(body) && d->exitCode != 0; |
234 | } |
235 | |
236 | bool QExternalTest::tryLinkFail(const QByteArray &body) |
237 | { |
238 | return d->tryLink(body) && d->exitCode != 0; |
239 | } |
240 | |
241 | bool QExternalTest::tryRunFail(const QByteArray &body) |
242 | { |
243 | return d->tryRun(body) && d->exitCode != 0; |
244 | } |
245 | |
246 | QExternalTest::Stage QExternalTest::failedStage() const |
247 | { |
248 | return d->failedStage; |
249 | } |
250 | |
251 | int QExternalTest::exitCode() const |
252 | { |
253 | return d->exitCode; |
254 | } |
255 | |
256 | QByteArray QExternalTest::fullProgramSource() const |
257 | { |
258 | return d->sourceCode; |
259 | } |
260 | |
261 | QByteArray QExternalTest::standardOutput() const |
262 | { |
263 | return d->std_out; |
264 | } |
265 | |
266 | QByteArray QExternalTest::standardError() const |
267 | { |
268 | return d->std_err; |
269 | } |
270 | |
271 | QString QExternalTest::errorReport() const |
272 | { |
273 | const char *stage = 0; |
274 | switch (d->failedStage) { |
275 | case FileStage: |
276 | stage = "creating files" ; |
277 | break; |
278 | case QmakeStage: |
279 | stage = "executing qmake" ; |
280 | break; |
281 | case CompilationStage: |
282 | stage = "during compilation" ; |
283 | break; |
284 | case LinkStage: |
285 | stage = "during linking" ; |
286 | break; |
287 | case RunStage: |
288 | stage = "executing program" ; |
289 | break; |
290 | } |
291 | |
292 | QString report = QString::fromLatin1( |
293 | str: "External test failed %1 with exit code %4\n" |
294 | "==== standard error: ====\n" |
295 | "%2\n" |
296 | "==== standard output: ====\n" |
297 | "%3\n" |
298 | "==== ====\n" ); |
299 | return report.arg(args: QString::fromLatin1(str: stage), |
300 | args: QString::fromLocal8Bit(str: d->std_err), |
301 | args: QString::fromLocal8Bit(str: d->std_out)) |
302 | .arg(a: d->exitCode); |
303 | } |
304 | |
305 | // actual execution code |
306 | void QExternalTestPrivate::clear() |
307 | { |
308 | delete temporaryDir; |
309 | temporaryDir = 0; |
310 | sourceCode.clear(); |
311 | std_out.clear(); |
312 | std_err.clear(); |
313 | exitCode = -1; |
314 | failedStage = QExternalTest::FileStage; |
315 | } |
316 | |
317 | bool QExternalTestPrivate::prepareSourceCode(const QByteArray &body) |
318 | { |
319 | sourceCode.clear(); |
320 | sourceCode.reserve(asize: 8192); |
321 | |
322 | sourceCode += programHeader; |
323 | |
324 | // Add Qt header includes |
325 | if (qtModules & QExternalTest::QtCore) |
326 | sourceCode += "#include <QtCore/QtCore>\n" ; |
327 | if (qtModules & QExternalTest::QtGui) |
328 | sourceCode += "#include <QtGui/QtGui>\n" ; |
329 | if (qtModules & QExternalTest::QtNetwork) |
330 | sourceCode += "#include <QtNetwork/QtNetwork>\n" ; |
331 | if (qtModules & QExternalTest::QtXml) |
332 | sourceCode += "#include <QtXml/QtXml>\n" ; |
333 | if (qtModules & QExternalTest::QtXmlPatterns) |
334 | sourceCode += "#include <QtXmlPatterns/QtXmlPatterns>\n" ; |
335 | if (qtModules & QExternalTest::QtOpenGL) |
336 | sourceCode += "#include <QtOpenGL/QtOpenGL>\n" ; |
337 | if (qtModules & QExternalTest::QtSql) |
338 | sourceCode += "#include <QtSql/QtSql>\n" ; |
339 | if (qtModules & QExternalTest::QtSvg) |
340 | sourceCode += "#include <QtSvg/QtSvg>\n" ; |
341 | if (qtModules & QExternalTest::QtScript) |
342 | sourceCode += "#include <QtScript/QtScript>\n" ; |
343 | if (qtModules & QExternalTest::QtTest) |
344 | sourceCode += "#include <QtTest/QtTest>\n" ; |
345 | if (qtModules & QExternalTest::QtDBus) |
346 | sourceCode += "#include <QtDBus/QtDBus>\n" ; |
347 | if (qtModules & QExternalTest::QtWebKit) |
348 | sourceCode += "#include <QtWebKit/QtWebKit>\n" ; |
349 | if (qtModules & QExternalTest::Phonon) |
350 | sourceCode += "#include <Phonon/Phonon>\n" ; |
351 | sourceCode += |
352 | "#include <stdlib.h>\n" |
353 | "#include <stddef.h>\n" ; |
354 | |
355 | sourceCode += |
356 | "\n" |
357 | "void q_external_test_user_code()\n" |
358 | "{\n" |
359 | "#include \"user_code.cpp\"\n" |
360 | "}\n" |
361 | "\n" |
362 | "#ifdef Q_OS_WIN\n" |
363 | "#include <windows.h>\n" |
364 | "#if defined(Q_CC_MSVC)\n" |
365 | "#include <crtdbg.h>\n" |
366 | "#endif\n" |
367 | "static void q_test_setup()\n" |
368 | "{\n" |
369 | " SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX);\n" |
370 | "}\n" |
371 | "static int __cdecl CrtDbgHook(int /*reportType*/, char * /*message*/, int * /*returnValue*/)\n" |
372 | "{\n" |
373 | " return TRUE;\n" |
374 | "}\n" |
375 | "#else\n" |
376 | "static void q_test_setup() { }\n" |
377 | "#endif\n" |
378 | "int main(int argc, char **argv)\n" |
379 | "{\n" |
380 | "#if defined(Q_CC_MSVC) && defined(QT_DEBUG) && defined(_DEBUG) && defined(_CRT_ERROR)\n" |
381 | " _CrtSetReportHook2(_CRT_RPTHOOK_INSTALL, CrtDbgHook);\n" |
382 | "#endif\n" ; |
383 | |
384 | switch (appType) { |
385 | applicationless: |
386 | case QExternalTest::Applicationless: |
387 | sourceCode += |
388 | " (void)argc; (void)argv;\n" ; |
389 | break; |
390 | |
391 | coreapplication: |
392 | case QExternalTest::QCoreApplication: |
393 | sourceCode += |
394 | " QCoreApplication app(argc, argv);\n" ; |
395 | break; |
396 | |
397 | guiapplication: |
398 | case QExternalTest::QGuiApplication: |
399 | sourceCode += |
400 | " QGuiApplication app(argc, argv);\n" ; |
401 | break; |
402 | |
403 | widgetsapplication: |
404 | case QExternalTest::QApplication: |
405 | sourceCode += |
406 | " QApplication app(argc, argv);\n" ; |
407 | break; |
408 | |
409 | case QExternalTest::AutoApplication: |
410 | if (qtModules & QExternalTest::QtWidgets) |
411 | goto widgetsapplication; |
412 | if (qtModules & QExternalTest::QtGui) |
413 | goto guiapplication; |
414 | if (qtModules == 0) |
415 | goto applicationless; |
416 | goto coreapplication; |
417 | } |
418 | |
419 | sourceCode += |
420 | " q_test_setup();\n" |
421 | " q_external_test_user_code();\n" |
422 | " return 0;\n" |
423 | "}\n" ; |
424 | |
425 | QFile sourceFile(temporaryDirPath + QLatin1String("/project.cpp" )); |
426 | if (!sourceFile.open(flags: QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { |
427 | std_err = sourceFile.errorString().toLocal8Bit(); |
428 | return false; |
429 | } |
430 | |
431 | sourceFile.write(data: sourceCode); |
432 | sourceFile.close(); |
433 | |
434 | sourceFile.setFileName(temporaryDirPath + QLatin1String("/user_code.cpp" )); |
435 | if (!sourceFile.open(flags: QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { |
436 | std_err = sourceFile.errorString().toLocal8Bit(); |
437 | return false; |
438 | } |
439 | sourceFile.write(data: body); |
440 | |
441 | return true; |
442 | } |
443 | |
444 | bool QExternalTestPrivate::createTemporaryDirectory() |
445 | { |
446 | delete temporaryDir; |
447 | temporaryDir = new QTemporaryDir; |
448 | if (temporaryDir->isValid()) { |
449 | temporaryDirPath = temporaryDir->path(); |
450 | return true; |
451 | } else { |
452 | delete temporaryDir; |
453 | temporaryDir = 0; |
454 | return false; |
455 | } |
456 | } |
457 | |
458 | bool QExternalTestPrivate::createProjectFile() |
459 | { |
460 | if (temporaryDirPath.isEmpty()) |
461 | qWarning() << "Temporary directory is expected to be non-empty" ; |
462 | |
463 | QFile projectFile(temporaryDirPath + QLatin1String("/project.pro" )); |
464 | if (!projectFile.open(flags: QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { |
465 | std_err = projectFile.errorString().toLocal8Bit(); |
466 | return false; |
467 | } |
468 | |
469 | projectFile.write( |
470 | data: "TEMPLATE = app\n" |
471 | "\n" |
472 | "TARGET = externaltest\n" |
473 | "CONFIG -= debug_and_release\n" |
474 | "CONFIG += cmdline\n" |
475 | "DESTDIR = .\n" |
476 | "OBJECTS_DIR = .\n" |
477 | "UI_DIR = .\n" |
478 | "MOC_DIR = .\n" |
479 | "RCC_DIR = .\n" |
480 | "HEADERS +=\n" |
481 | "SOURCES += project.cpp\n" |
482 | "QT -= core gui\n" |
483 | "INCLUDEPATH += . " ); |
484 | |
485 | QString workingDir = QDir::currentPath(); |
486 | if (extraProgramSources.count() > 0) |
487 | workingDir = QFileInfo(extraProgramSources.first()).absolutePath(); |
488 | projectFile.write(data: QFile::encodeName(fileName: workingDir)); |
489 | |
490 | #ifndef QT_NO_DEBUG |
491 | projectFile.write(data: "\nCONFIG += debug\n" ); |
492 | #else |
493 | projectFile.write("\nCONFIG += release\n" ); |
494 | #endif |
495 | |
496 | QByteArray = QFile::encodeName(fileName: extraProgramSources.join(sep: ' ')); |
497 | if (!extraSources.isEmpty()) { |
498 | projectFile.write(data: "SOURCES += " ); |
499 | projectFile.write(data: extraSources); |
500 | projectFile.putChar(c: '\n'); |
501 | } |
502 | |
503 | // Add Qt modules |
504 | if (qtModules & QExternalTest::QtCore) |
505 | projectFile.write(data: "QT += core\n" ); |
506 | if (qtModules & QExternalTest::QtGui) |
507 | projectFile.write(data: "QT += gui\n" ); |
508 | if (qtModules & QExternalTest::QtNetwork) |
509 | projectFile.write(data: "QT += network\n" ); |
510 | if (qtModules & QExternalTest::QtXml) |
511 | projectFile.write(data: "QT += xml\n" ); |
512 | if (qtModules & QExternalTest::QtXmlPatterns) |
513 | projectFile.write(data: "QT += xmlpatterns\n" ); |
514 | if (qtModules & QExternalTest::QtOpenGL) |
515 | projectFile.write(data: "QT += opengl\n" ); |
516 | if (qtModules & QExternalTest::QtSql) |
517 | projectFile.write(data: "QT += sql\n" ); |
518 | if (qtModules & QExternalTest::QtSvg) |
519 | projectFile.write(data: "QT += svg\n" ); |
520 | if (qtModules & QExternalTest::QtScript) |
521 | projectFile.write(data: "QT += script\n" ); |
522 | if (qtModules & QExternalTest::QtTest) |
523 | projectFile.write(data: "QT += testlib\n" ); |
524 | if (qtModules & QExternalTest::QtDBus) |
525 | projectFile.write(data: "QT += dbus\n" ); |
526 | if (qtModules & QExternalTest::QtWebKit) |
527 | projectFile.write(data: "QT += webkit\n" ); |
528 | if (qtModules & QExternalTest::Phonon) |
529 | projectFile.write(data: "QT += phonon\n" ); |
530 | |
531 | projectFile.write(data: "\n### User-specified settings start ###\n" ); |
532 | foreach (QByteArray line, qmakeLines) { |
533 | projectFile.write(data: line); |
534 | projectFile.write(data: "\n" ); |
535 | } |
536 | projectFile.write(data: "\n### User-specified settings end ###\n" ); |
537 | |
538 | // Use qmake to just compile: |
539 | projectFile.write( |
540 | data: "\n" |
541 | "test_compile.depends += $(OBJECTS)\n" |
542 | "QMAKE_EXTRA_TARGETS += test_compile\n" ); |
543 | |
544 | // Use qmake to run the app too: |
545 | projectFile.write( |
546 | data: "\n" |
547 | "unix:test_run.commands = ./$(QMAKE_TARGET)\n" |
548 | "else:test_run.commands = $(QMAKE_TARGET)\n" |
549 | "embedded:test_run.commands += -qws\n" |
550 | "QMAKE_EXTRA_TARGETS += test_run\n" ); |
551 | |
552 | // Use qmake to debug: |
553 | projectFile.write( |
554 | data: "\n" |
555 | "*-g++* {\n" |
556 | " unix:test_debug.commands = gdb --args ./$(QMAKE_TARGET)\n" |
557 | " else:test_debug.commands = gdb --args $(QMAKE_TARGET)\n" |
558 | " embedded:test_debug.commands += -qws\n" |
559 | " QMAKE_EXTRA_TARGETS += test_debug\n" |
560 | "}\n" ); |
561 | |
562 | // Also use qmake to run the app with valgrind: |
563 | projectFile.write( |
564 | data: "\n" |
565 | "unix:test_valgrind.commands = valgrind ./$(QMAKE_TARGET)\n" |
566 | "else:test_valgrind.commands = valgrind $(QMAKE_TARGET)\n" |
567 | "embedded:test_valgrind.commands += -qws\n" |
568 | "QMAKE_EXTRA_TARGETS += test_valgrind\n" ); |
569 | |
570 | return true; |
571 | } |
572 | |
573 | bool QExternalTestPrivate::runQmake() |
574 | { |
575 | #if QT_CONFIG(process) |
576 | if (temporaryDirPath.isEmpty()) |
577 | qWarning() << "Temporary directory is expected to be non-empty" ; |
578 | |
579 | if (!createProjectFile()) |
580 | return false; |
581 | |
582 | failedStage = QExternalTest::QmakeStage; |
583 | QProcess qmake; |
584 | QStringList args; |
585 | args << QLatin1String("-makefile" ) |
586 | << QLatin1String("-spec" ) |
587 | << makespec() |
588 | << QLatin1String("project.pro" ); |
589 | qmake.setWorkingDirectory(temporaryDirPath); |
590 | |
591 | QString cmd = QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/qmake" ; |
592 | #ifdef Q_OS_WIN |
593 | cmd.append(".exe" ); |
594 | #endif |
595 | if (!QFile::exists(fileName: cmd)) { |
596 | cmd = "qmake" ; |
597 | qWarning(msg: "qmake from build not found, fallback to PATH's qmake" ); |
598 | } |
599 | |
600 | qmake.start(program: cmd, arguments: args); |
601 | |
602 | std_out += "### --- stdout from qmake --- ###\n" ; |
603 | std_err += "### --- stderr from qmake --- ###\n" ; |
604 | bool ok = qmake.waitForStarted(); |
605 | if (!ok) { |
606 | exitCode = 255; |
607 | std_err += "qmake: " ; |
608 | std_err += qmake.errorString().toLocal8Bit(); |
609 | } else { |
610 | ok = qmake.waitForFinished(msecs: QMakeTimeout); |
611 | exitCode = qmake.exitCode(); |
612 | if (!ok) |
613 | QTest::ensureStopped(process&: qmake); |
614 | |
615 | std_out += qmake.readAllStandardOutput(); |
616 | std_err += qmake.readAllStandardError(); |
617 | } |
618 | |
619 | return ok && exitCode == 0; |
620 | #else // QT_CONFIG(process) |
621 | return false; |
622 | #endif // QT_CONFIG(process) |
623 | } |
624 | |
625 | bool QExternalTestPrivate::runMake(Target target, int timeout) |
626 | { |
627 | #if !QT_CONFIG(process) |
628 | return false; |
629 | #else |
630 | if (temporaryDirPath.isEmpty()) |
631 | qWarning() << "Temporary directory is expected to be non-empty" ; |
632 | |
633 | QExternalProcess make; |
634 | make.setWorkingDirectory(temporaryDirPath); |
635 | |
636 | QStringList environment = QProcess::systemEnvironment(); |
637 | environment += QLatin1String("LC_ALL=C" ); |
638 | make.setEnvironment(environment); |
639 | |
640 | QStringList args; |
641 | QProcess::ProcessChannelMode channelMode = QProcess::SeparateChannels; |
642 | if (target == Compile) { |
643 | args << QLatin1String("test_compile" ); |
644 | } else if (target == Run) { |
645 | QByteArray run = qgetenv(varName: "QTEST_EXTERNAL_RUN" ); |
646 | if (run == "valgrind" ) |
647 | args << QLatin1String("test_valgrind" ); |
648 | else if (run == "debug" ) |
649 | args << QLatin1String("test_debug" ); |
650 | else |
651 | args << QLatin1String("test_run" ); |
652 | if (!run.isEmpty()) |
653 | channelMode = QProcess::ForwardedChannels; |
654 | } |
655 | |
656 | make.setProcessChannelMode(channelMode); |
657 | |
658 | static const char makes[] = |
659 | "jom.exe\0" //preferred for visual c++ or mingw |
660 | "nmake.exe\0" //for visual c++ |
661 | "mingw32-make.exe\0" //for mingw |
662 | "gmake\0" |
663 | "make\0" ; |
664 | for (const char *p = makes; *p; p += strlen(s: p) + 1) { |
665 | make.start(program: QLatin1String(p), arguments: args); |
666 | if (make.waitForStarted()) |
667 | break; |
668 | } |
669 | |
670 | if (make.state() != QProcess::Running) { |
671 | exitCode = 255; |
672 | std_err += "make: " ; |
673 | std_err += make.errorString().toLocal8Bit(); |
674 | return false; |
675 | } |
676 | |
677 | make.closeWriteChannel(); |
678 | bool ok = make.waitForFinished(msecs: channelMode == QProcess::ForwardedChannels ? -1 : timeout); |
679 | if (!ok) |
680 | QTest::ensureStopped(process&: make); |
681 | exitCode = make.exitCode(); |
682 | std_out += make.readAllStandardOutput(); |
683 | std_err += make.readAllStandardError(); |
684 | |
685 | return ok; |
686 | #endif // QT_CONFIG(process) |
687 | } |
688 | |
689 | bool QExternalTestPrivate::commonSetup(const QByteArray &body) |
690 | { |
691 | clear(); |
692 | |
693 | if (!createTemporaryDirectory()) |
694 | return false; |
695 | if (!createProjectFile()) |
696 | return false; |
697 | if (!prepareSourceCode(body)) |
698 | return false; |
699 | if (!runQmake()) |
700 | return false; |
701 | return true; |
702 | } |
703 | |
704 | bool QExternalTestPrivate::tryCompile(const QByteArray &body) |
705 | { |
706 | if (!commonSetup(body)) |
707 | return false; |
708 | |
709 | // compile |
710 | failedStage = QExternalTest::CompilationStage; |
711 | std_out += "\n### --- stdout from make (compilation) --- ###\n" ; |
712 | std_err += "\n### --- stderr from make (compilation) --- ###\n" ; |
713 | return runMake(target: Compile, timeout: CompileTimeout); |
714 | } |
715 | |
716 | bool QExternalTestPrivate::tryLink(const QByteArray &body) |
717 | { |
718 | if (!tryCompile(body) || exitCode != 0) |
719 | return false; |
720 | |
721 | // link |
722 | failedStage = QExternalTest::LinkStage; |
723 | std_out += "\n### --- stdout from make (linking) --- ###\n" ; |
724 | std_err += "\n### --- stderr from make (linking) --- ###\n" ; |
725 | return runMake(target: Link, timeout: CompileTimeout); |
726 | } |
727 | |
728 | bool QExternalTestPrivate::tryRun(const QByteArray &body) |
729 | { |
730 | if (!tryLink(body) || exitCode != 0) |
731 | return false; |
732 | |
733 | // run |
734 | failedStage = QExternalTest::RunStage; |
735 | std_out += "\n### --- stdout from process --- ###\n" ; |
736 | std_err += "\n### --- stderr from process --- ###\n" ; |
737 | return runMake(target: Run, timeout: RunTimeout); |
738 | } |
739 | } |
740 | QT_END_NAMESPACE |
741 | |