Skip to content

Commit 341fead

Browse files
author
Elliot Boschwitz
authored
Added tests for interactive mode (#397)
* restructured query tests * added tests for interactive mode * commented out unused code
1 parent f641771 commit 341fead

File tree

5 files changed

+151
-87
lines changed

5 files changed

+151
-87
lines changed

build.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,8 @@ def unit_test():
152152
utility.exec_command(('pytest -l --cov mssqlcli --doctest-modules '
153153
'--junitxml=junit/test-{}-results.xml --cov-report=xml '
154154
'--cov-report=html --cov-append -o junit_suite_name=pytest-{} '
155-
'-s -m "not unstable" {}').format(runid, python_version,
156-
get_active_test_filepaths()),
155+
'-m "not unstable" {}').format(runid, python_version,
156+
get_active_test_filepaths()),
157157
utility.ROOT_DIR, continue_on_error=False)
158158

159159
def unstable_unit_test():
@@ -181,6 +181,7 @@ def get_active_test_filepaths():
181181
'tests/test_telemetry.py '
182182
'tests/test_localization.py '
183183
'tests/test_globalization.py '
184+
'tests/test_interactive_mode.py '
184185
'tests/test_noninteractive_mode.py '
185186
'tests/test_special.py'
186187
)

mssqlcli/mssql_cli.py

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -192,37 +192,36 @@ def __init__(self, options):
192192

193193
# exit and return error if user enters interactive mode with -i or -o arguments enabled
194194
if self.interactive_mode and (self.input_file or self.output_file):
195-
click.secho("Invalid arguments: -i and -o can only be used in non-interactive mode.",
196-
err=True, fg='red')
197-
sys.exit(1)
195+
raise ValueError("Invalid arguments: -i and -o can only be used in non-interactive "
196+
"mode.")
198197

199198
# exit and return error if both query text and an input file are specified
200199
if self.query and self.input_file:
201-
click.secho("Invalid arguments: either query [-Q] or input file [-i] may be specified.",
202-
err=True, fg='red')
203-
sys.exit(1)
200+
raise ValueError("Invalid arguments: either query [-Q] or input file [-i] may be "
201+
"specified.")
204202

205203
def __del__(self):
206204
# Shut-down sqltoolsservice
207205
if self.sqltoolsclient:
208206
self.sqltoolsclient.shutdown()
209207

210-
def write_to_file(self, pattern, **_):
211-
if not pattern:
212-
self.output_file = None
213-
message = 'File output disabled'
214-
return [(None, None, None, message, '', True)]
215-
filename = os.path.abspath(os.path.expanduser(pattern))
216-
if not os.path.isfile(filename):
217-
try:
218-
open(filename, 'w').close()
219-
except IOError as e:
220-
self.output_file = None
221-
message = str(e) + '\nFile output disabled'
222-
return [(None, None, None, message, '', False)]
223-
self.output_file = filename
224-
message = 'Writing to file "%s"' % self.output_file
225-
return [(None, None, None, message, '', True)]
208+
# TODO: possibly use at a later date for expanded output file functionality
209+
# def write_to_file(self, pattern, **_):
210+
# if not pattern:
211+
# self.output_file = None
212+
# message = 'File output disabled'
213+
# return [(None, None, None, message, '', True)]
214+
# filename = os.path.abspath(os.path.expanduser(pattern))
215+
# if not os.path.isfile(filename):
216+
# try:
217+
# open(filename, 'w').close()
218+
# except IOError as e:
219+
# self.output_file = None
220+
# message = str(e) + '\nFile output disabled'
221+
# return [(None, None, None, message, '', False)]
222+
# self.output_file = filename
223+
# message = 'Writing to file "%s"' % self.output_file
224+
# return [(None, None, None, message, '', True)]
226225

227226
def initialize_logging(self):
228227
log_file = self.config['main']['log_file']
@@ -390,15 +389,13 @@ def run(self):
390389

