1
0
Fork 0

Adding upstream version 3.1.0.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-02-09 19:48:22 +01:00
parent f2184ff4ed
commit ec5391b244
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
104 changed files with 15144 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,58 @@
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: run partial select command
When we send partial select command
then we see error message
then we see dbcli prompt
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
Scenario: list databases
When we list databases
then we see list of databases
Scenario: run the cli with --username
When we launch dbcli using --username
and we send "\?" command
then we see help output
Scenario: run the cli with --user
When we launch dbcli using --user
and we send "\?" command
then we see help output
Scenario: run the cli with --port
When we launch dbcli using --port
and we send "\?" command
then we see help output
Scenario: run the cli with --password
When we launch dbcli using --password
then we send password
and we see dbcli prompt
when we send "\?" command
then we see help output
@wip
Scenario: run the cli with dsn and password
When we launch dbcli using dsn_password
then we send password
and we see dbcli prompt
when we send "\?" command
then we see help output

View file

@ -0,0 +1,17 @@
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

View file

@ -0,0 +1,22 @@
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

View file

@ -0,0 +1,78 @@
from psycopg2 import connect
from psycopg2.extensions import AsIs
def create_db(
hostname="localhost", username=None, password=None, dbname=None, port=None
):
"""Create test database.
:param hostname: string
:param username: string
:param password: string
:param dbname: string
:param port: int
:return:
"""
cn = create_cn(hostname, password, username, "postgres", port)
# ISOLATION_LEVEL_AUTOCOMMIT = 0
# Needed for DB creation.
cn.set_isolation_level(0)
with cn.cursor() as cr:
cr.execute("drop database if exists %s", (AsIs(dbname),))
cr.execute("create database %s", (AsIs(dbname),))
cn.close()
cn = create_cn(hostname, password, username, dbname, port)
return cn
def create_cn(hostname, password, username, dbname, port):
"""
Open connection to database.
:param hostname:
:param password:
:param username:
:param dbname: string
:return: psycopg2.connection
"""
cn = connect(
host=hostname, user=username, database=dbname, password=password, port=port
)
print("Created connection: {0}.".format(cn.dsn))
return cn
def drop_db(hostname="localhost", username=None, password=None, dbname=None, port=None):
"""
Drop database.
:param hostname: string
:param username: string
:param password: string
:param dbname: string
"""
cn = create_cn(hostname, password, username, "postgres", port)
# ISOLATION_LEVEL_AUTOCOMMIT = 0
# Needed for DB drop.
cn.set_isolation_level(0)
with cn.cursor() as cr:
cr.execute("drop database if exists %s", (AsIs(dbname),))
close_cn(cn)
def close_cn(cn=None):
"""
Close connection.
:param connection: psycopg2.connection
"""
if cn:
cn.close()
print("Closed connection: {0}.".format(cn.dsn))

View file

