Sequoia
Loading...
Searching...
No Matches
TestRunner.hpp
Go to the documentation of this file.
1
2// Copyright Oliver J. Rosten 2018. //
3// Distributed under the GNU GENERAL PUBLIC LICENSE, Version 3.0. //
4// (See accompanying file LICENSE.md or copy at //
5// https://www.gnu.org/licenses/gpl-3.0.en.html) //
7
8#pragma once
9
17
23
24#include <iostream>
25#include <chrono>
26#include <set>
27
28namespace sequoia::testing
29{
30 enum class runner_mode { none=0, help=1, test=2, create=4, init=8};
31
32 enum class update_mode { none = 0, soft };
33
34 enum class recovery_mode { none = 0, recovery = 1, dump = 2 };
35
36 enum class prune_outcome { not_attempted, no_time_stamp, success };
37
38 enum class concurrency_mode {
39 serial,
40 dynamic,
41 fixed
42 };
43}
44
45NAMESPACE_SEQUOIA_AS_BITMASK
46{
47 template<>
48 struct as_bitmask<sequoia::testing::runner_mode> : std::true_type {};
49
50 template<>
51 struct as_bitmask<sequoia::testing::recovery_mode> : std::true_type {};
52}
53
54namespace sequoia::testing
55{
56 individual_materials_paths set_materials(const std::filesystem::path& sourceFile, const project_paths& projPaths, std::vector<std::filesystem::path>& materialsPaths);
57
59 {
60 public:
61 template<class Test>
62 requires (!std::is_same_v<Test, test_vessel> && concrete_test<Test>)
63 test_vessel(Test&& t)
64 : m_pTest{std::make_unique<essence<Test>>(std::forward<Test>(t))}
65 {
66 if constexpr(!is_parallelizable_v<Test>)
67 m_Parallelizable = parallelizable_candidate::no;
68 }
69
70 test_vessel(const test_vessel&) = delete;
71 test_vessel(test_vessel&&) noexcept = default;
72
73 test_vessel& operator=(const test_vessel&) = delete;
74 test_vessel& operator=(test_vessel&&) noexcept = default;
75
76 [[nodiscard]]
77 const std::string& name() const noexcept
78 {
79 return m_pTest->name();
80 }
81
82 [[nodiscard]]
83 const test_summary_path& summary_file_path() const noexcept
84 {
85 return m_pTest->summary_file_path();
86 }
87
88 [[nodiscard]]
89 std::filesystem::path source_file() const
90 {
91 return m_pTest->source_file();
92 }
93
94 [[nodiscard]]
95 std::filesystem::path working_materials() const
96 {
97 return m_pTest->working_materials();
98 }
99
100 [[nodiscard]]
101 std::filesystem::path predictive_materials() const
102 {
103 return m_pTest->predictive_materials();
104 }
105
106 [[nodiscard]]
107 bool parallelizable() const noexcept
108 {
109 return m_Parallelizable == parallelizable_candidate::yes;
110 }
111
112 [[nodiscard]]
113 log_summary execute(std::optional<std::size_t> index)
114 {
115 return m_pTest->execute(index);
116 }
117
118 void reset(const project_paths& projPaths, std::vector<std::filesystem::path>& materialsPaths)
119 {
120 m_pTest->reset(projPaths, materialsPaths);
121 }
122 private:
123 static void versioned_write(const std::filesystem::path& file, const failure_output& output);
124 static void versioned_write(const std::filesystem::path& file, std::string_view text);
125
126 struct soul
127 {
128 virtual ~soul() = default;
129
130 virtual const std::string& name() const noexcept = 0;
131 virtual const test_summary_path& summary_file_path() const noexcept = 0;
132 virtual std::filesystem::path source_file() const = 0;
133 virtual std::filesystem::path working_materials() const = 0;
134 virtual std::filesystem::path predictive_materials() const = 0;
135
136 virtual log_summary execute(std::optional<std::size_t> index) = 0;
137 virtual void reset(const project_paths& projPaths, std::vector<std::filesystem::path>& materialsPaths) = 0;
138 };
139
140 template<concrete_test Test>
141 class essence final : public soul
142 {
143 public:
144 essence(Test&& t) : m_Test{std::forward<Test>(t)}
145 {}
146
147 [[nodiscard]]
148 std::filesystem::path source_file() const final
149 {
150 return m_Test.source_file();
151 }
152
153 [[nodiscard]]
154 const std::string& name() const noexcept final
155 {
156 return m_Test.name();
157 }
158
159 [[nodiscard]]
160 const test_summary_path& summary_file_path() const noexcept final
161 {
162 return m_Test.summary_file_path();
163 }
164
165 [[nodiscard]]
166 std::filesystem::path working_materials() const final
167 {
168 return m_Test.working_materials();
169 }
170
171 [[nodiscard]]
172 std::filesystem::path predictive_materials() const final
173 {
174 return m_Test.predictive_materials();
175 }
176
177 [[nodiscard]]
178 log_summary execute(std::optional<std::size_t> index) final
179 {
180 const timer t{};
181
182 try
183 {
184 m_Test.run_tests();
185 }
186 catch(const std::exception& e)
187 {
188 m_Test.log_critical_failure(m_Test.source_file(), "Unexpected", e.what());
189 }
190 catch(...)
191 {
192 m_Test.log_critical_failure(m_Test.source_file(), "Unknown", "");
193 }
194
195 m_Test.write_instability_analysis_output(m_Test.source_file(), index);
196
197 return write_versioned_output(t);
198 }
199
200 void reset(const project_paths& projPaths, std::vector<std::filesystem::path>& materialsPaths) final
201 {
202 m_Test.reset_results();
203 set_materials(m_Test.source_file(), projPaths, materialsPaths);
204 }
205 private:
206 log_summary write_versioned_output(const timer& t) const
207 {
208 auto summary{m_Test.summarize(t.time_elapsed())};
209
210 if(!m_Test.has_critical_failures())
211 {
212 versioned_write(m_Test.diagnostics_file_paths().false_positive_or_negative_file_path(), summary.diagnostics_output());
213 versioned_write(m_Test.diagnostics_file_paths().caught_exceptions_file_path(), summary.caught_exceptions_output());
214 }
215
216 return summary;
217 }
218
219 Test m_Test;
220 };
221
222 enum class parallelizable_candidate : bool { no, yes };
223
224 std::unique_ptr<soul> m_pTest{};
225 parallelizable_candidate m_Parallelizable{parallelizable_candidate::yes};
226 };
227
228 template<concrete_test T>
229 [[nodiscard]]
230 std::optional<std::string> get_output_discriminator(const T& test){
231 if constexpr(has_discriminated_output_v<T>)
232 return test.output_discriminator();
233 else
234 return std::nullopt;
235 }
236
237 template<concrete_test T>
238 [[nodiscard]]
239 std::optional<std::string> get_reduction_discriminator(const T& test){
240 if constexpr(has_discriminated_summary_v<T>)
241 return test.summary_discriminator();
242 else
243 return std::nullopt;
244 }
245
253 {
254 public:
255 test_runner(int argc,
256 char** argv,
257 std::string copyright,
258 std::string codeIndent=" ",
259 const project_paths::customizer& projectPathsCustomization = {},
260 std::ostream& stream=std::cout);
261
262 test_runner(const test_runner&) = delete;
263 test_runner(test_runner&&) noexcept = default;
264
265 test_runner& operator=(const test_runner&) = delete;
266 test_runner& operator=(test_runner&&) noexcept = default;
267
268 template<concrete_test... Tests>
269 requires (sizeof...(Tests) > 0)
270 void add_test_suite(std::string_view name, Tests&&... tests)
271 {
272 using namespace object;
273
274 check_for_duplicates(name, tests...);
275
276 extract_suite_tree(name, m_Filter, std::forward<Tests>(tests)...);
277 }
278
279 template<class... Suites>
280 requires (object::is_suite_v<Suites> && ...)
281 void add_test_suite(std::string_view name, Suites... s)
282 {
283 using namespace object;
284
285 extract_suite_tree(m_Filter, suite{std::string{name}, std::move(s)...});
286 }
287
288 void execute([[maybe_unused]] timer_resolution r={});
289
290 std::ostream& stream() noexcept { return *m_Stream; }
291
292 const project_paths& proj_paths() const noexcept { return m_ProjPaths; }
293
294 const std::string& copyright() const noexcept { return m_Copyright; }
295
296 const indentation& code_indent() const noexcept { return m_CodeIndent; }
297
298 private:
299 enum class output_mode { standard = 0, verbose = 1 };
300 enum class instability_mode { none = 0, single_instance, coordinator, sandbox };
301
302 struct prune_info
303 {
304 prune_mode mode{prune_mode::passive};
305 std::string include_cutoff{};
306 };
307
308 struct test_to_path
309 {
310 template<concrete_test Test>
311 [[nodiscard]]
312 normal_path operator()(const Test& test) const { return test.source_file(); }
313 };
314
315 class path_equivalence
316 {
317 public:
318 explicit path_equivalence(const std::filesystem::path& repo)
319 : m_Repo{&repo}
320 {}
321
322 [[nodiscard]]
323 bool operator()(const normal_path& selectedSource, const normal_path& filepath) const;
324
325 private:
326 const std::filesystem::path* m_Repo;
327 };
328
329 struct suite_node
330 {
331 log_summary summary{};
332 std::optional<test_vessel> optTest{};
333 };
334
337
338 std::string m_Copyright{};
339 project_paths m_ProjPaths;
340 indentation m_CodeIndent{" "};
341 std::ostream* m_Stream;
342
343 suite_type m_Suites{};
344 filter_type m_Filter{path_equivalence{proj_paths().tests().repo()}, test_to_path{}};
345 prune_info m_PruneInfo{};
346
347 runner_mode m_RunnerMode{runner_mode::none};
348 output_mode m_OutputMode{output_mode::standard};
349 update_mode m_UpdateMode{update_mode::none};
350 recovery_mode m_RecoveryMode{recovery_mode::none};
351 concurrency_mode m_ConcurrencyMode{concurrency_mode::dynamic};
352 instability_mode m_InstabilityMode{instability_mode::none};
353
354 std::size_t m_NumReps{1},
355 m_RunnerID{},
356 m_PoolSize{8};
357
358 void process_args(int argc, char** argv);
359
360 void check_argument_consistency();
361
362 void check_for_missing_tests();
363
364 [[nodiscard]]
365 bool concurrent_execution() const noexcept { return m_ConcurrencyMode != concurrency_mode::serial; }
366
367 void sort_tests();
368
369 void reset_tests();
370
371 void run_tests(std::optional<std::size_t> id);
372
373 [[nodiscard]]
374 bool nothing_to_do();
375
376 [[nodiscard]]
377 bool in_mode(runner_mode m) const noexcept { return (m_RunnerMode & m) == m; }
378
379 void prune();
380
381 [[nodiscard]]
382 prune_outcome do_prune();
383
384 template<class Filter, class Suite>
385 requires object::is_suite_v<Suite>
386 void extract_suite_tree(Filter&& filter, Suite&& s)
387 {
388 using namespace object;
389
390 if(!m_Suites.order())
391 {
392 m_Suites.add_node(suite_type::npos);
393 }
394
395 std::vector<std::filesystem::path> materialsPaths{};
396
397 // TO DO: may need generalizing since suites can have arbitrary depth.
398 const std::string suiteName{s.name};
399
400 extract_tree(std::forward<Suite>(s),
401 std::forward<Filter>(filter),
403 [] <class... Ts> (const suite<Ts...>&s) -> suite_node { return {.summary{log_summary{s.name}}}; },
404 [this, &suiteName, &materialsPaths]<concrete_test T>(T&& test) -> suite_node {
405 test = T{test.name(),
406 suiteName,
407 test.source_file(),
408 proj_paths(),
409 set_materials(test.source_file(), proj_paths(), materialsPaths),
410 make_active_recovery_paths(m_RecoveryMode, proj_paths()),
411 get_output_discriminator(test),
412 get_reduction_discriminator(test)};
413
414 return {.summary{log_summary{test.name()}}, .optTest{std::move(test)}};
415 }
416 },
417 m_Suites,
418 0);
419 }
420
421 template<class Filter, concrete_test... Tests>
422 requires (sizeof...(Tests) > 0)
423 void extract_suite_tree(std::string_view name, Filter&& filter, Tests&&... tests)
424 {
425 extract_suite_tree(std::forward<Filter>(filter), object::suite{std::string{name}, std::forward<Tests>(tests)...});
426 }
427
428 template<concrete_test... Tests>
429 requires (sizeof...(Tests) > 0)
430 static void check_for_duplicates(std::string_view name, const Tests&... tests)
431 {
432 using duplicate_set = std::set<std::pair<std::string_view, std::filesystem::path>>;
433
434 duplicate_set namesAndSources{};
435
436 auto check{
437 [&,name](concrete_test auto const& test) {
438 if(!namesAndSources.emplace(test.name(), test.source_file()).second)
439 throw std::runtime_error{duplication_message(name, test.name(), test.source_file())};
440 }
441 };
442
443 (check(tests), ...);
444 }
445
446 [[nodiscard]]
447 static std::string duplication_message(std::string_view suiteName, std::string_view testName, const std::filesystem::path& source);
448
449 [[nodiscard]]
450 static active_recovery_files make_active_recovery_paths(recovery_mode mode, const project_paths& projPaths);
451 };
452}
Utilities to aid logical operations.
Facility to detect changes on disk and only run the relevant tests.
Restriction of Dynamic Graphs to Trees.
bool check(CheckType flavour, std::string description, test_logger< Mode > &logger, Iter first, Sentinel last, PredictionIter predictionFirst, PredictionSentinel predictionLast, tutor< Advisor > advisor={})
The workhorse for comparing the contents of ranges.
Definition: FreeCheckers.hpp:377
Platform-dependent utilities.
A collection of functions for formatting test output.
Extension of the testing framework for perfomance testing.
Utilities for defining a suite of objects, filtered at runtime.
Utilities for recording the outcome of tests.
concurrency_mode
Definition: TestRunner.hpp:38
@ fixed
determined implicitly by the stl
Tasks may be pushed, upon which they are immediately invoked.
Definition: ConcurrencyModels.hpp:150
Type-safe mechanism for indentations.
Definition: Indent.hpp:22
Definition: FileSystem.hpp:20
Definition: Suite.hpp:46
Summaries data generated by the logger, for the purposes of reporting.
Definition: TestLogger.hpp:299
Paths used by the project.
Definition: ProjectPaths.hpp:469
Consumes command-line arguments and holds all test suites.
Definition: TestRunner.hpp:253
Definition: IndividualTestPaths.hpp:90
Definition: TestRunner.hpp:59
Definition: FreeTestCore.hpp:31
Definition: Helpers.hpp:19
Definition: FreeTestCore.hpp:189
Definition: Utilities.hpp:73
Holds paths to files where recovery information will be written if the path is not empty.
Definition: TestLogger.hpp:36
Definition: ProjectPaths.hpp:472