391390
# raise error if interactive mode is set to false here
392391
if not self.interactive_mode:
393-
raise ValueError("'run' must be used in interactive mode! Please set \
394-
interactive_mode to True.")
392+
raise ValueError("Invalid arguments: 'run' must be used in interactive mode! Please set "
393+
"interactive_mode to True.")
395394

396395
# exit and return error if user enters interactive mode with -o argument enabled
397396
if self.output_file:
398-
click.secho("Invalid arguments: -o must be used with interactive mode set to false.",
399-
err=True, fg='red')
400-
self.shutdown()
401-
sys.exit(1)
397+
raise ValueError("Invalid arguments: -o must be used with interactive mode set to "
398+
"false.")
402399

403400
history_file = self.config['main']['history_file']
404401
if history_file == 'default':

tests/mssqltestutils.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,22 @@
99
from utility import random_str
1010

1111

12+
_BASELINE_DIR = os.path.dirname(os.path.abspath(__file__))
13+
14+
# test queries mapped to files
15+
test_queries = [
16+
("SELECT 1", 'small.txt'),
17+
("SELECT 1; SELECT 2;", 'multiple.txt'),
18+
("SELECT %s" % ('x' * 250), 'col_too_wide.txt'),
19+
("SELECT REPLICATE(CAST('X,' AS VARCHAR(MAX)), 1024)", 'col_wide.txt')
20+
]
21+
1222
def create_mssql_cli(**non_default_options):
1323
mssqlcli_options = create_mssql_cli_options(**non_default_options)
1424
mssql_cli = MssqlCli(mssqlcli_options)
1525

1626
return mssql_cli
1727

18-
1928
def create_mssql_cli_client(options=None, owner_uri=None, connect=True, sql_tools_client=None,
2029
**additional_params):
2130
"""
@@ -43,7 +52,6 @@ def create_mssql_cli_client(options=None, owner_uri=None, connect=True, sql_tool
4352
print('Connection failed')
4453
raise e
4554

46-
4755
def create_mssql_cli_options(**nondefault_options):
4856

4957
parser = create_parser()
@@ -62,7 +70,6 @@ def create_mssql_cli_options(**nondefault_options):
6270

6371
return default_mssql_cli_options
6472

65-
6673
def shutdown(connection):
6774
connection.shutdown()
6875

@@ -97,7 +104,6 @@ def create_test_db():
97104
clean_up_test_db(test_db_name)
98105
raise AssertionError("DB creation failed.")
99106

100-
101107
def clean_up_test_db(test_db_name):
102108
client = create_mssql_cli_client()
103109
query = u"DROP DATABASE {0};".format(test_db_name)
@@ -107,3 +113,18 @@ def clean_up_test_db(test_db_name):
107113
success = False
108114
shutdown(client)
109115
return success
116+
117+
def get_file_contents(file_path):
118+
""" Get expected result from file. """
119+
try:
120+
with open(file_path, 'r') as f:
121+
# remove string literals (needed in python2) and newlines
122+
return f.read().replace('\r', '').strip()
123+
except OSError as e:
124+
raise e
125+
126+
def get_io_paths(test_file_suffix):
127+
""" Returns tuple of file paths for the input and output of a test. """
128+
i = os.path.join(_BASELINE_DIR, 'test_query_inputs', 'input_%s' % test_file_suffix)
129+
o = os.path.join(_BASELINE_DIR, 'test_query_baseline', 'baseline_%s' % test_file_suffix)
130+
return (i, o)

tests/test_interactive_mode.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import pytest
2+
from mssqltestutils import (
3+
create_mssql_cli,
4+
shutdown,
5+
test_queries,
6+
get_file_contents,
7+
get_io_paths
8+
)
9+
10+
class TestInteractiveModeQueries:
11+
@staticmethod
12+
@pytest.fixture(scope='class')
13+
def mssqlcli():
14+
"""
15+
Pytest fixture which returns interactive mssql-cli instance
16+
and cleans up on teardown.
17+
"""
18+
mssqlcli = create_mssql_cli(interactive_mode=True)
19+
yield mssqlcli
20+
shutdown(mssqlcli)
21+
22+
@staticmethod
23+
@pytest.mark.parametrize("query_str, test_file", test_queries)
24+
@pytest.mark.timeout(60)
25+
def test_query(query_str, test_file, mssqlcli):
26+
_, file_baseline = get_io_paths(test_file)
27+
output_baseline = get_file_contents(file_baseline)
28+
output_query = '\n'.join(mssqlcli.execute_query(query_str)).replace('\r', '')
29+
assert output_query == output_baseline
30+
31+
class TestInteractiveModeInvalidRuns:
32+
@pytest.mark.timeout(60)
33+
def test_noninteractive_run(self):
34+
'''
35+
Test that calling run throws an exception only when interactive_mode is false
36+
'''
37+
self.invalid_run(interactive_mode=False)
38+
39+
def test_interactive_output(self):
40+
'''
41+
Test run with interactive mode enabled with output file, which should return ValueError
42+
'''
43+
self.invalid_run(output_file='will-fail.txt')
44+
45+
@staticmethod
46+
def test_output_with_interactive_change():
47+
'''
48+
Fails on run after interactive mode has been toggled
49+
'''
50+
mssqlcli = create_mssql_cli(interactive_mode=False, output_file='will-fail-eventually.txt')
51+
mssqlcli.interactive_mode = True
52+
try:
53+
mssqlcli.run()
54+
assert False
55+
except ValueError:
56+
assert True
57+
finally:
58+
shutdown(mssqlcli)
59+
60+
@staticmethod
61+
@pytest.mark.timeout(60)
62+
def invalid_run(**options):
63+
'''
64+
Tests mssql-cli runs with invalid combination of properities set
65+
'''
66+
mssqlcli = None
67+
try:
68+
mssqlcli = create_mssql_cli(**options)
69+
mssqlcli.run()
70+
assert False
71+
except ValueError:
72+
assert True
73+
finally:
74+
if mssqlcli is not None:
75+
shutdown(mssqlcli)

tests/test_noninteractive_mode.py

Lines changed: 23 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,14 @@
66
import pytest
77
from mssqltestutils import (
88
create_mssql_cli,
9-
random_str
9+
random_str,
10+
shutdown,
11+
test_queries,
12+
get_file_contents,
13+
get_io_paths,
14+
_BASELINE_DIR
1015
)
1116

12-
_BASELINE_DIR = os.path.dirname(os.path.abspath(__file__))
1317

1418
class TestNonInteractiveResults:
1519
"""
@@ -24,20 +28,14 @@ def tmp_filepath():
2428
yield fp
2529
os.remove(fp)
2630

27-
testdata = [
28-
("-Q \"SELECT 1\"", 'small.txt'),
29-
("-Q \"SELECT 1; SELECT 2;\"", 'multiple.txt'),
30-
("-Q \"SELECT %s\"" % ('x' * 250), 'col_too_wide.txt'),
31-
("-Q \"SELECT REPLICATE(CAST('X,' AS VARCHAR(MAX)), 1024)\"", 'col_wide.txt')
32-
]
33-
34-
@pytest.mark.parametrize("query_str, test_file", testdata)
31+
@pytest.mark.parametrize("query_str, test_file", test_queries)
3532
@pytest.mark.timeout(60)
3633
def test_query(self, query_str, test_file):
3734
""" Tests query outputs to command-line, ensuring -Q and -i produce
3835
the same results. """
39-
file_input, file_baseline = self.input_output_paths(test_file)
40-
output_baseline = self.get_file_contents(file_baseline)
36+
file_input, file_baseline = get_io_paths(test_file)
37+
output_baseline = get_file_contents(file_baseline)
38+
query_str = '-Q "{}"'.format(query_str) # append -Q for non-interactive call
4139

4240
# test with -Q
4341
output_query_for_Q = self.execute_query_via_subprocess(query_str)
@@ -47,12 +45,13 @@ def test_query(self, query_str, test_file):
4745
output_query_for_i = self.execute_query_via_subprocess("-i %s" % file_input)
4846
assert output_query_for_i == output_baseline
4947

50-
@pytest.mark.parametrize("query_str, test_file", testdata)
48+
@pytest.mark.parametrize("query_str, test_file", test_queries)
5149
@pytest.mark.timeout(60)
5250
def test_output_file(self, query_str, test_file, tmp_filepath):
5351
""" Tests -o (and ensures file overwrite works) """
54-
file_input, file_baseline = self.input_output_paths(test_file)
55-
output_baseline = self.get_file_contents(file_baseline)
52+
file_input, file_baseline = get_io_paths(test_file)
53+
output_baseline = get_file_contents(file_baseline)
54+
query_str = '-Q "{}"'.format(query_str) # append -Q for non-interactive call
5655

5756
# test with -Q
5857
output_query_for_Q = self.execute_query_via_subprocess(query_str, output_file=tmp_filepath)
@@ -63,35 +62,23 @@ def test_output_file(self, query_str, test_file, tmp_filepath):
6362
output_file=tmp_filepath)
6463
assert output_query_for_i == output_baseline
6564