@ -0,0 +1,192 @@
import copy
import os
import sys
import db_utils as dbutils
import fixture_utils as fixutils
import pexpect
import tempfile
import shutil
import signal
from steps import wrappers
def before_all(context):
"""Set env parameters."""
env_old = copy.deepcopy(dict(os.environ))
os.environ["LINES"] = "100"
os.environ["COLUMNS"] = "100"
os.environ["PAGER"] = "cat"
os.environ["EDITOR"] = "ex"
os.environ["VISUAL"] = "ex"
os.environ["PROMPT_TOOLKIT_NO_CPR"] = "1"
context.package_root = os.path.abspath(
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
)
fixture_dir = os.path.join(context.package_root, "tests/features/fixture_data")
print("package root:", context.package_root)
print("fixture dir:", fixture_dir)
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("pg_test_db", "pgcli_behave_tests")
db_name_full = "{0}_{1}".format(db_name, vi)
# Store get params from config.
context.conf = {
"host": context.config.userdata.get(
"pg_test_host", os.getenv("PGHOST", "localhost")
),
"user": context.config.userdata.get(
"pg_test_user", os.getenv("PGUSER", "postgres")
),
"pass": context.config.userdata.get(
"pg_test_pass", os.getenv("PGPASSWORD", None)
),
"port": context.config.userdata.get(
"pg_test_port", os.getenv("PGPORT", "5432")
),
"cli_command": (
context.config.userdata.get("pg_cli_command", None)
or '{python} -c "{startup}"'.format(
python=sys.executable,
startup="; ".join(
[
"import coverage",
"coverage.process_startup()",
"import pgcli.main",
"pgcli.main.cli()",
]
),
)
),
"dbname": db_name_full,
"dbname_tmp": db_name_full + "_tmp",
"vi": vi,
"pager_boundary": "---boundary---",
}
os.environ["PAGER"] = "{0} {1} {2}".format(
sys.executable,
os.path.join(context.package_root, "tests/features/wrappager.py"),
context.conf["pager_boundary"],
)
# Store old env vars.
context.pgenv = {
"PGDATABASE": os.environ.get("PGDATABASE", None),
"PGUSER": os.environ.get("PGUSER", None),
"PGHOST": os.environ.get("PGHOST", None),
"PGPASSWORD": os.environ.get("PGPASSWORD", None),
"PGPORT": os.environ.get("PGPORT", None),
"XDG_CONFIG_HOME": os.environ.get("XDG_CONFIG_HOME", None),
"PGSERVICEFILE": os.environ.get("PGSERVICEFILE", None),
}
# Set new env vars.
os.environ["PGDATABASE"] = context.conf["dbname"]
os.environ["PGUSER"] = context.conf["user"]
os.environ["PGHOST"] = context.conf["host"]
os.environ["PGPORT"] = context.conf["port"]
os.environ["PGSERVICEFILE"] = os.path.join(fixture_dir, "mock_pg_service.conf")
if context.conf["pass"]:
os.environ["PGPASSWORD"] = context.conf["pass"]
else:
if "PGPASSWORD" in os.environ:
del os.environ["PGPASSWORD"]
context.cn = dbutils.create_db(
context.conf["host"],
context.conf["user"],
context.conf["pass"],
context.conf["dbname"],
context.conf["port"],
)
context.fixture_data = fixutils.read_fixture_files()
# use temporary directory as config home
context.env_config_home = tempfile.mkdtemp(prefix="pgcli_home_")
os.environ["XDG_CONFIG_HOME"] = context.env_config_home
show_env_changes(env_old, dict(os.environ))
def show_env_changes(env_old, env_new):
"""Print out all test-specific env values."""
print("--- os.environ changed values: ---")
all_keys = set(list(env_old.keys()) + list(env_new.keys()))
for k in sorted(all_keys):
old_value = env_old.get(k, "")
new_value = env_new.get(k, "")
if new_value and old_value != new_value:
print('{}="{}"'.format(k, new_value))
print("-" * 20)
def after_all(context):
"""
Unset env parameters.
"""
dbutils.close_cn(context.cn)
dbutils.drop_db(
context.conf["host"],
context.conf["user"],
context.conf["pass"],
context.conf["dbname"],
context.conf["port"],
)
# Remove temp config direcotry
shutil.rmtree(context.env_config_home)
# 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, scenario):
if scenario.name == "list databases":
# not using the cli for that
return
wrappers.run_cli(context)
wrappers.wait_prompt(context)
def after_scenario(context, scenario):
"""Cleans up after each scenario completes."""
if hasattr(context, "cli") and context.cli and not context.exit_sent:
# Quit nicely.
if not context.atprompt:
dbname = context.currentdb
context.cli.expect_exact("{0}> ".format(dbname), timeout=15)
context.cli.sendcontrol("c")
context.cli.sendcontrol("d")
try:
context.cli.expect_exact(pexpect.EOF, timeout=15)
except pexpect.TIMEOUT:
print("--- after_scenario {}: kill cli".format(scenario.name))
context.cli.kill(signal.SIGKILL)
if hasattr(context, "tmpfile_sql_help") and context.tmpfile_sql_help:
context.tmpfile_sql_help.close()
context.tmpfile_sql_help = None
# # TODO: uncomment to debug a failure
# def after_step(context, step):
# if step.status == "failed":
# import pdb; pdb.set_trace()

View file

@ -0,0 +1,29 @@
Feature: expanded mode:
on, off, auto
Scenario: expanded on
When we prepare the test data
and we set expanded on
and we select from table
then we see expanded data selected
when we drop table
then we confirm the destructive warning
then we see table dropped
Scenario: expanded off
When we prepare the test data
and we set expanded off
and we select from table
then we see nonexpanded data selected
when we drop table
then we confirm the destructive warning
then we see table dropped
Scenario: expanded auto
When we prepare the test data
and we set expanded auto
and we select from table
then we see auto data selected
when we drop table
then we confirm the destructive warning
then we see table dropped

View file

