diff options
Diffstat (limited to 'src/qdoc/catch_generators/src/catch_generators/generators/path_generator.h')
-rw-r--r-- | src/qdoc/catch_generators/src/catch_generators/generators/path_generator.h | 853 |
1 files changed, 853 insertions, 0 deletions
diff --git a/src/qdoc/catch_generators/src/catch_generators/generators/path_generator.h b/src/qdoc/catch_generators/src/catch_generators/generators/path_generator.h new file mode 100644 index 000000000..875502e49 --- /dev/null +++ b/src/qdoc/catch_generators/src/catch_generators/generators/path_generator.h @@ -0,0 +1,853 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// TODO: Change the include paths to implicitly consider +// `catch_generators` a root directory and change the CMakeLists.txt +// file to make this possible. + +#include "../namespaces.h" +#include "qchar_generator.h" +#include "qstring_generator.h" +#include "../utilities/semantics/move_into_vector.h" +#include "../utilities/semantics/generator_handler.h" + +#if defined(Q_OS_WINDOWS) + + #include "combinators/cycle_generator.h" + +#endif + +#include <catch/catch.hpp> + +#include <random> + +#include <QChar> +#include <QString> +#include <QStringList> +#include <QRegularExpression> + +#if defined(Q_OS_WINDOWS) + + #include <QStorageInfo> + +#endif + +namespace QDOC_CATCH_GENERATORS_ROOT_NAMESPACE { + + + struct PathGeneratorConfiguration { + double multi_device_path_probability{0.5}; + double absolute_path_probability{0.5}; + double directory_path_probability{0.5}; + double has_trailing_separator_probability{0.5}; + std::size_t minimum_components_amount{1}; + std::size_t maximum_components_amount{10}; + + PathGeneratorConfiguration& set_multi_device_path_probability(double amount) { + multi_device_path_probability = amount; + return *this; + } + + PathGeneratorConfiguration& set_absolute_path_probability(double amount) { + absolute_path_probability = amount; + return *this; + } + + PathGeneratorConfiguration& set_directory_path_probability(double amount) { + directory_path_probability = amount; + return *this; + } + + PathGeneratorConfiguration& set_has_trailing_separator_probability(double amount) { + has_trailing_separator_probability = amount; + return *this; + } + + PathGeneratorConfiguration& set_minimum_components_amount(std::size_t amount) { + minimum_components_amount = amount; + return *this; + } + + PathGeneratorConfiguration& set_maximum_components_amount(std::size_t amount) { + maximum_components_amount = amount; + return *this; + } + }; + + /*! + * \class PathGeneratorConfiguration + * \brief Defines some parameters to customize the generation of + * paths by a PathGenerator. + */ + + /*! + * \variable PathGeneratorConfiguration::multi_device_path_probability + * + * Every path produced by a PathGenerator configured with a + * mutli_device_path_probability of n has a probability of n to be + * \e {Multi-Device} and a probability of 1.0 - n to not be \a + * {Multi-Device}. + * + * multi_device_path_probability should be a value in the range [0.0, + * 1.0]. + */ + + /*! + * \variable PathGeneratorConfiguration::absolute_path_probability + * + * Every path produced by a PathGenerator configured with an + * absolute_path_probability of n has a probability of n to be \e + * {Absolute} and a probability of 1.0 - n to be \e {Relative}. + * + * absolute_path_probability should be a value in the range [0.0, + * 1.0]. + */ + + /*! + * \variable PathGeneratorConfiguration::directory_path_probability + * + * Every path produced by a PathGenerator configured with a + * directory_path_probability of n has a probability of n to be \e + * {To a Directory} and a probability of 1.0 - n to be \e {To a + * File}. + * + * directory_path_probability should be a value in the range [0.0, + * 1.0]. + */ + + /*! + * \variable PathGeneratorConfiguration::has_trailing_separator_probability + * + * Every path produced by a PathGenerator configured with an + * has_trailing_separator_probability of n has a probability of n + * to \e {Have a Trailing Separator} and a probability of 1.0 - n + * to not \e {Have a Trailing Separator}, when this is applicable. + * + * has_trailing_separator_probability should be a value in the + * range [0.0, 1.0]. + */ + + /*! + * \variable PathGeneratorConfiguration::minimum_components_amount + * + * Every path produced by a PathGenerator configured with a + * minimum_components_amount of n will be the concatenation of at + * least n non \e {device}, non \e {root}, non \e {separator} + * components. + * + * minimum_components_amount should be greater than zero and less + * than maximum_components_amount. + */ + + /*! + * \variable PathGeneratorConfiguration::maximum_components_amount + * + * Every path produced by a PathGenerator configured with a + * maximum_components_amount of n will be the concatenation of at + * most n non \e {device}, non \e {root}, non \e {separator} components. + * + * maximum_components_amount should be greater than or equal to + * minimum_components_amount. + */ + + + namespace QDOC_CATCH_GENERATORS_PRIVATE_NAMESPACE { + + class PathGenerator : public Catch::Generators::IGenerator<QString> { + public: + PathGenerator( + Catch::Generators::GeneratorWrapper<QString>&& device_component_generator, + Catch::Generators::GeneratorWrapper<QString>&& root_component_generator, + Catch::Generators::GeneratorWrapper<QString>&& directory_component_generator, + Catch::Generators::GeneratorWrapper<QString>&& filename_component_generator, + Catch::Generators::GeneratorWrapper<QString>&& separator_component_generator, + PathGeneratorConfiguration configuration = PathGeneratorConfiguration{} + ) : device_component_generator{QDOC_CATCH_GENERATORS_UTILITIES_ABSOLUTE_NAMESPACE::handler(std::move(device_component_generator))}, + root_component_generator{QDOC_CATCH_GENERATORS_UTILITIES_ABSOLUTE_NAMESPACE::handler(std::move(root_component_generator))}, + directory_component_generator{QDOC_CATCH_GENERATORS_UTILITIES_ABSOLUTE_NAMESPACE::handler(std::move(directory_component_generator))}, + filename_component_generator{QDOC_CATCH_GENERATORS_UTILITIES_ABSOLUTE_NAMESPACE::handler(std::move(filename_component_generator))}, + separator_component_generator{QDOC_CATCH_GENERATORS_UTILITIES_ABSOLUTE_NAMESPACE::handler(std::move(separator_component_generator))}, + random_engine{std::random_device{}()}, + components_amount_distribution{configuration.minimum_components_amount, configuration.maximum_components_amount}, + is_multi_device_distribution{configuration.multi_device_path_probability}, + is_absolute_path_distribution{configuration.absolute_path_probability}, + is_directory_path_distribution{configuration.directory_path_probability}, + has_trailing_separator{configuration.has_trailing_separator_probability}, + current_path{} + { + assert(configuration.minimum_components_amount > 0); + assert(configuration.minimum_components_amount <= configuration.maximum_components_amount); + + if (!next()) + Catch::throw_exception("Not enough values to initialize the first string"); + } + + QString const& get() const override { return current_path; } + + bool next() override { + std::size_t components_amount{components_amount_distribution(random_engine)}; + + current_path = ""; + + // REMARK: As per our specification of a path, we + // do not count device components, and separators, + // when considering the amount of components in a + // path. + // This is a tradeoff that is not necessarily + // precise. + // Counting those kinds of components, on one + // hand, would allow a device component to stands + // on its own as a path, for example "C:", which + // might actually be correct in some path format. + // On the other hand, counting those kinds of + // components makes the construction of paths for + // our model much more complex with regards, for + // example, to the amount of component. + // + // Counting device components, since they can + // appear both in relative and absolute paths, + // makes the minimum amount of components + // different for different kinds of paths. + // + // Since absolute paths always require a root + // component, the minimum amount of components for + // a multi-device absolute path is 2. + // + // But an absolute path that is not multi-device + // would only require one minimum component. + // + // Similarly, problems arise with the existence of + // Windows' relative multi-device path, which + // require a leading separator component after a + // device component. + // + // This problem mostly comes from our model + // simplifying the definition of paths quite a bit + // into binary-forms. + // This simplifies the code and its structure, + // sacrificing some precision. + // The lost precision is almost none for POSIX + // based paths, but is graver for DOS paths, since + // they have a more complex specification. + // + // Currently, we expect that the paths that QDoc + // will encounter will mostly be in POSIX-like + // forms, even on Windows, and aim to support + // that, such that the simplification of code is + // considered a better tradeoff compared to the + // loss of precision. + // + // If this changes, the model should be changed to + // pursue a Windows-first modeling, moving the + // categorization of paths from the current binary + // model to the absolute, drive-relative and + // relative triptych that Windows uses. + // This more complex model should be able to + // completely describe posix paths too, making it + // a superior choice as long as the complexity is + // warranted. + // + // Do note that the model similarly can become + // inconsistent when used to generate format of + // paths such as the one used in some resource + // systems. + // Those are considered out-of-scope for our needs + // and were not taken into account when developing + // this generator. + if (is_multi_device_distribution(random_engine)) { + if (!device_component_generator.next()) return false; + current_path += device_component_generator.get(); + } + + // REMARK: Similarly to not counting other form of + // components, we do not count root components + // towards the amounts of components that the path + // has to simplify the code. + // To support the "special" root path on, for + // example, posix systems, we require a more + // complex branching logic that changes based on + // the path being absolute or not. + // + // We don't expect root to be a particularly + // useful path for QDoc purposes and expect to not + // have to consider it for our tests. + // If consideration for it become required, it is + // possible to test it directly in the affected + // systemss as a special case. + // + // If most systems are affected by the handling of + // a root path, then the model should be slightly + // changed to accommodate its generation. + if (is_absolute_path_distribution(random_engine)) { + if (!root_component_generator.next()) return false; + + current_path += root_component_generator.get(); + } + + std::size_t prefix_components_amount{std::max(std::size_t{1}, components_amount) - 1}; + while (prefix_components_amount > 0) { + if (!directory_component_generator.next()) return false; + if (!separator_component_generator.next()) return false; + + current_path += directory_component_generator.get() + separator_component_generator.get(); + --prefix_components_amount; + } + + if (is_directory_path_distribution(random_engine)) { + if (!directory_component_generator.next()) return false; + current_path += directory_component_generator.get(); + + if (has_trailing_separator(random_engine)) { + if (!separator_component_generator.next()) return false; + current_path += separator_component_generator.get(); + } + } else { + if (!filename_component_generator.next()) return false; + current_path += filename_component_generator.get(); + } + + return true; + } + + private: + Catch::Generators::GeneratorWrapper<QString> device_component_generator; + Catch::Generators::GeneratorWrapper<QString> root_component_generator; + Catch::Generators::GeneratorWrapper<QString> directory_component_generator; + Catch::Generators::GeneratorWrapper<QString> filename_component_generator; + Catch::Generators::GeneratorWrapper<QString> separator_component_generator; + + std::mt19937 random_engine; + std::uniform_int_distribution<std::size_t> components_amount_distribution; + std::bernoulli_distribution is_multi_device_distribution; + std::bernoulli_distribution is_absolute_path_distribution; + std::bernoulli_distribution is_directory_path_distribution; + std::bernoulli_distribution has_trailing_separator; + + QString current_path; + }; + + } // end QDOC_CATCH_GENERATORS_PRIVATE_NAMESPACE + +/*! + * Returns a generator that produces QStrings that represent a + * path in a filesystem. + * + * A path is formed by the following components, loosely based + * on the abstraction that is used by std::filesystem::path: + * + * \list + * \li \b {device}: + * Represents the device on the filesystem that + * the path should be considered in terms of. + * This is an optional components that is sometimes + * present on multi-device systems, such as Windows, to + * distinguish which device the path refers to. + * When present, it always appears before any other + * component. + * \li \b {root}: + * A special sequence that marks the path as absolute. + * This is an optional component that is present, always, + * in absolute paths. + * \li \b {directory}: + * A component that represents a directory on the + * filesystem that the path "passes-trough". + * Zero or more of this components can be present in the + * path. + * A path pointing to a directory on the filesystem that + * is not \e {root} always ends with a component of this + * type. + * \li \b {filename}: + * A component that represents a file on the + * filesystem. + * When this component is present, it is present only once + * and always as the last component of the path. + * A path that has such a component is a path that points + * to a file on the filesystem. + * For some path formats, there is no difference in the + * format of a \e {filename} and a \e {directory}. + * \li \b {separator}: + * A component that is interleaved between other types of + * components to separate them so that they are + * recognizable. + * A path that points to a directory on the filesystem may + * sometimes have a \e {separator} at the end, after the + * ending \e {directory} component. + * \endlist + * + * Each component is representable as a string and a path is a + * concatenation of the string representation of some + * components, with the following rules: + * + * \list + * \li There is at most one \e {device} component. + * \li If a \e {device} component is present it always + * precedes all other components. + * \li There is at most one \e {root} component. + * \li If a \e {root} component is present it: + * \list + * \li Succeeds the \e {device} component if it is present. + * \li Precedes every other components if the \e {device} + * component is not present. + * \endlist + * \li There are zero or more \e {directory} component. + * \li There is at most one \e {filename} component. + * \li If a \e {filename} component is present it always + * succeeds all other components. + * \li Between any two successive \e {directory} components + * there is a \e {separator} component. + * \li Between each successive \e {directory} and \e + * {filename} component there is a \e {separator} component. + * \li If the last component is a \e {directory} component it + * can be optionally followed by a \e {separator} component. + * \li At least one component that is not a \e {device}, a \e + * {root} or \e {separator} component is present. + * \endlist + * + * For example, if "C:" is a \e {device} component, "\\" is a + * \e {root} component, "\\" is a \e {separator} component, + * "directory" is a \e {directory} component and "filename" is + * a \e {filename} component, the following are all paths: + * + * "C:\\directory", "C:\\directory\\directory", "C:filename", + * "directory\\directory\\", "\\directory\\filename", "filename". + * + * While the following aren't: + * + * "C:", "C:\\", "directory\\C:", "foo", "C:filename\\", + * "filename\\directory\\filename", "filename\\filename", + * "directorydirectory"." + * + * The format of different components type can be the same. + * For example, the \e {root} and \e {separator} component in + * the above example. + * For the purpose of generation, we do not care about the + * format itself and consider a component of a certain type + * depending only on how it is generated/where it is generated + * from. + * + * For example, if every component is formatted as the string + * "a", the string "aaa" could be a generated path. + * By the string alone, it is not possible to simply discern + * which components form it, but it would be possible to + * generate it if the first "a" is a \a {device} component, + * the second "a" is a \e {root} component and the third "a" + * is a \e {directory} or \e {filename} component. + * + * A path, is further said to have some properties, pairs of + * which are exclusive to each other. + * + * A path is said to be: + * + * \list + * \li \b {Multi-Device}: + * When it contains a \e {device} component. + * \li \b {Absolute}: + * When it contains a \e {root} component. + * If the path is \e {Absolute} it is not \e {Relative}. + * \li \b {Relative}: + * When it does not contain a \e {root} component. + * If the path is \e {Relative} it is not \e {Absolute}. + * \li \b {To a Directory}: + * When its last component is a \e {directory} component + * or a \e {directory} component followed by a \e + * {separator} component. + * If the path is \e {To a Directory} it is not \e {To a + * File}. + * \li \b {To a File}: + * When its last component is a \e {filename}. + * If the path is \e {To a File} it is not \e {To a + * Directory}. + * \endlist + * + * All path are \e {Relative/Absolute}, \e {To a + * Directory/To a File} and \e {Multi-Device} or not. + * + * Furthermore, a path that is \e {To a Directory} and whose + * last component is a \e {separator} component is said to \e + * {Have a Trailing Separator}. + */ + inline Catch::Generators::GeneratorWrapper<QString> path( + Catch::Generators::GeneratorWrapper<QString>&& device_generator, + Catch::Generators::GeneratorWrapper<QString>&& root_component_generator, + Catch::Generators::GeneratorWrapper<QString>&& directory_generator, + Catch::Generators::GeneratorWrapper<QString>&& filename_generator, + Catch::Generators::GeneratorWrapper<QString>&& separator_generator, + PathGeneratorConfiguration configuration = PathGeneratorConfiguration{} + ) { + return Catch::Generators::GeneratorWrapper<QString>( + std::unique_ptr<Catch::Generators::IGenerator<QString>>( + new QDOC_CATCH_GENERATORS_PRIVATE_NAMESPACE::PathGenerator(std::move(device_generator), std::move(root_component_generator), std::move(directory_generator), std::move(filename_generator), std::move(separator_generator), configuration) + ) + ); + } + + namespace QDOC_CATCH_GENERATORS_PRIVATE_NAMESPACE { + + // REMARK: We need a bounded length for the generation of path + // components as strings. + // We trivially do not want components to be the empty string, + // such that we have a minimum length of 1, but the maximum + // length is more malleable. + // We don't want components that are too long to avoid + // incurring in a big performance overhead, as we may generate + // many of them. + // At the same time, we want some freedom in having diffent + // length components. + // The value that was chosen is based on the general value for + // POSIX's NAME_MAX, which seems to tend to be 14 on many systems. + // We see this value as a small enough but not too much value + // that further brings with itself a relation to paths, + // increasing our portability even if it is out of scope, as + // almost no modern respects NAME_MAX. + // We don't use POSIX's NAME_MAX directly as it may not be available + // on all systems. + inline static constexpr std::size_t minimum_component_length{1}; + inline static constexpr std::size_t maximum_component_length{14}; + + /*! + * Returns a generator that generates strings that are + * suitable to be used as a root component in POSIX paths. + * + * As per + * \l {https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_02}, + * this is any sequence of slash characters that is not of + * length 2. + */ + inline Catch::Generators::GeneratorWrapper<QString> posix_root() { + return uniformly_valued_oneof( + QDOC_CATCH_GENERATORS_UTILITIES_ABSOLUTE_NAMESPACE::move_into_vector( + string(character('/', '/'), 1, 1), + string(character('/', '/'), 3, maximum_component_length) + ), + std::vector{1, maximum_component_length - 3} + ); + } + + /*! + * Returns a generator that generates strings that are + * suitable to be used as directory components in POSIX paths + * and that use an alphabet that should generally be supported + * by other systems. + * + * Components of this kind use the \l + * {https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_282}{Portable Filename Character Set}. + */ + inline Catch::Generators::GeneratorWrapper<QString> portable_posix_directory_name() { + return string( + QDOC_CATCH_GENERATORS_QCHAR_ALPHABETS_NAMESPACE::portable_posix_filename(), + minimum_component_length, maximum_component_length + ); + } + + /*! + * Returns a generator that generates strings that are + * suitable to be used as filenames in POSIX paths and that + * use an alphabet that should generally be supported by + * other systems. + * + * Filenames of this kind use the \l + * {https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_282}{Portable Filename Character Set}. + */ + inline Catch::Generators::GeneratorWrapper<QString> portable_posix_filename() { + // REMARK: "." and ".." always represent directories so we + // avoid generating them. Other than this, there is no + // difference between a file name and a directory name. + return filter([](auto& filename) { return filename != "." && filename != ".."; }, portable_posix_directory_name()); + } + + /*! + * Returns a generator that generates strings that can be used + * as POSIX compliant separators. + * + * As per \l + * {https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_271}, + * a separator is a sequence of one or more slashes. + */ + inline Catch::Generators::GeneratorWrapper<QString> posix_separator() { + return string(character('/', '/'), minimum_component_length, maximum_component_length); + } + + /*! + * Returns a generator that generates strings that can be + * suitably used as logical drive names in Windows' paths. + * + * As per \l + * {https://docs.microsoft.com/en-us/dotnet/standard/io/file-path-formats#traditional-dos-paths} + * and \l + * {https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getlogicaldrives}, + * they are composed of a single letter. + * Each generated string always follows the lettet with a + * colon, as it is specifically intended for path usages, + * where this is required. + * + * We use only uppercase letters for the drives names albeit, + * depending on case sensitivity, lowercase letter could be + * used. + */ + inline Catch::Generators::GeneratorWrapper<QString> windows_logical_drives() { + // REMARK: If a Windows path is generated on Windows + // itself, we expect that it may be used to interact with + // the filesystem, similar to how we expect a POSIX path + // to be used on Linux. + // For this reason, we only generate a specific drive, the one + // that contains the current working directory, so that we + // know it is an actually available drive and to contain the + // possible modifications to the filesystem to an easily + // foundable place. + +#if defined(Q_OS_WINDOWS) + + auto root_device{QStorageInfo{QDir()}.rootPath().first(1) + ":"}; + + return cycle(Catch::Generators::value(std::move(root_device))); + +#else + + return Catch::Generators::map( + [](QString letter){ return letter + ':';}, + string(QDOC_CATCH_GENERATORS_QCHAR_ALPHABETS_NAMESPACE::ascii_uppercase(), 1, 1) + ); + +#endif + } + + /*! + * Returns a generator that generate strings that can be used + * as separators in Windows based paths. + * + * As per \l + * {https://docs.microsoft.com/en-us/dotnet/api/system.io.path.directoryseparatorchar?view=net-6.0} + * and \l + * {https://docs.microsoft.com/en-us/dotnet/standard/io/file-path-formats#canonicalize-separators}, + * this is a sequence of one or more backward or forward slashes. + */ + inline Catch::Generators::GeneratorWrapper<QString> windows_separator() { + return uniform_oneof( + QDOC_CATCH_GENERATORS_UTILITIES_ABSOLUTE_NAMESPACE::move_into_vector( + string(character('\\', '\\'), minimum_component_length, maximum_component_length), + string(character('/', '/'), minimum_component_length, maximum_component_length) + ) + ); + } + + } // end QDOC_CATCH_GENERATORS_PRIVATE_NAMESPACE + + /*! + * Returns a generator that generates strings representing + * POSIX compatible paths. + * + * The generated paths follows the format specified in \l + * {https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_271}. + * + * The optional length-requirements, such as PATH_MAX and + * NAME_MAX, are relaxed away as they are generally not + * respected by modern systems. + * + * It is possible to set the probability of obtaining a + * relative or absolute path through \a + * absolute_path_probability and the one of obtaining a path + * potentially pointing ot a directory or on a file through \a + * directory_path_probability. + */ + inline Catch::Generators::GeneratorWrapper<QString> relaxed_portable_posix_path(double absolute_path_probability = 0.5, double directory_path_probability = 0.5) { + return path( + // POSIX path are never multi-device, so that we have + // provide an empty device component generator and set + // the probability for Multi-Device paths to zero. + string(character(), 0, 0), + QDOC_CATCH_GENERATORS_PRIVATE_NAMESPACE::posix_root(), + QDOC_CATCH_GENERATORS_PRIVATE_NAMESPACE::portable_posix_directory_name(), + QDOC_CATCH_GENERATORS_PRIVATE_NAMESPACE::portable_posix_filename(), + QDOC_CATCH_GENERATORS_PRIVATE_NAMESPACE::posix_separator(), + PathGeneratorConfiguration{} + .set_multi_device_path_probability(0.0) + .set_absolute_path_probability(absolute_path_probability) + .set_directory_path_probability(directory_path_probability) + ); + } + + /*! + * Returns a generator that produces strings that represents + * traditional DOS paths as defined in \l + * {https://docs.microsoft.com/en-us/dotnet/standard/io/file-path-formats#traditional-dos-paths}. + * + * The directory and filename components of a path generated + * in this way are, currently, restricted to use a portable + * character set as defined by POSIX. + * + * Do note that most paths themselves, will not be portable, on + * the whole, albeit they may be valid paths on other systems, as + * Windows uses a path system that is generally incompatible with + * other systems. + * + * Some possibly valid special path, such as a "C:" or "\" + * will never be generated. + */ + inline Catch::Generators::GeneratorWrapper<QString> traditional_dos_path( + double absolute_path_probability = 0.5, + double directory_path_probability = 0.5, + double multi_device_path_probability = 0.5 + ) { + return path( + QDOC_CATCH_GENERATORS_PRIVATE_NAMESPACE::windows_logical_drives(), + QDOC_CATCH_GENERATORS_PRIVATE_NAMESPACE::windows_separator(), + // REMAKR: Windows treats trailing dots as if they were a + // component of their own, that is, as the special + // relative paths. + // This seems to not be correctly handled by Qt's + // filesystem methods, resulting in inconsistencies when + // one such path is encountered. + // To avoid the issue, considering that an equivalent path + // can be formed by actually having the dots on their own + // as a component, we filter out all those paths that have + // trailing dots but are not only composed of dots. + Catch::Generators::filter( + [](auto& path){ return !(path.endsWith(".") && path.contains(QRegularExpression("[^.]"))) ; }, + QDOC_CATCH_GENERATORS_PRIVATE_NAMESPACE::portable_posix_directory_name() + ), + QDOC_CATCH_GENERATORS_PRIVATE_NAMESPACE::portable_posix_filename(), + QDOC_CATCH_GENERATORS_PRIVATE_NAMESPACE::windows_separator(), + PathGeneratorConfiguration{} + .set_multi_device_path_probability(multi_device_path_probability) + .set_absolute_path_probability(absolute_path_probability) + .set_directory_path_probability(directory_path_probability) + ); + } + + // TODO: Find a good way to test the following functions. + // native_path can probably be tied to the tests for the + // OS-specific functions, with TEMPLATE_TEST_CASE. + // The other ones may follow a similar pattern but require a bit + // more work so that they tie to a specific case instead of the + // general one. + // Nonetheless, this approach is both error prone and difficult to + // parse, because of the required if preprocessor directives, + // and should be avoided if possible. + + /*! + * Returns a generator that generates QStrings that represents + * paths native to the underlying OS. + * + * On Windows, paths that refer to a drive always refer to the + * root drive. + * + * native* functions should always be chosen when using paths for + * testing interfacing with the filesystem itself. + * + * System outside Linux, macOS or Windows are not supported. + */ + inline Catch::Generators::GeneratorWrapper<QString> native_path(double absolute_path_probability = 0.5, double directory_path_probability = 0.5) { +#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS) + + return relaxed_portable_posix_path(absolute_path_probability, directory_path_probability); + +#elif defined(Q_OS_WINDOWS) + + // REMARK: When generating native paths for testing we + // generally want to avoid relative paths that are + // drive-specific, as we want them to be tied to a specific + // working directory that may not be the current directory on + // the drive. + // Hence, we avoid generating paths that may have a drive component. + // For tests where those kind of paths are interesting, a + // specific Windows-only test should be made, using + // traditional_dos_path to generate drive-relative paths only. + return traditional_dos_path(absolute_path_probability, directory_path_probability, 0.0); + +#endif + } + + /*! + * Returns a generator that generates QStrings that represents + * paths native to the underlying OS and that are always \e + * {Relative}. + * + * Avoids generating paths that refer to a directory that is not + * included in the path itself. + * + * System outside Linux, macOS or Windows are not supported. + */ + inline Catch::Generators::GeneratorWrapper<QString> native_relative_path(double directory_path_probability = 0.5) { + // REMARK: When testing, we generally use some specific + // directory as a root for relative paths. + // We want the generated path to be relative to that + // directory because we need a clean state for the test to + // be reliable. + // When generating paths, it is possible, correctly, to + // have a path that refers to that directory or some + // parent of it, removing us from the clean state that we + // need. + // To avoid that, we filter out paths that end up referring to a directory that is not under our "root" directory. + // + // We can think of each generated component moving us + // further down or up, in case of "..", a directory + // hierarchy, or keeping us at the same place in case of + // ".". + // Any path that ends up under our original "root" + // directory will safely keep our clean state for testing. + // + // Each "." keeps us at the same level in the hierarchy. + // Each ".." moves us up one level in the hierarchy. + // Each component that is not "." or ".." moves us down + // one level into the hierarchy. + // + // Then, to avoid referring to the "root" directory or one + // of its parents, we need to balance out each "." and + // ".." with the components that precedes or follow their + // appearance. + // + // Since "." keeps us at the same level, it can appear how + // many times it wants as long as the path referes to the + // "root" directory or a directory or file under it and at + // least one other component referes to a directory or + // file that is under the "root" directory. + // + // Since ".." moves us one level up in the hierarchy, a + // sequence of n ".." components is safe when at least n + + // 1 non "." or ".." components appear before it. + // + // To avoid the above problem, we filter away paths that + // do not respect those rules. + return Catch::Generators::filter( + [](auto& path){ + QStringList components{path.split(QRegularExpression{R"((\\|\/)+)"}, Qt::SkipEmptyParts)}; + int depth{0}; + + for (auto& component : components) { + if (component == "..") + --depth; + else if (component != ".") + ++depth; + + if (depth < 0) return false; + } + + return (depth > 0); + }, + native_path(0.0, directory_path_probability) + ); + } + + /*! + * Returns a generator that generates QStrings that represents + * paths native to the underlying OS and that are always \e + * {Relative} and \e {To a File}. + * + * System outside Linux, macOS or Windows are not supported. + */ + inline Catch::Generators::GeneratorWrapper<QString> native_relative_file_path() { + return native_relative_path(0.0); + } + + /*! + * Returns a generator that generates QStrings that represents + * paths native to the underlying OS and that are always \e + * {Relative} and \e {To a Directory}. + * + * System outside Linux, macOS or Windows are not supported. + */ + inline Catch::Generators::GeneratorWrapper<QString> native_relative_directory_path() { + return native_relative_path(1.0); + } + +} // end QDOC_CATCH_GENERATORS_ROOT_NAMESPACE |