1 /* Copyright (c) 2017-2023. The SimGrid Team. All rights reserved. */
3 /* This program is free software; you can redistribute it and/or modify it
4 * under the terms of the license (GNU LGPL) which comes with this package. */
6 #include "src/3rd-party/catch.hpp"
7 #include "src/mc/explo/udpor/Configuration.hpp"
8 #include "src/mc/explo/udpor/EventSet.hpp"
9 #include "src/mc/explo/udpor/History.hpp"
10 #include "src/mc/explo/udpor/Unfolding.hpp"
11 #include "src/mc/explo/udpor/UnfoldingEvent.hpp"
12 #include "src/mc/explo/udpor/maximal_subsets_iterator.hpp"
13 #include "src/mc/explo/udpor/udpor_tests_private.hpp"
15 #include <unordered_map>
17 using namespace simgrid::mc::udpor;
19 TEST_CASE("simgrid::mc::udpor::Configuration: Constructing Configurations")
21 // The following tests concern the given event structure:
29 UnfoldingEvent e1(EventSet(), std::make_shared<IndependentAction>());
30 UnfoldingEvent e2(EventSet({&e1}), std::make_shared<IndependentAction>());
31 UnfoldingEvent e3(EventSet({&e2}), std::make_shared<IndependentAction>());
32 UnfoldingEvent e4(EventSet({&e3}), std::make_shared<IndependentAction>());
33 UnfoldingEvent e5(EventSet({&e3}), std::make_shared<IndependentAction>());
35 SECTION("Creating a configuration without events")
38 REQUIRE(C.get_events().empty());
39 REQUIRE(C.get_latest_event() == nullptr);
42 SECTION("Creating a configuration with events (test violation of being causally closed)")
44 // 5 choose 0 = 1 test
45 REQUIRE_NOTHROW(Configuration({&e1}));
47 // 5 choose 1 = 5 tests
48 REQUIRE_THROWS_AS(Configuration({&e2}), std::invalid_argument);
49 REQUIRE_THROWS_AS(Configuration({&e3}), std::invalid_argument);
50 REQUIRE_THROWS_AS(Configuration({&e4}), std::invalid_argument);
51 REQUIRE_THROWS_AS(Configuration({&e5}), std::invalid_argument);
53 // 5 choose 2 = 10 tests
54 REQUIRE_NOTHROW(Configuration({&e1, &e2}));
55 REQUIRE_THROWS_AS(Configuration({&e1, &e3}), std::invalid_argument);
56 REQUIRE_THROWS_AS(Configuration({&e1, &e4}), std::invalid_argument);
57 REQUIRE_THROWS_AS(Configuration({&e1, &e5}), std::invalid_argument);
58 REQUIRE_THROWS_AS(Configuration({&e2, &e3}), std::invalid_argument);
59 REQUIRE_THROWS_AS(Configuration({&e2, &e4}), std::invalid_argument);
60 REQUIRE_THROWS_AS(Configuration({&e2, &e5}), std::invalid_argument);
61 REQUIRE_THROWS_AS(Configuration({&e3, &e4}), std::invalid_argument);
62 REQUIRE_THROWS_AS(Configuration({&e3, &e5}), std::invalid_argument);
63 REQUIRE_THROWS_AS(Configuration({&e4, &e5}), std::invalid_argument);
65 // 5 choose 3 = 10 tests
66 REQUIRE_NOTHROW(Configuration({&e1, &e2, &e3}));
67 REQUIRE_THROWS_AS(Configuration({&e1, &e2, &e4}), std::invalid_argument);
68 REQUIRE_THROWS_AS(Configuration({&e1, &e2, &e5}), std::invalid_argument);
69 REQUIRE_THROWS_AS(Configuration({&e1, &e3, &e4}), std::invalid_argument);
70 REQUIRE_THROWS_AS(Configuration({&e1, &e3, &e5}), std::invalid_argument);
71 REQUIRE_THROWS_AS(Configuration({&e1, &e4, &e5}), std::invalid_argument);
72 REQUIRE_THROWS_AS(Configuration({&e2, &e3, &e4}), std::invalid_argument);
73 REQUIRE_THROWS_AS(Configuration({&e2, &e3, &e5}), std::invalid_argument);
74 REQUIRE_THROWS_AS(Configuration({&e2, &e4, &e5}), std::invalid_argument);
75 REQUIRE_THROWS_AS(Configuration({&e3, &e4, &e5}), std::invalid_argument);
77 // 5 choose 4 = 5 tests
78 REQUIRE_NOTHROW(Configuration({&e1, &e2, &e3, &e4}));
79 REQUIRE_NOTHROW(Configuration({&e1, &e2, &e3, &e5}));
80 REQUIRE_THROWS_AS(Configuration({&e1, &e2, &e4, &e5}), std::invalid_argument);
81 REQUIRE_THROWS_AS(Configuration({&e1, &e3, &e4, &e5}), std::invalid_argument);
82 REQUIRE_THROWS_AS(Configuration({&e2, &e3, &e4, &e5}), std::invalid_argument);
84 // 5 choose 5 = 1 test
85 REQUIRE_NOTHROW(Configuration({&e1, &e2, &e3, &e4, &e5}));
89 TEST_CASE("simgrid::mc::udpor::Configuration: Adding Events")
91 // The following tests concern the given event structure:
98 UnfoldingEvent e1(EventSet(), std::make_shared<IndependentAction>());
99 UnfoldingEvent e2(EventSet({&e1}), std::make_shared<IndependentAction>());
100 UnfoldingEvent e3(EventSet({&e2}), std::make_shared<IndependentAction>());
101 UnfoldingEvent e4(EventSet({&e2}), std::make_shared<IndependentAction>());
103 REQUIRE_THROWS_AS(Configuration().add_event(nullptr), std::invalid_argument);
104 REQUIRE_THROWS_AS(Configuration().add_event(&e2), std::invalid_argument);
105 REQUIRE_THROWS_AS(Configuration().add_event(&e3), std::invalid_argument);
106 REQUIRE_THROWS_AS(Configuration().add_event(&e4), std::invalid_argument);
107 REQUIRE_THROWS_AS(Configuration({&e1}).add_event(&e3), std::invalid_argument);
108 REQUIRE_THROWS_AS(Configuration({&e1}).add_event(&e4), std::invalid_argument);
110 REQUIRE_NOTHROW(Configuration().add_event(&e1));
111 REQUIRE_NOTHROW(Configuration({&e1}).add_event(&e1));
112 REQUIRE_NOTHROW(Configuration({&e1}).add_event(&e2));
113 REQUIRE_NOTHROW(Configuration({&e1, &e2}).add_event(&e1));
114 REQUIRE_NOTHROW(Configuration({&e1, &e2}).add_event(&e2));
115 REQUIRE_NOTHROW(Configuration({&e1, &e2}).add_event(&e3));
116 REQUIRE_NOTHROW(Configuration({&e1, &e2}).add_event(&e4));
117 REQUIRE_NOTHROW(Configuration({&e1, &e2, &e3}).add_event(&e1));
118 REQUIRE_NOTHROW(Configuration({&e1, &e2, &e3}).add_event(&e2));
119 REQUIRE_NOTHROW(Configuration({&e1, &e2, &e3}).add_event(&e3));
120 REQUIRE_NOTHROW(Configuration({&e1, &e2, &e3}).add_event(&e4));
121 REQUIRE_NOTHROW(Configuration({&e1, &e2, &e4}).add_event(&e1));
122 REQUIRE_NOTHROW(Configuration({&e1, &e2, &e4}).add_event(&e2));
123 REQUIRE_NOTHROW(Configuration({&e1, &e2, &e4}).add_event(&e3));
124 REQUIRE_NOTHROW(Configuration({&e1, &e2, &e4}).add_event(&e4));
125 REQUIRE_NOTHROW(Configuration({&e1, &e2, &e3, &e4}).add_event(&e1));
126 REQUIRE_NOTHROW(Configuration({&e1, &e2, &e3, &e4}).add_event(&e2));
127 REQUIRE_NOTHROW(Configuration({&e1, &e2, &e3, &e4}).add_event(&e3));
128 REQUIRE_NOTHROW(Configuration({&e1, &e2, &e3, &e4}).add_event(&e4));
131 TEST_CASE("simgrid::mc::udpor::Configuration: Topological Sort Order")
133 // The following tests concern the given event structure:
141 UnfoldingEvent e1(EventSet(), std::make_shared<IndependentAction>());
142 UnfoldingEvent e2(EventSet({&e1}), std::make_shared<IndependentAction>());
143 UnfoldingEvent e3(EventSet({&e2}), std::make_shared<IndependentAction>());
144 UnfoldingEvent e4(EventSet({&e3}), std::make_shared<IndependentAction>());
146 SECTION("Topological ordering for entire set")
148 Configuration C{&e1, &e2, &e3, &e4};
149 REQUIRE(C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2, &e3, &e4});
150 REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() ==
151 std::vector<const UnfoldingEvent*>{&e4, &e3, &e2, &e1});
154 SECTION("Topological ordering for subsets")
156 SECTION("No elements")
159 REQUIRE(C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{});
160 REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() == std::vector<const UnfoldingEvent*>{});
165 Configuration C{&e1};
166 REQUIRE(C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1});
167 REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() == std::vector<const UnfoldingEvent*>{&e1});
170 SECTION("e1 and e2 only")
172 Configuration C{&e1, &e2};
173 REQUIRE(C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2});
174 REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() == std::vector<const UnfoldingEvent*>{&e2, &e1});
177 SECTION("e1, e2, and e3 only")
179 Configuration C{&e1, &e2, &e3};
180 REQUIRE(C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2, &e3});
181 REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() ==
182 std::vector<const UnfoldingEvent*>{&e3, &e2, &e1});
187 TEST_CASE("simgrid::mc::udpor::Configuration: Topological Sort Order More Complicated")
189 // The following tests concern the given event structure:
199 UnfoldingEvent e1(EventSet(), std::make_shared<IndependentAction>());
200 UnfoldingEvent e2(EventSet({&e1}), std::make_shared<IndependentAction>());
201 UnfoldingEvent e3(EventSet({&e2}), std::make_shared<IndependentAction>());
202 UnfoldingEvent e4(EventSet({&e3}), std::make_shared<IndependentAction>());
203 UnfoldingEvent e5(EventSet({&e4}), std::make_shared<IndependentAction>());
204 UnfoldingEvent e6(EventSet({&e3}), std::make_shared<IndependentAction>());
206 SECTION("Topological ordering for subsets")
208 SECTION("No elements")
211 REQUIRE(C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{});
212 REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() == std::vector<const UnfoldingEvent*>{});
217 Configuration C{&e1};
218 REQUIRE(C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1});
219 REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() == std::vector<const UnfoldingEvent*>{&e1});
222 SECTION("e1 and e2 only")
224 Configuration C{&e1, &e2};
225 REQUIRE(C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2});
226 REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() == std::vector<const UnfoldingEvent*>{&e2, &e1});
229 SECTION("e1, e2, and e3 only")
231 Configuration C{&e1, &e2, &e3};
232 REQUIRE(C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2, &e3});
233 REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() ==
234 std::vector<const UnfoldingEvent*>{&e3, &e2, &e1});
237 SECTION("e1, e2, e3, and e6 only")
239 Configuration C{&e1, &e2, &e3, &e6};
240 REQUIRE(C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2, &e3, &e6});
241 REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() ==
242 std::vector<const UnfoldingEvent*>{&e6, &e3, &e2, &e1});
245 SECTION("e1, e2, e3, and e4 only")
247 Configuration C{&e1, &e2, &e3, &e4};
248 REQUIRE(C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2, &e3, &e4});
249 REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() ==
250 std::vector<const UnfoldingEvent*>{&e4, &e3, &e2, &e1});
253 SECTION("e1, e2, e3, e4, and e5 only")
255 Configuration C{&e1, &e2, &e3, &e4, &e5};
256 REQUIRE(C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2, &e3, &e4, &e5});
257 REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() ==
258 std::vector<const UnfoldingEvent*>{&e5, &e4, &e3, &e2, &e1});
261 SECTION("e1, e2, e3, e4 and e6 only")
263 // In this case, e4 and e6 are interchangeable. Hence, we have to check
264 // if the sorting gives us *any* of the combinations
265 Configuration C{&e1, &e2, &e3, &e4, &e6};
266 REQUIRE((C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2, &e3, &e4, &e6} ||
267 C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2, &e3, &e6, &e4}));
268 REQUIRE((C.get_topologically_sorted_events_of_reverse_graph() ==
269 std::vector<const UnfoldingEvent*>{&e6, &e4, &e3, &e2, &e1} ||
270 C.get_topologically_sorted_events_of_reverse_graph() ==
271 std::vector<const UnfoldingEvent*>{&e4, &e6, &e3, &e2, &e1}));
274 SECTION("Topological ordering for entire set")
276 // In this case, e4/e5 are both interchangeable with e6. Hence, again we have to check
277 // if the sorting gives us *any* of the combinations
278 Configuration C{&e1, &e2, &e3, &e4, &e5, &e6};
280 (C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2, &e3, &e4, &e5, &e6} ||
281 C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2, &e3, &e4, &e6, &e5} ||
282 C.get_topologically_sorted_events() == std::vector<const UnfoldingEvent*>{&e1, &e2, &e3, &e6, &e4, &e5}));
283 REQUIRE((C.get_topologically_sorted_events_of_reverse_graph() ==
284 std::vector<const UnfoldingEvent*>{&e6, &e5, &e4, &e3, &e2, &e1} ||
285 C.get_topologically_sorted_events_of_reverse_graph() ==
286 std::vector<const UnfoldingEvent*>{&e5, &e6, &e4, &e3, &e2, &e1} ||
287 C.get_topologically_sorted_events_of_reverse_graph() ==
288 std::vector<const UnfoldingEvent*>{&e5, &e4, &e6, &e3, &e2, &e1}));
293 TEST_CASE("simgrid::mc::udpor::Configuration: Topological Sort Order Very Complicated")
295 // The following tests concern the given event structure:
308 UnfoldingEvent e1(EventSet(), std::make_shared<IndependentAction>());
309 UnfoldingEvent e2(EventSet({&e1}), std::make_shared<IndependentAction>());
310 UnfoldingEvent e8(EventSet({&e1}), std::make_shared<IndependentAction>());
311 UnfoldingEvent e3(EventSet({&e2}), std::make_shared<IndependentAction>());
312 UnfoldingEvent e4(EventSet({&e3}), std::make_shared<IndependentAction>());
313 UnfoldingEvent e5(EventSet({&e4}), std::make_shared<IndependentAction>());
314 UnfoldingEvent e6(EventSet({&e4}), std::make_shared<IndependentAction>());
315 UnfoldingEvent e7(EventSet({&e2, &e8}), std::make_shared<IndependentAction>());
316 UnfoldingEvent e9(EventSet({&e6, &e7}), std::make_shared<IndependentAction>());
317 UnfoldingEvent e10(EventSet({&e7}), std::make_shared<IndependentAction>());
318 UnfoldingEvent e11(EventSet({&e8}), std::make_shared<IndependentAction>());
319 UnfoldingEvent e12(EventSet({&e5, &e9, &e10}), std::make_shared<IndependentAction>());
320 Configuration C{&e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8, &e9, &e10, &e11, &e12};
322 SECTION("Test every combination of the maximal configuration (forward graph)")
324 // To test this, we ensure that for the `i`th event
325 // in `ordered_events`, each event in `ordered_events[0...<i]
326 // is contained in the history of `ordered_events[i]`.
327 EventSet events_seen;
328 const auto ordered_events = C.get_topologically_sorted_events();
330 std::for_each(ordered_events.begin(), ordered_events.end(), [&events_seen](const UnfoldingEvent* e) {
332 for (auto* e_hist : history) {
333 // In this demo, we want to make sure that
334 // we don't mark not yet seeing `e` as an error.
335 // The history of `e` traverses `e` itself. All
336 // other events in e's history are included in
340 // If this event has not been "seen" before,
341 // this implies that event `e` appears earlier
342 // in the list than one of its dependencies
343 REQUIRE(events_seen.contains(e_hist));
345 events_seen.insert(e);
349 SECTION("Test every combination of the maximal configuration (reverse graph)")
351 // To test this, we ensure that for the `i`th event
352 // in `ordered_events`, no event in `ordered_events[0...<i]
353 // is contained in the history of `ordered_events[i]`.
354 EventSet events_seen;
355 const auto ordered_events = C.get_topologically_sorted_events_of_reverse_graph();
357 std::for_each(ordered_events.begin(), ordered_events.end(), [&events_seen](const UnfoldingEvent* e) {
360 for (auto* e_hist : history) {
361 // Unlike the test above, we DO want to ensure
362 // that `e` itself ALSO isn't yet seen
364 // If this event has been "seen" before,
365 // this implies that event `e` appears later
366 // in the list than one of its ancestors
367 REQUIRE_FALSE(events_seen.contains(e_hist));
369 events_seen.insert(e);
373 SECTION("Test that the topological ordering contains only the events of the configuration")
375 const EventSet events_seen = C.get_events();
377 SECTION("Forward direction")
379 auto ordered_events = C.get_topologically_sorted_events();
380 const EventSet ordered_event_set = EventSet(std::move(ordered_events));
381 REQUIRE(events_seen == ordered_event_set);
384 SECTION("Reverse direction")
386 auto ordered_events = C.get_topologically_sorted_events_of_reverse_graph();
387 const EventSet ordered_event_set = EventSet(std::move(ordered_events));
388 REQUIRE(events_seen == ordered_event_set);
392 SECTION("Test that the topological ordering is equivalent to that of the configuration's events")
394 REQUIRE(C.get_topologically_sorted_events() == C.get_events().get_topological_ordering());
395 REQUIRE(C.get_topologically_sorted_events_of_reverse_graph() ==
396 C.get_events().get_topological_ordering_of_reverse_graph());
400 TEST_CASE("simgrid::mc::udpor::maximal_subsets_iterator: Basic Testing of Maximal Subsets")
402 // The following tests concern the given event structure:
410 UnfoldingEvent e1(EventSet(), std::make_shared<IndependentAction>());
411 UnfoldingEvent e2(EventSet({&e1}), std::make_shared<IndependentAction>());
412 UnfoldingEvent e3(EventSet({&e2}), std::make_shared<IndependentAction>());
413 UnfoldingEvent e4(EventSet({&e3}), std::make_shared<IndependentAction>());
414 UnfoldingEvent e5(EventSet({&e1}), std::make_shared<IndependentAction>());
415 UnfoldingEvent e6(EventSet({&e5}), std::make_shared<IndependentAction>());
416 UnfoldingEvent e7(EventSet({&e6}), std::make_shared<IndependentAction>());
417 UnfoldingEvent e8(EventSet({&e6}), std::make_shared<IndependentAction>());
419 SECTION("Iteration over an empty configuration yields only the empty set")
422 maximal_subsets_iterator first(C);
423 maximal_subsets_iterator last;
425 REQUIRE(*first == EventSet());
427 REQUIRE(first == last);
430 SECTION("Check counts of maximal event sets discovered")
432 std::unordered_map<int, int> maximal_subset_counts;
434 Configuration C{&e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8};
435 maximal_subsets_iterator first(C);
436 maximal_subsets_iterator last;
438 for (; first != last; ++first) {
439 maximal_subset_counts[(*first).size()]++;
442 // First, ensure that there are only sets of size 0, 1, 2, and 3
443 CHECK(maximal_subset_counts.size() == 4);
445 // The empty set should appear only once
446 REQUIRE(maximal_subset_counts[0] == 1);
448 // 8 is the number of nodes in the graph
449 REQUIRE(maximal_subset_counts[1] == 8);
451 // 13 = 3 * 4 (each of the left branch can combine with one in the right branch) + 1 (e7 + e8)
452 REQUIRE(maximal_subset_counts[2] == 13);
454 // e7 + e8 must be included, so that means we can combine from the left branch
455 REQUIRE(maximal_subset_counts[3] == 3);
458 SECTION("Check counts of maximal event sets discovered with a filter")
460 std::unordered_map<int, int> maximal_subset_counts;
462 Configuration C{&e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8};
464 SECTION("Filter with events part of initial maximal set")
466 EventSet interesting_bunch{&e2, &e4, &e7, &e8};
468 maximal_subsets_iterator first(C, [&](const UnfoldingEvent* e) { return interesting_bunch.contains(e); });
469 maximal_subsets_iterator last;
471 for (; first != last; ++first) {
472 const auto& event_set = *first;
473 // Only events in `interesting_bunch` can appear: thus no set
474 // should include anything else other than `interesting_bunch`
475 REQUIRE(event_set.is_subset_of(interesting_bunch));
476 REQUIRE(event_set.is_maximal());
477 maximal_subset_counts[event_set.size()]++;
480 // The empty set should (still) appear only once
481 REQUIRE(maximal_subset_counts[0] == 1);
483 // 4 is the number of nodes in the `interesting_bunch`
484 REQUIRE(maximal_subset_counts[1] == 4);
486 // 5 = 2 * 2 (each of the left branch can combine with one in the right branch) + 1 (e7 + e8)
487 REQUIRE(maximal_subset_counts[2] == 5);
489 // e7 + e8 must be included, so that means we can combine from the left branch (only e2 and e4)
490 REQUIRE(maximal_subset_counts[3] == 2);
492 // There are no subsets of size 4 (or higher, but that
493 // is tested by asserting each maximal set traversed is a subset)
494 REQUIRE(maximal_subset_counts[4] == 0);
497 SECTION("Filter with interesting subset not initially part of the maximal set")
499 EventSet interesting_bunch{&e3, &e5, &e6};
501 maximal_subsets_iterator first(C, [&](const UnfoldingEvent* e) { return interesting_bunch.contains(e); });
502 maximal_subsets_iterator last;
504 for (; first != last; ++first) {
505 const auto& event_set = *first;
506 // Only events in `interesting_bunch` can appear: thus no set
507 // should include anything else other than `interesting_bunch`
508 REQUIRE(event_set.is_subset_of(interesting_bunch));
509 REQUIRE(event_set.is_maximal());
510 maximal_subset_counts[event_set.size()]++;
513 // The empty set should (still) appear only once
514 REQUIRE(maximal_subset_counts[0] == 1);
516 // 3 is the number of nodes in the `interesting_bunch`
517 REQUIRE(maximal_subset_counts[1] == 3);
519 // 2 = e3, e5 and e3, e6
520 REQUIRE(maximal_subset_counts[2] == 2);
522 // There are no subsets of size 3 (or higher, but that
523 // is tested by asserting each maximal set traversed is a subset)
524 REQUIRE(maximal_subset_counts[3] == 0);
529 TEST_CASE("simgrid::mc::udpor::maximal_subsets_iterator: Stress Test for Maximal Subsets Iteration")
531 // The following tests concern the given event structure:
536 // +------* e4 *e5 e6 e7
540 // | e11 e12 e13 e14 e15
543 UnfoldingEvent e1(EventSet(), std::make_shared<IndependentAction>());
544 UnfoldingEvent e2(EventSet({&e1}), std::make_shared<IndependentAction>());
545 UnfoldingEvent e3(EventSet({&e1}), std::make_shared<IndependentAction>());
546 UnfoldingEvent e4(EventSet({&e2}), std::make_shared<IndependentAction>());
547 UnfoldingEvent e5(EventSet({&e2}), std::make_shared<IndependentAction>());
548 UnfoldingEvent e6(EventSet({&e3}), std::make_shared<IndependentAction>());
549 UnfoldingEvent e7(EventSet({&e3}), std::make_shared<IndependentAction>());
550 UnfoldingEvent e8(EventSet({&e4}), std::make_shared<IndependentAction>());
551 UnfoldingEvent e9(EventSet({&e4, &e5, &e6}), std::make_shared<IndependentAction>());
552 UnfoldingEvent e10(EventSet({&e6, &e7}), std::make_shared<IndependentAction>());
553 UnfoldingEvent e11(EventSet({&e8}), std::make_shared<IndependentAction>());
554 UnfoldingEvent e12(EventSet({&e8}), std::make_shared<IndependentAction>());
555 UnfoldingEvent e13(EventSet({&e9}), std::make_shared<IndependentAction>());
556 UnfoldingEvent e14(EventSet({&e9}), std::make_shared<IndependentAction>());
557 UnfoldingEvent e15(EventSet({&e10}), std::make_shared<IndependentAction>());
558 UnfoldingEvent e16(EventSet({&e5, &e11}), std::make_shared<IndependentAction>());
559 UnfoldingEvent e17(EventSet({&e12, &e13, &e14}), std::make_shared<IndependentAction>());
560 UnfoldingEvent e18(EventSet({&e14, &e15}), std::make_shared<IndependentAction>());
561 Configuration C{&e1, &e2, &e3, &e4, &e5, &e6, &e7, &e8, &e9, &e10, &e11, &e12, &e13, &e14, &e15, &e16, &e17, &e18};
563 SECTION("Every subset iterated over is maximal")
565 maximal_subsets_iterator first(C);
566 maximal_subsets_iterator last;
568 // Make sure we actually have something to iterate over
569 REQUIRE(first != last);
571 for (; first != last; ++first) {
572 REQUIRE((*first).size() <= C.get_events().size());
573 REQUIRE((*first).is_maximal());
577 SECTION("Test that the maximal set ordering is equivalent to that of the configuration's events")
579 maximal_subsets_iterator first_config(C);
580 maximal_subsets_iterator first_events(C.get_events());
581 maximal_subsets_iterator last;
583 // Make sure we actually have something to iterate over
584 REQUIRE(first_config != last);
585 REQUIRE(first_config == first_events);
586 REQUIRE(first_events != last);
588 for (; first_config != last; ++first_config, ++first_events) {
589 // first_events and first_config should always be at the same location
590 REQUIRE(first_events != last);
591 const auto& first_config_set = *first_config;
592 const auto& first_events_set = *first_events;
594 REQUIRE(first_config_set.size() <= C.get_events().size());
595 REQUIRE(first_config_set.is_maximal());
596 REQUIRE(first_events_set == first_config_set);
599 // Iteration with events directly should now also be finished
600 REQUIRE(first_events == last);
604 TEST_CASE("simgrid::mc::udpor:Configuration: Computing Full Alternatives in Reader/Writer Example")
606 // The following tests concern the given event structure that is given as
607 // an example in figure 1 of the original UDPOR paper.
616 // Theses tests walk through exactly the configurations and sets of `D` that
617 // UDPOR COULD encounter as it walks through the unfolding. Note that
618 // if there are multiple alternatives to any given configuration, UDPOR can
619 // continue searching any one of them. The sequence assumes UDPOR first picks `e1`,
620 // then `e4`, and then `e7`
623 auto e0 = std::make_unique<UnfoldingEvent>(EventSet(), std::make_shared<ConditionallyDependentAction>());
624 auto e0_handle = e0.get();
626 auto e1 = std::make_unique<UnfoldingEvent>(EventSet({e0_handle}), std::make_shared<DependentAction>());
627 auto e1_handle = e1.get();
629 auto e2 = std::make_unique<UnfoldingEvent>(EventSet({e1_handle}), std::make_shared<ConditionallyDependentAction>());
630 auto e2_handle = e2.get();
632 auto e3 = std::make_unique<UnfoldingEvent>(EventSet({e1_handle}), std::make_shared<ConditionallyDependentAction>());
633 auto e3_handle = e3.get();
635 auto e4 = std::make_unique<UnfoldingEvent>(EventSet({e0_handle}), std::make_shared<ConditionallyDependentAction>());
636 auto e4_handle = e4.get();
638 auto e5 = std::make_unique<UnfoldingEvent>(EventSet({e4_handle}), std::make_shared<DependentAction>());
639 auto e5_handle = e5.get();
641 auto e6 = std::make_unique<UnfoldingEvent>(EventSet({e5_handle}), std::make_shared<ConditionallyDependentAction>());
642 auto e6_handle = e6.get();
644 auto e7 = std::make_unique<UnfoldingEvent>(EventSet({e0_handle}), std::make_shared<ConditionallyDependentAction>());
645 auto e7_handle = e7.get();
647 auto e8 = std::make_unique<UnfoldingEvent>(EventSet({e4_handle, e7_handle}), std::make_shared<DependentAction>());
648 auto e8_handle = e8.get();
650 auto e9 = std::make_unique<UnfoldingEvent>(EventSet({e7_handle}), std::make_shared<DependentAction>());
651 auto e9_handle = e9.get();
653 auto e10 = std::make_unique<UnfoldingEvent>(EventSet({e9_handle}), std::make_shared<ConditionallyDependentAction>());
654 auto e10_handle = e10.get();
656 SECTION("Alternative computation call 1")
658 // During the first call to Alt(C, D + {e}),
659 // UDPOR believes `U` to be the following:
668 // C := {e0, e1, e2} and `Explore(C, D, A)` picked `e3`
669 // (since en(C') where C' := {e0, e1, e2, e3} is empty
670 // [so UDPOR will simply return when C' is reached])
672 // Thus the computation is (since D is empty at first)
674 // Alt(C, D + {e}) --> Alt({e0, e1, e2}, {e3})
676 // where U is given above. There are no alternatives in
677 // this case since `e4` and `e7` conflict with `e1` (so
678 // they cannot be added to C to form a configuration)
679 const Configuration C{e0_handle, e1_handle, e2_handle};
680 const EventSet D_plus_e{e3_handle};
683 U.insert(std::move(e0));
684 U.insert(std::move(e1));
685 U.insert(std::move(e2));
686 U.insert(std::move(e3));
687 U.insert(std::move(e4));
688 U.insert(std::move(e7));
690 const auto alternative = C.compute_alternative_to(D_plus_e, U);
691 REQUIRE_FALSE(alternative.has_value());
694 SECTION("Alternative computation call 2")
696 // During the second call to Alt(C, D + {e}),
697 // UDPOR believes `U` to be the following:
706 // C := {e0, e1} and `Explore(C, D, A)` picked `e2`.
708 // Thus the computation is (since D is still empty)
710 // Alt(C, D + {e}) --> Alt({e0, e1}, {e2})
712 // where U is given above. There are no alternatives in
713 // this case since `e4` and `e7` conflict with `e1` (so
714 // they cannot be added to C to form a configuration) and
715 // e3 is NOT in conflict with either e0 or e1
716 const Configuration C{e0_handle, e1_handle};
717 const EventSet D_plus_e{e2_handle};
720 U.insert(std::move(e0));
721 U.insert(std::move(e1));
722 U.insert(std::move(e2));
723 U.insert(std::move(e3));
724 U.insert(std::move(e4));
725 U.insert(std::move(e7));
727 const auto alternative = C.compute_alternative_to(D_plus_e, U);
728 REQUIRE_FALSE(alternative.has_value());
731 SECTION("Alternative computation call 3")
733 // During the thrid call to Alt(C, D + {e}),
734 // UDPOR believes `U` to be the following:
743 // C := {e0} and `Explore(C, D, A)` picked `e1`.
745 // Thus the computation is (since D is still empty)
747 // Alt(C, D + {e}) --> Alt({e0}, {e1})
749 // where U is given above. There are two alternatives in this case:
750 // {e0, e4} and {e0, e7}. Either one would be a valid choice for
751 // UDPOR, so we must check for the precense of either
752 const Configuration C{e0_handle};
753 const EventSet D_plus_e{e1_handle};
756 U.insert(std::move(e0));
757 U.insert(std::move(e1));
758 U.insert(std::move(e2));
759 U.insert(std::move(e3));
760 U.insert(std::move(e4));
761 U.insert(std::move(e7));
763 const auto alternative = C.compute_alternative_to(D_plus_e, U);
764 REQUIRE(alternative.has_value());
766 // The first alternative that is found is the one that is chosen. Since
767 // traversal over the elements of an unordered_set<> are not guaranteed,
768 // both {e0, e4} and {e0, e7} are valid alternatives
769 REQUIRE((alternative.value().get_events() == EventSet({e0_handle, e4_handle}) or
770 alternative.value().get_events() == EventSet({e0_handle, e7_handle})));
773 SECTION("Alternative computation call 4")
775 // During the fourth call to Alt(C, D + {e}),
776 // UDPOR believes `U` to be the following:
786 // C := {e0, e4, e5} and `Explore(C, D, A)` picked `e6`
787 // (since en(C') where C' := {e0, e4, e5, e6} is empty
788 // [so UDPOR will simply return when C' is reached])
790 // Thus the computation is (since D is {e1})
792 // Alt(C, D + {e}) --> Alt({e0, e4, e5}, {e1, e6})
794 // where U is given above. There are no alternatives in this
797 // 1.`e2/e3` are eliminated since their histories contain `e1`
798 // 2. `e7/e8` are eliminated because they conflict with `e5`
799 const Configuration C{e0_handle, e4_handle, e5_handle};
800 const EventSet D_plus_e{e1_handle, e6_handle};
803 U.insert(std::move(e0));
804 U.insert(std::move(e1));
805 U.insert(std::move(e2));
806 U.insert(std::move(e3));
807 U.insert(std::move(e4));
808 U.insert(std::move(e6));
809 U.insert(std::move(e7));
810 U.insert(std::move(e8));
812 const auto alternative = C.compute_alternative_to(D_plus_e, U);
813 REQUIRE_FALSE(alternative.has_value());
816 SECTION("Alternative computation call 5")
818 // During the fifth call to Alt(C, D + {e}),
819 // UDPOR believes `U` to be the following:
829 // C := {e0, e4} and `Explore(C, D, A)` picked `e5`
830 // (since en(C') where C' := {e0, e4, e5, e6} is empty
831 // [so UDPOR will simply return when C' is reached])
833 // Thus the computation is (since D is {e1})
835 // Alt(C, D + {e}) --> Alt({e0, e4}, {e1, e5})
837 // where U is given above. There are THREE alternatives in this case,
838 // viz. {e0, e7}, {e0, e4, e7} and {e0, e4, e7, e8}.
840 // To continue the search, UDPOR computes J / C which in this
841 // case gives {e7, e8}. Since `e8` is not in en(C), UDPOR will
842 // choose `e7` next and add `e5` to `D`
843 const Configuration C{e0_handle, e4_handle};
844 const EventSet D_plus_e{e1_handle, e5_handle};
847 U.insert(std::move(e0));
848 U.insert(std::move(e1));
849 U.insert(std::move(e2));
850 U.insert(std::move(e3));
851 U.insert(std::move(e4));
852 U.insert(std::move(e6));
853 U.insert(std::move(e7));
854 U.insert(std::move(e8));
855 REQUIRE(U.size() == 8);
857 const auto alternative = C.compute_alternative_to(D_plus_e, U);
858 REQUIRE(alternative.has_value());
859 REQUIRE((alternative.value().get_events() == EventSet({e0_handle, e7_handle}) or
860 alternative.value().get_events() == EventSet({e0_handle, e4_handle, e7_handle}) or
861 alternative.value().get_events() == EventSet({e0_handle, e4_handle, e7_handle, e8_handle})));
864 SECTION("Alternative computation call 6")
866 // During the sixth call to Alt(C, D + {e}),
867 // UDPOR believes `U` to be the following:
877 // C := {e0, e4, e7} and `Explore(C, D, A)` picked `e8`
878 // (since en(C') where C' := {e0, e4, e7, e8} is empty
879 // [so UDPOR will simply return when C' is reached])
881 // Thus the computation is (since D is {e1, e5} [see the last step])
883 // Alt(C, D + {e}) --> Alt({e0, e4, e7}, {e1, e5, e8})
885 // where U is given above. There are no alternatives in this case
886 // since all `e9` conflicts with `e4` and all other events of `U`
887 // are eliminated since their history intersects `D`
888 const Configuration C{e0_handle, e4_handle, e7_handle};
889 const EventSet D_plus_e{e1_handle, e5_handle, e8_handle};
892 U.insert(std::move(e0));
893 U.insert(std::move(e1));
894 U.insert(std::move(e2));
895 U.insert(std::move(e3));
896 U.insert(std::move(e4));
897 U.insert(std::move(e6));
898 U.insert(std::move(e7));
899 U.insert(std::move(e8));
900 U.insert(std::move(e9));
902 const auto alternative = C.compute_alternative_to(D_plus_e, U);
903 REQUIRE_FALSE(alternative.has_value());
906 SECTION("Alternative computation call 7")
908 // During the seventh call to Alt(C, D + {e}),
909 // UDPOR believes `U` to be the following:
919 // C := {e0, e4} and `Explore(C, D, A)` picked `e7`
921 // Thus the computation is (since D is {e1, e5} [see call 5])
923 // Alt(C, D + {e}) --> Alt({e0, e4}, {e1, e5, e7})
925 // where U is given above. There are no alternatives again in this case
926 // since all `e9` conflicts with `e4` and all other events of `U`
927 // are eliminated since their history intersects `D`
928 const Configuration C{e0_handle, e4_handle};
929 const EventSet D_plus_e{e1_handle, e5_handle, e7_handle};
932 U.insert(std::move(e0));
933 U.insert(std::move(e1));
934 U.insert(std::move(e2));
935 U.insert(std::move(e3));
936 U.insert(std::move(e4));
937 U.insert(std::move(e6));
938 U.insert(std::move(e7));
939 U.insert(std::move(e8));
940 U.insert(std::move(e9));
942 const auto alternative = C.compute_alternative_to(D_plus_e, U);
943 REQUIRE_FALSE(alternative.has_value());
946 SECTION("Alternative computation call 8")
948 // During the eigth call to Alt(C, D + {e}),
949 // UDPOR believes `U` to be the following:
959 // C := {e0} and `Explore(C, D, A)` picked `e4`. At this
960 // point, UDPOR finished its recursive search of {e0, e4}
961 // after having finished {e0, e1} prior.
963 // Thus the computation is (since D = {e1})
965 // Alt(C, D + {e}) --> Alt({e0}, {e1, e4})
967 // where U is given above. There is one alternative in this
968 // case, viz {e0, e7, e9} since
969 // 1. e9 conflicts with e4 in D
970 // 2. e7 conflicts with e1 in D
971 // 3. the set {e7, e9} is conflict-free since `e7 < e9`
972 // 4. all other events are eliminated since their histories
975 // UDPOR will continue its recursive search following `e7`
977 const Configuration C{e0_handle};
978 const EventSet D_plus_e{e1_handle, e4_handle};
981 U.insert(std::move(e0));
982 U.insert(std::move(e1));
983 U.insert(std::move(e2));
984 U.insert(std::move(e3));
985 U.insert(std::move(e4));
986 U.insert(std::move(e6));
987 U.insert(std::move(e7));
988 U.insert(std::move(e8));
989 U.insert(std::move(e9));
991 const auto alternative = C.compute_alternative_to(D_plus_e, U);
992 REQUIRE(alternative.has_value());
993 REQUIRE(alternative.value().get_events() == EventSet({e0_handle, e7_handle, e9_handle}));
996 SECTION("Alternative computation call 9")
998 // During the ninth call to Alt(C, D + {e}),
999 // UDPOR believes `U` to be the following:
1009 // C := {e0, e7, e9} and `Explore(C, D, A)` picked `e10`.
1010 // (since en(C') where C' := {e0, e7, e9, e10} is empty
1011 // [so UDPOR will simply return when C' is reached]).
1013 // Thus the computation is (since D = {e1, e4} [see the previous step])
1015 // Alt(C, D + {e}) --> Alt({e0}, {e1, e4, e10})
1017 // where U is given above. There are no alternatives in this case
1018 const Configuration C{e0_handle, e7_handle, e9_handle};
1019 const EventSet D_plus_e{e1_handle, e4_handle, e10_handle};
1022 U.insert(std::move(e0));
1023 U.insert(std::move(e1));
1024 U.insert(std::move(e2));
1025 U.insert(std::move(e3));
1026 U.insert(std::move(e4));
1027 U.insert(std::move(e6));
1028 U.insert(std::move(e7));
1029 U.insert(std::move(e8));
1030 U.insert(std::move(e9));
1031 U.insert(std::move(e10));
1033 const auto alternative = C.compute_alternative_to(D_plus_e, U);
1034 REQUIRE_FALSE(alternative.has_value());
1037 SECTION("Alternative computation call 10")
1039 // During the tenth call to Alt(C, D + {e}),
1040 // UDPOR believes `U` to be the following:
1050 // C := {e0, e7} and `Explore(C, D, A)` picked `e9`.
1052 // Thus the computation is (since D = {e1, e4} [see call 8])
1054 // Alt(C, D + {e}) --> Alt({e0}, {e1, e4, e9})
1056 // where U is given above. There are no alternatives in this case
1057 const Configuration C{e0_handle, e7_handle};
1058 const EventSet D_plus_e{e1_handle, e4_handle, e9_handle};
1061 U.insert(std::move(e0));
1062 U.insert(std::move(e1));
1063 U.insert(std::move(e2));
1064 U.insert(std::move(e3));
1065 U.insert(std::move(e4));
1066 U.insert(std::move(e6));
1067 U.insert(std::move(e7));
1068 U.insert(std::move(e8));
1069 U.insert(std::move(e9));
1070 U.insert(std::move(e10));
1072 const auto alternative = C.compute_alternative_to(D_plus_e, U);
1073 REQUIRE_FALSE(alternative.has_value());
1076 SECTION("Alternative computation call 11 (final call)")
1078 // During the eleventh and final call to Alt(C, D + {e}),
1079 // UDPOR believes `U` to be the following:
1089 // C := {e0} and `Explore(C, D, A)` picked `e7`.
1091 // Thus the computation is (since D = {e1, e4} [see call 8])
1093 // Alt(C, D + {e}) --> Alt({e0}, {e1, e4, e7})
1095 // where U is given above. There are no alternatives in this case:
1096 // everyone is eliminated!
1097 const Configuration C{e0_handle, e7_handle};
1098 const EventSet D_plus_e{e1_handle, e4_handle, e9_handle};
1101 U.insert(std::move(e0));
1102 U.insert(std::move(e1));
1103 U.insert(std::move(e2));
1104 U.insert(std::move(e3));
1105 U.insert(std::move(e4));
1106 U.insert(std::move(e6));
1107 U.insert(std::move(e7));
1108 U.insert(std::move(e8));
1109 U.insert(std::move(e9));
1110 U.insert(std::move(e10));
1112 const auto alternative = C.compute_alternative_to(D_plus_e, U);
1113 REQUIRE_FALSE(alternative.has_value());
1116 SECTION("Alternative computation next")
1118 SECTION("Followed {e0, e7} first")
1120 const EventSet D{e1_handle, e7_handle};
1121 const Configuration C{e0_handle};
1124 U.insert(std::move(e0));
1125 U.insert(std::move(e1));
1126 U.insert(std::move(e2));
1127 U.insert(std::move(e3));
1128 U.insert(std::move(e4));
1129 U.insert(std::move(e5));
1130 U.insert(std::move(e7));
1131 U.insert(std::move(e8));
1132 U.insert(std::move(e9));
1133 U.insert(std::move(e10));
1135 const auto alternative = C.compute_alternative_to(D, U);
1136 REQUIRE(alternative.has_value());
1138 // In this case, only {e0, e4} is a valid alternative
1139 REQUIRE(alternative.value().get_events() == EventSet({e0_handle, e4_handle, e5_handle}));
1142 SECTION("Followed {e0, e4} first")
1144 const EventSet D{e1_handle, e4_handle};
1145 const Configuration C{e0_handle};
1148 U.insert(std::move(e0));
1149 U.insert(std::move(e1));
1150 U.insert(std::move(e2));
1151 U.insert(std::move(e3));
1152 U.insert(std::move(e4));
1153 U.insert(std::move(e5));
1154 U.insert(std::move(e6));
1155 U.insert(std::move(e7));
1156 U.insert(std::move(e8));
1157 U.insert(std::move(e9));
1159 const auto alternative = C.compute_alternative_to(D, U);
1160 REQUIRE(alternative.has_value());
1162 // In this case, only {e0, e7} is a valid alternative
1163 REQUIRE(alternative.value().get_events() == EventSet({e0_handle, e7_handle, e9_handle}));