// 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 #include #include #include #include #include #if defined(Q_OS_WINDOWS) #include #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 { public: PathGenerator( Catch::Generators::GeneratorWrapper&& device_component_generator, Catch::Generators::GeneratorWrapper&& root_component_generator, Catch::Generators::GeneratorWrapper&& directory_component_generator, Catch::Generators::GeneratorWrapper&& filename_component_generator, Catch::Generators::GeneratorWrapper&& 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 device_component_generator; Catch::Generators::GeneratorWrapper root_component_generator; Catch::Generators::GeneratorWrapper directory_component_generator; Catch::Generators::GeneratorWrapper filename_component_generator; Catch::Generators::GeneratorWrapper separator_component_generator; std::mt19937 random_engine; std::uniform_int_distribution 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 path( Catch::Generators::GeneratorWrapper&& device_generator, Catch::Generators::GeneratorWrapper&& root_component_generator, Catch::Generators::GeneratorWrapper&& directory_generator, Catch::Generators::GeneratorWrapper&& filename_generator, Catch::Generators::GeneratorWrapper&& separator_generator, PathGeneratorConfiguration configuration = PathGeneratorConfiguration{} ) { return Catch::Generators::GeneratorWrapper( std::unique_ptr>( 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 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 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 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 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 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 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 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 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 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 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 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 native_relative_directory_path() { return native_relative_path(1.0); } } // end QDOC_CATCH_GENERATORS_ROOT_NAMESPACE