@ -0,0 +1,25 @@
+--------------------------+------------------------------------------------+
| Command | Description |
|--------------------------+------------------------------------------------|
| \# | Refresh auto-completions. |
| \? | Show Help. |
| \T [format] | Change the table format used to output results |
| \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,64 @@
Command
Description
\#
Refresh auto-completions.
\?
Show Commands.
\T [format]
Change the table format used to output results
\c[onnect] database_name
Change to a new database.
\copy [tablename] to/from [filename]
Copy data between a file and a table.
\d[+] [pattern]
List or describe tables, views and sequences.
\dT[S+] [pattern]
List data types
\db[+] [pattern]
List tablespaces.
\df[+] [pattern]
List functions.
\di[+] [pattern]
List indexes.
\dm[+] [pattern]
List materialized views.
\dn[+] [pattern]
List schemas.
\ds[+] [pattern]
List sequences.
\dt[+] [pattern]
List tables.
\du[+] [pattern]
List roles.
\dv[+] [pattern]
List views.
\dx[+] [pattern]
List extensions.
\e [file]
Edit the query with external editor.
\h
Show SQL syntax and help.
\i filename
Execute commands from file.
\l
List databases.
\n[+] [name] [param1 param2 ...]
List or execute named queries.
\nd [name]
Delete a named query.
\ns name query
Save a named query.
\o [filename]
Send all query results to file.
\pager [command]
Set PAGER. Print the query results via PAGER.
\pset [key] [value]
A limited version of traditional \pset
\refresh
Refresh auto-completions.
\sf[+] FUNCNAME
Show a function's definition.
\timing
Toggle timing of commands.
\x
Toggle expanded output.

View file

@ -0,0 +1,4 @@
[mock_postgres]
dbname=postgres
host=localhost
user=postgres

View file

@ -0,0 +1,28 @@
import os
import codecs
def read_fixture_lines(filename):
"""
Read lines of text from file.
:param filename: string name
:return: list of strings
"""
lines = []
for line in codecs.open(filename, "rb", encoding="utf-8"):
lines.append(line.strip())
return lines
def read_fixture_files():
"""Read all files inside fixture_data directory."""
current_dir = os.path.dirname(__file__)
fixture_dir = os.path.join(current_dir, "fixture_data/")
print("reading fixture data: {}".format(fixture_dir))
fixture_dict = {}
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,17 @@
Feature: I/O commands
Scenario: edit sql in file with external editor
When we start external editor providing a file name
and we type sql in the editor
and we exit the editor
then we see dbcli prompt
and we see the sql in prompt
Scenario: tee output from query
When we tee output
and we wait for prompt
and we query "select 123456"
and we wait for prompt
and we stop teeing output
and we wait for prompt
then we see 123456 in tee output

View file

@ -0,0 +1,10 @@
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 delete a named query
then we see the named query deleted

View file

