Skip to content

Commit

Permalink
more enhancements especially towards a more sophisticated prose throu…
Browse files Browse the repository at this point in the history
…ghout the project
  • Loading branch information
gabrielfalcao committed Jan 9, 2024
1 parent cea8766 commit 9132ee7
Show file tree
Hide file tree
Showing 64 changed files with 1,458 additions and 482 deletions.
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,14 @@ html-docs: clean-docs
docs: html-docs
$(OPEN_COMMAND) docs/build/html/index.html

test tests: clean | $(VENV)/bin/pytest # $(VENV)/bin/sure
test tests:
@$(VENV)/bin/pytest --cov=sure --ignore tests/crashes tests

# runs main command-line tool
run: | $(MAIN_CLI_PATH)
$(MAIN_CLI_PATH) --reporter=test tests/crashes || true
$(MAIN_CLI_PATH) --special-syntax --with-coverage --cover-branches --cover-module=sure.core --cover-module=sure tests/runner
$(MAIN_CLI_PATH) --special-syntax --with-coverage --cover-branches --cover-module=sure --immediate --cover-module=sure tests
$(MAIN_CLI_PATH) --special-syntax --with-coverage --cover-branches --cover-module=sure --immediate --cover-module=sure --ignore=crashes tests

push-release: dist # pushes distribution tarballs of the current version
$(VENV)/bin/twine upload dist/*.tar.gz
Expand Down Expand Up @@ -121,7 +121,7 @@ $(VENV)/bin/flake8: | $(VENV)/bin/pip

$(VENV)/bin/sure $(VENV)/bin/pytest $(MAIN_CLI_PATH): | $(VENV) $(VENV)/bin/pip $(VENV)/bin/python $(REQUIREMENTS_PATH)
$(VENV)/bin/pip install -r $(REQUIREMENTS_PATH)
$(VENV)/bin/pip install -e .
$(VENV)/bin/pip install .

# ensure that REQUIREMENTS_PATH exists
$(REQUIREMENTS_PATH):
Expand Down
41 changes: 15 additions & 26 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@ sure
.. image:: https://img.shields.io/badge/pydoc-web-ff69b4.svg
:target: http://pydoc.net/sure

An idiomatic testing library for python with powerful and flexible assertions, created by `Gabriel Falcão <https://github.com/gabrielfalcao>`_.
Sure's developer experience is inspired and modeled after `RSpec Expectations
<http://rspec.info/documentation/3.5/rspec-expectations/>`_ and
`should.js <https://github.com/shouldjs/should.js>`_.
The sophisticated automated test tool for Python, featuring a test
runner and a library with powerful and flexible assertions.

Originally authored by `Gabriel Falcão <https://github.com/gabrielfalcao>`_.


Installing
----------
Expand All @@ -49,6 +50,7 @@ Installing
pip install sure
Running tests
-------------

Expand All @@ -73,39 +75,26 @@ To build locally run:
make docs
Quick Library Showcase
----------------------

Equality
~~~~~~~~

(number).should.equal(number)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. code:: python
from sure import expect
expect(4).to.be.equal(2 + 2)
expect(7.5).to.be.eql(3.5 + 4)
expect(3).to.not_be.equal(5)
expect(9).to_not.be.equal(11)
from sure import expects
expects(4).to.be.equal(2 + 2)
expects(7.5).to.be.eql(3.5 + 4)
Assert dictionary and its contents
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
expects(3).to.not_be.equal(5)
expects(9).to_not.be.equal(11)
.. code:: python
from sure import expect
expect({'foo': 'bar'}).to.equal({'foo': 'bar'})
expect({'foo': 'bar'}).to.have.key('foo').being.equal('bar')
from sure import expects
"A string".lower().should.equal("a string") also works
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
expects({'foo': 'bar'}).to.equal({'foo': 'bar'})
expects({'foo': 'bar'}).to.have.key('foo').being.equal('bar')
.. code:: python
Expand Down
8 changes: 4 additions & 4 deletions docs/source/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,8 @@ This project adheres to `Semantic Versioning <http://semver.org/>`__.

- Presents better documentation
- Drops support to Python 2 obliterates the ``sure.compat`` module
- Introduces the modules ``sure.doubles``, ``sure.doubles.fakes`` and
``sure.doubles.stubs``

``sure.doubles.fakes``
- Introduces the modules ``sure.doubles``, ``sure.doubles.fakes``,
``sure.doubles.stubs`` and ``sure.doubles.dummies``
- Sure’s featured synctactic-sugar of injecting/monkey-patching
``.should``, ``.should_not``, et cetera methods into
:class:``object`` and its subclasses is disabled by default and
Expand All @@ -22,6 +20,8 @@ This project adheres to `Semantic Versioning <http://semver.org/>`__.
- Moves :class:``sure.original.that`` to :attr:``sure.that`` as
an instance of :class:``sure.original.AssertionHelper`` rather
than an alias to the class.
- ``AssertionHelper.every_one_is()`` renamed to ``AssertionHelper.every_item_is()``


[v2.0.0]
--------
Expand Down
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"Sure Documentation",
author,
"Sure",
"utility belt for automated testing in python for python.",
"sophisticated automated test library and runner for python.",
"Automated Testing",
),
]
Expand Down
20 changes: 20 additions & 0 deletions docs/source/definitions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ These terms might be updated in the event of emerging incorrectness or
general evolution of the :ref:`Sure` project.


.. _truthy:

truthy
------

Expand All @@ -31,6 +33,8 @@ More specifically, any valid Python code evaluted by :class:`bool` as
Synonyms: ``true``, ``truthy``, ``ok``


.. _falsy:

falsy
-----

Expand All @@ -44,10 +48,26 @@ More specifically, any valid Python code evaluted by :class:`bool` as
Synonyms: ``false``, ``falsy``, ``not_ok``


.. _none:

none
----

Defines Python objects whose logical value is equivalent to the
boolean value of ``False``.

Synonyms: ``none``, ``None``


.. _special syntax definition:

special syntax
--------------

:ref:`Special Syntax` refers to the unique feature of giving *special
properties* to every in-memory Python :class:`python:object` from
which to build assertions.

Such special properties are semantically divided in two categories:
:ref:`positive <positive assertion properties>` (``do``, ``does``, ``must``, ``should``, ``when``) and :ref:`negative
<negative assertion properties>` (``do_not``, ``dont``, ``does_not``, ``doesnt``, ``must_not``, ``mustnt``, ``should_not``, ``shouldnt``).
3 changes: 3 additions & 0 deletions docs/source/how-it-works.rst
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ As can be observed in the examples above, there are two kinds of
properties: positives and negatives


.. _positive assertion properties:

Positive Assertion Properties
.............................

Expand Down Expand Up @@ -200,6 +202,7 @@ Example:
Y = {'structured information': ['string', [110, 117, 109, 98, 101, 114, {'outmost': 'list'}], {'first key': 76, 'second key': 107}]}
X['structured information'][1]['first key'] is 75 whereas Y['structured information'][1]['first key'] is 107

.. _negative assertion properties:

Negative Assertion Properties
.............................
Expand Down
4 changes: 2 additions & 2 deletions docs/source/intro.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ Introduction
Sure is a both a library and a test-runner for for the Python Programming Languages, featuring a DSL for writing
assertions. Sure's original author is `Gabriel Falcão <https://github.com/gabrielfalcao>`_.

Sure provides a :ref:`Special Syntax` for writing tests in a
Sure provides a :ref:`special syntax definition` for writing tests in a
human-friendly, fluent and easy-to-use manner, In the context of the
Python Programming language, Sure is a pioneer at extending every
object with test-specific methods at test-runtime. This feature is
disabled by default starting on version 3.0.0 and MAY be optionally
enabled programmatically or via command-line. Read the section
:ref:`Special Syntax` for more information.
:ref:`special syntax definition` for more information.

Whether the :ref:`Special Syntax` is enabled or not, :ref:`sure`
generally aims at enabling software developers to writing tests in a
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tool:pytest]
addopts = -v --maxfail=1 --capture=no --cov=sure
addopts = --ignore tests/crashes -v --maxfail=1 --capture=no --cov=sure.runtime
testpaths =
tests
filterwarnings =
Expand Down
6 changes: 3 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# <sure - utility belt for automated testing in python>
# Copyright (C) <2010-2023> Gabriel Falcão <gabriel@nacaolivre.org>
# <sure - sophisticated automated test library and runner>
# Copyright (C) <2010-2024> Gabriel Falcão <gabriel@nacaolivre.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
Expand All @@ -16,7 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

"""utility belt for automated testing in python for python"""
"""sophisticated automated test library and runner"""

import os
import ast
Expand Down
70 changes: 39 additions & 31 deletions sure/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# <sure - utility belt for automated testing in python>
# Copyright (C) <2010-2023> Gabriel Falcão <gabriel@nacaolivre.org>
# <sure - sophisticated automated test library and runner>
# Copyright (C) <2010-2024> Gabriel Falcão <gabriel@nacaolivre.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
Expand All @@ -14,7 +14,7 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
"""Sure the sophisticated automated test library and runner for Python
"""

import re
Expand Down Expand Up @@ -1005,7 +1005,7 @@ def lower_than(self, expectation):
raise AssertionError(f"expected `{self.actual}' to not be lower than `{expectation}'")

else:
if not self.actual < expectation:
if not self.actual < expectation:
raise AssertionError(f"expected `{self.actual}' to be lower than `{expectation}'")

return True
Expand Down Expand Up @@ -1301,27 +1301,36 @@ def make_safe_property(method, name, should_be_property=True):

def deleter(method, self, *args, **kw):
if isinstance(self, type):
# if the attribute has to be deleted from a class object
# we cannot use '`del self.__dict__[name]'` directly because we cannot
# modify a mappingproxy object. Thus, we have to delete it in our
# proxy __dict__.
# In the event of deleting attributes from a "class
# object" the call to ``self.__dict__.pop(name)`` would
# not work because that would be equivalent to modifying a
# mappingproxy object directly. Instead the expected
# behavior is achieved in deleting the attribute
# inside the ``overwritten_object_handlers`` dict.
overwritten_object_handlers.pop((id(self), method.__name__), None)
else:
# if the attribute has to be deleted from an instance object
# we are able to directly delete it from the object's __dict__.
# Nevertheless, in the event of deleting attributes
# from an "instance object the expected behavior is
# achieved in a more common and straightforward
# manner: pop directly at the instance's __dict__
self.__dict__.pop(name, None)

def setter(method, self, other):
def setter(method, self, value):
if isinstance(self, type):
# if the attribute has to be set to a class object
# we cannot use '`self.__dict__[name] = other'` directly because we cannot
# modify a mappingproxy object. Thus, we have to set it in our
# proxy __dict__.
overwritten_object_handlers[(id(self), method.__name__)] = other
# In the event of setting attributes from a "class
# object" the direct assignment ``self.__dict__[name] = value`` would
# not work because that would be equivalent to modifying a
# mappingproxy object directly. Instead the expected
# behavior is achieved in deleting the attribute
# inside the ``overwritten_object_handlers`` dict.
overwritten_object_handlers[(id(self), method.__name__)] = value
else:
# if the attribute has to be set to an instance object
# we are able to directly set it in the object's __dict__.
self.__dict__[name] = other
# Nevertheless, in the event of deleting attributes
# from an "instance object the expected behavior is
# achieved in a more common and straightforward
# manner: set the attribute directly at instance's
# __dict__
self.__dict__[name] = value

return builtins.property(
fget=method,
Expand All @@ -1337,14 +1346,13 @@ def build_assertion_property(name, is_negative, prop=True):
"""

def method(self):
# check if the given object already has an attribute with the
# given name. If yes return it instead of patching it.
# avoid overwriting, patching attributes, methods or
# properties that already exist in the type's __dict__
try:
if name in self.__dict__:
return self.__dict__[name]
except AttributeError:
# we do not have an object with __dict__, thus
# it's safe to just continue and patch the `name'.
# nevertheless objects that do not have a __dict__ can be patched
pass

overwritten_object_handler = overwritten_object_handlers.get(
Expand All @@ -1363,19 +1371,19 @@ def method(self):
instance._callable_kw = callable_kw
return instance

method.__name__ = str(name)
method.__name__ = name
return make_safe_property(method, name, prop)

object_handler = patchable_builtin(object)
# We have to keep track of all objects which
# should overwrite a '`POSITIVES'` or '`NEGATIVES'`
# property. If we wouldn't do that in the
# make_safe_property.setter method we would loose
# the newly assigned object reference.
# Keeping track of all special properties of both `POSITIVES' and
# `NEGATIVES' categories is paramount to avoid losing the newly
# assigned object reference in the ``setter`` function within the
# ``make_safe_property`` function.
overwritten_object_handlers = {}

# None does not have a tp_dict associated to its PyObject, so this
# is the only way we could make it work like we expected.
# The `None' type does not have a "tp_dict" associated to its
# PyObject. One way to patch Nonetypes is via its ``__class__``
# attribute.
none = patchable_builtin(None.__class__)

for name in POSITIVES:
Expand Down

0 comments on commit 9132ee7

Please sign in to comment.