Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 01b314d

Browse filesBrowse files
Fishrock123Myles Borins
authored andcommitted
test: test TTY problems by fakeing a TTY using openpty
Many thanks to thefourtheye and addaleax who helped make the python bits of this possible. See #6980 for more info regarding the related TTY issues. Refs: #6456 Refs: #6773 Refs: #6816 PR-URL: #6895 Reviewed-By: Rod Vagg <rod@vagg.org> Reviewed-By: Anna Henningsen <anna@addaleax.net>
1 parent a1719a9 commit 01b314d
Copy full SHA for 01b314d

File tree

Expand file treeCollapse file tree

7 files changed

+292
-14
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

7 files changed

+292
-14
lines changed
Open diff view settings
Collapse file

‎Makefile‎

Copy file name to clipboardExpand all lines: Makefile
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ v8:
111111
$(MAKE) -C deps/v8 $(V8_ARCH) $(V8_BUILD_OPTIONS)
112112

113113
test: | cctest # Depends on 'all'.
114-
$(PYTHON) tools/test.py --mode=release doctool message parallel sequential -J
114+
$(PYTHON) tools/test.py --mode=release doctool message pseudo-tty parallel sequential -J
115115
$(MAKE) jslint
116116
$(MAKE) cpplint
117117

@@ -167,7 +167,7 @@ test-all-valgrind: test-build
167167
$(PYTHON) tools/test.py --mode=debug,release --valgrind
168168

169169
CI_NATIVE_SUITES := addons
170-
CI_JS_SUITES := doctool message parallel sequential
170+
CI_JS_SUITES := doctool message parallel pseudo-tty sequential
171171

172172
# Build and test addons without building anything else
173173
test-ci-native: | test/addons/.buildstamp
Collapse file
+15Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// https://github.com/nodejs/node/issues/6456#issuecomment-219320599
2+
// https://gist.github.com/isaacs/1495b91ec66b21d30b10572d72ad2cdd
3+
'use strict';
4+
require('../common');
5+
6+
// 1000 bytes wrapped at 50 columns
7+
// \n turns into a double-byte character
8+
// (48 + {2}) * 20 = 1000
9+
var out = ('o'.repeat(48) + '\n').repeat(20);
10+
// Add the remaining 24 bytes and terminate with an 'O'.
11+
// This results in 1025 bytes, just enough to overflow the 1kb OS X TTY buffer.
12+
out += 'o'.repeat(24) + 'O';
13+
14+
process.stdout.write(out);
15+
process.exit(0);
Collapse file
+21Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
oooooooooooooooooooooooooooooooooooooooooooooooo
2+
oooooooooooooooooooooooooooooooooooooooooooooooo
3+
oooooooooooooooooooooooooooooooooooooooooooooooo
4+
oooooooooooooooooooooooooooooooooooooooooooooooo
5+
oooooooooooooooooooooooooooooooooooooooooooooooo
6+
oooooooooooooooooooooooooooooooooooooooooooooooo
7+
oooooooooooooooooooooooooooooooooooooooooooooooo
8+
oooooooooooooooooooooooooooooooooooooooooooooooo
9+
oooooooooooooooooooooooooooooooooooooooooooooooo
10+
oooooooooooooooooooooooooooooooooooooooooooooooo
11+
oooooooooooooooooooooooooooooooooooooooooooooooo
12+
oooooooooooooooooooooooooooooooooooooooooooooooo
13+
oooooooooooooooooooooooooooooooooooooooooooooooo
14+
oooooooooooooooooooooooooooooooooooooooooooooooo
15+
oooooooooooooooooooooooooooooooooooooooooooooooo
16+
oooooooooooooooooooooooooooooooooooooooooooooooo
17+
oooooooooooooooooooooooooooooooooooooooooooooooo
18+
oooooooooooooooooooooooooooooooooooooooooooooooo
19+
oooooooooooooooooooooooooooooooooooooooooooooooo
20+
oooooooooooooooooooooooooooooooooooooooooooooooo
21+
ooooooooooooooooooooooooO
Collapse file
+17Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// https://github.com/nodejs/node/issues/6456#issuecomment-219320599
2+
// https://gist.github.com/isaacs/1495b91ec66b21d30b10572d72ad2cdd
3+
'use strict';
4+
require('../common');
5+
6+
// 1000 bytes wrapped at 50 columns
7+
// \n turns into a double-byte character
8+
// (48 + {2}) * 20 = 1000
9+
var out = ('o'.repeat(48) + '\n').repeat(20);
10+
// Add the remaining 24 bytes and terminate with an 'O'.
11+
// This results in 1025 bytes, just enough to overflow the 1kb OS X TTY buffer.
12+
out += 'o'.repeat(24) + 'O';
13+
14+
const err = '__This is some stderr__';
15+
16+
process.stdout.write(out);
17+
process.stderr.write(err);
Collapse file
+21Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
oooooooooooooooooooooooooooooooooooooooooooooooo
2+
oooooooooooooooooooooooooooooooooooooooooooooooo
3+
oooooooooooooooooooooooooooooooooooooooooooooooo
4+
oooooooooooooooooooooooooooooooooooooooooooooooo
5+
oooooooooooooooooooooooooooooooooooooooooooooooo
6+
oooooooooooooooooooooooooooooooooooooooooooooooo
7+
oooooooooooooooooooooooooooooooooooooooooooooooo
8+
oooooooooooooooooooooooooooooooooooooooooooooooo
9+
oooooooooooooooooooooooooooooooooooooooooooooooo
10+
oooooooooooooooooooooooooooooooooooooooooooooooo
11+
oooooooooooooooooooooooooooooooooooooooooooooooo
12+
oooooooooooooooooooooooooooooooooooooooooooooooo
13+
oooooooooooooooooooooooooooooooooooooooooooooooo
14+
oooooooooooooooooooooooooooooooooooooooooooooooo
15+
oooooooooooooooooooooooooooooooooooooooooooooooo
16+
oooooooooooooooooooooooooooooooooooooooooooooooo
17+
oooooooooooooooooooooooooooooooooooooooooooooooo
18+
oooooooooooooooooooooooooooooooooooooooooooooooo
19+
oooooooooooooooooooooooooooooooooooooooooooooooo
20+
oooooooooooooooooooooooooooooooooooooooooooooooo
21+
ooooooooooooooooooooooooO__This is some stderr__
Collapse file

