Skip to content

Commit

Permalink
Merge pull request #16 from frank113/feature/issue-11_start_pytest_te…
Browse files Browse the repository at this point in the history
…sting
  • Loading branch information
GrandMoff100 authored Jun 28, 2023
2 parents 7a40feb + 0c687c4 commit 60820aa
Show file tree
Hide file tree
Showing 11 changed files with 246 additions and 1 deletion.
27 changes: 27 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: CI Testing

on:
push:
pull_request:

workflow_dispatch:

jobs:
testing:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: '3.x'

- name: Install deps
run: |
pip3 --version
pip3 install .[dev]
- name: Run tests
run: pytest

code_style_checking:
uses: ./.github/workflows/styling.yml
1 change: 1 addition & 0 deletions .github/workflows/styling.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ on:
paths:
- "**.py"
workflow_dispatch:
workflow_call:

jobs:
code_style_checking:
Expand Down
2 changes: 1 addition & 1 deletion regexfactory/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
WHITESPACE,
WORD,
)
from .pattern import RegexPattern, escape, join
from .pattern import ESCAPED_CHARACTERS, RegexPattern, ValidPatternType, escape, join
from .patterns import (
Amount,
Comment,
Expand Down
17 changes: 17 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,23 @@ classifiers =
zip_safe = True
packages = regexfactory

[options.extras_require]
dev =
isort
black
flake8
pylint
pytest
hypothesis

[tool:pytest]
markers =
patterns: Tests for classes in regexfactory/patterns.py
pattern: Tests for classes in regexfactory/pattern.py
addopts = -ra --hypothesis-show-statistics --hypothesis-profile=default
testpaths =
tests

[flake8]
ignore = E501
exclude = .git, __pycache__, .github
Expand Down
4 changes: 4 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from hypothesis import settings

# Set default profile to use 500 examples
settings.register_profile("default", max_examples=500)
22 changes: 22 additions & 0 deletions tests/strategies.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from hypothesis import strategies as st

from regexfactory.pattern import ESCAPED_CHARACTERS

# Strategy to generate characters that are not used in escapes
non_escape_char = st.characters(blacklist_characters=list(ESCAPED_CHARACTERS))


# Strategy to generate text that avoids escaped characters
non_escaped_text = st.text(min_size=1, alphabet=non_escape_char)


# Strategy to produce either None or a positive integer
optional_step = st.one_of(st.none(), st.integers(min_value=1))


def build_bounds(lower_bound, step) -> range:
"""
Function to generate a tuple of (lower, upper) in which lower < upper
"""
upper_bound = lower_bound + step
return range(lower_bound, upper_bound + 1)
89 changes: 89 additions & 0 deletions tests/test_amount.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
from typing import Optional

import pytest
from hypothesis import given
from hypothesis import strategies as st
from strategies import build_bounds, optional_step

from regexfactory import Amount, ValidPatternType


def build_amount(
pattern: ValidPatternType,
start: int,
or_more: bool,
greedy: bool,
step: Optional[int],
):
"""
General Amount builder. Note that the `j` parameter is constructed as
the step plus start when step is defined. When step is None we assume
that no upper bound is present.
"""
stop_value = None
if step is not None:
stop_value = start + step
return Amount(
pattern=pattern, i=start, j=stop_value, or_more=or_more, greedy=greedy
)


@pytest.mark.patterns
@given(st.text(min_size=1), st.integers(min_value=1))
def test_amount_single_count(word, count):
"""
Test to ensure that when `or_more=False` and no upper bound is
provided the regex will be of the form word{count}.
"""
actual = Amount(word, i=count, or_more=False)
assert actual.regex == "{word}{{{count}}}".format(word=word, count=str(count))


@pytest.mark.patterns
@given(
st.text(min_size=1),
st.builds(
build_bounds,
lower_bound=st.integers(min_value=1),
step=st.integers(min_value=1),
),
)
def test_amount_lower_upper(word, bound: range):
"""
Test to ensure that if a lower and upper bound are provided then the
regex of the resulting `Amount` will be of the form {word}{lower,upper}.
"""
actual = Amount(word, bound.start, bound.stop)
expected = "{word}{{{lower},{upper}}}".format(
word=word, lower=str(bound.start), upper=str(bound.stop)
)
assert actual.regex == expected


@pytest.mark.patterns
@given(st.text(min_size=1), st.integers(min_value=1))
def test_amount_or_more(word, count):
"""
Test to ensure that when `or_more=True` and no upper bound is
provided the regex will be of the form word{count,}.
"""
actual = Amount(word, count, or_more=True)
assert actual.regex == "{word}{{{count},}}".format(word=word, count=str(count))


@pytest.mark.patterns
@given(
st.builds(
build_amount,
pattern=st.text(min_size=1),
start=st.integers(min_value=1),
or_more=st.booleans(),
greedy=st.just(False),
step=optional_step,
)
)
def test_amount_non_greedy(amt):
"""
Test to ensure that instances of Amount with greedy as False will end with "?"
"""
assert amt.regex.endswith("?")
18 changes: 18 additions & 0 deletions tests/test_join.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import pytest
from hypothesis import example, given
from hypothesis import strategies as st
from strategies import non_escaped_text

from regexfactory.pattern import join


@pytest.mark.pattern
@given(st.lists(elements=non_escaped_text, min_size=1, max_size=10, unique=True))
@example(words=["0", "1"])
def test_join(words: list):
"""
Tests to capture that the join function concatenates the expressions and
each word in the list is found in the larger regex.
"""
joined_regex = join(*words)
assert joined_regex.regex == "".join(words)
26 changes: 26 additions & 0 deletions tests/test_or.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import re

import pytest
from hypothesis import example, given
from hypothesis import strategies as st
from strategies import non_escaped_text

from regexfactory import Or


@pytest.mark.patterns
@given(
st.lists(
non_escaped_text,
min_size=1,
max_size=10,
)
)
@example(arr=["0", "0"])
def test_matching_or(arr: list):
actual = Or(*arr)
if len(arr) == 1:
assert isinstance(actual.match(arr[0]), re.Match)
else:
for value in arr:
assert isinstance(actual.match(value), re.Match)
25 changes: 25 additions & 0 deletions tests/test_range.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import pytest

from regexfactory import Range


@pytest.mark.patterns
def test_numeric_range():
start = "0"
end = "9"
assert Range(start, end).regex == "[0-9]"


@pytest.mark.patterns
@pytest.mark.parametrize(
"start, stop, expected",
[
("0", "9", "[0-9]"),
("a", "f", "[a-f]"),
("r", "q", "[r-q]"),
("A", "Z", "[A-Z]"),
],
)
def test_range_parameters(start, stop, expected):
actual = Range(start=start, stop=stop)
assert actual.regex == expected
16 changes: 16 additions & 0 deletions tests/test_set.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import re

import pytest
from hypothesis import given
from hypothesis import strategies as st
from strategies import non_escape_char

from regexfactory import Set


@pytest.mark.patterns
@given(st.lists(elements=non_escape_char, min_size=1))
def test_set(chars: list):
actual = Set(*chars)
for value in chars:
assert isinstance(actual.match(value), re.Match)

0 comments on commit 60820aa

Please sign in to comment.