Skip to content

Commit

Permalink
disables magic syntax by default and updates docs
Browse files Browse the repository at this point in the history
  • Loading branch information
gabrielfalcao committed Dec 26, 2023
1 parent 08d8ad6 commit 1968210
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 22 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).


## [v3.0.0]
- Sure's featured synctactic-sugar of injecting/monkey-patching
``.should``, ``.should_not``, et cetera methods into
:py:class:`object` and its subclasses is disabled by default and needs to be enabled explicitly, programmatically via ``sure.enable_magic_syntax()`` or via command-line with the flags: ``-s`` or ``--syntax-magic``

## [v2.0.0]
### Fixed
- No longer patch the builtin `dir()` function, which fixes pytest in some cases such as projects using gevent.
Expand Down
9 changes: 4 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ endif
export SURE_NO_COLORS := true
export SURE_LOG_FILE := $(GIT_ROOT)/sure-$(date +"%Y-%m-%d-%H:%M:%S").log
AUTO_STYLE_TARGETS := sure/runtime.py sure/runner.py sure/meta.py sure/meta.py sure/reporter.py sure/reporters
# export SURE_DISABLE_NEW_SYNTAX := true

######################################################################
# Phony targets (only exist for typing convenience and don't represent
Expand Down Expand Up @@ -59,10 +58,10 @@ test tests: clean | $(VENV)/bin/pytest # $(VENV)/bin/nosetests # @$(VENV)/bin/no

# run main command-line tool
run: | $(MAIN_CLI_PATH)
# $(MAIN_CLI_PATH) --with-coverage --cover-branches --cover-module=sure.core tests/
# $(MAIN_CLI_PATH) --with-coverage --cover-branches --cover-module=sure.core --immediate
# $(MAIN_CLI_PATH) --with-coverage --cover-branches --cover-module=sure.core --cover-module=sure tests/runner/
$(MAIN_CLI_PATH) --with-coverage --cover-branches --cover-module=sure.runtime tests/unit/
# $(MAIN_CLI_PATH) --syntax-magic --with-coverage --cover-branches --cover-module=sure.core tests/
# $(MAIN_CLI_PATH) --syntax-magic --with-coverage --cover-branches --cover-module=sure.core --immediate
# $(MAIN_CLI_PATH) --syntax-magic --with-coverage --cover-branches --cover-module=sure.core --cover-module=sure tests/runner/
$(MAIN_CLI_PATH) --syntax-magic --with-coverage --cover-branches --cover-module=sure.runtime tests/unit/

# Pushes release of this package to pypi
push-release: dist # pushes distribution tarballs of the current version
Expand Down
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import sys
import os
import sphinx_rtd_theme
os.environ['SURE_DISABLE_NEW_SYNTAX'] = 'true'


