Removed unused tests

This commit is contained in:
Robert Bendun 2022-09-21 16:40:40 +02:00
parent 1384e5f96e
commit 34eb56ac50
12 changed files with 2 additions and 1335 deletions

View File

@ -55,8 +55,6 @@ $ make bin/musique
### Testowanie ### Testowanie
- `make test` - Uruchom wszystkie dostępne testy automatyczne - `make test` - Uruchom wszystkie dostępne testy automatyczne
- `make unit-tests` - Uruchamia testy jednostkowe interpretera
- `make unit-test-coverage` - Uruchamia raport pokrycia kodu przez testy jednostkowe
- `scripts/test.py test examples` - Uruchamia testy zachowań przykładów - `scripts/test.py test examples` - Uruchamia testy zachowań przykładów
- `scripts/test.py record examples` - Nagrywa testy zachowań przykładów - `scripts/test.py record examples` - Nagrywa testy zachowań przykładów
@ -69,7 +67,6 @@ $ make bin/musique
``` ```
. .
├── bin Miejsce produkcji plików wykonywalnych ├── bin Miejsce produkcji plików wykonywalnych
├── coverage
├── doc Dokumentacja języka, interpretera ├── doc Dokumentacja języka, interpretera
│   └── build Miejsce produkcji dokumentacji │   └── build Miejsce produkcji dokumentacji
├── editor Pluginy do edytorów dodające wsparcie dla języka ├── editor Pluginy do edytorów dodające wsparcie dla języka
@ -79,7 +76,6 @@ $ make bin/musique
└── include Główny katalog z plikami nagłówkowymi └── include Główny katalog z plikami nagłówkowymi
├── scripts Skrypty wspierające budowanie i tworzenie ├── scripts Skrypty wspierające budowanie i tworzenie
└── src Główny katalog z plikami źródłowymi └── src Główny katalog z plikami źródłowymi
└── tests Katalog z testami jednostkowymi
``` ```
## Kolorowanie składni ## Kolorowanie składni

View File

@ -1,6 +1,6 @@
{ {
"returncode": 0, "returncode": 0,
"stdout": "1\n2\n6\n24\n120\n720\n5040\n40320\n362880\n3628800\n1\n2\n6\n24\n120\n720\n5040\n40320\n362880\n3628800\n", "stdout": "1\n1\n2\n6\n24\n120\n720\n5040\n40320\n362880\n1\n1\n2\n6\n24\n120\n720\n5040\n40320\n362880\n1\n1\n2\n6\n24\n120\n720\n5040\n40320\n362880\n",
"stderr": "", "stderr": "",
"flags": [] "flags": []
} }

View File

@ -11,23 +11,6 @@ Tests= \
Test_Obj=$(addprefix bin/debug/tests/,$(Tests)) Test_Obj=$(addprefix bin/debug/tests/,$(Tests))
test: unit-tests test: bin/debug/musique
scripts/test.py test examples scripts/test.py test examples
unit-tests: bin/unit-tests
./$<
bin/unit-tests: $(Test_Obj) $(Debug_Obj)
@echo "CXX $@"
@$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(DEBUG_FLAGS) -o $@ $^
unit-test-coverage:
@which gcov >/dev/null || ( echo "[ERROR] gcov is required for test coverage report"; false )
@which gcovr >/dev/null || ( echo "[ERROR] gcovr is required for test coverage report"; false )
CXXFLAGS=--coverage $(MAKE) bin/unit-tests -B
bin/unit-tests
rm -rf coverage
mkdir coverage
gcovr -e '.*\.hpp' -e 'src/tests/.*' -e 'src/pretty.cc' --html --html-details -o coverage/index.html
rm -rf bin/debug
xdg-open coverage/index.html

View File

@ -1,52 +0,0 @@
#include <boost/ut.hpp>
#include <musique.hh>
using namespace boost::ut;
using namespace std::string_view_literals;
void test_seconds_compute(
unsigned bpm,
Number length,
float value,
reflection::source_location const& sl = reflection::source_location::current())
{
Context const ctx { .bpm = bpm };
auto const dur = ctx.length_to_duration(length);
static_assert(std::same_as<decltype(dur)::period, std::ratio<1, 1>>,
"tests provided by this function expects Context::length_to_duration to return seconds");
expect(dur.count() == _f(value), sl);
}
suite context_suite = [] {
"Context"_test = [] {
should("resolve note duration length to seconds") = [] {
test_seconds_compute(60, Number(2,1), 8);
test_seconds_compute(60, Number(1,1), 4);
test_seconds_compute(60, Number(1,2), 2);
test_seconds_compute(60, Number(1,4), 1);
test_seconds_compute(96, Number(2,1), 5);
test_seconds_compute(96, Number(1,1), 2.5);
test_seconds_compute(96, Number(1,2), 1.25);
test_seconds_compute(96, Number(1,4), 0.625);
test_seconds_compute(120, Number(2,1), 4);
test_seconds_compute(120, Number(1,1), 2);
test_seconds_compute(120, Number(1,2), 1);
test_seconds_compute(120, Number(1,4), 0.5);
};
should("fill notes with default context values") = [] {
Context ctx;
ctx.length = Number(1, 42);
ctx.octave = 8;
expect(eq(ctx.fill({ .base = 0 }), Note { .base = 0, .octave = 8, .length = Number(1, 42) }));
expect(eq(ctx.fill({ .base = 0, .length = Number(4) }), Note { .base = 0, .octave = 8, .length = Number(4) }));
expect(eq(ctx.fill({ .base = 0, .octave = 1 }), Note { .base = 0, .octave = 1, .length = Number(1, 42) }));
expect(eq(ctx.fill({ .base = 0, .octave = 1, .length = Number(4) }), Note { .base = 0, .octave = 1, .length = Number(4) }));
};
};
};

View File

