|
| 1 | +importio |
1 | 2 | importplatform
|
2 | 3 | importqueue
|
3 | 4 | importre
|
4 | 5 | importsubprocess
|
5 | 6 | importsys
|
6 | 7 | importunittest
|
| 8 | +from_android_supportimportTextLogStream |
7 | 9 | fromarrayimportarray
|
8 |
| -fromcontextlibimportcontextmanager |
| 10 | +fromcontextlibimportExitStack,contextmanager |
9 | 11 | fromthreadingimportThread
|
10 | 12 | fromtest.supportimportLOOPBACK_TIMEOUT
|
11 |
| -fromtimeimporttime |
| 13 | +fromtimeimportsleep,time |
| 14 | +fromunittest.mockimportpatch |
12 | 15 |
|
13 | 16 |
|
14 | 17 | ifsys.platform!="android":
|
@@ -81,18 +84,39 @@ def unbuffered(self, stream):
|
81 | 84 | finally:
|
82 | 85 | stream.reconfigure(write_through=False)
|
83 | 86 |
|
| 87 | +# In --verbose3 mode, sys.stdout and sys.stderr are captured, so we can't |
| 88 | +# test them directly. Detect this mode and use some temporary streams with |
| 89 | +# the same properties. |
| 90 | +defstream_context(self,stream_name,level): |
| 91 | +# https://developer.android.com/ndk/reference/group/logging |
| 92 | +prio= {"I":4,"W":5}[level] |
| 93 | + |
| 94 | +stack=ExitStack() |
| 95 | +stack.enter_context(self.subTest(stream_name)) |
| 96 | +stream=getattr(sys,stream_name) |
| 97 | +ifisinstance(stream,io.StringIO): |
| 98 | +stack.enter_context( |
| 99 | +patch( |
| 100 | +f"sys.{stream_name}", |
| 101 | +TextLogStream( |
| 102 | +prio,f"python.{stream_name}",errors="backslashreplace" |
| 103 | + ), |
| 104 | + ) |
| 105 | + ) |
| 106 | +returnstack |
| 107 | + |
84 | 108 | deftest_str(self):
|
85 | 109 | forstream_name,levelin [("stdout","I"), ("stderr","W")]:
|
86 |
| -withself.subTest(stream=stream_name): |
| 110 | +withself.stream_context(stream_name,level): |
87 | 111 | stream=getattr(sys,stream_name)
|
88 | 112 | tag=f"python.{stream_name}"
|
89 | 113 | self.assertEqual(f"<TextLogStream '{tag}'>",repr(stream))
|
90 | 114 |
|
91 |
| -self.assertTrue(stream.writable()) |
92 |
| -self.assertFalse(stream.readable()) |
| 115 | +self.assertIs(stream.writable(),True) |
| 116 | +self.assertIs(stream.readable(),False) |
93 | 117 | self.assertEqual("UTF-8",stream.encoding)
|
94 |
| -self.assertTrue(stream.line_buffering) |
95 |
| -self.assertFalse(stream.write_through) |
| 118 | +self.assertIs(stream.line_buffering,True) |
| 119 | +self.assertIs(stream.write_through,False) |
96 | 120 |
|
97 | 121 | # stderr is backslashreplace by default; stdout is configured
|
98 | 122 | # that way by libregrtest.main.
|
@@ -147,6 +171,13 @@ def write(s, lines=None, *, write_len=None):
|
147 | 171 | write("f\n\ng", ["exxf",""])
|
148 | 172 | write("\n", ["g"])
|
149 | 173 |
|
| 174 | +# Since this is a line-based logging system, line buffering |
| 175 | +# cannot be turned off, i.e. a newline always causes a flush. |
| 176 | +stream.reconfigure(line_buffering=False) |
| 177 | +self.assertIs(stream.line_buffering,True) |
| 178 | + |
| 179 | +# However, buffering can be turned off completely if you want a |
| 180 | +# flush after every write. |
150 | 181 | withself.unbuffered(stream):
|
151 | 182 | write("\nx", ["","x"])
|
152 | 183 | write("\na\n", ["","a"])
|
@@ -209,30 +240,30 @@ def __str__(self):
|
209 | 240 | # (MAX_BYTES_PER_WRITE).
|
210 | 241 | #
|
211 | 242 | # ASCII (1 byte per character)
|
212 |
| -write(("foobar"*700)+"\n", |
213 |
| - [("foobar"*666)+"foob",# 4000 bytes |
214 |
| -"ar"+ ("foobar"*33)])# 200 bytes |
| 243 | +write(("foobar"*700)+"\n",# 4200 bytes in |
| 244 | + [("foobar"*666)+"foob",# 4000 bytes out |
| 245 | +"ar"+ ("foobar"*33)])# 200 bytes out |
215 | 246 |
|
216 | 247 | # "Full-width" digits 0-9 (3 bytes per character)
|
217 | 248 | s="\uff10\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18\uff19"
|
218 |
| -write((s*150)+"\n", |
219 |
| - [s*100,# 3000 bytes |
220 |
| -s*50])# 1500 bytes |
| 249 | +write((s*150)+"\n",# 4500 bytes in |
| 250 | + [s*100,# 3000 bytes out |
| 251 | +s*50])# 1500 bytes out |
221 | 252 |
|
222 | 253 | s="0123456789"
|
223 |
| -write(s*200, []) |
224 |
| -write(s*150, []) |
225 |
| -write(s*51, [s*350])# 3500 bytes |
226 |
| -write("\n", [s*51])# 510 bytes |
| 254 | +write(s*200, [])# 2000 bytes in |
| 255 | +write(s*150, [])# 1500 bytes in |
| 256 | +write(s*51, [s*350])#510 bytes in,3500 bytes out |
| 257 | +write("\n", [s*51])#0 bytes in,510 bytes out |
227 | 258 |
|
228 | 259 | deftest_bytes(self):
|
229 | 260 | forstream_name,levelin [("stdout","I"), ("stderr","W")]:
|
230 |
| -withself.subTest(stream=stream_name): |
| 261 | +withself.stream_context(stream_name,level): |
231 | 262 | stream=getattr(sys,stream_name).buffer
|
232 | 263 | tag=f"python.{stream_name}"
|
233 | 264 | self.assertEqual(f"<BinaryLogStream '{tag}'>",repr(stream))
|
234 |
| -self.assertTrue(stream.writable()) |
235 |
| -self.assertFalse(stream.readable()) |
| 265 | +self.assertIs(stream.writable(),True) |
| 266 | +self.assertIs(stream.readable(),False) |
236 | 267 |
|
237 | 268 | defwrite(b,lines=None,*,write_len=None):
|
238 | 269 | ifwrite_lenisNone:
|
@@ -330,3 +361,64 @@ def write(b, lines=None, *, write_len=None):
|
330 | 361 | fr"{type(obj).__name__}"
|
331 | 362 | ):
|
332 | 363 | stream.write(obj)
|
| 364 | + |
| 365 | +deftest_rate_limit(self): |
| 366 | +# https://cs.android.com/android/platform/superproject/+/android-14.0.0_r1:system/logging/liblog/include/log/log_read.h;l=39 |
| 367 | +PER_MESSAGE_OVERHEAD=28 |
| 368 | + |
| 369 | +# https://developer.android.com/ndk/reference/group/logging |
| 370 | +ANDROID_LOG_DEBUG=3 |
| 371 | + |
| 372 | +# To avoid flooding the test script output, use a different tag rather |
| 373 | +# than stdout or stderr. |
| 374 | +tag="python.rate_limit" |
| 375 | +stream=TextLogStream(ANDROID_LOG_DEBUG,tag) |
| 376 | + |
| 377 | +# Make a test message which consumes 1 KB of the logcat buffer. |
| 378 | +message="Line {:03d} " |
| 379 | +message+="."* ( |
| 380 | +1024-PER_MESSAGE_OVERHEAD-len(tag)-len(message.format(0)) |
| 381 | + )+"\n" |
| 382 | + |
| 383 | +# See _android_support.py. The default values of these parameters work |
| 384 | +# well across a wide range of devices, but we'll use smaller values to |
| 385 | +# ensure a quick and reliable test that doesn't flood the log too much. |
| 386 | +MAX_KB_PER_SECOND=100 |
| 387 | +BUCKET_KB=10 |
| 388 | +with ( |
| 389 | +patch("_android_support.MAX_BYTES_PER_SECOND",MAX_KB_PER_SECOND*1024), |
| 390 | +patch("_android_support.BUCKET_SIZE",BUCKET_KB*1024), |
| 391 | + ): |
| 392 | +# Make sure the token bucket is full. |
| 393 | +sleep(BUCKET_KB/MAX_KB_PER_SECOND) |
| 394 | +line_num=0 |
| 395 | + |
| 396 | +# Write BUCKET_KB messages, and return the rate at which they were |
| 397 | +# accepted in KB per second. |
| 398 | +defwrite_bucketful(): |
| 399 | +nonlocalline_num |
| 400 | +start=time() |
| 401 | +max_line_num=line_num+BUCKET_KB |
| 402 | +whileline_num<max_line_num: |
| 403 | +stream.write(message.format(line_num)) |
| 404 | +line_num+=1 |
| 405 | +returnBUCKET_KB/ (time()-start) |
| 406 | + |
| 407 | +# The first bucketful should be written with minimal delay. The |
| 408 | +# factor of 2 here is not arbitrary: it verifies that the system can |
| 409 | +# write fast enough to empty the bucket within two bucketfuls, which |
| 410 | +# the next part of the test depends on. |
| 411 | +self.assertGreater(write_bucketful(),MAX_KB_PER_SECOND*2) |
| 412 | + |
| 413 | +# Write another bucketful to empty the token bucket completely. |
| 414 | +write_bucketful() |
| 415 | + |
| 416 | +# The next bucketful should be written at the rate limit. |
| 417 | +self.assertAlmostEqual( |
| 418 | +write_bucketful(),MAX_KB_PER_SECOND, |
| 419 | +delta=MAX_KB_PER_SECOND*0.1 |
| 420 | + ) |
| 421 | + |
| 422 | +# Once the token bucket refills, we should go back to full speed. |
| 423 | +sleep(BUCKET_KB/MAX_KB_PER_SECOND) |
| 424 | +self.assertGreater(write_bucketful(),MAX_KB_PER_SECOND*2) |