23namespace sequoia::testing
25 template<std::invocable Task>
27 std::chrono::duration<double> profile(Task task)
32 return t.time_elapsed();
77 template<test_mode Mode, std::invocable F, std::invocable S>
78 bool check_relative_performance(std::string_view description,
test_logger<Mode>& logger, F fast, S slow,
const double minSpeedUp,
const double maxSpeedUp,
const std::size_t trials,
const double num_sds,
const std::size_t maxAttempts)
80 if((minSpeedUp <= 1) || (maxSpeedUp <= 1))
81 throw std::logic_error{
"Relative performance test requires speed-up factors > 1"};
83 if(minSpeedUp > maxSpeedUp)
84 throw std::logic_error{
"maxSpeedUp must be >= minSpeedUp"};
87 throw std::logic_error{
"Number of standard deviations is required to be > 1"};
90 throw std::logic_error{
"Number of attempts is required to be > 0"};
93 throw std::logic_error{
"Number of trials is required to be > 4"};
95 using namespace std::chrono;
96 using namespace maths;
98 std::string summary{};
99 std::size_t remainingAttempts{maxAttempts};
103 [](
auto task, std::vector<double>& timings){
104 timings.push_back(profile(task).count());
108 while(remainingAttempts > 0)
110 const auto adjustedTrials{trials*(maxAttempts - remainingAttempts + 1)};
112 std::vector<double> fastData, slowData;
113 fastData.reserve(adjustedTrials);
114 slowData.reserve(adjustedTrials);
116 std::random_device generator;
117 for(std::size_t i{}; i < adjustedTrials; ++i)
119 std::uniform_real_distribution<double> distribution{0.0, 1.0};
120 const bool fastFirst{(distribution(generator) < 0.5)};
124 timer(fast, fastData);
125 timer(slow, slowData);
129 timer(slow, slowData);
130 timer(fast, fastData);
135 [](
auto first,
auto last) {
136 const auto data{sample_standard_deviation(first, last)};
137 return std::make_pair(data.first.value(), data.second.value());
141 std::ranges::sort(fastData);
142 std::ranges::sort(slowData);
144 const auto [sig_f, m_f]{compute_stats(fastData.cbegin()+1, fastData.cend()-1)};
145 const auto [sig_s, m_s]{compute_stats(slowData.cbegin()+1, slowData.cend()-1)};
147 if(m_f + sig_f < m_s - sig_s)
151 passed = (minSpeedUp * m_f <= (m_s + num_sds * sig_s))
152 && (maxSpeedUp * m_f >= (m_s - num_sds * sig_s));
156 passed = (m_s / maxSpeedUp <= (m_f + num_sds * sig_f))
157 && (m_s / minSpeedUp >= (m_f - num_sds * sig_f));
166 [num_sds](std::string_view prefix,
const auto mean,
const auto sig){
168 std::ostringstream message{};
169 message << mean <<
"s" <<
" +- " << num_sds <<
" * " << sig <<
"s";
171 return std::string{prefix}.append(
" Task duration: ").append(message.str());
176 [m_f, m_s, minSpeedUp, maxSpeedUp](){
177 std::ostringstream message{};
178 message <<
" [" << m_s / m_f <<
"; (" << minSpeedUp <<
", " << maxSpeedUp <<
")]";
180 return message.str();
184 summary = append_lines(stats(
"Fast", m_f, sig_f), stats(
"Slow", m_s, sig_s)).append(summarizer());
194 sentinel<Mode> sentry{logger, append_lines(description, summary)};
195 sentry.log_performance_check();
199 sentry.log_performance_failure(
"");
205 template<
class T,
class Period>
207 std::chrono::duration<T, Period> calibrate(std::chrono::duration<T, Period> target)
209 using namespace std::chrono;
211 std::array<double, 7> timings{};
212 for (
auto& t : timings)
214 t = profile([target]() { std::this_thread::sleep_for(target); }).count();
217 std::ranges::sort(timings);
218 const auto [sig_f, m_f] {maths::sample_standard_deviation(timings.cbegin() + 1, timings.cend() - 1)};
221 if ((m_f.value() - sig_f.value()) > duration_cast<duration<double>>(target).count())
223 constexpr auto inverse{Period::den / Period::num};
224 return std::chrono::duration<T, Period>{
static_cast<T
>(std::ceil(inverse* (m_f.value() + 5* sig_f.value())))};
234 template<test_mode Mode>
242 template<
class Self, std::invocable F, std::invocable S>
243 bool check_relative_performance(
this Self& self,
const reporter& description, F fast, S slow,
const double minSpeedUp,
const double maxSpeedUp,
const std::size_t trials=5,
const double num_sds=4)
245 return testing::check_relative_performance(self.report(description), self.m_Logger, fast, slow, minSpeedUp, maxSpeedUp, trials, num_sds, 3);
255 std::string_view postprocess(std::string_view testOutput, std::string_view referenceOutput);
259 template<test_mode Mode>
264 using duration =
typename base_type::duration;
266 using base_type::base_type;
282 template<concrete_test T>
283 requires std::is_base_of_v<basic_performance_test<T::mode>, T>
Contains utilities for automatically editing certain files as part of the test creation process.
Utilities for checking regular semantics.
Tools for statistical analysis.
test_mode
Specifies whether tests are run as standard tests or in false postive/negative mode.
Definition: TestMode.hpp:20
class template from which all concrete tests should derive.
Definition: FreeTestCore.hpp:144
Summaries data generated by the logger, for the purposes of reporting.
Definition: TestLogger.hpp:299
Definition: Output.hpp:176
Definition: TestLogger.hpp:277
Definition: TestLogger.hpp:183
Definition: FreeTestCore.hpp:31
Definition: FreeTestCore.hpp:241