Skip to content

Commit 12ec0d4

Browse files
committed
[GR-23342] Pass test_sys.test_exit: honour PYTHONIOENCODING and make sys.exit more compatible.
PullRequest: graalpython/1277
2 parents e474e66 + 163e61d commit 12ec0d4

File tree

11 files changed

+80
-40
lines changed

11 files changed

+80
-40
lines changed

graalpython/com.oracle.graal.python.shell/src/com/oracle/graal/python/shell/GraalPythonMain.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,11 @@ protected void launch(Builder contextBuilder) {
408408
if (cachePrefix != null) {
409409
contextBuilder.option("python.PyCachePrefix", cachePrefix);
410410
}
411+
412+
String encoding = System.getenv("PYTHONIOENCODING");
413+
if (encoding != null) {
414+
contextBuilder.option("python.StandardStreamEncoding", encoding);
415+
}
411416
}
412417
if (warnOptions == null || warnOptions.isEmpty()) {
413418
warnOptions = "";

graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_sys.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*graalpython.lib-python.3.test.test_sys.SysModuleTest.test_debugmallocstats
1616
*graalpython.lib-python.3.test.test_sys.SysModuleTest.test_dlopenflags
1717
*graalpython.lib-python.3.test.test_sys.SysModuleTest.test_executable
18+
*graalpython.lib-python.3.test.test_sys.SysModuleTest.test_exit
1819
*graalpython.lib-python.3.test.test_sys.SysModuleTest.test_getallocatedblocks
1920
*graalpython.lib-python.3.test.test_sys.SysModuleTest.test_getandroidapilevel
2021
*graalpython.lib-python.3.test.test_sys.SysModuleTest.test_getdefaultencoding

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@
152152
import com.oracle.graal.python.builtins.objects.module.PythonModule;
153153
import com.oracle.graal.python.builtins.objects.object.ObjectBuiltins;
154154
import com.oracle.graal.python.builtins.objects.object.PythonObject;
155+
import com.oracle.graal.python.builtins.objects.object.PythonObjectLibrary;
155156
import com.oracle.graal.python.builtins.objects.posix.DirEntryBuiltins;
156157
import com.oracle.graal.python.builtins.objects.posix.ScandirIteratorBuiltins;
157158
import com.oracle.graal.python.builtins.objects.random.RandomBuiltins;
@@ -568,6 +569,21 @@ public void warn(PythonBuiltinClassType type, String format, Object... args) {
568569
WarningsModuleBuiltins.WarnNode.getUncached().warnFormat(null, null, type, 1, format, args);
569570
}
570571

572+
@Override
573+
public Object getStderr() {
574+
Object sys = lookupBuiltinModule("sys");
575+
try {
576+
return PythonObjectLibrary.getUncached().lookupAttribute(sys, null, "stderr");
577+
} catch (PException e) {
578+
try {
579+
getContext().getEnv().err().write("lost sys.stderr\n".getBytes());
580+
} catch (IOException ioe) {
581+
// nothing more we can do
582+
}
583+
throw e;
584+
}
585+
}
586+
571587
private void publishBuiltinModules() {
572588
PythonModule sysModule = builtinModules.get("sys");
573589
PDict sysModules = (PDict) sysModule.getAttribute("modules");

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/GraalPythonModuleBuiltins.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@
112112
@CoreFunctions(defineModule = __GRAALPYTHON__)
113113
public class GraalPythonModuleBuiltins extends PythonBuiltins {
114114
public static final String LLVM_LANGUAGE = "llvm";
115+
private static final TruffleLogger LOGGER = PythonLanguage.getLogger(GraalPythonModuleBuiltins.class);
115116

116117
@Override
117118
protected List<? extends NodeFactory<? extends PythonBuiltinBaseNode>> getNodeFactories() {
@@ -122,6 +123,24 @@ protected List<? extends NodeFactory<? extends PythonBuiltinBaseNode>> getNodeFa
122123
public void initialize(PythonCore core) {
123124
super.initialize(core);
124125
builtinConstants.put("is_native", TruffleOptions.AOT);
126+
PythonContext ctx = core.getContext();
127+
String encodingOpt = ctx.getLanguage().getEngineOption(PythonOptions.StandardStreamEncoding);
128+
String standardStreamEncoding;
129+
String standardStreamError;
130+
if (encodingOpt != null && !encodingOpt.isEmpty()) {
131+
String[] parts = encodingOpt.split(":");
132+
standardStreamEncoding = parts[0].isEmpty() ? "utf-8" : parts[0];
133+
standardStreamError = parts.length > 1 && !parts[1].isEmpty() ? parts[1] : "strict";
134+
} else {
135+
standardStreamEncoding = "utf-8";
136+
standardStreamError = "surrogateescape";
137+
}
138+
139+
if (LOGGER.isLoggable(Level.FINE)) {
140+
LOGGER.fine(String.format("Setting default stdio encoding to %s:%s", standardStreamEncoding, standardStreamError));
141+
}
142+
this.builtinConstants.put("stdio_encoding", standardStreamEncoding);
143+
this.builtinConstants.put("stdio_error", standardStreamError);
125144
// we need these during core initialization, they are re-set in postInitialize
126145
postInitialize(core);
127146
}
@@ -426,6 +445,7 @@ public synchronized PFunction convertToBuiltin(PFunction func) {
426445
assert !functionRootNode.isPythonInternal() : "a function cannot be rewritten as builtin twice";
427446
functionRootNode = functionRootNode.rewriteWithNewSignature(signature.createWithSelf(), new NodeVisitor() {
428447

448+
@Override
429449
public boolean visit(Node node) {
430450
if (node instanceof ReadVarArgsNode) {
431451
ReadVarArgsNode varArgsNode = (ReadVarArgsNode) node;

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/WarningsModuleBuiltins.java

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
*/
4141
package com.oracle.graal.python.builtins.modules;
4242

43-
import java.io.IOException;
4443
import java.util.IllegalFormatException;
4544
import java.util.List;
4645

@@ -529,18 +528,7 @@ private static void showWarning(Object filename, int lineno, Object text, Object
529528
} else {
530529
name = polib.lookupAttribute(category, null, SpecialAttributeNames.__NAME__);
531530
}
532-
Object sys = PythonLanguage.getCore().lookupBuiltinModule("sys");
533-
Object stderr;
534-
try {
535-
stderr = polib.lookupAttribute(sys, null, "stderr");
536-
} catch (PException e) {
537-
try {
538-
PythonLanguage.getContext().getEnv().err().write("lost sys.stderr\n".getBytes());
539-
} catch (IOException ioe) {
540-
// nothing more we can do
541-
}
542-
throw e;
543-
}
531+
Object stderr = PythonLanguage.getCore().getStderr();
544532

545533
// tfel: I've inlined PyFile_WriteObject, which just calls the "write" method and
546534
// decides if we should use "repr" or "str" - in this case its always "str" for objects
@@ -823,10 +811,12 @@ private static String getSourceLine(PDict globals, int lineno) {
823811
private final Assumption passFrame = Truffle.getRuntime().createAssumption();
824812
private final Assumption passExc = Truffle.getRuntime().createAssumption();
825813

814+
@Override
826815
public Assumption needNotPassFrameAssumption() {
827816
return passFrame;
828817
}
829818

819+
@Override
830820
public Assumption needNotPassExceptionAssumption() {
831821
return passExc;
832822
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/control/TopLevelExceptionHandler.java

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@
5454
import com.oracle.graal.python.builtins.objects.frame.PFrame;
5555
import com.oracle.graal.python.builtins.objects.function.PArguments;
5656
import com.oracle.graal.python.builtins.objects.function.PKeyword;
57-
import com.oracle.graal.python.builtins.objects.ints.PInt;
5857
import com.oracle.graal.python.builtins.objects.module.PythonModule;
5958
import com.oracle.graal.python.builtins.objects.object.PythonObjectLibrary;
6059
import com.oracle.graal.python.builtins.objects.traceback.LazyTraceback;
@@ -64,6 +63,8 @@
6463
import com.oracle.graal.python.nodes.call.special.LookupAndCallUnaryNode;
6564
import com.oracle.graal.python.nodes.object.IsBuiltinClassProfile;
6665
import com.oracle.graal.python.nodes.statement.ExceptionHandlingStatementNode;
66+
import com.oracle.graal.python.nodes.util.CannotCastException;
67+
import com.oracle.graal.python.nodes.util.CastToJavaLongLossyNode;
6768
import com.oracle.graal.python.runtime.ExecutionContext.IndirectCalleeContext;
6869
import com.oracle.graal.python.runtime.PythonContext;
6970
import com.oracle.graal.python.runtime.PythonCore;
@@ -235,26 +236,22 @@ private void handleSystemExit(VirtualFrame frame, PBaseException pythonException
235236
return;
236237
}
237238
Object attribute = pythonException.getAttribute("code");
238-
Integer exitcode = null;
239-
if (attribute instanceof Number) {
240-
exitcode = ((Number) attribute).intValue();
241-
} else if (attribute instanceof PInt) {
242-
exitcode = ((PInt) attribute).intValue();
243-
} else if (attribute instanceof PNone) {
244-
exitcode = 0; // "goto done" case in CPython
245-
} else if (attribute instanceof Boolean) {
246-
exitcode = ((boolean) attribute) ? 1 : 0;
247-
}
248-
if (exitcode != null) {
239+
try {
240+
int exitcode = 0;
241+
if (attribute != PNone.NONE) {
242+
// CPython checks if the object is subclass of PyLong and only then calls
243+
// PyLong_AsLong, so it always skips __index__/__int__
244+
exitcode = (int) CastToJavaLongLossyNode.getUncached().execute(attribute);
245+
}
249246
throw new PythonExitException(this, exitcode);
247+
} catch (CannotCastException e) {
248+
// fall through
250249
}
251250
if (theContext.getOption(PythonOptions.AlwaysRunExcepthook)) {
252251
// If we failed to dig out the exit code we just print and leave
253-
try {
254-
theContext.getEnv().err().write(callStrNode.executeObject(frame, pythonException).toString().getBytes());
255-
theContext.getEnv().err().write('\n');
256-
} catch (IOException e1) {
257-
}
252+
Object stderr = theContext.getCore().getStderr();
253+
Object message = callStrNode.executeObject(frame, pythonException);
254+
PythonObjectLibrary.getUncached().lookupAndCallRegularMethod(stderr, null, "write", message);
258255
throw new PythonExitException(this, 1);
259256
}
260257
PException e = pythonException.getExceptionForReraise(pythonException.getTraceback());

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonCore.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ public interface PythonCore extends ParserErrorCallback {
7272
@Override
7373
void warn(PythonBuiltinClassType type, String format, Object... args);
7474

75+
/**
76+
* Returns the stderr object or signals error when stderr is "lost".
77+
*/
78+
Object getStderr();
79+
7580
// Accessors
7681
@Override
7782
public PythonLanguage getLanguage();

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonOptions.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ private PythonOptions() {
9494
@Option(category = OptionCategory.USER, help = "Equivalent to setting the PYTHONPATH environment variable for the standard launcher. ':'-separated list of directories prefixed to the default module search path.", stability = OptionStability.STABLE) //
9595
public static final OptionKey<String> PythonPath = new OptionKey<>("");
9696

97+
@EngineOption @Option(category = OptionCategory.USER, help = "Equivalent to setting the PYTHONIOENCODING environment variable for the standard launcher. Format: Encoding[:errors]", stability = OptionStability.STABLE) //
98+
public static final OptionKey<String> StandardStreamEncoding = new OptionKey<>("");
99+
97100
@Option(category = OptionCategory.USER, help = "Remove assert statements and any code conditional on the value of __debug__.", stability = OptionStability.STABLE) //
98101
public static final OptionKey<Boolean> PythonOptimizeFlag = new OptionKey<>(false);
99102

graalpython/lib-graalpython/pyio_patches.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@
5050

5151

5252
import _io
53-
import _sysconfig
5453
import builtins
5554

5655

@@ -84,14 +83,13 @@ def open(*args, **kwargs):
8483

8584
setattr(builtins, 'open', open)
8685

87-
88-
sys.stdin = _pyio.TextIOWrapper(_pyio.BufferedReader(sys.stdin), encoding="utf-8", line_buffering=True)
86+
sys.stdin = _pyio.TextIOWrapper(_pyio.BufferedReader(sys.stdin), encoding=__graalpython__.stdio_encoding, errors=__graalpython__.stdio_error, line_buffering=True)
8987
sys.stdin.mode = "r"
9088
sys.__stdin__ = sys.stdin
91-
sys.stdout = _pyio.TextIOWrapper(_pyio.BufferedWriter(sys.stdout), encoding="utf-8", line_buffering=True)
89+
sys.stdout = _pyio.TextIOWrapper(_pyio.BufferedWriter(sys.stdout), encoding=__graalpython__.stdio_encoding, errors=__graalpython__.stdio_error, line_buffering=True)
9290
sys.stdout.mode = "w"
9391
sys.__stdout__ = sys.stdout
94-
sys.stderr = _pyio.TextIOWrapper(_pyio.BufferedWriter(sys.stderr), encoding="utf-8", errors="backslashreplace", line_buffering=True)
92+
sys.stderr = _pyio.TextIOWrapper(_pyio.BufferedWriter(sys.stderr), encoding=__graalpython__.stdio_encoding, errors="backslashreplace", line_buffering=True)
9593
sys.stderr.mode = "w"
9694
sys.__stderr__ = sys.stderr
9795

graalpython/lib-graalpython/sys.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,12 @@ def addaudithook(hook):
170170

171171

172172
@__graalpython__.builtin
173-
def exit(arg=0):
174-
raise SystemExit(arg)
173+
def exit(arg=None):
174+
# see SystemExit_init, tuple of size 1 is unpacked
175+
code = arg
176+
if isinstance(arg, tuple) and len(arg) == 1:
177+
code = arg[0]
178+
raise SystemExit(code)
175179

176180

177181
def make_excepthook():

0 commit comments

Comments
 (0)