65+
@staticmethod
6666
@pytest.mark.timeout(60)
67-
def test_long_query(self, tmp_filepath):
67+
def test_long_query(tmp_filepath):
6868
""" Output large query using Python class instance. """
6969
query_str = "SELECT * FROM STRING_SPLIT(REPLICATE(CAST('X,' AS VARCHAR(MAX)), 1024), ',')"
7070
try:
7171
mssqlcli = create_mssql_cli(interactive_mode=False, output_file=tmp_filepath)
7272
output_query = '\n'.join(mssqlcli.execute_query(query_str))
73-
file_baseline = self.input_output_paths('big.txt')[1]
74-
output_baseline = self.get_file_contents(file_baseline)
73+
file_baseline = get_io_paths('big.txt')[1]
74+
output_baseline = get_file_contents(file_baseline)
7575
assert output_query == output_baseline
7676

7777
# test output to file
78-
output_query_from_file = self.get_file_contents(tmp_filepath)
78+
output_query_from_file = get_file_contents(tmp_filepath)
7979
assert output_query_from_file == output_baseline
8080
finally:
81-
mssqlcli.shutdown()
82-
83-
@staticmethod
84-
@pytest.mark.timeout(60)
85-
def test_noninteractive_run():
86-
""" Test that calling run throws an exception only when interactive_mode is false """
87-
mssqlcli = create_mssql_cli(interactive_mode=False)
88-
try:
89-
mssqlcli.run()
90-
assert False
91-
except ValueError:
92-
assert True
93-
finally:
94-
mssqlcli.shutdown()
81+
shutdown(mssqlcli)
9582

