diff --git a/doc/api/errors.md b/doc/api/errors.md
index 53ef8395ac7065..59222b1aa82c10 100755
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -1147,12 +1147,32 @@ A call was made and the UDP subsystem was not running.
### ERR_STDERR_CLOSE
+
+
An attempt was made to close the `process.stderr` stream. By design, Node.js
does not allow `stdout` or `stderr` streams to be closed by user code.
### ERR_STDOUT_CLOSE
+
+
An attempt was made to close the `process.stdout` stream. By design, Node.js
does not allow `stdout` or `stderr` streams to be closed by user code.
diff --git a/doc/api/process.md b/doc/api/process.md
index 8279f596a51fc1..e7020249c3a267 100644
--- a/doc/api/process.md
+++ b/doc/api/process.md
@@ -1703,9 +1703,7 @@ important ways:
1. They are used internally by [`console.log()`][] and [`console.error()`][],
respectively.
-2. They cannot be closed ([`end()`][] will throw).
-3. They will never emit the [`'finish'`][] event.
-4. Writes may be synchronous depending on what the stream is connected to
+2. Writes may be synchronous depending on what the stream is connected to
and whether the system is Windows or POSIX:
- Files: *synchronous* on Windows and POSIX
- TTYs (Terminals): *asynchronous* on Windows, *synchronous* on POSIX
@@ -1925,7 +1923,6 @@ cases:
[`'exit'`]: #process_event_exit
-[`'finish'`]: stream.html#stream_event_finish
[`'message'`]: child_process.html#child_process_event_message
[`'rejectionHandled'`]: #process_event_rejectionhandled
[`'uncaughtException'`]: #process_event_uncaughtexception
@@ -1937,6 +1934,7 @@ cases:
[`console.error()`]: console.html#console_console_error_data_args
[`console.log()`]: console.html#console_console_log_data_args
[`end()`]: stream.html#stream_writable_end_chunk_encoding_callback
+[`domain`]: domain.html
[`net.Server`]: net.html#net_class_net_server
[`net.Socket`]: net.html#net_class_net_socket
[`process.argv`]: #process_process_argv
diff --git a/lib/internal/errors.js b/lib/internal/errors.js
index e878766f680605..c260b547b8a7e8 100644
--- a/lib/internal/errors.js
+++ b/lib/internal/errors.js
@@ -427,8 +427,6 @@ E('ERR_SOCKET_BUFFER_SIZE',
(reason) => `Could not get or set buffer size: ${reason}`);
E('ERR_SOCKET_CANNOT_SEND', 'Unable to send data');
E('ERR_SOCKET_DGRAM_NOT_RUNNING', 'Not running');
-E('ERR_STDERR_CLOSE', 'process.stderr cannot be closed');
-E('ERR_STDOUT_CLOSE', 'process.stdout cannot be closed');
E('ERR_UNKNOWN_BUILTIN_MODULE', (id) => `No such built-in module: ${id}`);
E('ERR_UNKNOWN_FILE_EXTENSION', 'Unknown file extension: %s');
E('ERR_UNKNOWN_MODULE_FORMAT', 'Unknown module format: %s');
diff --git a/lib/internal/process/stdio.js b/lib/internal/process/stdio.js
index 7faef381f895bf..46641b5871646f 100644
--- a/lib/internal/process/stdio.js
+++ b/lib/internal/process/stdio.js
@@ -1,9 +1,11 @@
'use strict';
-const errors = require('internal/errors');
+const errors = require('internal/errors').codes;
exports.setup = setupStdio;
+function dummyDestroy(err, cb) { cb(err); }
+
function setupStdio() {
var stdin;
var stdout;
@@ -13,11 +15,8 @@ function setupStdio() {
if (stdout) return stdout;
stdout = createWritableStdioStream(1);
stdout.destroySoon = stdout.destroy;
- stdout._destroy = function(er, cb) {
- // Avoid errors if we already emitted
- er = er || new errors.Error('ERR_STDOUT_CLOSE');
- cb(er);
- };
+ // Override _destroy so that the fd is never actually closed.
+ stdout._destroy = dummyDestroy;
if (stdout.isTTY) {
process.on('SIGWINCH', () => stdout._refreshSize());
}
@@ -28,11 +27,8 @@ function setupStdio() {
if (stderr) return stderr;
stderr = createWritableStdioStream(2);
stderr.destroySoon = stderr.destroy;
- stderr._destroy = function(er, cb) {
- // Avoid errors if we already emitted
- er = er || new errors.Error('ERR_STDERR_CLOSE');
- cb(er);
- };
+ // Override _destroy so that the fd is never actually closed.
+ stdout._destroy = dummyDestroy;
if (stderr.isTTY) {
process.on('SIGWINCH', () => stderr._refreshSize());
}
diff --git a/test/parallel/test-stdout-cannot-be-closed-child-process-pipe.js b/test/parallel/test-stdout-cannot-be-closed-child-process-pipe.js
index d19b522e290ba9..7cd4b90c008a2f 100644
--- a/test/parallel/test-stdout-cannot-be-closed-child-process-pipe.js
+++ b/test/parallel/test-stdout-cannot-be-closed-child-process-pipe.js
@@ -24,9 +24,9 @@ function parent() {
});
child.on('close', function(code, signal) {
- assert(code);
+ assert.strictEqual(code, 0);
+ assert.strictEqual(err, '');
assert.strictEqual(out, 'foo');
- assert(/process\.stdout cannot be closed/.test(err));
console.log('ok');
});
}
diff --git a/test/pseudo-tty/test-stdin-write.js b/test/pseudo-tty/test-stdin-write.js
new file mode 100644
index 00000000000000..39091f20bbb745
--- /dev/null
+++ b/test/pseudo-tty/test-stdin-write.js
@@ -0,0 +1,3 @@
+'use strict';
+require('../common');
+process.stdin.end('foobar\n');
diff --git a/test/pseudo-tty/test-stdin-write.out b/test/pseudo-tty/test-stdin-write.out
new file mode 100644
index 00000000000000..323fae03f4606e
--- /dev/null
+++ b/test/pseudo-tty/test-stdin-write.out
@@ -0,0 +1 @@
+foobar
diff --git a/test/pseudo-tty/test-stdout-read.in b/test/pseudo-tty/test-stdout-read.in
new file mode 100644
index 00000000000000..10ddd6d257e013
--- /dev/null
+++ b/test/pseudo-tty/test-stdout-read.in
@@ -0,0 +1 @@
+Hello!
diff --git a/test/pseudo-tty/test-stdout-read.js b/test/pseudo-tty/test-stdout-read.js
new file mode 100644
index 00000000000000..90f017ed77b7be
--- /dev/null
+++ b/test/pseudo-tty/test-stdout-read.js
@@ -0,0 +1,3 @@
+'use strict';
+const common = require('../common');
+process.stderr.on('data', common.mustCall(console.log));
diff --git a/test/pseudo-tty/test-stdout-read.out b/test/pseudo-tty/test-stdout-read.out
new file mode 100644
index 00000000000000..3b7fda223d0e6c
--- /dev/null
+++ b/test/pseudo-tty/test-stdout-read.out
@@ -0,0 +1 @@
+
diff --git a/test/pseudo-tty/test-tty-stdin-call-end.js b/test/pseudo-tty/test-tty-stdin-call-end.js
new file mode 100644
index 00000000000000..e3c3fd9af469ba
--- /dev/null
+++ b/test/pseudo-tty/test-tty-stdin-call-end.js
@@ -0,0 +1,8 @@
+'use strict';
+
+require('../common');
+
+// This tests verifies that process.stdin.end() does not
+// crash the process with ENOTCONN
+
+process.stdin.end();
diff --git a/test/pseudo-tty/test-tty-stdin-call-end.out b/test/pseudo-tty/test-tty-stdin-call-end.out
new file mode 100644
index 00000000000000..e69de29bb2d1d6
diff --git a/test/pseudo-tty/testcfg.py b/test/pseudo-tty/testcfg.py
index 40396db247e279..c6c93c13b98340 100644
--- a/test/pseudo-tty/testcfg.py
+++ b/test/pseudo-tty/testcfg.py
@@ -35,10 +35,11 @@
class TTYTestCase(test.TestCase):
- def __init__(self, path, file, expected, arch, mode, context, config):
+ def __init__(self, path, file, expected, input, arch, mode, context, config):
super(TTYTestCase, self).__init__(context, path, arch, mode)
self.file = file
self.expected = expected
+ self.input = input
self.config = config
self.arch = arch
self.mode = mode
@@ -104,12 +105,16 @@ def GetSource(self):
+ open(self.expected).read())
def RunCommand(self, command, env):
+ input = None
+ if self.input is not None and exists(self.input):
+ input = open(self.input).read()
full_command = self.context.processor(command)
output = test.Execute(full_command,
self.context,
self.context.GetTimeout(self.mode),
env,
- True)
+ faketty=True,
+ input=input)
self.Cleanup()
return test.TestOutput(self,
full_command,
@@ -140,11 +145,12 @@ def ListTests(self, current_path, path, arch, mode):
if self.Contains(path, test):
file_prefix = join(self.root, reduce(join, test[1:], ""))
file_path = file_prefix + ".js"
+ input_path = file_prefix + ".in"
output_path = file_prefix + ".out"
if not exists(output_path):
raise Exception("Could not find %s" % output_path)
result.append(TTYTestCase(test, file_path, output_path,
- arch, mode, self.context, self))
+ input_path, arch, mode, self.context, self))
return result
def GetBuildRequirements(self):
diff --git a/tools/test.py b/tools/test.py
index dafcc3d2ad4122..a51b475b1f23cd 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -717,12 +717,23 @@ def CheckedUnlink(name):
PrintError("os.unlink() " + str(e))
break
-def Execute(args, context, timeout=None, env={}, faketty=False, disable_core_files=False):
+def Execute(args, context, timeout=None, env={}, faketty=False, disable_core_files=False, input=None):
if faketty:
import pty
(out_master, fd_out) = pty.openpty()
fd_in = fd_err = fd_out
pty_out = out_master
+
+ if input is not None:
+ # Before writing input data, disable echo so the input doesn't show
+ # up as part of the output.
+ import termios
+ attr = termios.tcgetattr(fd_in)
+ attr[3] = attr[3] & ~termios.ECHO
+ termios.tcsetattr(fd_in, termios.TCSADRAIN, attr)
+
+ os.write(pty_out, input)
+ os.write(pty_out, '\x04') # End-of-file marker (Ctrl+D)
else:
(fd_out, outname) = tempfile.mkstemp()
(fd_err, errname) = tempfile.mkstemp()