Skip to content

Commit

Permalink
remove py2 remnants and use mocks in tests
Browse files Browse the repository at this point in the history
  • Loading branch information
pspeter committed Oct 31, 2019
1 parent ef23d7e commit 34f8f85
Show file tree
Hide file tree
Showing 25 changed files with 155 additions and 246 deletions.
8 changes: 4 additions & 4 deletions features/encryption.feature
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Scenario: Loading an encrypted journal
Given we use the config "encrypted.yaml"
When we run "jrnl -n 1" and enter "bad doggie no biscuit"
Then we should see the message "Password"
Then the output should contain "Password"
and the output should contain "2013-06-10 15:40 Life is good"

Scenario: Decrypting a journal
Expand All @@ -14,16 +14,16 @@

Scenario: Encrypting a journal
Given we use the config "basic.yaml"
When we run "jrnl --encrypt" and enter "swordfish"
When we run "jrnl --encrypt" and enter "swordfish" and "n"
Then we should see the message "Journal encrypted"
and the config for journal "default" should have "encrypt" set to "bool:True"
When we run "jrnl -n 1" and enter "swordfish"
Then we should see the message "Password"
Then the output should contain "Password"
and the output should contain "2013-06-10 15:40 Life is good"

Scenario: Storing a password in Keychain
Given we use the config "multiple.yaml"
When we run "jrnl simple --encrypt" and enter "sabertooth"
When we run "jrnl simple --encrypt" and enter "sabertooth" and "y"
When we set the keychain password of "simple" to "sabertooth"
Then the config for journal "simple" should have "encrypt" set to "bool:True"
When we run "jrnl simple -n 1"
Expand Down
15 changes: 2 additions & 13 deletions features/environment.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,15 @@
from behave import *
import shutil
import os
import jrnl
try:
from io import StringIO
except ImportError:
from cStringIO import StringIO


def before_scenario(context, scenario):
"""Before each scenario, backup all config and journal test data."""
context.messages = StringIO()
jrnl.util.STDERR = context.messages
jrnl.util.TEST = True

# Clean up in case something went wrong
for folder in ("configs", "journals"):
working_dir = os.path.join("features", folder)
if os.path.exists(working_dir):
shutil.rmtree(working_dir)


for folder in ("configs", "journals"):
original = os.path.join("features", "data", folder)
working_dir = os.path.join("features", folder)
Expand All @@ -32,10 +22,9 @@ def before_scenario(context, scenario):
else:
shutil.copy2(source, working_dir)


def after_scenario(context, scenario):
"""After each scenario, restore all test data and remove working_dirs."""
context.messages.close()
context.messages = None
for folder in ("configs", "journals"):
working_dir = os.path.join("features", folder)
if os.path.exists(working_dir):
Expand Down
2 changes: 1 addition & 1 deletion features/multiple_journals.feature
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,5 @@ Feature: Multiple journals

Scenario: Don't crash if no file exists for a configured encrypted journal
Given we use the config "multiple.yaml"
When we run "jrnl new_encrypted Adding first entry" and enter "these three eyes"
When we run "jrnl new_encrypted Adding first entry" and enter "these three eyes" and "y"
Then we should see the message "Journal 'new_encrypted' created"
68 changes: 40 additions & 28 deletions features/steps/core.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from __future__ import unicode_literals
from __future__ import absolute_import
from unittest.mock import patch

from behave import given, when, then
from jrnl import cli, install, Journal, util, plugins
Expand All @@ -10,10 +9,13 @@
import json
import yaml
import keyring
import tzlocal
import shlex
import sys


class TestKeyring(keyring.backend.KeyringBackend):
"""A test keyring that just stores its valies in a hash"""
"""A test keyring that just stores its values in a hash"""

priority = 1
keys = defaultdict(dict)
Expand All @@ -31,15 +33,6 @@ def delete_password(self, servicename, username, password):
keyring.set_keyring(TestKeyring())


try:
from io import StringIO
except ImportError:
from cStringIO import StringIO
import tzlocal
import shlex
import sys


def ushlex(command):
if sys.version_info[0] == 3:
return shlex.split(command)
Expand Down Expand Up @@ -73,18 +66,41 @@ def set_config(context, config_file):
cf.write("version: {}".format(__version__))


def _mock_getpass(inputs):
def prompt_return(prompt="Password: "):
print(prompt)
return next(inputs)
return prompt_return


def _mock_input(inputs):
def prompt_return(prompt=""):
val = next(inputs)
print(prompt, val)
return val
return prompt_return


