diff --git a/src/parsers/perf/perfparser.cpp b/src/parsers/perf/perfparser.cpp index f5efdd569..5fa8c3f6b 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,22 @@ bool PerfParser::initParserArgs(const QString& path) return false; } + // peek into file header + const auto filename = decompressIfNeeded(path); + QFile file(filename); + file.open(QIODevice::ReadOnly); + 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"))); + } + file.close(); + return false; + } + file.close(); + + // check perfparser and set initial values auto parserBinary = Util::perfParserBinaryPath(); if (parserBinary.isEmpty()) { emit parsingFailed(tr("Failed to find hotspot-perfparser binary.")); @@ -1497,8 +1514,8 @@ bool PerfParser::initParserArgs(const QString& path) auto parserArgs = [this](const QString& filename) { const auto settings = Settings::instance(); - QStringList parserArgs = {QStringLiteral("--input"), decompressIfNeeded(filename), - QStringLiteral("--max-frames"), QStringLiteral("1024")}; + QStringList parserArgs = {QStringLiteral("--input"), filename, QStringLiteral("--max-frames"), + QStringLiteral("1024")}; const auto sysroot = settings->sysroot(); if (!sysroot.isEmpty()) { parserArgs += {QStringLiteral("--sysroot"), sysroot}; @@ -1530,7 +1547,7 @@ bool PerfParser::initParserArgs(const QString& path) return parserArgs; }; - m_parserArgs = parserArgs(path); + m_parserArgs = parserArgs(filename); m_parserBinary = parserBinary; return true; } @@ -1583,22 +1600,23 @@ 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); + file.open(QIODevice::ReadOnly); + 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; } } finalize(); return; } + file.close(); QProcess process; process.setProcessEnvironment(perfparserEnvironment(debuginfodUrls)); diff --git a/tests/integrationtests/tst_perfparser.cpp b/tests/integrationtests/tst_perfparser.cpp index d8c2a6874..d2e6da642 100644 --- a/tests/integrationtests/tst_perfparser.cpp +++ b/tests/integrationtests/tst_perfparser.cpp @@ -227,23 +227,36 @@ private slots: QCOMPARE(parsingFailedSpy.count(), 0); QCOMPARE(parsingFinishedSpy.count(), 1); - // check for invalid data -> expected error + // check pre-exported perfparser with "bad extension" + const auto shell = QStandardPaths::findExecutable(QStringLiteral("bash")); + if (!shell.isEmpty()) { + const auto perfDataSomeName = QStringLiteral("fruitper"); + QProcess::execute(shell, + {QStringLiteral("-c"), QLatin1String("cp -f %1 %2").arg(perfData, perfDataSomeName)}); + + parser.startParseFile(perfDataSomeName); + QVERIFY(parsingFinishedSpy.wait(2000)); + QCOMPARE(parsingFailedSpy.count(), 0); + QCOMPARE(parsingFinishedSpy.count(), 2); + } + + // check for unexpected (here: no) file magic -> expected error const auto noPerf = QFINDTESTDATA("tst_perfparser.cpp"); QVERIFY(!noPerf.isEmpty() && QFile::exists(noPerf)); parser.startParseFile(noPerf); - QVERIFY(parsingFailedSpy.wait(1000)); QCOMPARE(parsingFailedSpy.count(), 1); message = qvariant_cast(parsingFailedSpy.takeFirst().at(0)); - QVERIFY(message.contains(QLatin1String("invalid perf data file"))); + QVERIFY(message.contains(noPerf)); + QVERIFY(message.contains(QLatin1String("File format unknown"))); // check for PERFv1 -> expected error const auto perf1 = QFINDTESTDATA("file_content/perf.data.true.v1"); QVERIFY(!perf1.isEmpty() && QFile::exists(perf1)); parser.startParseFile(perf1); - QVERIFY(parsingFailedSpy.wait(1000)); QCOMPARE(parsingFailedSpy.count(), 1); message = qvariant_cast(parsingFailedSpy.takeFirst().at(0)); - QVERIFY(message.contains(QLatin1String("invalid perf data file"))); + QVERIFY(message.contains(perf1)); + QVERIFY(message.contains(QLatin1String("V1 perf data"))); // check for PERFv2 const auto perf2 = QFINDTESTDATA("file_content/perf.data.true.v2"); @@ -253,7 +266,7 @@ private slots: 8000)); // FIXME: this waittime and the one below must be so high as unavailable elf files are checked // again and again via PerfSymbolTable::registerElf, as seen in the output QCOMPARE(parsingFailedSpy.count(), 0); - QCOMPARE(parsingFinishedSpy.count(), 2); + QCOMPARE(parsingFinishedSpy.count(), 3); #if KFArchive_FOUND // check for PERFv2, gzipped @@ -262,7 +275,7 @@ private slots: parser.startParseFile(perf2gz); QVERIFY(parsingFinishedSpy.wait(9000)); QCOMPARE(parsingFailedSpy.count(), 0); - QCOMPARE(parsingFinishedSpy.count(), 3); + QCOMPARE(parsingFinishedSpy.count(), 4); #endif }