‎test/pseudo-tty/testcfg.py‎

Copy file name to clipboard
+161Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
# Copyright 2008 the V8 project authors. All rights reserved.
2+
# Redistribution and use in source and binary forms, with or without
3+
# modification, are permitted provided that the following conditions are
4+
# met:
5+
#
6+
# * Redistributions of source code must retain the above copyright
7+
# notice, this list of conditions and the following disclaimer.
8+
# * Redistributions in binary form must reproduce the above
9+
# copyright notice, this list of conditions and the following
10+
# disclaimer in the documentation and/or other materials provided
11+
# with the distribution.
12+
# * Neither the name of Google Inc. nor the names of its
13+
# contributors may be used to endorse or promote products derived
14+
# from this software without specific prior written permission.
15+
#
16+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17+
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18+
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19+
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20+
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22+
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23+
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24+
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27+
28+
import test
29+
import os
30+
from os.path import join, exists, basename, isdir
31+
import re
32+
import utils
33+
34+
FLAGS_PATTERN = re.compile(r"//\s+Flags:(.*)")
35+
36+
class TTYTestCase(test.TestCase):
37+
38+
def __init__(self, path, file, expected, arch, mode, context, config):
39+
super(TTYTestCase, self).__init__(context, path, arch, mode)
40+
self.file = file
41+
self.expected = expected
42+
self.config = config
43+
self.arch = arch
44+
self.mode = mode
45+
46+
def IgnoreLine(self, str):
47+
"""Ignore empty lines and valgrind output."""
48+
if not str.strip(): return True
49+
else: return str.startswith('==') or str.startswith('**')
50+
51+
def IsFailureOutput(self, output):
52+
f = file(self.expected)
53+
# Convert output lines to regexps that we can match
54+
env = { 'basename': basename(self.file) }
55+
patterns = [ ]
56+
for line in f:
57+
if not line.strip():
58+
continue
59+
pattern = re.escape(line.rstrip() % env)
60+
pattern = pattern.replace('\\*', '.*')
61+
pattern = '^%s$' % pattern
62+
patterns.append(pattern)
63+
# Compare actual output with the expected
64+
raw_lines = (output.stdout + output.stderr).split('\n')
65+
outlines = [ s.strip() for s in raw_lines if not self.IgnoreLine(s) ]
66+
if len(outlines) != len(patterns):
67+
print "length differs."
68+
print "expect=%d" % len(patterns)
69+
print "actual=%d" % len(outlines)
70+
print "patterns:"
71+
for i in xrange(len(patterns)):
72+
print "pattern = %s" % patterns[i]
73+
print "outlines:"
74+
for i in xrange(len(outlines)):
75+
print "outline = %s" % outlines[i]
76+
return True
77+
for i in xrange(len(patterns)):
78+
if not re.match(patterns[i], outlines[i]):
79+
print "match failed"
80+
print "line=%d" % i
81+
print "expect=%s" % patterns[i]
82+
print "actual=%s" % outlines[i]
83+
return True
84+
return False
85+
86+
def GetLabel(self):
87+
return "%s %s" % (self.mode, self.GetName())
88+
89+
def GetName(self):
90+
return self.path[-1]
91+
92+
def GetCommand(self):
93+
result = [self.config.context.GetVm(self.arch, self.mode)]
94+
source = open(self.file).read()
95+
flags_match = FLAGS_PATTERN.search(source)
96+
if flags_match:
97+
result += flags_match.group(1).strip().split()
98+
result.append(self.file)
99+
return result
100+
101+
def GetSource(self):
102+
return (open(self.file).read()
103+
+ "\n--- expected output ---\n"
104+
+ open(self.expected).read())
105+
106+
def RunCommand(self, command, env):
107+
full_command = self.context.processor(command)
108+
output = test.Execute(full_command,
109+
self.context,
110+
self.context.GetTimeout(self.mode),
111+
env,
112+
True)
113+
self.Cleanup()
114+
return test.TestOutput(self,
115+
full_command,
116+
output,
117+
self.context.store_unexpected_output)
118+
119+
120+
class TTYTestConfiguration(test.TestConfiguration):
121+
122+
def __init__(self, context, root):
123+
super(TTYTestConfiguration, self).__init__(context, root)
124+
125+
def Ls(self, path):
126+
if isdir(path):
127+
return [f[:-3] for f in os.listdir(path) if f.endswith('.js')]
128+
else:
129+
return []
130+
131+
def ListTests(self, current_path, path, arch, mode):
132+
all_tests = [current_path + [t] for t in self.Ls(self.root)]
133+
result = []
134+
# Skip these tests on Windows, as pseudo terminals are not available
135+
if utils.IsWindows():
136+
print ("Skipping pseudo-tty tests, as pseudo terminals are not available"
137+
" on Windows.")
138+
return result
139+
for test in all_tests:
140+
if self.Contains(path, test):
141+
file_prefix = join(self.root, reduce(join, test[1:], ""))
142+
file_path = file_prefix + ".js"
143+
output_path = file_prefix + ".out"
144+
if not exists(output_path):
145+
print "Could not find %s" % output_path
146+
continue
147+
result.append(TTYTestCase(test, file_path, output_path,
148+
arch, mode, self.context, self))
149+
return result
150+
151+
def GetBuildRequirements(self):
152+
return ['sample', 'sample=shell']
153+
154+
def GetTestStatus(self, sections, defs):
155+
status_file = join(self.root, 'message.status')
156+
if exists(status_file):
157+
test.ReadConfigurationInto(status_file, sections, defs)
158+
159+
160+
def GetConfiguration(context, root):
161+
return TTYTestConfiguration(context, root)
Collapse file

