From 7c2a569fcac3bae05190e9e85bb9e5bb76e538af Mon Sep 17 00:00:00 2001 From: Martin Janiczek Date: Tue, 24 Oct 2023 01:47:26 +0200 Subject: [PATCH] LibTest: Add a suite of tests for the generators --- Meta/Lagom/CMakeLists.txt | 1 + Tests/LibTest/CMakeLists.txt | 1 + Tests/LibTest/TestGenerator.cpp | 168 ++++++++++++++++++++++++++++++++ 3 files changed, 170 insertions(+) create mode 100644 Tests/LibTest/TestGenerator.cpp diff --git a/Meta/Lagom/CMakeLists.txt b/Meta/Lagom/CMakeLists.txt index 20c99b2ef71..db3a6a7d93e 100644 --- a/Meta/Lagom/CMakeLists.txt +++ b/Meta/Lagom/CMakeLists.txt @@ -643,6 +643,7 @@ if (BUILD_LAGOM) LibMarkdown LibPDF LibSQL + LibTest LibTextCodec LibTTF LibTimeZone diff --git a/Tests/LibTest/CMakeLists.txt b/Tests/LibTest/CMakeLists.txt index 1efe46d0c45..84610cb211f 100644 --- a/Tests/LibTest/CMakeLists.txt +++ b/Tests/LibTest/CMakeLists.txt @@ -1,5 +1,6 @@ set(TEST_SOURCES TestNoCrash.cpp + TestGenerator.cpp ) foreach(source IN LISTS TEST_SOURCES) diff --git a/Tests/LibTest/TestGenerator.cpp b/Tests/LibTest/TestGenerator.cpp new file mode 100644 index 00000000000..efc1a72ee3d --- /dev/null +++ b/Tests/LibTest/TestGenerator.cpp @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2023, Martin Janiczek . + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +using namespace Test::Randomized; + +RANDOMIZED_TEST_CASE(unsigned_int_max_bounds) +{ + GEN(n, Gen::unsigned_int(10)); + EXPECT(n <= 10); +} + +RANDOMIZED_TEST_CASE(unsigned_int_min_max_bounds) +{ + GEN(n, Gen::unsigned_int(3, 6)); + EXPECT(n >= 3 && n <= 6); +} + +RANDOMIZED_TEST_CASE(assume) +{ + GEN(n, Gen::unsigned_int(10)); + ASSUME(n % 2 == 0); // This will try to generate until it finds an even number + EXPECT(n % 2 == 0); // This will then succeed + // It will give up if the value doesn't pass the ASSUME(...) predicate 15 times in a row. +} + +// TODO find a way to test that a test "unsigned_int(3) can't reach 0" fails +// TODO find a way to test that a test "unsigned_int(3) can't reach 3" fails +// TODO find a way to test that a test "unsigned_int(3,6) can't reach 3" fails +// TODO find a way to test that a test "unsigned_int(3,6) can't reach 6" fails +// TODO find a way to test that a test "unsigned_int(10) can reach n>10" fails + +RANDOMIZED_TEST_CASE(map_like) +{ + GEN(n1, Gen::unsigned_int(10)); + GEN(n2, n1 * 2); + EXPECT(n2 % 2 == 0); +} + +RANDOMIZED_TEST_CASE(bind_like) +{ + GEN(n1, Gen::unsigned_int(1, 9)); + GEN(n2, Gen::unsigned_int(n1 * 10, n1 * 100)); + EXPECT(n2 >= 10 && n2 <= 900); +} + +// An example of an user-defined generator (for the test bind_vector_suboptimal). +// +// For why this is a suboptimal way to generate collections, see the comment in +// Shrink::shrink_delete(). +// +// TL;DR: this makes the length non-local to the items we're trying to delete +// (except the first item). +// +// There's a better way: flip a (biased) coin to decide whether to generate +// a next item. That makes each item much better shrinkable, since its +// contribution to the sequence length (a boolean 0 or 1) is right next to its +// own data. +// +// Because it's a pretty natural way to do this, we take special care in the +// internal shrinker to work well on this style too. +template +Vector> vector_suboptimal(FN item_gen) +{ + u32 length = Gen::unsigned_int(5); + Vector> acc; + for (u32 i = 0; i < length; ++i) { + acc.append(item_gen()); + } + return acc; +} + +RANDOMIZED_TEST_CASE(bind_vector_suboptimal) +{ + u32 max_item = 5; + GEN(vec, vector_suboptimal([&]() { return Gen::unsigned_int(max_item); })); + u32 sum = 0; + for (u32 n : vec) { + sum += n; + } + EXPECT(sum <= vec.size() * max_item); +} + +RANDOMIZED_TEST_CASE(vector) +{ + u32 max_item = 5; + GEN(vec, Gen::vector([&]() { return Gen::unsigned_int(max_item); })); + EXPECT(vec.size() <= 32); +} + +RANDOMIZED_TEST_CASE(vector_length) +{ + u32 max_item = 5; + GEN(vec, Gen::vector(3, [&]() { return Gen::unsigned_int(max_item); })); + EXPECT(vec.size() == 3); +} + +RANDOMIZED_TEST_CASE(vector_min_max) +{ + u32 max_item = 5; + GEN(vec, Gen::vector(1, 4, [&]() { return Gen::unsigned_int(max_item); })); + EXPECT(vec.size() >= 1 && vec.size() <= 4); +} + +RANDOMIZED_TEST_CASE(weighted_boolean_below0) +{ + GEN(b, Gen::weighted_boolean(-0.5)); + EXPECT(b == false); +} + +RANDOMIZED_TEST_CASE(weighted_boolean_0) +{ + GEN(b, Gen::weighted_boolean(0)); + EXPECT(b == false); +} + +RANDOMIZED_TEST_CASE(weighted_boolean_1) +{ + GEN(b, Gen::weighted_boolean(1)); + EXPECT(b == true); +} + +RANDOMIZED_TEST_CASE(weighted_boolean_above1) +{ + GEN(b, Gen::weighted_boolean(1.5)); + EXPECT(b == true); +} + +RANDOMIZED_TEST_CASE(weighted_boolean_fair_true) +{ + GEN(b, Gen::weighted_boolean(0.5)); + ASSUME(b == true); + EXPECT(b == true); +} + +RANDOMIZED_TEST_CASE(weighted_boolean_fair_false) +{ + GEN(b, Gen::weighted_boolean(0.5)); + ASSUME(b == false); + EXPECT(b == false); +} + +RANDOMIZED_TEST_CASE(boolean_true) +{ + GEN(b, Gen::boolean()); + ASSUME(b == true); + EXPECT(b == true); +} + +RANDOMIZED_TEST_CASE(boolean_false) +{ + GEN(b, Gen::boolean()); + ASSUME(b == false); + EXPECT(b == false); +} + +RANDOMIZED_TEST_CASE(frequency_int) +{ + GEN(x, Gen::frequency(Gen::Choice { 5, 'x' }, Gen::Choice { 1, 'o' })); + ASSUME(x == 'x'); + EXPECT(x == 'x'); +}