Skip to content

Commit

Permalink
Merge pull request #1302 from benoit-pierre/improve_dictionary_implem…
Browse files Browse the repository at this point in the history
…entation_test_framework

Improve dictionary implementation test framework
  • Loading branch information
benoit-pierre authored May 14, 2021
2 parents 2674b81 + 24bbd90 commit 84c0b9d
Show file tree
Hide file tree
Showing 26 changed files with 966 additions and 533 deletions.
2 changes: 1 addition & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ include plover_build_utils/*.sh
include pyproject.toml
include reqs/*.txt
include test/__init__.py
include test/utils.py
include test/conftest.py
include tox.ini
include windows/*
# Exclude: CI/Git/GitHub specific files,
Expand Down
4 changes: 4 additions & 0 deletions news.d/api/1302.new.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Add some new helpers to `plover_build_utils.testing`:
- `dictionary_test`: torture tests for dictionary implementations.
- `make_dict`: create a temporary dictionary.
- `parametrize`: parametrize helper for tracking test data source line numbers.
3 changes: 3 additions & 0 deletions news.d/bugfix/1302.core.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix 2 corner cases when handling dictionaries:
- If the class implementation is marked as read-only, then loading from a writable file should still result in a read-only dictionary.
- Don't allow `clear` on a read-only dictionary.
6 changes: 4 additions & 2 deletions plover/steno_dictionary.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,9 @@ def load(cls, resource):
timestamp = resource_timestamp(filename)
d = cls()
d._load(filename)
if resource.startswith(ASSET_SCHEME) or \
not os.access(filename, os.W_OK):
if (cls.readonly or
resource.startswith(ASSET_SCHEME) or
not os.access(filename, os.W_OK)):
d.readonly = True
d.path = resource
d.timestamp = timestamp
Expand Down Expand Up @@ -96,6 +97,7 @@ def __getitem__(self, key):
return self._dict.__getitem__(key)

def clear(self):
assert not self.readonly
self._dict.clear()
self.reverse.clear()
self.casereverse.clear()
Expand Down
6 changes: 6 additions & 0 deletions plover_build_utils/testing/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from .blackbox import blackbox_test
from .dict import make_dict
from .output import CaptureOutput
from .parametrize import parametrize
from .steno import steno_to_stroke
from .steno_dictionary import dictionary_test
Original file line number Diff line number Diff line change
Expand Up @@ -5,70 +5,13 @@
import re
import textwrap

from plover import system
from plover.formatting import Formatter
from plover.steno import Stroke, normalize_steno
from plover.steno import normalize_steno
from plover.steno_dictionary import StenoDictionary
from plover.translation import Translator


class CaptureOutput:

def __init__(self):
self.instructions = []
self.text = ''

def send_backspaces(self, n):
assert n <= len(self.text)
self.text = self.text[:-n]
self.instructions.append(('b', n))

def send_string(self, s):
self.text += s
self.instructions.append(('s', s))

def send_key_combination(self, c):
self.instructions.append(('c', c))

def send_engine_command(self, c):
self.instructions.append(('e', c))


def steno_to_stroke(steno):
if steno_to_stroke.system != system.NAME:
keys = []
letters = ''
has_hyphen = False
for k in system.KEYS:
if not has_hyphen and k.startswith('-'):
has_hyphen = True
keys.append(None)
letters += '-'
keys.append(k)
letters += k.strip('-')
steno_to_stroke.keys = keys
steno_to_stroke.letters = letters
steno_to_stroke.system = system.NAME
steno_to_stroke.numbers = {
v.strip('-'): k.strip('-')
for k, v in system.NUMBERS.items()
}
n = -1
keys = set()
for li, l in enumerate(steno):
rl = steno_to_stroke.numbers.get(l)
if rl is not None:
keys.add('#')
l = rl
n = steno_to_stroke.letters.find(l, n + 1)
if n < 0:
raise ValueError('invalid steno letter at index %u:\n%s\n%s^' % (li, steno, ' ' * li))
k = steno_to_stroke.keys[n]
if k is not None:
keys.add(k)
return Stroke(keys)

steno_to_stroke.system = None
from .output import CaptureOutput
from .steno import steno_to_stroke


BLACKBOX_OUTPUT_RX = re.compile("r?['\"]")
Expand Down
20 changes: 20 additions & 0 deletions plover_build_utils/testing/dict.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from contextlib import contextmanager
from pathlib import Path
import os
import tempfile


@contextmanager
def make_dict(tmp_path, contents, extension=None, name=None):
kwargs = {'dir': str(tmp_path)}
if name is not None:
kwargs['prefix'] = name + '_'
if extension is not None:
kwargs['suffix'] = '.' + extension
fd, path = tempfile.mkstemp(**kwargs)
try:
os.write(fd, contents)
os.close(fd)
yield Path(path)
finally:
os.unlink(path)
20 changes: 20 additions & 0 deletions plover_build_utils/testing/output.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
class CaptureOutput:

def __init__(self):
self.instructions = []
self.text = ''

def send_backspaces(self, n):
assert n <= len(self.text)
self.text = self.text[:-n]
self.instructions.append(('b', n))

def send_string(self, s):
self.text += s
self.instructions.append(('s', s))

def send_key_combination(self, c):
self.instructions.append(('c', c))

def send_engine_command(self, c):
self.instructions.append(('e', c))
38 changes: 38 additions & 0 deletions plover_build_utils/testing/parametrize.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import inspect

import pytest


def parametrize(tests, arity=None):
'''Helper for parametrizing pytest tests.
Expects a list of lambdas, one per test. Each lambda must return
the parameters for its respective test.
Test identifiers will be automatically generated, from the test
number and its lambda definition line (1.10, 2.12, 3.20, ...).
If arity is None, the arguments being parametrized will be automatically
set from the function's last arguments, according to the numbers of
parameters for each test.
'''
ids = []
argvalues = []
for n, t in enumerate(tests):
line = inspect.getsourcelines(t)[1]
ids.append('%u:%u' % (n+1, line))
argvalues.append(t())
if arity is None:
arity = len(argvalues[0])
assert arity > 0
def decorator(fn):
argnames = list(
parameter.name
for parameter in inspect.signature(fn).parameters.values()
if parameter.default is inspect.Parameter.empty
)[-arity:]
if arity == 1:
argnames = argnames[0]
return pytest.mark.parametrize(argnames, argvalues, ids=ids)(fn)
return decorator
41 changes: 41 additions & 0 deletions plover_build_utils/testing/steno.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from plover import system
from plover.steno import Stroke


def steno_to_stroke(steno):
# Check if the system changed, or
# we need to perform initial setup.
if steno_to_stroke.system != system.NAME:
keys = []
letters = ''
has_hyphen = False
for k in system.KEYS:
if not has_hyphen and k.startswith('-'):
has_hyphen = True
keys.append(None)
letters += '-'
keys.append(k)
letters += k.strip('-')
steno_to_stroke.keys = keys
steno_to_stroke.letters = letters
steno_to_stroke.system = system.NAME
steno_to_stroke.numbers = {
v.strip('-'): k.strip('-')
for k, v in system.NUMBERS.items()
}
n = -1
keys = set()
for li, l in enumerate(steno):
rl = steno_to_stroke.numbers.get(l)
if rl is not None:
keys.add('#')
l = rl
n = steno_to_stroke.letters.find(l, n + 1)
if n < 0:
raise ValueError('invalid steno letter at index %u:\n%s\n%s^' % (li, steno, ' ' * li))
k = steno_to_stroke.keys[n]
if k is not None:
keys.add(k)
return Stroke(keys)

steno_to_stroke.system = None
Loading

0 comments on commit 84c0b9d

Please sign in to comment.