@ -0,0 +1,6 @@
Feature: Special commands
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,99 @@
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
| ?column? |\r
|------------|\r
| 1 |\r
+------------+\r
SELECT 1\r
"""
),
timeout=5,
)
@then("we see large results in vertical format")
def step_see_large_results(context):
wrappers.expect_pager(
context,
dedent(
"""\
-[ RECORD 1 ]-------------------------\r
?column? | 1\r
?column? | 2\r
?column? | 3\r
?column? | 4\r
?column? | 5\r
?column? | 6\r
?column? | 7\r
?column? | 8\r
?column? | 9\r
?column? | 10\r
?column? | 11\r
?column? | 12\r
?column? | 13\r
?column? | 14\r
?column? | 15\r
?column? | 16\r
?column? | 17\r
?column? | 18\r
?column? | 19\r
?column? | 20\r
?column? | 21\r
?column? | 22\r
?column? | 23\r
?column? | 24\r
?column? | 25\r
?column? | 26\r
?column? | 27\r
?column? | 28\r
?column? | 29\r
?column? | 30\r
?column? | 31\r
?column? | 32\r
?column? | 33\r
?column? | 34\r
?column? | 35\r
?column? | 36\r
?column? | 37\r
?column? | 38\r
?column? | 39\r
?column? | 40\r
?column? | 41\r
?column? | 42\r
?column? | 43\r
?column? | 44\r
?column? | 45\r
?column? | 46\r
?column? | 47\r
?column? | 48\r
?column? | 49\r
SELECT 1\r
"""
),
timeout=5,
)

View file

@ -0,0 +1,147 @@
"""
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 subprocess
import tempfile
from behave import when, then
from textwrap import dedent
import wrappers
@when("we list databases")
def step_list_databases(context):
cmd = ["pgcli", "--list"]
context.cmd_output = subprocess.check_output(cmd, cwd=context.package_root)
@then("we see list of databases")
def step_see_list_databases(context):
assert b"List of databases" in context.cmd_output
assert b"postgres" in context.cmd_output
context.cmd_output = None
@when("we run dbcli")
def step_run_cli(context):
wrappers.run_cli(context)
@when("we launch dbcli using {arg}")
def step_run_cli_using_arg(context, arg):
prompt_check = False
currentdb = None
if arg == "--username":
arg = "--username={}".format(context.conf["user"])
if arg == "--user":
arg = "--user={}".format(context.conf["user"])
if arg == "--port":
arg = "--port={}".format(context.conf["port"])
if arg == "--password":
arg = "--password"
prompt_check = False
# This uses the mock_pg_service.conf file in fixtures folder.
if arg == "dsn_password":
arg = "service=mock_postgres --password"
prompt_check = False
currentdb = "postgres"
wrappers.run_cli(
context, run_args=[arg], prompt_check=prompt_check, currentdb=currentdb
)
@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.
"""
# turn off pager before exiting
context.cli.sendline("\pset pager off")
wrappers.wait_prompt(context)
context.cli.sendcontrol("d")
context.cli.expect(pexpect.EOF, timeout=15)
context.exit_sent = True
@when('we send "\?" command')
def step_send_help(context):
"""
Send \? to see help.
"""
context.cli.sendline("\?")
@when("we send partial select command")
def step_send_partial_select_command(context):
"""
Send `SELECT a` to see completion.
"""
context.cli.sendline("SELECT a")
@then("we see error message")
def step_see_error_message(context):
wrappers.expect_exact(context, 'column "a" does not exist', timeout=2)
@when("we send source command")
def step_send_source_command(context):
context.tmpfile_sql_help = tempfile.NamedTemporaryFile(prefix="pgcli_")
context.tmpfile_sql_help.write(b"\?")
context.tmpfile_sql_help.flush()
context.cli.sendline("\i {0}".format(context.tmpfile_sql_help.name))
wrappers.expect_exact(context, context.conf["pager_boundary"] + "\r\n", timeout=5)
@when("we run query to check application_name")
def step_check_application_name(context):
context.cli.sendline(
"SELECT 'found' FROM pg_stat_activity WHERE application_name = 'pgcli' HAVING COUNT(*) > 0;"
)
@then("we see found")
def step_see_found(context):
wrappers.expect_exact(
context,
context.conf["pager_boundary"]
+ "\r"
+ dedent(
"""
+------------+\r
| ?column? |\r
|------------|\r
| found |\r
+------------+\r
SELECT 1\r
"""
)
+ context.conf["pager_boundary"],
timeout=5,
)
@then("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")
@then("we send password")
def step_send_password(context):
wrappers.expect_exact(context, "Password for", timeout=5)
context.cli.sendline(context.conf["pass"] or "DOES NOT MATTER")

View file

@ -0,0 +1,93 @@
"""
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
from behave import when, then
import wrappers
@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.cli.sendline("\\connect {0}".format(db_name))
@when("we connect to dbserver")
def step_db_connect_dbserver(context):
"""
Send connect to database.
"""
context.cli.sendline("\\connect postgres")
context.currentdb = "postgres"
@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.
"""
db_name = getattr(context, "currentdb", context.conf["dbname"])
wrappers.expect_exact(context, "{0}> ".format(db_name), timeout=5)
context.atprompt = True
@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=2)
@then("we see database created")
def step_see_db_created(context):
"""
Wait to see create database output.
"""
wrappers.expect_pager(context, "CREATE DATABASE\r\n", timeout=5)
@then("we see database dropped")
def step_see_db_dropped(context):
"""
Wait to see drop database output.
"""
wrappers.expect_pager(context, "DROP DATABASE\r\n", timeout=2)
@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)

View file

@ -0,0 +1,118 @@
"""
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, then
from textwrap import dedent
import wrappers
@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_pager(context, "CREATE TABLE\r\n", timeout=2)
@then("we see record inserted")
def step_see_record_inserted(context):
"""
Wait to see insert output.
"""
wrappers.expect_pager(context, "INSERT 0 1\r\n", timeout=2)
@then("we see record updated")
def step_see_record_updated(context):
"""
Wait to see update output.
"""
wrappers.expect_pager(context, "UPDATE 1\r\n", 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
SELECT 1\r
"""
),
timeout=1,
)
@then("we see record deleted")
def step_see_data_deleted(context):
"""
Wait to see delete output.
"""
wrappers.expect_pager(context, "DELETE 1\r\n", timeout=2)
@then("we see table dropped")
def step_see_table_dropped(context):
"""
Wait to see drop output.
"""
wrappers.expect_pager(context, "DROP TABLE\r\n", timeout=2)

View file

@ -0,0 +1,70 @@
"""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, then
from textwrap import dedent
import wrappers
@when("we prepare the test data")
def step_prepare_data(context):
"""Create table, insert a record."""
context.cli.sendline("drop table if exists a;")
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")
wrappers.wait_prompt(context)
context.cli.sendline("create table a(x integer, y real, z numeric(10, 4));")
wrappers.expect_pager(context, "CREATE TABLE\r\n", timeout=2)
context.cli.sendline("""insert into a(x, y, z) values(1, 1.0, 1.0);""")
wrappers.expect_pager(context, "INSERT 0 1\r\n", timeout=2)
@when("we set expanded {mode}")
def step_set_expanded(context, mode):
"""Set expanded to mode."""
context.cli.sendline("\\" + "x {}".format(mode))
wrappers.expect_exact(context, "Expanded display is", timeout=2)
wrappers.wait_prompt(context)
@then("we see {which} data selected")
def step_see_data(context, which):
"""Select data from expanded test table."""
if which == "expanded":
wrappers.expect_pager(
context,
dedent(
"""\
-[ RECORD 1 ]-------------------------\r
x | 1\r
y | 1.0\r
z | 1.0000\r
SELECT 1\r
"""
),
timeout=1,
)
else:
wrappers.expect_pager(
context,
dedent(
"""\
+-----+-----+--------+\r
| x | y | z |\r
|-----+-----+--------|\r
| 1 | 1.0 | 1.0000 |\r
+-----+-----+--------+\r
SELECT 1\r
"""
),
timeout=1,
)

View file

@ -0,0 +1,80 @@
import os
import os.path
from behave import when, then
import wrappers
@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, ":", timeout=2)
@when("we type sql in the editor")
def step_edit_type_sql(context):
context.cli.sendline("i")
context.cli.sendline("select * from abc")
context.cli.sendline(".")
wrappers.expect_exact(context, ":", 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 the sql in prompt")
def step_edit_done_sql(context):
for match in "select * from abc".split(" "):
wrappers.expect_exact(context, match, timeout=1)
# 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)
context.atprompt = True
@when("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("\o {0}".format(os.path.basename(context.tee_file_name)))
wrappers.expect_exact(context, context.conf["pager_boundary"] + "\r\n", timeout=5)
wrappers.expect_exact(context, "Writing to file", timeout=5)
wrappers.expect_exact(context, context.conf["pager_boundary"] + "\r\n", timeout=5)
wrappers.expect_exact(context, "Time", timeout=5)
@when('we query "select 123456"')
def step_query_select_123456(context):
context.cli.sendline("select 123456")
@when("we stop teeing output")
def step_notee_output(context):
context.cli.sendline("\o")
wrappers.expect_exact(context, "Time", timeout=5)
@then("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)
context.atprompt = True

View file

@ -0,0 +1,57 @@
"""
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, then
import wrappers
@when("we save a named query")
def step_save_named_query(context):
"""
Send \ns command
"""
context.cli.sendline("\\ns foo SELECT 12345")
@when("we use a named query")
def step_use_named_query(context):
"""
Send \n command
"""
context.cli.sendline("\\n foo")
@when("we delete a named query")
def step_delete_named_query(context):
"""
Send \nd command
"""
context.cli.sendline("\\nd 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, "12345", timeout=1)
wrappers.expect_exact(context, "SELECT 1", timeout=1)
@then("we see the named query deleted")
def step_see_named_query_deleted(context):
"""
Wait to see query deleted.
"""
wrappers.expect_pager(context, "foo: Deleted\r\n", timeout=1)

View file

@ -0,0 +1,26 @@
"""
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, then
import wrappers
@when("we refresh completions")
def step_refresh_completions(context):
"""
Send refresh command.
"""
context.cli.sendline("\\refresh")
@then("we see completions refresh started")
def step_see_refresh_started(context):
"""
Wait to see refresh output.
"""
wrappers.expect_pager(
context, "Auto-completion refresh started in the background.\r\n", timeout=2
)

View file

@ -0,0 +1,67 @@
import re
import pexpect
from pgcli.main import COLOR_CODE_REGEX
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.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, prompt_check=True, currentdb=None):
"""Run the process using pexpect."""
run_args = run_args or []
cli_cmd = context.conf.get("cli_command")
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 = currentdb or context.conf["dbname"]
context.cli.sendline("\pset pager always")
if prompt_check:
wait_prompt(context)
def wait_prompt(context):
"""Make sure prompt is displayed."""
expect_exact(context, "{0}> ".format(context.conf["dbname"]), timeout=5)

16
tests/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])