fudge-1.1.1/0000775000175000017500000000000013213771547013114 5ustar jsattjsatt00000000000000fudge-1.1.1/fudge.egg-info/0000775000175000017500000000000013213771547015700 5ustar jsattjsatt00000000000000fudge-1.1.1/fudge.egg-info/PKG-INFO0000664000175000017500000000413113213771547016774 0ustar jsattjsatt00000000000000Metadata-Version: 1.1 Name: fudge Version: 1.1.1 Summary: Replace real objects with fakes (mocks, stubs, etc) while testing. Home-page: https://github.com/fudge-py/fudge Author: Kumar McMillan Author-email: kumar.mcmillan@gmail.com License: The MIT License Description-Content-Type: UNKNOWN Description: Complete documentation is available at https://fudge.readthedocs.org/en/latest/ Fudge is a Python module for using fake objects (mocks and stubs) to test real ones. In readable Python code, you declare what methods are available on your fake and how they should be called. Then you inject that into your application and start testing. This declarative approach means you don't have to record and playback actions and you don't have to inspect your fakes after running code. If the fake object was used incorrectly then you'll see an informative exception message with a traceback that points to the culprit. Here is a quick preview of how you can test code that sends email without actually sending email:: @fudge.patch('smtplib.SMTP') def test_mailer(FakeSMTP): # Declare how the SMTP class should be used: (FakeSMTP.expects_call() .expects('connect') .expects('sendmail').with_arg_count(3)) # Run production code: send_mail() # ...expectations are verified automatically at the end of the test Platform: UNKNOWN Classifier: Intended Audience :: Developers Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 2.5 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.1 Classifier: Programming Language :: Python :: 3.2 Classifier: Topic :: Software Development :: Testing fudge-1.1.1/fudge.egg-info/top_level.txt0000664000175000017500000000000613213771547020426 0ustar jsattjsatt00000000000000fudge fudge-1.1.1/fudge.egg-info/SOURCES.txt0000664000175000017500000000217613213771547017572 0ustar jsattjsatt00000000000000LICENSE.txt MANIFEST.in README.md run_tests.sh setup.py docs/Makefile docs/conf.py docs/index.rst docs/javascript.rst docs/migrating-0.9-to-1.0.rst docs/using-fudge.rst docs/why-fudge.rst docs/_doctest_support/models.py docs/_doctest_support/auth/__init__.py docs/_doctest_support/oauthtwitter/__init__.py docs/_static/.hidden docs/api/fudge.inspector.rst docs/api/fudge.patcher.rst docs/api/fudge.rst fudge/__init__.py fudge/exc.py fudge/inspector.py fudge/patcher.py fudge/util.py fudge.egg-info/PKG-INFO fudge.egg-info/SOURCES.txt fudge.egg-info/dependency_links.txt fudge.egg-info/top_level.txt fudge/tests/__init__.py fudge/tests/_py3_suite.py fudge/tests/test_fudge.py fudge/tests/test_import_all.py fudge/tests/test_inspector.py fudge/tests/test_inspector_import_all.py fudge/tests/test_patcher.py fudge/tests/test_registry.py fudge/tests/support/__init__.py fudge/tests/support/_for_patch.py javascript/README.txt javascript/fudge/fudge.js javascript/fudge/testserver.py javascript/fudge/tests/test_fudge.html javascript/fudge/tests/test_fudge.js javascript/fudge/tests/jquery/jquery-1.2.6.js javascript/fudge/tests/jquery/qunit/testrunner.jsfudge-1.1.1/fudge.egg-info/dependency_links.txt0000664000175000017500000000000113213771547021746 0ustar jsattjsatt00000000000000 fudge-1.1.1/docs/0000775000175000017500000000000013213771547014044 5ustar jsattjsatt00000000000000fudge-1.1.1/docs/why-fudge.rst0000664000175000017500000000636312535357753016512 0ustar jsattjsatt00000000000000 =========================== Why Another Mock Framework? =========================== Can't you do most of this in plain old Python? If you're just replacing methods then yes but when you need to manage expectations, it's not so easy. Fudge started when a co-worker showed me `Mocha `_ for Ruby. I liked it because it was a much simpler version of `jMock`_ and jMock allows you to do two things at once: 1) build a fake version of a real object and 2) inspect that your code uses it correctly (post mortem). Up until now, I've built all my mock logic in plain Python and noticed that I spent gobs of code doing these two things in separate places. The jMock approach gets rid of the need for a post mortem and the expectation code is very readable. What about all the other mock frameworks for Python? I *really* didn't want to build another mock framework, honestly. Here were my observations of the scenery: - `pMock `_ (based on `jMock`_) - This of course is based on the same jMock interface that I like. However, its site claims it has not been maintained since 2004 and besides that jMock is too over engineered for my tastes and pMock does not attempt to fix that. - `minimock `_ - As far as I can tell, there is no easy, out-of-the-box way to use minimock in anything other than a doctest. - It doesn't really deal with expectations, just replacements (stubbing). - `mock `_ - I didn't like how mock focused on post mortem inspection. - `pyMock `_ (based on `EasyMock`_) - This uses a record / playback technique whereby you act upon your real objects then flip a switch and they become fake. This seems like it has some benefits for maintenance but I'm not sure that the overhead of recording with real objects is worth it. I suppose you'd need a real database, a real web service, etc. - `Mox `_ (based on `EasyMock`_) - This also uses the record / playback technique but with a DSL (domain specific language). It was brought to my attention after creating Fudge but thought it was worth mentioning. - `mocker `_ (based on `EasyMock`_ and others) - This was also `pointed out to me `_ after developing Fudge. - Mocker is another record / playback implementation but seems to have a cleaner interface than most. I still do not see the practicality of record / playback. How do you write tests in record mode? I am probably missing it but nowhere in the docs do I see practical examples for creating test code. Instead the examples are interactive sessions which is not how I typically write tests. - The docs for mocker, like docs for other tools, do not focus on any real-world problem that a mock framework can solve. This is hard for my brain. It is hard for me to look at code such as obj.hello() and imagine that this would be useful for, say, mocking out sendmail(). - However, mocker certainly looks like it has more features than Fudge so it is worth checking out. .. _jMock: http://www.jmock.org/ .. _EasyMock: http://www.easymock.org/fudge-1.1.1/docs/Makefile0000664000175000017500000000576112535357753015522 0ustar jsattjsatt00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d _build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # this is the best I can think of # for making it obvious to the user # that he forgot to set some environ vars FD_PROJECTS_HOST ?= FD_PROJECTS_HOST FD_PROJECTS_PATH_TARGET ?= FD_PROJECTS_PATH_TARGET .PHONY: help clean html web pickle htmlhelp latex changes linkcheck help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " pickle to make pickle files (usable by e.g. sphinx-web)" @echo " htmlhelp to make HTML files and a HTML help project" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " changes to make an overview over all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" clean: -rm -rf _build/* html: mkdir -p _build/html _build/doctrees $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html @echo @echo "Build finished. The HTML pages are in _build/html." docslive: cd _build/; cp -R html current && tar -czf current.tar.gz current scp _build/current.tar.gz $(FD_PROJECTS_HOST):$(FD_PROJECTS_PATH_TARGET)/fudge/ && \ ssh $(FD_PROJECTS_HOST) "cd $(FD_PROJECTS_PATH_TARGET)/fudge && rm -fr current && tar -xzf current.tar.gz && rm current.tar.gz" && \ rm -fr _build/current.tar.gz && \ rm -fr _build/current @echo "Docs sent to $(FD_PROJECTS_HOST):$(FD_PROJECTS_PATH_TARGET)/fudge/" doctest: mkdir -p _build/html _build/doctrees PYTHONPATH=../:./_doctest_support $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) _build/html pickle: mkdir -p _build/pickle _build/doctrees $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) _build/pickle @echo @echo "Build finished; now you can process the pickle files or run" @echo " sphinx-web _build/pickle" @echo "to start the sphinx-web server." web: pickle htmlhelp: mkdir -p _build/htmlhelp _build/doctrees $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) _build/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in _build/htmlhelp." latex: mkdir -p _build/latex _build/doctrees $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex @echo @echo "Build finished; the LaTeX files are in _build/latex." @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ "run these through (pdf)latex." changes: mkdir -p _build/changes _build/doctrees $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) _build/changes @echo @echo "The overview file is in _build/changes." linkcheck: mkdir -p _build/linkcheck _build/doctrees $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) _build/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in _build/linkcheck/output.txt." fudge-1.1.1/docs/api/0000775000175000017500000000000013213771547014615 5ustar jsattjsatt00000000000000fudge-1.1.1/docs/api/fudge.rst0000664000175000017500000000046212535357753016450 0ustar jsattjsatt00000000000000 ----- fudge ----- .. automodule:: fudge .. autofunction:: fudge.patch .. autofunction:: fudge.test .. autoclass:: fudge.Fake :members: .. autofunction:: fudge.clear_calls .. autofunction:: fudge.verify .. autofunction:: fudge.with_fakes .. autoclass:: fudge.FakeDeclarationError :members:fudge-1.1.1/docs/api/fudge.patcher.rst0000664000175000017500000000045412535357753020076 0ustar jsattjsatt00000000000000 .. _fudge.patcher: ------------- fudge.patcher ------------- .. automodule:: fudge.patcher .. autofunction:: fudge.patcher.with_patched_object .. autofunction:: fudge.patcher.patched_context .. autofunction:: fudge.patcher.patch_object .. autoclass:: fudge.patcher.PatchHandler :members:fudge-1.1.1/docs/api/fudge.inspector.rst0000664000175000017500000000033012535357753020447 0ustar jsattjsatt00000000000000 ---------------- fudge.inspector ---------------- .. automodule:: fudge.inspector .. autoclass:: fudge.inspector.ValueInspector :members: .. autoclass:: fudge.inspector.NotValueInspector :members: __call__ fudge-1.1.1/docs/_doctest_support/0000775000175000017500000000000013213771547017444 5ustar jsattjsatt00000000000000fudge-1.1.1/docs/_doctest_support/models.py0000664000175000017500000000012412535357753021303 0ustar jsattjsatt00000000000000 # this is so the SQLAlchemy doctest will work. class User(object): id = 1fudge-1.1.1/docs/_doctest_support/auth/0000775000175000017500000000000013213771547020405 5ustar jsattjsatt00000000000000fudge-1.1.1/docs/_doctest_support/auth/__init__.py0000664000175000017500000000023612535357753022524 0ustar jsattjsatt00000000000000 # these get replaced by fakes in the doctest def current_user(*args): raise NotImplementedError def login(*args): raise NotImplementedErrorfudge-1.1.1/docs/_doctest_support/oauthtwitter/0000775000175000017500000000000013213771547022207 5ustar jsattjsatt00000000000000fudge-1.1.1/docs/_doctest_support/oauthtwitter/__init__.py0000664000175000017500000000025512535357753024327 0ustar jsattjsatt00000000000000# this exists so the doctest can import oauthtwitter before fudging out its methods def OAuthApi(*args, **kw): raise RuntimeError("Test failed to mock out this method")fudge-1.1.1/docs/javascript.rst0000664000175000017500000000363712535357753016762 0ustar jsattjsatt00000000000000 .. _javascript-fudge: ==================== Fudge For JavaScript ==================== Although `Ersatz `_ is a port of `Mocha `_ to JavaScript and that's pretty much what :ref:`Fudge ` is, I couldn't get Ersatz to work with one of my libraries because it uses Prototype. So I started porting Fudge to JavaScript. As of this writing it has only been partially implemented. Install ======= Download the :ref:`Fudge source distribution ` and copy ``javascript/fudge/`` to your webroot. To use it in your tests all you need is a script tag like this: .. code-block:: html If you want to run Fudge's own tests, then cd into the ``javascript/`` directory, start a simple webserver:: $ python fudge/testserver.py and open http://localhost:8000/tests/test_fudge.html Take note that while Fudge's *tests* require jQuery, Fudge itself does not require jQuery. Usage ===== Refer to :ref:`using-fudge` in Python to get an idea for how to use the JavaScript version. As mentioned before, the JavaScript port is not yet fully implemented. Here is a quick example: .. code-block:: javascript // if you had e.g. a session object that looked something like: yourapp = {}; yourapp.session = { set: function(key, value) { // ... } } yourapp.startup = function() { yourapp.session.set('saw_landing_page',true); }; // and if you wanted to test the startup() method above, then you could // declare a fake object for a test: var fake_session = new fudge.Fake('session').expects('set').with_args('saw_landing_page',true); // patch your production code: yourapp.session = fake_session; // and run a test: fudge.clear_calls(); yourapp.startup(); fudge.verify(); fudge-1.1.1/docs/conf.py0000664000175000017500000001432012535357753015350 0ustar jsattjsatt00000000000000# -*- coding: utf-8 -*- # # Fudge documentation build configuration file, created by # sphinx-quickstart on Mon Nov 17 20:07:01 2008. # # This file is execfile()d with the current directory set to its containing dir. # # The contents of this file are pickled, so don't put values in the namespace # that aren't pickleable (module imports are okay, they're removed automatically). # # All configuration values have a default value; values that are commented out # serve to show the default value. import sys, os, re # put fudge on the path for automodule: sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) # If your extensions are in another directory, add it here. If the directory # is relative to the documentation root, use os.path.abspath to make it # absolute, like shown here. #sys.path.append(os.path.abspath('some/directory')) # General configuration # --------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General substitutions. project = 'Fudge' copyright = '2008, Kumar McMillan' version = None for line in open(os.path.join(os.path.dirname(__file__), "../fudge/__init__.py")): m = re.search("__version__\s*=\s*(.*)", line) if m: version = m.group(1).strip() version = version[1:-1] # quotes break assert version # The default replacements for |version| and |release|, also used in various # other places throughout the built documents. # # The short X.Y version. version = version # The full version, including alpha/beta/rc tags. release = version # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. #unused_docs = [] # List of directories, relative to source directories, that shouldn't be searched # for source files. #exclude_dirs = [] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # Options for HTML output # ----------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. html_theme = 'nature' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. html_theme_path = ['_theme'] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (within the static path) to place at the top of # the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_use_modindex = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, the reST sources are included in the HTML build as _sources/. #html_copy_source = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'Fudgedoc' # Options for LaTeX output # ------------------------ # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, document class [howto/manual]). latex_documents = [ ('index', 'Fudge.tex', 'Fudge Documentation', 'Kumar McMillan', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_use_modindex = True fudge-1.1.1/docs/migrating-0.9-to-1.0.rst0000664000175000017500000000241212535357753020003 0ustar jsattjsatt00000000000000 =============================== Migrating from Fudge 0.9 to 1.0 =============================== After :ref:`many 0.9.x versions ` and some great input from the community, Fudge has evolved to 1.0. This introduces a much *simpler* API and while it doesn't deprecate the old API you'll probably want to update your code. Take a look at the new code examples in :ref:`using Fudge ` to get a feel for it. Here is a summary of changes: The new @patch and @test decorators =================================== You no longer have to worry about when and where to call :func:`fudge.clear_calls`, :func:`fudge.verify`, and :func:`fudge.clear_expectations`! Instead, just wrap each test in the :func:`fudge.patch` decorator and declare expectations within your test. If you don't need to patch anything, use the :func:`fudge.test` decorator. Expectations that were declared in setup ======================================== If you were declaring expectations in a module-level ``setup()`` or ``unittest.setUp()`` method then you either have to continue managing the clear/verify calls manually and decorate your tests with :func:`fudge.with_fakes` or you need to move all declaration into the test function (not setup) using the :func:`fudge.patch` decorator. fudge-1.1.1/docs/_static/0000775000175000017500000000000013213771547015472 5ustar jsattjsatt00000000000000fudge-1.1.1/docs/_static/.hidden0000664000175000017500000000000012535357753016721 0ustar jsattjsatt00000000000000fudge-1.1.1/docs/using-fudge.rst0000664000175000017500000002745112535357753017031 0ustar jsattjsatt00000000000000 .. _using-fudge: =========== Using Fudge =========== Fudging A Web Service ===================== When testing code that uses a web service you probably want a fast set of tests that don't depend on an actual web service on the Internet. This is a good scenario in which to use mock objects. Say you have a Twitter bot that looks something like this: .. doctest:: >>> import oauthtwitter >>> def post_msg_to_twitter(msg): ... api = oauthtwitter.OAuthApi( ... '', '', ... '', '' ... ) ... api.UpdateStatus(msg) ... print "Sent: %s" % msg >>> Since the `oauthtwitter`_ module is maintained independently, your code should work as long as it calls the right methods. A Simple Test Case ================== You can use Fudge to replace the OAuthApi class with a fake and **declare an expectation** of how it should be used: .. _oauthtwitter: http://code.google.com/p/oauth-python-twitter/ .. doctest:: >>> import fudge >>> @fudge.patch('oauthtwitter.OAuthApi') ... def test(FakeOAuthApi): ... (FakeOAuthApi.expects_call() ... .with_args('', '', ... '', '') ... .returns_fake() ... .expects('UpdateStatus').with_arg_count(1)) ... ... post_msg_to_twitter("hey there fellow testing freaks!") >>> Let's break this down: 1. The :func:`patch ` decorator will temporarily patch in a fake object for the duration of the test and expose it as an argument to the test. This allows you to add expectations or stubs. 2. The :class:`fake ` object you see here expects a call (class instantiation) with four arguments having specific string values. The returned value is an object instance (a new fake) that expects you to call ``fake_oauth.UpdateStatus()`` with one argument. 3. Finally, ``post_msg_to_twitter()`` is called. Let's run the test! .. doctest:: >>> test() Sent: hey there fellow testing freaks! Sweet, it passed. Fudge lets you declare expectations as loose or as tight as you want. If you don't care about the exact arguments, you can leave off the call to :meth:`fudge.Fake.with_args`. If you don't care if a method is actually called you can use :meth:`fudge.Fake.provides` instead of :meth:`fudge.Fake.expects`. Likewise, :meth:`fudge.Fake.with_arg_count` can be used when you don't want to worry about the actual argument values. There are `argument inspectors `_ for checking values in other ways. Fake objects without patches (dependency injection) =================================================== If you don't need to patch anything, you can use the :func:`fudge.test` decorator instead. This will ensure an exception is raised in case any expectations aren't met. Here's an example: .. doctest:: >>> def send_msg(api): ... if False: # a mistake ... api.UpdateStatus('hello') ... >>> @fudge.test ... def test_msg(): ... FakeOAuthApi = (fudge.Fake('OAuthApi') ... .is_callable() ... .expects('UpdateStatus')) ... api = FakeOAuthApi() ... send_msg(api) ... >>> test_msg() Traceback (most recent call last): ... AssertionError: fake:OAuthApi.UpdateStatus() was not called .. doctest:: :hide: >>> fudge.clear_expectations() Stubs Without Expectations ========================== If you want a fake object where the methods can be called but are not expected to be called, the code is just the same but instead of :meth:`Fake.expects() ` you use :meth:`Fake.provides() `. Here is an example of always returning True for the method is_logged_in(): .. doctest:: :hide: >>> import fudge .. doctest:: >>> def show_secret_word(): ... import auth ... user = auth.current_user() ... if user.is_logged_in(): ... print "Bird is the word" ... else: ... print "Access denied" ... >>> @fudge.patch('auth.current_user') ... def test_secret_word(current_user): ... user = current_user.expects_call().returns_fake() ... user = user.provides('is_logged_in').returns(True) ... show_secret_word() ... >>> test_secret_word() Bird is the word Note that if ``user.is_logged_in()`` is not called then no error will be raised because it's provided, not expected: Replacing A Method ================== Sometimes returning a static value isn't good enough, you actually need to run some code. You can do this using :meth:`Fake.calls() ` like this: .. doctest:: >>> auth = fudge.Fake() >>> def check_user(username): ... if username=='bert': ... print "Bird is the word" ... else: ... print "Access denied" ... >>> auth = auth.provides('show_secret_word_for_user').calls(check_user) >>> # Now, the check_user function gets called instead: >>> auth.show_secret_word_for_user("bert") Bird is the word >>> auth.show_secret_word_for_user("ernie") Access denied Cascading Objects ================= Some objects you might want to work with will spawn a long chain of objects. Here is an example of fudging a cascading `SQLAlchemy query `_. Notice that :meth:`Fake.returns_fake() ` is used to specify that ``session.query(User)`` should return a new object. Notice also that because query() should be iterable, it is set to return a list of fake User objects. .. doctest:: >>> import fudge >>> session = fudge.Fake('session') >>> query = (session.provides('query') ... .returns_fake() ... .provides('order_by') ... .returns( ... [fudge.Fake('User').has_attr(name='Al', lastname='Capone')] ... ) ... ) >>> from models import User >>> for instance in session.query(User).order_by(User.id): ... print instance.name, instance.lastname ... Al Capone Multiple Return Values ====================== Let's say you want to test code that needs to call a function multiple times and get back multiple values. Up until now, you've just seen the :meth:`Fake.returns() ` method which will return a value infinitely. To change that, call :meth:`Fake.next_call() ` to advance the call sequence. Here is an example using a shopping cart scenario: .. doctest:: >>> cart = (fudge.Fake('cart') ... .provides('add') ... .with_args('book') ... .returns({'contents': ['book']}) ... .next_call() ... .with_args('dvd') ... .returns({'contents': ['book', 'dvd']})) >>> cart.add('book') {'contents': ['book']} >>> cart.add('dvd') {'contents': ['book', 'dvd']} >>> cart.add('monkey') Traceback (most recent call last): ... AssertionError: This attribute of fake:cart can only be called 2 time(s). Expecting A Specific Call Order =============================== You may need to test an object that expects its methods to be called in a specific order. Just preface any calls to :func:`fudge.Fake.expects` with :func:`fudge.Fake.remember_order` like this: .. doctest:: >>> import fudge >>> session = (fudge.Fake("session").remember_order() ... .expects("get_count").returns(0) ... .expects("set_count").with_args(5) ... .expects("get_count").returns(5)) ... >>> session.get_count() 0 >>> session.set_count(5) >>> session.get_count() 5 >>> fudge.verify() A descriptive error is printed if you call things out of order: .. doctest:: :hide: >>> fudge.clear_calls() .. doctest:: >>> session.set_count(5) Traceback (most recent call last): ... AssertionError: Call #1 was fake:session.set_count(5); Expected: #1 fake:session.get_count()[0], #2 fake:session.set_count(5), #3 fake:session.get_count()[1], end .. doctest:: :hide: >>> fudge.clear_expectations() .. _creating-a-stub: Allowing any call or attribute (a complete stub) ================================================ If you need an object that lazily provides any call or any attribute then you can declare :func:`fudge.Fake.is_a_stub`. Any requested method or attribute will always return a new :class:`fudge.Fake` instance making it easier to work complex objects. Here is an example: .. doctest:: >>> Server = fudge.Fake('xmlrpclib.Server').is_a_stub() >>> pypi = Server('http://pypi.python.org/pypi') >>> pypi.list_packages() fake:xmlrpclib.Server().list_packages() >>> pypi.package_releases() fake:xmlrpclib.Server().package_releases() Stubs like this carry on infinitely: .. doctest:: >>> f = fudge.Fake('base').is_a_stub() >>> f.one.two.three().four fake:base.one.two.three().four .. note:: When using :func:`fudge.Fake.is_a_stub` you can't lazily access any attributes or methods if they have the same name as a Fake method, like ``returns()`` or ``with_args()``. You would need to declare expectations for those directly using :func:`fudge.expects`, etc. .. _working-with-arguments: Working with Arguments ====================== The :func:`fudge.Fake.with_args` method optionally allows you to declare expectations of how arguments should be sent to your object. It's usually sufficient to expect an exact argument value but sometimes you need to use :mod:`fudge.inspector` functions for dynamic values. Here is a short example: .. doctest:: >>> import fudge >>> from fudge.inspector import arg >>> image = (fudge.Fake("image") ... .expects("save") ... .with_args("JPEG", arg.endswith(".jpg"), resolution=arg.any()) ... ) This declaration is very flexible; it allows the following calls: .. doctest:: >>> image.save("JPEG", "/tmp/unicorns-and-rainbows.jpg", resolution=72) >>> image.save("JPEG", "/tmp/me-being-serious.jpg", resolution=96) .. doctest:: :hide: >>> fudge.verify() >>> fudge.clear_expectations() The :class:`Fake ` class also provides a :meth:`without_args ` method, which functions just the opposite. With it, you can declare arguments that you expect NOT to be provided. .. doctest:: >>> image = (fudge.Fake('image') ... .expects('save') ... .without_args('GIF', filename=arg.endswith('.gif'))) This expectation will pass for any call that does not provide the string ``'GIF'`` as a positional argument and does not provide a ``filename`` keyword argument that ends in ``'.gif'`` .. doctest:: >>> image.save('JPEG', filename="funny_cat6.jpg") >>> image.save('total nonsense', {'fizz': 'buzz'}) There also inverted version of all the :class:`fudge.inspector.arg ` methods, available on the ``fudge.inspector.arg_not`` object. The methods all have the same name, but assert the opposite of the ``arg`` versions. See the docstrings for the various :meth:`fudge.inspector.arg ` methods for examples of their usage. :class:`fudge.inspector.arg_not ` can also be called on an object to match anything except that object. .. doctest:: :hide: >>> fudge.verify() >>> fudge.clear_expectations() .. _Nose: http://somethingaboutorange.com/mrl/projects/nose/ .. _py.test: http://codespeak.net/py/dist/test.html That's it! See the fudge API for details: .. toctree:: :glob: api/* fudge-1.1.1/docs/index.rst0000664000175000017500000001654613213771355015716 0ustar jsattjsatt00000000000000=================== Fudge Documentation =================== Fudge is a Python module for using fake objects (mocks and stubs) to test real ones. In readable Python code, you declare what methods are available on your fake and how they should be called. Then you inject that into your application and start testing. This declarative approach means you don't have to record and playback actions and you don't have to inspect your fakes after running code. If the fake object was used incorrectly then you'll see an informative exception message with a traceback that points to the culprit. Fudge was inspired by `Mocha `_ which is a simpler version of `jMock `_. But unlike Mocha, Fudge does not automatically hijack real objects; you explicitly :ref:`patch ` them in your test. And unlike jMock, Fudge is only as strict about expectations as you want it to be. If the type of arguments sent to the fake method aren't important then you don't have to declare an expectation for them. Download / Install ================== Just type:: $ pip install fudge You can get the `pip command here`_. Fudge requires Python 2.5 or higher. .. _pip command here: http://pip.openplans.org/ .. _install-for-python-3: Installing for Python 3 ======================= As of version 0.9.5, Fudge supports Python 3. Just install `distribute`_ and type:: $ python3.x setup.py install This step will convert the Fudge source code using the 2to3 tool. .. _distribute: http://packages.python.org/distribute/ .. _fudge-source: Source ====== The Fudge source can be downloaded as a tar.gz file from http://pypi.python.org/pypi/fudge Using `Git `_ you can clone the source from https://github.com/fudge-py/fudge/ Fudge is free and open for usage under the `MIT license`_. .. _MIT license: http://en.wikipedia.org/wiki/MIT_License Contents ======== .. toctree:: :maxdepth: 2 using-fudge javascript why-fudge migrating-0.9-to-1.0 .. _fudge-api: API Reference ============= .. toctree:: :glob: api/* Contributing ============ Please submit `bugs and patches `_, preferably with tests. All contributors will be acknowledged. Thanks! Credits ======= Fudge was created by `Kumar McMillan `_ and contains contributions by Cristian Esquivias, Michael Williamson, Luis Fagundes and Jeremy Satterfield. .. _fudge-changelog: Changelog ========= - 1.1.1 - Fixes error when providing tuple or list to arg.isinstance - 1.1.0 - **Changed** `moved to github `_ and added maintainers - **Changed** remove support for python 3.1 and 3.2 in tests in lieu of 3.4 - added :func:`fudge.Fake.has_property` - added :class:`IsInstance` - added :func:`without_args` - Deprecation warnings are now real warnings. - 1.0.3 - Added :func:`fudge.Fake.is_a_stub` :ref:`documented here ` - :func:`arg.any_value() ` is **DEPRECATED** in favor of :func:`arg.any() ` - Attributes declared by :func:`fudge.Fake.has_attr` are now settable. Thanks to Mike Kent for the bug report. - Fixes ImportError when patching certain class methods like smtplib.SMTP.sendmail - Fixes representation of chained fakes for class instances. - 1.0.2 - Object patching is a lot safer in many cases and now supports getter objects and static methods. Thanks to Michael Foord and mock._patch for ideas and code. - 1.0.1 - Fixed ImportError when a patched path traverses object attributes within a module. - 1.0.0 - After extensive usage and community input, the fudge interface has been greatly simplified! - There is now a :ref:`way better pattern ` for setting up fakes. The old way is still supported but you'll want to write all new code in this pattern once you see how much easier it is. - Added :func:`fudge.patch` and :func:`fudge.test` - Added :func:`fudge.Fake.expects_call` and :func:`fudge.Fake.is_callable` - **Changed**: The tests are no longer maintained in Python 2.4 although Fudge probably still supports 2.4 - 0.9.6 - Added support to patch builtin modules. Thanks to Luis Fagundes for the patch. - 0.9.5 - **Changed**: multiple calls to :func:`fudge.Fake.expects` behave just like :func:`fudge.Fake.next_call`. The same goes for :func:`fudge.Fake.provides`. You probably won't need to update any old code for this change, it's just a convenience. - Added :func:`fudge.Fake.with_matching_args` so that expected arguments can be declared more loosely - Added :ref:`support for Python 3 ` - Improved support for Jython - 0.9.4 - Fixed bug where __init__ would always return the Fake instance of itself. Now you can return a custom object if you want. - 0.9.3 - Added ``with_args()`` to :ref:`JavaScript Fudge `. - Fixed bug where argument values that overloaded __eq__ might cause declared expectations to fail (patch from Michael Williamson, Issue 9) - Fixed bug where :func:`fudge.Fake.raises` obscured :func:`fudge.Fake.with_args` (Issue 6) - Fixed ``returns_fake()`` in JavaScript Fudge. - 0.9.2 - **Changed**: values in failed comparisons are no longer shortened when too long. - **Changed**: :func:`fudge.Fake.calls` no longer trumps expectations (i.e. :func:`fudge.Fake.with_args`) - **Changed**: :func:`fudge.Fake.with_args` is more strict. You will now see an error when arguments are not expected yet keyword arguments were expected and vice versa. This was technically a bug but is listed under changes in case you need to update your code. Note that you can work with arguments more expressively using the new :mod:`fudge.inspector` functions. - Added :mod:`fudge.inspector` for :ref:`working-with-arguments`. - Added :func:`fudge.Fake.remember_order` so that order of expected calls can be verified. - Added :func:`fudge.Fake.raises` for simulating exceptions - Added keyword :func:`fudge.Fake.next_call(for_method="other_call") ` so that arbitrary methods can be modified (not just the last one). - Fixed: error is raised if you declare multiple :func:`fudge.Fake.provides` for the same Fake. This also applies to :func:`fudge.Fake.expects` - Fixed bug where :func:`fudge.Fake.returns` did not work if you had replaced a call with :func:`fudge.Fake.calls` - Fixed bug in :func:`fudge.Fake.next_call` so that this now works: ``Fake(callable=True).next_call().returns(...)`` - Fixed: Improved Python 2.4 compatibility. - Fixed bug where ``from fudge import *`` did not import proper objects. - 0.9.1 - **DEPRECATED** fudge.start() in favor of :func:`fudge.clear_calls` - **DEPRECATED** fudge.stop() in favor of :func:`fudge.verify` - Added context manager :func:`fudge.patcher.patched_context` so the with statement can be used for patching (contributed by Cristian Esquivias) - Added :func:`fudge.Fake.times_called` to expect a certain call count (contributed by Cristian Esquivias) - Added :class:`Fake(expect_call=True) ` to indicate an expected callable. Unlike :class:`Fake(callable=True) ` the former will raise an error if not called. - 0.9.0 - first release fudge-1.1.1/PKG-INFO0000664000175000017500000000413113213771547014210 0ustar jsattjsatt00000000000000Metadata-Version: 1.1 Name: fudge Version: 1.1.1 Summary: Replace real objects with fakes (mocks, stubs, etc) while testing. Home-page: https://github.com/fudge-py/fudge Author: Kumar McMillan Author-email: kumar.mcmillan@gmail.com License: The MIT License Description-Content-Type: UNKNOWN Description: Complete documentation is available at https://fudge.readthedocs.org/en/latest/ Fudge is a Python module for using fake objects (mocks and stubs) to test real ones. In readable Python code, you declare what methods are available on your fake and how they should be called. Then you inject that into your application and start testing. This declarative approach means you don't have to record and playback actions and you don't have to inspect your fakes after running code. If the fake object was used incorrectly then you'll see an informative exception message with a traceback that points to the culprit. Here is a quick preview of how you can test code that sends email without actually sending email:: @fudge.patch('smtplib.SMTP') def test_mailer(FakeSMTP): # Declare how the SMTP class should be used: (FakeSMTP.expects_call() .expects('connect') .expects('sendmail').with_arg_count(3)) # Run production code: send_mail() # ...expectations are verified automatically at the end of the test Platform: UNKNOWN Classifier: Intended Audience :: Developers Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 2.5 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.1 Classifier: Programming Language :: Python :: 3.2 Classifier: Topic :: Software Development :: Testing fudge-1.1.1/README.md0000664000175000017500000000105012554525511014362 0ustar jsattjsatt00000000000000Fudge is a module for replacing real objects with fakes (mocks, stubs, etc) while testing. Documentation is available at [https://fudge.readthedocs.org/en/latest/](https://fudge.readthedocs.org/en/latest/) or else, you can build it from source like this: $ easy_install Sphinx $ cd docs $ make html then open `_build/html/index.html` in your web browser. To run tests, you can use tox for all supported versions of Python. You can install it with pip: $ pip install tox Then execute: $ ./run_tests.sh Or simply: $ tox fudge-1.1.1/run_tests.sh0000775000175000017500000000020212535357753015500 0ustar jsattjsatt00000000000000#!/bin/sh if [ -e "`which tox`" ]; then tox $@ else echo "**** install tox to run the tests http://codespeak.net/tox/" fifudge-1.1.1/LICENSE.txt0000664000175000017500000000206412535357753014746 0ustar jsattjsatt00000000000000 The MIT License Copyright (c) 2009 Kumar McMillan Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. fudge-1.1.1/setup.cfg0000664000175000017500000000004613213771547014735 0ustar jsattjsatt00000000000000[egg_info] tag_build = tag_date = 0 fudge-1.1.1/setup.py0000664000175000017500000000465512554526722014640 0ustar jsattjsatt00000000000000 import re import sys from setuptools import setup, find_packages version = None for line in open("./fudge/__init__.py"): m = re.search("__version__\s*=\s*(.*)", line) if m: version = m.group(1).strip()[1:-1] # quotes break assert version extra_setup = {} if sys.version_info >= (3,): extra_setup['use_2to3'] = True # extra_setup['use_2to3_fixers'] = ['your.fixers'] setup( name='fudge', version=version, description="Replace real objects with fakes (mocks, stubs, etc) while testing.", long_description=""" Complete documentation is available at https://fudge.readthedocs.org/en/latest/ Fudge is a Python module for using fake objects (mocks and stubs) to test real ones. In readable Python code, you declare what methods are available on your fake and how they should be called. Then you inject that into your application and start testing. This declarative approach means you don't have to record and playback actions and you don't have to inspect your fakes after running code. If the fake object was used incorrectly then you'll see an informative exception message with a traceback that points to the culprit. Here is a quick preview of how you can test code that sends email without actually sending email:: @fudge.patch('smtplib.SMTP') def test_mailer(FakeSMTP): # Declare how the SMTP class should be used: (FakeSMTP.expects_call() .expects('connect') .expects('sendmail').with_arg_count(3)) # Run production code: send_mail() # ...expectations are verified automatically at the end of the test """, author='Kumar McMillan', author_email='kumar.mcmillan@gmail.com', license="The MIT License", packages=find_packages(exclude=['ez_setup']), install_requires=[], url='https://github.com/fudge-py/fudge', include_package_data=True, classifiers = [ 'Intended Audience :: Developers', 'Natural Language :: English', 'Operating System :: OS Independent', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 2.5', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.1', 'Programming Language :: Python :: 3.2', 'Topic :: Software Development :: Testing' ], **extra_setup ) fudge-1.1.1/MANIFEST.in0000664000175000017500000000034112535357753014655 0ustar jsattjsatt00000000000000include *.txt recursive-include javascript/fudge *.js *.html *.txt *.py include run_tests.sh include javascript/README.txt include docs/Makefile recursive-include docs *.py *.rst include docs/_static/.hidden prune docs/_buildfudge-1.1.1/fudge/0000775000175000017500000000000013213771547014206 5ustar jsattjsatt00000000000000fudge-1.1.1/fudge/util.py0000664000175000017500000000205612535357753015545 0ustar jsattjsatt00000000000000 try: from functools import wraps except ImportError: # for python < 2.5... this is a limited set of what we need to do # with a wrapped function : def wraps(f): def wrap_with_f(new_f): new_f.__name__ = f.__name__ if hasattr(f, '__module__'): new_f.__module__ = f.__module__ return new_f return wrap_with_f def fmt_val(val, shorten=True): """Format a value for inclusion in an informative text string. """ val = repr(val) max = 50 if shorten: if len(val) > max: close = val[-1] val = val[0:max-4] + "..." if close in (">", "'", '"', ']', '}', ')'): val = val + close return val def fmt_dict_vals(dict_vals, shorten=True): """Returns list of key=val pairs formatted for inclusion in an informative text string. """ items = dict_vals.items() if not items: return [fmt_val(None, shorten=shorten)] return ["%s=%s" % (k, fmt_val(v, shorten=shorten)) for k,v in items] fudge-1.1.1/fudge/__init__.py0000664000175000017500000013566413213771355016333 0ustar jsattjsatt00000000000000 """Fudge is a module for replacing real objects with fakes (mocks, stubs, etc) while testing. See :ref:`using-fudge` for common scenarios. """ __version__ = '1.1.1' import os import re import sys import thread import warnings from fudge.exc import FakeDeclarationError from fudge.patcher import * from fudge.util import wraps, fmt_val, fmt_dict_vals __all__ = ['Fake', 'patch', 'test', 'clear_calls', 'verify', 'clear_expectations'] class Registry(object): """An internal, thread-safe registry of expected calls. You do not need to use this directly, use Fake.expects(...), etc """ def __init__(self): self.expected_calls = {} self.expected_call_order = {} self.call_stacks = [] def __contains__(self, obj): return obj in self.get_expected_calls() def clear_actual_calls(self): for exp in self.get_expected_calls(): exp.was_called = False def clear_all(self): self.clear_actual_calls() self.clear_expectations() def clear_calls(self): """Clears out any calls that were made on previously registered fake objects and resets all call stacks. You do not need to use this directly. Use fudge.clear_calls() """ self.clear_actual_calls() for stack in self.call_stacks: stack.reset() for fake, call_order in self.get_expected_call_order().items(): call_order.reset_calls() def clear_expectations(self): c = self.get_expected_calls() c[:] = [] d = self.get_expected_call_order() d.clear() def expect_call(self, expected_call): c = self.get_expected_calls() c.append(expected_call) call_order = self.get_expected_call_order() if expected_call.fake in call_order: this_call_order = call_order[expected_call.fake] this_call_order.add_expected_call(expected_call) def get_expected_calls(self): self.expected_calls.setdefault(thread.get_ident(), []) return self.expected_calls[thread.get_ident()] def get_expected_call_order(self): self.expected_call_order.setdefault(thread.get_ident(), {}) return self.expected_call_order[thread.get_ident()] def remember_expected_call_order(self, expected_call_order): ordered_fakes = self.get_expected_call_order() fake = expected_call_order.fake ## does nothing if called twice like: # Fake().remember_order().remember_order() ordered_fakes.setdefault(fake, expected_call_order) def register_call_stack(self, call_stack): self.call_stacks.append(call_stack) def verify(self): """Ensure all expected calls were called, raise AssertionError otherwise. You do not need to use this directly. Use fudge.verify() """ try: for exp in self.get_expected_calls(): exp.assert_called() exp.assert_times_called() for fake, call_order in self.get_expected_call_order().items(): call_order.assert_order_met(finalize=True) finally: self.clear_calls() registry = Registry() def clear_calls(): """Begin a new set of calls on fake objects. Specifically, clear out any calls that were made on previously registered fake objects and reset all call stacks. You should call this any time you begin making calls on fake objects. This is also available in :func:`fudge.patch`, :func:`fudge.test` and :func:`fudge.with_fakes` """ registry.clear_calls() def verify(): """Verify that all methods have been called as expected. Specifically, analyze all registered fake objects and raise an AssertionError if an expected call was never made to one or more objects. This is also available in :func:`fudge.patch`, :func:`fudge.test` and :func:`fudge.with_fakes` """ registry.verify() ## Deprecated: def start(): """Start testing with fake objects. Deprecated. Use :func:`fudge.clear_calls` instead. """ warnings.warn( "fudge.start() has been deprecated. Use fudge.clear_calls() instead", DeprecationWarning, 3) clear_calls() def stop(): """Stop testing with fake objects. Deprecated. Use :func:`fudge.verify` instead. """ warnings.warn( "fudge.stop() has been deprecated. Use fudge.verify() instead", DeprecationWarning, 3) verify() ## def clear_expectations(): registry.clear_expectations() def with_fakes(method): """Decorator that calls :func:`fudge.clear_calls` before method() and :func:`fudge.verify` afterwards. """ @wraps(method) def apply_clear_and_verify(*args, **kw): clear_calls() method(*args, **kw) verify() # if no exceptions return apply_clear_and_verify def test(method): """Decorator for a test that uses fakes directly (not patched). Most of the time you probably want to use :func:`fudge.patch` instead. .. doctest:: :hide: >>> import fudge .. doctest:: >>> @fudge.test ... def test(): ... db = fudge.Fake('db').expects('connect') ... # do stuff... ... >>> test() Traceback (most recent call last): ... AssertionError: fake:db.connect() was not called .. doctest:: :hide: >>> fudge.clear_expectations() """ @wraps(method) def clear_and_verify(*args, **kw): clear_expectations() clear_calls() try: v = method(*args, **kw) verify() # if no exceptions finally: clear_expectations() return v return clear_and_verify test.__test__ = False # Nose: do not collect class Call(object): """A call that can be made on a Fake object. You do not need to use this directly, use Fake.provides(...), etc index=None When numerical, this indicates the position of the call (as in, a CallStack) callable=False Means this object acts like a function, not a method of an object. call_order=ExpectedCallOrder() A call order to append each call to. Default is None """ def __init__(self, fake, call_name=None, index=None, callable=False, call_order=None): self.fake = fake self.call_name = call_name self.call_replacement = None self.expected_arg_count = None self.expected_kwarg_count = None self.expected_args = None self.expected_kwargs = None self.expected_matching_args = None self.expected_matching_kwargs = None self.unexpected_args = None self.unexpected_kwargs = None self.index = index self.exception_to_raise = None self.return_val = None self.was_called = False self.expected_times_called = None self.actual_times_called = 0 self.callable = callable self.call_order = call_order def __call__(self, *args, **kwargs): self.was_called = True self.actual_times_called += 1 if self.call_order: self.call_order.add_actual_call(self) self.call_order.assert_order_met(finalize=False) # make sure call count doesn't go over : if self.expected_times_called is not None and \ self.actual_times_called > self.expected_times_called: raise AssertionError( '%s was called %s time(s). Expected %s.' % ( self, self.actual_times_called, self.expected_times_called)) return_val = None replacement_return = None if self.call_replacement: replacement_return = self.call_replacement(*args, **kwargs) if self.return_val is not None: # this wins: return_value = self.return_val else: # but it is intuitive to otherwise # return the replacement's return: return_value = replacement_return # determine whether we should inspect arguments or not: with_args = (self.expected_args or self.expected_kwargs) if with_args: # check keyword args first because of python arg coercion... if self.expected_kwargs is None: self.expected_kwargs = {} # empty **kw if self.expected_kwargs != kwargs: raise AssertionError( "%s was called unexpectedly with args %s" % ( self, self._repr_call(args, kwargs, shorten_long_vals=False))) if self.expected_args is None: self.expected_args = tuple([]) # empty *args if self.expected_args != args: raise AssertionError( "%s was called unexpectedly with args %s" % ( self, self._repr_call(args, kwargs, shorten_long_vals=False))) # now check for matching keyword args. # i.e. keyword args that are only checked if the call provided them if self.expected_matching_kwargs: for expected_arg, expected_value in \ self.expected_matching_kwargs.items(): if expected_arg in kwargs: if expected_value != kwargs[expected_arg]: raise AssertionError( "%s was called unexpectedly with args %s" % ( self, self._repr_call(args, {expected_arg: kwargs[expected_arg]}, shorten_long_vals=False)) ) # now check for matching args. # i.e. args that are only checked if the call provided them if self.expected_matching_args: if self.expected_matching_args != args: raise AssertionError( "%s was called unexpectedly with args %s" % ( self, self._repr_call(args, kwargs, shorten_long_vals=False))) # determine whether we should inspect argument counts or not: with_arg_counts = (self.expected_arg_count is not None or self.expected_kwarg_count is not None) if with_arg_counts: if self.expected_arg_count is None: self.expected_arg_count = 0 if len(args) != self.expected_arg_count: raise AssertionError( "%s was called with %s arg(s) but expected %s" % ( self, len(args), self.expected_arg_count)) if self.expected_kwarg_count is None: self.expected_kwarg_count = 0 if len(kwargs.keys()) != self.expected_kwarg_count: raise AssertionError( "%s was called with %s keyword arg(s) but expected %s" % ( self, len(kwargs.keys()), self.expected_kwarg_count)) if self.unexpected_kwargs: for un_key, un_val in self.unexpected_kwargs.items(): if un_key in kwargs and kwargs[un_key] == un_val: raise AssertionError( "%s was called unexpectedly with kwarg %s=%s" % (self, un_key, un_val) ) if self.unexpected_args: for un_arg in self.unexpected_args: if un_arg in args: raise AssertionError( "%s was called unexpectedly with arg %s" % (self, un_arg) ) if self.exception_to_raise is not None: raise self.exception_to_raise return return_value ## hmmm, arg diffing (for Call().__call__()) needs more thought # def _arg_diff(self, actual_args, expected_args): # """return differnce between keywords""" # if len(actual_args) > len(expected_args): # pass # # # def _keyword_diff(self, actual_kwargs, expected_kwargs): # """returns difference between keywords. # """ # expected_keys = set(expected_kwargs.keys()) # if (len(expected_keys)<=1 and len(actual_kwargs.keys())<=1): # # no need for detailed messages # if actual_kwargs == expected_kwargs: # return (True, "") # else: # return (False, "") # # for k,v in actual_kwargs.items(): # if k not in expected_keys: # return (False, "keyword %r was not expected" % k) # if v != expected_kwargs[k]: # return (False, "%s=%r != %s=%r" % (k, v, k, expected_kwargs[k])) # expected_keys.remove(k) # # exp_key_len = len(expected_keys) # if exp_key_len: # these = exp_key_len==1 and "this keyword" or "these keywords" # return (False, "%s never showed up: %r" % (these, tuple(expected_keys))) # # return (True, "") def _repr_call(self, expected_args, expected_kwargs, shorten_long_vals=True): args = [] if expected_args: args.extend([fmt_val(a, shorten=shorten_long_vals) for a in expected_args]) if expected_kwargs: args.extend(fmt_dict_vals(expected_kwargs, shorten=shorten_long_vals)) if args: call = "(%s)" % ", ".join(args) else: call = "()" return call def __repr__(self): cls_name = repr(self.fake) if self.call_name and not self.callable: call = "%s.%s" % (cls_name, self.call_name) else: call = "%s" % cls_name call = "%s%s" % (call, self._repr_call(self.expected_args, self.expected_kwargs)) if self.index is not None: call = "%s[%s]" % (call, self.index) return call def get_call_object(self): """return self. this exists for compatibility with :class:`CallStack` """ return self def assert_times_called(self): if self.expected_times_called is not None and \ self.actual_times_called != self.expected_times_called: raise AssertionError( '%s was called %s time(s). Expected %s.' % ( self, self.actual_times_called, self.expected_times_called)) class ExpectedCall(Call): """An expectation that a call will be made on a Fake object. You do not need to use this directly, use Fake.expects(...), etc """ def __init__(self, *args, **kw): super(ExpectedCall, self).__init__(*args, **kw) registry.expect_call(self) def assert_called(self): if not self.was_called: raise AssertionError("%s was not called" % (self)) class ExpectedCallOrder(object): """An expectation that calls should be called in a specific order.""" def __init__(self, fake): self.fake = fake self._call_order = [] self._actual_calls = [] def __repr__(self): return "%r(%r)" % (self.fake, self._call_order) __str__ = __repr__ def _repr_call_list(self, call_list): if not len(call_list): return "no calls" else: stack = ["#%s %r" % (i+1,c) for i,c in enumerate(call_list)] stack.append("end") return ", ".join(stack) def add_expected_call(self, call): self._call_order.append(call) def add_actual_call(self, call): self._actual_calls.append(call) def assert_order_met(self, finalize=False): """assert that calls have been made in the right order.""" error = None actual_call_len = len(self._actual_calls) expected_call_len = len(self._call_order) if actual_call_len == 0: error = "Not enough calls were made" else: for i,call in enumerate(self._call_order): if actual_call_len < i+1: if not finalize: # we're not done asserting calls so # forget about not having enough calls continue calls_made = len(self._actual_calls) if calls_made == 1: error = "Only 1 call was made" else: error = "Only %s calls were made" % calls_made break ac_call = self._actual_calls[i] if ac_call is not call: error = "Call #%s was %r" % (i+1, ac_call) break if not error: if actual_call_len > expected_call_len: # only show the first extra call since this # will be triggered before all calls are finished: error = "#%s %s was unexpected" % ( expected_call_len+1, self._actual_calls[expected_call_len] ) if error: msg = "%s; Expected: %s" % ( error, self._repr_call_list(self._call_order)) raise AssertionError(msg) def reset_calls(self): self._actual_calls[:] = [] class CallStack(object): """A stack of :class:`Call` objects Calling this object behaves just like Call except the Call instance you operate on gets changed each time __call__() is made expected=False When True, this indicates that the call stack was derived from an expected call. This is used by Fake to register each call on the stack. call_name Name of the call """ def __init__(self, fake, initial_calls=None, expected=False, call_name=None): self.fake = fake self._pointer = 0 # position of next call to be made (can be reset) self._calls = [] if initial_calls is not None: for c in initial_calls: self.add_call(c) self.expected = expected self.call_name = call_name registry.register_call_stack(self) def __iter__(self): for c in self._calls: yield c def __repr__(self): return "<%s for %r>" % (self.__class__.__name__, self._calls) __str__ = __repr__ def add_call(self, call): self._calls.append(call) call.index = len(self._calls)-1 def get_call_object(self): """returns the last *added* call object. this is so Fake knows which one to alter """ return self._calls[len(self._calls)-1] def reset(self): self._pointer = 0 def __call__(self, *args, **kw): try: current_call = self._calls[self._pointer] except IndexError: raise AssertionError( "This attribute of %s can only be called %s time(s). " "Call reset() if necessary or fudge.clear_calls()." % ( self.fake, len(self._calls))) self._pointer += 1 return current_call(*args, **kw) class Fake(object): """A fake object that replaces a real one while testing. Most calls with a few exceptions return ``self`` so that you can chain them together to create readable code. Instance methods will raise either AssertionError or :class:`fudge.FakeDeclarationError` Keyword arguments: **name=None** Name of the class, module, or function you mean to replace. If not specified, Fake() will try to guess the name by inspecting the calling frame (if possible). **allows_any_call=False** This is **deprecated**. Use :meth:`Fake:is_a_stub()` instead. **callable=False** This is **deprecated**. Use :meth:`Fake.is_callable` instead. **expect_call=True** This is **deprecated**. Use :meth:`Fake.expects_call` instead. """ def __init__(self, name=None, allows_any_call=False, callable=False, expect_call=False): self._attributes = {} self._properties = {} self._declared_calls = {} self._name = (name or self._guess_name()) self._last_declared_call_name = None self._is_a_stub = False if allows_any_call: warnings.warn('Fake(allows_any_call=True) is deprecated;' ' use Fake.is_a_stub()', DeprecationWarning, 3) self.is_a_stub() self._call_stack = None if expect_call: self.expects_call() elif callable or allows_any_call: self.is_callable() else: self._callable = None self._expected_call_order = None def __getattribute__(self, name): """Favors stubbed out attributes, falls back to real attributes """ # this getter circumvents infinite loops: def g(n): return object.__getattribute__(self, n) if name in g('_declared_calls'): # if it's a call that has been declared # as that of the real object then hand it over: return g('_declared_calls')[name] elif name in g('_attributes'): # return attribute declared on real object return g('_attributes')[name] elif name in g('_properties'): # execute function and return result return g('_properties')[name]() else: # otherwise, first check if it's a call # of Fake itself (i.e. returns(), with_args(), etc) try: self_call = g(name) except AttributeError: pass else: return self_call if g('_is_a_stub'): # Lazily create a attribute (which might later get called): stub = Fake(name=self._endpoint_name(name)).is_a_stub() self.has_attr(**{name: stub}) return getattr(self, name) raise AttributeError( "%s object does not allow call or attribute '%s' " "(maybe you want %s.is_a_stub() ?)" % ( self, name, self.__class__.__name__)) def __call__(self, *args, **kwargs): if '__init__' in self._declared_calls: # special case, simulation of __init__(): call = self._declared_calls['__init__'] result = call(*args, **kwargs) if result is None: # assume more calls were expected / provided by the same fake return self else: # a new custom object has been declared return result elif self._callable: return self._callable(*args, **kwargs) elif self._is_a_stub: self.is_callable().returns_fake().is_a_stub() return self.__call__(*args, **kwargs) else: raise RuntimeError( "%s object cannot be called (maybe you want " "%s.is_callable() ?)" % (self, self.__class__.__name__)) def __setattr__(self, name, val): if hasattr(self, '_attributes') and name in self._attributes: self._attributes[name] = val else: object.__setattr__(self, name, val) def __repr__(self): return "fake:%s" % (self._name or "unnamed") def _declare_call(self, call_name, call): self._declared_calls[call_name] = call _assignment = re.compile(r"\s*(?P[a-zA-Z0-9_]+)\s*=\s*(fudge\.)?Fake\(.*") def _guess_asn_from_file(self, frame): if frame.f_code.co_filename: if os.path.exists(frame.f_code.co_filename): cofile = open(frame.f_code.co_filename,'r') try: for ln, line in enumerate(cofile): # I'm not sure why -1 is needed if ln==frame.f_lineno-1: possible_asn = line m = self._assignment.match(possible_asn) if m: return m.group('name') finally: cofile.close() def _guess_name(self): if not hasattr(sys, '_getframe'): # Stackless? return None if sys.platform.startswith('java'): # frame objects are completely different, # not worth the hassle. return None # get frame where class was instantiated, # my_obj = Fake() # ^ # we want to set self._name = 'my_obj' frame = sys._getframe(2) if len(frame.f_code.co_varnames): # at the top-most frame: co_names = frame.f_code.co_varnames else: # any other frame: co_names = frame.f_code.co_names # find names that are not locals. # this probably indicates my_obj = ... candidates = [n for n in co_names if n not in frame.f_locals] if len(candidates)==0: # the value was possibly queued for deref # foo = 44 # foo = Fake() return self._guess_asn_from_file(frame) elif len(candidates)==1: return candidates[0] else: # we are possibly halfway through a module # where not all names have been compiled return self._guess_asn_from_file(frame) def _get_current_call(self): if not self._last_declared_call_name: if not self._callable: raise FakeDeclarationError( "Call to a method that expects a predefined call but no such call exists. " "Maybe you forgot expects('method') or provides('method') ?") return self._callable.get_call_object() exp = self._declared_calls[self._last_declared_call_name].get_call_object() return exp def _endpoint_name(self, endpoint): p = [self._name or 'unnamed'] if endpoint != self._name: p.append(str(endpoint)) return '.'.join(p) def expects_call(self): """The fake must be called. .. doctest:: :hide: >>> import fudge >>> fudge.clear_expectations() >>> fudge.clear_calls() This is useful for when you stub out a function as opposed to a class. For example:: >>> import fudge >>> remove = fudge.Fake('os.remove').expects_call() >>> fudge.verify() Traceback (most recent call last): ... AssertionError: fake:os.remove() was not called .. doctest:: :hide: >>> fudge.clear_expectations() """ self._callable = ExpectedCall(self, call_name=self._name, callable=True) return self def is_callable(self): """The fake can be called. This is useful for when you stub out a function as opposed to a class. For example:: >>> import fudge >>> remove = Fake('os.remove').is_callable() >>> remove('some/path') """ self._callable = Call(self, call_name=self._name, callable=True) return self def is_a_stub(self): """Turns this fake into a stub. When a stub, any method is allowed to be called on the Fake() instance and any attribute can be accessed. When an unknown attribute or call is made, a new Fake() is returned. You can of course override any of this with :meth:`Fake.expects` and the other methods. """ self._is_a_stub = True return self def calls(self, call): """Redefine a call. The fake method will execute your function. I.E.:: >>> f = Fake().provides('hello').calls(lambda: 'Why, hello there') >>> f.hello() 'Why, hello there' """ exp = self._get_current_call() exp.call_replacement = call return self def expects(self, call_name): """Expect a call. .. doctest:: :hide: >>> import fudge >>> fudge.clear_expectations() >>> fudge.clear_calls() If the method *call_name* is never called, then raise an error. I.E.:: >>> session = Fake('session').expects('open').expects('close') >>> session.open() >>> fudge.verify() Traceback (most recent call last): ... AssertionError: fake:session.close() was not called .. note:: If you want to also verify the order these calls are made in, use :func:`fudge.Fake.remember_order`. When using :func:`fudge.Fake.next_call` after ``expects(...)``, each new call will be part of the expected order Declaring ``expects()`` multiple times is the same as declaring :func:`fudge.Fake.next_call` """ if call_name in self._declared_calls: return self.next_call(for_method=call_name) self._last_declared_call_name = call_name c = ExpectedCall(self, call_name, call_order=self._expected_call_order) self._declare_call(call_name, c) return self def has_attr(self, **attributes): """Sets available attributes. I.E.:: >>> User = Fake('User').provides('__init__').has_attr(name='Harry') >>> user = User() >>> user.name 'Harry' """ self._attributes.update(attributes) return self def has_property(self, **properties): """Sets available properties. I.E.:: >>> mock_name = Fake().is_callable().returns('Jim Bob') >>> mock_age = Fake().is_callable().raises(AttributeError('DOB not set')) >>> user = Fake('User').has_property(name=mock_name, age=mock_age) >>> user.name 'Jim Bob' >>> user.age Traceback (most recent call last): ... AttributeError: DOB not set """ self._properties.update(properties) return self def next_call(self, for_method=None): """Start expecting or providing multiple calls. .. note:: next_call() cannot be used in combination with :func:`fudge.Fake.times_called` Up until calling this method, calls are infinite. For example, before next_call() ... :: >>> from fudge import Fake >>> f = Fake().provides('status').returns('Awake!') >>> f.status() 'Awake!' >>> f.status() 'Awake!' After next_call() ... :: >>> from fudge import Fake >>> f = Fake().provides('status').returns('Awake!') >>> f = f.next_call().returns('Asleep') >>> f = f.next_call().returns('Dreaming') >>> f.status() 'Awake!' >>> f.status() 'Asleep' >>> f.status() 'Dreaming' >>> f.status() Traceback (most recent call last): ... AssertionError: This attribute of fake:unnamed can only be called 3 time(s). Call reset() if necessary or fudge.clear_calls(). If you need to affect the next call of something other than the last declared call, use ``next_call(for_method="other_call")``. Here is an example using getters and setters on a session object :: >>> from fudge import Fake >>> sess = Fake('session').provides('get_count').returns(1) >>> sess = sess.provides('set_count').with_args(5) Now go back and adjust return values for get_count() :: >>> sess = sess.next_call(for_method='get_count').returns(5) This allows these calls to be made :: >>> sess.get_count() 1 >>> sess.set_count(5) >>> sess.get_count() 5 When using :func:`fudge.Fake.remember_order` in combination with :func:`fudge.Fake.expects` and :func:`fudge.Fake.next_call` each new call will be part of the expected order. """ last_call_name = self._last_declared_call_name if for_method: if for_method not in self._declared_calls: raise FakeDeclarationError( "next_call(for_method=%r) is not possible; " "declare expects(%r) or provides(%r) first" % ( for_method, for_method, for_method)) else: # set this for the local function: last_call_name = for_method # reset this for subsequent methods: self._last_declared_call_name = last_call_name if last_call_name: exp = self._declared_calls[last_call_name] elif self._callable: exp = self._callable else: raise FakeDeclarationError('next_call() must follow provides(), ' 'expects() or is_callable()') if getattr(exp, 'expected_times_called', None) is not None: raise FakeDeclarationError("Cannot use next_call() in combination with times_called()") if not isinstance(exp, CallStack): # lazily create a stack with the last defined # expected call as the first on the stack: stack = CallStack(self, initial_calls=[exp], expected=isinstance(exp, ExpectedCall), call_name=exp.call_name) # replace the old call obj using the same name: if last_call_name: self._declare_call(last_call_name, stack) elif self._callable: self._callable = stack else: stack = exp # hmm, we need a copy here so that the last call # falls off the stack. if stack.expected: next_call = ExpectedCall(self, call_name=exp.call_name, call_order=self._expected_call_order) else: next_call = Call(self, call_name=exp.call_name) stack.add_call(next_call) return self def provides(self, call_name): """Provide a call. The call acts as a stub -- no error is raised if it is not called.:: >>> session = Fake('session').provides('open').provides('close') >>> import fudge >>> fudge.clear_expectations() # from any previously declared fakes >>> fudge.clear_calls() >>> session.open() >>> fudge.verify() # close() not called but no error Declaring ``provides()`` multiple times is the same as declaring :func:`fudge.Fake.next_call` """ if call_name in self._declared_calls: return self.next_call(for_method=call_name) self._last_declared_call_name = call_name c = Call(self, call_name) self._declare_call(call_name, c) return self def raises(self, exc): """Set last call to raise an exception class or instance. For example:: >>> import fudge >>> db = fudge.Fake('db').provides('insert').raises(ValueError("not enough parameters for insert")) >>> db.insert() Traceback (most recent call last): ... ValueError: not enough parameters for insert """ exp = self._get_current_call() exp.exception_to_raise = exc return self def remember_order(self): """Verify that subsequent :func:`fudge.Fake.expects` are called in the right order. For example:: >>> import fudge >>> db = fudge.Fake('db').remember_order().expects('insert').expects('update') >>> db.update() Traceback (most recent call last): ... AssertionError: Call #1 was fake:db.update(); Expected: #1 fake:db.insert(), #2 fake:db.update(), end >>> fudge.clear_expectations() When declaring multiple calls using :func:`fudge.Fake.next_call`, each subsequent call will be added to the expected order of calls :: >>> import fudge >>> sess = fudge.Fake("session").remember_order().expects("get_id").returns(1) >>> sess = sess.expects("set_id").with_args(5) >>> sess = sess.next_call(for_method="get_id").returns(5) Multiple calls to ``get_id()`` are now expected :: >>> sess.get_id() 1 >>> sess.set_id(5) >>> sess.get_id() 5 >>> fudge.verify() >>> fudge.clear_expectations() """ if self._callable: raise FakeDeclarationError( "remember_order() cannot be used for Fake(callable=True) or Fake(expect_call=True)") self._expected_call_order = ExpectedCallOrder(self) registry.remember_expected_call_order(self._expected_call_order) return self def returns(self, val): """Set the last call to return a value. Set a static value to return when a method is called. I.E.:: >>> f = Fake().provides('get_number').returns(64) >>> f.get_number() 64 """ exp = self._get_current_call() exp.return_val = val return self def returns_fake(self, *args, **kwargs): """Set the last call to return a new :class:`fudge.Fake`. Any given arguments are passed to the :class:`fudge.Fake` constructor Take note that this is different from the cascading nature of other methods. This will return an instance of the *new* Fake, not self, so you should be careful to store its return value in a new variable. I.E.:: >>> session = Fake('session') >>> query = session.provides('query').returns_fake(name="Query") >>> assert query is not session >>> query = query.provides('one').returns(['object']) >>> session.query().one() ['object'] """ exp = self._get_current_call() endpoint = kwargs.get('name', exp.call_name) name = self._endpoint_name(endpoint) kwargs['name'] = '%s()' % name fake = self.__class__(*args, **kwargs) exp.return_val = fake return fake def times_called(self, n): """Set the number of times an object can be called. When working with provided calls, you'll only see an error if the expected call count is exceeded :: >>> auth = Fake('auth').provides('login').times_called(1) >>> auth.login() >>> auth.login() Traceback (most recent call last): ... AssertionError: fake:auth.login() was called 2 time(s). Expected 1. When working with expected calls, you'll see an error if the call count is never met :: >>> import fudge >>> auth = fudge.Fake('auth').expects('login').times_called(2) >>> auth.login() >>> fudge.verify() Traceback (most recent call last): ... AssertionError: fake:auth.login() was called 1 time(s). Expected 2. .. note:: This cannot be used in combination with :func:`fudge.Fake.next_call` """ if self._last_declared_call_name: actual_last_call = self._declared_calls[self._last_declared_call_name] if isinstance(actual_last_call, CallStack): raise FakeDeclarationError("Cannot use times_called() in combination with next_call()") # else: # self._callable is in effect exp = self._get_current_call() exp.expected_times_called = n return self def with_args(self, *args, **kwargs): """Set the last call to expect specific argument values. The app under test must send all declared arguments and keyword arguments otherwise your test will raise an AssertionError. For example: .. doctest:: >>> import fudge >>> counter = fudge.Fake('counter').expects('increment').with_args(25, table='hits') >>> counter.increment(24, table='clicks') Traceback (most recent call last): ... AssertionError: fake:counter.increment(25, table='hits') was called unexpectedly with args (24, table='clicks') If you need to work with dynamic argument values consider using :func:`fudge.Fake.with_matching_args` to make looser declarations. You can also use :mod:`fudge.inspector` functions. Here is an example of providing a more flexible ``with_args()`` declaration using inspectors: .. doctest:: :hide: >>> fudge.clear_expectations() .. doctest:: >>> import fudge >>> from fudge.inspector import arg >>> counter = fudge.Fake('counter') >>> counter = counter.expects('increment').with_args( ... arg.any(), ... table=arg.endswith("hits")) ... The above declaration would allow you to call counter like this: .. doctest:: >>> counter.increment(999, table="image_hits") >>> fudge.verify() .. doctest:: :hide: >>> fudge.clear_calls() Or like this: .. doctest:: >>> counter.increment(22, table="user_profile_hits") >>> fudge.verify() .. doctest:: :hide: >>> fudge.clear_expectations() """ exp = self._get_current_call() if args: exp.expected_args = args if kwargs: exp.expected_kwargs = kwargs return self def with_matching_args(self, *args, **kwargs): """Set the last call to expect specific argument values if those arguments exist. Unlike :func:`fudge.Fake.with_args` use this if you want to only declare expectations about matching arguments. Any unknown keyword arguments used by the app under test will be allowed. For example, you can declare positional arguments but ignore keyword arguments: .. doctest:: >>> import fudge >>> db = fudge.Fake('db').expects('transaction').with_matching_args('insert') With this declaration, any keyword argument is allowed: .. doctest:: >>> db.transaction('insert', isolation_level='lock') >>> db.transaction('insert', isolation_level='shared') >>> db.transaction('insert', retry_on_error=True) .. doctest:: :hide: >>> fudge.clear_expectations() .. note:: you may get more mileage out of :mod:`fudge.inspector` functions as described in :func:`fudge.Fake.with_args` """ exp = self._get_current_call() if args: exp.expected_matching_args = args if kwargs: exp.expected_matching_kwargs = kwargs return self def without_args(self, *args, **kwargs): """Set the last call to expect that certain arguments will not exist. This is the opposite of :func:`fudge.Fake.with_matching_args`. It will fail if any of the arguments are passed. .. doctest:: >>> import fudge >>> query = fudge.Fake('query').expects_call().without_args( ... 'http://example.com', name="Steve" ... ) >>> query('http://python.org', name="Joe") >>> query('http://example.com') Traceback (most recent call last): ... AssertionError: fake:query() was called unexpectedly with arg http://example.com >>> query("Joe", "Frank", "Bartholomew", "Steve") >>> query(name='Steve') Traceback (most recent call last): ... AssertionError: fake:query() was called unexpectedly with kwarg name=Steve >>> query('http://python.org', name='Steve') Traceback (most recent call last): ... AssertionError: fake:query() was called unexpectedly with kwarg name=Steve >>> query(city='Chicago', name='Steve') Traceback (most recent call last): ... AssertionError: fake:query() was called unexpectedly with kwarg name=Steve >>> query.expects_call().without_args('http://example2.com') fake:query >>> query('foobar') >>> query('foobar', 'http://example2.com') Traceback (most recent call last): ... AssertionError: fake:query() was called unexpectedly with arg http://example2.com >>> query.expects_call().without_args(name="Hieronymus") fake:query >>> query("Gottfried", "Hieronymus") >>> query(name="Wexter", other_name="Hieronymus") >>> query('asdf', name="Hieronymus") Traceback (most recent call last): ... AssertionError: fake:query() was called unexpectedly with kwarg name=Hieronymus >>> query(name="Hieronymus") Traceback (most recent call last): ... AssertionError: fake:query() was called unexpectedly with kwarg name=Hieronymus >>> query = fudge.Fake('query').expects_call().without_args( ... 'http://example.com', name="Steve" ... ).with_args('dog') >>> query('dog') >>> query('dog', 'http://example.com') Traceback (most recent call last): ... AssertionError: fake:query('dog') was called unexpectedly with args ('dog', 'http://example.com') >>> query() Traceback (most recent call last): ... AssertionError: fake:query('dog') was called unexpectedly with args () """ exp = self._get_current_call() if args: exp.unexpected_args = args if kwargs: exp.unexpected_kwargs = kwargs return self def with_arg_count(self, count): """Set the last call to expect an exact argument count. I.E.:: >>> auth = Fake('auth').provides('login').with_arg_count(2) >>> auth.login('joe_user') # forgot password Traceback (most recent call last): ... AssertionError: fake:auth.login() was called with 1 arg(s) but expected 2 """ exp = self._get_current_call() exp.expected_arg_count = count return self def with_kwarg_count(self, count): """Set the last call to expect an exact count of keyword arguments. I.E.:: >>> auth = Fake('auth').provides('login').with_kwarg_count(2) >>> auth.login(username='joe') # forgot password= Traceback (most recent call last): ... AssertionError: fake:auth.login() was called with 1 keyword arg(s) but expected 2 """ exp = self._get_current_call() exp.expected_kwarg_count = count return self fudge-1.1.1/fudge/exc.py0000664000175000017500000000035012535357753015342 0ustar jsattjsatt00000000000000 """Exceptions used by the fudge module. See :ref:`using-fudge` for common scenarios. """ __all__ = ['FakeDeclarationError'] class FakeDeclarationError(Exception): """Exception in how this :class:`fudge.Fake` was declared."""fudge-1.1.1/fudge/tests/0000775000175000017500000000000013213771547015350 5ustar jsattjsatt00000000000000fudge-1.1.1/fudge/tests/test_inspector.py0000664000175000017500000002124313213771355020766 0ustar jsattjsatt00000000000000# -*- coding: utf-8 -*- import re import unittest from nose.tools import eq_, raises import fudge from fudge import inspector from fudge.inspector import arg, arg_not from fudge import Fake class TestAnyValue(unittest.TestCase): def tearDown(self): fudge.clear_expectations() def test_any_value(self): db = Fake("db").expects("execute").with_args(arg.any()) db.execute("delete from foo where 1") def test_repr(self): any = inspector.AnyValue() eq_(repr(any), "arg.any()") def test_str(self): any = inspector.AnyValue() eq_(str(any), "arg.any()") class TestPassesTest(unittest.TestCase): def tearDown(self): fudge.clear_expectations() def test_passes(self): def isint(v): return isinstance(v,int) counter = Fake("counter").expects("increment").with_args(arg.passes_test(isint)) counter.increment(25) @raises(AssertionError) def test_passes_fail(self): def is_str(v): return isinstance(v,str) counter = Fake("counter").expects("set_name").with_args(arg.passes_test(is_str)) counter.set_name(25) def test_repr(self): class test(object): def __call__(self, v): return isinstance(v,int) def __repr__(self): return "v is an int" passes = inspector.PassesTest(test()) eq_(repr(passes), "arg.passes_test(v is an int)") def test_str(self): class test(object): def __call__(self, v): return isinstance(v,int) def __repr__(self): return "v is an int" passes = inspector.PassesTest(test()) eq_(str(passes), "arg.passes_test(v is an int)") class TestIsInstance(unittest.TestCase): def tearDown(self): fudge.clear_expectations() def test_passes(self): counter = Fake("counter").expects("increment").with_args(arg.isinstance(int)) counter.increment(25) @raises(AssertionError) def test_passes_fail(self): counter = Fake("counter").expects("set_name").with_args(arg.isinstance(str)) counter.set_name(25) def test_repr(self): passes = inspector.IsInstance(int) eq_(repr(passes), "arg.isinstance('int')") def test_str(self): passes = inspector.IsInstance(str) eq_(str(passes), "arg.isinstance('str')") def test_list(self): passes = inspector.IsInstance((str, int)) eq_(str(passes), "arg.isinstance(('str', 'int'))") class TestObjectlike(unittest.TestCase): def tearDown(self): fudge.clear_expectations() def test_has_attr_ok(self): class Config(object): size = 12 color = 'red' weight = 'heavy' widget = Fake("widget").expects("configure")\ .with_args(arg.has_attr(size=12,color='red')) widget.configure(Config()) @raises(AssertionError) def test_has_attr_fail(self): class Config(object): color = 'red' widget = Fake("widget").expects("configure")\ .with_args(arg.has_attr(size=12)) widget.configure(Config()) @raises(AssertionError) def test_has_attr_fail_wrong_value(self): class Config(object): color = 'red' widget = Fake("widget").expects("configure")\ .with_args(arg.has_attr(color="green")) widget.configure(Config()) def test_objectlike_str(self): o = inspector.HasAttr(one=1, two="two") eq_(str(o), "arg.has_attr(one=1, two='two')") def test_objectlike_repr(self): o = inspector.HasAttr(one=1, two="two") eq_(repr(o), "arg.has_attr(one=1, two='two')") def test_objectlike_unicode(self): o = inspector.HasAttr(one=1, ivan=u"Ivan_Krsti\u0107") eq_(repr(o), "arg.has_attr(ivan=%s, one=1)" % repr(u'Ivan_Krsti\u0107')) def test_objectlike_repr_long_val(self): o = inspector.HasAttr( bytes="011110101000101010011111111110000001010100000001110000000011") eq_(repr(o), "arg.has_attr(bytes='011110101000101010011111111110000001010100000...')") class TestStringlike(unittest.TestCase): def tearDown(self): fudge.clear_expectations() def test_startswith_ok(self): db = Fake("db").expects("execute").with_args(arg.startswith("insert into")) db.execute("insert into foo values (1,2,3,4)") @raises(AssertionError) def test_startswith_fail(self): db = Fake("db").expects("execute").with_args(arg.startswith("insert into")) db.execute("select from") def test_startswith_ok_uni(self): db = Fake("db").expects("execute").with_args(arg.startswith(u"Ivan_Krsti\u0107")) db.execute(u"Ivan_Krsti\u0107(); foo();") def test_startswith_unicode(self): p = inspector.Startswith(u"Ivan_Krsti\u0107") eq_(repr(p), "arg.startswith(%s)" % repr(u'Ivan_Krsti\u0107')) def test_endswith_ok(self): db = Fake("db").expects("execute").with_args(arg.endswith("values (1,2,3,4)")) db.execute("insert into foo values (1,2,3,4)") def test_endswith_ok_uni(self): db = Fake("db").expects("execute").with_args(arg.endswith(u"Ivan Krsti\u0107")) db.execute(u"select Ivan Krsti\u0107") def test_endswith_unicode(self): p = inspector.Endswith(u"Ivan_Krsti\u0107") eq_(repr(p), "arg.endswith(%s)" % repr(u'Ivan_Krsti\u0107')) def test_startswith_repr(self): p = inspector.Startswith("_start") eq_(repr(p), "arg.startswith('_start')") def test_endswith_repr(self): p = inspector.Endswith("_ending") eq_(repr(p), "arg.endswith('_ending')") def test_startswith_str(self): p = inspector.Startswith("_start") eq_(str(p), "arg.startswith('_start')") def test_endswith_str(self): p = inspector.Endswith("_ending") eq_(str(p), "arg.endswith('_ending')") def test_startswith_str_long_value(self): p = inspector.Startswith( "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" ) eq_(str(p), "arg.startswith('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA...')" ) def test_endswith_str_long_value(self): p = inspector.Endswith( "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" ) eq_(str(p), "arg.endswith('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA...')" ) class TestContains(unittest.TestCase): def tearDown(self): fudge.clear_expectations() def test_contains_str(self): db = Fake("db").expects("execute").with_args(arg.contains("table foo")) db.execute("select into table foo;") db.execute("select * from table foo where bar = 1") fudge.verify() @raises(AssertionError) def test_contains_fail(self): db = Fake("db").expects("execute").with_args(arg.contains("table foo")) db.execute("select into table notyourmama;") fudge.verify() def test_contains_list(self): db = Fake("db").expects("execute_statements").with_args( arg.contains("select * from foo")) db.execute_statements([ "update foo", "select * from foo", "drop table foo" ]) fudge.verify() def test_str(self): c = inspector.Contains(":part:") eq_(str(c), "arg.contains(':part:')") def test_str_long_val(self): c = inspector.Contains( "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") eq_(str(c), "arg.contains('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA...')") def test_repr(self): c = inspector.Contains(":part:") eq_(repr(c), "arg.contains(':part:')") def test_unicode(self): c = inspector.Contains(u"Ivan_Krsti\u0107") eq_(repr(c), "arg.contains(%s)" % repr(u'Ivan_Krsti\u0107')) class TestMakeValueTest(unittest.TestCase): def test_no_invert_just_inits(self): vi = inspector.ValueInspector() vi.invert_eq = False def value_test(*args, **kwargs): return 'hello, friends' ret = vi._make_value_test(value_test, 'asdf', foo='bar') self.assertEqual(ret, 'hello, friends') def test_invert_returns_inverter(self): vi = inspector.ValueInspector() vi.invert_eq = True class vt(object): def __eq__(self, other): return True inverter = vi._make_value_test(vt) assert isinstance(inverter, vt) self.assertEqual(inverter.__eq__('whatever'), False) fudge-1.1.1/fudge/tests/test_inspector_import_all.py0000664000175000017500000000012412535357753023213 0ustar jsattjsatt00000000000000 from fudge.inspector import * def test_import_all(): assert "arg" in globals()fudge-1.1.1/fudge/tests/__init__.py0000664000175000017500000000000012535357753017454 0ustar jsattjsatt00000000000000fudge-1.1.1/fudge/tests/support/0000775000175000017500000000000013213771547017064 5ustar jsattjsatt00000000000000fudge-1.1.1/fudge/tests/support/_for_patch.py0000664000175000017500000000006112535357753021544 0ustar jsattjsatt00000000000000class some_object: class inner: pass fudge-1.1.1/fudge/tests/support/__init__.py0000664000175000017500000000000012535357753021170 0ustar jsattjsatt00000000000000fudge-1.1.1/fudge/tests/_py3_suite.py0000664000175000017500000000061412535357753020013 0ustar jsattjsatt00000000000000 # This is a hack to make the built (2to3) tests # run in Python 3. That is, so Nose discovers all tests. # See tox.ini # FIXME: this is dumb from fudge.tests.test_fudge import * from fudge.tests.test_import_all import * from fudge.tests.test_inspector import * from fudge.tests.test_inspector_import_all import * from fudge.tests.test_patcher import * from fudge.tests.test_registry import *fudge-1.1.1/fudge/tests/test_import_all.py0000664000175000017500000000037612535357753021136 0ustar jsattjsatt00000000000000 from fudge import * def test_import_all(): assert "clear_calls" in globals() assert "clear_expectations" in globals() assert "verify" in globals() assert "Fake" in globals() assert "test" in globals() assert "patch" in globals()fudge-1.1.1/fudge/tests/test_fudge.py0000664000175000017500000011636512535367261020067 0ustar jsattjsatt00000000000000from __future__ import with_statement import sys import unittest from nose.tools import eq_, raises from nose.exc import SkipTest import fudge from fudge.inspector import arg from fudge import ( Fake, Registry, ExpectedCall, ExpectedCallOrder, Call, CallStack, FakeDeclarationError) def test_decorator_on_def(): class holder: test_called = False bobby = fudge.Fake() bobby.expects("suzie_called") @raises(AssertionError) @fudge.with_fakes def some_test(): holder.test_called = True eq_(some_test.__name__, 'some_test') some_test() eq_(holder.test_called, True) # for a test below _some_fake = fudge.Fake() class TestFake(unittest.TestCase): def test_guess_name(self): if sys.platform.startswith('java'): raise SkipTest("not supported") my_obj = fudge.Fake() eq_(repr(my_obj), "fake:my_obj") def test_guess_name_globals(self): if sys.platform.startswith('java'): raise SkipTest("not supported") eq_(repr(_some_fake), "fake:_some_fake") def test_guess_name_deref(self): if sys.platform.startswith('java'): raise SkipTest("not supported") my_obj = 44 my_obj = fudge.Fake() eq_(repr(my_obj), "fake:my_obj") def test_has_attr(self): my_obj = fudge.Fake().has_attr(vice='versa', beach='playa') eq_(my_obj.vice, 'versa') eq_(my_obj.beach, 'playa') def test_has_property(self): fake_vise = fudge.Fake().is_callable().returns('versa') fake_stuff = fudge.Fake().is_callable().raises( Exception('broken stuff')) my_obj = fudge.Fake().has_property(vice=fake_vise, stuff=fake_stuff) eq_(my_obj.vice, 'versa') try: my_obj.stuff except Exception, exc: eq_(str(exc), 'broken stuff') else: raise RuntimeError('expected Exception') def test_attributes_are_settable(self): my_obj = fudge.Fake().has_attr(vice='versa') my_obj.vice = 'miami' eq_(my_obj.vice, 'miami') def test_none_type_attributes_are_settable(self): my_obj = fudge.Fake().has_attr(vice=None) eq_(my_obj.vice, None) my_obj.vice = 'miami' eq_(my_obj.vice, 'miami') def test_attributes_can_replace_internals(self): my_obj = fudge.Fake().has_attr(provides='hijacked') eq_(my_obj.provides, 'hijacked') def test_repr_shortens_long_values(self): fake = Fake("widget").provides("set_bits").with_args( "12345678910111213141516171819202122232425262728293031" ) try: fake.set_bits() except AssertionError, exc: eq_(str(exc), "fake:widget.set_bits('123456789101112131415161718192021222324252627...') " "was called unexpectedly with args ()") else: raise RuntimeError("expected AssertionError") class TestChainedNames(unittest.TestCase): def setUp(self): self.fake = fudge.Fake('db.Adapter') def tearDown(self): fudge.clear_expectations() def test_basic(self): eq_(repr(self.fake), 'fake:db.Adapter') def test_nesting(self): f = self.fake f = f.provides('query').returns_fake().provides('fetchall') eq_(repr(f), 'fake:db.Adapter.query()') f = f.provides('cursor').returns_fake() eq_(repr(f), 'fake:db.Adapter.query().cursor()') def test_more_nesting(self): class ctx: fake = None @fudge.patch('smtplib.SMTP') def test(fake_SMTP): (fake_SMTP.is_callable() .returns_fake() .provides('sendmail')) ctx.fake = fake_SMTP() test() eq_(str(ctx.fake), 'fake:smtplib.SMTP()') class TestIsAStub(unittest.TestCase): def tearDown(self): fudge.clear_expectations() def test_is_callable(self): f = fudge.Fake().is_a_stub() result = f() assert isinstance(result, fudge.Fake) def test_infinite_callables(self): f = fudge.Fake().is_a_stub() result = f()()() assert isinstance(result, fudge.Fake) def test_is_any_call(self): f = fudge.Fake().is_a_stub() assert isinstance(f.foobar(), fudge.Fake) assert isinstance(f.foozilated(), fudge.Fake) def test_is_any_call_with_any_args(self): f = fudge.Fake().is_a_stub() assert isinstance(f.foobar(blazz='Blaz', kudoz='Klazzed'), fudge.Fake) def test_stubs_are_infinite(self): f = fudge.Fake().is_a_stub() assert isinstance(f.one().two().three(), fudge.Fake) def test_stubs_have_any_attribute(self): f = fudge.Fake().is_a_stub() assert isinstance(f.foo, fudge.Fake) assert isinstance(f.bar, fudge.Fake) def test_attributes_are_infinite(self): f = fudge.Fake().is_a_stub() assert isinstance(f.foo.bar.barfoo, fudge.Fake) def test_infinite_path_expectation(self): f = fudge.Fake().is_a_stub() f.foo.bar().expects('barfoo') f.foo.bar().barfoo() @raises(AssertionError) def test_infinite_path_expectation_is_verified(self): f = fudge.Fake().is_a_stub() f.foo.bar().expects('barfoo').with_args(foo='bar') f.foo.bar().barfoo() fudge.verify() def test_infinite_path_naming(self): f = fudge.Fake(name='base').is_a_stub() eq_(str(f.foo.bar().baz), 'fake:base.foo.bar().baz') class TestLongArgValues(unittest.TestCase): def test_arg_diffs_are_not_shortened(self): fake = Fake("widget").provides("set_bits").with_args( "12345678910111213141516171819202122232425262728293031" ) try: # this should not be shortened but the above arg spec should: fake.set_bits("99999999999999999999999999999999999999999999999999999999") except AssertionError, exc: eq_(str(exc), "fake:widget.set_bits('123456789101112131415161718192021222324252627...') " "was called unexpectedly with args " "('99999999999999999999999999999999999999999999999999999999')") else: raise RuntimeError("expected AssertionError") def test_kwarg_diffs_are_not_shortened(self): fake = Fake("widget").provides("set_bits").with_args( newbits="12345678910111213141516171819202122232425262728293031" ) try: # this should not be shortened but the above arg spec should: fake.set_bits(newbits="99999999999999999999999999999999999999999999999999999999") except AssertionError, exc: eq_(str(exc), "fake:widget.set_bits(newbits='123456789101112131415161718192021222324252627...') " "was called unexpectedly with args " "(newbits='99999999999999999999999999999999999999999999999999999999')") else: raise RuntimeError("expected AssertionError") class TestArguments(unittest.TestCase): def setUp(self): self.fake = fudge.Fake() def tearDown(self): fudge.clear_expectations() @raises(AssertionError) def test_wrong_args(self): exp = self.fake.expects('theCall').with_args( 1, "ho ho ho ho ho ho ho, it's Santa", {'ditto':False}) exp.theCall() @raises(AssertionError) def test_wrong_kwargs(self): exp = self.fake.expects('other').with_args(one="twozy", items=[1,2,3,4]) exp.other(nice="NOT NICE") @raises(AssertionError) def test_arg_count(self): exp = self.fake.expects('one').with_arg_count(3) exp.one('no', 'maybe') @raises(AssertionError) def test_kwarg_count(self): exp = self.fake.expects('__init__').with_kwarg_count(2) exp(maybe="yes, maybe") @raises(FakeDeclarationError) def test_with_args_requires_a_method(self): self.fake.with_args('something') @raises(AssertionError) def test_with_args_can_operate_on_provision(self): self.fake.provides("not_expected").with_args('something') self.fake.not_expected() # should still raise arg error @raises(AssertionError) def test_with_args_checks_args(self): self.fake.expects('count').with_args('one', two='two') self.fake.count(two='two') @raises(AssertionError) def test_with_args_checks_kwargs(self): self.fake.expects('count').with_args('one', two='two') self.fake.count('one') @raises(AssertionError) def test_raises_does_not_obscure_with_kwargs(self): # previously, this test failed because the raises(exc) # was raised too early. Issue 6 self.fake.expects('count').with_args(two='two').raises(RuntimeError('bork')) self.fake.count('one') # wrong kwargs @raises(AssertionError) def test_raises_does_not_obscure_with_args(self): # Issue 6 self.fake.expects('count').with_args('one').raises(RuntimeError('bork')) self.fake.count(two='two') # wrong args @raises(AssertionError) def test_too_many_args(self): db = Fake("db").expects("execute").with_args(bind={'one':1}) db.execute("select foozilate()", bind={'one':1}) # unexpected statement arg def test_zero_keywords_ok(self): mail = fudge.Fake('mail').expects('send').with_arg_count(3) mail.send("you", "me", "hi") # no kw args should be ok def test_zero_args_ok(self): mail = fudge.Fake('mail').expects('send').with_kwarg_count(3) mail.send(to="you", from_="me", msg="hi") # no args should be ok def test_with_args_with_object_that_is_never_equal_to_anything(self): class NeverEqual(object): def __eq__(self, other): return False obj = NeverEqual() self.fake.expects('save').with_args(arg.any()) self.fake.save(obj) # this should pass but was failing prior to issue 9 def test_with_kwargs_with_object_that_is_never_equal_to_anything(self): class NeverEqual(object): def __eq__(self, other): return False obj = NeverEqual() self.fake.expects('save').with_args(foo=arg.any()) self.fake.save(foo=obj) # this should pass but was failing prior to issue 9 def test_with_matching_positional_args(self): db = self.fake db.expects('transaction').with_matching_args('insert') db.transaction("insert", isolation_level="lock") fudge.verify() def test_with_matching_keyword_args(self): db = self.fake db.expects('transaction').with_matching_args(isolation_level="lock") db.transaction("insert", isolation_level="lock") fudge.verify() @raises(AssertionError) def test_with_non_matching_positional_args(self): db = self.fake db.expects('transaction').with_matching_args('update') db.transaction("insert", isolation_level="lock") fudge.verify() @raises(AssertionError) def test_with_too_many_non_matching_positional_args(self): # this may be unintuitve but specifying too many # arguments constitutes as non-matching. Why? # Because how else is it possible to implement, by index? db = self.fake db.expects('transaction').with_matching_args('update') db.transaction("update", "delete", isolation_level="lock") fudge.verify() @raises(AssertionError) def test_with_non_matching_keyword_args(self): db = self.fake db.expects('transaction').with_matching_args(isolation_level="read") db.transaction("insert", isolation_level="lock") fudge.verify() @raises(AssertionError) def test_missing_matching_positional_args_is_not_ok(self): # this is awkward to implement so I think it should not be supported db = self.fake db.expects('transaction').with_matching_args("update") db.transaction() fudge.verify() def test_missing_matching_keyword_args_is_ok(self): db = self.fake db.expects('transaction').with_matching_args(isolation_level="read") db.transaction() fudge.verify() ## hmm, arg diffing needs more thought # class TestArgDiff(unittest.TestCase): # # def setUp(self): # self.fake = Fake("foo") # self.call = Call(self.fake) # # def test_empty(self): # eq_(self.call._arg_diff(tuple(), tuple()), "") # # def test_one_unexpected(self): # eq_(self.call._arg_diff(('one',), tuple()), "arg #1 was unexpected") # # def test_one_missing(self): # eq_(self.call._arg_diff(tuple(), ('one',)), "arg #1 never showed up") # # def test_four_unexpected(self): # eq_(self.call._arg_diff( # ('one','two','three','four'), # ('one','two','three')), "arg #4 was unexpected") # # def test_four_missing(self): # eq_(self.call._arg_diff( # ('one','two','three'), # ('one','two','three','four')), "arg #4 never showed up") # # def test_one_mismatch(self): # eq_(self.call._arg_diff(('won',), ('one',)), "arg #1 'won' != 'one'") # # def test_four_mismatch(self): # eq_(self.call._arg_diff( # ('one','two','three','not four'), # ('one','two','three','four')), "arg #4 'not four' != 'four'") # class TestKeywordArgDiff(unittest.TestCase): # # def setUp(self): # self.fake = Fake("foo") # self.call = Call(self.fake) # # def test_empty(self): # eq_(self.call._keyword_diff({}, {}), (True, "")) # # def test_one_empty(self): # eq_(self.call._keyword_diff({}, # {'something':'here','another':'there'}), # (False, "these keywords never showed up: ('something', 'another')")) # # def test_complex_match_yields_no_reason(self): # actual = {'num':1, 'two':2, 'three':3} # expected = {'num':1, 'two':2, 'three':3} # eq_(self.call._keyword_diff(actual, expected), (True, "")) # # def test_simple_mismatch_yields_no_reason(self): # actual = {'num':1} # expected = {'num':2} # eq_(self.call._keyword_diff(actual, expected), (False, "")) # # def test_simple_match_yields_no_reason(self): # actual = {'num':1} # expected = {'num':1} # eq_(self.call._keyword_diff(actual, expected), (True, "")) # # def test_actual_kw_extra_key(self): # actual = {'one':1, 'two':2} # expected = {'one':1} # eq_(self.call._keyword_diff(actual, expected), # (False, "keyword 'two' was not expected")) # # def test_actual_kw_value_inequal(self): # actual = {'one':1, 'two':2} # expected = {'one':1, 'two':3} # eq_(self.call._keyword_diff(actual, expected), # (False, "two=2 != two=3")) # # def test_expected_kw_extra_key(self): # actual = {'one':1} # expected = {'one':1, 'two':2} # eq_(self.call._keyword_diff(actual, expected), # (False, "this keyword never showed up: ('two',)")) # # def test_expected_kw_value_inequal(self): # actual = {'one':1, 'two':'not two'} # expected = {'one':1, 'two':2} # eq_(self.call._keyword_diff(actual, expected), # (False, "two='not two' != two=2")) class TestCall(unittest.TestCase): def setUp(self): self.fake = fudge.Fake('SMTP') def test_repr(self): s = Call(self.fake) eq_(repr(s), "fake:SMTP()") def test_repr_callable(self): s = Call(self.fake.is_callable()) eq_(repr(s), "fake:SMTP()") def test_repr_with_args(self): s = Call(self.fake) s.expected_args = [1,"bad"] eq_(repr(s), "fake:SMTP(1, 'bad')") def test_repr_with_kwargs(self): s = Call(self.fake) s.expected_args = [1,"bad"] s.expected_kwargs = {'baz':'borzo'} eq_(repr(s), "fake:SMTP(1, 'bad', baz='borzo')") def test_named_repr_with_args(self): s = Call(self.fake, call_name='connect') s.expected_args = [1,"bad"] eq_(repr(s), "fake:SMTP.connect(1, 'bad')") def test_nested_named_repr_with_args(self): f = self.fake.provides('get_conn').returns_fake() s = Call(f, call_name='connect') s.expected_args = [1,"bad"] eq_(repr(s), "fake:SMTP.get_conn().connect(1, 'bad')") def test_named_repr_with_index(self): s = Call(self.fake, call_name='connect') s.expected_args = [1,"bad"] s.index = 0 eq_(repr(s), "fake:SMTP.connect(1, 'bad')[0]") s.index = 1 eq_(repr(s), "fake:SMTP.connect(1, 'bad')[1]") class TestCallStack(unittest.TestCase): def setUp(self): self.fake = fudge.Fake('SMTP') def test_calls(self): call_stack = CallStack(self.fake) c = Call(self.fake) c.return_val = 1 call_stack.add_call(c) c = Call(self.fake) c.return_val = 2 call_stack.add_call(c) eq_(call_stack(), 1) eq_(call_stack(), 2) @raises(AssertionError) def test_no_calls(self): call_stack = CallStack(self.fake) call_stack() @raises(AssertionError) def test_end_of_calls(self): call_stack = CallStack(self.fake) c = Call(self.fake) c.return_val = 1 call_stack.add_call(c) eq_(call_stack(), 1) call_stack() def test_get_call_object(self): call_stack = CallStack(self.fake) c = Call(self.fake) call_stack.add_call(c) assert call_stack.get_call_object() is c d = Call(self.fake) call_stack.add_call(d) assert call_stack.get_call_object() is d def test_with_initial_calls(self): c = Call(self.fake) c.return_val = 1 call_stack = CallStack(self.fake, initial_calls=[c]) eq_(call_stack(), 1) def test_reset(self): call_stack = CallStack(self.fake) c = Call(self.fake) c.return_val = 1 call_stack.add_call(c) c = Call(self.fake) c.return_val = 2 call_stack.add_call(c) eq_(call_stack(), 1) eq_(call_stack(), 2) call_stack.reset() eq_(call_stack(), 1) eq_(call_stack(), 2) class TestFakeCallables(unittest.TestCase): def tearDown(self): fudge.clear_expectations() @raises(RuntimeError) def test_not_callable_by_default(self): self.fake = fudge.Fake() self.fake() def test_callable(self): fake = fudge.Fake().is_callable() fake() # allow the call fudge.verify() # no error @raises(AttributeError) def test_cannot_stub_any_call_by_default(self): self.fake.Anything() # must define this first @raises(AssertionError) def test_stub_with_args(self): self.fake = fudge.Fake().is_callable().with_args(1,2) self.fake(1) @raises(AssertionError) def test_stub_with_arg_count(self): self.fake = fudge.Fake().is_callable().with_arg_count(3) self.fake('bah') @raises(AssertionError) def test_stub_with_kwarg_count(self): self.fake = fudge.Fake().is_callable().with_kwarg_count(3) self.fake(two=1) def test_stub_with_provides(self): self.fake = fudge.Fake().provides("something") self.fake.something() def test_fake_can_sabotage_itself(self): # I'm not sure if there should be a warning # for this but it seems that it should be # possible for maximum flexibility: # replace Fake.with_args() self.fake = fudge.Fake().provides("with_args").returns(1) eq_(self.fake.with_args(), 1) @raises(AssertionError) def test_stub_with_provides_and_args(self): self.fake = fudge.Fake().provides("something").with_args(1,2) self.fake.something() def test_stub_is_not_registered(self): self.fake = fudge.Fake().provides("something") exp = self.fake._get_current_call() eq_(exp.call_name, "something") assert exp not in fudge.registry @raises(RuntimeError) def test_raises_class(self): self.fake = fudge.Fake().provides("fail").raises(RuntimeError) self.fake.fail() @raises(RuntimeError) def test_raises_instance(self): self.fake = fudge.Fake().provides("fail").raises(RuntimeError("batteries ran out")) self.fake.fail() class TestReplacementCalls(unittest.TestCase): def tearDown(self): fudge.clear_expectations() def test_replace_call(self): def something(): return "hijacked" fake = fudge.Fake().provides("something").calls(something) eq_(fake.something(), "hijacked") def test_calls_mixed_with_returns(self): called = [] def something(): called.append(True) return "hijacked" fake = fudge.Fake().provides("something").calls(something).returns("other") eq_(fake.something(), "other") eq_(called, [True]) @raises(AssertionError) def test_calls_mixed_with_expectations(self): def something(): return "hijacked" # with_args() expectation should not get lost: fake = fudge.Fake().provides("something").calls(something).with_args(1,2) eq_(fake.something(), "hijacked") def test_replace_init(self): class custom_object: def hello(self): return "hi" fake = fudge.Fake().provides("__init__").returns(custom_object()) eq_(fake().hello(), "hi") class TestFakeTimesCalled(unittest.TestCase): def tearDown(self): fudge.clear_expectations() def test_when_provided(self): self.fake = fudge.Fake().provides("something").times_called(2) # this should not raise an error because the call was provided not expected fudge.verify() @raises(AssertionError) def test_when_provided_raises_on_too_many_calls(self): self.fake = fudge.Fake().provides("something").times_called(2) self.fake.something() self.fake.something() self.fake.something() # too many @raises(AssertionError) def test_when_expected(self): self.fake = fudge.Fake().expects("something").times_called(2) self.fake.something() fudge.verify() @raises(AssertionError) def test_when_expected_raises_on_too_many_calls(self): self.fake = fudge.Fake().expects("something").times_called(2) self.fake.something() self.fake.something() self.fake.something() # too many fudge.verify() @raises(AssertionError) def test_expected_callable(self): login = fudge.Fake('login',expect_call=True).times_called(2) login() fudge.verify() def test_callable_ok(self): self.fake = fudge.Fake(callable=True).times_called(2) self.fake() self.fake() fudge.verify() def test_when_provided_ok(self): self.fake = fudge.Fake().provides("something").times_called(2) self.fake.something() self.fake.something() fudge.verify() def test_when_expected_ok(self): self.fake = fudge.Fake().expects("something").times_called(2) self.fake.something() self.fake.something() fudge.verify() class TestNextCall(unittest.TestCase): def tearDown(self): fudge.clear_expectations() @raises(FakeDeclarationError) def test_next_call_then_times_called_is_error(self): self.fake = fudge.Fake().expects("hi").returns("goodday").next_call().times_called(4) self.fake.hi() self.fake.hi() fudge.verify() @raises(FakeDeclarationError) def test_times_called_then_next_call_is_error(self): self.fake = fudge.Fake().expects("hi").times_called(4).next_call() self.fake.hi() self.fake.hi() fudge.verify() def test_stacked_returns(self): fake = fudge.Fake().provides("something") fake = fake.returns(1) fake = fake.next_call() fake = fake.returns(2) fake = fake.next_call() fake = fake.returns(3) eq_(fake.something(), 1) eq_(fake.something(), 2) eq_(fake.something(), 3) @raises(AssertionError) def test_stacked_calls_are_finite(self): self.fake = fudge.Fake().provides("something") self.fake = self.fake.returns(1) self.fake = self.fake.next_call() self.fake = self.fake.returns(2) eq_(self.fake.something(), 1) eq_(self.fake.something(), 2) self.fake.something() def test_stack_is_reset_when_name_changes(self): self.fake = fudge.Fake().provides("something") self.fake = self.fake.returns(1) self.fake = self.fake.next_call() self.fake = self.fake.returns(2) self.fake = self.fake.provides("other") self.fake = self.fake.returns(3) eq_(self.fake.something(), 1) eq_(self.fake.something(), 2) eq_(self.fake.other(), 3) eq_(self.fake.other(), 3) eq_(self.fake.other(), 3) eq_(self.fake.other(), 3) eq_(self.fake.other(), 3) def test_next_call_with_multiple_returns(self): self.fake = fudge.Fake().provides("something") self.fake = self.fake.returns(1) self.fake = self.fake.next_call() self.fake = self.fake.returns(2) self.fake = self.fake.provides("other") self.fake = self.fake.returns(3) self.fake = self.fake.next_call() self.fake = self.fake.returns(4) eq_(self.fake.something(), 1) eq_(self.fake.something(), 2) eq_(self.fake.other(), 3) eq_(self.fake.other(), 4) def test_stacked_calls_do_not_collide(self): self.fake = fudge.Fake().provides("something") self.fake = self.fake.returns(1) self.fake = self.fake.next_call() self.fake = self.fake.returns(2) self.fake = self.fake.provides("other") self.fake = self.fake.returns(3) self.fake = self.fake.next_call() self.fake = self.fake.returns(4) eq_(self.fake.something(), 1) eq_(self.fake.other(), 3) eq_(self.fake.something(), 2) eq_(self.fake.other(), 4) def test_returns_are_infinite(self): self.fake = fudge.Fake().provides("something").returns(1) eq_(self.fake.something(), 1) eq_(self.fake.something(), 1) eq_(self.fake.something(), 1) eq_(self.fake.something(), 1) eq_(self.fake.something(), 1) eq_(self.fake.something(), 1) eq_(self.fake.something(), 1) eq_(self.fake.something(), 1) def test_stacked_does_not_copy_expectations(self): fake = fudge.Fake().expects("add") fake = fake.with_args(1,2).returns(3) fake = fake.next_call() fake = fake.returns(-1) eq_(fake.add(1,2), 3) eq_(fake.add(), -1) def test_stacked_calls_are_in_registry(self): fake = fudge.Fake().expects("count").with_args(1) fake = fake.next_call().with_args(2) fake = fake.next_call().with_args(3) fake = fake.next_call().with_args(4) # hmm call_stack = fake._declared_calls[fake._last_declared_call_name] calls = [c for c in call_stack] assert calls[0] in fudge.registry assert calls[1] in fudge.registry assert calls[2] in fudge.registry assert calls[3] in fudge.registry def test_stacked_calls_are_indexed(self): fake = fudge.Fake().expects("count").with_args(1) fake = fake.next_call().with_args(2) fake = fake.next_call().with_args(3) fake = fake.next_call().with_args(4) # hmm call_stack = fake._declared_calls[fake._last_declared_call_name] calls = [c for c in call_stack] eq_(calls[0].index, 0) eq_(calls[1].index, 1) eq_(calls[2].index, 2) eq_(calls[3].index, 3) def test_start_stop_resets_stack(self): fudge.clear_expectations() fake = fudge.Fake().provides("something") fake = fake.returns(1) fake = fake.next_call() fake = fake.returns(2) eq_(fake.something(), 1) eq_(fake.something(), 2) fudge.clear_calls() eq_(fake.something(), 1) eq_(fake.something(), 2) fudge.verify() eq_(fake.something(), 1) eq_(fake.something(), 2) def test_next_call_with_callables(self): login = (fudge.Fake('login') .is_callable() .returns("yes") .next_call() .returns("maybe") .next_call() .returns("no")) eq_(login(), "yes") eq_(login(), "maybe") eq_(login(), "no") def test_returns(self): db = Fake("db")\ .provides("get_id").returns(1)\ .provides("set_id")\ .next_call(for_method="get_id").returns(2) # print [c.return_val for c in db._declared_calls["get_id"]._calls] eq_(db.get_id(), 1) eq_(db.set_id(), None) eq_(db.get_id(), 2) def test_expectations_with_multiple_return_values(self): db = Fake("db")\ .expects("get_id").returns(1)\ .expects("set_id")\ .next_call(for_method="get_id").returns(2) eq_(db.get_id(), 1) eq_(db.set_id(), None) eq_(db.get_id(), 2) fudge.verify() class TestExpectsAndProvides(unittest.TestCase): def tearDown(self): fudge.clear_expectations() @raises(AssertionError) def test_nocall(self): fake = fudge.Fake() exp = fake.expects('something') fudge.verify() def test_multiple_provides_on_chained_fakes_ok(self): db = Fake("db").provides("insert").returns_fake().provides("insert") def test_multiple_expects_on_chained_fakes_ok(self): db = Fake("db").expects("insert").returns_fake().expects("insert") def test_callable_expectation(self): fake_setup = fudge.Fake('setup', expect_call=True) fake_setup() # was called so verification should pass: fudge.verify() def test_callable_expectation_with_args(self): fake_setup = (fudge.Fake('setup', expect_call=True) .with_args('')) fake_setup('') # was called so verification should pass: fudge.verify() def test_multiple_expects_act_like_next_call(self): self.fake = fudge.Fake().expects("something") self.fake = self.fake.returns(1) self.fake = self.fake.expects("something") self.fake = self.fake.returns(2) eq_(self.fake.something(), 1) eq_(self.fake.something(), 2) def test_multiple_provides_act_like_next_call(self): self.fake = fudge.Fake().provides("something") self.fake = self.fake.returns(1) self.fake = self.fake.provides("something") self.fake = self.fake.returns(2) eq_(self.fake.something(), 1) eq_(self.fake.something(), 2) def test_multiple_expects_for_sep_methods(self): self.fake = (fudge.Fake() .expects("marco") .returns(1) .expects("polo") .returns('A') .expects("marco") .returns(2) .expects("polo") .returns('B') ) eq_(self.fake.marco(), 1) eq_(self.fake.marco(), 2) eq_(self.fake.polo(), 'A') eq_(self.fake.polo(), 'B') def test_multiple_provides_for_sep_methods(self): self.fake = (fudge.Fake() .provides("marco") .returns(1) .provides("polo") .returns('A') .provides("marco") .returns(2) .provides("polo") .returns('B') ) eq_(self.fake.marco(), 1) eq_(self.fake.marco(), 2) eq_(self.fake.polo(), 'A') eq_(self.fake.polo(), 'B') class TestOrderedCalls(unittest.TestCase): def tearDown(self): fudge.clear_expectations() @raises(AssertionError) def test_out_of_order(self): fake = fudge.Fake().remember_order().expects("one").expects("two") fake.two() fake.one() fudge.verify() @raises(FakeDeclarationError) def test_cannot_remember_order_when_callable_is_true(self): fake = fudge.Fake().is_callable().remember_order() @raises(FakeDeclarationError) def test_cannot_remember_order_when_expect_call_is_true(self): fake = fudge.Fake(expect_call=True).remember_order() @raises(AssertionError) def test_not_enough_calls(self): # need to drop down a level to bypass expected calls: r = Registry() fake = Fake() call_order = ExpectedCallOrder(fake) r.remember_expected_call_order(call_order) exp = ExpectedCall(fake, "callMe", call_order=call_order) call_order.add_expected_call(exp) r.verify() @raises(AssertionError) def test_only_one_call(self): # need to drop down a level to bypass expected calls: r = Registry() fake = Fake() call_order = ExpectedCallOrder(fake) r.remember_expected_call_order(call_order) exp = ExpectedCall(fake, "one", call_order=call_order) call_order.add_expected_call(exp) exp() # call this exp = ExpectedCall(fake, "two", call_order=call_order) call_order.add_expected_call(exp) r.verify() def test_incremental_order_assertion_ok(self): # need to drop down a level to bypass expected calls: fake = Fake() call_order = ExpectedCallOrder(fake) exp = ExpectedCall(fake, "one", call_order=call_order) call_order.add_expected_call(exp) exp() # call this exp = ExpectedCall(fake, "two", call_order=call_order) call_order.add_expected_call(exp) # two() not called but assertion is not finalized: call_order.assert_order_met(finalize=False) def test_multiple_returns_affect_order(self): db = Fake("db")\ .remember_order()\ .expects("get_id").returns(1)\ .expects("set_id")\ .next_call(for_method="get_id").returns(2) eq_(db.get_id(), 1) eq_(db.set_id(), None) eq_(db.get_id(), 2) fudge.verify() @raises(AssertionError) def test_chained_fakes_honor_order(self): Thing = Fake("thing").remember_order().expects("__init__") holder = Thing.expects("get_holder").returns_fake() holder = holder.expects("init") thing = Thing() holder = thing.get_holder() # missing thing.init() fudge.verify() @raises(AssertionError) def test_too_many_calls(self): db = Fake("db")\ .remember_order()\ .expects("get_id").returns(1)\ .expects("set_id") eq_(db.get_id(), 1) eq_(db.set_id(), None) # extra : eq_(db.get_id(), 1) @raises(AssertionError) def test_expects_call_shortcut(self): remove = Fake("os.remove").expects_call() fudge.verify() assert isinstance(remove, Fake) def test_expects_call_shortcut_ok(self): remove = Fake("os.remove").expects_call() remove() fudge.verify() assert isinstance(remove, Fake) def test_provides_call_shortcut(self): remove = Fake("os.remove").is_callable() remove() assert isinstance(remove, Fake) class TestPatchedFakes(unittest.TestCase): def tearDown(self): fudge.clear_expectations() def test_expectations_are_cleared(self): class holder: test_called = False # Set up decoy expectation: fake = fudge.Fake('db').expects('save') @fudge.patch('shutil.copy') def some_test(copy): holder.test_called = True some_test() fudge.verify() # should be no errors eq_(holder.test_called, True) def test_expectations_are_verified(self): class holder: test_called = False @raises(AssertionError) @fudge.patch('shutil.copy') def some_test(copy): copy.expects('__call__') holder.test_called = True some_test() eq_(holder.test_called, True) def test_expectations_are_always_cleared(self): class holder: test_called = False @raises(RuntimeError) @fudge.patch('shutil.copy') def some_test(copy): holder.test_called = True copy.expects_call() raise RuntimeError some_test() fudge.verify() # should be no errors eq_(holder.test_called, True) def test_calls_are_cleared(self): class holder: test_called = False sess = fudge.Fake('session').expects('save') # call should be cleared: sess.save() @fudge.patch('shutil.copy') def some_test(copy): holder.test_called = True copy.expects_call().times_called(1) copy() some_test() fudge.verify() # should be no errors eq_(holder.test_called, True) def test_with_statement(self): class holder: test_called = False @raises(AssertionError) def run_test(): with fudge.patch('shutil.copy') as copy: copy.expects('__call__') holder.test_called = True run_test() eq_(holder.test_called, True) def test_with_statement_exception(self): @raises(RuntimeError) def run_test(): with fudge.patch('shutil.copy') as copy: copy.expects('__call__') raise RuntimeError() run_test() class TestNonPatchedFakeTest(unittest.TestCase): def tearDown(self): fudge.clear_expectations() def test_preserve_method(self): @fudge.test def some_test(): holder.test_called = True eq_(some_test.__name__, 'some_test') def test_expectations_are_cleared(self): class holder: test_called = False # Set up decoy expectation: fake = fudge.Fake('db').expects('save') @fudge.test def some_test(): holder.test_called = True some_test() fudge.verify() # should be no errors eq_(holder.test_called, True) def test_expectations_are_always_cleared(self): class holder: test_called = False @raises(RuntimeError) @fudge.test def some_test(): holder.test_called = True fake = fudge.Fake('db').expects('save') raise RuntimeError some_test() fudge.verify() # should be no errors eq_(holder.test_called, True) def test_calls_are_cleared(self): class holder: test_called = False sess = fudge.Fake('session').expects('save') # call should be cleared: sess.save() @fudge.test def some_test(): holder.test_called = True db = fudge.Fake('db').expects('save').times_called(1) db.save() some_test() fudge.verify() # should be no errors eq_(holder.test_called, True) @raises(AssertionError) def test_verify(self): @fudge.test def some_test(): fake = fudge.Fake('db').expects('save') some_test() fudge-1.1.1/fudge/tests/test_registry.py0000664000175000017500000001264712535357753020650 0ustar jsattjsatt00000000000000 import thread import sys import unittest import fudge from nose.exc import SkipTest from nose.tools import eq_, raises from fudge import ( Fake, Registry, ExpectedCall, ExpectedCallOrder, Call, CallStack, FakeDeclarationError) class TestRegistry(unittest.TestCase): def setUp(self): self.fake = fudge.Fake() self.reg = fudge.registry # in case of error, clear out everything: self.reg.clear_all() def tearDown(self): pass @raises(AssertionError) def test_expected_call_not_called(self): self.reg.clear_calls() self.reg.expect_call(ExpectedCall(self.fake, 'nothing')) self.reg.verify() def test_clear_calls_resets_calls(self): exp = ExpectedCall(self.fake, 'callMe') self.reg.expect_call(exp) exp() eq_(exp.was_called, True) self.reg.clear_calls() eq_(exp.was_called, False, "call was not reset by clear_calls()") def test_clear_calls_resets_call_order(self): exp_order = ExpectedCallOrder(self.fake) exp = ExpectedCall(self.fake, 'callMe', call_order=exp_order) exp_order.add_expected_call(exp) self.reg.remember_expected_call_order(exp_order) exp() eq_(exp_order._actual_calls, [exp]) self.reg.clear_calls() eq_(exp_order._actual_calls, [], "call order calls were not reset by clear_calls()") def test_verify_resets_calls(self): exp = ExpectedCall(self.fake, 'callMe') exp() eq_(exp.was_called, True) eq_(len(self.reg.get_expected_calls()), 1) self.reg.verify() eq_(exp.was_called, False, "call was not reset by verify()") eq_(len(self.reg.get_expected_calls()), 1, "verify() should not reset expectations") def test_verify_resets_call_order(self): exp_order = ExpectedCallOrder(self.fake) exp = ExpectedCall(self.fake, 'callMe', call_order=exp_order) exp_order.add_expected_call(exp) self.reg.remember_expected_call_order(exp_order) exp() eq_(exp_order._actual_calls, [exp]) self.reg.verify() eq_(exp_order._actual_calls, [], "call order calls were not reset by verify()") def test_global_verify(self): exp = ExpectedCall(self.fake, 'callMe') exp() eq_(exp.was_called, True) eq_(len(self.reg.get_expected_calls()), 1) fudge.verify() eq_(exp.was_called, False, "call was not reset by verify()") eq_(len(self.reg.get_expected_calls()), 1, "verify() should not reset expectations") def test_global_clear_expectations(self): exp = ExpectedCall(self.fake, 'callMe') exp() eq_(len(self.reg.get_expected_calls()), 1) exp_order = ExpectedCallOrder(self.fake) self.reg.remember_expected_call_order(exp_order) eq_(self.reg.get_expected_call_order().keys(), [self.fake]) fudge.clear_expectations() eq_(len(self.reg.get_expected_calls()), 0, "clear_expectations() should reset expectations") eq_(len(self.reg.get_expected_call_order().keys()), 0, "clear_expectations() should reset expected call order") def test_multithreading(self): if sys.platform.startswith('java'): raise SkipTest('this test is flaky in Jython') reg = fudge.registry class thread_run: waiting = 5 errors = [] # while this barely catches collisions # it ensures that each thread can use the registry ok def registry(num): try: try: fudge.clear_calls() fudge.clear_expectations() exp_order = ExpectedCallOrder(self.fake) reg.remember_expected_call_order(exp_order) eq_(len(reg.get_expected_call_order().keys()), 1) # registered first time on __init__ : exp = ExpectedCall(self.fake, 'callMe', call_order=exp_order) reg.expect_call(exp) reg.expect_call(exp) reg.expect_call(exp) eq_(len(reg.get_expected_calls()), 4) # actual calls: exp() exp() exp() exp() fudge.verify() fudge.clear_expectations() except Exception, er: thread_run.errors.append(er) raise finally: thread_run.waiting -= 1 thread.start_new_thread(registry, (1,)) thread.start_new_thread(registry, (2,)) thread.start_new_thread(registry, (3,)) thread.start_new_thread(registry, (4,)) thread.start_new_thread(registry, (5,)) count = 0 while thread_run.waiting > 0: count += 1 import time time.sleep(0.25) if count == 60: raise RuntimeError("timed out waiting for thread") if len(thread_run.errors): raise RuntimeError( "Error(s) in thread: %s" % ["%s: %s" % ( e.__class__.__name__, e) for e in thread_run.errors]) fudge-1.1.1/fudge/tests/test_patcher.py0000664000175000017500000002164212535357753020421 0ustar jsattjsatt00000000000000from __future__ import with_statement import inspect import unittest from nose.exc import SkipTest from nose.tools import eq_, raises import fudge class Freddie(object): pass def test_patch_obj(): class holder: exc = Exception() patched = fudge.patch_object(holder, "exc", Freddie()) eq_(type(holder.exc), type(Freddie())) patched.restore() eq_(type(holder.exc), type(Exception())) def test_patch_path(): from os.path import join as orig_join patched = fudge.patch_object("os.path", "join", Freddie()) import os.path eq_(type(os.path.join), type(Freddie())) patched.restore() eq_(type(os.path.join), type(orig_join)) def test_patch_builtin(): import datetime orig_datetime = datetime.datetime now = datetime.datetime(2010, 11, 4, 8, 19, 11, 28778) fake = fudge.Fake('now').is_callable().returns(now) patched = fudge.patch_object(datetime.datetime, 'now', fake) try: eq_(datetime.datetime.now(), now) finally: patched.restore() eq_(datetime.datetime.now, orig_datetime.now) def test_patch_long_path(): import fudge.tests.support._for_patch orig = fudge.tests.support._for_patch.some_object.inner long_path = 'fudge.tests.support._for_patch.some_object.inner' with fudge.patch(long_path) as fake: assert isinstance(fake, fudge.Fake) eq_(fudge.tests.support._for_patch.some_object.inner, orig) @raises(ImportError) def test_patch_non_existant_path(): with fudge.patch('__not_a_real_import_path.nested.one.two.three') as fake: pass @raises(AttributeError) def test_patch_non_existant_attribute(): with fudge.patch('fudge.tests.support._for_patch.does.not.exist') as fake: pass def test_patch_builtin_as_string(): import datetime orig_datetime = datetime.datetime now = datetime.datetime(2006, 11, 4, 8, 19, 11, 28778) fake_dt = fudge.Fake('datetime').provides('now').returns(now) patched = fudge.patch_object('datetime', 'datetime', fake_dt) try: # timetuple is a workaround for strange Jython behavior! eq_(datetime.datetime.now().timetuple(), now.timetuple()) finally: patched.restore() eq_(datetime.datetime.now, orig_datetime.now) def test_decorator_on_def(): class holder: test_called = False exc = Exception() @fudge.with_patched_object(holder, "exc", Freddie()) def some_test(): holder.test_called = True eq_(type(holder.exc), type(Freddie())) eq_(some_test.__name__, 'some_test') some_test() eq_(holder.test_called, True) eq_(type(holder.exc), type(Exception())) def test_decorator_on_class(): class holder: test_called = False exc = Exception() class SomeTest(object): @fudge.with_patched_object(holder, "exc", Freddie()) def some_test(self): holder.test_called = True eq_(type(holder.exc), type(Freddie())) eq_(SomeTest.some_test.__name__, 'some_test') s = SomeTest() s.some_test() eq_(holder.test_called, True) eq_(type(holder.exc), type(Exception())) def test_patched_context(): if not hasattr(fudge, "patched_context"): raise SkipTest("Cannot test with patched_context() because not in 2.5") class Boo: fargo = "is over there" ctx = fudge.patched_context(Boo, 'fargo', 'is right here') # simulate with fudge.patched_context(): ctx.__enter__() eq_(Boo.fargo, "is right here") ctx.__exit__(None, None, None) eq_(Boo.fargo, "is over there") def test_base_class_attribute(): class Base(object): foo = 'bar' class Main(Base): pass fake = fudge.Fake() p = fudge.patch_object(Main, 'foo', fake) eq_(Main.foo, fake) eq_(Base.foo, 'bar') p.restore() eq_(Main.foo, 'bar') assert 'foo' not in Main.__dict__, ('Main.foo was not restored correctly') def test_bound_methods(): class Klass(object): def method(self): return 'foozilate' instance = Klass() fake = fudge.Fake() p = fudge.patch_object(instance, 'method', fake) eq_(instance.method, fake) p.restore() eq_(instance.method(), Klass().method()) assert inspect.ismethod(instance.method) assert 'method' not in instance.__dict__, ( 'instance.method was not restored correctly') def test_staticmethod_descriptor(): class Klass(object): @staticmethod def static(): return 'OK' fake = fudge.Fake() p = fudge.patch_object(Klass, 'static', fake) eq_(Klass.static, fake) p.restore() eq_(Klass.static(), 'OK') def test_property(): class Klass(object): @property def prop(self): return 'OK' exact_prop = Klass.prop instance = Klass() fake = fudge.Fake() p = fudge.patch_object(instance, 'prop', fake) eq_(instance.prop, fake) p.restore() eq_(instance.prop, 'OK') eq_(Klass.prop, exact_prop) def test_inherited_property(): class SubKlass(object): @property def prop(self): return 'OK' class Klass(SubKlass): pass exact_prop = SubKlass.prop instance = Klass() fake = fudge.Fake() p = fudge.patch_object(instance, 'prop', fake) eq_(instance.prop, fake) p.restore() eq_(instance.prop, 'OK') assert 'prop' not in Klass.__dict__, ( 'Klass.prop was not restored properly') eq_(SubKlass.prop, exact_prop) class TestPatch(unittest.TestCase): def setUp(self): fudge.clear_expectations() def test_decorator_on_def(self): class holder: test_called = False @fudge.patch('shutil.copy') def some_test(copy): import shutil holder.test_called = True assert isinstance(copy, fudge.Fake) eq_(copy, shutil.copy) eq_(some_test.__name__, 'some_test') some_test() eq_(holder.test_called, True) import shutil assert not isinstance(shutil.copy, fudge.Fake) def test_decorator_on_class(self): class holder: test_called = False class MyTest(object): @fudge.patch('shutil.copy') def some_test(self, copy): import shutil holder.test_called = True assert isinstance(copy, fudge.Fake) eq_(copy, shutil.copy) eq_(MyTest.some_test.__name__, 'some_test') m = MyTest() m.some_test() eq_(holder.test_called, True) import shutil assert not isinstance(shutil.copy, fudge.Fake) def test_patch_many(self): class holder: test_called = False @fudge.patch('shutil.copy', 'os.remove') def some_test(copy, remove): import shutil import os holder.test_called = True assert isinstance(copy, fudge.Fake) assert isinstance(remove, fudge.Fake) eq_(copy, shutil.copy) eq_(remove, os.remove) eq_(some_test.__name__, 'some_test') some_test() eq_(holder.test_called, True) import shutil assert not isinstance(shutil.copy, fudge.Fake) import os assert not isinstance(os.remove, fudge.Fake) def test_with_patch(self): class holder: test_called = False def run_test(): with fudge.patch('shutil.copy') as copy: import shutil assert isinstance(copy, fudge.Fake) eq_(copy, shutil.copy) holder.test_called = True run_test() eq_(holder.test_called, True) import shutil assert not isinstance(shutil.copy, fudge.Fake) def test_with_multiple_patches(self): class holder: test_called = False def run_test(): with fudge.patch('shutil.copy', 'os.remove') as fakes: copy, remove = fakes import shutil import os assert isinstance(copy, fudge.Fake) assert isinstance(remove, fudge.Fake) eq_(copy, shutil.copy) eq_(remove, os.remove) holder.test_called = True run_test() eq_(holder.test_called, True) import shutil assert not isinstance(shutil.copy, fudge.Fake) import os assert not isinstance(os.remove, fudge.Fake) def test_class_method_path(self): class ctx: sendmail = None @fudge.patch('smtplib.SMTP.sendmail') def test(fake_sendmail): import smtplib s = smtplib.SMTP() ctx.sendmail = s.sendmail test() assert isinstance(ctx.sendmail, fudge.Fake) import smtplib s = smtplib.SMTP() assert not isinstance(s.sendmail, fudge.Fake) fudge-1.1.1/fudge/patcher.py0000664000175000017500000003014512535357753016216 0ustar jsattjsatt00000000000000 """Patching utilities for working with fake objects. See :ref:`using-fudge` for common scenarios. """ __all__ = ['patch_object', 'with_patched_object', 'PatchHandler', 'patched_context', 'patch'] import sys import fudge from fudge.util import wraps class patch(object): """A test decorator that patches importable names with :class:`fakes ` Each fake is exposed as an argument to the test: .. doctest:: :hide: >>> import fudge .. doctest:: >>> @fudge.patch('os.remove') ... def test(fake_remove): ... fake_remove.expects_call() ... # do stuff... ... >>> test() Traceback (most recent call last): ... AssertionError: fake:os.remove() was not called .. doctest:: :hide: >>> fudge.clear_expectations() Many paths can be patched at once: .. doctest:: >>> @fudge.patch('os.remove', ... 'shutil.rmtree') ... def test(fake_remove, fake_rmtree): ... fake_remove.is_callable() ... # do stuff... ... >>> test() For convenience, the patch method calls :func:`fudge.clear_calls`, :func:`fudge.verify`, and :func:`fudge.clear_expectations`. For that reason, you must manage all your fake objects within the test function itself. .. note:: If you are using a unittest class, you cannot declare fakes within ``setUp()`` unless you manually clear calls and clear expectations. If you do that, you'll want to use the :func:`fudge.with_fakes` decorator instead of ``@patch``. """ def __init__(self, *obj_paths): self.obj_paths = obj_paths def __call__(self, fn): @wraps(fn) def caller(*args, **kw): fakes = self.__enter__() if not isinstance(fakes, (tuple, list)): fakes = [fakes] args += tuple(fakes) value = None try: value = fn(*args, **kw) except: etype, val, tb = sys.exc_info() self.__exit__(etype, val, tb) raise etype, val, tb else: self.__exit__(None, None, None) return value return caller def __enter__(self): fudge.clear_expectations() fudge.clear_calls() self.patches = [] all_fakes = [] for path in self.obj_paths: try: target, attr = path.rsplit('.', 1) except (TypeError, ValueError): raise TypeError( "Need a valid target to patch. You supplied: %r" % path) fake = fudge.Fake(path) all_fakes.append(fake) self.patches.append(patch_object(target, attr, fake)) if len(all_fakes) == 1: return all_fakes[0] else: return all_fakes def __exit__(self, exc_type, exc_val, exc_tb): try: if not exc_type: fudge.verify() finally: for p in self.patches: p.restore() fudge.clear_expectations() def with_patched_object(obj, attr_name, patched_value): """Decorator that patches an object before the decorated method is called and restores it afterwards. This is a wrapper around :func:`fudge.patcher.patch_object` Example:: >>> from fudge import with_patched_object >>> class Session: ... state = 'clean' ... >>> @with_patched_object(Session, "state", "dirty") ... def test(): ... print Session.state ... >>> test() dirty >>> print Session.state clean """ def patcher(method): @wraps(method) def method_call(*m_args, **m_kw): patched_obj = patch_object(obj, attr_name, patched_value) try: return method(*m_args, **m_kw) finally: patched_obj.restore() return method_call return patcher class patched_context(object): """A context manager to patch an object temporarily during a `with statement`_ block. This is a wrapper around :func:`fudge.patcher.patch_object` .. lame, lame, cannot figure out how to apply __future__ to doctest so this output is currently skipped .. doctest:: python25 :options: +SKIP >>> from fudge import patched_context >>> class Session: ... state = 'clean' ... >>> with patched_context(Session, "state", "dirty"): # doctest: +SKIP ... print Session.state ... dirty >>> print Session.state clean .. _with statement: http://www.python.org/dev/peps/pep-0343/ """ def __init__(self, obj, attr_name, patched_value): # note that a @contextmanager decorator would be simpler # but it can't be used since a value cannot be yielded within a # try/finally block which is needed to restore the object on finally. self.patched_object = patch_object(obj, attr_name, patched_value) def __enter__(self): return self.patched_object def __exit__(self, exc_type, exc_val, exc_tb): self.patched_object.restore() def patch_object(obj, attr_name, patched_value): """Patches an object and returns an instance of :class:`fudge.patcher.PatchHandler` for later restoration. Note that if *obj* is not an object but a path to a module then it will be imported. You may want to use a more convenient wrapper :func:`with_patched_object` or :func:`patched_context` Example:: >>> from fudge import patch_object >>> class Session: ... state = 'clean' ... >>> patched_session = patch_object(Session, "state", "dirty") >>> Session.state 'dirty' >>> patched_session.restore() >>> Session.state 'clean' Here is another example showing how to patch multiple objects at once:: >>> class Session: ... state = 'clean' ... >>> class config: ... session_strategy = 'database' ... >>> patches = [ ... patch_object(config, "session_strategy", "filesystem"), ... patch_object(Session, "state", "dirty") ... ] >>> try: ... # your app under test would run here ... ... print "(while patched)" ... print "config.session_strategy=%r" % config.session_strategy ... print "Session.state=%r" % Session.state ... finally: ... for p in patches: ... p.restore() ... print "(patches restored)" (while patched) config.session_strategy='filesystem' Session.state='dirty' (patches restored) >>> config.session_strategy 'database' >>> Session.state 'clean' """ if isinstance(obj, (str, unicode)): obj_path = adjusted_path = obj done = False exc = None at_top_level = False while not done: try: obj = __import__(adjusted_path) done = True except ImportError: # Handle paths that traveerse object attributes. # Such as: smtplib.SMTP.connect # smtplib <- module to import adjusted_path = adjusted_path.rsplit('.', 1)[0] if not exc: exc = sys.exc_info() if at_top_level: # We're at the top level module and it doesn't exist. # Raise the first exception since it will make more sense: etype, val, tb = exc raise etype, val, tb if not adjusted_path.count('.'): at_top_level = True for part in obj_path.split('.')[1:]: obj = getattr(obj, part) handle = PatchHandler(obj, attr_name) handle.patch(patched_value) return handle class NonExistant(object): """Represents a non-existant value.""" class PatchHandler(object): """Low level patch handler that memorizes a patch so you can restore it later. You can use more convenient wrappers :func:`with_patched_object` and :func:`patched_context` """ def __init__(self, orig_object, attr_name): self.orig_object = orig_object self.attr_name = attr_name self.proxy_object = None self.orig_value, self.is_local = self._get_original(self.orig_object, self.attr_name) self.getter_class, self.getter = self._handle_getter(self.orig_object, self.attr_name) def patch(self, patched_value): """Set a new value for the attribute of the object.""" try: if self.getter: setattr(self.getter_class, self.attr_name, patched_value) else: setattr(self.orig_object, self.attr_name, patched_value) except TypeError: # Workaround for patching builtin objects: proxy_name = 'fudge_proxy_%s_%s_%s' % ( self.orig_object.__module__, self.orig_object.__name__, patched_value.__class__.__name__ ) self.proxy_object = type(proxy_name, (self.orig_object,), {self.attr_name: patched_value}) mod = sys.modules[self.orig_object.__module__] setattr(mod, self.orig_object.__name__, self.proxy_object) def restore(self): """Restore the saved value for the attribute of the object.""" if self.proxy_object is None: if self.getter: setattr(self.getter_class, self.attr_name, self.getter) elif self.is_local: setattr(self.orig_object, self.attr_name, self.orig_value) else: # Was not a local, safe to delete: delattr(self.orig_object, self.attr_name) else: setattr(sys.modules[self.orig_object.__module__], self.orig_object.__name__, self.orig_object) def _find_class_for_attr(self, cls, attr): if attr in cls.__dict__: return cls else: for base in cls.__bases__: if self._find_class_for_attr(base, attr) is not NonExistant: return base return NonExistant def _get_original(self, orig_object, name): try: value = orig_object.__dict__[name] is_local = True except (AttributeError, KeyError): value = getattr(orig_object, name, NonExistant) is_local = False if value is NonExistant: raise AttributeError( "%s does not have the attribute %r" % (orig_object, name)) return value, is_local def _get_exact_original(self, orig_object, name): if hasattr(orig_object, '__dict__'): if name not in orig_object.__dict__: # TODO: handle class objects, not just instance objects? # This is only here for Class.property.__get__ if hasattr(orig_object, '__class__'): cls = orig_object.__class__ orig_object = self._find_class_for_attr(cls, name) return orig_object def _handle_getter(self, orig_object, name): getter_class, getter = None, None exact_orig = self._get_exact_original(orig_object, name) try: ob = exact_orig.__dict__[name] except (AttributeError, KeyError): pass else: if hasattr(ob, '__get__'): getter_class = exact_orig getter = ob return getter_class, getter fudge-1.1.1/fudge/inspector.py0000664000175000017500000004230213213771355016564 0ustar jsattjsatt00000000000000 """Value inspectors that can be passed to :func:`fudge.Fake.with_args` for more expressive argument matching. As a mnemonic device, an instance of the :class:`fudge.inspector.ValueInspector` is available as "arg" : .. doctest:: >>> import fudge >>> from fudge.inspector import arg >>> image = fudge.Fake("image").expects("save").with_args(arg.endswith(".jpg")) In other words, this declares that the first argument to ``image.save()`` should end with the suffix ".jpg" .. doctest:: :hide: >>> fudge.clear_expectations() """ import warnings from fudge.util import fmt_val, fmt_dict_vals __all__ = ['arg', 'arg_not'] class ValueInspector(object): """Dispatches tests to inspect values. """ invert_eq = False def _make_value_test(self, test_class, *args, **kwargs): if not self.invert_eq: return test_class(*args, **kwargs) class ValueTestInverter(test_class): def __repr__(wrapper_self): return "(NOT) %s" % test_class.__repr__(wrapper_self) def __eq__(wrapper_self, other): return not test_class.__eq__(wrapper_self, other) return ValueTestInverter(*args, **kwargs) def any(self): """Match any value. This is pretty much just a placeholder for when you want to inspect multiple arguments but don't care about all of them. .. doctest:: >>> import fudge >>> from fudge.inspector import arg >>> db = fudge.Fake("db") >>> db = db.expects("transaction").with_args( ... "insert", isolation_level=arg.any()) ... >>> db.transaction("insert", isolation_level="lock") >>> fudge.verify() This also passes: .. doctest:: :hide: >>> fudge.clear_calls() .. doctest:: >>> db.transaction("insert", isolation_level="autocommit") >>> fudge.verify() .. doctest:: :hide: >>> fudge.clear_expectations() The arg_not version will not match anything and is probably not very useful. .. doctest:: >>> import fudge >>> from fudge.inspector import arg_not >>> query = fudge.Fake('query').expects_call().with_args( ... arg_not.any() ... ) >>> query('asdf') Traceback (most recent call last): ... AssertionError: fake:query((NOT) arg.any()) was called unexpectedly with args ('asdf') >>> query() Traceback (most recent call last): ... AssertionError: fake:query((NOT) arg.any()) was called unexpectedly with args () .. doctest:: :hide: >>> fudge.clear_expectations() """ return self._make_value_test(AnyValue) def any_value(self): """**DEPRECATED**: use :func:`arg.any() ` """ warnings.warn('arg.any_value() is deprecated in favor of arg.any()', DeprecationWarning, 3) return self.any() def contains(self, part): """Ensure that a value contains some part. This is useful for when you only care that a substring or subelement exists in a value. .. doctest:: >>> import fudge >>> from fudge.inspector import arg >>> addressbook = fudge.Fake().expects("import_").with_args( ... arg.contains("Baba Brooks")) ... >>> addressbook.import_("Bill Brooks; Baba Brooks; Henry Brooks;") >>> fudge.verify() .. doctest:: :hide: >>> fudge.clear_expectations() Since contains() just invokes the __in__() method, checking that a list item is present works as expected : .. doctest:: >>> colorpicker = fudge.Fake("colorpicker") >>> colorpicker = colorpicker.expects("select").with_args(arg.contains("red")) >>> colorpicker.select(["green","red","blue"]) >>> fudge.verify() .. doctest:: :hide: >>> fudge.clear_expectations() arg_not.contains matches an argument not containing some element. .. doctest:: >>> from fudge.inspector import arg_not >>> colorpicker = colorpicker.expects('select').with_args(arg_not.contains('blue')) >>> colorpicker.select('reddish') >>> colorpicker.select(['red', 'green']) >>> fudge.verify() >>> colorpicker.select('blue-green') Traceback (most recent call last): ... AssertionError: fake:colorpicker.select(arg.contains('red'))[0] was called unexpectedly with args ('blue-green') >>> colorpicker.select(['red', 'blue', 'green']) Traceback (most recent call last): ... AssertionError: fake:colorpicker.select((NOT) arg.contains('blue'))[1] was called unexpectedly with args (['red', 'blue', 'green']) .. doctest:: :hide: >>> fudge.clear_expectations() """ return self._make_value_test(Contains, part) def endswith(self, part): """Ensure that a value ends with some part. This is useful for when values with dynamic parts that are hard to replicate. .. doctest:: >>> import fudge >>> from fudge.inspector import arg >>> tmpfile = fudge.Fake("tempfile").expects("mkname").with_args( ... arg.endswith(".tmp")) ... >>> tmpfile.mkname("7AakkkLazUUKHKJgh908JKjlkh.tmp") >>> fudge.verify() .. doctest:: :hide: >>> fudge.clear_expectations() The arg_not version works as expected, matching arguments that do not end with the given element. .. doctest:: >>> from fudge.inspector import arg_not >>> query = fudge.Fake('query').expects_call().with_args(arg_not.endswith('Ringo')) >>> query('John, Paul, George and Steve') >>> fudge.verify() .. doctest:: :hide: >>> fudge.clear_expectations() """ return self._make_value_test(Endswith, part) def has_attr(self, **attributes): """Ensure that an object value has at least these attributes. This is useful for testing that an object has specific attributes. .. doctest:: >>> import fudge >>> from fudge.inspector import arg >>> db = fudge.Fake("db").expects("update").with_args(arg.has_attr( ... first_name="Bob", ... last_name="James" )) ... >>> class User: ... first_name = "Bob" ... last_name = "James" ... job = "jazz musician" # this is ignored ... >>> db.update(User()) >>> fudge.verify() In case of error, the other object's __repr__ will be invoked: .. doctest:: :hide: >>> fudge.clear_calls() .. doctest:: >>> class User: ... first_name = "Bob" ... ... def __repr__(self): ... return repr(dict(first_name=self.first_name)) ... >>> db.update(User()) Traceback (most recent call last): ... AssertionError: fake:db.update(arg.has_attr(first_name='Bob', last_name='James')) was called unexpectedly with args ({'first_name': 'Bob'}) When called as a method on arg_not, has_attr does the opposite, and ensures that the argument does not have the specified attributes. .. doctest:: >>> from fudge.inspector import arg_not >>> class User: ... first_name = 'Bob' ... last_name = 'Dobbs' >>> query = fudge.Fake('query').expects_call().with_args( ... arg_not.has_attr(first_name='James') ... ) >>> query(User()) >>> fudge.verify() .. doctest:: :hide: >>> fudge.clear_expectations() """ return self._make_value_test(HasAttr, **attributes) def passes_test(self, test): """Check that a value passes some test. For custom assertions you may need to create your own callable to inspect and verify a value. .. doctest:: >>> def is_valid(s): ... if s in ('active','deleted'): ... return True ... else: ... return False ... >>> import fudge >>> from fudge.inspector import arg >>> system = fudge.Fake("system") >>> system = system.expects("set_status").with_args(arg.passes_test(is_valid)) >>> system.set_status("active") >>> fudge.verify() .. doctest:: :hide: >>> fudge.clear_calls() The callable you pass takes one argument, the value, and should return True if it's an acceptable value or False if not. .. doctest:: >>> system.set_status("sleep") # doctest: +ELLIPSIS Traceback (most recent call last): ... AssertionError: fake:system.set_status(arg.passes_test(>> fudge.clear_expectations() If it makes more sense to perform assertions in your test function then be sure to return True : >>> def is_valid(s): ... assert s in ('active','deleted'), ( ... "Unexpected status value: %s" % s) ... return True ... >>> import fudge >>> from fudge.inspector import arg >>> system = fudge.Fake("system") >>> system = system.expects("set_status").with_args(arg.passes_test(is_valid)) >>> system.set_status("sleep") Traceback (most recent call last): ... AssertionError: Unexpected status value: sleep .. doctest:: :hide: >>> fudge.clear_expectations() Using the inverted version, arg_not.passes_test, asserts that the argument does not pass the provided test. .. doctest:: >>> from fudge.inspector import arg_not >>> query = fudge.Fake('query').expects_call().with_args( ... arg_not.passes_test(lambda x: x > 10) ... ) >>> query(5) >>> fudge.verify() .. doctest:: :hide: >>> fudge.clear_expectations() """ return self._make_value_test(PassesTest, test) def isinstance(self, cls): """Check that a value is instance of specified class. .. doctest:: >>> import fudge >>> from fudge.inspector import arg >>> system = fudge.Fake("system") >>> system = system.expects("set_status").with_args(arg.isinstance(str)) >>> system.set_status("active") >>> fudge.verify() .. doctest:: :hide: >>> fudge.clear_calls() Should return True if it's allowed class or False if not. .. doctest:: >>> system.set_status(31337) # doctest: +ELLIPSIS Traceback (most recent call last): ... AssertionError: fake:system.set_status(arg.isinstance('str')) was called unexpectedly with args (31337) .. doctest:: :hide: >>> fudge.clear_expectations() """ return self._make_value_test(IsInstance, cls) def startswith(self, part): """Ensure that a value starts with some part. This is useful for when values with dynamic parts that are hard to replicate. .. doctest:: >>> import fudge >>> from fudge.inspector import arg >>> keychain = fudge.Fake("keychain").expects("accept_key").with_args( ... arg.startswith("_key")) ... >>> keychain.accept_key("_key-18657yojgaodfty98618652olkj[oollk]") >>> fudge.verify() .. doctest:: :hide: >>> fudge.clear_expectations() Using arg_not.startswith instead ensures that arguments do not start with that part. .. doctest:: >>> from fudge.inspector import arg_not >>> query = fudge.Fake('query').expects_call().with_args( ... arg_not.startswith('asdf') ... ) >>> query('qwerty') >>> fudge.verify() .. doctest:: :hide: >>> fudge.clear_expectations() """ return self._make_value_test(Startswith, part) class NotValueInspector(ValueInspector): """Inherits all the argument methods from ValueInspector, but inverts them to expect the opposite. See the ValueInspector method docstrings for examples. """ invert_eq = True def __call__(self, thing): """This will match any value except the argument given. .. doctest:: >>> import fudge >>> from fudge.inspector import arg, arg_not >>> query = fudge.Fake('query').expects_call().with_args( ... arg.any(), ... arg_not('foobar') ... ) >>> query([1, 2, 3], 'asdf') >>> query('asdf', 'foobar') Traceback (most recent call last): ... AssertionError: fake:query(arg.any(), arg_not(foobar)) was called unexpectedly with args ('asdf', 'foobar') .. doctest:: :hide: >>> fudge.clear_expectations() """ return NotValue(thing) arg = ValueInspector() arg_not = NotValueInspector() class ValueTest(object): arg_method = None __test__ = False # nose def __eq__(self, other): raise NotImplementedError() def _repr_argspec(self): raise NotImplementedError() def __str__(self): return self._repr_argspec() def __unicode__(self): return self._repr_argspec() def __repr__(self): return self._repr_argspec() def _make_argspec(self, arg): if self.arg_method is None: raise NotImplementedError( "%r must have set attribute arg_method" % self.__class__) return "arg." + self.arg_method + "(" + arg + ")" class Stringlike(ValueTest): def __init__(self, part): self.part = part def _repr_argspec(self): return self._make_argspec(fmt_val(self.part)) def stringlike(self, value): if isinstance(value, (str, unicode)): return value else: return str(value) def __eq__(self, other): check_stringlike = getattr(self.stringlike(other), self.arg_method) return check_stringlike(self.part) class Startswith(Stringlike): arg_method = "startswith" class Endswith(Stringlike): arg_method = "endswith" class HasAttr(ValueTest): arg_method = "has_attr" def __init__(self, **attributes): self.attributes = attributes def _repr_argspec(self): return self._make_argspec(", ".join(sorted(fmt_dict_vals(self.attributes)))) def __eq__(self, other): for name, value in self.attributes.items(): if not hasattr(other, name): return False if getattr(other, name) != value: return False return True class AnyValue(ValueTest): arg_method = "any" def __eq__(self, other): # will match anything: return True def _repr_argspec(self): return self._make_argspec("") class Contains(ValueTest): arg_method = "contains" def __init__(self, part): self.part = part def _repr_argspec(self): return self._make_argspec(fmt_val(self.part)) def __eq__(self, other): if self.part in other: return True else: return False class PassesTest(ValueTest): arg_method = "passes_test" def __init__(self, test): self.test = test def __eq__(self, other): return self.test(other) def _repr_argspec(self): return self._make_argspec(repr(self.test)) class IsInstance(ValueTest): arg_method = "isinstance" def __init__(self, cls): self.cls = cls def __eq__(self, other): return isinstance(other, self.cls) def _repr_argspec(self): if isinstance(self.cls, (tuple, list)): return self._make_argspec(repr(tuple((cls.__name__ for cls in self.cls)))) else: return self._make_argspec(repr(self.cls.__name__)) class NotValue(ValueTest): def __init__(self, item): self.item = item def __eq__(self, other): return not self.item == other def _repr_argspec(self): return "arg_not(%s)" % self.item fudge-1.1.1/javascript/0000775000175000017500000000000013213771547015262 5ustar jsattjsatt00000000000000fudge-1.1.1/javascript/README.txt0000664000175000017500000000072712535357753016773 0ustar jsattjsatt00000000000000 Copy ``javascript/fudge/`` to your webroot. To use it in your tests all you need is a script tag like this:: If you want to run Fudge's own tests, then cd into the ``javascript/`` directory, start a simple webserver:: $ python fudge/testserver.py and open http://localhost:8000/tests/test_fudge.html Take note that while Fudge's *tests* require jQuery, Fudge itself does not require jQuery.fudge-1.1.1/javascript/fudge/0000775000175000017500000000000013213771547016354 5ustar jsattjsatt00000000000000fudge-1.1.1/javascript/fudge/tests/0000775000175000017500000000000013213771547017516 5ustar jsattjsatt00000000000000fudge-1.1.1/javascript/fudge/tests/test_fudge.js0000664000175000017500000001756612535357753022231 0ustar jsattjsatt00000000000000 if (typeof nosejs !== "undefined") { nosejs.requireResource("jquery-1.3.1.js"); nosejs.requireResource("jquery/qunit-testrunner.js"); nosejs.requireFile("../fudge.js"); } function raises(exception_name, func) { var caught = false; try { func(); } catch (e) { caught = true; equals(e.name, exception_name, e); } if (!caught) { throw new fudge.AssertionError("expected to catch " + exception_name); } } function init_test() { fudge.registry.clear_all(); } module("Test Fake"); test("Fake().toString", function() { equals(new fudge.Fake("something").toString(), "fake:something"); }); test("can find objects", function() { var fake; init_test(); simple = {iam: function() { return "simple"; }}; dot = {}; dot.sep = {iam: function() { return "dot.sep"; }}; fake = new fudge.Fake("simple"); equals(fake._object.iam(), "simple"); fake = new fudge.Fake("dot.sep"); equals(fake._object.iam(), "dot.sep"); }); // this test made more sense when Fake used _object = eval(name) /* test("cannot send bad JavaScript as name", function() { init_test(); expect(1); raises("TypeError", function() { var fake = new fudge.Fake("length()"); // TypeError: length is not a function }); }); */ test("can create objects", function() { init_test(); var fake = new fudge.Fake("_non_existant_.someCall"); ok(_non_existant_, "_non_existant_"); ok(_non_existant_.someCall, "_non_existant_.someCall"); }); test("expected call not called", function() { /* @raises(AssertionError) def test_nocall(self): exp = self.fake.expects('something') fudge.verify() */ init_test(); expect(1); var fake = new fudge.Fake("some_obj"); fake.expects("someCall"); raises("AssertionError", function() { fudge.verify(); }); }); test("call intercepted", function() { init_test(); var fake = new fudge.Fake("bob_loblaw"); fake.expects("blog"); bob_loblaw.blog(); // verify should pass the test... fudge.verify(); }); test("returns value", function() { init_test(); var fake = new fudge.Fake("grocery_store"); fake.expects("check_eggs").returns("eggs are good!"); equals(grocery_store.check_eggs(), "eggs are good!"); fudge.verify(); }); test("returns fake", function() { init_test(); expect(1); var fake = new fudge.Fake("ice_skates"); fake.expects("go").returns_fake().expects("not_called"); ice_skates.go(); raises("AssertionError", function() { fudge.verify(); }); }); test("returns fake creates calls", function() { init_test(); var fake = new fudge.Fake("ice_skates").expects("foo").returns_fake().expects("bar"); ice_skates.foo().bar(); }); test("returns fake maintains expectations", function() { init_test(); var fake = new fudge.Fake("ice_skates"); fake.expects("go").returns_fake().expects("show_off"); ice_skates.go().show_off(); fudge.verify(); }); test("expected arguments are set", function() { init_test(); fudge.clear_expectations(); var fake = new fudge.Fake("Session").expects("add").with_args("one", {"debug":false}); var call = fudge.registry.expected_calls[0]; equals(call.expected_arguments[0], "one"); equals(call.expected_arguments[1].debug, false); }); test("expected arguments raises error", function() { init_test(); fudge.clear_expectations(); fudge.clear_calls(); var fake = new fudge.Fake("session").expects("add").with_args("one", {"debug":false}); raises("AssertionError", function() { session.add(); }); }); test("expected arguments pass", function() { init_test(); fudge.clear_expectations(); fudge.clear_calls(); console.log("arg test"); var fake = new fudge.Fake("session").expects("add").with_args("one", {"debug":false}); session.add("one", {"debug":false}); }); module("Test ExpectedCall"); test("ExpectedCall properties", function() { init_test(); var fake = new fudge.Fake("my_auth_mod"); var ec = new fudge.ExpectedCall(fake, "logout"); equals(ec.call_name, "logout"); }); test("call is logged", function() { init_test(); var fake = new fudge.Fake("aa_some_obj"); var ec = new fudge.ExpectedCall(fake, "watchMe"); aa_some_obj.watchMe(); equals(ec.was_called, true, "aa_some_obj.watchMe() was not logged"); }); module("Test fudge.registry"); /* def setUp(self): self.fake = Fake() self.reg = fudge.registry # in case of error, clear out everything: self.reg.clear_all() */ test("expected call not called", function() { /* @raises(AssertionError) def test_expected_call_not_called(self): self.reg.clear_calls() self.reg.expect_call(ExpectedCall(self.fake, 'nothing')) self.reg.verify() */ init_test(); expect(1); var fake = new fudge.Fake("some_obj"); fudge.registry.expect_call(new fudge.ExpectedCall(fake, 'nothing')); raises("AssertionError", function() { fudge.registry.verify(); }); }); test("start resets calls", function() { /* def test_start_resets_calls(self): exp = ExpectedCall(self.fake, 'callMe') self.reg.expect_call(exp) exp() eq_(exp.was_called, True) self.reg.clear_calls() eq_(exp.was_called, False, "call was not reset by start()") */ init_test(); var fake = new fudge.Fake("yeah"); var exp = new fudge.ExpectedCall(fake, "sayYeah"); fudge.registry.expect_call(exp); yeah.sayYeah(); equals(exp.was_called, true, "call was never logged"); fudge.registry.clear_calls(); equals(exp.was_called, false, "call was not reset by clear_calls()"); }); test("verify resets calls", function() { init_test(); var fake = new fudge.Fake("reset_yeah"); var exp = new fudge.ExpectedCall(fake, "sayYeah"); fudge.registry.expect_call(exp); equals(fudge.registry.expected_calls.length, 1, "registry has too many calls"); reset_yeah.sayYeah(); equals(exp.was_called, true, "call was never logged"); fudge.registry.verify(); equals(exp.was_called, false, "call was not reset by verify()"); equals(fudge.registry.expected_calls.length, 1, "verify() should not reset expectations"); }); test("global verify", function() { init_test(); var fake = new fudge.Fake("gverify_yeah"); var exp = new fudge.ExpectedCall(fake, "sayYeah"); gverify_yeah.sayYeah(); fudge.registry.expect_call(exp); equals(exp.was_called, true, "call was never logged"); equals(fudge.registry.expected_calls.length, 1, "registry has wrong number of calls"); fudge.verify(); equals(exp.was_called, false, "call was not reset by verify()"); equals(fudge.registry.expected_calls.length, 1, "verify() should not reset expectations"); }); test("global clear expectations", function() { /* def test_global_clear_expectations(self): exp = ExpectedCall(self.fake, 'callMe') exp() self.reg.expect_call(exp) eq_(len(self.reg.get_expected_calls()), 1) fudge.clear_expectations() eq_(len(self.reg.get_expected_calls()), 0, "clear_expectations() should reset expectations") */ init_test(); var fake = new fudge.Fake("gclear_yeah"); var exp = new fudge.ExpectedCall(fake, "sayYeah"); gclear_yeah.sayYeah(); fudge.registry.expect_call(exp); equals(fudge.registry.expected_calls.length, 1, "registry has wrong number of calls"); fudge.clear_expectations(); equals(fudge.registry.expected_calls.length, 0, "clear_expectations() should reset expectations"); }); module("utilities"); test("reproduce call args", function() { equals(fudge.repr_call_args(new Array("yeah's", {debug:true, when:'now'})), "('yeah\\'s', {'debug': true, 'when': 'now'})"); }); fudge-1.1.1/javascript/fudge/tests/jquery/0000775000175000017500000000000013213771547021035 5ustar jsattjsatt00000000000000fudge-1.1.1/javascript/fudge/tests/jquery/qunit/0000775000175000017500000000000013213771547022175 5ustar jsattjsatt00000000000000fudge-1.1.1/javascript/fudge/tests/jquery/qunit/testrunner.js0000664000175000017500000005240012535357753024752 0ustar jsattjsatt00000000000000/* * QUnit - jQuery unit testrunner * * http://docs.jquery.com/QUnit * * Copyright (c) 2008 John Resig, Jörn Zaefferer * Dual licensed under the MIT (MIT-LICENSE.txt) * and GPL (GPL-LICENSE.txt) licenses. * * $Id: testrunner.js 6071 2009-01-09 08:42:39Z joern.zaefferer $ */ (function($) { // Tests for equality any JavaScript type and structure without unexpected results. // Discussions and reference: http://philrathe.com/articles/equiv // Test suites: http://philrathe.com/tests/equiv // Author: Philippe Rathé var equiv = function () { var innerEquiv; // the real equiv function var callers = []; // stack to decide between skip/abort functions // Determine what is o. function hoozit(o) { if (typeof o === "string") { return "string"; } else if (typeof o === "boolean") { return "boolean"; } else if (typeof o === "number") { if (isNaN(o)) { return "nan"; } else { return "number"; } } else if (typeof o === "undefined") { return "undefined"; // consider: typeof null === object } else if (o === null) { return "null"; // consider: typeof [] === object } else if (o instanceof Array) { return "array"; // consider: typeof new Date() === object } else if (o instanceof Date) { return "date"; // consider: /./ instanceof Object; // /./ instanceof RegExp; // typeof /./ === "function"; // => false in IE and Opera, // true in FF and Safari } else if (o instanceof RegExp) { return "regexp"; } else if (typeof o === "object") { return "object"; } else if (o instanceof Function) { return "function"; } } // Call the o related callback with the given arguments. function bindCallbacks(o, callbacks, args) { var prop = hoozit(o); if (prop) { if (hoozit(callbacks[prop]) === "function") { return callbacks[prop].apply(callbacks, args); } else { return callbacks[prop]; // or undefined } } } var callbacks = function () { // for string, boolean, number and null function useStrictEquality(b, a) { return a === b; } return { "string": useStrictEquality, "boolean": useStrictEquality, "number": useStrictEquality, "null": useStrictEquality, "undefined": useStrictEquality, "nan": function (b) { return isNaN(b); }, "date": function (b, a) { return hoozit(b) === "date" && a.valueOf() === b.valueOf(); }, "regexp": function (b, a) { return hoozit(b) === "regexp" && a.source === b.source && // the regex itself a.global === b.global && // and its modifers (gmi) ... a.ignoreCase === b.ignoreCase && a.multiline === b.multiline; }, // - skip when the property is a method of an instance (OOP) // - abort otherwise, // initial === would have catch identical references anyway "function": function () { var caller = callers[callers.length - 1]; return caller !== Object && typeof caller !== "undefined"; }, "array": function (b, a) { var i; var len; // b could be an object literal here if ( ! (hoozit(b) === "array")) { return false; } len = a.length; if (len !== b.length) { // safe and faster return false; } for (i = 0; i < len; i++) { if( ! innerEquiv(a[i], b[i])) { return false; } } return true; }, "object": function (b, a) { var i; var eq = true; // unless we can proove it var aProperties = [], bProperties = []; // collection of strings // comparing constructors is more strict than using instanceof if ( a.constructor !== b.constructor) { return false; } // stack constructor before traversing properties callers.push(a.constructor); for (i in a) { // be strict: don't ensures hasOwnProperty and go deep aProperties.push(i); // collect a's properties if ( ! innerEquiv(a[i], b[i])) { eq = false; } } callers.pop(); // unstack, we are done for (i in b) { bProperties.push(i); // collect b's properties } // Ensures identical properties name return eq && innerEquiv(aProperties.sort(), bProperties.sort()); } }; }(); innerEquiv = function () { // can take multiple arguments var args = Array.prototype.slice.apply(arguments); if (args.length < 2) { return true; // end transition } return (function (a, b) { if (a === b) { return true; // catch the most you can } else if (typeof a !== typeof b || a === null || b === null || typeof a === "undefined" || typeof b === "undefined") { return false; // don't lose time with error prone cases } else { return bindCallbacks(a, callbacks, [b, a]); } // apply transition with (1..n) arguments })(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1)); }; return innerEquiv; }(); // equiv var GETParams = $.map( location.search.slice(1).split('&'), decodeURIComponent ), ngindex = $.inArray("noglobals", GETParams), noglobals = ngindex !== -1; if( noglobals ) GETParams.splice( ngindex, 1 ); var config = { stats: { all: 0, bad: 0 }, queue: [], // block until document ready blocking: true, //restrict modules/tests by get parameters filters: GETParams, isLocal: !!(window.location.protocol == 'file:') }; // public API as global methods $.extend(window, { test: test, module: module, expect: expect, ok: ok, equals: equals, start: start, stop: stop, reset: reset, isLocal: config.isLocal, same: function(a, b, message) { push(equiv(a, b), a, b, message); }, QUnit: { equiv: equiv }, // legacy methods below isSet: isSet, isObj: isObj, compare: function() { throw "compare is deprecated - use same() instead"; }, compare2: function() { throw "compare2 is deprecated - use same() instead"; }, serialArray: function() { throw "serialArray is deprecated - use jsDump.parse() instead"; }, q: q, t: t, url: url, triggerEvent: triggerEvent }); $(window).load(function() { $('#userAgent').html(navigator.userAgent); var head = $('
').insertAfter("#userAgent"); $('').attr("disabled", true).prependTo(head).click(function() { $('li.pass')[this.checked ? 'hide' : 'show'](); }); runTest(); }); function synchronize(callback) { config.queue.push(callback); if(!config.blocking) { process(); } } function process() { while(config.queue.length && !config.blocking) { config.queue.shift()(); } } function stop(timeout) { config.blocking = true; if (timeout) config.timeout = setTimeout(function() { ok( false, "Test timed out" ); start(); }, timeout); } function start() { // A slight delay, to avoid any current callbacks setTimeout(function() { if(config.timeout) clearTimeout(config.timeout); config.blocking = false; process(); }, 13); } function validTest( name ) { var i = config.filters.length, run = false; if( !i ) return true; while( i-- ){ var filter = config.filters[i], not = filter.charAt(0) == '!'; if( not ) filter = filter.slice(1); if( name.indexOf(filter) != -1 ) return !not; if( not ) run = true; } return run; } function runTest() { config.blocking = false; var started = +new Date; config.fixture = document.getElementById('main').innerHTML; config.ajaxSettings = $.ajaxSettings; synchronize(function() { $('

