From c41d30711a03ada34091c40499a41ffdb0da3d84 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Thu, 23 Feb 2023 15:34:05 +0100 Subject: [PATCH] Utils: Make UnixDeviceFileAccess macOS compatible "stat" on macOS has slightly different formatting options. Also adds unittests for UnixDeviceFileAccess Task-number: QTCREATORBUG-28142 Change-Id: Ib42fc1c22ef2771365e915df34f2286e2c705568 Reviewed-by: hjk Reviewed-by: Qt CI Bot Reviewed-by: --- src/libs/utils/devicefileaccess.cpp | 94 +++++++++++++------ src/libs/utils/devicefileaccess.h | 10 ++ src/libs/utils/fileutils.cpp | 9 +- src/libs/utils/fileutils.h | 2 +- tests/auto/utils/CMakeLists.txt | 1 + tests/auto/utils/fileutils/tst_fileutils.cpp | 2 +- .../utils/unixdevicefileaccess/CMakeLists.txt | 4 + .../tst_unixdevicefileaccess.cpp | 84 +++++++++++++++++ .../unixdevicefileaccess.qbs | 11 +++ tests/auto/utils/utils.qbs | 1 + 10 files changed, 184 insertions(+), 34 deletions(-) create mode 100644 tests/auto/utils/unixdevicefileaccess/CMakeLists.txt create mode 100644 tests/auto/utils/unixdevicefileaccess/tst_unixdevicefileaccess.cpp create mode 100644 tests/auto/utils/unixdevicefileaccess/unixdevicefileaccess.qbs diff --git a/src/libs/utils/devicefileaccess.cpp b/src/libs/utils/devicefileaccess.cpp index 3d8354d7a0f..c50f3b145cb 100644 --- a/src/libs/utils/devicefileaccess.cpp +++ b/src/libs/utils/devicefileaccess.cpp @@ -8,6 +8,7 @@ #include "environment.h" #include "expected.h" #include "hostosinfo.h" +#include "osspecificaspects.h" #include "qtcassert.h" #include "utilstr.h" @@ -820,7 +821,7 @@ QByteArray DesktopDeviceFileAccess::fileId(const FilePath &filePath) const OsType DesktopDeviceFileAccess::osType(const FilePath &filePath) const { - Q_UNUSED(filePath); + Q_UNUSED(filePath) return HostOsInfo::hostOs(); } @@ -1054,10 +1055,28 @@ expected_str UnixDeviceFileAccess::createTempFile(const FilePath &file return newPath; } +OsType UnixDeviceFileAccess::osType() const +{ + if (m_osType) + return *m_osType; + + const RunResult result = runInShell({"uname", {"-s"}, OsType::OsTypeLinux}); + QTC_ASSERT(result.exitCode == 0, return OsTypeLinux); + const QString osName = QString::fromUtf8(result.stdOut).trimmed(); + if (osName == "Darwin") + m_osType = OsTypeMac; + else if (osName == "Linux") + m_osType = OsTypeLinux; + else + m_osType = OsTypeOtherUnix; + + return *m_osType; +} + OsType UnixDeviceFileAccess::osType(const FilePath &filePath) const { - Q_UNUSED(filePath) - return OsTypeLinux; + Q_UNUSED(filePath); + return osType(); } QDateTime UnixDeviceFileAccess::lastModified(const FilePath &filePath) const @@ -1069,10 +1088,19 @@ QDateTime UnixDeviceFileAccess::lastModified(const FilePath &filePath) const return dt; } +QStringList UnixDeviceFileAccess::statArgs(const FilePath &filePath, + const QString &linuxFormat, + const QString &macFormat) const +{ + return (osType() == OsTypeMac ? QStringList{"-f", macFormat} : QStringList{"-c", linuxFormat}) + << "-L" << filePath.path(); +} + QFile::Permissions UnixDeviceFileAccess::permissions(const FilePath &filePath) const { - const RunResult result = runInShell( - {"stat", {"-L", "-c", "%a", filePath.path()}, OsType::OsTypeLinux}); + QStringList args = statArgs(filePath, "%a", "%p"); + + const RunResult result = runInShell({"stat", args, OsType::OsTypeLinux}); const uint bits = result.stdOut.toUInt(nullptr, 8); QFileDevice::Permissions perm = {}; #define BIT(n, p) \ @@ -1100,8 +1128,8 @@ bool UnixDeviceFileAccess::setPermissions(const FilePath &filePath, QFile::Permi qint64 UnixDeviceFileAccess::fileSize(const FilePath &filePath) const { - const RunResult result = runInShell( - {"stat", {"-L", "-c", "%s", filePath.path()}, OsType::OsTypeLinux}); + const QStringList args = statArgs(filePath, "%s", "%z"); + const RunResult result = runInShell({"stat", args, OsType::OsTypeLinux}); return result.stdOut.toLongLong(); } @@ -1113,8 +1141,9 @@ qint64 UnixDeviceFileAccess::bytesAvailable(const FilePath &filePath) const QByteArray UnixDeviceFileAccess::fileId(const FilePath &filePath) const { - const RunResult result = runInShell( - {"stat", {"-L", "-c", "%D:%i", filePath.path()}, OsType::OsTypeLinux}); + const QStringList args = statArgs(filePath, "%D:%i", "%d:%i"); + + const RunResult result = runInShell({"stat", args, OsType::OsTypeLinux}); if (result.exitCode != 0) return {}; @@ -1136,9 +1165,12 @@ FilePathInfo UnixDeviceFileAccess::filePathInfo(const FilePath &filePath) const return r; } - const RunResult stat = runInShell( - {"stat", {"-L", "-c", "%f %Y %s", filePath.path()}, OsType::OsTypeLinux}); - return FileUtils::filePathInfoFromTriple(QString::fromLatin1(stat.stdOut)); + + const QStringList args = statArgs(filePath, "%f %Y %s", "%p %m %z"); + + const RunResult stat = runInShell({"stat", args, OsType::OsTypeLinux}); + return FileUtils::filePathInfoFromTriple(QString::fromLatin1(stat.stdOut), + osType() != OsTypeMac); } // returns whether 'find' could be used. @@ -1152,8 +1184,13 @@ bool UnixDeviceFileAccess::iterateWithFind(const FilePath &filePath, // TODO: Using stat -L will always return the link target, not the link itself. // We may wan't to add the information that it is a link at some point. + + const QString statFormat = osType() == OsTypeMac + ? QLatin1String("-f \"%p %m %z\"") : QLatin1String("-c \"%f %Y %s\""); + if (callBack.index() == 1) - cmdLine.addArgs(R"(-exec echo -n \"{}\"" " \; -exec stat -L -c "%f %Y %s" "{}" \;)", + cmdLine.addArgs(QString(R"(-exec echo -n \"{}\"" " \; -exec stat -L %1 "{}" \;)") + .arg(statFormat), CommandLine::Raw); const RunResult result = runInShell(cmdLine); @@ -1175,23 +1212,26 @@ bool UnixDeviceFileAccess::iterateWithFind(const FilePath &filePath, if (entries.isEmpty()) return true; - const auto toFilePath = [&filePath, &callBack](const QString &entry) { - if (callBack.index() == 0) - return std::get<0>(callBack)(filePath.withNewPath(entry)); + const int modeBase = osType() == OsTypeMac ? 8 : 16; - const QString fileName = entry.mid(1, entry.lastIndexOf('\"') - 1); - const QString infos = entry.mid(fileName.length() + 3); + const auto toFilePath = + [&filePath, &callBack, modeBase](const QString &entry) { + if (callBack.index() == 0) + return std::get<0>(callBack)(filePath.withNewPath(entry)); - const FilePathInfo fi = FileUtils::filePathInfoFromTriple(infos); - if (!fi.fileFlags) - return IterationPolicy::Continue; + const QString fileName = entry.mid(1, entry.lastIndexOf('\"') - 1); + const QString infos = entry.mid(fileName.length() + 3); - const FilePath fp = filePath.withNewPath(fileName); - // Do not return the entry for the directory we are searching in. - if (fp.path() == filePath.path()) - return IterationPolicy::Continue; - return std::get<1>(callBack)(fp, fi); - }; + const FilePathInfo fi = FileUtils::filePathInfoFromTriple(infos, modeBase); + if (!fi.fileFlags) + return IterationPolicy::Continue; + + const FilePath fp = filePath.withNewPath(fileName); + // Do not return the entry for the directory we are searching in. + if (fp.path() == filePath.path()) + return IterationPolicy::Continue; + return std::get<1>(callBack)(fp, fi); + }; // Remove the first line, this can be the directory we are searching in. // as long as we do not specify "mindepth > 0" diff --git a/src/libs/utils/devicefileaccess.h b/src/libs/utils/devicefileaccess.h index 8fe8201e088..238bab5e777 100644 --- a/src/libs/utils/devicefileaccess.h +++ b/src/libs/utils/devicefileaccess.h @@ -3,10 +3,13 @@ #pragma once +#include "hostosinfo.h" #include "utils_global.h" #include "fileutils.h" +class tst_unixdevicefileaccess; // For testing. + namespace Utils { // Base class including dummy implementation usable as fallback. @@ -19,6 +22,7 @@ public: protected: friend class FilePath; + friend class ::tst_unixdevicefileaccess; // For testing. virtual QString mapToDevicePath(const QString &hostPath) const; @@ -204,8 +208,14 @@ private: const FileFilter &filter, QStringList *found) const; + Utils::OsType osType() const; + QStringList statArgs(const FilePath &filePath, + const QString &linuxFormat, + const QString &macFormat) const; + mutable bool m_tryUseFind = true; mutable std::optional m_hasMkTemp; + mutable std::optional m_osType; }; } // Utils diff --git a/src/libs/utils/fileutils.cpp b/src/libs/utils/fileutils.cpp index d128f219742..c0a574f9c34 100644 --- a/src/libs/utils/fileutils.cpp +++ b/src/libs/utils/fileutils.cpp @@ -586,8 +586,7 @@ FilePaths FileUtils::getOpenFilePaths(QWidget *parent, #endif // QT_WIDGETS_LIB -// Converts a hex string of the st_mode field of a stat structure to FileFlags. -FilePathInfo::FileFlags fileInfoFlagsfromStatRawModeHex(const QString &hexString) +FilePathInfo::FileFlags fileInfoFlagsfromStatMode(const QString &hexString, int modeBase) { // Copied from stat.h enum st_mode { @@ -617,7 +616,7 @@ FilePathInfo::FileFlags fileInfoFlagsfromStatRawModeHex(const QString &hexString }; bool ok = false; - uint mode = hexString.toUInt(&ok, 16); + uint mode = hexString.toUInt(&ok, modeBase); QTC_ASSERT(ok, return {}); @@ -657,13 +656,13 @@ FilePathInfo::FileFlags fileInfoFlagsfromStatRawModeHex(const QString &hexString return result; } -FilePathInfo FileUtils::filePathInfoFromTriple(const QString &infos) +FilePathInfo FileUtils::filePathInfoFromTriple(const QString &infos, int modeBase) { const QStringList parts = infos.split(' ', Qt::SkipEmptyParts); if (parts.size() != 3) return {}; - FilePathInfo::FileFlags flags = fileInfoFlagsfromStatRawModeHex(parts[0]); + FilePathInfo::FileFlags flags = fileInfoFlagsfromStatMode(parts[0], modeBase); const QDateTime dt = QDateTime::fromSecsSinceEpoch(parts[1].toLongLong(), Qt::UTC); qint64 size = parts[2].toLongLong(); diff --git a/src/libs/utils/fileutils.h b/src/libs/utils/fileutils.h index ec3de7a5ba4..19f17a6d719 100644 --- a/src/libs/utils/fileutils.h +++ b/src/libs/utils/fileutils.h @@ -83,7 +83,7 @@ public: static qint64 bytesAvailableFromDFOutput(const QByteArray &dfOutput); - static FilePathInfo filePathInfoFromTriple(const QString &infos); + static FilePathInfo filePathInfoFromTriple(const QString &infos, int modeBase); #ifdef QT_WIDGETS_LIB static void setDialogParentGetter(const std::function &getter); diff --git a/tests/auto/utils/CMakeLists.txt b/tests/auto/utils/CMakeLists.txt index 65f47fc73f7..74fed43c104 100644 --- a/tests/auto/utils/CMakeLists.txt +++ b/tests/auto/utils/CMakeLists.txt @@ -17,3 +17,4 @@ add_subdirectory(stringutils) add_subdirectory(tasktree) add_subdirectory(templateengine) add_subdirectory(treemodel) +add_subdirectory(unixdevicefileaccess) diff --git a/tests/auto/utils/fileutils/tst_fileutils.cpp b/tests/auto/utils/fileutils/tst_fileutils.cpp index c4553eac645..f4596cb1a5c 100644 --- a/tests/auto/utils/fileutils/tst_fileutils.cpp +++ b/tests/auto/utils/fileutils/tst_fileutils.cpp @@ -172,7 +172,7 @@ void tst_fileutils::filePathInfoFromTriple() QFETCH(QString, statoutput); QFETCH(FilePathInfo, expected); - const FilePathInfo result = FileUtils::filePathInfoFromTriple(statoutput); + const FilePathInfo result = FileUtils::filePathInfoFromTriple(statoutput, 16); QCOMPARE(result, expected); } diff --git a/tests/auto/utils/unixdevicefileaccess/CMakeLists.txt b/tests/auto/utils/unixdevicefileaccess/CMakeLists.txt new file mode 100644 index 00000000000..0cf8d43c712 --- /dev/null +++ b/tests/auto/utils/unixdevicefileaccess/CMakeLists.txt @@ -0,0 +1,4 @@ +add_qtc_test(tst_utils_unixdevicefileaccess + DEPENDS Utils + SOURCES tst_unixdevicefileaccess.cpp +) diff --git a/tests/auto/utils/unixdevicefileaccess/tst_unixdevicefileaccess.cpp b/tests/auto/utils/unixdevicefileaccess/tst_unixdevicefileaccess.cpp new file mode 100644 index 00000000000..8aa9609057c --- /dev/null +++ b/tests/auto/utils/unixdevicefileaccess/tst_unixdevicefileaccess.cpp @@ -0,0 +1,84 @@ +// 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 +#include +#include +#include + +#include +#include +#include +#include + +//TESTED_COMPONENT=src/libs/utils +using namespace Utils; + +namespace QTest { +template<> +char *toString(const FilePath &filePath) +{ + return qstrdup(filePath.toString().toLocal8Bit().constData()); +} +} // namespace QTest + +class TestDFA : public UnixDeviceFileAccess +{ +public: + using UnixDeviceFileAccess::UnixDeviceFileAccess; + + virtual RunResult runInShell(const CommandLine &cmdLine, + const QByteArray &inputData = {}) const override + { + QProcess p; + p.setProgram(cmdLine.executable().toString()); + p.setArguments(cmdLine.splitArguments()); + p.setProcessChannelMode(QProcess::SeparateChannels); + + p.start(); + p.waitForStarted(); + if (inputData.size() > 0) { + p.write(inputData); + p.closeWriteChannel(); + } + p.waitForFinished(); + return {p.exitCode(), p.readAllStandardOutput(), p.readAllStandardError()}; + } +}; + +class tst_unixdevicefileaccess : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase() + { + if (HostOsInfo::isWindowsHost()) + QSKIP("This test is only for Unix hosts"); + + m_fileSizeTestFile.writeFileContents(QByteArray(1024, 'a')); + } + + void osType() + { + const auto osType = m_dfaPtr->osType({}); + QCOMPARE(osType, HostOsInfo::hostOs()); + } + + void fileSize() + { + const auto size = m_dfaPtr->fileSize(m_fileSizeTestFile); + QCOMPARE(size, 1024); + } + +private: + TestDFA m_dfa; + DeviceFileAccess *m_dfaPtr = &m_dfa; + + QTemporaryDir m_tempDir; + FilePath m_fileSizeTestFile = FilePath::fromString(m_tempDir.filePath("size-test")); +}; + +QTEST_GUILESS_MAIN(tst_unixdevicefileaccess) + +#include "tst_unixdevicefileaccess.moc" diff --git a/tests/auto/utils/unixdevicefileaccess/unixdevicefileaccess.qbs b/tests/auto/utils/unixdevicefileaccess/unixdevicefileaccess.qbs new file mode 100644 index 00000000000..1776ef4a6ee --- /dev/null +++ b/tests/auto/utils/unixdevicefileaccess/unixdevicefileaccess.qbs @@ -0,0 +1,11 @@ +import qbs + +QtcAutotest { + name: "UnixDeviceFileAccess autotest" + Depends { name: "Utils" } + Properties { + condition: qbs.toolchain.contains("gcc") + cpp.cxxFlags: base.concat(["-Wno-trigraphs"]) + } + files: "tst_unixdevicefileaccess.cpp" +} diff --git a/tests/auto/utils/utils.qbs b/tests/auto/utils/utils.qbs index d8d0a58b4b8..5f4d0439201 100644 --- a/tests/auto/utils/utils.qbs +++ b/tests/auto/utils/utils.qbs @@ -22,5 +22,6 @@ Project { "tasktree/tasktree.qbs", "templateengine/templateengine.qbs", "treemodel/treemodel.qbs", + "unixdevicefileaccess/unixdevicefileaccess.qbs", ] }