summaryrefslogtreecommitdiff
path: root/src/qdoc/catch_generators/tests/generators/catch_path_generator.cpp
diff options
context:
space:
mode:
authorLuca Di Sera <luca.disera@qt.io>2023-05-12 15:12:11 +0200
committerLuca Di Sera <luca.disera@qt.io>2023-05-15 13:02:08 +0200
commitac01635f461bd9f211d89f6eea833d9d928fd113 (patch)
tree4efbfd1fc2f551435605b1c72d93e372f2dbb90b /src/qdoc/catch_generators/tests/generators/catch_path_generator.cpp
parent7057d01fbb9f8f37c707b33e3b92c10a78919ddc (diff)
downloadqttools-ac01635f461bd9f211d89f6eea833d9d928fd113.tar.gz
QDoc: Move catch_generators under src
QDoc employs a support library, "catch_generators", to provide custom support for the data-generation that certain tests use or might want to use when using the Catch2 testing framework, which QDoc uses for some of its tests. "catch_generators" was currently kept under "tests/auto/qdoc" and its headers were included and used directly by related tests by relative paths. Due to a certain restructuring that is happening in QDoc, with one of the goals, among others, being to increase the locality of QDoc-related code under "src/qdoc", the "catch_generators" support library is now moved under "src/qdoc". To allow code that depended on it to keep their usages without requiring the addition of some relative paths from the "tests" directory to the "src" directory, a library target, "Qt::QDocCatchGeneratorsPrivate" was created for it. The target can be linked-to to gain access to the previously-directly-used headers, with include path "catch_generators/.*". To allow for this specific include path to work, the internal directory structure for "catch_generators" was slightly modified with the addition of some intermediate directories in between its root and the library headers. The root "CMakeLists.txt" file for the QDoc project was modified to add the moved "catch_generators" as a subdirectory, to include it in the build process. "catch_generators" contains its own test code under the subdirectory "tests". Previously the test target in "tests" included the relevant headers from the library by use of relative paths to its sources. Now that the library is exported as a target, the "CMakeLists.txt" file under "tests" was modified to rely on linking to the library itself to obtain access to the required headers. Similarly, targets under "tests/auto/qdoc" that used the dependency now link to it instead. Hence, their "CMakeLists.txt" files were modified to avoid using the dependency as an include directory and instead use "Qt::QDocCatcGeneratorsPrivate" as a library. The inclusions of the "catch_generators" headers in relevant sources was modified to respect the new "catch_generators/.*" path. Additionally, "catch_generators" exposed a top level header, "qdoc_catch_generators.h", including certain headers in the library that were expected to be commonly used by tests, so that the consumers did not have to include multiple headers when using various common generators. The header was removed in favor of including the library headers themselves now that they have a well-defined root provided by the exposed target. Sources that included the header were modified to include only their relevant headers from "catch_generators", respecting the additional granularity. Due to the Qt Project CI performing discovery of tests by configuring only the "tests" directory of the module; the moved "catch_generators", which contained its own testing code, will now escape the automated testing in CI for its own tests, as the relevant target does not reside under the "tests" directory of the repository. To avoid this issue, the "CMakeLists.txt" file under "tests/auto/qdoc", was modified to `include` the "CMakeLists.txt" file that describes "catch_generators"' test target, so as to "mirror" the test under the "tests" directory. To support working when included, the "CMakeLists.txt" file that describes "catch_generators"' test target was modified to refer to any relevant relative path by prefixing the value of `CMAKE_CURRENT_LIST_DIR`, so that the relative paths would not be incorrectly resolved when using `include`. Due to this "mirroring", the relevant test target would be configured twice when the whole repository was configured, resulting in an error. To ensure that this is not the case, the `include` directive was hidden behind `QT_BUILD_STANADALONE_TESTS`, which is not set during a general configuration of the repository but is set when CI performs test-discovery on the "tests" directory. Similarly, the "tests" directory of a repository is configured only when `QT_BUILD_TESTS` is `ON`, which further ensures that the required dependencies, such as `Qt::Test`, will be found before the directory is configured. By having moved the test declaration outside of the `tests` directory, the test will always be configured, failing to find its dependencies when this happens. To avoid the issue, the `add_subdirectory(tests)` directive in `src/qdoc/catch_generators/CMakeLists.txt` was conditioned over `QT_BUILD_TESTS`. Certain files that were missing an license header were enhanced with it, as the lack of an header will now be an issue outside the "tests" directory. Change-Id: I0bca477b7da75c121c24b8528fc9defa26a6123e Reviewed-by: Topi Reiniƶ <topi.reinio@qt.io>
Diffstat (limited to 'src/qdoc/catch_generators/tests/generators/catch_path_generator.cpp')
-rw-r--r--src/qdoc/catch_generators/tests/generators/catch_path_generator.cpp755
1 files changed, 755 insertions, 0 deletions
diff --git a/src/qdoc/catch_generators/tests/generators/catch_path_generator.cpp b/src/qdoc/catch_generators/tests/generators/catch_path_generator.cpp
new file mode 100644
index 000000000..deb33421b
--- /dev/null
+++ b/src/qdoc/catch_generators/tests/generators/catch_path_generator.cpp
@@ -0,0 +1,755 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include <catch_generators/namespaces.h>
+#include <catch_generators/generators/qchar_generator.h>
+#include <catch_generators/generators/qstring_generator.h>
+#include <catch_generators/generators/path_generator.h>
+#include <catch_generators/generators/combinators/cycle_generator.h>
+#include <catch_generators/utilities/statistics/percentages.h>
+#include <catch_generators/utilities/statistics/distribution.h>
+#include <catch_generators/utilities/semantics/copy_value.h>
+
+#include <catch_conversions/qt_catch_conversions.h>
+
+#include <catch/catch.hpp>
+
+#include <QString>
+#include <QStringList>
+#include <QRegularExpression>
+
+using namespace QDOC_CATCH_GENERATORS_ROOT_NAMESPACE;
+using namespace QDOC_CATCH_GENERATORS_UTILITIES_ABSOLUTE_NAMESPACE;
+
+using namespace Qt::StringLiterals;
+
+TEST_CASE("A path generated with a multi_device_path_probability of 1.0 always contains a device component.", "[Path][Content][SpecialCase]") {
+ QString device_component_value{"C:"};
+ auto path_generator = path(
+ Catch::Generators::value(copy_value(device_component_value)),
+ empty_string(),
+ empty_string(),
+ empty_string(),
+ empty_string(),
+ PathGeneratorConfiguration{}.set_multi_device_path_probability(1.0)
+ );
+
+ auto generated_path = GENERATE_REF(take(100, std::move(path_generator)));
+
+ REQUIRE(generated_path.contains(device_component_value));
+}
+
+TEST_CASE("A path generated with a multi_device_path_probability of 0.0 never contains a device component.", "[Path][Content][SpecialCase]") {
+ QString device_component_value{"C:"};
+ auto path_generator = path(
+ Catch::Generators::value(copy_value(device_component_value)),
+ empty_string(),
+ empty_string(),
+ empty_string(),
+ empty_string(),
+ PathGeneratorConfiguration{}.set_multi_device_path_probability(0.0)
+ );
+
+ auto generated_path = GENERATE_REF(take(100, std::move(path_generator)));
+
+ REQUIRE(!generated_path.contains(device_component_value));
+}
+
+TEST_CASE("A path generated with an absolute_path_probability of 1.0 always contains a root component.", "[Path][Content][SpecialCase]") {
+ QString root_component_value{"\\"};
+ auto path_generator = path(
+ empty_string(),
+ Catch::Generators::value(copy_value(root_component_value)),
+ empty_string(),
+ empty_string(),
+ empty_string(),
+ PathGeneratorConfiguration{}.set_absolute_path_probability(1.0)
+ );
+
+ auto generated_path = GENERATE_REF(take(100, std::move(path_generator)));
+
+ REQUIRE(generated_path.contains(root_component_value));
+}
+
+TEST_CASE("A path generated with an absolute_path_probability of 0.0 never contains a root component.", "[Path][Content][SpecialCase]") {
+ QString root_component_value{"\\"};
+ auto path_generator = path(
+ empty_string(),
+ Catch::Generators::value(copy_value(root_component_value)),
+ empty_string(),
+ empty_string(),
+ empty_string(),
+ PathGeneratorConfiguration{}.set_absolute_path_probability(0.0)
+ );
+
+ auto generated_path = GENERATE_REF(take(100, std::move(path_generator)));
+
+ REQUIRE(!generated_path.contains(root_component_value));
+}
+
+TEST_CASE("A path generated with a directory_path_probability of 1.0 always ends with a root, directory or directory followed by separator component.", "[Path][Content][SpecialCase]") {
+ QString root_component_value{"root"};
+ QString directory_component_value{"dir"};
+ QString separator_component_value{"sep"};
+
+ auto path_generator = path(
+ cycle(Catch::Generators::value(QString("device"))),
+ cycle(Catch::Generators::value(copy_value(root_component_value))),
+ cycle(Catch::Generators::value(copy_value(directory_component_value))),
+ cycle(Catch::Generators::value(QString("filename"))),
+ cycle(Catch::Generators::value(copy_value(separator_component_value))),
+ PathGeneratorConfiguration{}.set_directory_path_probability(1.0)
+ );
+
+ auto generated_path = GENERATE_REF(take(100, std::move(path_generator)));
+
+ REQUIRE((
+ generated_path.endsWith(root_component_value) ||
+ generated_path.endsWith(directory_component_value) ||
+ generated_path.endsWith(directory_component_value + separator_component_value)
+ ));
+}
+
+TEST_CASE("A path generated with a directory_path_probability of 0.0 always ends with a filename component.", "[Path][Content][SpecialCase]") {
+ QString filename_component_value{"file"};
+
+ auto path_generator = path(
+ cycle(Catch::Generators::value(QString("device"))),
+ cycle(Catch::Generators::value(QString("root"))),
+ cycle(Catch::Generators::value(QString("dir"))),
+ cycle(Catch::Generators::value(copy_value(filename_component_value))),
+ cycle(Catch::Generators::value(QString("sep"))),
+ PathGeneratorConfiguration{}.set_directory_path_probability(0.0)
+ );
+
+ auto generated_path = GENERATE_REF(take(100, std::move(path_generator)));
+
+ REQUIRE(generated_path.endsWith(filename_component_value));
+}
+
+TEST_CASE("A directory path generated with a has_trailing_separator_probability of 1.0 always ends with a separator component.", "[Path][Content][SpecialCase]") {
+ QString separator_component_value{"sep"};
+
+ auto path_generator = path(
+ cycle(Catch::Generators::value(QString("device"))),
+ cycle(Catch::Generators::value(QString("root"))),
+ cycle(Catch::Generators::value(QString("directory"))),
+ cycle(Catch::Generators::value(QString("filename"))),
+ cycle(Catch::Generators::value(copy_value(separator_component_value))),
+ PathGeneratorConfiguration{}.set_directory_path_probability(1.0).set_has_trailing_separator_probability(1.0)
+ );
+
+ auto generated_path = GENERATE_REF(take(100, std::move(path_generator)));
+
+ REQUIRE(generated_path.endsWith(separator_component_value));
+}
+
+TEST_CASE("A directory path generated with a has_trailing_separator_probability of 0.0 never ends with a separator component.", "[Path][Content][SpecialCase]") {
+ QString separator_component_value{"sep"};
+
+ auto path_generator = path(
+ cycle(Catch::Generators::value(QString("device"))),
+ cycle(Catch::Generators::value(QString("root"))),
+ cycle(Catch::Generators::value(QString("directory"))),
+ cycle(Catch::Generators::value(QString("filename"))),
+ cycle(Catch::Generators::value(copy_value(separator_component_value))),
+ PathGeneratorConfiguration{}.set_directory_path_probability(1.0).set_has_trailing_separator_probability(0.0)
+ );
+
+ auto generated_path = GENERATE_REF(take(100, std::move(path_generator)));
+
+ REQUIRE(!generated_path.endsWith(separator_component_value));
+}
+
+SCENARIO("Binding a path to a component range", "[Path][Bounds]") {
+ GIVEN("A minimum amount of components") {
+ auto minimum_components_amount = GENERATE(take(100, random(std::size_t{1}, std::size_t{100})));
+
+ AND_GIVEN("A maximum amount of components that is greater or equal than the minimum amount of components") {
+ auto maximum_components_amount = GENERATE_COPY(take(100, random(minimum_components_amount, std::size_t{100})));
+
+ WHEN("A path is generated from those bounds") {
+ QString countable_component_value{"a"};
+
+ QString generated_path = GENERATE_COPY(
+ take(1,
+ path(
+ empty_string(),
+ empty_string(),
+ cycle(Catch::Generators::value(copy_value(countable_component_value))),
+ cycle(Catch::Generators::value(copy_value(countable_component_value))),
+ empty_string(),
+ PathGeneratorConfiguration{}.set_minimum_components_amount(minimum_components_amount).set_maximum_components_amount(maximum_components_amount)
+ )
+ )
+ );
+
+ THEN("The amount of non device, non root, non separator components in the generated path is in the range [minimum_components_amount, maximum_components_amount]") {
+ std::size_t components_amount{static_cast<std::size_t>(generated_path.count(countable_component_value))};
+
+ REQUIRE(components_amount >= minimum_components_amount);
+ REQUIRE(components_amount <= maximum_components_amount);
+ }
+ }
+ }
+ }
+}
+
+TEST_CASE(
+ "When the maximum amount of components and the minimum amount of components are equal, all generated paths have the same amount of non device, non root, non separator components",
+ "[Path][Bounds][SpecialCase]")
+{
+ auto components_amount = GENERATE(take(10, random(std::size_t{1}, std::size_t{100})));
+
+ QString countable_component_value{"a"};
+ QString generated_path = GENERATE_COPY(
+ take(10,
+ path(
+ empty_string(),
+ empty_string(),
+ cycle(Catch::Generators::value(copy_value(countable_component_value))),
+ cycle(Catch::Generators::value(copy_value(countable_component_value))),
+ empty_string(),
+ PathGeneratorConfiguration{}.set_minimum_components_amount(components_amount).set_maximum_components_amount(components_amount)
+ )
+ )
+ );
+
+ REQUIRE(static_cast<std::size_t>(generated_path.count(countable_component_value)) == components_amount);
+}
+
+SCENARIO("The format of a path", "[Path][Contents]") {
+ GIVEN("A series of components generators") {
+ // TODO: Could probably move this to the global scope to
+ // lighen the tests.
+ QString device_component_value{"device"};
+ QString root_component_value{"root"};
+ QString directory_component_value{"dir"};
+ QString filename_component_value{"file"};
+ QString separator_component_value{"sep"};
+
+ auto device_component_generator = cycle(Catch::Generators::value(copy_value(device_component_value)));
+ auto root_component_generator = cycle(Catch::Generators::value(copy_value(root_component_value)));
+ auto directory_component_generator = cycle(Catch::Generators::value(copy_value(directory_component_value)));
+ auto filename_component_generator = cycle(Catch::Generators::value(copy_value(filename_component_value)));
+ auto separator_component_generator = cycle(Catch::Generators::value(copy_value(separator_component_value)));
+
+ AND_GIVEN("A generator of paths using those components generator") {
+ // TODO: We should actually randomize the configuration by
+ // making a simple generator for it.
+ auto path_generator = path(
+ std::move(device_component_generator),
+ std::move(root_component_generator),
+ std::move(directory_component_generator),
+ std::move(filename_component_generator),
+ std::move(separator_component_generator)
+ );
+
+ WHEN("A path is generated from that generator") {
+ auto generated_path = GENERATE_REF(take(10, std::move(path_generator)));
+
+ THEN("At most one device component is in the generated path") {
+ REQUIRE(generated_path.count(device_component_value) <= 1);
+ }
+
+ THEN("At most one root component is in the generated path") {
+ REQUIRE(generated_path.count(root_component_value) <= 1);
+ }
+
+ THEN("At most one filename component is in the generated path") {
+ REQUIRE(generated_path.count(filename_component_value) <= 1);
+ }
+
+ THEN("At least one non device, non root, non separator component is in the generated path") {
+ REQUIRE((generated_path.contains(directory_component_value) || generated_path.contains(filename_component_value)));
+ }
+
+ THEN("There is a separator component between any two successive directory components") {
+ // REMARK: To test this condition, which is not
+ // easy to test directly, as, if the generator is
+ // working as it should, the concept of successive
+ // directories stops existing.
+ // To test it, then, we split the condition into
+ // two parts, that are easier to test, that
+ // achieve the same effect.
+ // First, if all directories have a separator
+ // component between them, it is impossible to
+ // have a directory component that is directly
+ // followed by another directory component.
+ // Second, when this holds, any two directory
+ // components must have one or more non-directory
+ // components between them.
+ // For those directories that have exactly one
+ // component between them, it must be a separator.
+ // This is equivalent to the original condition as
+ // long as it is not allowed for anything else to
+ // be between two directory components that have
+ // exactly one component between them.
+ // This is true at the time of writing of this
+ // test, such that this will work correctly, but
+ // if this changes the test is invalidated.
+ // If a test for the original condition is found
+ // that is not contrived (as it is possible to
+ // test the original condition but it is a bit
+ // more complex than we would like the test to
+ // be), it should replace this current
+ // implementation to improve the resiliency of the
+ // test.
+ REQUIRE_FALSE(generated_path.contains(directory_component_value + directory_component_value));
+
+ auto successive_directories_re{
+ QRegularExpression(u"%1(%2)%3"_s.arg(directory_component_value)
+ .arg(QStringList{device_component_value, root_component_value, filename_component_value, separator_component_value}.join("|"))
+ .arg(directory_component_value)
+ )};
+
+ auto successive_directories_match(successive_directories_re.match(generated_path));
+ while (successive_directories_match.hasMatch()) {
+ auto in_between_component{successive_directories_match.captured(1)};
+
+ // TODO: Having this in a loop makes it so
+ // the amount of assertions will vary slightly
+ // per-run.
+ // It would be better to avoid this, even if
+ // it should not really be a problem
+ // generally.
+ // Try to find a better way to express this
+ // condition that does not require a loop.
+ // This could be as easy as just collection
+ // the results and then using a std::all_of.
+ REQUIRE(in_between_component == separator_component_value);
+
+ successive_directories_match = successive_directories_re.match(generated_path, successive_directories_match.capturedEnd(1));
+ }
+ }
+
+
+ THEN("There is a separator component between each successive directory and filename components") {
+ REQUIRE_FALSE(generated_path.contains(directory_component_value + filename_component_value));
+
+ auto successive_directory_filename_re{
+ QRegularExpression(u"%1(%2)%3"_s.arg(directory_component_value)
+ .arg(QStringList{device_component_value, root_component_value, filename_component_value, separator_component_value}.join("|"))
+ .arg(filename_component_value)
+ )};
+
+ auto successive_directory_filename_match(successive_directory_filename_re.match(generated_path));
+ while (successive_directory_filename_match.hasMatch()) {
+ auto in_between_component{successive_directory_filename_match.captured(1)};
+
+ REQUIRE(in_between_component == separator_component_value);
+
+ successive_directory_filename_match = successive_directory_filename_re.match(generated_path, successive_directory_filename_match.capturedEnd(1));
+ }
+ }
+ }
+ }
+
+ AND_GIVEN("A generator of paths using those components generator that generates Multi-Device paths") {
+ auto path_generator = path(
+ std::move(device_component_generator),
+ std::move(root_component_generator),
+ std::move(directory_component_generator),
+ std::move(filename_component_generator),
+ std::move(separator_component_generator),
+ PathGeneratorConfiguration{}.set_multi_device_path_probability(1.0)
+ );
+
+ WHEN("A path is generated from that generator") {
+ auto generated_path = GENERATE_REF(take(10, std::move(path_generator)));
+
+ THEN("Exactly one device component is in the generated path") {
+ REQUIRE(generated_path.count(device_component_value) == 1);
+
+ AND_THEN("The device component is the first component in the generated path") {
+ REQUIRE(generated_path.startsWith(device_component_value));
+ }
+ }
+ }
+ }
+
+ AND_GIVEN("A generator of paths using those components generator that generates Absolute paths") {
+ auto path_generator = path(
+ std::move(device_component_generator),
+ std::move(root_component_generator),
+ std::move(directory_component_generator),
+ std::move(filename_component_generator),
+ std::move(separator_component_generator),
+ PathGeneratorConfiguration{}.set_absolute_path_probability(1.0)
+ );
+
+ WHEN("A path is generated from that generator") {
+ auto generated_path = GENERATE_REF(take(10, std::move(path_generator)));
+
+ THEN("Exactly one root component is in the generated path") {
+ REQUIRE(generated_path.count(root_component_value) == 1);
+ }
+ }
+ }
+
+ AND_GIVEN("A generator of paths using those components generator that generates Absolute paths that are not Multi-Device") {
+ auto path_generator = path(
+ std::move(device_component_generator),
+ std::move(root_component_generator),
+ std::move(directory_component_generator),
+ std::move(filename_component_generator),
+ std::move(separator_component_generator),
+ PathGeneratorConfiguration{}.set_multi_device_path_probability(0.0).set_absolute_path_probability(1.0)
+ );
+
+ WHEN("A path is generated from that generator") {
+ auto generated_path = GENERATE_REF(take(10, std::move(path_generator)));
+
+ THEN("The root component is the first component in the generated path") {
+ REQUIRE(generated_path.startsWith(root_component_value));
+ }
+ }
+ }
+
+ AND_GIVEN("A generator of paths using those components generator that generates Multi-Device, Absolute paths") {
+ auto path_generator = path(
+ std::move(device_component_generator),
+ std::move(root_component_generator),
+ std::move(directory_component_generator),
+ std::move(filename_component_generator),
+ std::move(separator_component_generator),
+ PathGeneratorConfiguration{}.set_multi_device_path_probability(1.0).set_absolute_path_probability(1.0)
+ );
+
+ WHEN("A path is generated from that generator") {
+ auto generated_path = GENERATE_REF(take(10, std::move(path_generator)));
+
+ THEN("The root component succeeds the device component in the generated path") {
+ REQUIRE(generated_path.contains(device_component_value + root_component_value));
+ }
+ }
+ }
+
+ AND_GIVEN("A generator of paths using those components generator that generates paths that are To a Directory and do not Have a Trailing Separator") {
+ auto path_generator = path(
+ std::move(device_component_generator),
+ std::move(root_component_generator),
+ std::move(directory_component_generator),
+ std::move(filename_component_generator),
+ std::move(separator_component_generator),
+ PathGeneratorConfiguration{}.set_directory_path_probability(1.0).set_has_trailing_separator_probability(0.0)
+ );
+
+ WHEN("A path is generated from that generator") {
+ auto generated_path = GENERATE_REF(take(10, std::move(path_generator)));
+
+ THEN("The last component of in the path is a directory component") {
+ REQUIRE(generated_path.endsWith(directory_component_value));
+ }
+ }
+ }
+
+ AND_GIVEN("A generator of paths using those components generator that generates paths that are To a Directory and Have a Trailing Separator") {
+ auto path_generator = path(
+ std::move(device_component_generator),
+ std::move(root_component_generator),
+ std::move(directory_component_generator),
+ std::move(filename_component_generator),
+ std::move(separator_component_generator),
+ PathGeneratorConfiguration{}.set_directory_path_probability(1.0).set_has_trailing_separator_probability(1.0)
+ );
+
+ WHEN("A path is generated from that generator") {
+ auto generated_path = GENERATE_REF(take(10, std::move(path_generator)));
+
+ THEN("The last component in the path is a separator component that is preceded by a directory component") {
+ REQUIRE(generated_path.endsWith(directory_component_value + separator_component_value));
+ }
+ }
+ }
+
+
+ AND_GIVEN("A generator of paths using those components generator that generates paths that are To a File") {
+ auto path_generator = path(
+ std::move(device_component_generator),
+ std::move(root_component_generator),
+ std::move(directory_component_generator),
+ std::move(filename_component_generator),
+ std::move(separator_component_generator),
+ PathGeneratorConfiguration{}.set_directory_path_probability(0.0)
+ );
+
+ WHEN("A path is generated from that generator") {
+ auto generated_path = GENERATE_REF(take(10, std::move(path_generator)));
+
+ THEN("Exactly one filename component is in the path") {
+ REQUIRE(generated_path.contains(filename_component_value));
+
+ AND_THEN("The filename component is the last component in the path") {
+ REQUIRE(generated_path.endsWith(filename_component_value));
+ }
+ }
+ }
+ }
+ }
+}
+
+// REMARK: [mayfail][distribution]
+SCENARIO("Observing the distribution of paths based on their configuration", "[Path][Statistics][!mayfail]") {
+ GIVEN("A series of components generators") {
+ QString device_component_value{"device"};
+ QString root_component_value{"root"};
+ QString directory_component_value{"dir"};
+ QString filename_component_value{"file"};
+ QString separator_component_value{"sep"};
+
+ auto device_component_generator = cycle(Catch::Generators::value(copy_value(device_component_value)));
+ auto root_component_generator = cycle(Catch::Generators::value(copy_value(root_component_value)));
+ auto directory_component_generator = cycle(Catch::Generators::value(copy_value(directory_component_value)));
+ auto filename_component_generator = cycle(Catch::Generators::value(copy_value(filename_component_value)));
+ auto separator_component_generator = cycle(Catch::Generators::value(copy_value(separator_component_value)));
+
+ AND_GIVEN("A generator of paths using those components generator that produces paths that are Multi-Device with a probability of n") {
+ double multi_device_path_probability = GENERATE(take(10, random(0.0, 1.0)));
+
+ auto path_generator = path(
+ std::move(device_component_generator),
+ std::move(root_component_generator),
+ std::move(directory_component_generator),
+ std::move(filename_component_generator),
+ std::move(separator_component_generator),
+ PathGeneratorConfiguration{}.set_multi_device_path_probability(multi_device_path_probability)
+ );
+
+ WHEN("A certain amount of paths are generated from that generator") {
+ auto paths = GENERATE_REF(take(1, chunk(10000, std::move(path_generator))));
+
+ THEN("The amount of paths that are Multi-Device approximately respects the given probability and the amount of paths that are not approximately respects a probability of 1 - n") {
+ auto maybe_distribution_error{respects_distribution(
+ std::move(paths),
+ [&device_component_value](const QString& path){ return (path.startsWith(device_component_value)) ? "Multi-Device" : "Non Multi-Device"; },
+ [multi_device_path_probability](const QString& key){ return probability_to_percentage((key == "Multi-Device") ? multi_device_path_probability : 1 - multi_device_path_probability); }
+ )};
+
+ REQUIRE_FALSE(maybe_distribution_error);
+ }
+ }
+ }
+
+ AND_GIVEN("A generator of paths using those components generator that produces paths that are Absolute with a probability of n") {
+ double absolute_path_probability = GENERATE(take(10, random(0.0, 1.0)));
+
+ auto path_generator = path(
+ std::move(device_component_generator),
+ std::move(root_component_generator),
+ std::move(directory_component_generator),
+ std::move(filename_component_generator),
+ std::move(separator_component_generator),
+ PathGeneratorConfiguration{}.set_absolute_path_probability(absolute_path_probability)
+ );
+
+ WHEN("A certain amount of paths are generated from that generator") {
+ auto paths = GENERATE_REF(take(1, chunk(10000, std::move(path_generator))));
+
+ THEN("The amount of paths that are Absolute approximately respects the given probability and the amount of paths that are Relative approximately respects a probability of 1 - n") {
+ auto maybe_distribution_error{respects_distribution(
+ std::move(paths),
+ [&root_component_value](const QString& path){ return (path.contains(root_component_value)) ? "Absolute" : "Relative"; },
+ [absolute_path_probability](const QString& key){ return probability_to_percentage((key == "Absolute") ? absolute_path_probability : 1 - absolute_path_probability); }
+ )};
+
+ REQUIRE_FALSE(maybe_distribution_error);
+ }
+ }
+ }
+
+ AND_GIVEN("A generator of paths using those components generator that produces paths that are To a Directory with a probability of n") {
+ double directory_path_probability = GENERATE(take(10, random(0.0, 1.0)));
+
+ auto path_generator = path(
+ std::move(device_component_generator),
+ std::move(root_component_generator),
+ std::move(directory_component_generator),
+ std::move(filename_component_generator),
+ std::move(separator_component_generator),
+ PathGeneratorConfiguration{}.set_directory_path_probability(directory_path_probability)
+ );
+
+ WHEN("A certain amount of paths are generated from that generator") {
+ auto paths = GENERATE_REF(take(1, chunk(10000, std::move(path_generator))));
+
+ THEN("The amount of paths that are To a Directory approximately respects the given probability and the amount of paths that are To a File approximately respects a probability of 1 - n") {
+ auto maybe_distribution_error{respects_distribution(
+ std::move(paths),
+ [&filename_component_value](const QString& path){ return (path.contains(filename_component_value)) ? "To a File" : "To a Directory"; },
+ [directory_path_probability](const QString& key){ return probability_to_percentage((key == "To a Directory") ? directory_path_probability : 1 - directory_path_probability); }
+ )};
+
+ REQUIRE_FALSE(maybe_distribution_error);
+ }
+ }
+ }
+
+ AND_GIVEN("A generator of paths using those components generator that produces paths that are To a Directory with a probability of n to Have a Trailing Separator") {
+ double has_trailing_separator_probability = GENERATE(take(10, random(0.0, 1.0)));
+
+ auto path_generator = path(
+ std::move(device_component_generator),
+ std::move(root_component_generator),
+ std::move(directory_component_generator),
+ std::move(filename_component_generator),
+ std::move(separator_component_generator),
+ PathGeneratorConfiguration{}.set_directory_path_probability(1.0).set_has_trailing_separator_probability(has_trailing_separator_probability)
+ );
+
+ WHEN("A certain amount of paths are generated from that generator") {
+ auto paths = GENERATE_REF(take(1, chunk(10000, std::move(path_generator))));
+
+ THEN("The amount of paths that are Have a Trailing Separator approximately respects the given probability and the amount of paths that do not Have a Trailing Separator approximately respects a probability of 1 - n") {
+ auto maybe_distribution_error{respects_distribution(
+ std::move(paths),
+ [&separator_component_value](const QString& path){ return (path.endsWith(separator_component_value)) ? "Have a Trailing Separator" : "Doesn't Have a Trailing Separator"; },
+ [has_trailing_separator_probability](const QString& key){ return probability_to_percentage((key == "Have a Trailing Separator") ? has_trailing_separator_probability : 1 - has_trailing_separator_probability); }
+ )};
+
+ REQUIRE_FALSE(maybe_distribution_error);
+ }
+ }
+ }
+ }
+}
+
+TEST_CASE("The first component of the passed in device components generator is not lost", "[Path][GeneratorFirstElement][SpecialCase]") {
+ QString device_component_generator_first_value{"device"};
+
+ auto generated_path = GENERATE_COPY(take(1,
+ path(
+ values({device_component_generator_first_value, QString{""}}),
+ empty_string(),
+ empty_string(),
+ empty_string(),
+ empty_string(),
+ PathGeneratorConfiguration{}
+ .set_multi_device_path_probability(1.0)
+ .set_minimum_components_amount(1)
+ .set_maximum_components_amount(1)
+ )
+ ));
+
+ REQUIRE(generated_path.contains(device_component_generator_first_value));
+}
+
+TEST_CASE("The first component of the passed in root components generator is not lost", "[Path][GeneratorFirstElement][SpecialCase]") {
+ QString root_component_generator_first_value{"root"};
+
+ auto generated_path = GENERATE_COPY(take(1,
+ path(
+ empty_string(),
+ values({root_component_generator_first_value, QString{""}}),
+ empty_string(),
+ empty_string(),
+ empty_string(),
+ PathGeneratorConfiguration{}
+ .set_absolute_path_probability(1.0)
+ .set_minimum_components_amount(1)
+ .set_maximum_components_amount(1)
+ )
+ ));
+
+ REQUIRE(generated_path.contains(root_component_generator_first_value));
+}
+
+TEST_CASE("The first component of the passed in directory components generator is not lost", "[Path][GeneratorFirstElement][SpecialCase]") {
+ QString directory_component_generator_first_value{"dir"};
+
+ auto generated_path = GENERATE_COPY(take(1,
+ path(
+ empty_string(),
+ empty_string(),
+ values({directory_component_generator_first_value, QString{""}}),
+ empty_string(),
+ empty_string(),
+ PathGeneratorConfiguration{}
+ .set_directory_path_probability(1.0)
+ .set_minimum_components_amount(1)
+ .set_maximum_components_amount(1)
+ )
+ ));
+
+ REQUIRE(generated_path.contains(directory_component_generator_first_value));
+}
+
+TEST_CASE("The first component of the passed in filename components generator is not lost", "[Path][GeneratorFirstElement][SpecialCase]") {
+ QString filename_component_generator_first_value{"dir"};
+
+ auto generated_path = GENERATE_COPY(take(1,
+ path(
+ empty_string(),
+ empty_string(),
+ empty_string(),
+ values({filename_component_generator_first_value, QString{""}}),
+ empty_string(),
+ PathGeneratorConfiguration{}
+ .set_directory_path_probability(0.0)
+ .set_minimum_components_amount(1)
+ .set_maximum_components_amount(1)
+ )
+ ));
+
+ REQUIRE(generated_path.contains(filename_component_generator_first_value));
+}
+
+TEST_CASE("The first component of the passed in separator components generator is not lost", "[Path][GeneratorFirstElement][SpecialCase]") {
+ QString separator_component_generator_first_value{"sep"};
+
+ auto generated_path = GENERATE_COPY(take(1,
+ path(
+ empty_string(),
+ empty_string(),
+ empty_string(),
+ empty_string(),
+ values({separator_component_generator_first_value, QString{""}}),
+ PathGeneratorConfiguration{}
+ .set_directory_path_probability(0.0)
+ .set_minimum_components_amount(2)
+ .set_maximum_components_amount(2)
+ )
+ ));
+
+ REQUIRE(generated_path.contains(separator_component_generator_first_value));
+}
+
+SCENARIO("Generating paths that are suitable to be used on POSIX systems", "[Path][POSIX][Content]") {
+ GIVEN("A generator that generates Strings representing paths on a POSIX system that are portable") {
+ auto path_generator = relaxed_portable_posix_path();
+
+ WHEN("A path is generated from it") {
+ auto generated_path = GENERATE_REF(take(100, std::move(path_generator)));
+
+ THEN("The path is composed only by one or more characters in the class [-_./a-zA-Z0-9]") {
+ REQUIRE(QRegularExpression{R"(\A[-_.\/a-zA-Z0-9]+\z)"}.match(generated_path).hasMatch());
+ }
+ }
+ }
+}
+
+SCENARIO("Generating paths that are suitable to be used on Windows", "[Path][Windows][Content]") {
+ GIVEN("A generator that generates Strings representing paths on a Windows system") {
+ auto path_generator = traditional_dos_path();
+
+ WHEN("A path is generated from it") {
+ auto generated_path = GENERATE_REF(take(100, std::move(path_generator)));
+
+ CAPTURE(generated_path);
+
+ THEN("The path starts with an uppercase letter followed by a colon, a backward or forward slash or a character in the class [-_.a-zA-Z0-9]") {
+ QRegularExpression beginning_re{"([A-Z]:|\\|\\/|[-_.a-zA-Z0-9])"};
+
+ auto beginning_match{beginning_re.match(generated_path)};
+
+ REQUIRE(beginning_match.hasMatch());
+
+ generated_path.remove(0, beginning_match.capturedEnd());
+
+ AND_THEN("The rest of the path is composed by zero or more characters in the class [-_./\\a-zA-Z0-9]") {
+ REQUIRE(QRegularExpression{R"(\A[-_.\/\\a-zA-Z0-9]*\z)"}.match(generated_path).hasMatch());
+ }
+ }
+ }
+ }
+}