@ -1,61 +0,0 @@
#include <boost/ut.hpp>
#include <musique.hh>
using namespace boost::ut;
using namespace std::string_view_literals;
static void equals(
Value *received,
Value expected,
reflection::source_location sl = reflection::source_location::current())
{
expect(received != nullptr, sl) << "Value was not found";
expect(eq(*received, expected), sl);
}
suite environment_test = [] {
"Global enviroment exists"_test = [] {
expect(not bool(Env::global)) << "Before interpreter global environment does not exists";
{
Interpreter i;
expect(bool(Env::global)) << "When interpreter exists global environment exists";
}
expect(not bool(Env::global)) << "After interpreter destruction, global environment does not exists";
};
"Environment scoping"_test = [] {
should("nested scoping preserve outer scope") = [] {
Interpreter i;
i.env->force_define("x", Value::from(Number(10)));
i.env->force_define("y", Value::from(Number(20)));
equals(i.env->find("x"), Value::from(Number(10)));
equals(i.env->find("y"), Value::from(Number(20)));
i.enter_scope();
{
i.env->force_define("x", Value::from(Number(30)));
equals(i.env->find("x"), Value::from(Number(30)));
equals(i.env->find("y"), Value::from(Number(20)));
}
i.leave_scope();
equals(i.env->find("x"), Value::from(Number(10)));
equals(i.env->find("y"), Value::from(Number(20)));
};
should("nested variables missing from outer scope") = [] {
Interpreter i;
i.enter_scope();
{
i.env->force_define("x", Value::from(Number(30)));
equals(i.env->find("x"), Value::from(Number(30)));
}
i.leave_scope();
expect(eq(i.env->find("x"), nullptr));
};
};
};

View File

