Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,5 @@ path = "src/rexplain/__init__.py"

[tool.pytest.ini_options]
addopts = "--cov=src/rexplain --cov-report=term-missing --cov-fail-under=70"
testpaths = ["tests"]
testpaths = ["tests"]
norecursedirs = ["src", ".git", "dist", "build"]
9 changes: 9 additions & 0 deletions src/rexplain/core/tester.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ def __str__(self):
f"failed_at={self.failed_at}, partial_matches={self.partial_matches})"
)

def to_dict(self):
"""Convert the result to a dictionary format"""
return {
'matches': self.matches,
'reason': self.reason,
'failed_at': self.failed_at,
'partial_matches': self.partial_matches
}

class RegexTester:
"""
Tests if a string matches a regex pattern and provides detailed feedback.
Expand Down
105 changes: 105 additions & 0 deletions tests/test_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
"""
Tests for the public API wrapper functions in __init__.py
"""
import sys
import os
import re
import tempfile
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../src')))

import rexplain
from rexplain import explain, examples, diagram

def test_explain_api():
"""Test the explain() API function"""
result = explain(r'\d+')
assert isinstance(result, str)
assert 'digit' in result.lower() or r'\d' in result

def test_explain_api_with_flags():
"""Test explain() with regex flags"""
result = explain(r'abc', flags=re.IGNORECASE)
assert isinstance(result, str)
assert 'a' in result.lower()

def test_examples_api():
"""Test the examples() API function"""
result = examples(r'[a-z]{3}', count=5)
assert isinstance(result, list)
assert len(result) == 5
# Verify all examples match the pattern
pattern = re.compile(r'[a-z]{3}')
for ex in result:
assert pattern.fullmatch(ex)

def test_examples_api_default_count():
"""Test examples() with default count"""
result = examples(r'\d+')
assert isinstance(result, list)
assert len(result) == 3 # Default count

def test_examples_api_with_flags():
"""Test examples() with regex flags"""
result = examples(r'[a-z]+', count=2, flags=re.IGNORECASE)
assert isinstance(result, list)
assert len(result) == 2

def test_test_api_match():
"""Test the test() API function with matching string"""
result = rexplain.test(r'foo.*', 'foobar')
assert hasattr(result, 'matches')
assert result.matches is True
assert result.reason

def test_test_api_no_match():
"""Test the test() API function with non-matching string"""
result = rexplain.test(r'abc', 'xyz')
assert hasattr(result, 'matches')
assert result.matches is False
assert result.reason

def test_test_api_with_flags():
"""Test test() with regex flags"""
result = rexplain.test(r'abc', 'ABC', flags=re.IGNORECASE)
assert result.matches is True

def test_diagram_api_basic():
"""Test the diagram() API function without output file"""
result = diagram(r'\w+')
assert isinstance(result, str)
assert '<svg' in result

def test_diagram_api_with_output():
"""Test diagram() with output file"""
with tempfile.NamedTemporaryFile(suffix='.svg', delete=False) as tmp:
output_path = tmp.name

try:
result = diagram(r'\d+', output_path=output_path)
assert result == output_path
assert os.path.exists(output_path)
with open(output_path) as f:
content = f.read()
assert '<svg' in content
finally:
if os.path.exists(output_path):
os.unlink(output_path)

def test_diagram_api_detailed():
"""Test diagram() with detailed flag"""
result = diagram(r'\w+', detailed=True)
assert isinstance(result, str)
assert '<svg' in result

def test_diagram_api_detailed_with_output():
"""Test diagram() with detailed flag and output file"""
with tempfile.NamedTemporaryFile(suffix='.svg', delete=False) as tmp:
output_path = tmp.name

try:
result = diagram(r'[a-z]+', output_path=output_path, detailed=True)
assert result == output_path
assert os.path.exists(output_path)
finally:
if os.path.exists(output_path):
os.unlink(output_path)
99 changes: 98 additions & 1 deletion tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,106 @@
import subprocess
import sys
import os
import tempfile
import pytest
from unittest.mock import patch
from io import StringIO

def test_cli_help():
cli_path = os.path.join(os.path.dirname(__file__), '../src/rexplain/cli/main.py')
result = subprocess.run([sys.executable, cli_path, '--help'], capture_output=True, text=True)
assert result.returncode == 0
assert 'usage:' in result.stdout.lower()
assert 'usage:' in result.stdout.lower()

def test_cli_version():
"""Test --version flag"""
result = subprocess.run([sys.executable, '-m', 'rexplain.cli.main', '--version'],
capture_output=True, text=True, cwd='/home/user/rexplain/src')
assert result.returncode == 0
assert result.stdout.strip() # Should output version

def test_cli_about():
"""Test --about flag"""
result = subprocess.run([sys.executable, '-m', 'rexplain.cli.main', '--about'],
capture_output=True, text=True, cwd='/home/user/rexplain/src')
assert result.returncode == 0
assert 'rexplain' in result.stdout.lower()

def test_cli_explain_basic():
"""Test explain command"""
result = subprocess.run([sys.executable, '-m', 'rexplain.cli.main', 'explain', r'\d+'],
capture_output=True, text=True, cwd='/home/user/rexplain/src')
assert result.returncode == 0
assert 'digit' in result.stdout.lower() or r'\d' in result.stdout

def test_cli_explain_with_examples():
"""Test explain command with --examples flag"""
result = subprocess.run([sys.executable, '-m', 'rexplain.cli.main', 'explain', r'\d{2}', '--examples', '2'],
capture_output=True, text=True, cwd='/home/user/rexplain/src')
assert result.returncode == 0
assert 'example' in result.stdout.lower()

def test_cli_examples():
"""Test examples command"""
result = subprocess.run([sys.executable, '-m', 'rexplain.cli.main', 'examples', r'[a-z]{3}', '--count', '2'],
capture_output=True, text=True, cwd='/home/user/rexplain/src')
assert result.returncode == 0
lines = result.stdout.strip().split('\n')
assert len(lines) >= 2 # Should have at least 2 examples

def test_cli_test_match():
"""Test test command with matching string"""
result = subprocess.run([sys.executable, '-m', 'rexplain.cli.main', 'test', 'abc', 'abc'],
capture_output=True, text=True, cwd='/home/user/rexplain/src')
assert result.returncode == 0

def test_cli_test_no_match():
"""Test test command with non-matching string"""
result = subprocess.run([sys.executable, '-m', 'rexplain.cli.main', 'test', 'abc', 'xyz'],
capture_output=True, text=True, cwd='/home/user/rexplain/src')
assert result.returncode == 1 # Should exit with error code

def test_cli_diagram_stdout():
"""Test diagram command without output file"""
result = subprocess.run([sys.executable, '-m', 'rexplain.cli.main', 'diagram', r'\w+'],
capture_output=True, text=True, cwd='/home/user/rexplain/src')
assert result.returncode == 0
assert '<svg' in result.stdout

def test_cli_diagram_with_output():
"""Test diagram command with output file"""
with tempfile.NamedTemporaryFile(suffix='.svg', delete=False) as tmp:
output_path = tmp.name

try:
result = subprocess.run([sys.executable, '-m', 'rexplain.cli.main', 'diagram', r'\w+', '--output', output_path],
capture_output=True, text=True, cwd='/home/user/rexplain/src')
assert result.returncode == 0
assert os.path.exists(output_path)
with open(output_path) as f:
content = f.read()
assert '<svg' in content
finally:
if os.path.exists(output_path):
os.unlink(output_path)

def test_cli_diagram_detailed():
"""Test diagram command with --detailed flag"""
result = subprocess.run([sys.executable, '-m', 'rexplain.cli.main', 'diagram', r'\d+', '--detailed'],
capture_output=True, text=True, cwd='/home/user/rexplain/src')
assert result.returncode == 0
assert '<svg' in result.stdout

def test_cli_invalid_pattern():
"""Test CLI with invalid regex pattern"""
result = subprocess.run([sys.executable, '-m', 'rexplain.cli.main', 'explain', '(unclosed'],
capture_output=True, text=True, cwd='/home/user/rexplain/src')
assert result.returncode == 1
assert 'error' in result.stderr.lower() or 'error' in result.stdout.lower()

def test_cli_no_command():
"""Test CLI with no command shows help"""
result = subprocess.run([sys.executable, '-m', 'rexplain.cli.main'],
capture_output=True, text=True, cwd='/home/user/rexplain/src')
assert result.returncode == 1
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Test Functions Use Hardcoded Absolute Paths

Multiple CLI test functions use a hardcoded absolute path, /home/user/rexplain/src, for the cwd parameter in subprocess.run() calls. This path is specific to a single development environment, causing tests to fail on other systems or CI/CD environments where the project isn't located there. test_cli_help() avoids this, suggesting an accidental commit of development-specific code.

Fix in Cursor Fix in Web

assert 'usage:' in result.stdout.lower() or 'usage:' in result.stderr.lower()
54 changes: 54 additions & 0 deletions tests/test_explainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,66 @@ def test_explain_quantifiers():
# Accept both the new and fallback output
assert r"\d{2,4} - matches a digit character 2 to 4 times" in result or r"\d{2,4}" in result or "digit character" in result

def test_explain_negated_charclass():
parser = RegexParser()
pattern = r'[^a-z]'
ast = parser.parse(pattern)
result = explain(ast)
print('Explanation:', result)
assert r"[^a-z]" in result or "negated" in result.lower() or "not" in result.lower()

def test_explain_word_boundary():
parser = RegexParser()
pattern = r'\bword\b'
ast = parser.parse(pattern)
result = explain(ast)
print('Explanation:', result)
assert r"\b" in result

def test_explain_dot_metachar():
parser = RegexParser()
pattern = r'a.b'
ast = parser.parse(pattern)
result = explain(ast)
print('Explanation:', result)
assert '.' in result

def test_explain_star_quantifier():
parser = RegexParser()
pattern = r'a*'
ast = parser.parse(pattern)
result = explain(ast)
print('Explanation:', result)
assert '*' in result or 'zero or more' in result.lower()

def test_explain_plus_quantifier():
parser = RegexParser()
pattern = r'a+'
ast = parser.parse(pattern)
result = explain(ast)
print('Explanation:', result)
assert '+' in result or 'one or more' in result.lower()

def test_explain_alternation():
parser = RegexParser()
pattern = r'foo|bar'
ast = parser.parse(pattern)
result = explain(ast)
print('Explanation:', result)
assert 'f' in result and 'b' in result

def main():
test_explain_basic()
test_explain_named_group()
test_explain_lookahead()
test_explain_inline_flags()
test_explain_quantifiers()
test_explain_negated_charclass()
test_explain_word_boundary()
test_explain_dot_metachar()
test_explain_star_quantifier()
test_explain_plus_quantifier()
test_explain_alternation()
print('All explainer tests passed!')

if __name__ == '__main__':
Expand Down
Loading
Loading