1212from test .support .script_helper import run_python_until_end
1313
1414
15- # Set the list of ways we expect to be able to ask for the "C" locale
16- EXPECTED_C_LOCALE_EQUIVALENTS = ["C" , "invalid.ascii" ]
15+ # Set the list of ways we expect to be able to ask for the "C" locale.
16+ # 'invalid.ascii' is an invalid LOCALE name and so should get turned in to the
17+ # default locale, which is traditionally C.
18+ EXPECTED_C_LOCALE_EQUIVALENTS = ["C" , "POSIX" , "invalid.ascii" ]
1719
1820# Set our expectation for the default encoding used in the C locale
1921# for the filesystem encoding and the standard streams
2022EXPECTED_C_LOCALE_STREAM_ENCODING = "ascii"
2123EXPECTED_C_LOCALE_FS_ENCODING = "ascii"
2224
2325# Set our expectation for the default locale used when none is specified
26+ DEFAULT_LOCALE_IS_C = True
2427EXPECT_COERCION_IN_DEFAULT_LOCALE = True
2528
2629TARGET_LOCALES = ["C.UTF-8" , "C.utf8" , "UTF-8" ]
2730
2831# Apply some platform dependent overrides
29- if sys .platform .startswith ("linux" ):
30- if support .is_android :
31- # Android defaults to using UTF-8 for all system interfaces
32- EXPECTED_C_LOCALE_STREAM_ENCODING = "utf-8"
33- EXPECTED_C_LOCALE_FS_ENCODING = "utf-8"
34- else :
35- # Linux distros typically alias the POSIX locale directly to the C
36- # locale.
37- # TODO: Once https://bugs.python.org/issue30672 is addressed, we'll be
38- # able to check this case unconditionally
39- EXPECTED_C_LOCALE_EQUIVALENTS .append ("POSIX" )
32+ if sys .platform == "android" :
33+ # Android defaults to using UTF-8 for all system interfaces
34+ EXPECTED_C_LOCALE_STREAM_ENCODING = "utf-8"
35+ EXPECTED_C_LOCALE_FS_ENCODING = "utf-8"
36+ elif support .linked_to_musl ():
37+ # MUSL defaults to utf-8 unless the C locale is set explicitly.
38+ EXPECTED_C_LOCALE_EQUIVALENTS = ["C" ]
39+ DEFAULT_LOCALE_IS_C = False
40+ DEFAULT_ENCODING = 'utf-8'
41+ EXPECT_COERCION_IN_DEFAULT_LOCALE = False
4042elif sys .platform .startswith ("aix" ):
4143 # AIX uses iso8859-1 in the C locale, other *nix platforms use ASCII
4244 EXPECTED_C_LOCALE_STREAM_ENCODING = "iso8859-1"
@@ -112,12 +114,16 @@ class EncodingDetails(_EncodingDetails):
112114 ])
113115
114116 @classmethod
115- def get_expected_details (cls , coercion_expected , fs_encoding , stream_encoding , env_vars ):
117+ def get_expected_details (cls , coercion_expected , fs_encoding , stream_encoding , stream_errors , env_vars ):
116118 """Returns expected child process details for a given encoding"""
117119 _stream = stream_encoding + ":{}"
118- # stdin and stdout should use surrogateescape either because the
119- # coercion triggered, or because the C locale was detected
120- stream_info = 2 * [_stream .format ("surrogateescape" )]
120+ if stream_errors is None :
121+ # stdin and stdout should use surrogateescape either because the
122+ # coercion triggered, or because the C locale was detected
123+ stream_errors = "surrogateescape"
124+
125+ stream_info = [_stream .format (stream_errors )] * 2
126+
121127 # stderr should always use backslashreplace
122128 stream_info .append (_stream .format ("backslashreplace" ))
123129 expected_lang = env_vars .get ("LANG" , "not set" )
@@ -210,6 +216,7 @@ def _check_child_encoding_details(self,
210216 env_vars ,
211217 expected_fs_encoding ,
212218 expected_stream_encoding ,
219+ expected_stream_errors ,
213220 expected_warnings ,
214221 coercion_expected ):
215222 """Check the C locale handling for the given process environment
@@ -225,6 +232,7 @@ def _check_child_encoding_details(self,
225232 coercion_expected ,
226233 expected_fs_encoding ,
227234 expected_stream_encoding ,
235+ expected_stream_errors ,
228236 env_vars
229237 )
230238 self .assertEqual (encoding_details , expected_details )
@@ -257,6 +265,7 @@ def test_external_target_locale_configuration(self):
257265 "LC_CTYPE" : "" ,
258266 "LC_ALL" : "" ,
259267 "PYTHONCOERCECLOCALE" : "" ,
268+ "PYTHONIOENCODING" : "" ,
260269 }
261270 for env_var in ("LANG" , "LC_CTYPE" ):
262271 for locale_to_set in AVAILABLE_TARGETS :
@@ -273,10 +282,43 @@ def test_external_target_locale_configuration(self):
273282 self ._check_child_encoding_details (var_dict ,
274283 expected_fs_encoding ,
275284 expected_stream_encoding ,
285+ expected_stream_errors = None ,
276286 expected_warnings = None ,
277287 coercion_expected = False )
278288
289+ def test_with_ioencoding (self ):
290+ # Explicitly setting a target locale should give the same behaviour as
291+ # is seen when implicitly coercing to that target locale
292+ self .maxDiff = None
293+
294+ expected_fs_encoding = "utf-8"
295+ expected_stream_encoding = "utf-8"
296+
297+ base_var_dict = {
298+ "LANG" : "" ,
299+ "LC_CTYPE" : "" ,
300+ "LC_ALL" : "" ,
301+ "PYTHONCOERCECLOCALE" : "" ,
302+ "PYTHONIOENCODING" : "UTF-8" ,
303+ }
304+ for env_var in ("LANG" , "LC_CTYPE" ):
305+ for locale_to_set in AVAILABLE_TARGETS :
306+ # XXX (ncoghlan): LANG=UTF-8 doesn't appear to work as
307+ # expected, so skip that combination for now
308+ # See https://bugs.python.org/issue30672 for discussion
309+ if env_var == "LANG" and locale_to_set == "UTF-8" :
310+ continue
279311
312+ with self .subTest (env_var = env_var ,
313+ configured_locale = locale_to_set ):
314+ var_dict = base_var_dict .copy ()
315+ var_dict [env_var ] = locale_to_set
316+ self ._check_child_encoding_details (var_dict ,
317+ expected_fs_encoding ,
318+ expected_stream_encoding ,
319+ expected_stream_errors = "strict" ,
320+ expected_warnings = None ,
321+ coercion_expected = False )
280322
281323@support .cpython_only
282324@unittest .skipUnless (sysconfig .get_config_var ("PY_COERCE_C_LOCALE" ),
@@ -316,15 +358,21 @@ def _check_c_locale_coercion(self,
316358 "LC_CTYPE" : "" ,
317359 "LC_ALL" : "" ,
318360 "PYTHONCOERCECLOCALE" : "" ,
361+ "PYTHONIOENCODING" : "" ,
319362 }
320363 base_var_dict .update (extra_vars )
321364 if coerce_c_locale is not None :
322365 base_var_dict ["PYTHONCOERCECLOCALE" ] = coerce_c_locale
323366
324367 # Check behaviour for the default locale
368+ _fs_encoding = fs_encoding
369+ _stream_encoding = stream_encoding
370+ if not DEFAULT_LOCALE_IS_C and 'LC_ALL' not in extra_vars :
371+ _fs_encoding = _stream_encoding = DEFAULT_ENCODING
325372 with self .subTest (default_locale = True ,
326373 PYTHONCOERCECLOCALE = coerce_c_locale ):
327- if EXPECT_COERCION_IN_DEFAULT_LOCALE :
374+ if (EXPECT_COERCION_IN_DEFAULT_LOCALE
375+ or (not DEFAULT_LOCALE_IS_C and 'LC_ALL' in extra_vars )):
328376 _expected_warnings = expected_warnings
329377 _coercion_expected = coercion_expected
330378 else :
@@ -338,8 +386,9 @@ def _check_c_locale_coercion(self,
338386 _expected_warnings == [CLI_COERCION_WARNING ]):
339387 _expected_warnings = None
340388 self ._check_child_encoding_details (base_var_dict ,
341- fs_encoding ,
342- stream_encoding ,
389+ _fs_encoding ,
390+ _stream_encoding ,
391+ None ,
343392 _expected_warnings ,
344393 _coercion_expected )
345394
@@ -348,13 +397,15 @@ def _check_c_locale_coercion(self,
348397 for env_var in ("LANG" , "LC_CTYPE" ):
349398 with self .subTest (env_var = env_var ,
350399 nominal_locale = locale_to_set ,
351- PYTHONCOERCECLOCALE = coerce_c_locale ):
400+ PYTHONCOERCECLOCALE = coerce_c_locale ,
401+ PYTHONIOENCODING = "" ):
352402 var_dict = base_var_dict .copy ()
353403 var_dict [env_var ] = locale_to_set
354404 # Check behaviour on successful coercion
355405 self ._check_child_encoding_details (var_dict ,
356406 fs_encoding ,
357407 stream_encoding ,
408+ None ,
358409 expected_warnings ,
359410 coercion_expected )
360411
@@ -411,8 +462,9 @@ def test_PYTHONCOERCECLOCALE_set_to_one(self):
411462 loc = locale .setlocale (locale .LC_CTYPE , "" )
412463 except locale .Error as e :
413464 self .skipTest (str (e ))
414- if loc == "C" :
415- self .skipTest ("test requires LC_CTYPE locale different than C" )
465+ if loc in ("C" , "POSIX" ):
466+ self .skipTest ("test requires LC_CTYPE locale different "
467+ "than C and POSIX" )
416468 if loc in TARGET_LOCALES :
417469 self .skipTest ("coerced LC_CTYPE locale: %s" % loc )
418470
0 commit comments