xref: /aosp_15_r20/external/stg/order_test.cc (revision 9e3b08ae94a55201065475453d799e8b1378bea6)
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