@ -1,423 +0,0 @@
#include <boost/ut.hpp>
#include <musique.hh>
#include <algorithm>
#include <numeric>
#include <iterator>
using namespace boost::ut;
using namespace std::string_view_literals;
static auto capture_errors(auto lambda, reflection::source_location sl = reflection::source_location::current())
{
return [=] {
auto result = lambda();
if (not result.has_value()) {
expect(false, sl) << "failed test with error: " << result.error();
return false;
}
return true;
};
}
auto evaluates_to(Value value, std::string_view source_code, reflection::source_location sl = reflection::source_location::current())
{
return capture_errors([=]() -> Result<void> {
Interpreter interpreter;
auto result = Try(interpreter.eval(Try(Parser::parse(source_code, "test"))));
expect(eq(result, value), sl);
return {};
}, sl)();
}
template<typename T>
void expect_alternative(
auto const& variant,
boost::ut::reflection::source_location sl = boost::ut::reflection::source_location::current())
{
using namespace boost::ut;
expect(std::holds_alternative<T>(variant), sl) << "Expected to hold alternative but failed";
}
suite intepreter_test = [] {
"Interpreter"_test = [] {
should("evaluate literals") = [] {
evaluates_to(Value::from(false), "false");
evaluates_to(Value::from(true), "true");
evaluates_to(Value::from(Number(10)), "10");
evaluates_to(Value{}, "nil");
evaluates_to(Value::from("foo"), "'foo");
};
should("evaluate arithmetic") = [] {
evaluates_to(Value::from(Number(10)), "5 + 3 + 2");
evaluates_to(Value::from(Number(25)), "5 * (3 + 2)");
evaluates_to(Value::from(Number(1, 2)), "1 / 2");
evaluates_to(Value::from(Number(-10)), "10 - 20");
};
should("call builtin functions") = [] {
evaluates_to(Value::from("nil"), "typeof nil");
evaluates_to(Value::from("number"), "typeof 100");
};
should("allows only for calling which is callable") = [] {
evaluates_to(Value::from(Number(0)), "[i|i] 0");
{
Interpreter i;
{
auto result = Parser::parse("10 20", "test").and_then([&](Ast &&ast) { return i.eval(std::move(ast)); });
expect(!result.has_value()) << "Expected code to have failed";
expect_alternative<errors::Not_Callable>(result.error().details);
}
{
i.env->force_define("call_me", Value::from(Number(10)));
auto result = Parser::parse("call_me 20", "test").and_then([&](Ast &&ast) { return i.eval(std::move(ast)); });
expect(!result.has_value()) << "Expected code to have failed";
expect_alternative<errors::Not_Callable>(result.error().details);
}
}
};
should("allow for value (in)equality comparisons") = [] {
evaluates_to(Value::from(true), "nil == nil");
evaluates_to(Value::from(false), "nil != nil");
evaluates_to(Value::from(true), "true == true");
evaluates_to(Value::from(false), "true != true");
evaluates_to(Value::from(false), "true == false");
evaluates_to(Value::from(true), "true != false");
evaluates_to(Value::from(true), "0 == 0");
evaluates_to(Value::from(false), "0 != 0");
evaluates_to(Value::from(true), "1 != 0");
evaluates_to(Value::from(false), "1 == 0");
};
should("allow for value ordering comparisons") = [] {
evaluates_to(Value::from(false), "true < true");
evaluates_to(Value::from(true), "true <= true");
evaluates_to(Value::from(true), "false < true");
evaluates_to(Value::from(false), "false > true");
evaluates_to(Value::from(false), "0 < 0");
evaluates_to(Value::from(true), "0 <= 0");
evaluates_to(Value::from(true), "1 < 2");
evaluates_to(Value::from(false), "1 > 2");
evaluates_to(Value::from(true), "c == c");
evaluates_to(Value::from(false), "c != c");
evaluates_to(Value::from(true), "c < d");
evaluates_to(Value::from(true), "c != (c 4)");
evaluates_to(Value::from(true), "(c 4) == (c 4)");
evaluates_to(Value::from(true), "(c 3) != (c 4)");
evaluates_to(Value::from(true), "(c 3) < (c 4)");
evaluates_to(Value::from(true), "((c+12) 3) == (c 4)");
// Value is partially ordered, ensure that different types
// are always not equal and not ordered
evaluates_to(Value::from(false), "0 < c");
evaluates_to(Value::from(false), "0 > c");
evaluates_to(Value::from(false), "0 <= c");
evaluates_to(Value::from(false), "0 >= c");
evaluates_to(Value::from(true), "0 != c");
};
// Added to explicitly test against bug that was in old implementation of enviroments.
// Previously this test would segfault
should("allow assigning result of function calls to a variable") = [] {
evaluates_to(Value::from(Number(42)), "var x = [i|i] 42; x");
};
// Added to explicitly test against bug
// Previously this test would return 10
should("respect parens in block") = [] {
evaluates_to(Value::from(Number(42)), "[(10;42)].0");
};
should("allow modifying declared variable") = [] {
evaluates_to(Value::from(Number(43)), "var x = 42; x = 43; 10; x");
};
should("support array programming") = [] {
evaluates_to(Value::from(Array { .elements = {{
Value::from(Number(2)),
Value::from(Number(4)),
Value::from(Number(6)),
Value::from(Number(8))
}}}), "2 * [1;2;3;4]");
evaluates_to(Value::from(Array { .elements = {{
Value::from(Note { .base = 1 }),
Value::from(Note { .base = 2 }),
Value::from(Note { .base = 3 }),
Value::from(Note { .base = 4 })
}}}), "c + [1;2;3;4]");
evaluates_to(Value::from(Array { .elements = {{
Value::from(Number(2)),
Value::from(Number(4)),
Value::from(Number(6)),
Value::from(Number(8))
}}}), "[1;2;3;4] * 2");
evaluates_to(Value::from(Array { .elements = {{
Value::from(Note { .base = 1 }),
Value::from(Note { .base = 2 }),
Value::from(Note { .base = 3 }),
Value::from(Note { .base = 4 })
}}}), "[1;2;3;4] + c");
};
should("support number - music operations") = [] {
evaluates_to(Value::from(Chord { .notes = { Note { .base = 0 }, Note { .base = 3 }, Note { .base = 6 } } }),
"c#47 - 1");
evaluates_to(Value::from(Chord { .notes = { Note { .base = 2 }, Note { .base = 6 }, Note { .base = 9 } } }),
"c47 + 2");
evaluates_to(Value::from(Chord { .notes = { Note { .base = 2 }, Note { .base = 6 }, Note { .base = 9 } } }),
"2 + c47");
};
should("support direct chord creation") = [] {
evaluates_to(Value::from(Chord { .notes = { Note { .base = 0 }, Note { .base = 4 }, Note { .base = 7 } } }),
"chord [c;e;g]");
evaluates_to(Value::from(Chord { .notes = { Note { .base = 0 }, Note { .base = 4 }, Note { .base = 7 } } }),
"chord c e g");
evaluates_to(Value::from(Chord { .notes = { Note { .base = 0 }, Note { .base = 4 }, Note { .base = 7 } } }),
"chord [c;e] g");
};
should("support eager array creation") = [] {
evaluates_to(Value::from(Array { .elements = {{ Value::from(Number(10)), Value::from(Number(20)), Value::from(Number(30)) }}}),
"flat 10 20 30");
evaluates_to(Value::from(Array { .elements = {{ Value::from(Number(10)), Value::from(Number(20)), Value::from(Number(30)) }}}),
"flat [10;20] 30");
evaluates_to(Value::from(Array { .elements = {{ Value::from(Number(10)), Value::from(Number(20)), Value::from(Number(30)) }}}),
"flat (flat 10 20) 30");
};
should("support indexing") = [] {
evaluates_to(Value::from(Number(10)), "[5;10;15].1");
evaluates_to(Value::from(Number(10)), "(flat 5 10 15).1");
};
should("support size") = [] {
evaluates_to(Value::from(Number(3)), "len [5;10;15]");
evaluates_to(Value::from(Number(3)), "len (flat 5 10 15)");
};
should("support call like octave and len setting") = [] {
evaluates_to(Value::from(Chord { .notes = {{ Note { .base = 0, .octave = 5, .length = Number(10) }}}}), "c 5 10");
evaluates_to(Value::from(Chord { .notes = {{ Note { .base = 0, .octave = 5, .length = Number(10) }}}}), "(c 4 8) 5 10");
evaluates_to(Value::from(Chord { .notes = {{ Note { .base = 0, .octave = 5 }}}}), "c 5");
};
};
"Interpreter boolean operators"_test = [] {
should("Properly support 'and' truth table") = [] {
evaluates_to(Value::from(false), "false and false");
evaluates_to(Value::from(false), "true and false");
evaluates_to(Value::from(false), "false and true");
evaluates_to(Value::from(true), "true and true");
evaluates_to(Value::from(Number(0)), "0 and 0");
evaluates_to(Value::from(Number(0)), "0 and 1");
evaluates_to(Value::from(Number(0)), "1 and 0");
evaluates_to(Value::from(Number(1)), "1 and 1");
evaluates_to(Value::from(Number(42)), "1 and 42");
};
should("Properly support 'or' truth table") = [] {
evaluates_to(Value::from(false), "false or false");
evaluates_to(Value::from(true), "true or false");
evaluates_to(Value::from(true), "false or true");
evaluates_to(Value::from(true), "true or true");
evaluates_to(Value::from(Number(0)), "0 or 0");
evaluates_to(Value::from(Number(1)), "0 or 1");
evaluates_to(Value::from(Number(1)), "1 or 0");
evaluates_to(Value::from(Number(1)), "1 or 1");
evaluates_to(Value::from(Number(1)), "1 or 42");
evaluates_to(Value::from(Number(42)), "42 or 1");
};
// TODO Implement this test
// Currently not implemented due to interpeter inability to accept
// stateful intrinsics. Probably needs to be solved with using in-language
// variable like "__called_times_" or using names that cannot be variables
// like " ". But I need some time to think about that
skip / should("Short circuit on 'and' and 'or' operators") = [] {
};
};
"Interpreter's default builtins"_test = [] {
should("Conditional execution with 'if'") = [] {
evaluates_to(Value::from(Number(10)), "if true [10] [20]");
evaluates_to(Value::from(Number(20)), "if false [10] [20]");
evaluates_to(Value{}, "if false [10]");
};
should("Permutations generation with 'permute'") = [] {
std::vector<int> src(5);
std::iota(src.begin(), src.end(), 0);
auto exp = Value::from(Array{});
exp.array.elements.resize(src.size());
for (bool quit = false; !quit;) {
std::stringstream ss;
ss << "permute ";
std::copy(src.begin(), src.end(), std::ostream_iterator<int>(ss, " "));
quit = not std::next_permutation(src.begin(), src.end());
std::transform(src.begin(), src.end(), exp.array.elements.begin(), [](int x) {
return Value::from(Number(x));
});
auto str = ss.str();
if (not evaluates_to(exp, str))
quit = true;
};
};
should("Update value of array with 'update'") = [] {
evaluates_to(Value::from(Array { .elements = {
Value::from(Number(1)), Value::from(Number(10)), Value::from(Number(3))
}}), "update [1;2;3] 1 10");
evaluates_to(Value::from(Array { .elements = {
Value::from(Number(1)), Value::from(Number(10)), Value::from(Number(3))
}}), "update (flat 1 2 3) 1 10");
};
should("Support 'reverse' operation") = [] {
evaluates_to(Value::from(Array { .elements = {
Value::from(Number(1)), Value::from(Number(2)), Value::from(Number(3))
}}), "reverse 3 2 1");
evaluates_to(Value::from(Array { .elements = {
Value::from(Number(1)), Value::from(Number(2)), Value::from(Number(3))
}}), "reverse (flat 3 2) 1");
evaluates_to(Value::from(Array { .elements = {
Value::from(Number(1)), Value::from(Number(2)), Value::from(Number(3))
}}), "reverse [3;2] 1");
};
should("Support 'min' operation") = [] {
evaluates_to(Value::from(Number(10)), "min 10 20");
evaluates_to(Value::from(Number(10)), "min 20 10");
evaluates_to(Value::from(Chord { .notes = { Note { .base = 0 } } }), "min c d");
evaluates_to(Value::from(Chord { .notes = { Note { .base = 0 } } }), "min d c");
};
should("Support 'max' operation") = [] {
evaluates_to(Value::from(Number(20)), "max 10 20");
evaluates_to(Value::from(Number(20)), "max 20 10");
evaluates_to(Value::from(Chord { .notes = { Note { .base = 2 } } }), "max c d");
evaluates_to(Value::from(Chord { .notes = { Note { .base = 2 } } }), "max d c");
};
should("Support 'rotate' operation") = [] {
evaluates_to(Value::from(Array { .elements = {
Value::from(Number(1)),
Value::from(Number(2)),
Value::from(Number(3)),
Value::from(Number(4)),
Value::from(Number(5)),
}}), "rotate 0 [1;2;3;4;5]");
should("Support 'rotate' to right via negative rotation index") = [] {
evaluates_to(Value::from(Array { .elements = {
Value::from(Number(5)),
Value::from(Number(1)),
Value::from(Number(2)),
Value::from(Number(3)),
Value::from(Number(4)),
}}), "rotate (0-1) [1;2;3;4;5]");
evaluates_to(Value::from(Array { .elements = {
Value::from(Number(4)),
Value::from(Number(5)),
Value::from(Number(1)),
Value::from(Number(2)),
Value::from(Number(3)),
}}), "rotate (0-2) [1;2;3;4;5]");
evaluates_to(Value::from(Array { .elements = {
Value::from(Number(3)),
Value::from(Number(4)),
Value::from(Number(5)),
Value::from(Number(1)),
Value::from(Number(2)),
}}), "rotate (0-3) [1;2;3;4;5]");
evaluates_to(Value::from(Array { .elements = {
Value::from(Number(2)),
Value::from(Number(3)),
Value::from(Number(4)),
Value::from(Number(5)),
Value::from(Number(1)),
}}), "rotate (0-4) [1;2;3;4;5]");
evaluates_to(Value::from(Array { .elements = {
Value::from(Number(1)),
Value::from(Number(2)),
Value::from(Number(3)),
Value::from(Number(4)),
Value::from(Number(5)),
}}), "rotate (0-5) [1;2;3;4;5]");
};
};
should("Support 'rotate' to left via positive rotation index") = [] {
evaluates_to(Value::from(Array { .elements = {
Value::from(Number(2)),
Value::from(Number(3)),
Value::from(Number(4)),
Value::from(Number(5)),
Value::from(Number(1)),
}}), "rotate 1 [1;2;3;4;5]");
evaluates_to(Value::from(Array { .elements = {
Value::from(Number(3)),
Value::from(Number(4)),
Value::from(Number(5)),
Value::from(Number(1)),
Value::from(Number(2)),
}}), "rotate 2 [1;2;3;4;5]");
evaluates_to(Value::from(Array { .elements = {
Value::from(Number(4)),
Value::from(Number(5)),
Value::from(Number(1)),
Value::from(Number(2)),
Value::from(Number(3)),
}}), "rotate 3 [1;2;3;4;5]");
evaluates_to(Value::from(Array { .elements = {
Value::from(Number(5)),
Value::from(Number(1)),
Value::from(Number(2)),
Value::from(Number(3)),
Value::from(Number(4)),
}}), "rotate 4 [1;2;3;4;5]");
evaluates_to(Value::from(Array { .elements = {
Value::from(Number(1)),
Value::from(Number(2)),
Value::from(Number(3)),
Value::from(Number(4)),
Value::from(Number(5)),
}}), "rotate 5 [1;2;3;4;5]");
};
};
};

