1 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
2 // -*- mode: C++ -*-
3 //
4 // Copyright 2021-2024 Google LLC
5 //
6 // Licensed under the Apache License v2.0 with LLVM Exceptions (the
7 // "License"); you may not use this file except in compliance with the
8 // License. You may obtain a copy of the License at
9 //
10 // https://llvm.org/LICENSE.txt
11 //
12 // Unless required by applicable law or agreed to in writing, software
13 // distributed under the License is distributed on an "AS IS" BASIS,
14 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 // See the License for the specific language governing permissions and
16 // limitations under the License.
17 //
18 // Author: Giuliano Procida
19
20 #include "order.h"
21
22 #include <cstddef>
23 #include <optional>
24 #include <random>
25 #include <sstream>
26 #include <string>
27 #include <tuple>
28 #include <utility>
29 #include <vector>
30
31 #include <catch2/catch.hpp>
32
33 namespace Test {
34
35 // Safe for small k!
Factorial(size_t k)36 size_t Factorial(size_t k) {
37 size_t count = 1;
38 for (size_t i = 1; i <= k; ++i) {
39 count *= i;
40 }
41 return count;
42 }
43
44 TEST_CASE("hand-curated permutation") {
45 std::vector<std::string> data = {"emily", "george", "rose", "ted"};
46 std::vector<size_t> permutation = {2, 1, 3, 0};
47 const std::vector<std::string> expected = {"rose", "george", "ted", "emily"};
48 const std::vector<size_t> identity = {0, 1, 2, 3};
49 stg::Permute(data, permutation);
50 CHECK(data == expected);
51 CHECK(permutation == identity);
52 }
53
54 template <typename G>
MakePermutation(size_t k,G & gen)55 std::vector<size_t> MakePermutation(size_t k, G& gen) {
56 std::vector<size_t> result(k);
57 for (size_t i = 0; i < k; ++i) {
58 result[i] = i;
59 }
60 for (size_t i = 0; i < k; ++i) {
61 // pick one of [i, k)
62 std::uniform_int_distribution<size_t> toss(i, k - 1);
63 const auto pick = toss(gen);
64 using std::swap;
65 swap(result[i], result[pick]);
66 }
67 return result;
68 }
69
70 TEST_CASE("randomly-generated permutations") {
71 std::ranlux48 gen;
72 auto seed = gen();
73 // NOTES:
74 // Permutations of size 6 are plenty big enough to shake out bugs.
75 /// There are k! permutations of size k. Testing costs are O(k).
76 for (size_t k = 0; k < 7; ++k) {
77 const auto count = Factorial(k);
78 INFO("testing with " << count << " permutations of size " << k);
79 std::vector<size_t> identity(k);
80 for (size_t i = 0; i < k; ++i) {
81 identity[i] = i;
82 }
83 for (size_t n = 0; n < count; ++n, ++seed) {
84 gen.seed(seed);
85 auto permutation = MakePermutation(k, gen);
86 std::ostringstream os;
87 os << "permutation of " << k << " numbers generated using seed " << seed;
88 GIVEN(os.str()) {
89 // NOTE: We could test with something other than [0, k) as the data, but
90 // let's just say "parametric polymorphism" and move on.
91 auto permutation_copy = permutation;
92 auto identity_copy = identity;
93 stg::Permute(identity_copy, permutation_copy);
94 for (size_t i = 0; i < k; ++i) {
95 // permutation_copy should be the identity
96 CHECK(permutation_copy[i] == i);
97 // identity_copy should now be the same as permutation
98 CHECK(identity_copy[i] == permutation[i]);
99 }
100 }
101 }
102 }
103 }
104
105 TEST_CASE("randomly-generating ordering sequences, fully-matching") {
106 std::ranlux48 gen;
107 auto seed = gen();
108 // NOTES:
109 // Permutations of size 6 are plenty big enough to shake out bugs.
110 /// There are k! permutations of size k. Testing costs are O(k^2).
111 for (size_t k = 0; k < 7; ++k) {
112 const auto count = Factorial(k);
113 INFO("testing with " << count << " random orderings of size " << k);
114 for (size_t n = 0; n < count; ++n, ++seed) {
115 gen.seed(seed);
116 const auto order1 = MakePermutation(k, gen);
117 const auto order2 = MakePermutation(k, gen);
118 std::ostringstream os;
119 os << "orderings of " << k << " numbers generated using seed " << seed;
120 GIVEN(os.str()) {
121 const auto combined = stg::CombineOrders(order1, order2, k);
122 // combined should be identical to order2
123 CHECK(combined == order2);
124 }
125 }
126 }
127 }
128
129 TEST_CASE("randomly-generating ordering sequences, disjoint") {
130 std::ranlux48 gen;
131 auto seed = gen();
132 // NOTES:
133 // Orderings of size 4 are plenty big enough to shake out bugs.
134 /// There are k! permutations of size k. Testing costs are O(k^2).
135 for (size_t k = 0; k < 5; ++k) {
136 const auto count = Factorial(k);
137 INFO("testing with " << count << " random orderings of size " << k);
138 for (size_t n = 0; n < count; ++n, ++seed) {
139 gen.seed(seed);
140 const auto order1 = MakePermutation(k, gen);
141 auto order2 = MakePermutation(k, gen);
142 for (size_t i = 0; i < k; ++i) {
143 order2[i] += k;
144 }
145 std::ostringstream os;
146 os << "orderings of " << k << " numbers generated using seed " << seed;
147 GIVEN(os.str()) {
148 const auto combined = stg::CombineOrders(order1, order2, 2 * k);
149 for (size_t i = 0; i < k; ++i) {
150 // order1 should appear as the first part
151 CHECK(combined[i] == order1[i]);
152 // order2 should appear as the second part
153 CHECK(combined[i + k] == order2[i]);
154 }
155 }
156 }
157 }
158 }
159
160 TEST_CASE("randomly-generating ordering sequences, single overlap") {
161 std::ranlux48 gen;
162 auto seed = gen();
163 // NOTES:
164 // Orderings of size 4 are plenty big enough to shake out bugs.
165 /// There are k! permutations of size k. Testing costs are O(k^2).
166 for (size_t k = 1; k < 5; ++k) {
167 const auto count = Factorial(k);
168 INFO("testing with " << count << " random orderings of size " << k);
169 for (size_t n = 0; n < count; ++n, ++seed) {
170 gen.seed(seed);
171 const auto order1 = MakePermutation(k, gen);
172 auto order2 = MakePermutation(k, gen);
173 for (size_t i = 0; i < k; ++i) {
174 order2[i] += k - 1;
175 }
176 const auto pivot = k - 1;
177 std::ostringstream os;
178 os << "orderings of " << k << " numbers generated using seed " << seed;
179 GIVEN(os.str()) {
180 const auto combined = stg::CombineOrders(order1, order2, 2 * k - 1);
181 CHECK(combined.size() == 2 * k - 1);
182 // order1 pre, order2 pre, pivot, order1 post, order2 post
183 size_t ix = 0;
184 size_t ix1 = 0;
185 size_t ix2 = 0;
186 while (order1[ix1] != pivot) {
187 CHECK(combined[ix++] == order1[ix1++]);
188 }
189 while (order2[ix2] != pivot) {
190 CHECK(combined[ix++] == order2[ix2++]);
191 }
192 ++ix1;
193 ++ix2;
194 CHECK(combined[ix++] == pivot);
195 while (ix1 < k) {
196 CHECK(combined[ix++] == order1[ix1++]);
197 }
198 while (ix2 < k) {
199 CHECK(combined[ix++] == order2[ix2++]);
200 }
201 }
202 }
203 }
204 }
205
206 TEST_CASE("hand-curated ordering sequences") {
207 using Sequence = std::vector<std::string>;
208 // NOTES:
209 // The output sequence MUST include the second sequence as a subsequence.
210 // The first sequence's ordering is respected as far as possible.
211 const std::vector<std::tuple<Sequence, Sequence, Sequence>> cases = {
212 {{"rose", "george", "emily"}, {"george", "ted", "emily"},
213 {"rose", "george", "ted", "emily"}},
214 {{}, {}, {}},
215 {{"a"}, {}, {"a"}},
216 {{}, {"a"}, {"a"}},
217 {{"a", "z"}, {}, {"a", "z"}},
218 {{}, {"a", "z"}, {"a", "z"}},
219 {{"a", "b", "c"}, {"c", "d"}, {"a", "b", "c", "d"}},
220 {{"a", "b", "d"}, {"b", "c", "d"}, {"a", "b", "c", "d"}},
221 {{"a", "c", "d"}, {"a", "b", "c"}, {"a", "b", "c", "d"}},
222 {{"b", "c", "d"}, {"a", "b"}, {"a", "b", "c", "d"}},
223 {{"z", "a", "q"}, {"a", "z"}, {"a", "z", "q"}},
224 };
225 for (const auto& [order1, order2, expected] : cases) {
226 const auto combined = stg::CombineOrders(order1, order2, expected.size());
227 CHECK(combined == expected);
228 }
229 }
230
231 TEST_CASE("hand-curated reorderings with input order randomisation") {
232 using Constraint = std::pair<std::optional<size_t>, std::optional<size_t>>;
233 using Constraints = std::vector<Constraint>;
234 // NOTES:
235 // item removed at position x: {x}, {}
236 // item added at position y: {}, {y}
237 // item modified at positions x and y: {x}, {y}
238 // input item order should be irrelevant to output order
239 const std::vector<std::pair<Constraints, Constraints>> cases = {
240 {
241 {
242 {{2}, {2}}, // emily
243 {{1}, {0}}, // george
244 {{0}, {}}, // rose
245 {{}, {1}}, // ted
246 },
247 {
248 {{0}, {}}, // rose
249 {{1}, {0}}, // george
250 {{}, {1}}, // ted
251 {{2}, {2}}, // emily
252 },
253 },
254 {{}, {}},
255 {{{{0}, {0}}}, {{{0}, {0}}}},
256 {{{{0}, {}}}, {{{0}, {}}}},
257 {{{{}, {0}}}, {{{}, {0}}}},
258 {{{{}, {2}}, {{}, {1}}, {{}, {0}}},
259 {{{}, {0}}, {{}, {1}}, {{}, {2}}}},
260 {{{{2}, {}}, {{1}, {}}, {{0}, {}}},
261 {{{0}, {}}, {{1}, {}}, {{2}, {}}}},
262 // int b; int c; -> int b; int a; int c
263 {{{{}, {1}}, {{0}, {0}}, {{1}, {2}}},
264 {{{0}, {0}}, {{}, {1}}, {{1}, {2}}}},
265 };
266 std::ranlux48 gen;
267 auto seed = gen();
268 for (const auto& [given, expected] : cases) {
269 const auto k = given.size();
270 const auto count = Factorial(k);
271 INFO("testing with " << count << " random input orderings");
272 for (size_t n = 0; n < count; ++n, ++seed) {
273 gen.seed(seed);
274 std::ostringstream os;
275 os << "permutation of " << k << " items generated using seed " << seed;
276 GIVEN(os.str()) {
277 auto permutation = MakePermutation(k, gen);
278 auto copy = given;
279 stg::Permute(copy, permutation);
280 stg::Reorder(copy);
281 CHECK(copy == expected);
282 }
283 }
284 }
285 }
286
287 } // namespace Test
288