@@ -85,12 +85,16 @@ class EncodingDetails(_EncodingDetails):
8585 ])
8686
8787 @classmethod
88- def get_expected_details (cls , coercion_expected , fs_encoding , stream_encoding , env_vars ):
88+ def get_expected_details (cls , coercion_expected , fs_encoding , stream_encoding , stream_errors , env_vars ):
8989 """Returns expected child process details for a given encoding"""
9090 _stream = stream_encoding + ":{}"
91- # stdin and stdout should use surrogateescape either because the
92- # coercion triggered, or because the C locale was detected
93- stream_info = 2 * [_stream .format ("surrogateescape" )]
91+ if stream_errors is None :
92+ # stdin and stdout should use surrogateescape either because the
93+ # coercion triggered, or because the C locale was detected
94+ stream_errors = "surrogateescape"
95+
96+ stream_info = [_stream .format (stream_errors )] * 2
97+
9498 # stderr should always use backslashreplace
9599 stream_info .append (_stream .format ("backslashreplace" ))
96100 expected_lang = env_vars .get ("LANG" , "not set" ).lower ()
@@ -189,6 +193,7 @@ def _check_child_encoding_details(self,
189193 env_vars ,
190194 expected_fs_encoding ,
191195 expected_stream_encoding ,
196+ expected_stream_errors ,
192197 expected_warnings ,
193198 coercion_expected ):
194199 """Check the C locale handling for the given process environment
@@ -204,6 +209,7 @@ def _check_child_encoding_details(self,
204209 coercion_expected ,
205210 expected_fs_encoding ,
206211 expected_stream_encoding ,
212+ expected_stream_errors ,
207213 env_vars
208214 )
209215 self .assertEqual (encoding_details , expected_details )
@@ -234,6 +240,8 @@ def test_external_target_locale_configuration(self):
234240 "LANG" : "" ,
235241 "LC_CTYPE" : "" ,
236242 "LC_ALL" : "" ,
243+ "PYTHONCOERCECLOCALE" : "" ,
244+ "PYTHONIOENCODING" : "" ,
237245 }
238246 for env_var in ("LANG" , "LC_CTYPE" ):
239247 for locale_to_set in AVAILABLE_TARGETS :
@@ -250,10 +258,43 @@ def test_external_target_locale_configuration(self):
250258 self ._check_child_encoding_details (var_dict ,
251259 expected_fs_encoding ,
252260 expected_stream_encoding ,
261+ expected_stream_errors = None ,
253262 expected_warnings = None ,
254263 coercion_expected = False )
255264
265+ def test_with_ioencoding (self ):
266+ # Explicitly setting a target locale should give the same behaviour as
267+ # is seen when implicitly coercing to that target locale
268+ self .maxDiff = None
256269
270+ expected_fs_encoding = "utf-8"
271+ expected_stream_encoding = "utf-8"
272+
273+ base_var_dict = {
274+ "LANG" : "" ,
275+ "LC_CTYPE" : "" ,
276+ "LC_ALL" : "" ,
277+ "PYTHONCOERCECLOCALE" : "" ,
278+ "PYTHONIOENCODING" : "UTF-8" ,
279+ }
280+ for env_var in ("LANG" , "LC_CTYPE" ):
281+ for locale_to_set in AVAILABLE_TARGETS :
282+ # XXX (ncoghlan): LANG=UTF-8 doesn't appear to work as
283+ # expected, so skip that combination for now
284+ # See https://bugs.python.org/issue30672 for discussion
285+ if env_var == "LANG" and locale_to_set == "UTF-8" :
286+ continue
287+
288+ with self .subTest (env_var = env_var ,
289+ configured_locale = locale_to_set ):
290+ var_dict = base_var_dict .copy ()
291+ var_dict [env_var ] = locale_to_set
292+ self ._check_child_encoding_details (var_dict ,
293+ expected_fs_encoding ,
294+ expected_stream_encoding ,
295+ expected_stream_errors = "strict" ,
296+ expected_warnings = None ,
297+ coercion_expected = False )
257298
258299@test .support .cpython_only
259300@unittest .skipUnless (sysconfig .get_config_var ("PY_COERCE_C_LOCALE" ),
@@ -292,18 +333,43 @@ def _check_c_locale_coercion(self,
292333 "LANG" : "" ,
293334 "LC_CTYPE" : "" ,
294335 "LC_ALL" : "" ,
336+ "PYTHONCOERCECLOCALE" : "" ,
337+ "PYTHONIOENCODING" : "" ,
295338 }
296339 base_var_dict .update (extra_vars )
297- for env_var in ("LANG" , "LC_CTYPE" ):
298- for locale_to_set in ("" , "C" , "POSIX" , "invalid.ascii" ):
299- # XXX (ncoghlan): *BSD platforms don't behave as expected in the
300- # POSIX locale, so we skip that for now
301- # See https://bugs.python.org/issue30672 for discussion
302- if locale_to_set == "POSIX" :
303- continue
340+ if coerce_c_locale is not None :
341+ base_var_dict ["PYTHONCOERCECLOCALE" ] = coerce_c_locale
342+
343+ # Check behaviour for the default locale
344+ with self .subTest (default_locale = True ,
345+ PYTHONCOERCECLOCALE = coerce_c_locale ):
346+ if EXPECT_COERCION_IN_DEFAULT_LOCALE :
347+ _expected_warnings = expected_warnings
348+ _coercion_expected = coercion_expected
349+ else :
350+ _expected_warnings = None
351+ _coercion_expected = False
352+ # On Android CLI_COERCION_WARNING is not printed when all the
353+ # locale environment variables are undefined or empty. When
354+ # this code path is run with environ['LC_ALL'] == 'C', then
355+ # LEGACY_LOCALE_WARNING is printed.
356+ if (support .is_android and
357+ _expected_warnings == [CLI_COERCION_WARNING ]):
358+ _expected_warnings = None
359+ self ._check_child_encoding_details (base_var_dict ,
360+ fs_encoding ,
361+ stream_encoding ,
362+ None ,
363+ _expected_warnings ,
364+ _coercion_expected )
365+
366+ # Check behaviour for explicitly configured locales
367+ for locale_to_set in EXPECTED_C_LOCALE_EQUIVALENTS :
368+ for env_var in ("LANG" , "LC_CTYPE" ):
304369 with self .subTest (env_var = env_var ,
305370 nominal_locale = locale_to_set ,
306- PYTHONCOERCECLOCALE = coerce_c_locale ):
371+ PYTHONCOERCECLOCALE = coerce_c_locale ,
372+ PYTHONIOENCODING = "" ):
307373 var_dict = base_var_dict .copy ()
308374 var_dict [env_var ] = locale_to_set
309375 if coerce_c_locale is not None :
@@ -312,6 +378,7 @@ def _check_c_locale_coercion(self,
312378 self ._check_child_encoding_details (var_dict ,
313379 fs_encoding ,
314380 stream_encoding ,
381+ None ,
315382 expected_warnings ,
316383 coercion_expected )
317384
0 commit comments