Tests with rapidcheck !

This commit is contained in:
Stepland 2022-04-11 01:25:24 +02:00
parent f5d6199f27
commit be1eeaf21e
18 changed files with 301 additions and 25 deletions

View File

@ -2,7 +2,8 @@
In other words, how to create a new F.E.I.S. executable from the source code.
0. (If not done already) Set up you work environment by following [this page](docs/Setup.md)
0. (If not done already) Set up you work environment by following
[the Setup Steps](docs/Setup.md)
0. `cd` into the root of your local copy of F.E.I.S.'s source code
```console
@ -15,6 +16,13 @@ In other words, how to create a new F.E.I.S. executable from the source code.
$ meson setup build
```
If you want to compile the unit tests as well, pass in `-D tests=true`.
You can also set this option later by doing :
```console
$ meson configure build -D tests=true
```
0. Compile in that directory
```console

View File

@ -17,7 +17,8 @@ get a more up-to-date version than what your distro packages might have
$ pip install meson
```
Unfortunately this also means meson will not come with ninja so we need to install it ourselves :
Unfortunately this also means meson will not come with ninja so we need to
install it ourselves :
```console
$ sudo apt install ninja-build
@ -46,9 +47,9 @@ $ sudo apt install clang-format
#### MSYS2
MSYS2 is not the *usual* way to compile things for windows but it's the only
thing I know for now. If you know better, you're very welcome to do better
(and to also shower me with some of your knowledge, I absolutely *suck* at
build systems and would be delighted to hear from you)
thing I know for now. If you know better, by all means, do what you think is
best (and also please share some of your knowledge with me, I absolutely *suck*
at build systems and would be delighted to learn from an expert)
Installing MSYS2 is pretty simple. [Follow their instructions](https://www.msys2.org/)
@ -65,4 +66,4 @@ $ pacman -S \
```
Once this is done, open a new `MSYS2 MinGW x64` terminal and follow the
[instructions on how to compile](docs/Compiling.md)
[compilation instructions](docs/Compiling.md)

6
meson_options.txt Normal file
View File

@ -0,0 +1,6 @@
option(
'tests',
type: 'boolean',
value: false,
description: 'Build F.E.I.S\'s unit tests'
)

View File

