mirror of https://github.com/apache/cassandra
Fix cqlshlib tests on Windows
patch by Paulo Motta; reviewed by Jim Witschey for CASSANDRA-10541
This commit is contained in:
parent
b03ce9fd47
commit
9dd2b5ea0f
|
|
@ -210,6 +210,8 @@ parser.add_option('--cqlversion', default=DEFAULT_CQLVER,
|
|||
parser.add_option("-e", "--execute", help='Execute the statement and quit.')
|
||||
parser.add_option("--connect-timeout", default=DEFAULT_CONNECT_TIMEOUT_SECONDS, dest='connect_timeout',
|
||||
help='Specify the connection timeout in seconds (default: %default seconds).')
|
||||
parser.add_option("-t", "--tty", action='store_true', dest='tty',
|
||||
help='Force tty mode (command prompt).')
|
||||
|
||||
optvalues = optparse.Values()
|
||||
(options, arguments) = parser.parse_args(sys.argv[1:], values=optvalues)
|
||||
|
|
@ -2328,7 +2330,7 @@ def read_options(cmdlineargs, environment):
|
|||
optvalues.ssl = False
|
||||
optvalues.encoding = None
|
||||
|
||||
optvalues.tty = sys.stdin.isatty()
|
||||
optvalues.tty = option_with_default(configs.getboolean, 'ui', 'tty', sys.stdin.isatty())
|
||||
optvalues.cqlversion = option_with_default(configs.get, 'cql', 'version', DEFAULT_CQLVER)
|
||||
optvalues.connect_timeout = option_with_default(configs.getint, 'connection', 'timeout', DEFAULT_CONNECT_TIMEOUT_SECONDS)
|
||||
optvalues.execute = None
|
||||
|
|
|
|||
|
|
@ -17,17 +17,28 @@
|
|||
# NOTE: this testing tool is *nix specific
|
||||
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
import pty
|
||||
import fcntl
|
||||
import contextlib
|
||||
import subprocess
|
||||
import signal
|
||||
import math
|
||||
from time import time
|
||||
from . import basecase
|
||||
from os.path import join, normpath
|
||||
|
||||
DEFAULT_CQLSH_PROMPT = os.linesep + '(\S+@)?cqlsh(:\S+)?> '
|
||||
|
||||
def is_win():
|
||||
return sys.platform in ("cygwin", "win32")
|
||||
|
||||
if is_win():
|
||||
from winpty import WinPty
|
||||
DEFAULT_PREFIX = ''
|
||||
else:
|
||||
import pty
|
||||
DEFAULT_PREFIX = os.linesep
|
||||
|
||||
DEFAULT_CQLSH_PROMPT = DEFAULT_PREFIX + '(\S+@)?cqlsh(:\S+)?> '
|
||||
DEFAULT_CQLSH_TERM = 'xterm'
|
||||
|
||||
cqlshlog = basecase.cqlshlog
|
||||
|
|
@ -41,10 +52,6 @@ def set_controlling_pty(master, slave):
|
|||
os.close(slave)
|
||||
os.close(os.open(os.ttyname(1), os.O_RDWR))
|
||||
|
||||
def set_nonblocking(fd):
|
||||
flags = fcntl.fcntl(fd, fcntl.F_GETFL)
|
||||
fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def raising_signal(signum, exc):
|
||||
"""
|
||||
|
|
@ -93,12 +100,21 @@ def timing_out_alarm(seconds):
|
|||
finally:
|
||||
signal.alarm(0)
|
||||
|
||||
# setitimer is new in 2.6, but it's still worth supporting, for potentially
|
||||
# faster tests because of sub-second resolution on timeouts.
|
||||
if hasattr(signal, 'setitimer'):
|
||||
timing_out = timing_out_itimer
|
||||
if is_win():
|
||||
try:
|
||||
import eventlet
|
||||
except ImportError, e:
|
||||
sys.exit("evenlet library required to run cqlshlib tests on Windows")
|
||||
|
||||
def timing_out(seconds):
|
||||
return eventlet.Timeout(seconds, TimeoutError)
|
||||
else:
|
||||
timing_out = timing_out_alarm
|
||||
# setitimer is new in 2.6, but it's still worth supporting, for potentially
|
||||
# faster tests because of sub-second resolution on timeouts.
|
||||
if hasattr(signal, 'setitimer'):
|
||||
timing_out = timing_out_itimer
|
||||
else:
|
||||
timing_out = timing_out_alarm
|
||||
|
||||
def noop(*a):
|
||||
pass
|
||||
|
|
@ -108,6 +124,7 @@ class ProcRunner:
|
|||
self.exe_path = path
|
||||
self.args = args
|
||||
self.tty = bool(tty)
|
||||
self.realtty = self.tty and not is_win()
|
||||
if env is None:
|
||||
env = {}
|
||||
self.env = env
|
||||
|
|
@ -118,30 +135,35 @@ class ProcRunner:
|
|||
def start_proc(self):
|
||||
preexec = noop
|
||||
stdin = stdout = stderr = None
|
||||
if self.tty:
|
||||
masterfd, slavefd = pty.openpty()
|
||||
preexec = lambda: set_controlling_pty(masterfd, slavefd)
|
||||
else:
|
||||
stdin = stdout = subprocess.PIPE
|
||||
stderr = subprocess.STDOUT
|
||||
cqlshlog.info("Spawning %r subprocess with args: %r and env: %r"
|
||||
% (self.exe_path, self.args, self.env))
|
||||
self.proc = subprocess.Popen(('python', self.exe_path,) + tuple(self.args),
|
||||
env=self.env, preexec_fn=preexec,
|
||||
stdin=stdin, stdout=stdout, stderr=stderr,
|
||||
close_fds=False)
|
||||
if self.tty:
|
||||
if self.realtty:
|
||||
masterfd, slavefd = pty.openpty()
|
||||
preexec = (lambda: set_controlling_pty(masterfd, slavefd))
|
||||
self.proc = subprocess.Popen((self.exe_path,) + tuple(self.args),
|
||||
env=self.env, preexec_fn=preexec,
|
||||
stdin=stdin, stdout=stdout, stderr=stderr,
|
||||
close_fds=False)
|
||||
os.close(slavefd)
|
||||
self.childpty = masterfd
|
||||
self.send = self.send_tty
|
||||
self.read = self.read_tty
|
||||
else:
|
||||
stdin = stdout = subprocess.PIPE
|
||||
stderr = subprocess.STDOUT
|
||||
self.proc = subprocess.Popen((self.exe_path,) + tuple(self.args),
|
||||
env=self.env, stdin=stdin, stdout=stdout,
|
||||
stderr=stderr, bufsize=0, close_fds=False)
|
||||
self.send = self.send_pipe
|
||||
self.read = self.read_pipe
|
||||
if self.tty:
|
||||
self.winpty = WinPty(self.proc.stdout)
|
||||
self.read = self.read_winpty
|
||||
else:
|
||||
self.read = self.read_pipe
|
||||
|
||||
def close(self):
|
||||
cqlshlog.info("Closing %r subprocess." % (self.exe_path,))
|
||||
if self.tty:
|
||||
if self.realtty:
|
||||
os.close(self.childpty)
|
||||
else:
|
||||
self.proc.stdin.close()
|
||||
|
|
@ -154,20 +176,24 @@ class ProcRunner:
|
|||
def send_pipe(self, data):
|
||||
self.proc.stdin.write(data)
|
||||
|
||||
def read_tty(self, blksize):
|
||||
def read_tty(self, blksize, timeout=None):
|
||||
return os.read(self.childpty, blksize)
|
||||
|
||||
def read_pipe(self, blksize):
|
||||
def read_pipe(self, blksize, timeout=None):
|
||||
return self.proc.stdout.read(blksize)
|
||||
|
||||
def read_until(self, until, blksize=4096, timeout=None, flags=0):
|
||||
def read_winpty(self, blksize, timeout=None):
|
||||
return self.winpty.read(blksize, timeout)
|
||||
|
||||
def read_until(self, until, blksize=4096, timeout=None,
|
||||
flags=0, ptty_timeout=None):
|
||||
if not isinstance(until, re._pattern_type):
|
||||
until = re.compile(until, flags)
|
||||
got = self.readbuf
|
||||
self.readbuf = ''
|
||||
with timing_out(timeout):
|
||||
while True:
|
||||
val = self.read(blksize)
|
||||
val = self.read(blksize, ptty_timeout)
|
||||
cqlshlog.debug("read %r from subproc" % (val,))
|
||||
if val == '':
|
||||
raise EOFError("'until' pattern %r not found" % (until.pattern,))
|
||||
|
|
@ -205,15 +231,22 @@ class ProcRunner:
|
|||
|
||||
class CqlshRunner(ProcRunner):
|
||||
def __init__(self, path=None, host=None, port=None, keyspace=None, cqlver=None,
|
||||
args=(), prompt=DEFAULT_CQLSH_PROMPT, env=None, **kwargs):
|
||||
args=(), prompt=DEFAULT_CQLSH_PROMPT, env=None,
|
||||
win_force_colors=True, tty=True, **kwargs):
|
||||
if path is None:
|
||||
path = basecase.path_to_cqlsh
|
||||
cqlsh_bin = 'cqlsh'
|
||||
if is_win():
|
||||
cqlsh_bin = 'cqlsh.bat'
|
||||
path = normpath(join(basecase.cqlshdir, cqlsh_bin))
|
||||
if host is None:
|
||||
host = basecase.TEST_HOST
|
||||
if port is None:
|
||||
port = basecase.TEST_PORT
|
||||
if env is None:
|
||||
env = {}
|
||||
if is_win():
|
||||
env['PYTHONUNBUFFERED'] = '1'
|
||||
env.update(os.environ.copy())
|
||||
env.setdefault('TERM', 'xterm')
|
||||
env.setdefault('CQLSH_NO_BUNDLED', os.environ.get('CQLSH_NO_BUNDLED', ''))
|
||||
env.setdefault('PYTHONPATH', os.environ.get('PYTHONPATH', ''))
|
||||
|
|
@ -222,8 +255,13 @@ class CqlshRunner(ProcRunner):
|
|||
args += ('--cqlversion', str(cqlver))
|
||||
if keyspace is not None:
|
||||
args += ('--keyspace', keyspace)
|
||||
if tty and is_win():
|
||||
args += ('--tty',)
|
||||
args += ('--encoding', 'utf-8')
|
||||
if win_force_colors:
|
||||
args += ('--color',)
|
||||
self.keyspace = keyspace
|
||||
ProcRunner.__init__(self, path, args=args, env=env, **kwargs)
|
||||
ProcRunner.__init__(self, path, tty=tty, args=args, env=env, **kwargs)
|
||||
self.prompt = prompt
|
||||
if self.prompt is None:
|
||||
self.output_header = ''
|
||||
|
|
@ -231,7 +269,7 @@ class CqlshRunner(ProcRunner):
|
|||
self.output_header = self.read_to_next_prompt()
|
||||
|
||||
def read_to_next_prompt(self):
|
||||
return self.read_until(self.prompt, timeout=10.0)
|
||||
return self.read_until(self.prompt, timeout=10.0, ptty_timeout=3)
|
||||
|
||||
def read_up_to_timeout(self, timeout, blksize=4096):
|
||||
output = ProcRunner.read_up_to_timeout(self, timeout, blksize=blksize)
|
||||
|
|
@ -247,7 +285,7 @@ class CqlshRunner(ProcRunner):
|
|||
output = output.replace(' \r', '')
|
||||
output = output.replace('\r', '')
|
||||
output = output.replace(' \b', '')
|
||||
if self.tty:
|
||||
if self.realtty:
|
||||
echo, output = output.split('\n', 1)
|
||||
assert echo == cmd, "unexpected echo %r instead of %r" % (echo, cmd)
|
||||
try:
|
||||
|
|
@ -255,7 +293,7 @@ class CqlshRunner(ProcRunner):
|
|||
except ValueError:
|
||||
promptline = output
|
||||
output = ''
|
||||
assert re.match(self.prompt, '\n' + promptline), \
|
||||
assert re.match(self.prompt, DEFAULT_PREFIX + promptline), \
|
||||
'last line of output %r does not match %r?' % (promptline, self.prompt)
|
||||
return output + '\n'
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ from __future__ import with_statement
|
|||
import re
|
||||
from .basecase import BaseTestCase, cqlsh
|
||||
from .cassconnect import testrun_cqlsh
|
||||
import unittest
|
||||
import sys
|
||||
|
||||
BEL = '\x07' # the terminal-bell character
|
||||
CTRL_C = '\x03'
|
||||
|
|
@ -36,6 +38,7 @@ COMPLETION_RESPONSE_TIME = 0.5
|
|||
completion_separation_re = re.compile(r'\s+')
|
||||
|
||||
|
||||
@unittest.skipIf(sys.platform == "win32", 'Tab completion tests not supported on Windows')
|
||||
class CqlshCompletionCase(BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
|
|
|||
|
|
@ -27,11 +27,12 @@ from .cassconnect import (get_test_keyspace, testrun_cqlsh, testcall_cqlsh,
|
|||
cassandra_cursor, split_cql_commands, quote_name)
|
||||
from .ansi_colors import (ColoredText, lookup_colorcode, lookup_colorname,
|
||||
lookup_colorletter, ansi_seq)
|
||||
import unittest
|
||||
import sys
|
||||
|
||||
CONTROL_C = '\x03'
|
||||
CONTROL_D = '\x04'
|
||||
|
||||
|
||||
class TestCqlshOutput(BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
|
@ -92,7 +93,8 @@ class TestCqlshOutput(BaseTestCase):
|
|||
def test_no_color_output(self):
|
||||
for termname in ('', 'dumb', 'vt100'):
|
||||
cqlshlog.debug('TERM=%r' % termname)
|
||||
with testrun_cqlsh(tty=True, env={'TERM': termname}) as c:
|
||||
with testrun_cqlsh(tty=True, env={'TERM': termname},
|
||||
win_force_colors=False) as c:
|
||||
c.send('select * from has_all_types;\n')
|
||||
self.assertNoHasColors(c.read_to_next_prompt())
|
||||
c.send('select count(*) from has_all_types;\n')
|
||||
|
|
@ -526,9 +528,13 @@ class TestCqlshOutput(BaseTestCase):
|
|||
c.send('use NONEXISTENTKEYSPACE;\n')
|
||||
outputlines = c.read_to_next_prompt().splitlines()
|
||||
|
||||
self.assertEqual(outputlines[0], 'use NONEXISTENTKEYSPACE;')
|
||||
self.assertTrue(outputlines[2].endswith('cqlsh:system> '))
|
||||
midline = ColoredText(outputlines[1])
|
||||
start_index = 0
|
||||
if c.realtty:
|
||||
self.assertEqual(outputlines[start_index], 'use NONEXISTENTKEYSPACE;')
|
||||
start_index = 1
|
||||
|
||||
self.assertTrue(outputlines[start_index+1].endswith('cqlsh:system> '))
|
||||
midline = ColoredText(outputlines[start_index])
|
||||
self.assertEqual(midline.plain(),
|
||||
'InvalidRequest: code=2200 [Invalid query] message="Keyspace \'nonexistentkeyspace\' does not exist"')
|
||||
self.assertColorFromTags(midline,
|
||||
|
|
@ -716,6 +722,7 @@ class TestCqlshOutput(BaseTestCase):
|
|||
self.assertRegexpMatches(output, '^Connected to .* at %s:%d\.$'
|
||||
% (re.escape(TEST_HOST), TEST_PORT))
|
||||
|
||||
@unittest.skipIf(sys.platform == "win32", 'EOF signaling not supported on Windows')
|
||||
def test_eof_prints_newline(self):
|
||||
with testrun_cqlsh(tty=True) as c:
|
||||
c.send(CONTROL_D)
|
||||
|
|
@ -730,8 +737,9 @@ class TestCqlshOutput(BaseTestCase):
|
|||
with testrun_cqlsh(tty=True) as c:
|
||||
cmd = 'exit%s\n' % semicolon
|
||||
c.send(cmd)
|
||||
out = c.read_lines(1)[0].replace('\r', '')
|
||||
self.assertEqual(out, cmd)
|
||||
if c.realtty:
|
||||
out = c.read_lines(1)[0].replace('\r', '')
|
||||
self.assertEqual(out, cmd)
|
||||
with self.assertRaises(BaseException) as cm:
|
||||
c.read_lines(1)
|
||||
self.assertIn(type(cm.exception), (EOFError, OSError))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from threading import Thread
|
||||
from cStringIO import StringIO
|
||||
from Queue import Queue, Empty
|
||||
|
||||
|
||||
class WinPty:
|
||||
|
||||
def __init__(self, stdin):
|
||||
self._s = stdin
|
||||
self._q = Queue()
|
||||
|
||||
def _read_next_char(stdin, queue):
|
||||
while True:
|
||||
char = stdin.read(1) # potentially blocking read
|
||||
if char:
|
||||
queue.put(char)
|
||||
else:
|
||||
break
|
||||
|
||||
self._t = Thread(target=_read_next_char, args=(self._s, self._q))
|
||||
self._t.daemon = True
|
||||
self._t.start() # read characters asynchronously from stdin
|
||||
|
||||
def read(self, blksize=-1, timeout=1):
|
||||
buf = StringIO()
|
||||
count = 0
|
||||
try:
|
||||
while count < blksize or blksize == -1:
|
||||
next = self._q.get(block=timeout is not None, timeout=timeout)
|
||||
buf.write(next)
|
||||
count = count + 1
|
||||
except Empty:
|
||||
pass
|
||||
return buf.getvalue()
|
||||
Loading…
Reference in New Issue