Skip to content

Commit d002e1f

Browse files
miss-islingtonmhsmithfreakboy3742
authored
[3.13] gh-116622: Mock the passage of time in Android logcat rate limit tests (GH-124015) (#124035)
gh-116622: Mock the passage of time in Android logcat rate limit tests (GH-124015) Mock the passage of time in Android logcat rate limit tests (cherry picked from commit f554883) Co-authored-by: Malcolm Smith <[email protected]> Co-authored-by: Russell Keith-Magee <[email protected]>
1 parent 5e27117 commit d002e1f

File tree

2 files changed

+30
-7
lines changed

2 files changed

+30
-7
lines changed

Lib/_android_support.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,10 @@ def write(self, prio, tag, message):
165165
now = time()
166166
self._bucket_level += (
167167
(now - self._prev_write_time) * MAX_BYTES_PER_SECOND)
168-
self._bucket_level = min(self._bucket_level, BUCKET_SIZE)
168+
169+
# If the bucket level is still below zero, the clock must have gone
170+
# backwards, so reset it to zero and continue.
171+
self._bucket_level = max(0, min(self._bucket_level, BUCKET_SIZE))
169172
self._prev_write_time = now
170173

171174
self._bucket_level -= PER_MESSAGE_OVERHEAD + len(tag) + len(message)

Lib/test/test_android.py

+26-6
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from contextlib import ExitStack, contextmanager
1111
from threading import Thread
1212
from test.support import LOOPBACK_TIMEOUT
13-
from time import sleep, time
13+
from time import time
1414
from unittest.mock import patch
1515

1616

@@ -42,7 +42,8 @@ def logcat_thread():
4242
for line in self.logcat_process.stdout:
4343
self.logcat_queue.put(line.rstrip("\n"))
4444
self.logcat_process.stdout.close()
45-
Thread(target=logcat_thread).start()
45+
self.logcat_thread = Thread(target=logcat_thread)
46+
self.logcat_thread.start()
4647

4748
from ctypes import CDLL, c_char_p, c_int
4849
android_log_write = getattr(CDLL("liblog.so"), "__android_log_write")
@@ -78,6 +79,7 @@ def assert_log(self, level, tag, expected, *, skip=False, timeout=0.5):
7879
def tearDown(self):
7980
self.logcat_process.terminate()
8081
self.logcat_process.wait(LOOPBACK_TIMEOUT)
82+
self.logcat_thread.join(LOOPBACK_TIMEOUT)
8183

8284
@contextmanager
8385
def unbuffered(self, stream):
@@ -369,6 +371,8 @@ def write(b, lines=None, *, write_len=None):
369371
):
370372
stream.write(obj)
371373

374+
375+
class TestAndroidRateLimit(unittest.TestCase):
372376
def test_rate_limit(self):
373377
# https://cs.android.com/android/platform/superproject/+/android-14.0.0_r1:system/logging/liblog/include/log/log_read.h;l=39
374378
PER_MESSAGE_OVERHEAD = 28
@@ -387,6 +391,19 @@ def test_rate_limit(self):
387391
1024 - PER_MESSAGE_OVERHEAD - len(tag) - len(message.format(0))
388392
) + "\n"
389393

394+
# To avoid depending on the performance of the test device, we mock the
395+
# passage of time.
396+
mock_now = time()
397+
398+
def mock_time():
399+
# Avoid division by zero by simulating a small delay.
400+
mock_sleep(0.0001)
401+
return mock_now
402+
403+
def mock_sleep(duration):
404+
nonlocal mock_now
405+
mock_now += duration
406+
390407
# See _android_support.py. The default values of these parameters work
391408
# well across a wide range of devices, but we'll use smaller values to
392409
# ensure a quick and reliable test that doesn't flood the log too much.
@@ -395,21 +412,24 @@ def test_rate_limit(self):
395412
with (
396413
patch("_android_support.MAX_BYTES_PER_SECOND", MAX_KB_PER_SECOND * 1024),
397414
patch("_android_support.BUCKET_SIZE", BUCKET_KB * 1024),
415+
patch("_android_support.sleep", mock_sleep),
416+
patch("_android_support.time", mock_time),
398417
):
399418
# Make sure the token bucket is full.
400-
sleep(BUCKET_KB / MAX_KB_PER_SECOND)
419+
stream.write("Initial message to reset _prev_write_time")
420+
mock_sleep(BUCKET_KB / MAX_KB_PER_SECOND)
401421
line_num = 0
402422

403423
# Write BUCKET_KB messages, and return the rate at which they were
404424
# accepted in KB per second.
405425
def write_bucketful():
406426
nonlocal line_num
407-
start = time()
427+
start = mock_time()
408428
max_line_num = line_num + BUCKET_KB
409429
while line_num < max_line_num:
410430
stream.write(message.format(line_num))
411431
line_num += 1
412-
return BUCKET_KB / (time() - start)
432+
return BUCKET_KB / (mock_time() - start)
413433

414434
# The first bucketful should be written with minimal delay. The
415435
# factor of 2 here is not arbitrary: it verifies that the system can
@@ -427,5 +447,5 @@ def write_bucketful():
427447
)
428448

429449
# Once the token bucket refills, we should go back to full speed.
430-
sleep(BUCKET_KB / MAX_KB_PER_SECOND)
450+
mock_sleep(BUCKET_KB / MAX_KB_PER_SECOND)
431451
self.assertGreater(write_bucketful(), MAX_KB_PER_SECOND * 2)

0 commit comments

Comments
 (0)