9683
@classmethod
9784
@pytest.mark.timeout(60)
@@ -100,13 +87,6 @@ def test_Q_with_i_run(cls):
10087
output = cls.execute_query_via_subprocess("-Q 'select 1' -i 'this_breaks.txt'")
10188
assert output == "Invalid arguments: either -Q or -i may be specified."
10289

103-
@staticmethod
104-
def input_output_paths(test_file_suffix):
105-
""" Returns tuple of file paths for the input an output of a test. """
106-
i = os.path.join(_BASELINE_DIR, 'test_query_inputs', 'input_%s' % test_file_suffix)
107-
o = os.path.join(_BASELINE_DIR, 'test_query_baseline', 'baseline_%s' % test_file_suffix)
108-
return (i, o)
109-
11090
@classmethod
11191
def execute_query_via_subprocess(cls, query_str, output_file=None):
11292
""" Helper method for running a query. """
@@ -122,19 +102,9 @@ def execute_query_via_subprocess(cls, query_str, output_file=None):
122102

123103
if output_file:
124104
# get file contents if we used -o
125-
return cls.get_file_contents(output_file)
105+
return get_file_contents(output_file)
126106
return output.decode("utf-8").replace('\r', '').strip()
127107

128-
@staticmethod
129-
def get_file_contents(file_path):
130-
""" Get expected result from file. """
131-
try:
132-
with open(file_path, 'r') as f:
133-
# remove string literals (needed in python2) and newlines
134-
return f.read().replace('\r', '').strip()
135-
except OSError as e:
136-
raise e
137-
138108

139109
class TestNonInteractiveShutdownQuery:
140110
"""
@@ -160,7 +130,7 @@ def test_shutdown_after_query(query_str, mssqlcli):
160130
try:
161131
mssqlcli.execute_query(query_str)
162132
finally:
163-
mssqlcli.shutdown()
133+
shutdown(mssqlcli)
164134
assert mssqlcli.mssqlcliclient_main.sql_tools_client.\
165135
tools_service_process.poll() is not None
166136

@@ -192,6 +162,6 @@ def test_shutdown_after_query(query_str, mssqlcli):
192162
try:
193163
mssqlcli.execute_query(query_str)
194164
finally:
195-
mssqlcli.shutdown()
165+
shutdown(mssqlcli)
196166
assert mssqlcli.mssqlcliclient_main.sql_tools_client.\
197167
tools_service_process.poll() is not None

0 commit comments

Comments
 (0)