1
0
Fork 0

Adding upstream version 1.23.2.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-02-09 18:53:36 +01:00
parent f253096a15
commit 94e3fc38e7
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
93 changed files with 10761 additions and 0 deletions

View file

View 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

View 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

View 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

View 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
View 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()

View 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()

View 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. |
+--------------------------+-----------------------------------------------+

View 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). |
+-------------+----------------------------+------------------------------------------------------------+

View 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

View 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"

View 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

View 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

View file

View 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)

View 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')

View 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)

View 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)

View 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
)

View 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)

View 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)

View 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
View 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])