1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334
|
# coding=utf-8
# flake8: noqa E302
"""
Cmd2 functional testing based on transcript
"""
import os
import random
import re
import sys
import tempfile
from unittest import (
mock,
)
import pytest
import cmd2
from cmd2 import (
transcript,
)
from cmd2.utils import (
Settable,
StdSim,
)
from .conftest import (
run_cmd,
verify_help_text,
)
class CmdLineApp(cmd2.Cmd):
MUMBLES = ['like', '...', 'um', 'er', 'hmmm', 'ahh']
MUMBLE_FIRST = ['so', 'like', 'well']
MUMBLE_LAST = ['right?']
def __init__(self, *args, **kwargs):
self.maxrepeats = 3
super().__init__(*args, multiline_commands=['orate'], **kwargs)
# Make maxrepeats settable at runtime
self.add_settable(Settable('maxrepeats', int, 'Max number of `--repeat`s allowed', self))
self.intro = 'This is an intro banner ...'
speak_parser = cmd2.Cmd2ArgumentParser()
speak_parser.add_argument('-p', '--piglatin', action="store_true", help="atinLay")
speak_parser.add_argument('-s', '--shout', action="store_true", help="N00B EMULATION MODE")
speak_parser.add_argument('-r', '--repeat', type=int, help="output [n] times")
@cmd2.with_argparser(speak_parser, with_unknown_args=True)
def do_speak(self, opts, arg):
"""Repeats what you tell me to."""
arg = ' '.join(arg)
if opts.piglatin:
arg = '%s%say' % (arg[1:], arg[0])
if opts.shout:
arg = arg.upper()
repetitions = opts.repeat or 1
for _ in range(min(repetitions, self.maxrepeats)):
self.poutput(arg)
# recommend using the poutput function instead of
# self.stdout.write or "print", because Cmd allows the user
# to redirect output
do_say = do_speak # now "say" is a synonym for "speak"
do_orate = do_speak # another synonym, but this one takes multi-line input
mumble_parser = cmd2.Cmd2ArgumentParser()
mumble_parser.add_argument('-r', '--repeat', type=int, help="output [n] times")
@cmd2.with_argparser(mumble_parser, with_unknown_args=True)
def do_mumble(self, opts, arg):
"""Mumbles what you tell me to."""
repetitions = opts.repeat or 1
# arg = arg.split()
for _ in range(min(repetitions, self.maxrepeats)):
output = []
if random.random() < 0.33:
output.append(random.choice(self.MUMBLE_FIRST))
for word in arg:
if random.random() < 0.40:
output.append(random.choice(self.MUMBLES))
output.append(word)
if random.random() < 0.25:
output.append(random.choice(self.MUMBLE_LAST))
self.poutput(' '.join(output))
def do_nothing(self, statement):
"""Do nothing and output nothing"""
pass
def do_keyboard_interrupt(self, _):
raise KeyboardInterrupt('Interrupting this command')
def test_commands_at_invocation():
testargs = ["prog", "say hello", "say Gracie", "quit"]
expected = "This is an intro banner ...\nhello\nGracie\n"
with mock.patch.object(sys, 'argv', testargs):
app = CmdLineApp()
app.stdout = StdSim(app.stdout)
app.cmdloop()
out = app.stdout.getvalue()
assert out == expected
@pytest.mark.parametrize(
'filename,feedback_to_output',
[
('bol_eol.txt', False),
('characterclass.txt', False),
('dotstar.txt', False),
('extension_notation.txt', False),
('from_cmdloop.txt', True),
('multiline_no_regex.txt', False),
('multiline_regex.txt', False),
('no_output.txt', False),
('no_output_last.txt', False),
('regex_set.txt', False),
('singleslash.txt', False),
('slashes_escaped.txt', False),
('slashslash.txt', False),
('spaces.txt', False),
('word_boundaries.txt', False),
],
)
def test_transcript(request, capsys, filename, feedback_to_output):
# Get location of the transcript
test_dir = os.path.dirname(request.module.__file__)
transcript_file = os.path.join(test_dir, 'transcripts', filename)
# Need to patch sys.argv so cmd2 doesn't think it was called with
# arguments equal to the py.test args
testargs = ['prog', '-t', transcript_file]
with mock.patch.object(sys, 'argv', testargs):
# Create a cmd2.Cmd() instance and make sure basic settings are
# like we want for test
app = CmdLineApp()
app.feedback_to_output = feedback_to_output
# Run the command loop
sys_exit_code = app.cmdloop()
assert sys_exit_code == 0
# Check for the unittest "OK" condition for the 1 test which ran
expected_start = ".\n----------------------------------------------------------------------\nRan 1 test in"
expected_end = "s\n\nOK\n"
_, err = capsys.readouterr()
assert err.startswith(expected_start)
assert err.endswith(expected_end)
def test_history_transcript():
app = CmdLineApp()
app.stdout = StdSim(app.stdout)
run_cmd(app, 'orate this is\na /multiline/\ncommand;\n')
run_cmd(app, 'speak /tmp/file.txt is not a regex')
expected = r"""(Cmd) orate this is
> a /multiline/
> command;
this is a \/multiline\/ command
(Cmd) speak /tmp/file.txt is not a regex
\/tmp\/file.txt is not a regex
"""
# make a tmp file
fd, history_fname = tempfile.mkstemp(prefix='', suffix='.txt')
os.close(fd)
# tell the history command to create a transcript
run_cmd(app, 'history -t "{}"'.format(history_fname))
# read in the transcript created by the history command
with open(history_fname) as f:
xscript = f.read()
assert xscript == expected
def test_history_transcript_bad_path(mocker):
app = CmdLineApp()
app.stdout = StdSim(app.stdout)
run_cmd(app, 'orate this is\na /multiline/\ncommand;\n')
run_cmd(app, 'speak /tmp/file.txt is not a regex')
# Bad directory
history_fname = '~/fakedir/this_does_not_exist.txt'
out, err = run_cmd(app, 'history -t "{}"'.format(history_fname))
assert "is not a directory" in err[0]
# Cause os.open to fail and make sure error gets printed
mock_remove = mocker.patch('builtins.open')
mock_remove.side_effect = OSError
history_fname = 'outfile.txt'
out, err = run_cmd(app, 'history -t "{}"'.format(history_fname))
assert "Error saving transcript file" in err[0]
def test_run_script_record_transcript(base_app, request):
test_dir = os.path.dirname(request.module.__file__)
filename = os.path.join(test_dir, 'scripts', 'help.txt')
assert base_app._script_dir == []
assert base_app._current_script_dir is None
# make a tmp file to use as a transcript
fd, transcript_fname = tempfile.mkstemp(prefix='', suffix='.trn')
os.close(fd)
# Execute the run_script command with the -t option to generate a transcript
run_cmd(base_app, 'run_script {} -t {}'.format(filename, transcript_fname))
assert base_app._script_dir == []
assert base_app._current_script_dir is None
# read in the transcript created by the history command
with open(transcript_fname) as f:
xscript = f.read()
assert xscript.startswith('(Cmd) help -v\n')
verify_help_text(base_app, xscript)
def test_generate_transcript_stop(capsys):
# Verify transcript generation stops when a command returns True for stop
app = CmdLineApp()
# Make a tmp file to use as a transcript
fd, transcript_fname = tempfile.mkstemp(prefix='', suffix='.trn')
os.close(fd)
# This should run all commands
commands = ['help', 'set']
app._generate_transcript(commands, transcript_fname)
_, err = capsys.readouterr()
assert err.startswith("2 commands")
# Since quit returns True for stop, only the first 2 commands will run
commands = ['help', 'quit', 'set']
app._generate_transcript(commands, transcript_fname)
_, err = capsys.readouterr()
assert err.startswith("Command 2 triggered a stop")
# keyboard_interrupt command should stop the loop and not run the third command
commands = ['help', 'keyboard_interrupt', 'set']
app._generate_transcript(commands, transcript_fname)
_, err = capsys.readouterr()
assert err.startswith("Interrupting this command\nCommand 2 triggered a stop")
@pytest.mark.parametrize(
'expected, transformed',
[
# strings with zero or one slash or with escaped slashes means no regular
# expression present, so the result should just be what re.escape returns.
# we don't use static strings in these tests because re.escape behaves
# differently in python 3.7+ than in prior versions
('text with no slashes', re.escape('text with no slashes')),
('specials .*', re.escape('specials .*')),
('use 2/3 cup', re.escape('use 2/3 cup')),
('/tmp is nice', re.escape('/tmp is nice')),
('slash at end/', re.escape('slash at end/')),
# escaped slashes
(r'not this slash\/ or this one\/', re.escape('not this slash/ or this one/')),
# regexes
('/.*/', '.*'),
('specials ^ and + /[0-9]+/', re.escape('specials ^ and + ') + '[0-9]+'),
(r'/a{6}/ but not \/a{6} with /.*?/ more', 'a{6}' + re.escape(' but not /a{6} with ') + '.*?' + re.escape(' more')),
(r'not \/, use /\|?/, not \/', re.escape('not /, use ') + r'\|?' + re.escape(', not /')),
# inception: slashes in our regex. backslashed on input, bare on output
(r'not \/, use /\/?/, not \/', re.escape('not /, use ') + '/?' + re.escape(', not /')),
(r'lots /\/?/ more /.*/ stuff', re.escape('lots ') + '/?' + re.escape(' more ') + '.*' + re.escape(' stuff')),
],
)
def test_parse_transcript_expected(expected, transformed):
app = CmdLineApp()
class TestMyAppCase(transcript.Cmd2TestCase):
cmdapp = app
testcase = TestMyAppCase()
assert testcase._transform_transcript_expected(expected) == transformed
def test_transcript_failure(request, capsys):
# Get location of the transcript
test_dir = os.path.dirname(request.module.__file__)
transcript_file = os.path.join(test_dir, 'transcripts', 'failure.txt')
# Need to patch sys.argv so cmd2 doesn't think it was called with
# arguments equal to the py.test args
testargs = ['prog', '-t', transcript_file]
with mock.patch.object(sys, 'argv', testargs):
# Create a cmd2.Cmd() instance and make sure basic settings are
# like we want for test
app = CmdLineApp()
app.feedback_to_output = False
# Run the command loop
sys_exit_code = app.cmdloop()
assert sys_exit_code != 0
expected_start = "File "
expected_end = "s\n\nFAILED (failures=1)\n\n"
_, err = capsys.readouterr()
assert err.startswith(expected_start)
assert err.endswith(expected_end)
def test_transcript_no_file(request, capsys):
# Need to patch sys.argv so cmd2 doesn't think it was called with
# arguments equal to the py.test args
testargs = ['prog', '-t']
with mock.patch.object(sys, 'argv', testargs):
app = CmdLineApp()
app.feedback_to_output = False
# Run the command loop
sys_exit_code = app.cmdloop()
assert sys_exit_code != 0
# Check for the unittest "OK" condition for the 1 test which ran
expected = 'No test files found - nothing to test\n'
_, err = capsys.readouterr()
assert err == expected
|