‎tools/test.py‎

Copy file name to clipboardExpand all lines: tools/test.py
+55-12Lines changed: 55 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -575,11 +575,17 @@ def RunProcess(context, timeout, args, **rest):
575575
error_mode = SEM_NOGPFAULTERRORBOX;
576576
prev_error_mode = Win32SetErrorMode(error_mode);
577577
Win32SetErrorMode(error_mode | prev_error_mode);
578+
579+
faketty = rest.pop('faketty', False)
580+
pty_out = rest.pop('pty_out')
581+
578582
process = subprocess.Popen(
579583
shell = utils.IsWindows(),
580584
args = popen_args,
581585
**rest
582586
)
587+
if faketty:
588+
os.close(rest['stdout'])
583589
if utils.IsWindows() and context.suppress_dialogs and prev_error_mode != SEM_INVALID_VALUE:
584590
Win32SetErrorMode(prev_error_mode)
585591
# Compute the end time - if the process crosses this limit we
@@ -591,6 +597,29 @@ def RunProcess(context, timeout, args, **rest):
591597
# loop and keep track of whether or not it times out.
592598
exit_code = None
593599
sleep_time = INITIAL_SLEEP_TIME
600+
output = ''
601+
if faketty:
602+
while True:
603+
if time.time() >= end_time:
604+
# Kill the process and wait for it to exit.
605+
KillProcessWithID(process.pid)
606+
exit_code = process.wait()
607+
timed_out = True
608+
break
609+
610+
# source: http://stackoverflow.com/a/12471855/1903116
611+
# related: http://stackoverflow.com/q/11165521/1903116
612+
try:
613+
data = os.read(pty_out, 9999)
614+
except OSError as e:
615+
if e.errno != errno.EIO:
616+
raise
617+
break # EIO means EOF on some systems
618+
else:
619+
if not data: # EOF
620+
break
621+
output += data
622+
594623
while exit_code is None:
595624
if (not end_time is None) and (time.time() >= end_time):
596625
# Kill the process and wait for it to exit.
@@ -603,7 +632,7 @@ def RunProcess(context, timeout, args, **rest):
603632
sleep_time = sleep_time * SLEEP_TIME_FACTOR
604633
if sleep_time > MAX_SLEEP_TIME:
605634
sleep_time = MAX_SLEEP_TIME
606-
return (process, exit_code, timed_out)
635+
return (process, exit_code, timed_out, output)
607636