View File

@ -1,216 +0,0 @@
#include <boost/ut.hpp>
#include <musique.hh>
using namespace boost::ut;
enum class EOF_Status : bool
{
Reject = false,
Accept = true,
};
template<EOF_Status Expect_End_Of_File = EOF_Status::Reject>
static auto handle_end_of_file(
std::variant<Token, End_Of_File> token_or_eof,
reflection::source_location const& sl)
{
if constexpr (Expect_End_Of_File == EOF_Status::Accept) {
expect(std::holds_alternative<End_Of_File>(token_or_eof), sl) << "Expected to encounter end of file";
} else {
expect(std::holds_alternative<Token>(token_or_eof), sl) << "Expected to NOT encounter end of file";
return std::get<Token>(std::move(token_or_eof));
}
}
static void expect_token_type(
Token::Type expected_type,
std::string source,
reflection::source_location const& sl = reflection::source_location::current())
{
Lexer lexer{source};
auto result = lexer.next_token();
expect(result.has_value() >> fatal, sl) << "have not parsed any tokens";
auto token = handle_end_of_file(*std::move(result), sl);
expect(eq(token.type, expected_type), sl) << "different token type then expected";
}
static void expect_token_type_and_value(
Token::Type expected_type,
std::string_view source,
std::string_view expected,
reflection::source_location const& sl = reflection::source_location::current())
{
Lexer lexer{source};
auto result = lexer.next_token();
expect(result.has_value(), sl) << "have not parsed any tokens";
if (result.has_value()) {
auto token = handle_end_of_file(*std::move(result), sl);
expect(eq(token.type, expected_type), sl) << "different token type then expected";
expect(eq(token.source, expected), sl) << "tokenized source is not equal to original";
}
}
static void expect_token_type_and_value(
Token::Type expected_type,
std::string_view source,
reflection::source_location const& sl = reflection::source_location::current())
{
expect_token_type_and_value(expected_type, source, source, sl);
}
static void expect_token_type_and_location(
Token::Type expected_type,
std::string_view source,
Location location,
reflection::source_location const& sl = reflection::source_location::current())
{
Lexer lexer{source};
auto result = lexer.next_token();
expect(result.has_value() >> fatal, sl) << "have not parsed any tokens";
auto token = handle_end_of_file(*std::move(result), sl);
expect(eq(token.type, expected_type), sl) << "different token type then expected";
expect(eq(token.location, location), sl) << "tokenized source is at different place then expected";
}
static void expect_empty_file(
std::string_view source,
reflection::source_location const& sl = reflection::source_location::current())
{
Lexer lexer{source};
auto result = lexer.next_token();
expect(result.has_value(), sl) << "Encountered error when expecting end of file";
handle_end_of_file<EOF_Status::Accept>(*std::move(result), sl);
}
template<auto N>
static void expect_token_sequence(
std::string_view source,
std::array<Token, N> const& expected_tokens,
reflection::source_location const& sl = reflection::source_location::current())
{
Lexer lexer{source};
for (Token const& expected : expected_tokens) {
auto const result = lexer.next_token();
expect(result.has_value(), sl) << "expected token, received nothing";
if (result.has_value()) {
auto token = handle_end_of_file(*std::move(result), sl);
expect(eq(token.type, expected.type)) << "different token type then expected";
expect(eq(token.source, expected.source)) << "different token source then expected";
expect(eq(token.location, expected.location)) << "different token location then expected";
}
}
auto const result = lexer.next_token();
expect(result.has_value(), sl) << "expected end of file after sequence of tokens";
if (result.has_value()) {
expect(std::holds_alternative<End_Of_File>(*result), sl) << "expected end of file after sequence of tokens";
}
}
suite lexer_test = [] {
"Empty file"_test = [] {
expect_empty_file("");
};
"Comments"_test = [] {
expect_empty_file("#!/bin/sh");
expect_empty_file("-- line comment");
expect_token_type_and_value(Token::Type::Numeric, "--- block comment --- 0", "0");
expect_empty_file(R"musique(
--- hello
multiline comment
---
)musique");
};
"Simple token types"_test = [] {
expect_token_type(Token::Type::Close_Block, "]");
expect_token_type(Token::Type::Close_Paren, ")");
expect_token_type(Token::Type::Open_Block, "[");
expect_token_type(Token::Type::Open_Paren, "(");
expect_token_type(Token::Type::Parameter_Separator, "|");
expect_token_type(Token::Type::Expression_Separator, ";");
};
"Numeric tokens"_test = [] {
expect_token_type_and_value(Token::Type::Numeric, "0");
expect_token_type_and_value(Token::Type::Numeric, "123456789");
expect_token_type_and_value(Token::Type::Numeric, "0.75");
expect_token_type_and_value(Token::Type::Numeric, "123456789.123456789");
expect_token_type_and_value(Token::Type::Numeric, "123.", "123");
expect_token_type_and_value(Token::Type::Numeric, " 1 ", "1");
expect_token_type_and_value(Token::Type::Numeric, " 123 ", "123");
};
"Proper location marking"_test = [] {
expect_token_type_and_location(Token::Type::Numeric, "123", Location::at(1, 1));
expect_token_type_and_location(Token::Type::Numeric, " 123", Location::at(1, 4));
expect_token_type_and_location(Token::Type::Numeric, "\n123", Location::at(2, 1));
expect_token_type_and_location(Token::Type::Numeric, "\n 123", Location::at(2, 3));
};
"Chord literals"_test = [] {
expect_token_type_and_value(Token::Type::Chord, "c");
expect_token_type_and_value(Token::Type::Chord, "c#");
expect_token_type_and_value(Token::Type::Chord, "c1");
expect_token_type_and_value(Token::Type::Chord, "d1257");
expect_token_type_and_value(Token::Type::Chord, "e#5");
expect_token_type_and_value(Token::Type::Chord, "ef5");
expect_token_type_and_value(Token::Type::Chord, "es5");
expect_token_type_and_value(Token::Type::Chord, "eb5");
expect_token_type_and_value(Token::Type::Chord, "e##5");
expect_token_type_and_value(Token::Type::Chord, "ef#5");
expect_token_type_and_value(Token::Type::Chord, "esf5");
expect_token_type_and_value(Token::Type::Chord, "g127");
};
"Symbol literals"_test = [] {
expect_token_type_and_value(Token::Type::Symbol, "i");
expect_token_type_and_value(Token::Type::Symbol, "i2");
expect_token_type_and_value(Token::Type::Symbol, "example");
expect_token_type_and_value(Token::Type::Symbol, "d1envelope");
expect_token_type_and_value(Token::Type::Symbol, "snake_case");
expect_token_type_and_value(Token::Type::Symbol, "camelCase");
expect_token_type_and_value(Token::Type::Symbol, "PascalCase");
expect_token_type_and_value(Token::Type::Symbol, "haskell'");
expect_token_type_and_value(Token::Type::Symbol, "zażółć");
expect_token_type_and_value(Token::Type::Symbol, "$foo");
expect_token_type_and_value(Token::Type::Symbol, "@bar");
};
"Operators"_test = [] {
expect_token_type_and_value(Token::Type::Operator, "+");
expect_token_type_and_value(Token::Type::Operator, "&&");
expect_token_type_and_value(Token::Type::Operator, "||");
expect_token_type_and_value(Token::Type::Operator, "*");
expect_token_type_and_value(Token::Type::Operator, "**");
expect_token_type_and_value(Token::Type::Operator, "=");
expect_token_type_and_value(Token::Type::Operator, "<");
expect_token_type_and_value(Token::Type::Operator, ":");
expect_token_type_and_value(Token::Type::Operator, "%");
};
"Multiple tokens"_test = [] {
Location l;
expect_token_sequence("1 + foo", std::array {
Token { Token::Type::Numeric, "1", l.at(1, 1) },
Token { Token::Type::Operator, "+", l.at(1, 3) },
Token { Token::Type::Symbol, "foo", l.at(1, 5) }
});
expect_token_sequence("foo 1 2; bar 3 4", std::array {
Token { Token::Type::Symbol, "foo", l.at(1, 1) },
Token { Token::Type::Numeric, "1", l.at(1, 5) },
Token { Token::Type::Numeric, "2", l.at(1, 7) },
Token { Token::Type::Expression_Separator, ";", l.at(1, 8) },
Token { Token::Type::Symbol, "bar", l.at(1, 10) },
Token { Token::Type::Numeric, "3", l.at(1, 14) },
Token { Token::Type::Numeric, "4", l.at(1, 16) }
});
};
};