@when('we run "{command}" and enter')
@when('we run "{command}" and enter "{inputs}"')
def run_with_input(context, command, inputs=None):
text = inputs or context.text
@when('we run "{command}" and enter "{inputs1}"')
@when('we run "{command}" and enter "{inputs1}" and "{inputs2}"')
def run_with_input(context, command, inputs1="", inputs2=""):
# create an iterator through all inputs. These inputs will be fed one by one
# to the mocked calls for 'input()', 'util.getpass()' and 'sys.stdin.read()'
text = iter((inputs1, inputs2)) if inputs1 else iter(context.text.split("\n"))
args = ushlex(command)[1:]
buffer = StringIO(text.strip())
util.STDIN = buffer
try:
cli.run(args or [])
context.exit_status = 0
except SystemExit as e:
context.exit_status = e.code
with patch("builtins.input", side_effect=_mock_input(text)) as mock_input:
with patch("jrnl.util.getpass", side_effect=_mock_getpass(text)) as mock_getpass:
with patch("sys.stdin.read", side_effect=text) as mock_read:
try:
cli.run(args or [])
context.exit_status = 0
except SystemExit as e:
context.exit_status = e.code

# assert at least one of the mocked input methods got called
assert mock_input.called or mock_getpass.called or mock_read.called



@when('we run "{command}"')
Expand Down Expand Up @@ -190,28 +206,24 @@ def check_output_time_inline(context, text):
def check_output_inline(context, text=None):
text = text or context.text
out = context.stdout_capture.getvalue()
if isinstance(out, bytes):
out = out.decode('utf-8')
assert text in out, text


@then('the output should not contain "{text}"')
def check_output_not_inline(context, text):
out = context.stdout_capture.getvalue()
if isinstance(out, bytes):
out = out.decode('utf-8')
assert text not in out


@then('we should see the message "{text}"')
def check_message(context, text):
out = context.messages.getvalue()
out = context.stderr_capture.getvalue()
assert text in out, [text, out]


@then('we should not see the message "{text}"')
def check_not_message(context, text):
out = context.messages.getvalue()
out = context.stderr_capture.getvalue()
assert text not in out, [text, out]


Expand Down
4 changes: 2 additions & 2 deletions features/upgrade.feature
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ Feature: Upgrading Journals from 1.x.x to 2.x.x

Scenario: Upgrading a journal encrypted with jrnl 1.x
Given we use the config "encrypted_old.json"
When we run "jrnl -n 1" and enter
When we run "jrnl -n 1" and enter
"""
Y
bad doggie no biscuit
bad doggie no biscuit
"""
Then we should see the message "Password"
Then the output should contain "Password"
and the output should contain "2013-06-10 15:40 Life is good"
3 changes: 1 addition & 2 deletions jrnl/DayOneJournal.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#!/usr/bin/env python
# encoding: utf-8

from __future__ import absolute_import, unicode_literals
from . import Entry
from . import Journal
from . import time as jrnl_time
Expand Down Expand Up @@ -83,7 +82,7 @@ def write(self):
def editable_str(self):
"""Turns the journal into a string of entries that can be edited
manually and later be parsed with eslf.parse_editable_str."""
return "\n".join(["# {0}\n{1}".format(e.uuid, e.__unicode__()) for e in self.entries])
return "\n".join(["# {0}\n{1}".format(e.uuid, str(e)) for e in self.entries])

def parse_editable_str(self, edited):
"""Parses the output of self.editable_str and updates its entries."""
Expand Down
8 changes: 3 additions & 5 deletions jrnl/EncryptedJournal.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,13 @@
import sys
import os
import base64
import getpass
import logging

log = logging.getLogger()


def make_key(password):
password = util.bytes(password)
password = password.encode("utf-8")
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
Expand Down Expand Up @@ -48,9 +47,9 @@ def open(self, filename=None):
self.config['password'] = password
text = ""
self._store(filename, text)
util.prompt("[Journal '{0}' created at {1}]".format(self.name, filename))
print("[Journal '{0}' created at {1}]".format(self.name, filename), file=sys.stderr)
else:
util.prompt("No password supplied for encrypted journal")
print("No password supplied for encrypted journal", file=sys.stderr)
sys.exit(1)
else:
text = self._load(filename)
Expand All @@ -59,7 +58,6 @@ def open(self, filename=None):
log.debug("opened %s with %d entries", self.__class__.__name__, len(self))
return self


