Squish: Fix handling of suite.conf

Reading and writing using QSettings was quite a bad idea.
Introduce helper functions to read and write the content
of the suite.conf and ensure correct parsing of the values
for the AUT or the test cases.

Change-Id: Idc8b0935c7eb2603a476c358e3f2ba76d3d29d33
Reviewed-by: David Schulz <david.schulz@qt.io>
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
Christian Stenger
2022-09-28 15:03:46 +02:00
parent 2fa3b9dede
commit 2d8f02fb0e

View File

@@ -9,7 +9,6 @@
#include <utils/qtcassert.h>
#include <QRegularExpression>
#include <QSettings>
namespace Squish {
namespace Internal {
@@ -20,19 +19,120 @@ const char squishAutKey[] = "AUT";
const char objectsMapKey[] = "OBJECTMAP";
const char objectMapStyleKey[] = "OBJECTMAPSTYLE";
// splits an input string into chunks separated by ws, but keeps quoted items without splitting
// them (quotes get removed inside the resulting list)
static QStringList parseHelper(const QStringView input)
{
if (input.isEmpty())
return {};
QStringList result;
QString chunk;
auto appendChunk = [&]() {
if (!chunk.isEmpty())
result.append(chunk);
chunk.clear();
};
bool inQuote = false;
for (const QChar &inChar : input) {
switch (inChar.toLatin1()) {
case '"':
appendChunk();
inQuote = !inQuote;
break;
case ' ':
if (!inQuote) {
appendChunk();
break;
}
Q_FALLTHROUGH();
default:
chunk.append(inChar);
}
}
appendChunk();
return result;
}
static QString quoteIfNeeded(const QString &input)
{
if (input.contains(' '))
return QString('"' + input + '"');
return input;
}
// joins items, separating them by single ws and quoting items if needed
static QString joinItems(const QStringList &items)
{
QStringList result;
for (const QString &current : items)
result.append(quoteIfNeeded(current));
return result.join(' ');
}
static QMap<QString, QString> readSuiteConfContent(const Utils::FilePath &file)
{
if (!file.isReadableFile())
return {};
std::optional<QByteArray> suiteConfContent = file.fileContents();
if (!suiteConfContent)
return {};
QMap<QString, QString> suiteConf;
int invalidCounter = 0;
static const QRegularExpression validLine("^(?<key>[A-Z_]+)=(?<value>.*)$");
for (const QByteArray &line : suiteConfContent->split('\n')) {
const QString utf8Line = QString::fromUtf8(line.trimmed());
if (utf8Line.isEmpty()) // skip empty lines
continue;
const QRegularExpressionMatch match = validLine.match(utf8Line);
if (match.hasMatch())
suiteConf.insert(match.captured("key"), match.captured("value"));
else // save invalid lines
suiteConf.insert(QString::number(++invalidCounter), utf8Line);
}
return suiteConf;
}
static bool writeSuiteConfContent(const Utils::FilePath &file, const QMap<QString, QString> &data)
{
auto isNumber = [](const QString &str) {
return !str.isEmpty() && Utils::allOf(str, &QChar::isDigit);
};
QByteArray outData;
for (auto it = data.begin(), end = data.end(); it != end; ++it) {
if (isNumber(it.key())) // an invalid line we just write out as we got it
outData.append(it.value().toUtf8()).append('\n');
else
outData.append(it.key().toUtf8()).append('=').append(it.value().toUtf8()).append('\n');
}
return file.writeFileContents(outData);
}
bool SuiteConf::read()
{
if (!m_filePath.isReadableFile())
return false;
const QMap<QString, QString> suiteConf = readSuiteConfContent(m_filePath);
const QSettings suiteConf(m_filePath.toString(), QSettings::IniFormat);
// TODO get all information - actually only the information needed now is fetched
m_aut = suiteConf.value(squishAutKey).toString();
// TODO args are listed in config.xml?
setLanguage(suiteConf.value(squishLanguageKey).toString());
m_testcases = suiteConf.value(squishTestCasesKey).toString();
m_objectMap = suiteConf.value(objectsMapKey).toString();
m_objectMapStyle = suiteConf.value(objectMapStyleKey).toString();
const QStringList parsedAUT = parseHelper(suiteConf.value(squishAutKey));
if (parsedAUT.isEmpty()) {
m_aut.clear();
m_arguments.clear();
} else {
m_aut = parsedAUT.first();
if (parsedAUT.size() > 1)
m_arguments = joinItems(parsedAUT.mid(1));
else
m_arguments.clear();
}
setLanguage(suiteConf.value(squishLanguageKey));
m_testcases = suiteConf.value(squishTestCasesKey);
m_objectMap = suiteConf.value(objectsMapKey);
m_objectMapStyle = suiteConf.value(objectMapStyleKey);
return true;
}
@@ -51,15 +151,21 @@ static QString languageEntry(Language language)
bool SuiteConf::write()
{
Core::DocumentManager::expectFileChange(m_filePath);
QSettings suiteConf(m_filePath.toString(), QSettings::IniFormat);
suiteConf.setValue(squishAutKey, m_aut);
suiteConf.setValue(squishLanguageKey, languageEntry(m_language));
suiteConf.setValue(objectsMapKey, m_objectMap);
// we need the original suite.conf content to handle invalid content "correctly"
QMap<QString, QString> suiteConf = readSuiteConfContent(m_filePath);
if (m_arguments.isEmpty())
suiteConf.insert(squishAutKey, quoteIfNeeded(m_aut));
else if (QTC_GUARD(!m_aut.isEmpty()))
suiteConf.insert(squishAutKey, QString(quoteIfNeeded(m_aut) + ' ' + m_arguments));
suiteConf.insert(squishLanguageKey, languageEntry(m_language));
suiteConf.insert(objectsMapKey, m_objectMap);
if (!m_objectMap.isEmpty())
suiteConf.setValue(objectMapStyleKey, m_objectMapStyle);
suiteConf.setValue(squishTestCasesKey, m_testcases);
suiteConf.sync();
return suiteConf.status() == QSettings::NoError;
suiteConf.insert(objectMapStyleKey, m_objectMapStyle);
suiteConf.insert(squishTestCasesKey, m_testcases);
return writeSuiteConfContent(m_filePath, suiteConf);
}
QString SuiteConf::langParameter() const
@@ -90,7 +196,7 @@ QString SuiteConf::scriptExtension() const
QStringList SuiteConf::testCases() const
{
return m_testcases.split(QRegularExpression("\\s+"));
return parseHelper(m_testcases);
}
QStringList SuiteConf::usedTestCases() const
@@ -121,7 +227,7 @@ void SuiteConf::addTestCase(const QString &name)
break;
}
current.insert(insertAt, name);
m_testcases = current.join(' ');
m_testcases = joinItems(current);
}
void SuiteConf::setLanguage(const QString &language)