@ -15,7 +15,7 @@ bool is_expressible_as_240th(const Fraction& beat) {
nlohmann::ordered_json beat_to_best_form(const Fraction& beat) {
if (is_expressible_as_240th(beat)) {
return nlohmann::ordered_json(
(240 * convert_to_u64(beat.numerator()) / convert_to_u64(beat.denominator())
(240 * convert_to_u64(beat.numerator())) / convert_to_u64(beat.denominator())
);
} else {
return beat_to_fraction_tuple(beat);

View File

@ -2,6 +2,8 @@
#include <variant>
#include <fmt/core.h>
#include "better_beats.hpp"
namespace better {
@ -14,10 +16,10 @@ namespace better {
};
Position::Position(std::uint64_t x, std::uint64_t y) : x(x), y(y) {
if (x > 3 or y > 3) {
if (x > 3 or y > 2) {
std::stringstream ss;
ss << "Attempted to create Position from invalid coordinates : ";
ss << this;
ss << *this;
throw std::invalid_argument(ss.str());
}
};
@ -35,7 +37,7 @@ namespace better {
};
std::ostream& operator<< (std::ostream& out, const Position& pos) {
out << "(x: " << pos.get_x() << ", y: " << pos.get_y() << ")";
out << fmt::format("(x: {}, y: {})", pos.x, pos.y);
return out;
};
@ -67,7 +69,7 @@ namespace better {
if (duration < 0) {
std::stringstream ss;
ss << "Attempted to create a LongNote with negative duration : ";
ss << duration.get_str();
ss << duration;
throw std::invalid_argument(ss.str());
}
if (tail_tip == position) {
@ -232,7 +234,7 @@ namespace better {
return std::visit([](const auto& n){return n.dump_to_memon_1_0_0();}, this->note);
}
Note Note::load_from_memon_0_1_0(const nlohmann::json& json, std::uint64_t resolution) {
Note Note::load_from_memon_1_0_0(const nlohmann::json& json, std::uint64_t resolution) {
const auto position = Position{json["n"].get<std::uint64_t>()};
const auto time = load_memon_1_0_0_beat(json["t"], resolution);
if (not json.contains("l")) {

View File

@ -32,14 +32,13 @@ namespace better {
std::uint64_t get_y() const;
auto operator<=>(const Position&) const = default;
friend std::ostream& operator<<(std::ostream& out, const Position& pos);
private:
std::uint64_t x;
std::uint64_t y;
};
std::ostream& operator<<(std::ostream& out, const Position& pos);
class TapNote {
public:
TapNote(Fraction time, Position position);
@ -96,9 +95,9 @@ namespace better {
nlohmann::ordered_json dump_to_memon_1_0_0() const;
static Note load_from_memon_0_1_0(
static Note load_from_memon_1_0_0(
const nlohmann::json& json,
std::uint64_t resolution
std::uint64_t resolution = 240
);
static Note load_from_memon_legacy(

View File

@ -32,4 +32,6 @@ sources += files(
subdir('widgets')
subdir('tests')
if get_option('tests')
subdir('tests')
endif

View File

@ -0,0 +1,83 @@
#include <rapidcheck.h>
#include "../../better_note.hpp"
#include "../../special_numeric_types.hpp"
#include "rapidcheck/gen/Arbitrary.h"
#include "rapidcheck/gen/Numeric.h"
namespace rc {
template<>
struct Arbitrary<better::Position> {
static Gen<better::Position> arbitrary() {
return gen::construct<better::Position>(
gen::inRange<unsigned int>(0, 16)
);
}
};
template<>
struct Arbitrary<Fraction> {
static Gen<Fraction> arbitrary() {
return gen::apply([](const Fraction& a, const Fraction& b) {
return a + b;
},
gen::cast<Fraction>(
gen::nonNegative<unsigned>()
),
gen::construct<Fraction>(
gen::nonNegative<unsigned>(),
gen::positive<unsigned>()
)
);
}
static Gen<Fraction> positive() {
return gen::apply([](const Fraction& a, const Fraction& b) {
return a + b;
},
gen::cast<Fraction>(
gen::nonNegative<unsigned>()
),
gen::construct<Fraction>(
gen::positive<unsigned>(),
gen::positive<unsigned>()
)
);
}
};
template<>
struct Arbitrary<better::TapNote> {
static Gen<better::TapNote> arbitrary() {
return gen::construct<better::TapNote>(
gen::arbitrary<Fraction>(),
gen::arbitrary<better::Position>()
);
}
};
template<>
struct Arbitrary<better::LongNote> {
static Gen<better::LongNote> arbitrary() {
const auto pos = *gen::arbitrary<better::Position>();
const auto tail_6_notation = *gen::inRange<unsigned int>(0, 6);
const auto tail_pos = better::convert_6_notation_to_position(pos, tail_6_notation);
return gen::construct<better::LongNote>(
gen::arbitrary<Fraction>(),
gen::just(pos),
gen::positive<Fraction>(),
gen::just(tail_pos)
);
}
};
template<>
struct Arbitrary<better::Note> {
static Gen<better::Note> arbitrary() {
return gen::oneOf(
gen::construct<better::Note>(gen::arbitrary<better::TapNote>()),
gen::construct<better::Note>(gen::arbitrary<better::LongNote>())
);
}
};
}

View File

@ -0,0 +1,17 @@
#include <rapidcheck.h>
#include "generators.hpp"
#include "../../better_note.hpp"
int main() {
rc::check(
"Notes survive being converted to json and back",
[](const better::Note& n) {
const auto j = n.dump_to_memon_1_0_0();
const auto n_recovered = better::Note::load_from_memon_1_0_0(j);
RC_ASSERT(n_recovered == n);
}
);
return 0;
}

View File

@ -1,7 +1,8 @@
rapidcheck_tests = executable(
'rapidcheck_tests',
'rapidcheck_main.cpp',
'test_fractions.cpp',
'main.cpp',
'../../better_note.cpp',
'../../better_beats.cpp',
'../../special_numeric_types.cpp',
include_sources['fmt'],
dependencies: [

View File

@ -1,6 +0,0 @@
#include <rapidcheck.h>
int main() {
return 0;
}

View File

@ -0,0 +1,71 @@
project(
'rapidcheck',
'cpp',
version: '0.1.0',
license: 'BSD-2-Clause',
default_options : [
'cpp_std=c++11',
'warning_level=3',
],
meson_version : '>=0.62.0',
)
sources = []
subdir('src') # fills in 'sources'
includes = include_directories('include')
add_project_arguments(
'-Wno-missing-braces',
'-Wno-unused-command-line-argument',
language: 'cpp'
)
# Random is used a LOT so it should preferably be really fast
rapidcheck_random_lib = static_library(
'rapidcheck_random',
'src/Random.cpp',
include_directories: includes,
cpp_args: '-O3',
)
# On Windows under MinGW, random_device provides no entropy,
# so it will always return the same value.
# Seed using system time instead.
# See: https://stackoverflow.com/questions/18880654/why-do-i-get-the-same-sequence-for-every-run-with-stdrandom-device-with-mingw
if host_machine.system() == 'cygwin'
add_project_arguments('-DRC_SEED_SYSTEM_TIME', language: 'cpp')
endif
if not get_option('rtti')
add_project_arguments('-DRC_DONT_USE_RTTI', language: 'cpp')
endif
# if get_option('tests')
# subdir('ext')
# subdir('test')
# endif
# if get_option('examples')
# ... more unfun stuff
# endif
rapidcheck_lib = static_library(
'rapidcheck',
sources,
include_directories: includes,
link_with: rapidcheck_random_lib,
cpp_args: [
'-Wall',
'-Wno-missing-braces',
'-Wno-unused-command-line-argument',
]
)
rapidcheck_dep = declare_dependency(
include_directories: includes,
link_with: rapidcheck_lib,
)
meson.override_dependency('rapidcheck', rapidcheck_dep)

View File

@ -0,0 +1,20 @@
option(
'rtti',
type: 'boolean',
value: true,
description: 'Build RapidCheck with Run-Time Type Inspection'
)
# option(
# 'tests',
# type: 'boolean',
# value: false,
# description: 'Build RapidCheck tests'
# )
# option(
# 'examples',
# type: 'boolean',
# value: true,
# description: 'Build RapidCheck examples'
# )

View File

@ -0,0 +1,23 @@
sources += files(
'Any.cpp',
'Assertions.cpp',
'Base64.cpp',
'Configuration.cpp',
'DefaultTestListener.cpp',
'FrequencyMap.cpp',
'ImplicitParam.cpp',
'LogTestListener.cpp',
'MapParser.cpp',
'MulticastTestListener.cpp',
'ParseException.cpp',
'Platform.cpp',
'Property.cpp',
'PropertyContext.cpp',
'ReproduceListener.cpp',
'Results.cpp',
'Serialization.cpp',
'StringSerialization.cpp',
'TestMetadata.cpp',
'TestParams.cpp',
'Testing.cpp',
)

View File

@ -0,0 +1,6 @@
sources += files(
'ExecHandler.cpp',
'GenerationHandler.cpp',
'Recipe.cpp',
'ScaleInteger.cpp',
)

View File

@ -0,0 +1,6 @@
sources += files(
'Numeric.cpp',
'Text.cpp',
)
subdir('detail')

View File

@ -0,0 +1,13 @@
sources += files(
'BeforeMinimalTestCase.cpp',
'Check.cpp',
'Classify.cpp',
'GenerationFailure.cpp',
'Log.cpp',
# Leave this one out because it needs to be compiled spearately
# 'Random.cpp',
'Show.cpp',
)
subdir('detail')
subdir('gen')

24
utils/save_meson_wrap.py Normal file
View File

@ -0,0 +1,24 @@
"""implement the missing --save option of meson subprojects packagefiles
for wrap-git packages"""
from argparse import ArgumentParser
from path import Path
parser = ArgumentParser()
parser.add_argument("wrap", type=Path)
args = parser.parse_args()
subprojects = Path("subprojects")
if not subprojects.exists():
print(f"{subprojects} folder doesn't exist, are you in the right directory ?")
subproject = subprojects / args.wrap
patch = subprojects / "packagefiles" / args.wrap
if not patch.exists():
print(f"subproject folder doesn't exist ({subproject}), is the name correct ?")
for absolute in subproject.walkfiles("*meson*"):
relative = absolute.relpath(subproject)
(patch / relative).parent.makedirs_p()
absolute.copy(patch / relative)