View File

@ -1,12 +0,0 @@
#include <boost/ut.hpp>
int main()
{
using namespace boost::ut;
if (!isatty(STDOUT_FILENO) || getenv("NO_COLOR") != nullptr) {
cfg<override> = options {
.colors = colors { .none = "", .pass = "", .fail = "" }
};
}
}

View File

@ -1,98 +0,0 @@
#include <boost/ut.hpp>
#include <musique.hh>
using namespace boost::ut;
using namespace std::string_view_literals;
void test_number_from(
std::string_view source,
Number expected,
reflection::source_location sl = reflection::source_location::current())
{
auto result = Number::from(Token { Token::Type::Numeric, source, {} });
expect(result.has_value(), sl) << "failed to parse number";
if (result.has_value()) {
expect(eq(*result, expected), sl);
}
}
suite number_test = [] {
"Number"_test = [] {
should("provide arithmetic operators") = [] {
expect(eq(Number{1, 8} + Number{3, 4}, Number{ 7, 8})) << "for expr: (1/8) + (3/4)";
expect(eq(Number{1, 8} - Number{3, 4}, Number{-5, 8})) << "for expr: (1/8) - (3/4)";
expect(eq(Number{1, 8} * Number{3, 4}, Number{ 3, 32})) << "for expr: (1/8) * (3/4)";
expect(eq(Number{1, 8} / Number{3, 4}, Number{ 1, 6})) << "for expr: (1/8) / (3/4)";
};
should("provide assignment operators") = [] {
Number n;
n={1,8}; n += {3,4}; expect(eq(n, Number{ 7, 8})) << "for expr: (1/8) += (3/4)";
n={1,8}; n -= {3,4}; expect(eq(n, Number{-5, 8})) << "for expr: (1/8) -= (3/4)";
n={1,8}; n *= {3,4}; expect(eq(n, Number{ 3, 32})) << "for expr: (1/8) *= (3/4)";
n={1,8}; n /= {3,4}; expect(eq(n, Number{ 1, 6})) << "for expr: (1/8) /= (3/4)";
};
should("be comperable") = [] {
expect(gt(Number{1, 4}, Number{1, 8}));
expect(eq(Number{2, 4}, Number{1, 2}));
};
should("be convertable to int") = [] {
expect(eq(Number{4, 2}.as_int(), 2)) << "for fraction 4/2";
expect(eq(Number{2, 1}.as_int(), 2)) << "for fraction 2/1";
expect(eq(Number{0, 1000}.as_int(), 0)) << "for fraction 0/1000";
};
};
"Number::from"_test = [] {
test_number_from("0", Number(0));
test_number_from("100", Number(100));
test_number_from("0.75", Number(3, 4));
test_number_from(".75", Number(3, 4));
test_number_from("120.", Number(120, 1));
};
"Rounding"_test = [] {
should("Support floor operation") = [] {
expect(eq(Number(0), Number(1, 2).floor()));
expect(eq(Number(1), Number(3, 2).floor()));
expect(eq(Number(-1), Number(-1, 2).floor()));
expect(eq(Number(0), Number(0).floor()));
expect(eq(Number(1), Number(1).floor()));
expect(eq(Number(-1), Number(-1).floor()));
};
should("Support ceil operation") = [] {
expect(eq(Number(1), Number(1, 2).ceil()));
expect(eq(Number(2), Number(3, 2).ceil()));
expect(eq(Number(0), Number(-1, 2).ceil()));
expect(eq(Number(-1), Number(-3, 2).ceil()));
expect(eq(Number(0), Number(0).ceil()));
expect(eq(Number(1), Number(1).ceil()));
expect(eq(Number(-1), Number(-1).ceil()));
};
should("Support round operation") = [] {
expect(eq(Number(1), Number(3, 4).round()));
expect(eq(Number(0), Number(1, 4).round()));
expect(eq(Number(1), Number(5, 4).round()));
expect(eq(Number(2), Number(7, 4).round()));
expect(eq(Number(-1), Number(-3, 4).round()));
expect(eq(Number(0), Number(-1, 4).round()));
expect(eq(Number(-1), Number(-5, 4).round()));
expect(eq(Number(-2), Number(-7, 4).round()));
};
};
"Number exponantiation"_test = [] {
should("Support integer powers") = [] {
expect(eq(Number(1, 4), Number(1, 2).pow(Number(2))));
expect(eq(Number(4, 1), Number(1, 2).pow(Number(-2))));
expect(eq(Number(4, 1), Number(2).pow(Number(2))));
expect(eq(Number(1, 4), Number(2).pow(Number(-2))));
};
};
};

