Fix OS compatibility issues for editors with spaces, slashes, and quotes (#1153)

* Fix inverted POSIX check, refactor os_compat, and add tests for it
* Fix missing parentheses and remove skip_win on test that is passing in Windows now
* Fix expected quotes in quoted args
* Make  output clearer on failing test
* Bringing skip_win back to test whose failure is a bit more complicated than expected
This commit is contained in:
Micah Jerome Ellison 2021-01-16 15:19:11 -08:00 committed by GitHub
parent b6b6e7750e
commit 9e6cd8820f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 121 additions and 20 deletions

View file

@ -42,7 +42,7 @@ def before_feature(context, feature):
feature.skip() feature.skip()
return return
if "skip_win" in feature.tags and on_windows: if "skip_win" in feature.tags and on_windows():
feature.skip("Skipping on Windows") feature.skip("Skipping on Windows")
return return
@ -69,7 +69,7 @@ def before_scenario(context, scenario):
scenario.skip() scenario.skip()
return return
if "skip_win" in scenario.effective_tags and on_windows: if "skip_win" in scenario.effective_tags and on_windows():
scenario.skip("Skipping on Windows") scenario.skip("Skipping on Windows")
return return

View file

@ -6,7 +6,6 @@ from collections import defaultdict
import os import os
from pathlib import Path from pathlib import Path
import re import re
import shlex
import time import time
from unittest.mock import patch from unittest.mock import patch
@ -23,7 +22,7 @@ from jrnl import __version__
from jrnl import plugins from jrnl import plugins
from jrnl.cli import cli from jrnl.cli import cli
from jrnl.config import load_config from jrnl.config import load_config
from jrnl.os_compat import on_windows from jrnl.os_compat import split_args
try: try:
import parsedatetime.parsedatetime_consts as pdt import parsedatetime.parsedatetime_consts as pdt
@ -88,10 +87,6 @@ class FailedKeyring(keyring.backend.KeyringBackend):
keyring.set_keyring(TestKeyring()) keyring.set_keyring(TestKeyring())
def ushlex(command):
return shlex.split(command, posix=not on_windows)
def read_journal(context, journal_name="default"): def read_journal(context, journal_name="default"):
configuration = load_config(context.config_path) configuration = load_config(context.config_path)
with open(configuration["journals"][journal_name]) as journal_file: with open(configuration["journals"][journal_name]) as journal_file:
@ -319,7 +314,7 @@ def run_with_input(context, command, inputs=""):
else: else:
text = iter([inputs]) text = iter([inputs])
args = ushlex(command)[1:] args = split_args(command)[1:]
def _mock_editor(command): def _mock_editor(command):
context.editor_command = command context.editor_command = command
@ -403,7 +398,7 @@ def run(context, command, text=""):
cache_dir = os.path.join("features", "cache", context.cache_dir) cache_dir = os.path.join("features", "cache", context.cache_dir)
command = command.format(cache_dir=cache_dir) command = command.format(cache_dir=cache_dir)
args = ushlex(command) args = split_args(command)
def _mock_editor(command): def _mock_editor(command):
context.editor_command = command context.editor_command = command

View file

@ -169,17 +169,24 @@ def assert_exported_yaml_file_content(context, file_path, cache_dir=None):
for actual_line, expected_line in zip(actual_content, expected_content): for actual_line, expected_line in zip(actual_content, expected_content):
if actual_line.startswith("tags: ") and expected_line.startswith("tags: "): if actual_line.startswith("tags: ") and expected_line.startswith("tags: "):
assert_equal_tags_ignoring_order(actual_line, expected_line) assert_equal_tags_ignoring_order(
actual_line, expected_line, actual_content, expected_content
)
else: else:
assert actual_line.strip() == expected_line.strip(), [ assert actual_line.strip() == expected_line.strip(), [
actual_line.strip(), [actual_line.strip(), expected_line.strip()],
expected_line.strip(), [actual_content, expected_content],
] ]
def assert_equal_tags_ignoring_order(actual_line, expected_line): def assert_equal_tags_ignoring_order(
actual_line, expected_line, actual_content, expected_content
):
actual_tags = set(tag.strip() for tag in actual_line[len("tags: ") :].split(",")) actual_tags = set(tag.strip() for tag in actual_line[len("tags: ") :].split(","))
expected_tags = set( expected_tags = set(
tag.strip() for tag in expected_line[len("tags: ") :].split(",") tag.strip() for tag in expected_line[len("tags: ") :].split(",")
) )
assert actual_tags == expected_tags, [actual_tags, expected_tags] assert actual_tags == expected_tags, [
[actual_tags, expected_tags],
[expected_content, actual_content],
]

View file

@ -7,7 +7,7 @@ import colorama
from .os_compat import on_windows from .os_compat import on_windows
if on_windows: if on_windows():
colorama.init() colorama.init()
WARNING_COLOR = colorama.Fore.YELLOW WARNING_COLOR = colorama.Fore.YELLOW

View file

@ -1,6 +1,5 @@
import logging import logging
import os import os
import shlex
import subprocess import subprocess
import sys import sys
import tempfile import tempfile
@ -10,6 +9,7 @@ from pathlib import Path
from .color import ERROR_COLOR from .color import ERROR_COLOR
from .color import RESET_COLOR from .color import RESET_COLOR
from .os_compat import on_windows from .os_compat import on_windows
from .os_compat import split_args
def get_text_from_editor(config, template=""): def get_text_from_editor(config, template=""):
@ -25,7 +25,7 @@ def get_text_from_editor(config, template=""):
f.write(template) f.write(template)
try: try:
subprocess.call(shlex.split(config["editor"], posix=on_windows) + [tmpfile]) subprocess.call(split_args(config["editor"]) + [tmpfile])
except Exception as e: except Exception as e:
error_msg = f""" error_msg = f"""
{ERROR_COLOR}{str(e)}{RESET_COLOR} {ERROR_COLOR}{str(e)}{RESET_COLOR}
@ -47,7 +47,7 @@ def get_text_from_editor(config, template=""):
def get_text_from_stdin(): def get_text_from_stdin():
_how_to_quit = "Ctrl+z and then Enter" if on_windows else "Ctrl+d" _how_to_quit = "Ctrl+z and then Enter" if on_windows() else "Ctrl+d"
print( print(
f"[Writing Entry; on a blank line, press {_how_to_quit} to finish writing]\n", f"[Writing Entry; on a blank line, press {_how_to_quit} to finish writing]\n",
file=sys.stderr, file=sys.stderr,

View file

@ -1,6 +1,18 @@
# Copyright (C) 2012-2021 jrnl contributors # Copyright (C) 2012-2021 jrnl contributors
# License: https://www.gnu.org/licenses/gpl-3.0.html # License: https://www.gnu.org/licenses/gpl-3.0.html
import shlex
from sys import platform from sys import platform
on_windows = "win32" in platform
def on_windows():
return "win32" in platform
def on_posix():
return not on_windows()
def split_args(args):
"""Split arguments and add escape characters as appropriate for the OS"""
return shlex.split(args, posix=on_posix())

87
tests/test_os_compat.py Normal file
View file

@ -0,0 +1,87 @@
from unittest import mock
import pytest
from jrnl.os_compat import on_windows
from jrnl.os_compat import on_posix
from jrnl.os_compat import split_args
@pytest.mark.parametrize(
"systems",
[
["linux", False],
["win32", True],
["cygwin", False],
["msys", False],
["darwin", False],
["os2", False],
["os2emx", False],
["riscos", False],
["atheos", False],
["freebsd7", False],
["freebsd8", False],
["freebsdN", False],
["openbsd6", False],
],
)
def test_on_windows(systems):
osname, expected_on_windows = systems[0], systems[1]
with mock.patch("jrnl.os_compat.platform", osname):
assert on_windows() == expected_on_windows
@pytest.mark.parametrize(
"systems",
[
["linux", True],
["win32", False],
["cygwin", True],
["msys", True],
["darwin", True],
["os2", True],
["os2emx", True],
["riscos", True],
["atheos", True],
["freebsd7", True],
["freebsd8", True],
["freebsdN", True],
["openbsd6", True],
],
)
def test_on_posix(systems):
osname, expected_on_posix = systems[0], systems[1]
with mock.patch("jrnl.os_compat.platform", osname):
assert on_posix() == expected_on_posix
@pytest.mark.parametrize(
"args",
[
["notepad", ["notepad"]],
["subl -w", ["subl", "-w"]],
[
'"C:\\Program Files\\Sublime Text 3\\subl.exe" -w',
['"C:\\Program Files\\Sublime Text 3\\subl.exe"', "-w"],
],
],
)
def test_split_args_on_windows(args):
input_arguments, expected_split_args = args[0], args[1]
with mock.patch("jrnl.os_compat.on_windows", lambda: True):
assert split_args(input_arguments) == expected_split_args
@pytest.mark.parametrize(
"args",
[
["vim", ["vim"]],
[
'vim -f +Goyo +Limelight "+set spell linebreak"',
["vim", "-f", "+Goyo", "+Limelight", '"+set spell linebreak"'],
],
],
)
def test_split_args_on_not_windows(args):
input_arguments, expected_split_args = args[0], args[1]
with mock.patch("jrnl.os_compat.on_windows", lambda: True):
assert split_args(input_arguments) == expected_split_args