From 23c8828fe60346bd507c925c5313052782dd8285 Mon Sep 17 00:00:00 2001 From: Alexander Lisianoi Date: Wed, 14 Jun 2017 21:43:53 +0200 Subject: [PATCH 01/19] Change py36 to py361 due to a pyenv-virtualenv minor bug Refs: https://github.com/pyenv/pyenv-virtualenv/issues/206 Signed-off-by: Aleksandr Lisianoi --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 17a0a12..c5fc7ad 100644 --- a/tox.ini +++ b/tox.ini @@ -1,4 +1,4 @@ [tox] -envlist=py27,py36 +envlist=py27,py361 [testenv] commands=python setup.py test \ No newline at end of file From 9ef27de61063ca2b6e1708cf7a1d1330db5ca5f2 Mon Sep 17 00:00:00 2001 From: Alexander Lisianoi Date: Wed, 14 Jun 2017 21:51:07 +0200 Subject: [PATCH 02/19] Add pytest, pytest-cov and pytest-xdist Signed-off-by: Aleksandr Lisianoi --- test-requirements.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test-requirements.txt b/test-requirements.txt index a9be04b..e642cc0 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1 +1,4 @@ tox==2.7.0 +pytest==3.1.2 +pytest-cov==2.5.1 +pytest-xdist==1.17.1 From 5313d148f90bcc62a3032dc11aff8761bbb0fda3 Mon Sep 17 00:00:00 2001 From: Alexander Lisianoi Date: Wed, 14 Jun 2017 22:06:06 +0200 Subject: [PATCH 03/19] Change README.md to README.rst, adjust setup.py * Use "README.md as long_description" common practice * Fix license name: LICENSE is *simplified*, not *revised* BSD, see: https://en.wikipedia.org/wiki/BSD_licenses Signed-off-by: Aleksandr Lisianoi --- README.md | 69 --------------------------------------------- README.rst | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ setup.py | 25 +++------------- 3 files changed, 87 insertions(+), 90 deletions(-) delete mode 100644 README.md create mode 100644 README.rst diff --git a/README.md b/README.md deleted file mode 100644 index e37ac51..0000000 --- a/README.md +++ /dev/null @@ -1,69 +0,0 @@ -boolean.py -========== - -"boolean.py" is a small library implementing a boolean algebra. It -defines two base elements, TRUE and FALSE, and a Symbol class that can take -on one of these two values. Calculations are done in terms of AND, OR and -NOT - other compositions like XOR and NAND are not implemented but can be -emulated with AND or and NOT. -Expressions are constructed from parsed strings or in Python. - -It runs on Python 2.7 and Python 3. - -https://github.com/bastikr/boolean.py - -Build status: [![Build Status](https://travis-ci.org/bastikr/boolean.py.svg?branch=master)](https://travis-ci.org/bastikr/boolean.py) - -Example -------- -``` - >>> import boolean - >>> algebra = boolean.BooleanAlgebra() - >>> expression1 = algebra.parse(u'apple and (oranges or banana) and not banana', simplify=False) - >>> expression1 - AND(Symbol('apple'), OR(Symbol('oranges'), Symbol('banana')), NOT(Symbol('banana'))) - - >>> expression2 = algebra.parse(u'(oranges | banana) and not banana & apple', simplify=True) - >>> expression2 - AND(Symbol('apple'), NOT(Symbol('banana')), Symbol('oranges')) - - >>> expression1 == expression2 - False - >>> expression1.simplify() == expression2 - True -``` - -Documentation -------------- - -http://readthedocs.org/docs/booleanpy/en/latest/ - -Installation ------------- - -`pip install boolean.py` - -Testing -------- - -Test `boolean.py` with your current Python environment: - -`python setup.py test` - -Test with all of the supported Python environments using `tox`: - -``` -pip install -r test-requirements.txt -tox -``` - -If `tox` throws `InterpreterNotFound`, limit it to python interpreters that are actually installed on your machine: - -``` -tox -e py27,py36 -``` - -License -------- - -Simplified BSD License diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..5efc9f8 --- /dev/null +++ b/README.rst @@ -0,0 +1,83 @@ +========== +boolean.py +========== + +.. image:: https://img.shields.io/travis/bastikr/boolean.py.svg + :target: https://travis-ci.org/bastikr/boolean.py +.. image:: https://img.shields.io/pypi/wheel/boolean.py.svg + :target: https://pypi.python.org/pypi/boolean.py/ +.. image:: https://img.shields.io/pypi/v/boolean.py.svg + :target: https://pypi.python.org/pypi/boolean.py/ +.. image:: https://img.shields.io/pypi/pyversions/boolean.py.svg + :target: https://pypi.python.org/pypi/boolean.py/ +.. image:: https://img.shields.io/badge/license-BSD-blue.svg + :target: https://raw.githubusercontent.com/bastikr/boolean.py/master/LICENSE.txt + +This python package implements `Boolean algebra`_. It defines two base elements, +:code:`TRUE` and :code:`FALSE`, and a :code:`Symbol` class. Expressions are +built in terms of :code:`AND`, :code:`OR` and :code:`NOT`. Other functions, like +:code:`XOR` and :code:`NAND`, are not implemented but can be emulated with +:code:`AND` or and :code:`NOT`. Expressions are constructed from parsed strings +or in Python. + +.. _`Boolean algebra`: https://en.wikipedia.org/wiki/Boolean_algebra + +Example +======= + +.. code-block:: python + + >>> import boolean + >>> algebra = boolean.BooleanAlgebra() + >>> expression1 = algebra.parse(u'apple and (oranges or banana) and not banana', simplify=False) + >>> expression1 + AND(Symbol('apple'), OR(Symbol('oranges'), Symbol('banana')), NOT(Symbol('banana'))) + + >>> expression2 = algebra.parse(u'(oranges | banana) and not banana & apple', simplify=True) + >>> expression2 + AND(Symbol('apple'), NOT(Symbol('banana')), Symbol('oranges')) + + >>> expression1 == expression2 + False + >>> expression1.simplify() == expression2 + True + +Documentation +============= + +http://readthedocs.org/docs/booleanpy/en/latest/ + +Installation +============ + +.. code-block:: shell + + pip install boolean.py + +Testing +======= + +Test :code:`boolean.py` with your current Python environment: + +.. code-block:: shell + + python setup.py test + +Test with all of the supported Python environments using :code:`tox`: + +.. code-block:: shell + + pip install -r test-requirements.txt + tox + +If :code:`tox` throws :code:`InterpreterNotFound`, limit it to python +interpreters that are actually installed on your machine: + +.. code-block:: shell + + tox -e py27,py36 + +License +======= + +Simplified BSD License diff --git a/setup.py b/setup.py index 371b9f3..f316b5c 100644 --- a/setup.py +++ b/setup.py @@ -5,32 +5,15 @@ from setuptools import find_packages from setuptools import setup - -long_desc = ''' -This library helps you deal with boolean expressions and algebra with variables -and the boolean functions AND, OR, NOT. - -You can parse expressions from strings and simplify and compare expressions. -You can also easily create your custom algreba and mini DSL and create custom -tokenizers to handle custom expressions. - -For extensive documentation look either into the docs directory or view it online, at -https://booleanpy.readthedocs.org/en/latest/ - -https://github.com/bastikr/boolean.py - -Copyright (c) 2009-2017 Sebastian Kraemer, basti.kr@gmail.com and others - -Released under revised BSD license. -''' - +with open('README.rst') as readme: + long_description = readme.read() setup( name='boolean.py', version='3.4', - license='revised BSD license', + license='Simplified BSD license', description='Define boolean algebras, create and parse boolean expressions and create custom boolean DSL.', - long_description=long_desc, + long_description=long_description, author='Sebastian Kraemer', author_email='basti.kr@gmail.com', url='https://github.com/bastikr/boolean.py', From ce41fb3d9a6e3563767c258c738605736779fc4d Mon Sep 17 00:00:00 2001 From: Alexander Lisianoi Date: Sat, 17 Jun 2017 12:30:14 +0200 Subject: [PATCH 04/19] Factor test_boolean.py out into new tests directory Signed-off-by: Aleksandr Lisianoi --- MANIFEST.in | 1 + setup.py | 2 +- tests/__init__.py | 0 {boolean => tests}/test_boolean.py | 1 + 4 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 tests/__init__.py rename {boolean => tests}/test_boolean.py (99%) diff --git a/MANIFEST.in b/MANIFEST.in index dae87d1..75c64dd 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,5 @@ graft docs +graft tests graft boolean include LICENSE.txt diff --git a/setup.py b/setup.py index f316b5c..7d1fa6b 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ include_package_data=True, zip_safe=False, test_loader='unittest:TestLoader', - test_suite='boolean.test_boolean', + test_suite='tests.test_boolean', keywords='boolean expression, boolean algebra, logic, expression parser', classifiers=[ 'Development Status :: 4 - Beta', diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/boolean/test_boolean.py b/tests/test_boolean.py similarity index 99% rename from boolean/test_boolean.py rename to tests/test_boolean.py index fa9fbf8..2e3cb23 100644 --- a/boolean/test_boolean.py +++ b/tests/test_boolean.py @@ -10,6 +10,7 @@ from __future__ import absolute_import from __future__ import unicode_literals from __future__ import print_function + from boolean.boolean import PARSE_UNKNOWN_TOKEN try: From 46a60c5900bc12bf580dda5ae6f35a0e87bc725c Mon Sep 17 00:00:00 2001 From: Alexander Lisianoi Date: Sat, 17 Jun 2017 12:41:47 +0200 Subject: [PATCH 05/19] Make `python setup.py test` use pytest Use link below to verify setup: https://docs.pytest.org/en/latest/goodpractices.html Signed-off-by: Aleksandr Lisianoi --- setup.cfg | 1 + setup.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 49eae28..d037fcc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,6 +2,7 @@ universal = 1 [aliases] +test = pytest release = clean --all sdist --formats=gztar bdist_wheel register upload [metadata] diff --git a/setup.py b/setup.py index 7d1fa6b..3cedbc2 100644 --- a/setup.py +++ b/setup.py @@ -20,8 +20,8 @@ packages=find_packages(), include_package_data=True, zip_safe=False, - test_loader='unittest:TestLoader', - test_suite='tests.test_boolean', + setup_requires=['pytest-runner'], + tests_require=['pytest'], keywords='boolean expression, boolean algebra, logic, expression parser', classifiers=[ 'Development Status :: 4 - Beta', From 00200eadf5a8495ef769cf57c05191de47de0fce Mon Sep 17 00:00:00 2001 From: Alexander Lisianoi Date: Sat, 17 Jun 2017 13:12:06 +0200 Subject: [PATCH 06/19] Move AdvancedAlgebra into separate mock module Signed-off-by: Aleksandr Lisianoi --- tests/mock_advanced_algebra.py | 116 +++++++++++++++++++++++++++++++++ tests/test_boolean.py | 113 +------------------------------- 2 files changed, 118 insertions(+), 111 deletions(-) create mode 100644 tests/mock_advanced_algebra.py diff --git a/tests/mock_advanced_algebra.py b/tests/mock_advanced_algebra.py new file mode 100644 index 0000000..67b6963 --- /dev/null +++ b/tests/mock_advanced_algebra.py @@ -0,0 +1,116 @@ +import tokenize + +try: + # Python 2 + basestring +except NameError: + # Python 3 + basestring = str + +try: + from io import StringIO +except ImportError: + try: + from cStringIO import StringIO + except ImportError: + from StringIO import StringIO + +from boolean import BooleanAlgebra, Symbol +from boolean import TOKEN_LPAR, TOKEN_RPAR +from boolean import TOKEN_TRUE, TOKEN_FALSE +from boolean import TOKEN_AND, TOKEN_OR, TOKEN_NOT + +class PlainVar(Symbol): + "Plain boolean variable" + +class ColonDotVar(Symbol): + "Colon and dot-separated string boolean variable" + +class AdvancedAlgebra(BooleanAlgebra): + def tokenize(self, expr): + """ + Example custom tokenizer derived from the standard Python tokenizer + with a few extra features: #-style comments are supported and a + colon- and dot-separated string is recognized and stored in custom + symbols. In contrast with the standard tokenizer, only these + boolean operators are recognized : & | ! and or not. + + For more advanced tokenization you could also consider forking the + `tokenize` standard library module. + """ + + if not isinstance(expr, basestring): + raise TypeError('expr must be string but it is %s.' % type(expr)) + + # mapping of lowercase token strings to a token object instance for + # standard operators, parens and common true or false symbols + TOKENS = { + '&': TOKEN_AND, + 'and': TOKEN_AND, + '|': TOKEN_OR, + 'or': TOKEN_OR, + '!': TOKEN_NOT, + 'not': TOKEN_NOT, + '(': TOKEN_LPAR, + ')': TOKEN_RPAR, + 'true': TOKEN_TRUE, + '1': TOKEN_TRUE, + 'false': TOKEN_FALSE, + '0': TOKEN_FALSE, + 'none': TOKEN_FALSE, + } + + ignored_token_types = ( + tokenize.NL, tokenize.NEWLINE, tokenize.COMMENT, + tokenize.INDENT, tokenize.DEDENT, + tokenize.ENDMARKER + ) + + # note: an unbalanced expression may raise a TokenError here. + tokens = ((toktype, tok, row, col,) for toktype, tok, (row, col,), _, _ + in tokenize.generate_tokens(StringIO(expr).readline) + if tok and tok.strip()) + + COLON_DOT = (':', '.',) + + def build_symbol(current_dotted): + if current_dotted: + if any(s in current_dotted for s in COLON_DOT): + sym = ColonDotVar(current_dotted) + else: + sym = PlainVar(current_dotted) + return sym + + # accumulator for dotted symbols that span several `tokenize` tokens + dotted, srow, scol = '', None, None + + for toktype, tok, row, col in tokens: + if toktype in ignored_token_types: + # we reached a break point and should yield the current dotted + symbol = build_symbol(dotted) + if symbol is not None: + yield symbol, dotted, (srow, scol) + dotted, srow, scol = '', None, None + + continue + + std_token = TOKENS.get(tok.lower()) + if std_token is not None: + # we reached a break point and should yield the current dotted + symbol = build_symbol(dotted) + if symbol is not None: + yield symbol, dotted, (srow, scol) + dotted, srow, scol = '', 0, 0 + + yield std_token, tok, (row, col) + + continue + + if toktype == tokenize.NAME or (toktype == tokenize.OP and tok in COLON_DOT): + if not dotted: + srow = row + scol = col + dotted += tok + + else: + raise TypeError('Unknown token: %(tok)r at line: %(row)r, column: %(col)r' % locals()) diff --git a/tests/test_boolean.py b/tests/test_boolean.py index 2e3cb23..614f1f4 100644 --- a/tests/test_boolean.py +++ b/tests/test_boolean.py @@ -13,11 +13,6 @@ from boolean.boolean import PARSE_UNKNOWN_TOKEN -try: - basestring # Python 2 -except NameError: - basestring = str # Python 3 - import unittest from unittest.case import expectedFailure @@ -36,6 +31,8 @@ from boolean.boolean import PARSE_INVALID_EXPRESSION from boolean.boolean import PARSE_INVALID_NESTING +from tests.mock_advanced_algebra import AdvancedAlgebra +from tests.mock_advanced_algebra import PlainVar, ColonDotVar class BooleanAlgebraTestCase(unittest.TestCase): @@ -146,112 +143,6 @@ def tokenize(self, s): self.assertEqual(expected, expr) def test_parse_with_advanced_tokenizer_example(self): - import tokenize - - try: - from io import StringIO - except ImportError: - try: - from cStringIO import StringIO - except ImportError: - from StringIO import StringIO - - - class PlainVar(Symbol): - "Plain boolean variable" - - class ColonDotVar(Symbol): - "Colon and dot-separated string boolean variable" - - class AdvancedAlgebra(BooleanAlgebra): - def tokenize(self, expr): - """ - Example custom tokenizer derived from the standard Python tokenizer - with a few extra features: #-style comments are supported and a - colon- and dot-separated string is recognized and stored in custom - symbols. In contrast with the standard tokenizer, only these - boolean operators are recognized : & | ! and or not. - - For more advanced tokenization you could also consider forking the - `tokenize` standard library module. - """ - - if not isinstance(expr, basestring): - raise TypeError('expr must be string but it is %s.' % type(expr)) - - # mapping of lowercase token strings to a token object instance for - # standard operators, parens and common true or false symbols - TOKENS = { - '&': TOKEN_AND, - 'and': TOKEN_AND, - '|': TOKEN_OR, - 'or': TOKEN_OR, - '!': TOKEN_NOT, - 'not': TOKEN_NOT, - '(': TOKEN_LPAR, - ')': TOKEN_RPAR, - 'true': TOKEN_TRUE, - '1': TOKEN_TRUE, - 'false': TOKEN_FALSE, - '0': TOKEN_FALSE, - 'none': TOKEN_FALSE, - } - - ignored_token_types = ( - tokenize.NL, tokenize.NEWLINE, tokenize.COMMENT, - tokenize.INDENT, tokenize.DEDENT, - tokenize.ENDMARKER - ) - - # note: an unbalanced expression may raise a TokenError here. - tokens = ((toktype, tok, row, col,) for toktype, tok, (row, col,), _, _ - in tokenize.generate_tokens(StringIO(expr).readline) - if tok and tok.strip()) - - COLON_DOT = (':', '.',) - - def build_symbol(current_dotted): - if current_dotted: - if any(s in current_dotted for s in COLON_DOT): - sym = ColonDotVar(current_dotted) - else: - sym = PlainVar(current_dotted) - return sym - - # accumulator for dotted symbols that span several `tokenize` tokens - dotted, srow, scol = '', None, None - - for toktype, tok, row, col in tokens: - if toktype in ignored_token_types: - # we reached a break point and should yield the current dotted - symbol = build_symbol(dotted) - if symbol is not None: - yield symbol, dotted, (srow, scol) - dotted, srow, scol = '', None, None - - continue - - std_token = TOKENS.get(tok.lower()) - if std_token is not None: - # we reached a break point and should yield the current dotted - symbol = build_symbol(dotted) - if symbol is not None: - yield symbol, dotted, (srow, scol) - dotted, srow, scol = '', 0, 0 - - yield std_token, tok, (row, col) - - continue - - if toktype == tokenize.NAME or (toktype == tokenize.OP and tok in COLON_DOT): - if not dotted: - srow = row - scol = col - dotted += tok - - else: - raise TypeError('Unknown token: %(tok)r at line: %(row)r, column: %(col)r' % locals()) - test_expr = ''' (colon1:dot1.dot2 or colon2_name:col_on3:do_t1.do_t2.do_t3 ) and From 5f50736f3a1ee11077c3444c904e6f5417f3845b Mon Sep 17 00:00:00 2001 From: Alexander Lisianoi Date: Sat, 17 Jun 2017 13:18:37 +0200 Subject: [PATCH 07/19] Move CustomAlgebra into separate mock module Signed-off-by: Aleksandr Lisianoi --- tests/mock_custom_algebra.py | 31 +++++++++++++++++++++++++++++++ tests/test_boolean.py | 29 ++--------------------------- 2 files changed, 33 insertions(+), 27 deletions(-) create mode 100644 tests/mock_custom_algebra.py diff --git a/tests/mock_custom_algebra.py b/tests/mock_custom_algebra.py new file mode 100644 index 0000000..c6ac644 --- /dev/null +++ b/tests/mock_custom_algebra.py @@ -0,0 +1,31 @@ +from boolean import BooleanAlgebra, Symbol + +from boolean import TOKEN_SYMBOL +from boolean import TOKEN_LPAR, TOKEN_RPAR +from boolean import TOKEN_AND, TOKEN_OR, TOKEN_NOT + +class CustomSymbol(Symbol): + pass + +class CustomAlgebra(BooleanAlgebra): + def __init__(self, Symbol_class=CustomSymbol): + super(CustomAlgebra, self).__init__(Symbol_class=CustomSymbol) + + def tokenize(self, s): + "Sample tokenizer using custom operators and symbols" + ops = { + 'WHY_NOT': TOKEN_OR, + 'ALSO': TOKEN_AND, + 'NEITHER': TOKEN_NOT, + '(': TOKEN_LPAR, + ')': TOKEN_RPAR, + } + + for row, line in enumerate(s.splitlines(False)): + for col, tok in enumerate(line.split()): + if tok in ops: + yield ops[tok], tok, (row, col) + elif tok == 'Custom': + yield self.Symbol(tok), tok, (row, col) + else: + yield TOKEN_SYMBOL, tok, (row, col) diff --git a/tests/test_boolean.py b/tests/test_boolean.py index 614f1f4..316aa4a 100644 --- a/tests/test_boolean.py +++ b/tests/test_boolean.py @@ -31,6 +31,8 @@ from boolean.boolean import PARSE_INVALID_EXPRESSION from boolean.boolean import PARSE_INVALID_NESTING +from tests.mock_custom_algebra import CustomAlgebra + from tests.mock_advanced_algebra import AdvancedAlgebra from tests.mock_advanced_algebra import PlainVar, ColonDotVar @@ -95,33 +97,6 @@ def test_parse_recognizes_trueish_and_falsish_symbol_tokens(self): self.assertEqual(expected, expr) def test_parse_can_use_iterable_from_alternative_tokenizer(self): - - class CustomSymbol(Symbol): - pass - - class CustomAlgebra(BooleanAlgebra): - def __init__(self, Symbol_class=CustomSymbol): - super(CustomAlgebra, self).__init__(Symbol_class=CustomSymbol) - - def tokenize(self, s): - "Sample tokenizer using custom operators and symbols" - ops = { - 'WHY_NOT': TOKEN_OR, - 'ALSO': TOKEN_AND, - 'NEITHER': TOKEN_NOT, - '(': TOKEN_LPAR, - ')': TOKEN_RPAR, - } - - for row, line in enumerate(s.splitlines(False)): - for col, tok in enumerate(line.split()): - if tok in ops: - yield ops[tok], tok, (row, col) - elif tok == 'Custom': - yield self.Symbol(tok), tok, (row, col) - else: - yield TOKEN_SYMBOL, tok, (row, col) - expr_str = '''( Custom WHY_NOT regular ) ALSO NEITHER ( not_custom ALSO standard ) ''' From 7f8b0373bc7f84e0c2358ed2452696047264c803 Mon Sep 17 00:00:00 2001 From: Alexander Lisianoi Date: Sat, 17 Jun 2017 13:25:40 +0200 Subject: [PATCH 08/19] Remove misleading test_init in SymbolTestCase Signed-off-by: Aleksandr Lisianoi --- tests/test_boolean.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/test_boolean.py b/tests/test_boolean.py index 316aa4a..a13b981 100644 --- a/tests/test_boolean.py +++ b/tests/test_boolean.py @@ -249,14 +249,6 @@ def test_printing(self): class SymbolTestCase(unittest.TestCase): - def test_init(self): - Symbol(1) - Symbol('a') - Symbol(None) - Symbol(sum) - Symbol((1, 2, 3)) - Symbol([1, 2]) - def test_isliteral(self): self.assertTrue(Symbol(1).isliteral is True) From 39c2c7af2d0c3fd04d10f72a04a88adffbff63a3 Mon Sep 17 00:00:00 2001 From: Alexander Lisianoi Date: Sat, 17 Jun 2017 13:57:18 +0200 Subject: [PATCH 09/19] Improve Symbol tests for __eq__ and __ne__ Signed-off-by: Aleksandr Lisianoi --- tests/test_boolean.py | 113 +++++++++++++++++++++++++++++++++++------- 1 file changed, 96 insertions(+), 17 deletions(-) diff --git a/tests/test_boolean.py b/tests/test_boolean.py index a13b981..b810809 100644 --- a/tests/test_boolean.py +++ b/tests/test_boolean.py @@ -11,6 +11,8 @@ from __future__ import unicode_literals from __future__ import print_function +import copy + from boolean.boolean import PARSE_UNKNOWN_TOKEN import unittest @@ -269,28 +271,105 @@ def test_simplify(self): s = Symbol(1) self.assertEqual(s.simplify(), s) - def test_equal_symbols(self): + def test_symbols_eq_0(self): algebra = BooleanAlgebra() - a = algebra.Symbol('a') - a2 = algebra.Symbol('a') - c = algebra.Symbol('b') - d = algebra.Symbol('d') - e = algebra.Symbol('e') + a = algebra.Symbol('a') - # Test __eq__. self.assertTrue(a == a) - self.assertTrue(a == a2) - self.assertFalse(a == c) - self.assertFalse(a2 == c) - self.assertTrue(d == d) - self.assertFalse(d == e) - self.assertFalse(a == d) - # Test __ne__. + + def test_symbols_eq_1(self): + algebra = BooleanAlgebra() + + a0 = algebra.Symbol('a') + a1 = algebra.Symbol('a') + + self.assertTrue(a0 == a1) + self.assertTrue(a1 == a0) + + def test_symbols_eq_2(self): + algebra = BooleanAlgebra() + + a = algebra.Symbol('a') + b = algebra.Symbol('b') + + self.assertFalse(a == b) + self.assertFalse(b == a) + + def test_symbols_eq_3(self): + algebra = BooleanAlgebra() + + self.assertTrue(algebra.Symbol('a') == algebra.Symbol('a')) + self.assertFalse(algebra.Symbol('a') == algebra.Symbol('b')) + + def test_symbols_ne_0(self): + algebra = BooleanAlgebra() + + a = algebra.Symbol('a') + self.assertFalse(a != a) - self.assertFalse(a != a2) - self.assertTrue(a != c) - self.assertTrue(a2 != c) + + def test_symbols_ne_1(self): + algebra = BooleanAlgebra() + + a0 = algebra.Symbol('a') + a1 = algebra.Symbol('a') + + self.assertFalse(a0 != a1) + self.assertFalse(a1 != a0) + + def test_symbols_ne_2(self): + algebra = BooleanAlgebra() + + a = algebra.Symbol('a') + b = algebra.Symbol('b') + + self.assertTrue(a != b) + self.assertTrue(b != a) + + def test_symbols_ne_3(self): + algebra = BooleanAlgebra() + + self.assertFalse(algebra.Symbol('a') != algebra.Symbol('a')) + self.assertTrue(algebra.Symbol('a') != algebra.Symbol('b')) + + def test_symbols_eq_ne(self): + algebra = BooleanAlgebra() + + symbols0 = [ + algebra.Symbol('knights'), + algebra.Symbol('who'), + algebra.Symbol('say'), + algebra.Symbol('ni!'), + algebra.Symbol('Beautiful is better than ugly.'), + algebra.Symbol('Explicit is better than implicit.'), + algebra.Symbol('0'), + algebra.Symbol('1'), + algebra.Symbol('^'), + algebra.Symbol('123'), + algebra.Symbol('!!!'), + ] + + symbols1 = copy.deepcopy(symbols0) + + for symbol in symbols0: + self.assertTrue(symbol == symbol) + self.assertFalse(symbol != symbol) + + for symbol0, symbol1 in zip(symbols0, symbols1): + self.assertTrue(symbol0 == symbol1) + self.assertTrue(symbol1 == symbol0) + + self.assertFalse(symbol0 != symbol1) + self.assertFalse(symbol1 != symbol0) + + for i in range(len(symbols0)): + for j in range(i + 1, len(symbols0)): + self.assertFalse(symbols0[i] == symbols1[j]) + self.assertFalse(symbols1[j] == symbols0[i]) + + self.assertTrue(symbols0[i] != symbols1[j]) + self.assertTrue(symbols1[j] != symbols0[i]) def test_order(self): S = Symbol From 7deb053c8f4846afa474ec9b7c18baa2c8c7cd04 Mon Sep 17 00:00:00 2001 From: Alexander Lisianoi Date: Wed, 21 Jun 2017 09:53:31 +0200 Subject: [PATCH 10/19] Convert SymbolTestCase to pytest TestSymbol Signed-off-by: Aleksandr Lisianoi --- tests/test_boolean.py | 100 ++++++++++++++++++++++++------------------ 1 file changed, 58 insertions(+), 42 deletions(-) diff --git a/tests/test_boolean.py b/tests/test_boolean.py index b810809..42e89ac 100644 --- a/tests/test_boolean.py +++ b/tests/test_boolean.py @@ -12,6 +12,7 @@ from __future__ import print_function import copy +import pytest from boolean.boolean import PARSE_UNKNOWN_TOKEN @@ -249,34 +250,40 @@ def test_printing(self): self.assertEqual(repr(algebra.FALSE), 'FALSE') -class SymbolTestCase(unittest.TestCase): +class TestSymbolCase: def test_isliteral(self): - self.assertTrue(Symbol(1).isliteral is True) + assert Symbol(1).isliteral is True def test_literals(self): l1 = Symbol(1) l2 = Symbol(1) - self.assertTrue(l1 in l1.literals) - self.assertTrue(l1 in l2.literals) - self.assertTrue(l2 in l1.literals) - self.assertTrue(l2 in l2.literals) - self.assertRaises(AttributeError, setattr, l1, 'literals', 1) + + assert l1 in l1.literals + assert l1 in l2.literals + assert l2 in l1.literals + assert l2 in l2.literals + + for symbol in [l1, l2]: + with pytest.raises(AttributeError): + symbol.setattr('literals', 1) def test_literalize(self): s = Symbol(1) - self.assertEqual(s.literalize(), s) + + assert s.literalize() == s def test_simplify(self): s = Symbol(1) - self.assertEqual(s.simplify(), s) + + assert s.simplify() == s def test_symbols_eq_0(self): algebra = BooleanAlgebra() a = algebra.Symbol('a') - self.assertTrue(a == a) + assert a == a def test_symbols_eq_1(self): algebra = BooleanAlgebra() @@ -284,8 +291,8 @@ def test_symbols_eq_1(self): a0 = algebra.Symbol('a') a1 = algebra.Symbol('a') - self.assertTrue(a0 == a1) - self.assertTrue(a1 == a0) + assert a0 == a1 + assert a1 == a0 def test_symbols_eq_2(self): algebra = BooleanAlgebra() @@ -293,21 +300,21 @@ def test_symbols_eq_2(self): a = algebra.Symbol('a') b = algebra.Symbol('b') - self.assertFalse(a == b) - self.assertFalse(b == a) + assert not a == b + assert not b == a def test_symbols_eq_3(self): algebra = BooleanAlgebra() - self.assertTrue(algebra.Symbol('a') == algebra.Symbol('a')) - self.assertFalse(algebra.Symbol('a') == algebra.Symbol('b')) + assert algebra.Symbol('a') == algebra.Symbol('a') + assert not algebra.Symbol('a') == algebra.Symbol('b') def test_symbols_ne_0(self): algebra = BooleanAlgebra() a = algebra.Symbol('a') - self.assertFalse(a != a) + assert not a != a def test_symbols_ne_1(self): algebra = BooleanAlgebra() @@ -315,8 +322,8 @@ def test_symbols_ne_1(self): a0 = algebra.Symbol('a') a1 = algebra.Symbol('a') - self.assertFalse(a0 != a1) - self.assertFalse(a1 != a0) + assert not a0 != a1 + assert not a1 != a0 def test_symbols_ne_2(self): algebra = BooleanAlgebra() @@ -324,14 +331,14 @@ def test_symbols_ne_2(self): a = algebra.Symbol('a') b = algebra.Symbol('b') - self.assertTrue(a != b) - self.assertTrue(b != a) + assert a != b + assert b != a def test_symbols_ne_3(self): algebra = BooleanAlgebra() - self.assertFalse(algebra.Symbol('a') != algebra.Symbol('a')) - self.assertTrue(algebra.Symbol('a') != algebra.Symbol('b')) + assert not algebra.Symbol('a') != algebra.Symbol('a') + assert algebra.Symbol('a') != algebra.Symbol('b') def test_symbols_eq_ne(self): algebra = BooleanAlgebra() @@ -346,6 +353,9 @@ def test_symbols_eq_ne(self): algebra.Symbol('0'), algebra.Symbol('1'), algebra.Symbol('^'), + algebra.Symbol(-1), + algebra.Symbol(0), + algebra.Symbol(1), algebra.Symbol('123'), algebra.Symbol('!!!'), ] @@ -353,37 +363,43 @@ def test_symbols_eq_ne(self): symbols1 = copy.deepcopy(symbols0) for symbol in symbols0: - self.assertTrue(symbol == symbol) - self.assertFalse(symbol != symbol) + assert symbol == symbol + assert not symbol != symbol for symbol0, symbol1 in zip(symbols0, symbols1): - self.assertTrue(symbol0 == symbol1) - self.assertTrue(symbol1 == symbol0) + assert symbol0 == symbol1 + assert symbol1 == symbol0 - self.assertFalse(symbol0 != symbol1) - self.assertFalse(symbol1 != symbol0) + assert not symbol0 != symbol1 + assert not symbol1 != symbol0 for i in range(len(symbols0)): for j in range(i + 1, len(symbols0)): - self.assertFalse(symbols0[i] == symbols1[j]) - self.assertFalse(symbols1[j] == symbols0[i]) + assert not symbols0[i] == symbols1[j] + assert not symbols1[j] == symbols0[i] - self.assertTrue(symbols0[i] != symbols1[j]) - self.assertTrue(symbols1[j] != symbols0[i]) + assert symbols0[i] != symbols1[j] + assert symbols1[j] != symbols0[i] def test_order(self): - S = Symbol - self.assertTrue(S('x') < S('y')) - self.assertTrue(S('y') > S('x')) - self.assertTrue(S(1) < S(2)) - self.assertTrue(S(2) > S(1)) + assert Symbol(-1) < Symbol(0) + assert Symbol(0) > Symbol(-1) + + assert Symbol(1) < Symbol(2) + assert Symbol(2) > Symbol(1) + + assert Symbol('x') < Symbol('y') + assert Symbol('y') > Symbol('x') def test_printing(self): - self.assertEqual('a', str(Symbol('a'))) - self.assertEqual('1', str(Symbol(1))) - self.assertEqual("Symbol('a')", repr(Symbol('a'))) - self.assertEqual('Symbol(1)', repr(Symbol(1))) + assert 'a' == str(Symbol('a')) + assert "Symbol('a')" == repr(Symbol('a')) + + assert '1' == str(Symbol(1)) + assert 'Symbol(1)' == repr(Symbol(1)) + assert '-1' == str(Symbol(-1)) + assert 'Symbol(-1)' == repr(Symbol(-1)) class NOTTestCase(unittest.TestCase): From d4717e6763c983ab41d879a9585992b9a6b1a39a Mon Sep 17 00:00:00 2001 From: Alexander Lisianoi Date: Wed, 21 Jun 2017 10:40:16 +0200 Subject: [PATCH 11/19] Convert BaseElementTestCase to TestBaseElement, use pytest Signed-off-by: Aleksandr Lisianoi --- tests/test_boolean.py | 88 ++++++++++++++++++++++++++++--------------- 1 file changed, 57 insertions(+), 31 deletions(-) diff --git a/tests/test_boolean.py b/tests/test_boolean.py index 42e89ac..6897881 100644 --- a/tests/test_boolean.py +++ b/tests/test_boolean.py @@ -14,6 +14,7 @@ import copy import pytest +from boolean.boolean import BaseElement from boolean.boolean import PARSE_UNKNOWN_TOKEN import unittest @@ -194,61 +195,86 @@ def test_parse_side_by_side_symbols_with_parens_raise_exception(self): except ParseError as pe: assert pe.error_code == PARSE_INVALID_NESTING -class BaseElementTestCase(unittest.TestCase): +class TestBaseElement: - def test_creation(self): - from boolean.boolean import BaseElement + def test_base_element_raises(self): + with pytest.raises(TypeError): + BaseElement(2) + + with pytest.raises(TypeError): + BaseElement('a') + + def test_true_and_false(self): algebra = BooleanAlgebra() - self.assertEqual(algebra.TRUE, algebra.TRUE) - BaseElement() - self.assertRaises(TypeError, BaseElement, 2) - self.assertRaises(TypeError, BaseElement, 'a') - self.assertTrue(algebra.TRUE is algebra.TRUE) - self.assertTrue(algebra.TRUE is not algebra.FALSE) - self.assertTrue(algebra.FALSE is algebra.FALSE) - self.assertTrue(bool(algebra.TRUE) is True) - self.assertTrue(bool(algebra.FALSE) is False) - self.assertEqual(algebra.TRUE, True) - self.assertEqual(algebra.FALSE, False) + + assert algebra.TRUE is algebra.TRUE + assert algebra.FALSE is algebra.FALSE + + assert algebra.TRUE == algebra.TRUE + assert algebra.FALSE == algebra.FALSE + + assert algebra.TRUE is not algebra.FALSE + assert algebra.FALSE is not algebra.TRUE + + assert algebra.TRUE != algebra.FALSE + assert algebra.FALSE != algebra.TRUE + + assert bool(algebra.TRUE) is True + assert bool(algebra.FALSE) is False + + assert algebra.TRUE == True + assert algebra.FALSE == False def test_literals(self): algebra = BooleanAlgebra() - self.assertEqual(algebra.TRUE.literals, set()) - self.assertEqual(algebra.FALSE.literals, set()) + + assert algebra.TRUE.literals == set() + assert algebra.FALSE.literals == set() def test_literalize(self): algebra = BooleanAlgebra() - self.assertEqual(algebra.TRUE.literalize(), algebra.TRUE) - self.assertEqual(algebra.FALSE.literalize(), algebra.FALSE) + + assert algebra.TRUE.literalize() == algebra.TRUE + assert algebra.FALSE.literalize() == algebra.FALSE + + assert algebra.TRUE.literalize() != algebra.FALSE + assert algebra.FALSE.literalize() != algebra.TRUE def test_simplify(self): algebra = BooleanAlgebra() - self.assertEqual(algebra.TRUE.simplify(), algebra.TRUE) - self.assertEqual(algebra.FALSE.simplify(), algebra.FALSE) + + assert algebra.TRUE.simplify() == algebra.TRUE + assert algebra.FALSE.simplify() == algebra.FALSE def test_dual(self): algebra = BooleanAlgebra() - self.assertEqual(algebra.TRUE.dual, algebra.FALSE) - self.assertEqual(algebra.FALSE.dual, algebra.TRUE) + + assert algebra.TRUE.dual == algebra.FALSE + assert algebra.FALSE.dual == algebra.TRUE def test_equality(self): algebra = BooleanAlgebra() - self.assertEqual(algebra.TRUE, algebra.TRUE) - self.assertEqual(algebra.FALSE, algebra.FALSE) - self.assertNotEqual(algebra.TRUE, algebra.FALSE) + + assert algebra.TRUE == algebra.TRUE + assert algebra.FALSE == algebra.FALSE + + assert algebra.TRUE != algebra.FALSE + assert not algebra.TRUE == algebra.FALSE def test_order(self): algebra = BooleanAlgebra() - self.assertTrue(algebra.FALSE < algebra.TRUE) - self.assertTrue(algebra.TRUE > algebra.FALSE) + + assert algebra.FALSE < algebra.TRUE + assert algebra.TRUE > algebra.FALSE def test_printing(self): algebra = BooleanAlgebra() - self.assertEqual(str(algebra.TRUE), '1') - self.assertEqual(str(algebra.FALSE), '0') - self.assertEqual(repr(algebra.TRUE), 'TRUE') - self.assertEqual(repr(algebra.FALSE), 'FALSE') + assert str(algebra.TRUE) == '1' + assert str(algebra.FALSE) == '0' + + assert repr(algebra.TRUE) == 'TRUE' + assert repr(algebra.FALSE) == 'FALSE' class TestSymbolCase: From 0cdbfee0a539aa16d8875347532049e7e07ffcc9 Mon Sep 17 00:00:00 2001 From: Alexander Lisianoi Date: Wed, 21 Jun 2017 10:45:22 +0200 Subject: [PATCH 12/19] Convert NOTTestCase to TestNOT, use pytest Signed-off-by: Aleksandr Lisianoi --- tests/test_boolean.py | 166 +++++++++++++++++++++++++++++------------- 1 file changed, 116 insertions(+), 50 deletions(-) diff --git a/tests/test_boolean.py b/tests/test_boolean.py index 6897881..80be197 100644 --- a/tests/test_boolean.py +++ b/tests/test_boolean.py @@ -427,90 +427,156 @@ def test_printing(self): assert '-1' == str(Symbol(-1)) assert 'Symbol(-1)' == repr(Symbol(-1)) -class NOTTestCase(unittest.TestCase): +class TestNOT: - def test_init(self): + def test_raises(self): algebra = BooleanAlgebra() - self.assertRaises(TypeError, algebra.NOT) - self.assertRaises(TypeError, algebra.NOT, 'a', 'b') - algebra.NOT(algebra.Symbol('a')) - self.assertEqual(algebra.FALSE, (algebra.NOT(algebra.TRUE)).simplify()) - self.assertEqual(algebra.TRUE, (algebra.NOT(algebra.FALSE)).simplify()) + + with pytest.raises(TypeError): + algebra.NOT() + + with pytest.raises(TypeError): + algebra.NOT('a', 'b') + + def test_true_and_false(self): + algebra = BooleanAlgebra() + + assert algebra.TRUE is (algebra.NOT(algebra.FALSE)).simplify() + assert algebra.FALSE is (algebra.NOT(algebra.TRUE)).simplify() + + assert algebra.TRUE == (algebra.NOT(algebra.FALSE)).simplify() + assert algebra.FALSE == (algebra.NOT(algebra.TRUE)).simplify() def test_isliteral(self): algebra = BooleanAlgebra() + s = algebra.Symbol(1) - self.assertTrue(algebra.NOT(s).isliteral) - self.assertFalse(algebra.parse('~(a|b)').isliteral) - def test_literals(self): + # negation of a literal is still a literal + assert algebra.NOT(s).isliteral + # negation of a non-literal is still a non-literal + assert not algebra.parse('~(a|b)').isliteral + + def test_literals_0(self): algebra = BooleanAlgebra() + a = algebra.Symbol('a') - l = ~a - self.assertTrue(l.isliteral) - self.assertTrue(l in l.literals) - self.assertEqual(len(l.literals), 1) + b = ~a + + assert a.isliteral + assert b.isliteral + + assert a in a.literals + assert b in b.literals + + assert len(a.literals) == 1 + assert len(b.literals) == 1 + + def test_literals_1(self): + algebra = BooleanAlgebra() + + expression = algebra.parse('~(a&a)') - l = algebra.parse('~(a&a)') - self.assertFalse(l.isliteral) - self.assertTrue(a in l.literals) - self.assertEqual(len(l.literals), 1) + assert not expression.isliteral - l = algebra.parse('~(a&a)', simplify=True) - self.assertTrue(l.isliteral) + assert algebra.Symbol('a') in expression.literals + assert len(expression.literals) == 1 + + def test_literals_2(self): + algebra = BooleanAlgebra() + + expression = algebra.parse('~(a&a)', simplify=True) + + assert expression.isliteral + assert expression == algebra.NOT(algebra.Symbol('a')) def test_literalize(self): parse = BooleanAlgebra().parse - self.assertEqual(parse('~a').literalize(), parse('~a')) - self.assertEqual(parse('~(a&b)').literalize(), parse('~a|~b')) - self.assertEqual(parse('~(a|b)').literalize(), parse('~a&~b')) + + assert parse('~a').literalize() == parse('~a') + assert parse('~(a&b)').literalize() == parse('~a|~b') + assert parse('~(a|b)').literalize() == parse('~a&~b') + + def test_invert_eq_not(self): + algebra = BooleanAlgebra() + + a = algebra.Symbol('a') + + assert ~a == ~a + assert ~a == algebra.NOT(a) def test_simplify(self): algebra = BooleanAlgebra() + a = algebra.Symbol('a') - self.assertEqual(~a, ~a) - assert algebra.Symbol('a') == algebra.Symbol('a') - self.assertNotEqual(a, algebra.parse('~~a')) - self.assertEqual(a, (~~a).simplify()) - self.assertEqual(~a, (~~ ~a).simplify()) - self.assertEqual(a, (~~ ~~a).simplify()) - self.assertEqual((~(a & a & a)).simplify(), (~(a & a & a)).simplify()) - self.assertEqual(a, algebra.parse('~~a', simplify=True)) + assert a == a.simplify() + assert a == (~~a).simplify() + assert a == (~~ ~~a).simplify() + + assert ~a == (~a).simplify() + assert ~a == (~ ~~a).simplify() + assert ~a == (~ ~~ ~~a).simplify() + + assert (~(a & a & a)).simplify() == (~(a & a & a)).simplify() + assert (~(a | a | a)).simplify() == (~(a | a | a)).simplify() + + @pytest.mark.xfail(reason="a.cancel() should work but does not") def test_cancel(self): algebra = BooleanAlgebra() + a = algebra.Symbol('a') - self.assertEqual(~a, (~a).cancel()) - self.assertEqual(a, algebra.parse('~~a').cancel()) - self.assertEqual(~a, algebra.parse('~~~a').cancel()) - self.assertEqual(a, algebra.parse('~~~~a').cancel()) + + assert a == a.cancel() + assert a == (~~a).cancel() + assert a == (~~ ~~a).cancel() + + assert ~a == (~a).cancel() + assert ~a == (~ ~~a).cancel() + assert ~a == (~ ~~ ~~a).cancel() def test_demorgan(self): - algebra = BooleanAlgebra() - a = algebra.Symbol('a') - b = algebra.Symbol('b') - self.assertEqual(algebra.parse('~(a&b)').demorgan(), ~a | ~b) - self.assertEqual(algebra.parse('~(a|b|c)').demorgan(), algebra.parse('~a&~b&~c')) - self.assertEqual(algebra.parse('~(~a&b)').demorgan(), a | ~b) + parse = BooleanAlgebra().parse + + assert parse('~(a & a)').demorgan() == parse('~a | ~a') + + assert parse('~(a & b)').demorgan() == parse('~a | ~b') + assert parse('~(a & b & c)').demorgan() == parse('~a | ~b | ~c') + assert parse('~(~a & b)').demorgan() == parse('a | ~b') + assert parse('~(a & ~b)').demorgan() == parse('~a | b') + + # TODO: enforced order is obscure, must explain def test_order(self): algebra = BooleanAlgebra() + x = algebra.Symbol(1) y = algebra.Symbol(2) - self.assertTrue(x < ~x) - self.assertTrue(~x > x) - self.assertTrue(~x < y) - self.assertTrue(y > ~x) + + assert x < y + assert y > x + + assert x < ~x + assert ~x > x + + assert ~x < y + assert y > ~x + + assert ~y > x + assert x < ~y def test_printing(self): algebra = BooleanAlgebra() + a = algebra.Symbol('a') - self.assertEqual(str(~a), '~a') - self.assertEqual(repr(~a), "NOT(Symbol('a'))") - expr = algebra.parse('~(a&a)') - self.assertEqual(str(expr), '~(a&a)') - self.assertEqual(repr(expr), "NOT(AND(Symbol('a'), Symbol('a')))") + assert str(~a) == '~a' + assert repr(~a) == "NOT(Symbol('a'))" + + expression = algebra.parse('~(a&a)') + + assert str(expression) == '~(a&a)' + assert repr(expression) == "NOT(AND(Symbol('a'), Symbol('a')))" class DualBaseTestCase(unittest.TestCase): From 775d8e4b90cce69b74b999482e7e59b97632de92 Mon Sep 17 00:00:00 2001 From: Alexander Lisianoi Date: Wed, 21 Jun 2017 10:47:16 +0200 Subject: [PATCH 13/19] Add an expansion of OtherTestCase.test_parse as test_parse.py Aim of test_parse.py is to (later) supersede OtherTestCase.test_parse Better parsing is required due to these issues: Refs: https://github.com/bastikr/boolean.py/issues/60 Refs: https://github.com/bastikr/boolean.py/issues/61 Signed-off-by: Aleksandr Lisianoi --- tests/test_parse.py | 167 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 tests/test_parse.py diff --git a/tests/test_parse.py b/tests/test_parse.py new file mode 100644 index 0000000..95139c7 --- /dev/null +++ b/tests/test_parse.py @@ -0,0 +1,167 @@ +import pytest + +from boolean import BooleanAlgebra +from boolean import ParseError + +class TestParse: + def test_constant(self): + algebra = BooleanAlgebra() + + for x in ['0', 'false']: + assert algebra.parse(x) == algebra.FALSE + assert algebra.parse('(' + x + ')') == algebra.FALSE + assert algebra.parse('((' + x + '))') == algebra.FALSE + assert algebra.parse('(((' + x + ')))') == algebra.FALSE + + for x in ['1', 'true']: + assert algebra.parse(x) == algebra.TRUE + assert algebra.parse('(' + x + ')') == algebra.TRUE + assert algebra.parse('((' + x + '))') == algebra.TRUE + assert algebra.parse('(((' + x + ')))') == algebra.TRUE + + @pytest.mark.xfail(reason='IndexError indicates parsing problem') + def test_wrong_braces_0(self): + parse = BooleanAlgebra().parse + + for expression in ['(', '((', '(((']: + with pytest.raises(ParseError): + parse(expression) + + for expression in [')', '))', ')))']: + with pytest.raises(ParseError): + parse(expression) + + for expression in ['()', '(())', '((()))']: + with pytest.raises(ParseError): + parse(expression) + + for expression in [')(', '))((', ')))(((']: + with pytest.raises(ParseError): + parse(expression) + + for expression in ['()', '()()', '()()()']: + with pytest.raises(ParseError): + parse(expression) + + @pytest.mark.xfail(reason='IndexError indicates parsing problem') + def test_wrong_braces_1(self): + parse = BooleanAlgebra().parse + + for s in ['(a', 'a(', '((a', '(a(', 'a((']: + with pytest.raises(ParseError): + parse(s) + + for s in ['a)', ')a', '))a', ')a)', 'a))']: + with pytest.raises(ParseError): + parse(s) + + for s in ['a()', '()a', 'a(())', '(a())', '(()a)', '(())a']: + with pytest.raises(ParseError): + parse(s) + + for s in ['a)(', ')a(', ')(a']: + with pytest.raises(ParseError): + parse(s) + + for s in ['a))((', ')a)((', '))(a(', '))((a']: + with pytest.raises(ParseError): + parse(s) + + for s in ['(a)()', '()(a)', '(a)(a)']: + with pytest.raises(ParseError): + parse(s) + + def test_one_symbol_0(self): + algebra = BooleanAlgebra() + + a = algebra.Symbol('a') + + parse = algebra.parse + + assert parse('a') == a + assert parse('(a)') == a + assert parse('((a))') == a + assert parse('(((a)))') == a + + for neg in ['~', '!']: + for space in ['', ' ', ' ']: + assert parse(neg + space + 'a') == ~a + assert parse(neg + space + '(a)') == ~a + assert parse(neg + space + '((a))') == ~a + + assert parse('(' + neg + space + 'a)') == ~a + assert parse('(' + neg + space + '(a))') == ~a + assert parse('((' + neg + space + 'a))') == ~a + + # test `not` separately because it needs extra space + assert parse('not a') == ~a + + assert parse('not(a)') == ~a + assert parse('not (a)') == ~a + + assert parse('not((a))') == ~a + assert parse('not ((a))') == ~a + + assert parse('(not(a))') == ~a + assert parse('(not (a))') == ~a + + assert parse('((not a))') == ~a + + def test_one_symbol_1(self): + algebra = BooleanAlgebra() + + for neg in ['~', '!', 'not']: + with pytest.raises(ParseError): + algebra.parse(neg) + + def test_one_symbol_2(self): + algebra = BooleanAlgebra() + + for x in ['&', 'and', '*']: + with pytest.raises(ParseError): + algebra.parse(x) + + for x in ['|', 'or', '+']: + with pytest.raises(ParseError): + algebra.parse(x) + + def test_one_symbol_3(self): + algebra = BooleanAlgebra() + + a = algebra.Symbol('a') + + invalids = [ + 'a a', + 'a a a', + 'a not', + 'a!', + 'a~', + 'a !', + 'a ~', + 'a not a', + 'a!a', + 'a! a', + 'a !a', + 'a ! a', + 'a~a', + 'a~ a', + 'a ~a', + 'a ~ a', + 'not a a', + '!a a', + '! a a', + '~a a', + '~ a a', + 'a not not a', + 'a!!a', + 'a!! a', + 'a !!a', + 'a !! a', + 'a ! ! a', + 'a! ! a', + 'a ! !a', + ] + + for invalid in invalids: + with pytest.raises(ParseError): + algebra.parse(invalid) From c27119aaaf408086addec47a478d46e1d8767b7f Mon Sep 17 00:00:00 2001 From: Alexander Lisianoi Date: Wed, 21 Jun 2017 10:51:12 +0200 Subject: [PATCH 14/19] Remove unused maxDiff variable Signed-off-by: Aleksandr Lisianoi --- tests/test_boolean.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_boolean.py b/tests/test_boolean.py index 80be197..75847cd 100644 --- a/tests/test_boolean.py +++ b/tests/test_boolean.py @@ -580,8 +580,6 @@ def test_printing(self): class DualBaseTestCase(unittest.TestCase): - maxDiff = None - def test_init(self): from boolean.boolean import DualBase a, b, c = Symbol('a'), Symbol('b'), Symbol('c') From 25ee2d860c9652a7253891e3af070637be315c93 Mon Sep 17 00:00:00 2001 From: Alexander Lisianoi Date: Thu, 29 Jun 2017 10:28:26 +0200 Subject: [PATCH 15/19] Drop specific versions from test-requirements.txt Even though some places recommend fixing dependencies [1], if you perform a github search [2] you will see that those repositories do *not* pin their versions. So, unpinning versions here. [1]: https://devcenter.heroku.com/articles/python-pip#best-practices [2]: https://github.com/search?utf8=%E2%9C%93&q=language%3Apython+stars%3A%3E1000 Signed-off-by: Aleksandr Lisianoi --- test-requirements.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test-requirements.txt b/test-requirements.txt index e642cc0..fd153a6 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,4 +1,4 @@ -tox==2.7.0 -pytest==3.1.2 -pytest-cov==2.5.1 -pytest-xdist==1.17.1 +tox +pytest +pytest-cov +pytest-xdist From 29e66f0e82244f90608eaad9739cf0271cb9a983 Mon Sep 17 00:00:00 2001 From: Alexander Lisianoi Date: Thu, 29 Jun 2017 10:51:19 +0200 Subject: [PATCH 16/19] Improve .cancel() tests as per [1] Test .cancel() on both Python variables and .parse() results [1]: https://github.com/bastikr/boolean.py/pull/70#discussion_r122733188 Signed-off-by: Aleksandr Lisianoi --- tests/test_boolean.py | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/tests/test_boolean.py b/tests/test_boolean.py index 75847cd..86cbb4f 100644 --- a/tests/test_boolean.py +++ b/tests/test_boolean.py @@ -521,13 +521,14 @@ def test_simplify(self): assert (~(a & a & a)).simplify() == (~(a & a & a)).simplify() assert (~(a | a | a)).simplify() == (~(a | a | a)).simplify() - @pytest.mark.xfail(reason="a.cancel() should work but does not") - def test_cancel(self): + def test_cancel_0(self): + """ + Test .cancel() on python variables + """ algebra = BooleanAlgebra() a = algebra.Symbol('a') - assert a == a.cancel() assert a == (~~a).cancel() assert a == (~~ ~~a).cancel() @@ -535,6 +536,34 @@ def test_cancel(self): assert ~a == (~ ~~a).cancel() assert ~a == (~ ~~ ~~a).cancel() + def test_cancel_1(self): + """ + Test .cancel() on .parse() results + """ + parse = BooleanAlgebra().parse + + assert parse('a') == parse('~~a').cancel() + assert parse('a') == parse('~~ ~~a').cancel() + + assert parse('~a') == parse('~a').cancel() + assert parse('~a') == parse('~ ~~a').cancel() + assert parse('~a') == parse('~ ~~ ~~a').cancel() + + def test_cancel_2(self): + """ + Test .cancel() on both Python variables and .parse() results + """ + algebra = BooleanAlgebra() + + a, parse = algebra.Symbol('a'), algebra.parse + + assert a == parse('~~a').cancel() + assert a == parse('~~ ~~a').cancel() + + assert ~a == parse('~a').cancel() + assert ~a == parse('~ ~~a').cancel() + assert ~a == parse('~ ~~ ~~a').cancel() + def test_demorgan(self): parse = BooleanAlgebra().parse From d5eee7ed636327285e76d3c9eeeada532cd0ac17 Mon Sep 17 00:00:00 2001 From: Alexander Lisianoi Date: Fri, 30 Jun 2017 19:42:42 +0200 Subject: [PATCH 17/19] Use parentheses if an expression has 'not' in front Signed-off-by: Aleksandr Lisianoi --- tests/test_boolean.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/tests/test_boolean.py b/tests/test_boolean.py index 86cbb4f..5bcff00 100644 --- a/tests/test_boolean.py +++ b/tests/test_boolean.py @@ -258,8 +258,8 @@ def test_equality(self): assert algebra.TRUE == algebra.TRUE assert algebra.FALSE == algebra.FALSE - assert algebra.TRUE != algebra.FALSE - assert not algebra.TRUE == algebra.FALSE + assert algebra.TRUE != algebra.FALSE + assert not (algebra.TRUE == algebra.FALSE) def test_order(self): algebra = BooleanAlgebra() @@ -326,21 +326,21 @@ def test_symbols_eq_2(self): a = algebra.Symbol('a') b = algebra.Symbol('b') - assert not a == b - assert not b == a + assert not (a == b) + assert not (b == a) def test_symbols_eq_3(self): algebra = BooleanAlgebra() - assert algebra.Symbol('a') == algebra.Symbol('a') - assert not algebra.Symbol('a') == algebra.Symbol('b') + assert algebra.Symbol('a') == algebra.Symbol('a') + assert not (algebra.Symbol('a') == algebra.Symbol('b')) def test_symbols_ne_0(self): algebra = BooleanAlgebra() a = algebra.Symbol('a') - assert not a != a + assert not (a != a) def test_symbols_ne_1(self): algebra = BooleanAlgebra() @@ -348,8 +348,8 @@ def test_symbols_ne_1(self): a0 = algebra.Symbol('a') a1 = algebra.Symbol('a') - assert not a0 != a1 - assert not a1 != a0 + assert not (a0 != a1) + assert not (a1 != a0) def test_symbols_ne_2(self): algebra = BooleanAlgebra() @@ -363,8 +363,8 @@ def test_symbols_ne_2(self): def test_symbols_ne_3(self): algebra = BooleanAlgebra() - assert not algebra.Symbol('a') != algebra.Symbol('a') - assert algebra.Symbol('a') != algebra.Symbol('b') + assert not (algebra.Symbol('a') != algebra.Symbol('a')) + assert algebra.Symbol('a') != algebra.Symbol('b') def test_symbols_eq_ne(self): algebra = BooleanAlgebra() @@ -389,20 +389,20 @@ def test_symbols_eq_ne(self): symbols1 = copy.deepcopy(symbols0) for symbol in symbols0: - assert symbol == symbol - assert not symbol != symbol + assert symbol == symbol + assert not (symbol != symbol) for symbol0, symbol1 in zip(symbols0, symbols1): assert symbol0 == symbol1 assert symbol1 == symbol0 - assert not symbol0 != symbol1 - assert not symbol1 != symbol0 + assert not (symbol0 != symbol1) + assert not (symbol1 != symbol0) for i in range(len(symbols0)): for j in range(i + 1, len(symbols0)): - assert not symbols0[i] == symbols1[j] - assert not symbols1[j] == symbols0[i] + assert not (symbols0[i] == symbols1[j]) + assert not (symbols1[j] == symbols0[i]) assert symbols0[i] != symbols1[j] assert symbols1[j] != symbols0[i] From 747764a4a95a6bc5366b64e3e047f0088ecc839e Mon Sep 17 00:00:00 2001 From: Alexander Lisianoi Date: Fri, 30 Jun 2017 20:26:50 +0200 Subject: [PATCH 18/19] Make it clear which BaseElement and Symbol calls work Signed-off-by: Aleksandr Lisianoi --- tests/test_boolean.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/test_boolean.py b/tests/test_boolean.py index 5bcff00..d3af59b 100644 --- a/tests/test_boolean.py +++ b/tests/test_boolean.py @@ -197,6 +197,12 @@ def test_parse_side_by_side_symbols_with_parens_raise_exception(self): class TestBaseElement: + def test_base_element_works(self): + try: + BaseElement() + except Exception as e: + pytest.fail('Unexpected exception: ' + str(e)) + def test_base_element_raises(self): with pytest.raises(TypeError): BaseElement(2) @@ -278,6 +284,17 @@ def test_printing(self): class TestSymbolCase: + def test_symbol_works(self): + try: + Symbol(1) + Symbol('a') + Symbol(None) + Symbol(sum) + Symbol((1, 2, 3)) + Symbol([1, 2]) + except Exception as e: + pytest.fail('Unexpected exception: ' + str(e)) + def test_isliteral(self): assert Symbol(1).isliteral is True From ac88b910b687c1314875bbf71810f7ae8d05167d Mon Sep 17 00:00:00 2001 From: Alexander Lisianoi Date: Fri, 30 Jun 2017 20:32:27 +0200 Subject: [PATCH 19/19] Squash spaces between tildas in .cancel(), reposition TODO comment Signed-off-by: Aleksandr Lisianoi --- tests/test_boolean.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/test_boolean.py b/tests/test_boolean.py index d3af59b..a7e7222 100644 --- a/tests/test_boolean.py +++ b/tests/test_boolean.py @@ -529,11 +529,11 @@ def test_simplify(self): assert a == a.simplify() assert a == (~~a).simplify() - assert a == (~~ ~~a).simplify() + assert a == (~~~~a).simplify() assert ~a == (~a).simplify() - assert ~a == (~ ~~a).simplify() - assert ~a == (~ ~~ ~~a).simplify() + assert ~a == (~~~a).simplify() + assert ~a == (~~~~~a).simplify() assert (~(a & a & a)).simplify() == (~(a & a & a)).simplify() assert (~(a | a | a)).simplify() == (~(a | a | a)).simplify() @@ -547,11 +547,11 @@ def test_cancel_0(self): a = algebra.Symbol('a') assert a == (~~a).cancel() - assert a == (~~ ~~a).cancel() + assert a == (~~~~a).cancel() assert ~a == (~a).cancel() - assert ~a == (~ ~~a).cancel() - assert ~a == (~ ~~ ~~a).cancel() + assert ~a == (~~~a).cancel() + assert ~a == (~~~~~a).cancel() def test_cancel_1(self): """ @@ -560,11 +560,11 @@ def test_cancel_1(self): parse = BooleanAlgebra().parse assert parse('a') == parse('~~a').cancel() - assert parse('a') == parse('~~ ~~a').cancel() + assert parse('a') == parse('~~~~a').cancel() assert parse('~a') == parse('~a').cancel() - assert parse('~a') == parse('~ ~~a').cancel() - assert parse('~a') == parse('~ ~~ ~~a').cancel() + assert parse('~a') == parse('~~~a').cancel() + assert parse('~a') == parse('~~~~~a').cancel() def test_cancel_2(self): """ @@ -575,11 +575,11 @@ def test_cancel_2(self): a, parse = algebra.Symbol('a'), algebra.parse assert a == parse('~~a').cancel() - assert a == parse('~~ ~~a').cancel() + assert a == parse('~~~~a').cancel() assert ~a == parse('~a').cancel() - assert ~a == parse('~ ~~a').cancel() - assert ~a == parse('~ ~~ ~~a').cancel() + assert ~a == parse('~~~a').cancel() + assert ~a == parse('~~~~~a').cancel() def test_demorgan(self): parse = BooleanAlgebra().parse @@ -592,8 +592,8 @@ def test_demorgan(self): assert parse('~(~a & b)').demorgan() == parse('a | ~b') assert parse('~(a & ~b)').demorgan() == parse('~a | b') - # TODO: enforced order is obscure, must explain def test_order(self): + # TODO: enforced order is obscure, must explain algebra = BooleanAlgebra() x = algebra.Symbol(1)