/* * Copyright (C) 1996-2024 The Squid Software Foundation and contributors * * Squid software is distributed under GPLv2+ license and includes * contributions from numerous individuals and organizations. * Please see the COPYING and CONTRIBUTORS files for details. */ #include "squid.h" #include "AsyncEngine.h" #include "EventLoop.h" #include "tests/testEventLoop.h" #include "time/Engine.h" #include "unitTestMain.h" #include CPPUNIT_TEST_SUITE_REGISTRATION( testEventLoop ); void testEventLoop::testCreate() { EventLoop(); } class RecordingEngine : public AsyncEngine { public: RecordingEngine(int aTimeout = 0) : return_timeout(aTimeout) {} int checkEvents(int timeout) override { ++calls; lasttimeout = timeout; return return_timeout; } int calls = 0; int lasttimeout = 0; int return_timeout = 0; }; /* test that a registered async engine is invoked on each loop run * we do this with an instrumented async engine. */ void testEventLoop::testRunOnce() { { /* trivial case - no engine, should quit immediately */ EventLoop theLoop; CPPUNIT_ASSERT_EQUAL(true, theLoop.runOnce()); } { /* An event loop with all idle engines, and nothing dispatched in a run should * automatically quit. The runOnce call should return True when the loop is * entirely idle to make it easy for people running the loop by hand. */ EventLoop theLoop; RecordingEngine engine(AsyncEngine::EVENT_IDLE); theLoop.registerEngine(&engine); CPPUNIT_ASSERT_EQUAL(true, theLoop.runOnce()); CPPUNIT_ASSERT_EQUAL(1, engine.calls); theLoop.run(); CPPUNIT_ASSERT_EQUAL(2, engine.calls); } { /* an engine that asks for a timeout should not be detected as idle: * use runOnce which should return false */ EventLoop theLoop; RecordingEngine engine; theLoop.registerEngine(&engine); CPPUNIT_ASSERT_EQUAL(false, theLoop.runOnce()); CPPUNIT_ASSERT_EQUAL(1, engine.calls); CPPUNIT_ASSERT_EQUAL(EVENT_LOOP_TIMEOUT, engine.lasttimeout); } } /* each AsyncEngine needs to be given a timeout. We want one engine in each * loop to be given the timeout value - and the rest to have a timeout of 0. * The last registered engine should be given this timeout, which will mean * that we do not block in the loop until the last engine. This will allow for * dynamic introduction and removal of engines, as long as the last engine * is one which can do a os call rather than busy waiting. * * So - we want the timeout hints returned from the earlier engines to be * tracked, and the lowest non-negative value given to the last engine. */ void testEventLoop::testEngineTimeout() { EventLoop theLoop; RecordingEngine engineOne(5); RecordingEngine engineTwo; theLoop.registerEngine(&engineOne); theLoop.registerEngine(&engineTwo); theLoop.runOnce(); CPPUNIT_ASSERT_EQUAL(1, engineOne.calls); CPPUNIT_ASSERT_EQUAL(0, engineOne.lasttimeout); CPPUNIT_ASSERT_EQUAL(1, engineTwo.calls); CPPUNIT_ASSERT_EQUAL(5, engineTwo.lasttimeout); } /* An engine which is suffering errors. This should result in 10 * loops until the loop stops - because that's the error retry amount * hard-coded into EventLoop::runOnce() */ void testEventLoop::testEngineErrors() { EventLoop theLoop; RecordingEngine failing_engine(AsyncEngine::EVENT_ERROR); theLoop.registerEngine(&failing_engine); CPPUNIT_ASSERT_EQUAL(false, theLoop.runOnce()); CPPUNIT_ASSERT_EQUAL(1, failing_engine.calls); CPPUNIT_ASSERT_EQUAL(1, theLoop.errcount); theLoop.run(); /* run resets the error count ... */ CPPUNIT_ASSERT_EQUAL(10, theLoop.errcount); CPPUNIT_ASSERT_EQUAL(11, failing_engine.calls); } /* An event loop has a time service which is like an async engine but never * generates events and there can only be one such service. */ class StubTime : public Time::Engine { public: StubTime() : calls(0) {} int calls; void tick() override { ++calls; } }; void testEventLoop::testSetTimeService() { EventLoop theLoop; StubTime myTime; /* the loop will not error without a time service */ theLoop.runOnce(); /* we can set the time service */ theLoop.setTimeService(&myTime); /* it invokes our tick() call */ theLoop.runOnce(); CPPUNIT_ASSERT_EQUAL(1, myTime.calls); /* it invokes our tick() call again */ theLoop.runOnce(); CPPUNIT_ASSERT_EQUAL(2, myTime.calls); } /* one async engine is the primary engine - the engine that is allowed to block. * this defaults to the last added one, but can be explicitly nominated */ void testEventLoop::testSetPrimaryEngine() { EventLoop theLoop; RecordingEngine first_engine(10); RecordingEngine second_engine(10); /* one engine - gets a timeout */ theLoop.registerEngine(&first_engine); theLoop.runOnce(); CPPUNIT_ASSERT_EQUAL(EVENT_LOOP_TIMEOUT, first_engine.lasttimeout); /* two engines - the second gets the timeout */ theLoop.registerEngine(&second_engine); theLoop.runOnce(); CPPUNIT_ASSERT_EQUAL(0, first_engine.lasttimeout); CPPUNIT_ASSERT_EQUAL(10, second_engine.lasttimeout); /* set the first engine to be primary explicitly and now gets the timeout */ theLoop.setPrimaryEngine(&first_engine); theLoop.runOnce(); CPPUNIT_ASSERT_EQUAL(10, first_engine.lasttimeout); CPPUNIT_ASSERT_EQUAL(0, second_engine.lasttimeout); }