1
/****************************************************************************
3
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
4
** Contact: http://www.qt-project.org/legal
6
** This file is part of the test suite of the Qt Toolkit.
8
** $QT_BEGIN_LICENSE:LGPL$
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 Digia. For licensing terms and
14
** conditions see http://qt.digia.com/licensing. For further information
15
** use the contact form at http://qt.digia.com/contact-us.
17
** GNU Lesser General Public License Usage
18
** Alternatively, this file may be used under the terms of the GNU Lesser
19
** General Public License version 2.1 as published by the Free Software
20
** Foundation and appearing in the file LICENSE.LGPL included in the
21
** packaging of this file. Please review the following information to
22
** ensure the GNU Lesser General Public License version 2.1 requirements
23
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25
** In addition, as a special exception, Digia gives you certain additional
26
** rights. These rights are described in the Digia Qt LGPL Exception
27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29
** GNU General Public License Usage
30
** Alternatively, this file may be used under the terms of the GNU
31
** General Public License version 3.0 as published by the Free Software
32
** Foundation and appearing in the file LICENSE.GPL included in the
33
** packaging of this file. Please review the following information to
34
** ensure the GNU General Public License version 3.0 requirements will be
35
** met: http://www.gnu.org/copyleft/gpl.html.
40
****************************************************************************/
43
#include "externaltests.h"
45
#include <QtCore/QTemporaryFile>
46
#include <QtCore/QTemporaryDir>
47
#include <QtCore/QProcess>
48
#include <QtCore/QByteArray>
49
#include <QtCore/QString>
50
#include <QtCore/QFileInfo>
51
#include <QtCore/QDir>
52
#include <QtCore/QDirIterator>
53
#include <QtCore/QDateTime>
54
#include <QtCore/QDebug>
55
#include <QtCore/QLibraryInfo>
57
#ifndef DEFAULT_MAKESPEC
58
# error DEFAULT_MAKESPEC not defined
66
static QString makespec()
68
static const char default_makespec[] = DEFAULT_MAKESPEC;
69
if (default_makespec[0] == '/')
70
return QString::fromLatin1(default_makespec);
73
for (p = default_makespec + sizeof(default_makespec) - 1; p >= default_makespec; --p)
74
if (*p == '/' || *p == '\\')
77
return QString::fromLatin1(p + 1);
82
class QExternalProcess: public QProcess
86
void setupChildProcess()
89
QProcess::setupChildProcess();
91
if (processChannelMode() == ForwardedChannels) {
92
// reopen /dev/tty into stdin
93
int fd = ::open("/dev/tty", O_RDONLY);
103
class QExternalTestPrivate
106
QExternalTestPrivate()
107
: qtModules(QExternalTest::QtCore | QExternalTest::QtGui | QExternalTest::QtTest),
108
appType(QExternalTest::AutoApplication),
109
temporaryDir(0), exitCode(-1)
112
~QExternalTestPrivate()
117
enum Target { Compile, Link, Run };
119
QList<QByteArray> qmakeLines;
120
QStringList extraProgramSources;
121
QByteArray programHeader;
122
QExternalTest::QtModules qtModules;
123
QExternalTest::ApplicationType appType;
125
QString temporaryDirPath;
126
QTemporaryDir *temporaryDir;
127
QByteArray sourceCode;
131
QExternalTest::Stage failedStage;
134
bool tryCompile(const QByteArray &body);
135
bool tryLink(const QByteArray &body);
136
bool tryRun(const QByteArray &body);
139
void removeTemporaryDirectory();
140
bool createTemporaryDirectory();
141
bool prepareSourceCode(const QByteArray &body);
142
bool createProjectFile();
144
bool runMake(Target target);
145
bool commonSetup(const QByteArray &body);
148
QExternalTest::QExternalTest()
149
: d(new QExternalTestPrivate)
153
QExternalTest::~QExternalTest()
158
QList<QByteArray> QExternalTest::qmakeSettings() const
160
return d->qmakeLines;
163
void QExternalTest::setQmakeSettings(const QList<QByteArray> &settings)
165
d->qmakeLines = settings;
168
QExternalTest::QtModules QExternalTest::qtModules() const
173
void QExternalTest::setQtModules(QtModules modules)
175
d->qtModules = modules;
178
QExternalTest::ApplicationType QExternalTest::applicationType() const
183
void QExternalTest::setApplicationType(ApplicationType type)
188
QStringList QExternalTest::extraProgramSources() const
190
return d->extraProgramSources;
193
void QExternalTest::setExtraProgramSources(const QStringList &extra)
195
d->extraProgramSources = extra;
198
QByteArray QExternalTest::programHeader() const
200
return d->programHeader;
203
void QExternalTest::setProgramHeader(const QByteArray &header)
205
d->programHeader = header;
208
bool QExternalTest::tryCompile(const QByteArray &body)
210
return d->tryCompile(body) && d->exitCode == 0;
213
bool QExternalTest::tryLink(const QByteArray &body)
215
return d->tryLink(body) && d->exitCode == 0;
218
bool QExternalTest::tryRun(const QByteArray &body)
220
return d->tryRun(body) && d->exitCode == 0;
223
bool QExternalTest::tryCompileFail(const QByteArray &body)
225
return d->tryCompile(body) && d->exitCode != 0;
228
bool QExternalTest::tryLinkFail(const QByteArray &body)
230
return d->tryLink(body) && d->exitCode != 0;
233
bool QExternalTest::tryRunFail(const QByteArray &body)
235
return d->tryRun(body) && d->exitCode != 0;
238
QExternalTest::Stage QExternalTest::failedStage() const
240
return d->failedStage;
243
int QExternalTest::exitCode() const
248
QByteArray QExternalTest::fullProgramSource() const
250
return d->sourceCode;
253
QByteArray QExternalTest::standardOutput() const
258
QByteArray QExternalTest::standardError() const
263
QString QExternalTest::errorReport() const
265
const char *stage = 0;
266
switch (d->failedStage) {
268
stage = "creating files";
271
stage = "executing qmake";
273
case CompilationStage:
274
stage = "during compilation";
277
stage = "during linking";
280
stage = "executing program";
284
QString report = QString::fromLatin1(
285
"External test failed %1 with exit code %4\n"
286
"==== standard error: ====\n"
288
"==== standard output: ====\n"
291
return report.arg(QString::fromLatin1(stage),
292
QString::fromLocal8Bit(d->std_err),
293
QString::fromLocal8Bit(d->std_out))
297
// actual execution code
298
void QExternalTestPrivate::clear()
306
failedStage = QExternalTest::FileStage;
309
bool QExternalTestPrivate::prepareSourceCode(const QByteArray &body)
312
sourceCode.reserve(8192);
314
sourceCode += programHeader;
316
// Add Qt header includes
317
if (qtModules & QExternalTest::QtCore)
318
sourceCode += "#include <QtCore/QtCore>\n";
319
if (qtModules & QExternalTest::QtGui)
320
sourceCode += "#include <QtGui/QtGui>\n";
321
if (qtModules & QExternalTest::QtNetwork)
322
sourceCode += "#include <QtNetwork/QtNetwork>\n";
323
if (qtModules & QExternalTest::QtXml)
324
sourceCode += "#include <QtXml/QtXml>\n";
325
if (qtModules & QExternalTest::QtXmlPatterns)
326
sourceCode += "#include <QtXmlPatterns/QtXmlPatterns>\n";
327
if (qtModules & QExternalTest::QtOpenGL)
328
sourceCode += "#include <QtOpenGL/QtOpenGL>\n";
329
if (qtModules & QExternalTest::QtSql)
330
sourceCode += "#include <QtSql/QtSql>\n";
331
if (qtModules & QExternalTest::QtSvg)
332
sourceCode += "#include <QtSvg/QtSvg>\n";
333
if (qtModules & QExternalTest::QtScript)
334
sourceCode += "#include <QtScript/QtScript>\n";
335
if (qtModules & QExternalTest::QtTest)
336
sourceCode += "#include <QtTest/QtTest>\n";
337
if (qtModules & QExternalTest::QtDBus)
338
sourceCode += "#include <QtDBus/QtDBus>\n";
339
if (qtModules & QExternalTest::QtWebKit)
340
sourceCode += "#include <QtWebKit/QtWebKit>\n";
341
if (qtModules & QExternalTest::Phonon)
342
sourceCode += "#include <Phonon/Phonon>\n";
344
"#include <stdlib.h>\n"
345
"#include <stddef.h>\n";
349
"void q_external_test_user_code()\n"
351
"#include \"user_code.cpp\"\n"
355
"#include <windows.h>\n"
356
"#if defined(Q_CC_MSVC) && !defined(Q_OS_WINCE)\n"
357
"#include <crtdbg.h>\n"
359
"static void q_test_setup()\n"
361
" SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX);\n"
363
"static int __cdecl CrtDbgHook(int /*reportType*/, char * /*message*/, int * /*returnValue*/)\n"
368
"static void q_test_setup() { }\n"
370
"int main(int argc, char **argv)\n"
372
"#if defined(Q_CC_MSVC) && defined(QT_DEBUG) && defined(_DEBUG) && defined(_CRT_ERROR) && !defined(Q_OS_WINCE)\n"
373
" _CrtSetReportHook2(_CRT_RPTHOOK_INSTALL, CrtDbgHook);\n"
378
case QExternalTest::Applicationless:
380
" (void)argc; (void)argv;\n";
384
case QExternalTest::QCoreApplication:
386
" QCoreApplication app(argc, argv);\n";
390
case QExternalTest::QGuiApplication:
392
" QGuiApplication app(argc, argv);\n";
396
case QExternalTest::QApplication:
398
" QApplication app(argc, argv);\n";
401
case QExternalTest::AutoApplication:
402
if (qtModules & QExternalTest::QtWidgets)
403
goto widgetsapplication;
404
if (qtModules & QExternalTest::QtGui)
407
goto applicationless;
408
goto coreapplication;
413
" q_external_test_user_code();\n"
417
QFile sourceFile(temporaryDirPath + QLatin1String("/project.cpp"));
418
if (!sourceFile.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
419
std_err = sourceFile.errorString().toLocal8Bit();
423
sourceFile.write(sourceCode);
426
sourceFile.setFileName(temporaryDirPath + QLatin1String("/user_code.cpp"));
427
if (!sourceFile.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
428
std_err = sourceFile.errorString().toLocal8Bit();
431
sourceFile.write(body);
436
bool QExternalTestPrivate::createTemporaryDirectory()
439
temporaryDir = new QTemporaryDir;
440
if (temporaryDir->isValid()) {
441
temporaryDirPath = temporaryDir->path();
450
bool QExternalTestPrivate::createProjectFile()
452
if (temporaryDirPath.isEmpty())
453
qWarning() << "Temporary directory is expected to be non-empty";
455
QFile projectFile(temporaryDirPath + QLatin1String("/project.pro"));
456
if (!projectFile.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
457
std_err = projectFile.errorString().toLocal8Bit();
464
"TARGET = externaltest\n"
465
"CONFIG -= app_bundle\n" // for the Mac
466
"CONFIG -= debug_and_release\n"
467
"CONFIG += console\n"
474
"SOURCES += project.cpp\n"
476
"INCLUDEPATH += . ");
478
QString workingDir = QDir::currentPath();
479
if (extraProgramSources.count() > 0)
480
workingDir = QFileInfo(extraProgramSources.first()).absolutePath();
481
projectFile.write(QFile::encodeName(workingDir));
484
projectFile.write("\nCONFIG += debug\n");
486
projectFile.write("\nCONFIG += release\n");
489
QByteArray extraSources = QFile::encodeName(extraProgramSources.join(' '));
490
if (!extraSources.isEmpty()) {
491
projectFile.write("SOURCES += ");
492
projectFile.write(extraSources);
493
projectFile.putChar('\n');
497
if (qtModules & QExternalTest::QtCore)
498
projectFile.write("QT += core\n");
499
if (qtModules & QExternalTest::QtGui)
500
projectFile.write("QT += gui\n");
501
if (qtModules & QExternalTest::QtNetwork)
502
projectFile.write("QT += network\n");
503
if (qtModules & QExternalTest::QtXml)
504
projectFile.write("QT += xml\n");
505
if (qtModules & QExternalTest::QtXmlPatterns)
506
projectFile.write("QT += xmlpatterns\n");
507
if (qtModules & QExternalTest::QtOpenGL)
508
projectFile.write("QT += opengl\n");
509
if (qtModules & QExternalTest::QtSql)
510
projectFile.write("QT += sql\n");
511
if (qtModules & QExternalTest::QtSvg)
512
projectFile.write("QT += svg\n");
513
if (qtModules & QExternalTest::QtScript)
514
projectFile.write("QT += script\n");
515
if (qtModules & QExternalTest::QtTest)
516
projectFile.write("QT += testlib\n");
517
if (qtModules & QExternalTest::QtDBus)
518
projectFile.write("QT += dbus\n");
519
if (qtModules & QExternalTest::QtWebKit)
520
projectFile.write("QT += webkit\n");
521
if (qtModules & QExternalTest::Phonon)
522
projectFile.write("QT += phonon\n");
524
projectFile.write("\n### User-specified settings start ###\n");
525
foreach (QByteArray line, qmakeLines) {
526
projectFile.write(line);
527
projectFile.write("\n");
529
projectFile.write("\n### User-specified settings end ###\n");
531
// Use qmake to just compile:
534
"test_compile.depends += $(OBJECTS)\n"
535
"QMAKE_EXTRA_TARGETS += test_compile\n");
537
// Use qmake to run the app too:
540
"unix:test_run.commands = ./$(QMAKE_TARGET)\n"
541
"else:test_run.commands = $(QMAKE_TARGET)\n"
542
"embedded:test_run.commands += -qws\n"
543
"QMAKE_EXTRA_TARGETS += test_run\n");
545
// Use qmake to debug:
549
" unix:test_debug.commands = gdb --args ./$(QMAKE_TARGET)\n"
550
" else:test_debug.commands = gdb --args $(QMAKE_TARGET)\n"
551
" embedded:test_debug.commands += -qws\n"
552
" QMAKE_EXTRA_TARGETS += test_debug\n"
555
// Also use qmake to run the app with valgrind:
558
"unix:test_valgrind.commands = valgrind ./$(QMAKE_TARGET)\n"
559
"else:test_valgrind.commands = valgrind $(QMAKE_TARGET)\n"
560
"embedded:test_valgrind.commands += -qws\n"
561
"QMAKE_EXTRA_TARGETS += test_valgrind\n");
566
bool QExternalTestPrivate::runQmake()
568
if (temporaryDirPath.isEmpty())
569
qWarning() << "Temporary directory is expected to be non-empty";
571
if (!createProjectFile())
574
failedStage = QExternalTest::QmakeStage;
577
args << QLatin1String("-makefile")
578
<< QLatin1String("-spec")
580
<< QLatin1String("project.pro");
581
qmake.setWorkingDirectory(temporaryDirPath);
583
QString cmd = QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/qmake";
587
if (!QFile::exists(cmd)) {
589
qWarning("qmake from build not found, fallback to PATH's qmake");
592
qmake.start(cmd, args);
594
std_out += "### --- stdout from qmake --- ###\n";
595
std_err += "### --- stderr from qmake --- ###\n";
596
bool ok = qmake.waitForStarted();
599
std_err += "qmake: ";
600
std_err += qmake.errorString().toLocal8Bit();
602
ok = qmake.waitForFinished();
603
exitCode = qmake.exitCode();
605
std_out += qmake.readAllStandardOutput();
606
std_err += qmake.readAllStandardError();
609
return ok && exitCode == 0;
612
bool QExternalTestPrivate::runMake(Target target)
614
if (temporaryDirPath.isEmpty())
615
qWarning() << "Temporary directory is expected to be non-empty";
617
QExternalProcess make;
618
make.setWorkingDirectory(temporaryDirPath);
620
QStringList environment = QProcess::systemEnvironment();
621
environment += QLatin1String("LC_ALL=C");
622
make.setEnvironment(environment);
625
QProcess::ProcessChannelMode channelMode = QProcess::SeparateChannels;
626
if (target == Compile) {
627
args << QLatin1String("test_compile");
628
} else if (target == Run) {
629
QByteArray run = qgetenv("QTEST_EXTERNAL_RUN");
630
if (run == "valgrind")
631
args << QLatin1String("test_valgrind");
632
else if (run == "debug")
633
args << QLatin1String("test_debug");
635
args << QLatin1String("test_run");
637
channelMode = QProcess::ForwardedChannels;
640
make.setProcessChannelMode(channelMode);
642
static const char makes[] =
643
"nmake.exe\0" //for visual c++
644
"mingw32-make.exe\0" //for mingw
647
for (const char *p = makes; *p; p += strlen(p) + 1) {
648
make.start(QLatin1String(p), args);
649
if (make.waitForStarted())
653
if (make.state() != QProcess::Running) {
656
std_err += make.errorString().toLocal8Bit();
660
make.closeWriteChannel();
661
bool ok = make.waitForFinished(channelMode == QProcess::ForwardedChannels ? -1 : 60000);
664
exitCode = make.exitCode();
665
std_out += make.readAllStandardOutput();
666
std_err += make.readAllStandardError();
671
bool QExternalTestPrivate::commonSetup(const QByteArray &body)
675
if (!createTemporaryDirectory())
677
if (!createProjectFile())
679
if (!prepareSourceCode(body))
686
bool QExternalTestPrivate::tryCompile(const QByteArray &body)
688
if (!commonSetup(body))
692
failedStage = QExternalTest::CompilationStage;
693
std_out += "\n### --- stdout from make (compilation) --- ###\n";
694
std_err += "\n### --- stderr from make (compilation) --- ###\n";
695
return runMake(Compile);
698
bool QExternalTestPrivate::tryLink(const QByteArray &body)
700
if (!tryCompile(body) || exitCode != 0)
704
failedStage = QExternalTest::LinkStage;
705
std_out += "\n### --- stdout from make (linking) --- ###\n";
706
std_err += "\n### --- stderr from make (linking) --- ###\n";
707
return runMake(Link);
710
bool QExternalTestPrivate::tryRun(const QByteArray &body)
712
if (!tryLink(body) || exitCode != 0)
716
failedStage = QExternalTest::RunStage;
717
std_out += "\n### --- stdout from process --- ###\n";
718
std_err += "\n### --- stderr from process --- ###\n";