66import sysconfig
77import shutil
88import subprocess
9+ from collections import namedtuple
10+
911import test .support
1012from test .support .script_helper import (
1113 run_python_until_end ,
@@ -37,76 +39,113 @@ def _set_locale_in_subprocess(locale_name, category):
3739 "or PYTHONCOERCECLOCALE=0 to disable this locale coercion behaviour)."
3840)
3941
40- @test .support .cpython_only
41- @unittest .skipUnless (sysconfig .get_config_var ("PY_COERCE_C_LOCALE" ),
42- "C locale coercion disabled at build time" )
43- class LocaleOverrideTests (unittest .TestCase ):
42+ _EncodingDetails = namedtuple ("EncodingDetails" ,
43+ "fsencoding stdin_info stdout_info stderr_info" )
44+
45+ class EncodingDetails (_EncodingDetails ):
46+ CHILD_PROCESS_SCRIPT = ";" .join ([
47+ "import sys" ,
48+ "print(sys.getfilesystemencoding())" ,
49+ "print(sys.stdin.encoding + ':' + sys.stdin.errors)" ,
50+ "print(sys.stdout.encoding + ':' + sys.stdout.errors)" ,
51+ "print(sys.stderr.encoding + ':' + sys.stderr.errors)" ,
52+ ])
4453
4554 @classmethod
46- def setUpClass (cls ):
47- for target_locale , target_category , env_updates in _C_UTF8_LOCALES :
48- if _set_locale_in_subprocess (target_locale , target_category ):
49- break
50- else :
51- raise unittest .SkipTest ("No C-with-UTF-8 locale available" )
52- cls .EXPECTED_COERCION_WARNING = CLI_COERCION_WARNING_FMT .format (
53- env_updates , target_locale
54- )
55+ def get_expected_details (cls , expected_fsencoding ):
56+ """Returns expected child process details for a given encoding"""
57+ _stream = expected_fsencoding + ":{}"
58+ # stdin and stdout should use surrogateescape either because the
59+ # coercion triggered, or because the C locale was detected
60+ stream_info = 2 * [_stream .format ("surrogateescape" )]
61+ # stderr should always use backslashreplace
62+ stream_info .append (_stream .format ("backslashreplace" ))
63+ return dict (cls (expected_fsencoding , * stream_info )._asdict ())
64+
65+ @staticmethod
66+ def _replace_ascii_alias (data ):
67+ """ASCII may be reported as ANSI_X3.4-1968, so replace it in output"""
68+ return data .replace (b"ANSI_X3.4-1968" , b"ascii" )
5569
56- def _get_child_fsencoding (self , env_vars ):
57- """Retrieves sys.getfilesystemencoding() from a child process
70+ @classmethod
71+ def get_child_details (cls , env_vars ):
72+ """Retrieves fsencoding and standard stream details from a child process
5873
59- Returns (fsencoding , stderr_lines):
74+ Returns (encoding_details , stderr_lines):
6075
61- - fsencoding: a lowercase str value with the child's fsencoding
76+ - encoding_details: EncodingDetails for eager decoding
6277 - stderr_lines: result of calling splitlines() on the stderr output
6378
6479 The child is run in isolated mode if the current interpreter supports
6580 that.
6681 """
67- cmd = "import sys; print(sys.getfilesystemencoding().lower())"
6882 result , py_cmd = run_python_until_end (
69- "-c" , cmd ,
83+ "-c" , cls . CHILD_PROCESS_SCRIPT ,
7084 __isolated = True ,
7185 ** env_vars
7286 )
7387 if not result .rc == 0 :
7488 result .fail (py_cmd )
7589 # All subprocess outputs in this test case should be pure ASCII
76- child_fsencoding = result .out .decode ("ascii" ).rstrip ()
77- child_stderr_lines = result .err .decode ("ascii" ).rstrip ().splitlines ()
78- return child_fsencoding , child_stderr_lines
90+ adjusted_output = cls ._replace_ascii_alias (result .out )
91+ stdout_lines = adjusted_output .decode ("ascii" ).rstrip ().splitlines ()
92+ child_encoding_details = dict (cls (* stdout_lines )._asdict ())
93+ stderr_lines = result .err .decode ("ascii" ).rstrip ().splitlines ()
94+ return child_encoding_details , stderr_lines
7995
8096
81- def test_C_utf8_locale (self ):
82- # Ensure the C.UTF-8 locale is accepted entirely without complaint
83- base_var_dict = {
84- "LANG" : "" ,
85- "LC_CTYPE" : "" ,
86- "LC_ALL" : "" ,
87- }
88- for env_var in base_var_dict :
89- with self .subTest (env_var = env_var ):
90- var_dict = base_var_dict .copy ()
91- var_dict [env_var ] = "C.UTF-8"
92- fsencoding , stderr_lines = self ._get_child_fsencoding (var_dict )
93- self .assertEqual (fsencoding , "utf-8" )
94- self .assertFalse (stderr_lines )
97+ @test .support .cpython_only
98+ @unittest .skipUnless (sysconfig .get_config_var ("PY_COERCE_C_LOCALE" ),
99+ "C locale coercion disabled at build time" )
100+ class LocaleOverrideTests (unittest .TestCase ):
101+
102+ @classmethod
103+ def setUpClass (cls ):
104+ for target_locale , target_category , env_updates in _C_UTF8_LOCALES :
105+ if _set_locale_in_subprocess (target_locale , target_category ):
106+ break
107+ else :
108+ raise unittest .SkipTest ("No C-with-UTF-8 locale available" )
109+ cls .EXPECTED_COERCION_WARNING = CLI_COERCION_WARNING_FMT .format (
110+ env_updates , target_locale
111+ )
112+
113+ def _check_child_encoding_details (self ,
114+ env_vars ,
115+ expected_fsencoding ,
116+ expected_warning ):
117+ """Check the C locale handling for various configurations
118+
119+ Parameters:
120+ expected_fsencoding: the encoding the child is expected to report
121+ allow_c_locale: setting to use for PYTHONALLOWCLOCALE
122+ None: don't set the variable at all
123+ str: the value set in the child's environment
124+ """
125+ result = EncodingDetails .get_child_details (env_vars )
126+ encoding_details , stderr_lines = result
127+ self .assertEqual (encoding_details ,
128+ EncodingDetails .get_expected_details (
129+ expected_fsencoding ))
130+ self .assertEqual (stderr_lines , expected_warning )
95131
96132
97133 def _check_c_locale_coercion (self , expected_fsencoding , coerce_c_locale ):
98- """Check the handling of the C locale for various configurations
134+ """Check the C locale handling for various configurations
99135
100136 Parameters:
101137 expected_fsencoding: the encoding the child is expected to report
102138 allow_c_locale: setting to use for PYTHONALLOWCLOCALE
103139 None: don't set the variable at all
104140 str: the value set in the child's environment
105141 """
142+
143+ # Check for expected warning on stderr if C locale is coerced
106144 expected_warning = []
107145 if coerce_c_locale != "0" :
108- # Check C locale is coerced with a warning on stderr
109146 expected_warning .append (self .EXPECTED_COERCION_WARNING )
147+
148+ self .maxDiff = None
110149 base_var_dict = {
111150 "LANG" : "" ,
112151 "LC_CTYPE" : "" ,
@@ -121,9 +160,9 @@ def _check_c_locale_coercion(self, expected_fsencoding, coerce_c_locale):
121160 var_dict [env_var ] = locale_to_set
122161 if coerce_c_locale is not None :
123162 var_dict ["PYTHONCOERCECLOCALE" ] = coerce_c_locale
124- fsencoding , stderr_lines = self ._get_child_fsencoding (var_dict )
125- self . assertEqual ( fsencoding , expected_fsencoding )
126- self . assertEqual ( stderr_lines , expected_warning )
163+ self ._check_child_encoding_details (var_dict ,
164+ expected_fsencoding ,
165+ expected_warning )
127166
128167
129168 def test_test_PYTHONCOERCECLOCALE_not_set (self ):
0 commit comments