').html(['Tests completed in ', +new Date - started, ' milliseconds.
', '', config.stats.bad, ' tests of ', config.stats.all, ' failed.

'] .join('')) .appendTo("body"); $("#banner").addClass(config.stats.bad ? "fail" : "pass"); }); } var pollution; function saveGlobal(){ pollution = [ ]; if( noglobals ) for( var key in window ) pollution.push(key); } function checkPollution( name ){ var old = pollution; saveGlobal(); if( pollution.length > old.length ){ ok( false, "Introduced global variable(s): " + diff(old, pollution).join(", ") ); config.expected++; } } function diff( clean, dirty ){ return $.grep( dirty, function(name){ return $.inArray( name, clean ) == -1; }); } function test(name, callback) { if(config.currentModule) name = config.currentModule + " module: " + name; var lifecycle = $.extend({ setup: function() {}, teardown: function() {} }, config.moduleLifecycle); if ( !validTest(name) ) return; synchronize(function() { config.assertions = []; config.expected = null; try { if( !pollution ) saveGlobal(); lifecycle.setup(); } catch(e) { config.assertions.push( { result: false, message: "Setup failed on " + name + ": " + e.message }); } }) synchronize(function() { try { callback(); } catch(e) { if( typeof console != "undefined" && console.error && console.warn ) { console.error("Test " + name + " died, exception and test follows"); console.error(e); console.warn(callback.toString()); } config.assertions.push( { result: false, message: "Died on test #" + (config.assertions.length + 1) + ": " + e.message }); // else next test will carry the responsibility saveGlobal(); } }); synchronize(function() { try { checkPollution(); lifecycle.teardown(); } catch(e) { config.assertions.push( { result: false, message: "Teardown failed on " + name + ": " + e.message }); } }) synchronize(function() { try { reset(); } catch(e) { if( typeof console != "undefined" && console.error && console.warn ) { console.error("reset() failed, following Test " + name + ", exception and reset fn follows"); console.error(e); console.warn(reset.toString()); } } if(config.expected && config.expected != config.assertions.length) { config.assertions.push({ result: false, message: "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" }); } var good = 0, bad = 0; var ol = $("
    ").hide(); config.stats.all += config.assertions.length; for ( var i = 0; i < config.assertions.length; i++ ) { var assertion = config.assertions[i]; $("
  1. ").addClass(assertion.result ? "pass" : "fail").text(assertion.message || "(no message)").appendTo(ol); assertion.result ? good++ : bad++; } config.stats.bad += bad; var b = $("").html(name + " (" + bad + ", " + good + ", " + config.assertions.length + ")") .click(function(){ $(this).next().toggle(); }) .dblclick(function(event) { var target = $(event.target).filter("strong").clone(); if ( target.length ) { target.children().remove(); location.href = location.href.match(/^(.+?)(\?.*)?$/)[1] + "?" + encodeURIComponent($.trim(target.text())); } }); $("
  2. ").addClass(bad ? "fail" : "pass").append(b).append(ol).appendTo("#tests"); if(bad) { $("#filter").attr("disabled", null); } }); } // call on start of module test to prepend name to all tests function module(name, lifecycle) { config.currentModule = name; config.moduleLifecycle = lifecycle; } /** * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. */ function expect(asserts) { config.expected = asserts; } /** * Resets the test setup. Useful for tests that modify the DOM. */ function reset() { $("#main").html( config.fixture ); $.event.global = {}; $.ajaxSettings = $.extend({}, config.ajaxSettings); } /** * Asserts true. * @example ok( $("a").size() > 5, "There must be at least 5 anchors" ); */ function ok(a, msg) { config.assertions.push({ result: !!a, message: msg }); } /** * Asserts that two arrays are the same */ function isSet(a, b, msg) { function serialArray( a ) { var r = []; if ( a && a.length ) for ( var i = 0; i < a.length; i++ ) { var str = a[i].nodeName; if ( str ) { str = str.toLowerCase(); if ( a[i].id ) str += "#" + a[i].id; } else str = a[i]; r.push( str ); } return "[ " + r.join(", ") + " ]"; } var ret = true; if ( a && b && a.length != undefined && a.length == b.length ) { for ( var i = 0; i < a.length; i++ ) if ( a[i] != b[i] ) ret = false; } else ret = false; config.assertions.push({ result: ret, message: !ret ? (msg + " expected: " + serialArray(b) + " result: " + serialArray(a)) : msg }); } /** * Asserts that two objects are equivalent */ function isObj(a, b, msg) { var ret = true; if ( a && b ) { for ( var i in a ) if ( a[i] != b[i] ) ret = false; for ( i in b ) if ( a[i] != b[i] ) ret = false; } else ret = false; config.assertions.push({ result: ret, message: msg }); } /** * Returns an array of elements with the given IDs, eg. * @example q("main", "foo", "bar") * @result [
    , , ] */ function q() { var r = []; for ( var i = 0; i < arguments.length; i++ ) r.push( document.getElementById( arguments[i] ) ); return r; } /** * Asserts that a select matches the given IDs * @example t("Check for something", "//[a]", ["foo", "baar"]); * @result returns true if "//[a]" return two elements with the IDs 'foo' and 'baar' */ function t(a,b,c) { var f = $(b); var s = ""; for ( var i = 0; i < f.length; i++ ) s += (s && ",") + '"' + f[i].id + '"'; isSet(f, q.apply(q,c), a + " (" + b + ")"); } /** * Add random number to url to stop IE from caching * * @example url("data/test.html") * @result "data/test.html?10538358428943" * * @example url("data/test.php?foo=bar") * @result "data/test.php?foo=bar&10538358345554" */ function url(value) { return value + (/\?/.test(value) ? "&" : "?") + new Date().getTime() + "" + parseInt(Math.random()*100000); } /** * Checks that the first two arguments are equal, with an optional message. * Prints out both actual and expected values. * * Prefered to ok( actual == expected, message ) * * @example equals( $.format("Received {0} bytes.", 2), "Received 2 bytes." ); * * @param Object actual * @param Object expected * @param String message (optional) */ function equals(actual, expected, message) { push(expected == actual, actual, expected, message); } function push(result, actual, expected, message) { message = message || (result ? "okay" : "failed"); config.assertions.push({ result: result, message: result ? message + ": " + expected : message + ", expected: " + jsDump.parse(expected) + " result: " + jsDump.parse(actual) }); } /** * Trigger an event on an element. * * @example triggerEvent( document.body, "click" ); * * @param DOMElement elem * @param String type */ function triggerEvent( elem, type, event ) { if ( $.browser.mozilla || $.browser.opera ) { event = document.createEvent("MouseEvents"); event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, 0, 0, 0, 0, 0, false, false, false, false, 0, null); elem.dispatchEvent( event ); } else if ( $.browser.msie ) { elem.fireEvent("on"+type); } } })(jQuery); /** * jsDump * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php) * Date: 5/15/2008 * @projectDescription Advanced and extensible data dumping for Javascript. * @version 1.0.0 * @author Ariel Flesler * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} */ (function(){ function quote( str ){ return '"' + str.toString().replace(/"/g, '\\"') + '"'; }; function literal( o ){ return o + ''; }; function join( pre, arr, post ){ var s = jsDump.separator(), base = jsDump.indent(); inner = jsDump.indent(1); if( arr.join ) arr = arr.join( ',' + s + inner ); if( !arr ) return pre + post; return [ pre, inner + arr, base + post ].join(s); }; function array( arr ){ var i = arr.length, ret = Array(i); this.up(); while( i-- ) ret[i] = this.parse( arr[i] ); this.down(); return join( '[', ret, ']' ); }; var reName = /^function (\w+)/; var jsDump = window.jsDump = { parse:function( obj, type ){//type is used mostly internally, you can fix a (custom)type in advance var parser = this.parsers[ type || this.typeOf(obj) ]; type = typeof parser; return type == 'function' ? parser.call( this, obj ) : type == 'string' ? parser : this.parsers.error; }, typeOf:function( obj ){ var type = typeof obj, f = 'function';//we'll use it 3 times, save it return type != 'object' && type != f ? type : !obj ? 'null' : obj.exec ? 'regexp' :// some browsers (FF) consider regexps functions obj.getHours ? 'date' : obj.scrollBy ? 'window' : obj.nodeName == '#document' ? 'document' : obj.nodeName ? 'node' : obj.item ? 'nodelist' : // Safari reports nodelists as functions obj.callee ? 'arguments' : obj.call || obj.constructor != Array && //an array would also fall on this hack (obj+'').indexOf(f) != -1 ? f : //IE reports functions like alert, as objects 'length' in obj ? 'array' : type; }, separator:function(){ return this.multiline ? this.HTML ? '
    ' : '\n' : this.HTML ? ' ' : ' '; }, indent:function( extra ){// extra can be a number, shortcut for increasing-calling-decreasing if( !this.multiline ) return ''; var chr = this.indentChar; if( this.HTML ) chr = chr.replace(/\t/g,' ').replace(/ /g,' '); return Array( this._depth_ + (extra||0) ).join(chr); }, up:function( a ){ this._depth_ += a || 1; }, down:function( a ){ this._depth_ -= a || 1; }, setParser:function( name, parser ){ this.parsers[name] = parser; }, // The next 3 are exposed so you can use them quote:quote, literal:literal, join:join, // _depth_: 1, // This is the list of parsers, to modify them, use jsDump.setParser parsers:{ window: '[Window]', document: '[Document]', error:'[ERROR]', //when no parser is found, shouldn't happen unknown: '[Unknown]', 'null':'null', undefined:'undefined', 'function':function( fn ){ var ret = 'function', name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE if( name ) ret += ' ' + name; ret += '('; ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join(''); return join( ret, this.parse(fn,'functionCode'), '}' ); }, array: array, nodelist: array, arguments: array, object:function( map ){ var ret = [ ]; this.up(); for( var key in map ) ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) ); this.down(); return join( '{', ret, '}' ); }, node:function( node ){ var open = this.HTML ? '<' : '<', close = this.HTML ? '>' : '>'; var tag = node.nodeName.toLowerCase(), ret = open + tag; for( var a in this.DOMAttrs ){ var val = node[this.DOMAttrs[a]]; if( val ) ret += ' ' + a + '=' + this.parse( val, 'attribute' ); } return ret + close + open + '/' + tag + close; }, functionArgs:function( fn ){//function calls it internally, it's the arguments part of the function var l = fn.length; if( !l ) return ''; var args = Array(l); while( l-- ) args[l] = String.fromCharCode(97+l);//97 is 'a' return ' ' + args.join(', ') + ' '; }, key:quote, //object calls it internally, the key part of an item in a map functionCode:'[code]', //function calls it internally, it's the content of the function attribute:quote, //node calls it internally, it's an html attribute value string:quote, date:quote, regexp:literal, //regex number:literal, 'boolean':literal }, DOMAttrs:{//attributes to dump from nodes, name=>realName id:'id', name:'name', 'class':'className' }, HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) indentChar:' ',//indentation unit multiline:true //if true, items in a collection, are separated by a \n, else just a space. }; })(); fudge-1.1.1/javascript/fudge/tests/jquery/jquery-1.2.6.js0000664000175000017500000030354412535357753023372 0ustar jsattjsatt00000000000000(function(){ /* * jQuery 1.2.6 - New Wave Javascript * * Copyright (c) 2008 John Resig (jquery.com) * Dual licensed under the MIT (MIT-LICENSE.txt) * and GPL (GPL-LICENSE.txt) licenses. * * $Date: 2008-05-24 14:22:17 -0400 (Sat, 24 May 2008) $ * $Rev: 5685 $ */ // Map over jQuery in case of overwrite var _jQuery = window.jQuery, // Map over the $ in case of overwrite _$ = window.$; var jQuery = window.jQuery = window.$ = function( selector, context ) { // The jQuery object is actually just the init constructor 'enhanced' return new jQuery.fn.init( selector, context ); }; // A simple way to check for HTML strings or ID strings // (both of which we optimize for) var quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/, // Is it a simple selector isSimple = /^.[^:#\[\.]*$/, // Will speed up references to undefined, and allows munging its name. undefined; jQuery.fn = jQuery.prototype = { init: function( selector, context ) { // Make sure that a selection was provided selector = selector || document; // Handle $(DOMElement) if ( selector.nodeType ) { this[0] = selector; this.length = 1; return this; } // Handle HTML strings if ( typeof selector == "string" ) { // Are we dealing with HTML string or an ID? var match = quickExpr.exec( selector ); // Verify a match, and that no context was specified for #id if ( match && (match[1] || !context) ) { // HANDLE: $(html) -> $(array) if ( match[1] ) selector = jQuery.clean( [ match[1] ], context ); // HANDLE: $("#id") else { var elem = document.getElementById( match[3] ); // Make sure an element was located if ( elem ){ // Handle the case where IE and Opera return items // by name instead of ID if ( elem.id != match[3] ) return jQuery().find( selector ); // Otherwise, we inject the element directly into the jQuery object return jQuery( elem ); } selector = []; } // HANDLE: $(expr, [context]) // (which is just equivalent to: $(content).find(expr) } else return jQuery( context ).find( selector ); // HANDLE: $(function) // Shortcut for document ready } else if ( jQuery.isFunction( selector ) ) return jQuery( document )[ jQuery.fn.ready ? "ready" : "load" ]( selector ); return this.setArray(jQuery.makeArray(selector)); }, // The current version of jQuery being used jquery: "1.2.6", // The number of elements contained in the matched element set size: function() { return this.length; }, // The number of elements contained in the matched element set length: 0, // Get the Nth element in the matched element set OR // Get the whole matched element set as a clean array get: function( num ) { return num == undefined ? // Return a 'clean' array jQuery.makeArray( this ) : // Return just the object this[ num ]; }, // Take an array of elements and push it onto the stack // (returning the new matched element set) pushStack: function( elems ) { // Build a new jQuery matched element set var ret = jQuery( elems ); // Add the old object onto the stack (as a reference) ret.prevObject = this; // Return the newly-formed element set return ret; }, // Force the current matched set of elements to become // the specified array of elements (destroying the stack in the process) // You should use pushStack() in order to do this, but maintain the stack setArray: function( elems ) { // Resetting the length to 0, then using the native Array push // is a super-fast way to populate an object with array-like properties this.length = 0; Array.prototype.push.apply( this, elems ); return this; }, // Execute a callback for every element in the matched set. // (You can seed the arguments with an array of args, but this is // only used internally.) each: function( callback, args ) { return jQuery.each( this, callback, args ); }, // Determine the position of an element within // the matched set of elements index: function( elem ) { var ret = -1; // Locate the position of the desired element return jQuery.inArray( // If it receives a jQuery object, the first element is used elem && elem.jquery ? elem[0] : elem , this ); }, attr: function( name, value, type ) { var options = name; // Look for the case where we're accessing a style value if ( name.constructor == String ) if ( value === undefined ) return this[0] && jQuery[ type || "attr" ]( this[0], name ); else { options = {}; options[ name ] = value; } // Check to see if we're setting style values return this.each(function(i){ // Set all the styles for ( name in options ) jQuery.attr( type ? this.style : this, name, jQuery.prop( this, options[ name ], type, i, name ) ); }); }, css: function( key, value ) { // ignore negative width and height values if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 ) value = undefined; return this.attr( key, value, "curCSS" ); }, text: function( text ) { if ( typeof text != "object" && text != null ) return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); var ret = ""; jQuery.each( text || this, function(){ jQuery.each( this.childNodes, function(){ if ( this.nodeType != 8 ) ret += this.nodeType != 1 ? this.nodeValue : jQuery.fn.text( [ this ] ); }); }); return ret; }, wrapAll: function( html ) { if ( this[0] ) // The elements to wrap the target around jQuery( html, this[0].ownerDocument ) .clone() .insertBefore( this[0] ) .map(function(){ var elem = this; while ( elem.firstChild ) elem = elem.firstChild; return elem; }) .append(this); return this; }, wrapInner: function( html ) { return this.each(function(){ jQuery( this ).contents().wrapAll( html ); }); }, wrap: function( html ) { return this.each(function(){ jQuery( this ).wrapAll( html ); }); }, append: function() { return this.domManip(arguments, true, false, function(elem){ if (this.nodeType == 1) this.appendChild( elem ); }); }, prepend: function() { return this.domManip(arguments, true, true, function(elem){ if (this.nodeType == 1) this.insertBefore( elem, this.firstChild ); }); }, before: function() { return this.domManip(arguments, false, false, function(elem){ this.parentNode.insertBefore( elem, this ); }); }, after: function() { return this.domManip(arguments, false, true, function(elem){ this.parentNode.insertBefore( elem, this.nextSibling ); }); }, end: function() { return this.prevObject || jQuery( [] ); }, find: function( selector ) { var elems = jQuery.map(this, function(elem){ return jQuery.find( selector, elem ); }); return this.pushStack( /[^+>] [^+>]/.test( selector ) || selector.indexOf("..") > -1 ? jQuery.unique( elems ) : elems ); }, clone: function( events ) { // Do the clone var ret = this.map(function(){ if ( jQuery.browser.msie && !jQuery.isXMLDoc(this) ) { // IE copies events bound via attachEvent when // using cloneNode. Calling detachEvent on the // clone will also remove the events from the orignal // In order to get around this, we use innerHTML. // Unfortunately, this means some modifications to // attributes in IE that are actually only stored // as properties will not be copied (such as the // the name attribute on an input). var clone = this.cloneNode(true), container = document.createElement("div"); container.appendChild(clone); return jQuery.clean([container.innerHTML])[0]; } else return this.cloneNode(true); }); // Need to set the expando to null on the cloned set if it exists // removeData doesn't work here, IE removes it from the original as well // this is primarily for IE but the data expando shouldn't be copied over in any browser var clone = ret.find("*").andSelf().each(function(){ if ( this[ expando ] != undefined ) this[ expando ] = null; }); // Copy the events from the original to the clone if ( events === true ) this.find("*").andSelf().each(function(i){ if (this.nodeType == 3) return; var events = jQuery.data( this, "events" ); for ( var type in events ) for ( var handler in events[ type ] ) jQuery.event.add( clone[ i ], type, events[ type ][ handler ], events[ type ][ handler ].data ); }); // Return the cloned set return ret; }, filter: function( selector ) { return this.pushStack( jQuery.isFunction( selector ) && jQuery.grep(this, function(elem, i){ return selector.call( elem, i ); }) || jQuery.multiFilter( selector, this ) ); }, not: function( selector ) { if ( selector.constructor == String ) // test special case where just one selector is passed in if ( isSimple.test( selector ) ) return this.pushStack( jQuery.multiFilter( selector, this, true ) ); else selector = jQuery.multiFilter( selector, this ); var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType; return this.filter(function() { return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector; }); }, add: function( selector ) { return this.pushStack( jQuery.unique( jQuery.merge( this.get(), typeof selector == 'string' ? jQuery( selector ) : jQuery.makeArray( selector ) ))); }, is: function( selector ) { return !!selector && jQuery.multiFilter( selector, this ).length > 0; }, hasClass: function( selector ) { return this.is( "." + selector ); }, val: function( value ) { if ( value == undefined ) { if ( this.length ) { var elem = this[0]; // We need to handle select boxes special if ( jQuery.nodeName( elem, "select" ) ) { var index = elem.selectedIndex, values = [], options = elem.options, one = elem.type == "select-one"; // Nothing was selected if ( index < 0 ) return null; // Loop through all the selected options for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { var option = options[ i ]; if ( option.selected ) { // Get the specifc value for the option value = jQuery.browser.msie && !option.attributes.value.specified ? option.text : option.value; // We don't need an array for one selects if ( one ) return value; // Multi-Selects return an array values.push( value ); } } return values; // Everything else, we just grab the value } else return (this[0].value || "").replace(/\r/g, ""); } return undefined; } if( value.constructor == Number ) value += ''; return this.each(function(){ if ( this.nodeType != 1 ) return; if ( value.constructor == Array && /radio|checkbox/.test( this.type ) ) this.checked = (jQuery.inArray(this.value, value) >= 0 || jQuery.inArray(this.name, value) >= 0); else if ( jQuery.nodeName( this, "select" ) ) { var values = jQuery.makeArray(value); jQuery( "option", this ).each(function(){ this.selected = (jQuery.inArray( this.value, values ) >= 0 || jQuery.inArray( this.text, values ) >= 0); }); if ( !values.length ) this.selectedIndex = -1; } else this.value = value; }); }, html: function( value ) { return value == undefined ? (this[0] ? this[0].innerHTML : null) : this.empty().append( value ); }, replaceWith: function( value ) { return this.after( value ).remove(); }, eq: function( i ) { return this.slice( i, i + 1 ); }, slice: function() { return this.pushStack( Array.prototype.slice.apply( this, arguments ) ); }, map: function( callback ) { return this.pushStack( jQuery.map(this, function(elem, i){ return callback.call( elem, i, elem ); })); }, andSelf: function() { return this.add( this.prevObject ); }, data: function( key, value ){ var parts = key.split("."); parts[1] = parts[1] ? "." + parts[1] : ""; if ( value === undefined ) { var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); if ( data === undefined && this.length ) data = jQuery.data( this[0], key ); return data === undefined && parts[1] ? this.data( parts[0] ) : data; } else return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function(){ jQuery.data( this, key, value ); }); }, removeData: function( key ){ return this.each(function(){ jQuery.removeData( this, key ); }); }, domManip: function( args, table, reverse, callback ) { var clone = this.length > 1, elems; return this.each(function(){ if ( !elems ) { elems = jQuery.clean( args, this.ownerDocument ); if ( reverse ) elems.reverse(); } var obj = this; if ( table && jQuery.nodeName( this, "table" ) && jQuery.nodeName( elems[0], "tr" ) ) obj = this.getElementsByTagName("tbody")[0] || this.appendChild( this.ownerDocument.createElement("tbody") ); var scripts = jQuery( [] ); jQuery.each(elems, function(){ var elem = clone ? jQuery( this ).clone( true )[0] : this; // execute all scripts after the elements have been injected if ( jQuery.nodeName( elem, "script" ) ) scripts = scripts.add( elem ); else { // Remove any inner scripts for later evaluation if ( elem.nodeType == 1 ) scripts = scripts.add( jQuery( "script", elem ).remove() ); // Inject the elements into the document callback.call( obj, elem ); } }); scripts.each( evalScript ); }); } }; // Give the init function the jQuery prototype for later instantiation jQuery.fn.init.prototype = jQuery.fn; function evalScript( i, elem ) { if ( elem.src ) jQuery.ajax({ url: elem.src, async: false, dataType: "script" }); else jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); if ( elem.parentNode ) elem.parentNode.removeChild( elem ); } function now(){ return +new Date; } jQuery.extend = jQuery.fn.extend = function() { // copy reference to target object var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options; // Handle a deep copy situation if ( target.constructor == Boolean ) { deep = target; target = arguments[1] || {}; // skip the boolean and the target i = 2; } // Handle case when target is a string or something (possible in deep copy) if ( typeof target != "object" && typeof target != "function" ) target = {}; // extend jQuery itself if only one argument is passed if ( length == i ) { target = this; --i; } for ( ; i < length; i++ ) // Only deal with non-null/undefined values if ( (options = arguments[ i ]) != null ) // Extend the base object for ( var name in options ) { var src = target[ name ], copy = options[ name ]; // Prevent never-ending loop if ( target === copy ) continue; // Recurse if we're merging object values if ( deep && copy && typeof copy == "object" && !copy.nodeType ) target[ name ] = jQuery.extend( deep, // Never move original objects, clone them src || ( copy.length != null ? [ ] : { } ) , copy ); // Don't bring in undefined values else if ( copy !== undefined ) target[ name ] = copy; } // Return the modified object return target; }; var expando = "jQuery" + now(), uuid = 0, windowData = {}, // exclude the following css properties to add px exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i, // cache defaultView defaultView = document.defaultView || {}; jQuery.extend({ noConflict: function( deep ) { window.$ = _$; if ( deep ) window.jQuery = _jQuery; return jQuery; }, // See test/unit/core.js for details concerning this function. isFunction: function( fn ) { return !!fn && typeof fn != "string" && !fn.nodeName && fn.constructor != Array && /^[\s[]?function/.test( fn + "" ); }, // check if an element is in a (or is an) XML document isXMLDoc: function( elem ) { return elem.documentElement && !elem.body || elem.tagName && elem.ownerDocument && !elem.ownerDocument.body; }, // Evalulates a script in a global context globalEval: function( data ) { data = jQuery.trim( data ); if ( data ) { // Inspired by code by Andrea Giammarchi // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html var head = document.getElementsByTagName("head")[0] || document.documentElement, script = document.createElement("script"); script.type = "text/javascript"; if ( jQuery.browser.msie ) script.text = data; else script.appendChild( document.createTextNode( data ) ); // Use insertBefore instead of appendChild to circumvent an IE6 bug. // This arises when a base node is used (#2709). head.insertBefore( script, head.firstChild ); head.removeChild( script ); } }, nodeName: function( elem, name ) { return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase(); }, cache: {}, data: function( elem, name, data ) { elem = elem == window ? windowData : elem; var id = elem[ expando ]; // Compute a unique ID for the element if ( !id ) id = elem[ expando ] = ++uuid; // Only generate the data cache if we're // trying to access or manipulate it if ( name && !jQuery.cache[ id ] ) jQuery.cache[ id ] = {}; // Prevent overriding the named cache with undefined values if ( data !== undefined ) jQuery.cache[ id ][ name ] = data; // Return the named cache data, or the ID for the element return name ? jQuery.cache[ id ][ name ] : id; }, removeData: function( elem, name ) { elem = elem == window ? windowData : elem; var id = elem[ expando ]; // If we want to remove a specific section of the element's data if ( name ) { if ( jQuery.cache[ id ] ) { // Remove the section of cache data delete jQuery.cache[ id ][ name ]; // If we've removed all the data, remove the element's cache name = ""; for ( name in jQuery.cache[ id ] ) break; if ( !name ) jQuery.removeData( elem ); } // Otherwise, we want to remove all of the element's data } else { // Clean up the element expando try { delete elem[ expando ]; } catch(e){ // IE has trouble directly removing the expando // but it's ok with using removeAttribute if ( elem.removeAttribute ) elem.removeAttribute( expando ); } // Completely remove the data cache delete jQuery.cache[ id ]; } }, // args is for internal usage only each: function( object, callback, args ) { var name, i = 0, length = object.length; if ( args ) { if ( length == undefined ) { for ( name in object ) if ( callback.apply( object[ name ], args ) === false ) break; } else for ( ; i < length; ) if ( callback.apply( object[ i++ ], args ) === false ) break; // A special, fast, case for the most common use of each } else { if ( length == undefined ) { for ( name in object ) if ( callback.call( object[ name ], name, object[ name ] ) === false ) break; } else for ( var value = object[0]; i < length && callback.call( value, i, value ) !== false; value = object[++i] ){} } return object; }, prop: function( elem, value, type, i, name ) { // Handle executable functions if ( jQuery.isFunction( value ) ) value = value.call( elem, i ); // Handle passing in a number to a CSS property return value && value.constructor == Number && type == "curCSS" && !exclude.test( name ) ? value + "px" : value; }, className: { // internal only, use addClass("class") add: function( elem, classNames ) { jQuery.each((classNames || "").split(/\s+/), function(i, className){ if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) ) elem.className += (elem.className ? " " : "") + className; }); }, // internal only, use removeClass("class") remove: function( elem, classNames ) { if (elem.nodeType == 1) elem.className = classNames != undefined ? jQuery.grep(elem.className.split(/\s+/), function(className){ return !jQuery.className.has( classNames, className ); }).join(" ") : ""; }, // internal only, use hasClass("class") has: function( elem, className ) { return jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1; } }, // A method for quickly swapping in/out CSS properties to get correct calculations swap: function( elem, options, callback ) { var old = {}; // Remember the old values, and insert the new ones for ( var name in options ) { old[ name ] = elem.style[ name ]; elem.style[ name ] = options[ name ]; } callback.call( elem ); // Revert the old values for ( var name in options ) elem.style[ name ] = old[ name ]; }, css: function( elem, name, force ) { if ( name == "width" || name == "height" ) { var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ]; function getWH() { val = name == "width" ? elem.offsetWidth : elem.offsetHeight; var padding = 0, border = 0; jQuery.each( which, function() { padding += parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0; border += parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0; }); val -= Math.round(padding + border); } if ( jQuery(elem).is(":visible") ) getWH(); else jQuery.swap( elem, props, getWH ); return Math.max(0, val); } return jQuery.curCSS( elem, name, force ); }, curCSS: function( elem, name, force ) { var ret, style = elem.style; // A helper method for determining if an element's values are broken function color( elem ) { if ( !jQuery.browser.safari ) return false; // defaultView is cached var ret = defaultView.getComputedStyle( elem, null ); return !ret || ret.getPropertyValue("color") == ""; } // We need to handle opacity special in IE if ( name == "opacity" && jQuery.browser.msie ) { ret = jQuery.attr( style, "opacity" ); return ret == "" ? "1" : ret; } // Opera sometimes will give the wrong display answer, this fixes it, see #2037 if ( jQuery.browser.opera && name == "display" ) { var save = style.outline; style.outline = "0 solid black"; style.outline = save; } // Make sure we're using the right name for getting the float value if ( name.match( /float/i ) ) name = styleFloat; if ( !force && style && style[ name ] ) ret = style[ name ]; else if ( defaultView.getComputedStyle ) { // Only "float" is needed here if ( name.match( /float/i ) ) name = "float"; name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase(); var computedStyle = defaultView.getComputedStyle( elem, null ); if ( computedStyle && !color( elem ) ) ret = computedStyle.getPropertyValue( name ); // If the element isn't reporting its values properly in Safari // then some display: none elements are involved else { var swap = [], stack = [], a = elem, i = 0; // Locate all of the parent display: none elements for ( ; a && color(a); a = a.parentNode ) stack.unshift(a); // Go through and make them visible, but in reverse // (It would be better if we knew the exact display type that they had) for ( ; i < stack.length; i++ ) if ( color( stack[ i ] ) ) { swap[ i ] = stack[ i ].style.display; stack[ i ].style.display = "block"; } // Since we flip the display style, we have to handle that // one special, otherwise get the value ret = name == "display" && swap[ stack.length - 1 ] != null ? "none" : ( computedStyle && computedStyle.getPropertyValue( name ) ) || ""; // Finally, revert the display styles back for ( i = 0; i < swap.length; i++ ) if ( swap[ i ] != null ) stack[ i ].style.display = swap[ i ]; } // We should always get a number back from opacity if ( name == "opacity" && ret == "" ) ret = "1"; } else if ( elem.currentStyle ) { var camelCase = name.replace(/\-(\w)/g, function(all, letter){ return letter.toUpperCase(); }); ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ]; // From the awesome hack by Dean Edwards // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 // If we're not dealing with a regular pixel number // but a number that has a weird ending, we need to convert it to pixels if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) { // Remember the original values var left = style.left, rsLeft = elem.runtimeStyle.left; // Put in the new values to get a computed value out elem.runtimeStyle.left = elem.currentStyle.left; style.left = ret || 0; ret = style.pixelLeft + "px"; // Revert the changed values style.left = left; elem.runtimeStyle.left = rsLeft; } } return ret; }, clean: function( elems, context ) { var ret = []; context = context || document; // !context.createElement fails in IE with an error but returns typeof 'object' if (typeof context.createElement == 'undefined') context = context.ownerDocument || context[0] && context[0].ownerDocument || document; jQuery.each(elems, function(i, elem){ if ( !elem ) return; if ( elem.constructor == Number ) elem += ''; // Convert html string into DOM nodes if ( typeof elem == "string" ) { // Fix "XHTML"-style tags in all browsers elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){ return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ? all : front + ">"; }); // Trim whitespace, otherwise indexOf won't work as expected var tags = jQuery.trim( elem ).toLowerCase(), div = context.createElement("div"); var wrap = // option or optgroup !tags.indexOf("", "" ] || !tags.indexOf("", "" ] || tags.match(/^<(thead|tbody|tfoot|colg|cap)/) && [ 1, "", "
    " ] || !tags.indexOf("", "" ] || // matched above (!tags.indexOf("", "" ] || !tags.indexOf("", "" ] || // IE can't serialize and

    Test Fudge

      fudge-1.1.1/javascript/fudge/testserver.py0000664000175000017500000000333212535357753021142 0ustar jsattjsatt00000000000000import optparse import logging, time, os from wsgiref import simple_server log = logging.getLogger(__name__) document_root = os.path.dirname(__file__) def fileapp(environ, start_response): path_info = environ['PATH_INFO'] if path_info.startswith('/'): path_info = path_info[1:] # make relative full_path = os.path.join(document_root, path_info) if full_path == '': full_path = '.' # must be working dir if path_info=="" or path_info.endswith('/') or os.path.isdir(full_path): # directory listing: out = ['
        '] for filename in os.listdir(full_path): if filename.startswith('.'): continue if os.path.isdir(os.path.join(full_path, filename)): filename = filename + '/' out.append('
      • %s
      • ' % (filename, filename)) out.append("
      ") body = "".join( out ) else: f = open(full_path, 'r') body = f.read() # optimized for small files :) start_response('200 OK', [ ('Content-Type', 'text/html'), ('Content-Length', str(len(body)))]) return [body] def main(): p = optparse.OptionParser(usage="%prog") p.add_option("--port", help="Port to run server on. Default: %default", default=8090, type=int) (options, args) = p.parse_args() logging.basicConfig(level=logging.DEBUG, format='[%(asctime)s] %(message)s') log.info("starting test server on port %s", options.port) httpd = simple_server.WSGIServer(('', options.port), simple_server.WSGIRequestHandler) httpd.set_app(fileapp) httpd.serve_forever() if __name__ == '__main__': main()fudge-1.1.1/javascript/fudge/fudge.js0000664000175000017500000004165312535357753020022 0ustar jsattjsatt00000000000000 /** * Fudge is a JavaScript module for using fake objects (mocks, stubs, etc) to test real ones. *

      * The module is designed for two specific situations: *

      *
        *
      • Replace an object *
          *
        • * Temporarily return a canned value for a * method or allow a method to be called without affect. *
        • *
        *
      • *
      • Ensure an object is used correctly *
          *
        • * Declare expectations about what methods should be * called and what arguments should be sent. *
        • *
        *
      • *
      * @module fudge */ fudge = function() { var AssertionError = function(msg) { Error.call(this, msg); this.name = "AssertionError"; this.message = msg; }; AssertionError.prototype.toString = function() { return this.message; }; var registry = new function() { this.expected_calls = []; this.clear_actual_calls = function() { /* def clear_actual_calls(self): for exp in self.get_this.expected_calls(): exp.was_called = False */ for (var i=0; i A fake object to replace a real one while testing.

      All calls return ``this`` so that you can chain them together to create readable code.

      Arguments:

      • name
        • Name of the JavaScript global to replace.
      • config.allows_any_call = false
        • When True, any method is allowed to be called on the Fake() instance. Each method will be a stub that does nothing if it has not been defined. Implies callable=True.
      • config.callable = false
        • When True, the Fake() acts like a callable. Use this if you are replacing a single method.

      Short example:

      
             var auth = new fudge.Fake('auth')
                                      .expects('login')
                                      .with_args('joe_username', 'joes_password');
             fudge.clear_calls();
             auth.login();
             fudge.verify();
             
      * * @class Fake * @namespace fudge */ var Fake = function(name, config) { if (!config) { config = {}; } this._name = name; if (config.object) { this._object = config.object; } else { // object is a global by name if (name) { var parts = name.split("."); if (parts.length === 0) { // empty string? throw new Error("Fake('" + name + "'): invalid name"); } // descend into dot-separated object. // i.e. // foo.bar.baz // window[foo] // foo[bar] // baz var last_parent = window; for (var i=0; i
      If the method *call_name* is never called, then raise an error. * @method expects * @return Object */ Fake.prototype.expects = function(call_name) { this._last_declared_call_name = call_name; var c = new ExpectedCall(this, call_name); this._declared_calls[call_name] = c; registry.expect_call(c); return this; }; /** * Provide a call.

      The call acts as a stub -- no error is raised if it is not called. * * @method provides * @return Object */ Fake.prototype.provides = function(call_name) { /* def provides(self, call_name): """Provide a call.""" self._last_declared_call_name = call_name c = Call(self, call_name) self._declared_calls[call_name] = c return self */ }; /** * Set the last call to return a value.

      Set a static value to return when a method is called. * * @method returns * @return Object */ Fake.prototype.returns = function(val) { var exp = this._get_current_call(); exp.return_val = val; return this; }; /** *

      Set the last call to return a new :class:`fudge.Fake`.

      Any given arguments are passed to the :class:`fudge.Fake` constructor

      Take note that this is different from the cascading nature of other methods. This will return an instance of the *new* Fake, not self, so you should be careful to store its return value in a new variable.

      * * @method returns_fake * @return Object */ Fake.prototype.returns_fake = function() { // make an anonymous object ... var return_val = {}; var fake = new Fake(this._name, { "object": return_val }); // ... then attach it to the return value of the last declared method this.returns(return_val); return fake; }; /** * Set the last call to expect specific arguments. * * @method with_args * @return Object */ Fake.prototype.with_args = function() { var exp = this._get_current_call(); exp.expected_arguments = arguments; return this; }; /** * Set the last call to expect an exact argument count. * * @method with_arg_count * @return Object */ Fake.prototype.with_arg_count = function(count) { /* def with_arg_count(self, count): """Expect an exact argument count.""" exp = self._get_current_call() exp.expected_arg_count = count return self */ }; /** * Set the last call to expect an exact count of keyword arguments. * * @method with_kwarg_count * @return Object */ Fake.prototype.with_kwarg_count = function(count) { /* def with_kwarg_count(self, count): """Expect an exact count of keyword arguments.""" exp = self._get_current_call() exp.expected_kwarg_count = count return self */ }; // fill fudge.* namespace : return { '__version__': '0.9.3', AssertionError: AssertionError, clear_expectations: function() { return registry.clear_expectations(); }, ExpectedCall: ExpectedCall, Fake: Fake, registry: registry, clear_calls: function() { return registry.clear_calls(); }, verify: function() { return registry.verify(); }, repr_call_args: repr_call_args }; }(); // end fudge namespace