From 5b8afaafd698a922a94e2b997a839d14afe9d58f Mon Sep 17 00:00:00 2001 From: Iryna Cherniavska Date: Sun, 21 Jun 2015 15:00:18 -0700 Subject: Started working on integration tests. --- behave.ini | 5 +++ features/db_utils.py | 81 ++++++++++++++++++++++++++++++++++++ features/environment.py | 71 +++++++++++++++++++++++++++++++ features/simple_cli_commands.feature | 15 +++++++ features/steps/step_definitions.py | 34 +++++++++++++++ requirements-dev.txt | 5 +++ 6 files changed, 211 insertions(+) create mode 100644 behave.ini create mode 100644 features/db_utils.py create mode 100644 features/environment.py create mode 100644 features/simple_cli_commands.feature create mode 100644 features/steps/step_definitions.py create mode 100644 requirements-dev.txt diff --git a/behave.ini b/behave.ini new file mode 100644 index 00000000..e7b7b725 --- /dev/null +++ b/behave.ini @@ -0,0 +1,5 @@ +[behave.userdata] +pg_test_user = behave_user +pg_test_pass = +pg_test_host = localhost +pg_test_db = pgcli_behave_tests diff --git a/features/db_utils.py b/features/db_utils.py new file mode 100644 index 00000000..83bbbfb2 --- /dev/null +++ b/features/db_utils.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +from __future__ import print_function + +from psycopg2 import connect +from psycopg2.extensions import AsIs + + +def create_db(hostname='localhost', username=None, password=None, + dbname=None): + """ + Create test database. + :param hostname: string + :param username: string + :param password: string + :param dbname: string + :return: + """ + cn = create_cn(hostname, password, username, 'postgres') + + # ISOLATION_LEVEL_AUTOCOMMIT = 0 + # Needed for DB creation. + cn.set_isolation_level(0) + + with cn.cursor() as cr: + cr.execute('create database %s', (AsIs(dbname),)) + + cn.close() + + cn = create_cn(hostname, password, username, dbname) + return cn + + +def create_cn(hostname, password, username, dbname): + """ + Open connection to database. + :param hostname: + :param password: + :param username: + :param dbname: string + :return: psycopg2.connection + """ + if password: + cn = connect(host=hostname, user=username, database=dbname, + password=password) + else: + cn = connect(host=hostname, user=username, database=dbname) + + print('Created connection: {0}.'.format(cn.dsn)) + return cn + + +def drop_db(hostname='localhost', username=None, password=None, + dbname=None): + """ + Drop database. + :param hostname: string + :param username: string + :param password: string + :param dbname: string + """ + cn = create_cn(hostname, password, username, 'postgres') + + # ISOLATION_LEVEL_AUTOCOMMIT = 0 + # Needed for DB drop. + cn.set_isolation_level(0) + + with cn.cursor() as cr: + cr.execute('drop database %s', (AsIs(dbname),)) + + cn.close() + + +def close_cn(cn=None): + """ + Close connection. + :param connection: psycopg2.connection + """ + if cn: + cn.close() + print('Closed connection: {0}.'.format(cn.dsn)) diff --git a/features/environment.py b/features/environment.py new file mode 100644 index 00000000..62711263 --- /dev/null +++ b/features/environment.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +from __future__ import print_function + +import os +import pexpect +import db_utils as dbutils + + +def before_all(context): + """ + Set env parameters. + """ + os.environ['LINES'] = "50" + os.environ['COLUMNS'] = "120" + context.exit_sent = False + + # Store get params from config. + context.conf = { + 'host': context.config.userdata.get('pg_test_host', 'localhost'), + 'user': context.config.userdata.get('pg_test_user', 'root'), + 'pass': context.config.userdata.get('pg_test_pass', None), + 'dbname': context.config.userdata.get('pg_test_db', None), + } + + # Store old env vars. + context.pgenv = { + 'PGDATABASE': os.environ.get('PGDATABASE', None), + 'PGUSER': os.environ.get('PGUSER', None), + 'PGHOST': os.environ.get('PGHOST', None), + 'PGPASS': os.environ.get('PGPASS', None), + } + + # Set new env vars. + os.environ['PGDATABASE'] = context.conf['dbname'] + os.environ['PGUSER'] = context.conf['user'] + os.environ['PGHOST'] = context.conf['host'] + if context.conf['pass']: + os.environ['PGPASS'] = context.conf['pass'] + elif 'PGPASS' in os.environ: + del os.environ['PGPASS'] + + context.cn = dbutils.create_db(context.conf['host'], context.conf['user'], + context.conf['pass'], context.conf['dbname']) + + +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']) + + # 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 after_scenario(context, _): + """ + Cleans up after each test complete. + """ + + if hasattr(context, 'cli') and not context.exit_sent: + # Send Ctrl + D into cli + context.cli.sendcontrol('d') + context.cli.expect(pexpect.EOF) diff --git a/features/simple_cli_commands.feature b/features/simple_cli_commands.feature new file mode 100644 index 00000000..c60f14b0 --- /dev/null +++ b/features/simple_cli_commands.feature @@ -0,0 +1,15 @@ +Feature: run the cli, + call the help command, + exit the cli + + Scenario: run the cli + Given we have pgcli installed + when we run pgcli + then we see pgcli prompt + + Scenario: run the cli and exit + Given we have pgcli installed + when we run pgcli + and we wait for prompt + and we send "ctrl + d" + then pgcli exits diff --git a/features/steps/step_definitions.py b/features/steps/step_definitions.py new file mode 100644 index 00000000..e8ecb4ae --- /dev/null +++ b/features/steps/step_definitions.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import pip +import pexpect + +from behave import given, when, then + +@given('we have pgcli installed') +def step_impl(context): + ds = set([di.key for di in pip.get_installed_distributions()]) + assert 'pgcli' in ds + +@when('we run pgcli') +def step_impl(context): + context.cli = pexpect.spawnu('pgcli') + + +@when('we wait for prompt') +def step_impl(context): + context.cli.expect('{0}> '.format(context.conf['dbname'])) + +@when('we send "ctrl + d"') +def step_impl(context): + context.cli.sendcontrol('d') + context.exit_sent = True + +@then('pgcli exits') +def step_impl(context): + context.cli.expect(pexpect.EOF) + +@then('we see pgcli prompt') +def step_impl(context): + context.cli.expect('{0}> '.format(context.conf['dbname'])) diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 00000000..d6e3e92e --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,5 @@ +pytest>=2.7.0 +mock>=1.0.1 +tox>=1.9.2 +behave>=1.2.4 +pexpect>=3.3 -- cgit v1.2.3 From c31269cc788e09ca292975e4cb22618889af46d8 Mon Sep 17 00:00:00 2001 From: Iryna Cherniavska Date: Mon, 22 Jun 2015 09:50:24 -0700 Subject: Changes to make linter happy. --- features/db_utils.py | 2 +- features/environment.py | 3 ++- features/steps/step_definitions.py | 44 +++++++++++++++++++++++++++++++------- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/features/db_utils.py b/features/db_utils.py index 83bbbfb2..61222560 100644 --- a/features/db_utils.py +++ b/features/db_utils.py @@ -51,7 +51,7 @@ def create_cn(hostname, password, username, dbname): def drop_db(hostname='localhost', username=None, password=None, - dbname=None): + dbname=None): """ Drop database. :param hostname: string diff --git a/features/environment.py b/features/environment.py index 62711263..d7ee5613 100644 --- a/features/environment.py +++ b/features/environment.py @@ -41,7 +41,8 @@ def before_all(context): del os.environ['PGPASS'] context.cn = dbutils.create_db(context.conf['host'], context.conf['user'], - context.conf['pass'], context.conf['dbname']) + context.conf['pass'], + context.conf['dbname']) def after_all(context): diff --git a/features/steps/step_definitions.py b/features/steps/step_definitions.py index e8ecb4ae..28f4eff5 100644 --- a/features/steps/step_definitions.py +++ b/features/steps/step_definitions.py @@ -1,4 +1,9 @@ # -*- coding: utf-8 -*- +""" +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 __future__ import unicode_literals import pip @@ -6,29 +11,52 @@ import pexpect from behave import given, when, then + @given('we have pgcli installed') -def step_impl(context): - ds = set([di.key for di in pip.get_installed_distributions()]) - assert 'pgcli' in ds +def step_install_cli(_): + """ + Check that pgcli is in installed modules. + """ + dists = set([di.key for di in pip.get_installed_distributions()]) + assert 'pgcli' in dists + @when('we run pgcli') -def step_impl(context): +def step_run_cli(context): + """ + Run the process using pexpect. + """ context.cli = pexpect.spawnu('pgcli') @when('we wait for prompt') -def step_impl(context): +def step_wait_prompt(context): + """ + Make sure prompt is displayed. + """ context.cli.expect('{0}> '.format(context.conf['dbname'])) + @when('we send "ctrl + d"') -def step_impl(context): +def step_ctrl_d(context): + """ + Send Ctrl + D to hopefully exit. + """ context.cli.sendcontrol('d') context.exit_sent = True + @then('pgcli exits') -def step_impl(context): +def step_wait_exit(context): + """ + Make sure the cli exits. + """ context.cli.expect(pexpect.EOF) + @then('we see pgcli prompt') -def step_impl(context): +def step_see_prompt(context): + """ + Wait to see the prompt. + """ context.cli.expect('{0}> '.format(context.conf['dbname'])) -- cgit v1.2.3 From 52c98d4d2db71504aa9aa65c3e2d77170fe46a57 Mon Sep 17 00:00:00 2001 From: Iryna Cherniavska Date: Mon, 22 Jun 2015 12:26:24 -0700 Subject: Added test for '\?'. --- features/db_utils.py | 2 +- features/environment.py | 9 +++++++-- features/fixture_data/help.txt | 24 ++++++++++++++++++++++++ features/fixture_utils.py | 34 ++++++++++++++++++++++++++++++++++ features/simple_cli_commands.feature | 7 +++++++ features/steps/step_definitions.py | 14 ++++++++++++++ 6 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 features/fixture_data/help.txt create mode 100644 features/fixture_utils.py diff --git a/features/db_utils.py b/features/db_utils.py index 61222560..e059c762 100644 --- a/features/db_utils.py +++ b/features/db_utils.py @@ -68,7 +68,7 @@ def drop_db(hostname='localhost', username=None, password=None, with cn.cursor() as cr: cr.execute('drop database %s', (AsIs(dbname),)) - cn.close() + close_cn(cn) def close_cn(cn=None): diff --git a/features/environment.py b/features/environment.py index d7ee5613..64ca66de 100644 --- a/features/environment.py +++ b/features/environment.py @@ -5,14 +5,17 @@ from __future__ import print_function import os import pexpect import db_utils as dbutils +import fixture_utils as fixutils def before_all(context): """ Set env parameters. """ - os.environ['LINES'] = "50" - os.environ['COLUMNS'] = "120" + os.environ['LINES'] = "100" + os.environ['COLUMNS'] = "100" + os.environ['PAGER'] = 'cat' + context.exit_sent = False # Store get params from config. @@ -44,6 +47,8 @@ def before_all(context): context.conf['pass'], context.conf['dbname']) + context.fixture_data = fixutils.read_fixture_files() + def after_all(context): """ diff --git a/features/fixture_data/help.txt b/features/fixture_data/help.txt new file mode 100644 index 00000000..8a495041 --- /dev/null +++ b/features/fixture_data/help.txt @@ -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. | ++--------------------------+-----------------------------------------------+ diff --git a/features/fixture_utils.py b/features/fixture_utils.py new file mode 100644 index 00000000..af768977 --- /dev/null +++ b/features/fixture_utils.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +from __future__ import print_function + +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, 'r', encoding='utf-8'): + 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 diff --git a/features/simple_cli_commands.feature b/features/simple_cli_commands.feature index c60f14b0..1121651c 100644 --- a/features/simple_cli_commands.feature +++ b/features/simple_cli_commands.feature @@ -7,6 +7,13 @@ Feature: run the cli, when we run pgcli then we see pgcli prompt + Scenario: run "\?" command + Given we have pgcli installed + when we run pgcli + and we wait for prompt + and we send "\?" command + then we see help output + Scenario: run the cli and exit Given we have pgcli installed when we run pgcli diff --git a/features/steps/step_definitions.py b/features/steps/step_definitions.py index 28f4eff5..3d310be4 100644 --- a/features/steps/step_definitions.py +++ b/features/steps/step_definitions.py @@ -46,6 +46,14 @@ def step_ctrl_d(context): context.exit_sent = True +@when('we send "\?" command') +def step_send_help(context): + """ + Send \? to see help. + """ + context.cli.sendline('\?') + + @then('pgcli exits') def step_wait_exit(context): """ @@ -60,3 +68,9 @@ def step_see_prompt(context): Wait to see the prompt. """ context.cli.expect('{0}> '.format(context.conf['dbname'])) + + +@then('we see help output') +def step_see_help(context): + for expected_line in context.fixture_data['help.txt']: + context.cli.expect_exact(expected_line) -- cgit v1.2.3 From daeff7798a6155150a91f74a6c1dd65785a8f32a Mon Sep 17 00:00:00 2001 From: Iryna Cherniavska Date: Thu, 2 Jul 2015 16:12:19 -0700 Subject: Added readme instructions and changed test user to postgres. --- DEVELOP.rst | 30 ++++++++++++++++++++++++++++++ behave.ini | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/DEVELOP.rst b/DEVELOP.rst index 2daf4cb5..ed50f8cc 100644 --- a/DEVELOP.rst +++ b/DEVELOP.rst @@ -122,3 +122,33 @@ The rpm package can be installed as follows: :: $ sudo yum install pgcli*.rpm + +Running the integration tests +----------------------------- + +Integration tests use `behave package http://pythonhosted.org/behave/`_. +Configuration settings for this package are provided via ``behave.ini`` file +in root directory. + +The database user ``pg_test_user`` has to have permissions to create and drop +test database. Dafault user is ``postgres`` at ``localhost``, without the +password (authentication mode ****). + +First, install the requirements for testing: + +:: + + $ pip install -r requirements-dev.txt + +After that, tests can be run with: + +:: + + $ behave + +To see stdout/stderr, use the following command: + +:: + + $ behave --no-capture + diff --git a/behave.ini b/behave.ini index e7b7b725..c555d3c0 100644 --- a/behave.ini +++ b/behave.ini @@ -1,5 +1,5 @@ [behave.userdata] -pg_test_user = behave_user +pg_test_user = postgres pg_test_pass = pg_test_host = localhost pg_test_db = pgcli_behave_tests -- cgit v1.2.3 From de116beec973c3dbf8b5fd81d9cadf35a1fb20d9 Mon Sep 17 00:00:00 2001 From: Iryna Cherniavska Date: Fri, 3 Jul 2015 12:27:55 -0700 Subject: Changed the test to only expect text content of help output, without table formatting. --- features/fixture_data/help_commands.txt | 21 +++++++++++++++++++++ features/steps/step_definitions.py | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 features/fixture_data/help_commands.txt diff --git a/features/fixture_data/help_commands.txt b/features/fixture_data/help_commands.txt new file mode 100644 index 00000000..fcee0775 --- /dev/null +++ b/features/fixture_data/help_commands.txt @@ -0,0 +1,21 @@ +Command +\# +\? +\c[onnect] database_name +\d [pattern] +\dT[S+] [pattern] +\df[+] [pattern] +\di[+] [pattern] +\dn[+] [pattern] +\ds[+] [pattern] +\dt[+] [pattern] +\du[+] [pattern] +\dv[+] [pattern] +\e [file] +\l +\n[+] [name] +\nd [name [query]] +\ns [name [query]] +\refresh +\timing +\x \ No newline at end of file diff --git a/features/steps/step_definitions.py b/features/steps/step_definitions.py index 3d310be4..454d8cc3 100644 --- a/features/steps/step_definitions.py +++ b/features/steps/step_definitions.py @@ -72,5 +72,5 @@ def step_see_prompt(context): @then('we see help output') def step_see_help(context): - for expected_line in context.fixture_data['help.txt']: + for expected_line in context.fixture_data['help_commands.txt']: context.cli.expect_exact(expected_line) -- cgit v1.2.3 From 09cae83eb8c4acf3a787b26f354d627440fc3a40 Mon Sep 17 00:00:00 2001 From: Iryna Cherniavska Date: Sat, 4 Jul 2015 10:02:36 -0700 Subject: Updated readme about test user. --- DEVELOP.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DEVELOP.rst b/DEVELOP.rst index ed50f8cc..39e932f0 100644 --- a/DEVELOP.rst +++ b/DEVELOP.rst @@ -130,9 +130,9 @@ Integration tests use `behave package http://pythonhosted.org/behave/`_. Configuration settings for this package are provided via ``behave.ini`` file in root directory. -The database user ``pg_test_user`` has to have permissions to create and drop -test database. Dafault user is ``postgres`` at ``localhost``, without the -password (authentication mode ****). +The database user (``pg_test_user = postgres`` in .ini file) has to have +permissions to create and drop test database. Dafault user is ``postgres`` +at ``localhost``, without the password (authentication mode trust). First, install the requirements for testing: -- cgit v1.2.3