From 6f9bfc3a849d8dbb00a539a9a76a4d5b973f3983 Mon Sep 17 00:00:00 2001
From: Mick Jordan <mick.jordan@oracle.com>
Date: Fri, 27 Feb 2015 11:09:41 -0800
Subject: [PATCH] more connections refactoring, add more tests

---
 .../builtin/base/ConnectionFunctions.java     | 375 +++++++++++-------
 .../oracle/truffle/r/runtime/RConnection.java |  35 +-
 .../com/oracle/truffle/r/runtime/RError.java  |   1 +
 .../truffle/r/test/ExpectedTestOutput.test    |  66 ++-
 .../com/oracle/truffle/r/test/TestBase.java   |  31 ++
 .../oracle/truffle/r/test/all/AllTests.java   |  43 +-
 .../truffle/r/test/failing/FailingTests.java  |   6 +
 .../r/test/library/base/TestConnections.java  |  60 ++-
 .../r/test/rpackages/TestRPackages.java       |  24 +-
 9 files changed, 424 insertions(+), 217 deletions(-)

diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/ConnectionFunctions.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/ConnectionFunctions.java
index bbbe96ca2f..7b0e85c045 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/ConnectionFunctions.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/ConnectionFunctions.java
@@ -140,7 +140,7 @@ public abstract class ConnectionFunctions {
         Terminal("terminal"),
         File("file"),
         GZFile("gzfile"),
-        Socket("socket"),
+        Socket("socketconn"),
         Text("textConnection"),
         URL("url");
 
@@ -193,11 +193,6 @@ public abstract class ConnectionFunctions {
          * in, otherwise {@link AbstractOpenMode#Lazy}.
          */
         protected OpenMode openMode;
-        /**
-         * If {@link #opened} is {@code false} and {@link #openMode} is
-         * {@link AbstractOpenMode#Lazy}, the mode the connection should eventually be opened in.
-         */
-        private final OpenMode lazyOpenMode;
 
         private final RStringVector classHr;
 
@@ -208,28 +203,20 @@ public abstract class ConnectionFunctions {
 
         private int descriptor;
 
-        /**
-         * The constructor to use for a connection class whose default open mode is "read".
-         */
-        protected BaseRConnection(ConnectionClass conClass, String modeString) throws IOException {
-            this(conClass, modeString, new OpenMode("r", AbstractOpenMode.Read));
-        }
-
         /**
          * The constructor to use for a connection class whose default open mode is not "read", but
          * specified explicitly by "lazyMode".
          */
-        protected BaseRConnection(ConnectionClass conClass, String modeString, OpenMode lazyMode) throws IOException {
-            this(conClass, new OpenMode(modeString), lazyMode);
+        protected BaseRConnection(ConnectionClass conClass, String modeString) throws IOException {
+            this(conClass, new OpenMode(modeString));
         }
 
         /**
-         * Primitive constructor that just assigns state. USed by {@link StdConnection}s as they are
+         * Primitive constructor that just assigns state. Used by {@link StdConnection}s as they are
          * a special case but should not be used by other connection types.
          */
-        protected BaseRConnection(ConnectionClass conClass, OpenMode mode, OpenMode lazyMode) {
+        protected BaseRConnection(ConnectionClass conClass, OpenMode mode) {
             this.openMode = mode;
-            this.lazyOpenMode = lazyMode;
             String[] classes = new String[2];
             classes[0] = conClass.printName;
             classes[1] = "connection";
@@ -277,7 +264,7 @@ public abstract class ConnectionFunctions {
          */
         @Override
         public boolean isTextMode() {
-            return getRealOpenMode().isText();
+            return openMode.isText();
         }
 
         private void registerConnection() {
@@ -294,29 +281,22 @@ public abstract class ConnectionFunctions {
         @Override
         public boolean forceOpen(String modeString) throws IOException {
             boolean ret = opened;
-            checkOpen();
-            return ret;
-        }
-
-        protected void checkOpen() throws IOException {
             if (closed) {
                 throw new IOException(RError.Message.INVALID_CONNECTION.message);
             }
             if (!opened) {
                 // internal closed or lazy
                 if (openMode.abstractOpenMode == AbstractOpenMode.Lazy) {
-                    openMode = lazyOpenMode;
+                    // modeString may override the default
+                    openMode = new OpenMode(modeString);
                 }
                 createDelegateConnection();
             }
+            return ret;
         }
 
-        String getRealOpenModeAsString() {
-            return opened ? openMode.modeString : lazyOpenMode.modeString;
-        }
-
-        OpenMode getRealOpenMode() {
-            return opened ? openMode : lazyOpenMode;
+        protected void checkOpen() {
+            assert !closed && opened;
         }
 
         protected void setDelegate(DelegateRConnection conn) {
@@ -348,6 +328,18 @@ public abstract class ConnectionFunctions {
             theConnection.writeLines(lines, sep);
         }
 
+        @Override
+        public String readChar(int nchars, boolean useBytes) throws IOException {
+            checkOpen();
+            return theConnection.readChar(nchars, useBytes);
+        }
+
+        @Override
+        public void writeChar(String s, int pad, String eos, boolean useBytes) throws IOException {
+            checkOpen();
+            theConnection.writeChar(s, pad, eos, useBytes);
+        }
+
         @Override
         public void writeBin(ByteBuffer buffer) throws IOException {
             checkOpen();
@@ -459,33 +451,6 @@ public abstract class ConnectionFunctions {
         }
     }
 
-    /**
-     * Used by the {@code readXXX/writeXXX} builtins to force a connection open and return the
-     * initial state, so that it can be closed if required.
-     *
-     * TODO May need an extra argument to force text/binary depending on the caller.
-     */
-    private static String[] readLinesHelper(BufferedReader bufferedReader, int n) throws IOException {
-        ArrayList<String> lines = new ArrayList<>();
-        String line;
-        while ((line = bufferedReader.readLine()) != null) {
-            lines.add(line);
-            if (n > 0 && lines.size() == n) {
-                break;
-            }
-        }
-        String[] result = new String[lines.size()];
-        lines.toArray(result);
-        return result;
-    }
-
-    private static void writeLinesHelper(BufferedWriter bufferedWriter, RAbstractStringVector lines, String sep) throws IOException {
-        for (int i = 0; i < lines.getLength(); i++) {
-            bufferedWriter.write(lines.getDataAt(i));
-            bufferedWriter.write(sep);
-        }
-    }
-
     /**
      * {@code readLines} from an {@link InputStream}. It would be convenient to use a
      * {@link BufferedReader} but mixing binary and text operations, which is a requirement, would
@@ -543,6 +508,18 @@ public abstract class ConnectionFunctions {
         }
     }
 
+    private static void writeCharHelper(OutputStream out, String s, int pad, String eos) throws IOException {
+        out.write(s.getBytes());
+        if (pad > 0) {
+            for (int i = 0; i < pad; i++) {
+                out.write(0);
+            }
+        }
+        if (eos != null && eos.length() > 0) {
+            out.write(eos.getBytes());
+        }
+    }
+
     private static void writeBinHelper(ByteBuffer buffer, OutputStream outputStream) throws IOException {
         int n = buffer.remaining();
         byte[] b = new byte[n];
@@ -583,6 +560,19 @@ public abstract class ConnectionFunctions {
         return totalRead;
     }
 
+    private static String readCharHelper(int nchars, InputStream in, @SuppressWarnings("unused") boolean useBytes) throws IOException {
+        byte[] bytes = new byte[nchars];
+        in.read(bytes);
+        int j = 0;
+        for (; j < bytes.length; j++) {
+            // strings end at 0
+            if (bytes[j] == 0) {
+                break;
+            }
+        }
+        return new String(bytes, 0, j);
+    }
+
     private static byte[] checkBuffer(byte[] buffer, int n) {
         if (n > buffer.length - 1) {
             byte[] newBuffer = new byte[buffer.length + buffer.length / 2];
@@ -633,6 +623,11 @@ public abstract class ConnectionFunctions {
             throw new IOException(RError.Message.CANNOT_WRITE_CONNECTION.message);
         }
 
+        @Override
+        public void writeChar(String s, int pad, String eos, boolean useBytes) throws IOException {
+            throw new IOException(RError.Message.CANNOT_WRITE_CONNECTION.message);
+        }
+
         @Override
         public void writeBin(ByteBuffer buffer) throws IOException {
             throw new IOException(RError.Message.CANNOT_WRITE_CONNECTION.message);
@@ -669,6 +664,11 @@ public abstract class ConnectionFunctions {
             throw new IOException(RError.Message.CANNOT_READ_CONNECTION.message);
         }
 
+        @Override
+        public String readChar(int nchars, boolean useBytes) throws IOException {
+            throw new IOException(RError.Message.CANNOT_READ_CONNECTION.message);
+        }
+
         @Override
         public int readBin(ByteBuffer buffer) throws IOException {
             throw new IOException(RError.Message.CANNOT_READ_CONNECTION.message);
@@ -695,12 +695,29 @@ public abstract class ConnectionFunctions {
         }
     }
 
+    private abstract static class DelegateReadWriteRConnection extends DelegateRConnection {
+        protected DelegateReadWriteRConnection(BaseRConnection base) {
+            super(base);
+        }
+
+        @Override
+        public boolean canRead() {
+            return true;
+        }
+
+        @Override
+        public boolean canWrite() {
+            return true;
+        }
+
+    }
+
     /**
      * Subclasses are special in that they do not use delegation as the connection is always open.
      */
     private abstract static class StdConnection extends BaseRConnection {
         StdConnection(OpenMode openMode) {
-            super(ConnectionClass.Terminal, openMode, null);
+            super(ConnectionClass.Terminal, openMode);
             this.opened = true;
         }
 
@@ -887,11 +904,6 @@ public abstract class ConnectionFunctions {
             this.path = Utils.tildeExpand(path);
         }
 
-        protected BasePathRConnection(String path, ConnectionClass conClass, String modeString, OpenMode lazyMode) throws IOException {
-            super(conClass, modeString, lazyMode);
-            this.path = Utils.tildeExpand(path);
-        }
-
         @Override
         public String getSummaryDescription() {
             return path;
@@ -940,12 +952,10 @@ public abstract class ConnectionFunctions {
 
     private static class FileReadTextRConnection extends DelegateReadRConnection {
         private BufferedInputStream inputStream;
-        private BufferedReader bufferedReader;
 
         FileReadTextRConnection(BasePathRConnection base) throws IOException {
             super(base);
             inputStream = new BufferedInputStream(new FileInputStream(base.path));
-            bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
         }
 
         @Override
@@ -961,7 +971,12 @@ public abstract class ConnectionFunctions {
         @TruffleBoundary
         @Override
         public String[] readLinesInternal(int n) throws IOException {
-            return readLinesHelper(bufferedReader, n);
+            return readLinesHelper(inputStream, n);
+        }
+
+        @Override
+        public String readChar(int nchars, boolean useBytes) throws IOException {
+            return readCharHelper(nchars, inputStream, useBytes);
         }
 
         @Override
@@ -977,19 +992,22 @@ public abstract class ConnectionFunctions {
 
         @Override
         public void internalClose() throws IOException {
-            bufferedReader.close();
+            inputStream.close();
         }
 
     }
 
     private static class FileWriteTextRConnection extends DelegateWriteRConnection {
         private BufferedOutputStream outputStream;
-        private BufferedWriter bufferedWriter;
 
         FileWriteTextRConnection(FileRConnection base, boolean append) throws IOException {
             super(base);
             outputStream = new BufferedOutputStream(new FileOutputStream(base.path, append));
-            bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
+        }
+
+        @Override
+        public void writeChar(String s, int pad, String eos, boolean useBytes) throws IOException {
+            writeCharHelper(outputStream, s, pad, eos);
         }
 
         @Override
@@ -999,7 +1017,7 @@ public abstract class ConnectionFunctions {
 
         @Override
         public void writeLines(RAbstractStringVector lines, String sep) throws IOException {
-            writeLinesHelper(bufferedWriter, lines, sep);
+            writeLinesHelper(outputStream, lines, sep);
             flush();
         }
 
@@ -1016,12 +1034,12 @@ public abstract class ConnectionFunctions {
 
         @Override
         public void internalClose() throws IOException {
-            bufferedWriter.close();
+            outputStream.close();
         }
 
         @Override
         public void flush() throws IOException {
-            bufferedWriter.flush();
+            outputStream.flush();
         }
     }
 
@@ -1033,6 +1051,11 @@ public abstract class ConnectionFunctions {
             inputStream = new FileInputStream(base.path);
         }
 
+        @Override
+        public String readChar(int nchars, boolean useBytes) throws IOException {
+            return readCharHelper(nchars, inputStream, useBytes);
+        }
+
         @Override
         public int readBin(ByteBuffer buffer) throws IOException {
             return inputStream.getChannel().read(buffer);
@@ -1064,6 +1087,7 @@ public abstract class ConnectionFunctions {
         public void internalClose() throws IOException {
             inputStream.close();
         }
+
     }
 
     private static class FileWriteBinaryConnection extends DelegateWriteRConnection {
@@ -1074,6 +1098,11 @@ public abstract class ConnectionFunctions {
             outputStream = new FileOutputStream(base.path, append);
         }
 
+        @Override
+        public void writeChar(String s, int pad, String eos, boolean useBytes) throws IOException {
+            writeCharHelper(outputStream, s, pad, eos);
+        }
+
         @Override
         public void writeBin(ByteBuffer buffer) throws IOException {
             outputStream.getChannel().write(buffer);
@@ -1118,8 +1147,10 @@ public abstract class ConnectionFunctions {
         @TruffleBoundary
         @SuppressWarnings("unused")
         protected Object file(RAbstractStringVector description, RAbstractStringVector open, byte blocking, RAbstractStringVector encoding, byte raw) {
-            // temporarily return to avoid missing print support
             controlVisibility();
+            if (!RRuntime.fromLogical(blocking)) {
+                throw RError.nyi(getEncapsulatingSourceSection(), " non-blocking mode not supported");
+            }
             try {
                 return new FileRConnection(description.getDataAt(0), open.getDataAt(0));
             } catch (IOException ex) {
@@ -1141,7 +1172,7 @@ public abstract class ConnectionFunctions {
      */
     private static class GZIPRConnection extends BasePathRConnection {
         GZIPRConnection(String path, String modeString) throws IOException {
-            super(path, ConnectionClass.GZFile, modeString, new OpenMode("rb", AbstractOpenMode.ReadBinary));
+            super(path, ConnectionClass.GZFile, modeString);
             if (openMode.abstractOpenMode != AbstractOpenMode.Lazy) {
                 createDelegateConnection();
             }
@@ -1178,6 +1209,11 @@ public abstract class ConnectionFunctions {
             inputStream = new GZIPInputStream(new FileInputStream(base.path), RConnection.GZIP_BUFFER_SIZE);
         }
 
+        @Override
+        public String readChar(int nchars, boolean useBytes) throws IOException {
+            return readCharHelper(nchars, inputStream, useBytes);
+        }
+
         @Override
         public int readBin(ByteBuffer buffer) throws IOException {
             return readBinHelper(buffer, inputStream);
@@ -1241,6 +1277,11 @@ public abstract class ConnectionFunctions {
             writeLinesHelper(outputStream, lines, sep);
         }
 
+        @Override
+        public void writeChar(String s, int pad, String eos, boolean useBytes) throws IOException {
+            writeCharHelper(outputStream, s, pad, eos);
+        }
+
         @Override
         public void writeBin(ByteBuffer buffer) throws IOException {
             writeBinHelper(buffer, outputStream);
@@ -1378,6 +1419,11 @@ public abstract class ConnectionFunctions {
             throw RError.nyi(null, " readBinChars on text connection");
         }
 
+        @Override
+        public String readChar(int nchars, boolean useBytes) throws IOException {
+            throw RError.nyi(null, " readChar on text connection");
+        }
+
     }
 
     @RBuiltin(name = "textConnection", kind = INTERNAL, parameterNames = {"nm", "object", "open", "env", "type"})
@@ -1400,74 +1446,100 @@ public abstract class ConnectionFunctions {
      * While binary operations, e.g. {@code writeBin} are only legal on binary connections, text
      * operations are legal on text and binary connections.
      *
-     * TODO Non-blocking support. Also, while not the default, and evidently rare, GnuR does support
-     * lazy opening of a socket.
+     * TODO Non-blocking support.
      */
-    private abstract static class RSocketConnection extends BaseRConnection {
-        protected String host;
-        protected Socket socket;
-        protected InputStream inputStream;
-        protected OutputStream outputStream;
-
-        protected RSocketConnection(String modeString, String host) throws IOException {
+    private static class RSocketConnection extends BaseRConnection {
+        protected final boolean server;
+        protected final String host;
+        protected final int port;
+        protected final boolean blocking;
+        protected final int timeout;
+
+        protected RSocketConnection(String modeString, boolean server, String host, int port, boolean blocking, int timeout) throws IOException {
             super(ConnectionClass.Socket, modeString);
+            this.server = server;
             this.host = host;
+            this.port = port;
+            this.blocking = blocking;
+            this.timeout = timeout;
+            if (openMode.abstractOpenMode != AbstractOpenMode.Lazy) {
+                createDelegateConnection();
+            }
         }
 
         @Override
         protected void createDelegateConnection() throws IOException {
-            throw RInternalError.shouldNotReachHere();
+            DelegateRConnection delegate = server ? new RServerSocketConnection(this) : new RClientSocketConnection(this);
+            setDelegate(delegate);
         }
 
-        protected String getBasicSummaryData() {
-            return host + ":" + socket.getPort();
+        @Override
+        public String getSummaryDescription() {
+            return (server ? "<-" : "->") + host + ":" + port;
+        }
+    }
 
+    private abstract static class RSocketReadWriteConnection extends DelegateReadWriteRConnection {
+        protected Socket socket;
+        protected InputStream inputStream;
+        protected OutputStream outputStream;
+        protected final RSocketConnection thisBase;
+
+        protected RSocketReadWriteConnection(RSocketConnection base) {
+            super(base);
+            this.thisBase = base;
         }
 
         protected void openStreams() throws IOException {
+            if (!thisBase.blocking) {
+                socket.setSoTimeout(thisBase.timeout * 1000);
+            }
             inputStream = socket.getInputStream();
             outputStream = socket.getOutputStream();
         }
 
         @Override
         public String[] readLinesInternal(int n) throws IOException {
-            checkOpen();
             return readLinesHelper(inputStream, n);
         }
 
         @Override
         public InputStream getInputStream() throws IOException {
-            checkOpen();
             return inputStream;
         }
 
         @Override
         public OutputStream getOutputStream() throws IOException {
-            checkOpen();
             return outputStream;
         }
 
         @Override
         public void writeLines(RAbstractStringVector lines, String sep) throws IOException {
-            checkOpen();
             writeLinesHelper(outputStream, lines, sep);
         }
 
         @Override
         public void writeBin(ByteBuffer buffer) throws IOException {
-            checkOpen();
             writeBinHelper(buffer, outputStream);
         }
 
         @Override
         public int readBin(ByteBuffer buffer) throws IOException {
-            checkOpen();
             return readBinHelper(buffer, inputStream);
         }
 
+        @Override
+        public void writeChar(String s, int pad, String eos, boolean useBytes) throws IOException {
+            writeCharHelper(outputStream, s, pad, eos);
+        }
+
+        @Override
+        public String readChar(int nchars, boolean useBytes) throws IOException {
+            return readCharHelper(nchars, inputStream, useBytes);
+        }
+
         @Override
         public byte[] readBinChars() throws IOException {
-            checkOpen();
             return readBinCharsHelper(inputStream);
         }
 
@@ -1478,45 +1550,39 @@ public abstract class ConnectionFunctions {
 
         @Override
         public void close() throws IOException {
+            base.closed = true;
+            internalClose();
+        }
+
+        @Override
+        public void internalClose() throws IOException {
             socket.close();
-            closed = true;
         }
+
     }
 
-    private static class RServerSocketConnection extends RSocketConnection {
+    private static class RServerSocketConnection extends RSocketReadWriteConnection {
         private ServerSocket serverSocket;
 
-        RServerSocketConnection(String modeString, String host, int port) throws IOException {
-            super(modeString, host);
-            serverSocket = new ServerSocket(port);
+        RServerSocketConnection(RSocketConnection base) throws IOException {
+            super(base);
+            serverSocket = new ServerSocket(base.port);
             socket = serverSocket.accept();
             openStreams();
-            opened = true;
-        }
-
-        @Override
-        public String getSummaryDescription() {
-            return "<-" + getBasicSummaryData();
         }
 
         @Override
-        public void close() throws IOException {
-            super.close();
+        public void internalClose() throws IOException {
+            super.internalClose();
             serverSocket.close();
         }
     }
 
-    private static class RClientSocketConnection extends RSocketConnection {
-        RClientSocketConnection(String modeString, String host, int port) throws IOException {
-            super(modeString, host);
-            socket = new Socket(host, port);
+    private static class RClientSocketConnection extends RSocketReadWriteConnection {
+        RClientSocketConnection(RSocketConnection base) throws IOException {
+            super(base);
+            socket = new Socket(base.host, base.port);
             openStreams();
-            opened = true;
-        }
-
-        @Override
-        public String getSummaryDescription() {
-            return "->" + getBasicSummaryData();
         }
 
     }
@@ -1526,24 +1592,21 @@ public abstract class ConnectionFunctions {
         @CreateCast("arguments")
         public RNode[] castArguments(RNode[] arguments) {
             arguments[1] = CastIntegerNodeGen.create(arguments[1], false, false, false);
-            arguments[6] = CastDoubleNodeGen.create(arguments[6], false, false, false);
+            arguments[6] = CastIntegerNodeGen.create(arguments[6], false, false, false);
             return arguments;
         }
 
         @SuppressWarnings("unused")
         @TruffleBoundary
         @Specialization
-        protected Object socketConnection(RAbstractStringVector host, RAbstractIntVector portVec, byte server, byte blocking, RAbstractStringVector open, RAbstractStringVector encoding, double timeout) {
+        protected Object socketConnection(RAbstractStringVector host, RAbstractIntVector portVec, byte server, byte blocking, RAbstractStringVector open, RAbstractStringVector encoding, int timeout) {
             int port = portVec.getDataAt(0);
             String modeString = open.getDataAt(0);
-            if (modeString.length() == 0) {
-                throw RError.nyi(getEncapsulatingSourceSection(), " lazy open for sockets");
-            }
             try {
                 if (RRuntime.fromLogical(server)) {
-                    return new RServerSocketConnection(modeString, host.getDataAt(0), port);
+                    return new RSocketConnection(modeString, true, host.getDataAt(0), port, RRuntime.fromLogical(blocking), timeout);
                 } else {
-                    return new RClientSocketConnection(modeString, host.getDataAt(0), port);
+                    return new RSocketConnection(modeString, false, host.getDataAt(0), port, RRuntime.fromLogical(blocking), timeout);
                 }
             } catch (IOException ex) {
                 throw RError.error(getEncapsulatingSourceSection(), RError.Message.CANNOT_OPEN_CONNECTION);
@@ -1583,14 +1646,12 @@ public abstract class ConnectionFunctions {
 
     private static class URLReadRConnection extends DelegateReadRConnection {
 
-        private InputStream inputStream;
-        private BufferedReader bufferedReader;
+        private BufferedInputStream inputStream;
 
         protected URLReadRConnection(URLRConnection base) throws MalformedURLException, IOException {
             super(base);
             URL url = new URL(base.urlString);
-            inputStream = url.openStream();
-            bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
+            inputStream = new BufferedInputStream(url.openStream());
         }
 
         @Override
@@ -1605,7 +1666,7 @@ public abstract class ConnectionFunctions {
 
         @Override
         public String[] readLinesInternal(int n) throws IOException {
-            return readLinesHelper(bufferedReader, n);
+            return readLinesHelper(inputStream, n);
         }
 
         @Override
@@ -1621,7 +1682,12 @@ public abstract class ConnectionFunctions {
 
         @Override
         public void internalClose() throws IOException {
-            bufferedReader.close();
+            inputStream.close();
+        }
+
+        @Override
+        public String readChar(int nchars, boolean useBytes) throws IOException {
+            return readCharHelper(nchars, inputStream, useBytes);
         }
 
     }
@@ -1673,7 +1739,7 @@ public abstract class ConnectionFunctions {
             Object[] data = new Object[NAMES.getLength()];
             data[0] = baseCon.getSummaryDescription();
             data[1] = baseCon.classHr.getDataAt(0);
-            data[2] = baseCon.getRealOpenModeAsString();
+            data[2] = baseCon.openMode.modeString;
             data[3] = baseCon.getSummaryText();
             data[4] = baseCon.closed || !baseCon.opened ? "closed" : "opened";
             data[5] = baseCon.canRead() ? "yes" : "no";
@@ -1966,12 +2032,17 @@ public abstract class ConnectionFunctions {
         }
 
         @Specialization(guards = {"!ncharsEmpty", "!useBytesEmpty"})
+        @TruffleBoundary
         protected RStringVector readChar(RConnection con, RAbstractIntVector nchars, RAbstractLogicalVector useBytes) {
             controlVisibility();
             boolean wasOpen = true;
             try {
-                wasOpen = con.forceOpen("rt");
-                return con.readChar(nchars, useBytes.getDataAt(0) == RRuntime.LOGICAL_TRUE);
+                wasOpen = con.forceOpen("rb");
+                String[] data = new String[nchars.getLength()];
+                for (int i = 0; i < data.length; i++) {
+                    data[i] = con.readChar(nchars.getDataAt(i), useBytes.getDataAt(0) == RRuntime.LOGICAL_TRUE);
+                }
+                return RDataFactory.createStringVector(data, RDataFactory.COMPLETE_VECTOR);
             } catch (IOException x) {
                 throw RError.error(getEncapsulatingSourceSection(), RError.Message.ERROR_READING_CONNECTION, x.getMessage());
             } finally {
@@ -1992,6 +2063,37 @@ public abstract class ConnectionFunctions {
 
     }
 
+    @RBuiltin(name = "writeChar", kind = INTERNAL, parameterNames = {"object", "con", "nchars", "eos", "useBytes"})
+    public abstract static class WriteChar extends InternalCloseHelper {
+        @TruffleBoundary
+        @Specialization
+        protected RNull writeChar(RAbstractStringVector object, RConnection con, RAbstractIntVector nchars, RAbstractStringVector eos, byte useBytes) {
+            controlVisibility();
+            boolean wasOpen = true;
+            try {
+                wasOpen = con.forceOpen("wb");
+                int length = object.getLength();
+                for (int i = 0; i < length; i++) {
+                    String s = object.getDataAt(i);
+                    int nc = nchars.getDataAt(i % length);
+                    int pad = nc - s.length();
+                    if (pad > 0) {
+                        RContext.getInstance().setEvalWarning(RError.Message.MORE_CHARACTERS.message);
+                    }
+                    con.writeChar(s, pad, eos.getDataAt(i % length), RRuntime.fromLogical(useBytes));
+                }
+            } catch (IOException x) {
+                throw RError.error(getEncapsulatingSourceSection(), RError.Message.ERROR_READING_CONNECTION, x.getMessage());
+            } finally {
+                if (!wasOpen) {
+                    internalClose(con);
+                }
+            }
+            forceVisibility(false);
+            return RNull.instance;
+        }
+    }
+
     /**
      * Sets the order of the given {@code ByteBuffer} from value of {@code swap}. The value of
      * {@code swap} will be {@code true} iff the original requested order was the opposite of the
@@ -2186,9 +2288,10 @@ public abstract class ConnectionFunctions {
     public abstract static class WriteBin extends InternalCloseHelper {
         @TruffleBoundary
         @Specialization
-        protected Object writeBin(RAbstractVector object, RConnection con, int size, byte swapArg, @SuppressWarnings("unused") byte useBytesArg) {
+        protected Object writeBin(RAbstractVector object, RConnection con, int size, byte swapArg, byte useBytesArg) {
             boolean swap = RRuntime.fromLogical(swapArg);
             boolean wasOpen = true;
+            boolean useBytes = RRuntime.fromLogical(useBytesArg);
             if (object.getLength() > 0) {
                 try {
                     if (getBaseConnection(con).isTextMode()) {
@@ -2202,7 +2305,7 @@ public abstract class ConnectionFunctions {
                     } else if (object instanceof RAbstractComplexVector) {
                         writeComplex((RAbstractComplexVector) object, con, size, swap);
                     } else if (object instanceof RAbstractStringVector) {
-                        writeString((RAbstractStringVector) object, con, size, swap);
+                        writeString((RAbstractStringVector) object, con, size, swap, useBytes);
                     } else if (object instanceof RAbstractStringVector) {
                         writeLogical((RAbstractLogicalVector) object, con, size, swap);
                     } else if (object instanceof RRawVector) {
@@ -2265,7 +2368,7 @@ public abstract class ConnectionFunctions {
         }
 
         @SuppressWarnings("unused")
-        private static void writeString(RAbstractStringVector object, RConnection con, int size, boolean swap) throws IOException {
+        private static void writeString(RAbstractStringVector object, RConnection con, int size, boolean swap, boolean useBytes) throws IOException {
             int length = object.getLength();
             byte[][] data = new byte[length][];
             int totalLength = 0;
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RConnection.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RConnection.java
index b0a39f635f..0441903b68 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RConnection.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RConnection.java
@@ -242,27 +242,24 @@ public abstract class RConnection implements RClassHierarchy {
 
     public abstract void flush() throws IOException;
 
-    public RStringVector readChar(RAbstractIntVector nchars, @SuppressWarnings("unused") boolean useBytes) throws IOException {
-        InputStream inputStream = getInputStream();
-        String[] data = new String[nchars.getLength()];
-        for (int i = 0; i < data.length; i++) {
-            byte[] bytes = new byte[nchars.getDataAt(i)];
-            inputStream.read(bytes);
-            int j = 0;
-            for (; j < bytes.length; j++) {
-                // strings end at 0
-                if (bytes[j] == 0) {
-                    break;
-                }
-            }
-            data[i] = new String(bytes, 0, j, "US-ASCII");
-        }
-
-        return RDataFactory.createStringVector(data, RDataFactory.COMPLETE_VECTOR);
+    public abstract int getDescriptor();
 
-    }
+    /**
+     * Internal connection-specific support for the {@code writeChar} builtin.
+     *
+     * @param s string to output
+     * @param pad number of (zero) pad bytes
+     * @param eos string to append to s
+     * @param useBytes TODO
+     */
+    public abstract void writeChar(String s, int pad, String eos, boolean useBytes) throws IOException;
 
-    public abstract int getDescriptor();
+    /**
+     * Internal connection-specific support for the {@code readChar} builtin.
+     *
+     * @param nchars number of characters to read
+     */
+    public abstract String readChar(int nchars, boolean useBytes) throws IOException;
 
     /**
      * Internal connection-specific support for the {@code writeBin} builtin. The implementation
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RError.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RError.java
index 18f6321987..eac08f9cc0 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RError.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RError.java
@@ -297,6 +297,7 @@ public final class RError extends RuntimeException {
         CANNOT_WRITE_CONNECTION("cannot write to this connection"),
         ONLY_READ_BINARY_CONNECTION("can only read from a binary connection"),
         ONLY_WRITE_BINARY_CONNECTION("can only write to a binary connection"),
+        MORE_CHARACTERS("more characters requested than are in the string - will zero-pad"),
         TOO_FEW_LINES_READ_LINES("too few lines read in readLineWRITE_ONs"),
         INVALID_CONNECTION("invalid connection"),
         OUT_OF_RANGE("out-of-range values treated as 0 in coercion to raw"),
diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/ExpectedTestOutput.test b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/ExpectedTestOutput.test
index 058623b032..bd3fa51716 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/ExpectedTestOutput.test
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/ExpectedTestOutput.test
@@ -1,76 +1,104 @@
-##com.oracle.truffle.r.test.library.base.TestConnections.testPushBack
+##com.oracle.truffle.r.test.library.base.TestConnections.testFileWriteReadBin
+#{ readBin(file("com.oracle.truffle.r.test/library.base.conn/wb1", "rb"), 3) }
+numeric(0)
+
+##com.oracle.truffle.r.test.library.base.TestConnections.testFileWriteReadBin
+#{ writeBin("abc", file("com.oracle.truffle.r.test/library.base.conn/wb1", open="wb")) }
+
+##com.oracle.truffle.r.test.library.base.TestConnections.testFileWriteReadChar
+#{ readChar(file("com.oracle.truffle.r.test/library.base.conn/wc1"), 3) }
+[1] "abc"
+
+##com.oracle.truffle.r.test.library.base.TestConnections.testFileWriteReadChar
+#{ writeChar("abc", file("com.oracle.truffle.r.test/library.base.conn/wc1")) }
+
+##com.oracle.truffle.r.test.library.base.TestConnections.testFileWriteReadLines
+#{ con <- file("com.oracle.truffle.r.test/library.base.conn/wl2"); readLines(con, 2) }
+[1] "line1" "line2"
+
+##com.oracle.truffle.r.test.library.base.TestConnections.testFileWriteReadLines
+#{ con <- file("com.oracle.truffle.r.test/library.base.conn/wl2"); writeLines(c("line1", "line2"), con) }
+
+##com.oracle.truffle.r.test.library.base.TestConnections.testFileWriteReadLines
+#{ readLines(file("com.oracle.truffle.r.test/library.base.conn/wl1"), 2) }
+[1] "line1" "line2"
+
+##com.oracle.truffle.r.test.library.base.TestConnections.testFileWriteReadLines
+#{ writeLines(c("line1", "line2"), file("com.oracle.truffle.r.test/library.base.conn/wl1")) }
+
+##com.oracle.truffle.r.test.library.base.TestConnections.testPushBackTextConnection
 #{ con<-textConnection(c("a","b","c","d")); pushBack("G", con); clearPushBack(con); pushBackLength(con) }
 [1] 0
 
-##com.oracle.truffle.r.test.library.base.TestConnections.testPushBack
+##com.oracle.truffle.r.test.library.base.TestConnections.testPushBackTextConnection
 #{ con<-textConnection(c("a","b","c","d")); pushBack("G", con); pushBackLength(con) }
 [1] 1
 
-##com.oracle.truffle.r.test.library.base.TestConnections.testPushBack
+##com.oracle.truffle.r.test.library.base.TestConnections.testPushBackTextConnection
 #{ con<-textConnection(c("a","b","c","d")); pushBack("G", con); readLines(con, 1) }
 [1] "G"
 
-##com.oracle.truffle.r.test.library.base.TestConnections.testPushBack
+##com.oracle.truffle.r.test.library.base.TestConnections.testPushBackTextConnection
 #{ con<-textConnection(c("a","b","c","d")); pushBack("G", con); readLines(con, 2) }
 [1] "G" "a"
 
-##com.oracle.truffle.r.test.library.base.TestConnections.testPushBack
+##com.oracle.truffle.r.test.library.base.TestConnections.testPushBackTextConnection
 #{ con<-textConnection(c("a","b","c","d")); pushBack("G", con, newLine=FALSE); readLines(con, 1) }
 [1] "Ga"
 
-##com.oracle.truffle.r.test.library.base.TestConnections.testPushBack
+##com.oracle.truffle.r.test.library.base.TestConnections.testPushBackTextConnection
 #{ con<-textConnection(c("a","b","c","d")); pushBack("G", con, newLine=FALSE); readLines(con, 2) }
 [1] "Ga" "b"
 
-##com.oracle.truffle.r.test.library.base.TestConnections.testPushBack
+##com.oracle.truffle.r.test.library.base.TestConnections.testPushBackTextConnection
 #{ con<-textConnection(c("a","b","c","d")); pushBack(c("G", "H"), con); pushBackLength(con) }
 [1] 2
 
-##com.oracle.truffle.r.test.library.base.TestConnections.testPushBack
+##com.oracle.truffle.r.test.library.base.TestConnections.testPushBackTextConnection
 #{ con<-textConnection(c("a","b","c","d")); pushBack(c("G", "H"), con); readLines(con, 1) }
 [1] "G"
 
-##com.oracle.truffle.r.test.library.base.TestConnections.testPushBack
+##com.oracle.truffle.r.test.library.base.TestConnections.testPushBackTextConnection
 #{ con<-textConnection(c("a","b","c","d")); pushBack(c("G", "H"), con); readLines(con, 2) }
 [1] "G" "H"
 
-##com.oracle.truffle.r.test.library.base.TestConnections.testPushBack
+##com.oracle.truffle.r.test.library.base.TestConnections.testPushBackTextConnection
 #{ con<-textConnection(c("a","b","c","d")); pushBack(c("G", "H"), con, newLine=FALSE); pushBackLength(con) }
 [1] 2
 
-##com.oracle.truffle.r.test.library.base.TestConnections.testPushBack
+##com.oracle.truffle.r.test.library.base.TestConnections.testPushBackTextConnection
 #{ con<-textConnection(c("a","b","c","d")); pushBack(c("G", "H"), con, newLine=FALSE); readLines(con, 1) }
 [1] "GHa"
 
-##com.oracle.truffle.r.test.library.base.TestConnections.testPushBack
+##com.oracle.truffle.r.test.library.base.TestConnections.testPushBackTextConnection
 #{ con<-textConnection(c("a","b","c","d")); pushBack(c("G", "H"), con, newLine=FALSE); readLines(con, 2) }
 [1] "GHa" "b"
 
-##com.oracle.truffle.r.test.library.base.TestConnections.testPushBack
+##com.oracle.truffle.r.test.library.base.TestConnections.testPushBackTextConnection
 #{ con<-textConnection(c("a","b","c","d")); pushBack(c("G\nH"), con); pushBackLength(con) }
 [1] 1
 
-##com.oracle.truffle.r.test.library.base.TestConnections.testPushBack
+##com.oracle.truffle.r.test.library.base.TestConnections.testPushBackTextConnection
 #{ con<-textConnection(c("a","b","c","d")); pushBack(c("G\nH"), con); readLines(con, 1) }
 [1] "G"
 
-##com.oracle.truffle.r.test.library.base.TestConnections.testPushBack
+##com.oracle.truffle.r.test.library.base.TestConnections.testPushBackTextConnection
 #{ con<-textConnection(c("a","b","c","d")); pushBack(c("G\nH"), con); readLines(con, 2) }
 [1] "G" "H"
 
-##com.oracle.truffle.r.test.library.base.TestConnections.testPushBack
+##com.oracle.truffle.r.test.library.base.TestConnections.testPushBackTextConnection
 #{ con<-textConnection(c("a","b","c","d")); pushBack(c("G\nH"), con, newLine=FALSE); pushBackLength(con) }
 [1] 1
 
-##com.oracle.truffle.r.test.library.base.TestConnections.testPushBack
+##com.oracle.truffle.r.test.library.base.TestConnections.testPushBackTextConnection
 #{ con<-textConnection(c("a","b","c","d")); pushBack(c("G\nH"), con, newLine=FALSE); readLines(con, 1) }
 [1] "G"
 
-##com.oracle.truffle.r.test.library.base.TestConnections.testPushBack
+##com.oracle.truffle.r.test.library.base.TestConnections.testPushBackTextConnection
 #{ con<-textConnection(c("a","b","c","d")); pushBack(c("G\nH"), con, newLine=FALSE); readLines(con, 2) }
 [1] "G"  "Ha"
 
-##com.oracle.truffle.r.test.library.base.TestConnections.testPushBack
+##com.oracle.truffle.r.test.library.base.TestConnections.testPushBackTextConnection
 #{ con<-textConnection(c("a","b","c","d")); pushBackLength(con) }
 [1] 0
 
diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/TestBase.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/TestBase.java
index d5d0249726..c9a6ac21c1 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/TestBase.java
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/TestBase.java
@@ -13,6 +13,7 @@ package com.oracle.truffle.r.test;
 import java.io.*;
 import java.net.*;
 import java.nio.file.*;
+import java.nio.file.attribute.*;
 
 import static org.junit.Assert.fail;
 
@@ -703,4 +704,34 @@ public class TestBase {
             quiet = true;
         }
     }
+
+    protected static boolean deleteDir(Path dir) {
+        try {
+            Files.walkFileTree(dir, DELETE_VISITOR);
+        } catch (Exception e) {
+            return false;
+        }
+        return true;
+    }
+
+    private static final class DeleteVisitor extends SimpleFileVisitor<Path> {
+
+        @Override
+        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+            return del(file);
+        }
+
+        @Override
+        public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+            return del(dir);
+        }
+
+        private static FileVisitResult del(Path p) throws IOException {
+            Files.delete(p);
+            return FileVisitResult.CONTINUE;
+        }
+
+    }
+
+    private static final DeleteVisitor DELETE_VISITOR = new DeleteVisitor();
 }
diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/all/AllTests.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/all/AllTests.java
index 4728f5e3a8..eeb5adc9da 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/all/AllTests.java
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/all/AllTests.java
@@ -10,97 +10,97 @@ import com.oracle.truffle.r.test.*;
 public class AllTests extends TestBase {
 
     @Test
-    public void TestConnections_testPushBack_57eb33ce24132c578875659c3672161c() {
+    public void TestConnections_testPushBackTextConnection_57eb33ce24132c578875659c3672161c() {
         assertEval("{ con<-textConnection(c(\"a\",\"b\",\"c\",\"d\")); pushBackLength(con) }");
     }
 
     @Test
-    public void TestConnections_testPushBack_9ab94443d4655966c16600edc46013db() {
+    public void TestConnections_testPushBackTextConnection_9ab94443d4655966c16600edc46013db() {
         assertEval("{ con<-textConnection(c(\"a\",\"b\",\"c\",\"d\")); pushBack(\"G\", con); pushBackLength(con) }");
     }
 
     @Test
-    public void TestConnections_testPushBack_fb83ddf08b850f1fb0fcfa825768f88c() {
+    public void TestConnections_testPushBackTextConnection_fb83ddf08b850f1fb0fcfa825768f88c() {
         assertEval("{ con<-textConnection(c(\"a\",\"b\",\"c\",\"d\")); pushBack(\"G\", con); clearPushBack(con); pushBackLength(con) }");
     }
 
     @Test
-    public void TestConnections_testPushBack_55ed6d0b83e86e1385de091582e4af8d() {
+    public void TestConnections_testPushBackTextConnection_55ed6d0b83e86e1385de091582e4af8d() {
         assertEval("{ con<-textConnection(c(\"a\",\"b\",\"c\",\"d\")); pushBack(\"G\", con); readLines(con, 1) }");
     }
 
     @Test
-    public void TestConnections_testPushBack_d91ddfe61c4e439f5209ecaba013916d() {
+    public void TestConnections_testPushBackTextConnection_d91ddfe61c4e439f5209ecaba013916d() {
         assertEval("{ con<-textConnection(c(\"a\",\"b\",\"c\",\"d\")); pushBack(\"G\", con); readLines(con, 2) }");
     }
 
     @Test
-    public void TestConnections_testPushBack_94a34439238e4c99bceeda15b2b188e9() {
+    public void TestConnections_testPushBackTextConnection_94a34439238e4c99bceeda15b2b188e9() {
         assertEval("{ con<-textConnection(c(\"a\",\"b\",\"c\",\"d\")); pushBack(\"G\", con, newLine=FALSE); readLines(con, 1) }");
     }
 
     @Test
-    public void TestConnections_testPushBack_4fdcb63fc43fe75d3bcbc2d474febfd7() {
+    public void TestConnections_testPushBackTextConnection_4fdcb63fc43fe75d3bcbc2d474febfd7() {
         assertEval("{ con<-textConnection(c(\"a\",\"b\",\"c\",\"d\")); pushBack(\"G\", con, newLine=FALSE); readLines(con, 2) }");
     }
 
     @Test
-    public void TestConnections_testPushBack_db52df62c6e2dbab91e2a3304e913e32() {
+    public void TestConnections_testPushBackTextConnection_db52df62c6e2dbab91e2a3304e913e32() {
         assertEval("{ con<-textConnection(c(\"a\",\"b\",\"c\",\"d\")); pushBack(c(\"G\", \"H\"), con); pushBackLength(con) }");
     }
 
     @Test
-    public void TestConnections_testPushBack_c5817008fe12a3cea146ab439cf253d0() {
+    public void TestConnections_testPushBackTextConnection_c5817008fe12a3cea146ab439cf253d0() {
         assertEval("{ con<-textConnection(c(\"a\",\"b\",\"c\",\"d\")); pushBack(c(\"G\", \"H\"), con); readLines(con, 1) }");
     }
 
     @Test
-    public void TestConnections_testPushBack_fd6725165764cf8a366286d2f48fd0bd() {
+    public void TestConnections_testPushBackTextConnection_fd6725165764cf8a366286d2f48fd0bd() {
         assertEval("{ con<-textConnection(c(\"a\",\"b\",\"c\",\"d\")); pushBack(c(\"G\", \"H\"), con); readLines(con, 2) }");
     }
 
     @Test
-    public void TestConnections_testPushBack_196e9a486c227dad1cbdeb22ca8fbcb9() {
+    public void TestConnections_testPushBackTextConnection_196e9a486c227dad1cbdeb22ca8fbcb9() {
         assertEval("{ con<-textConnection(c(\"a\",\"b\",\"c\",\"d\")); pushBack(c(\"G\", \"H\"), con, newLine=FALSE); pushBackLength(con) }");
     }
 
     @Test
-    public void TestConnections_testPushBack_29dbbd688316a533cf6a889c7313ebf4() {
+    public void TestConnections_testPushBackTextConnection_29dbbd688316a533cf6a889c7313ebf4() {
         assertEval("{ con<-textConnection(c(\"a\",\"b\",\"c\",\"d\")); pushBack(c(\"G\", \"H\"), con, newLine=FALSE); readLines(con, 1) }");
     }
 
     @Test
-    public void TestConnections_testPushBack_76de44fe978f208462151f57d1ee3aed() {
+    public void TestConnections_testPushBackTextConnection_76de44fe978f208462151f57d1ee3aed() {
         assertEval("{ con<-textConnection(c(\"a\",\"b\",\"c\",\"d\")); pushBack(c(\"G\", \"H\"), con, newLine=FALSE); readLines(con, 2) }");
     }
 
     @Test
-    public void TestConnections_testPushBack_e4b327628b569f903a3f239f5a226fb7() {
+    public void TestConnections_testPushBackTextConnection_e4b327628b569f903a3f239f5a226fb7() {
         assertEval("{ con<-textConnection(c(\"a\",\"b\",\"c\",\"d\")); pushBack(c(\"G\\nH\"), con); pushBackLength(con) }");
     }
 
     @Test
-    public void TestConnections_testPushBack_d4d6eb9b95b2dee91619746ab87657ca() {
+    public void TestConnections_testPushBackTextConnection_d4d6eb9b95b2dee91619746ab87657ca() {
         assertEval("{ con<-textConnection(c(\"a\",\"b\",\"c\",\"d\")); pushBack(c(\"G\\nH\"), con); readLines(con, 1) }");
     }
 
     @Test
-    public void TestConnections_testPushBack_6f6542b9ba9f30da8906fc36a12e7b6f() {
+    public void TestConnections_testPushBackTextConnection_6f6542b9ba9f30da8906fc36a12e7b6f() {
         assertEval("{ con<-textConnection(c(\"a\",\"b\",\"c\",\"d\")); pushBack(c(\"G\\nH\"), con); readLines(con, 2) }");
     }
 
     @Test
-    public void TestConnections_testPushBack_3452d401d4b77d0a5da3cec670b75dd6() {
+    public void TestConnections_testPushBackTextConnection_3452d401d4b77d0a5da3cec670b75dd6() {
         assertEval("{ con<-textConnection(c(\"a\",\"b\",\"c\",\"d\")); pushBack(c(\"G\\nH\"), con, newLine=FALSE); pushBackLength(con) }");
     }
 
     @Test
-    public void TestConnections_testPushBack_ef4bb1fb2afbe783e73c8f6450c67c32() {
+    public void TestConnections_testPushBackTextConnection_ef4bb1fb2afbe783e73c8f6450c67c32() {
         assertEval("{ con<-textConnection(c(\"a\",\"b\",\"c\",\"d\")); pushBack(c(\"G\\nH\"), con, newLine=FALSE); readLines(con, 1) }");
     }
 
     @Test
-    public void TestConnections_testPushBack_e0fcd304300ae2e3c775fa989d5a667d() {
+    public void TestConnections_testPushBackTextConnection_e0fcd304300ae2e3c775fa989d5a667d() {
         assertEval("{ con<-textConnection(c(\"a\",\"b\",\"c\",\"d\")); pushBack(c(\"G\\nH\"), con, newLine=FALSE); readLines(con, 2) }");
     }
 
@@ -124,6 +124,11 @@ public class AllTests extends TestBase {
         assertEval("{ con <- textConnection(c(\"1\", \"2\", \"3\",\"4\")); readLines(con, 2); readLines(con, 2); readLines(con, 2) }");
     }
 
+    @Test
+    public void TestConnections_testWriteTextReadConnection_a7a7cde7deb5eff04d965a6084a4b5a3() {
+        assertEvalError("{ writeChar(\"x\", textConnection(\"abc\")) }");
+    }
+
     @Test
     public void TestPromiseOptimizations_testDeoptimization_78a373f06df48fb5d5c96a3d4827ae94() {
         assertEval("{ f <- function(x) { function() {x} } ; a <- 1 ; b <- f( a ) ; a <- 10 ; b() }");
diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/failing/FailingTests.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/failing/FailingTests.java
index 2f6789f302..fb793ee6b7 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/failing/FailingTests.java
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/failing/FailingTests.java
@@ -27,6 +27,12 @@ public class FailingTests extends TestBase {
         }
     }
 
+    @Test
+    public void TestConnections_testWriteTextReadConnection_a7a7cde7deb5eff04d965a6084a4b5a3() {
+        assertEvalError("{ writeChar(\"x\", textConnection(\"abc\")) }");
+        check("TestConnections_testWriteTextReadConnection_a7a7cde7deb5eff04d965a6084a4b5a3");
+    }
+
     @Test
     public void TestSimpleArithmetic_testIntegerOverflow_e56646664ed3ccebd0b978a474ccae3c() {
         assertEvalWarning("{ x <- 2147483647L ; x + 1L }");
diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/library/base/TestConnections.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/library/base/TestConnections.java
index bad94918a6..443c673408 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/library/base/TestConnections.java
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/library/base/TestConnections.java
@@ -22,11 +22,68 @@
  */
 package com.oracle.truffle.r.test.library.base;
 
+import java.nio.file.*;
+
 import org.junit.*;
 
+import com.oracle.truffle.r.runtime.*;
 import com.oracle.truffle.r.test.*;
 
 public class TestConnections extends TestBase {
+    private static final class TestDir {
+        private final Path testDirPath;
+
+        TestDir() {
+            Path rpackages = Paths.get(REnvVars.rHome(), "com.oracle.truffle.r.test");
+            testDirPath = TestBase.relativize(rpackages.resolve("library.base.conn"));
+            if (!testDirPath.toFile().exists()) {
+                testDirPath.toFile().mkdir();
+            }
+        }
+
+        String[] subDir(String p) {
+            return new String[]{testDirPath.resolve(p).toString()};
+        }
+    }
+
+    private static TestDir testDir;
+
+    @BeforeClass
+    public static void setupTestDir() {
+        testDir = new TestDir();
+    }
+
+    @AfterClass
+    public static void teardownTestDir() {
+        assertTrue(deleteDir(testDir.testDirPath));
+    }
+
+    @Test
+    public void testFileWriteReadLines() {
+        assertTemplateEval(TestBase.template("{ writeLines(c(\"line1\", \"line2\"), file(\"%0\")) }", testDir.subDir("wl1")));
+        assertTemplateEval(TestBase.template("{ readLines(file(\"%0\"), 2) }", testDir.subDir("wl1")));
+        assertTemplateEval(TestBase.template("{ con <- file(\"%0\"); writeLines(c(\"line1\", \"line2\"), con) }", testDir.subDir("wl2")));
+        assertTemplateEval(TestBase.template("{ con <- file(\"%0\"); readLines(con, 2) }", testDir.subDir("wl2")));
+    }
+
+    @Test
+    public void testFileWriteReadChar() {
+        assertTemplateEval(TestBase.template("{ writeChar(\"abc\", file(\"%0\")) }", testDir.subDir("wc1")));
+        assertTemplateEval(TestBase.template("{ readChar(file(\"%0\"), 3) }", testDir.subDir("wc1")));
+    }
+
+    @Test
+    public void testFileWriteReadBin() {
+        assertTemplateEval(TestBase.template("{ writeBin(\"abc\", file(\"%0\", open=\"wb\")) }", testDir.subDir("wb1")));
+        assertTemplateEval(TestBase.template("{ readBin(file(\"%0\", \"rb\"), 3) }", testDir.subDir("wb1")));
+    }
+
+    @Test
+    @Ignore
+    public void testWriteTextReadConnection() {
+        assertEvalError("{ writeChar(\"x\", textConnection(\"abc\")) }");
+    }
+
     @Test
     public void testTextReadConnection() {
         assertEval("{ con <- textConnection(c(\"1\", \"2\", \"3\",\"4\")); readLines(con) }");
@@ -36,7 +93,7 @@ public class TestConnections extends TestBase {
     }
 
     @Test
-    public void testPushBack() {
+    public void testPushBackTextConnection() {
         assertEval("{ con<-textConnection(c(\"a\",\"b\",\"c\",\"d\")); pushBackLength(con) }");
         assertEval("{ con<-textConnection(c(\"a\",\"b\",\"c\",\"d\")); pushBack(\"G\", con); pushBackLength(con) }");
         assertEval("{ con<-textConnection(c(\"a\",\"b\",\"c\",\"d\")); pushBack(\"G\", con); clearPushBack(con); pushBackLength(con) }");
@@ -57,4 +114,5 @@ public class TestConnections extends TestBase {
         assertEval("{ con<-textConnection(c(\"a\",\"b\",\"c\",\"d\")); pushBack(c(\"G\\nH\"), con, newLine=FALSE); readLines(con, 1) }");
         assertEval("{ con<-textConnection(c(\"a\",\"b\",\"c\",\"d\")); pushBack(c(\"G\\nH\"), con, newLine=FALSE); readLines(con, 2) }");
     }
+
 }
diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/rpackages/TestRPackages.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/rpackages/TestRPackages.java
index d16c38c80d..7586df6964 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/rpackages/TestRPackages.java
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/rpackages/TestRPackages.java
@@ -22,9 +22,7 @@
  */
 package com.oracle.truffle.r.test.rpackages;
 
-import java.io.*;
 import java.nio.file.*;
-import java.nio.file.attribute.*;
 import java.util.*;
 
 import org.junit.*;
@@ -94,33 +92,13 @@ public class TestRPackages extends TestBase {
         private boolean uninstallPackage(String packageName) {
             Path packageDir = rpackagesLibs.resolve(packageName);
             try {
-                Files.walkFileTree(packageDir, DELETE_VISITOR);
+                deleteDir(packageDir);
             } catch (Exception e) {
                 return false;
             }
             return true;
         }
 
-        private static final class DeleteVisitor extends SimpleFileVisitor<Path> {
-
-            @Override
-            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
-                return del(file);
-            }
-
-            @Override
-            public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
-                return del(dir);
-            }
-
-            private static FileVisitResult del(Path p) throws IOException {
-                Files.delete(p);
-                return FileVisitResult.CONTINUE;
-            }
-
-        }
-
-        private static final DeleteVisitor DELETE_VISITOR = new DeleteVisitor();
     }
 
     private static final PackagePaths packagePaths = new PackagePaths();
-- 
GitLab