diff --git a/src/parsers/perf/perfparser.cpp b/src/parsers/perf/perfparser.cpp index f5efdd569..f34c2afd1 100644 --- a/src/parsers/perf/perfparser.cpp +++ b/src/parsers/perf/perfparser.cpp @@ -1475,6 +1475,7 @@ PerfParser::~PerfParser() = default; bool PerfParser::initParserArgs(const QString& path) { + // check for common file issues const auto info = QFileInfo(path); if (!info.exists()) { emit parsingFailed(tr("File '%1' does not exist.").arg(path)); @@ -1489,6 +1490,18 @@ bool PerfParser::initParserArgs(const QString& path) return false; } + // peek into file header + QFile file(path); + if (file.peek(8) != "PERFILE2" && file.peek(11) != "QPERFSTREAM") { + if (file.peek(8) == "PERFFILE") { + emit parsingFailed(tr("Failed to parse file %1: %2").arg(path, tr("Unsupported V1 perf data"))); + } else { + emit parsingFailed(tr("Failed to parse file %1: %2").arg(path, tr("File format unknown"))); + } + return false; + } + + // check perfparser and set initial values auto parserBinary = Util::perfParserBinaryPath(); if (parserBinary.isEmpty()) { emit parsingFailed(tr("Failed to find hotspot-perfparser binary.")); @@ -1583,16 +1596,15 @@ void PerfParser::startParseFile(const QString& path) emit parsingFinished(); }; - if (path.endsWith(QLatin1String(".perfparser"))) { - QFile file(path); - if (!file.open(QIODevice::ReadOnly)) { - emit parsingFailed(tr("Failed to open file %1: %2").arg(path, file.errorString())); - return; - } + // note: file is always readable and in supported format here, + // already validated in initParserArgs() + QFile file(path); + if (file.peek(11) == "QPERFSTREAM") { d.setInput(&file); while (!file.atEnd() && !d.stopRequested) { if (!d.tryParse()) { - emit parsingFailed(tr("Failed to parse file")); + // TODO: provide reason + emit parsingFailed(tr("Failed to parse file %1: %2").arg(path, QStringLiteral("Unknown reason"))); return; } } diff --git a/tests/integrationtests/tst_perfparser.cpp b/tests/integrationtests/tst_perfparser.cpp index 184bff7ed..ab7734e23 100644 --- a/tests/integrationtests/tst_perfparser.cpp +++ b/tests/integrationtests/tst_perfparser.cpp @@ -172,6 +172,86 @@ private slots: m_capabilities = host.perfCapabilities(); } + void testFileErrorHandling() + { + PerfParser parser(this); + QSignalSpy parsingFailedSpy(&parser, &PerfParser::parsingFailed); + QString message; + + const auto notThereFile = QLatin1String("not_here"); + parser.initParserArgs(notThereFile); + + COMPARE_OR_THROW(parsingFailedSpy.count(), 1); + message = qvariant_cast(parsingFailedSpy.takeFirst().at(0)); + // qDebug("Error message is '%s'", qPrintable(message)); + QVERIFY(message.contains(notThereFile)); + QVERIFY(message.contains(QLatin1String("does not exist"))); + + const auto parentDirs = QLatin1String("../.."); + parser.initParserArgs(parentDirs); + // note: initializing parser args reset the attached spy counter + COMPARE_OR_THROW(parsingFailedSpy.count(), 1); + message = qvariant_cast(parsingFailedSpy.takeFirst().at(0)); + QVERIFY(message.contains(parentDirs)); + QVERIFY(message.contains(QLatin1String("is not a file"))); + + const auto noRead = QLatin1String("no_r_possible"); + const auto shell = QStandardPaths::findExecutable(QStringLiteral("bash")); + if (!shell.isEmpty()) { + QProcess::execute( + shell, {QLatin1String("-c"), QStringLiteral("rm -rf %1 && touch %1 && chmod a-r %1").arg(noRead)}); + parser.initParserArgs(noRead); + + COMPARE_OR_THROW(parsingFailedSpy.count(), 1); + message = qvariant_cast(parsingFailedSpy.takeFirst().at(0)); + QVERIFY(message.contains(noRead)); + QVERIFY(message.contains(QLatin1String("not readable"))); + } + } + + void testFileContent() + { + PerfParser parser(this); + QSignalSpy parsingFailedSpy(&parser, &PerfParser::parsingFailed); + QSignalSpy parsingFinishedSpy(&parser, &PerfParser::parsingFinished); + QString message; + + const auto perfData = QFINDTESTDATA("custom_cost_aggregation_testfiles/custom_cost_aggregation.perfparser"); + QVERIFY(!perfData.isEmpty() && QFile::exists(perfData)); + + parser.startParseFile(perfData); + + VERIFY_OR_THROW(parsingFinishedSpy.wait(6000)); + + // Verify that the test passed + COMPARE_OR_THROW(parsingFailedSpy.count(), 0); + COMPARE_OR_THROW(parsingFinishedSpy.count(), 1); + + const auto shell = QStandardPaths::findExecutable(QStringLiteral("bash")); + if (!shell.isEmpty()) { + const auto perfDataSomeName = QLatin1String("fruitper"); + QProcess::execute(shell, + {QLatin1String("-c"), QStringLiteral("cp -f %1 %2").arg(perfData, perfDataSomeName)}); + + parser.startParseFile(perfDataSomeName); + + VERIFY_OR_THROW(parsingFinishedSpy.wait(6000)); + + // Verify that the test passed + COMPARE_OR_THROW(parsingFailedSpy.count(), 0); + COMPARE_OR_THROW(parsingFinishedSpy.count(), 2); + } + + // TODO: add checks for PERFv1 and PERFv2 + // const auto parentDirs = + // QLatin1String("custom_cost_aggregation_testfiles/custom_cost_aggregation.perfparser"); + // parser.startParseFile(parentDirs); + // COMPARE_OR_THROW(parsingFailedSpy.count(), 1); + // message = qvariant_cast(parsingFailedSpy.takeFirst().at(0)); + // QVERIFY(message.contains(parentDirs)); + // QVERIFY(message.contains(QLatin1String("is not a file"))); + } + void init() { m_bottomUpData = {};