11import unittest
22import base64
33import binascii
4+ import string
45import os
56from array import array
67from test .support import cpython_only
@@ -14,6 +15,8 @@ class LazyImportTest(unittest.TestCase):
1415 def test_lazy_import (self ):
1516 ensure_lazy_imports ("base64" , {"re" , "getopt" })
1617
18+ from test .support .hypothesis_helper import hypothesis
19+
1720
1821class LegacyBase64TestCase (unittest .TestCase ):
1922
@@ -68,6 +71,13 @@ def test_decodebytes(self):
6871 eq (base64 .decodebytes (array ('B' , b'YWJj\n ' )), b'abc' )
6972 self .check_type_errors (base64 .decodebytes )
7073
74+ @hypothesis .given (payload = hypothesis .strategies .binary ())
75+ @hypothesis .example (b'abcdefghijklmnopqrstuvwxyz' )
76+ def test_bytes_encode_decode_round_trip (self , payload ):
77+ encoded = base64 .encodebytes (payload )
78+ decoded = base64 .decodebytes (encoded )
79+ self .assertEqual (payload , decoded )
80+
7181 def test_encode (self ):
7282 eq = self .assertEqual
7383 from io import BytesIO , StringIO
@@ -96,6 +106,19 @@ def test_decode(self):
96106 self .assertRaises (TypeError , base64 .encode , BytesIO (b'YWJj\n ' ), StringIO ())
97107 self .assertRaises (TypeError , base64 .encode , StringIO ('YWJj\n ' ), StringIO ())
98108
109+ @hypothesis .given (payload = hypothesis .strategies .binary ())
110+ @hypothesis .example (b'abcdefghijklmnopqrstuvwxyz' )
111+ def test_legacy_encode_decode_round_trip (self , payload ):
112+ from io import BytesIO
113+ payload_file_r = BytesIO (payload )
114+ encoded_file_w = BytesIO ()
115+ base64 .encode (payload_file_r , encoded_file_w )
116+ encoded_file_r = BytesIO (encoded_file_w .getvalue ())
117+ decoded_file_w = BytesIO ()
118+ base64 .decode (encoded_file_r , decoded_file_w )
119+ decoded = decoded_file_w .getvalue ()
120+ self .assertEqual (payload , decoded )
121+
99122
100123class BaseXYTestCase (unittest .TestCase ):
101124
@@ -276,6 +299,44 @@ def test_b64decode_invalid_chars(self):
276299 self .assertEqual (base64 .b64decode (b'++[[//]]' , b'[]' ), res )
277300 self .assertEqual (base64 .urlsafe_b64decode (b'++--//__' ), res )
278301
302+
303+ def _altchars_strategy ():
304+ """Generate 'altchars' for base64 encoding."""
305+ reserved_chars = (string .digits + string .ascii_letters + "=" ).encode ()
306+ allowed_chars = hypothesis .strategies .sampled_from (
307+ [n for n in range (256 ) if n not in reserved_chars ])
308+ two_bytes_strategy = hypothesis .strategies .lists (
309+ allowed_chars , min_size = 2 , max_size = 2 , unique = True ).map (bytes )
310+ return (hypothesis .strategies .none ()
311+ | hypothesis .strategies .just (b"_-" )
312+ | two_bytes_strategy )
313+
314+ @hypothesis .given (
315+ payload = hypothesis .strategies .binary (),
316+ altchars = _altchars_strategy (),
317+ validate = hypothesis .strategies .booleans ())
318+ @hypothesis .example (b'abcdefghijklmnopqrstuvwxyz' , b"_-" , True )
319+ @hypothesis .example (b'abcdefghijklmnopqrstuvwxyz' , b"_-" , False )
320+ def test_b64_encode_decode_round_trip (self , payload , altchars , validate ):
321+ encoded = base64 .b64encode (payload , altchars = altchars )
322+ decoded = base64 .b64decode (encoded , altchars = altchars ,
323+ validate = validate )
324+ self .assertEqual (payload , decoded )
325+
326+ @hypothesis .given (payload = hypothesis .strategies .binary ())
327+ @hypothesis .example (b'abcdefghijklmnopqrstuvwxyz' )
328+ def test_standard_b64_encode_decode_round_trip (self , payload ):
329+ encoded = base64 .standard_b64encode (payload )
330+ decoded = base64 .standard_b64decode (encoded )
331+ self .assertEqual (payload , decoded )
332+
333+ @hypothesis .given (payload = hypothesis .strategies .binary ())
334+ @hypothesis .example (b'abcdefghijklmnopqrstuvwxyz' )
335+ def test_urlsafe_b64_encode_decode_round_trip (self , payload ):
336+ encoded = base64 .urlsafe_b64encode (payload )
337+ decoded = base64 .urlsafe_b64decode (encoded )
338+ self .assertEqual (payload , decoded )
339+
279340 def test_b32encode (self ):
280341 eq = self .assertEqual
281342 eq (base64 .b32encode (b'' ), b'' )
@@ -363,6 +424,19 @@ def test_b32decode_error(self):
363424 with self .assertRaises (binascii .Error ):
364425 base64 .b32decode (data .decode ('ascii' ))
365426
427+ @hypothesis .given (
428+ payload = hypothesis .strategies .binary (),
429+ casefold = hypothesis .strategies .booleans (),
430+ map01 = (
431+ hypothesis .strategies .none ()
432+ | hypothesis .strategies .binary (min_size = 1 , max_size = 1 )))
433+ @hypothesis .example (b'abcdefghijklmnopqrstuvwxyz' , True , None )
434+ @hypothesis .example (b'abcdefghijklmnopqrstuvwxyz' , False , None )
435+ def test_b32_encode_decode_round_trip (self , payload , casefold , map01 ):
436+ encoded = base64 .b32encode (payload )
437+ decoded = base64 .b32decode (encoded , casefold = casefold , map01 = map01 )
438+ self .assertEqual (payload , decoded )
439+
366440 def test_b32hexencode (self ):
367441 test_cases = [
368442 # to_encode, expected
@@ -432,6 +506,15 @@ def test_b32hexdecode_error(self):
432506 with self .assertRaises (binascii .Error ):
433507 base64 .b32hexdecode (data .decode ('ascii' ))
434508
509+ @hypothesis .given (
510+ payload = hypothesis .strategies .binary (),
511+ casefold = hypothesis .strategies .booleans ())
512+ @hypothesis .example (b'abcdefghijklmnopqrstuvwxyz' , True )
513+ @hypothesis .example (b'abcdefghijklmnopqrstuvwxyz' , False )
514+ def test_b32_hexencode_decode_round_trip (self , payload , casefold ):
515+ encoded = base64 .b32hexencode (payload )
516+ decoded = base64 .b32hexdecode (encoded , casefold = casefold )
517+ self .assertEqual (payload , decoded )
435518
436519 def test_b16encode (self ):
437520 eq = self .assertEqual
@@ -469,6 +552,16 @@ def test_b16decode(self):
469552 # Incorrect "padding"
470553 self .assertRaises (binascii .Error , base64 .b16decode , '010' )
471554
555+ @hypothesis .given (
556+ payload = hypothesis .strategies .binary (),
557+ casefold = hypothesis .strategies .booleans ())
558+ @hypothesis .example (b'abcdefghijklmnopqrstuvwxyz' , True )
559+ @hypothesis .example (b'abcdefghijklmnopqrstuvwxyz' , False )
560+ def test_b16_encode_decode_round_trip (self , payload , casefold ):
561+ endoded = base64 .b16encode (payload )
562+ decoded = base64 .b16decode (endoded , casefold = casefold )
563+ self .assertEqual (payload , decoded )
564+
472565 def test_a85encode (self ):
473566 eq = self .assertEqual
474567
@@ -799,6 +892,61 @@ def test_z85decode_errors(self):
799892 self .assertRaises (ValueError , base64 .z85decode , b'%nSc' )
800893 self .assertRaises (ValueError , base64 .z85decode , b'%nSc1' )
801894
895+ def add_padding (self , payload ):
896+ """Add the expected padding for test_?85_encode_decode_round_trip."""
897+ if len (payload ) % 4 != 0 :
898+ padding = b"\0 " * ((- len (payload )) % 4 )
899+ payload = payload + padding
900+ return payload
901+
902+ @hypothesis .given (
903+ payload = hypothesis .strategies .binary (),
904+ foldspaces = hypothesis .strategies .booleans (),
905+ wrapcol = (
906+ hypothesis .strategies .just (0 )
907+ | hypothesis .strategies .integers (1 , 1000 )),
908+ pad = hypothesis .strategies .booleans (),
909+ adobe = hypothesis .strategies .booleans (),
910+ )
911+ @hypothesis .example (b'abcdefghijklmnopqrstuvwxyz' , False , 0 , False , False )
912+ @hypothesis .example (b'abcdefghijklmnopqrstuvwxyz' , False , 20 , True , True )
913+ @hypothesis .example (b'abcdefghijklmnopqrstuvwxyz' , True , 0 , False , True )
914+ @hypothesis .example (b'abcdefghijklmnopqrstuvwxyz' , True , 20 , True , False )
915+ def test_a85_encode_decode_round_trip (
916+ self , payload , foldspaces , wrapcol , pad , adobe
917+ ):
918+ encoded = base64 .a85encode (
919+ payload , foldspaces = foldspaces , wrapcol = wrapcol ,
920+ pad = pad , adobe = adobe ,
921+ )
922+ if wrapcol :
923+ if adobe and wrapcol == 1 :
924+ # "adobe" needs wrapcol to be at least 2.
925+ # a85decode quietly uses 2 if 1 is given; it's not worth
926+ # loudly deprecating this behavior.
927+ wrapcol = 2
928+ for line in encoded .splitlines (keepends = False ):
929+ self .assertLessEqual (len (line ), wrapcol )
930+ if adobe :
931+ self .assertTrue (encoded .startswith (b'<~' ))
932+ self .assertTrue (encoded .endswith (b'~>' ))
933+ decoded = base64 .a85decode (encoded , foldspaces = foldspaces , adobe = adobe )
934+ if pad :
935+ payload = self .add_padding (payload )
936+ self .assertEqual (payload , decoded )
937+
938+ @hypothesis .given (
939+ payload = hypothesis .strategies .binary (),
940+ pad = hypothesis .strategies .booleans ())
941+ @hypothesis .example (b'abcdefghijklmnopqrstuvwxyz' , True )
942+ @hypothesis .example (b'abcdefghijklmnopqrstuvwxyz' , False )
943+ def test_b85_encode_decode_round_trip (self , payload , pad ):
944+ encoded = base64 .b85encode (payload , pad = pad )
945+ if pad :
946+ payload = self .add_padding (payload )
947+ decoded = base64 .b85decode (encoded )
948+ self .assertEqual (payload , decoded )
949+
802950 def test_decode_nonascii_str (self ):
803951 decode_funcs = (base64 .b64decode ,
804952 base64 .standard_b64decode ,
0 commit comments