extensions = [
"sphinx.ext.autodoc",
Expand Down
81 changes: 70 additions & 11 deletions docs/source/how-it-works.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,80 @@ short assertions like:
PERSON.should.have.key("facebook_info").being.a(dict)
Monkey-patching
===============
A bit of history
================

Lincoln Clarete has written the module [``sure/magic.py``] which I
simply added to sure. The most exciting part of the story is that
Lincoln exposed the code with a super clean API, it's called `forbidden
fruit <http://clarete.github.io/forbiddenfruit/>`__
From Sure's absolute ideation, its original author - Gabriel Falcão -
had envisioned to somehow expand Python's :py:class:`object` with
assertion methods during test runtime so that software engineers,
coders or developers in general could benefit from somewhat more
human-friendly and fluent assertions in the sense of literal writing
fluency. At any rate, after much brainstorming, the best solution
Gabriel could come up with was to provide a Python class -
:py:class:`sure.AssertionBuilder` - where and whence friendly
assertions could be built upon.

Gabriel crafted the :py:class:`sure.AssertionBuilder` in such way that
its usage could seem like verbs or adverbs so as to work with or
without Python's ``assert`` statement. But even more so than that, the
during the crafting of the :py:class:`sure.AssertionBuilder` it was
kept in mind that if it were possible to "hack" Python's syntax to
inject methods such as ``.should``, ``.should_not``, ``.must``,
``.must_not``, ``.shouldnt`` and ``.mustnt`` into :py:class:`object`
during **test runtime only**, then :py:class:`sure.AssertionBuilder`
could be almost effortlessly leveraged within those method's
implementations.

To be sure - pun intended - Gabriel crafted the
:py:class:`sure.AssertionBuilder` such that its assertion methods
always returned ``True`` so that ``assert`` statements such as
``assert that(X).is_not(Y)`` where ``X = False`` and ``Y = True``,
would return ``True`` even in an occasion when, in this case, both
``X`` and ``Y`` were either ``True`` or ``False``.

Gabriel's purpose was not to allow or enable abuse of assertions but
to prevent Python from raising a :py:class:`AssertionError` with no
details and instead bring as much detail as possible in the occasion
of such exception, to the point of doing its best to show at what key
or what index there is a difference in the case of testing equality
between the datastructures :py:class:`dict` or :py:class:`list`,
respectivelly in this case. (See :py:class:`sure.DeepExplanation` for more)

Gabriel's initial idea came from believing that other programming
languages suchs as Ruby or Javascript had tools or libraries such as
RSpec or Should.js which provided a kind of syntax-sugar that seemed
much more appealing or inviting for developers, making the process of
writing tests more pleasant or rewarding.

At the time of Sure's inception, so to speak, which was around
the middle of the year of 2010, the testing tools for the Ruby programming language seemed
much more mature and the market seemed to be booming with innovative, stable and resilient products `crafted <https://en.wikipedia.org/wiki/Software_craftsmanship>`_ by `practicioners of Agile Methodologies <https://en.wikipedia.org/wiki/Agile_software_development>`_

Around the year of 2012 Gabriel Falcão was working at a startup in NYC
and recruited two colleagues, one of whom was Lincoln Clarete which
had been known to Gabriel to know quite a bit about the internals of
the Python language. Then Gabriel not so much as asked whether it was
possible to inject methods into :py:class:`object` during runtime but
actually challenged Lincoln to do try and do so.

As Gabriel imagined, it wouldn't take long for Lincoln Clarete to
achieve that goal, he then presently wrote most if not all the code
currently present inside :py:mod:`sure.magic` and also took the idea
forward and evolvend it, ultimately resulting in the publishing of the
Python Package `forbidden fruit
<http://clarete.github.io/forbiddenfruit/>`_.

The only catch is that the functionallity inside :py:mod:`sure.magic`
is primarily guaranteed to work only with CPython, the original
implementation of Python in the C programming language.

Why CPython-only ?
------------------

Sure uses the `ctypes <http://docs.python.org/library/ctypes>`__ module
to break in python protections against monkey patching.
Sure uses the `ctypes <http://docs.python.org/library/ctypes>`_ module
to gain write-access to the ``__dict__`` of :py:class:`object` at runtime.

Although ctypes might also be available in other implementations such as
`Jython <http://www.jython.org/>`__, only the CPython provide
Although `ctypes <http://docs.python.org/library/ctypes>`_ might also be available in other implementations such as
`Jython <http://www.jython.org/>`__, only the CPython provide
```ctypes.pythonapi`` <http://docs.python.org/library/ctypes#loading-shared-libraries>`__
the features required by sure.
the features required by Sure.
10 changes: 6 additions & 4 deletions sure/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
not_here_error = (
"you have tried to access the attribute %r from the context "
"(aka VariablesBag), but there is no such attribute assigned to it. "
"Maybe you misspelled it ? Well, here are the options: %s"
"Maybe you mispelled it ? Well, here are the options: %s"
)


Expand Down Expand Up @@ -1044,7 +1044,7 @@ def __exit__(self, exc_type, exc_value, traceback):
raise AssertionError(msg)


allows_new_syntax = not os.getenv("SURE_DISABLE_NEW_SYNTAX")
allows_new_syntax = os.getenv("SURE_ENABLE_NEW_SYNTAX")


def do_enable():
Expand Down Expand Up @@ -1143,7 +1143,7 @@ def method(self):
old_dir = dir


def enable():
def enable_magic_syntax():
@wraps(builtins.dir)
def _new_dir(*obj):
if not obj:
Expand Down Expand Up @@ -1175,5 +1175,7 @@ def _new_dir(*obj):
do_enable()


enable = enable_magic_syntax

if is_cpython and allows_new_syntax:
enable()
enable_magic_syntax()
7 changes: 6 additions & 1 deletion sure/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,11 @@
@click.option("-i", "--immediate", is_flag=True)
@click.option("-l", "--log-level", type=click.Choice(['none', 'debug', 'info', 'warning', 'error']), help="default='none'")
@click.option("-F", "--log-file", help='path to a log file. Default to SURE_LOG_FILE')
@click.option("-s", "--syntax-magic", is_flag=True)
@click.option("-c", "--with-coverage", is_flag=True)
@click.option("--cover-branches", is_flag=True)
@click.option("--cover-module", multiple=True, help="specify module names to cover")
def entrypoint(paths, reporter, immediate, log_level, log_file, with_coverage, cover_branches, cover_module):
def entrypoint(paths, reporter, immediate, log_level, log_file, syntax_magic, with_coverage, cover_branches, cover_module):
if not paths:
paths = glob('test*/**')
else:
Expand All @@ -61,9 +62,13 @@ def entrypoint(paths, reporter, immediate, log_level, log_file, with_coverage, c
}
cov = with_coverage and coverage.Coverage(**coverageopts) or None
if cov:
cov.erase()
cov.load()
cov.start()

if syntax_magic:
sure.enable_magic_syntax()

runner = Runner(resolve_path(os.getcwd()), reporter)
try:
result = runner.run(paths, immediate=immediate)
Expand Down

0 comments on commit 1968210

Please sign in to comment.