View File

@ -1,224 +0,0 @@
#include <boost/ut.hpp>
#include <musique.hh>
using namespace boost::ut;
void expect_ast(
std::string_view source,
Ast const& expected,
reflection::source_location sl = reflection::source_location::current())
{
auto result = Parser::parse(source, "test");
expect(result.has_value(), sl) << "code was expect to parse, but had not";
expect(eq(*result, expected), sl) << "parser yielded unexpected tree";
}
void expect_single_ast(
std::string_view source,
Ast const& expected,
reflection::source_location sl = reflection::source_location::current())
{
auto result = Parser::parse(source, "test");
expect(result.has_value(), sl) << "code was expect to parse, but had not";
expect(eq(result->type, Ast::Type::Sequence), sl) << "parsed does not yielded sequence";
expect(not result->arguments.empty(), sl) << "parsed yielded empty sequence";
expect(eq(result->arguments[0], expected), sl) << "parser yielded unexpected tree";
}
suite parser_test = [] {
"Empty file parsing"_test = [] {
expect_ast("", Ast::sequence({}));
};
"Literal parsing"_test = [] {
expect_single_ast("1", Ast::literal(Token { Token::Type::Numeric, "1", {} }));
};
"Binary opreator parsing"_test = [] {
expect_single_ast("1 + 2", Ast::binary(
Token { Token::Type::Operator, "+", {} },
Ast::literal({ Token::Type::Numeric, "1", {} }),
Ast::literal({ Token::Type::Numeric, "2", {} })
));
expect_single_ast("100 * 200", Ast::binary(
Token { Token::Type::Operator, "*", {} },
Ast::literal({ Token::Type::Numeric, "100", {} }),
Ast::literal({ Token::Type::Numeric, "200", {} })
));
expect_single_ast("101 + 202 * 303", Ast::binary(
Token { Token::Type::Operator, "+", {} },
Ast::literal({ Token::Type::Numeric, "101", {} }),
Ast::binary(
Token { Token::Type::Operator, "*", {} },
Ast::literal({ Token::Type::Numeric, "202", {} }),
Ast::literal({ Token::Type::Numeric, "303", {} })
)
));
};
// This test shouldn't be skipped since language will support precedense.
// It stays here as reminder that this feature should be implemented.
"Binary operator precedense"_test = [] {
expect_single_ast("101 * 202 + 303", Ast::binary(
Token { Token::Type::Operator, "+", {} },
Ast::binary(
Token { Token::Type::Operator, "*", {} },
Ast::literal({ Token::Type::Numeric, "101", {} }),
Ast::literal({ Token::Type::Numeric, "202", {} })
),
Ast::literal({ Token::Type::Numeric, "303", {} })
));
};
"Grouping expressions in parentheses"_test = [] {
expect_single_ast("(101 + 202) * 303", Ast::binary(
Token { Token::Type::Operator, "*", {} },
Ast::sequence({ Ast::binary(
Token { Token::Type::Operator, "+", {} },
Ast::literal({ Token::Type::Numeric, "101", {} }),
Ast::literal({ Token::Type::Numeric, "202", {} })
)}),
Ast::literal({ Token::Type::Numeric, "303", {} })
));
expect_single_ast("101 * (202 + 303)", Ast::binary(
Token { Token::Type::Operator, "*", {} },
Ast::literal({ Token::Type::Numeric, "101", {} }),
Ast::sequence({
Ast::binary(
Token { Token::Type::Operator, "+", {} },
Ast::literal({ Token::Type::Numeric, "202", {} }),
Ast::literal({ Token::Type::Numeric, "303", {} })
)
})
));
};
"Explicit function call"_test = [] {
expect_single_ast("foo 1 2", Ast::call({
Ast::literal({ Token::Type::Symbol, "foo", {} }),
Ast::literal({ Token::Type::Numeric, "1", {} }),
Ast::literal({ Token::Type::Numeric, "2", {} })
}));
should("Support function call with complicated expression") = [] {
expect_single_ast("say (fib (n-1) + fib (n-2))", Ast::call({
Ast::literal({ Token::Type::Symbol, "say", {} }),
Ast::sequence({
Ast::binary(
{ Token::Type::Operator, "+", {} },
Ast::call({
Ast::literal({ Token::Type::Symbol, "fib", {} }),
Ast::sequence({
Ast::binary(
{ Token::Type::Operator, "-", {} },
Ast::literal({ Token::Type::Symbol, "n", {} }),
Ast::literal({ Token::Type::Numeric, "1", {} })
)
})
}),
Ast::call({
Ast::literal({ Token::Type::Symbol, "fib", {} }),
Ast::sequence({
Ast::binary(
{ Token::Type::Operator, "-", {} },
Ast::literal({ Token::Type::Symbol, "n", {} }),
Ast::literal({ Token::Type::Numeric, "2", {} })
)
})
})
)
})
}));
};
};
"Sequence"_test = [] {
expect_ast("42; 101", Ast::sequence({
Ast::literal({ Token::Type::Numeric, "42", {} }),
Ast::literal({ Token::Type::Numeric, "101", {} })
}));
expect_ast("say hello; say world", Ast::sequence({
Ast::call({
Ast::literal({ Token::Type::Symbol, "say", {} }),
Ast::literal({ Token::Type::Symbol, "hello", {} })
}),
Ast::call({
Ast::literal({ Token::Type::Symbol, "say", {} }),
Ast::literal({ Token::Type::Symbol, "world", {} })
})
}));
};
"Block"_test = [] {
expect_single_ast("[]", Ast::block(Location{}));
expect_single_ast("[ i; j; k ]", Ast::block({}, Ast::sequence({
Ast::literal({ Token::Type::Symbol, "i", {} }),
Ast::literal({ Token::Type::Symbol, "j", {} }),
Ast::literal({ Token::Type::Symbol, "k", {} })
})));
expect_single_ast("[ i j k | i + j + k ]", Ast::lambda({}, Ast::sequence({
Ast::binary(
{ Token::Type::Operator, "+", {} },
Ast::literal({ Token::Type::Symbol, "i", {} }),
Ast::binary(
{ Token::Type::Operator, "+", {} },
Ast::literal({ Token::Type::Symbol, "j", {} }),
Ast::literal({ Token::Type::Symbol, "k", {} })
)
)
})
, {
Ast::literal({ Token::Type::Symbol, "i", {} }),
Ast::literal({ Token::Type::Symbol, "j", {} }),
Ast::literal({ Token::Type::Symbol, "k", {} })
}));
expect_single_ast("[ i; j; k | i + j + k ]", Ast::lambda({}, Ast::sequence({
Ast::binary(
{ Token::Type::Operator, "+", {} },
Ast::literal({ Token::Type::Symbol, "i", {} }),
Ast::binary(
{ Token::Type::Operator, "+", {} },
Ast::literal({ Token::Type::Symbol, "j", {} }),
Ast::literal({ Token::Type::Symbol, "k", {} })
)
)}), {
Ast::literal({ Token::Type::Symbol, "i", {} }),
Ast::literal({ Token::Type::Symbol, "j", {} }),
Ast::literal({ Token::Type::Symbol, "k", {} })
}));
expect_single_ast("[|1]", Ast::lambda({}, Ast::sequence({ Ast::literal({ Token::Type::Numeric, "1", {} })}), {}));
};
"Variable declarations"_test = [] {
should("Support variable declaration with assigment") = [] {
expect_single_ast("var x = 10", Ast::variable_declaration(
{},
{ Ast::literal({ Token::Type::Symbol, "x", {} }) },
Ast::literal({ Token::Type::Numeric, "10", {} })));
};
skip / should("Support variable declaration") = [] {
expect_ast("var x", Ast::variable_declaration(
{},
{ Ast::literal({ Token::Type::Symbol, "x", {} }) },
std::nullopt));
};
skip / should("Support multiple variables declaration") = [] {
expect_ast("var x y", Ast::variable_declaration(
{},
{Ast::literal({ Token::Type::Symbol, "x", {} }), Ast::literal({ Token::Type::Symbol, "y", {} })},
std::nullopt));
};
};
};

