diff options
-rw-r--r-- | CHANGELOG.md | 4 | ||||
-rw-r--r-- | cmd2/cmd2.py | 29 | ||||
-rw-r--r-- | tests/test_cmd2.py | 38 | ||||
-rw-r--r-- | tests/test_transcript.py | 26 |
4 files changed, 86 insertions, 11 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 08c4e15e..5c5f3b1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.8.8 (TBD, 2018) +* Bug Fixes + * Prevent crashes that could occur attempting to open a file in non-existent directory or with very long filename + ## 0.9.1 (May 28, 2018) * Bug Fixes * fix packaging error for 0.8.x versions (yes we had to deploy a new version diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py index 94773794..bb4ac4ad 100644 --- a/cmd2/cmd2.py +++ b/cmd2/cmd2.py @@ -1710,7 +1710,7 @@ class Cmd(cmd.Cmd): if self.timing: self.pfeedback('Elapsed: %s' % str(datetime.datetime.now() - timestart)) finally: - if self.allow_redirection: + if self.allow_redirection and self.redirecting: self._restore_output(statement) except EmptyStatement: pass @@ -1854,7 +1854,11 @@ class Cmd(cmd.Cmd): # REDIRECTION_APPEND or REDIRECTION_OUTPUT if statement.output == constants.REDIRECTION_APPEND: mode = 'a' - sys.stdout = self.stdout = open(os.path.expanduser(statement.output_to), mode) + try: + sys.stdout = self.stdout = open(os.path.expanduser(statement.output_to), mode) + except OSError as ex: + self.perror('Not Redirecting because - {}'.format(ex), traceback_war=False) + self.redirecting = False else: # going to a paste buffer sys.stdout = self.stdout = tempfile.TemporaryFile(mode="w+") @@ -2892,16 +2896,19 @@ a..b, a:b, a:, ..b items by indices (inclusive) self.echo = saved_echo # finally, we can write the transcript out to the file - with open(transcript_file, 'w') as fout: - fout.write(transcript) - - # and let the user know what we did - if len(history) > 1: - plural = 'commands and their outputs' + try: + with open(transcript_file, 'w') as fout: + fout.write(transcript) + except OSError as ex: + self.perror('Failed to save transcript: {}'.format(ex), traceback_war=False) else: - plural = 'command and its output' - msg = '{} {} saved to transcript file {!r}' - self.pfeedback(msg.format(len(history), plural, transcript_file)) + # and let the user know what we did + if len(history) > 1: + plural = 'commands and their outputs' + else: + plural = 'command and its output' + msg = '{} {} saved to transcript file {!r}' + self.pfeedback(msg.format(len(history), plural, transcript_file)) @with_argument_list def do_edit(self, arglist): diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py index 24a14ea2..37592da1 100644 --- a/tests/test_cmd2.py +++ b/tests/test_cmd2.py @@ -615,6 +615,44 @@ def test_output_redirection(base_app): finally: os.remove(filename) +def test_output_redirection_to_nonexistent_directory(base_app): + filename = '~/fakedir/this_does_not_exist.txt' + + # Verify that writing to a file in a non-existent directory doesn't work + run_cmd(base_app, 'help > {}'.format(filename)) + expected = normalize(BASE_HELP) + with pytest.raises(FileNotFoundError): + with open(filename) as f: + content = normalize(f.read()) + assert content == expected + + # Verify that appending to a file also works + run_cmd(base_app, 'help history >> {}'.format(filename)) + expected = normalize(BASE_HELP + '\n' + HELP_HISTORY) + with pytest.raises(FileNotFoundError): + with open(filename) as f: + content = normalize(f.read()) + assert content == expected + +def test_output_redirection_to_too_long_filename(base_app): + filename = '~/sdkfhksdjfhkjdshfkjsdhfkjsdhfkjdshfkjdshfkjshdfkhdsfkjhewfuihewiufhweiufhiweufhiuewhiuewhfiuwehfiuewhfiuewhfiuewhfiuewhiuewhfiuewhfiuewfhiuwehewiufhewiuhfiweuhfiuwehfiuewfhiuwehiuewfhiuewhiewuhfiuewhfiuwefhewiuhewiufhewiufhewiufhewiufhewiufhewiufhewiufhewiuhewiufhewiufhewiuheiufhiuewheiwufhewiufheiufheiufhieuwhfewiuhfeiufhiuewfhiuewheiwuhfiuewhfiuewhfeiuwfhewiufhiuewhiuewhfeiuwhfiuwehfuiwehfiuehiuewhfieuwfhieufhiuewhfeiuwfhiuefhueiwhfw' + + # Verify that writing to a file in a non-existent directory doesn't work + run_cmd(base_app, 'help > {}'.format(filename)) + expected = normalize(BASE_HELP) + with pytest.raises(OSError): + with open(filename) as f: + content = normalize(f.read()) + assert content == expected + + # Verify that appending to a file also works + run_cmd(base_app, 'help history >> {}'.format(filename)) + expected = normalize(BASE_HELP + '\n' + HELP_HISTORY) + with pytest.raises(OSError): + with open(filename) as f: + content = normalize(f.read()) + assert content == expected + def test_feedback_to_output_true(base_app): base_app.feedback_to_output = True diff --git a/tests/test_transcript.py b/tests/test_transcript.py index 302d80c8..3caf6a37 100644 --- a/tests/test_transcript.py +++ b/tests/test_transcript.py @@ -154,6 +154,32 @@ this is a \/multiline\/ command assert transcript == expected +def test_history_transcript_bad_filename(request, capsys): + app = CmdLineApp() + app.stdout = 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 + history_fname = '~/fakedir/this_does_not_exist.txt' + + # 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 pytest.raises(FileNotFoundError): + with open(history_fname) as f: + transcript = f.read() + assert transcript == expected + @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. |