608637

609638
def PrintError(str):
@@ -625,29 +654,43 @@ def CheckedUnlink(name):
625654
PrintError("os.unlink() " + str(e))
626655
break
627656

628-
def Execute(args, context, timeout=None, env={}):
629-
(fd_out, outname) = tempfile.mkstemp()
630-
(fd_err, errname) = tempfile.mkstemp()
657+
def Execute(args, context, timeout=None, env={}, faketty=False):
658+
if faketty:
659+
import pty
660+
(out_master, fd_out) = pty.openpty()
661+
fd_err = fd_out
662+
pty_out = out_master
663+
else:
664+
(fd_out, outname) = tempfile.mkstemp()
665+
(fd_err, errname) = tempfile.mkstemp()
666+
pty_out = None
631667

632668
# Extend environment
633669
env_copy = os.environ.copy()
634670
for key, value in env.iteritems():
635671
env_copy[key] = value
636672

637-
(process, exit_code, timed_out) = RunProcess(
673+
(process, exit_code, timed_out, output) = RunProcess(
638674
context,
639675
timeout,
640676
args = args,
641677
stdout = fd_out,
642678
stderr = fd_err,
643-
env = env_copy
679+
env = env_copy,
680+
faketty = faketty,
681+
pty_out = pty_out
644682
)
645-
os.close(fd_out)
646-
os.close(fd_err)
647-
output = file(outname).read()
648-
errors = file(errname).read()
649-
CheckedUnlink(outname)
650-
CheckedUnlink(errname)
683+
if faketty:
684+
os.close(out_master)
685+
errors = ''
686+
else:
687+
os.close(fd_out)
688+
os.close(fd_err)
689+
output = file(outname).read()
690+
errors = file(errname).read()
691+
CheckedUnlink(outname)
692+
CheckedUnlink(errname)
693+
651694
return CommandOutput(exit_code, timed_out, output, errors)
652695

653696

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.