8000 gh-101178: Add Ascii85, base85, and Z85 support to binascii by kangtastic · Pull Request #102753 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

gh-101178: Add Ascii85, base85, and Z85 support to binascii #102753

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
Prev Previous commit
Next Next commit
Test Python and C codepaths in base64 using mixins
Do we really need to test the legacy API twice?
  • Loading branch information
kangtastic committed Apr 28, 2025
commit d075593f0b65af3fece22382f326fd07f072986c
92 changes: 54 additions & 38 deletions Lib/test/test_base64.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,15 @@
import binascii
import os
from array import array
from functools import update_wrapper
from test.support import os_helper
from test.support import script_helper
from test.support.import_helper import import_fresh_module

base64 = import_fresh_module("base64", blocked=["_base64"])
py_base64 = import_fresh_module("base64", blocked=["_base64"])
c_base64 = import_fresh_module("base64", fresh=["_base64"])


def with_c_implementation(test_func):
if c_base64 is None:
return test_func

def _test_func(self):
global base64

# Test Python implementation
test_func(self)

# Test C implementation
base64_ = base64
try:
base64 = c_base64
test_func(self)
finally:
base64 = base64_

update_wrapper(_test_func, test_func)

return _test_func


class LegacyBase64TestCase(unittest.TestCase):
class LegacyBase64TestCase:

# Legacy API is not as permissive as the modern API
def check_type_errors(self, f):
Expand All @@ -46,6 +22,7 @@ def check_type_errors(self, f):
self.assertRaises(TypeError, f, int_data)

def test_encodebytes(self):
base64 = self.module
eq = self.assertEqual
eq(base64.encodebytes(b"www.python.org"), b"d3d3LnB5dGhvbi5vcmc=\n")
eq(base64.encodebytes(b"a"), b"YQ==\n")
Expand All @@ -67,6 +44,7 @@ def test_encodebytes(self):
self.check_type_errors(base64.encodebytes)

def test_decodebytes(self):
base64 = self.module
eq = self.assertEqual
eq(base64.decodebytes(b"d3d3LnB5dGhvbi5vcmc=\n"), b"www.python.org")
eq(base64.decodebytes(b"YQ==\n"), b"a")
Expand All @@ -88,6 +66,7 @@ def test_decodebytes(self):
self.check_type_errors(base64.decodebytes)

