link-Link-3.0.2/0000755000175000017500000000000013303226525013577 5ustar zmoelnigzmoelniglink-Link-3.0.2/.appveyor.yml0000644000175000017500000000465313303226525016255 0ustar zmoelnigzmoelnigclone_depth: 50 branches: only: - master # Also includes VS2013 pre-installed image: - Visual Studio 2015 - Visual Studio 2017 environment: matrix: # Visual Studio 2013, 32-bit Release, Asio driver - AUDIO_DRIVER: Asio CONFIGURATION: Release GENERATOR: Visual Studio 12 2013 QT_VERSION: 5.6 QT: msvc2013 # Visual Studio 2013, 64-bit Release, Asio driver - AUDIO_DRIVER: Asio CONFIGURATION: Release GENERATOR: Visual Studio 12 2013 Win64 QT_VERSION: 5.10 QT: msvc2013_64 # Visual Studio 2013, 64-bit Debug, Wasapi driver - AUDIO_DRIVER: Wasapi CONFIGURATION: Debug GENERATOR: Visual Studio 12 2013 Win64 QT_VERSION: 5.10 QT: msvc2013_64 # Visual Studio 2015, 32-bit Release, Asio driver - AUDIO_DRIVER: Asio CONFIGURATION: Release GENERATOR: Visual Studio 14 2015 QT_VERSION: 5.10 QT: msvc2015 # Visual Studio 2015, 64-bit Debug, Asio driver - AUDIO_DRIVER: Asio CONFIGURATION: Debug GENERATOR: Visual Studio 14 2015 Win64 QT_VERSION: 5.10 QT: msvc2015_64 # Visual Studio 2015, 64-bit Release, Asio driver - AUDIO_DRIVER: Asio CONFIGURATION: Release GENERATOR: Visual Studio 14 2015 Win64 QT_VERSION: 5.10 QT: msvc2015_64 # Visual Studio 2015, 64-bit Release, Wasapi driver - AUDIO_DRIVER: Wasapi CONFIGURATION: Release GENERATOR: Visual Studio 14 2015 Win64 QT_VERSION: 5.10 QT: msvc2015_64 # Visual Studio 2017, 64-bit Release, Asio driver - AUDIO_DRIVER: Asio CONFIGURATION: Release GENERATOR: Visual Studio 15 2017 Win64 QT_VERSION: 5.10 QT: msvc2017_64 matrix: exclude: - image: Visual Studio 2015 GENERATOR: Visual Studio 15 2017 Win64 - image: Visual Studio 2017 GENERATOR: Visual Studio 12 2013 - image: Visual Studio 2017 GENERATOR: Visual Studio 12 2013 Win64 - image: Visual Studio 2017 GENERATOR: Visual Studio 14 2015 - image: Visual Studio 2017 GENERATOR: Visual Studio 14 2015 Win64 install: - git submodule update --init --recursive - set PATH=%PATH%;C:\Qt\%QT_VERSION%\%QT%\bin build_script: - python ci/configure.py -q -a %AUDIO_DRIVER% -g "%GENERATOR%" - python ci/build.py --configuration %CONFIGURATION% test_script: - python ci/run-tests.py --target LinkCoreTest - python ci/run-tests.py --target LinkDiscoveryTest link-Link-3.0.2/src/0000755000175000017500000000000013303226525014366 5ustar zmoelnigzmoelniglink-Link-3.0.2/src/ableton/0000755000175000017500000000000013303226525016012 5ustar zmoelnigzmoelniglink-Link-3.0.2/src/ableton/test/0000755000175000017500000000000013303226525016771 5ustar zmoelnigzmoelniglink-Link-3.0.2/src/ableton/test/serial_io/0000755000175000017500000000000013303226525020737 5ustar zmoelnigzmoelniglink-Link-3.0.2/src/ableton/test/serial_io/SchedulerTree.cpp0000644000175000017500000000563313303226525024210 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #include #include namespace ableton { namespace test { namespace serial_io { void SchedulerTree::run() { while (handlePending()) { } } std::shared_ptr SchedulerTree::makeChild() { auto newChild = std::make_shared(); mChildren.push_back(newChild); return newChild; } void SchedulerTree::cancelTimer(const TimerId timerId) { const auto it = std::find_if( begin(mTimers), end(mTimers), [timerId](const TimerMap::value_type& timer) { return timer.first.second == timerId; }); if (it != end(mTimers)) { auto handler = std::move(it->second); mPendingHandlers.push_back([handler]() { handler(1); // truthy indicates error }); mTimers.erase(it); } } SchedulerTree::TimePoint SchedulerTree::nextTimerExpiration() { auto nextTimePoint = TimePoint::max(); withChildren([&nextTimePoint](SchedulerTree& child) { nextTimePoint = (std::min)(nextTimePoint, child.nextOwnTimerExpiration()); }); return (std::min)(nextTimePoint, nextOwnTimerExpiration()); } void SchedulerTree::triggerTimersUntil(const TimePoint t) { using namespace std; withChildren([t](SchedulerTree& child) { child.triggerTimersUntil(t); }); const auto it = mTimers.upper_bound(make_pair(t, numeric_limits::max())); for_each(begin(mTimers), it, [this](const TimerMap::value_type& timer) { mPendingHandlers.push_back([timer]() { timer.second(0); // 0 indicates no error }); }); mTimers.erase(begin(mTimers), it); } bool SchedulerTree::handlePending() { bool subtreeWorked = false; withChildren( [&subtreeWorked](SchedulerTree& child) { subtreeWorked |= child.handlePending(); }); if (mPendingHandlers.empty()) { return subtreeWorked; } else { mPendingHandlers.front()(); mPendingHandlers.pop_front(); return true; } } SchedulerTree::TimePoint SchedulerTree::nextOwnTimerExpiration() { return mTimers.empty() ? TimePoint::max() : begin(mTimers)->first.first; } } // namespace serial_io } // namespace test } // namespace ableton link-Link-3.0.2/src/ableton/test/catch/0000755000175000017500000000000013303226525020053 5ustar zmoelnigzmoelniglink-Link-3.0.2/src/ableton/test/catch/CatchMain.cpp0000644000175000017500000000163213303226525022410 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #define CATCH_CONFIG_MAIN #include link-Link-3.0.2/src/ableton/link/0000755000175000017500000000000013303226525016747 5ustar zmoelnigzmoelniglink-Link-3.0.2/src/ableton/link/tst_Tempo.cpp0000644000175000017500000000571313303226525021437 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #include #include namespace ableton { namespace link { TEST_CASE("Tempo | ConstructFromBpm", "[Tempo]") { const auto tempo = Tempo{120.}; CHECK(120. == Approx(tempo.bpm())); CHECK(std::chrono::microseconds{500000} == tempo.microsPerBeat()); } TEST_CASE("Tempo | ConstructFromMicros", "[Tempo]") { const auto tempo = Tempo{std::chrono::microseconds{500000}}; CHECK(120. == Approx(tempo.bpm())); CHECK(std::chrono::microseconds{500000} == tempo.microsPerBeat()); } TEST_CASE("Tempo | MicrosToBeats", "[Tempo]") { const auto tempo = Tempo{120.}; CHECK(Beats{2.} == tempo.microsToBeats(std::chrono::microseconds{1000000})); } TEST_CASE("Tempo | BeatsToMicros", "[Tempo]") { const auto tempo = Tempo{120.}; CHECK(std::chrono::microseconds{1000000} == tempo.beatsToMicros(Beats{2.})); } TEST_CASE("Tempo | ComparisonLT", "[Tempo]") { const auto tempo1 = Tempo{100.}; const auto tempo2 = Tempo{200.}; CHECK(tempo1 < tempo2); } TEST_CASE("Tempo | ComparisonGT", "[Tempo]") { const auto tempo1 = Tempo{100.}; const auto tempo2 = Tempo{200.}; CHECK(tempo2 > tempo1); } TEST_CASE("Tempo | ComparisonLE", "[Tempo]") { const auto tempo1 = Tempo{100.}; const auto tempo2 = Tempo{200.}; CHECK(tempo1 <= tempo2); CHECK(tempo2 <= tempo2); } TEST_CASE("Tempo | ComparisonGE", "[Tempo]") { const auto tempo1 = Tempo{100.}; const auto tempo2 = Tempo{200.}; CHECK(tempo2 >= tempo1); CHECK(tempo2 >= tempo2); } TEST_CASE("Tempo | ComparisonEQ", "[Tempo]") { const auto tempo1 = Tempo{100.}; const auto tempo2 = Tempo{100.}; CHECK(tempo1 == tempo2); } TEST_CASE("Tempo | ComparisonNE", "[Tempo]") { const auto tempo1 = Tempo{100.}; const auto tempo2 = Tempo{200.}; CHECK(tempo1 != tempo2); } TEST_CASE("Tempo | RoundtripByteStreamEncoding", "[Tempo]") { const auto tempo = Tempo{120.}; std::vector bytes(sizeInByteStream(tempo)); const auto end = toNetworkByteStream(tempo, begin(bytes)); const auto result = Tempo::fromNetworkByteStream(begin(bytes), end); CHECK(tempo == result.first); } } // namespace link } // namespace ableton link-Link-3.0.2/src/ableton/link/tst_ClientSessionTimelines.cpp0000644000175000017500000001360213303226525025003 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #include #include namespace ableton { namespace link { namespace { using std::chrono::microseconds; // Make the constants non-zero to make sure we're not hiding bugs const auto b0 = Beats{-1.}; const auto t0 = microseconds{-1}; const auto xform = GhostXForm{1., microseconds{-1000000}}; } // namespace TEST_CASE("session->client | UpdatesTempo", "[ClientSessionTimelines]") { const auto curClient = Timeline{Tempo{60.}, b0, t0}; const auto session = Timeline{Tempo{90.}, b0, xform.hostToGhost(t0)}; const auto newClient = updateClientTimelineFromSession(curClient, session, t0, xform); CHECK(newClient.tempo == Tempo{90.}); } TEST_CASE("session->client | PreservesClientBeatMagnitude", "[ClientSessionTimelines]") { const auto curClient = Timeline{Tempo{60.}, b0, t0}; const auto session = Timeline{Tempo{120.}, Beats{12.32}, microseconds{-123456789}}; // Transition to the session timeline at t1 const auto t1 = t0 + microseconds{5000000}; const auto b1 = curClient.toBeats(t1); const auto newClient = updateClientTimelineFromSession(curClient, session, t1, xform); // The new client timeline should have the same magnitude as the old // one at the transition point CHECK(newClient.toBeats(t1) == b1); // At t1 + dt, the new tempo should be in effect CHECK(newClient.toBeats(t1 + microseconds{1000000}) == b1 + Beats{2.}); } TEST_CASE("session->client | AtDifferentTimesGivesSameResult", "[ClientSessionTimelines]") { const auto curClient = Timeline{Tempo{60.}, b0, t0}; const auto session = Timeline{Tempo{120.}, Beats{-11.}, microseconds{-12356789}}; const auto t1 = t0 + microseconds{5000000}; const auto t2 = t1 + microseconds{6849353}; // Once a given session timeline has been converted to a client // timeline, converting again from the same session at different // times should not change the result because the result is colinear // and the origin should always be anchored at the session's beat 0. const auto updated1 = updateClientTimelineFromSession(curClient, session, t1, xform); const auto updated2 = updateClientTimelineFromSession(updated1, session, t2, xform); CHECK(updated1 == updated2); } TEST_CASE("session->client | EncodesSessionOrigin", "[ClientSessionTimelines]") { const auto curClient = Timeline{Tempo{60.}, b0, t0}; const auto session = Timeline{Tempo{21.3}, Beats{-421.3}, microseconds{15003240}}; const auto newClient = updateClientTimelineFromSession(curClient, session, microseconds{-1345298}, xform); // The new client timeline's origin should be at beat 0 on the // session timeline. This is how we encode the session origin in the // client timeline. CHECK(xform.hostToGhost(newClient.timeOrigin) == session.fromBeats(Beats{0.})); } TEST_CASE("client->session | ShiftedForward", "[ClientSessionTimeline]") { const auto session = Timeline{Tempo{60.}, b0, t0}; auto client = Timeline{Tempo{60.}, Beats{111.}, xform.ghostToHost(t0)}; client = updateClientTimelineFromSession(client, session, t0, xform); // Shift the phase one beat forward client = shiftClientTimeline(client, Beats{1.}); const auto newSession = updateSessionTimelineFromClient(session, client, xform.ghostToHost(t0), xform); CHECK(newSession.toBeats(t0) == b0 + Beats{1.}); } TEST_CASE("client->session | ShiftedBackward", "[ClientSessionTimeline]") { const auto session = Timeline{Tempo{60.}, b0, t0}; auto client = Timeline{Tempo{60.}, Beats{983.}, xform.ghostToHost(t0)}; client = updateClientTimelineFromSession(client, session, t0, xform); // Shift the phase one beat backward client = shiftClientTimeline(client, Beats{-1.}); const auto newSession = updateSessionTimelineFromClient(session, client, xform.ghostToHost(t0), xform); CHECK(newSession.toBeats(t0) == b0 - Beats{1.}); } TEST_CASE("session->client->session | Roundtrip", "[ClientSessionTimeline]") { const auto session = Timeline{Tempo{60.}, b0, t0}; // Initial client timeline values shouldn't matter auto client = Timeline{Tempo{213.5}, Beats{432.}, microseconds{5143503}}; client = updateClientTimelineFromSession(client, session, t0, xform); auto newSession = updateSessionTimelineFromClient(session, client, microseconds{42905944}, xform); CHECK(session == newSession); // Now verify that modifying the client timeline and then routing it // through a session update results in the same client timeline. const auto updateTime = microseconds{-35023900}; newSession = updateSessionTimelineFromClient(newSession, client, updateTime, xform); const auto newClient = updateClientTimelineFromSession(client, newSession, updateTime, xform); CHECK(client == newClient); } TEST_CASE("shiftClientTimelineOrigin | Shift", "[ClientSessionTimeline]") { const auto shift = Beats{1.1}; const auto timeline = Timeline{Tempo{60.}, b0, t0}; CHECK(shiftClientTimeline(timeline, shift).toBeats(t0) == timeline.toBeats(t0) + shift); CHECK( shiftClientTimeline(timeline, -shift).toBeats(t0) == timeline.toBeats(t0) - shift); } } // namespace link } // namespace ableton link-Link-3.0.2/src/ableton/link/tst_PingResponder.cpp0000644000175000017500000001042313303226525023124 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #include #include #include #include #include #include #include #include #include #include #include #include #include namespace ableton { namespace link { namespace { struct RpFixture { RpFixture() : mAddress(asio::ip::address_v4::from_string("127.0.0.1")) , mResponder(mAddress, NodeId::random(), GhostXForm{1.0, std::chrono::microseconds{0}}, util::injectVal(util::test::IoService{}), MockClock{}, util::injectVal(util::NullLog{})) { } discovery::test::Socket responderSocket() { return mResponder.socket(); } std::size_t numSentMessages() { return responderSocket().sentMessages.size(); } struct MockClock { std::chrono::microseconds micros() const { return std::chrono::microseconds{4}; } }; asio::ip::address_v4 mAddress = asio::ip::address_v4::from_string("127.0.0.1"); PingResponder mResponder; }; } // anonymous namespace TEST_CASE("PingResponder | ReplyToPing", "[PingResponder]") { using std::chrono::microseconds; RpFixture fixture; // Construct and send Ping Message. Check if Responder sent back a Message const auto payload = discovery::makePayload(HostTime{microseconds(2)}, PrevGHostTime{microseconds(1)}); v1::MessageBuffer buffer; const auto msgBegin = std::begin(buffer); const auto msgEnd = v1::pingMessage(payload, msgBegin); const auto endpoint = asio::ip::udp::endpoint(fixture.mAddress, 8888); fixture.responderSocket().incomingMessage(endpoint, msgBegin, msgEnd); CHECK(1 == fixture.numSentMessages()); // Check Responder's message const auto messageBuffer = fixture.responderSocket().sentMessages[0].first; const auto result = v1::parseMessageHeader(begin(messageBuffer), end(messageBuffer)); const auto& hdr = result.first; std::chrono::microseconds ghostTime{0}; std::chrono::microseconds prevGHostTime{0}; std::chrono::microseconds hostTime{0}; discovery::parsePayload(result.second, std::end(messageBuffer), [&ghostTime](GHostTime gt) { ghostTime = std::move(gt.time); }, [&prevGHostTime](PrevGHostTime gt) { prevGHostTime = std::move(gt.time); }, [&hostTime](HostTime ht) { hostTime = std::move(ht.time); }); CHECK(v1::kPong == hdr.messageType); CHECK(std::chrono::microseconds{2} == hostTime); CHECK(std::chrono::microseconds{1} == prevGHostTime); CHECK(std::chrono::microseconds{4} == ghostTime); } TEST_CASE("PingResponder | PingSizeExceeding", "[PingResponder]") { using std::chrono::microseconds; RpFixture fixture; const auto ht = HostTime{microseconds(2)}; const auto payload = discovery::makePayload(ht, ht, ht); v1::MessageBuffer buffer; const auto msgBegin = std::begin(buffer); const auto msgEnd = v1::pingMessage(payload, msgBegin); const auto endpoint = asio::ip::udp::endpoint(fixture.mAddress, 8888); fixture.responderSocket().incomingMessage(endpoint, msgBegin, msgEnd); CHECK(0 == fixture.numSentMessages()); } } // namespace link } // namespace ableton link-Link-3.0.2/src/ableton/link/tst_Phase.cpp0000644000175000017500000001500013303226525021401 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #include #include namespace ableton { namespace link { namespace { const auto zero = Beats{0.}; const auto one = Beats{1.}; const auto two = Beats{2.}; const auto three = Beats{3.}; const auto four = Beats{4.}; using std::chrono::microseconds; const auto tl0 = Timeline{Tempo{120.}, one, microseconds{0}}; const auto tl1 = Timeline{Tempo{60.}, Beats{-9.5}, microseconds{2000000}}; } // namespace TEST_CASE("phase | phase(x, 0) == 0", "[Phase]") { CHECK(phase(zero, zero) == zero); CHECK(phase(Beats{0.1}, zero) == zero); CHECK(phase(one, zero) == zero); CHECK(phase(-one, zero) == zero); } TEST_CASE("phase | phase(x, y) == x % y when x and y >= 0", "[Phase]") { CHECK(phase(zero, zero) == zero % zero); CHECK(phase(one, zero) == one % zero); CHECK(phase(zero, one) == zero % one); CHECK(phase(Beats{0.1}, one) == Beats{0.1} % one); CHECK(phase(Beats{2.3}, one) == Beats{2.3} % one); CHECK(phase(Beats{9.5}, Beats{2.3}) == Beats{9.5} % Beats{2.3}); } TEST_CASE("phase | phase of negatives is not mirrored around zero", "[Phase]") { CHECK(phase(-one, one) == zero); CHECK(phase(Beats{-0.1}, one) == Beats{0.9}); CHECK(phase(Beats{-2.3}, one) == Beats{0.7}); CHECK(phase(Beats{-9.5}, Beats{2.3}) == Beats{2.}); } TEST_CASE("nextPhaseMatch | result == x when quantum == 0", "[Phase]") { CHECK(nextPhaseMatch(Beats{0.1}, one, zero) == Beats{0.1}); CHECK(nextPhaseMatch(Beats{2.3}, Beats{9.5}, zero) == Beats{2.3}); CHECK(nextPhaseMatch(Beats{-0.1}, Beats{-2.3}, zero) == Beats{-0.1}); } TEST_CASE("nextPhaseMatch | result == target when 0 <= x < target < quantum", "[Phase]") { CHECK(nextPhaseMatch(zero, Beats{0.1}, one) == Beats{0.1}); CHECK(nextPhaseMatch(Beats{0.1}, one, two) == one); CHECK(nextPhaseMatch(one, two, Beats{2.3}) == two); CHECK(nextPhaseMatch(two, Beats{2.3}, three) == Beats{2.3}); } TEST_CASE("nextPhaseMatch | some example cases", "[Phase]") { CHECK(nextPhaseMatch(one, Beats{2.3}, two) == Beats{2.3}); CHECK(nextPhaseMatch(Beats{2.3}, Beats{-0.1}, two) == Beats{3.9}); CHECK(nextPhaseMatch(Beats{-9.5}, Beats{0.1}, two) == Beats{-7.9}); CHECK(nextPhaseMatch(Beats{-2.3}, Beats{0.1}, Beats{9.5}) == Beats{0.1}); } TEST_CASE("toPhaseEncodedBeats | result == tl.toBeats when quantum == 0", "[Phase]") { const auto t0 = microseconds{0}; const auto t1 = microseconds{2000000}; const auto t2 = microseconds{-3200000}; CHECK(toPhaseEncodedBeats(tl1, t0, zero) == tl1.toBeats(t0)); CHECK(toPhaseEncodedBeats(tl0, t1, zero) == tl0.toBeats(t1)); CHECK(toPhaseEncodedBeats(tl0, t2, zero) == tl0.toBeats(t2)); } TEST_CASE("toPhaseEncodedBeats | result is the nearest quantum boundary", "[Phase]") { const auto sec = microseconds{1000000}; // Takes the previous boundary CHECK(toPhaseEncodedBeats(tl0, sec, Beats{2.2}) == two); // Takes the next boundary CHECK(toPhaseEncodedBeats(tl0, sec, Beats{1.8}) == Beats{3.8}); // Takes the previous boundary when exactly in the middle CHECK(toPhaseEncodedBeats(tl0, sec, two) == two); } TEST_CASE("toPhaseEncodedBeats | some example cases", "[Phase]") { CHECK(toPhaseEncodedBeats(tl0, microseconds{-2000000}, four) == -four); CHECK(toPhaseEncodedBeats(tl0, microseconds{-3000000}, four) == Beats{-6.}); CHECK(toPhaseEncodedBeats(tl0, microseconds{3200000}, three) == Beats{6.4}); CHECK(toPhaseEncodedBeats(tl1, microseconds{0}, three) == Beats{-11.}); CHECK(toPhaseEncodedBeats(tl1, microseconds{1500000}, Beats{2.4}) == Beats{-10.1}); } namespace { std::chrono::microseconds phaseEncodingRoundtrip( const Timeline& tl, const std::chrono::microseconds t, const Beats quantum) { return fromPhaseEncodedBeats(tl, toPhaseEncodedBeats(tl, t, quantum), quantum); } } // namespace TEST_CASE("fromPhaseEncodedBeats | inverse of toPhaseEncodedBeats", "[Phase]") { const auto t0 = microseconds{0}; const auto t1 = microseconds{2000000}; const auto t2 = microseconds{-3200000}; const auto t3 = microseconds{87654321}; CHECK(phaseEncodingRoundtrip(tl0, t0, zero) == t0); CHECK(phaseEncodingRoundtrip(tl0, t1, zero) == t1); CHECK(phaseEncodingRoundtrip(tl0, t2, zero) == t2); CHECK(phaseEncodingRoundtrip(tl0, t3, zero) == t3); CHECK(phaseEncodingRoundtrip(tl0, t0, one) == t0); CHECK(phaseEncodingRoundtrip(tl0, t1, one) == t1); CHECK(phaseEncodingRoundtrip(tl0, t2, one) == t2); CHECK(phaseEncodingRoundtrip(tl0, t3, one) == t3); CHECK(phaseEncodingRoundtrip(tl0, t0, two) == t0); CHECK(phaseEncodingRoundtrip(tl0, t1, two) == t1); CHECK(phaseEncodingRoundtrip(tl0, t2, two) == t2); CHECK(phaseEncodingRoundtrip(tl0, t3, two) == t3); CHECK(phaseEncodingRoundtrip(tl0, t0, three) == t0); CHECK(phaseEncodingRoundtrip(tl0, t1, three) == t1); CHECK(phaseEncodingRoundtrip(tl0, t2, three) == t2); CHECK(phaseEncodingRoundtrip(tl0, t3, three) == t3); CHECK(phaseEncodingRoundtrip(tl1, t0, zero) == t0); CHECK(phaseEncodingRoundtrip(tl1, t1, zero) == t1); CHECK(phaseEncodingRoundtrip(tl1, t2, zero) == t2); CHECK(phaseEncodingRoundtrip(tl1, t3, zero) == t3); CHECK(phaseEncodingRoundtrip(tl1, t0, one) == t0); CHECK(phaseEncodingRoundtrip(tl1, t1, one) == t1); CHECK(phaseEncodingRoundtrip(tl1, t2, one) == t2); CHECK(phaseEncodingRoundtrip(tl1, t3, one) == t3); CHECK(phaseEncodingRoundtrip(tl1, t0, two) == t0); CHECK(phaseEncodingRoundtrip(tl1, t1, two) == t1); CHECK(phaseEncodingRoundtrip(tl1, t2, two) == t2); CHECK(phaseEncodingRoundtrip(tl1, t3, two) == t3); CHECK(phaseEncodingRoundtrip(tl1, t0, three) == t0); CHECK(phaseEncodingRoundtrip(tl1, t1, three) == t1); CHECK(phaseEncodingRoundtrip(tl1, t2, three) == t2); CHECK(phaseEncodingRoundtrip(tl1, t3, three) == t3); } } // namespace link } // namespace ableton link-Link-3.0.2/src/ableton/link/tst_HostTimeFilter.cpp0000644000175000017500000000410013303226525023242 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #include #include #include namespace ableton { namespace link { struct MockClock { MockClock() : time(std::chrono::microseconds(0)) { } std::chrono::microseconds micros() { const auto current = time; time += std::chrono::microseconds(1); return current; } std::chrono::microseconds time; }; using Filter = ableton::link::HostTimeFilter; TEST_CASE("HostTimeFilter | OneValue", "[HostTimeFilter]") { Filter filter; const auto ht = filter.sampleTimeToHostTime(5); CHECK(0 == ht.count()); } TEST_CASE("HostTimeFilter | MultipleValues", "[HostTimeFilter]") { Filter filter; const auto numValues = 600; auto ht = std::chrono::microseconds(0); for (int i = 0; i <= numValues; ++i) { ht = filter.sampleTimeToHostTime(i); } CHECK(numValues == ht.count()); } TEST_CASE("HostTimeFilter | Reset", "[HostTimeFilter]") { Filter filter; auto ht = filter.sampleTimeToHostTime(0); ht = filter.sampleTimeToHostTime(-230); ht = filter.sampleTimeToHostTime(40); REQUIRE(2 != ht.count()); filter.reset(); ht = filter.sampleTimeToHostTime(0); CHECK(3 == ht.count()); } } // namespace link } // namespace ableton link-Link-3.0.2/src/ableton/link/tst_Controller.cpp0000644000175000017500000003175513303226525022503 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #include #include #include #include namespace ableton { namespace link { using namespace std::chrono; static bool operator==(const IncomingClientState& lhs, const ClientState& rhs) { return static_cast(lhs.timeline) && static_cast(lhs.startStopState) && std::tie(*lhs.timeline, *lhs.startStopState) == std::tie(rhs.timeline, rhs.startStopState); } namespace { struct MockClock { template void advance(std::chrono::duration duration) { now() += duration; } microseconds micros() const { return now(); } private: static microseconds& now() { static microseconds now{1}; return now; } }; struct MockIoContext { template struct Socket { std::size_t send( const uint8_t* const, const size_t numBytes, const asio::ip::udp::endpoint&) { return numBytes; } template void receive(Handler) { } asio::ip::udp::endpoint endpoint() const { return {}; } }; template Socket openUnicastSocket(const asio::ip::address_v4&) { return {}; } template Socket openMulticastSocket(const asio::ip::address_v4&) { return {}; } std::vector scanNetworkInterfaces() { return {}; } using Timer = util::test::Timer; Timer makeTimer() { return {}; } template struct LockFreeCallbackDispatcher { LockFreeCallbackDispatcher(Callback callback, Duration) : mCallback(std::move(callback)) { } void invoke() { mCallback(); } Callback mCallback; }; using Log = util::NullLog; Log log() const { return {}; } template void async(Handler handler) const { handler(); } MockIoContext clone() const { return {}; } template MockIoContext clone(ExceptionHandler) const { return {}; } }; using MockController = Controller; const auto kAnyBeatTime = Beats{5.}; const auto kAnyTime = std::chrono::microseconds{6}; struct TempoClientCallback { void operator()(const Tempo bpm) { tempos.push_back(bpm); } std::vector tempos; }; struct StartStopStateClientCallback { void operator()(const bool isPlaying) { startStopStates.push_back(isPlaying); } std::vector startStopStates; }; template void testSetAndGetClientState( SetClientStateFunctionT setClientState, GetClientStateFunctionT getClientState) { using namespace std::chrono; auto clock = MockClock{}; MockController controller(Tempo{100.0}, [](std::size_t) {}, [](Tempo) {}, [](bool) {}, clock, util::injectVal(MockIoContext{})); clock.advance(microseconds{1}); const auto initialTimeline = Optional{Timeline{Tempo{60.}, Beats{0.}, kAnyTime}}; const auto initialStartStopState = Optional{StartStopState{true, kAnyBeatTime, clock.micros()}}; const auto initialClientState = IncomingClientState{initialTimeline, initialStartStopState, clock.micros()}; setClientState(controller, initialClientState); SECTION("Client state is correct after initial set") { CHECK(initialClientState == getClientState(controller)); } SECTION("Set outdated start stop state (timestamp unchanged)") { // Set client state with a StartStopState having the same timestamp as the current // StartStopState - don't advance clock const auto outdatedStartStopState = Optional{StartStopState{false, kAnyBeatTime, clock.micros()}}; setClientState(controller, IncomingClientState{Optional{}, outdatedStartStopState, clock.micros()}); CHECK(initialClientState == getClientState(controller)); } clock.advance(microseconds{1}); SECTION("Set outdated start stop state (timestamp in past)") { const auto outdatedStartStopState = Optional{StartStopState{false, kAnyBeatTime, microseconds{0}}}; setClientState(controller, IncomingClientState{Optional{}, outdatedStartStopState, clock.micros()}); CHECK(initialClientState == getClientState(controller)); } SECTION("Set empty client state") { setClientState(controller, IncomingClientState{Optional{}, Optional{}, clock.micros()}); CHECK(initialClientState == getClientState(controller)); } SECTION("Set client state with new Timeline and StartStopState") { const auto expectedTimeline = Optional{Timeline{Tempo{80.}, Beats{1.}, kAnyTime}}; const auto expectedStartStopState = Optional{StartStopState{false, kAnyBeatTime, clock.micros()}}; const auto expectedClientState = IncomingClientState{expectedTimeline, expectedStartStopState, clock.micros()}; setClientState(controller, expectedClientState); CHECK(expectedClientState == getClientState(controller)); } } template void testCallbackInvocation(SetClientStateFunctionT setClientState) { using namespace std::chrono; auto clock = MockClock{}; auto tempoCallback = TempoClientCallback{}; auto startStopStateCallback = StartStopStateClientCallback{}; MockController controller(Tempo{100.0}, [](std::size_t) {}, std::ref(tempoCallback), std::ref(startStopStateCallback), clock, util::injectVal(MockIoContext{})); clock.advance(microseconds{1}); const auto initialTempo = Tempo{50.}; const auto initialIsPlaying = true; const auto initialTimeline = Optional{Timeline{initialTempo, Beats{0.}, kAnyTime}}; const auto initialStartStopState = Optional{ StartStopState{initialIsPlaying, kAnyBeatTime, clock.micros()}}; setClientState(controller, {initialTimeline, initialStartStopState, clock.micros()}); SECTION("Callbacks are called when setting new client state") { CHECK(std::vector{initialTempo} == tempoCallback.tempos); CHECK(std::vector{initialIsPlaying} == startStopStateCallback.startStopStates); clock.advance(microseconds{1}); tempoCallback.tempos = {}; startStopStateCallback.startStopStates = {}; SECTION("Callbacks mustn't be called if Tempo and isPlaying don't change") { const auto timeline = Optional{Timeline{initialTempo, Beats{1.}, kAnyTime}}; const auto startStopState = Optional{ StartStopState{initialIsPlaying, kAnyBeatTime, clock.micros()}}; setClientState(controller, {timeline, startStopState, clock.micros()}); CHECK(tempoCallback.tempos.empty()); CHECK(startStopStateCallback.startStopStates.empty()); } } } } // namespace TEST_CASE("Controller | ConstructOptimistically", "[Controller]") { MockController controller(Tempo{100.0}, [](std::size_t) {}, [](Tempo) {}, [](bool) {}, MockClock{}, util::injectVal(MockIoContext{})); CHECK(!controller.isEnabled()); CHECK(!controller.isStartStopSyncEnabled()); CHECK(0 == controller.numPeers()); const auto tl = controller.clientState().timeline; CHECK(Tempo{100.0} == tl.tempo); } TEST_CASE("Controller | ConstructWithInvalidTempo", "[Controller]") { MockController controllerLowTempo(Tempo{1.0}, [](std::size_t) {}, [](Tempo) {}, [](bool) {}, MockClock{}, util::injectVal(MockIoContext{})); const auto tlLow = controllerLowTempo.clientState().timeline; CHECK(Tempo{20.0} == tlLow.tempo); MockController controllerHighTempo(Tempo{100000.0}, [](std::size_t) {}, [](Tempo) {}, [](bool) {}, MockClock{}, util::injectVal(MockIoContext{})); const auto tlHigh = controllerHighTempo.clientState().timeline; CHECK(Tempo{999.0} == tlHigh.tempo); } TEST_CASE("Controller | EnableDisable", "[Controller]") { MockController controller(Tempo{100.0}, [](std::size_t) {}, [](Tempo) {}, [](bool) {}, MockClock{}, util::injectVal(MockIoContext{})); controller.enable(true); CHECK(controller.isEnabled()); controller.enable(false); CHECK(!controller.isEnabled()); } TEST_CASE("Controller | EnableDisableStartStopSync", "[Controller]") { MockController controller(Tempo{100.0}, [](std::size_t) {}, [](Tempo) {}, [](bool) {}, MockClock{}, util::injectVal(MockIoContext{})); controller.enableStartStopSync(true); CHECK(controller.isStartStopSyncEnabled()); controller.enableStartStopSync(false); CHECK(!controller.isStartStopSyncEnabled()); } TEST_CASE("Controller | SetAndGetClientStateThreadSafe", "[Controller]") { testSetAndGetClientState( [](MockController& controller, IncomingClientState clientState) { controller.setClientState(clientState); }, [](MockController& controller) { return controller.clientState(); }); } TEST_CASE("Controller | SetAndGetClientStateRealtimeSafe", "[Controller]") { testSetAndGetClientState( [](MockController& controller, IncomingClientState clientState) { controller.setClientStateRtSafe(clientState); }, [](MockController& controller) { return controller.clientStateRtSafe(); }); } TEST_CASE("Controller | SetClientStateRealtimeSafeAndGetItThreadSafe", "[Controller]") { testSetAndGetClientState( [](MockController& controller, IncomingClientState clientState) { controller.setClientStateRtSafe(clientState); }, [](MockController& controller) { return controller.clientState(); }); } TEST_CASE("Controller | SetClientStateThreadSafeAndGetItRealtimeSafe", "[Controller]") { testSetAndGetClientState( [](MockController& controller, IncomingClientState clientState) { controller.setClientState(clientState); }, [](MockController& controller) { MockClock{}.advance(seconds{2}); return controller.clientStateRtSafe(); }); } TEST_CASE("Controller | CallbacksCalledBySettingClientStateThreadSafe", "[Controller]") { testCallbackInvocation([](MockController& controller, IncomingClientState clientState) { controller.setClientState(clientState); }); } TEST_CASE("Controller | CallbacksCalledBySettingClientStateRealtimeSafe", "[Controller]") { testCallbackInvocation([](MockController& controller, IncomingClientState clientState) { controller.setClientStateRtSafe(clientState); }); } TEST_CASE("Controller | GetClientStateRtSafeGracePeriod", "[Controller]") { using namespace std::chrono; auto clock = MockClock{}; auto tempoCallback = TempoClientCallback{}; auto startStopStateCallback = StartStopStateClientCallback{}; MockController controller(Tempo{100.0}, [](std::size_t) {}, std::ref(tempoCallback), std::ref(startStopStateCallback), clock, util::injectVal(MockIoContext{})); controller.enable(true); clock.advance(microseconds{1}); const auto initialTimeline = Optional{Timeline{Tempo{50.}, Beats{0.}, clock.micros()}}; const auto initialStartStopState = Optional{StartStopState{true, kAnyBeatTime, clock.micros()}}; const auto initialState = IncomingClientState{initialTimeline, initialStartStopState, clock.micros()}; controller.setClientStateRtSafe( {initialTimeline, initialStartStopState, clock.micros()}); REQUIRE(initialState == controller.clientState()); REQUIRE(initialState == controller.clientStateRtSafe()); clock.advance(microseconds{1}); const auto newTimeline = Optional{Timeline{Tempo{70.}, Beats{1.}, clock.micros()}}; const auto newStartStopState = Optional{StartStopState{false, kAnyBeatTime, clock.micros()}}; const auto newState = IncomingClientState{newTimeline, newStartStopState, clock.micros()}; controller.setClientState({newTimeline, newStartStopState, clock.micros()}); clock.advance(milliseconds{500}); CHECK(newState == controller.clientState()); CHECK(initialState == controller.clientStateRtSafe()); clock.advance(milliseconds{500}); CHECK(newState == controller.clientState()); CHECK(newState == controller.clientStateRtSafe()); } } // namespace link } // namespace ableton link-Link-3.0.2/src/ableton/link/tst_StartStopState.cpp0000644000175000017500000000274213303226525023316 0ustar zmoelnigzmoelnig/* Copyright 2017, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #include #include namespace ableton { namespace link { TEST_CASE("StartStopState | RoundtripByteStreamEncoding", "[StartStopState]") { const auto originalState = StartStopState{true, Beats{1234.}, std::chrono::microseconds{5678}}; std::vector bytes(sizeInByteStream(originalState)); const auto serializedEndIter = toNetworkByteStream(originalState, begin(bytes)); const auto deserialized = StartStopState::fromNetworkByteStream(begin(bytes), serializedEndIter); CHECK(originalState == deserialized.first); } } // namespace link } // namespace ableton link-Link-3.0.2/src/ableton/link/tst_CircularFifo.cpp0000644000175000017500000000357213303226525022724 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #include #include namespace ableton { namespace link { TEST_CASE("CircularFifo | PushNPop", "[CircularFifo]") { CircularFifo cf; for (int i = 0; i < 2; ++i) { CHECK(cf.push(i)); } CHECK(!cf.push(0)); for (int i = 0; i < 2; ++i) { auto result = cf.pop(); CHECK(result); CHECK(*result == i); } CHECK(!cf.pop()); } TEST_CASE("CircularFifo | Wrap", "[CircularFifo]") { CircularFifo cf; for (int i = 0; i < 5; ++i) { CHECK(cf.push(i)); auto result = cf.pop(); CHECK(result); CHECK(*result == i); } } TEST_CASE("CircularFifo | IsEmpty", "[CircularFifo]") { CircularFifo cf; CHECK(cf.isEmpty()); CHECK(cf.push(1)); CHECK(!cf.isEmpty()); CHECK(cf.push(2)); CHECK(!cf.isEmpty()); CHECK(!cf.push(3)); CHECK(!cf.isEmpty()); CHECK(cf.pop()); CHECK(!cf.isEmpty()); CHECK(cf.pop()); CHECK(cf.isEmpty()); CHECK(!cf.pop()); CHECK(cf.isEmpty()); } } // namespace link } // namespace ableton link-Link-3.0.2/src/ableton/link/tst_LinearRegression.cpp0000644000175000017500000000437313303226525023627 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #include #include #include #include namespace ableton { namespace link { using Vector = std::vector>; TEST_CASE("LinearRegression | EmptyVector", "[LinearRegression]") { Vector data; const auto result = linearRegression(data.begin(), data.end()); CHECK(0 == Approx(result.first)); } TEST_CASE("LinearRegression | OnePoint", "[LinearRegression]") { using Array = std::array, 1>; Array data; data[0] = {{}, {}}; const auto result = linearRegression(data.begin(), data.end()); CHECK(0 == Approx(result.first)); CHECK(0 == Approx(result.second)); } TEST_CASE("LinearRegression | TwoPoints", "[LinearRegression]") { Vector data; data.emplace_back(0.0, 0.0); data.emplace_back(666666.6, 66666.6); const auto result = linearRegression(data.begin(), data.end()); CHECK(0.1 == Approx(result.first)); CHECK(0.0 == Approx(result.second)); } TEST_CASE("LinearRegression | 10000Points", "[LinearRegression]") { Vector data; const double slope = -0.2; const double intercept = -357.53456; for (int i = 1; i < 10000; ++i) { data.emplace_back(i, i * slope + intercept); } const auto result = linearRegression(data.begin(), data.end()); CHECK(slope == Approx(result.first)); CHECK(intercept == Approx(result.second)); } } // namespace link } // namespace ableton link-Link-3.0.2/src/ableton/link/tst_Kalman.cpp0000644000175000017500000000322113303226525021546 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #include #include #include #include namespace ableton { namespace link { TEST_CASE("Kalman | Check1", "[Kalman]") { int peerTimeDiff = 0; Kalman<16> filter; for (int i = 0; i < 5; ++i) { filter.iterate(peerTimeDiff); } CHECK(peerTimeDiff == Approx(filter.getValue())); filter.iterate(100); CHECK(peerTimeDiff != Approx(filter.getValue())); } TEST_CASE("Kalman | Check2", "[Kalman]") { double peerTimeDiff = 3e11; Kalman<5> filter; for (int i = 0; i < 15; ++i) { filter.iterate(peerTimeDiff); } CHECK(peerTimeDiff == Approx(filter.getValue())); for (int i = 0; i < 15; ++i) { filter.iterate(11); } CHECK(peerTimeDiff != Approx(filter.getValue())); } } // namespace link } // namespace ableton link-Link-3.0.2/src/ableton/link/tst_Timeline.cpp0000644000175000017500000000343713303226525022122 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #include #include namespace ableton { namespace link { TEST_CASE("Timeline | TimeToBeats", "[Timeline]") { const auto tl = Timeline{Tempo{60.}, Beats{-1.}, std::chrono::microseconds{1000000}}; CHECK(Beats{2.5} == tl.toBeats(std::chrono::microseconds{4500000})); } TEST_CASE("Timeline | BeatsToTime", "[Timeline]") { const auto tl = Timeline{Tempo{60.}, Beats{-1.}, std::chrono::microseconds{1000000}}; CHECK(std::chrono::microseconds{5200000} == tl.fromBeats(Beats{3.2})); } TEST_CASE("Timeline | RoundtripByteStreamEncoding", "[Timeline]") { const auto tl = Timeline{Tempo{120.}, Beats{5.5}, std::chrono::microseconds{12558940}}; std::vector bytes(sizeInByteStream(tl)); const auto end = toNetworkByteStream(tl, begin(bytes)); const auto result = Timeline::fromNetworkByteStream(begin(bytes), end); CHECK(tl == result.first); } } // namespace link } // namespace ableton link-Link-3.0.2/src/ableton/link/tst_Peers.cpp0000644000175000017500000001556213303226525021434 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #include #include #include namespace ableton { namespace link { namespace { struct SessionMembershipCallback { void operator()() { ++calls; } std::size_t calls = 0; }; struct SessionTimelineCallback { void operator()(const SessionId& sessionId, const Timeline& timeline) { sessionTimelines.push_back(std::make_pair(sessionId, timeline)); } std::vector> sessionTimelines; }; struct SessionStartStopStateCallback { void operator()(const SessionId& sessionId, const StartStopState& startStopState) { sessionStartStopStates.push_back(std::make_pair(sessionId, startStopState)); } std::vector> sessionStartStopStates; }; const auto fooPeer = PeerState{{NodeId::random(), NodeId::random(), Timeline{Tempo{60.}, Beats{1.}, std::chrono::microseconds{1234}}, StartStopState{false, Beats{0.}, std::chrono::microseconds{2345}}}, {}}; const auto barPeer = PeerState{{NodeId::random(), NodeId::random(), Timeline{Tempo{120.}, Beats{10.}, std::chrono::microseconds{500}}, {}}, {}}; const auto bazPeer = PeerState{{NodeId::random(), NodeId::random(), Timeline{Tempo{100.}, Beats{4.}, std::chrono::microseconds{100}}, {}}, {}}; const auto gateway1 = asio::ip::address::from_string("123.123.123.123"); const auto gateway2 = asio::ip::address::from_string("210.210.210.210"); using PeerVector = std::vector::Peer>; void expectPeers(const PeerVector& expected, const PeerVector& actual) { CHECK(expected == actual); } void expectSessionTimelines(const std::vector>& expected, const SessionTimelineCallback& callback) { CHECK(expected == callback.sessionTimelines); } void expectStartStopStates( const std::vector>& expected, const SessionStartStopStateCallback& callback) { CHECK(expected == callback.sessionStartStopStates); } } // anonymous namespace TEST_CASE("Peers | EmptySessionPeersAfterInit", "[Peers]") { test::serial_io::Fixture io; auto peers = makePeers(util::injectVal(io.makeIoContext()), SessionMembershipCallback{}, SessionTimelineCallback{}, SessionStartStopStateCallback{}); io.flush(); expectPeers({}, peers.sessionPeers(fooPeer.sessionId())); } TEST_CASE("Peers | AddAndFindPeer", "[Peers]") { test::serial_io::Fixture io; auto membership = SessionMembershipCallback{}; auto sessions = SessionTimelineCallback{}; auto startStops = SessionStartStopStateCallback{}; auto peers = makePeers(util::injectVal(io.makeIoContext()), std::ref(membership), std::ref(sessions), std::ref(startStops)); auto observer = makeGatewayObserver(peers, gateway1); sawPeer(observer, fooPeer); io.flush(); expectPeers({{fooPeer, gateway1}}, peers.sessionPeers(fooPeer.sessionId())); CHECK(1u == membership.calls); expectSessionTimelines({make_pair(fooPeer.sessionId(), fooPeer.timeline())}, sessions); expectStartStopStates( {make_pair(fooPeer.sessionId(), fooPeer.startStopState())}, startStops); } TEST_CASE("Peers | AddAndRemovePeer", "[Peers]") { test::serial_io::Fixture io; auto membership = SessionMembershipCallback{}; auto peers = makePeers(util::injectVal(io.makeIoContext()), std::ref(membership), SessionTimelineCallback{}, SessionStartStopStateCallback{}); auto observer = makeGatewayObserver(peers, gateway1); sawPeer(observer, fooPeer); peerLeft(observer, fooPeer.ident()); io.flush(); expectPeers({}, peers.sessionPeers(fooPeer.sessionId())); CHECK(2u == membership.calls); } TEST_CASE("Peers | AddTwoPeersRemoveOne", "[Peers]") { test::serial_io::Fixture io; auto membership = SessionMembershipCallback{}; auto sessions = SessionTimelineCallback{}; auto startStops = SessionStartStopStateCallback{}; auto peers = makePeers(util::injectVal(io.makeIoContext()), std::ref(membership), std::ref(sessions), std::ref(startStops)); auto observer = makeGatewayObserver(peers, gateway1); sawPeer(observer, fooPeer); sawPeer(observer, barPeer); peerLeft(observer, fooPeer.ident()); io.flush(); expectPeers({}, peers.sessionPeers(fooPeer.sessionId())); expectPeers({{barPeer, gateway1}}, peers.sessionPeers(barPeer.sessionId())); CHECK(3u == membership.calls); } TEST_CASE("Peers | AddThreePeersTwoOnSameGateway", "[Peers]") { test::serial_io::Fixture io; auto membership = SessionMembershipCallback{}; auto sessions = SessionTimelineCallback{}; auto startStops = SessionStartStopStateCallback{}; auto peers = makePeers(util::injectVal(io.makeIoContext()), std::ref(membership), std::ref(sessions), std::ref(startStops)); auto observer1 = makeGatewayObserver(peers, gateway1); auto observer2 = makeGatewayObserver(peers, gateway2); sawPeer(observer1, fooPeer); sawPeer(observer2, fooPeer); sawPeer(observer1, barPeer); sawPeer(observer1, bazPeer); io.flush(); expectPeers( {{fooPeer, gateway1}, {fooPeer, gateway2}}, peers.sessionPeers(fooPeer.sessionId())); CHECK(3 == membership.calls); } TEST_CASE("Peers | CloseGateway", "[Peers]") { test::serial_io::Fixture io; auto membership = SessionMembershipCallback{}; auto sessions = SessionTimelineCallback{}; auto startStops = SessionStartStopStateCallback{}; auto peers = makePeers(util::injectVal(io.makeIoContext()), std::ref(membership), std::ref(sessions), std::ref(startStops)); auto observer1 = makeGatewayObserver(peers, gateway1); { // The observer will close the gateway when it goes out of scope auto observer2 = makeGatewayObserver(peers, gateway2); sawPeer(observer2, fooPeer); sawPeer(observer2, barPeer); sawPeer(observer1, fooPeer); sawPeer(observer2, bazPeer); } io.flush(); expectPeers({{fooPeer, gateway1}}, peers.sessionPeers(fooPeer.sessionId())); CHECK(4 == membership.calls); } } // namespace link } // namespace ableton link-Link-3.0.2/src/ableton/link/tst_Measurement.cpp0000644000175000017500000001137213303226525022636 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #include #include #include #include #include #include #include #include #include #include namespace ableton { namespace link { namespace { using Socket = discovery::test::Socket; struct MockClock { std::chrono::microseconds micros() const { return std::chrono::microseconds{4}; } }; struct TFixture { TFixture() : mAddress(asio::ip::address_v4::from_string("127.0.0.1")) , mMeasurement(mStateQuery(), [](std::vector>) {}, mAddress, MockClock{}, util::injectVal(util::NullLog{})) { } Socket& socket() { return *(mMeasurement.mpImpl->mpSocket); } struct StateQuery { StateQuery() { mState.nodeState.sessionId = NodeId::random(); mState.endpoint = asio::ip::udp::endpoint(asio::ip::address_v4::from_string("127.0.0.1"), 9999); } PeerState operator()() { return mState; } PeerState mState; }; struct GhostXFormQuery { GhostXForm operator()() const { return {1.0, std::chrono::microseconds{0}}; } }; StateQuery mStateQuery; GhostXFormQuery mGhostXFormQuery; asio::ip::address_v4 mAddress; Measurement mMeasurement; }; } // anonymous namespace TEST_CASE("Measurement | SendPingsOnConstruction", "[Measurement]") { TFixture fixture; CHECK(1 == fixture.socket().sentMessages.size()); const auto messageBuffer = fixture.socket().sentMessages[0].first; const auto result = v1::parseMessageHeader(std::begin(messageBuffer), std::end(messageBuffer)); const auto& header = result.first; std::chrono::microseconds gt{0}; std::chrono::microseconds ht{0}; discovery::parsePayload(result.second, std::end(messageBuffer), [>](GHostTime ghostTime) { gt = std::move(ghostTime.time); }, [&ht](HostTime hostTime) { ht = std::move(hostTime.time); }); CHECK(v1::kPing == header.messageType); CHECK(std::chrono::microseconds{4} == ht); CHECK(std::chrono::microseconds{0} == gt); } TEST_CASE("Measurement | ReceiveInitPong", "[Measurement]") { using Micros = std::chrono::microseconds; TFixture fixture; const auto id = SessionMembership{fixture.mStateQuery.mState.nodeState.sessionId}; const auto ht = HostTime{Micros(0)}; const auto gt = GHostTime{Micros(0)}; const auto payload = discovery::makePayload(id, gt, ht); v1::MessageBuffer buffer; const auto msgBegin = std::begin(buffer); const auto msgEnd = v1::pongMessage(payload, msgBegin); const auto endpoint = asio::ip::udp::endpoint(asio::ip::address_v4::from_string("127.0.0.1"), 8888); fixture.socket().incomingMessage(endpoint, msgBegin, msgEnd); CHECK(2 == fixture.socket().sentMessages.size()); CHECK(0 == fixture.mMeasurement.mpImpl->mData.size()); } TEST_CASE("Measurement | ReceivePong", "[Measurement]") { using Micros = std::chrono::microseconds; TFixture fixture; const auto id = SessionMembership{fixture.mStateQuery.mState.nodeState.sessionId}; const auto ht = HostTime{Micros(2)}; const auto gt = GHostTime{Micros(3)}; const auto pgt = PrevGHostTime{Micros(1)}; const auto payload = discovery::makePayload(id, gt, ht, pgt); v1::MessageBuffer buffer; const auto msgBegin = std::begin(buffer); const auto msgEnd = v1::pongMessage(payload, msgBegin); const auto endpoint = asio::ip::udp::endpoint(asio::ip::address_v4::from_string("127.0.0.1"), 8888); fixture.socket().incomingMessage(endpoint, msgBegin, msgEnd); CHECK(2 == fixture.socket().sentMessages.size()); CHECK(2 == fixture.mMeasurement.mpImpl->mData.size()); } } // namespace link } // namespace ableton link-Link-3.0.2/src/ableton/link/tst_Beats.cpp0000644000175000017500000000503013303226525021401 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #include #include namespace ableton { namespace link { TEST_CASE("Beats | ConstructFromFloating", "[Beats]") { const auto beats = Beats{0.5}; CHECK(500000 == beats.microBeats()); CHECK(0.5 == Approx(beats.floating())); } TEST_CASE("Beats | ConstructFromMicros", "[Beats]") { const auto beats = Beats{INT64_C(100000)}; CHECK(100000 == beats.microBeats()); CHECK(0.1 == Approx(beats.floating())); } TEST_CASE("Beats | Negation", "[Beats]") { const auto beat = Beats{1.}; CHECK(beat > -beat); CHECK(beat == -(-beat)); CHECK(-beat < Beats{0.}); } TEST_CASE("Beats | Addition", "[Beats]") { const auto beat1 = Beats{0.5}; const auto beat2 = Beats{INT64_C(200000)}; const auto beat3 = Beats{0.1}; CHECK(beat1 == beat2 + beat2 + beat3); } TEST_CASE("Beats | Subtraction", "[Beats]") { const auto beat1 = Beats{0.5}; const auto beat2 = Beats{INT64_C(200000)}; const auto beat3 = Beats{0.1}; CHECK(beat3 == beat1 - beat2 - beat2); } TEST_CASE("Beats | Modulo", "[Beats]") { const auto beat1 = Beats{0.1}; const auto beat2 = Beats{0.5}; const auto beat3 = Beats{0.6}; const auto beat4 = Beats{0.}; CHECK(beat1 == beat3 % beat2); CHECK(beat4 == beat3 % beat4); } TEST_CASE("Beats | SizeInByteStream", "[Beats]") { Beats beats{0.5}; CHECK(8 == sizeInByteStream(beats)); } TEST_CASE("Beats | RoundtripByteStreamEncoding", "[Beats]") { Beats beats{0.5}; std::vector bytes(sizeInByteStream(beats)); const auto end = toNetworkByteStream(beats, begin(bytes)); const auto result = Beats::fromNetworkByteStream(begin(bytes), end); CHECK(beats == result.first); } } // namespace link } // namespace ableton link-Link-3.0.2/src/ableton/discovery/0000755000175000017500000000000013303226525020021 5ustar zmoelnigzmoelniglink-Link-3.0.2/src/ableton/discovery/v1/0000755000175000017500000000000013303226525020347 5ustar zmoelnigzmoelniglink-Link-3.0.2/src/ableton/discovery/v1/tst_Messages.cpp0000644000175000017500000000502513303226525023516 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #include #include #include namespace ableton { namespace discovery { namespace v1 { namespace { // For testing just use a single byte identifier using NodeId = uint8_t; } // anonymous namespace TEST_CASE("ParseEmptyBuffer", "[Messages]") { const std::array buffer{}; auto result = parseMessageHeader(begin(buffer), end(buffer)); CHECK(kInvalid == result.first.messageType); CHECK(begin(buffer) == result.second); } TEST_CASE("ParseTruncatedMessageHeader", "[Messages]") { const std::array truncated = { {'_', 'a', 's', 'd', 'p', '_', 'v', 1, 'x', 'y'}}; const auto result = parseMessageHeader(begin(truncated), end(truncated)); CHECK(kInvalid == result.first.messageType); const auto consumedBytes = (begin(truncated) != result.second); CHECK_FALSE(consumedBytes); } TEST_CASE("MissingProtocolHeader", "[Messages]") { // Buffer should be large enough to proceed but shouldn't match the protocol header const std::array zeros{}; auto result = parseMessageHeader(begin(zeros), end(zeros)); CHECK(kInvalid == result.first.messageType); CHECK(begin(zeros) == result.second); } TEST_CASE("RoundtripAliveNoPayload", "[Messages]") { std::array buffer{}; const uint8_t ident = 1; const auto endAlive = aliveMessage(ident, 5, makePayload(), begin(buffer)); const auto result = parseMessageHeader(begin(buffer), endAlive); CHECK(endAlive == result.second); CHECK(kAlive == result.first.messageType); CHECK(5 == result.first.ttl); CHECK(ident == result.first.ident); } } // namespace v1 } // namespace discovery } // namespace ableton link-Link-3.0.2/src/ableton/discovery/tst_PeerGateway.cpp0000644000175000017500000001271613303226525023643 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #include #include #include #include namespace ableton { namespace discovery { namespace { struct TestNodeState : std::tuple { TestNodeState(std::string ident, double tempo) : std::tuple(std::move(ident), std::move(tempo)) { } std::string ident() const { return std::get<0>(*this); } }; struct TestMessenger { template void receive(Handler handler) { receivePeerState = [handler](const PeerState& msg) { handler(msg); }; receiveByeBye = [handler](const ByeBye& msg) { handler(msg); }; } std::function&)> receivePeerState; std::function&)> receiveByeBye; }; struct TestObserver { using GatewayObserverNodeState = TestNodeState; using GatewayObserverNodeId = std::string; friend void sawPeer(TestObserver& observer, const GatewayObserverNodeState& peer) { observer.mPeersSeen.push_back(peer); } friend void peerLeft(TestObserver& observer, const GatewayObserverNodeId& id) { observer.mPeersLeft.push_back(id); } friend void peerTimedOut(TestObserver& observer, const GatewayObserverNodeId& id) { observer.mPeersTimedOut.push_back(id); } std::vector mPeersSeen; std::vector mPeersLeft; std::vector mPeersTimedOut; }; void expectPeersSeen(std::vector expected, const TestObserver& observer) { CHECK(expected == observer.mPeersSeen); } void expectPeersLeft(std::vector expected, const TestObserver& observer) { CHECK(expected == observer.mPeersLeft); } void expectPeersTimedOut(std::vector expected, const TestObserver& observer) { CHECK(expected == observer.mPeersTimedOut); } // Test peers const auto peerA = TestNodeState{"peerA", 120}; const auto peerB = TestNodeState{"peerB", 150}; } // anonymous namespace TEST_CASE("PeerGateway | NoActivity", "[PeerGateway]") { test::serial_io::Fixture io; TestObserver observer; auto listener = makePeerGateway(util::injectVal(TestMessenger{}), util::injectRef(observer), util::injectVal(io.makeIoContext())); io.advanceTime(std::chrono::seconds(10)); // Without any outside interaction but the passage of time, our // listener should not have seen any peers. CHECK(observer.mPeersSeen.empty()); CHECK(observer.mPeersLeft.empty()); CHECK(observer.mPeersTimedOut.empty()); } TEST_CASE("PeerGateway | ReceivedPeerState", "[PeerGateway]") { test::serial_io::Fixture io; TestObserver observer; TestMessenger messenger; auto listener = makePeerGateway(util::injectRef(messenger), util::injectRef(observer), util::injectVal(io.makeIoContext())); messenger.receivePeerState({peerA, 5}); io.flush(); expectPeersSeen({peerA}, observer); } TEST_CASE("PeerGateway | TwoPeersOneLeaves", "[PeerGateway]") { test::serial_io::Fixture io; TestObserver observer; TestMessenger messenger; auto listener = makePeerGateway(util::injectRef(messenger), util::injectRef(observer), util::injectVal(io.makeIoContext())); messenger.receivePeerState({peerA, 5}); messenger.receivePeerState({peerB, 5}); messenger.receiveByeBye({peerA.ident()}); io.flush(); expectPeersSeen({peerA, peerB}, observer); expectPeersLeft({peerA.ident()}, observer); } TEST_CASE("PeerGateway | TwoPeersOneTimesOut", "[PeerGateway]") { test::serial_io::Fixture io; TestObserver observer; TestMessenger messenger; auto listener = makePeerGateway(util::injectRef(messenger), util::injectRef(observer), util::injectVal(io.makeIoContext())); messenger.receivePeerState({peerA, 5}); messenger.receivePeerState({peerB, 10}); io.advanceTime(std::chrono::seconds(3)); expectPeersTimedOut({}, observer); io.advanceTime(std::chrono::seconds(4)); expectPeersTimedOut({peerA.ident()}, observer); } TEST_CASE("PeerGateway | PeerTimesOutAndIsSeenAgain", "[PeerGateway]") { test::serial_io::Fixture io; TestObserver observer; TestMessenger messenger; auto listener = makePeerGateway(util::injectRef(messenger), util::injectRef(observer), util::injectVal(io.makeIoContext())); messenger.receivePeerState({peerA, 5}); io.advanceTime(std::chrono::seconds(7)); expectPeersTimedOut({peerA.ident()}, observer); messenger.receivePeerState({peerA, 5}); io.advanceTime(std::chrono::seconds(3)); expectPeersSeen({peerA, peerA}, observer); expectPeersTimedOut({peerA.ident()}, observer); } } // namespace discovery } // namespace ableton link-Link-3.0.2/src/ableton/discovery/tst_PeerGateways.cpp0000644000175000017500000001015113303226525024015 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #include #include #include namespace ableton { namespace discovery { namespace { struct Gateway { asio::ip::address addr; }; struct NodeState { }; struct Factory { template Gateway operator()(NodeState, util::Injected, const asio::ip::address& addr) { return {addr}; } }; const asio::ip::address addr1 = asio::ip::address::from_string("192.192.192.1"); const asio::ip::address addr2 = asio::ip::address::from_string("192.192.192.2"); template void expectGatewaysAsync( Gateways& gateways, test::serial_io::Fixture& io, std::vector addrs) { using namespace std; using GatewayIt = typename Gateways::GatewayMap::iterator; bool bTested = false; gateways.withGatewaysAsync([addrs, &bTested](GatewayIt begin, const GatewayIt end) { bTested = true; REQUIRE(static_cast(distance(begin, end)) == addrs.size()); std::size_t i = 0; for (; begin != end; ++begin) { CHECK(begin->first == addrs[i++]); } }); io.flush(); CHECK(bTested); } } // anonymous namespace TEST_CASE("PeerGateways | EmptyIfNoInterfaces", "[PeerGateways]") { test::serial_io::Fixture io; auto pGateways = makePeerGateways( std::chrono::seconds(2), NodeState{}, Factory{}, util::injectVal(io.makeIoContext())); pGateways->enable(true); expectGatewaysAsync(*pGateways, io, {}); } TEST_CASE("PeerGateways | MatchesAfterInitialScan", "[PeerGateways]") { test::serial_io::Fixture io; io.setNetworkInterfaces({addr1, addr2}); auto pGateways = makePeerGateways( std::chrono::seconds(2), NodeState{}, Factory{}, util::injectVal(io.makeIoContext())); pGateways->enable(true); expectGatewaysAsync(*pGateways, io, {addr1, addr2}); } TEST_CASE("PeerGateways | GatewayAppears", "[PeerGateways]") { test::serial_io::Fixture io; io.setNetworkInterfaces({addr1}); auto pGateways = makePeerGateways( std::chrono::seconds(2), NodeState{}, Factory{}, util::injectVal(io.makeIoContext())); pGateways->enable(true); expectGatewaysAsync(*pGateways, io, {addr1}); io.setNetworkInterfaces({addr1, addr2}); io.advanceTime(std::chrono::seconds(3)); expectGatewaysAsync(*pGateways, io, {addr1, addr2}); } TEST_CASE("PeerGateways | GatewayDisappears", "[PeerGateways]") { test::serial_io::Fixture io; io.setNetworkInterfaces({addr1, addr2}); auto pGateways = makePeerGateways( std::chrono::seconds(2), NodeState{}, Factory{}, util::injectVal(io.makeIoContext())); pGateways->enable(true); expectGatewaysAsync(*pGateways, io, {addr1, addr2}); io.setNetworkInterfaces({addr1}); io.advanceTime(std::chrono::seconds(3)); expectGatewaysAsync(*pGateways, io, {addr1}); } TEST_CASE("PeerGateways | GatewayChangesAddress", "[PeerGateways]") { test::serial_io::Fixture io; io.setNetworkInterfaces({addr1}); auto pGateways = makePeerGateways( std::chrono::seconds(2), NodeState{}, Factory{}, util::injectVal(io.makeIoContext())); pGateways->enable(true); expectGatewaysAsync(*pGateways, io, {addr1}); io.setNetworkInterfaces({addr2}); io.advanceTime(std::chrono::seconds(3)); expectGatewaysAsync(*pGateways, io, {addr2}); } } // namespace discovery } // namespace ableton link-Link-3.0.2/src/ableton/discovery/tst_InterfaceScanner.cpp0000644000175000017500000000532513303226525024636 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #include #include #include namespace ableton { namespace link { namespace { struct TestCallback { template void operator()(AddrRange addrs) { addrRanges.emplace_back(begin(addrs), end(addrs)); } std::vector> addrRanges; }; // some test addresses const asio::ip::address addr1 = asio::ip::address::from_string("123.123.123.1"); const asio::ip::address addr2 = asio::ip::address::from_string("123.123.123.2"); } // anonymous namespace TEST_CASE("InterfaceScanner | NoInterfacesThenOne", "[InterfaceScanner]") { test::serial_io::Fixture io; auto callback = TestCallback{}; { auto scanner = discovery::makeInterfaceScanner(std::chrono::seconds(2), util::injectRef(callback), util::injectVal(io.makeIoContext())); scanner.enable(true); CHECK(1 == callback.addrRanges.size()); io.setNetworkInterfaces({addr1}); io.advanceTime(std::chrono::seconds(3)); } REQUIRE(2 == callback.addrRanges.size()); CHECK(0 == callback.addrRanges[0].size()); REQUIRE(1 == callback.addrRanges[1].size()); CHECK(addr1 == callback.addrRanges[1].front()); } TEST_CASE("InterfaceScanner | InterfaceGoesAway", "[InterfaceScanner]") { test::serial_io::Fixture io; io.setNetworkInterfaces({addr1}); auto callback = TestCallback{}; { auto scanner = discovery::makeInterfaceScanner(std::chrono::seconds(2), util::injectRef(callback), util::injectVal(io.makeIoContext())); scanner.enable(true); io.setNetworkInterfaces({}); io.advanceTime(std::chrono::seconds(3)); } REQUIRE(2 == callback.addrRanges.size()); REQUIRE(1 == callback.addrRanges[0].size()); CHECK(addr1 == callback.addrRanges[0].front()); CHECK(0 == callback.addrRanges[1].size()); } } // namespace link } // namespace ableton link-Link-3.0.2/src/ableton/discovery/tst_Payload.cpp0000644000175000017500000001551413303226525023016 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #include #include #include #include #include namespace ableton { namespace discovery { TEST_CASE("Payload | EmptyPayload", "[Payload]") { CHECK(0 == sizeInByteStream(makePayload())); CHECK(nullptr == toNetworkByteStream(makePayload(), nullptr)); } TEST_CASE("Payload | FixedSizeEntryPayloadSize", "[Payload]") { CHECK(12 == sizeInByteStream(makePayload(test::Foo{}))); } TEST_CASE("Payload | SingleEntryPayloadEncoding", "[Payload]") { const auto payload = makePayload(test::Foo{-1}); std::vector bytes(sizeInByteStream(payload)); const auto end = toNetworkByteStream(payload, begin(bytes)); // Should have filled the buffer with the payload CHECK(bytes.size() == static_cast(end - begin(bytes))); // Should have encoded the value 1 after the payload entry header // as an unsigned in network byte order const auto uresult = ntohl(reinterpret_cast(*(begin(bytes) + 8))); CHECK(-1 == reinterpret_cast(uresult)); } TEST_CASE("Payload | DoubleEntryPayloadSize", "[Payload]") { CHECK(48 == sizeInByteStream(makePayload(test::Foo{}, test::Bar{{0, 1, 2}}))); } TEST_CASE("Payload | DoubleEntryPayloadEncoding", "[Payload]") { const auto payload = makePayload(test::Foo{1}, test::Bar{{0, 1, 2}}); std::vector bytes(sizeInByteStream(payload)); const auto end = toNetworkByteStream(payload, begin(bytes)); // Should have filled the buffer with the payload CHECK(bytes.size() == static_cast(end - begin(bytes))); // Should have encoded the value 1 after the first payload entry header // and the number of elements of the vector as well as 0,1,2 after the // second payload entry header in network byte order CHECK(1 == ntohl(reinterpret_cast(*(begin(bytes) + 8)))); CHECK(3 == ntohl(reinterpret_cast(*(begin(bytes) + 20)))); CHECK(0 == ntohll(reinterpret_cast(*(begin(bytes) + 24)))); CHECK(1 == ntohll(reinterpret_cast(*(begin(bytes) + 32)))); CHECK(2 == ntohll(reinterpret_cast(*(begin(bytes) + 40)))); } TEST_CASE("Payload | RoundtripSingleEntry", "[Payload]") { const auto expected = test::Foo{1}; const auto payload = makePayload(expected); std::vector bytes(sizeInByteStream(payload)); const auto end = toNetworkByteStream(payload, begin(bytes)); test::Foo actual{}; parsePayload( begin(bytes), end, [&actual](const test::Foo& foo) { actual = foo; }); CHECK(expected.fooVal == actual.fooVal); } TEST_CASE("Payload | RoundtripDoubleEntry", "[Payload]") { const auto expectedFoo = test::Foo{1}; const auto expectedBar = test::Bar{{0, 1, 2}}; const auto payload = makePayload(expectedBar, expectedFoo); std::vector bytes(sizeInByteStream(payload)); const auto end = toNetworkByteStream(payload, begin(bytes)); test::Foo actualFoo{}; test::Bar actualBar{}; parsePayload(begin(bytes), end, [&actualFoo](const test::Foo& foo) { actualFoo = foo; }, [&actualBar](const test::Bar& bar) { actualBar = bar; }); CHECK(expectedFoo.fooVal == actualFoo.fooVal); CHECK(expectedBar.barVals == actualBar.barVals); } TEST_CASE("Payload | RoundtripSingleEntryWithMultipleVectors", "[Payload]") { const auto expectedFoobar = test::Foobar{{0, 1, 2}, {3, 4, 5}}; const auto payload = makePayload(expectedFoobar); std::vector bytes(sizeInByteStream(payload)); const auto end = toNetworkByteStream(payload, begin(bytes)); test::Foobar actualFoobar{}; parsePayload(begin(bytes), end, [&actualFoobar](const test::Foobar& foobar) { actualFoobar = foobar; }); CHECK(expectedFoobar.asTuple() == actualFoobar.asTuple()); } TEST_CASE("Payload | ParseSubset", "[Payload]") { // Encode two payload entries const auto expectedFoo = test::Foo{1}; const auto expectedBar = test::Bar{{0, 1, 2}}; const auto payload = makePayload(expectedFoo, expectedBar); std::vector bytes(sizeInByteStream(payload)); const auto end = toNetworkByteStream(payload, begin(bytes)); // Only decode one of them test::Bar actualBar{}; parsePayload( begin(bytes), end, [&actualBar](const test::Bar& bar) { actualBar = bar; }); CHECK(expectedBar.barVals == actualBar.barVals); } TEST_CASE("Payload | ParseTruncatedEntry", "[Payload]") { const auto expectedFoo = test::Foo{1}; const auto expectedBar = test::Bar{{0, 1, 2}}; const auto payload = makePayload(expectedBar, expectedFoo); std::vector bytes(sizeInByteStream(payload)); const auto end = toNetworkByteStream(payload, begin(bytes)); test::Foo actualFoo{}; test::Bar actualBar{}; REQUIRE_THROWS_AS(( // We truncate the buffer by one byte parsePayload(begin(bytes), end - 1, [&actualFoo](const test::Foo& foo) { actualFoo = foo; }, [&actualBar](const test::Bar& bar) { actualBar = bar; })), std::runtime_error); // We expect that bar should be properly parsed but foo not CHECK(0 == actualFoo.fooVal); CHECK(expectedBar.barVals == actualBar.barVals); } TEST_CASE("Payload | AddPayloads", "[Payload]") { // The sum of a foo payload and a bar payload should be equal in // every way to a foobar payload const auto foo = test::Foo{1}; const auto bar = test::Bar{{0, 1, 2}}; const auto fooBarPayload = makePayload(foo, bar); const auto sumPayload = makePayload(foo) + makePayload(bar); REQUIRE(sizeInByteStream(fooBarPayload) == sizeInByteStream(sumPayload)); std::vector fooBarBytes(sizeInByteStream(fooBarPayload)); std::vector sumBytes(sizeInByteStream(sumPayload)); const auto fooBarEnd = toNetworkByteStream(fooBarPayload, begin(fooBarBytes)); toNetworkByteStream(sumPayload, begin(sumBytes)); CHECK(std::equal(begin(fooBarBytes), fooBarEnd, begin(sumBytes))); } } // namespace discovery } // namespace ableton link-Link-3.0.2/src/ableton/discovery/tst_UdpMessenger.cpp0000644000175000017500000001674713303226525024037 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #include #include #include #include #include #include namespace ableton { namespace discovery { namespace { struct TestNodeState { using IdType = uint8_t; IdType ident() const { return nodeId; } friend auto toPayload(const TestNodeState& state) -> decltype(makePayload(test::Foo{})) { return makePayload(test::Foo{state.fooVal}); } template static TestNodeState fromPayload(const uint8_t id, It begin, It end) { TestNodeState state = {id, 0}; parsePayload( begin, end, [&state](const test::Foo& foo) { state.fooVal = foo.fooVal; }); return state; } IdType nodeId; int32_t fooVal; }; // Test data for a simulated peer const TestNodeState peerState = {5, 15}; const asio::ip::udp::endpoint peerEndpoint = { asio::ip::address::from_string("123.123.234.234"), 1900}; } // anonymous namespace TEST_CASE("UdpMessenger | BroadcastsStateOnConstruction", "[UdpMessenger]") { ::ableton::test::serial_io::Fixture io; auto context = io.makeIoContext(); const auto state = TestNodeState{3, 10}; auto iface = test::Interface{}; auto messenger = makeUdpMessenger(util::injectRef(iface), state, util::injectRef(context), 1, 1); REQUIRE(1 == iface.sentMessages.size()); const auto messageBuffer = iface.sentMessages[0].first; const auto sentTo = iface.sentMessages[0].second; const auto result = v1::parseMessageHeader( begin(messageBuffer), end(messageBuffer)); // Expect an Alive header CHECK(v1::kAlive == result.first.messageType); CHECK(state.nodeId == result.first.ident); CHECK(1 == result.first.ttl); // Sent to the multicast endpoint CHECK(multicastEndpoint() == sentTo); // And the payload should parse to equal to the original state const auto actualState = TestNodeState::fromPayload(state.nodeId, result.second, end(messageBuffer)); CHECK(state.fooVal == actualState.fooVal); } TEST_CASE("UdpMessenger | Heartbeat", "[UdpMessenger]") { ::ableton::test::serial_io::Fixture io; const auto state = TestNodeState{3, 10}; auto iface = test::Interface{}; auto messenger = makeUdpMessenger( util::injectRef(iface), state, util::injectVal(io.makeIoContext()), 4, 2); REQUIRE(1 == iface.sentMessages.size()); // At two seconds the messenger should have broadcasted its state again io.advanceTime(std::chrono::seconds(3)); CHECK(2 == iface.sentMessages.size()); } TEST_CASE("UdpMessenger | Response", "[UdpMessenger]") { ::ableton::test::serial_io::Fixture io; const auto state = TestNodeState{3, 10}; auto iface = test::Interface{}; auto messenger = makeUdpMessenger( util::injectRef(iface), state, util::injectVal(io.makeIoContext()), 1, 1); // Simulate state broadcast from peer, leaving out details like payload v1::MessageBuffer buffer; const auto messageEnd = v1::aliveMessage(peerState.ident(), 0, makePayload(), begin(buffer)); iface.incomingMessage(peerEndpoint, begin(buffer), messageEnd); // The messenger should have responded to the alive message with its // current state REQUIRE(2 == iface.sentMessages.size()); const auto messageBuffer = iface.sentMessages[1].first; const auto sentTo = iface.sentMessages[1].second; const auto result = v1::parseMessageHeader( begin(messageBuffer), end(messageBuffer)); CHECK(v1::kResponse == result.first.messageType); CHECK(state.nodeId == result.first.ident); CHECK(1 == result.first.ttl); CHECK(peerEndpoint == sentTo); } namespace { struct TestHandler { void operator()(PeerState state) { peerStates.push_back(std::move(state)); } void operator()(ByeBye byeBye) { byeByes.push_back(std::move(byeBye)); } std::vector> peerStates; std::vector> byeByes; }; } // namespace TEST_CASE("UdpMessenger | Receive", "[UdpMessenger]") { ::ableton::test::serial_io::Fixture io; auto iface = test::Interface{}; auto tmpMessenger = makeUdpMessenger( util::injectRef(iface), TestNodeState{}, util::injectVal(io.makeIoContext()), 1, 1); auto messenger = std::move(tmpMessenger); auto handler = TestHandler{}; messenger.receive(std::ref(handler)); v1::MessageBuffer buffer; // Receive an alive message auto end = v1::aliveMessage(peerState.nodeId, 3, toPayload(peerState), begin(buffer)); iface.incomingMessage(peerEndpoint, begin(buffer), end); // And a bye bye message end = v1::byeByeMessage(peerState.nodeId, begin(buffer)); messenger.receive(std::ref(handler)); iface.incomingMessage(peerEndpoint, begin(buffer), end); REQUIRE(1 == handler.peerStates.size()); CHECK(3 == handler.peerStates[0].ttl); CHECK(peerState.nodeId == handler.peerStates[0].peerState.nodeId); CHECK(peerState.fooVal == handler.peerStates[0].peerState.fooVal); REQUIRE(1 == handler.byeByes.size()); CHECK(peerState.nodeId == handler.byeByes[0].peerId); } TEST_CASE("UdpMessenger | SendByeByeOnDestruction", "[UdpMessenger]") { ::ableton::test::serial_io::Fixture io; auto iface = test::Interface{}; { auto messenger = makeUdpMessenger(util::injectRef(iface), TestNodeState{5, 10}, util::injectVal(io.makeIoContext()), 1, 1); } REQUIRE(2 == iface.sentMessages.size()); const auto messageBuffer = iface.sentMessages[1].first; const auto sentTo = iface.sentMessages[1].second; const auto result = v1::parseMessageHeader( begin(messageBuffer), end(messageBuffer)); CHECK(v1::kByeBye == result.first.messageType); CHECK(multicastEndpoint() == sentTo); } namespace { template struct MessengerWrapper { MessengerWrapper(Messenger messenger) : mMessenger(std::move(messenger)) { } Messenger mMessenger; }; template MessengerWrapper wrapMessenger(Messenger messenger) { return {std::move(messenger)}; } } // namespace TEST_CASE("UdpMessenger | MovingMessengerDoesntSendByeBye", "[UdpMessenger]") { ::ableton::test::serial_io::Fixture io; // The destructor for the messenger is written so that if an // instance is moved from, it won't send the bye bye message. auto iface = test::Interface{}; { auto messenger = makeUdpMessenger(util::injectRef(iface), TestNodeState{5, 10}, util::injectVal(io.makeIoContext()), 1, 1); auto wrapper = wrapMessenger(std::move(messenger)); } // We should have an initial Alive and then a single ByeBye CHECK(2 == iface.sentMessages.size()); } } // namespace discovery } // namespace ableton link-Link-3.0.2/src/CMakeLists.txt0000644000175000017500000000632113303226525017130 0ustar zmoelnigzmoelnigcmake_minimum_required(VERSION 3.0) project(LinkTest) # ____ # / ___| ___ _ _ _ __ ___ ___ ___ # \___ \ / _ \| | | | '__/ __/ _ \/ __| # ___) | (_) | |_| | | | (_| __/\__ \ # |____/ \___/ \__,_|_| \___\___||___/ # set(link_discovery_test_SOURCES ableton/discovery/tst_InterfaceScanner.cpp ableton/discovery/tst_Payload.cpp ableton/discovery/tst_PeerGateway.cpp ableton/discovery/tst_PeerGateways.cpp ableton/discovery/tst_UdpMessenger.cpp ableton/discovery/v1/tst_Messages.cpp ) set(link_core_test_SOURCES ableton/link/tst_Beats.cpp ableton/link/tst_CircularFifo.cpp ableton/link/tst_ClientSessionTimelines.cpp ableton/link/tst_Controller.cpp ableton/link/tst_HostTimeFilter.cpp ableton/link/tst_Kalman.cpp ableton/link/tst_LinearRegression.cpp ableton/link/tst_Measurement.cpp ableton/link/tst_Peers.cpp ableton/link/tst_Phase.cpp ableton/link/tst_PingResponder.cpp ableton/link/tst_StartStopState.cpp ableton/link/tst_Tempo.cpp ableton/link/tst_Timeline.cpp ) set(link_test_SOURCES ableton/test/catch/CatchMain.cpp ableton/test/serial_io/SchedulerTree.cpp ) # ____ ____ # / ___| ___ _ _ _ __ ___ ___ / ___|_ __ ___ _ _ _ __ ___ # \___ \ / _ \| | | | '__/ __/ _ \ | | _| '__/ _ \| | | | '_ \/ __| # ___) | (_) | |_| | | | (_| __/ | |_| | | | (_) | |_| | |_) \__ \ # |____/ \___/ \__,_|_| \___\___| \____|_| \___/ \__,_| .__/|___/ # |_| source_group("Link" FILES ${link_core_HEADERS} ) source_group("Discovery" FILES ${link_discovery_HEADERS} ) source_group("Platform" FILES ${link_platform_HEADERS} ) source_group("Util" FILES ${link_util_HEADERS} ) source_group("Test Utils" FILES ${link_test_HEADERS} ${link_test_SOURCES} ) # _____ _ # |_ _|_ _ _ __ __ _ ___| |_ ___ # | |/ _` | '__/ _` |/ _ \ __|/ __| # | | (_| | | | (_| | __/ |_ \__ \ # |_|\__,_|_| \__, |\___|\__||___/ # |___/ function(configure_link_test_executable target) if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") target_link_libraries(${target} atomic pthread) endif() target_link_libraries(${target} Catch::Catch Ableton::Link) endfunction() # For the LinkCore test suite, we add header dependencies individually so that # the source groups above are shown correctly to the project. However, most # other projects integrating Link won't need this functionality, and they should # should just depend on ${link_HEADERS}. add_executable(LinkCoreTest ${link_core_HEADERS} ${link_discovery_HEADERS} ${link_platform_HEADERS} ${link_util_HEADERS} ${link_test_HEADERS} ${link_core_test_SOURCES} ${link_test_SOURCES} ) configure_link_test_executable(LinkCoreTest) # For the LinkDiscovery test suite, we only add dependencies on the headers # necessary to compile these tests, since the Discovery feature should not have # dependencies on the Link core code. Normal targets should always use # ${link_HEADERS} for their dependencies. add_executable(LinkDiscoveryTest ${link_discovery_HEADERS} ${link_platform_HEADERS} ${link_util_HEADERS} ${link_test_HEADERS} ${link_discovery_test_SOURCES} ${link_test_SOURCES} ) configure_link_test_executable(LinkDiscoveryTest) link-Link-3.0.2/examples/0000755000175000017500000000000013303226525015415 5ustar zmoelnigzmoelniglink-Link-3.0.2/examples/linkaudio/0000755000175000017500000000000013303226525017374 5ustar zmoelnigzmoelniglink-Link-3.0.2/examples/linkaudio/AudioPlatform_Jack.cpp0000644000175000017500000001215513303226525023602 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #include "AudioPlatform_Jack.hpp" #include #include #include namespace ableton { namespace linkaudio { AudioPlatform::AudioPlatform(Link& link) : mEngine(link) , mSampleTime(0.) , mpJackClient(NULL) , mpJackPorts(NULL) { initialize(); start(); } AudioPlatform::~AudioPlatform() { stop(); uninitialize(); } int AudioPlatform::audioCallback(jack_nframes_t nframes, void* pvUserData) { AudioPlatform* pAudioPlatform = static_cast(pvUserData); return pAudioPlatform->audioCallback(nframes); } int AudioPlatform::audioCallback(jack_nframes_t nframes) { using namespace std::chrono; AudioEngine& engine = mEngine; const auto hostTime = mHostTimeFilter.sampleTimeToHostTime(mSampleTime); mSampleTime += nframes; const auto bufferBeginAtOutput = hostTime + engine.mOutputLatency; engine.audioCallback(bufferBeginAtOutput, nframes); for (int k = 0; k < 2; ++k) { float* buffer = static_cast(jack_port_get_buffer(mpJackPorts[k], nframes)); for (unsigned long i = 0; i < nframes; ++i) buffer[i] = static_cast(engine.mBuffer[i]); } return 0; } void AudioPlatform::initialize() { jack_status_t status = JackFailure; mpJackClient = jack_client_open("LinkHut", JackNullOption, &status); if (mpJackClient == NULL) { std::cerr << "Could not initialize Audio Engine. "; std::cerr << "JACK: " << std::endl; if (status & JackFailure) std::cerr << "Overall operation failed." << std::endl; if (status & JackInvalidOption) std::cerr << "Invalid or unsupported option." << std::endl; if (status & JackNameNotUnique) std::cerr << "Client name not unique." << std::endl; if (status & JackServerStarted) std::cerr << "Server is started." << std::endl; if (status & JackServerFailed) std::cerr << "Unable to connect to server." << std::endl; if (status & JackServerError) std::cerr << "Server communication error." << std::endl; if (status & JackNoSuchClient) std::cerr << "Client does not exist." << std::endl; if (status & JackLoadFailure) std::cerr << "Unable to load internal client." << std::endl; if (status & JackInitFailure) std::cerr << "Unable to initialize client." << std::endl; if (status & JackShmFailure) std::cerr << "Unable to access shared memory." << std::endl; if (status & JackVersionError) std::cerr << "Client protocol version mismatch." << std::endl; std::cerr << std::endl; std::terminate(); }; mpJackPorts = new jack_port_t*[2]; for (int k = 0; k < 2; ++k) { const std::string port_name = "out_" + std::to_string(k + 1); mpJackPorts[k] = jack_port_register( mpJackClient, port_name.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); if (mpJackPorts[k] == NULL) { std::cerr << "Could not get Audio Device. " << std::endl; jack_client_close(mpJackClient); std::terminate(); } } jack_set_process_callback(mpJackClient, AudioPlatform::audioCallback, this); const double bufferSize = jack_get_buffer_size(mpJackClient); const double sampleRate = jack_get_sample_rate(mpJackClient); mEngine.setBufferSize(static_cast(bufferSize)); mEngine.setSampleRate(sampleRate); mEngine.mOutputLatency = std::chrono::microseconds(llround(1.0e6 * bufferSize / sampleRate)); } void AudioPlatform::uninitialize() { for (int k = 0; k < 2; ++k) { jack_port_unregister(mpJackClient, mpJackPorts[k]); mpJackPorts[k] = NULL; } delete[] mpJackPorts; mpJackPorts = NULL; jack_client_close(mpJackClient); mpJackClient = NULL; } void AudioPlatform::start() { jack_activate(mpJackClient); const char** playback_ports = jack_get_ports( mpJackClient, 0, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput | JackPortIsPhysical); if (playback_ports) { const std::string client_name = jack_get_client_name(mpJackClient); for (int k = 0; k < 2; ++k) { const std::string port_name = "out_" + std::to_string(k + 1); const std::string client_port = client_name + ':' + port_name; jack_connect(mpJackClient, client_port.c_str(), playback_ports[k]); } jack_free(playback_ports); } } void AudioPlatform::stop() { jack_deactivate(mpJackClient); } } // namespace linkaudio } // namespace ableton link-Link-3.0.2/examples/linkaudio/AudioPlatform_CoreAudio.hpp0000644000175000017500000000262513303226525024612 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include "AudioEngine.hpp" #include namespace ableton { namespace linkaudio { class AudioPlatform { public: AudioPlatform(Link& link); ~AudioPlatform(); AudioEngine mEngine; private: static OSStatus audioCallback(void* inRefCon, AudioUnitRenderActionFlags*, const AudioTimeStamp* inTimeStamp, UInt32, UInt32 inNumberFrames, AudioBufferList* ioData); void initialize(); void uninitialize(); void start(); void stop(); AudioUnit mIoUnit; }; } // namespace linkaudio } // namespace ableton link-Link-3.0.2/examples/linkaudio/AudioPlatform_Wasapi.cpp0000644000175000017500000002227513303226525024162 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #include "AudioPlatform_Wasapi.hpp" #include #include // WARNING: This file provides an audio driver for Windows using WASAPI. This driver is // considered experimental and has problems with low-latency playback. Please consider // using the ASIO driver instead. namespace ableton { namespace linkaudio { // GUID identifiers used to when looking up COM enumerators and devices static const IID kMMDeviceEnumeratorId = __uuidof(MMDeviceEnumerator); static const IID kIMMDeviceEnumeratorId = __uuidof(IMMDeviceEnumerator); static const IID kAudioClientId = __uuidof(IAudioClient); static const IID kAudioRenderClientId = __uuidof(IAudioRenderClient); // Controls how large the driver's ring buffer will be, expressed in terms of // 100-nanosecond units. This value also influences the overall driver latency. static const REFERENCE_TIME kBufferDuration = 1000000; // How long to block the runloop while waiting for an event callback. static const DWORD kWaitTimeoutInMs = 2000; void fatalError(HRESULT result, LPCTSTR context) { if (result > 0) { _com_error error(result); LPCTSTR errorMessage = error.ErrorMessage(); std::cerr << context << ": " << errorMessage << std::endl; } else { std::cerr << context << std::endl; } std::terminate(); } DWORD renderAudioRunloop(LPVOID lpParam) { AudioPlatform* platform = static_cast(lpParam); return platform->audioRunloop(); } AudioPlatform::AudioPlatform(Link& link) : mEngine(link) , mDevice(nullptr) , mAudioClient(nullptr) , mRenderClient(nullptr) , mStreamFormat(nullptr) , mEventHandle(nullptr) , mAudioThreadHandle(nullptr) , mIsRunning(false) , mSampleTime(0) { initialize(); mEngine.setBufferSize(bufferSize()); mEngine.setSampleRate(mStreamFormat->nSamplesPerSec); start(); } AudioPlatform::~AudioPlatform() { // WARNING: Here be dragons! // The WASAPI driver is not thread-safe, and crashes may occur when shutting down due // to these fields being concurrently accessed in the audio thread. Introducing a mutex // in the audio thread is not an appropriate solution to fix this race condition; a more // robust solution needs to be considered instead. if (mDevice != nullptr) { mDevice->Release(); } if (mAudioClient != nullptr) { mAudioClient->Release(); } if (mRenderClient != nullptr) { mRenderClient->Release(); } CoTaskMemFree(mStreamFormat); } UINT32 AudioPlatform::bufferSize() { UINT32 bufferSize; HRESULT result = mAudioClient->GetBufferSize(&bufferSize); if (FAILED(result)) { fatalError(result, "Could not get buffer size"); return 0; // not reached } return bufferSize; } void AudioPlatform::initialize() { HRESULT result = CoInitialize(nullptr); if (FAILED(result)) { fatalError(result, "Could not initialize COM library"); } IMMDeviceEnumerator* enumerator = nullptr; result = CoCreateInstance(kMMDeviceEnumeratorId, nullptr, CLSCTX_ALL, kIMMDeviceEnumeratorId, (void**)&enumerator); if (FAILED(result)) { fatalError(result, "Could not create device instance"); } result = enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &(mDevice)); if (FAILED(result)) { fatalError(result, "Could not get default audio endpoint"); } else { enumerator->Release(); enumerator = nullptr; } result = mDevice->Activate(kAudioClientId, CLSCTX_ALL, nullptr, (void**)&(mAudioClient)); if (FAILED(result)) { fatalError(result, "Could not activate audio device"); } result = mAudioClient->GetMixFormat(&(mStreamFormat)); if (FAILED(result)) { fatalError(result, "Could not get mix format"); } if (mStreamFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { WAVEFORMATEXTENSIBLE* streamFormatEx = reinterpret_cast(mStreamFormat); if (streamFormatEx->SubFormat != KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) { fatalError(0, "Sorry, only IEEE floating point streams are supported"); } } else { fatalError(0, "Sorry, only extensible wave streams are supported"); } result = mAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, kBufferDuration, 0, mStreamFormat, nullptr); if (FAILED(result)) { fatalError(result, "Could not initialize audio device"); } mEventHandle = CreateEvent(nullptr, false, false, nullptr); if (mEventHandle == nullptr) { fatalError(result, "Could not create event handle"); } result = mAudioClient->GetService(kAudioRenderClientId, (void**)&(mRenderClient)); if (FAILED(result)) { fatalError(result, "Could not get audio render service"); } mIsRunning = true; LPTHREAD_START_ROUTINE threadEntryPoint = reinterpret_cast(renderAudioRunloop); mAudioThreadHandle = CreateThread(nullptr, 0, threadEntryPoint, this, 0, nullptr); if (mAudioThreadHandle == nullptr) { fatalError(GetLastError(), "Could not create audio thread"); } } void AudioPlatform::start() { UINT32 bufSize = bufferSize(); BYTE* buffer; HRESULT result = mRenderClient->GetBuffer(bufSize, &buffer); if (FAILED(result)) { fatalError(result, "Could not get render client buffer (in start audio engine)"); } result = mRenderClient->ReleaseBuffer(bufSize, 0); if (FAILED(result)) { fatalError(result, "Could not release buffer"); } result = mAudioClient->SetEventHandle(mEventHandle); if (FAILED(result)) { fatalError(result, "Could not set event handle to audio client"); } REFERENCE_TIME latency; result = mAudioClient->GetStreamLatency(&latency); if (FAILED(result)) { fatalError(result, "Could not get stream latency"); } result = mAudioClient->Start(); if (FAILED(result)) { fatalError(result, "Could not start audio client"); } } DWORD AudioPlatform::audioRunloop() { while (mIsRunning) { DWORD wait = WaitForSingleObject(mEventHandle, kWaitTimeoutInMs); if (wait != WAIT_OBJECT_0) { mIsRunning = false; mAudioClient->Stop(); return wait; } // Get the amount of padding, which basically is the amount of data in the driver's // ring buffer that is filled with unread data. Thus, subtracting this amount from // the buffer size gives the effective buffer size, which is the amount of frames // that can be safely written to the driver. UINT32 paddingFrames; HRESULT result = mAudioClient->GetCurrentPadding(&paddingFrames); if (FAILED(result)) { fatalError(result, "Could not get number of padding frames"); } const UINT32 numSamples = bufferSize() - paddingFrames; BYTE* buffer; result = mRenderClient->GetBuffer(numSamples, &buffer); if (FAILED(result)) { fatalError(result, "Could not get render client buffer (in callback)"); } const double sampleRate = static_cast(mStreamFormat->nSamplesPerSec); using namespace std::chrono; const auto bufferDuration = duration_cast(duration{numSamples / sampleRate}); const auto hostTime = mHostTimeFilter.sampleTimeToHostTime(mSampleTime); mSampleTime += numSamples; const auto bufferBeginAtOutput = hostTime + mEngine.mOutputLatency; mEngine.audioCallback(bufferBeginAtOutput, numSamples); float* floatBuffer = reinterpret_cast(buffer); for (WORD i = 0; i < numSamples; ++i) { if (i >= mEngine.mBuffer.size()) { break; } for (WORD j = 0; j < mStreamFormat->nChannels; ++j) { floatBuffer[j + (i * mStreamFormat->nChannels)] = static_cast(mEngine.mBuffer[i]); } } // Write the buffer to the audio driver and subsequently free the buffer memory result = mRenderClient->ReleaseBuffer(numSamples, 0); if (FAILED(result)) { fatalError(result, "Error rendering data"); } } // end of runloop mIsRunning = false; return 0; } // void fillBuffer(MetronomeSynth& metronome, // const UINT32 startFrame, // const UINT32 numSamples, // const UINT32 numChannels, // BYTE* buffer) //{ // float* floatBuffer = reinterpret_cast(buffer); // UINT32 frame = startFrame; // while (frame < numSamples * numChannels) // { // const float sample = static_cast(metronome.getSample()); // for (UINT32 channel = 0; channel < numChannels; ++channel) // { // floatBuffer[frame++] = sample; // } // } //} } // namespace linkaudio } // namespace ableton link-Link-3.0.2/examples/linkaudio/AudioPlatform.hpp0000644000175000017500000000255713303226525022664 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #if defined(LINKHUT_AUDIO_PLATFORM_ASIO) #include "AudioPlatform_Asio.hpp" #endif #if defined(LINKHUT_AUDIO_PLATFORM_COREAUDIO) #include "AudioPlatform_CoreAudio.hpp" #endif #if defined(LINKHUT_AUDIO_PLATFORM_DUMMY) #include "AudioPlatform_Dummy.hpp" #endif #if defined(LINKHUT_AUDIO_PLATFORM_JACK) #include "AudioPlatform_Jack.hpp" #endif #if defined(LINKHUT_AUDIO_PLATFORM_PORTAUDIO) #include "AudioPlatform_Portaudio.hpp" #endif #if defined(LINKHUT_AUDIO_PLATFORM_WASAPI) #include "AudioPlatform_Wasapi.hpp" #endif link-Link-3.0.2/examples/linkaudio/AudioPlatform_Portaudio.hpp0000644000175000017500000000310613303226525024701 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include "AudioEngine.hpp" #include #include #include namespace ableton { namespace linkaudio { class AudioPlatform { public: AudioPlatform(Link& link); ~AudioPlatform(); AudioEngine mEngine; private: static int audioCallback(const void* inputBuffer, void* outputBuffer, unsigned long inNumFrames, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void* userData); void initialize(); void uninitialize(); void start(); void stop(); link::HostTimeFilter mHostTimeFilter; double mSampleTime; PaStream* pStream; }; } // namespace linkaudio } // namespace ableton link-Link-3.0.2/examples/linkaudio/AudioEngine.cpp0000644000175000017500000001525513303226525022277 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #include "AudioEngine.hpp" // Make sure to define this before is included for Windows #ifdef LINK_PLATFORM_WINDOWS #define _USE_MATH_DEFINES #endif #include namespace ableton { namespace linkaudio { AudioEngine::AudioEngine(Link& link) : mLink(link) , mSampleRate(44100.) , mOutputLatency(0) , mSharedEngineData({0., false, false, 4., false}) , mLockfreeEngineData(mSharedEngineData) , mTimeAtLastClick{} , mIsPlaying(false) { } void AudioEngine::startPlaying() { std::lock_guard lock(mEngineDataGuard); mSharedEngineData.requestStart = true; } void AudioEngine::stopPlaying() { std::lock_guard lock(mEngineDataGuard); mSharedEngineData.requestStop = true; } bool AudioEngine::isPlaying() const { return mLink.captureAppSessionState().isPlaying(); } double AudioEngine::beatTime() const { const auto sessionState = mLink.captureAppSessionState(); return sessionState.beatAtTime(mLink.clock().micros(), mSharedEngineData.quantum); } void AudioEngine::setTempo(double tempo) { std::lock_guard lock(mEngineDataGuard); mSharedEngineData.requestedTempo = tempo; } double AudioEngine::quantum() const { return mSharedEngineData.quantum; } void AudioEngine::setQuantum(double quantum) { std::lock_guard lock(mEngineDataGuard); mSharedEngineData.quantum = quantum; } bool AudioEngine::isStartStopSyncEnabled() const { return mLink.isStartStopSyncEnabled(); } void AudioEngine::setStartStopSyncEnabled(const bool enabled) { mLink.enableStartStopSync(enabled); } void AudioEngine::setBufferSize(std::size_t size) { mBuffer = std::vector(size, 0.); } void AudioEngine::setSampleRate(double sampleRate) { mSampleRate = sampleRate; } AudioEngine::EngineData AudioEngine::pullEngineData() { auto engineData = EngineData{}; if (mEngineDataGuard.try_lock()) { engineData.requestedTempo = mSharedEngineData.requestedTempo; mSharedEngineData.requestedTempo = 0; engineData.requestStart = mSharedEngineData.requestStart; mSharedEngineData.requestStart = false; engineData.requestStop = mSharedEngineData.requestStop; mSharedEngineData.requestStop = false; mLockfreeEngineData.quantum = mSharedEngineData.quantum; mLockfreeEngineData.startStopSyncOn = mSharedEngineData.startStopSyncOn; mEngineDataGuard.unlock(); } engineData.quantum = mLockfreeEngineData.quantum; return engineData; } void AudioEngine::renderMetronomeIntoBuffer(const Link::SessionState sessionState, const double quantum, const std::chrono::microseconds beginHostTime, const std::size_t numSamples) { using namespace std::chrono; // Metronome frequencies static const double highTone = 1567.98; static const double lowTone = 1108.73; // 100ms click duration static const auto clickDuration = duration{0.1}; // The number of microseconds that elapse between samples const auto microsPerSample = 1e6 / mSampleRate; for (std::size_t i = 0; i < numSamples; ++i) { double amplitude = 0.; // Compute the host time for this sample and the last. const auto hostTime = beginHostTime + microseconds(llround(i * microsPerSample)); const auto lastSampleHostTime = hostTime - microseconds(llround(microsPerSample)); // Only make sound for positive beat magnitudes. Negative beat // magnitudes are count-in beats. if (sessionState.beatAtTime(hostTime, quantum) >= 0.) { // If the phase wraps around between the last sample and the // current one with respect to a 1 beat quantum, then a click // should occur. if (sessionState.phaseAtTime(hostTime, 1) < sessionState.phaseAtTime(lastSampleHostTime, 1)) { mTimeAtLastClick = hostTime; } const auto secondsAfterClick = duration_cast>(hostTime - mTimeAtLastClick); // If we're within the click duration of the last beat, render // the click tone into this sample if (secondsAfterClick < clickDuration) { // If the phase of the last beat with respect to the current // quantum was zero, then it was at a quantum boundary and we // want to use the high tone. For other beats within the // quantum, use the low tone. const auto freq = floor(sessionState.phaseAtTime(hostTime, quantum)) == 0 ? highTone : lowTone; // Simple cosine synth amplitude = cos(2 * M_PI * secondsAfterClick.count() * freq) * (1 - sin(5 * M_PI * secondsAfterClick.count())); } } mBuffer[i] = amplitude; } } void AudioEngine::audioCallback( const std::chrono::microseconds hostTime, const std::size_t numSamples) { const auto engineData = pullEngineData(); auto sessionState = mLink.captureAudioSessionState(); // Clear the buffer std::fill(mBuffer.begin(), mBuffer.end(), 0); if (engineData.requestStart) { sessionState.setIsPlaying(true, hostTime); } if (engineData.requestStop) { sessionState.setIsPlaying(false, hostTime); } if (!mIsPlaying && sessionState.isPlaying()) { // Reset the timeline so that beat 0 corresponds to the time when transport starts sessionState.requestBeatAtStartPlayingTime(0, engineData.quantum); mIsPlaying = true; } else if (mIsPlaying && !sessionState.isPlaying()) { mIsPlaying = false; } if (engineData.requestedTempo > 0) { // Set the newly requested tempo from the beginning of this buffer sessionState.setTempo(engineData.requestedTempo, hostTime); } // Timeline modifications are complete, commit the results mLink.commitAudioSessionState(sessionState); if (mIsPlaying) { // As long as the engine is playing, generate metronome clicks in // the buffer at the appropriate beats. renderMetronomeIntoBuffer(sessionState, engineData.quantum, hostTime, numSamples); } } } // namespace linkaudio } // namespace ableton link-Link-3.0.2/examples/linkaudio/AudioPlatform_Asio.hpp0000644000175000017500000000561613303226525023636 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include "AudioEngine.hpp" #include #include #include "asiosys.h" // Should be included before asio.h #include "asio.h" #include "asiodrivers.h" // External functions in the ASIO SDK which aren't declared in the SDK headers extern AsioDrivers* asioDrivers; bool loadAsioDriver(char* name); namespace ableton { namespace linkaudio { #ifndef LINK_ASIO_DRIVER_NAME #define LINK_ASIO_DRIVER_NAME "ASIO4ALL v2" #endif #ifndef LINK_ASIO_INPUT_CHANNELS #define LINK_ASIO_INPUT_CHANNELS 0 #endif #ifndef LINK_ASIO_OUTPUT_CHANNELS #define LINK_ASIO_OUTPUT_CHANNELS 2 #endif struct DriverInfo { ASIODriverInfo driverInfo; long inputChannels; long outputChannels; long preferredSize; ASIOSampleRate sampleRate; bool outputReady; long numBuffers; ASIOBufferInfo bufferInfos[LINK_ASIO_INPUT_CHANNELS + LINK_ASIO_OUTPUT_CHANNELS]; ASIOChannelInfo channelInfos[LINK_ASIO_INPUT_CHANNELS + LINK_ASIO_OUTPUT_CHANNELS]; }; // Helper functions // Convenience function to print out an ASIO error code along with the function called void fatalError(const ASIOError result, const std::string& function); double asioSamplesToDouble(const ASIOSamples& samples); ASIOTime* bufferSwitchTimeInfo(ASIOTime* timeInfo, long index, ASIOBool); void bufferSwitch(long index, ASIOBool processNow); class AudioPlatform { public: AudioPlatform(Link& link); ~AudioPlatform(); void audioCallback(ASIOTime* timeInfo, long index); AudioEngine mEngine; // Unfortunately, the ASIO SDK does not allow passing void* user data to callback // functions, so we need to keep a singleton instance of the audio engine static AudioPlatform* singleton(); static void setSingleton(AudioPlatform* platform); private: void createAsioBuffers(); void initializeDriverInfo(); void initialize(); void start(); void stop(); DriverInfo mDriverInfo; ASIOCallbacks mAsioCallbacks; link::HostTimeFilter mHostTimeFilter; static AudioPlatform* _singleton; }; } // namespace linkaudio } // namespace ableton link-Link-3.0.2/examples/linkaudio/AudioPlatform_Portaudio.cpp0000644000175000017500000000766013303226525024705 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #include "AudioPlatform_Portaudio.hpp" #include #include namespace ableton { namespace linkaudio { AudioPlatform::AudioPlatform(Link& link) : mEngine(link) , mSampleTime(0.) { mEngine.setSampleRate(44100.); mEngine.setBufferSize(512); initialize(); start(); } AudioPlatform::~AudioPlatform() { stop(); uninitialize(); } int AudioPlatform::audioCallback(const void* /*inputBuffer*/, void* outputBuffer, unsigned long inNumFrames, const PaStreamCallbackTimeInfo* /*timeInfo*/, PaStreamCallbackFlags /*statusFlags*/, void* userData) { using namespace std::chrono; float* buffer = static_cast(outputBuffer); AudioPlatform& platform = *static_cast(userData); AudioEngine& engine = platform.mEngine; const auto hostTime = platform.mHostTimeFilter.sampleTimeToHostTime(platform.mSampleTime); platform.mSampleTime += inNumFrames; const auto bufferBeginAtOutput = hostTime + engine.mOutputLatency; engine.audioCallback(bufferBeginAtOutput, inNumFrames); for (unsigned long i = 0; i < inNumFrames; ++i) { buffer[i * 2] = static_cast(engine.mBuffer[i]); buffer[i * 2 + 1] = static_cast(engine.mBuffer[i]); } return paContinue; } void AudioPlatform::initialize() { PaError result = Pa_Initialize(); if (result) { std::cerr << "Could not initialize Audio Engine. " << result << std::endl; std::terminate(); }; PaStreamParameters outputParameters; outputParameters.device = Pa_GetDefaultOutputDevice(); if (outputParameters.device == paNoDevice) { std::cerr << "Could not get Audio Device. " << std::endl; std::terminate(); } outputParameters.channelCount = 2; outputParameters.sampleFormat = paFloat32; outputParameters.suggestedLatency = Pa_GetDeviceInfo(outputParameters.device)->defaultLowOutputLatency; outputParameters.hostApiSpecificStreamInfo = nullptr; mEngine.mOutputLatency = std::chrono::microseconds(llround(outputParameters.suggestedLatency * 1.0e6)); result = Pa_OpenStream(&pStream, nullptr, &outputParameters, mEngine.mSampleRate, mEngine.mBuffer.size(), paClipOff, &audioCallback, this); if (result) { std::cerr << "Could not open stream. " << result << std::endl; std::terminate(); } if (!pStream) { std::cerr << "No valid audio stream." << std::endl; std::terminate(); } } void AudioPlatform::uninitialize() { PaError result = Pa_CloseStream(pStream); if (result) { std::cerr << "Could not close Audio Stream. " << result << std::endl; } Pa_Terminate(); if (!pStream) { std::cerr << "No valid audio stream." << std::endl; std::terminate(); } } void AudioPlatform::start() { PaError result = Pa_StartStream(pStream); if (result) { std::cerr << "Could not start Audio Stream. " << result << std::endl; } } void AudioPlatform::stop() { if (pStream == nullptr) { return; } PaError result = Pa_StopStream(pStream); if (result) { std::cerr << "Could not stop Audio Stream. " << result << std::endl; std::terminate(); } } } // namespace linkaudio } // namespace ableton link-Link-3.0.2/examples/linkaudio/AudioEngine.hpp0000644000175000017500000000432113303226525022274 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once // Make sure to define this before is included for Windows #define _USE_MATH_DEFINES #include #include namespace ableton { namespace linkaudio { class AudioEngine { public: AudioEngine(Link& link); void startPlaying(); void stopPlaying(); bool isPlaying() const; double beatTime() const; void setTempo(double tempo); double quantum() const; void setQuantum(double quantum); bool isStartStopSyncEnabled() const; void setStartStopSyncEnabled(bool enabled); private: struct EngineData { double requestedTempo; bool requestStart; bool requestStop; double quantum; bool startStopSyncOn; }; void setBufferSize(std::size_t size); void setSampleRate(double sampleRate); EngineData pullEngineData(); void renderMetronomeIntoBuffer(Link::SessionState sessionState, double quantum, std::chrono::microseconds beginHostTime, std::size_t numSamples); void audioCallback(const std::chrono::microseconds hostTime, std::size_t numSamples); Link& mLink; double mSampleRate; std::chrono::microseconds mOutputLatency; std::vector mBuffer; EngineData mSharedEngineData; EngineData mLockfreeEngineData; std::chrono::microseconds mTimeAtLastClick; bool mIsPlaying; std::mutex mEngineDataGuard; friend class AudioPlatform; }; } // namespace linkaudio } // namespace ableton link-Link-3.0.2/examples/linkaudio/AudioPlatform_Jack.hpp0000644000175000017500000000301313303226525023600 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include "AudioEngine.hpp" #include #include #include namespace ableton { namespace linkaudio { class AudioPlatform { public: AudioPlatform(Link& link); ~AudioPlatform(); AudioEngine mEngine; private: static int audioCallback(jack_nframes_t nframes, void* pvUserData); int audioCallback(jack_nframes_t nframes); void initialize(); void uninitialize(); void start(); void stop(); link::HostTimeFilter mHostTimeFilter; double mSampleTime; jack_client_t* mpJackClient; jack_port_t** mpJackPorts; }; } // namespace linkaudio } // namespace ableton link-Link-3.0.2/examples/linkaudio/AudioPlatform_Dummy.hpp0000644000175000017500000000211513303226525024025 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include "AudioEngine.hpp" namespace ableton { namespace linkaudio { class AudioPlatform { public: AudioPlatform(Link& link) : mEngine(link) { } AudioEngine mEngine; }; } // namespace linkaudio } // namespace ableton link-Link-3.0.2/examples/linkaudio/AudioPlatform_Asio.cpp0000644000175000017500000002155113303226525023625 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #include "AudioPlatform_Asio.hpp" namespace ableton { namespace linkaudio { void fatalError(const ASIOError result, const std::string& function) { std::cerr << "Call to ASIO function " << function << " failed"; if (result != ASE_OK) { std::cerr << " (ASIO error code " << result << ")"; } std::cerr << std::endl; std::terminate(); } double asioSamplesToDouble(const ASIOSamples& samples) { return samples.lo + samples.hi * std::pow(2, 32); } // ASIO processing callbacks ASIOTime* bufferSwitchTimeInfo(ASIOTime* timeInfo, long index, ASIOBool) { AudioPlatform* platform = AudioPlatform::singleton(); if (platform) { platform->audioCallback(timeInfo, index); } return nullptr; } void bufferSwitch(long index, ASIOBool processNow) { ASIOTime timeInfo{}; ASIOError result = ASIOGetSamplePosition( &timeInfo.timeInfo.samplePosition, &timeInfo.timeInfo.systemTime); if (result != ASE_OK) { std::cerr << "ASIOGetSamplePosition failed with ASIO error: " << result << std::endl; } else { timeInfo.timeInfo.flags = kSystemTimeValid | kSamplePositionValid; } bufferSwitchTimeInfo(&timeInfo, index, processNow); } AudioPlatform* AudioPlatform::_singleton = nullptr; AudioPlatform* AudioPlatform::singleton() { return _singleton; } void AudioPlatform::setSingleton(AudioPlatform* platform) { _singleton = platform; } AudioPlatform::AudioPlatform(Link& link) : mEngine(link) { initialize(); mEngine.setBufferSize(mDriverInfo.preferredSize); mEngine.setSampleRate(mDriverInfo.sampleRate); setSingleton(this); start(); } AudioPlatform::~AudioPlatform() { stop(); ASIODisposeBuffers(); ASIOExit(); if (asioDrivers != nullptr) { asioDrivers->removeCurrentDriver(); } setSingleton(nullptr); } void AudioPlatform::audioCallback(ASIOTime* timeInfo, long index) { auto hostTime = std::chrono::microseconds(0); if (timeInfo->timeInfo.flags & kSystemTimeValid) { hostTime = mHostTimeFilter.sampleTimeToHostTime( asioSamplesToDouble(timeInfo->timeInfo.samplePosition)); } const auto bufferBeginAtOutput = hostTime + mEngine.mOutputLatency; ASIOBufferInfo* bufferInfos = mDriverInfo.bufferInfos; const long numSamples = mDriverInfo.preferredSize; const long numChannels = mDriverInfo.numBuffers; const double maxAmp = std::numeric_limits::max(); mEngine.audioCallback(bufferBeginAtOutput, numSamples); for (long i = 0; i < numSamples; ++i) { for (long j = 0; j < numChannels; ++j) { int* buffer = static_cast(bufferInfos[j].buffers[index]); buffer[i] = static_cast(mEngine.mBuffer[i] * maxAmp); } } if (mDriverInfo.outputReady) { ASIOOutputReady(); } } void AudioPlatform::createAsioBuffers() { DriverInfo& driverInfo = mDriverInfo; ASIOBufferInfo* bufferInfo = driverInfo.bufferInfos; driverInfo.numBuffers = 0; // Prepare input channels. Though this is not necessarily required, the opened input // channels will not work. int numInputBuffers; if (driverInfo.inputChannels > LINK_ASIO_INPUT_CHANNELS) { numInputBuffers = LINK_ASIO_INPUT_CHANNELS; } else { numInputBuffers = driverInfo.inputChannels; } for (long i = 0; i < numInputBuffers; ++i, ++bufferInfo) { bufferInfo->isInput = ASIOTrue; bufferInfo->channelNum = i; bufferInfo->buffers[0] = bufferInfo->buffers[1] = nullptr; } // Prepare output channels int numOutputBuffers; if (driverInfo.outputChannels > LINK_ASIO_OUTPUT_CHANNELS) { numOutputBuffers = LINK_ASIO_OUTPUT_CHANNELS; } else { numOutputBuffers = driverInfo.outputChannels; } for (long i = 0; i < numOutputBuffers; i++, bufferInfo++) { bufferInfo->isInput = ASIOFalse; bufferInfo->channelNum = i; bufferInfo->buffers[0] = bufferInfo->buffers[1] = nullptr; } driverInfo.numBuffers = numInputBuffers + numOutputBuffers; ASIOError result = ASIOCreateBuffers(driverInfo.bufferInfos, driverInfo.numBuffers, driverInfo.preferredSize, &(mAsioCallbacks)); if (result != ASE_OK) { fatalError(result, "ASIOCreateBuffers"); } // Now get all buffer details, sample word length, name, word clock group and latency for (long i = 0; i < driverInfo.numBuffers; ++i) { driverInfo.channelInfos[i].channel = driverInfo.bufferInfos[i].channelNum; driverInfo.channelInfos[i].isInput = driverInfo.bufferInfos[i].isInput; result = ASIOGetChannelInfo(&driverInfo.channelInfos[i]); if (result != ASE_OK) { fatalError(result, "ASIOGetChannelInfo"); } std::clog << "ASIOGetChannelInfo successful, type: " << (driverInfo.bufferInfos[i].isInput ? "input" : "output") << ", channel: " << i << ", sample type: " << driverInfo.channelInfos[i].type << std::endl; if (driverInfo.channelInfos[i].type != ASIOSTInt32LSB) { fatalError(ASE_OK, "Unsupported sample type!"); } } long inputLatency, outputLatency; result = ASIOGetLatencies(&inputLatency, &outputLatency); if (result != ASE_OK) { fatalError(result, "ASIOGetLatencies"); } std::clog << "Driver input latency: " << inputLatency << "usec" << ", output latency: " << outputLatency << "usec" << std::endl; const double bufferSize = driverInfo.preferredSize / driverInfo.sampleRate; mEngine.mOutputLatency = std::chrono::microseconds(llround(outputLatency / driverInfo.sampleRate)); mEngine.mOutputLatency += std::chrono::microseconds(llround(1.0e6 * bufferSize)); const auto totalLatency = mEngine.mOutputLatency.count(); std::clog << "Total latency: " << totalLatency << "usec" << std::endl; } void AudioPlatform::initializeDriverInfo() { ASIOError result = ASIOGetChannels(&mDriverInfo.inputChannels, &mDriverInfo.outputChannels); if (result != ASE_OK) { fatalError(result, "ASIOGetChannels"); } std::clog << "ASIOGetChannels succeeded, inputs:" << mDriverInfo.inputChannels << ", outputs: " << mDriverInfo.outputChannels << std::endl; long minSize, maxSize, granularity; result = ASIOGetBufferSize(&minSize, &maxSize, &mDriverInfo.preferredSize, &granularity); if (result != ASE_OK) { fatalError(result, "ASIOGetBufferSize"); } std::clog << "ASIOGetBufferSize succeeded, min: " << minSize << ", max: " << maxSize << ", preferred: " << mDriverInfo.preferredSize << ", granularity: " << granularity << std::endl; result = ASIOGetSampleRate(&mDriverInfo.sampleRate); if (result != ASE_OK) { fatalError(result, "ASIOGetSampleRate"); } std::clog << "ASIOGetSampleRate succeeded, sampleRate: " << mDriverInfo.sampleRate << "Hz" << std::endl; // Check wether the driver requires the ASIOOutputReady() optimization, which can be // used by the driver to reduce output latency by one block mDriverInfo.outputReady = (ASIOOutputReady() == ASE_OK); std::clog << "ASIOOutputReady optimization is " << (mDriverInfo.outputReady ? "enabled" : "disabled") << std::endl; } void AudioPlatform::initialize() { if (!loadAsioDriver(LINK_ASIO_DRIVER_NAME)) { std::cerr << "Failed opening ASIO driver for device named '" << LINK_ASIO_DRIVER_NAME << "', is the driver installed?" << std::endl; std::terminate(); } ASIOError result = ASIOInit(&mDriverInfo.driverInfo); if (result != ASE_OK) { fatalError(result, "ASIOInit"); } std::clog << "ASIOInit succeeded, asioVersion: " << mDriverInfo.driverInfo.asioVersion << ", driverVersion: " << mDriverInfo.driverInfo.driverVersion << ", name: " << mDriverInfo.driverInfo.name << std::endl; initializeDriverInfo(); ASIOCallbacks* callbacks = &(mAsioCallbacks); callbacks->bufferSwitch = &bufferSwitch; callbacks->bufferSwitchTimeInfo = &bufferSwitchTimeInfo; createAsioBuffers(); } void AudioPlatform::start() { ASIOError result = ASIOStart(); if (result != ASE_OK) { fatalError(result, "ASIOStart"); } } void AudioPlatform::stop() { ASIOError result = ASIOStop(); if (result != ASE_OK) { fatalError(result, "ASIOStop"); } } } // namespace linkaudio } // namespace ableton link-Link-3.0.2/examples/linkaudio/AudioPlatform_Wasapi.hpp0000644000175000017500000000374213303226525024165 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include "AudioEngine.hpp" #include #include #include #include // WARNING: This file provides an audio driver for Windows using WASAPI. This driver is // considered experimental and has problems with low-latency playback. Please consider // using the ASIO driver instead. namespace ableton { namespace linkaudio { // Convenience function to look up the human-readable WinAPI error code, print it out, // and then terminate the application. void fatalError(HRESULT result, LPCTSTR context); DWORD renderAudioRunloop(LPVOID); class AudioPlatform { public: AudioPlatform(Link& link); ~AudioPlatform(); DWORD audioRunloop(); AudioEngine mEngine; private: UINT32 bufferSize(); void initialize(); void start(); link::HostTimeFilter mHostTimeFilter; double mSampleTime; IMMDevice* mDevice; IAudioClient* mAudioClient; IAudioRenderClient* mRenderClient; WAVEFORMATEX* mStreamFormat; HANDLE mEventHandle; HANDLE mAudioThreadHandle; std::atomic mIsRunning; }; } // namespace linkaudio } // namespace ableton link-Link-3.0.2/examples/linkaudio/AudioPlatform_CoreAudio.cpp0000644000175000017500000001463313303226525024607 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #include "AudioPlatform_CoreAudio.hpp" #include #include #include namespace ableton { namespace linkaudio { AudioPlatform::AudioPlatform(Link& link) : mEngine(link) { initialize(); start(); } AudioPlatform::~AudioPlatform() { stop(); uninitialize(); } OSStatus AudioPlatform::audioCallback(void* inRefCon, AudioUnitRenderActionFlags*, const AudioTimeStamp* inTimeStamp, UInt32, UInt32 inNumberFrames, AudioBufferList* ioData) { AudioEngine* engine = static_cast(inRefCon); const auto bufferBeginAtOutput = engine->mLink.clock().ticksToMicros(inTimeStamp->mHostTime) + engine->mOutputLatency; engine->audioCallback(bufferBeginAtOutput, inNumberFrames); for (std::size_t i = 0; i < inNumberFrames; ++i) { for (UInt32 j = 0; j < ioData->mNumberBuffers; ++j) { SInt16* bufData = static_cast(ioData->mBuffers[j].mData); bufData[i] = static_cast(32761. * engine->mBuffer[i]); } } return noErr; } void AudioPlatform::initialize() { AudioComponentDescription cd = {}; cd.componentManufacturer = kAudioUnitManufacturer_Apple; cd.componentFlags = 0; cd.componentFlagsMask = 0; cd.componentType = kAudioUnitType_Output; cd.componentSubType = kAudioUnitSubType_DefaultOutput; AudioComponent component = AudioComponentFindNext(nullptr, &cd); OSStatus result = AudioComponentInstanceNew(component, &mIoUnit); if (result) { std::cerr << "Could not get Audio Unit. " << result << std::endl; std::terminate(); } UInt32 size = sizeof(mEngine.mSampleRate); result = AudioUnitGetProperty(mIoUnit, kAudioUnitProperty_SampleRate, kAudioUnitScope_Output, 0, &mEngine.mSampleRate, &size); if (result) { std::cerr << "Could not get sample rate. " << result << std::endl; std::terminate(); } std::clog << "SAMPLE RATE: " << mEngine.mSampleRate << std::endl; AudioStreamBasicDescription asbd = {}; asbd.mFormatID = kAudioFormatLinearPCM; asbd.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsNonInterleaved; asbd.mChannelsPerFrame = 2; asbd.mBytesPerPacket = sizeof(SInt16); asbd.mFramesPerPacket = 1; asbd.mBytesPerFrame = sizeof(SInt16); asbd.mBitsPerChannel = 8 * sizeof(SInt16); asbd.mSampleRate = mEngine.mSampleRate; result = AudioUnitSetProperty(mIoUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &asbd, sizeof(asbd)); if (result) { std::cerr << "Could not set stream format. " << result << std::endl; } char deviceName[512]; size = sizeof(deviceName); result = AudioUnitGetProperty(mIoUnit, kAudioDevicePropertyDeviceName, kAudioUnitScope_Global, 0, &deviceName, &size); if (result) { std::cerr << "Could not get device name. " << result << std::endl; std::terminate(); } std::clog << "DEVICE NAME: " << deviceName << std::endl; UInt32 bufferSize = 512; size = sizeof(bufferSize); result = AudioUnitSetProperty(mIoUnit, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Global, 0, &bufferSize, size); if (result) { std::cerr << "Could not set buffer size. " << result << std::endl; std::terminate(); } mEngine.setBufferSize(bufferSize); UInt32 propertyResult = 0; size = sizeof(propertyResult); result = AudioUnitGetProperty(mIoUnit, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Global, 0, &propertyResult, &size); if (result) { std::cerr << "Could not get buffer size. " << result << std::endl; std::terminate(); } std::clog << "BUFFER SIZE: " << propertyResult << " samples, " << propertyResult / mEngine.mSampleRate * 1e3 << " ms." << std::endl; // the buffer, stream and safety-offset latencies are part of inTimeStamp->mHostTime // within the audio callback. UInt32 deviceLatency = 0; size = sizeof(deviceLatency); result = AudioUnitGetProperty(mIoUnit, kAudioDevicePropertyLatency, kAudioUnitScope_Output, 0, &deviceLatency, &size); if (result) { std::cerr << "Could not get output device latency. " << result << std::endl; std::terminate(); } std::clog << "OUTPUT DEVICE LATENCY: " << deviceLatency << " samples, " << deviceLatency / mEngine.mSampleRate * 1e3 << " ms." << std::endl; using namespace std::chrono; const double latency = static_cast(deviceLatency) / mEngine.mSampleRate; mEngine.mOutputLatency = duration_cast(duration{latency}); AURenderCallbackStruct ioRemoteInput; ioRemoteInput.inputProc = audioCallback; ioRemoteInput.inputProcRefCon = &mEngine; result = AudioUnitSetProperty(mIoUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &ioRemoteInput, sizeof(ioRemoteInput)); if (result) { std::cerr << "Could not set render callback. " << result << std::endl; } result = AudioUnitInitialize(mIoUnit); if (result) { std::cerr << "Could not initialize audio unit. " << result << std::endl; } } void AudioPlatform::uninitialize() { OSStatus result = AudioUnitUninitialize(mIoUnit); if (result) { std::cerr << "Could not uninitialize Audio Unit. " << result << std::endl; } } void AudioPlatform::start() { OSStatus result = AudioOutputUnitStart(mIoUnit); if (result) { std::cerr << "Could not start Audio Unit. " << result << std::endl; std::terminate(); } } void AudioPlatform::stop() { OSStatus result = AudioOutputUnitStop(mIoUnit); if (result) { std::cerr << "Could not stop Audio Unit. " << result << std::endl; } } } // namespace linkaudio } // namespace ableton link-Link-3.0.2/examples/qlinkhut/0000755000175000017500000000000013303226525017254 5ustar zmoelnigzmoelniglink-Link-3.0.2/examples/qlinkhut/Controller.cpp0000644000175000017500000000467413303226525022116 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #include "Controller.hpp" namespace ableton { namespace qlinkhut { Controller::Controller() : mTempo(120) , mLink(mTempo) , mAudioPlatform(mLink) { mLink.setNumPeersCallback([this](std::size_t) { Q_EMIT onNumberOfPeersChanged(); }); mLink.setTempoCallback([this](const double bpm) { mTempo = bpm; Q_EMIT onTempoChanged(); }); } void Controller::setIsPlaying(bool isPlaying) { if (isPlaying) { mAudioPlatform.mEngine.startPlaying(); } else { mAudioPlatform.mEngine.stopPlaying(); } } bool Controller::isPlaying() { return mAudioPlatform.mEngine.isPlaying(); } void Controller::setTempo(double bpm) { mAudioPlatform.mEngine.setTempo(bpm); } double Controller::tempo() { return mTempo; } double Controller::quantum() { return mAudioPlatform.mEngine.quantum(); } void Controller::setQuantum(const double quantum) { mAudioPlatform.mEngine.setQuantum(quantum); Q_EMIT onQuantumChanged(); } unsigned long Controller::numberOfPeers() { return static_cast(mLink.numPeers()); } void Controller::setLinkEnabled(const bool isEnabled) { mLink.enable(isEnabled); Q_EMIT onIsLinkEnabledChanged(); } bool Controller::isLinkEnabled() { return mLink.isEnabled(); } void Controller::setStartStopSyncEnabled(const bool isEnabled) { mAudioPlatform.mEngine.setStartStopSyncEnabled(isEnabled); Q_EMIT onIsStartStopSyncEnabledChanged(); } bool Controller::isStartStopSyncEnabled() { return mAudioPlatform.mEngine.isStartStopSyncEnabled(); } double Controller::beatTime() { return mAudioPlatform.mEngine.beatTime(); } } // namespace qlinkhut } // namespace ableton link-Link-3.0.2/examples/qlinkhut/resources.qrc0000644000175000017500000000017113303226525021774 0ustar zmoelnigzmoelnig BeatTile.qml main.qml link-Link-3.0.2/examples/qlinkhut/main.qml0000644000175000017500000002224713303226525020722 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ import QtQuick 2.3 import QtQuick.Controls 1.2 import QtQuick.Layouts 1.1 Item { width: 768 height: 576 Rectangle { id: linkView x: 31 y: 25 width: 170 height: 50 border.color: "#404040" border.width: 1 color: controller.isLinkEnabled ? "#E6E6E6" : "#FFFFFF" Label { id: linkLabel property var linkEnabledText: "0 Links" text: controller.isLinkEnabled ? linkEnabledText : "Link" color: "#404040" font.pixelSize: 36 font.family: "Calibri" anchors.fill: parent horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter Connections { target: controller onNumberOfPeersChanged: { linkLabel.linkEnabledText = controller.numberOfPeers + " Link" linkLabel.linkEnabledText = controller.numberOfPeers == 1 ? linkLabel.linkEnabledText : linkLabel.linkEnabledText + "s" } } } MouseArea { anchors.fill: parent onPressed: { controller.isLinkEnabled = !controller.isLinkEnabled } } } Rectangle { id: startStopSyncView x: parent.width - width - 31 y: 25 width: 300 height: 50 border.color: "#404040" border.width: 1 color: controller.isStartStopSyncEnabled ? "#E6E6E6" : "#FFFFFF" Label { id: startStopSyncLabel text: controller.isStartStopSyncEnabled ? "StartStopSync On" : "StartStopSync Off" color: "#404040" font.pixelSize: 36 font.family: "Calibri" anchors.fill: parent horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } MouseArea { anchors.fill: parent onPressed: { controller.isStartStopSyncEnabled = !controller.isStartStopSyncEnabled } } } Rectangle { id: loopView x: 30 y: 90 width: 708 height: 328 function onQuantumChanged() { for(var i = loopView.children.length; i > 0 ; i--) { loopView.children[i-1].destroy() } var numSegments = Math.ceil(controller.quantum) for (var i = 0; i < numSegments; i++) { var width = loopView.width / controller.quantum var x = i * width var component = Qt.createComponent("BeatTile.qml"); var tile = component.createObject(loopView, { "index": i, "x": x, "width": x + width > loopView.width ? loopView.width - x : width, }) tile.activeColor = i == 0 ? "#FF6A00" : "#FFD500" } } Connections { target: controller onQuantumChanged: { loopView.onQuantumChanged() } } Timer { interval: 8 running: true repeat: true property var last: 1 onTriggered: { var index = controller.isPlaying ? Math.floor(controller.beatTime() % controller.quantum) : controller.quantum var countIn = index < 0; var beat = countIn ? controller.quantum + index : index for(var i = 0; i < loopView.children.length ; i++) { loopView.children[i].countIn = countIn loopView.children[i].currentBeat = beat } } } Component.onCompleted: { onQuantumChanged() } } Label { x: 31 y: 460 width: 170 height: 30 text: "Quantum"; color: "#404040" font.pixelSize: 24 font.family: "Calibri" horizontalAlignment: Text.AlignHCenter } Rectangle { id: quantumView x: 31 y: 500 width: 170 height: 50 border.width : 1 border.color: "#404040" Label { id: quantumLabel text: controller.quantum.toFixed() color: "#404040" font.pixelSize: 36 font.family: "Calibri" anchors.fill: parent horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } MouseArea { anchors.fill: parent property var clickQuantum: 0 property var clickY: 0 onPressed: { clickQuantum = controller.quantum clickY = mouseY } onPositionChanged: { var quantum = clickQuantum + 0.1 * (clickY - mouseY) controller.quantum = Math.max(1, quantum.toFixed()) } onDoubleClicked: { controller.quantum = 4 } } } Label { x: (parent.width - width) / 2 - 90 y: 460 width: 170 height: 30 text: "Tempo"; color: "#404040" font.pixelSize: 24 font.family: "Calibri" horizontalAlignment: Text.AlignHCenter } Rectangle { id: tempoView x: (parent.width - width) / 2 - 90 y: 500 width: 170 height: 50 border.width : 1 border.color: "#404040" Label { id: tempoLabel text: controller.tempo.toFixed(2) color: "#404040" font.pixelSize: 36 font.family: "Calibri" anchors.fill: parent horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } MouseArea { anchors.fill: parent property var clickTempo: 0 property var clickY: 0 onPressed: { clickTempo = controller.tempo clickY = mouseY } onPositionChanged: { var tempo = clickTempo + 0.5 * (clickY - mouseY) controller.tempo = tempo } onDoubleClicked: { controller.tempo = 120 } } } Label { id: beatTimeView x: (parent.width - width) / 2 + 90 y: 460 width: 170 height: 30 text: "Beats" color: "#404040" font.pixelSize: 24 font.family: "Calibri" horizontalAlignment: Text.AlignHCenter } Rectangle { id: beatTimeIntView x: (parent.width - width) / 2 + 90 y: 500 width: 170 height: 50 border.width: 1 border.color: "#404040" Label { id: beatTimeText color: "#404040" font.pixelSize: 36 font.family: "Calibri" anchors.fill: parent horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } } Timer { id: timer interval: 10 repeat: true running: true onTriggered: { var beatTime = controller.beatTime() beatTimeText.text = beatTime.toFixed(1) transportText.text = controller.isPlaying ? "Pause" : "Play"; transportView.color = controller.isPlaying ? "#E6E6E6" : "#FFFFFF"; } } Rectangle { id: transportView x: parent.width - width - 31 y: 500 width: 170 height: 50 border.width: 1 border.color: "#404040" Label { id: transportText color: "#404040" font.pixelSize: 36 font.family: "Calibri" anchors.fill: parent horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } MouseArea { anchors.fill: parent onPressed: { controller.isPlaying = !controller.isPlaying } } } Item { focus: true Keys.onPressed: { if ( event.key == 32) { controller.isPlaying = !controller.isPlaying } } } } link-Link-3.0.2/examples/qlinkhut/main.cpp0000644000175000017500000000241313303226525020704 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #include "Controller.hpp" #include #include #include #include #include int main(int argc, char* argv[]) { ableton::qlinkhut::Controller controller; QGuiApplication app(argc, argv); QQuickView view; view.rootContext()->setContextProperty("controller", &controller); view.setSource(QUrl("qrc:/main.qml")); view.show(); return app.exec(); } link-Link-3.0.2/examples/qlinkhut/Controller.hpp0000644000175000017500000000450513303226525022114 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include "AudioPlatform.hpp" #include namespace ableton { namespace qlinkhut { class Controller : public QObject { Q_OBJECT Q_DISABLE_COPY(Controller) public: Controller(); void setIsPlaying(bool); bool isPlaying(); Q_SIGNAL void onIsPlayingChanged(); Q_PROPERTY(bool isPlaying READ isPlaying WRITE setIsPlaying) void setTempo(double); double tempo(); Q_SIGNAL void onTempoChanged(); Q_PROPERTY(double tempo READ tempo WRITE setTempo NOTIFY onTempoChanged) void setQuantum(double quantum); double quantum(); Q_SIGNAL void onQuantumChanged(); Q_PROPERTY(double quantum READ quantum WRITE setQuantum NOTIFY onQuantumChanged) unsigned long numberOfPeers(); Q_SIGNAL void onNumberOfPeersChanged(); Q_PROPERTY(unsigned long numberOfPeers READ numberOfPeers NOTIFY onNumberOfPeersChanged) void setLinkEnabled(bool isEnabled); bool isLinkEnabled(); Q_SIGNAL void onIsLinkEnabledChanged(); Q_PROPERTY(bool isLinkEnabled WRITE setLinkEnabled READ isLinkEnabled NOTIFY onIsLinkEnabledChanged) void setStartStopSyncEnabled(bool isEnabled); bool isStartStopSyncEnabled(); Q_SIGNAL void onIsStartStopSyncEnabledChanged(); Q_PROPERTY(bool isStartStopSyncEnabled WRITE setStartStopSyncEnabled READ isStartStopSyncEnabled NOTIFY onIsStartStopSyncEnabledChanged) Q_INVOKABLE double beatTime(); private: double mTempo; Link mLink; linkaudio::AudioPlatform mAudioPlatform; }; } // namespace qlinkhut } // namespace ableton link-Link-3.0.2/examples/qlinkhut/BeatTile.qml0000644000175000017500000000227413303226525021465 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ import QtQuick 2.0 Rectangle { property var index: 0 property var currentBeat: -1 property var countIn: false property var activeColor: "#404040" property var countInColor: "#909090" height: 328; border.width: 1; border.color: "#FFFFFF"; color: index == currentBeat ? (countIn ? countInColor : activeColor) : "#404040" } link-Link-3.0.2/examples/CMakeLists.txt0000644000175000017500000001756213303226525020170 0ustar zmoelnigzmoelnigcmake_minimum_required(VERSION 3.0) project(LinkExamples) # _ ____ ___ ___ # / \ / ___|_ _/ _ \ # / _ \ \___ \| | | | | # / ___ \ ___) | | |_| | # /_/ \_\____/___\___/ # if(WIN32) function(configure_asio asio_sdk_path_OUT) # ASIO-related path/file variables set(asio_download_root "https://www.steinberg.net/sdk_downloads") set(asio_file_name "asiosdk2.3.zip") set(asio_dir_name "ASIOSDK2.3") set(asio_working_dir "${CMAKE_BINARY_DIR}/modules") set(asio_output_path "${asio_working_dir}/${asio_file_name}") message(STATUS "Downloading ASIO SDK") file(DOWNLOAD "${asio_download_root}/${asio_file_name}" ${asio_output_path}) file(SHA1 ${asio_output_path} asio_zip_hash) message(" ASIO SDK SHA1: ${asio_zip_hash}") message(" Extracting ASIO SDK") execute_process(COMMAND ${CMAKE_COMMAND} -E tar "xf" ${asio_output_path} --format=zip WORKING_DIRECTORY ${asio_working_dir} INPUT_FILE ${asio_output_path} ) # Set the ASIO SDK path for the caller set(${asio_sdk_path_OUT} "${asio_working_dir}/${asio_dir_name}" PARENT_SCOPE) endfunction() endif() # _ _ _ # / \ _ _ __| (_) ___ # / _ \| | | |/ _` | |/ _ \ # / ___ \ |_| | (_| | | (_) | # /_/ \_\__,_|\__,_|_|\___/ # set(linkhut_audio_SOURCES) if(APPLE) set(linkhut_audio_SOURCES linkaudio/AudioPlatform_CoreAudio.hpp linkaudio/AudioPlatform_CoreAudio.cpp ) elseif(WIN32) if(LINK_BUILD_ASIO) configure_asio(asio_sdk_path) include_directories(${asio_sdk_path}/common) include_directories(${asio_sdk_path}/host) include_directories(${asio_sdk_path}/host/pc) set(linkhut_audio_SOURCES ${asio_sdk_path}/common/asio.cpp ${asio_sdk_path}/host/asiodrivers.cpp ${asio_sdk_path}/host/pc/asiolist.cpp linkaudio/AudioPlatform_Asio.hpp linkaudio/AudioPlatform_Asio.cpp ) else() message(WARNING "LinkHut has been configured to be built with the WASAPI audio " "driver. This driver is considered experimental and has problems with low-latency " "playback. Please consider using the ASIO driver instead.") set(linkhut_audio_SOURCES linkaudio/AudioPlatform_Wasapi.hpp linkaudio/AudioPlatform_Wasapi.cpp ) endif() elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux") if(LINK_BUILD_JACK) set(linkhut_audio_SOURCES linkaudio/AudioPlatform_Jack.hpp linkaudio/AudioPlatform_Jack.cpp ) else() set(linkhut_audio_SOURCES linkaudio/AudioPlatform_Portaudio.hpp linkaudio/AudioPlatform_Portaudio.cpp ) endif() endif() include_directories(linkaudio) source_group("Audio Sources" FILES ${linkhut_audio_SOURCES}) # ____ # / ___|___ _ __ ___ _ __ ___ ___ _ __ # | | / _ \| '_ ` _ \| '_ ` _ \ / _ \| '_ \ # | |__| (_) | | | | | | | | | | | (_) | | | | # \____\___/|_| |_| |_|_| |_| |_|\___/|_| |_| # function(configure_linkhut_executable target) if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") target_link_libraries(${target} atomic pthread) endif() target_link_libraries(${target} Ableton::Link) endfunction() function(configure_qlinkhut_executable target) configure_linkhut_executable(${target}) target_link_libraries(${target} Qt5::Quick) if(${CMAKE_CXX_COMPILER_ID} MATCHES Clang) target_compile_options(${target} PRIVATE "-Wno-global-constructors" "-Wno-missing-prototypes" "-Wno-undefined-reinterpret-cast" "-Wno-zero-as-null-pointer-constant" ) endif() if(MSVC_VERSION VERSION_GREATER 1900) target_compile_options(${target} PRIVATE "/wd4946") endif() endfunction() function(configure_linkhut_audio_sources target) if(APPLE) target_link_libraries(${target} "-framework AudioUnit") target_compile_definitions(${target} PRIVATE -DLINKHUT_AUDIO_PLATFORM_COREAUDIO=1 ) elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux") if(LINK_BUILD_JACK) target_link_libraries(${target} jack) target_compile_definitions(${target} PRIVATE -DLINKHUT_AUDIO_PLATFORM_JACK=1 ) else() target_link_libraries(${target} asound portaudio) target_compile_definitions(${target} PRIVATE -DLINKHUT_AUDIO_PLATFORM_PORTAUDIO=1 ) endif() elseif(WIN32) if(LINK_BUILD_ASIO) # ASIO uses lots of old-school string APIs from the C stdlib add_definitions("/D_CRT_SECURE_NO_WARNINGS") target_compile_definitions(${target} PRIVATE -DLINKHUT_AUDIO_PLATFORM_ASIO=1 ) else() target_compile_definitions(${target} PRIVATE -DLINKHUT_AUDIO_PLATFORM_WASAPI=1 ) endif() target_link_libraries(${target} winmm) endif() endfunction() if(WIN32) # When building LinkHut, additional warnings are generated from third-party frameworks set(extra_ignored_warnings_LIST "/wd4127" # conditional expression is constant "/wd4242" # 'identifier' : conversion from 'type1' to 'type2', possible loss of data "/wd4619" # #pragma warning : there is no warning number 'number' "/wd4668" # 'symbol' is not defined as a preprocessor macro, replacing with '0' for 'directives' "/wd4702" # unreachable code "/wd4946" # reinterpret_cast used between related classes: 'class1' and 'class2' ) if(LINK_BUILD_ASIO) set(extra_ignored_warnings_LIST ${extra_ignored_warnings_LIST} "/wd4267" # 'argument': conversion from '?' to '?', possible loss of data "/wd4477" # 'printf': format string '%?' requires an argument of type '?' ) else() set(extra_ignored_warnings_LIST ${extra_ignored_warnings_LIST} "/wd4191" # 'operator/operation' : unsafe conversion from 'type of expression' to 'type required' ) endif() string(REPLACE ";" " " extra_ignored_warnings "${extra_ignored_warnings_LIST}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${extra_ignored_warnings}") endif() # _ _ _ _ _ _ # | | (_)_ __ | | _| | | |_ _| |_ # | | | | '_ \| |/ / |_| | | | | __| # | |___| | | | | <| _ | |_| | |_ # |_____|_|_| |_|_|\_\_| |_|\__,_|\__| # set(linkhut_HEADERS linkaudio/AudioEngine.hpp linkaudio/AudioPlatform.hpp ${link_HEADERS} ) set(linkhut_SOURCES linkaudio/AudioEngine.cpp linkhut/main.cpp ) add_executable(LinkHut ${linkhut_HEADERS} ${linkhut_SOURCES} ${linkhut_audio_SOURCES} ) configure_linkhut_audio_sources(LinkHut) configure_linkhut_executable(LinkHut) source_group("LinkHut" FILES ${linkhut_HEADERS} ${linkhut_SOURCES}) # ___ _ _ _ _ _ _ # / _ \| | (_)_ __ | | _| | | |_ _| |_ # | | | | | | | '_ \| |/ / |_| | | | | __| # | |_| | |___| | | | | <| _ | |_| | |_ # \__\_\_____|_|_| |_|_|\_\_| |_|\__,_|\__| # if(LINK_BUILD_QT_EXAMPLES) set(CMAKE_AUTOMOC ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) find_package(Qt5Quick REQUIRED) set(qlinkhut_HEADERS linkaudio/AudioEngine.hpp linkaudio/AudioPlatform.hpp qlinkhut/Controller.hpp ${link_HEADERS} ) set(qlinkhut_SOURCES linkaudio/AudioEngine.cpp qlinkhut/Controller.cpp qlinkhut/main.cpp ${link_SOURCES} ) source_group("QLinkHut" FILES ${qlinkhut_HEADERS} ${qlinkhut_SOURCES}) qt5_add_resources(UI_RESOURCES qlinkhut/resources.qrc) add_executable(QLinkHut ${qlinkhut_HEADERS} ${qlinkhut_SOURCES} ${linkhut_audio_SOURCES} ${UI_RESOURCES} ) configure_linkhut_audio_sources(QLinkHut) configure_qlinkhut_executable(QLinkHut) # A silent version of QLinkHut that uses the Link application context instead of # the audio buffer context. It is the same as QLinkHut except that it doesn't # generate sound. add_executable(QLinkHutSilent ${qlinkhut_HEADERS} ${qlinkhut_SOURCES} linkaudio/AudioPlatform_Dummy.hpp ${UI_RESOURCES} ) target_compile_definitions(QLinkHutSilent PRIVATE -DLINKHUT_AUDIO_PLATFORM_DUMMY=1 ) if(UNIX) set_target_properties(QLinkHutSilent PROPERTIES COMPILE_FLAGS "-Wno-unused" ) endif() configure_qlinkhut_executable(QLinkHutSilent) endif() link-Link-3.0.2/examples/linkhut/0000755000175000017500000000000013303226525017073 5ustar zmoelnigzmoelniglink-Link-3.0.2/examples/linkhut/main.cpp0000644000175000017500000001126413303226525020527 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #include "AudioPlatform.hpp" #include #include #include #include #include #if defined(LINK_PLATFORM_UNIX) #include #endif namespace { struct State { std::atomic running; ableton::Link link; ableton::linkaudio::AudioPlatform audioPlatform; State() : running(true) , link(120.) , audioPlatform(link) { link.enable(true); } }; void disableBufferedInput() { #if defined(LINK_PLATFORM_UNIX) termios t; tcgetattr(STDIN_FILENO, &t); t.c_lflag &= static_cast(~ICANON); tcsetattr(STDIN_FILENO, TCSANOW, &t); #endif } void enableBufferedInput() { #if defined(LINK_PLATFORM_UNIX) termios t; tcgetattr(STDIN_FILENO, &t); t.c_lflag |= ICANON; tcsetattr(STDIN_FILENO, TCSANOW, &t); #endif } void clearLine() { std::cout << " \r" << std::flush; std::cout.fill(' '); } void printHelp() { std::cout << std::endl << " < L I N K H U T >" << std::endl << std::endl; std::cout << "usage:" << std::endl; std::cout << " start / stop: space" << std::endl; std::cout << " decrease / increase tempo: w / e" << std::endl; std::cout << " decrease / increase quantum: r / t" << std::endl; std::cout << " enable / disable start stop sync: s" << std::endl; std::cout << " quit: q" << std::endl << std::endl; } void printState(const std::chrono::microseconds time, const ableton::Link::SessionState sessionState, const std::size_t numPeers, const double quantum, const bool startStopSyncOn) { const auto beats = sessionState.beatAtTime(time, quantum); const auto phase = sessionState.phaseAtTime(time, quantum); const auto startStop = startStopSyncOn ? "on" : "off"; std::cout << std::defaultfloat << "peers: " << numPeers << " | " << "quantum: " << quantum << " | " << "start stop sync: " << startStop << " | " << "tempo: " << sessionState.tempo() << " | " << std::fixed << "beats: " << beats << " | "; for (int i = 0; i < ceil(quantum); ++i) { if (i < phase) { std::cout << 'X'; } else { std::cout << 'O'; } } clearLine(); } void input(State& state) { char in; #if defined(LINK_PLATFORM_WINDOWS) HANDLE stdinHandle = GetStdHandle(STD_INPUT_HANDLE); DWORD numCharsRead; INPUT_RECORD inputRecord; do { ReadConsoleInput(stdinHandle, &inputRecord, 1, &numCharsRead); } while ((inputRecord.EventType != KEY_EVENT) || inputRecord.Event.KeyEvent.bKeyDown); in = inputRecord.Event.KeyEvent.uChar.AsciiChar; #elif defined(LINK_PLATFORM_UNIX) in = static_cast(std::cin.get()); #endif const auto tempo = state.link.captureAppSessionState().tempo(); auto& engine = state.audioPlatform.mEngine; switch (in) { case 'q': state.running = false; clearLine(); return; case 'w': engine.setTempo(tempo - 1); break; case 'e': engine.setTempo(tempo + 1); break; case 'r': engine.setQuantum(engine.quantum() - 1); break; case 't': engine.setQuantum(std::max(1., engine.quantum() + 1)); break; case 's': engine.setStartStopSyncEnabled(!engine.isStartStopSyncEnabled()); break; case ' ': if (engine.isPlaying()) { engine.stopPlaying(); } else { engine.startPlaying(); } break; } input(state); } } // namespace int main(int, char**) { State state; printHelp(); std::thread thread(input, std::ref(state)); disableBufferedInput(); while (state.running) { const auto time = state.link.clock().micros(); auto sessionState = state.link.captureAppSessionState(); printState(time, sessionState, state.link.numPeers(), state.audioPlatform.mEngine.quantum(), state.audioPlatform.mEngine.isStartStopSyncEnabled()); std::this_thread::sleep_for(std::chrono::milliseconds(10)); } enableBufferedInput(); thread.join(); return 0; } link-Link-3.0.2/GNU-GPL-v2.0.md0000644000175000017500000004302513303226525015661 0ustar zmoelnigzmoelnigGNU General Public License ========================== _Version 2, June 1991_ _Copyright © 1989, 1991 Free Software Foundation, Inc.,_ _51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA_ Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. ### Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: **(1)** copyright the software, and **(2)** offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. ### TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION **0.** This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The “Program”, below, refers to any such program or work, and a “work based on the Program” means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term “modification”.) Each licensee is addressed as “you”. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. **1.** You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. **2.** You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: * **a)** You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. * **b)** You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. * **c)** If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. **3.** You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: * **a)** Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, * **b)** Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, * **c)** Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. **4.** You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. **5.** You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. **6.** Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. **7.** If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. **8.** If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. **9.** The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and “any later version”, you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. **10.** If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. ### NO WARRANTY **11.** BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. **12.** IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS ### How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w` and `show c` should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w` and `show c`; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a “copyright disclaimer” for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. link-Link-3.0.2/ci/0000755000175000017500000000000013303226525014172 5ustar zmoelnigzmoelniglink-Link-3.0.2/ci/memcheck.supp0000644000175000017500000000076313303226525016665 0ustar zmoelnigzmoelnig# These Glibc-related leaks are common when running Valgrind. For more info, # see http://valgrind.org/docs/manual/faq.html#faq.reports { glibc-memory-pool-64-bit Memcheck:Leak fun:malloc obj:/usr/lib/x86_64-linux-gnu/libstdc++.so.6.* fun:call_init.part.0 ... fun:_dl_init obj:/lib/x86_64-linux-gnu/ld-*.so } { glibc-memory-pool-32-bit Memcheck:Leak fun:malloc obj:/usr/lib32/libstdc++.so.6.* fun:call_init fun:_dl_init obj:/lib/i386-linux-gnu/ld-*.so } link-Link-3.0.2/ci/build.py0000755000175000017500000000353713303226525015656 0ustar zmoelnigzmoelnig#!/usr/bin/env python import argparse import logging import os import sys from distutils.spawn import find_executable from subprocess import call def parse_args(): arg_parser = argparse.ArgumentParser() arg_parser.add_argument( '--cmake', default=find_executable("cmake"), help='Path to CMake executable (default: %(default)s)') arg_parser.add_argument( '-c', '--configuration', help='Build configuration to use (not supported by IDE generators)') arg_parser.add_argument( '-a', '--arguments', help='Arguments to pass to builder') return arg_parser.parse_args(sys.argv[1:]) def build_cmake_args(args, build_dir): if args.cmake is None: logging.error('CMake not found, please use the --cmake option') return None cmake_args = [] cmake_args.append(args.cmake) cmake_args.append('--build') cmake_args.append(build_dir) if args.configuration is not None: cmake_args.append('--config') cmake_args.append(args.configuration) if args.arguments is not None: cmake_args.append('--') for arg in args.arguments.split(): cmake_args.append(arg) return cmake_args def build(args): scripts_dir = os.path.dirname(os.path.realpath(__file__)) root_dir = os.path.join(scripts_dir, os.pardir) build_dir = os.path.join(root_dir, 'build') if not os.path.exists(build_dir): logging.error( 'Build directory not found, did you forget to run the configure.py script?') return 2 cmake_args = build_cmake_args(args, build_dir) if cmake_args is None: return 1 logging.info('Running CMake') return call(cmake_args) if __name__ == '__main__': logging.basicConfig(format='%(message)s', level=logging.INFO, stream=sys.stdout) sys.exit(build(parse_args())) link-Link-3.0.2/ci/run-tests.py0000755000175000017500000000510113303226525016510 0ustar zmoelnigzmoelnig#!/usr/bin/env python import argparse import logging import os import sys from distutils.spawn import find_executable from subprocess import call def parse_args(): arg_parser = argparse.ArgumentParser() arg_parser.add_argument( '-t', '--target', help='Target to test') arg_parser.add_argument( '--valgrind', default=False, help='Run with Valgrind', action='store_true') return arg_parser.parse_args(sys.argv[1:]) def get_system_exe_extension(): # Should return 'win32' even on 64-bit Windows if sys.platform == 'win32': return '.exe' else: return '' def find_exe(name, path): for root, dirs, files in os.walk(path): if name in files: return os.path.join(root, name) def build_test_exe_args(args, build_dir): if args.target is None: logging.error('Target not specified, please use the --target option') return None test_exe = find_exe(args.target + get_system_exe_extension(), build_dir) if not os.path.exists(test_exe): logging.error('Could not find test executable for target {}, ' 'did you forget to build?'.format(args.target)) else: logging.debug('Test executable is: {}'.format(test_exe)) test_exe_args = [test_exe] if args.valgrind is not False: valgrind_exe = find_executable('valgrind') if valgrind_exe is None: logging.error('Valgrind not found, cannot continue') return None test_exe_args = [ valgrind_exe, '--leak-check=full', '--show-reachable=yes', '--gen-suppressions=all', '--error-exitcode=1', '--track-origins=yes', '--suppressions=../ci/memcheck.supp', test_exe] return test_exe_args def run_tests(args): scripts_dir = os.path.dirname(os.path.realpath(__file__)) root_dir = os.path.join(scripts_dir, os.pardir) build_dir = os.path.join(root_dir, 'build') if not os.path.exists(build_dir): logging.error( 'Build directory not found, did you forget to run the configure.py script?') return 2 os.chdir(build_dir) env = os.environ.copy() env['GLIBCXX_FORCE_NEW'] = '1' test_exe_args = build_test_exe_args(args, build_dir) if test_exe_args is None: return 1 logging.info(test_exe_args) logging.info('Running Tests for {}'.format(args.target)) return call(test_exe_args, env=env) if __name__ == '__main__': logging.basicConfig(format='%(message)s', level=logging.INFO, stream=sys.stdout) sys.exit(run_tests(parse_args())) link-Link-3.0.2/ci/run_valgrind_tests.sh0000755000175000017500000000026213303226525020445 0ustar zmoelnigzmoelnig#!/bin/bash python configure.py --generator Ninja python build.py python run-tests.py --target LinkCoreTest --valgrind python run-tests.py --target LinkDiscoveryTest --valgrind link-Link-3.0.2/ci/configure.py0000755000175000017500000000531313303226525016532 0ustar zmoelnigzmoelnig#!/usr/bin/env python import argparse import logging import os import shutil import sys from distutils.spawn import find_executable from subprocess import call def parse_args(): arg_parser = argparse.ArgumentParser() arg_parser.add_argument( '-a', '--audio-driver', help='Audio driver to build (Windows only, default: %(default)s)') arg_parser.add_argument( '--cmake', default=find_executable("cmake"), help='Path to CMake executable (default: %(default)s)') arg_parser.add_argument( '-c', '--configuration', help='Build configuration to use (not supported by IDE generators)') arg_parser.add_argument( '-q', '--with-qt', help='Build Qt example apps', action='store_true') arg_parser.add_argument( '-g', '--generator', help='CMake generator to use (default: Determined by CMake)') arg_parser.add_argument( '-f', '--flags', help='Additional CMake flags') return arg_parser.parse_args(sys.argv[1:]) def build_cmake_args(args): if args.cmake is None: logging.error('CMake not found, please use the --cmake option') return None cmake_args = [] cmake_args.append(args.cmake) if args.generator is not None: cmake_args.append('-G') cmake_args.append(args.generator) if args.with_qt: cmake_args.append('-DLINK_BUILD_QT_EXAMPLES=ON') if args.configuration is not None: cmake_args.append('-DCMAKE_BUILD_TYPE=' + args.configuration) if args.flags is not None: cmake_args.append(args.flags) if sys.platform == 'win32': if args.audio_driver is None or args.audio_driver == 'Asio': cmake_args.append('-DLINK_BUILD_ASIO=ON') else: cmake_args.append('-DLINK_BUILD_ASIO=OFF') elif 'linux' in sys.platform: if args.audio_driver == 'Jack': cmake_args.append('-DLINK_BUILD_JACK=ON') # This must always be last cmake_args.append('..') return cmake_args def configure(args): scripts_dir = os.path.dirname(os.path.realpath(__file__)) root_dir = os.path.join(scripts_dir, os.pardir) build_dir = os.path.join(root_dir, 'build') if os.path.exists(build_dir): logging.info('Removing existing build directory') shutil.rmtree(build_dir) logging.debug('Creating build directory') os.mkdir(build_dir) os.chdir(build_dir) cmake_args = build_cmake_args(args) if cmake_args is None: return 1 logging.info('Running CMake') return call(cmake_args) if __name__ == '__main__': logging.basicConfig(format='%(message)s', level=logging.INFO, stream=sys.stdout) sys.exit(configure(parse_args())) link-Link-3.0.2/ci/check-formatting.py0000755000175000017500000000574013303226525020002 0ustar zmoelnigzmoelnig#!/usr/bin/env python import argparse import logging import os import subprocess import sys def parse_args(): arg_parser = argparse.ArgumentParser() arg_parser.add_argument( '-c', '--clang-format', default='clang-format-6.0', help='Path to clang-format executable') arg_parser.add_argument( '-f', '--fix', action='store_true', help='Automatically fix all files with formatting errors') return arg_parser.parse_args(sys.argv[1:]) def parse_clang_xml(xml): for line in xml.splitlines(): if line.startswith('. # Building and Running Link Examples Link relies on `asio-standalone` and `catch` as submodules. After checking out the main repositories, those submodules have to be loaded using ``` git submodule update --init --recursive ``` Link uses [CMake][cmake] to generate build files for the [Catch][catch]-based unit-tests and the example applications. ``` $ mkdir build $ cd build $ cmake .. $ cmake --build . ``` In order to build the GUI example application **QLinkHut**, the [Qt][qt] installation path must be set in the system PATH and `LINK_BUILD_QT_EXAMPLES` must be set: ``` $ mkdir build $ cd build $ cmake -DLINK_BUILD_QT_EXAMPLES=ON .. $ cmake --build . ``` The output binaries for the example applications and the unit-tests will be placed in a `bin` subdirectory of the CMake binary directory. Also note that the word size of the Qt installation must match how Link has been configured. Look for the value of `LINK_WORD_SIZE` in the CMake output to verify that the word size matches Qt's. When running QLinkHut on Windows, the Qt binary path must be in the system `PATH` before launching the executable. So to launch QLinkHut from Visual Studio, one must go to the QLinkHut Properties -> Debugging -> Environment, and set it to: ``` PATH=$(Path);C:\path\to\Qt\5.5\msvc64_bin\bin ``` # Integrating Link in your Application ## Test Plan To make sure users have the best possible experience using Link it is important all apps supporting Link behave consistently. This includes for example playing in sync with other apps as well as not hijacking a jams tempo when joining. To make sure your app behaves as intended make sure it complies to the [Test Plan](TEST-PLAN.md). ## Building Link Link is a header-only library, so it should be straightforward to integrate into your application. ### CMake-based Projects If you are using CMake, then you can simply add the following to your CMakeLists.txt file: ```cmake include($PATH_TO_LINK/AbletonLinkConfig.cmake) target_link_libraries($YOUR_TARGET Ableton::Link) ``` You can optionally have your build target depend on `${link_HEADERS}`, which will make the Link headers visible in your IDE. This variable exported to the `PARENT_SCOPE` by Link's CMakeLists.txt. ### Other Build Systems To include the Link library in your non CMake project, you must do the following: - Add the `link/include` and `modules/asio-standalone/asio/include` directories to your list of include paths - Define `LINK_PLATFORM_MACOSX=1`, `LINK_PLATFORM_LINUX=1`, or `LINK_PLATFORM_WINDOWS=1`, depending on which platform you are building on. If you get any compiler errors/warnings, have a look at [compile-flags.cmake](cmake_include/ConfigureCompileFlags.cmake), which might provide some insight as to the compiler flags needed to build Link. ### Build Requirements | Platform | Minimum Required | Optional (only required for examples) | |----------|----------------------|---------------------------------------| | **All** | CMake 3.0 | Qt 5.5 | | Windows | MSVC 2013 | Steinberg ASIO SDK 2.3 | | Mac | Xcode 7.0 | | | Linux | Clang 3.6 or GCC 5.2 | libportaudio19-dev | Other compilers with good C++11 support should work, but are not verified. iOS developers should not use this repo. See http://ableton.github.io/linkkit for information on the LinkKit SDK for iOS. # Documentation An overview of Link concepts can be found at http://ableton.github.io/link. Those that are new to Link should start there. The [Link.hpp](include/ableton/Link.hpp) header contains the full Link public interface. See the LinkHut and QLinkHut projects in this repo for an example usage of the `Link` type. ## Time and Clocks Link works by calculating a relationship between the system clocks of devices in a session. Since the mechanism for obtaining a system time value and the unit of these values differ across platforms, Link defines a `Clock` abstraction with platform-specific implementations. Please see: - `Link::clock()` method in [Link.hpp](include/ableton/Link.hpp) - OSX and iOS clock implementation in [platforms/darwin/Clock.hpp](include/ableton/platforms/darwin/Clock.hpp) - Windows clock implementation in [platforms/windows/Clock.hpp](include/ableton/platforms/windows/Clock.hpp) - C++ standard library `std::chrono::high_resolution_clock`-based implementation in [platforms/stl/Clock.hpp](include/ableton/platforms/stl/Clock.hpp) Using the system time correctly in the context of an audio callback gets a little complicated. Audio devices generally have a sample clock that is independent of the system Clock. Link maintains a mapping between system time and beat time and therefore can't use the sample time provided by the audio system directly. On OSX and iOS, the CoreAudio render callback is passed an `AudioTimeStamp` structure with a `mHostTime` member that represents the system time at which the audio buffer will be passed to the audio hardware. This is precisely the information needed to derive the beat time values corresponding to samples in the buffer using Link. Unfortunately, not all platforms provide this data to the audio callback. When a system timestamp is not provided with the audio buffer, the best a client can do in the audio callback is to get the current system time and filter it based on the provided sample time. Filtering is necessary because the audio callback will not be invoked at a perfectly regular interval and therefore the queried system time will exhibit jitter relative to the sample clock. The Link library provides a [HostTimeFilter](include/ableton/link/HostTimeFilter.hpp) utility class that performs a linear regression between system time and sample time in order to improve the accuracy of system time values used in an audio callback. See the audio callback implementations for the various [platforms](examples/linkaudio) used in the examples to see how this is used in practice. Note that for Windows-based systems, we recommend using the [ASIO][asio] audio driver. ## Latency Compensation As discussed in the previous section, the system time that a client is provided in an audio callback either represents the time at which the buffer will be submitted to the audio hardware (for OSX/iOS) or the time at which the callback was invoked (when the code in the callback queries the system time). Note that neither of these is what we actually want to synchronize between devices in order to play in time. In order for multiple devices to play in time, we need to synchronize the moment at which their signals hit the speaker or output cable. If this compensation is not performed, the output signals from devices with different output latencies will exhibit a persistent offset from each other. For this reason, the audio system's output latency should be added to system time values before passing them to Link methods. Examples of this latency compensation can be found in the [platform](examples/linkaudio) implementations of the example apps. [asio]: https://www.steinberg.net/en/company/developers.html [catch]: https://github.com/philsquared/Catch [cmake]: https://www.cmake.org [license]: LICENSE.md [qt]: https://www.qt.io link-Link-3.0.2/modules/0000755000175000017500000000000013303226525015247 5ustar zmoelnigzmoelniglink-Link-3.0.2/modules/asio-standalone/0000755000175000017500000000000013303226525020330 5ustar zmoelnigzmoelniglink-Link-3.0.2/CMakeLists.txt0000644000175000017500000000352513303226525016344 0ustar zmoelnigzmoelnigcmake_minimum_required(VERSION 3.0) project(Link) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) # ___ _ _ # / _ \ _ __ | |_(_) ___ _ __ ___ # | | | | '_ \| __| |/ _ \| '_ \/ __| # | |_| | |_) | |_| | (_) | | | \__ \ # \___/| .__/ \__|_|\___/|_| |_|___/ # |_| # Note: Please use the LINK_* prefix for all project-specific options option(LINK_BUILD_QT_EXAMPLES "Build examples (Requires Qt)" OFF) if(UNIX) option(LINK_ENABLE_ASAN "Build with Address Sanitizier (ASan)" OFF) option(LINK_BUILD_JACK "Build example applications with JACK support" OFF) endif() if(WIN32) option(LINK_BUILD_ASIO "Build example applications with ASIO driver" ON) option(LINK_BUILD_VLD "Build with VLD support (VLD must be installed separately)" OFF) endif() # ____ _ _ # | _ \ __ _| |_| |__ ___ # | |_) / _` | __| '_ \/ __| # | __/ (_| | |_| | | \__ \ # |_| \__,_|\__|_| |_|___/ # # Other CMake files must be included only after declaring build options include(cmake_include/ConfigureCompileFlags.cmake) include(cmake_include/CatchConfig.cmake) include(AbletonLinkConfig.cmake) add_subdirectory(include) add_subdirectory(src) add_subdirectory(examples) # ____ # / ___| _ _ _ __ ___ _ __ ___ __ _ _ __ _ _ # \___ \| | | | '_ ` _ \| '_ ` _ \ / _` | '__| | | | # ___) | |_| | | | | | | | | | | | (_| | | | |_| | # |____/ \__,_|_| |_| |_|_| |_| |_|\__,_|_| \__, | # |___/ message(STATUS "Build options") get_cmake_property(all_variables VARIABLES) string(REGEX MATCHALL "(^|;)LINK_[A-Z_]+" link_variables "${all_variables}") foreach(variable ${link_variables}) message(" ${variable}: ${${variable}}") endforeach() message(STATUS "Build configuration") if(CMAKE_BUILD_TYPE) message(" Build type: ${CMAKE_BUILD_TYPE}") else() message(" Build type: Set by IDE") endif() link-Link-3.0.2/AbletonLinkConfig.cmake0000644000175000017500000000267513303226525020143 0ustar zmoelnigzmoelnigif(CMAKE_VERSION VERSION_LESS 3.0) message(FATAL_ERROR "CMake 3.0 or greater is required") endif() add_library(Ableton::Link IMPORTED INTERFACE) set_property(TARGET Ableton::Link APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_LIST_DIR}/include ) # Force C++11 support for consuming targets set_property(TARGET Ableton::Link APPEND PROPERTY INTERFACE_COMPILE_FEATURES cxx_generalized_initializers ) if(UNIX) set_property(TARGET Ableton::Link APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS LINK_PLATFORM_UNIX=1 ) endif() if(APPLE) set_property(TARGET Ableton::Link APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS LINK_PLATFORM_MACOSX=1 ) elseif(WIN32) set_property(TARGET Ableton::Link APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS LINK_PLATFORM_WINDOWS=1 ) set_property(TARGET Ableton::Link APPEND PROPERTY INTERFACE_COMPILE_OPTIONS "/wd4503" # 'Identifier': decorated name length exceeded, name was truncated ) elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux") set_property(TARGET Ableton::Link APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS LINK_PLATFORM_LINUX=1 ) endif() include(${CMAKE_CURRENT_LIST_DIR}/cmake_include/AsioStandaloneConfig.cmake) set_property(TARGET Ableton::Link APPEND PROPERTY INTERFACE_LINK_LIBRARIES AsioStandalone::AsioStandalone ) set_property(TARGET Ableton::Link APPEND PROPERTY INTERFACE_SOURCES ${CMAKE_CURRENT_LIST_DIR}/include/ableton/Link.hpp ) link-Link-3.0.2/.travis.yml0000644000175000017500000001243413303226525015714 0ustar zmoelnigzmoelnig# Copyright (c) 2016 Ableton. All Rights Reserved. language: cpp sudo: required dist: trusty branches: only: - master matrix: include: ########################################################################### # Build with the main configuration on all the supported compilers ########################################################################### # Mac OS X / XCode 7.3, 64-bit Debug - os: osx env: WORDSIZE=64 CONFIGURATION=Debug osx_image: xcode7.3 compiler: clang # Mac OS X / XCode 7.3, 64-bit Release - os: osx env: WORDSIZE=64 CONFIGURATION=Release osx_image: xcode7.3 compiler: clang # Mac OS X / XCode 7.3, 32-bit Release - os: osx env: WORDSIZE=32 CONFIGURATION=Release osx_image: xcode7.3 compiler: clang # Mac OS X / XCode 9.0, 64-bit Debug - os: osx env: WORDSIZE=64 CONFIGURATION=Debug osx_image: xcode9 compiler: clang # Linux with Clang 3.6, 64-bit Debug, Alsa - os: linux compiler: clang addons: apt: sources: ['ubuntu-toolchain-r-test'] packages: ['clang-3.6', 'g++-5', 'valgrind', 'portaudio19-dev'] env: COMPILER=clang++-3.6 WORDSIZE=64 CONFIGURATION=Debug AUDIO=Alsa # Linux with Clang 3.6, 64-bit Release, Jack - os: linux compiler: clang addons: apt: sources: ['llvm-toolchain-precise-3.8', 'ubuntu-toolchain-r-test'] packages: ['clang-3.8', 'g++-5', 'valgrind', 'portaudio19-dev'] env: COMPILER=clang++-3.8 WORDSIZE=64 CONFIGURATION=Release AUDIO=Jack # Linux with Clang 5.0, 64-bit Debug, Alsa - os: linux compiler: clang addons: apt: sources: ['llvm-toolchain-trusty-5.0', 'ubuntu-toolchain-r-test'] packages: ['clang-5.0', 'g++-5', 'valgrind', 'portaudio19-dev'] env: COMPILER=clang++-5.0 WORDSIZE=64 CONFIGURATION=Debug AUDIO=Alsa # Linux with GCC 5.x, 32-bit Release # - os: linux # compiler: gcc # addons: # apt: # sources: ['ubuntu-toolchain-r-test'] # packages: ['g++-5', 'g++-5-multilib', 'linux-libc-dev:i386', 'valgrind:i386', # 'libxext-dev:i386', 'libglapi-mesa:i386', # 'libgl1-mesa-glx:i386', 'libgl1-mesa-dev:i386', # 'portaudio19-dev:i386', 'libglib2.0-0:i386'] # env: COMPILER=g++-5 WORDSIZE=32 CONFIGURATION=Release # Linux with GCC 5.x, 64-bit Release, Alsa - os: linux compiler: gcc addons: apt: sources: ['ubuntu-toolchain-r-test'] packages: ['g++-5', 'valgrind', 'portaudio19-dev'] env: COMPILER=g++-5 WORDSIZE=64 CONFIGURATION=Release AUDIO=Alsa # Linux with GCC 6.x, 64-bit Release, Alsa - os: linux compiler: gcc addons: apt: sources: ['ubuntu-toolchain-r-test'] packages: ['g++-6', 'valgrind', 'portaudio19-dev'] env: COMPILER=g++-6 WORDSIZE=64 CONFIGURATION=Release AUDIO=Alsa # Code formatting verification - os: linux compiler: clang env: CONFIGURATION=Formatting LLVM_VERSION=6.0 services: - docker before_install: # Do indentation check - | if [ "$CONFIGURATION" = "Formatting" ]; then docker pull dalg24/clang-format:18.04.0 docker run -v $TRAVIS_BUILD_DIR:/ws dalg24/clang-format python /ws/ci/check-formatting.py -c /usr/bin/clang-format-6.0 exit $? fi # Override Travis' CXX Flag - CXX=$COMPILER # Install homebrew packages for Mac OS X - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew update && brew install ninja qt5; fi # Install QT for Linux - | if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-add-repository -y ppa:beineri/opt-qt-5.10.1-trusty sudo apt-get -qy update sudo apt-get -qy install qtdeclarative5-dev fi install: - git submodule update --init --recursive script: - | set -e if [ "$TRAVIS_OS_NAME" = "osx" ]; then PATH=`brew --prefix qt5`/bin:${PATH} if [ "$WORDSIZE" -eq 64 ]; then python ci/configure.py --configuration $CONFIGURATION --generator Ninja --with-qt else python ci/configure.py --configuration $CONFIGURATION --generator Ninja --flags "\-DCMAKE_OSX_ARCHITECTURES=i386" fi python ci/build.py --configuration $CONFIGURATION --arguments "all -v" else if [ "$WORDSIZE" -eq 64 ]; then PATH=$PWD/5.5/gcc_64:${PATH} python ci/configure.py --configuration $CONFIGURATION --with-qt -a $AUDIO else PATH=$PWD/5.5/gcc:${PATH} python ci/configure.py --configuration $CONFIGURATION -a $AUDIO --flags "\-DCMAKE_CXX_FLAGS=\"\-m32\"" fi python ci/build.py --configuration $CONFIGURATION --arguments "VERBOSE=1 -j8" fi set +e # Build Tests and run with Valgrind (Linux 64-bit only). Mac OSX supports # valgrind via homebrew, but there is no bottle formula, so it must be # compiled by brew and this takes way too much time on the build server. - | set -e if [ "$TRAVIS_OS_NAME" = "linux" ]; then python ci/run-tests.py --target LinkCoreTest --valgrind python ci/run-tests.py --target LinkDiscoveryTest --valgrind else python ci/run-tests.py --target LinkCoreTest python ci/run-tests.py --target LinkDiscoveryTest fi set +e link-Link-3.0.2/TEST-PLAN.md0000644000175000017500000001007613303226525015434 0ustar zmoelnigzmoelnig# Test Plan Below are a set of user interactions that are expected to work consistently across all Link-enabled apps. In order to provide the best user experience, it's important that apps behave consistently with respect to these test cases. ## Tempo Changes ### TEMPO-1: Tempo changes should be transmitted between connected apps. - Open LinkHut, press **Play**, when using QLinkHut click the **Link** button to enable Link. - Open App and **enable** Link. - Without starting to play, change tempo in App **⇒** LinkHut clicks should speed up or slow down to match the tempo specified in the App. - Start playing in the App **⇒** App and LinkHut should be in sync - Change tempo in App and in LinkHut **⇒** App and LinkHut should remain in sync ### TEMPO-2: Opening an app with Link enabled should not change the tempo of an existing Link session. - Open App and **enable** Link. - Set App tempo to 100bpm. - Terminate App. - Open LinkHut, press **Play** and **enable** Link. - Set LinkHut tempo to 130bpm. - Open App and **enable** Link. **⇒** Link should be connected (“1 Link”) and the App and LinkHut’s tempo should both be 130bpm. ### TEMPO-3: When connected, loading a new document should not change the Link session tempo. - Open LinkHut, press **Play**, when using QLinkHut click the **Link** button to enable Link. - Set LinkHut tempo to 130bpm. - Open App and **enable** Link **⇒** LinkHut’s tempo should not change. - Load new Song/Set/Session with a tempo other than 130bpm **⇒** App and LinkHut tempo should both be 130bpm. ### TEMPO-4: Tempo range handling. - Open LinkHut, press **Play**, when using QLinkHut click the **Link** button to enable Link. - Open App, start Audio, and **enable** Link. - Change tempo in LinkHut to **20bpm** **⇒** App and LinkHut should stay in sync. - Change Tempo in LinkHut to **999bpm** **⇒** App and LinkHut should stay in sync. - If App does not support the full range of tempos supported by Link, it should stay in sync by switching to a multiple of the Link session tempo. ### TEMPO-5: Enabling Link does not change app's tempo if there is no Link session to join. - Open App, start playing. - Change App tempo to something other than the default. - **Enable** Link **⇒** App's tempo should not change. - Change App tempo to a new value (not the default). - **Disable** Link **⇒** App's tempo should not change. ## Beat Time These cases verify the continuity of beat time across Link operations. ### BEATTIME-1: Enabling Link does not change app's beat time if there is no Link session to join. - Open App, start playing. - **Enable** Link **⇒** No beat time jump or audible discontinuity should occur. - **Disable** Link **⇒** No beat time jump or audible discontinuity should occur. ### BEATTIME-2: App's beat time does not change if another participant joins its session. - Open App and **enable** Link. - Start playing. - Open LinkHut, whenusing QLinkHut **enable** Link **⇒** No beat time jump or audible discontinuity should occur in the App. **Note**: When joining an existing Link session, an app should adjust to the existing session's tempo and phase, which will usually result in a beat time jump. Apps that are already in a session should never have any kind of beat time or audio discontinuity when a new participant joins the session. ## Audio Engine These cases verify the correct implementation of latency compensation within an app's audio engine. ### AUDIOENGINE-1: Correct alignment of app audio with shared session - Connect the audio out of your computer to the audio in. Alternatively use [SoundFlower](https://github.com/mattingalls/Soundflower) to be able to record the output of your app and LinkHut. - Open LinkHut, press **Play**, , when using QLinkHut click the **Link** button to enable Link. - Open App and **enable** Link. - Start playing audio (preferably a short, click-like sample) with notes on the same beats as LinkHut. - Record audio within application of choice. - Validate whether onset of the sample aligns with the pulse generated by LinkHut (tolerance: less than 3 ms). link-Link-3.0.2/cmake_include/0000755000175000017500000000000013303226525016362 5ustar zmoelnigzmoelniglink-Link-3.0.2/cmake_include/ConfigureCompileFlags.cmake0000644000175000017500000001400613303226525023574 0ustar zmoelnigzmoelnigcmake_minimum_required(VERSION 3.0) set(build_flags_COMMON_LIST) set(build_flags_DEBUG_LIST) set(build_flags_RELEASE_LIST) # _ _ _ # | | | |_ __ (_)_ __ # | | | | '_ \| \ \/ / # | |_| | | | | |> < # \___/|_| |_|_/_/\_\ # if(UNIX) # Common flags for all Unix compilers set(build_flags_DEBUG_LIST "-DDEBUG=1" ) set(build_flags_RELEASE_LIST "-DNDEBUG=1" ) # Clang-specific flags if(${CMAKE_CXX_COMPILER_ID} MATCHES Clang) set(build_flags_COMMON_LIST ${build_flags_COMMON_LIST} "-Weverything" "-Werror" "-Wno-c++98-compat" "-Wno-c++98-compat-pedantic" "-Wno-deprecated" "-Wno-disabled-macro-expansion" "-Wno-exit-time-destructors" "-Wno-padded" "-Wno-reserved-id-macro" "-Wno-unknown-warning-option" "-Wno-unused-member-function" ) # GCC-specific flags # Unfortunately, we can't use -Werror on GCC, since there is no way to suppress the # warnings generated by -fpermissive. elseif(${CMAKE_CXX_COMPILER_ID} STREQUAL GNU) set(build_flags_COMMON_LIST ${build_flags_COMMON_LIST} "-Werror" "-Wno-multichar" ) endif() # ASan support if(LINK_ENABLE_ASAN) set(build_flags_COMMON_LIST ${build_flags_COMMON_LIST} "-fsanitize=address" "-fno-omit-frame-pointer" ) endif() # __ ___ _ # \ \ / (_)_ __ __| | _____ _____ # \ \ /\ / /| | '_ \ / _` |/ _ \ \ /\ / / __| # \ V V / | | | | | (_| | (_) \ V V /\__ \ # \_/\_/ |_|_| |_|\__,_|\___/ \_/\_/ |___/ # elseif(${CMAKE_CXX_COMPILER_ID} STREQUAL MSVC) add_definitions("/D_SCL_SECURE_NO_WARNINGS") if(LINK_BUILD_VLD) add_definitions("/DLINK_BUILD_VLD=1") else() add_definitions("/DLINK_BUILD_VLD=0") endif() set(build_flags_DEBUG_LIST "/DDEBUG=1" ) set(build_flags_RELEASE_LIST "/DNDEBUG=1" ) set(build_flags_COMMON_LIST ${build_flags_COMMON_LIST} "/MP" "/Wall" "/WX" "/EHsc" ############################# # Ignored compiler warnings # ############################# "/wd4061" # Enumerator 'identifier' in switch of enum 'enumeration' is not explicitly handled by a case label "/wd4265" # 'Class' : class has virtual functions, but destructor is not virtual "/wd4350" # Behavior change: 'member1' called instead of 'member2' "/wd4355" # 'This' : used in base member initializer list "/wd4365" # 'Action': conversion from 'type_1' to 'type_2', signed/unsigned mismatch "/wd4371" # Layout of class may have changed from a previous version of the compiler due to better packing of member 'member' "/wd4503" # 'Identifier': decorated name length exceeded, name was truncated "/wd4510" # 'Class': default constructor could not be generated "/wd4512" # 'Class': assignment operator could not be generated "/wd4514" # 'Function' : unreferenced inline function has been removed "/wd4571" # Informational: catch(...) semantics changed since Visual C++ 7.1; structured exceptions (SEH) are no longer caught "/wd4610" # 'Class': can never be instantiated - user defined constructor required "/wd4625" # 'Derived class' : copy constructor was implicitly defined as deleted because a base class copy constructor is inaccessible or deleted "/wd4626" # 'Derived class' : assignment operator was implicitly defined as deleted because a base class assignment operator is inaccessible or deleted "/wd4628" # digraphs not supported with -Ze. Character sequence 'digraph' not interpreted as alternate token for 'char' "/wd4640" # 'Instance': construction of local static object is not thread-safe "/wd4710" # 'Function': function not inlined "/wd4711" # Function 'function' selected for inline expansion "/wd4738" # Storing 32-bit float result in memory, possible loss of performance "/wd4820" # 'Bytes': bytes padding added after construct 'member_name' ) if(MSVC_VERSION VERSION_GREATER 1800) set(build_flags_COMMON_LIST ${build_flags_COMMON_LIST} "/wd4464" # Relative include path contains '..' "/wd4548" # Expression before comma has no effect; expected expression with side-effect "/wd4623" # 'Derived class': default constructor could not be generated because a base class default constructor is inaccessible "/wd4868" # Compiler may not enforce left-to-right evaluation order in braced initializer list "/wd5026" # Move constructor was implicitly defined as deleted "/wd5027" # Move assignment operator was implicitly defined as deleted ) endif() if(MSVC_VERSION VERSION_GREATER 1900) set(build_flags_COMMON_LIST ${build_flags_COMMON_LIST} "/wd4987" # nonstandard extension used: 'throw (...)' "/wd4774" # 'printf_s' : format string expected in argument 1 is not a string literal "/wd5039" # "pointer or reference to potentially throwing function passed to extern C function under -EHc. Undefined behavior may occur if this function throws an exception." ) endif() if(NOT LINK_BUILD_ASIO) set(build_flags_COMMON_LIST ${build_flags_COMMON_LIST} "/wd4917" # 'Symbol': a GUID can only be associated with a class, interface or namespace # This compiler warning is generated by Microsoft's own ocidl.h, which is # used when building the WASAPI driver. ) endif() endif() # ____ _ __ _ # / ___| ___| |_ / _| | __ _ __ _ ___ # \___ \ / _ \ __| | |_| |/ _` |/ _` / __| # ___) | __/ |_ | _| | (_| | (_| \__ \ # |____/ \___|\__| |_| |_|\__,_|\__, |___/ # |___/ # Translate lists to strings string(REPLACE ";" " " build_flags_COMMON "${build_flags_COMMON_LIST}") string(REPLACE ";" " " build_flags_DEBUG "${build_flags_DEBUG_LIST}") string(REPLACE ";" " " build_flags_RELEASE "${build_flags_RELEASE_LIST}") # Set flags for different build types set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${build_flags_COMMON}") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${build_flags_DEBUG}") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${build_flags_RELEASE}") link-Link-3.0.2/cmake_include/CatchConfig.cmake0000644000175000017500000000025113303226525021532 0ustar zmoelnigzmoelnigadd_library(Catch::Catch IMPORTED INTERFACE) set_property(TARGET Catch::Catch APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_SOURCE_DIR}/third_party/catch ) link-Link-3.0.2/cmake_include/AsioStandaloneConfig.cmake0000644000175000017500000000035113303226525023415 0ustar zmoelnigzmoelnigadd_library(AsioStandalone::AsioStandalone IMPORTED INTERFACE) set_property(TARGET AsioStandalone::AsioStandalone APPEND PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_LIST_DIR}/../modules/asio-standalone/asio/include ) link-Link-3.0.2/LICENSE.md0000644000175000017500000000144113303226525015203 0ustar zmoelnigzmoelnig# License Copyright 2016, Ableton AG, Berlin. All rights reserved. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . If you would like to incorporate Link into a proprietary software application, please contact . link-Link-3.0.2/include/0000755000175000017500000000000013303226525015222 5ustar zmoelnigzmoelniglink-Link-3.0.2/include/ableton/0000755000175000017500000000000013303226525016646 5ustar zmoelnigzmoelniglink-Link-3.0.2/include/ableton/test/0000755000175000017500000000000013303226525017625 5ustar zmoelnigzmoelniglink-Link-3.0.2/include/ableton/test/serial_io/0000755000175000017500000000000013303226525021573 5ustar zmoelnigzmoelniglink-Link-3.0.2/include/ableton/test/serial_io/Context.hpp0000644000175000017500000000503713303226525023735 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include #include #include #include namespace ableton { namespace test { namespace serial_io { class Context { public: Context(const SchedulerTree::TimePoint& now, const std::vector<::asio::ip::address>& ifAddrs, std::shared_ptr pScheduler) : mNow(now) , mIfAddrs(ifAddrs) , mpScheduler(std::move(pScheduler)) , mNextTimerId(0) { } ~Context() { if (mpScheduler != nullptr) { // Finish any pending tasks before shutting down mpScheduler->run(); } } Context(const Context&) = delete; Context& operator=(const Context&) = delete; Context(Context&& rhs) : mNow(rhs.mNow) , mIfAddrs(rhs.mIfAddrs) , mpScheduler(std::move(rhs.mpScheduler)) , mLog(std::move(rhs.mLog)) , mNextTimerId(rhs.mNextTimerId) { } template void async(Handler handler) { mpScheduler->async(std::move(handler)); } Context clone() { return {mNow, mIfAddrs, mpScheduler->makeChild()}; } using Timer = serial_io::Timer; Timer makeTimer() { return {mNextTimerId++, mNow, mpScheduler}; } using Log = util::NullLog; Log& log() { return mLog; } std::vector<::asio::ip::address> scanNetworkInterfaces() { return mIfAddrs; } private: const SchedulerTree::TimePoint& mNow; const std::vector<::asio::ip::address>& mIfAddrs; std::shared_ptr mpScheduler; Log mLog; SchedulerTree::TimerId mNextTimerId; }; } // namespace serial_io } // namespace test } // namespace ableton link-Link-3.0.2/include/ableton/test/serial_io/SchedulerTree.hpp0000644000175000017500000000534513303226525025051 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include #include #include namespace ableton { namespace test { namespace serial_io { class SchedulerTree { public: using TimePoint = std::chrono::system_clock::time_point; using TimerId = std::size_t; using TimerErrorCode = int; void run(); std::shared_ptr makeChild(); template void async(Handler handler) { mPendingHandlers.push_back(std::move(handler)); } template void setTimer(const TimerId timerId, const TimePoint expiration, Handler handler) { using namespace std; mTimers[make_pair(move(expiration), timerId)] = move(handler); } void cancelTimer(const TimerId timerId); // returns the time that the next timer in the subtree expires TimePoint nextTimerExpiration(); // triggers all timers in the subtree that expire at time t or before void triggerTimersUntil(const TimePoint t); private: // returns true if some work was done, false if there was none to do bool handlePending(); // returns the time that the next timer from this node expires TimePoint nextOwnTimerExpiration(); // Traversal function over children that cleans up children that // have been destroyed. template void withChildren(Fn fn) { auto it = begin(mChildren); while (it != end(mChildren)) { const auto childIt = it++; auto pChild = childIt->lock(); if (pChild) { fn(*pChild); } else { mChildren.erase(childIt); } } } using TimerHandler = std::function; using TimerMap = std::map, TimerHandler>; TimerMap mTimers; std::list> mPendingHandlers; std::list> mChildren; }; } // namespace serial_io } // namespace test } // namespace ableton link-Link-3.0.2/include/ableton/test/serial_io/Fixture.hpp0000644000175000017500000000435413303226525023740 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include #include namespace ableton { namespace test { namespace serial_io { class Fixture { public: Fixture() : mpScheduler(std::make_shared()) , mNow(std::chrono::milliseconds{123456789}) { } ~Fixture() { flush(); } Fixture(const Fixture&) = delete; Fixture& operator=(const Fixture&) = delete; Fixture(Fixture&&) = delete; Fixture& operator=(Fixture&&) = delete; void setNetworkInterfaces(std::vector<::asio::ip::address> ifAddrs) { mIfAddrs = std::move(ifAddrs); } Context makeIoContext() { return {mNow, mIfAddrs, mpScheduler}; } void flush() { mpScheduler->run(); } template void advanceTime(std::chrono::duration duration) { const auto target = mNow + duration; mpScheduler->run(); auto nextTimer = mpScheduler->nextTimerExpiration(); while (nextTimer <= target) { mNow = nextTimer; mpScheduler->triggerTimersUntil(mNow); mpScheduler->run(); nextTimer = mpScheduler->nextTimerExpiration(); } mNow = target; } private: std::shared_ptr mpScheduler; SchedulerTree::TimePoint mNow; std::vector<::asio::ip::address> mIfAddrs; }; } // namespace serial_io } // namespace test } // namespace ableton link-Link-3.0.2/include/ableton/test/serial_io/Socket.hpp0000644000175000017500000000433213303226525023536 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include namespace ableton { namespace test { namespace serial_io { struct Socket { using SendFn = std::function; struct ReceiveHandler { template void operator()(const asio::ip::udp::endpoint& from, const It begin, const It end) { std::vector buffer{begin, end}; mReceive(from, buffer); } std::function&> mReceive; }; using ReceiveFn = std::function; Socket(SendFn sendFn, ReceiveFn receiveFn) : mSendFn(std::move(sendFn)) , mReceiveFn(std::move(receiveFn)) { } std::size_t send( const uint8_t* const pData, const size_t numBytes, const asio::ip::udp::endpoint& to) { return mSendFn(pData, numBytes, to); } template void receive(Handler handler) { mReceiveFn(ReceiveHandler{ [handler](const asio::ip::udp::endpoint& from, const std::vector& buffer) { handler(from, begin(buffer), end(buffer)); }}); } asio::ip::udp::endpoint endpoint() const { return asio::ip::udp::endpoint({}, 0); } SendFn mSendFn; ReceiveFn mReceiveFn; }; } // namespace serial_io } // namespace test } // namespace ableton link-Link-3.0.2/include/ableton/test/serial_io/Timer.hpp0000644000175000017500000000467513303226525023400 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include namespace ableton { namespace test { namespace serial_io { struct Timer { using ErrorCode = SchedulerTree::TimerErrorCode; using TimePoint = SchedulerTree::TimePoint; Timer(const SchedulerTree::TimerId timerId, const TimePoint& now, std::shared_ptr pScheduler) : mId(timerId) , mNow(now) , mpScheduler(std::move(pScheduler)) { } ~Timer() { if (!mbMovedFrom) { cancel(); } } Timer(const Timer&) = delete; Timer(Timer&& rhs) : mId(rhs.mId) , mNow(rhs.mNow) , mExpiration(std::move(rhs.mExpiration)) , mpScheduler(std::move(rhs.mpScheduler)) { rhs.mbMovedFrom = true; } void expires_at(const TimePoint t) { if (t < mNow) { throw std::runtime_error("Setting timer in the past"); } else { cancel(); mExpiration = t; } } template void expires_from_now(std::chrono::duration duration) { expires_at(mNow + duration); } void cancel() { auto pScheduler = mpScheduler.lock(); pScheduler->cancelTimer(mId); } template void async_wait(Handler handler) { auto pScheduler = mpScheduler.lock(); pScheduler->setTimer(mId, mExpiration, std::move(handler)); } TimePoint now() const { return mNow; } const SchedulerTree::TimerId mId; const TimePoint& mNow; TimePoint mExpiration; std::weak_ptr mpScheduler; bool mbMovedFrom = false; }; } // namespace serial_io } // namespace test } // namespace ableton link-Link-3.0.2/include/ableton/test/CatchWrapper.hpp0000644000175000017500000000237113303226525022724 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once /*! * \brief Wrapper file for Catch library * * This file includes the Catch header for Link, and also disables some compiler warnings * which are specific to that library. */ #if defined(_MSC_VER) #pragma warning(push, 0) #pragma warning(disable : 4242) #pragma warning(disable : 4244) #pragma warning(disable : 4702) #endif #include #if defined(_MSC_VER) #pragma warning(pop) #endif link-Link-3.0.2/include/ableton/Link.ipp0000644000175000017500000001700113303226525020254 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include namespace ableton { namespace detail { inline Link::SessionState toSessionState( const link::ClientState& state, const bool isConnected) { const auto time = state.timeline.fromBeats(state.startStopState.beats); const auto startStopState = link::ApiStartStopState{state.startStopState.isPlaying, time}; return {{state.timeline, startStopState}, isConnected}; } inline link::IncomingClientState toIncomingClientState(const link::ApiState& state, const link::ApiState& originalState, const std::chrono::microseconds timestamp) { const auto timeline = originalState.timeline != state.timeline ? link::OptionalTimeline{state.timeline} : link::OptionalTimeline{}; const auto startStopState = originalState.startStopState != state.startStopState ? link::OptionalStartStopState{{state.startStopState.isPlaying, state.timeline.toBeats(state.startStopState.time), timestamp}} : link::OptionalStartStopState{}; return {timeline, startStopState, timestamp}; } } // namespace detail inline Link::Link(const double bpm) : mPeerCountCallback([](std::size_t) {}) , mTempoCallback([](link::Tempo) {}) , mStartStopCallback([](bool) {}) , mClock{} , mController(link::Tempo(bpm), [this](const std::size_t peers) { std::lock_guard lock(mCallbackMutex); mPeerCountCallback(peers); }, [this](const link::Tempo tempo) { std::lock_guard lock(mCallbackMutex); mTempoCallback(tempo); }, [this](const bool isPlaying) { std::lock_guard lock(mCallbackMutex); mStartStopCallback(isPlaying); }, mClock, util::injectVal(link::platform::IoContext{})) { } inline bool Link::isEnabled() const { return mController.isEnabled(); } inline void Link::enable(const bool bEnable) { mController.enable(bEnable); } inline bool Link::isStartStopSyncEnabled() const { return mController.isStartStopSyncEnabled(); } inline void Link::enableStartStopSync(bool bEnable) { mController.enableStartStopSync(bEnable); } inline std::size_t Link::numPeers() const { return mController.numPeers(); } template void Link::setNumPeersCallback(Callback callback) { std::lock_guard lock(mCallbackMutex); mPeerCountCallback = [callback](const std::size_t numPeers) { callback(numPeers); }; } template void Link::setTempoCallback(Callback callback) { std::lock_guard lock(mCallbackMutex); mTempoCallback = [callback](const link::Tempo tempo) { callback(tempo.bpm()); }; } template void Link::setStartStopCallback(Callback callback) { std::lock_guard lock(mCallbackMutex); mStartStopCallback = callback; } inline Link::Clock Link::clock() const { return mClock; } inline Link::SessionState Link::captureAudioSessionState() const { return detail::toSessionState(mController.clientStateRtSafe(), numPeers() > 0); } inline void Link::commitAudioSessionState(const Link::SessionState state) { mController.setClientStateRtSafe( detail::toIncomingClientState(state.mState, state.mOriginalState, mClock.micros())); } inline Link::SessionState Link::captureAppSessionState() const { return detail::toSessionState(mController.clientState(), numPeers() > 0); } inline void Link::commitAppSessionState(const Link::SessionState state) { mController.setClientState( detail::toIncomingClientState(state.mState, state.mOriginalState, mClock.micros())); } // Link::SessionState inline Link::SessionState::SessionState( const link::ApiState state, const bool bRespectQuantum) : mOriginalState(state) , mState(state) , mbRespectQuantum(bRespectQuantum) { } inline double Link::SessionState::tempo() const { return mState.timeline.tempo.bpm(); } inline void Link::SessionState::setTempo( const double bpm, const std::chrono::microseconds atTime) { const auto desiredTl = link::clampTempo( link::Timeline{link::Tempo(bpm), mState.timeline.toBeats(atTime), atTime}); mState.timeline.tempo = desiredTl.tempo; mState.timeline.timeOrigin = desiredTl.fromBeats(mState.timeline.beatOrigin); } inline double Link::SessionState::beatAtTime( const std::chrono::microseconds time, const double quantum) const { return link::toPhaseEncodedBeats(mState.timeline, time, link::Beats{quantum}) .floating(); } inline double Link::SessionState::phaseAtTime( const std::chrono::microseconds time, const double quantum) const { return link::phase(link::Beats{beatAtTime(time, quantum)}, link::Beats{quantum}) .floating(); } inline std::chrono::microseconds Link::SessionState::timeAtBeat( const double beat, const double quantum) const { return link::fromPhaseEncodedBeats( mState.timeline, link::Beats{beat}, link::Beats{quantum}); } inline void Link::SessionState::requestBeatAtTime( const double beat, std::chrono::microseconds time, const double quantum) { if (mbRespectQuantum) { time = timeAtBeat(link::nextPhaseMatch(link::Beats{beatAtTime(time, quantum)}, link::Beats{beat}, link::Beats{quantum}) .floating(), quantum); } forceBeatAtTime(beat, time, quantum); } inline void Link::SessionState::forceBeatAtTime( const double beat, const std::chrono::microseconds time, const double quantum) { // There are two components to the beat adjustment: a phase shift // and a beat magnitude adjustment. const auto curBeatAtTime = link::Beats{beatAtTime(time, quantum)}; const auto closestInPhase = link::closestPhaseMatch(curBeatAtTime, link::Beats{beat}, link::Beats{quantum}); mState.timeline = shiftClientTimeline(mState.timeline, closestInPhase - curBeatAtTime); // Now adjust the magnitude mState.timeline.beatOrigin = mState.timeline.beatOrigin + (link::Beats{beat} - closestInPhase); } inline void Link::SessionState::setIsPlaying( const bool isPlaying, const std::chrono::microseconds time) { mState.startStopState = {isPlaying, time}; } inline bool Link::SessionState::isPlaying() const { return mState.startStopState.isPlaying; } inline std::chrono::microseconds Link::SessionState::timeForIsPlaying() const { return mState.startStopState.time; } inline void Link::SessionState::requestBeatAtStartPlayingTime( const double beat, const double quantum) { if (isPlaying()) { requestBeatAtTime(beat, mState.startStopState.time, quantum); } } inline void Link::SessionState::setIsPlayingAndRequestBeatAtTime( bool isPlaying, std::chrono::microseconds time, double beat, double quantum) { mState.startStopState = {isPlaying, time}; requestBeatAtStartPlayingTime(beat, quantum); } } // namespace ableton link-Link-3.0.2/include/ableton/platforms/0000755000175000017500000000000013303226525020655 5ustar zmoelnigzmoelniglink-Link-3.0.2/include/ableton/platforms/posix/0000755000175000017500000000000013303226525022017 5ustar zmoelnigzmoelniglink-Link-3.0.2/include/ableton/platforms/posix/ScanIpIfAddrs.hpp0000644000175000017500000000571413303226525025151 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include #include #include #include namespace ableton { namespace platforms { namespace posix { namespace detail { // RAII type to make [get,free]ifaddrs function pairs exception safe class GetIfAddrs { public: GetIfAddrs() { if (getifaddrs(&interfaces)) // returns 0 on success { interfaces = NULL; } } ~GetIfAddrs() { if (interfaces) freeifaddrs(interfaces); } // RAII must not copy GetIfAddrs(GetIfAddrs&) = delete; GetIfAddrs& operator=(GetIfAddrs&) = delete; template void withIfAddrs(Function f) { if (interfaces) f(*interfaces); } private: struct ifaddrs* interfaces = NULL; }; } // namespace detail // Posix implementation of ip interface address scanner struct ScanIpIfAddrs { // Scan active network interfaces and return corresponding addresses // for all ip-based interfaces. std::vector<::asio::ip::address> operator()() { std::vector<::asio::ip::address> addrs; detail::GetIfAddrs getIfAddrs; getIfAddrs.withIfAddrs([&addrs](const struct ifaddrs& interfaces) { const struct ifaddrs* interface; for (interface = &interfaces; interface; interface = interface->ifa_next) { auto addr = reinterpret_cast(interface->ifa_addr); if (addr && interface->ifa_flags & IFF_UP) { if (addr->sin_family == AF_INET) { auto bytes = reinterpret_cast(&addr->sin_addr); addrs.emplace_back(asio::makeAddress<::asio::ip::address_v4>(bytes)); } else if (addr->sin_family == AF_INET6) { auto addr6 = reinterpret_cast(addr); auto bytes = reinterpret_cast(&addr6->sin6_addr); addrs.emplace_back(asio::makeAddress<::asio::ip::address_v6>(bytes)); } } } }); return addrs; } }; } // namespace posix } // namespace platforms } // namespace ableton link-Link-3.0.2/include/ableton/platforms/Config.hpp0000644000175000017500000000432013303226525022572 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #if defined(LINK_PLATFORM_WINDOWS) #include #include #include #elif defined(LINK_PLATFORM_MACOSX) #include #include #include #elif defined(LINK_PLATFORM_LINUX) #include #include #include #endif namespace ableton { namespace link { namespace platform { #if defined(LINK_PLATFORM_WINDOWS) using Clock = platforms::windows::Clock; using IoContext = platforms::asio::Context; #elif defined(LINK_PLATFORM_MACOSX) using Clock = platforms::darwin::Clock; using IoContext = platforms::asio::Context; #elif defined(LINK_PLATFORM_LINUX) using Clock = platforms::linux::ClockMonotonic; using IoContext = platforms::asio::Context; #endif using Controller = Controller; } // namespace platform } // namespace link } // namespace ableton link-Link-3.0.2/include/ableton/platforms/linux/0000755000175000017500000000000013303226525022014 5ustar zmoelnigzmoelniglink-Link-3.0.2/include/ableton/platforms/linux/Clock.hpp0000644000175000017500000000266313303226525023567 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include namespace ableton { namespace platforms { #ifdef linux #undef linux #endif namespace linux { template class Clock { public: std::chrono::microseconds micros() const { ::timespec ts; ::clock_gettime(CLOCK, &ts); std::uint64_t ns = ts.tv_sec * 1000000000ULL + ts.tv_nsec; return std::chrono::microseconds(ns / 1000ULL); } }; using ClockMonotonic = Clock; using ClockMonotonicRaw = Clock; } // namespace linux } // namespace platforms } // namespace ableton link-Link-3.0.2/include/ableton/platforms/linux/Linux.hpp0000644000175000017500000000174413303226525023632 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #ifndef ntohll #define ntohll(x) bswap_64(x) #endif #ifndef htonll #define htonll(x) bswap_64(x) #endif link-Link-3.0.2/include/ableton/platforms/windows/0000755000175000017500000000000013303226525022347 5ustar zmoelnigzmoelniglink-Link-3.0.2/include/ableton/platforms/windows/Clock.hpp0000644000175000017500000000331113303226525024111 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include namespace ableton { namespace platforms { namespace windows { struct Clock { using Ticks = std::int64_t; using Micros = std::chrono::microseconds; Clock() { LARGE_INTEGER frequency; QueryPerformanceFrequency(&frequency); mTicksToMicros = 1.0e6 / frequency.QuadPart; } Micros ticksToMicros(const Ticks ticks) const { return Micros{llround(mTicksToMicros * ticks)}; } Ticks microsToTicks(const Micros micros) const { return static_cast(micros.count() / mTicksToMicros); } Ticks ticks() const { LARGE_INTEGER count; QueryPerformanceCounter(&count); return count.QuadPart; } std::chrono::microseconds micros() const { return ticksToMicros(ticks()); } double mTicksToMicros; }; } // namespace windows } // namespace platforms } // namespace ableton link-Link-3.0.2/include/ableton/platforms/windows/ScanIpIfAddrs.hpp0000644000175000017500000001004513303226525025472 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include #include #include #include #include #pragma comment(lib, "iphlpapi.lib") #pragma comment(lib, "ws2_32.lib") namespace ableton { namespace platforms { namespace windows { namespace detail { // RAII type to make [get,free]ifaddrs function pairs exception safe class GetIfAddrs { public: GetIfAddrs() { const int MAX_TRIES = 3; // MSFT recommendation const int WORKING_BUFFER_SIZE = 15000; // MSFT recommendation DWORD adapter_addrs_buffer_size = WORKING_BUFFER_SIZE; for (int i = 0; i < MAX_TRIES; i++) { adapter_addrs = (IP_ADAPTER_ADDRESSES*)malloc(adapter_addrs_buffer_size); assert(adapter_addrs); DWORD error = ::GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_FRIENDLY_NAME, NULL, adapter_addrs, &adapter_addrs_buffer_size); if (error == ERROR_SUCCESS) { break; } // if buffer too small, use new buffer size in next iteration if (error == ERROR_BUFFER_OVERFLOW) { free(adapter_addrs); adapter_addrs = NULL; continue; } } } ~GetIfAddrs() { if (adapter_addrs) free(adapter_addrs); } // RAII must not copy GetIfAddrs(GetIfAddrs&) = delete; GetIfAddrs& operator=(GetIfAddrs&) = delete; template void withIfAddrs(Function f) { if (adapter_addrs) f(*adapter_addrs); } private: IP_ADAPTER_ADDRESSES* adapter_addrs; IP_ADAPTER_ADDRESSES* adapter; }; } // namespace detail struct ScanIpIfAddrs { // Scan active network interfaces and return corresponding addresses // for all ip-based interfaces. std::vector<::asio::ip::address> operator()() { std::vector<::asio::ip::address> addrs; detail::GetIfAddrs getIfAddrs; getIfAddrs.withIfAddrs([&addrs](const IP_ADAPTER_ADDRESSES& interfaces) { const IP_ADAPTER_ADDRESSES* networkInterface; for (networkInterface = &interfaces; networkInterface; networkInterface = networkInterface->Next) { for (IP_ADAPTER_UNICAST_ADDRESS* address = networkInterface->FirstUnicastAddress; NULL != address; address = address->Next) { auto family = address->Address.lpSockaddr->sa_family; if (AF_INET == family) { // IPv4 SOCKADDR_IN* addr4 = reinterpret_cast(address->Address.lpSockaddr); auto bytes = reinterpret_cast(&addr4->sin_addr); addrs.emplace_back(asio::makeAddress<::asio::ip::address_v4>(bytes)); } else if (AF_INET6 == family) { SOCKADDR_IN6* addr6 = reinterpret_cast(address->Address.lpSockaddr); auto bytes = reinterpret_cast(&addr6->sin6_addr); addrs.emplace_back(asio::makeAddress<::asio::ip::address_v6>(bytes)); } } } }); return addrs; } }; } // namespace windows } // namespace platforms } // namespace ableton link-Link-3.0.2/include/ableton/platforms/stl/0000755000175000017500000000000013303226525021457 5ustar zmoelnigzmoelniglink-Link-3.0.2/include/ableton/platforms/stl/Clock.hpp0000644000175000017500000000232213303226525023222 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include namespace ableton { namespace platforms { namespace stl { struct Clock { std::chrono::microseconds micros() const { using namespace std::chrono; auto nowInMicros = time_point_cast(steady_clock::now()); return nowInMicros.time_since_epoch(); } }; } // namespace stl } // namespace platforms } // namespace ableton link-Link-3.0.2/include/ableton/platforms/asio/0000755000175000017500000000000013303226525021610 5ustar zmoelnigzmoelniglink-Link-3.0.2/include/ableton/platforms/asio/Context.hpp0000644000175000017500000001162213303226525023747 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include #include #include #include namespace ableton { namespace platforms { namespace asio { template class Context { public: using Timer = AsioTimer; using Log = LogT; template using LockFreeCallbackDispatcher = LockFreeCallbackDispatcher; template using Socket = asio::Socket; Context() : Context(DefaultHandler{}) { } template explicit Context(ExceptionHandler exceptHandler) : mpService(new ::asio::io_service()) , mpWork(new ::asio::io_service::work(*mpService)) { mThread = std::thread{[](::asio::io_service& service, ExceptionHandler handler) { for (;;) { try { service.run(); break; } catch (const typename ExceptionHandler::Exception& exception) { handler(exception); } } }, std::ref(*mpService), std::move(exceptHandler)}; } Context(const Context&) = delete; Context(Context&& rhs) : mpService(std::move(rhs.mpService)) , mpWork(std::move(rhs.mpWork)) , mThread(std::move(rhs.mThread)) , mLog(std::move(rhs.mLog)) , mScanIpIfAddrs(std::move(rhs.mScanIpIfAddrs)) { } ~Context() { if (mpService) { mpWork.reset(); mThread.join(); } } template Socket openUnicastSocket(const ::asio::ip::address_v4& addr) { auto socket = Socket{*mpService}; socket.mpImpl->mSocket.set_option( ::asio::ip::multicast::enable_loopback(addr.is_loopback())); socket.mpImpl->mSocket.set_option(::asio::ip::multicast::outbound_interface(addr)); socket.mpImpl->mSocket.bind(::asio::ip::udp::endpoint{addr, 0}); return socket; } template Socket openMulticastSocket(const ::asio::ip::address_v4& addr) { auto socket = Socket{*mpService}; socket.mpImpl->mSocket.set_option(::asio::ip::udp::socket::reuse_address(true)); socket.mpImpl->mSocket.set_option( ::asio::socket_base::broadcast(!addr.is_loopback())); socket.mpImpl->mSocket.set_option( ::asio::ip::multicast::enable_loopback(addr.is_loopback())); socket.mpImpl->mSocket.set_option(::asio::ip::multicast::outbound_interface(addr)); socket.mpImpl->mSocket.bind({::asio::ip::address::from_string("0.0.0.0"), discovery::multicastEndpoint().port()}); socket.mpImpl->mSocket.set_option(::asio::ip::multicast::join_group( discovery::multicastEndpoint().address().to_v4(), addr)); return socket; } std::vector<::asio::ip::address> scanNetworkInterfaces() { return mScanIpIfAddrs(); } Timer makeTimer() const { return {*mpService}; } Log& log() { return mLog; } template void async(Handler handler) { mpService->post(std::move(handler)); } Context clone() const { return {}; } template Context clone(ExceptionHandler handler) const { return Context{std::move(handler)}; } private: // Default handler is hidden and defines a hidden exception type // that will never be thrown by other code, so it effectively does // not catch. struct DefaultHandler { struct Exception { }; void operator()(const Exception&) { } }; std::unique_ptr<::asio::io_service> mpService; std::unique_ptr<::asio::io_service::work> mpWork; std::thread mThread; Log mLog; ScanIpIfAddrs mScanIpIfAddrs; }; } // namespace asio } // namespace platforms } // namespace ableton link-Link-3.0.2/include/ableton/platforms/asio/Util.hpp0000644000175000017500000000246013303226525023240 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include namespace ableton { namespace platforms { namespace asio { // Utility for making v4 or v6 ip addresses from raw bytes in network byte-order template AsioAddrType makeAddress(const char* pAddr) { using namespace std; typename AsioAddrType::bytes_type bytes; copy(pAddr, pAddr + bytes.size(), begin(bytes)); return AsioAddrType{bytes}; } } // namespace asio } // namespace platforms } // namespace ableton link-Link-3.0.2/include/ableton/platforms/asio/AsioTimer.hpp0000644000175000017500000000701113303226525024214 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include namespace ableton { namespace platforms { namespace asio { // This implementation is based on the boost::asio::system_timer concept. // Since boost::system_timer doesn't support move semantics, we create a wrapper // with a unique_ptr to get a movable type. It also handles an inconvenient // aspect of asio timers, which is that you must explicitly guard against the // handler firing after cancellation. We handle this by use of the SafeAsyncHandler // utility. AsioTimer therefore guarantees that a handler will not be called after // the destruction of the timer, or after the timer has been canceled. class AsioTimer { public: using ErrorCode = ::asio::error_code; using TimePoint = std::chrono::system_clock::time_point; AsioTimer(::asio::io_service& io) : mpTimer(new ::asio::system_timer(io)) , mpAsyncHandler(std::make_shared()) { } ~AsioTimer() { // The timer may not be valid anymore if this instance was moved from if (mpTimer != nullptr) { // Ignore errors during cancellation cancel(); } } AsioTimer(const AsioTimer&) = delete; AsioTimer& operator=(const AsioTimer&) = delete; // Enable move construction but not move assignment. Move assignment // would get weird - would have to handle outstanding handlers AsioTimer(AsioTimer&& rhs) : mpTimer(std::move(rhs.mpTimer)) , mpAsyncHandler(std::move(rhs.mpAsyncHandler)) { } void expires_at(std::chrono::system_clock::time_point tp) { mpTimer->expires_at(std::move(tp)); } template void expires_from_now(T duration) { mpTimer->expires_from_now(std::move(duration)); } ErrorCode cancel() { ErrorCode ec; mpTimer->cancel(ec); mpAsyncHandler->mpHandler = nullptr; return ec; } template void async_wait(Handler handler) { *mpAsyncHandler = std::move(handler); mpTimer->async_wait(util::makeAsyncSafe(mpAsyncHandler)); } TimePoint now() const { return std::chrono::system_clock::now(); } private: struct AsyncHandler { template AsyncHandler& operator=(Handler handler) { mpHandler = [handler](ErrorCode ec) { handler(std::move(ec)); }; return *this; } void operator()(ErrorCode ec) { if (mpHandler) { mpHandler(std::move(ec)); } } std::function mpHandler; }; std::unique_ptr<::asio::system_timer> mpTimer; std::shared_ptr mpAsyncHandler; }; } // namespace asio } // namespace platforms } // namespace ableton link-Link-3.0.2/include/ableton/platforms/asio/AsioService.hpp0000644000175000017500000000500713303226525024537 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include namespace ableton { namespace platforms { namespace asio { class AsioService { public: using Timer = AsioTimer; AsioService() : AsioService(DefaultHandler{}) { } template explicit AsioService(ExceptionHandler exceptHandler) : mpWork(new ::asio::io_service::work(mService)) { mThread = std::thread{[](::asio::io_service& service, ExceptionHandler handler) { for (;;) { try { service.run(); break; } catch (const typename ExceptionHandler::Exception& exception) { handler(exception); } } }, std::ref(mService), std::move(exceptHandler)}; } ~AsioService() { mpWork.reset(); mThread.join(); } AsioTimer makeTimer() { return {mService}; } template void post(Handler handler) { mService.post(std::move(handler)); } ::asio::io_service mService; private: // Default handler is hidden and defines a hidden exception type // that will never be thrown by other code, so it effectively does // not catch. struct DefaultHandler { struct Exception { }; void operator()(const Exception&) { } }; std::unique_ptr<::asio::io_service::work> mpWork; std::thread mThread; }; } // namespace asio } // namespace platforms } // namespace ableton link-Link-3.0.2/include/ableton/platforms/asio/LockFreeCallbackDispatcher.hpp0000644000175000017500000000453513303226525027446 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include namespace ableton { namespace platforms { // Utility to signal invocation of a callback on another thread in a lock free manner. // The callback is evoked on a thread owned by the instance of this class. // // A condition variable is used to notify a waiting thread, but only if the required // lock can be acquired immediately. If that fails, we fall back on signaling // after a timeout. This gives us a guaranteed minimum signalling rate which is defined // by the fallbackPeriod parameter. template class LockFreeCallbackDispatcher { public: LockFreeCallbackDispatcher(Callback callback, Duration fallbackPeriod) : mCallback(std::move(callback)) , mFallbackPeriod(std::move(fallbackPeriod)) , mRunning(true) , mThread([this] { run(); }) { } ~LockFreeCallbackDispatcher() { mRunning = false; mCondition.notify_one(); mThread.join(); } void invoke() { if (mMutex.try_lock()) { mCondition.notify_one(); mMutex.unlock(); } } private: void run() { while (mRunning.load()) { { std::unique_lock lock(mMutex); mCondition.wait_for(lock, mFallbackPeriod); } mCallback(); } } Callback mCallback; Duration mFallbackPeriod; std::atomic mRunning; std::mutex mMutex; std::condition_variable mCondition; std::thread mThread; }; } // namespace platforms } // namespace ableton link-Link-3.0.2/include/ableton/platforms/asio/AsioWrapper.hpp0000644000175000017500000000415513303226525024562 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once /*! * \brief Wrapper file for AsioStandalone library * * This file includes all necessary headers from the AsioStandalone library which are used * by Link. */ #pragma push_macro("ASIO_STANDALONE") #define ASIO_STANDALONE 1 #pragma push_macro("ASIO_NO_TYPEID") #define ASIO_NO_TYPEID 1 #if defined(LINK_PLATFORM_WINDOWS) #pragma push_macro("INCL_EXTRA_HTON_FUNCTIONS") #define INCL_EXTRA_HTON_FUNCTIONS 1 #endif #if defined(WIN32) || defined(_WIN32) #if !defined(_WIN32_WINNT) #define _WIN32_WINNT 0x0501 #endif #endif #if defined(__clang__) #pragma clang diagnostic push #if __has_warning("-Wcomma") #pragma clang diagnostic ignored "-Wcomma" #endif #if __has_warning("-Wunused-local-typedef") #pragma clang diagnostic ignored "-Wunused-local-typedef" #endif #endif #if defined(_MSC_VER) #define _SCL_SECURE_NO_WARNINGS 1 #pragma warning(push, 0) #pragma warning(disable : 4242) #pragma warning(disable : 4702) #endif #include #include #if defined(LINK_PLATFORM_WINDOWS) #pragma pop_macro("INCL_EXTRA_HTON_FUNCTIONS") #endif #pragma pop_macro("ASIO_STANDALONE") #pragma pop_macro("ASIO_NO_TYPEID") #if defined(_MSC_VER) #pragma warning(pop) #undef _SCL_SECURE_NO_WARNINGS #endif #if defined(__clang__) #pragma clang diagnostic pop #endif link-Link-3.0.2/include/ableton/platforms/asio/Socket.hpp0000644000175000017500000000574713303226525023566 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include #include namespace ableton { namespace platforms { namespace asio { template struct Socket { Socket(::asio::io_service& io) : mpImpl(std::make_shared(io)) { } Socket(const Socket&) = delete; Socket& operator=(const Socket&) = delete; Socket(Socket&& rhs) : mpImpl(std::move(rhs.mpImpl)) { } std::size_t send(const uint8_t* const pData, const size_t numBytes, const ::asio::ip::udp::endpoint& to) { assert(numBytes < MaxPacketSize); return mpImpl->mSocket.send_to(::asio::buffer(pData, numBytes), to); } template void receive(Handler handler) { mpImpl->mHandler = std::move(handler); mpImpl->mSocket.async_receive_from( ::asio::buffer(mpImpl->mReceiveBuffer, MaxPacketSize), mpImpl->mSenderEndpoint, util::makeAsyncSafe(mpImpl)); } ::asio::ip::udp::endpoint endpoint() const { return mpImpl->mSocket.local_endpoint(); } struct Impl { Impl(::asio::io_service& io) : mSocket(io, ::asio::ip::udp::v4()) { } ~Impl() { // Ignore error codes in shutdown and close as the socket may // have already been forcibly closed ::asio::error_code ec; mSocket.shutdown(::asio::ip::udp::socket::shutdown_both, ec); mSocket.close(ec); } void operator()(const ::asio::error_code& error, const std::size_t numBytes) { if (!error && numBytes > 0 && numBytes <= MaxPacketSize) { const auto bufBegin = begin(mReceiveBuffer); mHandler(mSenderEndpoint, bufBegin, bufBegin + static_cast(numBytes)); } } ::asio::ip::udp::socket mSocket; ::asio::ip::udp::endpoint mSenderEndpoint; using Buffer = std::array; Buffer mReceiveBuffer; using ByteIt = typename Buffer::const_iterator; std::function mHandler; }; std::shared_ptr mpImpl; }; } // namespace asio } // namespace platforms } // namespace ableton link-Link-3.0.2/include/ableton/platforms/darwin/0000755000175000017500000000000013303226525022141 5ustar zmoelnigzmoelniglink-Link-3.0.2/include/ableton/platforms/darwin/Clock.hpp0000644000175000017500000000335013303226525023706 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include namespace ableton { namespace platforms { namespace darwin { struct Clock { using Ticks = std::uint64_t; using Micros = std::chrono::microseconds; Clock() { mach_timebase_info_data_t timeInfo; mach_timebase_info(&timeInfo); // numer / denom gives nanoseconds, we want microseconds mTicksToMicros = timeInfo.numer / (timeInfo.denom * 1000.); } Micros ticksToMicros(const Ticks ticks) const { return Micros{llround(mTicksToMicros * ticks)}; } Ticks microsToTicks(const Micros micros) const { return static_cast(micros.count() / mTicksToMicros); } Ticks ticks() const { return mach_absolute_time(); } std::chrono::microseconds micros() const { return ticksToMicros(ticks()); } double mTicksToMicros; }; } // namespace darwin } // namespace platforms } // namespace ableton link-Link-3.0.2/include/ableton/platforms/darwin/Darwin.hpp0000644000175000017500000000210113303226525024070 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once // ntohll and htonll are not defined in 10.7 SDK, so we provide a compatibility macro here #ifndef ntohll #define ntohll(x) __DARWIN_OSSwapInt64(x) #endif #ifndef htonll #define htonll(x) __DARWIN_OSSwapInt64(x) #endif link-Link-3.0.2/include/ableton/link/0000755000175000017500000000000013303226525017603 5ustar zmoelnigzmoelniglink-Link-3.0.2/include/ableton/link/Timeline.hpp0000644000175000017500000000570413303226525022070 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include #include #include #include namespace ableton { namespace link { // A tuple of (tempo, beats, time), with integral units // based on microseconds. This type establishes a bijection between // beats and wall time, given a valid tempo. It also serves as a // payload entry. struct Timeline { static const std::int32_t key = 'tmln'; static_assert(key == 0x746d6c6e, "Unexpected byte order"); Beats toBeats(const std::chrono::microseconds time) const { return beatOrigin + tempo.microsToBeats(time - timeOrigin); } std::chrono::microseconds fromBeats(const Beats beats) const { return timeOrigin + tempo.beatsToMicros(beats - beatOrigin); } friend bool operator==(const Timeline& lhs, const Timeline& rhs) { return std::tie(lhs.tempo, lhs.beatOrigin, lhs.timeOrigin) == std::tie(rhs.tempo, rhs.beatOrigin, rhs.timeOrigin); } friend bool operator!=(const Timeline& lhs, const Timeline& rhs) { return !(lhs == rhs); } // Model the NetworkByteStreamSerializable concept friend std::uint32_t sizeInByteStream(const Timeline& tl) { return discovery::sizeInByteStream(std::tie(tl.tempo, tl.beatOrigin, tl.timeOrigin)); } template friend It toNetworkByteStream(const Timeline& tl, It out) { return discovery::toNetworkByteStream( std::tie(tl.tempo, tl.beatOrigin, tl.timeOrigin), std::move(out)); } template static std::pair fromNetworkByteStream(It begin, It end) { using namespace std; using namespace discovery; Timeline timeline; auto result = Deserialize>::fromNetworkByteStream( move(begin), move(end)); tie(timeline.tempo, timeline.beatOrigin, timeline.timeOrigin) = move(result.first); return make_pair(move(timeline), move(result.second)); } Tempo tempo; Beats beatOrigin; std::chrono::microseconds timeOrigin; }; } // namespace link } // namespace ableton link-Link-3.0.2/include/ableton/link/CircularFifo.hpp0000644000175000017500000000415613303226525022672 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include #include namespace ableton { namespace link { // Single producer, single consumer lockfree Fifo template class CircularFifo { public: CircularFifo() : tail(0) , head(0) { assert(head.is_lock_free() && tail.is_lock_free()); } bool push(Type item) { const auto currentTail = tail.load(); const auto nextTail = nextIndex(currentTail); if (nextTail != head.load()) { data[currentTail] = std::move(item); tail.store(nextTail); return true; } return false; } Optional pop() { const auto currentHead = head.load(); if (currentHead == tail.load()) { return {}; } auto item = data[currentHead]; head.store(nextIndex(currentHead)); return Optional{std::move(item)}; } bool isEmpty() const { return tail == head; } private: size_t nextIndex(const size_t index) const { return (index + 1) % (size + 1); } size_t previousIndex(const size_t index) const { return (index + size) % (size + 1); } std::atomic_size_t tail; std::atomic_size_t head; std::array data; }; } // namespace link } // namespace ableton link-Link-3.0.2/include/ableton/link/Tempo.hpp0000644000175000017500000000503513303226525021403 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include namespace ableton { namespace link { struct Tempo : std::tuple { Tempo() = default; // Beats per minute explicit Tempo(const double bpm) : std::tuple(bpm) { } Tempo(const std::chrono::microseconds microsPerBeat) : std::tuple(60. * 1e6 / microsPerBeat.count()) { } double bpm() const { return std::get<0>(*this); } std::chrono::microseconds microsPerBeat() const { return std::chrono::microseconds{llround(60. * 1e6 / bpm())}; } // Given the tempo, convert a time to a beat value Beats microsToBeats(const std::chrono::microseconds micros) const { return Beats{micros.count() / static_cast(microsPerBeat().count())}; } // Given the tempo, convert a beat to a time value std::chrono::microseconds beatsToMicros(const Beats beats) const { return std::chrono::microseconds{llround(beats.floating() * microsPerBeat().count())}; } // Model the NetworkByteStreamSerializable concept friend std::uint32_t sizeInByteStream(const Tempo tempo) { return discovery::sizeInByteStream(tempo.microsPerBeat()); } template friend It toNetworkByteStream(const Tempo tempo, It out) { return discovery::toNetworkByteStream(tempo.microsPerBeat(), std::move(out)); } template static std::pair fromNetworkByteStream(It begin, It end) { auto result = discovery::Deserialize::fromNetworkByteStream( std::move(begin), std::move(end)); return std::make_pair(Tempo{std::move(result.first)}, std::move(result.second)); } }; } // namespace link } // namespace ableton link-Link-3.0.2/include/ableton/link/MeasurementService.hpp0000644000175000017500000001255313303226525024130 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace ableton { namespace link { template class MeasurementService { public: using Point = std::pair; using MeasurementInstance = Measurement, Log>; using MeasurementServicePingResponder = PingResponder, Log>; static const std::size_t kNumberThreads = 1; MeasurementService(asio::ip::address_v4 address, SessionId sessionId, GhostXForm ghostXForm, Clock clock, util::Injected log) : mClock(std::move(clock)) , mLog(std::move(log)) , mPingResponder(std::move(address), std::move(sessionId), std::move(ghostXForm), util::injectRef(mIo), mClock, mLog) { } MeasurementService(const MeasurementService&) = delete; MeasurementService(MeasurementService&&) = delete; ~MeasurementService() { // Clear the measurement map in the io service so that whatever // cleanup code executes in response to the destruction of the // measurement objects still have access to the io service mIo.post([this] { mMeasurementMap.clear(); }); } void updateNodeState(const SessionId& sessionId, const GhostXForm& xform) { mPingResponder.updateNodeState(sessionId, xform); } asio::ip::udp::endpoint endpoint() const { return mPingResponder.endpoint(); } // Measure the peer and invoke the handler with a GhostXForm template void measurePeer(const PeerState& state, const Handler handler) { using namespace std; mIo.post([this, state, handler] { const auto nodeId = state.nodeState.nodeId; auto addr = mPingResponder.endpoint().address().to_v4(); auto callback = CompletionCallback{*this, nodeId, handler}; try { mMeasurementMap[nodeId] = MeasurementInstance{state, move(callback), move(addr), mClock, mLog}; } catch (const runtime_error& err) { info(*mLog) << "Failed to measure. Reason: " << err.what(); handler(GhostXForm{}); } }); } static GhostXForm filter( std::vector::const_iterator begin, std::vector::const_iterator end) { using namespace std; using std::chrono::microseconds; Kalman<5> kalman; for (auto it = begin; it != end; ++it) { kalman.iterate(it->second - it->first); } return GhostXForm{1, microseconds(llround(kalman.getValue()))}; } private: template struct CompletionCallback { void operator()(const std::vector data) { using namespace std; using std::chrono::microseconds; // Post this to the measurement service's io service so that we // don't delete the measurement object in its stack. Capture all // needed data separately from this, since this object may be // gone by the time the block gets executed. auto nodeId = mNodeId; auto handler = mHandler; auto& measurementMap = mService.mMeasurementMap; mService.mIo.post([nodeId, handler, &measurementMap, data] { const auto it = measurementMap.find(nodeId); if (it != measurementMap.end()) { if (data.empty()) { handler(GhostXForm{}); } else { handler(MeasurementService::filter(begin(data), end(data))); } measurementMap.erase(it); } }); } MeasurementService& mService; NodeId mNodeId; Handler mHandler; }; // Make sure the measurement map outlives the io service so that the rest of // the members are guaranteed to be valid when any final handlers // are begin run. using MeasurementMap = std::map; MeasurementMap mMeasurementMap; Clock mClock; util::Injected mLog; platforms::asio::AsioService mIo; MeasurementServicePingResponder mPingResponder; }; } // namespace link } // namespace ableton link-Link-3.0.2/include/ableton/link/v1/0000755000175000017500000000000013303226525020131 5ustar zmoelnigzmoelniglink-Link-3.0.2/include/ableton/link/v1/Messages.hpp0000644000175000017500000001022413303226525022410 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include namespace ableton { namespace link { namespace v1 { // The maximum size of a message, in bytes const std::size_t kMaxMessageSize = 512; // Utility typedef for an array of bytes of maximum message size using MessageBuffer = std::array; using MessageType = uint8_t; const MessageType kPing = 1; const MessageType kPong = 2; struct MessageHeader { MessageType messageType; friend std::uint32_t sizeInByteStream(const MessageHeader& header) { return discovery::sizeInByteStream(header.messageType); } template friend It toNetworkByteStream(const MessageHeader& header, It out) { return discovery::toNetworkByteStream(header.messageType, std::move(out)); } template static std::pair fromNetworkByteStream(It begin, const It end) { using namespace discovery; MessageHeader header; std::tie(header.messageType, begin) = Deserialize::fromNetworkByteStream(begin, end); return std::make_pair(std::move(header), std::move(begin)); } }; namespace detail { // Types that are only used in the sending/parsing of messages, not // publicly exposed. using ProtocolHeader = std::array; const ProtocolHeader kProtocolHeader = {{'_', 'l', 'i', 'n', 'k', '_', 'v', 1}}; // Must have at least kMaxMessageSize bytes available in the output stream template It encodeMessage(const MessageType messageType, const Payload& payload, It out) { using namespace std; const MessageHeader header = {messageType}; const auto messageSize = kProtocolHeader.size() + sizeInByteStream(header) + sizeInByteStream(payload); if (messageSize < kMaxMessageSize) { return toNetworkByteStream( payload, toNetworkByteStream( header, copy(begin(kProtocolHeader), end(kProtocolHeader), move(out)))); } else { throw range_error("Exceeded maximum message size"); } } } // namespace detail template It pingMessage(const Payload& payload, It out) { return detail::encodeMessage(kPing, payload, std::move(out)); } template It pongMessage(const Payload& payload, It out) { return detail::encodeMessage(kPong, payload, std::move(out)); } template std::pair parseMessageHeader(It bytesBegin, const It bytesEnd) { using ItDiff = typename std::iterator_traits::difference_type; MessageHeader header = {}; const auto protocolHeaderSize = discovery::sizeInByteStream(detail::kProtocolHeader); const auto minMessageSize = static_cast(protocolHeaderSize + sizeInByteStream(header)); // If there are enough bytes in the stream to make a header and if // the first bytes in the stream are the protocol header, then // proceed to parse the stream. if (std::distance(bytesBegin, bytesEnd) >= minMessageSize && std::equal( begin(detail::kProtocolHeader), end(detail::kProtocolHeader), bytesBegin)) { std::tie(header, bytesBegin) = MessageHeader::fromNetworkByteStream(bytesBegin + protocolHeaderSize, bytesEnd); } return std::make_pair(std::move(header), std::move(bytesBegin)); } } // namespace v1 } // namespace link } // namespace ableton link-Link-3.0.2/include/ableton/link/Beats.hpp0000644000175000017500000000516613303226525021362 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include #include namespace ableton { namespace link { struct Beats : std::tuple { Beats() = default; explicit Beats(const double beats) : std::tuple(llround(beats * 1e6)) { } explicit Beats(const std::int64_t microBeats) : std::tuple(microBeats) { } double floating() const { return microBeats() / 1e6; } std::int64_t microBeats() const { return std::get<0>(*this); } Beats operator-() const { return Beats{-microBeats()}; } friend Beats abs(const Beats b) { return Beats{std::abs(b.microBeats())}; } friend Beats operator+(const Beats lhs, const Beats rhs) { return Beats{lhs.microBeats() + rhs.microBeats()}; } friend Beats operator-(const Beats lhs, const Beats rhs) { return Beats{lhs.microBeats() - rhs.microBeats()}; } friend Beats operator%(const Beats lhs, const Beats rhs) { return rhs == Beats{0.} ? Beats{0.} : Beats{lhs.microBeats() % rhs.microBeats()}; } // Model the NetworkByteStreamSerializable concept friend std::uint32_t sizeInByteStream(const Beats beats) { return discovery::sizeInByteStream(beats.microBeats()); } template friend It toNetworkByteStream(const Beats beats, It out) { return discovery::toNetworkByteStream(beats.microBeats(), std::move(out)); } template static std::pair fromNetworkByteStream(It begin, It end) { auto result = discovery::Deserialize::fromNetworkByteStream( std::move(begin), std::move(end)); return std::make_pair(Beats{result.first}, std::move(result.second)); } }; } // namespace link } // namespace ableton link-Link-3.0.2/include/ableton/link/PayloadEntries.hpp0000644000175000017500000000750313303226525023244 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include namespace ableton { namespace link { struct HostTime { static const std::int32_t key = '__ht'; static_assert(key == 0x5f5f6874, "Unexpected byte order"); HostTime() = default; HostTime(const std::chrono::microseconds tm) : time(tm) { } // Model the NetworkByteStreamSerializable concept friend std::uint32_t sizeInByteStream(const HostTime& sht) { return discovery::sizeInByteStream(std::move(sht.time)); } template friend It toNetworkByteStream(const HostTime& sht, It out) { return discovery::toNetworkByteStream(std::move(sht.time), std::move(out)); } template static std::pair fromNetworkByteStream(It begin, It end) { using namespace std; auto result = discovery::Deserialize::fromNetworkByteStream( move(begin), move(end)); return make_pair(HostTime{move(result.first)}, move(result.second)); } std::chrono::microseconds time; }; struct GHostTime { static const std::int32_t key = '__gt'; static_assert(key == 0x5f5f6774, "Unexpected byte order"); GHostTime() = default; GHostTime(const std::chrono::microseconds tm) : time(tm) { } // Model the NetworkByteStreamSerializable concept friend std::uint32_t sizeInByteStream(const GHostTime& dgt) { return discovery::sizeInByteStream(std::move(dgt.time)); } template friend It toNetworkByteStream(const GHostTime& dgt, It out) { return discovery::toNetworkByteStream(std::move(dgt.time), std::move(out)); } template static std::pair fromNetworkByteStream(It begin, It end) { using namespace std; auto result = discovery::Deserialize::fromNetworkByteStream( move(begin), move(end)); return make_pair(GHostTime{move(result.first)}, move(result.second)); } std::chrono::microseconds time; }; struct PrevGHostTime { static const std::int32_t key = '_pgt'; static_assert(key == 0x5f706774, "Unexpected byte order"); PrevGHostTime() = default; PrevGHostTime(const std::chrono::microseconds tm) : time(tm) { } // Model the NetworkByteStreamSerializable concept friend std::uint32_t sizeInByteStream(const PrevGHostTime& dgt) { return discovery::sizeInByteStream(std::move(dgt.time)); } template friend It toNetworkByteStream(const PrevGHostTime& pdgt, It out) { return discovery::toNetworkByteStream(std::move(pdgt.time), std::move(out)); } template static std::pair fromNetworkByteStream(It begin, It end) { using namespace std; auto result = discovery::Deserialize::fromNetworkByteStream( move(begin), move(end)); return make_pair(PrevGHostTime{move(result.first)}, move(result.second)); } std::chrono::microseconds time; }; } // namespace link } // namespace ableton link-Link-3.0.2/include/ableton/link/Optional.hpp0000644000175000017500000000370313303226525022104 0ustar zmoelnigzmoelnig/* Copyright 2017, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include namespace ableton { namespace link { // Subset of the C++ 17 std::optional API. T has to be default constructible. template struct Optional { Optional() : mHasValue(false) { } explicit Optional(T value) : mValue(std::move(value)) , mHasValue(true) { } Optional(const Optional&) = default; Optional(Optional&& other) : mValue(std::move(other.mValue)) , mHasValue(other.mHasValue) { } Optional& operator=(const Optional&) = default; Optional& operator=(Optional&& other) { mValue = std::move(other.mValue); mHasValue = other.mHasValue; return *this; } explicit operator bool() const { return mHasValue; } const T& operator*() const { assert(mHasValue); return mValue; } T& operator*() { assert(mHasValue); return mValue; } const T* operator->() const { assert(mHasValue); return &mValue; } T* operator->() { assert(mHasValue); return &mValue; } private: T mValue; bool mHasValue; }; } // namespace link } // namespace ableton link-Link-3.0.2/include/ableton/link/MeasurementEndpointV4.hpp0000644000175000017500000000447213303226525024523 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include namespace ableton { namespace link { struct MeasurementEndpointV4 { static const std::int32_t key = 'mep4'; static_assert(key == 0x6d657034, "Unexpected byte order"); // Model the NetworkByteStreamSerializable concept friend std::uint32_t sizeInByteStream(const MeasurementEndpointV4 mep) { return discovery::sizeInByteStream( static_cast(mep.ep.address().to_v4().to_ulong())) + discovery::sizeInByteStream(mep.ep.port()); } template friend It toNetworkByteStream(const MeasurementEndpointV4 mep, It out) { return discovery::toNetworkByteStream(mep.ep.port(), discovery::toNetworkByteStream( static_cast(mep.ep.address().to_v4().to_ulong()), std::move(out))); } template static std::pair fromNetworkByteStream(It begin, It end) { using namespace std; auto addrRes = discovery::Deserialize::fromNetworkByteStream(move(begin), end); auto portRes = discovery::Deserialize::fromNetworkByteStream( move(addrRes.second), end); return make_pair(MeasurementEndpointV4{{asio::ip::address_v4{move(addrRes.first)}, move(portRes.first)}}, move(portRes.second)); } asio::ip::udp::endpoint ep; }; } // namespace link } // namespace ableton link-Link-3.0.2/include/ableton/link/Controller.hpp0000644000175000017500000005750113303226525022447 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include #include #include #include #include #include #include #include #include namespace ableton { namespace link { namespace detail { template GhostXForm initXForm(const Clock& clock) { // Make the current time map to a ghost time of 0 with ghost time // increasing at the same rate as clock time return {1.0, -clock.micros()}; } template inline SessionState initSessionState(const Tempo tempo, const Clock& clock) { using namespace std::chrono; return {clampTempo(Timeline{tempo, Beats{0.}, microseconds{0}}), StartStopState{false, Beats{0.}, microseconds{0}}, initXForm(clock)}; } inline ClientState initClientState(const SessionState& sessionState) { const auto hostTime = sessionState.ghostXForm.ghostToHost(std::chrono::microseconds{0}); return { Timeline{sessionState.timeline.tempo, sessionState.timeline.beatOrigin, hostTime}, StartStopState{sessionState.startStopState.isPlaying, sessionState.startStopState.beats, hostTime}}; } inline RtClientState initRtClientState(const ClientState& clientState) { using namespace std::chrono; return { clientState.timeline, clientState.startStopState, microseconds{0}, microseconds{0}}; } // The timespan in which local modifications to the timeline will be // preferred over any modifications coming from the network. const auto kLocalModGracePeriod = std::chrono::milliseconds(1000); const auto kRtHandlerFallbackPeriod = kLocalModGracePeriod / 2; inline StartStopState selectPreferredStartStopState( const StartStopState currentStartStopState, const StartStopState startStopState) { return startStopState.timestamp > currentStartStopState.timestamp ? startStopState : currentStartStopState; } inline StartStopState mapStartStopStateFromSessionToClient( const StartStopState& sessionStartStopState, const Timeline& clientTimeline, const Timeline& sessionTimeline, const GhostXForm& xForm) { const auto clientBeats = clientTimeline.toBeats( xForm.ghostToHost(sessionTimeline.fromBeats(sessionStartStopState.beats))); const auto clientTime = xForm.ghostToHost(sessionStartStopState.timestamp); return StartStopState{sessionStartStopState.isPlaying, clientBeats, clientTime}; } inline StartStopState mapStartStopStateFromClientToSession( const StartStopState& clientStartStopState, const Timeline& clientTimeline, const Timeline& sessionTimeline, const GhostXForm& xForm) { const auto sessionBeats = sessionTimeline.toBeats( xForm.hostToGhost(clientTimeline.fromBeats(clientStartStopState.beats))); const auto sessionTime = xForm.hostToGhost(clientStartStopState.timestamp); return StartStopState{clientStartStopState.isPlaying, sessionBeats, sessionTime}; } } // namespace detail // function types corresponding to the Controller callback type params using PeerCountCallback = std::function; using TempoCallback = std::function; using StartStopStateCallback = std::function; // The main Link controller template class Controller { public: Controller(Tempo tempo, PeerCountCallback peerCallback, TempoCallback tempoCallback, StartStopStateCallback startStopStateCallback, Clock clock, util::Injected io) : mTempoCallback(std::move(tempoCallback)) , mStartStopStateCallback(std::move(startStopStateCallback)) , mClock(std::move(clock)) , mNodeId(NodeId::random()) , mSessionId(mNodeId) , mSessionState(detail::initSessionState(tempo, mClock)) , mClientState(detail::initClientState(mSessionState)) , mLastIsPlayingForStartStopStateCallback(false) , mRtClientState(detail::initRtClientState(mClientState)) , mHasPendingRtClientStates(false) , mSessionPeerCounter(*this, std::move(peerCallback)) , mEnabled(false) , mStartStopSyncEnabled(false) , mIo(std::move(io)) , mRtClientStateSetter(*this) , mPeers(util::injectRef(*mIo), std::ref(mSessionPeerCounter), SessionTimelineCallback{*this}, SessionStartStopStateCallback{*this}) , mSessions( {mSessionId, mSessionState.timeline, {mSessionState.ghostXForm, mClock.micros()}}, util::injectRef(mPeers), MeasurePeer{*this}, JoinSessionCallback{*this}, util::injectRef(*mIo), mClock) , mDiscovery(std::make_pair(NodeState{mNodeId, mSessionId, mSessionState.timeline, mSessionState.startStopState}, mSessionState.ghostXForm), GatewayFactory{*this}, util::injectVal(mIo->clone(UdpSendExceptionHandler{*this}))) { } Controller(const Controller&) = delete; Controller(Controller&&) = delete; Controller& operator=(const Controller&) = delete; Controller& operator=(Controller&&) = delete; void enable(const bool bEnable) { const bool bWasEnabled = mEnabled.exchange(bEnable); if (bWasEnabled != bEnable) { mIo->async([this, bEnable] { if (bEnable) { // Always reset when first enabling to avoid hijacking // tempo in existing sessions resetState(); } mDiscovery.enable(bEnable); }); } } bool isEnabled() const { return mEnabled; } void enableStartStopSync(const bool bEnable) { mStartStopSyncEnabled = bEnable; } bool isStartStopSyncEnabled() const { return mStartStopSyncEnabled; } std::size_t numPeers() const { return mSessionPeerCounter.mSessionPeerCount; } // Get the current Link client state. Thread-safe but may block, so // it cannot be used from audio thread. ClientState clientState() const { std::lock_guard lock(mClientStateGuard); return mClientState; } // Set the client state to be used, starting at the given time. // Thread-safe but may block, so it cannot be used from audio thread. void setClientState(IncomingClientState newClientState) { { std::lock_guard lock(mClientStateGuard); if (newClientState.timeline) { *newClientState.timeline = clampTempo(*newClientState.timeline); mClientState.timeline = *newClientState.timeline; } if (newClientState.startStopState) { // Prevent updating client start stop state with an outdated start stop state *newClientState.startStopState = detail::selectPreferredStartStopState( mClientState.startStopState, *newClientState.startStopState); mClientState.startStopState = *newClientState.startStopState; } } mIo->async([this, newClientState] { handleClientState(newClientState); }); } // Non-blocking client state access for a realtime context. NOT // thread-safe. Must not be called from multiple threads // concurrently and must not be called concurrently with setClientStateRtSafe. ClientState clientStateRtSafe() const { // Respect the session state guard and the client state guard but don't // block on them. If we can't access one or both because of concurrent modification // we fall back to our cached version of the timeline and/or start stop state. if (!mHasPendingRtClientStates) { const auto now = mClock.micros(); const auto timelineGracePeriodOver = now - mRtClientState.timelineTimestamp > detail::kLocalModGracePeriod; const auto startStopStateGracePeriodOver = now - mRtClientState.startStopStateTimestamp > detail::kLocalModGracePeriod; if ((timelineGracePeriodOver || startStopStateGracePeriodOver) && mClientStateGuard.try_lock()) { const auto clientState = mClientState; mClientStateGuard.unlock(); if (timelineGracePeriodOver && clientState.timeline != mRtClientState.timeline) { mRtClientState.timeline = clientState.timeline; } if (startStopStateGracePeriodOver && clientState.startStopState != mRtClientState.startStopState) { mRtClientState.startStopState = clientState.startStopState; } } } return {mRtClientState.timeline, mRtClientState.startStopState}; } // should only be called from the audio thread void setClientStateRtSafe(IncomingClientState newClientState) { if (!newClientState.timeline && !newClientState.startStopState) { return; } if (newClientState.timeline) { *newClientState.timeline = clampTempo(*newClientState.timeline); } if (newClientState.startStopState) { // Prevent updating client start stop state with an outdated start stop state *newClientState.startStopState = detail::selectPreferredStartStopState( mRtClientState.startStopState, *newClientState.startStopState); } // This flag ensures that mRtClientState is only updated after all incoming // client states were processed mHasPendingRtClientStates = true; // This will fail in case the Fifo in the RtClientStateSetter is full. This indicates // a very high rate of calls to the setter. In this case we ignore one value because // we expect the setter to be called again soon. if (mRtClientStateSetter.tryPush(newClientState)) { const auto now = mClock.micros(); // Cache the new timeline and StartStopState for serving back to the client if (newClientState.timeline) { // Cache the new timeline and StartStopState for serving back to the client mRtClientState.timeline = *newClientState.timeline; mRtClientState.timelineTimestamp = makeRtTimestamp(now); } if (newClientState.startStopState) { mRtClientState.startStopState = *newClientState.startStopState; mRtClientState.startStopStateTimestamp = makeRtTimestamp(now); } } } private: std::chrono::microseconds makeRtTimestamp(const std::chrono::microseconds now) const { return isEnabled() ? now : std::chrono::microseconds(0); } void invokeStartStopStateCallbackIfChanged() { bool shouldInvokeCallback = false; { std::lock_guard lock(mClientStateGuard); shouldInvokeCallback = mLastIsPlayingForStartStopStateCallback != mClientState.startStopState.isPlaying; mLastIsPlayingForStartStopStateCallback = mClientState.startStopState.isPlaying; } if (shouldInvokeCallback) { mStartStopStateCallback(mLastIsPlayingForStartStopStateCallback); } } void updateDiscovery() { // Push the change to the discovery service mDiscovery.updateNodeState( std::make_pair(NodeState{mNodeId, mSessionId, mSessionState.timeline, mSessionState.startStopState}, mSessionState.ghostXForm)); } void updateSessionTiming(const Timeline newTimeline, const GhostXForm newXForm) { const auto oldTimeline = mSessionState.timeline; const auto oldXForm = mSessionState.ghostXForm; if (oldTimeline != newTimeline || oldXForm != newXForm) { { std::lock_guard lock(mSessionStateGuard); mSessionState.timeline = newTimeline; mSessionState.ghostXForm = newXForm; } // Update the client timeline based on the new session timing data { std::lock_guard lock(mClientStateGuard); mClientState.timeline = updateClientTimelineFromSession(mClientState.timeline, mSessionState.timeline, mClock.micros(), mSessionState.ghostXForm); } if (oldTimeline.tempo != newTimeline.tempo) { mTempoCallback(newTimeline.tempo); } } } void handleTimelineFromSession(SessionId id, Timeline timeline) { debug(mIo->log()) << "Received timeline with tempo: " << timeline.tempo.bpm() << " for session: " << id; updateSessionTiming(mSessions.sawSessionTimeline(std::move(id), std::move(timeline)), mSessionState.ghostXForm); updateDiscovery(); } void resetSessionStartStopState() { mSessionState.startStopState = StartStopState{}; } void handleStartStopStateFromSession(SessionId sessionId, StartStopState startStopState) { debug(mIo->log()) << "Received start stop state. isPlaying: " << startStopState.isPlaying << ", beats: " << startStopState.beats.floating() << ", time: " << startStopState.timestamp.count() << " for session: " << sessionId; if (sessionId == mSessionId && startStopState.timestamp > mSessionState.startStopState.timestamp) { mSessionState.startStopState = startStopState; // Always propagate the session start stop state so even a client that doesn't have // the feature enabled can function as a relay. updateDiscovery(); if (mStartStopSyncEnabled) { { std::lock_guard lock(mClientStateGuard); mClientState.startStopState = detail::mapStartStopStateFromSessionToClient(startStopState, mClientState.timeline, mSessionState.timeline, mSessionState.ghostXForm); } invokeStartStopStateCallbackIfChanged(); } } } void handleClientState(const IncomingClientState clientState) { auto mustUpdateDiscovery = false; if (clientState.timeline) { auto sessionTimeline = updateSessionTimelineFromClient(mSessionState.timeline, *clientState.timeline, clientState.timelineTimestamp, mSessionState.ghostXForm); mSessions.resetTimeline(sessionTimeline); mPeers.setSessionTimeline(mSessionId, sessionTimeline); updateSessionTiming(std::move(sessionTimeline), mSessionState.ghostXForm); mustUpdateDiscovery = true; } if (mStartStopSyncEnabled && clientState.startStopState) { // Prevent updating with an outdated start stop state const auto newGhostTime = mSessionState.ghostXForm.hostToGhost(clientState.startStopState->timestamp); if (newGhostTime > mSessionState.startStopState.timestamp) { { std::lock_guard lock(mClientStateGuard); mSessionState.startStopState = detail::mapStartStopStateFromClientToSession(*clientState.startStopState, mClientState.timeline, mSessionState.timeline, mSessionState.ghostXForm); mClientState.startStopState = *clientState.startStopState; } mustUpdateDiscovery = true; } } if (mustUpdateDiscovery) { updateDiscovery(); } invokeStartStopStateCallbackIfChanged(); } void handleRtClientState(IncomingClientState clientState) { { std::lock_guard lock(mClientStateGuard); if (clientState.timeline) { mClientState.timeline = *clientState.timeline; } if (clientState.startStopState) { // Prevent updating client start stop state with an outdated start stop state *clientState.startStopState = detail::selectPreferredStartStopState( mClientState.startStopState, *clientState.startStopState); mClientState.startStopState = *clientState.startStopState; } } handleClientState(clientState); mHasPendingRtClientStates = false; } void joinSession(const Session& session) { const bool sessionIdChanged = mSessionId != session.sessionId; mSessionId = session.sessionId; // Prevent passing the start stop state of the previous session to the new one. if (sessionIdChanged) { resetSessionStartStopState(); } updateSessionTiming(session.timeline, session.measurement.xform); updateDiscovery(); if (sessionIdChanged) { debug(mIo->log()) << "Joining session " << session.sessionId << " with tempo " << session.timeline.tempo.bpm(); mSessionPeerCounter(); } } void resetState() { mNodeId = NodeId::random(); mSessionId = mNodeId; const auto xform = detail::initXForm(mClock); const auto hostTime = -xform.intercept; // When creating the new timeline, make it continuous by finding // the beat on the old session timeline corresponding to the // current host time and mapping it to the new ghost time // representation of the current host time. const auto newTl = Timeline{mSessionState.timeline.tempo, mSessionState.timeline.toBeats(mSessionState.ghostXForm.hostToGhost(hostTime)), xform.hostToGhost(hostTime)}; resetSessionStartStopState(); updateSessionTiming(newTl, xform); updateDiscovery(); mSessions.resetSession({mNodeId, newTl, {xform, hostTime}}); mPeers.resetPeers(); } struct SessionTimelineCallback { void operator()(SessionId id, Timeline timeline) { mController.handleTimelineFromSession(std::move(id), std::move(timeline)); } Controller& mController; }; struct RtClientStateSetter { using CallbackDispatcher = typename IoContext::template LockFreeCallbackDispatcher, std::chrono::milliseconds>; RtClientStateSetter(Controller& controller) : mController(controller) , mCallbackDispatcher( [this] { processPendingClientStates(); }, detail::kRtHandlerFallbackPeriod) { } bool tryPush(const IncomingClientState clientState) { const auto success = mClientStateFifo.push(clientState); if (success) { mCallbackDispatcher.invoke(); } return success; } private: IncomingClientState buildMergedPendingClientState() { auto clientState = IncomingClientState{}; while (const auto result = mClientStateFifo.pop()) { if (result->timeline) { clientState.timeline = result->timeline; clientState.timelineTimestamp = result->timelineTimestamp; } if (result->startStopState) { clientState.startStopState = result->startStopState; } } return clientState; } void processPendingClientStates() { const auto clientState = buildMergedPendingClientState(); mController.mIo->async( [this, clientState]() { mController.handleRtClientState(clientState); }); } Controller& mController; // Assuming a wake up time of one ms for the threads owned by the CallbackDispatcher // and the ioService, buffering 16 client states allows to set eight client states // per ms. static const std::size_t kBufferSize = 16; CircularFifo mClientStateFifo; CallbackDispatcher mCallbackDispatcher; }; struct SessionStartStopStateCallback { void operator()(SessionId sessionId, StartStopState startStopState) { mController.handleStartStopStateFromSession(sessionId, startStopState); } Controller& mController; }; struct SessionPeerCounter { SessionPeerCounter(Controller& controller, PeerCountCallback callback) : mController(controller) , mCallback(std::move(callback)) , mSessionPeerCount(0) { } void operator()() { const auto count = mController.mPeers.uniqueSessionPeerCount(mController.mSessionId); const auto oldCount = mSessionPeerCount.exchange(count); if (oldCount != count) { if (count == 0) { // When the count goes down to zero, completely reset the // state, effectively founding a new session mController.resetState(); } mCallback(count); } } Controller& mController; PeerCountCallback mCallback; std::atomic mSessionPeerCount; }; struct MeasurePeer { template void operator()(Peer peer, Handler handler) { using It = typename Discovery::ServicePeerGateways::GatewayMap::iterator; using ValueType = typename Discovery::ServicePeerGateways::GatewayMap::value_type; mController.mDiscovery.withGatewaysAsync([peer, handler](It begin, const It end) { const auto addr = peer.second; const auto it = std::find_if( begin, end, [&addr](const ValueType& vt) { return vt.first == addr; }); if (it != end) { it->second->measurePeer(std::move(peer.first), std::move(handler)); } else { // invoke the handler with an empty result if we couldn't // find the peer's gateway handler(GhostXForm{}); } }); } Controller& mController; }; struct JoinSessionCallback { void operator()(Session session) { mController.joinSession(std::move(session)); } Controller& mController; }; using IoType = typename util::Injected::type; using ControllerPeers = Peers, SessionTimelineCallback, SessionStartStopStateCallback>; using ControllerGateway = Gateway; using GatewayPtr = std::shared_ptr; struct GatewayFactory { GatewayPtr operator()(std::pair state, util::Injected io, const asio::ip::address& addr) { if (addr.is_v4()) { return GatewayPtr{new ControllerGateway{std::move(io), addr.to_v4(), util::injectVal(makeGatewayObserver(mController.mPeers, addr)), std::move(state.first), std::move(state.second), mController.mClock}}; } else { throw std::runtime_error("Could not create peer gateway on non-ipV4 address"); } } Controller& mController; }; struct UdpSendExceptionHandler { using Exception = discovery::UdpSendException; void operator()(const Exception& exception) { mController.mDiscovery.repairGateway(exception.interfaceAddr); } Controller& mController; }; TempoCallback mTempoCallback; StartStopStateCallback mStartStopStateCallback; Clock mClock; NodeId mNodeId; SessionId mSessionId; mutable std::mutex mSessionStateGuard; SessionState mSessionState; mutable std::mutex mClientStateGuard; ClientState mClientState; bool mLastIsPlayingForStartStopStateCallback; mutable RtClientState mRtClientState; std::atomic mHasPendingRtClientStates; SessionPeerCounter mSessionPeerCounter; std::atomic mEnabled; std::atomic mStartStopSyncEnabled; util::Injected mIo; RtClientStateSetter mRtClientStateSetter; ControllerPeers mPeers; using ControllerSessions = Sessions::type&, Clock>; ControllerSessions mSessions; using Discovery = discovery::Service, GatewayFactory, IoContext>; Discovery mDiscovery; }; } // namespace link } // namespace ableton link-Link-3.0.2/include/ableton/link/Peers.hpp0000644000175000017500000002767213303226525021410 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include namespace ableton { namespace link { // SessionMembershipCallback is invoked when any change to session // membership occurs (when any peer joins or leaves a session) // // SessionTimelineCallback is invoked with a session id and a timeline // whenever a new combination of these values is seen // // SessionStartStopStateCallback is invoked with a session id and a startStopState // whenever a new combination of these values is seen template class Peers { // non-movable private implementation type struct Impl; public: using Peer = std::pair; Peers(util::Injected io, SessionMembershipCallback membership, SessionTimelineCallback timeline, SessionStartStopStateCallback startStop) : mpImpl(std::make_shared( std::move(io), std::move(membership), std::move(timeline), std::move(startStop))) { } // The set of peers for a given session, ordered by (peerId, addr). // The result will possibly contain multiple entries for the same // peer if it is visible through multiple gateways. std::vector sessionPeers(const SessionId& sid) const { using namespace std; vector result; auto& peerVec = mpImpl->mPeers; copy_if(begin(peerVec), end(peerVec), back_inserter(result), SessionMemberPred{sid}); return result; } // Number of individual for a given session. std::size_t uniqueSessionPeerCount(const SessionId& sid) const { using namespace std; auto peerVec = sessionPeers(sid); auto last = unique(begin(peerVec), end(peerVec), [](const Peer& a, const Peer& b) { return a.first.ident() == b.first.ident(); }); return static_cast(distance(begin(peerVec), last)); } void setSessionTimeline(const SessionId& sid, const Timeline& tl) { // Set the cached timeline for all peers to a new client-specified // timeline. When we make a timeline change, we do so // optimistically and clients assume that all peers in a session // have adopted the newly specified timeline. We must represent // this in our cache or else we risk failing to notify about a // higher-priority peer timeline that was already seen. for (auto& peer : mpImpl->mPeers) { if (peer.first.sessionId() == sid) { peer.first.nodeState.timeline = tl; } } } // Purge all cached peers that are members of the given session void forgetSession(const SessionId& sid) { using namespace std; auto& peerVec = mpImpl->mPeers; peerVec.erase( remove_if(begin(peerVec), end(peerVec), SessionMemberPred{sid}), end(peerVec)); } void resetPeers() { mpImpl->mPeers.clear(); } // Observer type that monitors peer discovery on a particular // gateway and relays the information to a Peers instance. // Models the PeerObserver concept from the discovery module. struct GatewayObserver { using GatewayObserverNodeState = PeerState; using GatewayObserverNodeId = NodeId; GatewayObserver(std::shared_ptr pImpl, asio::ip::address addr) : mpImpl(std::move(pImpl)) , mAddr(std::move(addr)) { } GatewayObserver(const GatewayObserver&) = delete; GatewayObserver(GatewayObserver&& rhs) : mpImpl(std::move(rhs.mpImpl)) , mAddr(std::move(rhs.mAddr)) { } ~GatewayObserver() { // Check to handle the moved from case if (mpImpl) { auto& io = *mpImpl->mIo; io.async(Deleter{*this}); } } // model the PeerObserver concept from discovery friend void sawPeer(GatewayObserver& observer, const PeerState& state) { auto pImpl = observer.mpImpl; auto addr = observer.mAddr; assert(pImpl); pImpl->mIo->async([pImpl, addr, state] { pImpl->sawPeerOnGateway(std::move(state), std::move(addr)); }); } friend void peerLeft(GatewayObserver& observer, const NodeId& id) { auto pImpl = observer.mpImpl; auto addr = observer.mAddr; pImpl->mIo->async( [pImpl, addr, id] { pImpl->peerLeftGateway(std::move(id), std::move(addr)); }); } friend void peerTimedOut(GatewayObserver& observer, const NodeId& id) { auto pImpl = observer.mpImpl; auto addr = observer.mAddr; pImpl->mIo->async( [pImpl, addr, id] { pImpl->peerLeftGateway(std::move(id), std::move(addr)); }); } struct Deleter { Deleter(GatewayObserver& observer) : mpImpl(std::move(observer.mpImpl)) , mAddr(std::move(observer.mAddr)) { } void operator()() { mpImpl->gatewayClosed(mAddr); } std::shared_ptr mpImpl; asio::ip::address mAddr; }; std::shared_ptr mpImpl; asio::ip::address mAddr; }; // Factory function for the gateway observer friend GatewayObserver makeGatewayObserver(Peers& peers, asio::ip::address addr) { return GatewayObserver{peers.mpImpl, std::move(addr)}; } private: struct Impl { Impl(util::Injected io, SessionMembershipCallback membership, SessionTimelineCallback timeline, SessionStartStopStateCallback startStop) : mIo(std::move(io)) , mSessionMembershipCallback(std::move(membership)) , mSessionTimelineCallback(std::move(timeline)) , mSessionStartStopStateCallback(std::move(startStop)) { } void sawPeerOnGateway(PeerState peerState, asio::ip::address gatewayAddr) { using namespace std; const auto peerSession = peerState.sessionId(); const auto peerTimeline = peerState.timeline(); const auto peerStartStopState = peerState.startStopState(); bool isNewSessionTimeline = false; bool isNewSessionStartStopState = false; bool didSessionMembershipChange = false; { isNewSessionTimeline = !sessionTimelineExists(peerSession, peerTimeline); isNewSessionStartStopState = !sessionStartStopStateExists(peerSession, peerStartStopState); auto peer = make_pair(move(peerState), move(gatewayAddr)); const auto idRange = equal_range(begin(mPeers), end(mPeers), peer, PeerIdComp{}); if (idRange.first == idRange.second) { // This peer is not currently known on any gateway didSessionMembershipChange = true; mPeers.insert(move(idRange.first), move(peer)); } else { // We've seen this peer before... does it have a new session? didSessionMembershipChange = all_of(idRange.first, idRange.second, [&peerSession](const Peer& test) { return test.first.sessionId() != peerSession; }); // was it on this gateway? const auto addrRange = equal_range(idRange.first, idRange.second, peer, AddrComp{}); if (addrRange.first == addrRange.second) { // First time on this gateway, add it mPeers.insert(move(addrRange.first), move(peer)); } else { // We have an entry for this peer on this gateway, update it *addrRange.first = move(peer); } } } // end lock // Invoke callbacks outside the critical section if (isNewSessionTimeline) { mSessionTimelineCallback(peerSession, peerTimeline); } // Pass the start stop state to the Controller after it processed the timeline. // A new timeline can cause a session Id change which will prevent processing the // new start stop state. By handling the start stop state after the timeline we // assure that the start stop state is processed with the correct session Id. if (isNewSessionStartStopState) { mSessionStartStopStateCallback(peerSession, peerStartStopState); } if (didSessionMembershipChange) { mSessionMembershipCallback(); } } void peerLeftGateway(const NodeId& nodeId, const asio::ip::address& gatewayAddr) { using namespace std; bool didSessionMembershipChange = false; { auto it = find_if(begin(mPeers), end(mPeers), [&](const Peer& peer) { return peer.first.ident() == nodeId && peer.second == gatewayAddr; }); if (it != end(mPeers)) { mPeers.erase(move(it)); didSessionMembershipChange = true; } } // end lock if (didSessionMembershipChange) { mSessionMembershipCallback(); } } void gatewayClosed(const asio::ip::address& gatewayAddr) { using namespace std; { mPeers.erase( remove_if(begin(mPeers), end(mPeers), [&gatewayAddr](const Peer& peer) { return peer.second == gatewayAddr; }), end(mPeers)); } // end lock mSessionMembershipCallback(); } template bool hasPeerWith(const SessionId& sessionId, Predicate predicate) { using namespace std; return find_if(begin(mPeers), end(mPeers), [&](const Peer& peer) { return peer.first.sessionId() == sessionId && predicate(peer.first); }) != end(mPeers); } bool sessionTimelineExists(const SessionId& session, const Timeline& timeline) { return hasPeerWith(session, [&](const PeerState& peerState) { return peerState.timeline() == timeline; }); } bool sessionStartStopStateExists( const SessionId& sessionId, const StartStopState& startStopState) { return hasPeerWith(sessionId, [&](const PeerState& peerState) { return peerState.startStopState() == startStopState; }); } struct PeerIdComp { bool operator()(const Peer& lhs, const Peer& rhs) const { return lhs.first.ident() < rhs.first.ident(); } }; struct AddrComp { bool operator()(const Peer& lhs, const Peer& rhs) const { return lhs.second < rhs.second; } }; util::Injected mIo; SessionMembershipCallback mSessionMembershipCallback; SessionTimelineCallback mSessionTimelineCallback; SessionStartStopStateCallback mSessionStartStopStateCallback; std::vector mPeers; // sorted by peerId, unique by (peerId, addr) }; struct SessionMemberPred { bool operator()(const Peer& peer) const { return peer.first.sessionId() == sid; } const SessionId& sid; }; std::shared_ptr mpImpl; }; template Peers makePeers(util::Injected io, SessionMembershipCallback membershipCallback, SessionTimelineCallback timelineCallback, SessionStartStopStateCallback startStopStateCallback) { return {std::move(io), std::move(membershipCallback), std::move(timelineCallback), std::move(startStopStateCallback)}; } } // namespace link } // namespace ableton link-Link-3.0.2/include/ableton/link/LinearRegression.hpp0000644000175000017500000000371213303226525023572 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include #include namespace ableton { namespace link { template std::pair linearRegression(It begin, It end) { using namespace std; using Point = pair; const double numPoints = static_cast(distance(begin, end)); const double meanX = accumulate(begin, end, 0.0, [](double a, Point b) { return a + b.first; }) / numPoints; const double productXX = accumulate(begin, end, 0.0, [&meanX](double a, Point b) { return a + pow(b.first - meanX, 2.0); }); const double meanY = accumulate(begin, end, 0.0, [](double a, Point b) { return a + b.second; }) / numPoints; const double productXY = inner_product(begin, end, begin, 0.0, [](double a, double b) { return a + b; }, [&meanX, &meanY]( Point a, Point b) { return ((a.first - meanX) * (b.second - meanY)); }); const double slope = productXX == 0.0 ? 0.0 : productXY / productXX; const double intercept = meanY - (slope * meanX); return make_pair(slope, intercept); } } // namespace link } // namespace ableton link-Link-3.0.2/include/ableton/link/PeerState.hpp0000644000175000017500000000465513303226525022222 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include namespace ableton { namespace link { // A state type for peers. PeerState stores the normal NodeState plus // additional information (the remote endpoint at which to find its // ping/pong measurement server). struct PeerState { using IdType = NodeId; IdType ident() const { return nodeState.ident(); } SessionId sessionId() const { return nodeState.sessionId; } Timeline timeline() const { return nodeState.timeline; } StartStopState startStopState() const { return nodeState.startStopState; } friend bool operator==(const PeerState& lhs, const PeerState& rhs) { return lhs.nodeState == rhs.nodeState && lhs.endpoint == rhs.endpoint; } friend auto toPayload(const PeerState& state) -> decltype(std::declval() + discovery::makePayload(MeasurementEndpointV4{{}})) { return toPayload(state.nodeState) + discovery::makePayload(MeasurementEndpointV4{state.endpoint}); } template static PeerState fromPayload(NodeId id, It begin, It end) { using namespace std; auto peerState = PeerState{NodeState::fromPayload(move(id), begin, end), {}}; discovery::parsePayload(move(begin), move(end), [&peerState](MeasurementEndpointV4 me4) { peerState.endpoint = move(me4.ep); }); return peerState; } NodeState nodeState; asio::ip::udp::endpoint endpoint; }; } // namespace link } // namespace ableton link-Link-3.0.2/include/ableton/link/PingResponder.hpp0000644000175000017500000001230513303226525023074 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include #include #include #include #include #include #include #include namespace ableton { namespace link { template class PingResponder { public: PingResponder(asio::ip::address_v4 address, SessionId sessionId, GhostXForm ghostXForm, util::Injected io, Clock clock, util::Injected log) : mIo(std::move(io)) , mpImpl(std::make_shared(*mIo, std::move(address), std::move(sessionId), std::move(ghostXForm), std::move(clock), std::move(log))) { mpImpl->listen(); } PingResponder(const PingResponder&) = delete; PingResponder(PingResponder&&) = delete; ~PingResponder() { // post the release of the impl object into the io service so that // it happens in the same thread as its handlers auto pImpl = mpImpl; mIo->post([pImpl]() mutable { pImpl.reset(); }); } void updateNodeState(const SessionId& sessionId, const GhostXForm& xform) { auto pImpl = mpImpl; mIo->post([pImpl, sessionId, xform] { pImpl->mSessionId = std::move(sessionId); pImpl->mGhostXForm = std::move(xform); }); } asio::ip::udp::endpoint endpoint() const { return mpImpl->mSocket.endpoint(); } asio::ip::address address() const { return endpoint().address(); } Socket socket() const { return mpImpl->mSocket; } private: struct Impl : std::enable_shared_from_this { Impl(typename util::Injected::type& io, asio::ip::address_v4 address, SessionId sessionId, GhostXForm ghostXForm, Clock clock, util::Injected log) : mSessionId(std::move(sessionId)) , mGhostXForm(std::move(ghostXForm)) , mClock(std::move(clock)) , mLog(std::move(log)) , mSocket(io) { configureUnicastSocket(mSocket, address); } void listen() { mSocket.receive(util::makeAsyncSafe(this->shared_from_this())); } // Operator to handle incoming messages on the interface template void operator()(const asio::ip::udp::endpoint& from, const It begin, const It end) { using namespace discovery; // Decode Ping Message const auto result = link::v1::parseMessageHeader(begin, end); const auto& header = result.first; const auto payloadBegin = result.second; // Check Payload size const auto payloadSize = static_cast(std::distance(payloadBegin, end)); const auto maxPayloadSize = sizeInByteStream(makePayload(HostTime{}, PrevGHostTime{})); if (header.messageType == v1::kPing && payloadSize <= maxPayloadSize) { debug(*mLog) << "Received ping message from " << from; try { reply(std::move(payloadBegin), std::move(end), from); } catch (const std::runtime_error& err) { info(*mLog) << "Failed to send pong to " << from << ". Reason: " << err.what(); } } else { info(*mLog) << "Received invalid Message from " << from << "."; } listen(); } template void reply(It begin, It end, const asio::ip::udp::endpoint& to) { using namespace discovery; // Encode Pong Message const auto id = SessionMembership{mSessionId}; const auto currentGt = GHostTime{mGhostXForm.hostToGhost(mClock.micros())}; const auto pongPayload = makePayload(id, currentGt); v1::MessageBuffer pongBuffer; const auto pongMsgBegin = std::begin(pongBuffer); auto pongMsgEnd = v1::pongMessage(pongPayload, pongMsgBegin); // Append ping payload to pong message. pongMsgEnd = std::copy(begin, end, pongMsgEnd); const auto numBytes = static_cast(std::distance(pongMsgBegin, pongMsgEnd)); mSocket.send(pongBuffer.data(), numBytes, to); } SessionId mSessionId; GhostXForm mGhostXForm; Clock mClock; util::Injected mLog; Socket mSocket; }; util::Injected mIo; std::shared_ptr mpImpl; }; } // namespace link } // namespace ableton link-Link-3.0.2/include/ableton/link/StartStopState.hpp0000644000175000017500000000675413303226525023274 0ustar zmoelnigzmoelnig/* Copyright 2017, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include #include namespace ableton { namespace link { // A tuple of (isPlaying, time) that represents the playing state // with an according timestamp in microseconds. It also serves as a // payload entry. struct StartStopState { static const std::int32_t key = 'stst'; static_assert(key == 0x73747374, "Unexpected byte order"); using StartStopStateTuple = std::tuple; StartStopState() = default; StartStopState( const bool aIsPlaying, const Beats aBeats, const std::chrono::microseconds aTimestamp) : isPlaying(aIsPlaying) , beats(aBeats) , timestamp(aTimestamp) { } friend bool operator==(const StartStopState& lhs, const StartStopState& rhs) { return std::tie(lhs.isPlaying, lhs.beats, lhs.timestamp) == std::tie(rhs.isPlaying, rhs.beats, rhs.timestamp); } friend bool operator!=(const StartStopState& lhs, const StartStopState& rhs) { return !(lhs == rhs); } // Model the NetworkByteStreamSerializable concept friend std::uint32_t sizeInByteStream(const StartStopState& state) { return discovery::sizeInByteStream(state.asTuple()); } template friend It toNetworkByteStream(const StartStopState& state, It out) { return discovery::toNetworkByteStream(state.asTuple(), std::move(out)); } template static std::pair fromNetworkByteStream(It begin, It end) { using namespace std; using namespace discovery; auto result = Deserialize::fromNetworkByteStream(move(begin), move(end)); auto state = StartStopState{get<0>(result.first), get<1>(result.first), get<2>(result.first)}; return make_pair(move(state), move(result.second)); } bool isPlaying{false}; Beats beats{0.}; std::chrono::microseconds timestamp{0}; private: StartStopStateTuple asTuple() const { return std::make_tuple(isPlaying, beats, timestamp); } }; struct ApiStartStopState { ApiStartStopState() = default; ApiStartStopState(const bool aIsPlaying, const std::chrono::microseconds aTime) : isPlaying(aIsPlaying) , time(aTime) { } friend bool operator==(const ApiStartStopState& lhs, const ApiStartStopState& rhs) { return std::tie(lhs.isPlaying, lhs.time) == std::tie(rhs.isPlaying, rhs.time); } friend bool operator!=(const ApiStartStopState& lhs, const ApiStartStopState& rhs) { return !(lhs == rhs); } bool isPlaying{false}; std::chrono::microseconds time{0}; }; } // namespace link } // namespace ableton link-Link-3.0.2/include/ableton/link/Gateway.hpp0000644000175000017500000000553413303226525021724 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include namespace ableton { namespace link { template class Gateway { public: Gateway(util::Injected io, asio::ip::address_v4 addr, util::Injected observer, NodeState nodeState, GhostXForm ghostXForm, Clock clock) // TODO: Measurement should have an IoContext injected : mIo(std::move(io)) , mMeasurement(addr, nodeState.sessionId, std::move(ghostXForm), std::move(clock), util::injectVal(channel(mIo->log(), "gateway@" + addr.to_string()))) , mPeerGateway(discovery::makeIpV4Gateway(util::injectRef(*mIo), std::move(addr), std::move(observer), PeerState{std::move(nodeState), mMeasurement.endpoint()})) { } Gateway(const Gateway& rhs) = delete; Gateway& operator=(const Gateway& rhs) = delete; Gateway(Gateway&& rhs) : mIo(std::move(rhs.mIo)) , mMeasurement(std::move(rhs.mMeasurement)) , mPeerGateway(std::move(rhs.mPeerGateway)) { } Gateway& operator=(Gateway&& rhs) { mIo = std::move(rhs.mIo); mMeasurement = std::move(rhs.mMeasurement); mPeerGateway = std::move(rhs.mPeerGateway); return *this; } void updateNodeState(std::pair state) { mMeasurement.updateNodeState(state.first.sessionId, state.second); mPeerGateway.updateState(PeerState{std::move(state.first), mMeasurement.endpoint()}); } template void measurePeer(const PeerState& peer, Handler handler) { mMeasurement.measurePeer(peer, std::move(handler)); } private: util::Injected mIo; MeasurementService::type::Log> mMeasurement; discovery:: IpV4Gateway::type&> mPeerGateway; }; } // namespace link } // namespace ableton link-Link-3.0.2/include/ableton/link/NodeState.hpp0000644000175000017500000000455713303226525022215 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include #include #include namespace ableton { namespace link { struct NodeState { using Payload = decltype(discovery::makePayload(Timeline{}, SessionMembership{}, StartStopState{})); NodeId ident() const { return nodeId; } friend bool operator==(const NodeState& lhs, const NodeState& rhs) { return std::tie(lhs.nodeId, lhs.sessionId, lhs.timeline, lhs.startStopState) == std::tie(rhs.nodeId, rhs.sessionId, rhs.timeline, rhs.startStopState); } friend Payload toPayload(const NodeState& state) { return discovery::makePayload( state.timeline, SessionMembership{state.sessionId}, state.startStopState); } template static NodeState fromPayload(NodeId nodeId, It begin, It end) { using namespace std; auto nodeState = NodeState{move(nodeId), {}, {}, {}}; discovery::parsePayload(move(begin), move(end), [&nodeState](Timeline tl) { nodeState.timeline = move(tl); }, [&nodeState](SessionMembership membership) { nodeState.sessionId = move(membership.sessionId); }, [&nodeState](StartStopState ststst) { nodeState.startStopState = move(ststst); }); return nodeState; } NodeId nodeId; SessionId sessionId; Timeline timeline; StartStopState startStopState; }; } // namespace link } // namespace ableton link-Link-3.0.2/include/ableton/link/HostTimeFilter.hpp0000644000175000017500000000403313303226525023216 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include namespace ableton { namespace link { template class HostTimeFilter { static const std::size_t kNumPoints = 512; using Points = std::vector>; using PointIt = typename Points::iterator; public: HostTimeFilter() : mIndex(0) { mPoints.reserve(kNumPoints); } ~HostTimeFilter() = default; void reset() { mIndex = 0; mPoints.clear(); } std::chrono::microseconds sampleTimeToHostTime(const double sampleTime) { const auto micros = static_cast(mHostTimeSampler.micros().count()); const auto point = std::make_pair(sampleTime, micros); if (mPoints.size() < kNumPoints) { mPoints.push_back(point); } else { mPoints[mIndex] = point; } mIndex = (mIndex + 1) % kNumPoints; const auto result = linearRegression(mPoints.begin(), mPoints.end()); const auto hostTime = (result.first * sampleTime) + result.second; return std::chrono::microseconds(llround(hostTime)); } private: std::size_t mIndex; Points mPoints; T mHostTimeSampler; }; } // namespace link } // namespace ableton link-Link-3.0.2/include/ableton/link/SessionState.hpp0000644000175000017500000000402213303226525022736 0ustar zmoelnigzmoelnig/* Copyright 2017, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include namespace ableton { namespace link { using OptionalTimeline = Optional; using OptionalStartStopState = Optional; struct SessionState { Timeline timeline; StartStopState startStopState; GhostXForm ghostXForm; }; struct ClientState { friend bool operator==(const ClientState& lhs, const ClientState& rhs) { return std::tie(lhs.timeline, lhs.startStopState) == std::tie(rhs.timeline, rhs.startStopState); } friend bool operator!=(const ClientState& lhs, const ClientState& rhs) { return !(lhs == rhs); } Timeline timeline; StartStopState startStopState; }; struct RtClientState { Timeline timeline; StartStopState startStopState; std::chrono::microseconds timelineTimestamp; std::chrono::microseconds startStopStateTimestamp; }; struct IncomingClientState { OptionalTimeline timeline; OptionalStartStopState startStopState; std::chrono::microseconds timelineTimestamp; }; struct ApiState { Timeline timeline; ApiStartStopState startStopState; }; } // namespace link } // namespace ableton link-Link-3.0.2/include/ableton/link/Measurement.hpp0000644000175000017500000002001013303226525022572 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include #include #include #include #include #include #include #include namespace ableton { namespace link { template struct Measurement { using Point = std::pair; using Callback = std::function)>; using Micros = std::chrono::microseconds; using Timer = typename IoService::Timer; static const std::size_t kNumberDataPoints = 100; static const std::size_t kNumberMeasurements = 5; Measurement() = default; Measurement(const PeerState& state, Callback callback, asio::ip::address_v4 address, Clock clock, util::Injected log) : mpIo(new IoService{}) , mpImpl(std::make_shared(*mpIo, std::move(state), std::move(callback), std::move(address), std::move(clock), std::move(log))) { mpImpl->listen(); } Measurement(Measurement&& rhs) : mpIo(std::move(rhs.mpIo)) , mpImpl(std::move(rhs.mpImpl)) { } ~Measurement() { postImplDestruction(); } Measurement& operator=(Measurement&& rhs) { postImplDestruction(); mpIo = std::move(rhs.mpIo); mpImpl = std::move(rhs.mpImpl); return *this; } void postImplDestruction() { // Post destruction of the impl object into the io thread if valid if (mpIo) { mpIo->post(ImplDeleter{*this}); } } struct Impl : std::enable_shared_from_this { Impl(IoService& io, const PeerState& state, Callback callback, asio::ip::address_v4 address, Clock clock, util::Injected log) : mpSocket(std::make_shared(io)) , mSessionId(state.nodeState.sessionId) , mEndpoint(state.endpoint) , mCallback(std::move(callback)) , mClock(std::move(clock)) , mTimer(util::injectVal(io.makeTimer())) , mMeasurementsStarted(0) , mLog(std::move(log)) , mSuccess(false) { configureUnicastSocket(*mpSocket, address); const auto ht = HostTime{mClock.micros()}; sendPing(mEndpoint, discovery::makePayload(ht)); resetTimer(); } void resetTimer() { mTimer->cancel(); mTimer->expires_from_now(std::chrono::milliseconds(50)); mTimer->async_wait([this](const typename Timer::ErrorCode e) { if (!e) { if (mMeasurementsStarted < kNumberMeasurements) { const auto ht = HostTime{mClock.micros()}; sendPing(mEndpoint, discovery::makePayload(ht)); ++mMeasurementsStarted; resetTimer(); } else { fail(); } } }); } void listen() { mpSocket->receive(util::makeAsyncSafe(this->shared_from_this())); } // Operator to handle incoming messages on the interface template void operator()( const asio::ip::udp::endpoint& from, const It messageBegin, const It messageEnd) { using namespace std; const auto result = v1::parseMessageHeader(messageBegin, messageEnd); const auto& header = result.first; const auto payloadBegin = result.second; if (header.messageType == v1::kPong) { debug(*mLog) << "Received Pong message from " << from; // parse for all entries SessionId sessionId{}; std::chrono::microseconds ghostTime{0}; std::chrono::microseconds prevGHostTime{0}; std::chrono::microseconds prevHostTime{0}; try { discovery::parsePayload( payloadBegin, messageEnd, [&sessionId](const SessionMembership& sms) { sessionId = sms.sessionId; }, [&ghostTime](GHostTime gt) { ghostTime = std::move(gt.time); }, [&prevGHostTime](PrevGHostTime gt) { prevGHostTime = std::move(gt.time); }, [&prevHostTime](HostTime ht) { prevHostTime = std::move(ht.time); }); } catch (const std::runtime_error& err) { warning(*mLog) << "Failed parsing payload, caught exception: " << err.what(); listen(); return; } if (mSessionId == sessionId) { const auto hostTime = mClock.micros(); const auto payload = discovery::makePayload(HostTime{hostTime}, PrevGHostTime{ghostTime}); sendPing(from, payload); listen(); if (prevGHostTime != Micros{0}) { mData.push_back( std::make_pair(static_cast((hostTime + prevHostTime).count()) * 0.5, static_cast(ghostTime.count()))); mData.push_back(std::make_pair(static_cast(prevHostTime.count()), static_cast((ghostTime + prevGHostTime).count()) * 0.5)); } if (mData.size() > kNumberDataPoints) { finish(); } else { resetTimer(); } } else { fail(); } } else { debug(*mLog) << "Received invalid message from " << from; listen(); } } template void sendPing(asio::ip::udp::endpoint to, const Payload& payload) { v1::MessageBuffer buffer; const auto msgBegin = std::begin(buffer); const auto msgEnd = v1::pingMessage(payload, msgBegin); const auto numBytes = static_cast(std::distance(msgBegin, msgEnd)); try { mpSocket->send(buffer.data(), numBytes, to); } catch (const std::runtime_error& err) { info(*mLog) << "Failed to send Ping to " << to.address().to_string() << ": " << err.what(); } } void finish() { mTimer->cancel(); mCallback(std::move(mData)); mData = {}; mSuccess = true; debug(*mLog) << "Measuring " << mEndpoint << " done."; } void fail() { mCallback(std::vector{}); mData = {}; debug(*mLog) << "Measuring " << mEndpoint << " failed."; } std::shared_ptr mpSocket; SessionId mSessionId; asio::ip::udp::endpoint mEndpoint; std::vector> mData; Callback mCallback; Clock mClock; util::Injected mTimer; std::size_t mMeasurementsStarted; util::Injected mLog; bool mSuccess; }; struct ImplDeleter { ImplDeleter(Measurement& measurement) : mpImpl(std::move(measurement.mpImpl)) { } void operator()() { // Notify callback that the measurement has failed if it did // not succeed before destruction if (!mpImpl->mSuccess) { mpImpl->fail(); } mpImpl.reset(); } std::shared_ptr mpImpl; }; std::unique_ptr mpIo; std::shared_ptr mpImpl; }; } // namespace link } // namespace ableton link-Link-3.0.2/include/ableton/link/Phase.hpp0000644000175000017500000000766513303226525021372 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include namespace ableton { namespace link { // Returns a value in the range [0,quantum) corresponding to beats % // quantum except that negative beat values are handled correctly. // If the given quantum is zero, returns zero. inline Beats phase(const Beats beats, const Beats quantum) { if (quantum == Beats{INT64_C(0)}) { return Beats{INT64_C(0)}; } else { // Handle negative beat values by doing the computation relative to an // origin that is on the nearest quantum boundary less than -(abs(x)) const auto quantumMicros = quantum.microBeats(); const auto quantumBins = (llabs(beats.microBeats()) + quantumMicros) / quantumMicros; const std::int64_t quantumBeats{quantumBins * quantumMicros}; return (beats + Beats{quantumBeats}) % quantum; } } // Return the least value greater than x that matches the phase of // target with respect to the given quantum. If the given quantum // quantum is 0, x is returned. inline Beats nextPhaseMatch(const Beats x, const Beats target, const Beats quantum) { const auto desiredPhase = phase(target, quantum); const auto xPhase = phase(x, quantum); const auto phaseDiff = (desiredPhase - xPhase + quantum) % quantum; return x + phaseDiff; } // Return the closest value to x that matches the phase of the target // with respect to the given quantum. The result deviates from x by at // most quantum/2, but may be less than x. inline Beats closestPhaseMatch(const Beats x, const Beats target, const Beats quantum) { return nextPhaseMatch(x - Beats{0.5 * quantum.floating()}, target, quantum); } // Interprets the given timeline as encoding a quantum boundary at its // origin. Given such a timeline, returns a phase-encoded beat value // relative to the given quantum that corresponds to the given // time. The phase of the resulting beat value can be calculated with // phase(beats, quantum). The result will deviate by up to +- // (quantum/2) beats compared to the result of tl.toBeats(time). inline Beats toPhaseEncodedBeats( const Timeline& tl, const std::chrono::microseconds time, const Beats quantum) { const auto beat = tl.toBeats(time); return closestPhaseMatch(beat, beat - tl.beatOrigin, quantum); } // The inverse of toPhaseEncodedBeats. Given a phase encoded beat // value from the given timeline and quantum, find the time value that // it maps to. inline std::chrono::microseconds fromPhaseEncodedBeats( const Timeline& tl, const Beats beat, const Beats quantum) { const auto fromOrigin = beat - tl.beatOrigin; const auto originOffset = fromOrigin - phase(fromOrigin, quantum); // invert the phase calculation so that it always rounds up in the // middle instead of down like closestPhaseMatch. Otherwise we'll // end up rounding down twice when a value is at phase quantum/2. const auto inversePhaseOffset = closestPhaseMatch( quantum - phase(fromOrigin, quantum), quantum - phase(beat, quantum), quantum); return tl.fromBeats(tl.beatOrigin + originOffset + quantum - inversePhaseOffset); } } // namespace link } // namespace ableton link-Link-3.0.2/include/ableton/link/Sessions.hpp0000644000175000017500000002211213303226525022120 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include namespace ableton { namespace link { struct SessionMeasurement { GhostXForm xform; std::chrono::microseconds timestamp; }; struct Session { SessionId sessionId; Timeline timeline; SessionMeasurement measurement; }; template class Sessions { public: using Timer = typename util::Injected::type::Timer; Sessions(Session init, util::Injected peers, MeasurePeer measure, JoinSessionCallback join, util::Injected io, Clock clock) : mPeers(std::move(peers)) , mMeasure(std::move(measure)) , mCallback(std::move(join)) , mCurrent(std::move(init)) , mIo(std::move(io)) , mTimer(mIo->makeTimer()) , mClock(std::move(clock)) { } void resetSession(Session session) { mCurrent = std::move(session); mOtherSessions.clear(); } void resetTimeline(Timeline timeline) { mCurrent.timeline = std::move(timeline); } // Consider the observed session/timeline pair and return a possibly // new timeline that should be used going forward. Timeline sawSessionTimeline(SessionId sid, Timeline timeline) { using namespace std; if (sid == mCurrent.sessionId) { // matches our current session, update the timeline if necessary updateTimeline(mCurrent, move(timeline)); } else { auto session = Session{move(sid), move(timeline), {}}; const auto range = equal_range(begin(mOtherSessions), end(mOtherSessions), session, SessionIdComp{}); if (range.first == range.second) { // brand new session, insert it into our list of known // sessions and launch a measurement launchSessionMeasurement(session); mOtherSessions.insert(range.first, move(session)); } else { // we've seen this session before, update its timeline if necessary updateTimeline(*range.first, move(timeline)); } } return mCurrent.timeline; } private: void launchSessionMeasurement(Session& session) { using namespace std; auto peers = mPeers->sessionPeers(session.sessionId); if (!peers.empty()) { // first criteria: always prefer the founding peer const auto it = find_if(begin(peers), end(peers), [&session](const Peer& peer) { return session.sessionId == peer.first.ident(); }); // TODO: second criteria should be degree. We don't have that // represented yet so just use the first peer for now auto peer = it == end(peers) ? peers.front() : *it; // mark that a session is in progress by clearing out the // session's timestamp session.measurement.timestamp = {}; mMeasure(move(peer), MeasurementResultsHandler{*this, session.sessionId}); } } void handleSuccessfulMeasurement(const SessionId& id, GhostXForm xform) { using namespace std; debug(mIo->log()) << "Session " << id << " measurement completed with result " << "(" << xform.slope << ", " << xform.intercept.count() << ")"; auto measurement = SessionMeasurement{move(xform), mClock.micros()}; if (mCurrent.sessionId == id) { mCurrent.measurement = move(measurement); mCallback(mCurrent); } else { const auto range = equal_range( begin(mOtherSessions), end(mOtherSessions), Session{id, {}, {}}, SessionIdComp{}); if (range.first != range.second) { const auto SESSION_EPS = chrono::microseconds{500000}; // should we join this session? const auto hostTime = mClock.micros(); const auto curGhost = mCurrent.measurement.xform.hostToGhost(hostTime); const auto newGhost = measurement.xform.hostToGhost(hostTime); // update the measurement for the session entry range.first->measurement = move(measurement); // If session times too close - fall back to session id order const auto ghostDiff = newGhost - curGhost; if (ghostDiff > SESSION_EPS || (std::abs(ghostDiff.count()) < SESSION_EPS.count() && id < mCurrent.sessionId)) { // The new session wins, switch over to it auto current = mCurrent; mCurrent = move(*range.first); mOtherSessions.erase(range.first); // Put the old current session back into our list of known // sessions so that we won't re-measure it const auto it = upper_bound( begin(mOtherSessions), end(mOtherSessions), current, SessionIdComp{}); mOtherSessions.insert(it, move(current)); // And notify that we have a new session and make sure that // we remeasure it periodically. mCallback(mCurrent); scheduleRemeasurement(); } } } } void scheduleRemeasurement() { // set a timer to re-measure the active session after a period mTimer.expires_from_now(std::chrono::microseconds{30000000}); mTimer.async_wait([this](const typename Timer::ErrorCode e) { if (!e) { launchSessionMeasurement(mCurrent); scheduleRemeasurement(); } }); } void handleFailedMeasurement(const SessionId& id) { using namespace std; debug(mIo->log()) << "Session " << id << " measurement failed."; // if we failed to measure for our current session, schedule a // retry in the future. Otherwise, remove the session from our set // of known sessions (if it is seen again it will be measured as // if new). if (mCurrent.sessionId == id) { scheduleRemeasurement(); } else { const auto range = equal_range( begin(mOtherSessions), end(mOtherSessions), Session{id, {}, {}}, SessionIdComp{}); if (range.first != range.second) { mOtherSessions.erase(range.first); mPeers->forgetSession(id); } } } void updateTimeline(Session& session, Timeline timeline) { // We use beat origin magnitude to prioritize sessions. if (timeline.beatOrigin > session.timeline.beatOrigin) { debug(mIo->log()) << "Adopting peer timeline (" << timeline.tempo.bpm() << ", " << timeline.beatOrigin.floating() << ", " << timeline.timeOrigin.count() << ")"; session.timeline = std::move(timeline); } else { debug(mIo->log()) << "Rejecting peer timeline with beat origin: " << timeline.beatOrigin.floating() << ". Current timeline beat origin: " << session.timeline.beatOrigin.floating(); } } struct MeasurementResultsHandler { void operator()(GhostXForm xform) const { Sessions& sessions = mSessions; const SessionId& sessionId = mSessionId; if (xform == GhostXForm{}) { mSessions.mIo->async([&sessions, sessionId] { sessions.handleFailedMeasurement(std::move(sessionId)); }); } else { mSessions.mIo->async([&sessions, sessionId, xform] { sessions.handleSuccessfulMeasurement(std::move(sessionId), std::move(xform)); }); } } Sessions& mSessions; SessionId mSessionId; }; struct SessionIdComp { bool operator()(const Session& lhs, const Session& rhs) const { return lhs.sessionId < rhs.sessionId; } }; using Peer = typename util::Injected::type::Peer; util::Injected mPeers; MeasurePeer mMeasure; JoinSessionCallback mCallback; Session mCurrent; util::Injected mIo; Timer mTimer; Clock mClock; std::vector mOtherSessions; // sorted/unique by session id }; template Sessions makeSessions( Session init, util::Injected peers, MeasurePeer measure, JoinSessionCallback join, util::Injected io, Clock clock) { using namespace std; return {move(init), move(peers), move(measure), move(join), move(io), move(clock)}; } } // namespace link } // namespace ableton link-Link-3.0.2/include/ableton/link/SessionId.hpp0000644000175000017500000000370713303226525022223 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include namespace ableton { namespace link { // SessionIds occupy the same value space as NodeIds and are // identified by their founding node. using SessionId = NodeId; // A payload entry indicating membership in a particular session struct SessionMembership { static const std::int32_t key = 'sess'; static_assert(key == 0x73657373, "Unexpected byte order"); // Model the NetworkByteStreamSerializable concept friend std::uint32_t sizeInByteStream(const SessionMembership& sm) { return discovery::sizeInByteStream(sm.sessionId); } template friend It toNetworkByteStream(const SessionMembership& sm, It out) { return discovery::toNetworkByteStream(sm.sessionId, std::move(out)); } template static std::pair fromNetworkByteStream(It begin, It end) { using namespace std; auto idRes = SessionId::fromNetworkByteStream(move(begin), move(end)); return make_pair(SessionMembership{move(idRes.first)}, move(idRes.second)); } SessionId sessionId; }; } // namespace link } // namespace ableton link-Link-3.0.2/include/ableton/link/NodeId.hpp0000644000175000017500000000434613303226525021465 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include #include #include #include namespace ableton { namespace link { using NodeIdArray = std::array; struct NodeId : NodeIdArray { NodeId() = default; NodeId(NodeIdArray rhs) : NodeIdArray(std::move(rhs)) { } static NodeId random() { using namespace std; random_device rd; mt19937 gen(rd()); // uint8_t not standardized for this type - use unsigned uniform_int_distribution dist(33, 126); // printable ascii chars NodeId nodeId; generate( nodeId.begin(), nodeId.end(), [&] { return static_cast(dist(gen)); }); return nodeId; } friend std::ostream& operator<<(std::ostream& stream, const NodeId& id) { return stream << std::string{id.cbegin(), id.cend()}; } template friend It toNetworkByteStream(const NodeId& nodeId, It out) { return discovery::toNetworkByteStream(nodeId, std::move(out)); } template static std::pair fromNetworkByteStream(It begin, It end) { using namespace std; auto result = discovery::Deserialize::fromNetworkByteStream(move(begin), move(end)); return make_pair(NodeId(move(result.first)), move(result.second)); } }; } // namespace link } // namespace ableton link-Link-3.0.2/include/ableton/link/GhostXForm.hpp0000644000175000017500000000313213303226525022353 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include namespace ableton { namespace link { using std::chrono::microseconds; struct GhostXForm { microseconds hostToGhost(const microseconds hostTime) const { return microseconds{llround(slope * hostTime.count())} + intercept; } microseconds ghostToHost(const microseconds ghostTime) const { return microseconds{llround((ghostTime - intercept).count() / slope)}; } friend bool operator==(const GhostXForm lhs, const GhostXForm rhs) { return lhs.slope == rhs.slope && lhs.intercept == rhs.intercept; } friend bool operator!=(const GhostXForm lhs, const GhostXForm rhs) { return !(lhs == rhs); } double slope; microseconds intercept; }; } // namespace link } // namespace ableton link-Link-3.0.2/include/ableton/link/ClientSessionTimelines.hpp0000644000175000017500000001156113303226525024754 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include namespace ableton { namespace link { // Clamp the tempo of the given timeline to the valid Link range inline Timeline clampTempo(const Timeline timeline) { const double kMinBpm = 20.0; const double kMaxBpm = 999.0; return {Tempo{(std::min)((std::max)(timeline.tempo.bpm(), kMinBpm), kMaxBpm)}, timeline.beatOrigin, timeline.timeOrigin}; } // Given an existing client timeline, a session timeline, and the // global host transform of the session, return a new version of the client // timeline. The resulting new client timeline is continuous with the // previous timeline so that curClient.toBeats(atTime) == // result.toBeats(atTime). inline Timeline updateClientTimelineFromSession(const Timeline curClient, const Timeline session, const std::chrono::microseconds atTime, const GhostXForm xform) { // An intermediate timeline representing the continuation of the // existing client timeline with the tempo from the session timeline. const auto tempTl = Timeline{session.tempo, curClient.toBeats(atTime), atTime}; // The host time corresponding to beat 0 on the session // timeline. Beat 0 on the session timeline is important because it // serves as the origin of the quantization grid for all participants. const auto hostBeatZero = xform.ghostToHost(session.fromBeats(Beats{INT64_C(0)})); // The new client timeline becomes the result of sliding the // intermediate timeline back so that it's anchor corresponds to // beat zero on the session timeline. The result preserves the // magnitude of beats on the client timeline while encoding the // quantization reference point in the time and beatOrigins. return {tempTl.tempo, tempTl.toBeats(hostBeatZero), hostBeatZero}; } inline Timeline updateSessionTimelineFromClient(const Timeline curSession, const Timeline client, const std::chrono::microseconds atTime, const GhostXForm xform) { // The client timeline was constructed so that it's timeOrigin // corresponds to beat 0 on the session timeline. const auto ghostBeat0 = xform.hostToGhost(client.timeOrigin); const auto zero = Beats{INT64_C(0)}; // If beat 0 was not shifted and there is not a new tempo, an update // of the session timeline is not required. Don't create an // equivalent timeline with different anchor points if not needed as // this will trigger other unnecessary changes. if (curSession.toBeats(ghostBeat0) == zero && client.tempo == curSession.tempo) { return curSession; } else { // An intermediate timeline representing the new tempo, the // effective time, and a possibly adjusted origin. const auto tempTl = Timeline{client.tempo, zero, ghostBeat0}; // The final session timeline must have the beat corresponding to // atTime on the old session timeline as its beatOrigin because this is // used for prioritization of timelines among peers - we can't let a // modification applied by the client artificially increase or // reduce the timeline's priority in the session. The new beat // origin should be as close as possible to lining up with atTime, // but we must also make sure that it's > curSession.beatOrigin // because otherwise it will get ignored. const auto newBeatOrigin = (std::max)(curSession.toBeats(xform.hostToGhost(atTime)), curSession.beatOrigin + Beats{INT64_C(1)}); return {client.tempo, newBeatOrigin, tempTl.fromBeats(newBeatOrigin)}; } } // Shift timeline so result.toBeats(t) == client.toBeats(t) + // shift. This takes into account the fact that the timeOrigin // corresponds to beat 0 on the session timeline. Using this function // and then setting the session timeline with the result will change // the phase of the session by the given shift amount. inline Timeline shiftClientTimeline(Timeline client, const Beats shift) { const auto timeDelta = client.fromBeats(shift) - client.fromBeats(Beats{INT64_C(0)}); client.timeOrigin = client.timeOrigin - timeDelta; return client; } } // namespace link } // namespace ableton link-Link-3.0.2/include/ableton/link/Kalman.hpp0000644000175000017500000000724213303226525021524 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include namespace ableton { namespace link { template struct Kalman { Kalman() : mValue(0) , mCoVariance(1) , mVarianceLength(n) , mCounter(mVarianceLength) { } double getValue() { return mValue; } double calculateVVariance() { auto vVar = 0.; auto meanOfDiffs = 0.; for (size_t k = 0; k < (mVarianceLength); k++) { meanOfDiffs += (mMeasuredValues[k] - mFilterValues[k]); } meanOfDiffs /= (mVarianceLength); for (size_t i = 0; i < (mVarianceLength); i++) { vVar += (pow(mMeasuredValues[i] - mFilterValues[i] - meanOfDiffs, 2.0)); } vVar /= (mVarianceLength - 1); return vVar; } double calculateWVariance() { auto wVar = 0.; auto meanOfDiffs = 0.; for (size_t k = 0; k < (mVarianceLength); k++) { meanOfDiffs += (mFilterValues[(mCounter - k - 1) % mVarianceLength] - mFilterValues[(mCounter - k - 2) % mVarianceLength]); } meanOfDiffs /= (mVarianceLength); for (size_t i = 0; i < (mVarianceLength); i++) { wVar += (pow(mFilterValues[(mCounter - i - 1) % mVarianceLength] - mFilterValues[(mCounter - i - 2) % mVarianceLength] - meanOfDiffs, 2.0)); } wVar /= (mVarianceLength - 1); return wVar; } void iterate(const double value) { const std::size_t currentIndex = mCounter % mVarianceLength; mMeasuredValues[currentIndex] = value; if (mCounter < (mVarianceLength + mVarianceLength)) { if (mCounter == mVarianceLength) { mValue = value; } else { mValue = (mValue + value) / 2; } } else { // prediction equations const double prevFilterValue = mFilterValues[(mCounter - 1) % mVarianceLength]; mFilterValues[currentIndex] = prevFilterValue; const auto wVariance = calculateWVariance(); const double coVarianceEstimation = mCoVariance + wVariance; // update equations const auto vVariance = calculateVVariance(); // Gain defines how easily the filter will adjust to a new condition // With gain = 1 the output equals the input, with gain = 0 the input // is ignored and the output equals the last filtered value const auto divisor = coVarianceEstimation + vVariance; const auto gain = divisor != 0. ? coVarianceEstimation / divisor : 0.7; mValue = prevFilterValue + gain * (value - prevFilterValue); mCoVariance = (1 - gain) * coVarianceEstimation; } mFilterValues[currentIndex] = mValue; ++mCounter; } double mValue; double mCoVariance; size_t mVarianceLength; size_t mCounter; std::array mFilterValues; std::array mMeasuredValues; }; } // namespace link } // namespace ableton link-Link-3.0.2/include/ableton/Link.hpp0000644000175000017500000003516413303226525020265 0ustar zmoelnigzmoelnig/*! @file Link.hpp * @copyright 2016, Ableton AG, Berlin. All rights reserved. * @brief Library for cross-device shared tempo and quantized beat grid * * @license: * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include namespace ableton { /*! @class Link * @brief Class that represents a participant in a Link session. * * @discussion Each Link instance has its own session state which * represents a beat timeline and a transport start/stop state. The * timeline starts running from beat 0 at the initial tempo when * constructed. The timeline always advances at a speed defined by * its current tempo, even if transport is stopped. Synchronizing to the * transport start/stop state of Link is optional for every peer. * The transport start/stop state is only shared with other peers when * start/stop synchronization is enabled. * * A Link instance is initially disabled after construction, which * means that it will not communicate on the network. Once enabled, * a Link instance initiates network communication in an effort to * discover other peers. When peers are discovered, they immediately * become part of a shared Link session. * * Each method of the Link type documents its thread-safety and * realtime-safety properties. When a method is marked thread-safe, * it means it is safe to call from multiple threads * concurrently. When a method is marked realtime-safe, it means that * it does not block and is appropriate for use in the thread that * performs audio IO. * * Link provides one session state capture/commit method pair for use * in the audio thread and one for all other application contexts. In * general, modifying the session state should be done in the audio * thread for the most accurate timing results. The ability to modify * the session state from application threads should only be used in * cases where an application's audio thread is not actively running * or if it doesn't generate audio at all. Modifying the Link session * state from both the audio thread and an application thread * concurrently is not advised and will potentially lead to unexpected * behavior. */ class Link { public: using Clock = link::platform::Clock; class SessionState; /*! @brief Construct with an initial tempo. */ Link(double bpm); /*! @brief Link instances cannot be copied or moved */ Link(const Link&) = delete; Link& operator=(const Link&) = delete; Link(Link&&) = delete; Link& operator=(Link&&) = delete; /*! @brief Is Link currently enabled? * Thread-safe: yes * Realtime-safe: yes */ bool isEnabled() const; /*! @brief Enable/disable Link. * Thread-safe: yes * Realtime-safe: no */ void enable(bool bEnable); /*! @brief: Is start/stop synchronization enabled? * Thread-safe: yes * Realtime-safe: no */ bool isStartStopSyncEnabled() const; /*! @brief: Enable start/stop synchronization. * Thread-safe: yes * Realtime-safe: no */ void enableStartStopSync(bool bEnable); /*! @brief How many peers are currently connected in a Link session? * Thread-safe: yes * Realtime-safe: yes */ std::size_t numPeers() const; /*! @brief Register a callback to be notified when the number of * peers in the Link session changes. * Thread-safe: yes * Realtime-safe: no * * @discussion The callback is invoked on a Link-managed thread. * * @param callback The callback signature is: * void (std::size_t numPeers) */ template void setNumPeersCallback(Callback callback); /*! @brief Register a callback to be notified when the session * tempo changes. * Thread-safe: yes * Realtime-safe: no * * @discussion The callback is invoked on a Link-managed thread. * * @param callback The callback signature is: void (double bpm) */ template void setTempoCallback(Callback callback); /*! brief: Register a callback to be notified when the state of * start/stop isPlaying changes. * Thread-safe: yes * Realtime-safe: no * * @discussion The callback is invoked on a Link-managed thread. * * @param callback The callback signature is: * void (bool isPlaying) */ template void setStartStopCallback(Callback callback); /*! @brief The clock used by Link. * Thread-safe: yes * Realtime-safe: yes * * @discussion The Clock type is a platform-dependent * representation of the system clock. It exposes a ticks() method * that returns the current ticks of the system clock as well as * micros(), which is a normalized representation of the current system * time in std::chrono::microseconds. It also provides conversion * functions ticksToMicros() and microsToTicks() to faciliate * converting between these units. */ Clock clock() const; /*! @brief Capture the current Link Session State from the audio thread. * Thread-safe: no * Realtime-safe: yes * * @discussion This method should ONLY be called in the audio thread * and must not be accessed from any other threads. The returned * object stores a snapshot of the current Link Session State, so it * should be captured and used in a local scope. Storing the * Session State for later use in a different context is not advised * because it will provide an outdated view. */ SessionState captureAudioSessionState() const; /*! @brief Commit the given Session State to the Link session from the * audio thread. * Thread-safe: no * Realtime-safe: yes * * @discussion This method should ONLY be called in the audio * thread. The given Session State will replace the current Link * state. Modifications will be communicated to other peers in the * session. */ void commitAudioSessionState(SessionState state); /*! @brief Capture the current Link Session State from an application * thread. * Thread-safe: yes * Realtime-safe: no * * @discussion Provides a mechanism for capturing the Link Session * State from an application thread (other than the audio thread). * The returned Session State stores a snapshot of the current Link * state, so it should be captured and used in a local scope. * Storing the it for later use in a different context is not * advised because it will provide an outdated view. */ SessionState captureAppSessionState() const; /*! @brief Commit the given Session State to the Link session from an * application thread. * Thread-safe: yes * Realtime-safe: no * * @discussion The given Session State will replace the current Link * Session State. Modifications of the Session State will be * communicated to other peers in the session. */ void commitAppSessionState(SessionState state); /*! @class SessionState * @brief Representation of a timeline and the start/stop state * * @discussion A SessionState object is intended for use in a local scope within * a single thread - none of its methods are thread-safe. All of its methods are * non-blocking, so it is safe to use from a realtime thread. * It provides functions to observe and manipulate the timeline and start/stop * state. * * The timeline is a representation of a mapping between time and beats for varying * quanta. * The start/stop state represents the user intention to start or stop transport at * a specific time. Start stop synchronization is an optional feature that allows to * share the user request to start or stop transport between a subgroup of peers in * a Link session. When observing a change of start/stop state, audio playback of a * peer should be started or stopped the same way it would have happened if the user * had requested that change at the according time locally. The start/stop state can * only be changed by the user. This means that the current local start/stop state * persists when joining or leaving a Link session. After joining a Link session * start/stop change requests will be communicated to all connected peers. */ class SessionState { public: SessionState(const link::ApiState state, const bool bRespectQuantum); /*! @brief: The tempo of the timeline, in bpm */ double tempo() const; /*! @brief: Set the timeline tempo to the given bpm value, taking * effect at the given time. */ void setTempo(double bpm, std::chrono::microseconds atTime); /*! @brief: Get the beat value corresponding to the given time * for the given quantum. * * @discussion: The magnitude of the resulting beat value is * unique to this Link instance, but its phase with respect to * the provided quantum is shared among all session * peers. For non-negative beat values, the following * property holds: fmod(beatAtTime(t, q), q) == phaseAtTime(t, q) */ double beatAtTime(std::chrono::microseconds time, double quantum) const; /*! @brief: Get the session phase at the given time for the given * quantum. * * @discussion: The result is in the interval [0, quantum). The * result is equivalent to fmod(beatAtTime(t, q), q) for * non-negative beat values. This method is convenient if the * client is only interested in the phase and not the beat * magnitude. Also, unlike fmod, it handles negative beat values * correctly. */ double phaseAtTime(std::chrono::microseconds time, double quantum) const; /*! @brief: Get the time at which the given beat occurs for the * given quantum. * * @discussion: The inverse of beatAtTime, assuming a constant * tempo. beatAtTime(timeAtBeat(b, q), q) === b. */ std::chrono::microseconds timeAtBeat(double beat, double quantum) const; /*! @brief: Attempt to map the given beat to the given time in the * context of the given quantum. * * @discussion: This method behaves differently depending on the * state of the session. If no other peers are connected, * then this instance is in a session by itself and is free to * re-map the beat/time relationship whenever it pleases. In this * case, beatAtTime(time, quantum) == beat after this method has * been called. * * If there are other peers in the session, this instance * should not abruptly re-map the beat/time relationship in the * session because that would lead to beat discontinuities among * the other peers. In this case, the given beat will be mapped * to the next time value greater than the given time with the * same phase as the given beat. * * This method is specifically designed to enable the concept of * "quantized launch" in client applications. If there are no other * peers in the session, then an event (such as starting * transport) happens immediately when it is requested. If there * are other peers, however, we wait until the next time at which * the session phase matches the phase of the event, thereby * executing the event in-phase with the other peers in the * session. The client only needs to invoke this method to * achieve this behavior and should not need to explicitly check * the number of peers. */ void requestBeatAtTime(double beat, std::chrono::microseconds time, double quantum); /*! @brief: Rudely re-map the beat/time relationship for all peers * in a session. * * @discussion: DANGER: This method should only be needed in * certain special circumstances. Most applications should not * use it. It is very similar to requestBeatAtTime except that it * does not fall back to the quantizing behavior when it is in a * session with other peers. Calling this method will * unconditionally map the given beat to the given time and * broadcast the result to the session. This is very anti-social * behavior and should be avoided. * * One of the few legitimate uses of this method is to * synchronize a Link session with an external clock source. By * periodically forcing the beat/time mapping according to an * external clock source, a peer can effectively bridge that * clock into a Link session. Much care must be taken at the * application layer when implementing such a feature so that * users do not accidentally disrupt Link sessions that they may * join. */ void forceBeatAtTime(double beat, std::chrono::microseconds time, double quantum); /*! @brief: Set if transport should be playing or stopped, taking effect * at the given time. */ void setIsPlaying(bool isPlaying, std::chrono::microseconds time); /*! @brief: Is transport playing? */ bool isPlaying() const; /*! @brief: Get the time at which a transport start/stop occurs */ std::chrono::microseconds timeForIsPlaying() const; /*! @brief: Convenience function to attempt to map the given beat to the time * when transport is starting to play in context of the given quantum. * This function evaluates to a no-op if isPlaying() equals false. */ void requestBeatAtStartPlayingTime(double beat, double quantum); /*! @brief: Convenience function to start or stop transport at a given time and * attempt to map the given beat to this time in context of the given quantum. */ void setIsPlayingAndRequestBeatAtTime( bool isPlaying, std::chrono::microseconds time, double beat, double quantum); private: friend Link; link::ApiState mOriginalState; link::ApiState mState; bool mbRespectQuantum; }; private: std::mutex mCallbackMutex; link::PeerCountCallback mPeerCountCallback; link::TempoCallback mTempoCallback; link::StartStopStateCallback mStartStopCallback; Clock mClock; link::platform::Controller mController; }; } // namespace ableton #include link-Link-3.0.2/include/ableton/discovery/0000755000175000017500000000000013303226525020655 5ustar zmoelnigzmoelniglink-Link-3.0.2/include/ableton/discovery/test/0000755000175000017500000000000013303226525021634 5ustar zmoelnigzmoelniglink-Link-3.0.2/include/ableton/discovery/test/PayloadEntries.hpp0000644000175000017500000000732513303226525025277 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include #include namespace ableton { namespace discovery { namespace test { // Test payload entries // A fixed-size entry type struct Foo { static const std::int32_t key = '_foo'; static_assert(key == 0x5f666f6f, "Unexpected byte order"); std::int32_t fooVal; friend std::uint32_t sizeInByteStream(const Foo& foo) { // Namespace qualification is needed to avoid ambiguous function definitions return discovery::sizeInByteStream(foo.fooVal); } template friend It toNetworkByteStream(const Foo& foo, It out) { return discovery::toNetworkByteStream(foo.fooVal, std::move(out)); } template static std::pair fromNetworkByteStream(It begin, It end) { auto result = Deserialize::fromNetworkByteStream( std::move(begin), std::move(end)); return std::make_pair(Foo{std::move(result.first)}, std::move(result.second)); } }; // A variable-size entry type struct Bar { static const std::int32_t key = '_bar'; static_assert(key == 0x5f626172, "Unexpected byte order"); std::vector barVals; friend std::uint32_t sizeInByteStream(const Bar& bar) { return discovery::sizeInByteStream(bar.barVals); } template friend It toNetworkByteStream(const Bar& bar, It out) { return discovery::toNetworkByteStream(bar.barVals, out); } template static std::pair fromNetworkByteStream(It begin, It end) { auto result = Deserialize::fromNetworkByteStream( std::move(begin), std::move(end)); return std::make_pair(Bar{std::move(result.first)}, std::move(result.second)); } }; // An entry type with two vectors struct Foobar { static const std::int32_t key = 'fbar'; static_assert(key == 0x66626172, "Unexpected byte order"); using FoobarVector = std::vector; using FoobarTuple = std::tuple; FoobarVector fooVals; FoobarVector barVals; friend std::uint32_t sizeInByteStream(const Foobar& foobar) { return discovery::sizeInByteStream(foobar.asTuple()); } template friend It toNetworkByteStream(const Foobar& foobar, It out) { return discovery::toNetworkByteStream(foobar.asTuple(), out); } template static std::pair fromNetworkByteStream(It begin, It end) { const auto result = Deserialize::fromNetworkByteStream(std::move(begin), std::move(end)); const auto foobar = Foobar{std::get<0>(result.first), std::get<1>(result.first)}; return std::make_pair(std::move(foobar), std::move(result.second)); } FoobarTuple asTuple() const { return std::make_tuple(fooVals, barVals); } }; } // namespace test } // namespace discovery } // namespace ableton link-Link-3.0.2/include/ableton/discovery/test/Interface.hpp0000644000175000017500000000415613303226525024253 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include namespace ableton { namespace discovery { namespace test { class Interface { public: void send(const uint8_t* const bytes, const size_t numBytes, const asio::ip::udp::endpoint& endpoint) { sentMessages.push_back( std::make_pair(std::vector{bytes, bytes + numBytes}, endpoint)); } template void receive(Callback callback, Tag tag) { mCallback = [callback, tag](const asio::ip::udp::endpoint& from, const std::vector& buffer) { callback(tag, from, begin(buffer), end(buffer)); }; } template void incomingMessage( const asio::ip::udp::endpoint& from, It messageBegin, It messageEnd) { std::vector buffer{messageBegin, messageEnd}; mCallback(from, buffer); } asio::ip::udp::endpoint endpoint() const { return asio::ip::udp::endpoint({}, 0); } using SentMessage = std::pair, asio::ip::udp::endpoint>; std::vector sentMessages; private: using ReceiveCallback = std::function&)>; ReceiveCallback mCallback; }; } // namespace test } // namespace discovery } // namespace ableton link-Link-3.0.2/include/ableton/discovery/test/Socket.hpp0000644000175000017500000000445513303226525023605 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include namespace ableton { namespace discovery { namespace test { class Socket { public: Socket(util::test::IoService&) { } friend void configureUnicastSocket(Socket&, const asio::ip::address_v4&) { } std::size_t send( const uint8_t* const pData, const size_t numBytes, const asio::ip::udp::endpoint& to) { sentMessages.push_back( std::make_pair(std::vector{pData, pData + numBytes}, to)); return numBytes; } template void receive(Handler handler) { mCallback = [handler](const asio::ip::udp::endpoint& from, const std::vector& buffer) { handler(from, begin(buffer), end(buffer)); }; } template void incomingMessage( const asio::ip::udp::endpoint& from, It messageBegin, It messageEnd) { std::vector buffer{messageBegin, messageEnd}; mCallback(from, buffer); } asio::ip::udp::endpoint endpoint() const { return asio::ip::udp::endpoint({}, 0); } using SentMessage = std::pair, asio::ip::udp::endpoint>; std::vector sentMessages; private: using ReceiveCallback = std::function&)>; ReceiveCallback mCallback; }; } // namespace test } // namespace discovery } // namespace ableton link-Link-3.0.2/include/ableton/discovery/PeerGateway.hpp0000644000175000017500000001704013303226525023605 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include #include #include namespace ableton { namespace discovery { template class PeerGateway { public: // The peer types are defined by the observer but must match with those // used by the Messenger using ObserverT = typename util::Injected::type; using NodeState = typename ObserverT::GatewayObserverNodeState; using NodeId = typename ObserverT::GatewayObserverNodeId; using Timer = typename util::Injected::type::Timer; using TimerError = typename Timer::ErrorCode; PeerGateway(util::Injected messenger, util::Injected observer, util::Injected io) : mpImpl(new Impl(std::move(messenger), std::move(observer), std::move(io))) { mpImpl->listen(); } PeerGateway(const PeerGateway&) = delete; PeerGateway& operator=(const PeerGateway&) = delete; PeerGateway(PeerGateway&& rhs) : mpImpl(std::move(rhs.mpImpl)) { } void updateState(NodeState state) { mpImpl->updateState(std::move(state)); } private: using PeerTimeout = std::pair; using PeerTimeouts = std::vector; struct Impl : std::enable_shared_from_this { Impl(util::Injected messenger, util::Injected observer, util::Injected io) : mMessenger(std::move(messenger)) , mObserver(std::move(observer)) , mIo(std::move(io)) , mPruneTimer(mIo->makeTimer()) { } void updateState(NodeState state) { mMessenger->updateState(std::move(state)); try { mMessenger->broadcastState(); } catch (const std::runtime_error& err) { info(mIo->log()) << "State broadcast failed on gateway: " << err.what(); } } void listen() { mMessenger->receive(util::makeAsyncSafe(this->shared_from_this())); } // Operators for handling incoming messages void operator()(const PeerState& msg) { onPeerState(msg.peerState, msg.ttl); listen(); } void operator()(const ByeBye& msg) { onByeBye(msg.peerId); listen(); } void onPeerState(const NodeState& nodeState, const int ttl) { using namespace std; const auto peerId = nodeState.ident(); const auto existing = findPeer(peerId); if (existing != end(mPeerTimeouts)) { // If the peer is already present in our timeout list, remove it // as it will be re-inserted below. mPeerTimeouts.erase(existing); } auto newTo = make_pair(mPruneTimer.now() + std::chrono::seconds(ttl), peerId); mPeerTimeouts.insert( upper_bound(begin(mPeerTimeouts), end(mPeerTimeouts), newTo, TimeoutCompare{}), move(newTo)); sawPeer(*mObserver, nodeState); scheduleNextPruning(); } void onByeBye(const NodeId& peerId) { const auto it = findPeer(peerId); if (it != mPeerTimeouts.end()) { peerLeft(*mObserver, it->second); mPeerTimeouts.erase(it); } } void pruneExpiredPeers() { using namespace std; const auto test = make_pair(mPruneTimer.now(), NodeId{}); debug(mIo->log()) << "pruning peers @ " << test.first.time_since_epoch().count(); const auto endExpired = lower_bound(begin(mPeerTimeouts), end(mPeerTimeouts), test, TimeoutCompare{}); for_each(begin(mPeerTimeouts), endExpired, [this](const PeerTimeout& pto) { info(mIo->log()) << "pruning peer " << pto.second; peerTimedOut(*mObserver, pto.second); }); mPeerTimeouts.erase(begin(mPeerTimeouts), endExpired); scheduleNextPruning(); } void scheduleNextPruning() { // Find the next peer to expire and set the timer based on it if (!mPeerTimeouts.empty()) { // Add a second of padding to the timer to avoid over-eager timeouts const auto t = mPeerTimeouts.front().first + std::chrono::seconds(1); debug(mIo->log()) << "scheduling next pruning for " << t.time_since_epoch().count() << " because of peer " << mPeerTimeouts.front().second; mPruneTimer.expires_at(t); mPruneTimer.async_wait([this](const TimerError e) { if (!e) { pruneExpiredPeers(); } }); } } struct TimeoutCompare { bool operator()(const PeerTimeout& lhs, const PeerTimeout& rhs) const { return lhs.first < rhs.first; } }; typename PeerTimeouts::iterator findPeer(const NodeId& peerId) { return std::find_if(begin(mPeerTimeouts), end(mPeerTimeouts), [&peerId](const PeerTimeout& pto) { return pto.second == peerId; }); } util::Injected mMessenger; util::Injected mObserver; util::Injected mIo; Timer mPruneTimer; PeerTimeouts mPeerTimeouts; // Invariant: sorted by time_point }; std::shared_ptr mpImpl; }; template PeerGateway makePeerGateway( util::Injected messenger, util::Injected observer, util::Injected io) { return {std::move(messenger), std::move(observer), std::move(io)}; } // IpV4 gateway types template using IpV4Messenger = UdpMessenger< IpV4Interface::type&, v1::kMaxMessageSize>, StateQuery, IoContext>; template using IpV4Gateway = PeerGateway::type&>, PeerObserver, IoContext>; // Factory function to bind a PeerGateway to an IpV4Interface with the given address. template IpV4Gateway makeIpV4Gateway( util::Injected io, const asio::ip::address_v4& addr, util::Injected observer, NodeState state) { using namespace std; using namespace util; const uint8_t ttl = 5; const uint8_t ttlRatio = 20; auto iface = makeIpV4Interface(injectRef(*io), addr); auto messenger = makeUdpMessenger(injectVal(move(iface)), move(state), injectRef(*io), ttl, ttlRatio); return {injectVal(move(messenger)), move(observer), move(io)}; } } // namespace discovery } // namespace ableton link-Link-3.0.2/include/ableton/discovery/v1/0000755000175000017500000000000013303226525021203 5ustar zmoelnigzmoelniglink-Link-3.0.2/include/ableton/discovery/v1/Messages.hpp0000644000175000017500000001260313303226525023465 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include namespace ableton { namespace discovery { namespace v1 { // The maximum size of a message, in bytes const std::size_t kMaxMessageSize = 512; // Utility typedef for an array of bytes of maximum message size using MessageBuffer = std::array; using MessageType = uint8_t; using SessionGroupId = uint16_t; const MessageType kInvalid = 0; const MessageType kAlive = 1; const MessageType kResponse = 2; const MessageType kByeBye = 3; template struct MessageHeader { MessageType messageType; uint8_t ttl; SessionGroupId groupId; NodeId ident; friend std::uint32_t sizeInByteStream(const MessageHeader& header) { return discovery::sizeInByteStream(header.messageType) + discovery::sizeInByteStream(header.ttl) + discovery::sizeInByteStream(header.groupId) + discovery::sizeInByteStream(header.ident); } template friend It toNetworkByteStream(const MessageHeader& header, It out) { return discovery::toNetworkByteStream(header.ident, discovery::toNetworkByteStream(header.groupId, discovery::toNetworkByteStream(header.ttl, discovery::toNetworkByteStream(header.messageType, std::move(out))))); } template static std::pair fromNetworkByteStream(It begin, const It end) { using namespace std; MessageHeader header; tie(header.messageType, begin) = Deserialize::fromNetworkByteStream(begin, end); tie(header.ttl, begin) = Deserialize::fromNetworkByteStream(begin, end); tie(header.groupId, begin) = Deserialize::fromNetworkByteStream(begin, end); tie(header.ident, begin) = Deserialize::fromNetworkByteStream(begin, end); return make_pair(move(header), move(begin)); } }; namespace detail { // Types that are only used in the sending/parsing of messages, not // publicly exposed. using ProtocolHeader = std::array; const ProtocolHeader kProtocolHeader = {{'_', 'a', 's', 'd', 'p', '_', 'v', 1}}; // Must have at least kMaxMessageSize bytes available in the output stream template It encodeMessage(NodeId from, const uint8_t ttl, const MessageType messageType, const Payload& payload, It out) { using namespace std; const MessageHeader header = {messageType, ttl, 0, std::move(from)}; const auto messageSize = kProtocolHeader.size() + sizeInByteStream(header) + sizeInByteStream(payload); if (messageSize < kMaxMessageSize) { return toNetworkByteStream( payload, toNetworkByteStream( header, copy(begin(kProtocolHeader), end(kProtocolHeader), move(out)))); } else { throw range_error("Exceeded maximum message size"); } } } // namespace detail template It aliveMessage(NodeId from, const uint8_t ttl, const Payload& payload, It out) { return detail::encodeMessage(std::move(from), ttl, kAlive, payload, std::move(out)); } template It responseMessage(NodeId from, const uint8_t ttl, const Payload& payload, It out) { return detail::encodeMessage(std::move(from), ttl, kResponse, payload, std::move(out)); } template It byeByeMessage(NodeId from, It out) { return detail::encodeMessage( std::move(from), 0, kByeBye, makePayload(), std::move(out)); } template std::pair, It> parseMessageHeader(It bytesBegin, const It bytesEnd) { using namespace std; using ItDiff = typename iterator_traits::difference_type; MessageHeader header = {}; const auto protocolHeaderSize = discovery::sizeInByteStream(detail::kProtocolHeader); const auto minMessageSize = static_cast(protocolHeaderSize + sizeInByteStream(header)); // If there are enough bytes in the stream to make a header and if // the first bytes in the stream are the protocol header, then // proceed to parse the stream. if (distance(bytesBegin, bytesEnd) >= minMessageSize && equal(begin(detail::kProtocolHeader), end(detail::kProtocolHeader), bytesBegin)) { tie(header, bytesBegin) = MessageHeader::fromNetworkByteStream( bytesBegin + protocolHeaderSize, bytesEnd); } return make_pair(move(header), move(bytesBegin)); } } // namespace v1 } // namespace discovery } // namespace ableton link-Link-3.0.2/include/ableton/discovery/Payload.hpp0000644000175000017500000001752513303226525022771 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include namespace ableton { namespace discovery { struct PayloadEntryHeader { using Key = std::uint32_t; using Size = std::uint32_t; Key key; Size size; friend Size sizeInByteStream(const PayloadEntryHeader& header) { return sizeInByteStream(header.key) + sizeInByteStream(header.size); } template friend It toNetworkByteStream(const PayloadEntryHeader& header, It out) { return toNetworkByteStream( header.size, toNetworkByteStream(header.key, std::move(out))); } template static std::pair fromNetworkByteStream(It begin, const It end) { using namespace std; Key key; Size size; tie(key, begin) = Deserialize::fromNetworkByteStream(begin, end); tie(size, begin) = Deserialize::fromNetworkByteStream(begin, end); return make_pair(PayloadEntryHeader{move(key), move(size)}, move(begin)); } }; template struct PayloadEntry { PayloadEntry(EntryType entryVal) : value(std::move(entryVal)) { header = {EntryType::key, sizeInByteStream(value)}; } PayloadEntryHeader header; EntryType value; friend std::uint32_t sizeInByteStream(const PayloadEntry& entry) { return sizeInByteStream(entry.header) + sizeInByteStream(entry.value); } template friend It toNetworkByteStream(const PayloadEntry& entry, It out) { return toNetworkByteStream( entry.value, toNetworkByteStream(entry.header, std::move(out))); } }; namespace detail { template using HandlerMap = std::unordered_map>; // Given an index of handlers and a byte range, parse the bytes as a // sequence of payload entries and invoke the appropriate handler for // each entry type. Entries that are encountered that do not have a // corresponding handler in the map are ignored. Throws // std::runtime_error if parsing fails for any entry. Note that if an // exception is thrown, some of the handlers may have already been called. template void parseByteStream(HandlerMap& map, It bsBegin, const It bsEnd) { using namespace std; while (bsBegin < bsEnd) { // Try to parse an entry header at this location in the byte stream PayloadEntryHeader header; It valueBegin; tie(header, valueBegin) = Deserialize::fromNetworkByteStream(bsBegin, bsEnd); // Ensure that the reported size of the entry does not exceed the // length of the byte stream It valueEnd = valueBegin + header.size; if (bsEnd < valueEnd) { throw range_error("Payload with incorrect size."); } // The next entry will start at the end of this one bsBegin = valueEnd; // Use the appropriate handler for this entry, if available auto handlerIt = map.find(header.key); if (handlerIt != end(map)) { handlerIt->second(move(valueBegin), move(valueEnd)); } } } } // namespace detail // Payload encoding template struct Payload; template struct Payload { Payload(First first, Rest rest) : mFirst(std::move(first)) , mRest(std::move(rest)) { } Payload(PayloadEntry first, Rest rest) : mFirst(std::move(first)) , mRest(std::move(rest)) { } template using PayloadSum = Payload>; // Concatenate payloads together into a single payload template friend PayloadSum operator+( Payload lhs, Payload rhs) { return {std::move(lhs.mFirst), std::move(lhs.mRest) + std::move(rhs)}; } friend std::size_t sizeInByteStream(const Payload& payload) { return sizeInByteStream(payload.mFirst) + sizeInByteStream(payload.mRest); } template friend It toNetworkByteStream(const Payload& payload, It streamIt) { return toNetworkByteStream( payload.mRest, toNetworkByteStream(payload.mFirst, std::move(streamIt))); } PayloadEntry mFirst; Rest mRest; }; template <> struct Payload<> { template using PayloadSum = Payload; template friend PayloadSum operator+(Payload, Payload rhs) { return rhs; } friend std::size_t sizeInByteStream(const Payload&) { return 0; } template friend It toNetworkByteStream(const Payload&, It streamIt) { return streamIt; } }; template struct PayloadBuilder; // Payload factory function template auto makePayload(Entries... entries) -> decltype(PayloadBuilder{}(std::move(entries)...)) { return PayloadBuilder{}(std::move(entries)...); } template struct PayloadBuilder { auto operator()(First first, Rest... rest) -> Payload { return {std::move(first), makePayload(std::move(rest)...)}; } }; template <> struct PayloadBuilder<> { Payload<> operator()() { return {}; } }; // Parse payloads to values template struct ParsePayload; template struct ParsePayload { template static void parse(It begin, It end, Handlers... handlers) { detail::HandlerMap map; collectHandlers(map, std::move(handlers)...); detail::parseByteStream(map, std::move(begin), std::move(end)); } template static void collectHandlers( detail::HandlerMap& map, FirstHandler handler, RestHandlers... rest) { using namespace std; map[First::key] = [handler](const It begin, const It end) { const auto res = First::fromNetworkByteStream(begin, end); if (res.second != end) { std::ostringstream stringStream; stringStream << "Parsing payload entry " << First::key << " did not consume the expected number of bytes. " << " Expected: " << distance(begin, end) << ", Actual: " << distance(begin, res.second); throw range_error(stringStream.str()); } handler(res.first); }; ParsePayload::collectHandlers(map, std::move(rest)...); } }; template <> struct ParsePayload<> { template static void collectHandlers(detail::HandlerMap&) { } }; template void parsePayload(It begin, It end, Handlers... handlers) { using namespace std; ParsePayload::parse(move(begin), move(end), move(handlers)...); } } // namespace discovery } // namespace ableton link-Link-3.0.2/include/ableton/discovery/IpV4Interface.hpp0000644000175000017500000000643413303226525024000 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include namespace ableton { namespace discovery { inline asio::ip::udp::endpoint multicastEndpoint() { return {asio::ip::address::from_string("224.76.78.75"), 20808}; } // Type tags for dispatching between unicast and multicast packets struct MulticastTag { }; struct UnicastTag { }; template class IpV4Interface { public: using Socket = typename util::Injected::type::template Socket; IpV4Interface(util::Injected io, const asio::ip::address_v4& addr) : mIo(std::move(io)) , mMulticastReceiveSocket(mIo->template openMulticastSocket(addr)) , mSendSocket(mIo->template openUnicastSocket(addr)) { } IpV4Interface(const IpV4Interface&) = delete; IpV4Interface& operator=(const IpV4Interface&) = delete; IpV4Interface(IpV4Interface&& rhs) : mIo(std::move(rhs.mIo)) , mMulticastReceiveSocket(std::move(rhs.mMulticastReceiveSocket)) , mSendSocket(std::move(rhs.mSendSocket)) { } std::size_t send( const uint8_t* const pData, const size_t numBytes, const asio::ip::udp::endpoint& to) { return mSendSocket.send(pData, numBytes, to); } template void receive(Handler handler, UnicastTag) { mSendSocket.receive(SocketReceiver{std::move(handler)}); } template void receive(Handler handler, MulticastTag) { mMulticastReceiveSocket.receive( SocketReceiver(std::move(handler))); } asio::ip::udp::endpoint endpoint() const { return mSendSocket.endpoint(); } private: template struct SocketReceiver { SocketReceiver(Handler handler) : mHandler(std::move(handler)) { } template void operator()( const asio::ip::udp::endpoint& from, const It messageBegin, const It messageEnd) { mHandler(Tag{}, from, messageBegin, messageEnd); } Handler mHandler; }; util::Injected mIo; Socket mMulticastReceiveSocket; Socket mSendSocket; }; template IpV4Interface makeIpV4Interface( util::Injected io, const asio::ip::address_v4& addr) { return {std::move(io), addr}; } } // namespace discovery } // namespace ableton link-Link-3.0.2/include/ableton/discovery/InterfaceScanner.hpp0000644000175000017500000000543513303226525024607 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include #include namespace ableton { namespace discovery { // Callback takes a range of asio::ip:address which is // guaranteed to be sorted and unique template class InterfaceScanner { public: using Timer = typename util::Injected::type::Timer; InterfaceScanner(const std::chrono::seconds period, util::Injected callback, util::Injected io) : mPeriod(period) , mCallback(std::move(callback)) , mIo(std::move(io)) , mTimer(mIo->makeTimer()) { } void enable(const bool bEnable) { if (bEnable) { scan(); } else { mTimer.cancel(); } } void scan() { using namespace std; debug(mIo->log()) << "Scanning network interfaces"; // Rescan the hardware for available network interface addresses vector addrs = mIo->scanNetworkInterfaces(); // Sort and unique them to guarantee consistent comparison sort(begin(addrs), end(addrs)); addrs.erase(unique(begin(addrs), end(addrs)), end(addrs)); // Pass them to the callback (*mCallback)(std::move(addrs)); // setup the next scanning mTimer.expires_from_now(mPeriod); using ErrorCode = typename Timer::ErrorCode; mTimer.async_wait([this](const ErrorCode e) { if (!e) { scan(); } }); } private: const std::chrono::seconds mPeriod; util::Injected mCallback; util::Injected mIo; Timer mTimer; }; // Factory function template InterfaceScanner makeInterfaceScanner( const std::chrono::seconds period, util::Injected callback, util::Injected io) { using namespace std; return {period, move(callback), move(io)}; } } // namespace discovery } // namespace ableton link-Link-3.0.2/include/ableton/discovery/NetworkByteStreamSerializable.hpp0000644000175000017500000003072113303226525027351 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #if defined(LINK_PLATFORM_MACOSX) #include #elif defined(LINK_PLATFORM_LINUX) #include #endif #include #include #include #include #include #if defined(LINK_PLATFORM_WINDOWS) #include #include #include #endif namespace ableton { namespace discovery { // Concept: NetworkByteStreamSerializable // // A type that can be encoded to a stream of bytes and decoded from a // stream of bytes in network byte order. The following type is for // documentation purposes only. struct NetworkByteStreamSerializable { friend std::uint32_t sizeInByteStream(const NetworkByteStreamSerializable&); // The byte stream pointed to by 'out' must have sufficient space to // hold this object, as defined by sizeInByteStream. template friend It toNetworkByteStream(const NetworkByteStreamSerializable&, It out); }; // Deserialization aspect of the concept. Outside of the demonstration // type above because clients must specify the type // explicitly. Default implementation just defers to a class static // method on T. For types that can't provide such a method, specialize // this template. template struct Deserialize { // Throws std::runtime_exception if parsing the type from the given // byte range fails. Returns a pair of the correctly parsed value // and an iterator to the next byte to parse. template static std::pair fromNetworkByteStream(It begin, It end) { return T::fromNetworkByteStream(std::move(begin), std::move(end)); } }; // Default size implementation. Works for primitive types. template ::value>::type* = nullptr> std::uint32_t sizeInByteStream(T) { return sizeof(T); } namespace detail { // utilities for implementing concept for primitive types template It copyToByteStream(T t, It out) { using namespace std; return copy_n( reinterpret_cast::pointer>(&t), sizeof(t), out); } template std::pair copyFromByteStream(It begin, const It end) { using namespace std; using ItDiff = typename iterator_traits::difference_type; if (distance(begin, end) < static_cast(sizeof(T))) { throw range_error("Parsing type from byte stream failed"); } else { T t; const auto n = sizeof(t); copy_n(begin, n, reinterpret_cast(&t)); return make_pair(t, begin + n); } } } // namespace detail // Model the concept for unsigned integral types // uint8_t template It toNetworkByteStream(const uint8_t byte, It out) { return detail::copyToByteStream(byte, std::move(out)); } template <> struct Deserialize { template static std::pair fromNetworkByteStream(It begin, It end) { return detail::copyFromByteStream(std::move(begin), std::move(end)); } }; // uint16_t template It toNetworkByteStream(uint16_t s, It out) { return detail::copyToByteStream(htons(s), std::move(out)); } template <> struct Deserialize { template static std::pair fromNetworkByteStream(It begin, It end) { auto result = detail::copyFromByteStream(std::move(begin), std::move(end)); result.first = ntohs(result.first); return result; } }; // uint32_t template It toNetworkByteStream(uint32_t l, It out) { return detail::copyToByteStream(htonl(l), std::move(out)); } template <> struct Deserialize { template static std::pair fromNetworkByteStream(It begin, It end) { auto result = detail::copyFromByteStream(std::move(begin), std::move(end)); result.first = ntohl(result.first); return result; } }; // int32_t in terms of uint32_t template It toNetworkByteStream(int32_t l, It out) { return toNetworkByteStream(reinterpret_cast(l), std::move(out)); } template <> struct Deserialize { template static std::pair fromNetworkByteStream(It begin, It end) { auto result = Deserialize::fromNetworkByteStream(std::move(begin), std::move(end)); return std::make_pair(reinterpret_cast(result.first), result.second); } }; // uint64_t template It toNetworkByteStream(uint64_t ll, It out) { return detail::copyToByteStream(htonll(ll), std::move(out)); } template <> struct Deserialize { template static std::pair fromNetworkByteStream(It begin, It end) { auto result = detail::copyFromByteStream(std::move(begin), std::move(end)); result.first = ntohll(result.first); return result; } }; // int64_t in terms of uint64_t template It toNetworkByteStream(int64_t ll, It out) { return toNetworkByteStream(reinterpret_cast(ll), std::move(out)); } template <> struct Deserialize { template static std::pair fromNetworkByteStream(It begin, It end) { auto result = Deserialize::fromNetworkByteStream(std::move(begin), std::move(end)); return std::make_pair(reinterpret_cast(result.first), result.second); } }; // bool inline std::uint32_t sizeInByteStream(bool) { return sizeof(uint8_t); } template It toNetworkByteStream(bool bl, It out) { return toNetworkByteStream(static_cast(bl), std::move(out)); } template <> struct Deserialize { template static std::pair fromNetworkByteStream(It begin, It end) { auto result = Deserialize::fromNetworkByteStream(std::move(begin), std::move(end)); return std::make_pair(result.first != 0, result.second); } }; // std::chrono::microseconds inline std::uint32_t sizeInByteStream(const std::chrono::microseconds micros) { return sizeInByteStream(micros.count()); } template It toNetworkByteStream(const std::chrono::microseconds micros, It out) { static_assert(sizeof(int64_t) == sizeof(std::chrono::microseconds::rep), "The size of microseconds::rep must matche the size of int64_t."); return toNetworkByteStream(static_cast(micros.count()), std::move(out)); } template <> struct Deserialize { template static std::pair fromNetworkByteStream(It begin, It end) { using namespace std; auto result = Deserialize::fromNetworkByteStream(move(begin), move(end)); return make_pair(chrono::microseconds{result.first}, result.second); } }; namespace detail { // Generic serialize/deserialize utilities for containers template std::uint32_t containerSizeInByteStream(const Container& container) { std::uint32_t totalSize = 0; for (const auto& val : container) { totalSize += sizeInByteStream(val); } return totalSize; } template It containerToNetworkByteStream(const Container& container, It out) { for (const auto& val : container) { out = toNetworkByteStream(val, out); } return out; } template BytesIt deserializeContainer(BytesIt bytesBegin, const BytesIt bytesEnd, InsertIt contBegin, const std::uint32_t maxElements) { using namespace std; std::uint32_t numElements = 0; while (bytesBegin < bytesEnd && numElements < maxElements) { T newVal; tie(newVal, bytesBegin) = Deserialize::fromNetworkByteStream(bytesBegin, bytesEnd); *contBegin++ = newVal; ++numElements; } return bytesBegin; } } // namespace detail // Need specific overloads for each container type, but use above // utilities for common implementation // array template std::uint32_t sizeInByteStream(const std::array& arr) { return detail::containerSizeInByteStream(arr); } template It toNetworkByteStream(const std::array& arr, It out) { return detail::containerToNetworkByteStream(arr, std::move(out)); } template struct Deserialize> { template static std::pair, It> fromNetworkByteStream(It begin, It end) { using namespace std; array result{}; auto resultIt = detail::deserializeContainer(move(begin), move(end), move(result.begin()), Size); return make_pair(move(result), move(resultIt)); } }; // vector template std::uint32_t sizeInByteStream(const std::vector& vec) { return sizeof(uint32_t) + detail::containerSizeInByteStream(vec); } template It toNetworkByteStream(const std::vector& vec, It out) { out = toNetworkByteStream(static_cast(vec.size()), out); return detail::containerToNetworkByteStream(vec, std::move(out)); } template struct Deserialize> { template static std::pair, It> fromNetworkByteStream( It bytesBegin, It bytesEnd) { using namespace std; auto result_size = Deserialize::fromNetworkByteStream(move(bytesBegin), bytesEnd); vector result; auto resultIt = detail::deserializeContainer( move(result_size.second), move(bytesEnd), back_inserter(result), result_size.first); return make_pair(move(result), move(resultIt)); } }; // 2-tuple template std::uint32_t sizeInByteStream(const std::tuple& tup) { return sizeInByteStream(std::get<0>(tup)) + sizeInByteStream(std::get<1>(tup)); } template It toNetworkByteStream(const std::tuple& tup, It out) { return toNetworkByteStream( std::get<1>(tup), toNetworkByteStream(std::get<0>(tup), std::move(out))); } template struct Deserialize> { template static std::pair, It> fromNetworkByteStream(It begin, It end) { using namespace std; auto xres = Deserialize::fromNetworkByteStream(begin, end); auto yres = Deserialize::fromNetworkByteStream(xres.second, end); return make_pair(make_tuple(move(xres.first), move(yres.first)), move(yres.second)); } }; // 3-tuple template std::uint32_t sizeInByteStream(const std::tuple& tup) { return sizeInByteStream(std::get<0>(tup)) + sizeInByteStream(std::get<1>(tup)) + sizeInByteStream(std::get<2>(tup)); } template It toNetworkByteStream(const std::tuple& tup, It out) { return toNetworkByteStream( std::get<2>(tup), toNetworkByteStream(std::get<1>(tup), toNetworkByteStream(std::get<0>(tup), std::move(out)))); } template struct Deserialize> { template static std::pair, It> fromNetworkByteStream(It begin, It end) { using namespace std; auto xres = Deserialize::fromNetworkByteStream(begin, end); auto yres = Deserialize::fromNetworkByteStream(xres.second, end); auto zres = Deserialize::fromNetworkByteStream(yres.second, end); return make_pair(make_tuple(move(xres.first), move(yres.first), move(zres.first)), move(zres.second)); } }; } // namespace discovery } // namespace ableton link-Link-3.0.2/include/ableton/discovery/MessageTypes.hpp0000644000175000017500000000274213303226525024004 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once namespace ableton { namespace discovery { // Message types used in the Ableton service discovery protocol. There // are two logical messages: a state dump and a bye bye. // // A state dump provides all relevant information about the peer's // current state as well as a Time To Live (TTL) value that indicates // how many seconds this state should be considered valid. // // The bye bye indicates that the sender is leaving the session. template struct PeerState { NodeState peerState; int ttl; }; template struct ByeBye { NodeId peerId; }; } // namespace discovery } // namespace ableton link-Link-3.0.2/include/ableton/discovery/Service.hpp0000644000175000017500000000411413303226525022766 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include namespace ableton { namespace discovery { template class Service { public: using ServicePeerGateways = PeerGateways; Service(NodeState state, GatewayFactory factory, util::Injected io) : mGateways( std::chrono::seconds(5), std::move(state), std::move(factory), std::move(io)) { } void enable(const bool bEnable) { mGateways.enable(bEnable); } // Asynchronously operate on the current set of peer gateways. The // handler will be invoked in the service's io context. template void withGatewaysAsync(Handler handler) { mGateways.withGatewaysAsync(std::move(handler)); } void updateNodeState(const NodeState& state) { mGateways.updateNodeState(state); } // Repair the gateway with the given address if possible. Its // sockets may have been closed, for example, and the gateway needs // to be regenerated. void repairGateway(const asio::ip::address& gatewayAddr) { mGateways.repairGateway(gatewayAddr); } private: ServicePeerGateways mGateways; }; } // namespace discovery } // namespace ableton link-Link-3.0.2/include/ableton/discovery/PeerGateways.hpp0000644000175000017500000001525013303226525023771 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include namespace ableton { namespace discovery { // GatewayFactory must have an operator()(NodeState, IoRef, asio::ip::address) // that constructs a new PeerGateway on a given interface address. template class PeerGateways { public: using IoType = typename util::Injected::type; using Gateway = typename std::result_of, asio::ip::address)>::type; using GatewayMap = std::map; PeerGateways(const std::chrono::seconds rescanPeriod, NodeState state, GatewayFactory factory, util::Injected io) : mIo(std::move(io)) { mpScannerCallback = std::make_shared(std::move(state), std::move(factory), *mIo); mpScanner = std::make_shared( rescanPeriod, util::injectShared(mpScannerCallback), util::injectRef(*mIo)); } ~PeerGateways() { // Release the callback in the io thread so that gateway cleanup // doesn't happen in the client thread mIo->async(Deleter{*this}); } PeerGateways(const PeerGateways&) = delete; PeerGateways& operator=(const PeerGateways&) = delete; PeerGateways(PeerGateways&&) = delete; PeerGateways& operator=(PeerGateways&&) = delete; void enable(const bool bEnable) { auto pCallback = mpScannerCallback; auto pScanner = mpScanner; if (pCallback && pScanner) { mIo->async([pCallback, pScanner, bEnable] { pCallback->mGateways.clear(); pScanner->enable(bEnable); }); } } template void withGatewaysAsync(Handler handler) { auto pCallback = mpScannerCallback; if (pCallback) { mIo->async([pCallback, handler] { handler(pCallback->mGateways.begin(), pCallback->mGateways.end()); }); } } void updateNodeState(const NodeState& state) { auto pCallback = mpScannerCallback; if (pCallback) { mIo->async([pCallback, state] { pCallback->mState = state; for (const auto& entry : pCallback->mGateways) { entry.second->updateNodeState(state); } }); } } // If a gateway has become non-responsive or is throwing exceptions, // this method can be invoked to either fix it or discard it. void repairGateway(const asio::ip::address& gatewayAddr) { auto pCallback = mpScannerCallback; auto pScanner = mpScanner; if (pCallback && pScanner) { mIo->async([pCallback, pScanner, gatewayAddr] { if (pCallback->mGateways.erase(gatewayAddr)) { // If we erased a gateway, rescan again immediately so that // we will re-initialize it if it's still present pScanner->scan(); } }); } } private: struct Callback { Callback(NodeState state, GatewayFactory factory, IoType& io) : mState(std::move(state)) , mFactory(std::move(factory)) , mIo(io) { } template void operator()(const AddrRange& range) { using namespace std; // Get the set of current addresses. vector curAddrs; curAddrs.reserve(mGateways.size()); transform(std::begin(mGateways), std::end(mGateways), back_inserter(curAddrs), [](const typename GatewayMap::value_type& vt) { return vt.first; }); // Now use set_difference to determine the set of addresses that // are new and the set of cur addresses that are no longer there vector newAddrs; set_difference(std::begin(range), std::end(range), std::begin(curAddrs), std::end(curAddrs), back_inserter(newAddrs)); vector staleAddrs; set_difference(std::begin(curAddrs), std::end(curAddrs), std::begin(range), std::end(range), back_inserter(staleAddrs)); // Remove the stale addresses for (const auto& addr : staleAddrs) { mGateways.erase(addr); } // Add the new addresses for (const auto& addr : newAddrs) { try { // Only handle v4 for now if (addr.is_v4()) { info(mIo.log()) << "initializing peer gateway on interface " << addr; mGateways.emplace(addr, mFactory(mState, util::injectRef(mIo), addr.to_v4())); } } catch (const runtime_error& e) { warning(mIo.log()) << "failed to init gateway on interface " << addr << " reason: " << e.what(); } } } NodeState mState; GatewayFactory mFactory; IoType& mIo; GatewayMap mGateways; }; using Scanner = InterfaceScanner, IoType&>; struct Deleter { Deleter(PeerGateways& gateways) : mpScannerCallback(std::move(gateways.mpScannerCallback)) , mpScanner(std::move(gateways.mpScanner)) { } void operator()() { mpScanner.reset(); mpScannerCallback.reset(); } std::shared_ptr mpScannerCallback; std::shared_ptr mpScanner; }; std::shared_ptr mpScannerCallback; std::shared_ptr mpScanner; util::Injected mIo; }; // Factory function template std::unique_ptr> makePeerGateways( const std::chrono::seconds rescanPeriod, NodeState state, GatewayFactory factory, util::Injected io) { using namespace std; using Gateways = PeerGateways; return unique_ptr{ new Gateways{rescanPeriod, move(state), move(factory), move(io)}}; } } // namespace discovery } // namespace ableton link-Link-3.0.2/include/ableton/discovery/UdpMessenger.hpp0000644000175000017500000002356113303226525023776 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include #include #include #include #include #include namespace ableton { namespace discovery { // An exception thrown when sending a udp message fails. Stores the // interface through which the sending failed. struct UdpSendException : std::runtime_error { UdpSendException(const std::runtime_error& e, asio::ip::address ifAddr) : std::runtime_error(e.what()) , interfaceAddr(std::move(ifAddr)) { } asio::ip::address interfaceAddr; }; // Throws UdpSendException template void sendUdpMessage(Interface& iface, NodeId from, const uint8_t ttl, const v1::MessageType messageType, const Payload& payload, const asio::ip::udp::endpoint& to) { using namespace std; v1::MessageBuffer buffer; const auto messageBegin = begin(buffer); const auto messageEnd = v1::detail::encodeMessage(move(from), ttl, messageType, payload, messageBegin); const auto numBytes = static_cast(distance(messageBegin, messageEnd)); try { iface.send(buffer.data(), numBytes, to); } catch (const std::runtime_error& err) { throw UdpSendException{err, iface.endpoint().address()}; } } // UdpMessenger uses a "shared_ptr pImpl" pattern to make it movable // and to support safe async handler callbacks when receiving messages // on the given interface. template class UdpMessenger { public: using NodeState = NodeStateT; using NodeId = typename NodeState::IdType; using Timer = typename util::Injected::type::Timer; using TimerError = typename Timer::ErrorCode; using TimePoint = typename Timer::TimePoint; UdpMessenger(util::Injected iface, NodeState state, util::Injected io, const uint8_t ttl, const uint8_t ttlRatio) : mpImpl(std::make_shared( std::move(iface), std::move(state), std::move(io), ttl, ttlRatio)) { // We need to always listen for incoming traffic in order to // respond to peer state broadcasts mpImpl->listen(MulticastTag{}); mpImpl->listen(UnicastTag{}); mpImpl->broadcastState(); } UdpMessenger(const UdpMessenger&) = delete; UdpMessenger& operator=(const UdpMessenger&) = delete; UdpMessenger(UdpMessenger&& rhs) : mpImpl(std::move(rhs.mpImpl)) { } ~UdpMessenger() { if (mpImpl != nullptr) { try { mpImpl->sendByeBye(); } catch (const UdpSendException& err) { debug(mpImpl->mIo->log()) << "Failed to send bye bye message: " << err.what(); } } } void updateState(NodeState state) { mpImpl->updateState(std::move(state)); } // Broadcast the current state of the system to all peers. May throw // std::runtime_error if assembling a broadcast message fails or if // there is an error at the transport layer. Throws on failure. void broadcastState() { mpImpl->broadcastState(); } // Asynchronous receive function for incoming messages from peers. Will // return immediately and the handler will be invoked when a message // is received. Handler must have operator() overloads for PeerState and // ByeBye messages. template void receive(Handler handler) { mpImpl->setReceiveHandler(std::move(handler)); } private: struct Impl : std::enable_shared_from_this { Impl(util::Injected iface, NodeState state, util::Injected io, const uint8_t ttl, const uint8_t ttlRatio) : mIo(std::move(io)) , mInterface(std::move(iface)) , mState(std::move(state)) , mTimer(mIo->makeTimer()) , mLastBroadcastTime{} , mTtl(ttl) , mTtlRatio(ttlRatio) , mPeerStateHandler([](PeerState) {}) , mByeByeHandler([](ByeBye) {}) { } template void setReceiveHandler(Handler handler) { mPeerStateHandler = [handler]( PeerState state) { handler(std::move(state)); }; mByeByeHandler = [handler](ByeBye byeBye) { handler(std::move(byeBye)); }; } void sendByeBye() { sendUdpMessage( *mInterface, mState.ident(), 0, v1::kByeBye, makePayload(), multicastEndpoint()); } void updateState(NodeState state) { mState = std::move(state); } void broadcastState() { using namespace std::chrono; const auto minBroadcastPeriod = milliseconds{50}; const auto nominalBroadcastPeriod = milliseconds(mTtl * 1000 / mTtlRatio); const auto timeSinceLastBroadcast = duration_cast(mTimer.now() - mLastBroadcastTime); // The rate is limited to maxBroadcastRate to prevent flooding the network. const auto delay = minBroadcastPeriod - timeSinceLastBroadcast; // Schedule the next broadcast before we actually send the // message so that if sending throws an exception we are still // scheduled to try again. We want to keep trying at our // interval as long as this instance is alive. mTimer.expires_from_now(delay > milliseconds{0} ? delay : nominalBroadcastPeriod); mTimer.async_wait([this](const TimerError e) { if (!e) { broadcastState(); } }); // If we're not delaying, broadcast now if (delay < milliseconds{1}) { debug(mIo->log()) << "Broadcasting state"; sendPeerState(v1::kAlive, multicastEndpoint()); } } void sendPeerState( const v1::MessageType messageType, const asio::ip::udp::endpoint& to) { sendUdpMessage( *mInterface, mState.ident(), mTtl, messageType, toPayload(mState), to); mLastBroadcastTime = mTimer.now(); } void sendResponse(const asio::ip::udp::endpoint& to) { sendPeerState(v1::kResponse, to); } template void listen(Tag tag) { mInterface->receive(util::makeAsyncSafe(this->shared_from_this()), tag); } template void operator()(Tag tag, const asio::ip::udp::endpoint& from, const It messageBegin, const It messageEnd) { auto result = v1::parseMessageHeader(messageBegin, messageEnd); const auto& header = result.first; // Ignore messages from self and other groups if (header.ident != mState.ident() && header.groupId == 0) { debug(mIo->log()) << "Received message type " << static_cast(header.messageType) << " from peer " << header.ident; switch (header.messageType) { case v1::kAlive: sendResponse(from); receivePeerState(std::move(result.first), result.second, messageEnd); break; case v1::kResponse: receivePeerState(std::move(result.first), result.second, messageEnd); break; case v1::kByeBye: receiveByeBye(std::move(result.first.ident)); break; default: info(mIo->log()) << "Unknown message received of type: " << header.messageType; } } listen(tag); } template void receivePeerState( v1::MessageHeader header, It payloadBegin, It payloadEnd) { try { auto state = NodeState::fromPayload( std::move(header.ident), std::move(payloadBegin), std::move(payloadEnd)); // Handlers must only be called once auto handler = std::move(mPeerStateHandler); mPeerStateHandler = [](PeerState) {}; handler(PeerState{std::move(state), header.ttl}); } catch (const std::runtime_error& err) { info(mIo->log()) << "Ignoring peer state message: " << err.what(); } } void receiveByeBye(NodeId nodeId) { // Handlers must only be called once auto byeByeHandler = std::move(mByeByeHandler); mByeByeHandler = [](ByeBye) {}; byeByeHandler(ByeBye{std::move(nodeId)}); } util::Injected mIo; util::Injected mInterface; NodeState mState; Timer mTimer; TimePoint mLastBroadcastTime; uint8_t mTtl; uint8_t mTtlRatio; std::function)> mPeerStateHandler; std::function)> mByeByeHandler; }; std::shared_ptr mpImpl; }; // Factory function template UdpMessenger makeUdpMessenger( util::Injected iface, NodeState state, util::Injected io, const uint8_t ttl, const uint8_t ttlRatio) { return UdpMessenger{ std::move(iface), std::move(state), std::move(io), ttl, ttlRatio}; } } // namespace discovery } // namespace ableton link-Link-3.0.2/include/ableton/discovery/Socket.hpp0000644000175000017500000001051713303226525022622 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include #include #include #include namespace ableton { namespace discovery { template struct Socket { Socket(platforms::asio::AsioService& io) : mpImpl(std::make_shared(io)) { } Socket(const Socket&) = delete; Socket& operator=(const Socket&) = delete; Socket(Socket&& rhs) : mpImpl(std::move(rhs.mpImpl)) { } std::size_t send( const uint8_t* const pData, const size_t numBytes, const asio::ip::udp::endpoint& to) { assert(numBytes < MaxPacketSize); return mpImpl->mSocket.send_to(asio::buffer(pData, numBytes), to); } template void receive(Handler handler) { mpImpl->mHandler = std::move(handler); mpImpl->mSocket.async_receive_from( asio::buffer(mpImpl->mReceiveBuffer, MaxPacketSize), mpImpl->mSenderEndpoint, util::makeAsyncSafe(mpImpl)); } asio::ip::udp::endpoint endpoint() const { return mpImpl->mSocket.local_endpoint(); } struct Impl { Impl(platforms::asio::AsioService& io) : mSocket(io.mService, asio::ip::udp::v4()) { } ~Impl() { // Ignore error codes in shutdown and close as the socket may // have already been forcibly closed asio::error_code ec; mSocket.shutdown(asio::ip::udp::socket::shutdown_both, ec); mSocket.close(ec); } void operator()(const asio::error_code& error, const std::size_t numBytes) { if (!error && numBytes > 0 && numBytes <= MaxPacketSize) { const auto bufBegin = begin(mReceiveBuffer); mHandler(mSenderEndpoint, bufBegin, bufBegin + static_cast(numBytes)); } } asio::ip::udp::socket mSocket; asio::ip::udp::endpoint mSenderEndpoint; using Buffer = std::array; Buffer mReceiveBuffer; using ByteIt = typename Buffer::const_iterator; std::function mHandler; }; std::shared_ptr mpImpl; }; // Configure an asio socket for receiving multicast messages template void configureMulticastSocket(Socket& socket, const asio::ip::address_v4& addr, const asio::ip::udp::endpoint& multicastEndpoint) { socket.mpImpl->mSocket.set_option(asio::ip::udp::socket::reuse_address(true)); // ??? socket.mpImpl->mSocket.set_option(asio::socket_base::broadcast(!addr.is_loopback())); // ??? socket.mpImpl->mSocket.set_option( asio::ip::multicast::enable_loopback(addr.is_loopback())); socket.mpImpl->mSocket.set_option(asio::ip::multicast::outbound_interface(addr)); // Is from_string("0.0.0.0") best approach? socket.mpImpl->mSocket.bind( {asio::ip::address::from_string("0.0.0.0"), multicastEndpoint.port()}); socket.mpImpl->mSocket.set_option( asio::ip::multicast::join_group(multicastEndpoint.address().to_v4(), addr)); } // Configure an asio socket for receiving unicast messages template void configureUnicastSocket( Socket& socket, const asio::ip::address_v4& addr) { // ??? really necessary? socket.mpImpl->mSocket.set_option( asio::ip::multicast::enable_loopback(addr.is_loopback())); socket.mpImpl->mSocket.set_option(asio::ip::multicast::outbound_interface(addr)); socket.mpImpl->mSocket.bind(asio::ip::udp::endpoint{addr, 0}); } } // namespace discovery } // namespace ableton link-Link-3.0.2/include/ableton/util/0000755000175000017500000000000013303226525017623 5ustar zmoelnigzmoelniglink-Link-3.0.2/include/ableton/util/SafeAsyncHandler.hpp0000644000175000017500000000372613303226525023516 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include namespace ableton { namespace util { // A utility handler for passing to async functions that may call the // handler past the lifetime of the wrapped delegate object. // The need for this is particularly driven by boost::asio timer // objects, which explicitly document that they may be called without // an error code after they have been cancelled. This has led to // several crashes. This handler wrapper implements a useful idiom for // avoiding this problem. template struct SafeAsyncHandler { SafeAsyncHandler(const std::shared_ptr& pDelegate) : mpDelegate(pDelegate) { } template void operator()(T&&... t) const { std::shared_ptr pDelegate = mpDelegate.lock(); if (pDelegate) { (*pDelegate)(std::forward(t)...); } } std::weak_ptr mpDelegate; }; // Factory function for easily wrapping a shared_ptr to a handler template SafeAsyncHandler makeAsyncSafe(const std::shared_ptr& pDelegate) { return {pDelegate}; } } // namespace util } // namespace ableton link-Link-3.0.2/include/ableton/util/test/0000755000175000017500000000000013303226525020602 5ustar zmoelnigzmoelniglink-Link-3.0.2/include/ableton/util/test/IoService.hpp0000644000175000017500000000471213303226525023207 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include namespace ableton { namespace util { namespace test { struct IoService { // Wrapper around the internal util::test::Timer in the list struct Timer { using ErrorCode = test::Timer::ErrorCode; using TimePoint = test::Timer::TimePoint; Timer(util::test::Timer* pTimer) : mpTimer(pTimer) { } void expires_at(std::chrono::system_clock::time_point t) { mpTimer->expires_at(t); } template void expires_from_now(std::chrono::duration duration) { mpTimer->expires_from_now(duration); } ErrorCode cancel() { return mpTimer->cancel(); } template void async_wait(Handler handler) { mpTimer->async_wait(std::move(handler)); } TimePoint now() const { return mpTimer->now(); } util::test::Timer* mpTimer; }; IoService() = default; Timer makeTimer() { mTimers.emplace_back(); return Timer{&mTimers.back()}; } template void post(Handler handler) { mHandlers.emplace_back(std::move(handler)); } template void advance(std::chrono::duration duration) { runHandlers(); for (auto& timer : mTimers) { timer.advance(duration); } } void runHandlers() { for (auto& handler : mHandlers) { handler(); } mHandlers.clear(); } std::vector> mHandlers; std::vector mTimers; }; } // namespace test } // namespace util } // namespace ableton link-Link-3.0.2/include/ableton/util/test/Timer.hpp0000644000175000017500000000437613303226525022405 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include #include namespace ableton { namespace util { namespace test { struct Timer { using ErrorCode = int; using TimePoint = std::chrono::system_clock::time_point; // Initialize timer with an arbitrary large value to simulate the // time_since_epoch of a real clock. Timer() : mNow{std::chrono::milliseconds{123456789}} { } void expires_at(std::chrono::system_clock::time_point t) { cancel(); mFireAt = std::move(t); } template void expires_from_now(std::chrono::duration duration) { cancel(); mFireAt = now() + duration; } ErrorCode cancel() { if (mHandler) { mHandler(1); // call existing handler with truthy error code } mHandler = nullptr; return 0; } template void async_wait(Handler handler) { mHandler = [handler](ErrorCode ec) { handler(ec); }; } std::chrono::system_clock::time_point now() const { return mNow; } template void advance(std::chrono::duration duration) { mNow += duration; if (mHandler && mFireAt < mNow) { mHandler(0); mHandler = nullptr; } } std::function mHandler; std::chrono::system_clock::time_point mFireAt; std::chrono::system_clock::time_point mNow; }; } // namespace test } // namespace util } // namespace ableton link-Link-3.0.2/include/ableton/util/Injected.hpp0000644000175000017500000001051213303226525022060 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include namespace ableton { namespace util { // Utility type for aiding in dependency injection. // Base template and implementation for injected valued template struct Injected { using type = T; Injected() = default; explicit Injected(T t) : val(std::move(t)) { } Injected(const Injected&) = default; Injected& operator=(const Injected&) = default; Injected(Injected&& rhs) : val(std::move(rhs.val)) { } Injected& operator=(Injected&& rhs) { val = std::move(rhs.val); return *this; } T* operator->() { return &val; } const T* operator->() const { return &val; } T& operator*() { return val; } const T& operator*() const { return val; } T val; }; // Utility function for injecting values template Injected injectVal(T t) { return Injected(std::move(t)); } // Specialization for injected references template struct Injected { using type = T; explicit Injected(T& t) : ref(std::ref(t)) { } Injected(const Injected&) = default; Injected& operator=(const Injected&) = default; Injected(Injected&& rhs) : ref(std::move(rhs.ref)) { } Injected& operator=(Injected&& rhs) { ref = std::move(rhs.ref); return *this; } T* operator->() { return &ref.get(); } const T* operator->() const { return &ref.get(); } T& operator*() { return ref; } const T& operator*() const { return ref; } std::reference_wrapper ref; }; // Utility function for injecting references template Injected injectRef(T& t) { return Injected(t); } // Specialization for injected shared_ptr template struct Injected> { using type = T; explicit Injected(std::shared_ptr pT) : shared(std::move(pT)) { } Injected(const Injected&) = default; Injected& operator=(const Injected&) = default; Injected(Injected&& rhs) : shared(std::move(rhs.shared)) { } Injected& operator=(Injected&& rhs) { shared = std::move(rhs.shared); return *this; } T* operator->() { return shared.get(); } const T* operator->() const { return shared.get(); } T& operator*() { return *shared; } const T& operator*() const { return *shared; } std::shared_ptr shared; }; // Utility function for injected shared_ptr template Injected> injectShared(std::shared_ptr shared) { return Injected>(std::move(shared)); } // Specialization for injected unique_ptr template struct Injected> { using type = T; explicit Injected(std::unique_ptr pT) : unique(std::move(pT)) { } Injected(const Injected&) = default; Injected& operator=(const Injected&) = default; Injected(Injected&& rhs) : unique(std::move(rhs.unique)) { } Injected& operator=(Injected&& rhs) { unique = std::move(rhs.unique); return *this; } T* operator->() { return unique.get(); } const T* operator->() const { return unique.get(); } T& operator*() { return *unique; } const T& operator*() const { return *unique; } std::unique_ptr unique; }; // Utility function for injected unique_ptr template Injected> injectUnique(std::unique_ptr unique) { return Injected>(std::move(unique)); } } // namespace util } // namespace ableton link-Link-3.0.2/include/ableton/util/SampleTiming.hpp0000644000175000017500000000307213303226525022727 0ustar zmoelnigzmoelnig/* Copyright 2016, Ableton AG, Berlin. All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * If you would like to incorporate Link into a proprietary software application, * please contact . */ #pragma once #include namespace ableton { namespace util { /*! Utility type to convert between time and sample index given the * time at the beginning of a buffer and the sample rate. */ struct SampleTiming { double sampleAtTime(std::chrono::microseconds time) const { using namespace std::chrono; return duration_cast>(time - mBufferBegin).count() * mSampleRate; } std::chrono::microseconds timeAtSample(const double sample) const { using namespace std::chrono; return mBufferBegin + duration_cast(duration{sample / mSampleRate}); } std::chrono::microseconds mBufferBegin; double mSampleRate; }; } // namespace util } // namespace ableton link-Link-3.0.2/include/ableton/util/Log.hpp0000644000175000017500000000704613303226525021064 0ustar zmoelnigzmoelnig// Copyright: 2014, Ableton AG, Berlin, all rights reserved #pragma once #include #include #include namespace ableton { namespace util { // Null object for the Log concept struct NullLog { template friend const NullLog& operator<<(const NullLog& log, const T&) { return log; } friend const NullLog& debug(const NullLog& log) { return log; } friend const NullLog& info(const NullLog& log) { return log; } friend const NullLog& warning(const NullLog& log) { return log; } friend const NullLog& error(const NullLog& log) { return log; } friend NullLog channel(const NullLog&, std::string) { return {}; } }; // std streams-based log struct StdLog { StdLog(std::string channelName = "") : mChannelName(std::move(channelName)) { } // Stream type used by std log to prepend the channel name to log messages struct StdLogStream { StdLogStream(std::ostream& ioStream, const std::string& channelName) : mpIoStream(&ioStream) , mChannelName(channelName) { ioStream << "[" << mChannelName << "] "; } StdLogStream(StdLogStream&& rhs) : mpIoStream(rhs.mpIoStream) , mChannelName(rhs.mChannelName) { rhs.mpIoStream = nullptr; } ~StdLogStream() { if (mpIoStream) { (*mpIoStream) << "\n"; } } template std::ostream& operator<<(const T& rhs) { (*mpIoStream) << rhs; return *mpIoStream; } std::ostream* mpIoStream; const std::string& mChannelName; }; friend StdLogStream debug(const StdLog& log) { return {std::clog, log.mChannelName}; } friend StdLogStream info(const StdLog& log) { return {std::clog, log.mChannelName}; } friend StdLogStream warning(const StdLog& log) { return {std::clog, log.mChannelName}; } friend StdLogStream error(const StdLog& log) { return {std::cerr, log.mChannelName}; } friend StdLog channel(const StdLog& log, const std::string& channelName) { auto compositeName = log.mChannelName.empty() ? channelName : log.mChannelName + "::" + channelName; return {std::move(compositeName)}; } std::string mChannelName; }; // Log adapter that adds timestamps template struct Timestamped { using InnerLog = typename util::Injected::type; Timestamped() = default; Timestamped(util::Injected log) : mLog(std::move(log)) { } util::Injected mLog; friend decltype(debug(std::declval())) debug(const Timestamped& log) { return log.logTimestamp(debug(*log.mLog)); } friend decltype(info(std::declval())) info(const Timestamped& log) { return log.logTimestamp(info(*log.mLog)); } friend decltype(warning(std::declval())) warning(const Timestamped& log) { return log.logTimestamp(warning(*log.mLog)); } friend decltype(error(std::declval())) error(const Timestamped& log) { return log.logTimestamp(error(*log.mLog)); } friend Timestamped channel(const Timestamped& log, const std::string& channelName) { return {channel(*log.mLog, channelName)}; } template Stream logTimestamp(Stream&& streamRef) const { using namespace std::chrono; Stream stream = std::forward(streamRef); stream << "|" << duration_cast(system_clock::now().time_since_epoch()).count() << "ms| "; return stream; } }; } // namespace util } // namespace ableton link-Link-3.0.2/include/CMakeLists.txt0000644000175000017500000001151713303226525017767 0ustar zmoelnigzmoelnigcmake_minimum_required(VERSION 3.0) project(LinkCore) # ____ # / ___|___ _ __ ___ # | | / _ \| '__/ _ \ # | |__| (_) | | | __/ # \____\___/|_| \___| # set(link_core_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ableton/link) set(link_core_HEADERS ${link_core_DIR}/Beats.hpp ${link_core_DIR}/CircularFifo.hpp ${link_core_DIR}/ClientSessionTimelines.hpp ${link_core_DIR}/Controller.hpp ${link_core_DIR}/Gateway.hpp ${link_core_DIR}/GhostXForm.hpp ${link_core_DIR}/HostTimeFilter.hpp ${link_core_DIR}/Kalman.hpp ${link_core_DIR}/LinearRegression.hpp ${link_core_DIR}/Measurement.hpp ${link_core_DIR}/MeasurementEndpointV4.hpp ${link_core_DIR}/MeasurementService.hpp ${link_core_DIR}/NodeId.hpp ${link_core_DIR}/NodeState.hpp ${link_core_DIR}/PayloadEntries.hpp ${link_core_DIR}/Optional.hpp ${link_core_DIR}/Peers.hpp ${link_core_DIR}/PeerState.hpp ${link_core_DIR}/Phase.hpp ${link_core_DIR}/PingResponder.hpp ${link_core_DIR}/SessionId.hpp ${link_core_DIR}/SessionState.hpp ${link_core_DIR}/Sessions.hpp ${link_core_DIR}/StartStopState.hpp ${link_core_DIR}/Tempo.hpp ${link_core_DIR}/Timeline.hpp ${link_core_DIR}/v1/Messages.hpp PARENT_SCOPE ) # ____ _ # | _ \(_)___ ___ _____ _____ _ __ _ _ # | | | | / __|/ __/ _ \ \ / / _ \ '__| | | | # | |_| | \__ \ (_| (_) \ V / __/ | | |_| | # |____/|_|___/\___\___/ \_/ \___|_| \__, | # |___/ set(link_discovery_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ableton/discovery) set(link_discovery_HEADERS ${link_discovery_DIR}/InterfaceScanner.hpp ${link_discovery_DIR}/IpV4Interface.hpp ${link_discovery_DIR}/MessageTypes.hpp ${link_discovery_DIR}/NetworkByteStreamSerializable.hpp ${link_discovery_DIR}/Payload.hpp ${link_discovery_DIR}/PeerGateway.hpp ${link_discovery_DIR}/PeerGateways.hpp ${link_discovery_DIR}/Service.hpp ${link_discovery_DIR}/Socket.hpp ${link_discovery_DIR}/UdpMessenger.hpp ${link_discovery_DIR}/v1/Messages.hpp PARENT_SCOPE ) # ____ _ _ __ # | _ \| | __ _| |_ / _| ___ _ __ _ __ ___ # | |_) | |/ _` | __| |_ / _ \| '__| '_ ` _ \ # | __/| | (_| | |_| _| (_) | | | | | | | | # |_| |_|\__,_|\__|_| \___/|_| |_| |_| |_| # set(link_platform_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ableton/platforms) set(link_platform_HEADERS ${link_platform_DIR}/Config.hpp ${link_platform_DIR}/asio/AsioService.hpp ${link_platform_DIR}/asio/AsioTimer.hpp ${link_platform_DIR}/asio/AsioWrapper.hpp ${link_platform_DIR}/asio/Context.hpp ${link_platform_DIR}/asio/LockFreeCallbackDispatcher.hpp ${link_platform_DIR}/asio/Socket.hpp ${link_platform_DIR}/asio/Util.hpp ) if(UNIX) set(link_platform_HEADERS ${link_platform_HEADERS} ${link_platform_DIR}/posix/ScanIpIfAddrs.hpp ) if(APPLE) set(link_platform_HEADERS ${link_platform_HEADERS} ${link_platform_DIR}/darwin/Clock.hpp ${link_platform_DIR}/darwin/Darwin.hpp ) elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux") set(link_platform_HEADERS ${link_platform_HEADERS} ${link_platform_DIR}/linux/Clock.hpp ${link_platform_DIR}/linux/Linux.hpp ${link_platform_DIR}/stl/Clock.hpp ) endif() elseif(WIN32) set(link_platform_HEADERS ${link_platform_HEADERS} ${link_platform_DIR}/windows/Clock.hpp ${link_platform_DIR}/windows/ScanIpIfAddrs.hpp ) endif() set(link_platform_HEADERS ${link_platform_HEADERS} PARENT_SCOPE ) # _ _ _ _ _ # | | | | |_(_) | # | | | | __| | | # | |_| | |_| | | # \___/ \__|_|_| # set(link_util_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ableton/util) set(link_util_HEADERS ${link_util_DIR}/Injected.hpp ${link_util_DIR}/Log.hpp ${link_util_DIR}/SafeAsyncHandler.hpp ${link_util_DIR}/SampleTiming.hpp PARENT_SCOPE ) # _____ _ _ # | ____|_ ___ __ ___ _ __| |_ ___ __| | # | _| \ \/ / '_ \ / _ \| '__| __/ _ \/ _` | # | |___ > <| |_) | (_) | | | || __/ (_| | # |_____/_/\_\ .__/ \___/|_| \__\___|\__,_| # |_| # This list contains all of the headers needed by most Link projects. # Usually, just adding this variable to your linker targets will place # all relevant Link headers in your project. set(link_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ableton) set(link_HEADERS ${link_core_HEADERS} ${link_discovery_HEADERS} ${link_platform_HEADERS} ${link_util_HEADERS} ${link_DIR}/Link.hpp ${link_DIR}/Link.ipp PARENT_SCOPE ) set(link_test_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ableton/test) set(link_test_HEADERS ${link_discovery_DIR}/test/Interface.hpp ${link_discovery_DIR}/test/PayloadEntries.hpp ${link_discovery_DIR}/test/Socket.hpp ${link_util_DIR}/test/IoService.hpp ${link_util_DIR}/test/Timer.hpp ${link_test_DIR}/CatchWrapper.hpp ${link_test_DIR}/serial_io/Context.hpp ${link_test_DIR}/serial_io/Fixture.hpp ${link_test_DIR}/serial_io/SchedulerTree.hpp ${link_test_DIR}/serial_io/Timer.hpp PARENT_SCOPE ) link-Link-3.0.2/CONTRIBUTING.md0000644000175000017500000000660513303226525016037 0ustar zmoelnigzmoelnigBug Reports =========== If you've found a bug in Link itself, then please file a new issue here at GitHub. If you have found a bug in a Link-enabled app, it might be wiser to reach out to the developer of the app before filing an issue here. Any and all information that you can provide regarding the bug will help in our being able to find it. Specifically, that could include: - Stacktraces, in the event of a crash - Versions of the software used, and the underlying operating system - Steps to reproduce - Screenshots, in the case of a bug which results in a visual error Pull Requests ============= We are happy to accept pull requests from the GitHub community, assuming that they meet the following criteria: - You have signed and returned Ableton's [CLA][cla] - The [tests pass](#testing) - The PR passes all [CI service checks](#ci-services) - The code is [well-formatted](#code-formatting) - The git commit messages comply to [the commonly accepted standards][git-commit-msgs] Testing ------- Link ships with unit tests that are run by our [CI services](#ci-services) for all PRs. There are two test suites: `LinkCoreTest`, which tests the core Link functionality, and `LinkDiscoverTest`, which tests the network discovery feature of Link. A third virtual target, `LinkAllTest` is provided by the CMake project as a convenience to run all tests at once. The unit tests are run on every platform which Link is officially supported on, and also are run through [Valgrind][valgrind] on Linux to check for memory corruption and leaks. If valgrind detects any memory errors when running the tests, it will fail the build. If you are submitting a PR which fixes a bug or introduces new functionality, please add a test which helps to verify the correctness of the code in the PR. CI Services ----------- Every PR submitted to Link must build and pass tests on the following platforms | Service | Platform | Compiler | Word size | Checks | | -------- | -------- | --------- | ----------| ------ | | AppVeyor | Windows | MSVC 2013 | 32/64-bit | Build (Release), run tests | | AppVeyor | Windows | MSVC 2015 | 32/64-bit | Build (Debug/Release), run tests | | Travis | Mac OS X | Xcode 7.1 | 64-bit | Build (Debug/Release), run tests | | Travis | Linux | Clang-3.6 | 64-bit | Build (Debug/Release), run tests with Valgrind | | Travis | Linux | GCC-5.2 | 32/64-bit | Build (Release), run tests with Valgrind | | Travis | Linux | Clang-3.8 | N/A | Check code formatting with clang-format | Assuming that your PR gets a green checkmark from each service, you're good to go. In case of a build failure, please examine the build logs from the respective CI service. If you have any questions, feel free to contact one of the maintainers -- we're here to help you! Code Formatting --------------- Link uses [clang-format][clang-format] to enforce our preferred code style. At the moment, we use **clang-format version 3.9**. Note that other versions may format code differently. Any PRs submitted to Link are also checked with clang-format by the Travis CI service. If you get a build failure, then you can format your code by running the following command: ``` clang-format -style=file -i (filename) ``` [cla]: http://ableton.github.io/cla/ [git-commit-msgs]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html [clang-format]: http://llvm.org/builds [valgrind]: http://valgrind.org link-Link-3.0.2/third_party/0000755000175000017500000000000013303226525016130 5ustar zmoelnigzmoelniglink-Link-3.0.2/third_party/catch/0000755000175000017500000000000013303226525017212 5ustar zmoelnigzmoelniglink-Link-3.0.2/third_party/catch/catch.hpp0000644000175000017500000147075713303226525021032 0ustar zmoelnigzmoelnig/* * Catch v1.10.0 * Generated: 2017-08-26 15:16:46.676990 * ---------------------------------------------------------- * This file has been merged from multiple headers. Please don't edit it directly * Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. * * Distributed under the Boost Software License, Version 1.0. (See accompanying * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ #ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED #define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED #define TWOBLUECUBES_CATCH_HPP_INCLUDED #ifdef __clang__ # pragma clang system_header #elif defined __GNUC__ # pragma GCC system_header #endif // #included from: internal/catch_suppress_warnings.h #ifdef __clang__ # ifdef __ICC // icpc defines the __clang__ macro # pragma warning(push) # pragma warning(disable: 161 1682) # else // __ICC # pragma clang diagnostic ignored "-Wglobal-constructors" # pragma clang diagnostic ignored "-Wvariadic-macros" # pragma clang diagnostic ignored "-Wc99-extensions" # pragma clang diagnostic ignored "-Wunused-variable" # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wpadded" # pragma clang diagnostic ignored "-Wc++98-compat" # pragma clang diagnostic ignored "-Wc++98-compat-pedantic" # pragma clang diagnostic ignored "-Wswitch-enum" # pragma clang diagnostic ignored "-Wcovered-switch-default" # endif #elif defined __GNUC__ # pragma GCC diagnostic ignored "-Wvariadic-macros" # pragma GCC diagnostic ignored "-Wunused-variable" # pragma GCC diagnostic ignored "-Wparentheses" # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wpadded" #endif #if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) # define CATCH_IMPL #endif #ifdef CATCH_IMPL # ifndef CLARA_CONFIG_MAIN # define CLARA_CONFIG_MAIN_NOT_DEFINED # define CLARA_CONFIG_MAIN # endif #endif // #included from: internal/catch_notimplemented_exception.h #define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_H_INCLUDED // #included from: catch_common.h #define TWOBLUECUBES_CATCH_COMMON_H_INCLUDED // #included from: catch_compiler_capabilities.h #define TWOBLUECUBES_CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED // Detect a number of compiler features - mostly C++11/14 conformance - by compiler // The following features are defined: // // CATCH_CONFIG_CPP11_NULLPTR : is nullptr supported? // CATCH_CONFIG_CPP11_NOEXCEPT : is noexcept supported? // CATCH_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods // CATCH_CONFIG_CPP11_IS_ENUM : std::is_enum is supported? // CATCH_CONFIG_CPP11_TUPLE : std::tuple is supported // CATCH_CONFIG_CPP11_LONG_LONG : is long long supported? // CATCH_CONFIG_CPP11_OVERRIDE : is override supported? // CATCH_CONFIG_CPP11_UNIQUE_PTR : is unique_ptr supported (otherwise use auto_ptr) // CATCH_CONFIG_CPP11_SHUFFLE : is std::shuffle supported? // CATCH_CONFIG_CPP11_TYPE_TRAITS : are type_traits and enable_if supported? // CATCH_CONFIG_CPP11_OR_GREATER : Is C++11 supported? // CATCH_CONFIG_VARIADIC_MACROS : are variadic macros supported? // CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? // CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? // CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? // **************** // Note to maintainers: if new toggles are added please document them // in configuration.md, too // **************** // In general each macro has a _NO_ form // (e.g. CATCH_CONFIG_CPP11_NO_NULLPTR) which disables the feature. // Many features, at point of detection, define an _INTERNAL_ macro, so they // can be combined, en-mass, with the _NO_ forms later. // All the C++11 features can be disabled with CATCH_CONFIG_NO_CPP11 #ifdef __cplusplus # if __cplusplus >= 201103L # define CATCH_CPP11_OR_GREATER # endif # if __cplusplus >= 201402L # define CATCH_CPP14_OR_GREATER # endif #endif #ifdef __clang__ # if __has_feature(cxx_nullptr) # define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR # endif # if __has_feature(cxx_noexcept) # define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT # endif # if defined(CATCH_CPP11_OR_GREATER) # define CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ _Pragma( "clang diagnostic push" ) \ _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) # define CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \ _Pragma( "clang diagnostic pop" ) # define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ _Pragma( "clang diagnostic push" ) \ _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) # define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ _Pragma( "clang diagnostic pop" ) # endif #endif // __clang__ //////////////////////////////////////////////////////////////////////////////// // We know some environments not to support full POSIX signals #if defined(__CYGWIN__) || defined(__QNX__) # if !defined(CATCH_CONFIG_POSIX_SIGNALS) # define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS # endif #endif #ifdef __OS400__ # define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS # define CATCH_CONFIG_COLOUR_NONE #endif //////////////////////////////////////////////////////////////////////////////// // Cygwin #ifdef __CYGWIN__ // Required for some versions of Cygwin to declare gettimeofday // see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin # define _BSD_SOURCE #endif // __CYGWIN__ //////////////////////////////////////////////////////////////////////////////// // Borland #ifdef __BORLANDC__ #endif // __BORLANDC__ //////////////////////////////////////////////////////////////////////////////// // EDG #ifdef __EDG_VERSION__ #endif // __EDG_VERSION__ //////////////////////////////////////////////////////////////////////////////// // Digital Mars #ifdef __DMC__ #endif // __DMC__ //////////////////////////////////////////////////////////////////////////////// // GCC #ifdef __GNUC__ # if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) # define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR # endif // - otherwise more recent versions define __cplusplus >= 201103L // and will get picked up below #endif // __GNUC__ //////////////////////////////////////////////////////////////////////////////// // Visual C++ #ifdef _MSC_VER #define CATCH_INTERNAL_CONFIG_WINDOWS_SEH #if (_MSC_VER >= 1600) # define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR # define CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR #endif #if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015)) #define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT #define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS #define CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE #define CATCH_INTERNAL_CONFIG_CPP11_TYPE_TRAITS #endif #endif // _MSC_VER //////////////////////////////////////////////////////////////////////////////// // Use variadic macros if the compiler supports them #if ( defined _MSC_VER && _MSC_VER > 1400 && !defined __EDGE__) || \ ( defined __WAVE__ && __WAVE_HAS_VARIADICS ) || \ ( defined __GNUC__ && __GNUC__ >= 3 ) || \ ( !defined __cplusplus && __STDC_VERSION__ >= 199901L || __cplusplus >= 201103L ) #define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS #endif // Use __COUNTER__ if the compiler supports it #if ( defined _MSC_VER && _MSC_VER >= 1300 ) || \ ( defined __GNUC__ && ( __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3 )) ) || \ ( defined __clang__ && __clang_major__ >= 3 ) #define CATCH_INTERNAL_CONFIG_COUNTER #endif //////////////////////////////////////////////////////////////////////////////// // C++ language feature support // catch all support for C++11 #if defined(CATCH_CPP11_OR_GREATER) # if !defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) # define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR # endif # ifndef CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT # define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT # endif # ifndef CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS # define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS # endif # ifndef CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM # define CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM # endif # ifndef CATCH_INTERNAL_CONFIG_CPP11_TUPLE # define CATCH_INTERNAL_CONFIG_CPP11_TUPLE # endif # ifndef CATCH_INTERNAL_CONFIG_VARIADIC_MACROS # define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS # endif # if !defined(CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG) # define CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG # endif # if !defined(CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE) # define CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE # endif # if !defined(CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) # define CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR # endif # if !defined(CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE) # define CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE # endif # if !defined(CATCH_INTERNAL_CONFIG_CPP11_TYPE_TRAITS) # define CATCH_INTERNAL_CONFIG_CPP11_TYPE_TRAITS # endif #endif // __cplusplus >= 201103L // Now set the actual defines based on the above + anything the user has configured #if defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NO_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_NULLPTR #endif #if defined(CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NO_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_NOEXCEPT #endif #if defined(CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_NO_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_GENERATED_METHODS #endif #if defined(CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_NO_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_IS_ENUM #endif #if defined(CATCH_INTERNAL_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_CPP11_NO_TUPLE) && !defined(CATCH_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_TUPLE #endif #if defined(CATCH_INTERNAL_CONFIG_VARIADIC_MACROS) && !defined(CATCH_CONFIG_NO_VARIADIC_MACROS) && !defined(CATCH_CONFIG_VARIADIC_MACROS) # define CATCH_CONFIG_VARIADIC_MACROS #endif #if defined(CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG) && !defined(CATCH_CONFIG_CPP11_NO_LONG_LONG) && !defined(CATCH_CONFIG_CPP11_LONG_LONG) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_LONG_LONG #endif #if defined(CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE) && !defined(CATCH_CONFIG_CPP11_NO_OVERRIDE) && !defined(CATCH_CONFIG_CPP11_OVERRIDE) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_OVERRIDE #endif #if defined(CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_CPP11_NO_UNIQUE_PTR) && !defined(CATCH_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_UNIQUE_PTR #endif // Use of __COUNTER__ is suppressed if __JETBRAINS_IDE__ is #defined (meaning we're being parsed by a JetBrains IDE for // analytics) because, at time of writing, __COUNTER__ is not properly handled by it. // This does not affect compilation #if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) && !defined(__JETBRAINS_IDE__) # define CATCH_CONFIG_COUNTER #endif #if defined(CATCH_INTERNAL_CONFIG_CPP11_SHUFFLE) && !defined(CATCH_CONFIG_CPP11_NO_SHUFFLE) && !defined(CATCH_CONFIG_CPP11_SHUFFLE) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_SHUFFLE #endif # if defined(CATCH_INTERNAL_CONFIG_CPP11_TYPE_TRAITS) && !defined(CATCH_CONFIG_CPP11_NO_TYPE_TRAITS) && !defined(CATCH_CONFIG_CPP11_TYPE_TRAITS) && !defined(CATCH_CONFIG_NO_CPP11) # define CATCH_CONFIG_CPP11_TYPE_TRAITS # endif #if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) # define CATCH_CONFIG_WINDOWS_SEH #endif // This is set by default, because we assume that unix compilers are posix-signal-compatible by default. #if !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) # define CATCH_CONFIG_POSIX_SIGNALS #endif #if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) # define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS # define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS #endif #if !defined(CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS) # define CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS # define CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS #endif // noexcept support: #if defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_NOEXCEPT) # define CATCH_NOEXCEPT noexcept # define CATCH_NOEXCEPT_IS(x) noexcept(x) #else # define CATCH_NOEXCEPT throw() # define CATCH_NOEXCEPT_IS(x) #endif // nullptr support #ifdef CATCH_CONFIG_CPP11_NULLPTR # define CATCH_NULL nullptr #else # define CATCH_NULL NULL #endif // override support #ifdef CATCH_CONFIG_CPP11_OVERRIDE # define CATCH_OVERRIDE override #else # define CATCH_OVERRIDE #endif // unique_ptr support #ifdef CATCH_CONFIG_CPP11_UNIQUE_PTR # define CATCH_AUTO_PTR( T ) std::unique_ptr #else # define CATCH_AUTO_PTR( T ) std::auto_ptr #endif #define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line #define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) #ifdef CATCH_CONFIG_COUNTER # define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) #else # define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) #endif #define INTERNAL_CATCH_STRINGIFY2( expr ) #expr #define INTERNAL_CATCH_STRINGIFY( expr ) INTERNAL_CATCH_STRINGIFY2( expr ) #include #include namespace Catch { struct IConfig; struct CaseSensitive { enum Choice { Yes, No }; }; class NonCopyable { #ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS NonCopyable( NonCopyable const& ) = delete; NonCopyable( NonCopyable && ) = delete; NonCopyable& operator = ( NonCopyable const& ) = delete; NonCopyable& operator = ( NonCopyable && ) = delete; #else NonCopyable( NonCopyable const& info ); NonCopyable& operator = ( NonCopyable const& ); #endif protected: NonCopyable() {} virtual ~NonCopyable(); }; class SafeBool { public: typedef void (SafeBool::*type)() const; static type makeSafe( bool value ) { return value ? &SafeBool::trueValue : 0; } private: void trueValue() const {} }; template void deleteAll( ContainerT& container ) { typename ContainerT::const_iterator it = container.begin(); typename ContainerT::const_iterator itEnd = container.end(); for(; it != itEnd; ++it ) delete *it; } template void deleteAllValues( AssociativeContainerT& container ) { typename AssociativeContainerT::const_iterator it = container.begin(); typename AssociativeContainerT::const_iterator itEnd = container.end(); for(; it != itEnd; ++it ) delete it->second; } bool startsWith( std::string const& s, std::string const& prefix ); bool startsWith( std::string const& s, char prefix ); bool endsWith( std::string const& s, std::string const& suffix ); bool endsWith( std::string const& s, char suffix ); bool contains( std::string const& s, std::string const& infix ); void toLowerInPlace( std::string& s ); std::string toLower( std::string const& s ); std::string trim( std::string const& str ); bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ); struct pluralise { pluralise( std::size_t count, std::string const& label ); friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ); std::size_t m_count; std::string m_label; }; struct SourceLineInfo { SourceLineInfo(); SourceLineInfo( char const* _file, std::size_t _line ); # ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS SourceLineInfo(SourceLineInfo const& other) = default; SourceLineInfo( SourceLineInfo && ) = default; SourceLineInfo& operator = ( SourceLineInfo const& ) = default; SourceLineInfo& operator = ( SourceLineInfo && ) = default; # endif bool empty() const; bool operator == ( SourceLineInfo const& other ) const; bool operator < ( SourceLineInfo const& other ) const; char const* file; std::size_t line; }; std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); // This is just here to avoid compiler warnings with macro constants and boolean literals inline bool isTrue( bool value ){ return value; } inline bool alwaysTrue() { return true; } inline bool alwaysFalse() { return false; } void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ); void seedRng( IConfig const& config ); unsigned int rngSeed(); // Use this in variadic streaming macros to allow // >> +StreamEndStop // as well as // >> stuff +StreamEndStop struct StreamEndStop { std::string operator+() { return std::string(); } }; template T const& operator + ( T const& value, StreamEndStop ) { return value; } } #define CATCH_INTERNAL_LINEINFO ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) #define CATCH_INTERNAL_ERROR( msg ) ::Catch::throwLogicError( msg, CATCH_INTERNAL_LINEINFO ); namespace Catch { class NotImplementedException : public std::exception { public: NotImplementedException( SourceLineInfo const& lineInfo ); virtual ~NotImplementedException() CATCH_NOEXCEPT {} virtual const char* what() const CATCH_NOEXCEPT; private: std::string m_what; SourceLineInfo m_lineInfo; }; } // end namespace Catch /////////////////////////////////////////////////////////////////////////////// #define CATCH_NOT_IMPLEMENTED throw Catch::NotImplementedException( CATCH_INTERNAL_LINEINFO ) // #included from: internal/catch_context.h #define TWOBLUECUBES_CATCH_CONTEXT_H_INCLUDED // #included from: catch_interfaces_generators.h #define TWOBLUECUBES_CATCH_INTERFACES_GENERATORS_H_INCLUDED #include namespace Catch { struct IGeneratorInfo { virtual ~IGeneratorInfo(); virtual bool moveNext() = 0; virtual std::size_t getCurrentIndex() const = 0; }; struct IGeneratorsForTest { virtual ~IGeneratorsForTest(); virtual IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) = 0; virtual bool moveNext() = 0; }; IGeneratorsForTest* createGeneratorsForTest(); } // end namespace Catch // #included from: catch_ptr.hpp #define TWOBLUECUBES_CATCH_PTR_HPP_INCLUDED #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpadded" #endif namespace Catch { // An intrusive reference counting smart pointer. // T must implement addRef() and release() methods // typically implementing the IShared interface template class Ptr { public: Ptr() : m_p( CATCH_NULL ){} Ptr( T* p ) : m_p( p ){ if( m_p ) m_p->addRef(); } Ptr( Ptr const& other ) : m_p( other.m_p ){ if( m_p ) m_p->addRef(); } ~Ptr(){ if( m_p ) m_p->release(); } void reset() { if( m_p ) m_p->release(); m_p = CATCH_NULL; } Ptr& operator = ( T* p ){ Ptr temp( p ); swap( temp ); return *this; } Ptr& operator = ( Ptr const& other ){ Ptr temp( other ); swap( temp ); return *this; } void swap( Ptr& other ) { std::swap( m_p, other.m_p ); } T* get() const{ return m_p; } T& operator*() const { return *m_p; } T* operator->() const { return m_p; } bool operator !() const { return m_p == CATCH_NULL; } operator SafeBool::type() const { return SafeBool::makeSafe( m_p != CATCH_NULL ); } private: T* m_p; }; struct IShared : NonCopyable { virtual ~IShared(); virtual void addRef() const = 0; virtual void release() const = 0; }; template struct SharedImpl : T { SharedImpl() : m_rc( 0 ){} virtual void addRef() const { ++m_rc; } virtual void release() const { if( --m_rc == 0 ) delete this; } mutable unsigned int m_rc; }; } // end namespace Catch #ifdef __clang__ #pragma clang diagnostic pop #endif namespace Catch { class TestCase; class Stream; struct IResultCapture; struct IRunner; struct IGeneratorsForTest; struct IConfig; struct IContext { virtual ~IContext(); virtual IResultCapture* getResultCapture() = 0; virtual IRunner* getRunner() = 0; virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) = 0; virtual bool advanceGeneratorsForCurrentTest() = 0; virtual Ptr getConfig() const = 0; }; struct IMutableContext : IContext { virtual ~IMutableContext(); virtual void setResultCapture( IResultCapture* resultCapture ) = 0; virtual void setRunner( IRunner* runner ) = 0; virtual void setConfig( Ptr const& config ) = 0; }; IContext& getCurrentContext(); IMutableContext& getCurrentMutableContext(); void cleanUpContext(); Stream createStream( std::string const& streamName ); } // #included from: internal/catch_test_registry.hpp #define TWOBLUECUBES_CATCH_TEST_REGISTRY_HPP_INCLUDED // #included from: catch_interfaces_testcase.h #define TWOBLUECUBES_CATCH_INTERFACES_TESTCASE_H_INCLUDED #include namespace Catch { class TestSpec; struct ITestCase : IShared { virtual void invoke () const = 0; protected: virtual ~ITestCase(); }; class TestCase; struct IConfig; struct ITestCaseRegistry { virtual ~ITestCaseRegistry(); virtual std::vector const& getAllTests() const = 0; virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; }; bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); std::vector const& getAllTestCasesSorted( IConfig const& config ); } namespace Catch { template class MethodTestCase : public SharedImpl { public: MethodTestCase( void (C::*method)() ) : m_method( method ) {} virtual void invoke() const { C obj; (obj.*m_method)(); } private: virtual ~MethodTestCase() {} void (C::*m_method)(); }; typedef void(*TestFunction)(); struct NameAndDesc { NameAndDesc( const char* _name = "", const char* _description= "" ) : name( _name ), description( _description ) {} const char* name; const char* description; }; void registerTestCase ( ITestCase* testCase, char const* className, NameAndDesc const& nameAndDesc, SourceLineInfo const& lineInfo ); struct AutoReg { AutoReg ( TestFunction function, SourceLineInfo const& lineInfo, NameAndDesc const& nameAndDesc ); template AutoReg ( void (C::*method)(), char const* className, NameAndDesc const& nameAndDesc, SourceLineInfo const& lineInfo ) { registerTestCase ( new MethodTestCase( method ), className, nameAndDesc, lineInfo ); } ~AutoReg(); private: AutoReg( AutoReg const& ); void operator= ( AutoReg const& ); }; void registerTestCaseFunction ( TestFunction function, SourceLineInfo const& lineInfo, NameAndDesc const& nameAndDesc ); } // end namespace Catch #ifdef CATCH_CONFIG_VARIADIC_MACROS /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \ static void TestName(); \ CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); } /* NOLINT */ \ CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \ static void TestName() #define INTERNAL_CATCH_TESTCASE( ... ) \ INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), __VA_ARGS__ ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); } /* NOLINT */ \ CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\ CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ namespace{ \ struct TestName : ClassName{ \ void test(); \ }; \ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestName::test, #ClassName, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); /* NOLINT */ \ } \ CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \ void TestName::test() #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \ INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, __VA_ARGS__ ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \ CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ Catch::AutoReg( Function, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); /* NOLINT */ \ CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS #else /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TESTCASE2( TestName, Name, Desc ) \ static void TestName(); \ CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &TestName, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); } /* NOLINT */ \ CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \ static void TestName() #define INTERNAL_CATCH_TESTCASE( Name, Desc ) \ INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), Name, Desc ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, Name, Desc ) \ CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( Name, Desc ), CATCH_INTERNAL_LINEINFO ); } /* NOLINT */ \ CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestCaseName, ClassName, TestName, Desc )\ CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ namespace{ \ struct TestCaseName : ClassName{ \ void test(); \ }; \ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &TestCaseName::test, #ClassName, Catch::NameAndDesc( TestName, Desc ), CATCH_INTERNAL_LINEINFO ); /* NOLINT */ \ } \ CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS \ void TestCaseName::test() #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, TestName, Desc )\ INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, TestName, Desc ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, Name, Desc ) \ CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS \ Catch::AutoReg( Function, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); /* NOLINT */ \ CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS #endif // #included from: internal/catch_capture.hpp #define TWOBLUECUBES_CATCH_CAPTURE_HPP_INCLUDED // #included from: catch_result_builder.h #define TWOBLUECUBES_CATCH_RESULT_BUILDER_H_INCLUDED // #included from: catch_result_type.h #define TWOBLUECUBES_CATCH_RESULT_TYPE_H_INCLUDED namespace Catch { // ResultWas::OfType enum struct ResultWas { enum OfType { Unknown = -1, Ok = 0, Info = 1, Warning = 2, FailureBit = 0x10, ExpressionFailed = FailureBit | 1, ExplicitFailure = FailureBit | 2, Exception = 0x100 | FailureBit, ThrewException = Exception | 1, DidntThrowException = Exception | 2, FatalErrorCondition = 0x200 | FailureBit }; }; inline bool isOk( ResultWas::OfType resultType ) { return ( resultType & ResultWas::FailureBit ) == 0; } inline bool isJustInfo( int flags ) { return flags == ResultWas::Info; } // ResultDisposition::Flags enum struct ResultDisposition { enum Flags { Normal = 0x01, ContinueOnFailure = 0x02, // Failures fail test, but execution continues FalseTest = 0x04, // Prefix expression with ! SuppressFail = 0x08 // Failures are reported but do not fail the test }; }; inline ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) { return static_cast( static_cast( lhs ) | static_cast( rhs ) ); } inline bool shouldContinueOnFailure( int flags ) { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; } inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; } inline bool shouldSuppressFailure( int flags ) { return ( flags & ResultDisposition::SuppressFail ) != 0; } } // end namespace Catch // #included from: catch_assertionresult.h #define TWOBLUECUBES_CATCH_ASSERTIONRESULT_H_INCLUDED #include namespace Catch { struct STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison; struct DecomposedExpression { virtual ~DecomposedExpression() {} virtual bool isBinaryExpression() const { return false; } virtual void reconstructExpression( std::string& dest ) const = 0; // Only simple binary comparisons can be decomposed. // If more complex check is required then wrap sub-expressions in parentheses. template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator + ( T const& ); template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator - ( T const& ); template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator * ( T const& ); template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator / ( T const& ); template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator % ( T const& ); template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( T const& ); template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( T const& ); private: DecomposedExpression& operator = (DecomposedExpression const&); }; struct AssertionInfo { AssertionInfo(); AssertionInfo( char const * _macroName, SourceLineInfo const& _lineInfo, char const * _capturedExpression, ResultDisposition::Flags _resultDisposition, char const * _secondArg = ""); char const * macroName; SourceLineInfo lineInfo; char const * capturedExpression; ResultDisposition::Flags resultDisposition; char const * secondArg; }; struct AssertionResultData { AssertionResultData() : decomposedExpression( CATCH_NULL ) , resultType( ResultWas::Unknown ) , negated( false ) , parenthesized( false ) {} void negate( bool parenthesize ) { negated = !negated; parenthesized = parenthesize; if( resultType == ResultWas::Ok ) resultType = ResultWas::ExpressionFailed; else if( resultType == ResultWas::ExpressionFailed ) resultType = ResultWas::Ok; } std::string const& reconstructExpression() const { if( decomposedExpression != CATCH_NULL ) { decomposedExpression->reconstructExpression( reconstructedExpression ); if( parenthesized ) { reconstructedExpression.insert( 0, 1, '(' ); reconstructedExpression.append( 1, ')' ); } if( negated ) { reconstructedExpression.insert( 0, 1, '!' ); } decomposedExpression = CATCH_NULL; } return reconstructedExpression; } mutable DecomposedExpression const* decomposedExpression; mutable std::string reconstructedExpression; std::string message; ResultWas::OfType resultType; bool negated; bool parenthesized; }; class AssertionResult { public: AssertionResult(); AssertionResult( AssertionInfo const& info, AssertionResultData const& data ); ~AssertionResult(); # ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS AssertionResult( AssertionResult const& ) = default; AssertionResult( AssertionResult && ) = default; AssertionResult& operator = ( AssertionResult const& ) = default; AssertionResult& operator = ( AssertionResult && ) = default; # endif bool isOk() const; bool succeeded() const; ResultWas::OfType getResultType() const; bool hasExpression() const; bool hasMessage() const; std::string getExpression() const; std::string getExpressionInMacro() const; bool hasExpandedExpression() const; std::string getExpandedExpression() const; std::string getMessage() const; SourceLineInfo getSourceInfo() const; std::string getTestMacroName() const; void discardDecomposedExpression() const; void expandDecomposedExpression() const; protected: AssertionInfo m_info; AssertionResultData m_resultData; }; } // end namespace Catch // #included from: catch_matchers.hpp #define TWOBLUECUBES_CATCH_MATCHERS_HPP_INCLUDED namespace Catch { namespace Matchers { namespace Impl { template struct MatchAllOf; template struct MatchAnyOf; template struct MatchNotOf; class MatcherUntypedBase { public: std::string toString() const { if( m_cachedToString.empty() ) m_cachedToString = describe(); return m_cachedToString; } protected: virtual ~MatcherUntypedBase(); virtual std::string describe() const = 0; mutable std::string m_cachedToString; private: MatcherUntypedBase& operator = ( MatcherUntypedBase const& ); }; template struct MatcherMethod { virtual bool match( ObjectT const& arg ) const = 0; }; template struct MatcherMethod { virtual bool match( PtrT* arg ) const = 0; }; template struct MatcherBase : MatcherUntypedBase, MatcherMethod { MatchAllOf operator && ( MatcherBase const& other ) const; MatchAnyOf operator || ( MatcherBase const& other ) const; MatchNotOf operator ! () const; }; template struct MatchAllOf : MatcherBase { virtual bool match( ArgT const& arg ) const CATCH_OVERRIDE { for( std::size_t i = 0; i < m_matchers.size(); ++i ) { if (!m_matchers[i]->match(arg)) return false; } return true; } virtual std::string describe() const CATCH_OVERRIDE { std::string description; description.reserve( 4 + m_matchers.size()*32 ); description += "( "; for( std::size_t i = 0; i < m_matchers.size(); ++i ) { if( i != 0 ) description += " and "; description += m_matchers[i]->toString(); } description += " )"; return description; } MatchAllOf& operator && ( MatcherBase const& other ) { m_matchers.push_back( &other ); return *this; } std::vector const*> m_matchers; }; template struct MatchAnyOf : MatcherBase { virtual bool match( ArgT const& arg ) const CATCH_OVERRIDE { for( std::size_t i = 0; i < m_matchers.size(); ++i ) { if (m_matchers[i]->match(arg)) return true; } return false; } virtual std::string describe() const CATCH_OVERRIDE { std::string description; description.reserve( 4 + m_matchers.size()*32 ); description += "( "; for( std::size_t i = 0; i < m_matchers.size(); ++i ) { if( i != 0 ) description += " or "; description += m_matchers[i]->toString(); } description += " )"; return description; } MatchAnyOf& operator || ( MatcherBase const& other ) { m_matchers.push_back( &other ); return *this; } std::vector const*> m_matchers; }; template struct MatchNotOf : MatcherBase { MatchNotOf( MatcherBase const& underlyingMatcher ) : m_underlyingMatcher( underlyingMatcher ) {} virtual bool match( ArgT const& arg ) const CATCH_OVERRIDE { return !m_underlyingMatcher.match( arg ); } virtual std::string describe() const CATCH_OVERRIDE { return "not " + m_underlyingMatcher.toString(); } MatcherBase const& m_underlyingMatcher; }; template MatchAllOf MatcherBase::operator && ( MatcherBase const& other ) const { return MatchAllOf() && *this && other; } template MatchAnyOf MatcherBase::operator || ( MatcherBase const& other ) const { return MatchAnyOf() || *this || other; } template MatchNotOf MatcherBase::operator ! () const { return MatchNotOf( *this ); } } // namespace Impl // The following functions create the actual matcher objects. // This allows the types to be inferred // - deprecated: prefer ||, && and ! template Impl::MatchNotOf Not( Impl::MatcherBase const& underlyingMatcher ) { return Impl::MatchNotOf( underlyingMatcher ); } template Impl::MatchAllOf AllOf( Impl::MatcherBase const& m1, Impl::MatcherBase const& m2 ) { return Impl::MatchAllOf() && m1 && m2; } template Impl::MatchAllOf AllOf( Impl::MatcherBase const& m1, Impl::MatcherBase const& m2, Impl::MatcherBase const& m3 ) { return Impl::MatchAllOf() && m1 && m2 && m3; } template Impl::MatchAnyOf AnyOf( Impl::MatcherBase const& m1, Impl::MatcherBase const& m2 ) { return Impl::MatchAnyOf() || m1 || m2; } template Impl::MatchAnyOf AnyOf( Impl::MatcherBase const& m1, Impl::MatcherBase const& m2, Impl::MatcherBase const& m3 ) { return Impl::MatchAnyOf() || m1 || m2 || m3; } } // namespace Matchers using namespace Matchers; using Matchers::Impl::MatcherBase; } // namespace Catch namespace Catch { struct TestFailureException{}; template class ExpressionLhs; struct CopyableStream { CopyableStream() {} CopyableStream( CopyableStream const& other ) { oss << other.oss.str(); } CopyableStream& operator=( CopyableStream const& other ) { oss.str(std::string()); oss << other.oss.str(); return *this; } std::ostringstream oss; }; class ResultBuilder : public DecomposedExpression { public: ResultBuilder( char const* macroName, SourceLineInfo const& lineInfo, char const* capturedExpression, ResultDisposition::Flags resultDisposition, char const* secondArg = "" ); ~ResultBuilder(); template ExpressionLhs operator <= ( T const& operand ); ExpressionLhs operator <= ( bool value ); template ResultBuilder& operator << ( T const& value ) { stream().oss << value; return *this; } ResultBuilder& setResultType( ResultWas::OfType result ); ResultBuilder& setResultType( bool result ); void endExpression( DecomposedExpression const& expr ); virtual void reconstructExpression( std::string& dest ) const CATCH_OVERRIDE; AssertionResult build() const; AssertionResult build( DecomposedExpression const& expr ) const; void useActiveException( ResultDisposition::Flags resultDisposition = ResultDisposition::Normal ); void captureResult( ResultWas::OfType resultType ); void captureExpression(); void captureExpectedException( std::string const& expectedMessage ); void captureExpectedException( Matchers::Impl::MatcherBase const& matcher ); void handleResult( AssertionResult const& result ); void react(); bool shouldDebugBreak() const; bool allowThrows() const; template void captureMatch( ArgT const& arg, MatcherT const& matcher, char const* matcherString ); void setExceptionGuard(); void unsetExceptionGuard(); private: AssertionInfo m_assertionInfo; AssertionResultData m_data; CopyableStream &stream() { if(!m_usedStream) { m_usedStream = true; m_stream().oss.str(""); } return m_stream(); } static CopyableStream &m_stream() { static CopyableStream s; return s; } bool m_shouldDebugBreak; bool m_shouldThrow; bool m_guardException; bool m_usedStream; }; } // namespace Catch // Include after due to circular dependency: // #included from: catch_expression_lhs.hpp #define TWOBLUECUBES_CATCH_EXPRESSION_LHS_HPP_INCLUDED // #included from: catch_evaluate.hpp #define TWOBLUECUBES_CATCH_EVALUATE_HPP_INCLUDED #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable:4389) // '==' : signed/unsigned mismatch #pragma warning(disable:4018) // more "signed/unsigned mismatch" #pragma warning(disable:4312) // Converting int to T* using reinterpret_cast (issue on x64 platform) #endif #include namespace Catch { namespace Internal { enum Operator { IsEqualTo, IsNotEqualTo, IsLessThan, IsGreaterThan, IsLessThanOrEqualTo, IsGreaterThanOrEqualTo }; template struct OperatorTraits { static const char* getName(){ return "*error*"; } }; template<> struct OperatorTraits { static const char* getName(){ return "=="; } }; template<> struct OperatorTraits { static const char* getName(){ return "!="; } }; template<> struct OperatorTraits { static const char* getName(){ return "<"; } }; template<> struct OperatorTraits { static const char* getName(){ return ">"; } }; template<> struct OperatorTraits { static const char* getName(){ return "<="; } }; template<> struct OperatorTraits{ static const char* getName(){ return ">="; } }; template T& removeConst(T const &t) { return const_cast(t); } #ifdef CATCH_CONFIG_CPP11_NULLPTR inline std::nullptr_t removeConst(std::nullptr_t) { return nullptr; } #endif // So the compare overloads can be operator agnostic we convey the operator as a template // enum, which is used to specialise an Evaluator for doing the comparison. template struct Evaluator{}; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs) { return bool(removeConst(lhs) == removeConst(rhs) ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { return bool(removeConst(lhs) != removeConst(rhs) ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { return bool(removeConst(lhs) < removeConst(rhs) ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { return bool(removeConst(lhs) > removeConst(rhs) ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { return bool(removeConst(lhs) >= removeConst(rhs) ); } }; template struct Evaluator { static bool evaluate( T1 const& lhs, T2 const& rhs ) { return bool(removeConst(lhs) <= removeConst(rhs) ); } }; // Special case for comparing a pointer to an int (deduced for p==0) template struct Evaluator { static bool evaluate( int lhs, T* rhs) { return reinterpret_cast( lhs ) == rhs; } }; template struct Evaluator { static bool evaluate( T* lhs, int rhs) { return lhs == reinterpret_cast( rhs ); } }; template struct Evaluator { static bool evaluate( int lhs, T* rhs) { return reinterpret_cast( lhs ) != rhs; } }; template struct Evaluator { static bool evaluate( T* lhs, int rhs) { return lhs != reinterpret_cast( rhs ); } }; template struct Evaluator { static bool evaluate( long lhs, T* rhs) { return reinterpret_cast( lhs ) == rhs; } }; template struct Evaluator { static bool evaluate( T* lhs, long rhs) { return lhs == reinterpret_cast( rhs ); } }; template struct Evaluator { static bool evaluate( long lhs, T* rhs) { return reinterpret_cast( lhs ) != rhs; } }; template struct Evaluator { static bool evaluate( T* lhs, long rhs) { return lhs != reinterpret_cast( rhs ); } }; } // end of namespace Internal } // end of namespace Catch #ifdef _MSC_VER #pragma warning(pop) #endif // #included from: catch_tostring.h #define TWOBLUECUBES_CATCH_TOSTRING_H_INCLUDED #include #include #include #include #include #ifdef __OBJC__ // #included from: catch_objc_arc.hpp #define TWOBLUECUBES_CATCH_OBJC_ARC_HPP_INCLUDED #import #ifdef __has_feature #define CATCH_ARC_ENABLED __has_feature(objc_arc) #else #define CATCH_ARC_ENABLED 0 #endif void arcSafeRelease( NSObject* obj ); id performOptionalSelector( id obj, SEL sel ); #if !CATCH_ARC_ENABLED inline void arcSafeRelease( NSObject* obj ) { [obj release]; } inline id performOptionalSelector( id obj, SEL sel ) { if( [obj respondsToSelector: sel] ) return [obj performSelector: sel]; return nil; } #define CATCH_UNSAFE_UNRETAINED #define CATCH_ARC_STRONG #else inline void arcSafeRelease( NSObject* ){} inline id performOptionalSelector( id obj, SEL sel ) { #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" #endif if( [obj respondsToSelector: sel] ) return [obj performSelector: sel]; #ifdef __clang__ #pragma clang diagnostic pop #endif return nil; } #define CATCH_UNSAFE_UNRETAINED __unsafe_unretained #define CATCH_ARC_STRONG __strong #endif #endif #ifdef CATCH_CONFIG_CPP11_TUPLE #include #endif #ifdef CATCH_CONFIG_CPP11_IS_ENUM #include #endif namespace Catch { // Why we're here. template std::string toString( T const& value ); // Built in overloads std::string toString( std::string const& value ); std::string toString( std::wstring const& value ); std::string toString( const char* const value ); std::string toString( char* const value ); std::string toString( const wchar_t* const value ); std::string toString( wchar_t* const value ); std::string toString( int value ); std::string toString( unsigned long value ); std::string toString( unsigned int value ); std::string toString( const double value ); std::string toString( const float value ); std::string toString( bool value ); std::string toString( char value ); std::string toString( signed char value ); std::string toString( unsigned char value ); #ifdef CATCH_CONFIG_CPP11_LONG_LONG std::string toString( long long value ); std::string toString( unsigned long long value ); #endif #ifdef CATCH_CONFIG_CPP11_NULLPTR std::string toString( std::nullptr_t ); #endif #ifdef __OBJC__ std::string toString( NSString const * const& nsstring ); std::string toString( NSString * CATCH_ARC_STRONG & nsstring ); std::string toString( NSObject* const& nsObject ); #endif namespace Detail { extern const std::string unprintableString; #if !defined(CATCH_CONFIG_CPP11_STREAM_INSERTABLE_CHECK) struct BorgType { template BorgType( T const& ); }; struct TrueType { char sizer[1]; }; struct FalseType { char sizer[2]; }; TrueType& testStreamable( std::ostream& ); FalseType testStreamable( FalseType ); FalseType operator<<( std::ostream const&, BorgType const& ); template struct IsStreamInsertable { static std::ostream &s; static T const&t; enum { value = sizeof( testStreamable(s << t) ) == sizeof( TrueType ) }; }; #else template class IsStreamInsertable { template static auto test(int) -> decltype( std::declval() << std::declval(), std::true_type() ); template static auto test(...) -> std::false_type; public: static const bool value = decltype(test(0))::value; }; #endif #if defined(CATCH_CONFIG_CPP11_IS_ENUM) template::value > struct EnumStringMaker { static std::string convert( T const& ) { return unprintableString; } }; template struct EnumStringMaker { static std::string convert( T const& v ) { return ::Catch::toString( static_cast::type>(v) ); } }; #endif template struct StringMakerBase { #if defined(CATCH_CONFIG_CPP11_IS_ENUM) template static std::string convert( T const& v ) { return EnumStringMaker::convert( v ); } #else template static std::string convert( T const& ) { return unprintableString; } #endif }; template<> struct StringMakerBase { template static std::string convert( T const& _value ) { std::ostringstream oss; oss << _value; return oss.str(); } }; std::string rawMemoryToString( const void *object, std::size_t size ); template std::string rawMemoryToString( const T& object ) { return rawMemoryToString( &object, sizeof(object) ); } } // end namespace Detail template struct StringMaker : Detail::StringMakerBase::value> {}; template struct StringMaker { template static std::string convert( U* p ) { if( !p ) return "NULL"; else return Detail::rawMemoryToString( p ); } }; template struct StringMaker { static std::string convert( R C::* p ) { if( !p ) return "NULL"; else return Detail::rawMemoryToString( p ); } }; namespace Detail { template std::string rangeToString( InputIterator first, InputIterator last ); } //template //struct StringMaker > { // static std::string convert( std::vector const& v ) { // return Detail::rangeToString( v.begin(), v.end() ); // } //}; template std::string toString( std::vector const& v ) { return Detail::rangeToString( v.begin(), v.end() ); } #ifdef CATCH_CONFIG_CPP11_TUPLE // toString for tuples namespace TupleDetail { template< typename Tuple, std::size_t N = 0, bool = (N < std::tuple_size::value) > struct ElementPrinter { static void print( const Tuple& tuple, std::ostream& os ) { os << ( N ? ", " : " " ) << Catch::toString(std::get(tuple)); ElementPrinter::print(tuple,os); } }; template< typename Tuple, std::size_t N > struct ElementPrinter { static void print( const Tuple&, std::ostream& ) {} }; } template struct StringMaker> { static std::string convert( const std::tuple& tuple ) { std::ostringstream os; os << '{'; TupleDetail::ElementPrinter>::print( tuple, os ); os << " }"; return os.str(); } }; #endif // CATCH_CONFIG_CPP11_TUPLE namespace Detail { template std::string makeString( T const& value ) { return StringMaker::convert( value ); } } // end namespace Detail /// \brief converts any type to a string /// /// The default template forwards on to ostringstream - except when an /// ostringstream overload does not exist - in which case it attempts to detect /// that and writes {?}. /// Overload (not specialise) this template for custom typs that you don't want /// to provide an ostream overload for. template std::string toString( T const& value ) { return StringMaker::convert( value ); } namespace Detail { template std::string rangeToString( InputIterator first, InputIterator last ) { std::ostringstream oss; oss << "{ "; if( first != last ) { oss << Catch::toString( *first ); for( ++first ; first != last ; ++first ) oss << ", " << Catch::toString( *first ); } oss << " }"; return oss.str(); } } } // end namespace Catch namespace Catch { template class BinaryExpression; template class MatchExpression; // Wraps the LHS of an expression and overloads comparison operators // for also capturing those and RHS (if any) template class ExpressionLhs : public DecomposedExpression { public: ExpressionLhs( ResultBuilder& rb, T lhs ) : m_rb( rb ), m_lhs( lhs ), m_truthy(false) {} ExpressionLhs& operator = ( const ExpressionLhs& ); template BinaryExpression operator == ( RhsT const& rhs ) { return captureExpression( rhs ); } template BinaryExpression operator != ( RhsT const& rhs ) { return captureExpression( rhs ); } template BinaryExpression operator < ( RhsT const& rhs ) { return captureExpression( rhs ); } template BinaryExpression operator > ( RhsT const& rhs ) { return captureExpression( rhs ); } template BinaryExpression operator <= ( RhsT const& rhs ) { return captureExpression( rhs ); } template BinaryExpression operator >= ( RhsT const& rhs ) { return captureExpression( rhs ); } BinaryExpression operator == ( bool rhs ) { return captureExpression( rhs ); } BinaryExpression operator != ( bool rhs ) { return captureExpression( rhs ); } void endExpression() { m_truthy = m_lhs ? true : false; m_rb .setResultType( m_truthy ) .endExpression( *this ); } virtual void reconstructExpression( std::string& dest ) const CATCH_OVERRIDE { dest = Catch::toString( m_lhs ); } private: template BinaryExpression captureExpression( RhsT& rhs ) const { return BinaryExpression( m_rb, m_lhs, rhs ); } template BinaryExpression captureExpression( bool rhs ) const { return BinaryExpression( m_rb, m_lhs, rhs ); } private: ResultBuilder& m_rb; T m_lhs; bool m_truthy; }; template class BinaryExpression : public DecomposedExpression { public: BinaryExpression( ResultBuilder& rb, LhsT lhs, RhsT rhs ) : m_rb( rb ), m_lhs( lhs ), m_rhs( rhs ) {} BinaryExpression& operator = ( BinaryExpression& ); void endExpression() const { m_rb .setResultType( Internal::Evaluator::evaluate( m_lhs, m_rhs ) ) .endExpression( *this ); } virtual bool isBinaryExpression() const CATCH_OVERRIDE { return true; } virtual void reconstructExpression( std::string& dest ) const CATCH_OVERRIDE { std::string lhs = Catch::toString( m_lhs ); std::string rhs = Catch::toString( m_rhs ); char delim = lhs.size() + rhs.size() < 40 && lhs.find('\n') == std::string::npos && rhs.find('\n') == std::string::npos ? ' ' : '\n'; dest.reserve( 7 + lhs.size() + rhs.size() ); // 2 for spaces around operator // 2 for operator // 2 for parentheses (conditionally added later) // 1 for negation (conditionally added later) dest = lhs; dest += delim; dest += Internal::OperatorTraits::getName(); dest += delim; dest += rhs; } private: ResultBuilder& m_rb; LhsT m_lhs; RhsT m_rhs; }; template class MatchExpression : public DecomposedExpression { public: MatchExpression( ArgT arg, MatcherT matcher, char const* matcherString ) : m_arg( arg ), m_matcher( matcher ), m_matcherString( matcherString ) {} virtual bool isBinaryExpression() const CATCH_OVERRIDE { return true; } virtual void reconstructExpression( std::string& dest ) const CATCH_OVERRIDE { std::string matcherAsString = m_matcher.toString(); dest = Catch::toString( m_arg ); dest += ' '; if( matcherAsString == Detail::unprintableString ) dest += m_matcherString; else dest += matcherAsString; } private: ArgT m_arg; MatcherT m_matcher; char const* m_matcherString; }; } // end namespace Catch namespace Catch { template ExpressionLhs ResultBuilder::operator <= ( T const& operand ) { return ExpressionLhs( *this, operand ); } inline ExpressionLhs ResultBuilder::operator <= ( bool value ) { return ExpressionLhs( *this, value ); } template void ResultBuilder::captureMatch( ArgT const& arg, MatcherT const& matcher, char const* matcherString ) { MatchExpression expr( arg, matcher, matcherString ); setResultType( matcher.match( arg ) ); endExpression( expr ); } } // namespace Catch // #included from: catch_message.h #define TWOBLUECUBES_CATCH_MESSAGE_H_INCLUDED #include namespace Catch { struct MessageInfo { MessageInfo( std::string const& _macroName, SourceLineInfo const& _lineInfo, ResultWas::OfType _type ); std::string macroName; SourceLineInfo lineInfo; ResultWas::OfType type; std::string message; unsigned int sequence; bool operator == ( MessageInfo const& other ) const { return sequence == other.sequence; } bool operator < ( MessageInfo const& other ) const { return sequence < other.sequence; } private: static unsigned int globalCount; }; struct MessageBuilder { MessageBuilder( std::string const& macroName, SourceLineInfo const& lineInfo, ResultWas::OfType type ) : m_info( macroName, lineInfo, type ) {} template MessageBuilder& operator << ( T const& value ) { m_stream << value; return *this; } MessageInfo m_info; std::ostringstream m_stream; }; class ScopedMessage { public: ScopedMessage( MessageBuilder const& builder ); ScopedMessage( ScopedMessage const& other ); ~ScopedMessage(); MessageInfo m_info; }; } // end namespace Catch // #included from: catch_interfaces_capture.h #define TWOBLUECUBES_CATCH_INTERFACES_CAPTURE_H_INCLUDED #include namespace Catch { class TestCase; class AssertionResult; struct AssertionInfo; struct SectionInfo; struct SectionEndInfo; struct MessageInfo; class ScopedMessageBuilder; struct Counts; struct IResultCapture { virtual ~IResultCapture(); virtual void assertionEnded( AssertionResult const& result ) = 0; virtual bool sectionStarted( SectionInfo const& sectionInfo, Counts& assertions ) = 0; virtual void sectionEnded( SectionEndInfo const& endInfo ) = 0; virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) = 0; virtual void pushScopedMessage( MessageInfo const& message ) = 0; virtual void popScopedMessage( MessageInfo const& message ) = 0; virtual std::string getCurrentTestName() const = 0; virtual const AssertionResult* getLastResult() const = 0; virtual void exceptionEarlyReported() = 0; virtual void handleFatalErrorCondition( std::string const& message ) = 0; virtual bool lastAssertionPassed() = 0; virtual void assertionPassed() = 0; virtual void assertionRun() = 0; }; IResultCapture& getResultCapture(); } // #included from: catch_debugger.h #define TWOBLUECUBES_CATCH_DEBUGGER_H_INCLUDED // #included from: catch_platform.h #define TWOBLUECUBES_CATCH_PLATFORM_H_INCLUDED #if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) # define CATCH_PLATFORM_MAC #elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED) # define CATCH_PLATFORM_IPHONE #elif defined(linux) || defined(__linux) || defined(__linux__) # define CATCH_PLATFORM_LINUX #elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) # define CATCH_PLATFORM_WINDOWS # if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX) # define CATCH_DEFINES_NOMINMAX # endif # if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN) # define CATCH_DEFINES_WIN32_LEAN_AND_MEAN # endif #endif #include namespace Catch{ bool isDebuggerActive(); void writeToDebugConsole( std::string const& text ); } #ifdef CATCH_PLATFORM_MAC // The following code snippet based on: // http://cocoawithlove.com/2008/03/break-into-debugger.html #if defined(__ppc64__) || defined(__ppc__) #define CATCH_TRAP() \ __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n" \ : : : "memory","r0","r3","r4" ) /* NOLINT */ #else #define CATCH_TRAP() __asm__("int $3\n" : : /* NOLINT */ ) #endif #elif defined(CATCH_PLATFORM_LINUX) // If we can use inline assembler, do it because this allows us to break // directly at the location of the failing check instead of breaking inside // raise() called from it, i.e. one stack frame below. #if defined(__GNUC__) && (defined(__i386) || defined(__x86_64)) #define CATCH_TRAP() asm volatile ("int $3") /* NOLINT */ #else // Fall back to the generic way. #include #define CATCH_TRAP() raise(SIGTRAP) #endif #elif defined(_MSC_VER) #define CATCH_TRAP() __debugbreak() #elif defined(__MINGW32__) extern "C" __declspec(dllimport) void __stdcall DebugBreak(); #define CATCH_TRAP() DebugBreak() #endif #ifdef CATCH_TRAP #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { CATCH_TRAP(); } #else #define CATCH_BREAK_INTO_DEBUGGER() Catch::alwaysTrue(); #endif // #included from: catch_interfaces_runner.h #define TWOBLUECUBES_CATCH_INTERFACES_RUNNER_H_INCLUDED namespace Catch { class TestCase; struct IRunner { virtual ~IRunner(); virtual bool aborting() const = 0; }; } #if !defined(CATCH_CONFIG_DISABLE_STRINGIFICATION) # define CATCH_INTERNAL_STRINGIFY(expr) #expr #else # define CATCH_INTERNAL_STRINGIFY(expr) "Disabled by CATCH_CONFIG_DISABLE_STRINGIFICATION" #endif #if defined(CATCH_CONFIG_FAST_COMPILE) /////////////////////////////////////////////////////////////////////////////// // We can speedup compilation significantly by breaking into debugger lower in // the callstack, because then we don't have to expand CATCH_BREAK_INTO_DEBUGGER // macro in each assertion #define INTERNAL_CATCH_REACT( resultBuilder ) \ resultBuilder.react(); /////////////////////////////////////////////////////////////////////////////// // Another way to speed-up compilation is to omit local try-catch for REQUIRE* // macros. // This can potentially cause false negative, if the test code catches // the exception before it propagates back up to the runner. #define INTERNAL_CATCH_TEST_NO_TRY( macroName, resultDisposition, expr ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr), resultDisposition ); \ __catchResult.setExceptionGuard(); \ CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ ( __catchResult <= expr ).endExpression(); \ CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ __catchResult.unsetExceptionGuard(); \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::isTrue( false && static_cast( !!(expr) ) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look // The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&. #define INTERNAL_CHECK_THAT_NO_TRY( macroName, matcher, resultDisposition, arg ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(arg) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ __catchResult.setExceptionGuard(); \ __catchResult.captureMatch( arg, matcher, CATCH_INTERNAL_STRINGIFY(matcher) ); \ __catchResult.unsetExceptionGuard(); \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::alwaysFalse() ) #else /////////////////////////////////////////////////////////////////////////////// // In the event of a failure works out if the debugger needs to be invoked // and/or an exception thrown and takes appropriate action. // This needs to be done as a macro so the debugger will stop in the user // source code rather than in Catch library code #define INTERNAL_CATCH_REACT( resultBuilder ) \ if( resultBuilder.shouldDebugBreak() ) CATCH_BREAK_INTO_DEBUGGER(); \ resultBuilder.react(); #endif /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TEST( macroName, resultDisposition, expr ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr), resultDisposition ); \ try { \ CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ ( __catchResult <= expr ).endExpression(); \ CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ } \ catch( ... ) { \ __catchResult.useActiveException( resultDisposition ); \ } \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::isTrue( false && static_cast( !!(expr) ) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look // The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&. /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_IF( macroName, resultDisposition, expr ) \ INTERNAL_CATCH_TEST( macroName, resultDisposition, expr ); \ if( Catch::getResultCapture().lastAssertionPassed() ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_ELSE( macroName, resultDisposition, expr ) \ INTERNAL_CATCH_TEST( macroName, resultDisposition, expr ); \ if( !Catch::getResultCapture().lastAssertionPassed() ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_NO_THROW( macroName, resultDisposition, expr ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr), resultDisposition ); \ try { \ static_cast(expr); \ __catchResult.captureResult( Catch::ResultWas::Ok ); \ } \ catch( ... ) { \ __catchResult.useActiveException( resultDisposition ); \ } \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::alwaysFalse() ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_THROWS( macroName, resultDisposition, matcher, expr ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr), resultDisposition, CATCH_INTERNAL_STRINGIFY(matcher) ); \ if( __catchResult.allowThrows() ) \ try { \ static_cast(expr); \ __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ } \ catch( ... ) { \ __catchResult.captureExpectedException( matcher ); \ } \ else \ __catchResult.captureResult( Catch::ResultWas::Ok ); \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::alwaysFalse() ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_THROWS_AS( macroName, exceptionType, resultDisposition, expr ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr) ", " CATCH_INTERNAL_STRINGIFY(exceptionType), resultDisposition ); \ if( __catchResult.allowThrows() ) \ try { \ static_cast(expr); \ __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ } \ catch( exceptionType ) { \ __catchResult.captureResult( Catch::ResultWas::Ok ); \ } \ catch( ... ) { \ __catchResult.useActiveException( resultDisposition ); \ } \ else \ __catchResult.captureResult( Catch::ResultWas::Ok ); \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::alwaysFalse() ) /////////////////////////////////////////////////////////////////////////////// #ifdef CATCH_CONFIG_VARIADIC_MACROS #define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, ... ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ __catchResult << __VA_ARGS__ + ::Catch::StreamEndStop(); \ __catchResult.captureResult( messageType ); \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::alwaysFalse() ) #else #define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, log ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ __catchResult << log + ::Catch::StreamEndStop(); \ __catchResult.captureResult( messageType ); \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::alwaysFalse() ) #endif /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_INFO( macroName, log ) \ Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage ) = Catch::MessageBuilder( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log; /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CHECK_THAT( macroName, matcher, resultDisposition, arg ) \ do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(arg) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ try { \ __catchResult.captureMatch( arg, matcher, CATCH_INTERNAL_STRINGIFY(matcher) ); \ } catch( ... ) { \ __catchResult.useActiveException( resultDisposition | Catch::ResultDisposition::ContinueOnFailure ); \ } \ INTERNAL_CATCH_REACT( __catchResult ) \ } while( Catch::alwaysFalse() ) // #included from: internal/catch_section.h #define TWOBLUECUBES_CATCH_SECTION_H_INCLUDED // #included from: catch_section_info.h #define TWOBLUECUBES_CATCH_SECTION_INFO_H_INCLUDED // #included from: catch_totals.hpp #define TWOBLUECUBES_CATCH_TOTALS_HPP_INCLUDED #include namespace Catch { struct Counts { Counts() : passed( 0 ), failed( 0 ), failedButOk( 0 ) {} Counts operator - ( Counts const& other ) const { Counts diff; diff.passed = passed - other.passed; diff.failed = failed - other.failed; diff.failedButOk = failedButOk - other.failedButOk; return diff; } Counts& operator += ( Counts const& other ) { passed += other.passed; failed += other.failed; failedButOk += other.failedButOk; return *this; } std::size_t total() const { return passed + failed + failedButOk; } bool allPassed() const { return failed == 0 && failedButOk == 0; } bool allOk() const { return failed == 0; } std::size_t passed; std::size_t failed; std::size_t failedButOk; }; struct Totals { Totals operator - ( Totals const& other ) const { Totals diff; diff.assertions = assertions - other.assertions; diff.testCases = testCases - other.testCases; return diff; } Totals delta( Totals const& prevTotals ) const { Totals diff = *this - prevTotals; if( diff.assertions.failed > 0 ) ++diff.testCases.failed; else if( diff.assertions.failedButOk > 0 ) ++diff.testCases.failedButOk; else ++diff.testCases.passed; return diff; } Totals& operator += ( Totals const& other ) { assertions += other.assertions; testCases += other.testCases; return *this; } Counts assertions; Counts testCases; }; } #include namespace Catch { struct SectionInfo { SectionInfo ( SourceLineInfo const& _lineInfo, std::string const& _name, std::string const& _description = std::string() ); std::string name; std::string description; SourceLineInfo lineInfo; }; struct SectionEndInfo { SectionEndInfo( SectionInfo const& _sectionInfo, Counts const& _prevAssertions, double _durationInSeconds ) : sectionInfo( _sectionInfo ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds ) {} SectionInfo sectionInfo; Counts prevAssertions; double durationInSeconds; }; } // end namespace Catch // #included from: catch_timer.h #define TWOBLUECUBES_CATCH_TIMER_H_INCLUDED #ifdef _MSC_VER namespace Catch { typedef unsigned long long UInt64; } #else #include namespace Catch { typedef uint64_t UInt64; } #endif namespace Catch { class Timer { public: Timer() : m_ticks( 0 ) {} void start(); unsigned int getElapsedMicroseconds() const; unsigned int getElapsedMilliseconds() const; double getElapsedSeconds() const; private: UInt64 m_ticks; }; } // namespace Catch #include namespace Catch { class Section : NonCopyable { public: Section( SectionInfo const& info ); ~Section(); // This indicates whether the section should be executed or not operator bool() const; private: SectionInfo m_info; std::string m_name; Counts m_assertions; bool m_sectionIncluded; Timer m_timer; }; } // end namespace Catch #ifdef CATCH_CONFIG_VARIADIC_MACROS #define INTERNAL_CATCH_SECTION( ... ) \ if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) #else #define INTERNAL_CATCH_SECTION( name, desc ) \ if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, name, desc ) ) #endif // #included from: internal/catch_generators.hpp #define TWOBLUECUBES_CATCH_GENERATORS_HPP_INCLUDED #include #include #include namespace Catch { template struct IGenerator { virtual ~IGenerator() {} virtual T getValue( std::size_t index ) const = 0; virtual std::size_t size () const = 0; }; template class BetweenGenerator : public IGenerator { public: BetweenGenerator( T from, T to ) : m_from( from ), m_to( to ){} virtual T getValue( std::size_t index ) const { return m_from+static_cast( index ); } virtual std::size_t size() const { return static_cast( 1+m_to-m_from ); } private: T m_from; T m_to; }; template class ValuesGenerator : public IGenerator { public: ValuesGenerator(){} void add( T value ) { m_values.push_back( value ); } virtual T getValue( std::size_t index ) const { return m_values[index]; } virtual std::size_t size() const { return m_values.size(); } private: std::vector m_values; }; template class CompositeGenerator { public: CompositeGenerator() : m_totalSize( 0 ) {} // *** Move semantics, similar to auto_ptr *** CompositeGenerator( CompositeGenerator& other ) : m_fileInfo( other.m_fileInfo ), m_totalSize( 0 ) { move( other ); } CompositeGenerator& setFileInfo( const char* fileInfo ) { m_fileInfo = fileInfo; return *this; } ~CompositeGenerator() { deleteAll( m_composed ); } operator T () const { size_t overallIndex = getCurrentContext().getGeneratorIndex( m_fileInfo, m_totalSize ); typename std::vector*>::const_iterator it = m_composed.begin(); typename std::vector*>::const_iterator itEnd = m_composed.end(); for( size_t index = 0; it != itEnd; ++it ) { const IGenerator* generator = *it; if( overallIndex >= index && overallIndex < index + generator->size() ) { return generator->getValue( overallIndex-index ); } index += generator->size(); } CATCH_INTERNAL_ERROR( "Indexed past end of generated range" ); return T(); // Suppress spurious "not all control paths return a value" warning in Visual Studio - if you know how to fix this please do so } void add( const IGenerator* generator ) { m_totalSize += generator->size(); m_composed.push_back( generator ); } CompositeGenerator& then( CompositeGenerator& other ) { move( other ); return *this; } CompositeGenerator& then( T value ) { ValuesGenerator* valuesGen = new ValuesGenerator(); valuesGen->add( value ); add( valuesGen ); return *this; } private: void move( CompositeGenerator& other ) { m_composed.insert( m_composed.end(), other.m_composed.begin(), other.m_composed.end() ); m_totalSize += other.m_totalSize; other.m_composed.clear(); } std::vector*> m_composed; std::string m_fileInfo; size_t m_totalSize; }; namespace Generators { template CompositeGenerator between( T from, T to ) { CompositeGenerator generators; generators.add( new BetweenGenerator( from, to ) ); return generators; } template CompositeGenerator values( T val1, T val2 ) { CompositeGenerator generators; ValuesGenerator* valuesGen = new ValuesGenerator(); valuesGen->add( val1 ); valuesGen->add( val2 ); generators.add( valuesGen ); return generators; } template CompositeGenerator values( T val1, T val2, T val3 ){ CompositeGenerator generators; ValuesGenerator* valuesGen = new ValuesGenerator(); valuesGen->add( val1 ); valuesGen->add( val2 ); valuesGen->add( val3 ); generators.add( valuesGen ); return generators; } template CompositeGenerator values( T val1, T val2, T val3, T val4 ) { CompositeGenerator generators; ValuesGenerator* valuesGen = new ValuesGenerator(); valuesGen->add( val1 ); valuesGen->add( val2 ); valuesGen->add( val3 ); valuesGen->add( val4 ); generators.add( valuesGen ); return generators; } } // end namespace Generators using namespace Generators; } // end namespace Catch #define INTERNAL_CATCH_LINESTR2( line ) #line #define INTERNAL_CATCH_LINESTR( line ) INTERNAL_CATCH_LINESTR2( line ) #define INTERNAL_CATCH_GENERATE( expr ) expr.setFileInfo( __FILE__ "(" INTERNAL_CATCH_LINESTR( __LINE__ ) ")" ) // #included from: internal/catch_interfaces_exception.h #define TWOBLUECUBES_CATCH_INTERFACES_EXCEPTION_H_INCLUDED #include #include // #included from: catch_interfaces_registry_hub.h #define TWOBLUECUBES_CATCH_INTERFACES_REGISTRY_HUB_H_INCLUDED #include namespace Catch { class TestCase; struct ITestCaseRegistry; struct IExceptionTranslatorRegistry; struct IExceptionTranslator; struct IReporterRegistry; struct IReporterFactory; struct ITagAliasRegistry; struct IRegistryHub { virtual ~IRegistryHub(); virtual IReporterRegistry const& getReporterRegistry() const = 0; virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0; virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() = 0; }; struct IMutableRegistryHub { virtual ~IMutableRegistryHub(); virtual void registerReporter( std::string const& name, Ptr const& factory ) = 0; virtual void registerListener( Ptr const& factory ) = 0; virtual void registerTest( TestCase const& testInfo ) = 0; virtual void registerTranslator( const IExceptionTranslator* translator ) = 0; virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0; }; IRegistryHub& getRegistryHub(); IMutableRegistryHub& getMutableRegistryHub(); void cleanUp(); std::string translateActiveException(); } namespace Catch { typedef std::string(*exceptionTranslateFunction)(); struct IExceptionTranslator; typedef std::vector ExceptionTranslators; struct IExceptionTranslator { virtual ~IExceptionTranslator(); virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const = 0; }; struct IExceptionTranslatorRegistry { virtual ~IExceptionTranslatorRegistry(); virtual std::string translateActiveException() const = 0; }; class ExceptionTranslatorRegistrar { template class ExceptionTranslator : public IExceptionTranslator { public: ExceptionTranslator( std::string(*translateFunction)( T& ) ) : m_translateFunction( translateFunction ) {} virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const CATCH_OVERRIDE { try { if( it == itEnd ) throw; else return (*it)->translate( it+1, itEnd ); } catch( T& ex ) { return m_translateFunction( ex ); } } protected: std::string(*m_translateFunction)( T& ); }; public: template ExceptionTranslatorRegistrar( std::string(*translateFunction)( T& ) ) { getMutableRegistryHub().registerTranslator ( new ExceptionTranslator( translateFunction ) ); } }; } /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TRANSLATE_EXCEPTION2( translatorName, signature ) \ static std::string translatorName( signature ); \ namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); }\ static std::string translatorName( signature ) #define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION2( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) // #included from: internal/catch_approx.hpp #define TWOBLUECUBES_CATCH_APPROX_HPP_INCLUDED #include #include #if defined(CATCH_CONFIG_CPP11_TYPE_TRAITS) #include #endif namespace Catch { namespace Detail { class Approx { public: explicit Approx ( double value ) : m_epsilon( std::numeric_limits::epsilon()*100 ), m_margin( 0.0 ), m_scale( 1.0 ), m_value( value ) {} static Approx custom() { return Approx( 0 ); } #if defined(CATCH_CONFIG_CPP11_TYPE_TRAITS) template ::value>::type> Approx operator()( T value ) { Approx approx( static_cast(value) ); approx.epsilon( m_epsilon ); approx.margin( m_margin ); approx.scale( m_scale ); return approx; } template ::value>::type> explicit Approx( T value ): Approx(static_cast(value)) {} template ::value>::type> friend bool operator == ( const T& lhs, Approx const& rhs ) { // Thanks to Richard Harris for his help refining this formula auto lhs_v = double(lhs); bool relativeOK = std::fabs(lhs_v - rhs.m_value) < rhs.m_epsilon * (rhs.m_scale + (std::max)(std::fabs(lhs_v), std::fabs(rhs.m_value))); if (relativeOK) { return true; } return std::fabs(lhs_v - rhs.m_value) < rhs.m_margin; } template ::value>::type> friend bool operator == ( Approx const& lhs, const T& rhs ) { return operator==( rhs, lhs ); } template ::value>::type> friend bool operator != ( T lhs, Approx const& rhs ) { return !operator==( lhs, rhs ); } template ::value>::type> friend bool operator != ( Approx const& lhs, T rhs ) { return !operator==( rhs, lhs ); } template ::value>::type> friend bool operator <= ( T lhs, Approx const& rhs ) { return double(lhs) < rhs.m_value || lhs == rhs; } template ::value>::type> friend bool operator <= ( Approx const& lhs, T rhs ) { return lhs.m_value < double(rhs) || lhs == rhs; } template ::value>::type> friend bool operator >= ( T lhs, Approx const& rhs ) { return double(lhs) > rhs.m_value || lhs == rhs; } template ::value>::type> friend bool operator >= ( Approx const& lhs, T rhs ) { return lhs.m_value > double(rhs) || lhs == rhs; } template ::value>::type> Approx& epsilon( T newEpsilon ) { m_epsilon = double(newEpsilon); return *this; } template ::value>::type> Approx& margin( T newMargin ) { m_margin = double(newMargin); return *this; } template ::value>::type> Approx& scale( T newScale ) { m_scale = double(newScale); return *this; } #else Approx operator()( double value ) { Approx approx( value ); approx.epsilon( m_epsilon ); approx.margin( m_margin ); approx.scale( m_scale ); return approx; } friend bool operator == ( double lhs, Approx const& rhs ) { // Thanks to Richard Harris for his help refining this formula bool relativeOK = std::fabs( lhs - rhs.m_value ) < rhs.m_epsilon * (rhs.m_scale + (std::max)( std::fabs(lhs), std::fabs(rhs.m_value) ) ); if (relativeOK) { return true; } return std::fabs(lhs - rhs.m_value) < rhs.m_margin; } friend bool operator == ( Approx const& lhs, double rhs ) { return operator==( rhs, lhs ); } friend bool operator != ( double lhs, Approx const& rhs ) { return !operator==( lhs, rhs ); } friend bool operator != ( Approx const& lhs, double rhs ) { return !operator==( rhs, lhs ); } friend bool operator <= ( double lhs, Approx const& rhs ) { return lhs < rhs.m_value || lhs == rhs; } friend bool operator <= ( Approx const& lhs, double rhs ) { return lhs.m_value < rhs || lhs == rhs; } friend bool operator >= ( double lhs, Approx const& rhs ) { return lhs > rhs.m_value || lhs == rhs; } friend bool operator >= ( Approx const& lhs, double rhs ) { return lhs.m_value > rhs || lhs == rhs; } Approx& epsilon( double newEpsilon ) { m_epsilon = newEpsilon; return *this; } Approx& margin( double newMargin ) { m_margin = newMargin; return *this; } Approx& scale( double newScale ) { m_scale = newScale; return *this; } #endif std::string toString() const { std::ostringstream oss; oss << "Approx( " << Catch::toString( m_value ) << " )"; return oss.str(); } private: double m_epsilon; double m_margin; double m_scale; double m_value; }; } template<> inline std::string toString( Detail::Approx const& value ) { return value.toString(); } } // end namespace Catch // #included from: internal/catch_matchers_string.h #define TWOBLUECUBES_CATCH_MATCHERS_STRING_H_INCLUDED namespace Catch { namespace Matchers { namespace StdString { struct CasedString { CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ); std::string adjustString( std::string const& str ) const; std::string caseSensitivitySuffix() const; CaseSensitive::Choice m_caseSensitivity; std::string m_str; }; struct StringMatcherBase : MatcherBase { StringMatcherBase( std::string const& operation, CasedString const& comparator ); virtual std::string describe() const CATCH_OVERRIDE; CasedString m_comparator; std::string m_operation; }; struct EqualsMatcher : StringMatcherBase { EqualsMatcher( CasedString const& comparator ); virtual bool match( std::string const& source ) const CATCH_OVERRIDE; }; struct ContainsMatcher : StringMatcherBase { ContainsMatcher( CasedString const& comparator ); virtual bool match( std::string const& source ) const CATCH_OVERRIDE; }; struct StartsWithMatcher : StringMatcherBase { StartsWithMatcher( CasedString const& comparator ); virtual bool match( std::string const& source ) const CATCH_OVERRIDE; }; struct EndsWithMatcher : StringMatcherBase { EndsWithMatcher( CasedString const& comparator ); virtual bool match( std::string const& source ) const CATCH_OVERRIDE; }; } // namespace StdString // The following functions create the actual matcher objects. // This allows the types to be inferred StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); } // namespace Matchers } // namespace Catch // #included from: internal/catch_matchers_vector.h #define TWOBLUECUBES_CATCH_MATCHERS_VECTOR_H_INCLUDED namespace Catch { namespace Matchers { namespace Vector { template struct ContainsElementMatcher : MatcherBase, T> { ContainsElementMatcher(T const &comparator) : m_comparator( comparator) {} bool match(std::vector const &v) const CATCH_OVERRIDE { return std::find(v.begin(), v.end(), m_comparator) != v.end(); } virtual std::string describe() const CATCH_OVERRIDE { return "Contains: " + Catch::toString( m_comparator ); } T const& m_comparator; }; template struct ContainsMatcher : MatcherBase, std::vector > { ContainsMatcher(std::vector const &comparator) : m_comparator( comparator ) {} bool match(std::vector const &v) const CATCH_OVERRIDE { // !TBD: see note in EqualsMatcher if (m_comparator.size() > v.size()) return false; for (size_t i = 0; i < m_comparator.size(); ++i) if (std::find(v.begin(), v.end(), m_comparator[i]) == v.end()) return false; return true; } virtual std::string describe() const CATCH_OVERRIDE { return "Contains: " + Catch::toString( m_comparator ); } std::vector const& m_comparator; }; template struct EqualsMatcher : MatcherBase, std::vector > { EqualsMatcher(std::vector const &comparator) : m_comparator( comparator ) {} bool match(std::vector const &v) const CATCH_OVERRIDE { // !TBD: This currently works if all elements can be compared using != // - a more general approach would be via a compare template that defaults // to using !=. but could be specialised for, e.g. std::vector etc // - then just call that directly if (m_comparator.size() != v.size()) return false; for (size_t i = 0; i < v.size(); ++i) if (m_comparator[i] != v[i]) return false; return true; } virtual std::string describe() const CATCH_OVERRIDE { return "Equals: " + Catch::toString( m_comparator ); } std::vector const& m_comparator; }; } // namespace Vector // The following functions create the actual matcher objects. // This allows the types to be inferred template Vector::ContainsMatcher Contains( std::vector const& comparator ) { return Vector::ContainsMatcher( comparator ); } template Vector::ContainsElementMatcher VectorContains( T const& comparator ) { return Vector::ContainsElementMatcher( comparator ); } template Vector::EqualsMatcher Equals( std::vector const& comparator ) { return Vector::EqualsMatcher( comparator ); } } // namespace Matchers } // namespace Catch // #included from: internal/catch_interfaces_tag_alias_registry.h #define TWOBLUECUBES_CATCH_INTERFACES_TAG_ALIAS_REGISTRY_H_INCLUDED // #included from: catch_tag_alias.h #define TWOBLUECUBES_CATCH_TAG_ALIAS_H_INCLUDED #include namespace Catch { struct TagAlias { TagAlias( std::string const& _tag, SourceLineInfo _lineInfo ) : tag( _tag ), lineInfo( _lineInfo ) {} std::string tag; SourceLineInfo lineInfo; }; struct RegistrarForTagAliases { RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); }; } // end namespace Catch #define CATCH_REGISTER_TAG_ALIAS( alias, spec ) namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } // #included from: catch_option.hpp #define TWOBLUECUBES_CATCH_OPTION_HPP_INCLUDED namespace Catch { // An optional type template class Option { public: Option() : nullableValue( CATCH_NULL ) {} Option( T const& _value ) : nullableValue( new( storage ) T( _value ) ) {} Option( Option const& _other ) : nullableValue( _other ? new( storage ) T( *_other ) : CATCH_NULL ) {} ~Option() { reset(); } Option& operator= ( Option const& _other ) { if( &_other != this ) { reset(); if( _other ) nullableValue = new( storage ) T( *_other ); } return *this; } Option& operator = ( T const& _value ) { reset(); nullableValue = new( storage ) T( _value ); return *this; } void reset() { if( nullableValue ) nullableValue->~T(); nullableValue = CATCH_NULL; } T& operator*() { return *nullableValue; } T const& operator*() const { return *nullableValue; } T* operator->() { return nullableValue; } const T* operator->() const { return nullableValue; } T valueOr( T const& defaultValue ) const { return nullableValue ? *nullableValue : defaultValue; } bool some() const { return nullableValue != CATCH_NULL; } bool none() const { return nullableValue == CATCH_NULL; } bool operator !() const { return nullableValue == CATCH_NULL; } operator SafeBool::type() const { return SafeBool::makeSafe( some() ); } private: T *nullableValue; union { char storage[sizeof(T)]; // These are here to force alignment for the storage long double dummy1; void (*dummy2)(); long double dummy3; #ifdef CATCH_CONFIG_CPP11_LONG_LONG long long dummy4; #endif }; }; } // end namespace Catch namespace Catch { struct ITagAliasRegistry { virtual ~ITagAliasRegistry(); virtual Option find( std::string const& alias ) const = 0; virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0; static ITagAliasRegistry const& get(); }; } // end namespace Catch // These files are included here so the single_include script doesn't put them // in the conditionally compiled sections // #included from: internal/catch_test_case_info.h #define TWOBLUECUBES_CATCH_TEST_CASE_INFO_H_INCLUDED #include #include #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpadded" #endif namespace Catch { struct ITestCase; struct TestCaseInfo { enum SpecialProperties{ None = 0, IsHidden = 1 << 1, ShouldFail = 1 << 2, MayFail = 1 << 3, Throws = 1 << 4, NonPortable = 1 << 5 }; TestCaseInfo( std::string const& _name, std::string const& _className, std::string const& _description, std::set const& _tags, SourceLineInfo const& _lineInfo ); TestCaseInfo( TestCaseInfo const& other ); friend void setTags( TestCaseInfo& testCaseInfo, std::set const& tags ); bool isHidden() const; bool throws() const; bool okToFail() const; bool expectedToFail() const; std::string name; std::string className; std::string description; std::set tags; std::set lcaseTags; std::string tagsAsString; SourceLineInfo lineInfo; SpecialProperties properties; }; class TestCase : public TestCaseInfo { public: TestCase( ITestCase* testCase, TestCaseInfo const& info ); TestCase( TestCase const& other ); TestCase withName( std::string const& _newName ) const; void invoke() const; TestCaseInfo const& getTestCaseInfo() const; void swap( TestCase& other ); bool operator == ( TestCase const& other ) const; bool operator < ( TestCase const& other ) const; TestCase& operator = ( TestCase const& other ); private: Ptr test; }; TestCase makeTestCase( ITestCase* testCase, std::string const& className, std::string const& name, std::string const& description, SourceLineInfo const& lineInfo ); } #ifdef __clang__ #pragma clang diagnostic pop #endif #ifdef __OBJC__ // #included from: internal/catch_objc.hpp #define TWOBLUECUBES_CATCH_OBJC_HPP_INCLUDED #import #include // NB. Any general catch headers included here must be included // in catch.hpp first to make sure they are included by the single // header for non obj-usage /////////////////////////////////////////////////////////////////////////////// // This protocol is really only here for (self) documenting purposes, since // all its methods are optional. @protocol OcFixture @optional -(void) setUp; -(void) tearDown; @end namespace Catch { class OcMethod : public SharedImpl { public: OcMethod( Class cls, SEL sel ) : m_cls( cls ), m_sel( sel ) {} virtual void invoke() const { id obj = [[m_cls alloc] init]; performOptionalSelector( obj, @selector(setUp) ); performOptionalSelector( obj, m_sel ); performOptionalSelector( obj, @selector(tearDown) ); arcSafeRelease( obj ); } private: virtual ~OcMethod() {} Class m_cls; SEL m_sel; }; namespace Detail{ inline std::string getAnnotation( Class cls, std::string const& annotationName, std::string const& testCaseName ) { NSString* selStr = [[NSString alloc] initWithFormat:@"Catch_%s_%s", annotationName.c_str(), testCaseName.c_str()]; SEL sel = NSSelectorFromString( selStr ); arcSafeRelease( selStr ); id value = performOptionalSelector( cls, sel ); if( value ) return [(NSString*)value UTF8String]; return ""; } } inline size_t registerTestMethods() { size_t noTestMethods = 0; int noClasses = objc_getClassList( CATCH_NULL, 0 ); Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses); objc_getClassList( classes, noClasses ); for( int c = 0; c < noClasses; c++ ) { Class cls = classes[c]; { u_int count; Method* methods = class_copyMethodList( cls, &count ); for( u_int m = 0; m < count ; m++ ) { SEL selector = method_getName(methods[m]); std::string methodName = sel_getName(selector); if( startsWith( methodName, "Catch_TestCase_" ) ) { std::string testCaseName = methodName.substr( 15 ); std::string name = Detail::getAnnotation( cls, "Name", testCaseName ); std::string desc = Detail::getAnnotation( cls, "Description", testCaseName ); const char* className = class_getName( cls ); getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, name.c_str(), desc.c_str(), SourceLineInfo() ) ); noTestMethods++; } } free(methods); } } return noTestMethods; } namespace Matchers { namespace Impl { namespace NSStringMatchers { struct StringHolder : MatcherBase{ StringHolder( NSString* substr ) : m_substr( [substr copy] ){} StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){} StringHolder() { arcSafeRelease( m_substr ); } virtual bool match( NSString* arg ) const CATCH_OVERRIDE { return false; } NSString* m_substr; }; struct Equals : StringHolder { Equals( NSString* substr ) : StringHolder( substr ){} virtual bool match( NSString* str ) const CATCH_OVERRIDE { return (str != nil || m_substr == nil ) && [str isEqualToString:m_substr]; } virtual std::string describe() const CATCH_OVERRIDE { return "equals string: " + Catch::toString( m_substr ); } }; struct Contains : StringHolder { Contains( NSString* substr ) : StringHolder( substr ){} virtual bool match( NSString* str ) const { return (str != nil || m_substr == nil ) && [str rangeOfString:m_substr].location != NSNotFound; } virtual std::string describe() const CATCH_OVERRIDE { return "contains string: " + Catch::toString( m_substr ); } }; struct StartsWith : StringHolder { StartsWith( NSString* substr ) : StringHolder( substr ){} virtual bool match( NSString* str ) const { return (str != nil || m_substr == nil ) && [str rangeOfString:m_substr].location == 0; } virtual std::string describe() const CATCH_OVERRIDE { return "starts with: " + Catch::toString( m_substr ); } }; struct EndsWith : StringHolder { EndsWith( NSString* substr ) : StringHolder( substr ){} virtual bool match( NSString* str ) const { return (str != nil || m_substr == nil ) && [str rangeOfString:m_substr].location == [str length] - [m_substr length]; } virtual std::string describe() const CATCH_OVERRIDE { return "ends with: " + Catch::toString( m_substr ); } }; } // namespace NSStringMatchers } // namespace Impl inline Impl::NSStringMatchers::Equals Equals( NSString* substr ){ return Impl::NSStringMatchers::Equals( substr ); } inline Impl::NSStringMatchers::Contains Contains( NSString* substr ){ return Impl::NSStringMatchers::Contains( substr ); } inline Impl::NSStringMatchers::StartsWith StartsWith( NSString* substr ){ return Impl::NSStringMatchers::StartsWith( substr ); } inline Impl::NSStringMatchers::EndsWith EndsWith( NSString* substr ){ return Impl::NSStringMatchers::EndsWith( substr ); } } // namespace Matchers using namespace Matchers; } // namespace Catch /////////////////////////////////////////////////////////////////////////////// #define OC_TEST_CASE( name, desc )\ +(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Name_test ) \ {\ return @ name; \ }\ +(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Description_test ) \ { \ return @ desc; \ } \ -(void) INTERNAL_CATCH_UNIQUE_NAME( Catch_TestCase_test ) #endif #ifdef CATCH_IMPL // !TBD: Move the leak detector code into a separate header #ifdef CATCH_CONFIG_WINDOWS_CRTDBG #include class LeakDetector { public: LeakDetector() { int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); flag |= _CRTDBG_LEAK_CHECK_DF; flag |= _CRTDBG_ALLOC_MEM_DF; _CrtSetDbgFlag(flag); _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); // Change this to leaking allocation's number to break there _CrtSetBreakAlloc(-1); } }; #else class LeakDetector {}; #endif LeakDetector leakDetector; // #included from: internal/catch_impl.hpp #define TWOBLUECUBES_CATCH_IMPL_HPP_INCLUDED // Collect all the implementation files together here // These are the equivalent of what would usually be cpp files #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wweak-vtables" #endif // #included from: ../catch_session.hpp #define TWOBLUECUBES_CATCH_RUNNER_HPP_INCLUDED // #included from: internal/catch_commandline.hpp #define TWOBLUECUBES_CATCH_COMMANDLINE_HPP_INCLUDED // #included from: catch_config.hpp #define TWOBLUECUBES_CATCH_CONFIG_HPP_INCLUDED // #included from: catch_test_spec_parser.hpp #define TWOBLUECUBES_CATCH_TEST_SPEC_PARSER_HPP_INCLUDED #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpadded" #endif // #included from: catch_test_spec.hpp #define TWOBLUECUBES_CATCH_TEST_SPEC_HPP_INCLUDED #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpadded" #endif // #included from: catch_wildcard_pattern.hpp #define TWOBLUECUBES_CATCH_WILDCARD_PATTERN_HPP_INCLUDED #include namespace Catch { class WildcardPattern { enum WildcardPosition { NoWildcard = 0, WildcardAtStart = 1, WildcardAtEnd = 2, WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd }; public: WildcardPattern( std::string const& pattern, CaseSensitive::Choice caseSensitivity ) : m_caseSensitivity( caseSensitivity ), m_wildcard( NoWildcard ), m_pattern( adjustCase( pattern ) ) { if( startsWith( m_pattern, '*' ) ) { m_pattern = m_pattern.substr( 1 ); m_wildcard = WildcardAtStart; } if( endsWith( m_pattern, '*' ) ) { m_pattern = m_pattern.substr( 0, m_pattern.size()-1 ); m_wildcard = static_cast( m_wildcard | WildcardAtEnd ); } } virtual ~WildcardPattern(); virtual bool matches( std::string const& str ) const { switch( m_wildcard ) { case NoWildcard: return m_pattern == adjustCase( str ); case WildcardAtStart: return endsWith( adjustCase( str ), m_pattern ); case WildcardAtEnd: return startsWith( adjustCase( str ), m_pattern ); case WildcardAtBothEnds: return contains( adjustCase( str ), m_pattern ); } #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunreachable-code" #endif throw std::logic_error( "Unknown enum" ); #ifdef __clang__ #pragma clang diagnostic pop #endif } private: std::string adjustCase( std::string const& str ) const { return m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str; } CaseSensitive::Choice m_caseSensitivity; WildcardPosition m_wildcard; std::string m_pattern; }; } #include #include namespace Catch { class TestSpec { struct Pattern : SharedImpl<> { virtual ~Pattern(); virtual bool matches( TestCaseInfo const& testCase ) const = 0; }; class NamePattern : public Pattern { public: NamePattern( std::string const& name ) : m_wildcardPattern( toLower( name ), CaseSensitive::No ) {} virtual ~NamePattern(); virtual bool matches( TestCaseInfo const& testCase ) const { return m_wildcardPattern.matches( toLower( testCase.name ) ); } private: WildcardPattern m_wildcardPattern; }; class TagPattern : public Pattern { public: TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {} virtual ~TagPattern(); virtual bool matches( TestCaseInfo const& testCase ) const { return testCase.lcaseTags.find( m_tag ) != testCase.lcaseTags.end(); } private: std::string m_tag; }; class ExcludedPattern : public Pattern { public: ExcludedPattern( Ptr const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {} virtual ~ExcludedPattern(); virtual bool matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); } private: Ptr m_underlyingPattern; }; struct Filter { std::vector > m_patterns; bool matches( TestCaseInfo const& testCase ) const { // All patterns in a filter must match for the filter to be a match for( std::vector >::const_iterator it = m_patterns.begin(), itEnd = m_patterns.end(); it != itEnd; ++it ) { if( !(*it)->matches( testCase ) ) return false; } return true; } }; public: bool hasFilters() const { return !m_filters.empty(); } bool matches( TestCaseInfo const& testCase ) const { // A TestSpec matches if any filter matches for( std::vector::const_iterator it = m_filters.begin(), itEnd = m_filters.end(); it != itEnd; ++it ) if( it->matches( testCase ) ) return true; return false; } private: std::vector m_filters; friend class TestSpecParser; }; } #ifdef __clang__ #pragma clang diagnostic pop #endif namespace Catch { class TestSpecParser { enum Mode{ None, Name, QuotedName, Tag, EscapedName }; Mode m_mode; bool m_exclusion; std::size_t m_start, m_pos; std::string m_arg; std::vector m_escapeChars; TestSpec::Filter m_currentFilter; TestSpec m_testSpec; ITagAliasRegistry const* m_tagAliases; public: TestSpecParser( ITagAliasRegistry const& tagAliases ) :m_mode(None), m_exclusion(false), m_start(0), m_pos(0), m_tagAliases( &tagAliases ) {} TestSpecParser& parse( std::string const& arg ) { m_mode = None; m_exclusion = false; m_start = std::string::npos; m_arg = m_tagAliases->expandAliases( arg ); m_escapeChars.clear(); for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) visitChar( m_arg[m_pos] ); if( m_mode == Name ) addPattern(); return *this; } TestSpec testSpec() { addFilter(); return m_testSpec; } private: void visitChar( char c ) { if( m_mode == None ) { switch( c ) { case ' ': return; case '~': m_exclusion = true; return; case '[': return startNewMode( Tag, ++m_pos ); case '"': return startNewMode( QuotedName, ++m_pos ); case '\\': return escape(); default: startNewMode( Name, m_pos ); break; } } if( m_mode == Name ) { if( c == ',' ) { addPattern(); addFilter(); } else if( c == '[' ) { if( subString() == "exclude:" ) m_exclusion = true; else addPattern(); startNewMode( Tag, ++m_pos ); } else if( c == '\\' ) escape(); } else if( m_mode == EscapedName ) m_mode = Name; else if( m_mode == QuotedName && c == '"' ) addPattern(); else if( m_mode == Tag && c == ']' ) addPattern(); } void startNewMode( Mode mode, std::size_t start ) { m_mode = mode; m_start = start; } void escape() { if( m_mode == None ) m_start = m_pos; m_mode = EscapedName; m_escapeChars.push_back( m_pos ); } std::string subString() const { return m_arg.substr( m_start, m_pos - m_start ); } template void addPattern() { std::string token = subString(); for( size_t i = 0; i < m_escapeChars.size(); ++i ) token = token.substr( 0, m_escapeChars[i]-m_start-i ) + token.substr( m_escapeChars[i]-m_start-i+1 ); m_escapeChars.clear(); if( startsWith( token, "exclude:" ) ) { m_exclusion = true; token = token.substr( 8 ); } if( !token.empty() ) { Ptr pattern = new T( token ); if( m_exclusion ) pattern = new TestSpec::ExcludedPattern( pattern ); m_currentFilter.m_patterns.push_back( pattern ); } m_exclusion = false; m_mode = None; } void addFilter() { if( !m_currentFilter.m_patterns.empty() ) { m_testSpec.m_filters.push_back( m_currentFilter ); m_currentFilter = TestSpec::Filter(); } } }; inline TestSpec parseTestSpec( std::string const& arg ) { return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec(); } } // namespace Catch #ifdef __clang__ #pragma clang diagnostic pop #endif // #included from: catch_interfaces_config.h #define TWOBLUECUBES_CATCH_INTERFACES_CONFIG_H_INCLUDED #include #include #include namespace Catch { struct Verbosity { enum Level { NoOutput = 0, Quiet, Normal }; }; struct WarnAbout { enum What { Nothing = 0x00, NoAssertions = 0x01 }; }; struct ShowDurations { enum OrNot { DefaultForReporter, Always, Never }; }; struct RunTests { enum InWhatOrder { InDeclarationOrder, InLexicographicalOrder, InRandomOrder }; }; struct UseColour { enum YesOrNo { Auto, Yes, No }; }; struct WaitForKeypress { enum When { Never, BeforeStart = 1, BeforeExit = 2, BeforeStartAndExit = BeforeStart | BeforeExit }; }; class TestSpec; struct IConfig : IShared { virtual ~IConfig(); virtual bool allowThrows() const = 0; virtual std::ostream& stream() const = 0; virtual std::string name() const = 0; virtual bool includeSuccessfulResults() const = 0; virtual bool shouldDebugBreak() const = 0; virtual bool warnAboutMissingAssertions() const = 0; virtual int abortAfter() const = 0; virtual bool showInvisibles() const = 0; virtual ShowDurations::OrNot showDurations() const = 0; virtual TestSpec const& testSpec() const = 0; virtual RunTests::InWhatOrder runOrder() const = 0; virtual unsigned int rngSeed() const = 0; virtual UseColour::YesOrNo useColour() const = 0; virtual std::vector const& getSectionsToRun() const = 0; }; } // #included from: catch_stream.h #define TWOBLUECUBES_CATCH_STREAM_H_INCLUDED // #included from: catch_streambuf.h #define TWOBLUECUBES_CATCH_STREAMBUF_H_INCLUDED #include namespace Catch { class StreamBufBase : public std::streambuf { public: virtual ~StreamBufBase() CATCH_NOEXCEPT; }; } #include #include #include #include namespace Catch { std::ostream& cout(); std::ostream& cerr(); std::ostream& clog(); struct IStream { virtual ~IStream() CATCH_NOEXCEPT; virtual std::ostream& stream() const = 0; }; class FileStream : public IStream { mutable std::ofstream m_ofs; public: FileStream( std::string const& filename ); virtual ~FileStream() CATCH_NOEXCEPT; public: // IStream virtual std::ostream& stream() const CATCH_OVERRIDE; }; class CoutStream : public IStream { mutable std::ostream m_os; public: CoutStream(); virtual ~CoutStream() CATCH_NOEXCEPT; public: // IStream virtual std::ostream& stream() const CATCH_OVERRIDE; }; class DebugOutStream : public IStream { CATCH_AUTO_PTR( StreamBufBase ) m_streamBuf; mutable std::ostream m_os; public: DebugOutStream(); virtual ~DebugOutStream() CATCH_NOEXCEPT; public: // IStream virtual std::ostream& stream() const CATCH_OVERRIDE; }; } #include #include #include #include #ifndef CATCH_CONFIG_CONSOLE_WIDTH #define CATCH_CONFIG_CONSOLE_WIDTH 80 #endif namespace Catch { struct ConfigData { ConfigData() : listTests( false ), listTags( false ), listReporters( false ), listTestNamesOnly( false ), listExtraInfo( false ), showSuccessfulTests( false ), shouldDebugBreak( false ), noThrow( false ), showHelp( false ), showInvisibles( false ), filenamesAsTags( false ), libIdentify( false ), abortAfter( -1 ), rngSeed( 0 ), verbosity( Verbosity::Normal ), warnings( WarnAbout::Nothing ), showDurations( ShowDurations::DefaultForReporter ), runOrder( RunTests::InDeclarationOrder ), useColour( UseColour::Auto ), waitForKeypress( WaitForKeypress::Never ) {} bool listTests; bool listTags; bool listReporters; bool listTestNamesOnly; bool listExtraInfo; bool showSuccessfulTests; bool shouldDebugBreak; bool noThrow; bool showHelp; bool showInvisibles; bool filenamesAsTags; bool libIdentify; int abortAfter; unsigned int rngSeed; Verbosity::Level verbosity; WarnAbout::What warnings; ShowDurations::OrNot showDurations; RunTests::InWhatOrder runOrder; UseColour::YesOrNo useColour; WaitForKeypress::When waitForKeypress; std::string outputFilename; std::string name; std::string processName; std::vector reporterNames; std::vector testsOrTags; std::vector sectionsToRun; }; class Config : public SharedImpl { private: Config( Config const& other ); Config& operator = ( Config const& other ); virtual void dummy(); public: Config() {} Config( ConfigData const& data ) : m_data( data ), m_stream( openStream() ) { if( !data.testsOrTags.empty() ) { TestSpecParser parser( ITagAliasRegistry::get() ); for( std::size_t i = 0; i < data.testsOrTags.size(); ++i ) parser.parse( data.testsOrTags[i] ); m_testSpec = parser.testSpec(); } } virtual ~Config() {} std::string const& getFilename() const { return m_data.outputFilename ; } bool listTests() const { return m_data.listTests; } bool listTestNamesOnly() const { return m_data.listTestNamesOnly; } bool listTags() const { return m_data.listTags; } bool listReporters() const { return m_data.listReporters; } bool listExtraInfo() const { return m_data.listExtraInfo; } std::string getProcessName() const { return m_data.processName; } std::vector const& getReporterNames() const { return m_data.reporterNames; } std::vector const& getSectionsToRun() const CATCH_OVERRIDE { return m_data.sectionsToRun; } virtual TestSpec const& testSpec() const CATCH_OVERRIDE { return m_testSpec; } bool showHelp() const { return m_data.showHelp; } // IConfig interface virtual bool allowThrows() const CATCH_OVERRIDE { return !m_data.noThrow; } virtual std::ostream& stream() const CATCH_OVERRIDE { return m_stream->stream(); } virtual std::string name() const CATCH_OVERRIDE { return m_data.name.empty() ? m_data.processName : m_data.name; } virtual bool includeSuccessfulResults() const CATCH_OVERRIDE { return m_data.showSuccessfulTests; } virtual bool warnAboutMissingAssertions() const CATCH_OVERRIDE { return m_data.warnings & WarnAbout::NoAssertions; } virtual ShowDurations::OrNot showDurations() const CATCH_OVERRIDE { return m_data.showDurations; } virtual RunTests::InWhatOrder runOrder() const CATCH_OVERRIDE { return m_data.runOrder; } virtual unsigned int rngSeed() const CATCH_OVERRIDE { return m_data.rngSeed; } virtual UseColour::YesOrNo useColour() const CATCH_OVERRIDE { return m_data.useColour; } virtual bool shouldDebugBreak() const CATCH_OVERRIDE { return m_data.shouldDebugBreak; } virtual int abortAfter() const CATCH_OVERRIDE { return m_data.abortAfter; } virtual bool showInvisibles() const CATCH_OVERRIDE { return m_data.showInvisibles; } private: IStream const* openStream() { if( m_data.outputFilename.empty() ) return new CoutStream(); else if( m_data.outputFilename[0] == '%' ) { if( m_data.outputFilename == "%debug" ) return new DebugOutStream(); else throw std::domain_error( "Unrecognised stream: " + m_data.outputFilename ); } else return new FileStream( m_data.outputFilename ); } ConfigData m_data; CATCH_AUTO_PTR( IStream const ) m_stream; TestSpec m_testSpec; }; } // end namespace Catch // #included from: catch_clara.h #define TWOBLUECUBES_CATCH_CLARA_H_INCLUDED // Use Catch's value for console width (store Clara's off to the side, if present) #ifdef CLARA_CONFIG_CONSOLE_WIDTH #define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CLARA_CONFIG_CONSOLE_WIDTH #undef CLARA_CONFIG_CONSOLE_WIDTH #endif #define CLARA_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH // Declare Clara inside the Catch namespace #define STITCH_CLARA_OPEN_NAMESPACE namespace Catch { // #included from: ../external/clara.h // Version 0.0.2.4 // Only use header guard if we are not using an outer namespace #if !defined(TWOBLUECUBES_CLARA_H_INCLUDED) || defined(STITCH_CLARA_OPEN_NAMESPACE) #ifndef STITCH_CLARA_OPEN_NAMESPACE #define TWOBLUECUBES_CLARA_H_INCLUDED #define STITCH_CLARA_OPEN_NAMESPACE #define STITCH_CLARA_CLOSE_NAMESPACE #else #define STITCH_CLARA_CLOSE_NAMESPACE } #endif #define STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE STITCH_CLARA_OPEN_NAMESPACE // ----------- #included from tbc_text_format.h ----------- // Only use header guard if we are not using an outer namespace #if !defined(TBC_TEXT_FORMAT_H_INCLUDED) || defined(STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE) #ifndef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE #define TBC_TEXT_FORMAT_H_INCLUDED #endif #include #include #include #include #include // Use optional outer namespace #ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE namespace STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE { #endif namespace Tbc { #ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; #else const unsigned int consoleWidth = 80; #endif struct TextAttributes { TextAttributes() : initialIndent( std::string::npos ), indent( 0 ), width( consoleWidth-1 ), tabChar( '\t' ) {} TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; } TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; } TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; } TextAttributes& setTabChar( char _value ) { tabChar = _value; return *this; } std::size_t initialIndent; // indent of first line, or npos std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos std::size_t width; // maximum width of text, including indent. Longer text will wrap char tabChar; // If this char is seen the indent is changed to current pos }; class Text { public: Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() ) : attr( _attr ) { std::string wrappableChars = " [({.,/|\\-"; std::size_t indent = _attr.initialIndent != std::string::npos ? _attr.initialIndent : _attr.indent; std::string remainder = _str; while( !remainder.empty() ) { if( lines.size() >= 1000 ) { lines.push_back( "... message truncated due to excessive size" ); return; } std::size_t tabPos = std::string::npos; std::size_t width = (std::min)( remainder.size(), _attr.width - indent ); std::size_t pos = remainder.find_first_of( '\n' ); if( pos <= width ) { width = pos; } pos = remainder.find_last_of( _attr.tabChar, width ); if( pos != std::string::npos ) { tabPos = pos; if( remainder[width] == '\n' ) width--; remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 ); } if( width == remainder.size() ) { spliceLine( indent, remainder, width ); } else if( remainder[width] == '\n' ) { spliceLine( indent, remainder, width ); if( width <= 1 || remainder.size() != 1 ) remainder = remainder.substr( 1 ); indent = _attr.indent; } else { pos = remainder.find_last_of( wrappableChars, width ); if( pos != std::string::npos && pos > 0 ) { spliceLine( indent, remainder, pos ); if( remainder[0] == ' ' ) remainder = remainder.substr( 1 ); } else { spliceLine( indent, remainder, width-1 ); lines.back() += "-"; } if( lines.size() == 1 ) indent = _attr.indent; if( tabPos != std::string::npos ) indent += tabPos; } } } void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) { lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) ); _remainder = _remainder.substr( _pos ); } typedef std::vector::const_iterator const_iterator; const_iterator begin() const { return lines.begin(); } const_iterator end() const { return lines.end(); } std::string const& last() const { return lines.back(); } std::size_t size() const { return lines.size(); } std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } std::string toString() const { std::ostringstream oss; oss << *this; return oss.str(); } friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); it != itEnd; ++it ) { if( it != _text.begin() ) _stream << "\n"; _stream << *it; } return _stream; } private: std::string str; TextAttributes attr; std::vector lines; }; } // end namespace Tbc #ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE } // end outer namespace #endif #endif // TBC_TEXT_FORMAT_H_INCLUDED // ----------- end of #include from tbc_text_format.h ----------- // ........... back in clara.h #undef STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE // ----------- #included from clara_compilers.h ----------- #ifndef TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED #define TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED // Detect a number of compiler features - mostly C++11/14 conformance - by compiler // The following features are defined: // // CLARA_CONFIG_CPP11_NULLPTR : is nullptr supported? // CLARA_CONFIG_CPP11_NOEXCEPT : is noexcept supported? // CLARA_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods // CLARA_CONFIG_CPP11_OVERRIDE : is override supported? // CLARA_CONFIG_CPP11_UNIQUE_PTR : is unique_ptr supported (otherwise use auto_ptr) // CLARA_CONFIG_CPP11_OR_GREATER : Is C++11 supported? // CLARA_CONFIG_VARIADIC_MACROS : are variadic macros supported? // In general each macro has a _NO_ form // (e.g. CLARA_CONFIG_CPP11_NO_NULLPTR) which disables the feature. // Many features, at point of detection, define an _INTERNAL_ macro, so they // can be combined, en-mass, with the _NO_ forms later. // All the C++11 features can be disabled with CLARA_CONFIG_NO_CPP11 #ifdef __clang__ #if __has_feature(cxx_nullptr) #define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR #endif #if __has_feature(cxx_noexcept) #define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT #endif #endif // __clang__ //////////////////////////////////////////////////////////////////////////////// // GCC #ifdef __GNUC__ #if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) #define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR #endif // - otherwise more recent versions define __cplusplus >= 201103L // and will get picked up below #endif // __GNUC__ //////////////////////////////////////////////////////////////////////////////// // Visual C++ #ifdef _MSC_VER #if (_MSC_VER >= 1600) #define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR #define CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR #endif #if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015)) #define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT #define CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS #endif #endif // _MSC_VER //////////////////////////////////////////////////////////////////////////////// // C++ language feature support // catch all support for C++11 #if defined(__cplusplus) && __cplusplus >= 201103L #define CLARA_CPP11_OR_GREATER #if !defined(CLARA_INTERNAL_CONFIG_CPP11_NULLPTR) #define CLARA_INTERNAL_CONFIG_CPP11_NULLPTR #endif #ifndef CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT #define CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT #endif #ifndef CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS #define CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS #endif #if !defined(CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE) #define CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE #endif #if !defined(CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) #define CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR #endif #endif // __cplusplus >= 201103L // Now set the actual defines based on the above + anything the user has configured #if defined(CLARA_INTERNAL_CONFIG_CPP11_NULLPTR) && !defined(CLARA_CONFIG_CPP11_NO_NULLPTR) && !defined(CLARA_CONFIG_CPP11_NULLPTR) && !defined(CLARA_CONFIG_NO_CPP11) #define CLARA_CONFIG_CPP11_NULLPTR #endif #if defined(CLARA_INTERNAL_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_CONFIG_CPP11_NO_NOEXCEPT) && !defined(CLARA_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_CONFIG_NO_CPP11) #define CLARA_CONFIG_CPP11_NOEXCEPT #endif #if defined(CLARA_INTERNAL_CONFIG_CPP11_GENERATED_METHODS) && !defined(CLARA_CONFIG_CPP11_NO_GENERATED_METHODS) && !defined(CLARA_CONFIG_CPP11_GENERATED_METHODS) && !defined(CLARA_CONFIG_NO_CPP11) #define CLARA_CONFIG_CPP11_GENERATED_METHODS #endif #if defined(CLARA_INTERNAL_CONFIG_CPP11_OVERRIDE) && !defined(CLARA_CONFIG_NO_OVERRIDE) && !defined(CLARA_CONFIG_CPP11_OVERRIDE) && !defined(CLARA_CONFIG_NO_CPP11) #define CLARA_CONFIG_CPP11_OVERRIDE #endif #if defined(CLARA_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) && !defined(CLARA_CONFIG_NO_UNIQUE_PTR) && !defined(CLARA_CONFIG_CPP11_UNIQUE_PTR) && !defined(CLARA_CONFIG_NO_CPP11) #define CLARA_CONFIG_CPP11_UNIQUE_PTR #endif // noexcept support: #if defined(CLARA_CONFIG_CPP11_NOEXCEPT) && !defined(CLARA_NOEXCEPT) #define CLARA_NOEXCEPT noexcept # define CLARA_NOEXCEPT_IS(x) noexcept(x) #else #define CLARA_NOEXCEPT throw() # define CLARA_NOEXCEPT_IS(x) #endif // nullptr support #ifdef CLARA_CONFIG_CPP11_NULLPTR #define CLARA_NULL nullptr #else #define CLARA_NULL NULL #endif // override support #ifdef CLARA_CONFIG_CPP11_OVERRIDE #define CLARA_OVERRIDE override #else #define CLARA_OVERRIDE #endif // unique_ptr support #ifdef CLARA_CONFIG_CPP11_UNIQUE_PTR # define CLARA_AUTO_PTR( T ) std::unique_ptr #else # define CLARA_AUTO_PTR( T ) std::auto_ptr #endif #endif // TWOBLUECUBES_CLARA_COMPILERS_H_INCLUDED // ----------- end of #include from clara_compilers.h ----------- // ........... back in clara.h #include #include #include #if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) #define CLARA_PLATFORM_WINDOWS #endif // Use optional outer namespace #ifdef STITCH_CLARA_OPEN_NAMESPACE STITCH_CLARA_OPEN_NAMESPACE #endif namespace Clara { struct UnpositionalTag {}; extern UnpositionalTag _; #ifdef CLARA_CONFIG_MAIN UnpositionalTag _; #endif namespace Detail { #ifdef CLARA_CONSOLE_WIDTH const unsigned int consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH; #else const unsigned int consoleWidth = 80; #endif using namespace Tbc; inline bool startsWith( std::string const& str, std::string const& prefix ) { return str.size() >= prefix.size() && str.substr( 0, prefix.size() ) == prefix; } template struct RemoveConstRef{ typedef T type; }; template struct RemoveConstRef{ typedef T type; }; template struct RemoveConstRef{ typedef T type; }; template struct RemoveConstRef{ typedef T type; }; template struct IsBool { static const bool value = false; }; template<> struct IsBool { static const bool value = true; }; template void convertInto( std::string const& _source, T& _dest ) { std::stringstream ss; ss << _source; ss >> _dest; if( ss.fail() ) throw std::runtime_error( "Unable to convert " + _source + " to destination type" ); } inline void convertInto( std::string const& _source, std::string& _dest ) { _dest = _source; } char toLowerCh(char c) { return static_cast( std::tolower( c ) ); } inline void convertInto( std::string const& _source, bool& _dest ) { std::string sourceLC = _source; std::transform( sourceLC.begin(), sourceLC.end(), sourceLC.begin(), toLowerCh ); if( sourceLC == "y" || sourceLC == "1" || sourceLC == "true" || sourceLC == "yes" || sourceLC == "on" ) _dest = true; else if( sourceLC == "n" || sourceLC == "0" || sourceLC == "false" || sourceLC == "no" || sourceLC == "off" ) _dest = false; else throw std::runtime_error( "Expected a boolean value but did not recognise:\n '" + _source + "'" ); } template struct IArgFunction { virtual ~IArgFunction() {} #ifdef CLARA_CONFIG_CPP11_GENERATED_METHODS IArgFunction() = default; IArgFunction( IArgFunction const& ) = default; #endif virtual void set( ConfigT& config, std::string const& value ) const = 0; virtual bool takesArg() const = 0; virtual IArgFunction* clone() const = 0; }; template class BoundArgFunction { public: BoundArgFunction() : functionObj( CLARA_NULL ) {} BoundArgFunction( IArgFunction* _functionObj ) : functionObj( _functionObj ) {} BoundArgFunction( BoundArgFunction const& other ) : functionObj( other.functionObj ? other.functionObj->clone() : CLARA_NULL ) {} BoundArgFunction& operator = ( BoundArgFunction const& other ) { IArgFunction* newFunctionObj = other.functionObj ? other.functionObj->clone() : CLARA_NULL; delete functionObj; functionObj = newFunctionObj; return *this; } ~BoundArgFunction() { delete functionObj; } void set( ConfigT& config, std::string const& value ) const { functionObj->set( config, value ); } bool takesArg() const { return functionObj->takesArg(); } bool isSet() const { return functionObj != CLARA_NULL; } private: IArgFunction* functionObj; }; template struct NullBinder : IArgFunction{ virtual void set( C&, std::string const& ) const {} virtual bool takesArg() const { return true; } virtual IArgFunction* clone() const { return new NullBinder( *this ); } }; template struct BoundDataMember : IArgFunction{ BoundDataMember( M C::* _member ) : member( _member ) {} virtual void set( C& p, std::string const& stringValue ) const { convertInto( stringValue, p.*member ); } virtual bool takesArg() const { return !IsBool::value; } virtual IArgFunction* clone() const { return new BoundDataMember( *this ); } M C::* member; }; template struct BoundUnaryMethod : IArgFunction{ BoundUnaryMethod( void (C::*_member)( M ) ) : member( _member ) {} virtual void set( C& p, std::string const& stringValue ) const { typename RemoveConstRef::type value; convertInto( stringValue, value ); (p.*member)( value ); } virtual bool takesArg() const { return !IsBool::value; } virtual IArgFunction* clone() const { return new BoundUnaryMethod( *this ); } void (C::*member)( M ); }; template struct BoundNullaryMethod : IArgFunction{ BoundNullaryMethod( void (C::*_member)() ) : member( _member ) {} virtual void set( C& p, std::string const& stringValue ) const { bool value; convertInto( stringValue, value ); if( value ) (p.*member)(); } virtual bool takesArg() const { return false; } virtual IArgFunction* clone() const { return new BoundNullaryMethod( *this ); } void (C::*member)(); }; template struct BoundUnaryFunction : IArgFunction{ BoundUnaryFunction( void (*_function)( C& ) ) : function( _function ) {} virtual void set( C& obj, std::string const& stringValue ) const { bool value; convertInto( stringValue, value ); if( value ) function( obj ); } virtual bool takesArg() const { return false; } virtual IArgFunction* clone() const { return new BoundUnaryFunction( *this ); } void (*function)( C& ); }; template struct BoundBinaryFunction : IArgFunction{ BoundBinaryFunction( void (*_function)( C&, T ) ) : function( _function ) {} virtual void set( C& obj, std::string const& stringValue ) const { typename RemoveConstRef::type value; convertInto( stringValue, value ); function( obj, value ); } virtual bool takesArg() const { return !IsBool::value; } virtual IArgFunction* clone() const { return new BoundBinaryFunction( *this ); } void (*function)( C&, T ); }; } // namespace Detail inline std::vector argsToVector( int argc, char const* const* const argv ) { std::vector args( static_cast( argc ) ); for( std::size_t i = 0; i < static_cast( argc ); ++i ) args[i] = argv[i]; return args; } class Parser { enum Mode { None, MaybeShortOpt, SlashOpt, ShortOpt, LongOpt, Positional }; Mode mode; std::size_t from; bool inQuotes; public: struct Token { enum Type { Positional, ShortOpt, LongOpt }; Token( Type _type, std::string const& _data ) : type( _type ), data( _data ) {} Type type; std::string data; }; Parser() : mode( None ), from( 0 ), inQuotes( false ){} void parseIntoTokens( std::vector const& args, std::vector& tokens ) { const std::string doubleDash = "--"; for( std::size_t i = 1; i < args.size() && args[i] != doubleDash; ++i ) parseIntoTokens( args[i], tokens); } void parseIntoTokens( std::string const& arg, std::vector& tokens ) { for( std::size_t i = 0; i < arg.size(); ++i ) { char c = arg[i]; if( c == '"' ) inQuotes = !inQuotes; mode = handleMode( i, c, arg, tokens ); } mode = handleMode( arg.size(), '\0', arg, tokens ); } Mode handleMode( std::size_t i, char c, std::string const& arg, std::vector& tokens ) { switch( mode ) { case None: return handleNone( i, c ); case MaybeShortOpt: return handleMaybeShortOpt( i, c ); case ShortOpt: case LongOpt: case SlashOpt: return handleOpt( i, c, arg, tokens ); case Positional: return handlePositional( i, c, arg, tokens ); default: throw std::logic_error( "Unknown mode" ); } } Mode handleNone( std::size_t i, char c ) { if( inQuotes ) { from = i; return Positional; } switch( c ) { case '-': return MaybeShortOpt; #ifdef CLARA_PLATFORM_WINDOWS case '/': from = i+1; return SlashOpt; #endif default: from = i; return Positional; } } Mode handleMaybeShortOpt( std::size_t i, char c ) { switch( c ) { case '-': from = i+1; return LongOpt; default: from = i; return ShortOpt; } } Mode handleOpt( std::size_t i, char c, std::string const& arg, std::vector& tokens ) { if( std::string( ":=\0", 3 ).find( c ) == std::string::npos ) return mode; std::string optName = arg.substr( from, i-from ); if( mode == ShortOpt ) for( std::size_t j = 0; j < optName.size(); ++j ) tokens.push_back( Token( Token::ShortOpt, optName.substr( j, 1 ) ) ); else if( mode == SlashOpt && optName.size() == 1 ) tokens.push_back( Token( Token::ShortOpt, optName ) ); else tokens.push_back( Token( Token::LongOpt, optName ) ); return None; } Mode handlePositional( std::size_t i, char c, std::string const& arg, std::vector& tokens ) { if( inQuotes || std::string( "\0", 1 ).find( c ) == std::string::npos ) return mode; std::string data = arg.substr( from, i-from ); tokens.push_back( Token( Token::Positional, data ) ); return None; } }; template struct CommonArgProperties { CommonArgProperties() {} CommonArgProperties( Detail::BoundArgFunction const& _boundField ) : boundField( _boundField ) {} Detail::BoundArgFunction boundField; std::string description; std::string detail; std::string placeholder; // Only value if boundField takes an arg bool takesArg() const { return !placeholder.empty(); } void validate() const { if( !boundField.isSet() ) throw std::logic_error( "option not bound" ); } }; struct OptionArgProperties { std::vector shortNames; std::string longName; bool hasShortName( std::string const& shortName ) const { return std::find( shortNames.begin(), shortNames.end(), shortName ) != shortNames.end(); } bool hasLongName( std::string const& _longName ) const { return _longName == longName; } }; struct PositionalArgProperties { PositionalArgProperties() : position( -1 ) {} int position; // -1 means non-positional (floating) bool isFixedPositional() const { return position != -1; } }; template class CommandLine { struct Arg : CommonArgProperties, OptionArgProperties, PositionalArgProperties { Arg() {} Arg( Detail::BoundArgFunction const& _boundField ) : CommonArgProperties( _boundField ) {} using CommonArgProperties::placeholder; // !TBD std::string dbgName() const { if( !longName.empty() ) return "--" + longName; if( !shortNames.empty() ) return "-" + shortNames[0]; return "positional args"; } std::string commands() const { std::ostringstream oss; bool first = true; std::vector::const_iterator it = shortNames.begin(), itEnd = shortNames.end(); for(; it != itEnd; ++it ) { if( first ) first = false; else oss << ", "; oss << "-" << *it; } if( !longName.empty() ) { if( !first ) oss << ", "; oss << "--" << longName; } if( !placeholder.empty() ) oss << " <" << placeholder << ">"; return oss.str(); } }; typedef CLARA_AUTO_PTR( Arg ) ArgAutoPtr; friend void addOptName( Arg& arg, std::string const& optName ) { if( optName.empty() ) return; if( Detail::startsWith( optName, "--" ) ) { if( !arg.longName.empty() ) throw std::logic_error( "Only one long opt may be specified. '" + arg.longName + "' already specified, now attempting to add '" + optName + "'" ); arg.longName = optName.substr( 2 ); } else if( Detail::startsWith( optName, "-" ) ) arg.shortNames.push_back( optName.substr( 1 ) ); else throw std::logic_error( "option must begin with - or --. Option was: '" + optName + "'" ); } friend void setPositionalArg( Arg& arg, int position ) { arg.position = position; } class ArgBuilder { public: ArgBuilder( Arg* arg ) : m_arg( arg ) {} // Bind a non-boolean data member (requires placeholder string) template void bind( M C::* field, std::string const& placeholder ) { m_arg->boundField = new Detail::BoundDataMember( field ); m_arg->placeholder = placeholder; } // Bind a boolean data member (no placeholder required) template void bind( bool C::* field ) { m_arg->boundField = new Detail::BoundDataMember( field ); } // Bind a method taking a single, non-boolean argument (requires a placeholder string) template void bind( void (C::* unaryMethod)( M ), std::string const& placeholder ) { m_arg->boundField = new Detail::BoundUnaryMethod( unaryMethod ); m_arg->placeholder = placeholder; } // Bind a method taking a single, boolean argument (no placeholder string required) template void bind( void (C::* unaryMethod)( bool ) ) { m_arg->boundField = new Detail::BoundUnaryMethod( unaryMethod ); } // Bind a method that takes no arguments (will be called if opt is present) template void bind( void (C::* nullaryMethod)() ) { m_arg->boundField = new Detail::BoundNullaryMethod( nullaryMethod ); } // Bind a free function taking a single argument - the object to operate on (no placeholder string required) template void bind( void (* unaryFunction)( C& ) ) { m_arg->boundField = new Detail::BoundUnaryFunction( unaryFunction ); } // Bind a free function taking a single argument - the object to operate on (requires a placeholder string) template void bind( void (* binaryFunction)( C&, T ), std::string const& placeholder ) { m_arg->boundField = new Detail::BoundBinaryFunction( binaryFunction ); m_arg->placeholder = placeholder; } ArgBuilder& describe( std::string const& description ) { m_arg->description = description; return *this; } ArgBuilder& detail( std::string const& detail ) { m_arg->detail = detail; return *this; } protected: Arg* m_arg; }; class OptBuilder : public ArgBuilder { public: OptBuilder( Arg* arg ) : ArgBuilder( arg ) {} OptBuilder( OptBuilder& other ) : ArgBuilder( other ) {} OptBuilder& operator[]( std::string const& optName ) { addOptName( *ArgBuilder::m_arg, optName ); return *this; } }; public: CommandLine() : m_boundProcessName( new Detail::NullBinder() ), m_highestSpecifiedArgPosition( 0 ), m_throwOnUnrecognisedTokens( false ) {} CommandLine( CommandLine const& other ) : m_boundProcessName( other.m_boundProcessName ), m_options ( other.m_options ), m_positionalArgs( other.m_positionalArgs ), m_highestSpecifiedArgPosition( other.m_highestSpecifiedArgPosition ), m_throwOnUnrecognisedTokens( other.m_throwOnUnrecognisedTokens ) { if( other.m_floatingArg.get() ) m_floatingArg.reset( new Arg( *other.m_floatingArg ) ); } CommandLine& setThrowOnUnrecognisedTokens( bool shouldThrow = true ) { m_throwOnUnrecognisedTokens = shouldThrow; return *this; } OptBuilder operator[]( std::string const& optName ) { m_options.push_back( Arg() ); addOptName( m_options.back(), optName ); OptBuilder builder( &m_options.back() ); return builder; } ArgBuilder operator[]( int position ) { m_positionalArgs.insert( std::make_pair( position, Arg() ) ); if( position > m_highestSpecifiedArgPosition ) m_highestSpecifiedArgPosition = position; setPositionalArg( m_positionalArgs[position], position ); ArgBuilder builder( &m_positionalArgs[position] ); return builder; } // Invoke this with the _ instance ArgBuilder operator[]( UnpositionalTag ) { if( m_floatingArg.get() ) throw std::logic_error( "Only one unpositional argument can be added" ); m_floatingArg.reset( new Arg() ); ArgBuilder builder( m_floatingArg.get() ); return builder; } template void bindProcessName( M C::* field ) { m_boundProcessName = new Detail::BoundDataMember( field ); } template void bindProcessName( void (C::*_unaryMethod)( M ) ) { m_boundProcessName = new Detail::BoundUnaryMethod( _unaryMethod ); } void optUsage( std::ostream& os, std::size_t indent = 0, std::size_t width = Detail::consoleWidth ) const { typename std::vector::const_iterator itBegin = m_options.begin(), itEnd = m_options.end(), it; std::size_t maxWidth = 0; for( it = itBegin; it != itEnd; ++it ) maxWidth = (std::max)( maxWidth, it->commands().size() ); for( it = itBegin; it != itEnd; ++it ) { Detail::Text usage( it->commands(), Detail::TextAttributes() .setWidth( maxWidth+indent ) .setIndent( indent ) ); Detail::Text desc( it->description, Detail::TextAttributes() .setWidth( width - maxWidth - 3 ) ); for( std::size_t i = 0; i < (std::max)( usage.size(), desc.size() ); ++i ) { std::string usageCol = i < usage.size() ? usage[i] : ""; os << usageCol; if( i < desc.size() && !desc[i].empty() ) os << std::string( indent + 2 + maxWidth - usageCol.size(), ' ' ) << desc[i]; os << "\n"; } } } std::string optUsage() const { std::ostringstream oss; optUsage( oss ); return oss.str(); } void argSynopsis( std::ostream& os ) const { for( int i = 1; i <= m_highestSpecifiedArgPosition; ++i ) { if( i > 1 ) os << " "; typename std::map::const_iterator it = m_positionalArgs.find( i ); if( it != m_positionalArgs.end() ) os << "<" << it->second.placeholder << ">"; else if( m_floatingArg.get() ) os << "<" << m_floatingArg->placeholder << ">"; else throw std::logic_error( "non consecutive positional arguments with no floating args" ); } // !TBD No indication of mandatory args if( m_floatingArg.get() ) { if( m_highestSpecifiedArgPosition > 1 ) os << " "; os << "[<" << m_floatingArg->placeholder << "> ...]"; } } std::string argSynopsis() const { std::ostringstream oss; argSynopsis( oss ); return oss.str(); } void usage( std::ostream& os, std::string const& procName ) const { validate(); os << "usage:\n " << procName << " "; argSynopsis( os ); if( !m_options.empty() ) { os << " [options]\n\nwhere options are: \n"; optUsage( os, 2 ); } os << "\n"; } std::string usage( std::string const& procName ) const { std::ostringstream oss; usage( oss, procName ); return oss.str(); } ConfigT parse( std::vector const& args ) const { ConfigT config; parseInto( args, config ); return config; } std::vector parseInto( std::vector const& args, ConfigT& config ) const { std::string processName = args.empty() ? std::string() : args[0]; std::size_t lastSlash = processName.find_last_of( "/\\" ); if( lastSlash != std::string::npos ) processName = processName.substr( lastSlash+1 ); m_boundProcessName.set( config, processName ); std::vector tokens; Parser parser; parser.parseIntoTokens( args, tokens ); return populate( tokens, config ); } std::vector populate( std::vector const& tokens, ConfigT& config ) const { validate(); std::vector unusedTokens = populateOptions( tokens, config ); unusedTokens = populateFixedArgs( unusedTokens, config ); unusedTokens = populateFloatingArgs( unusedTokens, config ); return unusedTokens; } std::vector populateOptions( std::vector const& tokens, ConfigT& config ) const { std::vector unusedTokens; std::vector errors; for( std::size_t i = 0; i < tokens.size(); ++i ) { Parser::Token const& token = tokens[i]; typename std::vector::const_iterator it = m_options.begin(), itEnd = m_options.end(); for(; it != itEnd; ++it ) { Arg const& arg = *it; try { if( ( token.type == Parser::Token::ShortOpt && arg.hasShortName( token.data ) ) || ( token.type == Parser::Token::LongOpt && arg.hasLongName( token.data ) ) ) { if( arg.takesArg() ) { if( i == tokens.size()-1 || tokens[i+1].type != Parser::Token::Positional ) errors.push_back( "Expected argument to option: " + token.data ); else arg.boundField.set( config, tokens[++i].data ); } else { arg.boundField.set( config, "true" ); } break; } } catch( std::exception& ex ) { errors.push_back( std::string( ex.what() ) + "\n- while parsing: (" + arg.commands() + ")" ); } } if( it == itEnd ) { if( token.type == Parser::Token::Positional || !m_throwOnUnrecognisedTokens ) unusedTokens.push_back( token ); else if( errors.empty() && m_throwOnUnrecognisedTokens ) errors.push_back( "unrecognised option: " + token.data ); } } if( !errors.empty() ) { std::ostringstream oss; for( std::vector::const_iterator it = errors.begin(), itEnd = errors.end(); it != itEnd; ++it ) { if( it != errors.begin() ) oss << "\n"; oss << *it; } throw std::runtime_error( oss.str() ); } return unusedTokens; } std::vector populateFixedArgs( std::vector const& tokens, ConfigT& config ) const { std::vector unusedTokens; int position = 1; for( std::size_t i = 0; i < tokens.size(); ++i ) { Parser::Token const& token = tokens[i]; typename std::map::const_iterator it = m_positionalArgs.find( position ); if( it != m_positionalArgs.end() ) it->second.boundField.set( config, token.data ); else unusedTokens.push_back( token ); if( token.type == Parser::Token::Positional ) position++; } return unusedTokens; } std::vector populateFloatingArgs( std::vector const& tokens, ConfigT& config ) const { if( !m_floatingArg.get() ) return tokens; std::vector unusedTokens; for( std::size_t i = 0; i < tokens.size(); ++i ) { Parser::Token const& token = tokens[i]; if( token.type == Parser::Token::Positional ) m_floatingArg->boundField.set( config, token.data ); else unusedTokens.push_back( token ); } return unusedTokens; } void validate() const { if( m_options.empty() && m_positionalArgs.empty() && !m_floatingArg.get() ) throw std::logic_error( "No options or arguments specified" ); for( typename std::vector::const_iterator it = m_options.begin(), itEnd = m_options.end(); it != itEnd; ++it ) it->validate(); } private: Detail::BoundArgFunction m_boundProcessName; std::vector m_options; std::map m_positionalArgs; ArgAutoPtr m_floatingArg; int m_highestSpecifiedArgPosition; bool m_throwOnUnrecognisedTokens; }; } // end namespace Clara STITCH_CLARA_CLOSE_NAMESPACE #undef STITCH_CLARA_OPEN_NAMESPACE #undef STITCH_CLARA_CLOSE_NAMESPACE #endif // TWOBLUECUBES_CLARA_H_INCLUDED #undef STITCH_CLARA_OPEN_NAMESPACE // Restore Clara's value for console width, if present #ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH #define CLARA_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH #undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH #endif #include #include namespace Catch { inline void abortAfterFirst( ConfigData& config ) { config.abortAfter = 1; } inline void abortAfterX( ConfigData& config, int x ) { if( x < 1 ) throw std::runtime_error( "Value after -x or --abortAfter must be greater than zero" ); config.abortAfter = x; } inline void addTestOrTags( ConfigData& config, std::string const& _testSpec ) { config.testsOrTags.push_back( _testSpec ); } inline void addSectionToRun( ConfigData& config, std::string const& sectionName ) { config.sectionsToRun.push_back( sectionName ); } inline void addReporterName( ConfigData& config, std::string const& _reporterName ) { config.reporterNames.push_back( _reporterName ); } inline void addWarning( ConfigData& config, std::string const& _warning ) { if( _warning == "NoAssertions" ) config.warnings = static_cast( config.warnings | WarnAbout::NoAssertions ); else throw std::runtime_error( "Unrecognised warning: '" + _warning + '\'' ); } inline void setOrder( ConfigData& config, std::string const& order ) { if( startsWith( "declared", order ) ) config.runOrder = RunTests::InDeclarationOrder; else if( startsWith( "lexical", order ) ) config.runOrder = RunTests::InLexicographicalOrder; else if( startsWith( "random", order ) ) config.runOrder = RunTests::InRandomOrder; else throw std::runtime_error( "Unrecognised ordering: '" + order + '\'' ); } inline void setRngSeed( ConfigData& config, std::string const& seed ) { if( seed == "time" ) { config.rngSeed = static_cast( std::time(0) ); } else { std::stringstream ss; ss << seed; ss >> config.rngSeed; if( ss.fail() ) throw std::runtime_error( "Argument to --rng-seed should be the word 'time' or a number" ); } } inline void setVerbosity( ConfigData& config, int level ) { // !TBD: accept strings? config.verbosity = static_cast( level ); } inline void setShowDurations( ConfigData& config, bool _showDurations ) { config.showDurations = _showDurations ? ShowDurations::Always : ShowDurations::Never; } inline void setUseColour( ConfigData& config, std::string const& value ) { std::string mode = toLower( value ); if( mode == "yes" ) config.useColour = UseColour::Yes; else if( mode == "no" ) config.useColour = UseColour::No; else if( mode == "auto" ) config.useColour = UseColour::Auto; else throw std::runtime_error( "colour mode must be one of: auto, yes or no" ); } inline void setWaitForKeypress( ConfigData& config, std::string const& keypress ) { std::string keypressLc = toLower( keypress ); if( keypressLc == "start" ) config.waitForKeypress = WaitForKeypress::BeforeStart; else if( keypressLc == "exit" ) config.waitForKeypress = WaitForKeypress::BeforeExit; else if( keypressLc == "both" ) config.waitForKeypress = WaitForKeypress::BeforeStartAndExit; else throw std::runtime_error( "keypress argument must be one of: start, exit or both. '" + keypress + "' not recognised" ); }; inline void forceColour( ConfigData& config ) { config.useColour = UseColour::Yes; } inline void loadTestNamesFromFile( ConfigData& config, std::string const& _filename ) { std::ifstream f( _filename.c_str() ); if( !f.is_open() ) throw std::domain_error( "Unable to load input file: " + _filename ); std::string line; while( std::getline( f, line ) ) { line = trim(line); if( !line.empty() && !startsWith( line, '#' ) ) { if( !startsWith( line, '"' ) ) line = '"' + line + '"'; addTestOrTags( config, line + ',' ); } } } inline Clara::CommandLine makeCommandLineParser() { using namespace Clara; CommandLine cli; cli.bindProcessName( &ConfigData::processName ); cli["-?"]["-h"]["--help"] .describe( "display usage information" ) .bind( &ConfigData::showHelp ); cli["-l"]["--list-tests"] .describe( "list all/matching test cases" ) .bind( &ConfigData::listTests ); cli["-t"]["--list-tags"] .describe( "list all/matching tags" ) .bind( &ConfigData::listTags ); cli["-s"]["--success"] .describe( "include successful tests in output" ) .bind( &ConfigData::showSuccessfulTests ); cli["-b"]["--break"] .describe( "break into debugger on failure" ) .bind( &ConfigData::shouldDebugBreak ); cli["-e"]["--nothrow"] .describe( "skip exception tests" ) .bind( &ConfigData::noThrow ); cli["-i"]["--invisibles"] .describe( "show invisibles (tabs, newlines)" ) .bind( &ConfigData::showInvisibles ); cli["-o"]["--out"] .describe( "output filename" ) .bind( &ConfigData::outputFilename, "filename" ); cli["-r"]["--reporter"] // .placeholder( "name[:filename]" ) .describe( "reporter to use (defaults to console)" ) .bind( &addReporterName, "name" ); cli["-n"]["--name"] .describe( "suite name" ) .bind( &ConfigData::name, "name" ); cli["-a"]["--abort"] .describe( "abort at first failure" ) .bind( &abortAfterFirst ); cli["-x"]["--abortx"] .describe( "abort after x failures" ) .bind( &abortAfterX, "no. failures" ); cli["-w"]["--warn"] .describe( "enable warnings" ) .bind( &addWarning, "warning name" ); // - needs updating if reinstated // cli.into( &setVerbosity ) // .describe( "level of verbosity (0=no output)" ) // .shortOpt( "v") // .longOpt( "verbosity" ) // .placeholder( "level" ); cli[_] .describe( "which test or tests to use" ) .bind( &addTestOrTags, "test name, pattern or tags" ); cli["-d"]["--durations"] .describe( "show test durations" ) .bind( &setShowDurations, "yes|no" ); cli["-f"]["--input-file"] .describe( "load test names to run from a file" ) .bind( &loadTestNamesFromFile, "filename" ); cli["-#"]["--filenames-as-tags"] .describe( "adds a tag for the filename" ) .bind( &ConfigData::filenamesAsTags ); cli["-c"]["--section"] .describe( "specify section to run" ) .bind( &addSectionToRun, "section name" ); // Less common commands which don't have a short form cli["--list-test-names-only"] .describe( "list all/matching test cases names only" ) .bind( &ConfigData::listTestNamesOnly ); cli["--list-extra-info"] .describe( "list all/matching test cases with more info" ) .bind( &ConfigData::listExtraInfo ); cli["--list-reporters"] .describe( "list all reporters" ) .bind( &ConfigData::listReporters ); cli["--order"] .describe( "test case order (defaults to decl)" ) .bind( &setOrder, "decl|lex|rand" ); cli["--rng-seed"] .describe( "set a specific seed for random numbers" ) .bind( &setRngSeed, "'time'|number" ); cli["--force-colour"] .describe( "force colourised output (deprecated)" ) .bind( &forceColour ); cli["--use-colour"] .describe( "should output be colourised" ) .bind( &setUseColour, "yes|no" ); cli["--use-colour"] .describe( "should output be colourised" ) .bind( &setUseColour, "yes|no" ); cli["--libidentify"] .describe( "report name and version according to libidentify standard" ) .bind( &ConfigData::libIdentify ); cli["--wait-for-keypress"] .describe( "waits for a keypress before exiting" ) .bind( &setWaitForKeypress, "start|exit|both" ); return cli; } } // end namespace Catch // #included from: internal/catch_list.hpp #define TWOBLUECUBES_CATCH_LIST_HPP_INCLUDED // #included from: catch_text.h #define TWOBLUECUBES_CATCH_TEXT_H_INCLUDED #define TBC_TEXT_FORMAT_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH #define CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE Catch // #included from: ../external/tbc_text_format.h // Only use header guard if we are not using an outer namespace #ifndef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE # ifdef TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED # ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED # define TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED # endif # else # define TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED # endif #endif #ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED #include #include #include // Use optional outer namespace #ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE namespace CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE { #endif namespace Tbc { #ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; #else const unsigned int consoleWidth = 80; #endif struct TextAttributes { TextAttributes() : initialIndent( std::string::npos ), indent( 0 ), width( consoleWidth-1 ) {} TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; } TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; } TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; } std::size_t initialIndent; // indent of first line, or npos std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos std::size_t width; // maximum width of text, including indent. Longer text will wrap }; class Text { public: Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() ) : attr( _attr ) { const std::string wrappableBeforeChars = "[({<\t"; const std::string wrappableAfterChars = "])}>-,./|\\"; const std::string wrappableInsteadOfChars = " \n\r"; std::string indent = _attr.initialIndent != std::string::npos ? std::string( _attr.initialIndent, ' ' ) : std::string( _attr.indent, ' ' ); typedef std::string::const_iterator iterator; iterator it = _str.begin(); const iterator strEnd = _str.end(); while( it != strEnd ) { if( lines.size() >= 1000 ) { lines.push_back( "... message truncated due to excessive size" ); return; } std::string suffix; std::size_t width = (std::min)( static_cast( strEnd-it ), _attr.width-static_cast( indent.size() ) ); iterator itEnd = it+width; iterator itNext = _str.end(); iterator itNewLine = std::find( it, itEnd, '\n' ); if( itNewLine != itEnd ) itEnd = itNewLine; if( itEnd != strEnd ) { bool foundWrapPoint = false; iterator findIt = itEnd; do { if( wrappableAfterChars.find( *findIt ) != std::string::npos && findIt != itEnd ) { itEnd = findIt+1; itNext = findIt+1; foundWrapPoint = true; } else if( findIt > it && wrappableBeforeChars.find( *findIt ) != std::string::npos ) { itEnd = findIt; itNext = findIt; foundWrapPoint = true; } else if( wrappableInsteadOfChars.find( *findIt ) != std::string::npos ) { itNext = findIt+1; itEnd = findIt; foundWrapPoint = true; } if( findIt == it ) break; else --findIt; } while( !foundWrapPoint ); if( !foundWrapPoint ) { // No good wrap char, so we'll break mid word and add a hyphen --itEnd; itNext = itEnd; suffix = "-"; } else { while( itEnd > it && wrappableInsteadOfChars.find( *(itEnd-1) ) != std::string::npos ) --itEnd; } } lines.push_back( indent + std::string( it, itEnd ) + suffix ); if( indent.size() != _attr.indent ) indent = std::string( _attr.indent, ' ' ); it = itNext; } } typedef std::vector::const_iterator const_iterator; const_iterator begin() const { return lines.begin(); } const_iterator end() const { return lines.end(); } std::string const& last() const { return lines.back(); } std::size_t size() const { return lines.size(); } std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } std::string toString() const { std::ostringstream oss; oss << *this; return oss.str(); } inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); it != itEnd; ++it ) { if( it != _text.begin() ) _stream << "\n"; _stream << *it; } return _stream; } private: std::string str; TextAttributes attr; std::vector lines; }; } // end namespace Tbc #ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE } // end outer namespace #endif #endif // TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED #undef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE namespace Catch { using Tbc::Text; using Tbc::TextAttributes; } // #included from: catch_console_colour.hpp #define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_HPP_INCLUDED namespace Catch { struct Colour { enum Code { None = 0, White, Red, Green, Blue, Cyan, Yellow, Grey, Bright = 0x10, BrightRed = Bright | Red, BrightGreen = Bright | Green, LightGrey = Bright | Grey, BrightWhite = Bright | White, // By intention FileName = LightGrey, Warning = Yellow, ResultError = BrightRed, ResultSuccess = BrightGreen, ResultExpectedFailure = Warning, Error = BrightRed, Success = Green, OriginalExpression = Cyan, ReconstructedExpression = Yellow, SecondaryText = LightGrey, Headers = White }; // Use constructed object for RAII guard Colour( Code _colourCode ); Colour( Colour const& other ); ~Colour(); // Use static method for one-shot changes static void use( Code _colourCode ); private: bool m_moved; }; inline std::ostream& operator << ( std::ostream& os, Colour const& ) { return os; } } // end namespace Catch // #included from: catch_interfaces_reporter.h #define TWOBLUECUBES_CATCH_INTERFACES_REPORTER_H_INCLUDED #include #include #include namespace Catch { struct ReporterConfig { explicit ReporterConfig( Ptr const& _fullConfig ) : m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {} ReporterConfig( Ptr const& _fullConfig, std::ostream& _stream ) : m_stream( &_stream ), m_fullConfig( _fullConfig ) {} std::ostream& stream() const { return *m_stream; } Ptr fullConfig() const { return m_fullConfig; } private: std::ostream* m_stream; Ptr m_fullConfig; }; struct ReporterPreferences { ReporterPreferences() : shouldRedirectStdOut( false ) {} bool shouldRedirectStdOut; }; template struct LazyStat : Option { LazyStat() : used( false ) {} LazyStat& operator=( T const& _value ) { Option::operator=( _value ); used = false; return *this; } void reset() { Option::reset(); used = false; } bool used; }; struct TestRunInfo { TestRunInfo( std::string const& _name ) : name( _name ) {} std::string name; }; struct GroupInfo { GroupInfo( std::string const& _name, std::size_t _groupIndex, std::size_t _groupsCount ) : name( _name ), groupIndex( _groupIndex ), groupsCounts( _groupsCount ) {} std::string name; std::size_t groupIndex; std::size_t groupsCounts; }; struct AssertionStats { AssertionStats( AssertionResult const& _assertionResult, std::vector const& _infoMessages, Totals const& _totals ) : assertionResult( _assertionResult ), infoMessages( _infoMessages ), totals( _totals ) { if( assertionResult.hasMessage() ) { // Copy message into messages list. // !TBD This should have been done earlier, somewhere MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() ); builder << assertionResult.getMessage(); builder.m_info.message = builder.m_stream.str(); infoMessages.push_back( builder.m_info ); } } virtual ~AssertionStats(); # ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS AssertionStats( AssertionStats const& ) = default; AssertionStats( AssertionStats && ) = default; AssertionStats& operator = ( AssertionStats const& ) = default; AssertionStats& operator = ( AssertionStats && ) = default; # endif AssertionResult assertionResult; std::vector infoMessages; Totals totals; }; struct SectionStats { SectionStats( SectionInfo const& _sectionInfo, Counts const& _assertions, double _durationInSeconds, bool _missingAssertions ) : sectionInfo( _sectionInfo ), assertions( _assertions ), durationInSeconds( _durationInSeconds ), missingAssertions( _missingAssertions ) {} virtual ~SectionStats(); # ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS SectionStats( SectionStats const& ) = default; SectionStats( SectionStats && ) = default; SectionStats& operator = ( SectionStats const& ) = default; SectionStats& operator = ( SectionStats && ) = default; # endif SectionInfo sectionInfo; Counts assertions; double durationInSeconds; bool missingAssertions; }; struct TestCaseStats { TestCaseStats( TestCaseInfo const& _testInfo, Totals const& _totals, std::string const& _stdOut, std::string const& _stdErr, bool _aborting ) : testInfo( _testInfo ), totals( _totals ), stdOut( _stdOut ), stdErr( _stdErr ), aborting( _aborting ) {} virtual ~TestCaseStats(); # ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS TestCaseStats( TestCaseStats const& ) = default; TestCaseStats( TestCaseStats && ) = default; TestCaseStats& operator = ( TestCaseStats const& ) = default; TestCaseStats& operator = ( TestCaseStats && ) = default; # endif TestCaseInfo testInfo; Totals totals; std::string stdOut; std::string stdErr; bool aborting; }; struct TestGroupStats { TestGroupStats( GroupInfo const& _groupInfo, Totals const& _totals, bool _aborting ) : groupInfo( _groupInfo ), totals( _totals ), aborting( _aborting ) {} TestGroupStats( GroupInfo const& _groupInfo ) : groupInfo( _groupInfo ), aborting( false ) {} virtual ~TestGroupStats(); # ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS TestGroupStats( TestGroupStats const& ) = default; TestGroupStats( TestGroupStats && ) = default; TestGroupStats& operator = ( TestGroupStats const& ) = default; TestGroupStats& operator = ( TestGroupStats && ) = default; # endif GroupInfo groupInfo; Totals totals; bool aborting; }; struct TestRunStats { TestRunStats( TestRunInfo const& _runInfo, Totals const& _totals, bool _aborting ) : runInfo( _runInfo ), totals( _totals ), aborting( _aborting ) {} virtual ~TestRunStats(); # ifndef CATCH_CONFIG_CPP11_GENERATED_METHODS TestRunStats( TestRunStats const& _other ) : runInfo( _other.runInfo ), totals( _other.totals ), aborting( _other.aborting ) {} # else TestRunStats( TestRunStats const& ) = default; TestRunStats( TestRunStats && ) = default; TestRunStats& operator = ( TestRunStats const& ) = default; TestRunStats& operator = ( TestRunStats && ) = default; # endif TestRunInfo runInfo; Totals totals; bool aborting; }; class MultipleReporters; struct IStreamingReporter : IShared { virtual ~IStreamingReporter(); // Implementing class must also provide the following static method: // static std::string getDescription(); virtual ReporterPreferences getPreferences() const = 0; virtual void noMatchingTestCases( std::string const& spec ) = 0; virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0; virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0; virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0; virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0; virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0; // The return value indicates if the messages buffer should be cleared: virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0; virtual void sectionEnded( SectionStats const& sectionStats ) = 0; virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0; virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0; virtual void testRunEnded( TestRunStats const& testRunStats ) = 0; virtual void skipTest( TestCaseInfo const& testInfo ) = 0; virtual MultipleReporters* tryAsMulti() { return CATCH_NULL; } }; struct IReporterFactory : IShared { virtual ~IReporterFactory(); virtual IStreamingReporter* create( ReporterConfig const& config ) const = 0; virtual std::string getDescription() const = 0; }; struct IReporterRegistry { typedef std::map > FactoryMap; typedef std::vector > Listeners; virtual ~IReporterRegistry(); virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const = 0; virtual FactoryMap const& getFactories() const = 0; virtual Listeners const& getListeners() const = 0; }; Ptr addReporter( Ptr const& existingReporter, Ptr const& additionalReporter ); } #include #include namespace Catch { inline std::size_t listTests( Config const& config ) { TestSpec testSpec = config.testSpec(); if( config.testSpec().hasFilters() ) Catch::cout() << "Matching test cases:\n"; else { Catch::cout() << "All available test cases:\n"; testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); } std::size_t matchedTests = 0; TextAttributes nameAttr, descAttr, tagsAttr; nameAttr.setInitialIndent( 2 ).setIndent( 4 ); descAttr.setIndent( 4 ); tagsAttr.setIndent( 6 ); std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); it != itEnd; ++it ) { matchedTests++; TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); Colour::Code colour = testCaseInfo.isHidden() ? Colour::SecondaryText : Colour::None; Colour colourGuard( colour ); Catch::cout() << Text( testCaseInfo.name, nameAttr ) << std::endl; if( config.listExtraInfo() ) { Catch::cout() << " " << testCaseInfo.lineInfo << std::endl; std::string description = testCaseInfo.description; if( description.empty() ) description = "(NO DESCRIPTION)"; Catch::cout() << Text( description, descAttr ) << std::endl; } if( !testCaseInfo.tags.empty() ) Catch::cout() << Text( testCaseInfo.tagsAsString, tagsAttr ) << std::endl; } if( !config.testSpec().hasFilters() ) Catch::cout() << pluralise( matchedTests, "test case" ) << '\n' << std::endl; else Catch::cout() << pluralise( matchedTests, "matching test case" ) << '\n' << std::endl; return matchedTests; } inline std::size_t listTestsNamesOnly( Config const& config ) { TestSpec testSpec = config.testSpec(); if( !config.testSpec().hasFilters() ) testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); std::size_t matchedTests = 0; std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); it != itEnd; ++it ) { matchedTests++; TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); if( startsWith( testCaseInfo.name, '#' ) ) Catch::cout() << '"' << testCaseInfo.name << '"'; else Catch::cout() << testCaseInfo.name; if ( config.listExtraInfo() ) Catch::cout() << "\t@" << testCaseInfo.lineInfo; Catch::cout() << std::endl; } return matchedTests; } struct TagInfo { TagInfo() : count ( 0 ) {} void add( std::string const& spelling ) { ++count; spellings.insert( spelling ); } std::string all() const { std::string out; for( std::set::const_iterator it = spellings.begin(), itEnd = spellings.end(); it != itEnd; ++it ) out += "[" + *it + "]"; return out; } std::set spellings; std::size_t count; }; inline std::size_t listTags( Config const& config ) { TestSpec testSpec = config.testSpec(); if( config.testSpec().hasFilters() ) Catch::cout() << "Tags for matching test cases:\n"; else { Catch::cout() << "All available tags:\n"; testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); } std::map tagCounts; std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); it != itEnd; ++it ) { for( std::set::const_iterator tagIt = it->getTestCaseInfo().tags.begin(), tagItEnd = it->getTestCaseInfo().tags.end(); tagIt != tagItEnd; ++tagIt ) { std::string tagName = *tagIt; std::string lcaseTagName = toLower( tagName ); std::map::iterator countIt = tagCounts.find( lcaseTagName ); if( countIt == tagCounts.end() ) countIt = tagCounts.insert( std::make_pair( lcaseTagName, TagInfo() ) ).first; countIt->second.add( tagName ); } } for( std::map::const_iterator countIt = tagCounts.begin(), countItEnd = tagCounts.end(); countIt != countItEnd; ++countIt ) { std::ostringstream oss; oss << " " << std::setw(2) << countIt->second.count << " "; Text wrapper( countIt->second.all(), TextAttributes() .setInitialIndent( 0 ) .setIndent( oss.str().size() ) .setWidth( CATCH_CONFIG_CONSOLE_WIDTH-10 ) ); Catch::cout() << oss.str() << wrapper << '\n'; } Catch::cout() << pluralise( tagCounts.size(), "tag" ) << '\n' << std::endl; return tagCounts.size(); } inline std::size_t listReporters( Config const& /*config*/ ) { Catch::cout() << "Available reporters:\n"; IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); IReporterRegistry::FactoryMap::const_iterator itBegin = factories.begin(), itEnd = factories.end(), it; std::size_t maxNameLen = 0; for(it = itBegin; it != itEnd; ++it ) maxNameLen = (std::max)( maxNameLen, it->first.size() ); for(it = itBegin; it != itEnd; ++it ) { Text wrapper( it->second->getDescription(), TextAttributes() .setInitialIndent( 0 ) .setIndent( 7+maxNameLen ) .setWidth( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 ) ); Catch::cout() << " " << it->first << ':' << std::string( maxNameLen - it->first.size() + 2, ' ' ) << wrapper << '\n'; } Catch::cout() << std::endl; return factories.size(); } inline Option list( Config const& config ) { Option listedCount; if( config.listTests() || ( config.listExtraInfo() && !config.listTestNamesOnly() ) ) listedCount = listedCount.valueOr(0) + listTests( config ); if( config.listTestNamesOnly() ) listedCount = listedCount.valueOr(0) + listTestsNamesOnly( config ); if( config.listTags() ) listedCount = listedCount.valueOr(0) + listTags( config ); if( config.listReporters() ) listedCount = listedCount.valueOr(0) + listReporters( config ); return listedCount; } } // end namespace Catch // #included from: internal/catch_run_context.hpp #define TWOBLUECUBES_CATCH_RUNNER_IMPL_HPP_INCLUDED // #included from: catch_test_case_tracker.hpp #define TWOBLUECUBES_CATCH_TEST_CASE_TRACKER_HPP_INCLUDED #include #include #include #include #include CATCH_INTERNAL_SUPPRESS_ETD_WARNINGS namespace Catch { namespace TestCaseTracking { struct NameAndLocation { std::string name; SourceLineInfo location; NameAndLocation( std::string const& _name, SourceLineInfo const& _location ) : name( _name ), location( _location ) {} }; struct ITracker : SharedImpl<> { virtual ~ITracker(); // static queries virtual NameAndLocation const& nameAndLocation() const = 0; // dynamic queries virtual bool isComplete() const = 0; // Successfully completed or failed virtual bool isSuccessfullyCompleted() const = 0; virtual bool isOpen() const = 0; // Started but not complete virtual bool hasChildren() const = 0; virtual ITracker& parent() = 0; // actions virtual void close() = 0; // Successfully complete virtual void fail() = 0; virtual void markAsNeedingAnotherRun() = 0; virtual void addChild( Ptr const& child ) = 0; virtual ITracker* findChild( NameAndLocation const& nameAndLocation ) = 0; virtual void openChild() = 0; // Debug/ checking virtual bool isSectionTracker() const = 0; virtual bool isIndexTracker() const = 0; }; class TrackerContext { enum RunState { NotStarted, Executing, CompletedCycle }; Ptr m_rootTracker; ITracker* m_currentTracker; RunState m_runState; public: static TrackerContext& instance() { static TrackerContext s_instance; return s_instance; } TrackerContext() : m_currentTracker( CATCH_NULL ), m_runState( NotStarted ) {} ITracker& startRun(); void endRun() { m_rootTracker.reset(); m_currentTracker = CATCH_NULL; m_runState = NotStarted; } void startCycle() { m_currentTracker = m_rootTracker.get(); m_runState = Executing; } void completeCycle() { m_runState = CompletedCycle; } bool completedCycle() const { return m_runState == CompletedCycle; } ITracker& currentTracker() { return *m_currentTracker; } void setCurrentTracker( ITracker* tracker ) { m_currentTracker = tracker; } }; class TrackerBase : public ITracker { protected: enum CycleState { NotStarted, Executing, ExecutingChildren, NeedsAnotherRun, CompletedSuccessfully, Failed }; class TrackerHasName { NameAndLocation m_nameAndLocation; public: TrackerHasName( NameAndLocation const& nameAndLocation ) : m_nameAndLocation( nameAndLocation ) {} bool operator ()( Ptr const& tracker ) { return tracker->nameAndLocation().name == m_nameAndLocation.name && tracker->nameAndLocation().location == m_nameAndLocation.location; } }; typedef std::vector > Children; NameAndLocation m_nameAndLocation; TrackerContext& m_ctx; ITracker* m_parent; Children m_children; CycleState m_runState; public: TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) : m_nameAndLocation( nameAndLocation ), m_ctx( ctx ), m_parent( parent ), m_runState( NotStarted ) {} virtual ~TrackerBase(); virtual NameAndLocation const& nameAndLocation() const CATCH_OVERRIDE { return m_nameAndLocation; } virtual bool isComplete() const CATCH_OVERRIDE { return m_runState == CompletedSuccessfully || m_runState == Failed; } virtual bool isSuccessfullyCompleted() const CATCH_OVERRIDE { return m_runState == CompletedSuccessfully; } virtual bool isOpen() const CATCH_OVERRIDE { return m_runState != NotStarted && !isComplete(); } virtual bool hasChildren() const CATCH_OVERRIDE { return !m_children.empty(); } virtual void addChild( Ptr const& child ) CATCH_OVERRIDE { m_children.push_back( child ); } virtual ITracker* findChild( NameAndLocation const& nameAndLocation ) CATCH_OVERRIDE { Children::const_iterator it = std::find_if( m_children.begin(), m_children.end(), TrackerHasName( nameAndLocation ) ); return( it != m_children.end() ) ? it->get() : CATCH_NULL; } virtual ITracker& parent() CATCH_OVERRIDE { assert( m_parent ); // Should always be non-null except for root return *m_parent; } virtual void openChild() CATCH_OVERRIDE { if( m_runState != ExecutingChildren ) { m_runState = ExecutingChildren; if( m_parent ) m_parent->openChild(); } } virtual bool isSectionTracker() const CATCH_OVERRIDE { return false; } virtual bool isIndexTracker() const CATCH_OVERRIDE { return false; } void open() { m_runState = Executing; moveToThis(); if( m_parent ) m_parent->openChild(); } virtual void close() CATCH_OVERRIDE { // Close any still open children (e.g. generators) while( &m_ctx.currentTracker() != this ) m_ctx.currentTracker().close(); switch( m_runState ) { case NotStarted: case CompletedSuccessfully: case Failed: throw std::logic_error( "Illogical state" ); case NeedsAnotherRun: break;; case Executing: m_runState = CompletedSuccessfully; break; case ExecutingChildren: if( m_children.empty() || m_children.back()->isComplete() ) m_runState = CompletedSuccessfully; break; default: throw std::logic_error( "Unexpected state" ); } moveToParent(); m_ctx.completeCycle(); } virtual void fail() CATCH_OVERRIDE { m_runState = Failed; if( m_parent ) m_parent->markAsNeedingAnotherRun(); moveToParent(); m_ctx.completeCycle(); } virtual void markAsNeedingAnotherRun() CATCH_OVERRIDE { m_runState = NeedsAnotherRun; } private: void moveToParent() { assert( m_parent ); m_ctx.setCurrentTracker( m_parent ); } void moveToThis() { m_ctx.setCurrentTracker( this ); } }; class SectionTracker : public TrackerBase { std::vector m_filters; public: SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) : TrackerBase( nameAndLocation, ctx, parent ) { if( parent ) { while( !parent->isSectionTracker() ) parent = &parent->parent(); SectionTracker& parentSection = static_cast( *parent ); addNextFilters( parentSection.m_filters ); } } virtual ~SectionTracker(); virtual bool isSectionTracker() const CATCH_OVERRIDE { return true; } static SectionTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ) { SectionTracker* section = CATCH_NULL; ITracker& currentTracker = ctx.currentTracker(); if( ITracker* childTracker = currentTracker.findChild( nameAndLocation ) ) { assert( childTracker ); assert( childTracker->isSectionTracker() ); section = static_cast( childTracker ); } else { section = new SectionTracker( nameAndLocation, ctx, ¤tTracker ); currentTracker.addChild( section ); } if( !ctx.completedCycle() ) section->tryOpen(); return *section; } void tryOpen() { if( !isComplete() && (m_filters.empty() || m_filters[0].empty() || m_filters[0] == m_nameAndLocation.name ) ) open(); } void addInitialFilters( std::vector const& filters ) { if( !filters.empty() ) { m_filters.push_back(""); // Root - should never be consulted m_filters.push_back(""); // Test Case - not a section filter m_filters.insert( m_filters.end(), filters.begin(), filters.end() ); } } void addNextFilters( std::vector const& filters ) { if( filters.size() > 1 ) m_filters.insert( m_filters.end(), ++filters.begin(), filters.end() ); } }; class IndexTracker : public TrackerBase { int m_size; int m_index; public: IndexTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent, int size ) : TrackerBase( nameAndLocation, ctx, parent ), m_size( size ), m_index( -1 ) {} virtual ~IndexTracker(); virtual bool isIndexTracker() const CATCH_OVERRIDE { return true; } static IndexTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation, int size ) { IndexTracker* tracker = CATCH_NULL; ITracker& currentTracker = ctx.currentTracker(); if( ITracker* childTracker = currentTracker.findChild( nameAndLocation ) ) { assert( childTracker ); assert( childTracker->isIndexTracker() ); tracker = static_cast( childTracker ); } else { tracker = new IndexTracker( nameAndLocation, ctx, ¤tTracker, size ); currentTracker.addChild( tracker ); } if( !ctx.completedCycle() && !tracker->isComplete() ) { if( tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun ) tracker->moveNext(); tracker->open(); } return *tracker; } int index() const { return m_index; } void moveNext() { m_index++; m_children.clear(); } virtual void close() CATCH_OVERRIDE { TrackerBase::close(); if( m_runState == CompletedSuccessfully && m_index < m_size-1 ) m_runState = Executing; } }; inline ITracker& TrackerContext::startRun() { m_rootTracker = new SectionTracker( NameAndLocation( "{root}", CATCH_INTERNAL_LINEINFO ), *this, CATCH_NULL ); m_currentTracker = CATCH_NULL; m_runState = Executing; return *m_rootTracker; } } // namespace TestCaseTracking using TestCaseTracking::ITracker; using TestCaseTracking::TrackerContext; using TestCaseTracking::SectionTracker; using TestCaseTracking::IndexTracker; } // namespace Catch CATCH_INTERNAL_UNSUPPRESS_ETD_WARNINGS // #included from: catch_fatal_condition.hpp #define TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED namespace Catch { // Report the error condition inline void reportFatal( std::string const& message ) { IContext& context = Catch::getCurrentContext(); IResultCapture* resultCapture = context.getResultCapture(); resultCapture->handleFatalErrorCondition( message ); } } // namespace Catch #if defined ( CATCH_PLATFORM_WINDOWS ) ///////////////////////////////////////// // #included from: catch_windows_h_proxy.h #define TWOBLUECUBES_CATCH_WINDOWS_H_PROXY_H_INCLUDED #ifdef CATCH_DEFINES_NOMINMAX # define NOMINMAX #endif #ifdef CATCH_DEFINES_WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN #endif #ifdef __AFXDLL #include #else #include #endif #ifdef CATCH_DEFINES_NOMINMAX # undef NOMINMAX #endif #ifdef CATCH_DEFINES_WIN32_LEAN_AND_MEAN # undef WIN32_LEAN_AND_MEAN #endif # if !defined ( CATCH_CONFIG_WINDOWS_SEH ) namespace Catch { struct FatalConditionHandler { void reset() {} }; } # else // CATCH_CONFIG_WINDOWS_SEH is defined namespace Catch { struct SignalDefs { DWORD id; const char* name; }; extern SignalDefs signalDefs[]; // There is no 1-1 mapping between signals and windows exceptions. // Windows can easily distinguish between SO and SigSegV, // but SigInt, SigTerm, etc are handled differently. SignalDefs signalDefs[] = { { EXCEPTION_ILLEGAL_INSTRUCTION, "SIGILL - Illegal instruction signal" }, { EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow" }, { EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal" }, { EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error" }, }; struct FatalConditionHandler { static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) { for (int i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { if (ExceptionInfo->ExceptionRecord->ExceptionCode == signalDefs[i].id) { reportFatal(signalDefs[i].name); } } // If its not an exception we care about, pass it along. // This stops us from eating debugger breaks etc. return EXCEPTION_CONTINUE_SEARCH; } FatalConditionHandler() { isSet = true; // 32k seems enough for Catch to handle stack overflow, // but the value was found experimentally, so there is no strong guarantee guaranteeSize = 32 * 1024; exceptionHandlerHandle = CATCH_NULL; // Register as first handler in current chain exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException); // Pass in guarantee size to be filled SetThreadStackGuarantee(&guaranteeSize); } static void reset() { if (isSet) { // Unregister handler and restore the old guarantee RemoveVectoredExceptionHandler(exceptionHandlerHandle); SetThreadStackGuarantee(&guaranteeSize); exceptionHandlerHandle = CATCH_NULL; isSet = false; } } ~FatalConditionHandler() { reset(); } private: static bool isSet; static ULONG guaranteeSize; static PVOID exceptionHandlerHandle; }; bool FatalConditionHandler::isSet = false; ULONG FatalConditionHandler::guaranteeSize = 0; PVOID FatalConditionHandler::exceptionHandlerHandle = CATCH_NULL; } // namespace Catch # endif // CATCH_CONFIG_WINDOWS_SEH #else // Not Windows - assumed to be POSIX compatible ////////////////////////// # if !defined(CATCH_CONFIG_POSIX_SIGNALS) namespace Catch { struct FatalConditionHandler { void reset() {} }; } # else // CATCH_CONFIG_POSIX_SIGNALS is defined #include namespace Catch { struct SignalDefs { int id; const char* name; }; extern SignalDefs signalDefs[]; SignalDefs signalDefs[] = { { SIGINT, "SIGINT - Terminal interrupt signal" }, { SIGILL, "SIGILL - Illegal instruction signal" }, { SIGFPE, "SIGFPE - Floating point error signal" }, { SIGSEGV, "SIGSEGV - Segmentation violation signal" }, { SIGTERM, "SIGTERM - Termination request signal" }, { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } }; struct FatalConditionHandler { static bool isSet; static struct sigaction oldSigActions [sizeof(signalDefs)/sizeof(SignalDefs)]; static stack_t oldSigStack; static char altStackMem[SIGSTKSZ]; static void handleSignal( int sig ) { std::string name = ""; for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { SignalDefs &def = signalDefs[i]; if (sig == def.id) { name = def.name; break; } } reset(); reportFatal(name); raise( sig ); } FatalConditionHandler() { isSet = true; stack_t sigStack; sigStack.ss_sp = altStackMem; sigStack.ss_size = SIGSTKSZ; sigStack.ss_flags = 0; sigaltstack(&sigStack, &oldSigStack); struct sigaction sa = { 0 }; sa.sa_handler = handleSignal; sa.sa_flags = SA_ONSTACK; for (std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i) { sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); } } ~FatalConditionHandler() { reset(); } static void reset() { if( isSet ) { // Set signals back to previous values -- hopefully nobody overwrote them in the meantime for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) { sigaction(signalDefs[i].id, &oldSigActions[i], CATCH_NULL); } // Return the old stack sigaltstack(&oldSigStack, CATCH_NULL); isSet = false; } } }; bool FatalConditionHandler::isSet = false; struct sigaction FatalConditionHandler::oldSigActions[sizeof(signalDefs)/sizeof(SignalDefs)] = {}; stack_t FatalConditionHandler::oldSigStack = {}; char FatalConditionHandler::altStackMem[SIGSTKSZ] = {}; } // namespace Catch # endif // CATCH_CONFIG_POSIX_SIGNALS #endif // not Windows #include #include namespace Catch { class StreamRedirect { public: StreamRedirect( std::ostream& stream, std::string& targetString ) : m_stream( stream ), m_prevBuf( stream.rdbuf() ), m_targetString( targetString ) { stream.rdbuf( m_oss.rdbuf() ); } ~StreamRedirect() { m_targetString += m_oss.str(); m_stream.rdbuf( m_prevBuf ); } private: std::ostream& m_stream; std::streambuf* m_prevBuf; std::ostringstream m_oss; std::string& m_targetString; }; // StdErr has two constituent streams in C++, std::cerr and std::clog // This means that we need to redirect 2 streams into 1 to keep proper // order of writes and cannot use StreamRedirect on its own class StdErrRedirect { public: StdErrRedirect(std::string& targetString) :m_cerrBuf( cerr().rdbuf() ), m_clogBuf(clog().rdbuf()), m_targetString(targetString){ cerr().rdbuf(m_oss.rdbuf()); clog().rdbuf(m_oss.rdbuf()); } ~StdErrRedirect() { m_targetString += m_oss.str(); cerr().rdbuf(m_cerrBuf); clog().rdbuf(m_clogBuf); } private: std::streambuf* m_cerrBuf; std::streambuf* m_clogBuf; std::ostringstream m_oss; std::string& m_targetString; }; /////////////////////////////////////////////////////////////////////////// class RunContext : public IResultCapture, public IRunner { RunContext( RunContext const& ); void operator =( RunContext const& ); public: explicit RunContext( Ptr const& _config, Ptr const& reporter ) : m_runInfo( _config->name() ), m_context( getCurrentMutableContext() ), m_activeTestCase( CATCH_NULL ), m_config( _config ), m_reporter( reporter ), m_shouldReportUnexpected ( true ) { m_context.setRunner( this ); m_context.setConfig( m_config ); m_context.setResultCapture( this ); m_reporter->testRunStarting( m_runInfo ); } virtual ~RunContext() { m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, aborting() ) ); } void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ) { m_reporter->testGroupStarting( GroupInfo( testSpec, groupIndex, groupsCount ) ); } void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount ) { m_reporter->testGroupEnded( TestGroupStats( GroupInfo( testSpec, groupIndex, groupsCount ), totals, aborting() ) ); } Totals runTest( TestCase const& testCase ) { Totals prevTotals = m_totals; std::string redirectedCout; std::string redirectedCerr; TestCaseInfo testInfo = testCase.getTestCaseInfo(); m_reporter->testCaseStarting( testInfo ); m_activeTestCase = &testCase; do { ITracker& rootTracker = m_trackerContext.startRun(); assert( rootTracker.isSectionTracker() ); static_cast( rootTracker ).addInitialFilters( m_config->getSectionsToRun() ); do { m_trackerContext.startCycle(); m_testCaseTracker = &SectionTracker::acquire( m_trackerContext, TestCaseTracking::NameAndLocation( testInfo.name, testInfo.lineInfo ) ); runCurrentTest( redirectedCout, redirectedCerr ); } while( !m_testCaseTracker->isSuccessfullyCompleted() && !aborting() ); } // !TBD: deprecated - this will be replaced by indexed trackers while( getCurrentContext().advanceGeneratorsForCurrentTest() && !aborting() ); Totals deltaTotals = m_totals.delta( prevTotals ); if( testInfo.expectedToFail() && deltaTotals.testCases.passed > 0 ) { deltaTotals.assertions.failed++; deltaTotals.testCases.passed--; deltaTotals.testCases.failed++; } m_totals.testCases += deltaTotals.testCases; m_reporter->testCaseEnded( TestCaseStats( testInfo, deltaTotals, redirectedCout, redirectedCerr, aborting() ) ); m_activeTestCase = CATCH_NULL; m_testCaseTracker = CATCH_NULL; return deltaTotals; } Ptr config() const { return m_config; } private: // IResultCapture virtual void assertionEnded( AssertionResult const& result ) { if( result.getResultType() == ResultWas::Ok ) { m_totals.assertions.passed++; } else if( !result.isOk() ) { if( m_activeTestCase->getTestCaseInfo().okToFail() ) m_totals.assertions.failedButOk++; else m_totals.assertions.failed++; } // We have no use for the return value (whether messages should be cleared), because messages were made scoped // and should be let to clear themselves out. static_cast(m_reporter->assertionEnded(AssertionStats(result, m_messages, m_totals))); // Reset working state m_lastAssertionInfo = AssertionInfo( "", m_lastAssertionInfo.lineInfo, "{Unknown expression after the reported line}" , m_lastAssertionInfo.resultDisposition ); m_lastResult = result; } virtual bool lastAssertionPassed() { return m_totals.assertions.passed == (m_prevPassed + 1); } virtual void assertionPassed() { m_totals.assertions.passed++; m_lastAssertionInfo.capturedExpression = "{Unknown expression after the reported line}"; m_lastAssertionInfo.macroName = ""; } virtual void assertionRun() { m_prevPassed = m_totals.assertions.passed; } virtual bool sectionStarted ( SectionInfo const& sectionInfo, Counts& assertions ) { ITracker& sectionTracker = SectionTracker::acquire( m_trackerContext, TestCaseTracking::NameAndLocation( sectionInfo.name, sectionInfo.lineInfo ) ); if( !sectionTracker.isOpen() ) return false; m_activeSections.push_back( §ionTracker ); m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; m_reporter->sectionStarting( sectionInfo ); assertions = m_totals.assertions; return true; } bool testForMissingAssertions( Counts& assertions ) { if( assertions.total() != 0 ) return false; if( !m_config->warnAboutMissingAssertions() ) return false; if( m_trackerContext.currentTracker().hasChildren() ) return false; m_totals.assertions.failed++; assertions.failed++; return true; } virtual void sectionEnded( SectionEndInfo const& endInfo ) { Counts assertions = m_totals.assertions - endInfo.prevAssertions; bool missingAssertions = testForMissingAssertions( assertions ); if( !m_activeSections.empty() ) { m_activeSections.back()->close(); m_activeSections.pop_back(); } m_reporter->sectionEnded( SectionStats( endInfo.sectionInfo, assertions, endInfo.durationInSeconds, missingAssertions ) ); m_messages.clear(); } virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) { if( m_unfinishedSections.empty() ) m_activeSections.back()->fail(); else m_activeSections.back()->close(); m_activeSections.pop_back(); m_unfinishedSections.push_back( endInfo ); } virtual void pushScopedMessage( MessageInfo const& message ) { m_messages.push_back( message ); } virtual void popScopedMessage( MessageInfo const& message ) { m_messages.erase( std::remove( m_messages.begin(), m_messages.end(), message ), m_messages.end() ); } virtual std::string getCurrentTestName() const { return m_activeTestCase ? m_activeTestCase->getTestCaseInfo().name : std::string(); } virtual const AssertionResult* getLastResult() const { return &m_lastResult; } virtual void exceptionEarlyReported() { m_shouldReportUnexpected = false; } virtual void handleFatalErrorCondition( std::string const& message ) { // Don't rebuild the result -- the stringification itself can cause more fatal errors // Instead, fake a result data. AssertionResultData tempResult; tempResult.resultType = ResultWas::FatalErrorCondition; tempResult.message = message; AssertionResult result(m_lastAssertionInfo, tempResult); getResultCapture().assertionEnded(result); handleUnfinishedSections(); // Recreate section for test case (as we will lose the one that was in scope) TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description ); Counts assertions; assertions.failed = 1; SectionStats testCaseSectionStats( testCaseSection, assertions, 0, false ); m_reporter->sectionEnded( testCaseSectionStats ); TestCaseInfo testInfo = m_activeTestCase->getTestCaseInfo(); Totals deltaTotals; deltaTotals.testCases.failed = 1; deltaTotals.assertions.failed = 1; m_reporter->testCaseEnded( TestCaseStats( testInfo, deltaTotals, std::string(), std::string(), false ) ); m_totals.testCases.failed++; testGroupEnded( std::string(), m_totals, 1, 1 ); m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, false ) ); } public: // !TBD We need to do this another way! bool aborting() const { return m_totals.assertions.failed == static_cast( m_config->abortAfter() ); } private: void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr ) { TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description ); m_reporter->sectionStarting( testCaseSection ); Counts prevAssertions = m_totals.assertions; double duration = 0; m_shouldReportUnexpected = true; try { m_lastAssertionInfo = AssertionInfo( "TEST_CASE", testCaseInfo.lineInfo, "", ResultDisposition::Normal ); seedRng( *m_config ); Timer timer; timer.start(); if( m_reporter->getPreferences().shouldRedirectStdOut ) { StreamRedirect coutRedir( Catch::cout(), redirectedCout ); StdErrRedirect errRedir( redirectedCerr ); invokeActiveTestCase(); } else { invokeActiveTestCase(); } duration = timer.getElapsedSeconds(); } catch( TestFailureException& ) { // This just means the test was aborted due to failure } catch(...) { // Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions // are reported without translation at the point of origin. if (m_shouldReportUnexpected) { makeUnexpectedResultBuilder().useActiveException(); } } m_testCaseTracker->close(); handleUnfinishedSections(); m_messages.clear(); Counts assertions = m_totals.assertions - prevAssertions; bool missingAssertions = testForMissingAssertions( assertions ); SectionStats testCaseSectionStats( testCaseSection, assertions, duration, missingAssertions ); m_reporter->sectionEnded( testCaseSectionStats ); } void invokeActiveTestCase() { FatalConditionHandler fatalConditionHandler; // Handle signals m_activeTestCase->invoke(); fatalConditionHandler.reset(); } private: ResultBuilder makeUnexpectedResultBuilder() const { return ResultBuilder( m_lastAssertionInfo.macroName, m_lastAssertionInfo.lineInfo, m_lastAssertionInfo.capturedExpression, m_lastAssertionInfo.resultDisposition ); } void handleUnfinishedSections() { // If sections ended prematurely due to an exception we stored their // infos here so we can tear them down outside the unwind process. for( std::vector::const_reverse_iterator it = m_unfinishedSections.rbegin(), itEnd = m_unfinishedSections.rend(); it != itEnd; ++it ) sectionEnded( *it ); m_unfinishedSections.clear(); } TestRunInfo m_runInfo; IMutableContext& m_context; TestCase const* m_activeTestCase; ITracker* m_testCaseTracker; ITracker* m_currentSectionTracker; AssertionResult m_lastResult; Ptr m_config; Totals m_totals; Ptr m_reporter; std::vector m_messages; AssertionInfo m_lastAssertionInfo; std::vector m_unfinishedSections; std::vector m_activeSections; TrackerContext m_trackerContext; size_t m_prevPassed; bool m_shouldReportUnexpected; }; IResultCapture& getResultCapture() { if( IResultCapture* capture = getCurrentContext().getResultCapture() ) return *capture; else throw std::logic_error( "No result capture instance" ); } } // end namespace Catch // #included from: internal/catch_version.h #define TWOBLUECUBES_CATCH_VERSION_H_INCLUDED namespace Catch { // Versioning information struct Version { Version( unsigned int _majorVersion, unsigned int _minorVersion, unsigned int _patchNumber, char const * const _branchName, unsigned int _buildNumber ); unsigned int const majorVersion; unsigned int const minorVersion; unsigned int const patchNumber; // buildNumber is only used if branchName is not null char const * const branchName; unsigned int const buildNumber; friend std::ostream& operator << ( std::ostream& os, Version const& version ); private: void operator=( Version const& ); }; inline Version libraryVersion(); } #include #include #include namespace Catch { Ptr createReporter( std::string const& reporterName, Ptr const& config ) { Ptr reporter = getRegistryHub().getReporterRegistry().create( reporterName, config.get() ); if( !reporter ) { std::ostringstream oss; oss << "No reporter registered with name: '" << reporterName << "'"; throw std::domain_error( oss.str() ); } return reporter; } #if !defined(CATCH_CONFIG_DEFAULT_REPORTER) #define CATCH_CONFIG_DEFAULT_REPORTER "console" #endif Ptr makeReporter( Ptr const& config ) { std::vector reporters = config->getReporterNames(); if( reporters.empty() ) reporters.push_back( CATCH_CONFIG_DEFAULT_REPORTER ); Ptr reporter; for( std::vector::const_iterator it = reporters.begin(), itEnd = reporters.end(); it != itEnd; ++it ) reporter = addReporter( reporter, createReporter( *it, config ) ); return reporter; } Ptr addListeners( Ptr const& config, Ptr reporters ) { IReporterRegistry::Listeners listeners = getRegistryHub().getReporterRegistry().getListeners(); for( IReporterRegistry::Listeners::const_iterator it = listeners.begin(), itEnd = listeners.end(); it != itEnd; ++it ) reporters = addReporter(reporters, (*it)->create( ReporterConfig( config ) ) ); return reporters; } Totals runTests( Ptr const& config ) { Ptr iconfig = config.get(); Ptr reporter = makeReporter( config ); reporter = addListeners( iconfig, reporter ); RunContext context( iconfig, reporter ); Totals totals; context.testGroupStarting( config->name(), 1, 1 ); TestSpec testSpec = config->testSpec(); if( !testSpec.hasFilters() ) testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "~[.]" ).testSpec(); // All not hidden tests std::vector const& allTestCases = getAllTestCasesSorted( *iconfig ); for( std::vector::const_iterator it = allTestCases.begin(), itEnd = allTestCases.end(); it != itEnd; ++it ) { if( !context.aborting() && matchTest( *it, testSpec, *iconfig ) ) totals += context.runTest( *it ); else reporter->skipTest( *it ); } context.testGroupEnded( iconfig->name(), totals, 1, 1 ); return totals; } void applyFilenamesAsTags( IConfig const& config ) { std::vector const& tests = getAllTestCasesSorted( config ); for(std::size_t i = 0; i < tests.size(); ++i ) { TestCase& test = const_cast( tests[i] ); std::set tags = test.tags; std::string filename = test.lineInfo.file; std::string::size_type lastSlash = filename.find_last_of( "\\/" ); if( lastSlash != std::string::npos ) filename = filename.substr( lastSlash+1 ); std::string::size_type lastDot = filename.find_last_of( '.' ); if( lastDot != std::string::npos ) filename = filename.substr( 0, lastDot ); tags.insert( '#' + filename ); setTags( test, tags ); } } class Session : NonCopyable { static bool alreadyInstantiated; public: struct OnUnusedOptions { enum DoWhat { Ignore, Fail }; }; Session() : m_cli( makeCommandLineParser() ) { if( alreadyInstantiated ) { std::string msg = "Only one instance of Catch::Session can ever be used"; Catch::cerr() << msg << std::endl; throw std::logic_error( msg ); } alreadyInstantiated = true; } ~Session() { Catch::cleanUp(); } void showHelp( std::string const& processName ) { Catch::cout() << "\nCatch v" << libraryVersion() << "\n"; m_cli.usage( Catch::cout(), processName ); Catch::cout() << "For more detail usage please see the project docs\n" << std::endl; } void libIdentify() { Catch::cout() << std::left << std::setw(16) << "description: " << "A Catch test executable\n" << std::left << std::setw(16) << "category: " << "testframework\n" << std::left << std::setw(16) << "framework: " << "Catch Test\n" << std::left << std::setw(16) << "version: " << libraryVersion() << std::endl; } int applyCommandLine( int argc, char const* const* const argv, OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) { try { m_cli.setThrowOnUnrecognisedTokens( unusedOptionBehaviour == OnUnusedOptions::Fail ); m_unusedTokens = m_cli.parseInto( Clara::argsToVector( argc, argv ), m_configData ); if( m_configData.showHelp ) showHelp( m_configData.processName ); if( m_configData.libIdentify ) libIdentify(); m_config.reset(); } catch( std::exception& ex ) { { Colour colourGuard( Colour::Red ); Catch::cerr() << "\nError(s) in input:\n" << Text( ex.what(), TextAttributes().setIndent(2) ) << "\n\n"; } m_cli.usage( Catch::cout(), m_configData.processName ); return (std::numeric_limits::max)(); } return 0; } void useConfigData( ConfigData const& _configData ) { m_configData = _configData; m_config.reset(); } int run( int argc, char const* const* const argv ) { int returnCode = applyCommandLine( argc, argv ); if( returnCode == 0 ) returnCode = run(); return returnCode; } #if defined(WIN32) && defined(UNICODE) int run( int argc, wchar_t const* const* const argv ) { char **utf8Argv = new char *[ argc ]; for ( int i = 0; i < argc; ++i ) { int bufSize = WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, NULL, 0, NULL, NULL ); utf8Argv[ i ] = new char[ bufSize ]; WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, NULL, NULL ); } int returnCode = applyCommandLine( argc, utf8Argv ); if( returnCode == 0 ) returnCode = run(); for ( int i = 0; i < argc; ++i ) delete [] utf8Argv[ i ]; delete [] utf8Argv; return returnCode; } #endif int run() { if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeStart ) != 0 ) { Catch::cout() << "...waiting for enter/ return before starting" << std::endl; static_cast(std::getchar()); } int exitCode = runInternal(); if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) { Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << std::endl; static_cast(std::getchar()); } return exitCode; } Clara::CommandLine const& cli() const { return m_cli; } std::vector const& unusedTokens() const { return m_unusedTokens; } ConfigData& configData() { return m_configData; } Config& config() { if( !m_config ) m_config = new Config( m_configData ); return *m_config; } private: int runInternal() { if( m_configData.showHelp || m_configData.libIdentify ) return 0; try { config(); // Force config to be constructed seedRng( *m_config ); if( m_configData.filenamesAsTags ) applyFilenamesAsTags( *m_config ); // Handle list request if( Option listed = list( config() ) ) return static_cast( *listed ); return static_cast( runTests( m_config ).assertions.failed ); } catch( std::exception& ex ) { Catch::cerr() << ex.what() << std::endl; return (std::numeric_limits::max)(); } } Clara::CommandLine m_cli; std::vector m_unusedTokens; ConfigData m_configData; Ptr m_config; }; bool Session::alreadyInstantiated = false; } // end namespace Catch // #included from: catch_registry_hub.hpp #define TWOBLUECUBES_CATCH_REGISTRY_HUB_HPP_INCLUDED // #included from: catch_test_case_registry_impl.hpp #define TWOBLUECUBES_CATCH_TEST_CASE_REGISTRY_IMPL_HPP_INCLUDED #include #include #include #include namespace Catch { struct RandomNumberGenerator { typedef std::ptrdiff_t result_type; result_type operator()( result_type n ) const { return std::rand() % n; } #ifdef CATCH_CONFIG_CPP11_SHUFFLE static constexpr result_type min() { return 0; } static constexpr result_type max() { return 1000000; } result_type operator()() const { return std::rand() % max(); } #endif template static void shuffle( V& vector ) { RandomNumberGenerator rng; #ifdef CATCH_CONFIG_CPP11_SHUFFLE std::shuffle( vector.begin(), vector.end(), rng ); #else std::random_shuffle( vector.begin(), vector.end(), rng ); #endif } }; inline std::vector sortTests( IConfig const& config, std::vector const& unsortedTestCases ) { std::vector sorted = unsortedTestCases; switch( config.runOrder() ) { case RunTests::InLexicographicalOrder: std::sort( sorted.begin(), sorted.end() ); break; case RunTests::InRandomOrder: { seedRng( config ); RandomNumberGenerator::shuffle( sorted ); } break; case RunTests::InDeclarationOrder: // already in declaration order break; } return sorted; } bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ) { return testSpec.matches( testCase ) && ( config.allowThrows() || !testCase.throws() ); } void enforceNoDuplicateTestCases( std::vector const& functions ) { std::set seenFunctions; for( std::vector::const_iterator it = functions.begin(), itEnd = functions.end(); it != itEnd; ++it ) { std::pair::const_iterator, bool> prev = seenFunctions.insert( *it ); if( !prev.second ) { std::ostringstream ss; ss << Colour( Colour::Red ) << "error: TEST_CASE( \"" << it->name << "\" ) already defined.\n" << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << '\n' << "\tRedefined at " << it->getTestCaseInfo().lineInfo << std::endl; throw std::runtime_error(ss.str()); } } } std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ) { std::vector filtered; filtered.reserve( testCases.size() ); for( std::vector::const_iterator it = testCases.begin(), itEnd = testCases.end(); it != itEnd; ++it ) if( matchTest( *it, testSpec, config ) ) filtered.push_back( *it ); return filtered; } std::vector const& getAllTestCasesSorted( IConfig const& config ) { return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config ); } class TestRegistry : public ITestCaseRegistry { public: TestRegistry() : m_currentSortOrder( RunTests::InDeclarationOrder ), m_unnamedCount( 0 ) {} virtual ~TestRegistry(); virtual void registerTest( TestCase const& testCase ) { std::string name = testCase.getTestCaseInfo().name; if( name.empty() ) { std::ostringstream oss; oss << "Anonymous test case " << ++m_unnamedCount; return registerTest( testCase.withName( oss.str() ) ); } m_functions.push_back( testCase ); } virtual std::vector const& getAllTests() const { return m_functions; } virtual std::vector const& getAllTestsSorted( IConfig const& config ) const { if( m_sortedFunctions.empty() ) enforceNoDuplicateTestCases( m_functions ); if( m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty() ) { m_sortedFunctions = sortTests( config, m_functions ); m_currentSortOrder = config.runOrder(); } return m_sortedFunctions; } private: std::vector m_functions; mutable RunTests::InWhatOrder m_currentSortOrder; mutable std::vector m_sortedFunctions; size_t m_unnamedCount; std::ios_base::Init m_ostreamInit; // Forces cout/ cerr to be initialised }; /////////////////////////////////////////////////////////////////////////// class FreeFunctionTestCase : public SharedImpl { public: FreeFunctionTestCase( TestFunction fun ) : m_fun( fun ) {} virtual void invoke() const { m_fun(); } private: virtual ~FreeFunctionTestCase(); TestFunction m_fun; }; inline std::string extractClassName( std::string const& classOrQualifiedMethodName ) { std::string className = classOrQualifiedMethodName; if( startsWith( className, '&' ) ) { std::size_t lastColons = className.rfind( "::" ); std::size_t penultimateColons = className.rfind( "::", lastColons-1 ); if( penultimateColons == std::string::npos ) penultimateColons = 1; className = className.substr( penultimateColons, lastColons-penultimateColons ); } return className; } void registerTestCase ( ITestCase* testCase, char const* classOrQualifiedMethodName, NameAndDesc const& nameAndDesc, SourceLineInfo const& lineInfo ) { getMutableRegistryHub().registerTest ( makeTestCase ( testCase, extractClassName( classOrQualifiedMethodName ), nameAndDesc.name, nameAndDesc.description, lineInfo ) ); } void registerTestCaseFunction ( TestFunction function, SourceLineInfo const& lineInfo, NameAndDesc const& nameAndDesc ) { registerTestCase( new FreeFunctionTestCase( function ), "", nameAndDesc, lineInfo ); } /////////////////////////////////////////////////////////////////////////// AutoReg::AutoReg ( TestFunction function, SourceLineInfo const& lineInfo, NameAndDesc const& nameAndDesc ) { registerTestCaseFunction( function, lineInfo, nameAndDesc ); } AutoReg::~AutoReg() {} } // end namespace Catch // #included from: catch_reporter_registry.hpp #define TWOBLUECUBES_CATCH_REPORTER_REGISTRY_HPP_INCLUDED #include namespace Catch { class ReporterRegistry : public IReporterRegistry { public: virtual ~ReporterRegistry() CATCH_OVERRIDE {} virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const CATCH_OVERRIDE { FactoryMap::const_iterator it = m_factories.find( name ); if( it == m_factories.end() ) return CATCH_NULL; return it->second->create( ReporterConfig( config ) ); } void registerReporter( std::string const& name, Ptr const& factory ) { m_factories.insert( std::make_pair( name, factory ) ); } void registerListener( Ptr const& factory ) { m_listeners.push_back( factory ); } virtual FactoryMap const& getFactories() const CATCH_OVERRIDE { return m_factories; } virtual Listeners const& getListeners() const CATCH_OVERRIDE { return m_listeners; } private: FactoryMap m_factories; Listeners m_listeners; }; } // #included from: catch_exception_translator_registry.hpp #define TWOBLUECUBES_CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED #ifdef __OBJC__ #import "Foundation/Foundation.h" #endif namespace Catch { class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry { public: ~ExceptionTranslatorRegistry() { deleteAll( m_translators ); } virtual void registerTranslator( const IExceptionTranslator* translator ) { m_translators.push_back( translator ); } virtual std::string translateActiveException() const { try { #ifdef __OBJC__ // In Objective-C try objective-c exceptions first @try { return tryTranslators(); } @catch (NSException *exception) { return Catch::toString( [exception description] ); } #else return tryTranslators(); #endif } catch( TestFailureException& ) { throw; } catch( std::exception& ex ) { return ex.what(); } catch( std::string& msg ) { return msg; } catch( const char* msg ) { return msg; } catch(...) { return "Unknown exception"; } } std::string tryTranslators() const { if( m_translators.empty() ) throw; else return m_translators[0]->translate( m_translators.begin()+1, m_translators.end() ); } private: std::vector m_translators; }; } // #included from: catch_tag_alias_registry.h #define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_H_INCLUDED #include namespace Catch { class TagAliasRegistry : public ITagAliasRegistry { public: virtual ~TagAliasRegistry(); virtual Option find( std::string const& alias ) const; virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const; void add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ); private: std::map m_registry; }; } // end namespace Catch namespace Catch { namespace { class RegistryHub : public IRegistryHub, public IMutableRegistryHub { RegistryHub( RegistryHub const& ); void operator=( RegistryHub const& ); public: // IRegistryHub RegistryHub() { } virtual IReporterRegistry const& getReporterRegistry() const CATCH_OVERRIDE { return m_reporterRegistry; } virtual ITestCaseRegistry const& getTestCaseRegistry() const CATCH_OVERRIDE { return m_testCaseRegistry; } virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() CATCH_OVERRIDE { return m_exceptionTranslatorRegistry; } virtual ITagAliasRegistry const& getTagAliasRegistry() const CATCH_OVERRIDE { return m_tagAliasRegistry; } public: // IMutableRegistryHub virtual void registerReporter( std::string const& name, Ptr const& factory ) CATCH_OVERRIDE { m_reporterRegistry.registerReporter( name, factory ); } virtual void registerListener( Ptr const& factory ) CATCH_OVERRIDE { m_reporterRegistry.registerListener( factory ); } virtual void registerTest( TestCase const& testInfo ) CATCH_OVERRIDE { m_testCaseRegistry.registerTest( testInfo ); } virtual void registerTranslator( const IExceptionTranslator* translator ) CATCH_OVERRIDE { m_exceptionTranslatorRegistry.registerTranslator( translator ); } virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) CATCH_OVERRIDE { m_tagAliasRegistry.add( alias, tag, lineInfo ); } private: TestRegistry m_testCaseRegistry; ReporterRegistry m_reporterRegistry; ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; TagAliasRegistry m_tagAliasRegistry; }; // Single, global, instance inline RegistryHub*& getTheRegistryHub() { static RegistryHub* theRegistryHub = CATCH_NULL; if( !theRegistryHub ) theRegistryHub = new RegistryHub(); return theRegistryHub; } } IRegistryHub& getRegistryHub() { return *getTheRegistryHub(); } IMutableRegistryHub& getMutableRegistryHub() { return *getTheRegistryHub(); } void cleanUp() { delete getTheRegistryHub(); getTheRegistryHub() = CATCH_NULL; cleanUpContext(); } std::string translateActiveException() { return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); } } // end namespace Catch // #included from: catch_notimplemented_exception.hpp #define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_HPP_INCLUDED #include namespace Catch { NotImplementedException::NotImplementedException( SourceLineInfo const& lineInfo ) : m_lineInfo( lineInfo ) { std::ostringstream oss; oss << lineInfo << ": function "; oss << "not implemented"; m_what = oss.str(); } const char* NotImplementedException::what() const CATCH_NOEXCEPT { return m_what.c_str(); } } // end namespace Catch // #included from: catch_context_impl.hpp #define TWOBLUECUBES_CATCH_CONTEXT_IMPL_HPP_INCLUDED // #included from: catch_stream.hpp #define TWOBLUECUBES_CATCH_STREAM_HPP_INCLUDED #include #include #include namespace Catch { template class StreamBufImpl : public StreamBufBase { char data[bufferSize]; WriterF m_writer; public: StreamBufImpl() { setp( data, data + sizeof(data) ); } ~StreamBufImpl() CATCH_NOEXCEPT { sync(); } private: int overflow( int c ) { sync(); if( c != EOF ) { if( pbase() == epptr() ) m_writer( std::string( 1, static_cast( c ) ) ); else sputc( static_cast( c ) ); } return 0; } int sync() { if( pbase() != pptr() ) { m_writer( std::string( pbase(), static_cast( pptr() - pbase() ) ) ); setp( pbase(), epptr() ); } return 0; } }; /////////////////////////////////////////////////////////////////////////// FileStream::FileStream( std::string const& filename ) { m_ofs.open( filename.c_str() ); if( m_ofs.fail() ) { std::ostringstream oss; oss << "Unable to open file: '" << filename << '\''; throw std::domain_error( oss.str() ); } } std::ostream& FileStream::stream() const { return m_ofs; } struct OutputDebugWriter { void operator()( std::string const&str ) { writeToDebugConsole( str ); } }; DebugOutStream::DebugOutStream() : m_streamBuf( new StreamBufImpl() ), m_os( m_streamBuf.get() ) {} std::ostream& DebugOutStream::stream() const { return m_os; } // Store the streambuf from cout up-front because // cout may get redirected when running tests CoutStream::CoutStream() : m_os( Catch::cout().rdbuf() ) {} std::ostream& CoutStream::stream() const { return m_os; } #ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement these functions std::ostream& cout() { return std::cout; } std::ostream& cerr() { return std::cerr; } std::ostream& clog() { return std::clog; } #endif } namespace Catch { class Context : public IMutableContext { Context() : m_config( CATCH_NULL ), m_runner( CATCH_NULL ), m_resultCapture( CATCH_NULL ) {} Context( Context const& ); void operator=( Context const& ); public: virtual ~Context() { deleteAllValues( m_generatorsByTestName ); } public: // IContext virtual IResultCapture* getResultCapture() { return m_resultCapture; } virtual IRunner* getRunner() { return m_runner; } virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) { return getGeneratorsForCurrentTest() .getGeneratorInfo( fileInfo, totalSize ) .getCurrentIndex(); } virtual bool advanceGeneratorsForCurrentTest() { IGeneratorsForTest* generators = findGeneratorsForCurrentTest(); return generators && generators->moveNext(); } virtual Ptr getConfig() const { return m_config; } public: // IMutableContext virtual void setResultCapture( IResultCapture* resultCapture ) { m_resultCapture = resultCapture; } virtual void setRunner( IRunner* runner ) { m_runner = runner; } virtual void setConfig( Ptr const& config ) { m_config = config; } friend IMutableContext& getCurrentMutableContext(); private: IGeneratorsForTest* findGeneratorsForCurrentTest() { std::string testName = getResultCapture()->getCurrentTestName(); std::map::const_iterator it = m_generatorsByTestName.find( testName ); return it != m_generatorsByTestName.end() ? it->second : CATCH_NULL; } IGeneratorsForTest& getGeneratorsForCurrentTest() { IGeneratorsForTest* generators = findGeneratorsForCurrentTest(); if( !generators ) { std::string testName = getResultCapture()->getCurrentTestName(); generators = createGeneratorsForTest(); m_generatorsByTestName.insert( std::make_pair( testName, generators ) ); } return *generators; } private: Ptr m_config; IRunner* m_runner; IResultCapture* m_resultCapture; std::map m_generatorsByTestName; }; namespace { Context* currentContext = CATCH_NULL; } IMutableContext& getCurrentMutableContext() { if( !currentContext ) currentContext = new Context(); return *currentContext; } IContext& getCurrentContext() { return getCurrentMutableContext(); } void cleanUpContext() { delete currentContext; currentContext = CATCH_NULL; } } // #included from: catch_console_colour_impl.hpp #define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_IMPL_HPP_INCLUDED // #included from: catch_errno_guard.hpp #define TWOBLUECUBES_CATCH_ERRNO_GUARD_HPP_INCLUDED #include namespace Catch { class ErrnoGuard { public: ErrnoGuard():m_oldErrno(errno){} ~ErrnoGuard() { errno = m_oldErrno; } private: int m_oldErrno; }; } namespace Catch { namespace { struct IColourImpl { virtual ~IColourImpl() {} virtual void use( Colour::Code _colourCode ) = 0; }; struct NoColourImpl : IColourImpl { void use( Colour::Code ) {} static IColourImpl* instance() { static NoColourImpl s_instance; return &s_instance; } }; } // anon namespace } // namespace Catch #if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI ) # ifdef CATCH_PLATFORM_WINDOWS # define CATCH_CONFIG_COLOUR_WINDOWS # else # define CATCH_CONFIG_COLOUR_ANSI # endif #endif #if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) ///////////////////////////////////////// namespace Catch { namespace { class Win32ColourImpl : public IColourImpl { public: Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) ) { CONSOLE_SCREEN_BUFFER_INFO csbiInfo; GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo ); originalForegroundAttributes = csbiInfo.wAttributes & ~( BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY ); originalBackgroundAttributes = csbiInfo.wAttributes & ~( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY ); } virtual void use( Colour::Code _colourCode ) { switch( _colourCode ) { case Colour::None: return setTextAttribute( originalForegroundAttributes ); case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); case Colour::Red: return setTextAttribute( FOREGROUND_RED ); case Colour::Green: return setTextAttribute( FOREGROUND_GREEN ); case Colour::Blue: return setTextAttribute( FOREGROUND_BLUE ); case Colour::Cyan: return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN ); case Colour::Yellow: return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN ); case Colour::Grey: return setTextAttribute( 0 ); case Colour::LightGrey: return setTextAttribute( FOREGROUND_INTENSITY ); case Colour::BrightRed: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED ); case Colour::BrightGreen: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN ); case Colour::BrightWhite: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); case Colour::Bright: throw std::logic_error( "not a colour" ); } } private: void setTextAttribute( WORD _textAttribute ) { SetConsoleTextAttribute( stdoutHandle, _textAttribute | originalBackgroundAttributes ); } HANDLE stdoutHandle; WORD originalForegroundAttributes; WORD originalBackgroundAttributes; }; IColourImpl* platformColourInstance() { static Win32ColourImpl s_instance; Ptr config = getCurrentContext().getConfig(); UseColour::YesOrNo colourMode = config ? config->useColour() : UseColour::Auto; if( colourMode == UseColour::Auto ) colourMode = !isDebuggerActive() ? UseColour::Yes : UseColour::No; return colourMode == UseColour::Yes ? &s_instance : NoColourImpl::instance(); } } // end anon namespace } // end namespace Catch #elif defined( CATCH_CONFIG_COLOUR_ANSI ) ////////////////////////////////////// #include namespace Catch { namespace { // use POSIX/ ANSI console terminal codes // Thanks to Adam Strzelecki for original contribution // (http://github.com/nanoant) // https://github.com/philsquared/Catch/pull/131 class PosixColourImpl : public IColourImpl { public: virtual void use( Colour::Code _colourCode ) { switch( _colourCode ) { case Colour::None: case Colour::White: return setColour( "[0m" ); case Colour::Red: return setColour( "[0;31m" ); case Colour::Green: return setColour( "[0;32m" ); case Colour::Blue: return setColour( "[0;34m" ); case Colour::Cyan: return setColour( "[0;36m" ); case Colour::Yellow: return setColour( "[0;33m" ); case Colour::Grey: return setColour( "[1;30m" ); case Colour::LightGrey: return setColour( "[0;37m" ); case Colour::BrightRed: return setColour( "[1;31m" ); case Colour::BrightGreen: return setColour( "[1;32m" ); case Colour::BrightWhite: return setColour( "[1;37m" ); case Colour::Bright: throw std::logic_error( "not a colour" ); } } static IColourImpl* instance() { static PosixColourImpl s_instance; return &s_instance; } private: void setColour( const char* _escapeCode ) { Catch::cout() << '\033' << _escapeCode; } }; IColourImpl* platformColourInstance() { ErrnoGuard guard; Ptr config = getCurrentContext().getConfig(); UseColour::YesOrNo colourMode = config ? config->useColour() : UseColour::Auto; if( colourMode == UseColour::Auto ) colourMode = (!isDebuggerActive() && isatty(STDOUT_FILENO) ) ? UseColour::Yes : UseColour::No; return colourMode == UseColour::Yes ? PosixColourImpl::instance() : NoColourImpl::instance(); } } // end anon namespace } // end namespace Catch #else // not Windows or ANSI /////////////////////////////////////////////// namespace Catch { static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); } } // end namespace Catch #endif // Windows/ ANSI/ None namespace Catch { Colour::Colour( Code _colourCode ) : m_moved( false ) { use( _colourCode ); } Colour::Colour( Colour const& _other ) : m_moved( false ) { const_cast( _other ).m_moved = true; } Colour::~Colour(){ if( !m_moved ) use( None ); } void Colour::use( Code _colourCode ) { static IColourImpl* impl = platformColourInstance(); impl->use( _colourCode ); } } // end namespace Catch // #included from: catch_generators_impl.hpp #define TWOBLUECUBES_CATCH_GENERATORS_IMPL_HPP_INCLUDED #include #include #include namespace Catch { struct GeneratorInfo : IGeneratorInfo { GeneratorInfo( std::size_t size ) : m_size( size ), m_currentIndex( 0 ) {} bool moveNext() { if( ++m_currentIndex == m_size ) { m_currentIndex = 0; return false; } return true; } std::size_t getCurrentIndex() const { return m_currentIndex; } std::size_t m_size; std::size_t m_currentIndex; }; /////////////////////////////////////////////////////////////////////////// class GeneratorsForTest : public IGeneratorsForTest { public: ~GeneratorsForTest() { deleteAll( m_generatorsInOrder ); } IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) { std::map::const_iterator it = m_generatorsByName.find( fileInfo ); if( it == m_generatorsByName.end() ) { IGeneratorInfo* info = new GeneratorInfo( size ); m_generatorsByName.insert( std::make_pair( fileInfo, info ) ); m_generatorsInOrder.push_back( info ); return *info; } return *it->second; } bool moveNext() { std::vector::const_iterator it = m_generatorsInOrder.begin(); std::vector::const_iterator itEnd = m_generatorsInOrder.end(); for(; it != itEnd; ++it ) { if( (*it)->moveNext() ) return true; } return false; } private: std::map m_generatorsByName; std::vector m_generatorsInOrder; }; IGeneratorsForTest* createGeneratorsForTest() { return new GeneratorsForTest(); } } // end namespace Catch // #included from: catch_assertionresult.hpp #define TWOBLUECUBES_CATCH_ASSERTIONRESULT_HPP_INCLUDED namespace Catch { AssertionInfo::AssertionInfo():macroName(""), capturedExpression(""), resultDisposition(ResultDisposition::Normal), secondArg(""){} AssertionInfo::AssertionInfo( char const * _macroName, SourceLineInfo const& _lineInfo, char const * _capturedExpression, ResultDisposition::Flags _resultDisposition, char const * _secondArg) : macroName( _macroName ), lineInfo( _lineInfo ), capturedExpression( _capturedExpression ), resultDisposition( _resultDisposition ), secondArg( _secondArg ) {} AssertionResult::AssertionResult() {} AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data ) : m_info( info ), m_resultData( data ) {} AssertionResult::~AssertionResult() {} // Result was a success bool AssertionResult::succeeded() const { return Catch::isOk( m_resultData.resultType ); } // Result was a success, or failure is suppressed bool AssertionResult::isOk() const { return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition ); } ResultWas::OfType AssertionResult::getResultType() const { return m_resultData.resultType; } bool AssertionResult::hasExpression() const { return m_info.capturedExpression[0] != 0; } bool AssertionResult::hasMessage() const { return !m_resultData.message.empty(); } std::string capturedExpressionWithSecondArgument( char const * capturedExpression, char const * secondArg ) { return (secondArg[0] == 0 || secondArg[0] == '"' && secondArg[1] == '"') ? capturedExpression : std::string(capturedExpression) + ", " + secondArg; } std::string AssertionResult::getExpression() const { if( isFalseTest( m_info.resultDisposition ) ) return '!' + capturedExpressionWithSecondArgument(m_info.capturedExpression, m_info.secondArg); else return capturedExpressionWithSecondArgument(m_info.capturedExpression, m_info.secondArg); } std::string AssertionResult::getExpressionInMacro() const { if( m_info.macroName[0] == 0 ) return capturedExpressionWithSecondArgument(m_info.capturedExpression, m_info.secondArg); else return std::string(m_info.macroName) + "( " + capturedExpressionWithSecondArgument(m_info.capturedExpression, m_info.secondArg) + " )"; } bool AssertionResult::hasExpandedExpression() const { return hasExpression() && getExpandedExpression() != getExpression(); } std::string AssertionResult::getExpandedExpression() const { return m_resultData.reconstructExpression(); } std::string AssertionResult::getMessage() const { return m_resultData.message; } SourceLineInfo AssertionResult::getSourceInfo() const { return m_info.lineInfo; } std::string AssertionResult::getTestMacroName() const { return m_info.macroName; } void AssertionResult::discardDecomposedExpression() const { m_resultData.decomposedExpression = CATCH_NULL; } void AssertionResult::expandDecomposedExpression() const { m_resultData.reconstructExpression(); } } // end namespace Catch // #included from: catch_test_case_info.hpp #define TWOBLUECUBES_CATCH_TEST_CASE_INFO_HPP_INCLUDED #include namespace Catch { inline TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) { if( startsWith( tag, '.' ) || tag == "hide" || tag == "!hide" ) return TestCaseInfo::IsHidden; else if( tag == "!throws" ) return TestCaseInfo::Throws; else if( tag == "!shouldfail" ) return TestCaseInfo::ShouldFail; else if( tag == "!mayfail" ) return TestCaseInfo::MayFail; else if( tag == "!nonportable" ) return TestCaseInfo::NonPortable; else return TestCaseInfo::None; } inline bool isReservedTag( std::string const& tag ) { return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !std::isalnum( tag[0] ); } inline void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) { if( isReservedTag( tag ) ) { std::ostringstream ss; ss << Colour(Colour::Red) << "Tag name [" << tag << "] not allowed.\n" << "Tag names starting with non alpha-numeric characters are reserved\n" << Colour(Colour::FileName) << _lineInfo << '\n'; throw std::runtime_error(ss.str()); } } TestCase makeTestCase( ITestCase* _testCase, std::string const& _className, std::string const& _name, std::string const& _descOrTags, SourceLineInfo const& _lineInfo ) { bool isHidden( startsWith( _name, "./" ) ); // Legacy support // Parse out tags std::set tags; std::string desc, tag; bool inTag = false; for( std::size_t i = 0; i < _descOrTags.size(); ++i ) { char c = _descOrTags[i]; if( !inTag ) { if( c == '[' ) inTag = true; else desc += c; } else { if( c == ']' ) { TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag ); if( prop == TestCaseInfo::IsHidden ) isHidden = true; else if( prop == TestCaseInfo::None ) enforceNotReservedTag( tag, _lineInfo ); tags.insert( tag ); tag.clear(); inTag = false; } else tag += c; } } if( isHidden ) { tags.insert( "hide" ); tags.insert( "." ); } TestCaseInfo info( _name, _className, desc, tags, _lineInfo ); return TestCase( _testCase, info ); } void setTags( TestCaseInfo& testCaseInfo, std::set const& tags ) { testCaseInfo.tags = tags; testCaseInfo.lcaseTags.clear(); std::ostringstream oss; for( std::set::const_iterator it = tags.begin(), itEnd = tags.end(); it != itEnd; ++it ) { oss << '[' << *it << ']'; std::string lcaseTag = toLower( *it ); testCaseInfo.properties = static_cast( testCaseInfo.properties | parseSpecialTag( lcaseTag ) ); testCaseInfo.lcaseTags.insert( lcaseTag ); } testCaseInfo.tagsAsString = oss.str(); } TestCaseInfo::TestCaseInfo( std::string const& _name, std::string const& _className, std::string const& _description, std::set const& _tags, SourceLineInfo const& _lineInfo ) : name( _name ), className( _className ), description( _description ), lineInfo( _lineInfo ), properties( None ) { setTags( *this, _tags ); } TestCaseInfo::TestCaseInfo( TestCaseInfo const& other ) : name( other.name ), className( other.className ), description( other.description ), tags( other.tags ), lcaseTags( other.lcaseTags ), tagsAsString( other.tagsAsString ), lineInfo( other.lineInfo ), properties( other.properties ) {} bool TestCaseInfo::isHidden() const { return ( properties & IsHidden ) != 0; } bool TestCaseInfo::throws() const { return ( properties & Throws ) != 0; } bool TestCaseInfo::okToFail() const { return ( properties & (ShouldFail | MayFail ) ) != 0; } bool TestCaseInfo::expectedToFail() const { return ( properties & (ShouldFail ) ) != 0; } TestCase::TestCase( ITestCase* testCase, TestCaseInfo const& info ) : TestCaseInfo( info ), test( testCase ) {} TestCase::TestCase( TestCase const& other ) : TestCaseInfo( other ), test( other.test ) {} TestCase TestCase::withName( std::string const& _newName ) const { TestCase other( *this ); other.name = _newName; return other; } void TestCase::swap( TestCase& other ) { test.swap( other.test ); name.swap( other.name ); className.swap( other.className ); description.swap( other.description ); tags.swap( other.tags ); lcaseTags.swap( other.lcaseTags ); tagsAsString.swap( other.tagsAsString ); std::swap( TestCaseInfo::properties, static_cast( other ).properties ); std::swap( lineInfo, other.lineInfo ); } void TestCase::invoke() const { test->invoke(); } bool TestCase::operator == ( TestCase const& other ) const { return test.get() == other.test.get() && name == other.name && className == other.className; } bool TestCase::operator < ( TestCase const& other ) const { return name < other.name; } TestCase& TestCase::operator = ( TestCase const& other ) { TestCase temp( other ); swap( temp ); return *this; } TestCaseInfo const& TestCase::getTestCaseInfo() const { return *this; } } // end namespace Catch // #included from: catch_version.hpp #define TWOBLUECUBES_CATCH_VERSION_HPP_INCLUDED namespace Catch { Version::Version ( unsigned int _majorVersion, unsigned int _minorVersion, unsigned int _patchNumber, char const * const _branchName, unsigned int _buildNumber ) : majorVersion( _majorVersion ), minorVersion( _minorVersion ), patchNumber( _patchNumber ), branchName( _branchName ), buildNumber( _buildNumber ) {} std::ostream& operator << ( std::ostream& os, Version const& version ) { os << version.majorVersion << '.' << version.minorVersion << '.' << version.patchNumber; // branchName is never null -> 0th char is \0 if it is empty if (version.branchName[0]) { os << '-' << version.branchName << '.' << version.buildNumber; } return os; } inline Version libraryVersion() { static Version version( 1, 10, 0, "", 0 ); return version; } } // #included from: catch_message.hpp #define TWOBLUECUBES_CATCH_MESSAGE_HPP_INCLUDED namespace Catch { MessageInfo::MessageInfo( std::string const& _macroName, SourceLineInfo const& _lineInfo, ResultWas::OfType _type ) : macroName( _macroName ), lineInfo( _lineInfo ), type( _type ), sequence( ++globalCount ) {} // This may need protecting if threading support is added unsigned int MessageInfo::globalCount = 0; //////////////////////////////////////////////////////////////////////////// ScopedMessage::ScopedMessage( MessageBuilder const& builder ) : m_info( builder.m_info ) { m_info.message = builder.m_stream.str(); getResultCapture().pushScopedMessage( m_info ); } ScopedMessage::ScopedMessage( ScopedMessage const& other ) : m_info( other.m_info ) {} ScopedMessage::~ScopedMessage() { if ( !std::uncaught_exception() ){ getResultCapture().popScopedMessage(m_info); } } } // end namespace Catch // #included from: catch_legacy_reporter_adapter.hpp #define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_HPP_INCLUDED // #included from: catch_legacy_reporter_adapter.h #define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_H_INCLUDED namespace Catch { // Deprecated struct IReporter : IShared { virtual ~IReporter(); virtual bool shouldRedirectStdout() const = 0; virtual void StartTesting() = 0; virtual void EndTesting( Totals const& totals ) = 0; virtual void StartGroup( std::string const& groupName ) = 0; virtual void EndGroup( std::string const& groupName, Totals const& totals ) = 0; virtual void StartTestCase( TestCaseInfo const& testInfo ) = 0; virtual void EndTestCase( TestCaseInfo const& testInfo, Totals const& totals, std::string const& stdOut, std::string const& stdErr ) = 0; virtual void StartSection( std::string const& sectionName, std::string const& description ) = 0; virtual void EndSection( std::string const& sectionName, Counts const& assertions ) = 0; virtual void NoAssertionsInSection( std::string const& sectionName ) = 0; virtual void NoAssertionsInTestCase( std::string const& testName ) = 0; virtual void Aborted() = 0; virtual void Result( AssertionResult const& result ) = 0; }; class LegacyReporterAdapter : public SharedImpl { public: LegacyReporterAdapter( Ptr const& legacyReporter ); virtual ~LegacyReporterAdapter(); virtual ReporterPreferences getPreferences() const; virtual void noMatchingTestCases( std::string const& ); virtual void testRunStarting( TestRunInfo const& ); virtual void testGroupStarting( GroupInfo const& groupInfo ); virtual void testCaseStarting( TestCaseInfo const& testInfo ); virtual void sectionStarting( SectionInfo const& sectionInfo ); virtual void assertionStarting( AssertionInfo const& ); virtual bool assertionEnded( AssertionStats const& assertionStats ); virtual void sectionEnded( SectionStats const& sectionStats ); virtual void testCaseEnded( TestCaseStats const& testCaseStats ); virtual void testGroupEnded( TestGroupStats const& testGroupStats ); virtual void testRunEnded( TestRunStats const& testRunStats ); virtual void skipTest( TestCaseInfo const& ); private: Ptr m_legacyReporter; }; } namespace Catch { LegacyReporterAdapter::LegacyReporterAdapter( Ptr const& legacyReporter ) : m_legacyReporter( legacyReporter ) {} LegacyReporterAdapter::~LegacyReporterAdapter() {} ReporterPreferences LegacyReporterAdapter::getPreferences() const { ReporterPreferences prefs; prefs.shouldRedirectStdOut = m_legacyReporter->shouldRedirectStdout(); return prefs; } void LegacyReporterAdapter::noMatchingTestCases( std::string const& ) {} void LegacyReporterAdapter::testRunStarting( TestRunInfo const& ) { m_legacyReporter->StartTesting(); } void LegacyReporterAdapter::testGroupStarting( GroupInfo const& groupInfo ) { m_legacyReporter->StartGroup( groupInfo.name ); } void LegacyReporterAdapter::testCaseStarting( TestCaseInfo const& testInfo ) { m_legacyReporter->StartTestCase( testInfo ); } void LegacyReporterAdapter::sectionStarting( SectionInfo const& sectionInfo ) { m_legacyReporter->StartSection( sectionInfo.name, sectionInfo.description ); } void LegacyReporterAdapter::assertionStarting( AssertionInfo const& ) { // Not on legacy interface } bool LegacyReporterAdapter::assertionEnded( AssertionStats const& assertionStats ) { if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) { for( std::vector::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end(); it != itEnd; ++it ) { if( it->type == ResultWas::Info ) { ResultBuilder rb( it->macroName.c_str(), it->lineInfo, "", ResultDisposition::Normal ); rb << it->message; rb.setResultType( ResultWas::Info ); AssertionResult result = rb.build(); m_legacyReporter->Result( result ); } } } m_legacyReporter->Result( assertionStats.assertionResult ); return true; } void LegacyReporterAdapter::sectionEnded( SectionStats const& sectionStats ) { if( sectionStats.missingAssertions ) m_legacyReporter->NoAssertionsInSection( sectionStats.sectionInfo.name ); m_legacyReporter->EndSection( sectionStats.sectionInfo.name, sectionStats.assertions ); } void LegacyReporterAdapter::testCaseEnded( TestCaseStats const& testCaseStats ) { m_legacyReporter->EndTestCase ( testCaseStats.testInfo, testCaseStats.totals, testCaseStats.stdOut, testCaseStats.stdErr ); } void LegacyReporterAdapter::testGroupEnded( TestGroupStats const& testGroupStats ) { if( testGroupStats.aborting ) m_legacyReporter->Aborted(); m_legacyReporter->EndGroup( testGroupStats.groupInfo.name, testGroupStats.totals ); } void LegacyReporterAdapter::testRunEnded( TestRunStats const& testRunStats ) { m_legacyReporter->EndTesting( testRunStats.totals ); } void LegacyReporterAdapter::skipTest( TestCaseInfo const& ) { } } // #included from: catch_timer.hpp #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wc++11-long-long" #endif #ifdef CATCH_PLATFORM_WINDOWS #else #include #endif namespace Catch { namespace { #ifdef CATCH_PLATFORM_WINDOWS UInt64 getCurrentTicks() { static UInt64 hz=0, hzo=0; if (!hz) { QueryPerformanceFrequency( reinterpret_cast( &hz ) ); QueryPerformanceCounter( reinterpret_cast( &hzo ) ); } UInt64 t; QueryPerformanceCounter( reinterpret_cast( &t ) ); return ((t-hzo)*1000000)/hz; } #else UInt64 getCurrentTicks() { timeval t; gettimeofday(&t,CATCH_NULL); return static_cast( t.tv_sec ) * 1000000ull + static_cast( t.tv_usec ); } #endif } void Timer::start() { m_ticks = getCurrentTicks(); } unsigned int Timer::getElapsedMicroseconds() const { return static_cast(getCurrentTicks() - m_ticks); } unsigned int Timer::getElapsedMilliseconds() const { return static_cast(getElapsedMicroseconds()/1000); } double Timer::getElapsedSeconds() const { return getElapsedMicroseconds()/1000000.0; } } // namespace Catch #ifdef __clang__ #pragma clang diagnostic pop #endif // #included from: catch_common.hpp #define TWOBLUECUBES_CATCH_COMMON_HPP_INCLUDED #include #include namespace Catch { bool startsWith( std::string const& s, std::string const& prefix ) { return s.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), s.begin()); } bool startsWith( std::string const& s, char prefix ) { return !s.empty() && s[0] == prefix; } bool endsWith( std::string const& s, std::string const& suffix ) { return s.size() >= suffix.size() && std::equal(suffix.rbegin(), suffix.rend(), s.rbegin()); } bool endsWith( std::string const& s, char suffix ) { return !s.empty() && s[s.size()-1] == suffix; } bool contains( std::string const& s, std::string const& infix ) { return s.find( infix ) != std::string::npos; } char toLowerCh(char c) { return static_cast( std::tolower( c ) ); } void toLowerInPlace( std::string& s ) { std::transform( s.begin(), s.end(), s.begin(), toLowerCh ); } std::string toLower( std::string const& s ) { std::string lc = s; toLowerInPlace( lc ); return lc; } std::string trim( std::string const& str ) { static char const* whitespaceChars = "\n\r\t "; std::string::size_type start = str.find_first_not_of( whitespaceChars ); std::string::size_type end = str.find_last_not_of( whitespaceChars ); return start != std::string::npos ? str.substr( start, 1+end-start ) : std::string(); } bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) { bool replaced = false; std::size_t i = str.find( replaceThis ); while( i != std::string::npos ) { replaced = true; str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() ); if( i < str.size()-withThis.size() ) i = str.find( replaceThis, i+withThis.size() ); else i = std::string::npos; } return replaced; } pluralise::pluralise( std::size_t count, std::string const& label ) : m_count( count ), m_label( label ) {} std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) { os << pluraliser.m_count << ' ' << pluraliser.m_label; if( pluraliser.m_count != 1 ) os << 's'; return os; } SourceLineInfo::SourceLineInfo() : file(""), line( 0 ){} SourceLineInfo::SourceLineInfo( char const* _file, std::size_t _line ) : file( _file ), line( _line ) {} bool SourceLineInfo::empty() const { return file[0] == '\0'; } bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const { return line == other.line && (file == other.file || std::strcmp(file, other.file) == 0); } bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const { return line < other.line || ( line == other.line && (std::strcmp(file, other.file) < 0)); } void seedRng( IConfig const& config ) { if( config.rngSeed() != 0 ) std::srand( config.rngSeed() ); } unsigned int rngSeed() { return getCurrentContext().getConfig()->rngSeed(); } std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) { #ifndef __GNUG__ os << info.file << '(' << info.line << ')'; #else os << info.file << ':' << info.line; #endif return os; } void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ) { std::ostringstream oss; oss << locationInfo << ": Internal Catch error: '" << message << '\''; if( alwaysTrue() ) throw std::logic_error( oss.str() ); } } // #included from: catch_section.hpp #define TWOBLUECUBES_CATCH_SECTION_HPP_INCLUDED namespace Catch { SectionInfo::SectionInfo ( SourceLineInfo const& _lineInfo, std::string const& _name, std::string const& _description ) : name( _name ), description( _description ), lineInfo( _lineInfo ) {} Section::Section( SectionInfo const& info ) : m_info( info ), m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) ) { m_timer.start(); } #if defined(_MSC_VER) #pragma warning(push) #pragma warning(disable:4996) // std::uncaught_exception is deprecated in C++17 #endif Section::~Section() { if( m_sectionIncluded ) { SectionEndInfo endInfo( m_info, m_assertions, m_timer.getElapsedSeconds() ); if( std::uncaught_exception() ) getResultCapture().sectionEndedEarly( endInfo ); else getResultCapture().sectionEnded( endInfo ); } } #if defined(_MSC_VER) #pragma warning(pop) #endif // This indicates whether the section should be executed or not Section::operator bool() const { return m_sectionIncluded; } } // end namespace Catch // #included from: catch_debugger.hpp #define TWOBLUECUBES_CATCH_DEBUGGER_HPP_INCLUDED #ifdef CATCH_PLATFORM_MAC #include #include #include #include #include namespace Catch{ // The following function is taken directly from the following technical note: // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html // Returns true if the current process is being debugged (either // running under the debugger or has a debugger attached post facto). bool isDebuggerActive(){ int mib[4]; struct kinfo_proc info; size_t size; // Initialize the flags so that, if sysctl fails for some bizarre // reason, we get a predictable result. info.kp_proc.p_flag = 0; // Initialize mib, which tells sysctl the info we want, in this case // we're looking for information about a specific process ID. mib[0] = CTL_KERN; mib[1] = KERN_PROC; mib[2] = KERN_PROC_PID; mib[3] = getpid(); // Call sysctl. size = sizeof(info); if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, CATCH_NULL, 0) != 0 ) { Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl; return false; } // We're being debugged if the P_TRACED flag is set. return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); } } // namespace Catch #elif defined(CATCH_PLATFORM_LINUX) #include #include namespace Catch{ // The standard POSIX way of detecting a debugger is to attempt to // ptrace() the process, but this needs to be done from a child and not // this process itself to still allow attaching to this process later // if wanted, so is rather heavy. Under Linux we have the PID of the // "debugger" (which doesn't need to be gdb, of course, it could also // be strace, for example) in /proc/$PID/status, so just get it from // there instead. bool isDebuggerActive(){ // Libstdc++ has a bug, where std::ifstream sets errno to 0 // This way our users can properly assert over errno values ErrnoGuard guard; std::ifstream in("/proc/self/status"); for( std::string line; std::getline(in, line); ) { static const int PREFIX_LEN = 11; if( line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0 ) { // We're traced if the PID is not 0 and no other PID starts // with 0 digit, so it's enough to check for just a single // character. return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0'; } } return false; } } // namespace Catch #elif defined(_MSC_VER) extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); namespace Catch { bool isDebuggerActive() { return IsDebuggerPresent() != 0; } } #elif defined(__MINGW32__) extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); namespace Catch { bool isDebuggerActive() { return IsDebuggerPresent() != 0; } } #else namespace Catch { inline bool isDebuggerActive() { return false; } } #endif // Platform #ifdef CATCH_PLATFORM_WINDOWS namespace Catch { void writeToDebugConsole( std::string const& text ) { ::OutputDebugStringA( text.c_str() ); } } #else namespace Catch { void writeToDebugConsole( std::string const& text ) { // !TBD: Need a version for Mac/ XCode and other IDEs Catch::cout() << text; } } #endif // Platform // #included from: catch_tostring.hpp #define TWOBLUECUBES_CATCH_TOSTRING_HPP_INCLUDED namespace Catch { namespace Detail { const std::string unprintableString = "{?}"; namespace { const int hexThreshold = 255; struct Endianness { enum Arch { Big, Little }; static Arch which() { union _{ int asInt; char asChar[sizeof (int)]; } u; u.asInt = 1; return ( u.asChar[sizeof(int)-1] == 1 ) ? Big : Little; } }; } std::string rawMemoryToString( const void *object, std::size_t size ) { // Reverse order for little endian architectures int i = 0, end = static_cast( size ), inc = 1; if( Endianness::which() == Endianness::Little ) { i = end-1; end = inc = -1; } unsigned char const *bytes = static_cast(object); std::ostringstream os; os << "0x" << std::setfill('0') << std::hex; for( ; i != end; i += inc ) os << std::setw(2) << static_cast(bytes[i]); return os.str(); } } std::string toString( std::string const& value ) { std::string s = value; if( getCurrentContext().getConfig()->showInvisibles() ) { for(size_t i = 0; i < s.size(); ++i ) { std::string subs; switch( s[i] ) { case '\n': subs = "\\n"; break; case '\t': subs = "\\t"; break; default: break; } if( !subs.empty() ) { s = s.substr( 0, i ) + subs + s.substr( i+1 ); ++i; } } } return '"' + s + '"'; } std::string toString( std::wstring const& value ) { std::string s; s.reserve( value.size() ); for(size_t i = 0; i < value.size(); ++i ) s += value[i] <= 0xff ? static_cast( value[i] ) : '?'; return Catch::toString( s ); } std::string toString( const char* const value ) { return value ? Catch::toString( std::string( value ) ) : std::string( "{null string}" ); } std::string toString( char* const value ) { return Catch::toString( static_cast( value ) ); } std::string toString( const wchar_t* const value ) { return value ? Catch::toString( std::wstring(value) ) : std::string( "{null string}" ); } std::string toString( wchar_t* const value ) { return Catch::toString( static_cast( value ) ); } std::string toString( int value ) { std::ostringstream oss; oss << value; if( value > Detail::hexThreshold ) oss << " (0x" << std::hex << value << ')'; return oss.str(); } std::string toString( unsigned long value ) { std::ostringstream oss; oss << value; if( value > Detail::hexThreshold ) oss << " (0x" << std::hex << value << ')'; return oss.str(); } std::string toString( unsigned int value ) { return Catch::toString( static_cast( value ) ); } template std::string fpToString( T value, int precision ) { std::ostringstream oss; oss << std::setprecision( precision ) << std::fixed << value; std::string d = oss.str(); std::size_t i = d.find_last_not_of( '0' ); if( i != std::string::npos && i != d.size()-1 ) { if( d[i] == '.' ) i++; d = d.substr( 0, i+1 ); } return d; } std::string toString( const double value ) { return fpToString( value, 10 ); } std::string toString( const float value ) { return fpToString( value, 5 ) + 'f'; } std::string toString( bool value ) { return value ? "true" : "false"; } std::string toString( char value ) { if ( value == '\r' ) return "'\\r'"; if ( value == '\f' ) return "'\\f'"; if ( value == '\n' ) return "'\\n'"; if ( value == '\t' ) return "'\\t'"; if ( '\0' <= value && value < ' ' ) return toString( static_cast( value ) ); char chstr[] = "' '"; chstr[1] = value; return chstr; } std::string toString( signed char value ) { return toString( static_cast( value ) ); } std::string toString( unsigned char value ) { return toString( static_cast( value ) ); } #ifdef CATCH_CONFIG_CPP11_LONG_LONG std::string toString( long long value ) { std::ostringstream oss; oss << value; if( value > Detail::hexThreshold ) oss << " (0x" << std::hex << value << ')'; return oss.str(); } std::string toString( unsigned long long value ) { std::ostringstream oss; oss << value; if( value > Detail::hexThreshold ) oss << " (0x" << std::hex << value << ')'; return oss.str(); } #endif #ifdef CATCH_CONFIG_CPP11_NULLPTR std::string toString( std::nullptr_t ) { return "nullptr"; } #endif #ifdef __OBJC__ std::string toString( NSString const * const& nsstring ) { if( !nsstring ) return "nil"; return "@" + toString([nsstring UTF8String]); } std::string toString( NSString * CATCH_ARC_STRONG & nsstring ) { if( !nsstring ) return "nil"; return "@" + toString([nsstring UTF8String]); } std::string toString( NSObject* const& nsObject ) { return toString( [nsObject description] ); } #endif } // end namespace Catch // #included from: catch_result_builder.hpp #define TWOBLUECUBES_CATCH_RESULT_BUILDER_HPP_INCLUDED namespace Catch { ResultBuilder::ResultBuilder( char const* macroName, SourceLineInfo const& lineInfo, char const* capturedExpression, ResultDisposition::Flags resultDisposition, char const* secondArg ) : m_assertionInfo( macroName, lineInfo, capturedExpression, resultDisposition, secondArg ), m_shouldDebugBreak( false ), m_shouldThrow( false ), m_guardException( false ), m_usedStream( false ) {} ResultBuilder::~ResultBuilder() { #if defined(CATCH_CONFIG_FAST_COMPILE) if ( m_guardException ) { stream().oss << "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE"; captureResult( ResultWas::ThrewException ); getCurrentContext().getResultCapture()->exceptionEarlyReported(); } #endif } ResultBuilder& ResultBuilder::setResultType( ResultWas::OfType result ) { m_data.resultType = result; return *this; } ResultBuilder& ResultBuilder::setResultType( bool result ) { m_data.resultType = result ? ResultWas::Ok : ResultWas::ExpressionFailed; return *this; } void ResultBuilder::endExpression( DecomposedExpression const& expr ) { // Flip bool results if FalseTest flag is set if( isFalseTest( m_assertionInfo.resultDisposition ) ) { m_data.negate( expr.isBinaryExpression() ); } getResultCapture().assertionRun(); if(getCurrentContext().getConfig()->includeSuccessfulResults() || m_data.resultType != ResultWas::Ok) { AssertionResult result = build( expr ); handleResult( result ); } else getResultCapture().assertionPassed(); } void ResultBuilder::useActiveException( ResultDisposition::Flags resultDisposition ) { m_assertionInfo.resultDisposition = resultDisposition; stream().oss << Catch::translateActiveException(); captureResult( ResultWas::ThrewException ); } void ResultBuilder::captureResult( ResultWas::OfType resultType ) { setResultType( resultType ); captureExpression(); } void ResultBuilder::captureExpectedException( std::string const& expectedMessage ) { if( expectedMessage.empty() ) captureExpectedException( Matchers::Impl::MatchAllOf() ); else captureExpectedException( Matchers::Equals( expectedMessage ) ); } void ResultBuilder::captureExpectedException( Matchers::Impl::MatcherBase const& matcher ) { assert( !isFalseTest( m_assertionInfo.resultDisposition ) ); AssertionResultData data = m_data; data.resultType = ResultWas::Ok; data.reconstructedExpression = capturedExpressionWithSecondArgument(m_assertionInfo.capturedExpression, m_assertionInfo.secondArg); std::string actualMessage = Catch::translateActiveException(); if( !matcher.match( actualMessage ) ) { data.resultType = ResultWas::ExpressionFailed; data.reconstructedExpression = actualMessage; } AssertionResult result( m_assertionInfo, data ); handleResult( result ); } void ResultBuilder::captureExpression() { AssertionResult result = build(); handleResult( result ); } void ResultBuilder::handleResult( AssertionResult const& result ) { getResultCapture().assertionEnded( result ); if( !result.isOk() ) { if( getCurrentContext().getConfig()->shouldDebugBreak() ) m_shouldDebugBreak = true; if( getCurrentContext().getRunner()->aborting() || (m_assertionInfo.resultDisposition & ResultDisposition::Normal) ) m_shouldThrow = true; } } void ResultBuilder::react() { #if defined(CATCH_CONFIG_FAST_COMPILE) if (m_shouldDebugBreak) { /////////////////////////////////////////////////////////////////// // To inspect the state during test, you need to go one level up the callstack // To go back to the test and change execution, jump over the throw statement /////////////////////////////////////////////////////////////////// CATCH_BREAK_INTO_DEBUGGER(); } #endif if( m_shouldThrow ) throw Catch::TestFailureException(); } bool ResultBuilder::shouldDebugBreak() const { return m_shouldDebugBreak; } bool ResultBuilder::allowThrows() const { return getCurrentContext().getConfig()->allowThrows(); } AssertionResult ResultBuilder::build() const { return build( *this ); } // CAVEAT: The returned AssertionResult stores a pointer to the argument expr, // a temporary DecomposedExpression, which in turn holds references to // operands, possibly temporary as well. // It should immediately be passed to handleResult; if the expression // needs to be reported, its string expansion must be composed before // the temporaries are destroyed. AssertionResult ResultBuilder::build( DecomposedExpression const& expr ) const { assert( m_data.resultType != ResultWas::Unknown ); AssertionResultData data = m_data; if(m_usedStream) data.message = m_stream().oss.str(); data.decomposedExpression = &expr; // for lazy reconstruction return AssertionResult( m_assertionInfo, data ); } void ResultBuilder::reconstructExpression( std::string& dest ) const { dest = capturedExpressionWithSecondArgument(m_assertionInfo.capturedExpression, m_assertionInfo.secondArg); } void ResultBuilder::setExceptionGuard() { m_guardException = true; } void ResultBuilder::unsetExceptionGuard() { m_guardException = false; } } // end namespace Catch // #included from: catch_tag_alias_registry.hpp #define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_HPP_INCLUDED namespace Catch { TagAliasRegistry::~TagAliasRegistry() {} Option TagAliasRegistry::find( std::string const& alias ) const { std::map::const_iterator it = m_registry.find( alias ); if( it != m_registry.end() ) return it->second; else return Option(); } std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const { std::string expandedTestSpec = unexpandedTestSpec; for( std::map::const_iterator it = m_registry.begin(), itEnd = m_registry.end(); it != itEnd; ++it ) { std::size_t pos = expandedTestSpec.find( it->first ); if( pos != std::string::npos ) { expandedTestSpec = expandedTestSpec.substr( 0, pos ) + it->second.tag + expandedTestSpec.substr( pos + it->first.size() ); } } return expandedTestSpec; } void TagAliasRegistry::add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) { if( !startsWith( alias, "[@" ) || !endsWith( alias, ']' ) ) { std::ostringstream oss; oss << Colour( Colour::Red ) << "error: tag alias, \"" << alias << "\" is not of the form [@alias name].\n" << Colour( Colour::FileName ) << lineInfo << '\n'; throw std::domain_error( oss.str().c_str() ); } if( !m_registry.insert( std::make_pair( alias, TagAlias( tag, lineInfo ) ) ).second ) { std::ostringstream oss; oss << Colour( Colour::Red ) << "error: tag alias, \"" << alias << "\" already registered.\n" << "\tFirst seen at " << Colour( Colour::Red ) << find(alias)->lineInfo << '\n' << Colour( Colour::Red ) << "\tRedefined at " << Colour( Colour::FileName) << lineInfo << '\n'; throw std::domain_error( oss.str().c_str() ); } } ITagAliasRegistry::~ITagAliasRegistry() {} ITagAliasRegistry const& ITagAliasRegistry::get() { return getRegistryHub().getTagAliasRegistry(); } RegistrarForTagAliases::RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) { getMutableRegistryHub().registerTagAlias( alias, tag, lineInfo ); } } // end namespace Catch // #included from: catch_matchers_string.hpp namespace Catch { namespace Matchers { namespace StdString { CasedString::CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ) : m_caseSensitivity( caseSensitivity ), m_str( adjustString( str ) ) {} std::string CasedString::adjustString( std::string const& str ) const { return m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str; } std::string CasedString::caseSensitivitySuffix() const { return m_caseSensitivity == CaseSensitive::No ? " (case insensitive)" : std::string(); } StringMatcherBase::StringMatcherBase( std::string const& operation, CasedString const& comparator ) : m_comparator( comparator ), m_operation( operation ) { } std::string StringMatcherBase::describe() const { std::string description; description.reserve(5 + m_operation.size() + m_comparator.m_str.size() + m_comparator.caseSensitivitySuffix().size()); description += m_operation; description += ": \""; description += m_comparator.m_str; description += "\""; description += m_comparator.caseSensitivitySuffix(); return description; } EqualsMatcher::EqualsMatcher( CasedString const& comparator ) : StringMatcherBase( "equals", comparator ) {} bool EqualsMatcher::match( std::string const& source ) const { return m_comparator.adjustString( source ) == m_comparator.m_str; } ContainsMatcher::ContainsMatcher( CasedString const& comparator ) : StringMatcherBase( "contains", comparator ) {} bool ContainsMatcher::match( std::string const& source ) const { return contains( m_comparator.adjustString( source ), m_comparator.m_str ); } StartsWithMatcher::StartsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "starts with", comparator ) {} bool StartsWithMatcher::match( std::string const& source ) const { return startsWith( m_comparator.adjustString( source ), m_comparator.m_str ); } EndsWithMatcher::EndsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "ends with", comparator ) {} bool EndsWithMatcher::match( std::string const& source ) const { return endsWith( m_comparator.adjustString( source ), m_comparator.m_str ); } } // namespace StdString StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity ) { return StdString::EqualsMatcher( StdString::CasedString( str, caseSensitivity) ); } StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity ) { return StdString::ContainsMatcher( StdString::CasedString( str, caseSensitivity) ); } StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) { return StdString::EndsWithMatcher( StdString::CasedString( str, caseSensitivity) ); } StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) { return StdString::StartsWithMatcher( StdString::CasedString( str, caseSensitivity) ); } } // namespace Matchers } // namespace Catch // #included from: ../reporters/catch_reporter_multi.hpp #define TWOBLUECUBES_CATCH_REPORTER_MULTI_HPP_INCLUDED namespace Catch { class MultipleReporters : public SharedImpl { typedef std::vector > Reporters; Reporters m_reporters; public: void add( Ptr const& reporter ) { m_reporters.push_back( reporter ); } public: // IStreamingReporter virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE { return m_reporters[0]->getPreferences(); } virtual void noMatchingTestCases( std::string const& spec ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->noMatchingTestCases( spec ); } virtual void testRunStarting( TestRunInfo const& testRunInfo ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->testRunStarting( testRunInfo ); } virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->testGroupStarting( groupInfo ); } virtual void testCaseStarting( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->testCaseStarting( testInfo ); } virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->sectionStarting( sectionInfo ); } virtual void assertionStarting( AssertionInfo const& assertionInfo ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->assertionStarting( assertionInfo ); } // The return value indicates if the messages buffer should be cleared: virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { bool clearBuffer = false; for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) clearBuffer |= (*it)->assertionEnded( assertionStats ); return clearBuffer; } virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->sectionEnded( sectionStats ); } virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->testCaseEnded( testCaseStats ); } virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->testGroupEnded( testGroupStats ); } virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->testRunEnded( testRunStats ); } virtual void skipTest( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); it != itEnd; ++it ) (*it)->skipTest( testInfo ); } virtual MultipleReporters* tryAsMulti() CATCH_OVERRIDE { return this; } }; Ptr addReporter( Ptr const& existingReporter, Ptr const& additionalReporter ) { Ptr resultingReporter; if( existingReporter ) { MultipleReporters* multi = existingReporter->tryAsMulti(); if( !multi ) { multi = new MultipleReporters; resultingReporter = Ptr( multi ); if( existingReporter ) multi->add( existingReporter ); } else resultingReporter = existingReporter; multi->add( additionalReporter ); } else resultingReporter = additionalReporter; return resultingReporter; } } // end namespace Catch // #included from: ../reporters/catch_reporter_xml.hpp #define TWOBLUECUBES_CATCH_REPORTER_XML_HPP_INCLUDED // #included from: catch_reporter_bases.hpp #define TWOBLUECUBES_CATCH_REPORTER_BASES_HPP_INCLUDED #include #include #include #include namespace Catch { namespace { // Because formatting using c++ streams is stateful, drop down to C is required // Alternatively we could use stringstream, but its performance is... not good. std::string getFormattedDuration( double duration ) { // Max exponent + 1 is required to represent the whole part // + 1 for decimal point // + 3 for the 3 decimal places // + 1 for null terminator const size_t maxDoubleSize = DBL_MAX_10_EXP + 1 + 1 + 3 + 1; char buffer[maxDoubleSize]; // Save previous errno, to prevent sprintf from overwriting it ErrnoGuard guard; #ifdef _MSC_VER sprintf_s(buffer, "%.3f", duration); #else sprintf(buffer, "%.3f", duration); #endif return std::string(buffer); } } struct StreamingReporterBase : SharedImpl { StreamingReporterBase( ReporterConfig const& _config ) : m_config( _config.fullConfig() ), stream( _config.stream() ) { m_reporterPrefs.shouldRedirectStdOut = false; } virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE { return m_reporterPrefs; } virtual ~StreamingReporterBase() CATCH_OVERRIDE; virtual void noMatchingTestCases( std::string const& ) CATCH_OVERRIDE {} virtual void testRunStarting( TestRunInfo const& _testRunInfo ) CATCH_OVERRIDE { currentTestRunInfo = _testRunInfo; } virtual void testGroupStarting( GroupInfo const& _groupInfo ) CATCH_OVERRIDE { currentGroupInfo = _groupInfo; } virtual void testCaseStarting( TestCaseInfo const& _testInfo ) CATCH_OVERRIDE { currentTestCaseInfo = _testInfo; } virtual void sectionStarting( SectionInfo const& _sectionInfo ) CATCH_OVERRIDE { m_sectionStack.push_back( _sectionInfo ); } virtual void sectionEnded( SectionStats const& /* _sectionStats */ ) CATCH_OVERRIDE { m_sectionStack.pop_back(); } virtual void testCaseEnded( TestCaseStats const& /* _testCaseStats */ ) CATCH_OVERRIDE { currentTestCaseInfo.reset(); } virtual void testGroupEnded( TestGroupStats const& /* _testGroupStats */ ) CATCH_OVERRIDE { currentGroupInfo.reset(); } virtual void testRunEnded( TestRunStats const& /* _testRunStats */ ) CATCH_OVERRIDE { currentTestCaseInfo.reset(); currentGroupInfo.reset(); currentTestRunInfo.reset(); } virtual void skipTest( TestCaseInfo const& ) CATCH_OVERRIDE { // Don't do anything with this by default. // It can optionally be overridden in the derived class. } Ptr m_config; std::ostream& stream; LazyStat currentTestRunInfo; LazyStat currentGroupInfo; LazyStat currentTestCaseInfo; std::vector m_sectionStack; ReporterPreferences m_reporterPrefs; }; struct CumulativeReporterBase : SharedImpl { template struct Node : SharedImpl<> { explicit Node( T const& _value ) : value( _value ) {} virtual ~Node() {} typedef std::vector > ChildNodes; T value; ChildNodes children; }; struct SectionNode : SharedImpl<> { explicit SectionNode( SectionStats const& _stats ) : stats( _stats ) {} virtual ~SectionNode(); bool operator == ( SectionNode const& other ) const { return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo; } bool operator == ( Ptr const& other ) const { return operator==( *other ); } SectionStats stats; typedef std::vector > ChildSections; typedef std::vector Assertions; ChildSections childSections; Assertions assertions; std::string stdOut; std::string stdErr; }; struct BySectionInfo { BySectionInfo( SectionInfo const& other ) : m_other( other ) {} BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {} bool operator() ( Ptr const& node ) const { return ((node->stats.sectionInfo.name == m_other.name) && (node->stats.sectionInfo.lineInfo == m_other.lineInfo)); } private: void operator=( BySectionInfo const& ); SectionInfo const& m_other; }; typedef Node TestCaseNode; typedef Node TestGroupNode; typedef Node TestRunNode; CumulativeReporterBase( ReporterConfig const& _config ) : m_config( _config.fullConfig() ), stream( _config.stream() ) { m_reporterPrefs.shouldRedirectStdOut = false; } ~CumulativeReporterBase(); virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE { return m_reporterPrefs; } virtual void testRunStarting( TestRunInfo const& ) CATCH_OVERRIDE {} virtual void testGroupStarting( GroupInfo const& ) CATCH_OVERRIDE {} virtual void testCaseStarting( TestCaseInfo const& ) CATCH_OVERRIDE {} virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE { SectionStats incompleteStats( sectionInfo, Counts(), 0, false ); Ptr node; if( m_sectionStack.empty() ) { if( !m_rootSection ) m_rootSection = new SectionNode( incompleteStats ); node = m_rootSection; } else { SectionNode& parentNode = *m_sectionStack.back(); SectionNode::ChildSections::const_iterator it = std::find_if( parentNode.childSections.begin(), parentNode.childSections.end(), BySectionInfo( sectionInfo ) ); if( it == parentNode.childSections.end() ) { node = new SectionNode( incompleteStats ); parentNode.childSections.push_back( node ); } else node = *it; } m_sectionStack.push_back( node ); m_deepestSection = node; } virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {} virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { assert( !m_sectionStack.empty() ); SectionNode& sectionNode = *m_sectionStack.back(); sectionNode.assertions.push_back( assertionStats ); // AssertionResult holds a pointer to a temporary DecomposedExpression, // which getExpandedExpression() calls to build the expression string. // Our section stack copy of the assertionResult will likely outlive the // temporary, so it must be expanded or discarded now to avoid calling // a destroyed object later. prepareExpandedExpression( sectionNode.assertions.back().assertionResult ); return true; } virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE { assert( !m_sectionStack.empty() ); SectionNode& node = *m_sectionStack.back(); node.stats = sectionStats; m_sectionStack.pop_back(); } virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { Ptr node = new TestCaseNode( testCaseStats ); assert( m_sectionStack.size() == 0 ); node->children.push_back( m_rootSection ); m_testCases.push_back( node ); m_rootSection.reset(); assert( m_deepestSection ); m_deepestSection->stdOut = testCaseStats.stdOut; m_deepestSection->stdErr = testCaseStats.stdErr; } virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { Ptr node = new TestGroupNode( testGroupStats ); node->children.swap( m_testCases ); m_testGroups.push_back( node ); } virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE { Ptr node = new TestRunNode( testRunStats ); node->children.swap( m_testGroups ); m_testRuns.push_back( node ); testRunEndedCumulative(); } virtual void testRunEndedCumulative() = 0; virtual void skipTest( TestCaseInfo const& ) CATCH_OVERRIDE {} virtual void prepareExpandedExpression( AssertionResult& result ) const { if( result.isOk() ) result.discardDecomposedExpression(); else result.expandDecomposedExpression(); } Ptr m_config; std::ostream& stream; std::vector m_assertions; std::vector > > m_sections; std::vector > m_testCases; std::vector > m_testGroups; std::vector > m_testRuns; Ptr m_rootSection; Ptr m_deepestSection; std::vector > m_sectionStack; ReporterPreferences m_reporterPrefs; }; template char const* getLineOfChars() { static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0}; if( !*line ) { std::memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 ); line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0; } return line; } struct TestEventListenerBase : StreamingReporterBase { TestEventListenerBase( ReporterConfig const& _config ) : StreamingReporterBase( _config ) {} virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {} virtual bool assertionEnded( AssertionStats const& ) CATCH_OVERRIDE { return false; } }; } // end namespace Catch // #included from: ../internal/catch_reporter_registrars.hpp #define TWOBLUECUBES_CATCH_REPORTER_REGISTRARS_HPP_INCLUDED namespace Catch { template class LegacyReporterRegistrar { class ReporterFactory : public IReporterFactory { virtual IStreamingReporter* create( ReporterConfig const& config ) const { return new LegacyReporterAdapter( new T( config ) ); } virtual std::string getDescription() const { return T::getDescription(); } }; public: LegacyReporterRegistrar( std::string const& name ) { getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); } }; template class ReporterRegistrar { class ReporterFactory : public SharedImpl { // *** Please Note ***: // - If you end up here looking at a compiler error because it's trying to register // your custom reporter class be aware that the native reporter interface has changed // to IStreamingReporter. The "legacy" interface, IReporter, is still supported via // an adapter. Just use REGISTER_LEGACY_REPORTER to take advantage of the adapter. // However please consider updating to the new interface as the old one is now // deprecated and will probably be removed quite soon! // Please contact me via github if you have any questions at all about this. // In fact, ideally, please contact me anyway to let me know you've hit this - as I have // no idea who is actually using custom reporters at all (possibly no-one!). // The new interface is designed to minimise exposure to interface changes in the future. virtual IStreamingReporter* create( ReporterConfig const& config ) const { return new T( config ); } virtual std::string getDescription() const { return T::getDescription(); } }; public: ReporterRegistrar( std::string const& name ) { getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); } }; template class ListenerRegistrar { class ListenerFactory : public SharedImpl { virtual IStreamingReporter* create( ReporterConfig const& config ) const { return new T( config ); } virtual std::string getDescription() const { return std::string(); } }; public: ListenerRegistrar() { getMutableRegistryHub().registerListener( new ListenerFactory() ); } }; } #define INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) \ namespace{ Catch::LegacyReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } #define INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) \ namespace{ Catch::ReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } // Deprecated - use the form without INTERNAL_ #define INTERNAL_CATCH_REGISTER_LISTENER( listenerType ) \ namespace{ Catch::ListenerRegistrar catch_internal_RegistrarFor##listenerType; } #define CATCH_REGISTER_LISTENER( listenerType ) \ namespace{ Catch::ListenerRegistrar catch_internal_RegistrarFor##listenerType; } // #included from: ../internal/catch_xmlwriter.hpp #define TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED #include #include #include #include namespace Catch { class XmlEncode { public: enum ForWhat { ForTextNodes, ForAttributes }; XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ) : m_str( str ), m_forWhat( forWhat ) {} void encodeTo( std::ostream& os ) const { // Apostrophe escaping not necessary if we always use " to write attributes // (see: http://www.w3.org/TR/xml/#syntax) for( std::size_t i = 0; i < m_str.size(); ++ i ) { char c = m_str[i]; switch( c ) { case '<': os << "<"; break; case '&': os << "&"; break; case '>': // See: http://www.w3.org/TR/xml/#syntax if( i > 2 && m_str[i-1] == ']' && m_str[i-2] == ']' ) os << ">"; else os << c; break; case '\"': if( m_forWhat == ForAttributes ) os << """; else os << c; break; default: // Escape control chars - based on contribution by @espenalb in PR #465 and // by @mrpi PR #588 if ( ( c >= 0 && c < '\x09' ) || ( c > '\x0D' && c < '\x20') || c=='\x7F' ) { // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 os << "\\x" << std::uppercase << std::hex << std::setfill('0') << std::setw(2) << static_cast( c ); } else os << c; } } } friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { xmlEncode.encodeTo( os ); return os; } private: std::string m_str; ForWhat m_forWhat; }; class XmlWriter { public: class ScopedElement { public: ScopedElement( XmlWriter* writer ) : m_writer( writer ) {} ScopedElement( ScopedElement const& other ) : m_writer( other.m_writer ){ other.m_writer = CATCH_NULL; } ~ScopedElement() { if( m_writer ) m_writer->endElement(); } ScopedElement& writeText( std::string const& text, bool indent = true ) { m_writer->writeText( text, indent ); return *this; } template ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { m_writer->writeAttribute( name, attribute ); return *this; } private: mutable XmlWriter* m_writer; }; XmlWriter() : m_tagIsOpen( false ), m_needsNewline( false ), m_os( Catch::cout() ) { writeDeclaration(); } XmlWriter( std::ostream& os ) : m_tagIsOpen( false ), m_needsNewline( false ), m_os( os ) { writeDeclaration(); } ~XmlWriter() { while( !m_tags.empty() ) endElement(); } XmlWriter& startElement( std::string const& name ) { ensureTagClosed(); newlineIfNecessary(); m_os << m_indent << '<' << name; m_tags.push_back( name ); m_indent += " "; m_tagIsOpen = true; return *this; } ScopedElement scopedElement( std::string const& name ) { ScopedElement scoped( this ); startElement( name ); return scoped; } XmlWriter& endElement() { newlineIfNecessary(); m_indent = m_indent.substr( 0, m_indent.size()-2 ); if( m_tagIsOpen ) { m_os << "/>"; m_tagIsOpen = false; } else { m_os << m_indent << ""; } m_os << std::endl; m_tags.pop_back(); return *this; } XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ) { if( !name.empty() && !attribute.empty() ) m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; return *this; } XmlWriter& writeAttribute( std::string const& name, bool attribute ) { m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"'; return *this; } template XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { std::ostringstream oss; oss << attribute; return writeAttribute( name, oss.str() ); } XmlWriter& writeText( std::string const& text, bool indent = true ) { if( !text.empty() ){ bool tagWasOpen = m_tagIsOpen; ensureTagClosed(); if( tagWasOpen && indent ) m_os << m_indent; m_os << XmlEncode( text ); m_needsNewline = true; } return *this; } XmlWriter& writeComment( std::string const& text ) { ensureTagClosed(); m_os << m_indent << ""; m_needsNewline = true; return *this; } void writeStylesheetRef( std::string const& url ) { m_os << "\n"; } XmlWriter& writeBlankLine() { ensureTagClosed(); m_os << '\n'; return *this; } void ensureTagClosed() { if( m_tagIsOpen ) { m_os << ">" << std::endl; m_tagIsOpen = false; } } private: XmlWriter( XmlWriter const& ); void operator=( XmlWriter const& ); void writeDeclaration() { m_os << "\n"; } void newlineIfNecessary() { if( m_needsNewline ) { m_os << std::endl; m_needsNewline = false; } } bool m_tagIsOpen; bool m_needsNewline; std::vector m_tags; std::string m_indent; std::ostream& m_os; }; } namespace Catch { class XmlReporter : public StreamingReporterBase { public: XmlReporter( ReporterConfig const& _config ) : StreamingReporterBase( _config ), m_xml(_config.stream()), m_sectionDepth( 0 ) { m_reporterPrefs.shouldRedirectStdOut = true; } virtual ~XmlReporter() CATCH_OVERRIDE; static std::string getDescription() { return "Reports test results as an XML document"; } virtual std::string getStylesheetRef() const { return std::string(); } void writeSourceInfo( SourceLineInfo const& sourceInfo ) { m_xml .writeAttribute( "filename", sourceInfo.file ) .writeAttribute( "line", sourceInfo.line ); } public: // StreamingReporterBase virtual void noMatchingTestCases( std::string const& s ) CATCH_OVERRIDE { StreamingReporterBase::noMatchingTestCases( s ); } virtual void testRunStarting( TestRunInfo const& testInfo ) CATCH_OVERRIDE { StreamingReporterBase::testRunStarting( testInfo ); std::string stylesheetRef = getStylesheetRef(); if( !stylesheetRef.empty() ) m_xml.writeStylesheetRef( stylesheetRef ); m_xml.startElement( "Catch" ); if( !m_config->name().empty() ) m_xml.writeAttribute( "name", m_config->name() ); } virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE { StreamingReporterBase::testGroupStarting( groupInfo ); m_xml.startElement( "Group" ) .writeAttribute( "name", groupInfo.name ); } virtual void testCaseStarting( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { StreamingReporterBase::testCaseStarting(testInfo); m_xml.startElement( "TestCase" ) .writeAttribute( "name", trim( testInfo.name ) ) .writeAttribute( "description", testInfo.description ) .writeAttribute( "tags", testInfo.tagsAsString ); writeSourceInfo( testInfo.lineInfo ); if ( m_config->showDurations() == ShowDurations::Always ) m_testCaseTimer.start(); m_xml.ensureTagClosed(); } virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE { StreamingReporterBase::sectionStarting( sectionInfo ); if( m_sectionDepth++ > 0 ) { m_xml.startElement( "Section" ) .writeAttribute( "name", trim( sectionInfo.name ) ) .writeAttribute( "description", sectionInfo.description ); writeSourceInfo( sectionInfo.lineInfo ); m_xml.ensureTagClosed(); } } virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE { } virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { AssertionResult const& result = assertionStats.assertionResult; bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); if( includeResults ) { // Print any info messages in tags. for( std::vector::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end(); it != itEnd; ++it ) { if( it->type == ResultWas::Info ) { m_xml.scopedElement( "Info" ) .writeText( it->message ); } else if ( it->type == ResultWas::Warning ) { m_xml.scopedElement( "Warning" ) .writeText( it->message ); } } } // Drop out if result was successful but we're not printing them. if( !includeResults && result.getResultType() != ResultWas::Warning ) return true; // Print the expression if there is one. if( result.hasExpression() ) { m_xml.startElement( "Expression" ) .writeAttribute( "success", result.succeeded() ) .writeAttribute( "type", result.getTestMacroName() ); writeSourceInfo( result.getSourceInfo() ); m_xml.scopedElement( "Original" ) .writeText( result.getExpression() ); m_xml.scopedElement( "Expanded" ) .writeText( result.getExpandedExpression() ); } // And... Print a result applicable to each result type. switch( result.getResultType() ) { case ResultWas::ThrewException: m_xml.startElement( "Exception" ); writeSourceInfo( result.getSourceInfo() ); m_xml.writeText( result.getMessage() ); m_xml.endElement(); break; case ResultWas::FatalErrorCondition: m_xml.startElement( "FatalErrorCondition" ); writeSourceInfo( result.getSourceInfo() ); m_xml.writeText( result.getMessage() ); m_xml.endElement(); break; case ResultWas::Info: m_xml.scopedElement( "Info" ) .writeText( result.getMessage() ); break; case ResultWas::Warning: // Warning will already have been written break; case ResultWas::ExplicitFailure: m_xml.startElement( "Failure" ); writeSourceInfo( result.getSourceInfo() ); m_xml.writeText( result.getMessage() ); m_xml.endElement(); break; default: break; } if( result.hasExpression() ) m_xml.endElement(); return true; } virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE { StreamingReporterBase::sectionEnded( sectionStats ); if( --m_sectionDepth > 0 ) { XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); e.writeAttribute( "successes", sectionStats.assertions.passed ); e.writeAttribute( "failures", sectionStats.assertions.failed ); e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk ); if ( m_config->showDurations() == ShowDurations::Always ) e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds ); m_xml.endElement(); } } virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { StreamingReporterBase::testCaseEnded( testCaseStats ); XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() ); if ( m_config->showDurations() == ShowDurations::Always ) e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() ); if( !testCaseStats.stdOut.empty() ) m_xml.scopedElement( "StdOut" ).writeText( trim( testCaseStats.stdOut ), false ); if( !testCaseStats.stdErr.empty() ) m_xml.scopedElement( "StdErr" ).writeText( trim( testCaseStats.stdErr ), false ); m_xml.endElement(); } virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { StreamingReporterBase::testGroupEnded( testGroupStats ); // TODO: Check testGroupStats.aborting and act accordingly. m_xml.scopedElement( "OverallResults" ) .writeAttribute( "successes", testGroupStats.totals.assertions.passed ) .writeAttribute( "failures", testGroupStats.totals.assertions.failed ) .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk ); m_xml.endElement(); } virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE { StreamingReporterBase::testRunEnded( testRunStats ); m_xml.scopedElement( "OverallResults" ) .writeAttribute( "successes", testRunStats.totals.assertions.passed ) .writeAttribute( "failures", testRunStats.totals.assertions.failed ) .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk ); m_xml.endElement(); } private: Timer m_testCaseTimer; XmlWriter m_xml; int m_sectionDepth; }; INTERNAL_CATCH_REGISTER_REPORTER( "xml", XmlReporter ) } // end namespace Catch // #included from: ../reporters/catch_reporter_junit.hpp #define TWOBLUECUBES_CATCH_REPORTER_JUNIT_HPP_INCLUDED #include namespace Catch { namespace { std::string getCurrentTimestamp() { // Beware, this is not reentrant because of backward compatibility issues // Also, UTC only, again because of backward compatibility (%z is C++11) time_t rawtime; std::time(&rawtime); const size_t timeStampSize = sizeof("2017-01-16T17:06:45Z"); #ifdef _MSC_VER std::tm timeInfo = {}; gmtime_s(&timeInfo, &rawtime); #else std::tm* timeInfo; timeInfo = std::gmtime(&rawtime); #endif char timeStamp[timeStampSize]; const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; #ifdef _MSC_VER std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); #else std::strftime(timeStamp, timeStampSize, fmt, timeInfo); #endif return std::string(timeStamp); } } class JunitReporter : public CumulativeReporterBase { public: JunitReporter( ReporterConfig const& _config ) : CumulativeReporterBase( _config ), xml( _config.stream() ), unexpectedExceptions( 0 ), m_okToFail( false ) { m_reporterPrefs.shouldRedirectStdOut = true; } virtual ~JunitReporter() CATCH_OVERRIDE; static std::string getDescription() { return "Reports test results in an XML format that looks like Ant's junitreport target"; } virtual void noMatchingTestCases( std::string const& /*spec*/ ) CATCH_OVERRIDE {} virtual void testRunStarting( TestRunInfo const& runInfo ) CATCH_OVERRIDE { CumulativeReporterBase::testRunStarting( runInfo ); xml.startElement( "testsuites" ); } virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE { suiteTimer.start(); stdOutForSuite.str(""); stdErrForSuite.str(""); unexpectedExceptions = 0; CumulativeReporterBase::testGroupStarting( groupInfo ); } virtual void testCaseStarting( TestCaseInfo const& testCaseInfo ) CATCH_OVERRIDE { m_okToFail = testCaseInfo.okToFail(); } virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail ) unexpectedExceptions++; return CumulativeReporterBase::assertionEnded( assertionStats ); } virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { stdOutForSuite << testCaseStats.stdOut; stdErrForSuite << testCaseStats.stdErr; CumulativeReporterBase::testCaseEnded( testCaseStats ); } virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { double suiteTime = suiteTimer.getElapsedSeconds(); CumulativeReporterBase::testGroupEnded( testGroupStats ); writeGroup( *m_testGroups.back(), suiteTime ); } virtual void testRunEndedCumulative() CATCH_OVERRIDE { xml.endElement(); } void writeGroup( TestGroupNode const& groupNode, double suiteTime ) { XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); TestGroupStats const& stats = groupNode.value; xml.writeAttribute( "name", stats.groupInfo.name ); xml.writeAttribute( "errors", unexpectedExceptions ); xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions ); xml.writeAttribute( "tests", stats.totals.assertions.total() ); xml.writeAttribute( "hostname", "tbd" ); // !TBD if( m_config->showDurations() == ShowDurations::Never ) xml.writeAttribute( "time", "" ); else xml.writeAttribute( "time", suiteTime ); xml.writeAttribute( "timestamp", getCurrentTimestamp() ); // Write test cases for( TestGroupNode::ChildNodes::const_iterator it = groupNode.children.begin(), itEnd = groupNode.children.end(); it != itEnd; ++it ) writeTestCase( **it ); xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite.str() ), false ); xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite.str() ), false ); } void writeTestCase( TestCaseNode const& testCaseNode ) { TestCaseStats const& stats = testCaseNode.value; // All test cases have exactly one section - which represents the // test case itself. That section may have 0-n nested sections assert( testCaseNode.children.size() == 1 ); SectionNode const& rootSection = *testCaseNode.children.front(); std::string className = stats.testInfo.className; if( className.empty() ) { if( rootSection.childSections.empty() ) className = "global"; } writeSection( className, "", rootSection ); } void writeSection( std::string const& className, std::string const& rootName, SectionNode const& sectionNode ) { std::string name = trim( sectionNode.stats.sectionInfo.name ); if( !rootName.empty() ) name = rootName + '/' + name; if( !sectionNode.assertions.empty() || !sectionNode.stdOut.empty() || !sectionNode.stdErr.empty() ) { XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); if( className.empty() ) { xml.writeAttribute( "classname", name ); xml.writeAttribute( "name", "root" ); } else { xml.writeAttribute( "classname", className ); xml.writeAttribute( "name", name ); } xml.writeAttribute( "time", Catch::toString( sectionNode.stats.durationInSeconds ) ); writeAssertions( sectionNode ); if( !sectionNode.stdOut.empty() ) xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false ); if( !sectionNode.stdErr.empty() ) xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false ); } for( SectionNode::ChildSections::const_iterator it = sectionNode.childSections.begin(), itEnd = sectionNode.childSections.end(); it != itEnd; ++it ) if( className.empty() ) writeSection( name, "", **it ); else writeSection( className, name, **it ); } void writeAssertions( SectionNode const& sectionNode ) { for( SectionNode::Assertions::const_iterator it = sectionNode.assertions.begin(), itEnd = sectionNode.assertions.end(); it != itEnd; ++it ) writeAssertion( *it ); } void writeAssertion( AssertionStats const& stats ) { AssertionResult const& result = stats.assertionResult; if( !result.isOk() ) { std::string elementName; switch( result.getResultType() ) { case ResultWas::ThrewException: case ResultWas::FatalErrorCondition: elementName = "error"; break; case ResultWas::ExplicitFailure: elementName = "failure"; break; case ResultWas::ExpressionFailed: elementName = "failure"; break; case ResultWas::DidntThrowException: elementName = "failure"; break; // We should never see these here: case ResultWas::Info: case ResultWas::Warning: case ResultWas::Ok: case ResultWas::Unknown: case ResultWas::FailureBit: case ResultWas::Exception: elementName = "internalError"; break; } XmlWriter::ScopedElement e = xml.scopedElement( elementName ); xml.writeAttribute( "message", result.getExpandedExpression() ); xml.writeAttribute( "type", result.getTestMacroName() ); std::ostringstream oss; if( !result.getMessage().empty() ) oss << result.getMessage() << '\n'; for( std::vector::const_iterator it = stats.infoMessages.begin(), itEnd = stats.infoMessages.end(); it != itEnd; ++it ) if( it->type == ResultWas::Info ) oss << it->message << '\n'; oss << "at " << result.getSourceInfo(); xml.writeText( oss.str(), false ); } } XmlWriter xml; Timer suiteTimer; std::ostringstream stdOutForSuite; std::ostringstream stdErrForSuite; unsigned int unexpectedExceptions; bool m_okToFail; }; INTERNAL_CATCH_REGISTER_REPORTER( "junit", JunitReporter ) } // end namespace Catch // #included from: ../reporters/catch_reporter_console.hpp #define TWOBLUECUBES_CATCH_REPORTER_CONSOLE_HPP_INCLUDED #include #include namespace Catch { struct ConsoleReporter : StreamingReporterBase { ConsoleReporter( ReporterConfig const& _config ) : StreamingReporterBase( _config ), m_headerPrinted( false ) {} virtual ~ConsoleReporter() CATCH_OVERRIDE; static std::string getDescription() { return "Reports test results as plain lines of text"; } virtual void noMatchingTestCases( std::string const& spec ) CATCH_OVERRIDE { stream << "No test cases matched '" << spec << '\'' << std::endl; } virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE { } virtual bool assertionEnded( AssertionStats const& _assertionStats ) CATCH_OVERRIDE { AssertionResult const& result = _assertionStats.assertionResult; bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); // Drop out if result was successful but we're not printing them. if( !includeResults && result.getResultType() != ResultWas::Warning ) return false; lazyPrint(); AssertionPrinter printer( stream, _assertionStats, includeResults ); printer.print(); stream << std::endl; return true; } virtual void sectionStarting( SectionInfo const& _sectionInfo ) CATCH_OVERRIDE { m_headerPrinted = false; StreamingReporterBase::sectionStarting( _sectionInfo ); } virtual void sectionEnded( SectionStats const& _sectionStats ) CATCH_OVERRIDE { if( _sectionStats.missingAssertions ) { lazyPrint(); Colour colour( Colour::ResultError ); if( m_sectionStack.size() > 1 ) stream << "\nNo assertions in section"; else stream << "\nNo assertions in test case"; stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl; } if( m_config->showDurations() == ShowDurations::Always ) { stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; } if( m_headerPrinted ) { m_headerPrinted = false; } StreamingReporterBase::sectionEnded( _sectionStats ); } virtual void testCaseEnded( TestCaseStats const& _testCaseStats ) CATCH_OVERRIDE { StreamingReporterBase::testCaseEnded( _testCaseStats ); m_headerPrinted = false; } virtual void testGroupEnded( TestGroupStats const& _testGroupStats ) CATCH_OVERRIDE { if( currentGroupInfo.used ) { printSummaryDivider(); stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; printTotals( _testGroupStats.totals ); stream << '\n' << std::endl; } StreamingReporterBase::testGroupEnded( _testGroupStats ); } virtual void testRunEnded( TestRunStats const& _testRunStats ) CATCH_OVERRIDE { printTotalsDivider( _testRunStats.totals ); printTotals( _testRunStats.totals ); stream << std::endl; StreamingReporterBase::testRunEnded( _testRunStats ); } private: class AssertionPrinter { void operator= ( AssertionPrinter const& ); public: AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) : stream( _stream ), stats( _stats ), result( _stats.assertionResult ), colour( Colour::None ), message( result.getMessage() ), messages( _stats.infoMessages ), printInfoMessages( _printInfoMessages ) { switch( result.getResultType() ) { case ResultWas::Ok: colour = Colour::Success; passOrFail = "PASSED"; //if( result.hasMessage() ) if( _stats.infoMessages.size() == 1 ) messageLabel = "with message"; if( _stats.infoMessages.size() > 1 ) messageLabel = "with messages"; break; case ResultWas::ExpressionFailed: if( result.isOk() ) { colour = Colour::Success; passOrFail = "FAILED - but was ok"; } else { colour = Colour::Error; passOrFail = "FAILED"; } if( _stats.infoMessages.size() == 1 ) messageLabel = "with message"; if( _stats.infoMessages.size() > 1 ) messageLabel = "with messages"; break; case ResultWas::ThrewException: colour = Colour::Error; passOrFail = "FAILED"; messageLabel = "due to unexpected exception with "; if (_stats.infoMessages.size() == 1) messageLabel += "message"; if (_stats.infoMessages.size() > 1) messageLabel += "messages"; break; case ResultWas::FatalErrorCondition: colour = Colour::Error; passOrFail = "FAILED"; messageLabel = "due to a fatal error condition"; break; case ResultWas::DidntThrowException: colour = Colour::Error; passOrFail = "FAILED"; messageLabel = "because no exception was thrown where one was expected"; break; case ResultWas::Info: messageLabel = "info"; break; case ResultWas::Warning: messageLabel = "warning"; break; case ResultWas::ExplicitFailure: passOrFail = "FAILED"; colour = Colour::Error; if( _stats.infoMessages.size() == 1 ) messageLabel = "explicitly with message"; if( _stats.infoMessages.size() > 1 ) messageLabel = "explicitly with messages"; break; // These cases are here to prevent compiler warnings case ResultWas::Unknown: case ResultWas::FailureBit: case ResultWas::Exception: passOrFail = "** internal error **"; colour = Colour::Error; break; } } void print() const { printSourceInfo(); if( stats.totals.assertions.total() > 0 ) { if( result.isOk() ) stream << '\n'; printResultType(); printOriginalExpression(); printReconstructedExpression(); } else { stream << '\n'; } printMessage(); } private: void printResultType() const { if( !passOrFail.empty() ) { Colour colourGuard( colour ); stream << passOrFail << ":\n"; } } void printOriginalExpression() const { if( result.hasExpression() ) { Colour colourGuard( Colour::OriginalExpression ); stream << " "; stream << result.getExpressionInMacro(); stream << '\n'; } } void printReconstructedExpression() const { if( result.hasExpandedExpression() ) { stream << "with expansion:\n"; Colour colourGuard( Colour::ReconstructedExpression ); stream << Text( result.getExpandedExpression(), TextAttributes().setIndent(2) ) << '\n'; } } void printMessage() const { if( !messageLabel.empty() ) stream << messageLabel << ':' << '\n'; for( std::vector::const_iterator it = messages.begin(), itEnd = messages.end(); it != itEnd; ++it ) { // If this assertion is a warning ignore any INFO messages if( printInfoMessages || it->type != ResultWas::Info ) stream << Text( it->message, TextAttributes().setIndent(2) ) << '\n'; } } void printSourceInfo() const { Colour colourGuard( Colour::FileName ); stream << result.getSourceInfo() << ": "; } std::ostream& stream; AssertionStats const& stats; AssertionResult const& result; Colour::Code colour; std::string passOrFail; std::string messageLabel; std::string message; std::vector messages; bool printInfoMessages; }; void lazyPrint() { if( !currentTestRunInfo.used ) lazyPrintRunInfo(); if( !currentGroupInfo.used ) lazyPrintGroupInfo(); if( !m_headerPrinted ) { printTestCaseAndSectionHeader(); m_headerPrinted = true; } } void lazyPrintRunInfo() { stream << '\n' << getLineOfChars<'~'>() << '\n'; Colour colour( Colour::SecondaryText ); stream << currentTestRunInfo->name << " is a Catch v" << libraryVersion() << " host application.\n" << "Run with -? for options\n\n"; if( m_config->rngSeed() != 0 ) stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n"; currentTestRunInfo.used = true; } void lazyPrintGroupInfo() { if( !currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1 ) { printClosedHeader( "Group: " + currentGroupInfo->name ); currentGroupInfo.used = true; } } void printTestCaseAndSectionHeader() { assert( !m_sectionStack.empty() ); printOpenHeader( currentTestCaseInfo->name ); if( m_sectionStack.size() > 1 ) { Colour colourGuard( Colour::Headers ); std::vector::const_iterator it = m_sectionStack.begin()+1, // Skip first section (test case) itEnd = m_sectionStack.end(); for( ; it != itEnd; ++it ) printHeaderString( it->name, 2 ); } SourceLineInfo lineInfo = m_sectionStack.back().lineInfo; if( !lineInfo.empty() ){ stream << getLineOfChars<'-'>() << '\n'; Colour colourGuard( Colour::FileName ); stream << lineInfo << '\n'; } stream << getLineOfChars<'.'>() << '\n' << std::endl; } void printClosedHeader( std::string const& _name ) { printOpenHeader( _name ); stream << getLineOfChars<'.'>() << '\n'; } void printOpenHeader( std::string const& _name ) { stream << getLineOfChars<'-'>() << '\n'; { Colour colourGuard( Colour::Headers ); printHeaderString( _name ); } } // if string has a : in first line will set indent to follow it on // subsequent lines void printHeaderString( std::string const& _string, std::size_t indent = 0 ) { std::size_t i = _string.find( ": " ); if( i != std::string::npos ) i+=2; else i = 0; stream << Text( _string, TextAttributes() .setIndent( indent+i) .setInitialIndent( indent ) ) << '\n'; } struct SummaryColumn { SummaryColumn( std::string const& _label, Colour::Code _colour ) : label( _label ), colour( _colour ) {} SummaryColumn addRow( std::size_t count ) { std::ostringstream oss; oss << count; std::string row = oss.str(); for( std::vector::iterator it = rows.begin(); it != rows.end(); ++it ) { while( it->size() < row.size() ) *it = ' ' + *it; while( it->size() > row.size() ) row = ' ' + row; } rows.push_back( row ); return *this; } std::string label; Colour::Code colour; std::vector rows; }; void printTotals( Totals const& totals ) { if( totals.testCases.total() == 0 ) { stream << Colour( Colour::Warning ) << "No tests ran\n"; } else if( totals.assertions.total() > 0 && totals.testCases.allPassed() ) { stream << Colour( Colour::ResultSuccess ) << "All tests passed"; stream << " (" << pluralise( totals.assertions.passed, "assertion" ) << " in " << pluralise( totals.testCases.passed, "test case" ) << ')' << '\n'; } else { std::vector columns; columns.push_back( SummaryColumn( "", Colour::None ) .addRow( totals.testCases.total() ) .addRow( totals.assertions.total() ) ); columns.push_back( SummaryColumn( "passed", Colour::Success ) .addRow( totals.testCases.passed ) .addRow( totals.assertions.passed ) ); columns.push_back( SummaryColumn( "failed", Colour::ResultError ) .addRow( totals.testCases.failed ) .addRow( totals.assertions.failed ) ); columns.push_back( SummaryColumn( "failed as expected", Colour::ResultExpectedFailure ) .addRow( totals.testCases.failedButOk ) .addRow( totals.assertions.failedButOk ) ); printSummaryRow( "test cases", columns, 0 ); printSummaryRow( "assertions", columns, 1 ); } } void printSummaryRow( std::string const& label, std::vector const& cols, std::size_t row ) { for( std::vector::const_iterator it = cols.begin(); it != cols.end(); ++it ) { std::string value = it->rows[row]; if( it->label.empty() ) { stream << label << ": "; if( value != "0" ) stream << value; else stream << Colour( Colour::Warning ) << "- none -"; } else if( value != "0" ) { stream << Colour( Colour::LightGrey ) << " | "; stream << Colour( it->colour ) << value << ' ' << it->label; } } stream << '\n'; } static std::size_t makeRatio( std::size_t number, std::size_t total ) { std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number/ total : 0; return ( ratio == 0 && number > 0 ) ? 1 : ratio; } static std::size_t& findMax( std::size_t& i, std::size_t& j, std::size_t& k ) { if( i > j && i > k ) return i; else if( j > k ) return j; else return k; } void printTotalsDivider( Totals const& totals ) { if( totals.testCases.total() > 0 ) { std::size_t failedRatio = makeRatio( totals.testCases.failed, totals.testCases.total() ); std::size_t failedButOkRatio = makeRatio( totals.testCases.failedButOk, totals.testCases.total() ); std::size_t passedRatio = makeRatio( totals.testCases.passed, totals.testCases.total() ); while( failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH-1 ) findMax( failedRatio, failedButOkRatio, passedRatio )++; while( failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH-1 ) findMax( failedRatio, failedButOkRatio, passedRatio )--; stream << Colour( Colour::Error ) << std::string( failedRatio, '=' ); stream << Colour( Colour::ResultExpectedFailure ) << std::string( failedButOkRatio, '=' ); if( totals.testCases.allPassed() ) stream << Colour( Colour::ResultSuccess ) << std::string( passedRatio, '=' ); else stream << Colour( Colour::Success ) << std::string( passedRatio, '=' ); } else { stream << Colour( Colour::Warning ) << std::string( CATCH_CONFIG_CONSOLE_WIDTH-1, '=' ); } stream << '\n'; } void printSummaryDivider() { stream << getLineOfChars<'-'>() << '\n'; } private: bool m_headerPrinted; }; INTERNAL_CATCH_REGISTER_REPORTER( "console", ConsoleReporter ) } // end namespace Catch // #included from: ../reporters/catch_reporter_compact.hpp #define TWOBLUECUBES_CATCH_REPORTER_COMPACT_HPP_INCLUDED namespace Catch { struct CompactReporter : StreamingReporterBase { CompactReporter( ReporterConfig const& _config ) : StreamingReporterBase( _config ) {} virtual ~CompactReporter(); static std::string getDescription() { return "Reports test results on a single line, suitable for IDEs"; } virtual ReporterPreferences getPreferences() const { ReporterPreferences prefs; prefs.shouldRedirectStdOut = false; return prefs; } virtual void noMatchingTestCases( std::string const& spec ) { stream << "No test cases matched '" << spec << '\'' << std::endl; } virtual void assertionStarting( AssertionInfo const& ) {} virtual bool assertionEnded( AssertionStats const& _assertionStats ) { AssertionResult const& result = _assertionStats.assertionResult; bool printInfoMessages = true; // Drop out if result was successful and we're not printing those if( !m_config->includeSuccessfulResults() && result.isOk() ) { if( result.getResultType() != ResultWas::Warning ) return false; printInfoMessages = false; } AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); printer.print(); stream << std::endl; return true; } virtual void sectionEnded(SectionStats const& _sectionStats) CATCH_OVERRIDE { if (m_config->showDurations() == ShowDurations::Always) { stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; } } virtual void testRunEnded( TestRunStats const& _testRunStats ) { printTotals( _testRunStats.totals ); stream << '\n' << std::endl; StreamingReporterBase::testRunEnded( _testRunStats ); } private: class AssertionPrinter { void operator= ( AssertionPrinter const& ); public: AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) : stream( _stream ) , stats( _stats ) , result( _stats.assertionResult ) , messages( _stats.infoMessages ) , itMessage( _stats.infoMessages.begin() ) , printInfoMessages( _printInfoMessages ) {} void print() { printSourceInfo(); itMessage = messages.begin(); switch( result.getResultType() ) { case ResultWas::Ok: printResultType( Colour::ResultSuccess, passedString() ); printOriginalExpression(); printReconstructedExpression(); if ( ! result.hasExpression() ) printRemainingMessages( Colour::None ); else printRemainingMessages(); break; case ResultWas::ExpressionFailed: if( result.isOk() ) printResultType( Colour::ResultSuccess, failedString() + std::string( " - but was ok" ) ); else printResultType( Colour::Error, failedString() ); printOriginalExpression(); printReconstructedExpression(); printRemainingMessages(); break; case ResultWas::ThrewException: printResultType( Colour::Error, failedString() ); printIssue( "unexpected exception with message:" ); printMessage(); printExpressionWas(); printRemainingMessages(); break; case ResultWas::FatalErrorCondition: printResultType( Colour::Error, failedString() ); printIssue( "fatal error condition with message:" ); printMessage(); printExpressionWas(); printRemainingMessages(); break; case ResultWas::DidntThrowException: printResultType( Colour::Error, failedString() ); printIssue( "expected exception, got none" ); printExpressionWas(); printRemainingMessages(); break; case ResultWas::Info: printResultType( Colour::None, "info" ); printMessage(); printRemainingMessages(); break; case ResultWas::Warning: printResultType( Colour::None, "warning" ); printMessage(); printRemainingMessages(); break; case ResultWas::ExplicitFailure: printResultType( Colour::Error, failedString() ); printIssue( "explicitly" ); printRemainingMessages( Colour::None ); break; // These cases are here to prevent compiler warnings case ResultWas::Unknown: case ResultWas::FailureBit: case ResultWas::Exception: printResultType( Colour::Error, "** internal error **" ); break; } } private: // Colour::LightGrey static Colour::Code dimColour() { return Colour::FileName; } #ifdef CATCH_PLATFORM_MAC static const char* failedString() { return "FAILED"; } static const char* passedString() { return "PASSED"; } #else static const char* failedString() { return "failed"; } static const char* passedString() { return "passed"; } #endif void printSourceInfo() const { Colour colourGuard( Colour::FileName ); stream << result.getSourceInfo() << ':'; } void printResultType( Colour::Code colour, std::string const& passOrFail ) const { if( !passOrFail.empty() ) { { Colour colourGuard( colour ); stream << ' ' << passOrFail; } stream << ':'; } } void printIssue( std::string const& issue ) const { stream << ' ' << issue; } void printExpressionWas() { if( result.hasExpression() ) { stream << ';'; { Colour colour( dimColour() ); stream << " expression was:"; } printOriginalExpression(); } } void printOriginalExpression() const { if( result.hasExpression() ) { stream << ' ' << result.getExpression(); } } void printReconstructedExpression() const { if( result.hasExpandedExpression() ) { { Colour colour( dimColour() ); stream << " for: "; } stream << result.getExpandedExpression(); } } void printMessage() { if ( itMessage != messages.end() ) { stream << " '" << itMessage->message << '\''; ++itMessage; } } void printRemainingMessages( Colour::Code colour = dimColour() ) { if ( itMessage == messages.end() ) return; // using messages.end() directly yields compilation error: std::vector::const_iterator itEnd = messages.end(); const std::size_t N = static_cast( std::distance( itMessage, itEnd ) ); { Colour colourGuard( colour ); stream << " with " << pluralise( N, "message" ) << ':'; } for(; itMessage != itEnd; ) { // If this assertion is a warning ignore any INFO messages if( printInfoMessages || itMessage->type != ResultWas::Info ) { stream << " '" << itMessage->message << '\''; if ( ++itMessage != itEnd ) { Colour colourGuard( dimColour() ); stream << " and"; } } } } private: std::ostream& stream; AssertionStats const& stats; AssertionResult const& result; std::vector messages; std::vector::const_iterator itMessage; bool printInfoMessages; }; // Colour, message variants: // - white: No tests ran. // - red: Failed [both/all] N test cases, failed [both/all] M assertions. // - white: Passed [both/all] N test cases (no assertions). // - red: Failed N tests cases, failed M assertions. // - green: Passed [both/all] N tests cases with M assertions. std::string bothOrAll( std::size_t count ) const { return count == 1 ? std::string() : count == 2 ? "both " : "all " ; } void printTotals( const Totals& totals ) const { if( totals.testCases.total() == 0 ) { stream << "No tests ran."; } else if( totals.testCases.failed == totals.testCases.total() ) { Colour colour( Colour::ResultError ); const std::string qualify_assertions_failed = totals.assertions.failed == totals.assertions.total() ? bothOrAll( totals.assertions.failed ) : std::string(); stream << "Failed " << bothOrAll( totals.testCases.failed ) << pluralise( totals.testCases.failed, "test case" ) << ", " "failed " << qualify_assertions_failed << pluralise( totals.assertions.failed, "assertion" ) << '.'; } else if( totals.assertions.total() == 0 ) { stream << "Passed " << bothOrAll( totals.testCases.total() ) << pluralise( totals.testCases.total(), "test case" ) << " (no assertions)."; } else if( totals.assertions.failed ) { Colour colour( Colour::ResultError ); stream << "Failed " << pluralise( totals.testCases.failed, "test case" ) << ", " "failed " << pluralise( totals.assertions.failed, "assertion" ) << '.'; } else { Colour colour( Colour::ResultSuccess ); stream << "Passed " << bothOrAll( totals.testCases.passed ) << pluralise( totals.testCases.passed, "test case" ) << " with " << pluralise( totals.assertions.passed, "assertion" ) << '.'; } } }; INTERNAL_CATCH_REGISTER_REPORTER( "compact", CompactReporter ) } // end namespace Catch namespace Catch { // These are all here to avoid warnings about not having any out of line // virtual methods NonCopyable::~NonCopyable() {} IShared::~IShared() {} IStream::~IStream() CATCH_NOEXCEPT {} FileStream::~FileStream() CATCH_NOEXCEPT {} CoutStream::~CoutStream() CATCH_NOEXCEPT {} DebugOutStream::~DebugOutStream() CATCH_NOEXCEPT {} StreamBufBase::~StreamBufBase() CATCH_NOEXCEPT {} IContext::~IContext() {} IResultCapture::~IResultCapture() {} ITestCase::~ITestCase() {} ITestCaseRegistry::~ITestCaseRegistry() {} IRegistryHub::~IRegistryHub() {} IMutableRegistryHub::~IMutableRegistryHub() {} IExceptionTranslator::~IExceptionTranslator() {} IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() {} IReporter::~IReporter() {} IReporterFactory::~IReporterFactory() {} IReporterRegistry::~IReporterRegistry() {} IStreamingReporter::~IStreamingReporter() {} AssertionStats::~AssertionStats() {} SectionStats::~SectionStats() {} TestCaseStats::~TestCaseStats() {} TestGroupStats::~TestGroupStats() {} TestRunStats::~TestRunStats() {} CumulativeReporterBase::SectionNode::~SectionNode() {} CumulativeReporterBase::~CumulativeReporterBase() {} StreamingReporterBase::~StreamingReporterBase() {} ConsoleReporter::~ConsoleReporter() {} CompactReporter::~CompactReporter() {} IRunner::~IRunner() {} IMutableContext::~IMutableContext() {} IConfig::~IConfig() {} XmlReporter::~XmlReporter() {} JunitReporter::~JunitReporter() {} TestRegistry::~TestRegistry() {} FreeFunctionTestCase::~FreeFunctionTestCase() {} IGeneratorInfo::~IGeneratorInfo() {} IGeneratorsForTest::~IGeneratorsForTest() {} WildcardPattern::~WildcardPattern() {} TestSpec::Pattern::~Pattern() {} TestSpec::NamePattern::~NamePattern() {} TestSpec::TagPattern::~TagPattern() {} TestSpec::ExcludedPattern::~ExcludedPattern() {} Matchers::Impl::MatcherUntypedBase::~MatcherUntypedBase() {} void Config::dummy() {} namespace TestCaseTracking { ITracker::~ITracker() {} TrackerBase::~TrackerBase() {} SectionTracker::~SectionTracker() {} IndexTracker::~IndexTracker() {} } } #ifdef __clang__ #pragma clang diagnostic pop #endif #endif #ifdef CATCH_CONFIG_MAIN // #included from: internal/catch_default_main.hpp #define TWOBLUECUBES_CATCH_DEFAULT_MAIN_HPP_INCLUDED #ifndef __OBJC__ #if defined(WIN32) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN) // Standard C/C++ Win32 Unicode wmain entry point extern "C" int wmain (int argc, wchar_t * argv[], wchar_t * []) { #else // Standard C/C++ main entry point int main (int argc, char * argv[]) { #endif int result = Catch::Session().run( argc, argv ); return ( result < 0xff ? result : 0xff ); } #else // __OBJC__ // Objective-C entry point int main (int argc, char * const argv[]) { #if !CATCH_ARC_ENABLED NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; #endif Catch::registerTestMethods(); int result = Catch::Session().run( argc, (char* const*)argv ); #if !CATCH_ARC_ENABLED [pool drain]; #endif return ( result < 0xff ? result : 0xff ); } #endif // __OBJC__ #endif #ifdef CLARA_CONFIG_MAIN_NOT_DEFINED # undef CLARA_CONFIG_MAIN #endif ////// // If this config identifier is defined then all CATCH macros are prefixed with CATCH_ #ifdef CATCH_CONFIG_PREFIX_ALL #if defined(CATCH_CONFIG_FAST_COMPILE) #define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST_NO_TRY( "CATCH_REQUIRE", Catch::ResultDisposition::Normal, expr ) #define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST_NO_TRY( "CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, expr ) #else #define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE", Catch::ResultDisposition::Normal, expr ) #define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, expr ) #endif #define CATCH_REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS", Catch::ResultDisposition::Normal, "", expr ) #define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) #define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) #define CATCH_REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( "CATCH_REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, expr ) #define CATCH_CHECK( expr ) INTERNAL_CATCH_TEST( "CATCH_CHECK", Catch::ResultDisposition::ContinueOnFailure, expr ) #define CATCH_CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( "CATCH_CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, expr ) #define CATCH_CHECKED_IF( expr ) INTERNAL_CATCH_IF( "CATCH_CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, expr ) #define CATCH_CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( "CATCH_CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, expr ) #define CATCH_CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( "CATCH_CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, expr ) #define CATCH_CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, "", expr ) #define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) #define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) #define CATCH_CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( "CATCH_CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, expr ) #define CATCH_CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg ) #if defined(CATCH_CONFIG_FAST_COMPILE) #define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT_NO_TRY( "CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) #else #define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) #endif #define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg ) #define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( "CATCH_WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) #define CATCH_SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg ) #define CATCH_CAPTURE( msg ) INTERNAL_CATCH_INFO( "CATCH_CAPTURE", #msg " := " << Catch::toString(msg) ) #define CATCH_SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( "CATCH_CAPTURE", #msg " := " << Catch::toString(msg) ) #ifdef CATCH_CONFIG_VARIADIC_MACROS #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) #define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) #define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) #define CATCH_FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) #else #define CATCH_TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) #define CATCH_TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) #define CATCH_METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) #define CATCH_REGISTER_TEST_CASE( function, name, description ) INTERNAL_CATCH_REGISTER_TESTCASE( function, name, description ) #define CATCH_SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) #define CATCH_FAIL( msg ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, msg ) #define CATCH_FAIL_CHECK( msg ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, msg ) #define CATCH_SUCCEED( msg ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, msg ) #endif #define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) #define CATCH_REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) #define CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) #define CATCH_GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) // "BDD-style" convenience wrappers #ifdef CATCH_CONFIG_VARIADIC_MACROS #define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ ) #define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) #else #define CATCH_SCENARIO( name, tags ) CATCH_TEST_CASE( "Scenario: " name, tags ) #define CATCH_SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) #endif #define CATCH_GIVEN( desc ) CATCH_SECTION( std::string( "Given: ") + desc, "" ) #define CATCH_WHEN( desc ) CATCH_SECTION( std::string( " When: ") + desc, "" ) #define CATCH_AND_WHEN( desc ) CATCH_SECTION( std::string( " And: ") + desc, "" ) #define CATCH_THEN( desc ) CATCH_SECTION( std::string( " Then: ") + desc, "" ) #define CATCH_AND_THEN( desc ) CATCH_SECTION( std::string( " And: ") + desc, "" ) // If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required #else #if defined(CATCH_CONFIG_FAST_COMPILE) #define REQUIRE( expr ) INTERNAL_CATCH_TEST_NO_TRY( "REQUIRE", Catch::ResultDisposition::Normal, expr ) #define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST_NO_TRY( "REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, expr ) #else #define REQUIRE( expr ) INTERNAL_CATCH_TEST( "REQUIRE", Catch::ResultDisposition::Normal, expr ) #define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( "REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, expr ) #endif #define REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( "REQUIRE_THROWS", Catch::ResultDisposition::Normal, "", expr ) #define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) #define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( "REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) #define REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( "REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, expr ) #define CHECK( expr ) INTERNAL_CATCH_TEST( "CHECK", Catch::ResultDisposition::ContinueOnFailure, expr ) #define CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( "CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, expr ) #define CHECKED_IF( expr ) INTERNAL_CATCH_IF( "CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, expr ) #define CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( "CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, expr ) #define CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( "CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, expr ) #define CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( "CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, "", expr ) #define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) #define CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( "CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) #define CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( "CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, expr ) #define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg ) #if defined(CATCH_CONFIG_FAST_COMPILE) #define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT_NO_TRY( "REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) #else #define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) #endif #define INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg ) #define WARN( msg ) INTERNAL_CATCH_MSG( "WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) #define SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg ) #define CAPTURE( msg ) INTERNAL_CATCH_INFO( "CAPTURE", #msg " := " << Catch::toString(msg) ) #define SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( "CAPTURE", #msg " := " << Catch::toString(msg) ) #ifdef CATCH_CONFIG_VARIADIC_MACROS #define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) #define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) #define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) #define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) #define FAIL( ... ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) #define FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) #define SUCCEED( ... ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) #else #define TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) #define TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) #define METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) #define REGISTER_TEST_CASE( method, name, description ) INTERNAL_CATCH_REGISTER_TESTCASE( method, name, description ) #define SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) #define FAIL( msg ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, msg ) #define FAIL_CHECK( msg ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, msg ) #define SUCCEED( msg ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, msg ) #endif #define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) #define REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) #define REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) #define GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) #endif #define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) // "BDD-style" convenience wrappers #ifdef CATCH_CONFIG_VARIADIC_MACROS #define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ ) #define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) #else #define SCENARIO( name, tags ) TEST_CASE( "Scenario: " name, tags ) #define SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) #endif #define GIVEN( desc ) SECTION( std::string(" Given: ") + desc, "" ) #define WHEN( desc ) SECTION( std::string(" When: ") + desc, "" ) #define AND_WHEN( desc ) SECTION( std::string("And when: ") + desc, "" ) #define THEN( desc ) SECTION( std::string(" Then: ") + desc, "" ) #define AND_THEN( desc ) SECTION( std::string(" And: ") + desc, "" ) using Catch::Detail::Approx; // #included from: internal/catch_reenable_warnings.h #define TWOBLUECUBES_CATCH_REENABLE_WARNINGS_H_INCLUDED #ifdef __clang__ # ifdef __ICC // icpc defines the __clang__ macro # pragma warning(pop) # else # pragma clang diagnostic pop # endif #elif defined __GNUC__ # pragma GCC diagnostic pop #endif #endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED link-Link-3.0.2/.clang-format0000644000175000017500000000302013303226525016145 0ustar zmoelnigzmoelnigLanguage: Cpp AccessModifierOffset: -2 AlignAfterOpenBracket: DontAlign AlignEscapedNewlinesLeft: false AlignOperands: true AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: None AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: true AlwaysBreakTemplateDeclarations: true BinPackArguments: true BinPackParameters: false BreakBeforeBinaryOperators: NonAssignment BreakBeforeBraces: Allman BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: true ColumnLimit: 90 ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 2 ContinuationIndentWidth: 2 Cpp11BracedListStyle: true DerivePointerAlignment: false IndentCaseLabels: false IndentFunctionDeclarationAfterType: false IndentWidth: 2 IndentWrappedFunctionNames: false KeepEmptyLinesAtTheStartOfBlocks: true MaxEmptyLinesToKeep: 2 NamespaceIndentation: None PenaltyBreakBeforeFirstCallParameter: 0 PenaltyReturnTypeOnItsOwnLine: 1000 PointerAlignment: Left SpaceAfterCStyleCast: false SpaceBeforeAssignmentOperators: true SpaceBeforeParens: ControlStatements SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false Standard: Cpp11 UseTab: Never