Utils: Remove AbstractMacroExpander

It was only used by its own tests. Instead move the functionality into
MacroExpander(Private).

The existing unittests are moved into tst_expander.

Change-Id: Ia54f659efa7976b17f47a0084900f98acc834939
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Marcus Tillmanns
2024-10-17 12:33:09 +02:00
parent 8b78b9de1c
commit 67f4a720a8
9 changed files with 522 additions and 613 deletions

View File

@@ -766,7 +766,7 @@ static int quoteArgInternalWin(QString &ret, int bslashes)
* \return false if the string could not be parsed and therefore no safe * \return false if the string could not be parsed and therefore no safe
* substitution was possible * substitution was possible
*/ */
bool ProcessArgs::expandMacros(QString *cmd, AbstractMacroExpander *mx, OsType osType) bool ProcessArgs::expandMacros(QString *cmd, const FindMacro &findMacro, OsType osType)
{ {
QString str = *cmd; QString str = *cmd;
if (str.isEmpty()) if (str.isEmpty())
@@ -775,7 +775,7 @@ bool ProcessArgs::expandMacros(QString *cmd, AbstractMacroExpander *mx, OsType o
QString rsts; QString rsts;
int varLen; int varLen;
int varPos = 0; int varPos = 0;
if (!(varLen = mx->findMacro(str, &varPos, &rsts))) if (!(varLen = findMacro(str, &varPos, &rsts)))
return true; return true;
int pos = 0; int pos = 0;
@@ -839,7 +839,7 @@ bool ProcessArgs::expandMacros(QString *cmd, AbstractMacroExpander *mx, OsType o
str.replace(pos, varLen, rsts); str.replace(pos, varLen, rsts);
pos += rsts.length(); pos += rsts.length();
varPos = pos; varPos = pos;
if (!(varLen = mx->findMacro(str, &varPos, &rsts))) { if (!(varLen = findMacro(str, &varPos, &rsts))) {
// Don't leave immediately, as we may be in CrtNeedWord state which could // Don't leave immediately, as we may be in CrtNeedWord state which could
// be still resolved, or we may have inserted trailing backslashes. // be still resolved, or we may have inserted trailing backslashes.
varPos = INT_MAX; varPos = INT_MAX;
@@ -954,7 +954,7 @@ bool ProcessArgs::expandMacros(QString *cmd, AbstractMacroExpander *mx, OsType o
str.replace(pos, varLen, rsts); str.replace(pos, varLen, rsts);
pos += rsts.length(); pos += rsts.length();
varPos = pos; varPos = pos;
if (!(varLen = mx->findMacro(str, &varPos, &rsts))) if (!(varLen = findMacro(str, &varPos, &rsts)))
break; break;
continue; continue;
} }

View File

@@ -59,9 +59,12 @@ public:
static QStringList splitArgs(const QString &cmd, OsType osType, static QStringList splitArgs(const QString &cmd, OsType osType,
bool abortOnMeta = false, SplitError *err = nullptr, bool abortOnMeta = false, SplitError *err = nullptr,
const Environment *env = nullptr, const QString *pwd = nullptr); const Environment *env = nullptr, const QString *pwd = nullptr);
using FindMacro = std::function<int(const QString &str, int *pos, QString *ret)>;
//! Safely replace the expandos in a shell command //! Safely replace the expandos in a shell command
static bool expandMacros(QString *cmd, AbstractMacroExpander *mx, static bool expandMacros(
OsType osType = HostOsInfo::hostOs()); QString *cmd, const FindMacro &findMacro, OsType osType = HostOsInfo::hostOs());
/*! Iterate over arguments from a command line. /*! Iterate over arguments from a command line.
* Assumes that the name of the actual command is *not* part of the line. * Assumes that the name of the actual command is *not* part of the line.

View File

@@ -14,6 +14,7 @@
#include <QFileInfo> #include <QFileInfo>
#include <QLoggingCategory> #include <QLoggingCategory>
#include <QMap> #include <QMap>
#include <QRegularExpression>
namespace Utils { namespace Utils {
namespace Internal { namespace Internal {
@@ -27,12 +28,112 @@ const char kNativePathPostfix[] = ":NativePath";
const char kFileNamePostfix[] = ":FileName"; const char kFileNamePostfix[] = ":FileName";
const char kFileBaseNamePostfix[] = ":FileBaseName"; const char kFileBaseNamePostfix[] = ":FileBaseName";
class MacroExpanderPrivate : public AbstractMacroExpander class MacroExpanderPrivate
{ {
public: public:
MacroExpanderPrivate() = default; MacroExpanderPrivate() = default;
bool resolveMacro(const QString &name, QString *ret, QSet<AbstractMacroExpander *> &seen) override static bool validateVarName(const QString &varName) { return !varName.startsWith("JS:"); }
bool expandNestedMacros(const QString &str, int *pos, QString *ret)
{
QString varName;
QString pattern, replace;
QString defaultValue;
QString *currArg = &varName;
QChar prev;
QChar c;
QChar replacementChar;
bool replaceAll = false;
int i = *pos;
int strLen = str.length();
varName.reserve(strLen - i);
for (; i < strLen; prev = c) {
c = str.at(i++);
if (c == '\\' && i < strLen) {
c = str.at(i++);
// For the replacement, do not skip the escape sequence when followed by a digit.
// This is needed for enabling convenient capture group replacement,
// like %{var/(.)(.)/\2\1}, without escaping the placeholders.
if (currArg == &replace && c.isDigit())
*currArg += '\\';
*currArg += c;
} else if (c == '}') {
if (varName.isEmpty()) { // replace "%{}" with "%"
*ret = QString('%');
*pos = i;
return true;
}
QSet<MacroExpanderPrivate *> seen;
if (resolveMacro(varName, ret, seen)) {
*pos = i;
if (!pattern.isEmpty() && currArg == &replace) {
const QRegularExpression regexp(pattern);
if (regexp.isValid()) {
if (replaceAll) {
ret->replace(regexp, replace);
} else {
// There isn't an API for replacing once...
const QRegularExpressionMatch match = regexp.match(*ret);
if (match.hasMatch()) {
*ret = ret->left(match.capturedStart(0))
+ match.captured(0).replace(regexp, replace)
+ ret->mid(match.capturedEnd(0));
}
}
}
}
return true;
}
if (!defaultValue.isEmpty()) {
*pos = i;
*ret = defaultValue;
return true;
}
return false;
} else if (c == '{' && prev == '%') {
if (!expandNestedMacros(str, &i, ret))
return false;
varName.chop(1);
varName += *ret;
} else if (currArg == &varName && c == '-' && prev == ':' && validateVarName(varName)) {
varName.chop(1);
currArg = &defaultValue;
} else if (currArg == &varName && (c == '/' || c == '#') && validateVarName(varName)) {
replacementChar = c;
currArg = &pattern;
if (i < strLen && str.at(i) == replacementChar) {
++i;
replaceAll = true;
}
} else if (currArg == &pattern && c == replacementChar) {
currArg = &replace;
} else {
*currArg += c;
}
}
return false;
}
int findMacro(const QString &str, int *pos, QString *ret)
{
forever {
int openPos = str.indexOf("%{", *pos);
if (openPos < 0)
return 0;
int varPos = openPos + 2;
if (expandNestedMacros(str, &varPos, ret)) {
*pos = openPos;
return varPos - openPos;
}
// An actual expansion may be nested into a "false" one,
// so we continue right after the last %{.
*pos = openPos + 2;
}
}
bool resolveMacro(const QString &name, QString *ret, QSet<MacroExpanderPrivate *> &seen)
{ {
// Prevent loops: // Prevent loops:
const int count = seen.count(); const int count = seen.count();
@@ -229,7 +330,7 @@ MacroExpander::~MacroExpander()
*/ */
bool MacroExpander::resolveMacro(const QString &name, QString *ret) const bool MacroExpander::resolveMacro(const QString &name, QString *ret) const
{ {
QSet<AbstractMacroExpander*> seen; QSet<MacroExpanderPrivate *> seen;
return d->resolveMacro(name, ret, seen); return d->resolveMacro(name, ret, seen);
} }
@@ -242,6 +343,16 @@ QString MacroExpander::value(const QByteArray &variable, bool *found) const
return d->value(variable, found); return d->value(variable, found);
} }
static void expandMacros(QString *str, MacroExpanderPrivate *mx)
{
QString rsts;
for (int pos = 0; int len = mx->findMacro(*str, &pos, &rsts);) {
str->replace(pos, len, rsts);
pos += rsts.length();
}
}
/*! /*!
* Returns \a stringWithVariables with all variables replaced by their values. * Returns \a stringWithVariables with all variables replaced by their values.
* See the MacroExpander overview documentation for other ways to expand variables. * See the MacroExpander overview documentation for other ways to expand variables.
@@ -261,7 +372,7 @@ QString MacroExpander::expand(const QString &stringWithVariables) const
++d->m_lockDepth; ++d->m_lockDepth;
QString res = stringWithVariables; QString res = stringWithVariables;
Utils::expandMacros(&res, d); expandMacros(&res, d);
--d->m_lockDepth; --d->m_lockDepth;
@@ -302,10 +413,14 @@ QVariant MacroExpander::expandVariant(const QVariant &v) const
return v; return v;
} }
QString MacroExpander::expandProcessArgs(const QString &argsWithVariables) const QString MacroExpander::expandProcessArgs(const QString &argsWithVariables, Utils::OsType osType) const
{ {
QString result = argsWithVariables; QString result = argsWithVariables;
const bool ok = ProcessArgs::expandMacros(&result, d); const bool ok = ProcessArgs::expandMacros(
&result,
[this](const QString &str, int *pos, QString *ret) { return d->findMacro(str, pos, ret); },
osType);
QTC_ASSERT(ok, qCDebug(expanderLog) << "Expanding failed: " << argsWithVariables); QTC_ASSERT(ok, qCDebug(expanderLog) << "Expanding failed: " << argsWithVariables);
return result; return result;
} }

View File

@@ -5,6 +5,8 @@
#include "utils_global.h" #include "utils_global.h"
#include "hostosinfo.h"
#include <QList> #include <QList>
#include <functional> #include <functional>
@@ -35,7 +37,8 @@ public:
QByteArray expand(const QByteArray &stringWithVariables) const; QByteArray expand(const QByteArray &stringWithVariables) const;
QVariant expandVariant(const QVariant &v) const; QVariant expandVariant(const QVariant &v) const;
QString expandProcessArgs(const QString &argsWithVariables) const; QString expandProcessArgs(
const QString &argsWithVariables, Utils::OsType osType = Utils::HostOsInfo::hostOs()) const;
using PrefixFunction = std::function<QString(QString)>; using PrefixFunction = std::function<QString(QString)>;
using ResolverFunction = std::function<bool(QString, QString *)>; using ResolverFunction = std::function<bool(QString, QString *)>;

View File

@@ -78,126 +78,6 @@ QTCREATOR_UTILS_EXPORT QString commonPrefix(const QStringList &strings)
return strings.at(0).left(commonLength); return strings.at(0).left(commonLength);
} }
static bool validateVarName(const QString &varName)
{
return !varName.startsWith("JS:");
}
bool AbstractMacroExpander::expandNestedMacros(const QString &str, int *pos, QString *ret)
{
QString varName;
QString pattern, replace;
QString defaultValue;
QString *currArg = &varName;
QChar prev;
QChar c;
QChar replacementChar;
bool replaceAll = false;
int i = *pos;
int strLen = str.length();
varName.reserve(strLen - i);
for (; i < strLen; prev = c) {
c = str.at(i++);
if (c == '\\' && i < strLen) {
c = str.at(i++);
// For the replacement, do not skip the escape sequence when followed by a digit.
// This is needed for enabling convenient capture group replacement,
// like %{var/(.)(.)/\2\1}, without escaping the placeholders.
if (currArg == &replace && c.isDigit())
*currArg += '\\';
*currArg += c;
} else if (c == '}') {
if (varName.isEmpty()) { // replace "%{}" with "%"
*ret = QString('%');
*pos = i;
return true;
}
QSet<AbstractMacroExpander*> seen;
if (resolveMacro(varName, ret, seen)) {
*pos = i;
if (!pattern.isEmpty() && currArg == &replace) {
const QRegularExpression regexp(pattern);
if (regexp.isValid()) {
if (replaceAll) {
ret->replace(regexp, replace);
} else {
// There isn't an API for replacing once...
const QRegularExpressionMatch match = regexp.match(*ret);
if (match.hasMatch()) {
*ret = ret->left(match.capturedStart(0))
+ match.captured(0).replace(regexp, replace)
+ ret->mid(match.capturedEnd(0));
}
}
}
}
return true;
}
if (!defaultValue.isEmpty()) {
*pos = i;
*ret = defaultValue;
return true;
}
return false;
} else if (c == '{' && prev == '%') {
if (!expandNestedMacros(str, &i, ret))
return false;
varName.chop(1);
varName += *ret;
} else if (currArg == &varName && c == '-' && prev == ':' && validateVarName(varName)) {
varName.chop(1);
currArg = &defaultValue;
} else if (currArg == &varName && (c == '/' || c == '#') && validateVarName(varName)) {
replacementChar = c;
currArg = &pattern;
if (i < strLen && str.at(i) == replacementChar) {
++i;
replaceAll = true;
}
} else if (currArg == &pattern && c == replacementChar) {
currArg = &replace;
} else {
*currArg += c;
}
}
return false;
}
int AbstractMacroExpander::findMacro(const QString &str, int *pos, QString *ret)
{
forever {
int openPos = str.indexOf("%{", *pos);
if (openPos < 0)
return 0;
int varPos = openPos + 2;
if (expandNestedMacros(str, &varPos, ret)) {
*pos = openPos;
return varPos - openPos;
}
// An actual expansion may be nested into a "false" one,
// so we continue right after the last %{.
*pos = openPos + 2;
}
}
QTCREATOR_UTILS_EXPORT void expandMacros(QString *str, AbstractMacroExpander *mx)
{
QString rsts;
for (int pos = 0; int len = mx->findMacro(*str, &pos, &rsts); ) {
str->replace(pos, len, rsts);
pos += rsts.length();
}
}
QTCREATOR_UTILS_EXPORT QString expandMacros(const QString &str, AbstractMacroExpander *mx)
{
QString ret = str;
expandMacros(&ret, mx);
return ret;
}
QTCREATOR_UTILS_EXPORT QString stripAccelerator(const QString &text) QTCREATOR_UTILS_EXPORT QString stripAccelerator(const QString &text)
{ {
QString res = text; QString res = text;

View File

@@ -41,29 +41,6 @@ QTCREATOR_UTILS_EXPORT bool readMultiLineString(const QJsonValue &value, QString
// Compare case insensitive and use case sensitive comparison in case of that being equal. // Compare case insensitive and use case sensitive comparison in case of that being equal.
QTCREATOR_UTILS_EXPORT int caseFriendlyCompare(const QString &a, const QString &b); QTCREATOR_UTILS_EXPORT int caseFriendlyCompare(const QString &a, const QString &b);
class QTCREATOR_UTILS_EXPORT AbstractMacroExpander
{
public:
virtual ~AbstractMacroExpander() {}
// Not const, as it may change the state of the expander.
//! Find an expando to replace and provide a replacement string.
//! \param str The string to scan
//! \param pos Position to start scan on input, found position on output
//! \param ret Replacement string on output
//! \return Length of string part to replace, zero if no (further) matches found
virtual int findMacro(const QString &str, int *pos, QString *ret);
//! Provide a replacement string for an expando
//! \param name The name of the expando
//! \param ret Replacement string on output
//! \return True if the expando was found
virtual bool resolveMacro(const QString &name, QString *ret, QSet<AbstractMacroExpander *> &seen) = 0;
private:
bool expandNestedMacros(const QString &str, int *pos, QString *ret);
};
QTCREATOR_UTILS_EXPORT void expandMacros(QString *str, AbstractMacroExpander *mx);
QTCREATOR_UTILS_EXPORT QString expandMacros(const QString &str, AbstractMacroExpander *mx);
QTCREATOR_UTILS_EXPORT int parseUsedPortFromNetstatOutput(const QByteArray &line); QTCREATOR_UTILS_EXPORT int parseUsedPortFromNetstatOutput(const QByteArray &line);
QTCREATOR_UTILS_EXPORT QString appendHelper(const QString &base, int n); QTCREATOR_UTILS_EXPORT QString appendHelper(const QString &base, int n);

View File

@@ -173,6 +173,7 @@ private slots:
{ {
QTest::addColumn<QString>("input"); QTest::addColumn<QString>("input");
QTest::addColumn<QString>("expected"); QTest::addColumn<QString>("expected");
QTest::addColumn<OsType>("os");
QTest::addColumn<std::function<void(MacroExpander &)>>("setup"); QTest::addColumn<std::function<void(MacroExpander &)>>("setup");
std::function<void(MacroExpander &)> empty; std::function<void(MacroExpander &)> empty;
@@ -184,57 +185,63 @@ private slots:
expander.registerVariable("WithSpace", "", [] { return "This has spaces"; }); expander.registerVariable("WithSpace", "", [] { return "This has spaces"; });
}; };
QTest::newRow("empty") << QString() << QString() << empty; QTest::newRow("empty") << QString() << QString() << Utils::OsTypeLinux << empty;
QTest::newRow("no expansion") << QString("foo") << QString("foo") << empty; QTest::newRow("no expansion")
<< QString("foo") << QString("foo") << Utils::OsTypeLinux << empty;
QTest::newRow("simple-expansion") QTest::newRow("simple-expansion")
<< QString("cat %{file}") << QString("cat foo.txt") << file; << QString("cat %{file}") << QString("cat foo.txt") << Utils::OsTypeLinux << file;
QTest::newRow("with-ticks") QTest::newRow("with-ticks")
<< QString("echo -n 'foo %{file}'") << QString("echo -n 'foo foo.txt'") << file; << QString("echo -n 'foo %{file}'") << QString("echo -n 'foo foo.txt'")
<< Utils::OsTypeLinux << file;
QTest::newRow("with-ticks-env") << QString("file=%{file} echo -n 'foo \"$file\"'") QTest::newRow("with-ticks-env")
<< QString("file=foo.txt echo -n 'foo \"$file\"'") << file; << QString("file=%{file} echo -n 'foo \"$file\"'")
<< QString("file=foo.txt echo -n 'foo \"$file\"'") << Utils::OsTypeLinux << file;
if (Utils::HostOsInfo::isWindowsHost()) {
QTest::newRow("with-spaces") QTest::newRow("with-spaces")
<< QString("echo %{WithSpace}") << QString("echo \"This has spaces\"") << withspace; << QString("echo %{WithSpace}") << QString("echo 'This has spaces'")
<< Utils::OsTypeLinux << withspace;
QTest::newRow("with-spaces-manual")
<< QString("echo \"Some: %{WithSpace}\"")
<< QString("echo \"Some: This has spaces\"") << withspace;
QTest::newRow("with-spaces-nested")
<< QString("cmd /k \"echo %{WithSpace}\"")
<< QString("cmd /k \"echo This has spaces\"") << withspace;
} else {
QTest::newRow("with-spaces")
<< QString("echo %{WithSpace}") << QString("echo 'This has spaces'") << withspace;
QTest::newRow("with-spaces-pre-quoted") QTest::newRow("with-spaces-pre-quoted")
<< QString("echo 'Some: %{WithSpace}'") << QString("echo 'Some: This has spaces'") << QString("echo 'Some: %{WithSpace}'") << QString("echo 'Some: This has spaces'")
<< withspace; << Utils::OsTypeLinux << withspace;
QTest::newRow("with-spaces-nested") QTest::newRow("with-spaces-nested")
<< QString("sh -c 'echo %{WithSpace}'") << QString("sh -c 'echo This has spaces'") << QString("sh -c 'echo %{WithSpace}'") << QString("sh -c 'echo This has spaces'")
<< withspace; << Utils::OsTypeLinux << withspace;
// Due to security concerns, backslash-escaping an expando is treated as a quoting error // Due to security concerns, backslash-escaping an expando is treated as a quoting error
QTest::newRow("backslash-escaping") QTest::newRow("backslash-escaping")
<< QString("echo \\%{file}") << QString("echo \\%{file}") << file; << QString("echo \\%{file}") << QString("echo \\%{file}") << Utils::OsTypeLinux << file;
QTest::newRow("expando-within-shell-substitution") QTest::newRow("expando-within-shell-substitution")
<< QString("${VAR:-%{file}}") << QString("${VAR:-foo.txt}") << file; << QString("${VAR:-%{file}}") << QString("${VAR:-foo.txt}") << Utils::OsTypeLinux
<< file;
QTest::newRow("expando-within-shell-substitution-with-space") QTest::newRow("expando-within-shell-substitution-with-space")
<< QString("echo \"Some: ${VAR:-%{WithSpace}}\"") << QString("echo \"Some: ${VAR:-%{WithSpace}}\"")
<< QString("echo \"Some: ${VAR:-This has spaces}\"") << withspace; << QString("echo \"Some: ${VAR:-This has spaces}\"") << Utils::OsTypeLinux << withspace;
}
// Windows tests
QTest::newRow("with-spaces")
<< QString("echo %{WithSpace}") << QString("echo \"This has spaces\"")
<< Utils::OsTypeWindows << withspace;
QTest::newRow("with-spaces-manual")
<< QString("echo \"Some: %{WithSpace}\"") << QString("echo \"Some: This has spaces\"")
<< Utils::OsTypeWindows << withspace;
QTest::newRow("with-spaces-nested")
<< QString("cmd /k \"echo %{WithSpace}\"") << QString("cmd /k \"echo This has spaces\"")
<< Utils::OsTypeWindows << withspace;
} }
void expandCommandArgs() void expandCommandArgs()
{ {
QFETCH(QString, input); QFETCH(QString, input);
QFETCH(QString, expected); QFETCH(QString, expected);
QFETCH(OsType, os);
QFETCH(std::function<void(MacroExpander &)>, setup); QFETCH(std::function<void(MacroExpander &)>, setup);
MacroExpander expander; MacroExpander expander;
@@ -242,7 +249,354 @@ private slots:
if (setup) if (setup)
setup(expander); setup(expander);
QCOMPARE(expander.expandProcessArgs(input), expected); QCOMPARE(expander.expandProcessArgs(input, os), expected);
}
void expandProcessArgs_data()
{
QTest::addColumn<QString>("in");
QTest::addColumn<QString>("out");
QTest::addColumn<OsType>("os");
QChar sp(QLatin1Char(' '));
struct Val
{
const char *in;
const char *out;
OsType os;
};
const std::array vals
= {Val{"plain", 0, OsTypeWindows},
Val{"%{a}", "hi", OsTypeWindows},
Val{"%{aa}", "\"hi ho\"", OsTypeWindows},
Val{"%{b}", "h\\i", OsTypeWindows},
Val{"%{c}", "\\hi", OsTypeWindows},
Val{"%{d}", "hi\\", OsTypeWindows},
Val{"%{ba}", "\"h\\i ho\"", OsTypeWindows},
Val{"%{ca}", "\"\\hi ho\"", OsTypeWindows},
Val{"%{da}", "\"hi ho\\\\\"", OsTypeWindows}, // or "\"hi ho\"\\"
Val{"%{e}", "\"h\"\\^\"\"i\"", OsTypeWindows},
Val{"%{f}", "\"\"\\^\"\"hi\"", OsTypeWindows},
Val{"%{g}", "\"hi\"\\^\"\"\"", OsTypeWindows},
Val{"%{h}", "\"h\\\\\"\\^\"\"i\"", OsTypeWindows},
Val{"%{i}", "\"\\\\\"\\^\"\"hi\"", OsTypeWindows},
Val{"%{j}", "\"hi\\\\\"\\^\"\"\"", OsTypeWindows},
Val{"%{k}", "\"&special;\"", OsTypeWindows},
Val{"%{x}", "\\", OsTypeWindows},
Val{"%{y}", "\"\"\\^\"\"\"", OsTypeWindows},
Val{"%{z}", "\"\"", OsTypeWindows},
Val{"^%{z}%{z}", "^%{z}%{z}", OsTypeWindows}, // stupid user check
Val{"quoted", 0, OsTypeWindows},
Val{"\"%{a}\"", "\"hi\"", OsTypeWindows},
Val{"\"%{aa}\"", "\"hi ho\"", OsTypeWindows},
Val{"\"%{b}\"", "\"h\\i\"", OsTypeWindows},
Val{"\"%{c}\"", "\"\\hi\"", OsTypeWindows},
Val{"\"%{d}\"", "\"hi\\\\\"", OsTypeWindows},
Val{"\"%{ba}\"", "\"h\\i ho\"", OsTypeWindows},
Val{"\"%{ca}\"", "\"\\hi ho\"", OsTypeWindows},
Val{"\"%{da}\"", "\"hi ho\\\\\"", OsTypeWindows},
Val{"\"%{e}\"", "\"h\"\\^\"\"i\"", OsTypeWindows},
Val{"\"%{f}\"", "\"\"\\^\"\"hi\"", OsTypeWindows},
Val{"\"%{g}\"", "\"hi\"\\^\"\"\"", OsTypeWindows},
Val{"\"%{h}\"", "\"h\\\\\"\\^\"\"i\"", OsTypeWindows},
Val{"\"%{i}\"", "\"\\\\\"\\^\"\"hi\"", OsTypeWindows},
Val{"\"%{j}\"", "\"hi\\\\\"\\^\"\"\"", OsTypeWindows},
Val{"\"%{k}\"", "\"&special;\"", OsTypeWindows},
Val{"\"%{x}\"", "\"\\\\\"", OsTypeWindows},
Val{"\"%{y}\"", "\"\"\\^\"\"\"", OsTypeWindows},
Val{"\"%{z}\"", "\"\"", OsTypeWindows},
Val{"leading bs", 0, OsTypeWindows},
Val{"\\%{a}", "\\hi", OsTypeWindows},
Val{"\\%{aa}", "\\\\\"hi ho\"", OsTypeWindows},
Val{"\\%{b}", "\\h\\i", OsTypeWindows},
Val{"\\%{c}", "\\\\hi", OsTypeWindows},
Val{"\\%{d}", "\\hi\\", OsTypeWindows},
Val{"\\%{ba}", "\\\\\"h\\i ho\"", OsTypeWindows},
Val{"\\%{ca}", "\\\\\"\\hi ho\"", OsTypeWindows},
Val{"\\%{da}", "\\\\\"hi ho\\\\\"", OsTypeWindows},
Val{"\\%{e}", "\\\\\"h\"\\^\"\"i\"", OsTypeWindows},
Val{"\\%{f}", "\\\\\"\"\\^\"\"hi\"", OsTypeWindows},
Val{"\\%{g}", "\\\\\"hi\"\\^\"\"\"", OsTypeWindows},
Val{"\\%{h}", "\\\\\"h\\\\\"\\^\"\"i\"", OsTypeWindows},
Val{"\\%{i}", "\\\\\"\\\\\"\\^\"\"hi\"", OsTypeWindows},
Val{"\\%{j}", "\\\\\"hi\\\\\"\\^\"\"\"", OsTypeWindows},
Val{"\\%{x}", "\\\\", OsTypeWindows},
Val{"\\%{y}", "\\\\\"\"\\^\"\"\"", OsTypeWindows},
Val{"\\%{z}", "\\", OsTypeWindows},
Val{"trailing bs", 0, OsTypeWindows},
Val{"%{a}\\", "hi\\", OsTypeWindows},
Val{"%{aa}\\", "\"hi ho\"\\", OsTypeWindows},
Val{"%{b}\\", "h\\i\\", OsTypeWindows},
Val{"%{c}\\", "\\hi\\", OsTypeWindows},
Val{"%{d}\\", "hi\\\\", OsTypeWindows},
Val{"%{ba}\\", "\"h\\i ho\"\\", OsTypeWindows},
Val{"%{ca}\\", "\"\\hi ho\"\\", OsTypeWindows},
Val{"%{da}\\", "\"hi ho\\\\\"\\", OsTypeWindows},
Val{"%{e}\\", "\"h\"\\^\"\"i\"\\", OsTypeWindows},
Val{"%{f}\\", "\"\"\\^\"\"hi\"\\", OsTypeWindows},
Val{"%{g}\\", "\"hi\"\\^\"\"\"\\", OsTypeWindows},
Val{"%{h}\\", "\"h\\\\\"\\^\"\"i\"\\", OsTypeWindows},
Val{"%{i}\\", "\"\\\\\"\\^\"\"hi\"\\", OsTypeWindows},
Val{"%{j}\\", "\"hi\\\\\"\\^\"\"\"\\", OsTypeWindows},
Val{"%{x}\\", "\\\\", OsTypeWindows},
Val{"%{y}\\", "\"\"\\^\"\"\"\\", OsTypeWindows},
Val{"%{z}\\", "\\", OsTypeWindows},
Val{"bs-enclosed", 0, OsTypeWindows},
Val{"\\%{a}\\", "\\hi\\", OsTypeWindows},
Val{"\\%{aa}\\", "\\\\\"hi ho\"\\", OsTypeWindows},
Val{"\\%{b}\\", "\\h\\i\\", OsTypeWindows},
Val{"\\%{c}\\", "\\\\hi\\", OsTypeWindows},
Val{"\\%{d}\\", "\\hi\\\\", OsTypeWindows},
Val{"\\%{ba}\\", "\\\\\"h\\i ho\"\\", OsTypeWindows},
Val{"\\%{ca}\\", "\\\\\"\\hi ho\"\\", OsTypeWindows},
Val{"\\%{da}\\", "\\\\\"hi ho\\\\\"\\", OsTypeWindows},
Val{"\\%{e}\\", "\\\\\"h\"\\^\"\"i\"\\", OsTypeWindows},
Val{"\\%{f}\\", "\\\\\"\"\\^\"\"hi\"\\", OsTypeWindows},
Val{"\\%{g}\\", "\\\\\"hi\"\\^\"\"\"\\", OsTypeWindows},
Val{"\\%{h}\\", "\\\\\"h\\\\\"\\^\"\"i\"\\", OsTypeWindows},
Val{"\\%{i}\\", "\\\\\"\\\\\"\\^\"\"hi\"\\", OsTypeWindows},
Val{"\\%{j}\\", "\\\\\"hi\\\\\"\\^\"\"\"\\", OsTypeWindows},
Val{"\\%{x}\\", "\\\\\\", OsTypeWindows},
Val{"\\%{y}\\", "\\\\\"\"\\^\"\"\"\\", OsTypeWindows},
Val{"\\%{z}\\", "\\\\", OsTypeWindows},
Val{"bs-enclosed and trailing literal quote", 0, OsTypeWindows},
Val{"\\%{a}\\\\\\^\"", "\\hi\\\\\\^\"", OsTypeWindows},
Val{"\\%{aa}\\\\\\^\"", "\\\\\"hi ho\"\\\\\\^\"", OsTypeWindows},
Val{"\\%{b}\\\\\\^\"", "\\h\\i\\\\\\^\"", OsTypeWindows},
Val{"\\%{c}\\\\\\^\"", "\\\\hi\\\\\\^\"", OsTypeWindows},
Val{"\\%{d}\\\\\\^\"", "\\hi\\\\\\\\\\^\"", OsTypeWindows},
Val{"\\%{ba}\\\\\\^\"", "\\\\\"h\\i ho\"\\\\\\^\"", OsTypeWindows},
Val{"\\%{ca}\\\\\\^\"", "\\\\\"\\hi ho\"\\\\\\^\"", OsTypeWindows},
Val{"\\%{da}\\\\\\^\"", "\\\\\"hi ho\\\\\"\\\\\\^\"", OsTypeWindows},
Val{"\\%{e}\\\\\\^\"", "\\\\\"h\"\\^\"\"i\"\\\\\\^\"", OsTypeWindows},
Val{"\\%{f}\\\\\\^\"", "\\\\\"\"\\^\"\"hi\"\\\\\\^\"", OsTypeWindows},
Val{"\\%{g}\\\\\\^\"", "\\\\\"hi\"\\^\"\"\"\\\\\\^\"", OsTypeWindows},
Val{"\\%{h}\\\\\\^\"", "\\\\\"h\\\\\"\\^\"\"i\"\\\\\\^\"", OsTypeWindows},
Val{"\\%{i}\\\\\\^\"", "\\\\\"\\\\\"\\^\"\"hi\"\\\\\\^\"", OsTypeWindows},
Val{"\\%{j}\\\\\\^\"", "\\\\\"hi\\\\\"\\^\"\"\"\\\\\\^\"", OsTypeWindows},
Val{"\\%{x}\\\\\\^\"", "\\\\\\\\\\\\\\^\"", OsTypeWindows},
Val{"\\%{y}\\\\\\^\"", "\\\\\"\"\\^\"\"\"\\\\\\^\"", OsTypeWindows},
Val{"\\%{z}\\\\\\^\"", "\\\\\\\\\\^\"", OsTypeWindows},
Val{"bs-enclosed and trailing unclosed quote", 0, OsTypeWindows},
Val{"\\%{a}\\\\\"", "\\hi\\\\\"", OsTypeWindows},
Val{"\\%{aa}\\\\\"", "\\\\\"hi ho\"\\\\\"", OsTypeWindows},
Val{"\\%{b}\\\\\"", "\\h\\i\\\\\"", OsTypeWindows},
Val{"\\%{c}\\\\\"", "\\\\hi\\\\\"", OsTypeWindows},
Val{"\\%{d}\\\\\"", "\\hi\\\\\\\\\"", OsTypeWindows},
Val{"\\%{ba}\\\\\"", "\\\\\"h\\i ho\"\\\\\"", OsTypeWindows},
Val{"\\%{ca}\\\\\"", "\\\\\"\\hi ho\"\\\\\"", OsTypeWindows},
Val{"\\%{da}\\\\\"", "\\\\\"hi ho\\\\\"\\\\\"", OsTypeWindows},
Val{"\\%{e}\\\\\"", "\\\\\"h\"\\^\"\"i\"\\\\\"", OsTypeWindows},
Val{"\\%{f}\\\\\"", "\\\\\"\"\\^\"\"hi\"\\\\\"", OsTypeWindows},
Val{"\\%{g}\\\\\"", "\\\\\"hi\"\\^\"\"\"\\\\\"", OsTypeWindows},
Val{"\\%{h}\\\\\"", "\\\\\"h\\\\\"\\^\"\"i\"\\\\\"", OsTypeWindows},
Val{"\\%{i}\\\\\"", "\\\\\"\\\\\"\\^\"\"hi\"\\\\\"", OsTypeWindows},
Val{"\\%{j}\\\\\"", "\\\\\"hi\\\\\"\\^\"\"\"\\\\\"", OsTypeWindows},
Val{"\\%{x}\\\\\"", "\\\\\\\\\\\\\"", OsTypeWindows},
Val{"\\%{y}\\\\\"", "\\\\\"\"\\^\"\"\"\\\\\"", OsTypeWindows},
Val{"\\%{z}\\\\\"", "\\\\\\\\\"", OsTypeWindows},
Val{"multi-var", 0, OsTypeWindows},
Val{"%{x}%{y}%{z}", "\\\\\"\"\\^\"\"\"", OsTypeWindows},
Val{"%{x}%{z}%{y}%{z}", "\\\\\"\"\\^\"\"\"", OsTypeWindows},
Val{"%{x}%{z}%{y}", "\\\\\"\"\\^\"\"\"", OsTypeWindows},
Val{"%{x}\\^\"%{z}", "\\\\\\^\"", OsTypeWindows},
Val{"%{x}%{z}\\^\"%{z}", "\\\\\\^\"", OsTypeWindows},
Val{"%{x}%{z}\\^\"", "\\\\\\^\"", OsTypeWindows},
Val{"%{x}\\%{z}", "\\\\", OsTypeWindows},
Val{"%{x}%{z}\\%{z}", "\\\\", OsTypeWindows},
Val{"%{x}%{z}\\", "\\\\", OsTypeWindows},
Val{"%{aa}%{a}", "\"hi hohi\"", OsTypeWindows},
Val{"%{aa}%{aa}", "\"hi hohi ho\"", OsTypeWindows},
Val{"%{aa}:%{aa}", "\"hi ho\":\"hi ho\"", OsTypeWindows},
Val{"hallo ^|%{aa}^|", "hallo ^|\"hi ho\"^|", OsTypeWindows},
Val{"quoted multi-var", 0, OsTypeWindows},
Val{"\"%{x}%{y}%{z}\"", "\"\\\\\"\\^\"\"\"", OsTypeWindows},
Val{"\"%{x}%{z}%{y}%{z}\"", "\"\\\\\"\\^\"\"\"", OsTypeWindows},
Val{"\"%{x}%{z}%{y}\"", "\"\\\\\"\\^\"\"\"", OsTypeWindows},
Val{"\"%{x}\"^\"\"%{z}\"", "\"\\\\\"^\"\"\"", OsTypeWindows},
Val{"\"%{x}%{z}\"^\"\"%{z}\"", "\"\\\\\"^\"\"\"", OsTypeWindows},
Val{"\"%{x}%{z}\"^\"\"\"", "\"\\\\\"^\"\"\"", OsTypeWindows},
Val{"\"%{x}\\%{z}\"", "\"\\\\\\\\\"", OsTypeWindows},
Val{"\"%{x}%{z}\\%{z}\"", "\"\\\\\\\\\"", OsTypeWindows},
Val{"\"%{x}%{z}\\\\\"", "\"\\\\\\\\\"", OsTypeWindows},
Val{"\"%{aa}%{a}\"", "\"hi hohi\"", OsTypeWindows},
Val{"\"%{aa}%{aa}\"", "\"hi hohi ho\"", OsTypeWindows},
Val{"\"%{aa}:%{aa}\"", "\"hi ho:hi ho\"", OsTypeWindows},
Val{"plain", 0, OsTypeLinux},
Val{"%{a}", "hi", OsTypeLinux},
Val{"%{b}", "'hi ho'", OsTypeLinux},
Val{"%{c}", "'&special;'", OsTypeLinux},
Val{"%{d}", "'h\\i'", OsTypeLinux},
Val{"%{e}", "'h\"i'", OsTypeLinux},
Val{"%{f}", "'h'\\''i'", OsTypeLinux},
Val{"%{z}", "''", OsTypeLinux},
Val{"\\%{z}%{z}", "\\%{z}%{z}", OsTypeLinux}, // stupid user check
Val{"single-quoted", 0, OsTypeLinux},
Val{"'%{a}'", "'hi'", OsTypeLinux},
Val{"'%{b}'", "'hi ho'", OsTypeLinux},
Val{"'%{c}'", "'&special;'", OsTypeLinux},
Val{"'%{d}'", "'h\\i'", OsTypeLinux},
Val{"'%{e}'", "'h\"i'", OsTypeLinux},
Val{"'%{f}'", "'h'\\''i'", OsTypeLinux},
Val{"'%{z}'", "''", OsTypeLinux},
Val{"double-quoted", 0, OsTypeLinux},
Val{"\"%{a}\"", "\"hi\"", OsTypeLinux},
Val{"\"%{b}\"", "\"hi ho\"", OsTypeLinux},
Val{"\"%{c}\"", "\"&special;\"", OsTypeLinux},
Val{"\"%{d}\"", "\"h\\\\i\"", OsTypeLinux},
Val{"\"%{e}\"", "\"h\\\"i\"", OsTypeLinux},
Val{"\"%{f}\"", "\"h'i\"", OsTypeLinux},
Val{"\"%{z}\"", "\"\"", OsTypeLinux},
Val{"complex", 0, OsTypeLinux},
Val{"echo \"$(echo %{a})\"", "echo \"$(echo hi)\"", OsTypeLinux},
Val{"echo \"$(echo %{b})\"", "echo \"$(echo 'hi ho')\"", OsTypeLinux},
Val{"echo \"$(echo \"%{a}\")\"", "echo \"$(echo \"hi\")\"", OsTypeLinux},
// These make no sense shell-wise, but they test expando nesting
Val{"echo \"%{echo %{a}}\"", "echo \"%{echo hi}\"", OsTypeLinux},
Val{"echo \"%{echo %{b}}\"", "echo \"%{echo hi ho}\"", OsTypeLinux},
Val{"echo \"%{echo \"%{a}\"}\"", "echo \"%{echo \"hi\"}\"", OsTypeLinux}};
const char *title = 0;
for (const auto &val : vals) {
if (!val.out) {
title = val.in;
} else {
QString name
= QString("%1: %2 (%3)")
.arg(title, val.in, val.os == OsTypeWindows ? "windows" : "linux");
QTest::newRow(name.toLatin1())
<< QString::fromLatin1(val.in) << QString::fromLatin1(val.out) << val.os;
QTest::newRow(("padded " + name).toLatin1())
<< QString(sp + QString::fromLatin1(val.in) + sp)
<< QString(sp + QString::fromLatin1(val.out) + sp) << val.os;
}
}
}
void expandProcessArgs()
{
QFETCH(QString, in);
QFETCH(QString, out);
QFETCH(OsType, os);
MacroExpander expander;
if (os == Utils::OsTypeWindows) {
expander.registerVariable("a", "", [] { return "hi"; });
expander.registerVariable("aa", "", [] { return "hi ho"; });
expander.registerVariable("b", "", [] { return "h\\i"; });
expander.registerVariable("c", "", [] { return "\\hi"; });
expander.registerVariable("d", "", [] { return "hi\\"; });
expander.registerVariable("ba", "", [] { return "h\\i ho"; });
expander.registerVariable("ca", "", [] { return "\\hi ho"; });
expander.registerVariable("da", "", [] { return "hi ho\\"; });
expander.registerVariable("e", "", [] { return "h\"i"; });
expander.registerVariable("f", "", [] { return "\"hi"; });
expander.registerVariable("g", "", [] { return "hi\""; });
expander.registerVariable("h", "", [] { return "h\\\"i"; });
expander.registerVariable("i", "", [] { return "\\\"hi"; });
expander.registerVariable("j", "", [] { return "hi\\\""; });
expander.registerVariable("k", "", [] { return "&special;"; });
expander.registerVariable("x", "", [] { return "\\"; });
expander.registerVariable("y", "", [] { return "\""; });
expander.registerVariable("z", "", [] { return ""; });
} else {
expander.registerVariable("a", "", [] { return "hi"; });
expander.registerVariable("b", "", [] { return "hi ho"; });
expander.registerVariable("c", "", [] { return "&special;"; });
expander.registerVariable("d", "", [] { return "h\\i"; });
expander.registerVariable("e", "", [] { return "h\"i"; });
expander.registerVariable("f", "", [] { return "h'i"; });
expander.registerVariable("z", "", [] { return ""; });
}
QCOMPARE(expander.expandProcessArgs(in, os), out);
}
void testMacroExpander_data()
{
QTest::addColumn<QString>("in");
QTest::addColumn<QString>("out");
struct Val
{
const char *in;
const char *out;
};
const std::array vals = {
Val{"text", "text"},
Val{"%{a}", "hi"},
Val{"%%{a}", "%hi"},
Val{"%%%{a}", "%%hi"},
Val{"%{b}", "%{b}"},
Val{"pre%{a}", "prehi"},
Val{"%{a}post", "hipost"},
Val{"pre%{a}post", "prehipost"},
Val{"%{a}%{a}", "hihi"},
Val{"%{a}text%{a}", "hitexthi"},
Val{"%{foo}%{a}text%{a}", "ahitexthi"},
Val{"%{}{a}", "%{a}"},
Val{"%{}", "%"},
Val{"test%{}", "test%"},
Val{"%{}test", "%test"},
Val{"%{abc", "%{abc"},
Val{"%{%{a}", "%{hi"},
Val{"%{%{a}}", "ho"},
Val{"%{%{a}}}post", "ho}post"},
Val{"%{hi%{a}}", "bar"},
Val{"%{hi%{%{foo}}}", "bar"},
Val{"%{hihi/b/c}", "car"},
Val{"%{hihi/a/}", "br"}, // empty replacement
Val{"%{hihi/b}", "bar"}, // incomplete substitution
Val{"%{hihi/./c}", "car"},
Val{"%{hihi//./c}", "ccc"},
Val{"%{hihi/(.)(.)r/\\2\\1c}", "abc"}, // no escape for capture groups
Val{"%{hihi/b/c/d}", "c/dar"},
Val{"%{hihi/a/e{\\}e}", "be{}er"}, // escape closing brace
Val{"%{JS:with \\} inside}", "yay"}, // escape closing brace also in JS:
Val{"%{JS:literal%\\{}", "hurray"},
Val{"%{slash/o\\/b/ol's c}", "fool's car"},
Val{"%{sl\\/sh/(.)(a)(.)/\\2\\1\\3as}", "salsash"}, // escape in variable name
Val{"%{JS:foo/b/c}", "%{JS:foo/b/c}"}, // No replacement for JS (all considered varName)
Val{"%{%{a}%{a}/b/c}", "car"},
Val{"%{nonsense:-sense}", "sense"},
};
for (const auto &val : vals)
QTest::newRow(val.in) << QString::fromLatin1(val.in) << QString::fromLatin1(val.out);
}
void testMacroExpander()
{
QFETCH(QString, in);
QFETCH(QString, out);
MacroExpander expander;
expander.registerVariable("foo", "", [] { return "a"; });
expander.registerVariable("a", "", [] { return "hi"; });
expander.registerVariable("hi", "", [] { return "ho"; });
expander.registerVariable("hihi", "", [] { return "bar"; });
expander.registerVariable("slash", "", [] { return "foo/bar"; });
expander.registerVariable("sl/sh", "", [] { return "slash"; });
expander.registerVariable("JS:foor", "", [] { return "bar"; });
expander.registerVariable("JS:with } inside", "", [] { return "yay"; });
expander.registerVariable("JS:literal%{", "", [] { return "hurray"; });
QCOMPARE(expander.expand(in), out);
} }
}; };

View File

@@ -66,29 +66,6 @@ protected:
int MessageHandler::s_destroyCount = 0; int MessageHandler::s_destroyCount = 0;
QtMessageHandler MessageHandler::s_oldMessageHandler = 0; QtMessageHandler MessageHandler::s_oldMessageHandler = 0;
class MacroMapExpander : public AbstractMacroExpander {
public:
bool resolveMacro(const QString &name, QString *ret, QSet<AbstractMacroExpander*> &seen)
override
{
// loop prevention
const int count = seen.count();
seen.insert(this);
if (seen.count() == count)
return false;
QHash<QString, QString>::const_iterator it = m_map.constFind(name);
if (it != m_map.constEnd()) {
*ret = it.value();
return true;
}
return false;
}
void insert(const QString &key, const QString &value) { m_map.insert(key, value); }
private:
QHash<QString, QString> m_map;
};
static constexpr char s_skipTerminateOnWindows[] = static constexpr char s_skipTerminateOnWindows[] =
"Windows implementation of this test is lacking handling of WM_CLOSE message."; "Windows implementation of this test is lacking handling of WM_CLOSE message.";
@@ -135,8 +112,6 @@ private slots:
void prepareArgs(); void prepareArgs();
void prepareArgsEnv_data(); void prepareArgsEnv_data();
void prepareArgsEnv(); void prepareArgsEnv();
void expandMacros_data();
void expandMacros();
void iterations_data(); void iterations_data();
void iterations(); void iterations();
void iteratorEditsWindows(); void iteratorEditsWindows();
@@ -181,8 +156,6 @@ private:
Environment envWindows; Environment envWindows;
Environment envLinux; Environment envLinux;
MacroMapExpander mxWin;
MacroMapExpander mxUnix;
QString homeStr; QString homeStr;
QString home; QString home;
@@ -206,38 +179,6 @@ void tst_Process::initTestCase()
env << "empty=" << "word=hi" << "words=hi ho" << "spacedwords= hi ho sucker "; env << "empty=" << "word=hi" << "words=hi ho" << "spacedwords= hi ho sucker ";
envWindows = Environment(env, OsTypeWindows); envWindows = Environment(env, OsTypeWindows);
envLinux = Environment(env, OsTypeLinux); envLinux = Environment(env, OsTypeLinux);
mxWin.insert("a", "hi");
mxWin.insert("aa", "hi ho");
mxWin.insert("b", "h\\i");
mxWin.insert("c", "\\hi");
mxWin.insert("d", "hi\\");
mxWin.insert("ba", "h\\i ho");
mxWin.insert("ca", "\\hi ho");
mxWin.insert("da", "hi ho\\");
mxWin.insert("e", "h\"i");
mxWin.insert("f", "\"hi");
mxWin.insert("g", "hi\"");
mxWin.insert("h", "h\\\"i");
mxWin.insert("i", "\\\"hi");
mxWin.insert("j", "hi\\\"");
mxWin.insert("k", "&special;");
mxWin.insert("x", "\\");
mxWin.insert("y", "\"");
mxWin.insert("z", "");
mxUnix.insert("a", "hi");
mxUnix.insert("b", "hi ho");
mxUnix.insert("c", "&special;");
mxUnix.insert("d", "h\\i");
mxUnix.insert("e", "h\"i");
mxUnix.insert("f", "h'i");
mxUnix.insert("z", "");
} }
void tst_Process::cleanupTestCase() void tst_Process::cleanupTestCase()
@@ -550,252 +491,7 @@ void tst_Process::prepareArgsEnv()
QCOMPARE(outstr, out); QCOMPARE(outstr, out);
} }
void tst_Process::expandMacros_data()
{
QTest::addColumn<QString>("in");
QTest::addColumn<QString>("out");
QTest::addColumn<OsType>("os");
QChar sp(QLatin1Char(' '));
static const struct {
const char * const in;
const char * const out;
OsType os;
} vals[] = {
{"plain", 0, OsTypeWindows},
{"%{a}", "hi", OsTypeWindows},
{"%{aa}", "\"hi ho\"", OsTypeWindows},
{"%{b}", "h\\i", OsTypeWindows},
{"%{c}", "\\hi", OsTypeWindows},
{"%{d}", "hi\\", OsTypeWindows},
{"%{ba}", "\"h\\i ho\"", OsTypeWindows},
{"%{ca}", "\"\\hi ho\"", OsTypeWindows},
{"%{da}", "\"hi ho\\\\\"", OsTypeWindows}, // or "\"hi ho\"\\"
{"%{e}", "\"h\"\\^\"\"i\"", OsTypeWindows},
{"%{f}", "\"\"\\^\"\"hi\"", OsTypeWindows},
{"%{g}", "\"hi\"\\^\"\"\"", OsTypeWindows},
{"%{h}", "\"h\\\\\"\\^\"\"i\"", OsTypeWindows},
{"%{i}", "\"\\\\\"\\^\"\"hi\"", OsTypeWindows},
{"%{j}", "\"hi\\\\\"\\^\"\"\"", OsTypeWindows},
{"%{k}", "\"&special;\"", OsTypeWindows},
{"%{x}", "\\", OsTypeWindows},
{"%{y}", "\"\"\\^\"\"\"", OsTypeWindows},
{"%{z}", "\"\"", OsTypeWindows},
{"^%{z}%{z}", "^%{z}%{z}", OsTypeWindows}, // stupid user check
{"quoted", 0, OsTypeWindows},
{"\"%{a}\"", "\"hi\"", OsTypeWindows},
{"\"%{aa}\"", "\"hi ho\"", OsTypeWindows},
{"\"%{b}\"", "\"h\\i\"", OsTypeWindows},
{"\"%{c}\"", "\"\\hi\"", OsTypeWindows},
{"\"%{d}\"", "\"hi\\\\\"", OsTypeWindows},
{"\"%{ba}\"", "\"h\\i ho\"", OsTypeWindows},
{"\"%{ca}\"", "\"\\hi ho\"", OsTypeWindows},
{"\"%{da}\"", "\"hi ho\\\\\"", OsTypeWindows},
{"\"%{e}\"", "\"h\"\\^\"\"i\"", OsTypeWindows},
{"\"%{f}\"", "\"\"\\^\"\"hi\"", OsTypeWindows},
{"\"%{g}\"", "\"hi\"\\^\"\"\"", OsTypeWindows},
{"\"%{h}\"", "\"h\\\\\"\\^\"\"i\"", OsTypeWindows},
{"\"%{i}\"", "\"\\\\\"\\^\"\"hi\"", OsTypeWindows},
{"\"%{j}\"", "\"hi\\\\\"\\^\"\"\"", OsTypeWindows},
{"\"%{k}\"", "\"&special;\"", OsTypeWindows},
{"\"%{x}\"", "\"\\\\\"", OsTypeWindows},
{"\"%{y}\"", "\"\"\\^\"\"\"", OsTypeWindows},
{"\"%{z}\"", "\"\"", OsTypeWindows},
{"leading bs", 0, OsTypeWindows},
{"\\%{a}", "\\hi", OsTypeWindows},
{"\\%{aa}", "\\\\\"hi ho\"", OsTypeWindows},
{"\\%{b}", "\\h\\i", OsTypeWindows},
{"\\%{c}", "\\\\hi", OsTypeWindows},
{"\\%{d}", "\\hi\\", OsTypeWindows},
{"\\%{ba}", "\\\\\"h\\i ho\"", OsTypeWindows},
{"\\%{ca}", "\\\\\"\\hi ho\"", OsTypeWindows},
{"\\%{da}", "\\\\\"hi ho\\\\\"", OsTypeWindows},
{"\\%{e}", "\\\\\"h\"\\^\"\"i\"", OsTypeWindows},
{"\\%{f}", "\\\\\"\"\\^\"\"hi\"", OsTypeWindows},
{"\\%{g}", "\\\\\"hi\"\\^\"\"\"", OsTypeWindows},
{"\\%{h}", "\\\\\"h\\\\\"\\^\"\"i\"", OsTypeWindows},
{"\\%{i}", "\\\\\"\\\\\"\\^\"\"hi\"", OsTypeWindows},
{"\\%{j}", "\\\\\"hi\\\\\"\\^\"\"\"", OsTypeWindows},
{"\\%{x}", "\\\\", OsTypeWindows},
{"\\%{y}", "\\\\\"\"\\^\"\"\"", OsTypeWindows},
{"\\%{z}", "\\", OsTypeWindows},
{"trailing bs", 0, OsTypeWindows},
{"%{a}\\", "hi\\", OsTypeWindows},
{"%{aa}\\", "\"hi ho\"\\", OsTypeWindows},
{"%{b}\\", "h\\i\\", OsTypeWindows},
{"%{c}\\", "\\hi\\", OsTypeWindows},
{"%{d}\\", "hi\\\\", OsTypeWindows},
{"%{ba}\\", "\"h\\i ho\"\\", OsTypeWindows},
{"%{ca}\\", "\"\\hi ho\"\\", OsTypeWindows},
{"%{da}\\", "\"hi ho\\\\\"\\", OsTypeWindows},
{"%{e}\\", "\"h\"\\^\"\"i\"\\", OsTypeWindows},
{"%{f}\\", "\"\"\\^\"\"hi\"\\", OsTypeWindows},
{"%{g}\\", "\"hi\"\\^\"\"\"\\", OsTypeWindows},
{"%{h}\\", "\"h\\\\\"\\^\"\"i\"\\", OsTypeWindows},
{"%{i}\\", "\"\\\\\"\\^\"\"hi\"\\", OsTypeWindows},
{"%{j}\\", "\"hi\\\\\"\\^\"\"\"\\", OsTypeWindows},
{"%{x}\\", "\\\\", OsTypeWindows},
{"%{y}\\", "\"\"\\^\"\"\"\\", OsTypeWindows},
{"%{z}\\", "\\", OsTypeWindows},
{"bs-enclosed", 0, OsTypeWindows},
{"\\%{a}\\", "\\hi\\", OsTypeWindows},
{"\\%{aa}\\", "\\\\\"hi ho\"\\", OsTypeWindows},
{"\\%{b}\\", "\\h\\i\\", OsTypeWindows},
{"\\%{c}\\", "\\\\hi\\", OsTypeWindows},
{"\\%{d}\\", "\\hi\\\\", OsTypeWindows},
{"\\%{ba}\\", "\\\\\"h\\i ho\"\\", OsTypeWindows},
{"\\%{ca}\\", "\\\\\"\\hi ho\"\\", OsTypeWindows},
{"\\%{da}\\", "\\\\\"hi ho\\\\\"\\", OsTypeWindows},
{"\\%{e}\\", "\\\\\"h\"\\^\"\"i\"\\", OsTypeWindows},
{"\\%{f}\\", "\\\\\"\"\\^\"\"hi\"\\", OsTypeWindows},
{"\\%{g}\\", "\\\\\"hi\"\\^\"\"\"\\", OsTypeWindows},
{"\\%{h}\\", "\\\\\"h\\\\\"\\^\"\"i\"\\", OsTypeWindows},
{"\\%{i}\\", "\\\\\"\\\\\"\\^\"\"hi\"\\", OsTypeWindows},
{"\\%{j}\\", "\\\\\"hi\\\\\"\\^\"\"\"\\", OsTypeWindows},
{"\\%{x}\\", "\\\\\\", OsTypeWindows},
{"\\%{y}\\", "\\\\\"\"\\^\"\"\"\\", OsTypeWindows},
{"\\%{z}\\", "\\\\", OsTypeWindows},
{"bs-enclosed and trailing literal quote", 0, OsTypeWindows},
{"\\%{a}\\\\\\^\"", "\\hi\\\\\\^\"", OsTypeWindows},
{"\\%{aa}\\\\\\^\"", "\\\\\"hi ho\"\\\\\\^\"", OsTypeWindows},
{"\\%{b}\\\\\\^\"", "\\h\\i\\\\\\^\"", OsTypeWindows},
{"\\%{c}\\\\\\^\"", "\\\\hi\\\\\\^\"", OsTypeWindows},
{"\\%{d}\\\\\\^\"", "\\hi\\\\\\\\\\^\"", OsTypeWindows},
{"\\%{ba}\\\\\\^\"", "\\\\\"h\\i ho\"\\\\\\^\"", OsTypeWindows},
{"\\%{ca}\\\\\\^\"", "\\\\\"\\hi ho\"\\\\\\^\"", OsTypeWindows},
{"\\%{da}\\\\\\^\"", "\\\\\"hi ho\\\\\"\\\\\\^\"", OsTypeWindows},
{"\\%{e}\\\\\\^\"", "\\\\\"h\"\\^\"\"i\"\\\\\\^\"", OsTypeWindows},
{"\\%{f}\\\\\\^\"", "\\\\\"\"\\^\"\"hi\"\\\\\\^\"", OsTypeWindows},
{"\\%{g}\\\\\\^\"", "\\\\\"hi\"\\^\"\"\"\\\\\\^\"", OsTypeWindows},
{"\\%{h}\\\\\\^\"", "\\\\\"h\\\\\"\\^\"\"i\"\\\\\\^\"", OsTypeWindows},
{"\\%{i}\\\\\\^\"", "\\\\\"\\\\\"\\^\"\"hi\"\\\\\\^\"", OsTypeWindows},
{"\\%{j}\\\\\\^\"", "\\\\\"hi\\\\\"\\^\"\"\"\\\\\\^\"", OsTypeWindows},
{"\\%{x}\\\\\\^\"", "\\\\\\\\\\\\\\^\"", OsTypeWindows},
{"\\%{y}\\\\\\^\"", "\\\\\"\"\\^\"\"\"\\\\\\^\"", OsTypeWindows},
{"\\%{z}\\\\\\^\"", "\\\\\\\\\\^\"", OsTypeWindows},
{"bs-enclosed and trailing unclosed quote", 0, OsTypeWindows},
{"\\%{a}\\\\\"", "\\hi\\\\\"", OsTypeWindows},
{"\\%{aa}\\\\\"", "\\\\\"hi ho\"\\\\\"", OsTypeWindows},
{"\\%{b}\\\\\"", "\\h\\i\\\\\"", OsTypeWindows},
{"\\%{c}\\\\\"", "\\\\hi\\\\\"", OsTypeWindows},
{"\\%{d}\\\\\"", "\\hi\\\\\\\\\"", OsTypeWindows},
{"\\%{ba}\\\\\"", "\\\\\"h\\i ho\"\\\\\"", OsTypeWindows},
{"\\%{ca}\\\\\"", "\\\\\"\\hi ho\"\\\\\"", OsTypeWindows},
{"\\%{da}\\\\\"", "\\\\\"hi ho\\\\\"\\\\\"", OsTypeWindows},
{"\\%{e}\\\\\"", "\\\\\"h\"\\^\"\"i\"\\\\\"", OsTypeWindows},
{"\\%{f}\\\\\"", "\\\\\"\"\\^\"\"hi\"\\\\\"", OsTypeWindows},
{"\\%{g}\\\\\"", "\\\\\"hi\"\\^\"\"\"\\\\\"", OsTypeWindows},
{"\\%{h}\\\\\"", "\\\\\"h\\\\\"\\^\"\"i\"\\\\\"", OsTypeWindows},
{"\\%{i}\\\\\"", "\\\\\"\\\\\"\\^\"\"hi\"\\\\\"", OsTypeWindows},
{"\\%{j}\\\\\"", "\\\\\"hi\\\\\"\\^\"\"\"\\\\\"", OsTypeWindows},
{"\\%{x}\\\\\"", "\\\\\\\\\\\\\"", OsTypeWindows},
{"\\%{y}\\\\\"", "\\\\\"\"\\^\"\"\"\\\\\"", OsTypeWindows},
{"\\%{z}\\\\\"", "\\\\\\\\\"", OsTypeWindows},
{"multi-var", 0, OsTypeWindows},
{"%{x}%{y}%{z}", "\\\\\"\"\\^\"\"\"", OsTypeWindows},
{"%{x}%{z}%{y}%{z}", "\\\\\"\"\\^\"\"\"", OsTypeWindows},
{"%{x}%{z}%{y}", "\\\\\"\"\\^\"\"\"", OsTypeWindows},
{"%{x}\\^\"%{z}", "\\\\\\^\"", OsTypeWindows},
{"%{x}%{z}\\^\"%{z}", "\\\\\\^\"", OsTypeWindows},
{"%{x}%{z}\\^\"", "\\\\\\^\"", OsTypeWindows},
{"%{x}\\%{z}", "\\\\", OsTypeWindows},
{"%{x}%{z}\\%{z}", "\\\\", OsTypeWindows},
{"%{x}%{z}\\", "\\\\", OsTypeWindows},
{"%{aa}%{a}", "\"hi hohi\"", OsTypeWindows},
{"%{aa}%{aa}", "\"hi hohi ho\"", OsTypeWindows},
{"%{aa}:%{aa}", "\"hi ho\":\"hi ho\"", OsTypeWindows},
{"hallo ^|%{aa}^|", "hallo ^|\"hi ho\"^|", OsTypeWindows},
{"quoted multi-var", 0, OsTypeWindows},
{"\"%{x}%{y}%{z}\"", "\"\\\\\"\\^\"\"\"", OsTypeWindows},
{"\"%{x}%{z}%{y}%{z}\"", "\"\\\\\"\\^\"\"\"", OsTypeWindows},
{"\"%{x}%{z}%{y}\"", "\"\\\\\"\\^\"\"\"", OsTypeWindows},
{"\"%{x}\"^\"\"%{z}\"", "\"\\\\\"^\"\"\"", OsTypeWindows},
{"\"%{x}%{z}\"^\"\"%{z}\"", "\"\\\\\"^\"\"\"", OsTypeWindows},
{"\"%{x}%{z}\"^\"\"\"", "\"\\\\\"^\"\"\"", OsTypeWindows},
{"\"%{x}\\%{z}\"", "\"\\\\\\\\\"", OsTypeWindows},
{"\"%{x}%{z}\\%{z}\"", "\"\\\\\\\\\"", OsTypeWindows},
{"\"%{x}%{z}\\\\\"", "\"\\\\\\\\\"", OsTypeWindows},
{"\"%{aa}%{a}\"", "\"hi hohi\"", OsTypeWindows},
{"\"%{aa}%{aa}\"", "\"hi hohi ho\"", OsTypeWindows},
{"\"%{aa}:%{aa}\"", "\"hi ho:hi ho\"", OsTypeWindows},
{"plain", 0, OsTypeLinux},
{"%{a}", "hi", OsTypeLinux},
{"%{b}", "'hi ho'", OsTypeLinux},
{"%{c}", "'&special;'", OsTypeLinux},
{"%{d}", "'h\\i'", OsTypeLinux},
{"%{e}", "'h\"i'", OsTypeLinux},
{"%{f}", "'h'\\''i'", OsTypeLinux},
{"%{z}", "''", OsTypeLinux},
{"\\%{z}%{z}", "\\%{z}%{z}", OsTypeLinux}, // stupid user check
{"single-quoted", 0, OsTypeLinux},
{"'%{a}'", "'hi'", OsTypeLinux},
{"'%{b}'", "'hi ho'", OsTypeLinux},
{"'%{c}'", "'&special;'", OsTypeLinux},
{"'%{d}'", "'h\\i'", OsTypeLinux},
{"'%{e}'", "'h\"i'", OsTypeLinux},
{"'%{f}'", "'h'\\''i'", OsTypeLinux},
{"'%{z}'", "''", OsTypeLinux},
{"double-quoted", 0, OsTypeLinux},
{"\"%{a}\"", "\"hi\"", OsTypeLinux},
{"\"%{b}\"", "\"hi ho\"", OsTypeLinux},
{"\"%{c}\"", "\"&special;\"", OsTypeLinux},
{"\"%{d}\"", "\"h\\\\i\"", OsTypeLinux},
{"\"%{e}\"", "\"h\\\"i\"", OsTypeLinux},
{"\"%{f}\"", "\"h'i\"", OsTypeLinux},
{"\"%{z}\"", "\"\"", OsTypeLinux},
{"complex", 0, OsTypeLinux},
{"echo \"$(echo %{a})\"", "echo \"$(echo hi)\"", OsTypeLinux},
{"echo \"$(echo %{b})\"", "echo \"$(echo 'hi ho')\"", OsTypeLinux},
{"echo \"$(echo \"%{a}\")\"", "echo \"$(echo \"hi\")\"", OsTypeLinux},
// These make no sense shell-wise, but they test expando nesting
{"echo \"%{echo %{a}}\"", "echo \"%{echo hi}\"", OsTypeLinux},
{"echo \"%{echo %{b}}\"", "echo \"%{echo hi ho}\"", OsTypeLinux},
{"echo \"%{echo \"%{a}\"}\"", "echo \"%{echo \"hi\"}\"", OsTypeLinux },
};
const char *title = 0;
for (unsigned i = 0; i < sizeof(vals)/sizeof(vals[0]); i++) {
if (!vals[i].out) {
title = vals[i].in;
} else {
char buf[80];
snprintf(buf, 80, "%s: %s", title, vals[i].in);
QTest::newRow(buf) << QString::fromLatin1(vals[i].in)
<< QString::fromLatin1(vals[i].out)
<< vals[i].os;
snprintf(buf, 80, "padded %s: %s", title, vals[i].in);
QTest::newRow(buf) << QString(sp + QString::fromLatin1(vals[i].in) + sp)
<< QString(sp + QString::fromLatin1(vals[i].out) + sp)
<< vals[i].os;
}
}
}
void tst_Process::expandMacros()
{
QFETCH(QString, in);
QFETCH(QString, out);
QFETCH(OsType, os);
if (os == OsTypeWindows)
ProcessArgs::expandMacros(&in, &mxWin, os);
else
ProcessArgs::expandMacros(&in, &mxUnix, os);
QCOMPARE(in, out);
}
void tst_Process::iterations_data() void tst_Process::iterations_data()
{ {

View File

@@ -10,66 +10,12 @@
using namespace Utils; using namespace Utils;
class TestMacroExpander : public Utils::AbstractMacroExpander
{
public:
bool resolveMacro(const QString &name, QString *ret, QSet<AbstractMacroExpander*> &seen)
override
{
// loop prevention
const int count = seen.count();
seen.insert(this);
if (seen.count() == count)
return false;
if (name == QLatin1String("foo")) {
*ret = QLatin1String("a");
return true;
}
if (name == QLatin1String("a")) {
*ret = QLatin1String("hi");
return true;
}
if (name == QLatin1String("hi")) {
*ret = QLatin1String("ho");
return true;
}
if (name == QLatin1String("hihi")) {
*ret = QLatin1String("bar");
return true;
}
if (name == "slash") {
*ret = "foo/bar";
return true;
}
if (name == "sl/sh") {
*ret = "slash";
return true;
}
if (name == "JS:foo") {
*ret = "bar";
return true;
}
if (name == "JS:with } inside") {
*ret = "yay";
return true;
}
if (name == "JS:literal%{") {
*ret = "hurray";
return true;
}
return false;
}
};
class tst_StringUtils : public QObject class tst_StringUtils : public QObject
{ {
Q_OBJECT Q_OBJECT
private slots: private slots:
void testWithTildeHomePath(); void testWithTildeHomePath();
void testMacroExpander_data();
void testMacroExpander();
void testStripAccelerator_data(); void testStripAccelerator_data();
void testStripAccelerator(); void testStripAccelerator();
void testParseUsedPortFromNetstatOutput_data(); void testParseUsedPortFromNetstatOutput_data();
@@ -84,9 +30,6 @@ private slots:
void testSplitAtFirst(); void testSplitAtFirst();
void testAsciify_data(); void testAsciify_data();
void testAsciify(); void testAsciify();
private:
TestMacroExpander mx;
}; };
void tst_StringUtils::testWithTildeHomePath() void tst_StringUtils::testWithTildeHomePath()
@@ -118,68 +61,6 @@ void tst_StringUtils::testWithTildeHomePath()
#endif #endif
} }
void tst_StringUtils::testMacroExpander_data()
{
QTest::addColumn<QString>("in");
QTest::addColumn<QString>("out");
static const struct {
const char * const in;
const char * const out;
} vals[] = {
{"text", "text"},
{"%{a}", "hi"},
{"%%{a}", "%hi"},
{"%%%{a}", "%%hi"},
{"%{b}", "%{b}"},
{"pre%{a}", "prehi"},
{"%{a}post", "hipost"},
{"pre%{a}post", "prehipost"},
{"%{a}%{a}", "hihi"},
{"%{a}text%{a}", "hitexthi"},
{"%{foo}%{a}text%{a}", "ahitexthi"},
{"%{}{a}", "%{a}"},
{"%{}", "%"},
{"test%{}", "test%"},
{"%{}test", "%test"},
{"%{abc", "%{abc"},
{"%{%{a}", "%{hi"},
{"%{%{a}}", "ho"},
{"%{%{a}}}post", "ho}post"},
{"%{hi%{a}}", "bar"},
{"%{hi%{%{foo}}}", "bar"},
{"%{hihi/b/c}", "car"},
{"%{hihi/a/}", "br"}, // empty replacement
{"%{hihi/b}", "bar"}, // incomplete substitution
{"%{hihi/./c}", "car"},
{"%{hihi//./c}", "ccc"},
{"%{hihi/(.)(.)r/\\2\\1c}", "abc"}, // no escape for capture groups
{"%{hihi/b/c/d}", "c/dar"},
{"%{hihi/a/e{\\}e}", "be{}er"}, // escape closing brace
{"%{JS:with \\} inside}", "yay"}, // escape closing brace also in JS:
{"%{JS:literal%\\{}", "hurray"},
{"%{slash/o\\/b/ol's c}", "fool's car"},
{"%{sl\\/sh/(.)(a)(.)/\\2\\1\\3as}", "salsash"}, // escape in variable name
{"%{JS:foo/b/c}", "%{JS:foo/b/c}"}, // No replacement for JS (all considered varName)
{"%{%{a}%{a}/b/c}", "car"},
{"%{nonsense:-sense}", "sense"},
};
for (unsigned i = 0; i < sizeof(vals)/sizeof(vals[0]); i++)
QTest::newRow(vals[i].in) << QString::fromLatin1(vals[i].in)
<< QString::fromLatin1(vals[i].out);
}
void tst_StringUtils::testMacroExpander()
{
QFETCH(QString, in);
QFETCH(QString, out);
Utils::expandMacros(&in, &mx);
QCOMPARE(in, out);
}
void tst_StringUtils::testStripAccelerator_data() void tst_StringUtils::testStripAccelerator_data()
{ {
QTest::addColumn<QString>("expected"); QTest::addColumn<QString>("expected");