def _load(self, filename, password=None):
"""Loads an encrypted journal from a file and tries to decrypt it.
If password is not provided, will look for password in the keychain
Expand Down
5 changes: 2 additions & 3 deletions jrnl/Entry.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#!/usr/bin/env python
# encoding: utf-8

from __future__ import unicode_literals
import re
import textwrap
from datetime import datetime
Expand Down Expand Up @@ -52,13 +51,13 @@ def tags(self):
@staticmethod
def tag_regex(tagsymbols):
pattern = r'(?u)(?:^|\s)([{tags}][-+*#/\w]+)'.format(tags=tagsymbols)
return re.compile(pattern, re.UNICODE)
return re.compile(pattern)

def _parse_tags(self):
tagsymbols = self.journal.config['tagsymbols']
return set(tag.lower() for tag in re.findall(Entry.tag_regex(tagsymbols), self.text))

def __unicode__(self):
def __str__(self):
"""Returns a string representation of the entry to be written into a journal file."""
date_str = self.date.strftime(self.journal.config['timeformat'])
title = "[{}] {}".format(date_str, self.title.rstrip("\n "))
Expand Down
24 changes: 12 additions & 12 deletions jrnl/Journal.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#!/usr/bin/env python
# encoding: utf-8

from __future__ import absolute_import, unicode_literals
from . import Entry
from . import util
from . import time
Expand All @@ -15,7 +14,7 @@
log = logging.getLogger(__name__)


class Tag(object):
class Tag:
def __init__(self, name, count=0):
self.name = name
self.count = count
Expand All @@ -27,7 +26,7 @@ def __repr__(self):
return "<Tag '{}'>".format(self.name)


class Journal(object):
class Journal:
def __init__(self, name='default', **kwargs):
self.config = {
'journal': "journal.txt",
Expand Down Expand Up @@ -72,7 +71,7 @@ def open(self, filename=None):
filename = filename or self.config['journal']

if not os.path.exists(filename):
util.prompt("[Journal '{0}' created at {1}]".format(self.name, filename))
print("[Journal '{0}' created at {1}]".format(self.name, filename), file=sys.stderr)
self._create(filename)

text = self._load(filename)
Expand All @@ -96,7 +95,7 @@ def validate_parsing(self):
return True

def _to_text(self):
return "\n".join([e.__unicode__() for e in self.entries])
return "\n".join([str(e) for e in self.entries])

def _load(self, filename):
raise NotImplementedError
Expand Down Expand Up @@ -140,9 +139,6 @@ def _parse(self, journal_txt):
entry._parse_text()
return entries

def __unicode__(self):
return self.pprint()

def pprint(self, short=False):
"""Prettyprints the journal's entries"""
sep = "\n"
Expand All @@ -153,7 +149,7 @@ def pprint(self, short=False):
tagre = re.compile(re.escape(tag), re.IGNORECASE)
pp = re.sub(tagre,
lambda match: util.colorize(match.group(0)),
pp, re.UNICODE)
pp)
else:
pp = re.sub(
Entry.Entry.tag_regex(self.config['tagsymbols']),
Expand All @@ -162,6 +158,9 @@ def pprint(self, short=False):
)
return pp

def __str__(self):
return self.pprint()

def __repr__(self):
return "<Journal with {0} entries>".format(len(self.entries))

Expand Down Expand Up @@ -254,7 +253,7 @@ def new_entry(self, raw, date=None, sort=True):
def editable_str(self):
"""Turns the journal into a string of entries that can be edited
manually and later be parsed with eslf.parse_editable_str."""
return "\n".join([e.__unicode__() for e in self.entries])
return "\n".join([str(e) for e in self.entries])

def parse_editable_str(self, edited):
"""Parses the output of self.editable_str and updates it's entries."""
Expand Down Expand Up @@ -347,8 +346,9 @@ def open_journal(name, config, legacy=False):
from . import DayOneJournal
return DayOneJournal.DayOne(**config).open()
else:
util.prompt(
u"[Error: {0} is a directory, but doesn't seem to be a DayOne journal either.".format(config['journal'])
print(
u"[Error: {0} is a directory, but doesn't seem to be a DayOne journal either.".format(config['journal']),
file=sys.stderr
)

sys.exit(1)
Expand Down
1 change: 0 additions & 1 deletion jrnl/__main__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#!/usr/bin/env python
# encoding: utf-8
from __future__ import absolute_import, unicode_literals
from . import cli


Expand Down
Loading

0 comments on commit 34f8f85

Please sign in to comment.