def test_encode(self):
base64 = self.module
eq = self.assertEqual
from io import BytesIO, StringIO
infp = BytesIO(b'abcdefghijklmnopqrstuvwxyz'
Expand All @@ -105,6 +84,7 @@ def test_encode(self):
self.assertRaises(TypeError, base64.encode, StringIO('abc'), StringIO())

def test_decode(self):
base64 = self.module
from io import BytesIO, StringIO
infp = BytesIO(b'd3d3LnB5dGhvbi5vcmc=')
outfp = BytesIO()
Expand All @@ -116,7 +96,16 @@ def test_decode(self):
self.assertRaises(TypeError, base64.encode, StringIO('YWJj\n'), StringIO())


class BaseXYTestCase(unittest.TestCase):
class LegacyBase64TestCasePython(LegacyBase64TestCase, unittest.TestCase):
module = py_base64


@unittest.skipUnless(c_base64, "requires _base64")
class LegacyBase64TestCaseC(LegacyBase64TestCase, unittest.TestCase):
module = c_base64


class BaseXYTestCase:

# Modern API completely ignores exported dimension and format data and
# treats any buffer as a stream of bytes
Expand All @@ -128,6 +117,7 @@ def check_decode_type_errors(self, f):
self.assertRaises(TypeError, f, [])

def check_other_types(self, f, bytes_data, expected):
base64 = self.module
eq = self.assertEqual
b = bytearray(bytes_data)
eq(f(b), expected)
Expand All @@ -154,6 +144,7 @@ def check_nonbyte_element_format(self, f, data):


def test_b64encode(self):
base64 = self.module
eq = self.assertEqual
# Test default alphabet
eq(base64.b64encode(b"www.python.org"), b"d3d3LnB5dGhvbi5vcmc=")
Expand Down Expand Up @@ -204,6 +195,7 @@ def test_b64encode(self):
self.check_encode_type_errors(base64.urlsafe_b64encode)

def test_b64decode(self):
base64 = self.module
eq = self.assertEqual

tests = {b"d3d3LnB5dGhvbi5vcmc=": b"www.python.org",
Expand Down Expand Up @@ -260,10 +252,12 @@ def test_b64decode(self):
self.check_decode_type_errors(base64.urlsafe_b64decode)

def test_b64decode_padding_error(self):
base64 = self.module
self.assertRaises(binascii.Error, base64.b64decode, b'abc')
self.assertRaises(binascii.Error, base64.b64decode, 'abc')

def test_b64decode_invalid_chars(self):
base64 = self.module
# issue 1466065: Test some invalid characters.
tests = ((b'%3d==', b'\xdd'),
(b'$3d==', b'\xdd'),
Expand Down Expand Up @@ -296,6 +290,7 @@ def test_b64decode_invalid_chars(self):
self.assertEqual(base64.urlsafe_b64decode(b'++--//__'), res)

def test_b32encode(self):
base64 = self.module
eq = self.assertEqual
eq(base64.b32encode(b''), b'')
eq(base64.b32encode(b'\x00'), b'AA======')
Expand All @@ -309,6 +304,7 @@ def test_b32encode(self):
self.check_encode_type_errors(base64.b32encode)

def test_b32decode(self):
base64 = self.module
eq = self.assertEqual
tests = {b'': b'',
b'AA======': b'\x00',
Expand All @@ -326,6 +322,7 @@ def test_b32decode(self):
self.check_decode_type_errors(base64.b32decode)

def test_b32decode_casefold(self):
base64 = self.module
eq = self.assertEqual
tests = {b'': b'',
b'ME======': b'a',
Expand Down Expand Up @@ -367,6 +364,7 @@ def test_b32decode_casefold(self):
self.assertRaises(binascii.Error, base64.b32decode, data_str)

def test_b32decode_error(self):
base64 = self.module
tests = [b'abc', b'ABCDEF==', b'==ABCDEF']
prefixes = [b'M', b'ME', b'MFRA', b'MFRGG', b'MFRGGZA', b'MFRGGZDF']
for i in range(0, 17):
Expand All @@ -383,6 +381,7 @@ def test_b32decode_error(self):
base64.b32decode(data.decode('ascii'))

def test_b32hexencode(self):
base64 = self.module
test_cases = [
# to_encode, expected
(b'', b''),
Expand All @@ -398,10 +397,12 @@ def test_b32hexencode(self):
self.assertEqual(base64.b32hexencode(to_encode), expected)

def test_b32hexencode_other_types(self):
base64 = self.module
self.check_other_types(base64.b32hexencode, b'abcd', b'C5H66P0=')
self.check_encode_type_errors(base64.b32hexencode)

def test_b32hexdecode(self):
base64 = self.module
test_cases = [
# to_decode, expected, casefold
(b'', b'', False),
Expand Down Expand Up @@ -432,10 +433,12 @@ def test_b32hexdecode(self):
casefold), expected)

def test_b32hexdecode_other_types(self):
base64 = self.module
self.check_other_types(base64.b32hexdecode, b'C5H66===', b'abc')
self.check_decode_type_errors(base64.b32hexdecode)

def test_b32hexdecode_error(self):
base64 = self.module
tests = [b'abc', b'ABCDEF==', b'==ABCDEF', b'c4======']
prefixes = [b'M', b'ME', b'MFRA', b'MFRGG', b'MFRGGZA', b'MFRGGZDF']
for i in range(0, 17):
Expand All @@ -453,6 +456,7 @@ def test_b32hexdecode_error(self):


def test_b16encode(self):
base64 = self.module
eq = self.assertEqual
eq(base64.b16encode(b'\x01\x02\xab\xcd\xef'), b'0102ABCDEF')
eq(base64.b16encode(b'\x00'), b'00')
Expand All @@ -462,6 +466,7 @@ def test_b16encode(self):
self.check_encode_type_errors(base64.b16encode)

def test_b16decode(self):
base64 = self.module
eq = self.assertEqual
eq(base64.b16decode(b'0102ABCDEF'), b'\x01\x02\xab\xcd\xef')
eq(base64.b16decode('0102ABCDEF'), b'\x01\x02\xab\xcd\xef')
Expand All @@ -488,8 +493,8 @@ def test_b16decode(self):
# Incorrect "padding"
self.assertRaises(binascii.Error, base64.b16decode, '010')

@with_c_implementation
def test_a85encode(self):
base64 = self.module
eq = self.assertEqual

tests = {
Expand Down Expand Up @@ -539,8 +544,8 @@ def test_a85encode(self):
eq(base64.a85encode(b' '*6, foldspaces=True, adobe=False), b'y+<U')
eq(base64.a85encode(b' '*5, foldspaces=True, adobe=False), b'y+9')

@with_c_implementation
def test_b85encode(self):
base64 = self.module
eq = self.assertEqual

tests = {
Expand Down Expand Up @@ -574,8 +579,8 @@ def test_b85encode(self):
self.check_other_types(base64.b85encode, b"www.python.org",
b'cXxL#aCvlSZ*DGca%T')

@with_c_implementation
def test_z85encode(self):
base64 = self.module
eq = self.assertEqual

tests = {
Expand Down Expand Up @@ -609,8 +614,8 @@ def test_z85encode(self):
self.check_other_types(base64.z85encode, b"www.python.org",
b'CxXl-AcVLsz/dgCA+t')

@with_c_implementation
def test_a85decode(self):
base64 = self.module
eq = self.assertEqual

tests = {
Expand Down Expand Up @@ -656,8 +661,8 @@ def test_a85decode(self):
self.check_other_types(base64.a85decode, b'GB\\6`E-ZP=Df.1GEb>',
b"www.python.org")

@with_c_implementation
def test_b85decode(self):
base64 = self.module
eq = self.assertEqual

tests = {
Expand Down Expand Up @@ -692,8 +697,8 @@ def test_b85decode(self):
self.check_other_types(base64.b85decode, b'cXxL#aCvlSZ*DGca%T',
b"www.python.org")

@with_c_implementation
def test_z85decode(self):
base64 = self.module
eq = self.assertEqual

tests = {
Expand Down Expand Up @@ -728,8 +733,8 @@ def test_z85decode(self):
self.check_other_types(base64.z85decode, b'CxXl-AcVLsz/dgCA+t',
b'www.python.org')

@with_c_implementation
def test_a85_padding(self):
base64 = self.module
eq = self.assertEqual

eq(base64.a85encode(b"x", pad=True), b'GQ7^D')
Expand All @@ -744,8 +749,8 @@ def test_a85_padding(self):
eq(base64.a85decode(b'G^+IX'), b"xxxx")
eq(base64.a85decode(b'G^+IXGQ7^D'), b"xxxxx\x00\x00\x00")

@with_c_implementation
def test_b85_padding(self):
base64 = self.module
eq = self.assertEqual

eq(base64.b85encode(b"x", pad=True), b'cmMzZ')
Expand All @@ -760,8 +765,8 @@ def test_b85_padding(self):
eq(base64.b85decode(b'czAet'), b"xxxx")
eq(base64.b85decode(b'czAetcmMzZ'), b"xxxxx\x00\x00\x00")

@with_c_implementation
def test_a85decode_errors(self):
base64 = self.module
illegal = (set(range(32)) | set(range(118, 256))) - set(b' \t\n\r\v')
for c in illegal:
with self.assertRaises(ValueError, msg=bytes([c])):
Expand Down Expand Up @@ -798,8 +803,8 @@ def test_a85decode_errors(self):
self.assertRaises(ValueError, base64.a85decode, b'aaaay',
foldspaces=True)

@with_c_implementation
def test_b85decode_errors(self):
base64 = self.module
illegal = list(range(33)) + \
list(b'"\',./:[\\]') + \
list(range(128, 256))
Expand All @@ -813,8 +818,8 @@ def test_b85decode_errors(self):
self.assertRaises(ValueError, base64.b85decode, b'|NsC')
self.assertRaises(ValueError, base64.b85decode, b'|NsC1')

@with_c_implementation
def test_z85decode_errors(self):
base64 = self.module
illegal = list(range(33)) + \
list(b'"\',;_`|\\~') + \
list(range(128, 256))
Expand All @@ -830,6 +835,7 @@ def test_z85decode_errors(self):
self.assertRaises(ValueError, base64.z85decode, b'%nSc1')

def test_decode_nonascii_str(self):
base64 = self.module
decode_funcs = (base64.b64decode,
base64.standard_b64decode,
base64.urlsafe_b64decode,
Expand All @@ -845,6 +851,7 @@ def test_ErrorHeritage(self):
self.assertTrue(issubclass(binascii.Error, ValueError))

def test_RFC4648_test_cases(self):
base64 = self.module
# test cases from RFC 4648 section 10
b64encode = base64.b64encode
b32hexencode = base64.b32hexencode
Expand Down Expand Up @@ -884,6 +891,15 @@ def test_RFC4648_test_cases(self):
self.assertEqual(b16encode(b"foobar"), b"666F6F626172")


class BaseXYTestCasePython(BaseXYTestCase, unittest.TestCase):
module = py_base64


@unittest.skipUnless(c_base64, "requires _base64")
class BaseXYTestCaseC(BaseXYTestCase, unittest.TestCase):
module = c_base64


class TestMain(unittest.TestCase):
def tearDown(self):
if os.path.exists(os_helper.TESTFN):
Expand Down
Loading
0