Files
qt-creator/tests/auto/profilewriter/tst_profilewriter.cpp
hjk c7884a2b17 Qmake: Enhance remote parsing
When parsing remote .pro files the parsers needs to pretend it runs
on the remote device.

All internal data are now "local on the remote", so that string
manipulation in .pro files "just works", and a 'device root'
string is passed around to relevant functions which then use
it to construct FilePath::toFSPathString()-style paths that
our custom FSEngineImpl can then map to the real device.

Remote $$system() calls are intercepted by the local parser and
redirected using the usual QtcProcess mechanims.

Quite a bit of the ProParser needed adjustments, so there's some
potential for regression.

Task-number: QTCREATORBUG-28242
Task-number: QTCREATORBUG-28161
Task-number: QTCREATORBUG-28355
Change-Id: I6100e7aca4e1db35f5e9689004110aab57e2c595
Reviewed-by: hjk <hjk@qt.io>
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
2023-01-10 12:02:23 +00:00

732 lines
22 KiB
C++

// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <qmakevfs.h>
#include <qmakeparser.h>
#include <prowriter.h>
#include <QtTest>
#define BASE_DIR "/some/stuff"
///////////// callbacks for parser/evaluator
static void print(const QString &fileName, int lineNo, const QString &msg)
{
if (lineNo > 0)
qWarning("%s(%d): %s", qPrintable(fileName), lineNo, qPrintable(msg));
else if (lineNo)
qWarning("%s: %s", qPrintable(fileName), qPrintable(msg));
else
qWarning("%s", qPrintable(msg));
}
class ParseHandler : public QMakeParserHandler {
public:
virtual void message(int /* type */, const QString &msg, const QString &fileName, int lineNo)
{ print(fileName, lineNo, msg); }
};
static ParseHandler parseHandler;
//////////////// the actual autotest
typedef QmakeProjectManager::Internal::ProWriter PW;
class tst_ProFileWriter : public QObject
{
Q_OBJECT
private slots:
void adds_data();
void adds();
void removes_data();
void removes();
void multiVar();
void addFiles();
void removeFiles();
};
static QStringList strList(const char * const *array)
{
QStringList values;
for (const char * const *value = array; *value; value++)
values << QString::fromLatin1(*value);
return values;
}
void tst_ProFileWriter::adds_data()
{
QTest::addColumn<int>("flags");
QTest::addColumn<QStringList>("values");
QTest::addColumn<QString>("scope");
QTest::addColumn<QString>("input");
QTest::addColumn<QString>("output");
struct Case {
int flags;
const char *title;
const char * const *values;
const char *scope;
const char *input;
const char *output;
};
static const char *f_foo[] = {"foo", 0};
static const char *f_foo_bar[] = {"foo", "bar", 0};
static const Case cases[] = {
{
PW::AppendValues|PW::AppendOperator|PW::MultiLine,
"add new append multi", f_foo, 0,
"",
"SOURCES += \\\n"
"\tfoo"
},
{
PW::AppendValues|PW::AppendOperator|PW::MultiLine,
"add new append multi after comment", f_foo, 0,
"# test file",
"# test file\n"
"\n"
"SOURCES += \\\n"
"\tfoo"
},
{
PW::AppendValues|PW::AppendOperator|PW::MultiLine,
"add new append multi before newlines", f_foo, 0,
"\n"
"\n"
"\n",
"SOURCES += \\\n"
"\tfoo\n"
"\n"
"\n"
"\n"
},
{
PW::AppendValues|PW::AppendOperator|PW::MultiLine,
"add new append multi after comment before newlines", f_foo, 0,
"# test file\n"
"\n"
"\n"
"\n",
"# test file\n"
"\n"
"SOURCES += \\\n"
"\tfoo\n"
"\n"
"\n"
"\n"
},
{
PW::AppendValues|PW::AssignOperator|PW::MultiLine,
"add new assign multi", f_foo, 0,
"# test file",
"# test file\n"
"\n"
"SOURCES = \\\n"
"\tfoo"
},
{
PW::AppendValues|PW::AppendOperator|PW::OneLine,
"add new append oneline", f_foo, 0,
"# test file",
"# test file\n"
"\n"
"SOURCES += foo"
},
{
PW::AppendValues|PW::AssignOperator|PW::OneLine,
"add new assign oneline", f_foo, 0,
"# test file",
"# test file\n"
"\n"
"SOURCES = foo"
},
{
PW::AppendValues|PW::AssignOperator|PW::OneLine,
"add new assign oneline after existing", f_foo, 0,
"# test file\n"
"\n"
"HEADERS = foo",
"# test file\n"
"\n"
"HEADERS = foo\n"
"\n"
"SOURCES = foo"
},
{
PW::AppendValues|PW::AppendOperator|PW::MultiLine,
"add new ignoring scoped", f_foo, 0,
"unix:SOURCES = some files",
"unix:SOURCES = some files\n"
"\n"
"SOURCES += \\\n"
"\tfoo"
},
{
PW::AppendValues|PW::AppendOperator|PW::MultiLine,
"add new after some scope", f_foo, 0,
"unix {\n"
"\tSOMEVAR = foo\n"
"}",
"unix {\n"
"\tSOMEVAR = foo\n"
"}\n"
"\n"
"SOURCES += \\\n"
"\tfoo"
},
{
PW::AppendValues|PW::AppendOperator|PW::MultiLine,
"add to existing (wrong operator)", f_foo, 0,
"SOURCES = some files",
"SOURCES = some files \\\n"
"\tfoo"
},
{
PW::AppendValues|PW::AppendOperator|PW::MultiLine,
"insert at end", f_foo_bar, 0,
"SOURCES = some files",
"SOURCES = some files \\\n"
"\tbar \\\n"
"\tfoo"
},
{
PW::AppendValues|PW::AppendOperator|PW::MultiLine,
"insert into empty", f_foo_bar, 0,
"SOURCES =",
"SOURCES = \\\n"
"\tbar \\\n"
"\tfoo"
},
{
PW::AppendValues|PW::AppendOperator|PW::MultiLine,
"insert into middle", f_foo_bar, 0,
"SOURCES = some files \\\n"
" aargh \\\n"
" zoo",
"SOURCES = some files \\\n"
" aargh \\\n"
" bar \\\n"
" foo \\\n"
" zoo"
},
{
PW::AppendValues|PW::AppendOperator|PW::MultiLine,
"add to existing after comment (wrong operator)", f_foo, 0,
"SOURCES = some files # comment",
"SOURCES = some files \\ # comment\n"
"\tfoo"
},
{
PW::AppendValues|PW::AppendOperator|PW::MultiLine,
"add to existing after comment line (wrong operator)", f_foo, 0,
"SOURCES = some \\\n"
" # comment\n"
"\tfiles",
"SOURCES = some \\\n"
" # comment\n"
"\tfiles \\\n"
"\tfoo"
},
{
PW::AppendValues|PW::AssignOperator|PW::MultiLine,
"add to existing", f_foo, 0,
"SOURCES = some files",
"SOURCES = some files \\\n"
"\tfoo"
},
{
PW::ReplaceValues|PW::AssignOperator|PW::MultiLine,
"replace existing multi", f_foo_bar, 0,
"SOURCES = some files",
"SOURCES = \\\n"
"\tfoo \\\n"
"\tbar"
},
{
PW::ReplaceValues|PW::AssignOperator|PW::OneLine,
"replace existing oneline", f_foo_bar, 0,
"SOURCES = some files",
"SOURCES = foo bar"
},
{
PW::ReplaceValues|PW::AssignOperator|PW::OneLine,
"replace existing complex last", f_foo_bar, 0,
"SOURCES = some \\\n"
" # comment\n"
"\tfiles",
"SOURCES = foo bar"
},
{
PW::ReplaceValues|PW::AssignOperator|PW::OneLine,
"replace existing complex middle 1", f_foo_bar, 0,
"SOURCES = some \\\n"
" # comment\n"
"\tfiles\n"
"HEADERS = blubb",
"SOURCES = foo bar\n"
"HEADERS = blubb"
},
{
PW::ReplaceValues|PW::AssignOperator|PW::OneLine,
"replace existing complex middle 2", f_foo_bar, 0,
"SOURCES = some \\\n"
" # comment\n"
"\tfiles\n"
"\n"
"HEADERS = blubb",
"SOURCES = foo bar\n"
"\n"
"HEADERS = blubb"
},
{
PW::ReplaceValues|PW::AssignOperator|PW::OneLine,
"replace existing complex middle 3", f_foo_bar, 0,
"SOURCES = some \\\n"
" # comment\n"
"\tfiles \\\n"
"\n"
"HEADERS = blubb",
"SOURCES = foo bar\n"
"\n"
"HEADERS = blubb"
},
{
PW::AppendValues|PW::AppendOperator|PW::OneLine,
"scoped new / new scope", f_foo, "dog",
"# test file\n"
"SOURCES = yo",
"# test file\n"
"SOURCES = yo\n"
"\n"
"dog {\n"
"\tSOURCES += foo\n"
"}"
},
{
PW::AppendValues|PW::AppendOperator|PW::OneLine,
"scoped new / extend scope", f_foo, "dog",
"# test file\n"
"dog {\n"
"\tHEADERS += yo\n"
"}",
"# test file\n"
"dog {\n"
"\tHEADERS += yo\n"
"\n"
"\tSOURCES += foo\n"
"}"
},
{
PW::AppendValues|PW::AppendOperator|PW::OneLine,
"scoped new / extend elongated scope", f_foo, "dog",
"# test file\n"
"dog {\n"
" HEADERS += \\\n"
" yo \\\n"
" blubb\n"
"}",
"# test file\n"
"dog {\n"
" HEADERS += \\\n"
" yo \\\n"
" blubb\n"
"\n"
"\tSOURCES += foo\n"
"}"
},
{
PW::AppendValues|PW::AppendOperator|PW::OneLine,
"scoped new / extend empty scope", f_foo, "dog",
"# test file\n"
"dog {\n"
"}",
"# test file\n"
"dog {\n"
"\tSOURCES += foo\n"
"}"
},
{
PW::AppendValues|PW::AppendOperator|PW::OneLine,
"scoped new / extend oneline scope", f_foo, "dog",
"# test file\n"
"dog:HEADERS += yo",
"# test file\n"
"dog {\n"
"\tHEADERS += yo\n"
"\n"
"\tSOURCES += foo\n"
"}"
},
{
PW::AppendValues|PW::AppendOperator|PW::OneLine,
"scoped new / extend oneline scope with multiline body", f_foo, "dog",
"# test file\n"
"dog:HEADERS += yo \\\n"
" you\n"
"\n"
"blubb()",
"# test file\n"
"dog {\n"
"\tHEADERS += yo \\\n"
" you\n"
"\n"
"\tSOURCES += foo\n"
"}\n"
"\n"
"blubb()"
},
{
PW::AppendValues|PW::AppendOperator|PW::MultiLine,
"add new after some scope inside scope", f_foo, "dog",
"dog {\n"
" unix {\n"
" SOMEVAR = foo\n"
" }\n"
"}",
"dog {\n"
" unix {\n"
" SOMEVAR = foo\n"
" }\n"
"\n"
"\tSOURCES += \\\n"
"\t\tfoo\n"
"}"
},
{
PW::AppendValues|PW::AppendOperator|PW::OneLine,
"scoped new / pseudo-oneline-scope", f_foo, "dog",
"# test file\n"
"dog: {\n"
"}",
"# test file\n"
"dog: {\n"
"\tSOURCES += foo\n"
"}"
},
{
PW::AppendValues|PW::AppendOperator|PW::MultiLine,
"scoped append", f_foo, "dog",
"# test file\n"
"dog:SOURCES = yo",
"# test file\n"
"dog:SOURCES = yo \\\n"
"\t\tfoo"
},
{
PW::AppendValues|PW::AppendOperator|PW::MultiLine,
"complex scoped append", f_foo, "dog",
"# test file\n"
"animal:!dog:SOURCES = yo",
"# test file\n"
"animal:!dog:SOURCES = yo\n"
"\n"
"dog {\n"
"\tSOURCES += \\\n"
"\t\tfoo\n"
"}"
},
};
for (uint i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
const Case *_case = &cases[i];
QTest::newRow(_case->title)
<< _case->flags
<< strList(_case->values)
<< QString::fromLatin1(_case->scope)
<< QString::fromLatin1(_case->input)
<< QString::fromLatin1(_case->output);
}
}
void tst_ProFileWriter::adds()
{
QFETCH(int, flags);
QFETCH(QStringList, values);
QFETCH(QString, scope);
QFETCH(QString, input);
QFETCH(QString, output);
QStringList lines = input.isEmpty() ? QStringList() : input.split(QLatin1String("\n"));
QString var = QLatin1String("SOURCES");
QMakeVfs vfs;
QMakeParser parser(0, &vfs, &parseHandler);
ProFile *proFile = parser.parsedProBlock(QString(), // device
QStringView(input),
0,
QLatin1String(BASE_DIR "/test.pro"),
1);
QVERIFY(proFile);
PW::putVarValues(proFile, &lines, values, var, PW::PutFlags(flags), scope, "\t");
proFile->deref();
QCOMPARE(lines.join(QLatin1Char('\n')), output);
}
void tst_ProFileWriter::removes_data()
{
QTest::addColumn<QStringList>("values");
QTest::addColumn<QString>("input");
QTest::addColumn<QString>("output");
struct Case {
const char *title;
const char * const *values;
const char *input;
const char *output;
};
static const char *f_foo[] = {"foo", 0};
static const char *f_foo_bar[] = {"foo", "bar", 0};
static const Case cases[] = {
{
"remove fail", f_foo,
"SOURCES = bak bar",
"SOURCES = bak bar"
},
{
"remove one-line middle", f_foo,
"SOURCES = bak foo bar",
"SOURCES = bak bar"
},
{
"remove one-line trailing", f_foo,
"SOURCES = bak bar foo",
"SOURCES = bak bar"
},
{
"remove multi-line single leading", f_foo,
"SOURCES = foo \\\n"
" bak \\\n"
" bar",
"SOURCES = \\\n"
" bak \\\n"
" bar"
},
{
"remove multi-line single middle", f_foo,
"SOURCES = bak \\\n"
" foo \\\n"
" bar",
"SOURCES = bak \\\n"
" bar"
},
{
"remove multi-line single trailing", f_foo,
"SOURCES = bak \\\n"
" bar \\\n"
" foo",
"SOURCES = bak \\\n"
" bar"
},
{
"remove multi-line single leading with comment", f_foo,
"SOURCES = foo \\ # comment\n"
" bak \\\n"
" bar",
"SOURCES = \\ # foo # comment\n"
" bak \\\n"
" bar"
},
{
"remove multi-line single middle with comment", f_foo,
"SOURCES = bak \\\n"
" foo \\ # comment\n"
" bar",
"SOURCES = bak \\\n"
" \\ # foo # comment\n"
" bar"
},
{
"remove multi-line single trailing with comment", f_foo,
"SOURCES = bak \\\n"
" bar \\\n"
" foo # comment",
"SOURCES = bak \\\n"
" bar\n"
" # foo # comment"
},
{
"remove multi-line single trailing after empty line", f_foo,
"SOURCES = bak \\\n"
" bar \\\n"
" \\\n"
" foo",
"SOURCES = bak \\\n"
" bar\n"
},
{
"remove multi-line single trailing after comment line", f_foo,
"SOURCES = bak \\\n"
" bar \\\n"
" # just a comment\n"
" foo",
"SOURCES = bak \\\n"
" bar\n"
" # just a comment"
},
{
"remove multi-line single trailing after empty line with comment", f_foo,
"SOURCES = bak \\\n"
" bar \\\n"
" \\ # just a comment\n"
" foo",
"SOURCES = bak \\\n"
" bar\n"
" # just a comment"
},
{
"remove multiple one-line middle", f_foo_bar,
"SOURCES = bak foo bar baz",
"SOURCES = bak baz"
},
{
"remove multiple one-line trailing", f_foo_bar,
"SOURCES = bak baz foo bar",
"SOURCES = bak baz"
},
{
"remove multiple one-line interleaved", f_foo_bar,
"SOURCES = bak foo baz bar",
"SOURCES = bak baz"
},
{
"remove multiple one-line middle with comment", f_foo_bar,
"SOURCES = bak foo bar baz # comment",
"SOURCES = bak baz # bar # foo # comment"
},
{
"remove multi-line multiple trailing with empty line with comment", f_foo_bar,
"SOURCES = bak \\\n"
" bar \\\n"
" \\ # just a comment\n"
" foo",
"SOURCES = bak\n"
" # just a comment"
},
};
for (uint i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
const Case *_case = &cases[i];
QTest::newRow(_case->title)
<< strList(_case->values)
<< QString::fromLatin1(_case->input)
<< QString::fromLatin1(_case->output);
}
}
void tst_ProFileWriter::removes()
{
QFETCH(QStringList, values);
QFETCH(QString, input);
QFETCH(QString, output);
QStringList lines = input.split(QLatin1String("\n"));
QStringList vars; vars << QLatin1String("SOURCES");
QMakeVfs vfs;
QMakeParser parser(0, &vfs, &parseHandler);
ProFile *proFile = parser.parsedProBlock(QString(), // device
QStringView(input),
0,
QLatin1String(BASE_DIR "/test.pro"),
1);
QVERIFY(proFile);
QmakeProjectManager::Internal::ProWriter::removeVarValues(proFile, &lines, values, vars);
proFile->deref();
QCOMPARE(lines.join(QLatin1Char('\n')), output);
}
void tst_ProFileWriter::multiVar()
{
QDir baseDir(BASE_DIR);
QString input = QLatin1String(
"SOURCES = foo bar\n"
"# comment line\n"
"HEADERS = baz bak"
);
QStringList lines = input.split(QLatin1String("\n"));
QString output = QLatin1String(
"SOURCES = bar\n"
"# comment line\n"
"HEADERS = baz"
);
QStringList files; files
<< QString::fromLatin1(BASE_DIR "/foo")
<< QString::fromLatin1(BASE_DIR "/bak");
QStringList vars; vars << QLatin1String("SOURCES") << QLatin1String("HEADERS");
QMakeVfs vfs;
QMakeParser parser(0, &vfs, &parseHandler);
ProFile *proFile = parser.parsedProBlock(QString(), // device
QStringView(input),
0,
QLatin1String(BASE_DIR "/test.pro"),
1);
QVERIFY(proFile);
QmakeProjectManager::Internal::ProWriter::removeFiles(proFile, &lines, baseDir, files, vars);
proFile->deref();
QCOMPARE(lines.join(QLatin1Char('\n')), output);
}
void tst_ProFileWriter::addFiles()
{
const QStringList equivalentInputs = {"SOURCES = foo.cpp", "SOURCES = foo.cpp \\"};
for (const QString &input : equivalentInputs) {
QStringList lines = input.split(QLatin1Char('\n'));
QString output = QLatin1String(
"SOURCES = foo.cpp \\\n"
"\tsub/bar.cpp"
);
QMakeVfs vfs;
QMakeParser parser(0, &vfs, &parseHandler);
ProFile *proFile = parser.parsedProBlock(QString(), // device
QStringView(input),
0,
BASE_DIR "/test.pro",
1);
QVERIFY(proFile);
QmakeProjectManager::Internal::ProWriter::addFiles(proFile, &lines,
QStringList(BASE_DIR "/sub/bar.cpp"),
QLatin1String("SOURCES"), "\t");
proFile->deref();
QCOMPARE(lines.join(QLatin1Char('\n')), output);
}
}
void tst_ProFileWriter::removeFiles()
{
QString input = QLatin1String(
"SOURCES = foo.cpp sub/bar.cpp"
);
QStringList lines = input.split(QLatin1Char('\n'));
QString output = QLatin1String(
"SOURCES = foo.cpp"
);
QMakeVfs vfs;
QMakeParser parser(0, &vfs, &parseHandler);
ProFile *proFile = parser.parsedProBlock(QString(), // device
QStringView(input),
0,
QLatin1String(BASE_DIR "/test.pro"),
1);
QVERIFY(proFile);
QmakeProjectManager::Internal::ProWriter::removeFiles(proFile, &lines, QDir(BASE_DIR),
QStringList() << QString::fromLatin1(BASE_DIR "/sub/bar.cpp"),
QStringList() << QLatin1String("SOURCES") << QLatin1String("HEADERS"));
proFile->deref();
QCOMPARE(lines.join(QLatin1Char('\n')), output);
}
QTEST_GUILESS_MAIN(tst_ProFileWriter)
#include "tst_profilewriter.moc"