View File

@ -1,38 +0,0 @@
#include <boost/ut.hpp>
#include <musique.hh>
using namespace boost::ut;
using namespace std::string_view_literals;
void test_encoding(
u32 rune,
std::string_view expected,
reflection::source_location sl = reflection::source_location::current())
{
std::stringstream ss;
ss << utf8::Print{rune};
expect(eq(ss.str(), expected), sl);
}
suite utf8_test = [] {
"UTF-8 Character length"_test = [] {
expect(utf8::length(" ") == 1_u);
expect(utf8::length("ą") == 2_u);
expect(utf8::length("\u2705") == 3_u);
expect(utf8::length("\U000132d1") == 4_u);
};
"UTF-8 Character decoding"_test = [] {
expect(eq(utf8::decode(" ").first, 0x20u));
expect(eq(utf8::decode("ą").first, 0x105u));
expect(eq(utf8::decode("\u2705").first, 0x2705u));
expect(eq(utf8::decode("\U000132d1").first, 0x132d1u));
};
"UTF-8 Character encoding"_test = [] {
test_encoding(0x20u, " ");
test_encoding(0x105u, "ą");
test_encoding(0x2705u, "\u2705");
test_encoding(0x132d1u, "\U000132d1");
};
};

View File

@ -1,188 +0,0 @@
#include <boost/ut.hpp>
#include <musique.hh>
using namespace boost::ut;
using namespace std::string_view_literals;
static std::string str(auto const& printable)
{
std::stringstream ss;
ss << printable;
return std::move(ss).str();
}
static void expect_value(
Result<Value> received,
Value expected,
reflection::source_location sl = reflection::source_location::current())
{
expect(received.has_value(), sl) << "Received error, instead of value";
expect(eq(*received, expected), sl);
}
static void either_truthy_or_falsy(
bool(Value::*desired)() const,
Value v,
reflection::source_location sl = reflection::source_location::current())
{
if (desired == &Value::truthy) {
expect(v.truthy(), sl) << "Value " << v << " should be" << "truthy";
expect(!v.falsy(), sl) << "Value " << v << " should NOT be" << "falsy";
} else {
expect(v.falsy(), sl) << "Value " << v << " should be" << "falsy";
expect(!v.truthy(), sl) << "Value " << v << " should NOT be" << "truthy";
}
}
static void test_note_resolution(
std::string_view name,
i8 octave,
u8 expected,
reflection::source_location sl = reflection::source_location::current())
{
auto const maybe_note = Note::from(name);
expect(maybe_note.has_value(), sl) << "Note::from didn't recognized " << name << " as a note";
if (maybe_note) {
auto note = *maybe_note;
note.octave = octave;
auto const midi_note = note.into_midi_note();
expect(midi_note.has_value(), sl) << "Note::into_midi_note returned nullopt, but should not";
expect(eq(int(*midi_note), int(expected)), sl) << "Note::into_midi_note returned wrong value";
}
}
static void test_note_resolution(
std::string_view name,
u8 expected,
reflection::source_location sl = reflection::source_location::current())
{
auto const maybe_note = Note::from(name);
expect(maybe_note.has_value(), sl) << "Note::from didn't recognized " << name << " as a note";
if (maybe_note) {
auto note = *maybe_note;
note.octave = 4;
auto const midi_note = note.into_midi_note();
expect(midi_note.has_value(), sl) << "Note::into_midi_note returned nullopt, but should not";
expect(eq(int(*midi_note), int(expected)), sl) << "Note::into_midi_note returned wrong value";
}
}
static void test_note_resolution(
std::string_view name,
std::string_view expected = {},
reflection::source_location sl = reflection::source_location::current())
{
if (expected.empty())
expected = name;
auto const maybe_note = Note::from(name);
expect(maybe_note.has_value(), sl) << "Note::from didn't recognized " << name << " as a note";
if (maybe_note) {
auto note = *maybe_note;
std::stringstream ss;
ss << note;
expect(eq(expected, ss.str())) << "Different string representation then expected";
}
}
suite value_test = [] {
"Value"_test = [] {
should("be properly created using Value::from") = [] {
expect_value(Value::from({ Token::Type::Numeric, "10", {} }), Value::from(Number(10)));
expect_value(Value::from({ Token::Type::Keyword, "nil", {} }), Value{});
expect_value(Value::from({ Token::Type::Keyword, "true", {} }), Value::from(true));
expect_value(Value::from({ Token::Type::Keyword, "false", {} }), Value::from(false));
expect_value(Value::from({ Token::Type::Symbol, "foobar", {} }), Value::from("foobar"));
};
should("have be considered truthy or falsy") = [] {
either_truthy_or_falsy(&Value::truthy, Value::from(true));
either_truthy_or_falsy(&Value::truthy, Value::from(Number(1)));
either_truthy_or_falsy(&Value::truthy, Value::from("foo"));
either_truthy_or_falsy(&Value::truthy, Value(Intrinsic(nullptr)));
either_truthy_or_falsy(&Value::falsy, Value{});
either_truthy_or_falsy(&Value::falsy, Value::from(false));
either_truthy_or_falsy(&Value::falsy, Value::from(Number(0)));
};
};
"Value comparisons"_test = [] {
should("are always not equal when types differ") = [] {
expect(neq(Value::from("0"), Value::from(Number(0))));
};
};
"Value printing"_test = [] {
expect(eq("nil"sv, str(Value{})));
expect(eq("true"sv, str(Value::from(true))));
expect(eq("false"sv, str(Value::from(false))));
expect(eq("10"sv, str(Value::from(Number(10)))));
expect(eq("1/2"sv, str(Value::from(Number(2, 4)))));
expect(eq("foo"sv, str(Value::from("foo"))));
expect(eq("<intrinsic>"sv, str(Value(Intrinsic(nullptr)))));
};
"Note"_test = [] {
should("properly resolve notes") = [] {
for (i8 i = 0; i < 9; ++i) {
test_note_resolution("c", i + -1, i * 12 + 0);
test_note_resolution("c#", i + -1, i * 12 + 1);
test_note_resolution("d", i + -1, i * 12 + 2);
test_note_resolution("d#", i + -1, i * 12 + 3);
test_note_resolution("e", i + -1, i * 12 + 4);
test_note_resolution("e#", i + -1, i * 12 + 5);
test_note_resolution("f", i + -1, i * 12 + 5);
test_note_resolution("f#", i + -1, i * 12 + 6);
test_note_resolution("g", i + -1, i * 12 + 7);
test_note_resolution("g#", i + -1, i * 12 + 8);
test_note_resolution("a", i + -1, i * 12 + 9);
test_note_resolution("a#", i + -1, i * 12 + 10);
test_note_resolution("b", i + -1, i * 12 + 11);
}
};
should("Support flat and sharp") = [] {
test_note_resolution("c#", 61);
test_note_resolution("cs", 61);
test_note_resolution("cf", 59);
test_note_resolution("cb", 59);
test_note_resolution("c##", 62);
test_note_resolution("css", 62);
test_note_resolution("cff", 58);
test_note_resolution("cbb", 58);
test_note_resolution("cs#", 62);
test_note_resolution("c#s", 62);
test_note_resolution("cbf", 58);
test_note_resolution("cfb", 58);
test_note_resolution("c#b", 60);
test_note_resolution("cb#", 60);
test_note_resolution("cfs", 60);
test_note_resolution("csf", 60);
test_note_resolution("c##########", 70);
test_note_resolution("cbbbbbbbbbb", 50);
};
should("Preserve note when printing") = [] {
test_note_resolution("c");
test_note_resolution("c#");
test_note_resolution("d");
test_note_resolution("d#");
test_note_resolution("e");
test_note_resolution("f");
test_note_resolution("f#");
test_note_resolution("g");
test_note_resolution("g#");
test_note_resolution("a");
test_note_resolution("a#");
test_note_resolution("b");
};
};
};