Adding upstream version 1.23.2.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
f253096a15
commit
94e3fc38e7
93 changed files with 10761 additions and 0 deletions
0
test/features/__init__.py
Normal file
0
test/features/__init__.py
Normal file
12
test/features/auto_vertical.feature
Normal file
12
test/features/auto_vertical.feature
Normal file
|
@ -0,0 +1,12 @@
|
|||
Feature: auto_vertical mode:
|
||||
on, off
|
||||
|
||||
Scenario: auto_vertical on with small query
|
||||
When we run dbcli with --auto-vertical-output
|
||||
and we execute a small query
|
||||
then we see small results in horizontal format
|
||||
|
||||
Scenario: auto_vertical on with large query
|
||||
When we run dbcli with --auto-vertical-output
|
||||
and we execute a large query
|
||||
then we see large results in vertical format
|
19
test/features/basic_commands.feature
Normal file
19
test/features/basic_commands.feature
Normal file
|
@ -0,0 +1,19 @@
|
|||
Feature: run the cli,
|
||||
call the help command,
|
||||
exit the cli
|
||||
|
||||
Scenario: run "\?" command
|
||||
When we send "\?" command
|
||||
then we see help output
|
||||
|
||||
Scenario: run source command
|
||||
When we send source command
|
||||
then we see help output
|
||||
|
||||
Scenario: check our application_name
|
||||
When we run query to check application_name
|
||||
then we see found
|
||||
|
||||
Scenario: run the cli and exit
|
||||
When we send "ctrl + d"
|
||||
then dbcli exits
|
30
test/features/crud_database.feature
Normal file
30
test/features/crud_database.feature
Normal file
|
@ -0,0 +1,30 @@
|
|||
Feature: manipulate databases:
|
||||
create, drop, connect, disconnect
|
||||
|
||||
Scenario: create and drop temporary database
|
||||
When we create database
|
||||
then we see database created
|
||||
when we drop database
|
||||
then we confirm the destructive warning
|
||||
then we see database dropped
|
||||
when we connect to dbserver
|
||||
then we see database connected
|
||||
|
||||
Scenario: connect and disconnect from test database
|
||||
When we connect to test database
|
||||
then we see database connected
|
||||
when we connect to dbserver
|
||||
then we see database connected
|
||||
|
||||
Scenario: connect and disconnect from quoted test database
|
||||
When we connect to quoted test database
|
||||
then we see database connected
|
||||
|
||||
Scenario: create and drop default database
|
||||
When we create database
|
||||
then we see database created
|
||||
when we connect to tmp database
|
||||
then we see database connected
|
||||
when we drop database
|
||||
then we confirm the destructive warning
|
||||
then we see database dropped and no default database
|
49
test/features/crud_table.feature
Normal file
49
test/features/crud_table.feature
Normal file
|
@ -0,0 +1,49 @@
|
|||
Feature: manipulate tables:
|
||||
create, insert, update, select, delete from, drop
|
||||
|
||||
Scenario: create, insert, select from, update, drop table
|
||||
When we connect to test database
|
||||
then we see database connected
|
||||
when we create table
|
||||
then we see table created
|
||||
when we insert into table
|
||||
then we see record inserted
|
||||
when we update table
|
||||
then we see record updated
|
||||
when we select from table
|
||||
then we see data selected
|
||||
when we delete from table
|
||||
then we confirm the destructive warning
|
||||
then we see record deleted
|
||||
when we drop table
|
||||
then we confirm the destructive warning
|
||||
then we see table dropped
|
||||
when we connect to dbserver
|
||||
then we see database connected
|
||||
|
||||
Scenario: select null values
|
||||
When we connect to test database
|
||||
then we see database connected
|
||||
when we select null
|
||||
then we see null selected
|
||||
|
||||
Scenario: confirm destructive query
|
||||
When we query "create table foo(x integer);"
|
||||
and we query "delete from foo;"
|
||||
and we answer the destructive warning with "y"
|
||||
then we see text "Your call!"
|
||||
|
||||
Scenario: decline destructive query
|
||||
When we query "delete from foo;"
|
||||
and we answer the destructive warning with "n"
|
||||
then we see text "Wise choice!"
|
||||
|
||||
Scenario: no destructive warning if disabled in config
|
||||
When we run dbcli with --no-warn
|
||||
and we query "create table blabla(x integer);"
|
||||
and we query "delete from blabla;"
|
||||
Then we see text "Query OK"
|
||||
|
||||
Scenario: confirm destructive query with invalid response
|
||||
When we query "delete from foo;"
|
||||
then we answer the destructive warning with invalid "1" and see text "is not a valid boolean"
|
93
test/features/db_utils.py
Normal file
93
test/features/db_utils.py
Normal file
|
@ -0,0 +1,93 @@
|
|||
import pymysql
|
||||
|
||||
|
||||
def create_db(hostname='localhost', port=3306, username=None,
|
||||
password=None, dbname=None):
|
||||
"""Create test database.
|
||||
|
||||
:param hostname: string
|
||||
:param port: int
|
||||
:param username: string
|
||||
:param password: string
|
||||
:param dbname: string
|
||||
:return:
|
||||
|
||||
"""
|
||||
cn = pymysql.connect(
|
||||
host=hostname,
|
||||
port=port,
|
||||
user=username,
|
||||
password=password,
|
||||
charset='utf8mb4',
|
||||
cursorclass=pymysql.cursors.DictCursor
|
||||
)
|
||||
|
||||
with cn.cursor() as cr:
|
||||
cr.execute('drop database if exists ' + dbname)
|
||||
cr.execute('create database ' + dbname)
|
||||
|
||||
cn.close()
|
||||
|
||||
cn = create_cn(hostname, port, password, username, dbname)
|
||||
return cn
|
||||
|
||||
|
||||
def create_cn(hostname, port, password, username, dbname):
|
||||
"""Open connection to database.
|
||||
|
||||
:param hostname:
|
||||
:param port:
|
||||
:param password:
|
||||
:param username:
|
||||
:param dbname: string
|
||||
:return: psycopg2.connection
|
||||
|
||||
"""
|
||||
cn = pymysql.connect(
|
||||
host=hostname,
|
||||
port=port,
|
||||
user=username,
|
||||
password=password,
|
||||
db=dbname,
|
||||
charset='utf8mb4',
|
||||
cursorclass=pymysql.cursors.DictCursor
|
||||
)
|
||||
|
||||
return cn
|
||||
|
||||
|
||||
def drop_db(hostname='localhost', port=3306, username=None,
|
||||
password=None, dbname=None):
|
||||
"""Drop database.
|
||||
|
||||
:param hostname: string
|
||||
:param port: int
|
||||
:param username: string
|
||||
:param password: string
|
||||
:param dbname: string
|
||||
|
||||
"""
|
||||
cn = pymysql.connect(
|
||||
host=hostname,
|
||||
port=port,
|
||||
user=username,
|
||||
password=password,
|
||||
db=dbname,
|
||||
charset='utf8mb4',
|
||||
cursorclass=pymysql.cursors.DictCursor
|
||||
)
|
||||
|
||||
with cn.cursor() as cr:
|
||||
cr.execute('drop database if exists ' + dbname)
|
||||
|
||||
close_cn(cn)
|
||||
|
||||
|
||||
def close_cn(cn=None):
|
||||
"""Close connection.
|
||||
|
||||
:param connection: pymysql.connection
|
||||
|
||||
"""
|
||||
if cn:
|
||||
cn.close()
|
140
test/features/environment.py
Normal file
140
test/features/environment.py
Normal file
|
@ -0,0 +1,140 @@
|
|||
import os
|
||||
import sys
|
||||
from tempfile import mkstemp
|
||||
|
||||
import db_utils as dbutils
|
||||
import fixture_utils as fixutils
|
||||
import pexpect
|
||||
|
||||
from steps.wrappers import run_cli, wait_prompt
|
||||
|
||||
test_log_file = os.path.join(os.environ['HOME'], '.mycli.test.log')
|
||||
|
||||
|
||||
def before_all(context):
|
||||
"""Set env parameters."""
|
||||
os.environ['LINES'] = "100"
|
||||
os.environ['COLUMNS'] = "100"
|
||||
os.environ['EDITOR'] = 'ex'
|
||||
os.environ['LC_ALL'] = 'en_US.UTF-8'
|
||||
os.environ['PROMPT_TOOLKIT_NO_CPR'] = '1'
|
||||
os.environ['MYCLI_HISTFILE'] = os.devnull
|
||||
|
||||
test_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
|
||||
login_path_file = os.path.join(test_dir, 'mylogin.cnf')
|
||||
os.environ['MYSQL_TEST_LOGIN_FILE'] = login_path_file
|
||||
|
||||
context.package_root = os.path.abspath(
|
||||
os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
|
||||
|
||||
os.environ["COVERAGE_PROCESS_START"] = os.path.join(context.package_root,
|
||||
'.coveragerc')
|
||||
|
||||
context.exit_sent = False
|
||||
|
||||
vi = '_'.join([str(x) for x in sys.version_info[:3]])
|
||||
db_name = context.config.userdata.get(
|
||||
'my_test_db', None) or "mycli_behave_tests"
|
||||
db_name_full = '{0}_{1}'.format(db_name, vi)
|
||||
|
||||
# Store get params from config/environment variables
|
||||
context.conf = {
|
||||
'host': context.config.userdata.get(
|
||||
'my_test_host',
|
||||
os.getenv('PYTEST_HOST', 'localhost')
|
||||
),
|
||||
'port': context.config.userdata.get(
|
||||
'my_test_port',
|
||||
int(os.getenv('PYTEST_PORT', '3306'))
|
||||
),
|
||||
'user': context.config.userdata.get(
|
||||
'my_test_user',
|
||||
os.getenv('PYTEST_USER', 'root')
|
||||
),
|
||||
'pass': context.config.userdata.get(
|
||||
'my_test_pass',
|
||||
os.getenv('PYTEST_PASSWORD', None)
|
||||
),
|
||||
'cli_command': context.config.userdata.get(
|
||||
'my_cli_command', None) or
|
||||
sys.executable + ' -c "import coverage ; coverage.process_startup(); import mycli.main; mycli.main.cli()"',
|
||||
'dbname': db_name,
|
||||
'dbname_tmp': db_name_full + '_tmp',
|
||||
'vi': vi,
|
||||
'pager_boundary': '---boundary---',
|
||||
}
|
||||
|
||||
_, my_cnf = mkstemp()
|
||||
with open(my_cnf, 'w') as f:
|
||||
f.write(
|
||||
'[client]\n'
|
||||
'pager={0} {1} {2}\n'.format(
|
||||
sys.executable, os.path.join(context.package_root,
|
||||
'test/features/wrappager.py'),
|
||||
context.conf['pager_boundary'])
|
||||
)
|
||||
context.conf['defaults-file'] = my_cnf
|
||||
context.conf['myclirc'] = os.path.join(context.package_root, 'test',
|
||||
'myclirc')
|
||||
|
||||
context.cn = dbutils.create_db(context.conf['host'], context.conf['port'],
|
||||
context.conf['user'],
|
||||
context.conf['pass'],
|
||||
context.conf['dbname'])
|
||||
|
||||
context.fixture_data = fixutils.read_fixture_files()
|
||||
|
||||
|
||||
def after_all(context):
|
||||
"""Unset env parameters."""
|
||||
dbutils.close_cn(context.cn)
|
||||
dbutils.drop_db(context.conf['host'], context.conf['port'],
|
||||
context.conf['user'], context.conf['pass'],
|
||||
context.conf['dbname'])
|
||||
|
||||
# Restore env vars.
|
||||
#for k, v in context.pgenv.items():
|
||||
# if k in os.environ and v is None:
|
||||
# del os.environ[k]
|
||||
# elif v:
|
||||
# os.environ[k] = v
|
||||
|
||||
|
||||
def before_step(context, _):
|
||||
context.atprompt = False
|
||||
|
||||
|
||||
def before_scenario(context, _):
|
||||
with open(test_log_file, 'w') as f:
|
||||
f.write('')
|
||||
run_cli(context)
|
||||
wait_prompt(context)
|
||||
|
||||
|
||||
def after_scenario(context, _):
|
||||
"""Cleans up after each test complete."""
|
||||
with open(test_log_file) as f:
|
||||
for line in f:
|
||||
if 'error' in line.lower():
|
||||
raise RuntimeError(f'Error in log file: {line}')
|
||||
|
||||
if hasattr(context, 'cli') and not context.exit_sent:
|
||||
# Quit nicely.
|
||||
if not context.atprompt:
|
||||
user = context.conf['user']
|
||||
host = context.conf['host']
|
||||
dbname = context.currentdb
|
||||
context.cli.expect_exact(
|
||||
'{0}@{1}:{2}>'.format(
|
||||
user, host, dbname
|
||||
),
|
||||
timeout=5
|
||||
)
|
||||
context.cli.sendcontrol('c')
|
||||
context.cli.sendcontrol('d')
|
||||
context.cli.expect_exact(pexpect.EOF, timeout=5)
|
||||
|
||||
# TODO: uncomment to debug a failure
|
||||
# def after_step(context, step):
|
||||
# if step.status == "failed":
|
||||
# import ipdb; ipdb.set_trace()
|
24
test/features/fixture_data/help.txt
Normal file
24
test/features/fixture_data/help.txt
Normal file
|
@ -0,0 +1,24 @@
|
|||
+--------------------------+-----------------------------------------------+
|
||||
| Command | Description |
|
||||
|--------------------------+-----------------------------------------------|
|
||||
| \# | Refresh auto-completions. |
|
||||
| \? | Show Help. |
|
||||
| \c[onnect] database_name | Change to a new database. |
|
||||
| \d [pattern] | List or describe tables, views and sequences. |
|
||||
| \dT[S+] [pattern] | List data types |
|
||||
| \df[+] [pattern] | List functions. |
|
||||
| \di[+] [pattern] | List indexes. |
|
||||
| \dn[+] [pattern] | List schemas. |
|
||||
| \ds[+] [pattern] | List sequences. |
|
||||
| \dt[+] [pattern] | List tables. |
|
||||
| \du[+] [pattern] | List roles. |
|
||||
| \dv[+] [pattern] | List views. |
|
||||
| \e [file] | Edit the query with external editor. |
|
||||
| \l | List databases. |
|
||||
| \n[+] [name] | List or execute named queries. |
|
||||
| \nd [name [query]] | Delete a named query. |
|
||||
| \ns name query | Save a named query. |
|
||||
| \refresh | Refresh auto-completions. |
|
||||
| \timing | Toggle timing of commands. |
|
||||
| \x | Toggle expanded output. |
|
||||
+--------------------------+-----------------------------------------------+
|
31
test/features/fixture_data/help_commands.txt
Normal file
31
test/features/fixture_data/help_commands.txt
Normal file
|
@ -0,0 +1,31 @@
|
|||
+-------------+----------------------------+------------------------------------------------------------+
|
||||
| Command | Shortcut | Description |
|
||||
+-------------+----------------------------+------------------------------------------------------------+
|
||||
| \G | \G | Display current query results vertically. |
|
||||
| \clip | \clip | Copy query to the system clipboard. |
|
||||
| \dt | \dt[+] [table] | List or describe tables. |
|
||||
| \e | \e | Edit command with editor (uses $EDITOR). |
|
||||
| \f | \f [name [args..]] | List or execute favorite queries. |
|
||||
| \fd | \fd [name] | Delete a favorite query. |
|
||||
| \fs | \fs name query | Save a favorite query. |
|
||||
| \l | \l | List databases. |
|
||||
| \once | \o [-o] filename | Append next result to an output file (overwrite using -o). |
|
||||
| \pipe_once | \| command | Send next result to a subprocess. |
|
||||
| \timing | \t | Toggle timing of commands. |
|
||||
| connect | \r | Reconnect to the database. Optional database argument. |
|
||||
| exit | \q | Exit. |
|
||||
| help | \? | Show this help. |
|
||||
| nopager | \n | Disable pager, print to stdout. |
|
||||
| notee | notee | Stop writing results to an output file. |
|
||||
| pager | \P [command] | Set PAGER. Print the query results via PAGER. |
|
||||
| prompt | \R | Change prompt format. |
|
||||
| quit | \q | Quit. |
|
||||
| rehash | \# | Refresh auto-completions. |
|
||||
| source | \. filename | Execute commands from file. |
|
||||
| status | \s | Get status information from the server. |
|
||||
| system | system [command] | Execute a system shell commmand. |
|
||||
| tableformat | \T | Change the table format used to output results. |
|
||||
| tee | tee [-o] filename | Append all results to an output file (overwrite using -o). |
|
||||
| use | \u | Change to a new database. |
|
||||
| watch | watch [seconds] [-c] query | Executes the query every [seconds] seconds (by default 5). |
|
||||
+-------------+----------------------------+------------------------------------------------------------+
|
29
test/features/fixture_utils.py
Normal file
29
test/features/fixture_utils.py
Normal file
|
@ -0,0 +1,29 @@
|
|||
import os
|
||||
import io
|
||||
|
||||
|
||||
def read_fixture_lines(filename):
|
||||
"""Read lines of text from file.
|
||||
|
||||
:param filename: string name
|
||||
:return: list of strings
|
||||
|
||||
"""
|
||||
lines = []
|
||||
for line in open(filename):
|
||||
lines.append(line.strip())
|
||||
return lines
|
||||
|
||||
|
||||
def read_fixture_files():
|
||||
"""Read all files inside fixture_data directory."""
|
||||
fixture_dict = {}
|
||||
|
||||
current_dir = os.path.dirname(__file__)
|
||||
fixture_dir = os.path.join(current_dir, 'fixture_data/')
|
||||
for filename in os.listdir(fixture_dir):
|
||||
if filename not in ['.', '..']:
|
||||
fullname = os.path.join(fixture_dir, filename)
|
||||
fixture_dict[filename] = read_fixture_lines(fullname)
|
||||
|
||||
return fixture_dict
|
47
test/features/iocommands.feature
Normal file
47
test/features/iocommands.feature
Normal file
|
@ -0,0 +1,47 @@
|
|||
Feature: I/O commands
|
||||
|
||||
Scenario: edit sql in file with external editor
|
||||
When we start external editor providing a file name
|
||||
and we type "select * from abc" in the editor
|
||||
and we exit the editor
|
||||
then we see dbcli prompt
|
||||
and we see "select * from abc" in prompt
|
||||
|
||||
Scenario: tee output from query
|
||||
When we tee output
|
||||
and we wait for prompt
|
||||
and we select "select 123456"
|
||||
and we wait for prompt
|
||||
and we notee output
|
||||
and we wait for prompt
|
||||
then we see 123456 in tee output
|
||||
|
||||
Scenario: set delimiter
|
||||
When we query "delimiter $"
|
||||
then delimiter is set to "$"
|
||||
|
||||
Scenario: set delimiter twice
|
||||
When we query "delimiter $"
|
||||
and we query "delimiter ]]"
|
||||
then delimiter is set to "]]"
|
||||
|
||||
Scenario: set delimiter and query on same line
|
||||
When we query "select 123; delimiter $ select 456 $ delimiter %"
|
||||
then we see result "123"
|
||||
and we see result "456"
|
||||
and delimiter is set to "%"
|
||||
|
||||
Scenario: send output to file
|
||||
When we query "\o /tmp/output1.sql"
|
||||
and we query "select 123"
|
||||
and we query "system cat /tmp/output1.sql"
|
||||
then we see result "123"
|
||||
|
||||
Scenario: send output to file two times
|
||||
When we query "\o /tmp/output1.sql"
|
||||
and we query "select 123"
|
||||
and we query "\o /tmp/output2.sql"
|
||||
and we query "select 456"
|
||||
and we query "system cat /tmp/output2.sql"
|
||||
then we see result "456"
|
||||
|
24
test/features/named_queries.feature
Normal file
24
test/features/named_queries.feature
Normal file
|
@ -0,0 +1,24 @@
|
|||
Feature: named queries:
|
||||
save, use and delete named queries
|
||||
|
||||
Scenario: save, use and delete named queries
|
||||
When we connect to test database
|
||||
then we see database connected
|
||||
when we save a named query
|
||||
then we see the named query saved
|
||||
when we use a named query
|
||||
then we see the named query executed
|
||||
when we delete a named query
|
||||
then we see the named query deleted
|
||||
|
||||
Scenario: save, use and delete named queries with parameters
|
||||
When we connect to test database
|
||||
then we see database connected
|
||||
when we save a named query with parameters
|
||||
then we see the named query saved
|
||||
when we use named query with parameters
|
||||
then we see the named query with parameters executed
|
||||
when we use named query with too few parameters
|
||||
then we see the named query with parameters fail with missing parameters
|
||||
when we use named query with too many parameters
|
||||
then we see the named query with parameters fail with extra parameters
|
7
test/features/specials.feature
Normal file
7
test/features/specials.feature
Normal file
|
@ -0,0 +1,7 @@
|
|||
Feature: Special commands
|
||||
|
||||
@wip
|
||||
Scenario: run refresh command
|
||||
When we refresh completions
|
||||
and we wait for prompt
|
||||
then we see completions refresh started
|
0
test/features/steps/__init__.py
Normal file
0
test/features/steps/__init__.py
Normal file
45
test/features/steps/auto_vertical.py
Normal file
45
test/features/steps/auto_vertical.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
from textwrap import dedent
|
||||
|
||||
from behave import then, when
|
||||
|
||||
import wrappers
|
||||
|
||||
|
||||
@when('we run dbcli with {arg}')
|
||||
def step_run_cli_with_arg(context, arg):
|
||||
wrappers.run_cli(context, run_args=arg.split('='))
|
||||
|
||||
|
||||
@when('we execute a small query')
|
||||
def step_execute_small_query(context):
|
||||
context.cli.sendline('select 1')
|
||||
|
||||
|
||||
@when('we execute a large query')
|
||||
def step_execute_large_query(context):
|
||||
context.cli.sendline(
|
||||
'select {}'.format(','.join([str(n) for n in range(1, 50)])))
|
||||
|
||||
|
||||
@then('we see small results in horizontal format')
|
||||
def step_see_small_results(context):
|
||||
wrappers.expect_pager(context, dedent("""\
|
||||
+---+\r
|
||||
| 1 |\r
|
||||
+---+\r
|
||||
| 1 |\r
|
||||
+---+\r
|
||||
\r
|
||||
"""), timeout=5)
|
||||
wrappers.expect_exact(context, '1 row in set', timeout=2)
|
||||
|
||||
|
||||
@then('we see large results in vertical format')
|
||||
def step_see_large_results(context):
|
||||
rows = ['{n:3}| {n}'.format(n=str(n)) for n in range(1, 50)]
|
||||
expected = ('***************************[ 1. row ]'
|
||||
'***************************\r\n' +
|
||||
'{}\r\n'.format('\r\n'.join(rows) + '\r\n'))
|
||||
|
||||
wrappers.expect_pager(context, expected, timeout=10)
|
||||
wrappers.expect_exact(context, '1 row in set', timeout=2)
|
100
test/features/steps/basic_commands.py
Normal file
100
test/features/steps/basic_commands.py
Normal file
|
@ -0,0 +1,100 @@
|
|||
"""Steps for behavioral style tests are defined in this module.
|
||||
|
||||
Each step is defined by the string decorating it. This string is used
|
||||
to call the step in "*.feature" file.
|
||||
|
||||
"""
|
||||
|
||||
from behave import when
|
||||
from textwrap import dedent
|
||||
import tempfile
|
||||
import wrappers
|
||||
|
||||
|
||||
@when('we run dbcli')
|
||||
def step_run_cli(context):
|
||||
wrappers.run_cli(context)
|
||||
|
||||
|
||||
@when('we wait for prompt')
|
||||
def step_wait_prompt(context):
|
||||
wrappers.wait_prompt(context)
|
||||
|
||||
|
||||
@when('we send "ctrl + d"')
|
||||
def step_ctrl_d(context):
|
||||
"""Send Ctrl + D to hopefully exit."""
|
||||
context.cli.sendcontrol('d')
|
||||
context.exit_sent = True
|
||||
|
||||
|
||||
@when('we send "\?" command')
|
||||
def step_send_help(context):
|
||||
"""Send \?
|
||||
|
||||
to see help.
|
||||
|
||||
"""
|
||||
context.cli.sendline('\\?')
|
||||
wrappers.expect_exact(
|
||||
context, context.conf['pager_boundary'] + '\r\n', timeout=5)
|
||||
|
||||
|
||||
@when(u'we send source command')
|
||||
def step_send_source_command(context):
|
||||
with tempfile.NamedTemporaryFile() as f:
|
||||
f.write(b'\?')
|
||||
f.flush()
|
||||
context.cli.sendline('\. {0}'.format(f.name))
|
||||
wrappers.expect_exact(
|
||||
context, context.conf['pager_boundary'] + '\r\n', timeout=5)
|
||||
|
||||
|
||||
@when(u'we run query to check application_name')
|
||||
def step_check_application_name(context):
|
||||
context.cli.sendline(
|
||||
"SELECT 'found' FROM performance_schema.session_connect_attrs WHERE attr_name = 'program_name' AND attr_value = 'mycli'"
|
||||
)
|
||||
|
||||
|
||||
@then(u'we see found')
|
||||
def step_see_found(context):
|
||||
wrappers.expect_exact(
|
||||
context,
|
||||
context.conf['pager_boundary'] + '\r' + dedent('''
|
||||
+-------+\r
|
||||
| found |\r
|
||||
+-------+\r
|
||||
| found |\r
|
||||
+-------+\r
|
||||
\r
|
||||
''') + context.conf['pager_boundary'],
|
||||
timeout=5
|
||||
)
|
||||
|
||||
|
||||
@then(u'we confirm the destructive warning')
|
||||
def step_confirm_destructive_command(context):
|
||||
"""Confirm destructive command."""
|
||||
wrappers.expect_exact(
|
||||
context, 'You\'re about to run a destructive command.\r\nDo you want to proceed? (y/n):', timeout=2)
|
||||
context.cli.sendline('y')
|
||||
|
||||
|
||||
@when(u'we answer the destructive warning with "{confirmation}"')
|
||||
def step_confirm_destructive_command(context, confirmation):
|
||||
"""Confirm destructive command."""
|
||||
wrappers.expect_exact(
|
||||
context, 'You\'re about to run a destructive command.\r\nDo you want to proceed? (y/n):', timeout=2)
|
||||
context.cli.sendline(confirmation)
|
||||
|
||||
|
||||
@then(u'we answer the destructive warning with invalid "{confirmation}" and see text "{text}"')
|
||||
def step_confirm_destructive_command(context, confirmation, text):
|
||||
"""Confirm destructive command."""
|
||||
wrappers.expect_exact(
|
||||
context, 'You\'re about to run a destructive command.\r\nDo you want to proceed? (y/n):', timeout=2)
|
||||
context.cli.sendline(confirmation)
|
||||
wrappers.expect_exact(context, text, timeout=2)
|
||||
# we must exit the Click loop, or the feature will hang
|
||||
context.cli.sendline('n')
|
115
test/features/steps/crud_database.py
Normal file
115
test/features/steps/crud_database.py
Normal file
|
@ -0,0 +1,115 @@
|
|||
"""Steps for behavioral style tests are defined in this module.
|
||||
|
||||
Each step is defined by the string decorating it. This string is used
|
||||
to call the step in "*.feature" file.
|
||||
|
||||
"""
|
||||
|
||||
import pexpect
|
||||
|
||||
import wrappers
|
||||
from behave import when, then
|
||||
|
||||
|
||||
@when('we create database')
|
||||
def step_db_create(context):
|
||||
"""Send create database."""
|
||||
context.cli.sendline('create database {0};'.format(
|
||||
context.conf['dbname_tmp']))
|
||||
|
||||
context.response = {
|
||||
'database_name': context.conf['dbname_tmp']
|
||||
}
|
||||
|
||||
|
||||
@when('we drop database')
|
||||
def step_db_drop(context):
|
||||
"""Send drop database."""
|
||||
context.cli.sendline('drop database {0};'.format(
|
||||
context.conf['dbname_tmp']))
|
||||
|
||||
|
||||
@when('we connect to test database')
|
||||
def step_db_connect_test(context):
|
||||
"""Send connect to database."""
|
||||
db_name = context.conf['dbname']
|
||||
context.currentdb = db_name
|
||||
context.cli.sendline('use {0};'.format(db_name))
|
||||
|
||||
|
||||
@when('we connect to quoted test database')
|
||||
def step_db_connect_quoted_tmp(context):
|
||||
"""Send connect to database."""
|
||||
db_name = context.conf['dbname']
|
||||
context.currentdb = db_name
|
||||
context.cli.sendline('use `{0}`;'.format(db_name))
|
||||
|
||||
|
||||
@when('we connect to tmp database')
|
||||
def step_db_connect_tmp(context):
|
||||
"""Send connect to database."""
|
||||
db_name = context.conf['dbname_tmp']
|
||||
context.currentdb = db_name
|
||||
context.cli.sendline('use {0}'.format(db_name))
|
||||
|
||||
|
||||
@when('we connect to dbserver')
|
||||
def step_db_connect_dbserver(context):
|
||||
"""Send connect to database."""
|
||||
context.currentdb = 'mysql'
|
||||
context.cli.sendline('use mysql')
|
||||
|
||||
|
||||
@then('dbcli exits')
|
||||
def step_wait_exit(context):
|
||||
"""Make sure the cli exits."""
|
||||
wrappers.expect_exact(context, pexpect.EOF, timeout=5)
|
||||
|
||||
|
||||
@then('we see dbcli prompt')
|
||||
def step_see_prompt(context):
|
||||
"""Wait to see the prompt."""
|
||||
user = context.conf['user']
|
||||
host = context.conf['host']
|
||||
dbname = context.currentdb
|
||||
wrappers.wait_prompt(context, '{0}@{1}:{2}> '.format(user, host, dbname))
|
||||
|
||||
|
||||
@then('we see help output')
|
||||
def step_see_help(context):
|
||||
for expected_line in context.fixture_data['help_commands.txt']:
|
||||
wrappers.expect_exact(context, expected_line, timeout=1)
|
||||
|
||||
|
||||
@then('we see database created')
|
||||
def step_see_db_created(context):
|
||||
"""Wait to see create database output."""
|
||||
wrappers.expect_exact(context, 'Query OK, 1 row affected', timeout=2)
|
||||
|
||||
|
||||
@then('we see database dropped')
|
||||
def step_see_db_dropped(context):
|
||||
"""Wait to see drop database output."""
|
||||
wrappers.expect_exact(context, 'Query OK, 0 rows affected', timeout=2)
|
||||
|
||||
|
||||
@then('we see database dropped and no default database')
|
||||
def step_see_db_dropped_no_default(context):
|
||||
"""Wait to see drop database output."""
|
||||
user = context.conf['user']
|
||||
host = context.conf['host']
|
||||
database = '(none)'
|
||||
context.currentdb = None
|
||||
|
||||
wrappers.expect_exact(context, 'Query OK, 0 rows affected', timeout=2)
|
||||
wrappers.wait_prompt(context, '{0}@{1}:{2}>'.format(user, host, database))
|
||||
|
||||
|
||||
@then('we see database connected')
|
||||
def step_see_db_connected(context):
|
||||
"""Wait to see drop database output."""
|
||||
wrappers.expect_exact(
|
||||
context, 'You are now connected to database "', timeout=2)
|
||||
wrappers.expect_exact(context, '"', timeout=2)
|
||||
wrappers.expect_exact(context, ' as user "{0}"'.format(
|
||||
context.conf['user']), timeout=2)
|
112
test/features/steps/crud_table.py
Normal file
112
test/features/steps/crud_table.py
Normal file
|
@ -0,0 +1,112 @@
|
|||
"""Steps for behavioral style tests are defined in this module.
|
||||
|
||||
Each step is defined by the string decorating it. This string is used
|
||||
to call the step in "*.feature" file.
|
||||
|
||||
"""
|
||||
|
||||
import wrappers
|
||||
from behave import when, then
|
||||
from textwrap import dedent
|
||||
|
||||
|
||||
@when('we create table')
|
||||
def step_create_table(context):
|
||||
"""Send create table."""
|
||||
context.cli.sendline('create table a(x text);')
|
||||
|
||||
|
||||
@when('we insert into table')
|
||||
def step_insert_into_table(context):
|
||||
"""Send insert into table."""
|
||||
context.cli.sendline('''insert into a(x) values('xxx');''')
|
||||
|
||||
|
||||
@when('we update table')
|
||||
def step_update_table(context):
|
||||
"""Send insert into table."""
|
||||
context.cli.sendline('''update a set x = 'yyy' where x = 'xxx';''')
|
||||
|
||||
|
||||
@when('we select from table')
|
||||
def step_select_from_table(context):
|
||||
"""Send select from table."""
|
||||
context.cli.sendline('select * from a;')
|
||||
|
||||
|
||||
@when('we delete from table')
|
||||
def step_delete_from_table(context):
|
||||
"""Send deete from table."""
|
||||
context.cli.sendline('''delete from a where x = 'yyy';''')
|
||||
|
||||
|
||||
@when('we drop table')
|
||||
def step_drop_table(context):
|
||||
"""Send drop table."""
|
||||
context.cli.sendline('drop table a;')
|
||||
|
||||
|
||||
@then('we see table created')
|
||||
def step_see_table_created(context):
|
||||
"""Wait to see create table output."""
|
||||
wrappers.expect_exact(context, 'Query OK, 0 rows affected', timeout=2)
|
||||
|
||||
|
||||
@then('we see record inserted')
|
||||
def step_see_record_inserted(context):
|
||||
"""Wait to see insert output."""
|
||||
wrappers.expect_exact(context, 'Query OK, 1 row affected', timeout=2)
|
||||
|
||||
|
||||
@then('we see record updated')
|
||||
def step_see_record_updated(context):
|
||||
"""Wait to see update output."""
|
||||
wrappers.expect_exact(context, 'Query OK, 1 row affected', timeout=2)
|
||||
|
||||
|
||||
@then('we see data selected')
|
||||
def step_see_data_selected(context):
|
||||
"""Wait to see select output."""
|
||||
wrappers.expect_pager(
|
||||
context, dedent("""\
|
||||
+-----+\r
|
||||
| x |\r
|
||||
+-----+\r
|
||||
| yyy |\r
|
||||
+-----+\r
|
||||
\r
|
||||
"""), timeout=2)
|
||||
wrappers.expect_exact(context, '1 row in set', timeout=2)
|
||||
|
||||
|
||||
@then('we see record deleted')
|
||||
def step_see_data_deleted(context):
|
||||
"""Wait to see delete output."""
|
||||
wrappers.expect_exact(context, 'Query OK, 1 row affected', timeout=2)
|
||||
|
||||
|
||||
@then('we see table dropped')
|
||||
def step_see_table_dropped(context):
|
||||
"""Wait to see drop output."""
|
||||
wrappers.expect_exact(context, 'Query OK, 0 rows affected', timeout=2)
|
||||
|
||||
|
||||
@when('we select null')
|
||||
def step_select_null(context):
|
||||
"""Send select null."""
|
||||
context.cli.sendline('select null;')
|
||||
|
||||
|
||||
@then('we see null selected')
|
||||
def step_see_null_selected(context):
|
||||
"""Wait to see null output."""
|
||||
wrappers.expect_pager(
|
||||
context, dedent("""\
|
||||
+--------+\r
|
||||
| NULL |\r
|
||||
+--------+\r
|
||||
| <null> |\r
|
||||
+--------+\r
|
||||
\r
|
||||
"""), timeout=2)
|
||||
wrappers.expect_exact(context, '1 row in set', timeout=2)
|
105
test/features/steps/iocommands.py
Normal file
105
test/features/steps/iocommands.py
Normal file
|
@ -0,0 +1,105 @@
|
|||
import os
|
||||
import wrappers
|
||||
|
||||
from behave import when, then
|
||||
from textwrap import dedent
|
||||
|
||||
|
||||
@when('we start external editor providing a file name')
|
||||
def step_edit_file(context):
|
||||
"""Edit file with external editor."""
|
||||
context.editor_file_name = os.path.join(
|
||||
context.package_root, 'test_file_{0}.sql'.format(context.conf['vi']))
|
||||
if os.path.exists(context.editor_file_name):
|
||||
os.remove(context.editor_file_name)
|
||||
context.cli.sendline('\e {0}'.format(
|
||||
os.path.basename(context.editor_file_name)))
|
||||
wrappers.expect_exact(
|
||||
context, 'Entering Ex mode. Type "visual" to go to Normal mode.', timeout=2)
|
||||
wrappers.expect_exact(context, '\r\n:', timeout=2)
|
||||
|
||||
|
||||
@when('we type "{query}" in the editor')
|
||||
def step_edit_type_sql(context, query):
|
||||
context.cli.sendline('i')
|
||||
context.cli.sendline(query)
|
||||
context.cli.sendline('.')
|
||||
wrappers.expect_exact(context, '\r\n:', timeout=2)
|
||||
|
||||
|
||||
@when('we exit the editor')
|
||||
def step_edit_quit(context):
|
||||
context.cli.sendline('x')
|
||||
wrappers.expect_exact(context, "written", timeout=2)
|
||||
|
||||
|
||||
@then('we see "{query}" in prompt')
|
||||
def step_edit_done_sql(context, query):
|
||||
for match in query.split(' '):
|
||||
wrappers.expect_exact(context, match, timeout=5)
|
||||
# Cleanup the command line.
|
||||
context.cli.sendcontrol('c')
|
||||
# Cleanup the edited file.
|
||||
if context.editor_file_name and os.path.exists(context.editor_file_name):
|
||||
os.remove(context.editor_file_name)
|
||||
|
||||
|
||||
@when(u'we tee output')
|
||||
def step_tee_ouptut(context):
|
||||
context.tee_file_name = os.path.join(
|
||||
context.package_root, 'tee_file_{0}.sql'.format(context.conf['vi']))
|
||||
if os.path.exists(context.tee_file_name):
|
||||
os.remove(context.tee_file_name)
|
||||
context.cli.sendline('tee {0}'.format(
|
||||
os.path.basename(context.tee_file_name)))
|
||||
|
||||
|
||||
@when(u'we select "select {param}"')
|
||||
def step_query_select_number(context, param):
|
||||
context.cli.sendline(u'select {}'.format(param))
|
||||
wrappers.expect_pager(context, dedent(u"""\
|
||||
+{dashes}+\r
|
||||
| {param} |\r
|
||||
+{dashes}+\r
|
||||
| {param} |\r
|
||||
+{dashes}+\r
|
||||
\r
|
||||
""".format(param=param, dashes='-' * (len(param) + 2))
|
||||
), timeout=5)
|
||||
wrappers.expect_exact(context, '1 row in set', timeout=2)
|
||||
|
||||
|
||||
@then(u'we see result "{result}"')
|
||||
def step_see_result(context, result):
|
||||
wrappers.expect_exact(
|
||||
context,
|
||||
u"| {} |".format(result),
|
||||
timeout=2
|
||||
)
|
||||
|
||||
|
||||
@when(u'we query "{query}"')
|
||||
def step_query(context, query):
|
||||
context.cli.sendline(query)
|
||||
|
||||
|
||||
@when(u'we notee output')
|
||||
def step_notee_output(context):
|
||||
context.cli.sendline('notee')
|
||||
|
||||
|
||||
@then(u'we see 123456 in tee output')
|
||||
def step_see_123456_in_ouput(context):
|
||||
with open(context.tee_file_name) as f:
|
||||
assert '123456' in f.read()
|
||||
if os.path.exists(context.tee_file_name):
|
||||
os.remove(context.tee_file_name)
|
||||
|
||||
|
||||
@then(u'delimiter is set to "{delimiter}"')
|
||||
def delimiter_is_set(context, delimiter):
|
||||
wrappers.expect_exact(
|
||||
context,
|
||||
u'Changed delimiter to {}'.format(delimiter),
|
||||
timeout=2
|
||||
)
|
90
test/features/steps/named_queries.py
Normal file
90
test/features/steps/named_queries.py
Normal file
|
@ -0,0 +1,90 @@
|
|||
"""Steps for behavioral style tests are defined in this module.
|
||||
|
||||
Each step is defined by the string decorating it. This string is used
|
||||
to call the step in "*.feature" file.
|
||||
|
||||
"""
|
||||
|
||||
import wrappers
|
||||
from behave import when, then
|
||||
|
||||
|
||||
@when('we save a named query')
|
||||
def step_save_named_query(context):
|
||||
"""Send \fs command."""
|
||||
context.cli.sendline('\\fs foo SELECT 12345')
|
||||
|
||||
|
||||
@when('we use a named query')
|
||||
def step_use_named_query(context):
|
||||
"""Send \f command."""
|
||||
context.cli.sendline('\\f foo')
|
||||
|
||||
|
||||
@when('we delete a named query')
|
||||
def step_delete_named_query(context):
|
||||
"""Send \fd command."""
|
||||
context.cli.sendline('\\fd foo')
|
||||
|
||||
|
||||
@then('we see the named query saved')
|
||||
def step_see_named_query_saved(context):
|
||||
"""Wait to see query saved."""
|
||||
wrappers.expect_exact(context, 'Saved.', timeout=2)
|
||||
|
||||
|
||||
@then('we see the named query executed')
|
||||
def step_see_named_query_executed(context):
|
||||
"""Wait to see select output."""
|
||||
wrappers.expect_exact(context, 'SELECT 12345', timeout=2)
|
||||
|
||||
|
||||
@then('we see the named query deleted')
|
||||
def step_see_named_query_deleted(context):
|
||||
"""Wait to see query deleted."""
|
||||
wrappers.expect_exact(context, 'foo: Deleted', timeout=2)
|
||||
|
||||
|
||||
@when('we save a named query with parameters')
|
||||
def step_save_named_query_with_parameters(context):
|
||||
"""Send \fs command for query with parameters."""
|
||||
context.cli.sendline('\\fs foo_args SELECT $1, "$2", "$3"')
|
||||
|
||||
|
||||
@when('we use named query with parameters')
|
||||
def step_use_named_query_with_parameters(context):
|
||||
"""Send \f command with parameters."""
|
||||
context.cli.sendline('\\f foo_args 101 second "third value"')
|
||||
|
||||
|
||||
@then('we see the named query with parameters executed')
|
||||
def step_see_named_query_with_parameters_executed(context):
|
||||
"""Wait to see select output."""
|
||||
wrappers.expect_exact(
|
||||
context, 'SELECT 101, "second", "third value"', timeout=2)
|
||||
|
||||
|
||||
@when('we use named query with too few parameters')
|
||||
def step_use_named_query_with_too_few_parameters(context):
|
||||
"""Send \f command with missing parameters."""
|
||||
context.cli.sendline('\\f foo_args 101')
|
||||
|
||||
|
||||
@then('we see the named query with parameters fail with missing parameters')
|
||||
def step_see_named_query_with_parameters_fail_with_missing_parameters(context):
|
||||
"""Wait to see select output."""
|
||||
wrappers.expect_exact(
|
||||
context, 'missing substitution for $2 in query:', timeout=2)
|
||||
|
||||
|
||||
@when('we use named query with too many parameters')
|
||||
def step_use_named_query_with_too_many_parameters(context):
|
||||
"""Send \f command with extra parameters."""
|
||||
context.cli.sendline('\\f foo_args 101 102 103 104')
|
||||
|
||||
|
||||
@then('we see the named query with parameters fail with extra parameters')
|
||||
def step_see_named_query_with_parameters_fail_with_extra_parameters(context):
|
||||
"""Wait to see select output."""
|
||||
wrappers.expect_exact(
|
||||
context, 'query does not have substitution parameter $4:', timeout=2)
|
27
test/features/steps/specials.py
Normal file
27
test/features/steps/specials.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
"""Steps for behavioral style tests are defined in this module.
|
||||
|
||||
Each step is defined by the string decorating it. This string is used
|
||||
to call the step in "*.feature" file.
|
||||
|
||||
"""
|
||||
|
||||
import wrappers
|
||||
from behave import when, then
|
||||
|
||||
|
||||
@when('we refresh completions')
|
||||
def step_refresh_completions(context):
|
||||
"""Send refresh command."""
|
||||
context.cli.sendline('rehash')
|
||||
|
||||
|
||||
@then('we see text "{text}"')
|
||||
def step_see_text(context, text):
|
||||
"""Wait to see given text message."""
|
||||
wrappers.expect_exact(context, text, timeout=2)
|
||||
|
||||
@then('we see completions refresh started')
|
||||
def step_see_refresh_started(context):
|
||||
"""Wait to see refresh output."""
|
||||
wrappers.expect_exact(
|
||||
context, 'Auto-completion refresh started in the background.', timeout=2)
|
94
test/features/steps/wrappers.py
Normal file
94
test/features/steps/wrappers.py
Normal file
|
@ -0,0 +1,94 @@
|
|||
import re
|
||||
import pexpect
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
try:
|
||||
from StringIO import StringIO
|
||||
except ImportError:
|
||||
from io import StringIO
|
||||
|
||||
|
||||
def expect_exact(context, expected, timeout):
|
||||
timedout = False
|
||||
try:
|
||||
context.cli.expect_exact(expected, timeout=timeout)
|
||||
except pexpect.exceptions.TIMEOUT:
|
||||
timedout = True
|
||||
if timedout:
|
||||
# Strip color codes out of the output.
|
||||
actual = re.sub(r'\x1b\[([0-9A-Za-z;?])+[m|K]?',
|
||||
'', context.cli.before)
|
||||
raise Exception(
|
||||
textwrap.dedent('''\
|
||||
Expected:
|
||||
---
|
||||
{0!r}
|
||||
---
|
||||
Actual:
|
||||
---
|
||||
{1!r}
|
||||
---
|
||||
Full log:
|
||||
---
|
||||
{2!r}
|
||||
---
|
||||
''').format(
|
||||
expected,
|
||||
actual,
|
||||
context.logfile.getvalue()
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def expect_pager(context, expected, timeout):
|
||||
expect_exact(context, "{0}\r\n{1}{0}\r\n".format(
|
||||
context.conf['pager_boundary'], expected), timeout=timeout)
|
||||
|
||||
|
||||
def run_cli(context, run_args=None):
|
||||
"""Run the process using pexpect."""
|
||||
run_args = run_args or []
|
||||
if context.conf.get('host', None):
|
||||
run_args.extend(('-h', context.conf['host']))
|
||||
if context.conf.get('user', None):
|
||||
run_args.extend(('-u', context.conf['user']))
|
||||
if context.conf.get('pass', None):
|
||||
run_args.extend(('-p', context.conf['pass']))
|
||||
if context.conf.get('dbname', None):
|
||||
run_args.extend(('-D', context.conf['dbname']))
|
||||
if context.conf.get('defaults-file', None):
|
||||
run_args.extend(('--defaults-file', context.conf['defaults-file']))
|
||||
if context.conf.get('myclirc', None):
|
||||
run_args.extend(('--myclirc', context.conf['myclirc']))
|
||||
try:
|
||||
cli_cmd = context.conf['cli_command']
|
||||
except KeyError:
|
||||
cli_cmd = (
|
||||
'{0!s} -c "'
|
||||
'import coverage ; '
|
||||
'coverage.process_startup(); '
|
||||
'import mycli.main; '
|
||||
'mycli.main.cli()'
|
||||
'"'
|
||||
).format(sys.executable)
|
||||
|
||||
cmd_parts = [cli_cmd] + run_args
|
||||
cmd = ' '.join(cmd_parts)
|
||||
context.cli = pexpect.spawnu(cmd, cwd=context.package_root)
|
||||
context.logfile = StringIO()
|
||||
context.cli.logfile = context.logfile
|
||||
context.exit_sent = False
|
||||
context.currentdb = context.conf['dbname']
|
||||
|
||||
|
||||
def wait_prompt(context, prompt=None):
|
||||
"""Make sure prompt is displayed."""
|
||||
if prompt is None:
|
||||
user = context.conf['user']
|
||||
host = context.conf['host']
|
||||
dbname = context.currentdb
|
||||
prompt = '{0}@{1}:{2}>'.format(
|
||||
user, host, dbname),
|
||||
expect_exact(context, prompt, timeout=5)
|
||||
context.atprompt = True
|
16
test/features/wrappager.py
Executable file
16
test/features/wrappager.py
Executable file
|
@ -0,0 +1,16 @@
|
|||
#!/usr/bin/env python
|
||||
import sys
|
||||
|
||||
|
||||
def wrappager(boundary):
|
||||
print(boundary)
|
||||
while 1:
|
||||
buf = sys.stdin.read(2048)
|
||||
if not buf:
|
||||
break
|
||||
sys.stdout.write(buf)
|
||||
print(boundary)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
wrappager(sys.argv[1])
|
Loading…
Add table
Add a link
Reference in a new issue