diff --git a/tests/tool_scheduling_tests.cpp b/tests/tool_scheduling_tests.cpp index 5450f4c..ec334a8 100644 --- a/tests/tool_scheduling_tests.cpp +++ b/tests/tool_scheduling_tests.cpp @@ -9,11 +9,14 @@ #include #include +#include +#include #include #include #include #include #include +#include #include #include #include @@ -130,8 +133,26 @@ namespace struct Timeline { std::mutex mutex; + std::condition_variable recorded; std::map> runs; + /// Records that `run` started at `start` and ends now. + void record(const std::string& run, Clock::time_point start) + { + { + const std::lock_guard lock(mutex); + runs[run] = {start, Clock::now()}; + } + recorded.notify_all(); + } + + /// Whether `count` runs are recorded before `limit` passes. + [[nodiscard]] bool waitForRuns(std::size_t count, std::chrono::milliseconds limit) + { + std::unique_lock lock(mutex); + return recorded.wait_for(lock, limit, [&] { return runs.size() >= count; }); + } + [[nodiscard]] bool anyOverlap() { const std::lock_guard lock(mutex); @@ -168,10 +189,7 @@ namespace const Clock::time_point start = Clock::now(); std::this_thread::sleep_for(file == "slow.c" ? std::chrono::milliseconds(1000) : std::chrono::milliseconds(150)); - { - const std::lock_guard lock(m_timeline->mutex); - m_timeline->runs[file] = {start, Clock::now()}; - } + m_timeline->record(file, start); output.diagnostics( {{"timed", "rule", file, 1, 1, ctrace::Severity::Warning, "finding", ""}}); } @@ -222,8 +240,7 @@ namespace { const Clock::time_point start = Clock::now(); std::this_thread::sleep_for(std::chrono::milliseconds(100)); - const std::lock_guard lock(m_timeline->mutex); - m_timeline->runs[run] = {start, Clock::now()}; + m_timeline->record(run, start); } std::string m_name; @@ -231,12 +248,15 @@ namespace std::shared_ptr m_timeline; }; - /// Takes a while on each file, records when, and allows one run at a time. + /// Allows one run at a time. Each run waits for another tool to record `otherRuns` runs, + /// and fails if it has not within 10 s: it can only succeed if that tool runs while this + /// one holds a worker. It then takes a while and records when. class OneAtATimeTool : public ctrace::AnalysisToolBase { public: - explicit OneAtATimeTool(std::shared_ptr timeline) - : m_timeline(std::move(timeline)) + OneAtATimeTool(std::shared_ptr timeline, std::shared_ptr other, + std::size_t otherRuns) + : m_timeline(std::move(timeline)), m_other(std::move(other)), m_otherRuns(otherRuns) { } @@ -244,9 +264,13 @@ namespace ctrace::ToolOutput&) const override { const Clock::time_point start = Clock::now(); + const bool otherDone = m_other->waitForRuns(m_otherRuns, std::chrono::seconds(10)); std::this_thread::sleep_for(std::chrono::milliseconds(200)); - const std::lock_guard lock(m_timeline->mutex); - m_timeline->runs[file] = {start, Clock::now()}; + m_timeline->record(file, start); + if (!otherDone) + { + throw std::runtime_error("the other tool did not run while this one waited"); + } } [[nodiscard]] std::size_t maxConcurrentRuns() const override @@ -261,6 +285,8 @@ namespace private: std::shared_ptr m_timeline; + std::shared_ptr m_other; + std::size_t m_otherRuns; }; std::vector runTimedTool(std::launch policy, std::size_t workers, @@ -370,19 +396,21 @@ namespace } // A tool that allows one run at a time gets one, and does not keep the other tools waiting. + // The limited tool's runs wait for the other tool instead of racing it, so that the check + // does not depend on how fast the machine is. void testToolConcurrencyLimit(TestReport& report) { + const std::vector files = {"a.c", "b.c", "c.c"}; auto limited = std::make_shared(); auto timed = std::make_shared(); ctrace::ToolInvoker invoker(ctrace::ProgramConfig{}, poolFor(std::launch::async, 2)); - invoker.registerTool("one_at_a_time", std::make_unique(limited)); + invoker.registerTool("one_at_a_time", + std::make_unique(limited, timed, files.size())); invoker.registerTool("timed", std::make_unique(timed)); - invoker.runSpecificTools({"one_at_a_time", "timed"}, {"a.c", "b.c", "c.c"}); + invoker.runSpecificTools({"one_at_a_time", "timed"}, files); report.expect(!limited->anyOverlap(), "a tool limited to one run never runs twice at once"); - const Clock::time_point limitedEnd = limited->end("c.c"); - report.expect(timed->end("a.c") < limitedEnd && timed->end("b.c") < limitedEnd && - timed->end("c.c") < limitedEnd, + report.expect(invoker.failedTools().empty(), "the other tools run while the limited tool works through its files"); }