Complete the FakeRedis test double for the subsystem metrics pipe (#16745)

TestCallbackBrokerWorker::test_duplicate_key_not_saved_twice can crash
with AttributeError: 'FakeRedis' object has no attribute 'hincrby'.

CallbackBrokerWorker.flush() ends with

    if self.subsystem_metrics.should_pipe_execute() is True:
        self.subsystem_metrics.pipe_execute()

and should_pipe_execute() is True once more than
SUBSYSTEM_METRICS_INTERVAL_SAVE_TO_REDIS seconds have passed since the
last pipe execute. On a fast machine that interval never elapses during
the test, so the pipe is never executed and nothing notices; on a slow
one it does, pipe_execute() calls store_value() on every metric, and
IntM/FloatM/SetIntM reach for hincrby/hincrbyfloat/hset, which the
FakeRedis double in awx/main/tests/functional/conftest.py does not have.

Rather than suppress the pipe execute in the one test, make the double
complete enough to execute it: add the hash commands the metric
store_value() implementations use, an execute() for the pipeline
(pipeline() returns self), and a lock() returning a lock that never
acquires, so send_metrics() takes its "another thread holds the lock"
early return instead of trying to emit a websocket notification through
a channel layer that is not available under test.
This commit is contained in:
Hao Liu
2026-10-15 15:22:47 +00:00
committed by GitHub
parent 1ac181813a
commit 4e1b00898b
+28
View File
@@ -76,6 +76,16 @@ def swagger_autogen(requests=__SWAGGER_REQUESTS__):
return requests
class FakeLock:
# Never acquirable, so callers that guard work with a redis lock take the
# "someone else holds it" branch instead of reaching for real services.
def acquire(self, *args, **kwargs):
return False
def release(self, *args, **kwargs):
pass
class FakeRedis:
def __init__(self, *args, **kwargs):
# Accept and ignore all arguments to match redis.Redis signature
@@ -90,6 +100,21 @@ class FakeRedis:
def get(self, *args, **kwargs):
return None
def hset(self, *args, **kwargs):
return 1
def hget(self, *args, **kwargs):
return None
def hincrby(self, *args, **kwargs):
return 0
def hincrbyfloat(self, *args, **kwargs):
return 0.0
def lock(self, *args, **kwargs):
return FakeLock()
def rpush(self, *args, **kwargs):
return 1
@@ -112,6 +137,9 @@ class FakeRedis:
def pipeline(self):
return self
def execute(self, *args, **kwargs):
return []
def ping(self):
return