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
56enum {
57 QMakeTimeout = 300000, // 5 minutes
58 CompileTimeout = 600000, // 10 minutes
59 RunTimeout = 300000 // 5 minutes
60};
61
62static 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
76QT_BEGIN_NAMESPACE
77namespace 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 extraProgramSources;
129 QByteArray programHeader;
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::extraProgramSources() const
197 {
198 return d->extraProgramSources;
199 }
200
201 void QExternalTest::setExtraProgramSources(const QStringList &extra)
202 {
203 d->extraProgramSources = extra;
204 }
205
206 QByteArray QExternalTest::programHeader() const
207 {
208 return d->programHeader;
209 }
210
211 void QExternalTest::setProgramHeader(const QByteArray &header)
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 extraSources = 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}
740QT_END_NAMESPACE
741

source code of qtbase/tests/auto/corelib/tools/qsharedpointer/externaltests.cpp