1010from typing import Final
1111from typing import Optional
1212
13+ from apscheduler .job import Job
14+ from apscheduler .jobstores .base import JobLookupError
15+ from apscheduler .schedulers .background import BackgroundScheduler
16+ from apscheduler .triggers .interval import IntervalTrigger
1317from packaging import version
18+ from tzlocal import get_localzone
1419
1520from tests .integration .test_utils import RunSubprocessMixin
1621from tests .integration .test_utils import get_podman_version
22+ from tests .integration .test_utils import is_systemd_available
1723from tests .integration .test_utils import podman_compose_path
1824from tests .integration .test_utils import test_path
1925
@@ -55,6 +61,22 @@ def __exit__(self, *_: Any) -> None:
5561 get_podman_version () < version .parse ("4.6.0" ), "Wait feature not supported below 4.6.0."
5662)
5763class TestComposeWait (unittest .TestCase , RunSubprocessMixin ):
64+ # WORKAROUND for https://github.com/containers/podman/issues/28192
65+ scheduler : Optional [BackgroundScheduler ] = None
66+
67+ @classmethod
68+ def setUpClass (cls ) -> None :
69+ # use APScheduler to trigger a healtcheck periodically if systemd and its timers are not
70+ # available
71+ if not is_systemd_available ():
72+ cls .scheduler = BackgroundScheduler ()
73+ cls .scheduler .start ()
74+
75+ @classmethod
76+ def tearDownClass (cls ) -> None :
77+ if cls .scheduler is not None :
78+ cls .scheduler .shutdown (wait = True )
79+
5880 def setUp (self ) -> None :
5981 # build the test image before starting the tests
6082 # this is not possible in setUpClass method, because the run_subprocess_* methods are not
@@ -66,7 +88,36 @@ def setUp(self) -> None:
6688 "build" ,
6789 ])
6890
91+ # start the healthcheck job
92+ self .healthcheck_job : Optional [Job ] = None
93+ if self .scheduler is not None :
94+ self .healthcheck_job = self .scheduler .add_job (
95+ func = self .run_subprocess ,
96+ # run health checking only for the wait_app_health_1 container
97+ kwargs = {"args" : ["podman" , "healthcheck" , "run" , "wait_app_health_1" ]},
98+ # trigger the healthcheck every 3 seconds (like defined in docker-compose.yml)
99+ trigger = IntervalTrigger (
100+ # trigger the healthcheck every 3 seconds (like defined in docker-compose.yml)
101+ seconds = 3 ,
102+ # run first healthcheck after 3 seconds (initial interval)
103+ start_date = datetime .now (get_localzone ()) + timedelta (seconds = 3 ),
104+ # stop after 21 seconds = 3s (interval) * 6 (retries) + 3s (initial interval)
105+ end_date = datetime .now (get_localzone ()) + timedelta (seconds = 21 ),
106+ ),
107+ )
108+
69109 def tearDown (self ) -> None :
110+ # stop and remove the healtcheck job
111+ if self .scheduler is not None :
112+ try :
113+ if self .healthcheck_job is not None :
114+ self .scheduler .remove_job (self .healthcheck_job .id )
115+ else :
116+ self .scheduler .remove_all_jobs ()
117+ except JobLookupError :
118+ # failover
119+ self .scheduler .remove_all_jobs ()
120+
70121 # clean up compose after every test
71122 self .run_subprocess ([
72123 podman_compose_path (),
0 commit comments