Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
path_generator.h
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4// TODO: Change the include paths to implicitly consider
5// `catch_generators` a root directory and change the CMakeLists.txt
6// file to make this possible.
7
8#include "../namespaces.h"
11#include "../utilities/semantics/move_into_vector.h"
12#include "../utilities/semantics/generator_handler.h"
13
14#if defined(Q_OS_WINDOWS)
15
16 #include "combinators/cycle_generator.h"
17
18#endif
19
20#include <catch/catch.hpp>
21
22#include <random>
23
24#include <QChar>
25#include <QString>
26#include <QStringList>
27#include <QRegularExpression>
28
29#if defined(Q_OS_WINDOWS)
30
31 #include <QStorageInfo>
32
33#endif
34
36
37
76
77 /*!
78 * \class PathGeneratorConfiguration
79 * \brief Defines some parameters to customize the generation of
80 * paths by a PathGenerator.
81 */
82
83 /*!
84 * \variable PathGeneratorConfiguration::multi_device_path_probability
85 *
86 * Every path produced by a PathGenerator configured with a
87 * mutli_device_path_probability of n has a probability of n to be
88 * \e {Multi-Device} and a probability of 1.0 - n to not be \a
89 * {Multi-Device}.
90 *
91 * multi_device_path_probability should be a value in the range [0.0,
92 * 1.0].
93 */
94
95 /*!
96 * \variable PathGeneratorConfiguration::absolute_path_probability
97 *
98 * Every path produced by a PathGenerator configured with an
99 * absolute_path_probability of n has a probability of n to be \e
100 * {Absolute} and a probability of 1.0 - n to be \e {Relative}.
101 *
102 * absolute_path_probability should be a value in the range [0.0,
103 * 1.0].
104 */
105
106 /*!
107 * \variable PathGeneratorConfiguration::directory_path_probability
108 *
109 * Every path produced by a PathGenerator configured with a
110 * directory_path_probability of n has a probability of n to be \e
111 * {To a Directory} and a probability of 1.0 - n to be \e {To a
112 * File}.
113 *
114 * directory_path_probability should be a value in the range [0.0,
115 * 1.0].
116 */
117
118 /*!
119 * \variable PathGeneratorConfiguration::has_trailing_separator_probability
120 *
121 * Every path produced by a PathGenerator configured with an
122 * has_trailing_separator_probability of n has a probability of n
123 * to \e {Have a Trailing Separator} and a probability of 1.0 - n
124 * to not \e {Have a Trailing Separator}, when this is applicable.
125 *
126 * has_trailing_separator_probability should be a value in the
127 * range [0.0, 1.0].
128 */
129
130 /*!
131 * \variable PathGeneratorConfiguration::minimum_components_amount
132 *
133 * Every path produced by a PathGenerator configured with a
134 * minimum_components_amount of n will be the concatenation of at
135 * least n non \e {device}, non \e {root}, non \e {separator}
136 * components.
137 *
138 * minimum_components_amount should be greater than zero and less
139 * than maximum_components_amount.
140 */
141
142 /*!
143 * \variable PathGeneratorConfiguration::maximum_components_amount
144 *
145 * Every path produced by a PathGenerator configured with a
146 * maximum_components_amount of n will be the concatenation of at
147 * most n non \e {device}, non \e {root}, non \e {separator} components.
148 *
149 * maximum_components_amount should be greater than or equal to
150 * minimum_components_amount.
151 */
152
153
155
157 public:
159 Catch::Generators::GeneratorWrapper<QString>&& device_component_generator,
160 Catch::Generators::GeneratorWrapper<QString>&& root_component_generator,
161 Catch::Generators::GeneratorWrapper<QString>&& directory_component_generator,
162 Catch::Generators::GeneratorWrapper<QString>&& filename_component_generator,
163 Catch::Generators::GeneratorWrapper<QString>&& separator_component_generator,
170 random_engine{std::random_device{}()},
172 is_multi_device_distribution{configuration.multi_device_path_probability},
173 is_absolute_path_distribution{configuration.absolute_path_probability},
174 is_directory_path_distribution{configuration.directory_path_probability},
175 has_trailing_separator{configuration.has_trailing_separator_probability},
177 {
178 assert(configuration.minimum_components_amount > 0);
180
181 if (!next())
182 Catch::throw_exception("Not enough values to initialize the first string");
183 }
184
185 QString const& get() const override { return current_path; }
186
187 bool next() override {
188 std::size_t components_amount{components_amount_distribution(random_engine)};
189
190 current_path = "";
191
192 // REMARK: As per our specification of a path, we
193 // do not count device components, and separators,
194 // when considering the amount of components in a
195 // path.
196 // This is a tradeoff that is not necessarily
197 // precise.
198 // Counting those kinds of components, on one
199 // hand, would allow a device component to stands
200 // on its own as a path, for example "C:", which
201 // might actually be correct in some path format.
202 // On the other hand, counting those kinds of
203 // components makes the construction of paths for
204 // our model much more complex with regards, for
205 // example, to the amount of component.
206 //
207 // Counting device components, since they can
208 // appear both in relative and absolute paths,
209 // makes the minimum amount of components
210 // different for different kinds of paths.
211 //
212 // Since absolute paths always require a root
213 // component, the minimum amount of components for
214 // a multi-device absolute path is 2.
215 //
216 // But an absolute path that is not multi-device
217 // would only require one minimum component.
218 //
219 // Similarly, problems arise with the existence of
220 // Windows' relative multi-device path, which
221 // require a leading separator component after a
222 // device component.
223 //
224 // This problem mostly comes from our model
225 // simplifying the definition of paths quite a bit
226 // into binary-forms.
227 // This simplifies the code and its structure,
228 // sacrificing some precision.
229 // The lost precision is almost none for POSIX
230 // based paths, but is graver for DOS paths, since
231 // they have a more complex specification.
232 //
233 // Currently, we expect that the paths that QDoc
234 // will encounter will mostly be in POSIX-like
235 // forms, even on Windows, and aim to support
236 // that, such that the simplification of code is
237 // considered a better tradeoff compared to the
238 // loss of precision.
239 //
240 // If this changes, the model should be changed to
241 // pursue a Windows-first modeling, moving the
242 // categorization of paths from the current binary
243 // model to the absolute, drive-relative and
244 // relative triptych that Windows uses.
245 // This more complex model should be able to
246 // completely describe posix paths too, making it
247 // a superior choice as long as the complexity is
248 // warranted.
249 //
250 // Do note that the model similarly can become
251 // inconsistent when used to generate format of
252 // paths such as the one used in some resource
253 // systems.
254 // Those are considered out-of-scope for our needs
255 // and were not taken into account when developing
256 // this generator.
257 if (is_multi_device_distribution(random_engine)) {
258 if (!device_component_generator.next()) return false;
259 current_path += device_component_generator.get();
260 }
261
262 // REMARK: Similarly to not counting other form of
263 // components, we do not count root components
264 // towards the amounts of components that the path
265 // has to simplify the code.
266 // To support the "special" root path on, for
267 // example, posix systems, we require a more
268 // complex branching logic that changes based on
269 // the path being absolute or not.
270 //
271 // We don't expect root to be a particularly
272 // useful path for QDoc purposes and expect to not
273 // have to consider it for our tests.
274 // If consideration for it become required, it is
275 // possible to test it directly in the affected
276 // systemss as a special case.
277 //
278 // If most systems are affected by the handling of
279 // a root path, then the model should be slightly
280 // changed to accommodate its generation.
281 if (is_absolute_path_distribution(random_engine)) {
282 if (!root_component_generator.next()) return false;
283
284 current_path += root_component_generator.get();
285 }
286
287 std::size_t prefix_components_amount{std::max(std::size_t{1}, components_amount) - 1};
288 while (prefix_components_amount > 0) {
289 if (!directory_component_generator.next()) return false;
290 if (!separator_component_generator.next()) return false;
291
292 current_path += directory_component_generator.get() + separator_component_generator.get();
293 --prefix_components_amount;
294 }
295
296 if (is_directory_path_distribution(random_engine)) {
297 if (!directory_component_generator.next()) return false;
298 current_path += directory_component_generator.get();
299
300 if (has_trailing_separator(random_engine)) {
301 if (!separator_component_generator.next()) return false;
302 current_path += separator_component_generator.get();
303 }
304 } else {
305 if (!filename_component_generator.next()) return false;
306 current_path += filename_component_generator.get();
307 }
308
309 return true;
310 }
311
312 private:
313 Catch::Generators::GeneratorWrapper<QString> device_component_generator;
314 Catch::Generators::GeneratorWrapper<QString> root_component_generator;
315 Catch::Generators::GeneratorWrapper<QString> directory_component_generator;
316 Catch::Generators::GeneratorWrapper<QString> filename_component_generator;
317 Catch::Generators::GeneratorWrapper<QString> separator_component_generator;
318
319 std::mt19937 random_engine;
320 std::uniform_int_distribution<std::size_t> components_amount_distribution;
321 std::bernoulli_distribution is_multi_device_distribution;
322 std::bernoulli_distribution is_absolute_path_distribution;
323 std::bernoulli_distribution is_directory_path_distribution;
324 std::bernoulli_distribution has_trailing_separator;
325
326 QString current_path;
327 };
328
329 } // end QDOC_CATCH_GENERATORS_PRIVATE_NAMESPACE
330
331/*!
332 * Returns a generator that produces QStrings that represent a
333 * path in a filesystem.
334 *
335 * A path is formed by the following components, loosely based
336 * on the abstraction that is used by std::filesystem::path:
337 *
338 * \list
339 * \li \b {device}:
340 * Represents the device on the filesystem that
341 * the path should be considered in terms of.
342 * This is an optional components that is sometimes
343 * present on multi-device systems, such as Windows, to
344 * distinguish which device the path refers to.
345 * When present, it always appears before any other
346 * component.
347 * \li \b {root}:
348 * A special sequence that marks the path as absolute.
349 * This is an optional component that is present, always,
350 * in absolute paths.
351 * \li \b {directory}:
352 * A component that represents a directory on the
353 * filesystem that the path "passes-trough".
354 * Zero or more of this components can be present in the
355 * path.
356 * A path pointing to a directory on the filesystem that
357 * is not \e {root} always ends with a component of this
358 * type.
359 * \li \b {filename}:
360 * A component that represents a file on the
361 * filesystem.
362 * When this component is present, it is present only once
363 * and always as the last component of the path.
364 * A path that has such a component is a path that points
365 * to a file on the filesystem.
366 * For some path formats, there is no difference in the
367 * format of a \e {filename} and a \e {directory}.
368 * \li \b {separator}:
369 * A component that is interleaved between other types of
370 * components to separate them so that they are
371 * recognizable.
372 * A path that points to a directory on the filesystem may
373 * sometimes have a \e {separator} at the end, after the
374 * ending \e {directory} component.
375 * \endlist
376 *
377 * Each component is representable as a string and a path is a
378 * concatenation of the string representation of some
379 * components, with the following rules:
380 *
381 * \list
382 * \li There is at most one \e {device} component.
383 * \li If a \e {device} component is present it always
384 * precedes all other components.
385 * \li There is at most one \e {root} component.
386 * \li If a \e {root} component is present it:
387 * \list
388 * \li Succeeds the \e {device} component if it is present.
389 * \li Precedes every other components if the \e {device}
390 * component is not present.
391 * \endlist
392 * \li There are zero or more \e {directory} component.
393 * \li There is at most one \e {filename} component.
394 * \li If a \e {filename} component is present it always
395 * succeeds all other components.
396 * \li Between any two successive \e {directory} components
397 * there is a \e {separator} component.
398 * \li Between each successive \e {directory} and \e
399 * {filename} component there is a \e {separator} component.
400 * \li If the last component is a \e {directory} component it
401 * can be optionally followed by a \e {separator} component.
402 * \li At least one component that is not a \e {device}, a \e
403 * {root} or \e {separator} component is present.
404 * \endlist
405 *
406 * For example, if "C:" is a \e {device} component, "\\" is a
407 * \e {root} component, "\\" is a \e {separator} component,
408 * "directory" is a \e {directory} component and "filename" is
409 * a \e {filename} component, the following are all paths:
410 *
411 * "C:\\directory", "C:\\directory\\directory", "C:filename",
412 * "directory\\directory\\", "\\directory\\filename", "filename".
413 *
414 * While the following aren't:
415 *
416 * "C:", "C:\\", "directory\\C:", "foo", "C:filename\\",
417 * "filename\\directory\\filename", "filename\\filename",
418 * "directorydirectory"."
419 *
420 * The format of different components type can be the same.
421 * For example, the \e {root} and \e {separator} component in
422 * the above example.
423 * For the purpose of generation, we do not care about the
424 * format itself and consider a component of a certain type
425 * depending only on how it is generated/where it is generated
426 * from.
427 *
428 * For example, if every component is formatted as the string
429 * "a", the string "aaa" could be a generated path.
430 * By the string alone, it is not possible to simply discern
431 * which components form it, but it would be possible to
432 * generate it if the first "a" is a \a {device} component,
433 * the second "a" is a \e {root} component and the third "a"
434 * is a \e {directory} or \e {filename} component.
435 *
436 * A path, is further said to have some properties, pairs of
437 * which are exclusive to each other.
438 *
439 * A path is said to be:
440 *
441 * \list
442 * \li \b {Multi-Device}:
443 * When it contains a \e {device} component.
444 * \li \b {Absolute}:
445 * When it contains a \e {root} component.
446 * If the path is \e {Absolute} it is not \e {Relative}.
447 * \li \b {Relative}:
448 * When it does not contain a \e {root} component.
449 * If the path is \e {Relative} it is not \e {Absolute}.
450 * \li \b {To a Directory}:
451 * When its last component is a \e {directory} component
452 * or a \e {directory} component followed by a \e
453 * {separator} component.
454 * If the path is \e {To a Directory} it is not \e {To a
455 * File}.
456 * \li \b {To a File}:
457 * When its last component is a \e {filename}.
458 * If the path is \e {To a File} it is not \e {To a
459 * Directory}.
460 * \endlist
461 *
462 * All path are \e {Relative/Absolute}, \e {To a
463 * Directory/To a File} and \e {Multi-Device} or not.
464 *
465 * Furthermore, a path that is \e {To a Directory} and whose
466 * last component is a \e {separator} component is said to \e
467 * {Have a Trailing Separator}.
468 */
470 Catch::Generators::GeneratorWrapper<QString>&& device_generator,
471 Catch::Generators::GeneratorWrapper<QString>&& root_component_generator,
472 Catch::Generators::GeneratorWrapper<QString>&& directory_generator,
473 Catch::Generators::GeneratorWrapper<QString>&& filename_generator,
474 Catch::Generators::GeneratorWrapper<QString>&& separator_generator,
476 ) {
477 return Catch::Generators::GeneratorWrapper<QString>(
478 std::unique_ptr<Catch::Generators::IGenerator<QString>>(
479 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)
480 )
481 );
482 }
483
485
486 // REMARK: We need a bounded length for the generation of path
487 // components as strings.
488 // We trivially do not want components to be the empty string,
489 // such that we have a minimum length of 1, but the maximum
490 // length is more malleable.
491 // We don't want components that are too long to avoid
492 // incurring in a big performance overhead, as we may generate
493 // many of them.
494 // At the same time, we want some freedom in having diffent
495 // length components.
496 // The value that was chosen is based on the general value for
497 // POSIX's NAME_MAX, which seems to tend to be 14 on many systems.
498 // We see this value as a small enough but not too much value
499 // that further brings with itself a relation to paths,
500 // increasing our portability even if it is out of scope, as
501 // almost no modern respects NAME_MAX.
502 // We don't use POSIX's NAME_MAX directly as it may not be available
503 // on all systems.
504 inline static constexpr std::size_t minimum_component_length{1};
505 inline static constexpr std::size_t maximum_component_length{14};
506
507 /*!
508 * Returns a generator that generates strings that are
509 * suitable to be used as a root component in POSIX paths.
510 *
511 * As per
512 * \l {https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_02},
513 * this is any sequence of slash characters that is not of
514 * length 2.
515 */
517 return uniformly_valued_oneof(
519 string(character('/', '/'), 1, 1),
520 string(character('/', '/'), 3, maximum_component_length)
521 ),
522 std::vector{1, maximum_component_length - 3}
523 );
524 }
525
526 /*!
527 * Returns a generator that generates strings that are
528 * suitable to be used as directory components in POSIX paths
529 * and that use an alphabet that should generally be supported
530 * by other systems.
531 *
532 * Components of this kind use the \l
533 * {https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_282}{Portable Filename Character Set}.
534 */
536 return string(
537 QDOC_CATCH_GENERATORS_QCHAR_ALPHABETS_NAMESPACE::portable_posix_filename(),
538 minimum_component_length, maximum_component_length
539 );
540 }
541
542 /*!
543 * Returns a generator that generates strings that are
544 * suitable to be used as filenames in POSIX paths and that
545 * use an alphabet that should generally be supported by
546 * other systems.
547 *
548 * Filenames of this kind use the \l
549 * {https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_282}{Portable Filename Character Set}.
550 */
552 // REMARK: "." and ".." always represent directories so we
553 // avoid generating them. Other than this, there is no
554 // difference between a file name and a directory name.
555 return filter([](auto& filename) { return filename != "." && filename != ".."; }, portable_posix_directory_name());
556 }
557
558 /*!
559 * Returns a generator that generates strings that can be used
560 * as POSIX compliant separators.
561 *
562 * As per \l
563 * {https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_271},
564 * a separator is a sequence of one or more slashes.
565 */
567 return string(character('/', '/'), minimum_component_length, maximum_component_length);
568 }
569
570 /*!
571 * Returns a generator that generates strings that can be
572 * suitably used as logical drive names in Windows' paths.
573 *
574 * As per \l
575 * {https://docs.microsoft.com/en-us/dotnet/standard/io/file-path-formats#traditional-dos-paths}
576 * and \l
577 * {https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getlogicaldrives},
578 * they are composed of a single letter.
579 * Each generated string always follows the lettet with a
580 * colon, as it is specifically intended for path usages,
581 * where this is required.
582 *
583 * We use only uppercase letters for the drives names albeit,
584 * depending on case sensitivity, lowercase letter could be
585 * used.
586 */
588 // REMARK: If a Windows path is generated on Windows
589 // itself, we expect that it may be used to interact with
590 // the filesystem, similar to how we expect a POSIX path
591 // to be used on Linux.
592 // For this reason, we only generate a specific drive, the one
593 // that contains the current working directory, so that we
594 // know it is an actually available drive and to contain the
595 // possible modifications to the filesystem to an easily
596 // foundable place.
597
598#if defined(Q_OS_WINDOWS)
599
600 auto root_device{QStorageInfo{QDir()}.rootPath().first(1) + ":"};
601
602 return cycle(Catch::Generators::value(std::move(root_device)));
603
604#else
605
606 return Catch::Generators::map(
607 [](QString letter){ return letter + ':';},
608 string(QDOC_CATCH_GENERATORS_QCHAR_ALPHABETS_NAMESPACE::ascii_uppercase(), 1, 1)
609 );
610
611#endif
612 }
613
614 /*!
615 * Returns a generator that generate strings that can be used
616 * as separators in Windows based paths.
617 *
618 * As per \l
619 * {https://docs.microsoft.com/en-us/dotnet/api/system.io.path.directoryseparatorchar?view=net-6.0}
620 * and \l
621 * {https://docs.microsoft.com/en-us/dotnet/standard/io/file-path-formats#canonicalize-separators},
622 * this is a sequence of one or more backward or forward slashes.
623 */
625 return uniform_oneof(
627 string(character('\\', '\\'), minimum_component_length, maximum_component_length),
628 string(character('/', '/'), minimum_component_length, maximum_component_length)
629 )
630 );
631 }
632
633 } // end QDOC_CATCH_GENERATORS_PRIVATE_NAMESPACE
634
635 /*!
636 * Returns a generator that generates strings representing
637 * POSIX compatible paths.
638 *
639 * The generated paths follows the format specified in \l
640 * {https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_271}.
641 *
642 * The optional length-requirements, such as PATH_MAX and
643 * NAME_MAX, are relaxed away as they are generally not
644 * respected by modern systems.
645 *
646 * It is possible to set the probability of obtaining a
647 * relative or absolute path through \a
648 * absolute_path_probability and the one of obtaining a path
649 * potentially pointing ot a directory or on a file through \a
650 * directory_path_probability.
651 */
652 inline Catch::Generators::GeneratorWrapper<QString> relaxed_portable_posix_path(double absolute_path_probability = 0.5, double directory_path_probability = 0.5) {
653 return path(
654 // POSIX path are never multi-device, so that we have
655 // provide an empty device component generator and set
656 // the probability for Multi-Device paths to zero.
657 string(character(), 0, 0),
659 QDOC_CATCH_GENERATORS_PRIVATE_NAMESPACE::portable_posix_directory_name(),
660 QDOC_CATCH_GENERATORS_PRIVATE_NAMESPACE::portable_posix_filename(),
662 PathGeneratorConfiguration{}
663 .set_multi_device_path_probability(0.0)
664 .set_absolute_path_probability(absolute_path_probability)
665 .set_directory_path_probability(directory_path_probability)
666 );
667 }
668
669 /*!
670 * Returns a generator that produces strings that represents
671 * traditional DOS paths as defined in \l
672 * {https://docs.microsoft.com/en-us/dotnet/standard/io/file-path-formats#traditional-dos-paths}.
673 *
674 * The directory and filename components of a path generated
675 * in this way are, currently, restricted to use a portable
676 * character set as defined by POSIX.
677 *
678 * Do note that most paths themselves, will not be portable, on
679 * the whole, albeit they may be valid paths on other systems, as
680 * Windows uses a path system that is generally incompatible with
681 * other systems.
682 *
683 * Some possibly valid special path, such as a "C:" or "\"
684 * will never be generated.
685 */
687 double absolute_path_probability = 0.5,
688 double directory_path_probability = 0.5,
689 double multi_device_path_probability = 0.5
690 ) {
691 return path(
692 QDOC_CATCH_GENERATORS_PRIVATE_NAMESPACE::windows_logical_drives(),
693 QDOC_CATCH_GENERATORS_PRIVATE_NAMESPACE::windows_separator(),
694 // REMAKR: Windows treats trailing dots as if they were a
695 // component of their own, that is, as the special
696 // relative paths.
697 // This seems to not be correctly handled by Qt's
698 // filesystem methods, resulting in inconsistencies when
699 // one such path is encountered.
700 // To avoid the issue, considering that an equivalent path
701 // can be formed by actually having the dots on their own
702 // as a component, we filter out all those paths that have
703 // trailing dots but are not only composed of dots.
704 Catch::Generators::filter(
705 [](auto& path){ return !(path.endsWith(".") && path.contains(QRegularExpression("[^.]"))) ; },
706 QDOC_CATCH_GENERATORS_PRIVATE_NAMESPACE::portable_posix_directory_name()
707 ),
708 QDOC_CATCH_GENERATORS_PRIVATE_NAMESPACE::portable_posix_filename(),
709 QDOC_CATCH_GENERATORS_PRIVATE_NAMESPACE::windows_separator(),
710 PathGeneratorConfiguration{}
711 .set_multi_device_path_probability(multi_device_path_probability)
712 .set_absolute_path_probability(absolute_path_probability)
713 .set_directory_path_probability(directory_path_probability)
714 );
715 }
716
717 // TODO: Find a good way to test the following functions.
718 // native_path can probably be tied to the tests for the
719 // OS-specific functions, with TEMPLATE_TEST_CASE.
720 // The other ones may follow a similar pattern but require a bit
721 // more work so that they tie to a specific case instead of the
722 // general one.
723 // Nonetheless, this approach is both error prone and difficult to
724 // parse, because of the required if preprocessor directives,
725 // and should be avoided if possible.
726
727 /*!
728 * Returns a generator that generates QStrings that represents
729 * paths native to the underlying OS.
730 *
731 * On Windows, paths that refer to a drive always refer to the
732 * root drive.
733 *
734 * native* functions should always be chosen when using paths for
735 * testing interfacing with the filesystem itself.
736 *
737 * System outside Linux, macOS or Windows are not supported.
738 */
739 inline Catch::Generators::GeneratorWrapper<QString> native_path(double absolute_path_probability = 0.5, double directory_path_probability = 0.5) {
740#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
741
742 return relaxed_portable_posix_path(absolute_path_probability, directory_path_probability);
743
744#elif defined(Q_OS_WINDOWS)
745
746 // REMARK: When generating native paths for testing we
747 // generally want to avoid relative paths that are
748 // drive-specific, as we want them to be tied to a specific
749 // working directory that may not be the current directory on
750 // the drive.
751 // Hence, we avoid generating paths that may have a drive component.
752 // For tests where those kind of paths are interesting, a
753 // specific Windows-only test should be made, using
754 // traditional_dos_path to generate drive-relative paths only.
755 return traditional_dos_path(absolute_path_probability, directory_path_probability, 0.0);
756
757#endif
758 }
759
760 /*!
761 * Returns a generator that generates QStrings that represents
762 * paths native to the underlying OS and that are always \e
763 * {Relative}.
764 *
765 * Avoids generating paths that refer to a directory that is not
766 * included in the path itself.
767 *
768 * System outside Linux, macOS or Windows are not supported.
769 */
770 inline Catch::Generators::GeneratorWrapper<QString> native_relative_path(double directory_path_probability = 0.5) {
771 // REMARK: When testing, we generally use some specific
772 // directory as a root for relative paths.
773 // We want the generated path to be relative to that
774 // directory because we need a clean state for the test to
775 // be reliable.
776 // When generating paths, it is possible, correctly, to
777 // have a path that refers to that directory or some
778 // parent of it, removing us from the clean state that we
779 // need.
780 // To avoid that, we filter out paths that end up referring to a directory that is not under our "root" directory.
781 //
782 // We can think of each generated component moving us
783 // further down or up, in case of "..", a directory
784 // hierarchy, or keeping us at the same place in case of
785 // ".".
786 // Any path that ends up under our original "root"
787 // directory will safely keep our clean state for testing.
788 //
789 // Each "." keeps us at the same level in the hierarchy.
790 // Each ".." moves us up one level in the hierarchy.
791 // Each component that is not "." or ".." moves us down
792 // one level into the hierarchy.
793 //
794 // Then, to avoid referring to the "root" directory or one
795 // of its parents, we need to balance out each "." and
796 // ".." with the components that precedes or follow their
797 // appearance.
798 //
799 // Since "." keeps us at the same level, it can appear how
800 // many times it wants as long as the path referes to the
801 // "root" directory or a directory or file under it and at
802 // least one other component referes to a directory or
803 // file that is under the "root" directory.
804 //
805 // Since ".." moves us one level up in the hierarchy, a
806 // sequence of n ".." components is safe when at least n +
807 // 1 non "." or ".." components appear before it.
808 //
809 // To avoid the above problem, we filter away paths that
810 // do not respect those rules.
811 return Catch::Generators::filter(
812 [](auto& path){
813 QStringList components{path.split(QRegularExpression{R"((\\|\/)+)"}, Qt::SkipEmptyParts)};
814 int depth{0};
815
816 for (auto& component : components) {
817 if (component == "..")
818 --depth;
819 else if (component != ".")
820 ++depth;
821
822 if (depth < 0) return false;
823 }
824
825 return (depth > 0);
826 },
827 native_path(0.0, directory_path_probability)
828 );
829 }
830
831 /*!
832 * Returns a generator that generates QStrings that represents
833 * paths native to the underlying OS and that are always \e
834 * {Relative} and \e {To a File}.
835 *
836 * System outside Linux, macOS or Windows are not supported.
837 */
839 return native_relative_path(0.0);
840 }
841
842 /*!
843 * Returns a generator that generates QStrings that represents
844 * paths native to the underlying OS and that are always \e
845 * {Relative} and \e {To a Directory}.
846 *
847 * System outside Linux, macOS or Windows are not supported.
848 */
850 return native_relative_path(1.0);
851 }
852
853} // end QDOC_CATCH_GENERATORS_ROOT_NAMESPACE
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{})
Catch::Generators::GeneratorWrapper< QString > posix_root()
Returns a generator that generates strings that are suitable to be used as a root component in POSIX ...
Catch::Generators::GeneratorWrapper< QString > windows_logical_drives()
Returns a generator that generates strings that can be suitably used as logical drive names in Window...
Catch::Generators::GeneratorWrapper< QString > windows_separator()
Returns a generator that generate strings that can be used as separators in Windows based paths.
Catch::Generators::GeneratorWrapper< QString > portable_posix_directory_name()
Returns a generator that generates strings that are suitable to be used as directory components in PO...
Catch::Generators::GeneratorWrapper< QString > portable_posix_filename()
Returns a generator that generates strings that are suitable to be used as filenames in POSIX paths a...
Catch::Generators::GeneratorWrapper< QString > posix_separator()
Returns a generator that generates strings that can be used as POSIX compliant separators.
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)
Returns a generator that produces strings that represents traditional DOS paths as defined in \l {htt...
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{})
Returns a generator that produces QStrings that represent a path in a filesystem.
Catch::Generators::GeneratorWrapper< QString > native_relative_path(double directory_path_probability=0.5)
Returns a generator that generates QStrings that represents paths native to the underlying OS and tha...
Catch::Generators::GeneratorWrapper< QString > native_relative_file_path()
Returns a generator that generates QStrings that represents paths native to the underlying OS and tha...
Catch::Generators::GeneratorWrapper< QString > native_relative_directory_path()
Returns a generator that generates QStrings that represents paths native to the underlying OS and tha...
Catch::Generators::GeneratorWrapper< QString > native_path(double absolute_path_probability=0.5, double directory_path_probability=0.5)
Returns a generator that generates QStrings that represents paths native to the underlying OS.
Catch::Generators::GeneratorWrapper< QString > relaxed_portable_posix_path(double absolute_path_probability=0.5, double directory_path_probability=0.5)
Returns a generator that generates strings representing POSIX compatible paths.
#define QDOC_CATCH_GENERATORS_PRIVATE_NAMESPACE
Definition namespaces.h:8
#define QDOC_CATCH_GENERATORS_UTILITIES_ABSOLUTE_NAMESPACE
Definition namespaces.h:14
#define QDOC_CATCH_GENERATORS_QCHAR_ALPHABETS_NAMESPACE
Definition namespaces.h:12
#define QDOC_CATCH_GENERATORS_ROOT_NAMESPACE
Definition namespaces.h:6
#define assert
Defines some parameters to customize the generation of paths by a PathGenerator.
PathGeneratorConfiguration & set_absolute_path_probability(double amount)
PathGeneratorConfiguration & set_multi_device_path_probability(double amount)
PathGeneratorConfiguration & set_minimum_components_amount(std::size_t amount)
PathGeneratorConfiguration & set_directory_path_probability(double amount)
PathGeneratorConfiguration & set_has_trailing_separator_probability(double amount)
PathGeneratorConfiguration & set_maximum_components_amount(std::size_t amount)