pax_global_header00006660000000000000000000000064131357511410014513gustar00rootroot0000000000000052 comment=d92262b9a50aaa0cd44211420651b8d15ed7d5b9 hiro-0.5/000077500000000000000000000000001313575114100123205ustar00rootroot00000000000000hiro-0.5/.codeclimate.yml000066400000000000000000000000331313575114100153660ustar00rootroot00000000000000exclude_paths: - tests/* hiro-0.5/.coveragerc000066400000000000000000000002311313575114100144350ustar00rootroot00000000000000[run] include = **/hiro/** omit = **/hiro/tests/** **/setup.py [report] exclude_lines = pragma: no cover raise NotImplementedError hiro-0.5/.gitignore000066400000000000000000000002411313575114100143050ustar00rootroot00000000000000.installed.cfg bin develop-eggs dist eggs parts src/*.egg-info lib lib64 *.pyc *.log cover/* build .coverage* .test_env .python-version .idea htmlcov *egg-info* hiro-0.5/.travis.yml000066400000000000000000000003361313575114100144330ustar00rootroot00000000000000language: python python: - "2.6" - "2.7" - "3.3" - "3.4" - "3.5" - "pypy" install: - pip install -r requirements/ci.txt script: nosetests --with-cov -v -w tests after_success: - coveralls hiro-0.5/CLASSIFIERS000066400000000000000000000007541313575114100140200ustar00rootroot00000000000000Development Status :: 4 - Beta Environment :: Console Intended Audience :: Developers Intended Audience :: End Users/Desktop License :: OSI Approved :: MIT License Operating System :: OS Independent Topic :: Software Development :: Testing Topic :: Software Development :: Libraries Programming Language :: Python :: 2.6 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3.2 Programming Language :: Python :: 3.3 Programming Language :: Python :: Implementation :: PyPy hiro-0.5/LICENSE000066400000000000000000000020731313575114100133270ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2013 Ali-Akber Saifee 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. hiro-0.5/MANIFEST.in000066400000000000000000000001341313575114100140540ustar00rootroot00000000000000include README.rst include LICENSE include CLASSIFIERS recursive-include requirements *.txt hiro-0.5/README.rst000066400000000000000000000133221313575114100140100ustar00rootroot00000000000000.. |travis| image:: https://img.shields.io/travis/alisaifee/hiro/master.svg?style=flat-square :target: https://travis-ci.org/#!/alisaifee/hiro?branch=master .. |coveralls| image:: https://img.shields.io/coveralls/alisaifee/hiro/master.svg?style=flat-square :target: https://coveralls.io/r/alisaifee/hiro?branch=master .. |license| image:: https://img.shields.io/pypi/l/hiro.svg?style=flat-square :target: https://pypi.python.org/pypi/hiro .. |pypi| image:: https://img.shields.io/pypi/v/hiro.svg?style=flat-square :target: https://pypi.python.org/pypi/hiro ********************************************* Hiro - time manipulation utilities for python ********************************************* |travis| |coveralls| |pypi| |license| Yatta! -- Hiro Nakamura ==================== Hiro context manager ==================== Timeline context ================ The ``hiro.Timeline`` context manager hijacks a few commonly used time functions to allow time manipulation within its context. Specifically ``time.sleep``, ``time.time``, ``time.gmtime``, ``datetime.now``, ``datetime.utcnow`` and ``datetime.today`` behave according the configuration of the context. The context provides the following manipulation options: * ``rewind``: accepts seconds as an integer or a ``timedelta`` object. * ``forward``: accepts seconds as an integer or a ``timedelta`` object. * ``freeze``: accepts a floating point time since epoch or ``datetime`` or ``date`` object to freeze the time at. * ``unfreeze``: resumes time from the point it was frozen at. * ``scale``: accepts a floating point to accelerate/decelerate time by. ``> 1 = acceleration, < 1 = deceleration`` * ``reset``: resets all time alterations. .. code-block:: python import hiro from datetime import timedelta, datetime import time datetime.now().isoformat() # OUT: '2013-12-01T06:55:41.706060' with hiro.Timeline() as timeline: # forward by an hour timeline.forward(60*60) datetime.now().isoformat() # OUT: '2013-12-01T07:55:41.707383' # jump forward by 10 minutes timeline.forward(timedelta(minutes=10)) datetime.now().isoformat() # OUT: '2013-12-01T08:05:41.707425' # jump to yesterday and freeze timeline.freeze(datetime.now() - timedelta(hours=24)) datetime.now().isoformat() # OUT: '2013-11-30T09:15:41' timeline.scale(5) # scale time by 5x time.sleep(5) # this will effectively only sleep for 1 second # since time is frozen the sleep has no effect datetime.now().isoformat() # OUT: '2013-11-30T09:15:41' timeline.rewind(timedelta(days=365)) datetime.now().isoformat() # OUT: '2012-11-30T09:15:41' To reduce the amount of statements inside the context, certain timeline setup tasks can be done via the constructor and/or by using the fluent interface. .. code-block:: python import hiro import time from datetime import timedelta, datetime start_point = datetime(2012,12,12,0,0,0) my_timeline = hiro.Timeline(scale=5).forward(60*60).freeze() with my_timeline as timeline: print datetime.now() # OUT: '2012-12-12 01:00:00.000315' time.sleep(5) # effectively 1 second # no effect as time is frozen datetime.now() # OUT: '2012-12-12 01:00:00.000315' timeline.unfreeze() # back to starting point datetime.now() # OUT: '2012-12-12 01:00:00.000317' time.sleep(5) # effectively 1 second # takes effect (+5 seconds) datetime.now() # OUT: '2012-12-12 01:00:05.003100' ``Timeline`` can additionally be used as a decorator .. code-block:: python import hiro import time, datetime @hiro.Timeline(scale=50000) def sleeper(): datetime.datetime.now() # OUT: '2013-11-30 14:27:43.409291' time.sleep(60*60) # effectively 72 ms datetime.datetime.now() # OUT: '2013-11-30 15:28:36.240675' @hiro.Timeline() def sleeper_aware(timeline): datetime.datetime.now() # OUT: '2013-11-30 14:27:43.409291' timeline.forward(60*60) datetime.datetime.now() # OUT: '2013-11-30 15:28:36.240675' ============== Hiro executors ============== In order to execute certain callables within a ``Timeline`` context, two shortcut functions are provided. * ``run_sync(factor=1, callable, *args, **kwargs)`` * ``run_async(factor=1, callable, *args, **kwargs)`` Both functions return a ``ScaledRunner`` object which provides the following methods * ``get_execution_time``: The actual execution time of the ``callable`` * ``get_response`` (will either return the actual return value of ``callable`` or raise the exception that was thrown) ``run_async`` returns a derived class of ``ScaledRunner`` that additionally provides the following methods * ``is_running``: ``True/False`` depending on whether the callable has completed execution * ``join``: blocks until the ``callable`` completes execution Example ======= .. code-block:: python import hiro import time def _slow_function(n): time.sleep(n) if n > 10: raise RuntimeError() return n runner = hiro.run_sync(10, _slow_function, 10) runner.get_response() # OUT: 10 # due to the scale factor 10 it only took 1s to execute runner.get_execution_time() # OUT: 1.1052658557891846 runner = hiro.run_async(10, _slow_function, 11) runner.is_running() # OUT: True runner.join() runner.get_execution_time() # OUT: 1.1052658557891846 runner.get_response() # OUT: Traceback (most recent call last): # .... # OUT: File "", line 4, in _slow_function # OUT: RuntimeError hiro-0.5/doc/000077500000000000000000000000001313575114100130655ustar00rootroot00000000000000hiro-0.5/doc/Makefile000066400000000000000000000126751313575114100145400ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/hiro.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/hiro.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/hiro" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/hiro" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." hiro-0.5/doc/source/000077500000000000000000000000001313575114100143655ustar00rootroot00000000000000hiro-0.5/doc/source/api.rst000066400000000000000000000004761313575114100156770ustar00rootroot00000000000000***************** API Documentation ***************** .. currentmodule:: hiro .. autoclass:: Timeline :members: .. autofunction:: run_async .. autofunction:: run_sync .. currentmodule:: hiro.core .. autoclass:: ScaledRunner :members: .. autoclass:: ScaledAsyncRunner :members: :inherited-members: hiro-0.5/doc/source/conf.py000066400000000000000000000176661313575114100157040ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # hiro documentation build configuration file, created by # sphinx-quickstart on Sun Dec 1 07:29:51 2013. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os sys.path += ["../../"] import hiro.version # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # 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', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.pngmath', 'sphinx.ext.mathjax', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'hiro' copyright = u'2013, Ali-Akber Saifee' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = hiro.version.__version__ # The full version, including alpha/beta/rc tags. release = hiro.version.__version__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # 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 patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # 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' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' # 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 = [] # 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 (relative to this directory) 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_domain_indices = 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, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = 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 = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'hirodoc' # -- Options for LaTeX output -------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'hiro.tex', u'hiro Documentation', u'Ali-Akber Saifee', '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 # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'hiro', u'hiro Documentation', [u'Ali-Akber Saifee'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'hiro', u'hiro Documentation', u'Ali-Akber Saifee', 'hiro', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'http://docs.python.org/': None} hiro-0.5/doc/source/index.rst000066400000000000000000000010431313575114100162240ustar00rootroot00000000000000 ############################################# Hiro - time manipulation utilities for python ############################################# Often testing code that can be time dependent can become either fragile or slow. Hiro provides context managers and utilities to either freeze, accelerate or decelerate and jump between different points in time. Functions exposed by the standard library's ``time``, ``datetime`` and ``date`` modules are patched within the the contexts exposed. .. toctree:: :maxdepth: 4 intro api project hiro-0.5/doc/source/intro.rst000066400000000000000000000122711313575114100162550ustar00rootroot00000000000000********************************** Hiro context manager and utilities ********************************** Timeline context ================ The :class:`hiro.Timeline` context manager hijacks a few commonly used time functions to allow time manipulation within its context. Specifically :func:`time.sleep`, :func:`time.time`, :func:`time.gmtime`, :meth:`datetime.datetime.now`, :meth:`datetime.datetime.utcnow` and :meth:`datetime.datetime.today` behave according the configuration of the context. The context provides the following manipulation options: * ``rewind``: accepts seconds as an integer or a ``timedelta`` object. * ``forward``: accepts seconds as an integer or a ``timedelta`` object. * ``freeze``: accepts a floating point time since epoch or ``datetime`` or ``date`` object to freeze the time at. * ``unfreeze``: resumes time from the point it was frozen at. * ``scale``: accepts a floating point to accelerate/decelerate time by. ``> 1 = acceleration, < 1 = deceleration`` * ``reset``: resets all time alterations. .. code-block:: python import hiro from datetime import timedelta, datetime import time datetime.now().isoformat() # OUT: '2013-12-01T06:55:41.706060' with hiro.Timeline() as timeline: # forward by an hour timeline.forward(60*60) datetime.now().isoformat() # OUT: '2013-12-01T07:55:41.707383' # jump forward by 10 minutes timeline.forward(timedelta(minutes=10)) datetime.now().isoformat() # OUT: '2013-12-01T08:05:41.707425' # jump to yesterday and freeze timeline.freeze(datetime.now() - timedelta(hours=24)) datetime.now().isoformat() # OUT: '2013-11-30T09:15:41' timeline.scale(5) # scale time by 5x time.sleep(5) # this will effectively only sleep for 1 second # since time is frozen the sleep has no effect datetime.now().isoformat() # OUT: '2013-11-30T09:15:41' timeline.rewind(timedelta(days=365)) datetime.now().isoformat() # OUT: '2012-11-30T09:15:41' To reduce the amount of statements inside the context, certain timeline setup tasks can be done via the constructor and/or by using the fluent interface. .. code-block:: python import hiro import time from datetime import timedelta, datetime start_point = datetime(2012,12,12,0,0,0) my_timeline = hiro.Timeline(scale=5).forward(60*60).freeze() with my_timeline as timeline: print datetime.now() # OUT: '2012-12-12 01:00:00.000315' time.sleep(5) # effectively 1 second # no effect as time is frozen datetime.now() # OUT: '2012-12-12 01:00:00.000315' timeline.unfreeze() # back to starting point datetime.now() # OUT: '2012-12-12 01:00:00.000317' time.sleep(5) # effectively 1 second # takes effect (+5 seconds) datetime.now() # OUT: '2012-12-12 01:00:05.003100' :class:`hiro.Timeline` can additionally be used as a decorator. If the decorated function expects has a ``timeline`` argument, the :class:`hiro.Timeline` will be passed to it. .. code-block:: python import hiro import time, datetime @hiro.Timeline(scale=50000) def sleeper(): datetime.datetime.now() # OUT: '2013-11-30 14:27:43.409291' time.sleep(60*60) # effectively 72 ms datetime.datetime.now() # OUT: '2013-11-30 15:28:36.240675' @hiro.Timeline() def sleeper_aware(timeline): datetime.datetime.now() # OUT: '2013-11-30 14:27:43.409291' timeline.forward(60*60) datetime.datetime.now() # OUT: '2013-11-30 15:28:36.240675' run_sync and run_async ====================== In order to execute certain callables within a :class:`hiro.ScaledTimeline` context, two shortcut functions are provided. * ``run_sync(factor=1, callable, *args, **kwargs)`` * ``run_async(factor=1, callable, *args, **kwargs)`` Both functions return a :class:`hiro.core.ScaledRunner` object which provides the following methods * ``get_execution_time``: The actual execution time of the ``callable`` * ``get_response`` (will either return the actual return value of ``callable`` or raise the exception that was thrown) ``run_async`` returns a derived class of :class:`hiro.core.ScaledRunner` that additionally provides the following methods * ``is_running``: ``True/False`` depending on whether the callable has completed execution * ``join``: blocks until the ``callable`` completes execution .. code-block:: python import hiro import time def _slow_function(n): time.sleep(n) if n > 10: raise RuntimeError() return n runner = hiro.run_sync(10, _slow_function, 10) runner.get_response() # OUT: 10 # due to the scale factor 10 it only took 1s to execute runner.get_execution_time() # OUT: 1.1052658557891846 runner = hiro.run_async(10, _slow_function, 11) runner.is_running() # OUT: True runner.join() runner.get_execution_time() # OUT: 1.1052658557891846 runner.get_response() # OUT: Traceback (most recent call last): # .... # OUT: File "", line 4, in _slow_function # OUT: RuntimeError hiro-0.5/doc/source/project.rst000066400000000000000000000020461313575114100165670ustar00rootroot00000000000000***************** Project resources ***************** .. only:: html - Source : `Github`_ - Continuous Integration: |travis| - Test coverage: |coveralls| - PyPi: |crate| / `pypi `_ .. only:: latex - Source : `Github`_ - Continuous Integration: `Travis-CI `_ - Test coverage: `Coveralls `_ - PyPi: `pypi `_ .. _Github: http://github.com/alisaifee/hiro .. |travis| image:: https://travis-ci.org/alisaifee/hiro.png?branch=master :target: https://travis-ci.org/alisaifee/hiro :alt: Travis-CI .. |coveralls| image:: https://coveralls.io/repos/alisaifee/hiro/badge.png?branch=master :target: https://coveralls.io/r/alisaifee/hiro?branch=master :alt: Coveralls .. |crate| image:: https://pypip.in/v/hiro/badge.png :target: https://crate.io/packages/hiro/ :alt: pypi .. note:: Hiro is tested on pythons version 2.6, 2.7, 3.2, 3.3, 3.4 and pypy >= 2.1 hiro-0.5/hiro/000077500000000000000000000000001313575114100132615ustar00rootroot00000000000000hiro-0.5/hiro/__init__.py000066400000000000000000000002431313575114100153710ustar00rootroot00000000000000""" time manipulation utilities for python """ from .core import run_async, run_sync from .core import Timeline __all__ = ["run_async", "run_sync", "Timeline"] hiro-0.5/hiro/core.py000066400000000000000000000344451313575114100145750ustar00rootroot00000000000000""" timeline & runner implementation """ import copy import inspect import sys import threading import time import datetime from six import reraise import mock from functools import wraps from .errors import SegmentNotComplete, TimeOutofBounds from .utils import timedelta_to_seconds, chained, time_in_seconds from .patches import Date, Datetime BLACKLIST = set() _NO_EXCEPTION = (None, None, None) class Decorator(object): def __call__(self, fn): @wraps(fn) def inner(*args, **kw): self.__enter__() exc = _NO_EXCEPTION try: if "timeline" in inspect.getargspec(fn).args: result = fn(*args, timeline=self, **kw) else: result = fn(*args, **kw) except Exception: exc = sys.exc_info() catch = self.__exit__(*exc) if not catch and exc is not _NO_EXCEPTION: reraise(*exc) return result return inner class Segment(object): """ utility class to manager execution result and timings for :class:`SyncRunner """ def __init__(self): self.__error = False self.__response = None self.__start = time.time() self.__end = None def complete(self, response): """ called upon successful completion of the segment """ self.__response = response def complete_with_error(self, exception): """ called if the segment errored during execution """ self.__error = exception @property def complete_time(self): """ returns the completion time """ return self.__end @complete_time.setter def complete_time(self, completion_time): """ sets the completion time """ self.__end = completion_time @property def start_time(self): """ returns the start time """ return self.__start @start_time.setter def start_time(self, start_time): """ sets the start time """ self.__start = start_time @property def runtime(self): """ returns the total execution time of the segment """ if self.__end: return self.complete_time - self.start_time else: raise SegmentNotComplete @property def response(self): """ returns the return value captured in the segment or raises the :exception:`exceptions.Exception` that was caught. """ if not self.__end: raise SegmentNotComplete else: if self.__error: reraise(self.__error[0], self.__error[1], self.__error[2]) return self.__response class Timeline(Decorator): """ Timeline context manager. Within this context the builtins :func:`time.time`, :func:`time.sleep`, :meth:`datetime.datetime.now`, :meth:`datetime.date.today`, :meth:`datetime.datetime.utcnow` and :func:`time.gmtime` respect the alterations made to the timeline. The class can be used either as a context manager or a decorator. The following are all valid ways to use it. .. code-block:: python with Timeline(scale=10, start=datetime.datetime(2012,12,12)): .... fast_timeline = Timeline(scale=10).forward(120) with fast_timeline as timeline: .... delta = datetime.date(2015,1,1) - datetime.date.today() future_frozen_timeline = Timeline(scale=10000).freeze().forward(delta) with future_frozen_timeline as timeline: ... @Timeline(scale=100) def slow(): time.sleep(120) :param float scale: > 1 time will go faster and < 1 it will be slowed down. :param start: if specified starts the timeline at the given value (either a floating point representing seconds since epoch or a :class:`datetime.datetime` object) """ class_mappings = { "date": (datetime.date, Date), "datetime": (datetime.datetime, Datetime) } def __init__(self, scale=1, start=None): self.reference = time.time() self.offset = time_in_seconds(start) - self.reference if start else 0.0 self.freeze_point = self.freeze_at = None self.patchers = [] self.mock_mappings = { "datetime.date": (datetime.date, Date), "datetime.datetime": (datetime.datetime, Datetime), "time.time": (time.time, self.__time_time), "time.sleep": (time.sleep, self.__time_sleep), "time.gmtime": (time.gmtime, self.__time_gmtime) } self.func_mappings = { "time": (time.time, self.__time_time), "sleep": (time.sleep, self.__time_sleep), "gmtime": (time.gmtime, self.__time_gmtime) } self.factor = scale def _get_original(self, fn_or_mod): """ returns the original moduel or function """ if fn_or_mod in self.mock_mappings: return self.mock_mappings[fn_or_mod][0] elif fn_or_mod in self.func_mappings: return self.func_mappings[fn_or_mod][0] else: return self.class_mappings[fn_or_mod][0] def _get_fake(self, fn_or_mod): """ returns the mocked/patched module or function """ if fn_or_mod in self.mock_mappings: return self.mock_mappings[fn_or_mod][1] elif fn_or_mod in self.func_mappings: return self.func_mappings[fn_or_mod][1] else: return self.class_mappings[fn_or_mod][1] def __compute_time(self, freeze_point, offset): """ computes the current_time after accounting for any adjustments due to :attr:`factor` or invocations of :meth:`freeze`, :meth:`rewind` or :meth:`forward` """ if not freeze_point is None: return offset + freeze_point else: delta = self._get_original("time.time")() - self.reference return self.reference + (delta * self.factor) + offset def __check_out_of_bounds(self, offset=None, freeze_point=None): """ ensures that the time that would be calculated based on any offset or freeze point would not result in jumping beyond the epoch """ next_time = self.__compute_time(freeze_point or self.freeze_point, offset or self.offset ) if next_time < 0: raise TimeOutofBounds(next_time) def __time_time(self): """ patched version of :func:`time.time` """ return self.__compute_time(self.freeze_point, self.offset) def __time_gmtime(self, seconds=None): """ patched version of :func:`time.gmtime` """ return self._get_original("time.gmtime")(seconds or self.__time_time()) def __time_sleep(self, amount): """ patched version of :func:`time.sleep` """ self._get_original("time.sleep")(1.0 * amount / self.factor) @chained def forward(self, amount): """ forwards the timeline by the specified :attr:`amount` :param amount: either an integer representing seconds or a :class:`datetime.timedelta` object """ offset = self.offset if isinstance(amount, datetime.timedelta): offset += timedelta_to_seconds(amount) else: offset += amount self.__check_out_of_bounds(offset=offset) self.offset = offset @chained def rewind(self, amount): """ rewinds the timeline by the specified :attr:`amount` :param amount: either an integer representing seconds or a :class:`datetime.timedelta` object """ offset = self.offset if isinstance(amount, datetime.timedelta): offset -= timedelta_to_seconds(amount) else: offset -= amount self.__check_out_of_bounds(offset=offset) self.offset = offset @chained def freeze(self, target_time=None): """ freezes the timeline :param target_time: the time to freeze at as either a float representing seconds since the epoch or a :class:`datetime.datetime` object. If not provided time will be frozen at the current time of the enclosing :class:`Timeline` """ if target_time is None: freeze_point = self._get_fake("time.time")() else: freeze_point = time_in_seconds(target_time) self.__check_out_of_bounds(freeze_point=freeze_point) self.freeze_point = freeze_point self.offset = 0 @chained def unfreeze(self): """ if a call to :meth:`freeze` was previously made, the timeline will be unfrozen to the point which :meth:`freeze` was invoked. .. warning:: Since unfreezing will reset the timeline back to the point in when the :meth:`freeze` was invoked - the effect of previous invocations of :meth:`forward` and :meth:`rewind` will be lost. This is by design so that freeze/unfreeze can be used as a checkpoint mechanism. """ if self.freeze_point is not None: self.reference = self._get_original("time.time")() self.offset = time_in_seconds(self.freeze_point) - self.reference self.freeze_point = None @chained def scale(self, factor): """ changes the speed at which time elapses and how long sleeps last for. :param float factor: > 1 time will go faster and < 1 it will be slowed down. """ self.factor = factor self.reference = self._get_original("time.time")() @chained def reset(self): """ resets the current timeline to the actual time now with a scale factor 1 """ self.factor = 1 self.freeze_point = None self.reference = self._get_original("time.time")() self.offset = 0 def __enter__(self): for name in list(sys.modules.keys()): module = sys.modules[name] if module in BLACKLIST: continue mappings = copy.copy(self.class_mappings) mappings.update(self.func_mappings) for obj in mappings: try: if obj in dir(module) and getattr(module, obj) == self._get_original( obj): path = "%s.%s" % (name, obj) if not path in self.mock_mappings: patcher = mock.patch(path, self._get_fake(obj)) patcher.start() self.patchers.append(patcher) # this is done for cases where invalid modules are on # sys modules. # pylint: disable=bare-except except: BLACKLIST.add(module) for time_obj in self.mock_mappings: patcher = mock.patch(time_obj, self._get_fake(time_obj)) patcher.start() self.patchers.append(patcher) return self def __exit__(self, exc_type, exc_value, traceback): for patcher in self.patchers: patcher.stop() self.patchers = [] class ScaledRunner(object): """ manages the execution of a callable within a :class:`hiro.Timeline` context. """ def __init__(self, factor, func, *args, **kwargs): self.func = func self.func_args = args self.func_kwargs = kwargs self.segment = Segment() self.factor = factor self.__call__() def _run(self): """ managed execution of :attr:`func` """ self.segment.start_time = time.time() with Timeline(scale=self.factor): try: self.segment.complete( self.func(*self.func_args, **self.func_kwargs)) # will be rethrown # pylint: disable=bare-except except: self.segment.complete_with_error(sys.exc_info()) self.segment.complete_time = time.time() def __call__(self): self._run() return self def get_response(self): """ :returns: the return value from :attr:`func` :raises: Exception if the :attr:`func` raised one during execution """ return self.segment.response def get_execution_time(self): """ :returns: the real execution time of :attr:`func` in seconds """ return self.segment.runtime class ScaledAsyncRunner(ScaledRunner): """ manages the asynchronous execution of a callable within a :class:`hiro.Timeline` context. """ def __init__(self, *args, **kwargs): self.thread_runner = threading.Thread(target=self._run) super(ScaledAsyncRunner, self).__init__(*args, **kwargs) def __call__(self): self.thread_runner.start() return self def is_running(self): """ :rtype bool: whether the :attr:`func` is still running or not. """ return self.thread_runner.is_alive() def join(self): """ waits for the :attr:`func` to complete execution. """ return self.thread_runner.join() def run_sync(factor, func, *args, **kwargs): """ Executes a callable within a :class:`hiro.Timeline` :param int factor: scale factor to use for the timeline during execution :param function func: the function to invoke :param args: the arguments to pass to the function :param kwargs: the keyword arguments to pass to the function :returns: an instance of :class:`hiro.core.ScaledRunner` """ return ScaledRunner(factor, func, *args, **kwargs) def run_async(factor, func, *args, **kwargs): """ Asynchronously executes a callable within a :class:`hiro.Timeline` :param int factor: scale factor to use for the timeline during execution :param function func: the function to invoke :param args: the arguments to pass to the function :param kwargs: the keyword arguments to pass to the function :returns: an instance of :class:`hiro.core.ScaledAsyncRunner` """ return ScaledAsyncRunner(factor, func, *args, **kwargs) hiro-0.5/hiro/errors.py000066400000000000000000000015761313575114100151600ustar00rootroot00000000000000""" exceptions used by hiro. """ class SegmentNotComplete(Exception): """ used to raise an exception if an async segment hasn't completed yet """ pass class TimeOutofBounds(AttributeError): """ used to raise an exception when time is rewound beyond the epoch """ def __init__(self, oob_time): message = ("you've frozen time at a point before the epoch (%d)." "hiro only supports going back to 1970/01/01 07:30:00" % oob_time) super(TimeOutofBounds, self).__init__(message) class InvalidTypeError(TypeError): """ used to raise an exception when an invalid type is provided for type operations """ def __init__(self, value): message = ("%s provided when only float, int, datetime, or date objects" "are supported" % type(value)) super(InvalidTypeError, self).__init__(message) hiro-0.5/hiro/patches.py000066400000000000000000000026211313575114100152630ustar00rootroot00000000000000""" patched builtin time classes for use by :class:`hiro.Timeline` """ import abc from datetime import date as realdate from datetime import datetime as realdatetime import time import six class DatetimeMeta(abc.ABCMeta): """ meta class to allow interaction between :class:`datetime.datetime` objects create inside the :class:`hiro.Timeline` with those created outside it. """ def __instancecheck__(cls, instance): return isinstance(instance, realdatetime) class DateMeta(type): """ meta class to allow interaction between :class:`datetime.date` objects create inside the :class:`hiro.Timeline` with those created outside it. """ def __instancecheck__(cls, instance): return isinstance(instance, realdate) @six.add_metaclass(DatetimeMeta) class Datetime(realdatetime): """ used to patch :class:`datetime.datetime` to follow the rules of the parent :class:`hiro.Timeline` """ @classmethod def now(cls, tz=None): return cls.fromtimestamp(time.time(), tz) @classmethod def utcnow(cls): return cls.utcfromtimestamp(time.time()) @six.add_metaclass(DateMeta) class Date(realdate): """ used to patch :class:`datetime.date` to follow the rules of the parent :class:`hiro.Timeline` """ __metaclass__ = DateMeta @classmethod def today(cls): return cls.fromtimestamp(time.time()) hiro-0.5/hiro/utils.py000066400000000000000000000021031313575114100147670ustar00rootroot00000000000000""" random utility functions """ import calendar import datetime import functools from .errors import InvalidTypeError def timedelta_to_seconds(delta): """ converts a timedelta object to seconds """ seconds = delta.microseconds seconds += (delta.seconds + delta.days * 24 * 3600) * 10 ** 6 return float(seconds) / 10 ** 6 def time_in_seconds(value): """ normalized either a datetime.date, datetime.datetime or float to a float """ if isinstance(value, (float, int)): return value elif isinstance(value, (datetime.date, datetime.datetime)): return calendar.timegm(value.timetuple()) else: raise InvalidTypeError(value) #adopted from: http://www.snip2code.com/Snippet/2535/Fluent-interface-decorators def chained(method): """ Method decorator to allow chaining. """ @functools.wraps(method) def wrapper(self, *args, **kwargs): """ fluent wrapper """ result = method(self, *args, **kwargs) return self if result is None else result return wrapper hiro-0.5/hiro/version.py000066400000000000000000000000761313575114100153230ustar00rootroot00000000000000""" module version """ __version__ = "0.5" # pragma: no cover hiro-0.5/requirements/000077500000000000000000000000001313575114100150435ustar00rootroot00000000000000hiro-0.5/requirements/ci.txt000066400000000000000000000000351313575114100161750ustar00rootroot00000000000000-r test.txt python-coveralls hiro-0.5/requirements/main.txt000066400000000000000000000000211313575114100165210ustar00rootroot00000000000000six>=1.4.1 mock hiro-0.5/requirements/test.txt000066400000000000000000000000321313575114100165560ustar00rootroot00000000000000-r main.txt nose nose-cov hiro-0.5/setup.py000077500000000000000000000016461313575114100140440ustar00rootroot00000000000000""" setup.py for hiro """ __author__ = "Ali-Akber Saifee" __email__ = "ali@indydevs.org.com" __copyright__ = "Copyright 2013, Ali-Akber Saifee" from setuptools import setup import os import sys import re this_dir = os.path.abspath(os.path.dirname(__file__)) REQUIREMENTS = filter(None, open(os.path.join(this_dir, 'requirements/main.txt')).read().splitlines()) extra = {} version = re.compile("__version__\s*=\s*\"(.*?)\"").findall(open("hiro/version.py").read())[0] setup( name='hiro', author = __author__, author_email = __email__, license = "MIT", url="http://hiro.readthedocs.org", zip_safe = False, include_package_data = True, version = version, install_requires = REQUIREMENTS, classifiers=[k for k in open('CLASSIFIERS').read().split('\n') if k], description='time manipulation utilities for python', long_description=open('README.rst').read(), packages = ["hiro"] ) hiro-0.5/tag.sh000077500000000000000000000007141313575114100134340ustar00rootroot00000000000000#!/bin/bash echo current version:$(python -c "import hiro.version;print hiro.version.__version__") read -p "new version:" new_version sed -i -e "s/__version__.*/__version__ = \"${new_version}\" # pragma: no cover/g" hiro/version.py echo "tagging $new_version" git add hiro/version.py git commit -m "updating version to ${new_version}" git tag -s $(python setup.py --version) -m "tagging version ${new_version}" python setup.py build sdist bdist_egg upload hiro-0.5/tests/000077500000000000000000000000001313575114100134625ustar00rootroot00000000000000hiro-0.5/tests/__init__.py000066400000000000000000000002031313575114100155660ustar00rootroot00000000000000# utter bullshit to work around pypy+six on a headless environment from hiro import Timeline with Timeline().freeze(): pass hiro-0.5/tests/emulated_modules/000077500000000000000000000000001313575114100170125ustar00rootroot00000000000000hiro-0.5/tests/emulated_modules/__init__.py000066400000000000000000000000111313575114100211130ustar00rootroot00000000000000""" """ hiro-0.5/tests/emulated_modules/sample_1.py000066400000000000000000000000461313575114100210650ustar00rootroot00000000000000""" """ from . import sub_module_1 hiro-0.5/tests/emulated_modules/sample_2.py000066400000000000000000000000711313575114100210640ustar00rootroot00000000000000import datetime import time from . import sub_module_2 hiro-0.5/tests/emulated_modules/sample_3.py000066400000000000000000000001211313575114100210610ustar00rootroot00000000000000import datetime from time import time, gmtime, sleep from . import sub_module_3 hiro-0.5/tests/emulated_modules/sub_module_1/000077500000000000000000000000001313575114100213705ustar00rootroot00000000000000hiro-0.5/tests/emulated_modules/sub_module_1/__init__.py000066400000000000000000000000351313575114100234770ustar00rootroot00000000000000from .sub_sample_1_1 import *hiro-0.5/tests/emulated_modules/sub_module_1/sub_sample_1_1.py000066400000000000000000000004321313575114100245330ustar00rootroot00000000000000""" """ import datetime import time def sub_sample_1_1_now(): return datetime.datetime.now() def sub_sample_1_1_today(): return datetime.date.today() def sub_sample_1_1_sleep(seconds): datetime.time.sleep(seconds) def sub_sample_1_1_time(): return time.time() hiro-0.5/tests/emulated_modules/sub_module_2/000077500000000000000000000000001313575114100213715ustar00rootroot00000000000000hiro-0.5/tests/emulated_modules/sub_module_2/__init__.py000066400000000000000000000000361313575114100235010ustar00rootroot00000000000000from .sub_sample_2_1 import * hiro-0.5/tests/emulated_modules/sub_module_2/sub_sample_2_1.py000066400000000000000000000004231313575114100245350ustar00rootroot00000000000000""" """ from datetime import datetime, date import time def sub_sample_2_1_now(): return datetime.now() def sub_sample_2_1_today(): return date.today() def sub_sample_2_1_sleep(seconds): time.sleep(seconds) def sub_sample_2_1_time(): return time.time() hiro-0.5/tests/emulated_modules/sub_module_3/000077500000000000000000000000001313575114100213725ustar00rootroot00000000000000hiro-0.5/tests/emulated_modules/sub_module_3/__init__.py000066400000000000000000000000361313575114100235020ustar00rootroot00000000000000from .sub_sample_3_1 import * hiro-0.5/tests/emulated_modules/sub_module_3/sub_sample_3_1.py000066400000000000000000000005241313575114100245410ustar00rootroot00000000000000""" """ from datetime import datetime, date from time import time, sleep, gmtime def sub_sample_3_1_now(): return datetime.now() def sub_sample_3_1_today(): return date.today() def sub_sample_3_1_sleep(seconds): sleep(seconds) def sub_sample_3_1_time(): return time() def sub_sample_3_1_gmtime(): return gmtime() hiro-0.5/tests/test_context_mgr.py000066400000000000000000000154361313575114100174350ustar00rootroot00000000000000import os import unittest import time from datetime import datetime, date, timedelta import math from hiro import Timeline from hiro.utils import timedelta_to_seconds from tests.emulated_modules import sample_1, sample_2, sample_3 class TestScaledContext(unittest.TestCase): def test_accelerate(self): s = time.time() with Timeline(100): time.sleep(10) self.assertTrue(time.time() - s < 10) def test_deccelerate(self): s = time.time() with Timeline(0.5): time.sleep(0.25) self.assertTrue(time.time() - s > 0.25) def test_check_time(self): start = time.time() with Timeline(100): last = time.time() for i in range(0, 10): time.sleep(1) delta = time.time() - (start + (i+1)*100) self.assertTrue(delta <= 1.5, delta) def test_utc(self): start_local = datetime.now() start_utc = datetime.utcnow() with Timeline(100000): time.sleep(60*60) self.assertEquals( math.ceil((datetime.now() - datetime.utcnow()).seconds / 60.0 / 60.0), math.ceil((start_local - start_utc).seconds / 60.0 / 60.0)) time.sleep(60*60*23) self.assertEquals( (datetime.now() - start_local).days, 1 ) self.assertEquals( (datetime.utcnow() - start_utc).days, 1 ) class TestTimelineContext(unittest.TestCase): def test_forward(self): original = date.today() with Timeline() as timeline: timeline.forward(86400) self.assertEquals( (date.today() - original).days, 1) def test_rewind(self): original_day = date.today() original_datetime = datetime.now() with Timeline() as timeline: timeline.rewind(86400) self.assertEquals( (original_day - date.today()).days, 1) timeline.forward(timedelta(days=1)) self.assertEquals( (original_day - date.today()).days, 0) timeline.rewind(timedelta(days=1)) amount = (original_datetime - datetime.now()) amount_seconds = timedelta_to_seconds(amount) self.assertTrue( int(amount_seconds) >= 86399, amount_seconds) def test_freeze(self): with Timeline() as timeline: timeline.freeze() originals = sample_1.sub_module_1.sub_sample_1_1_time(), sample_1.sub_module_1.sub_sample_1_1_now() time.sleep(1) self.assertEquals(time.time(), originals[0]) self.assertEquals(datetime.now(), originals[1]) with Timeline() as timeline: timeline.freeze() originals = sample_2.sub_module_2.sub_sample_2_1_time(), sample_2.sub_module_2.sub_sample_2_1_now() time.sleep(1) self.assertEquals(time.time(), originals[0]) self.assertEquals(datetime.now(), originals[1]) with Timeline() as timeline: timeline.freeze() originals = sample_3.sub_module_3.sub_sample_3_1_time(), sample_3.sub_module_3.sub_sample_3_1_now() time.sleep(1) self.assertEquals(time.time(), originals[0]) self.assertEquals(datetime.now(), originals[1]) def test_freeze_target(self): with Timeline() as timeline: timeline.freeze( datetime.fromtimestamp(0) ) self.assertAlmostEquals(time.time(), 0, 1) with Timeline() as timeline: timeline.freeze( 0 ) self.assertAlmostEquals(time.time(), 0, 1) with Timeline() as timeline: self.assertRaises(TypeError, timeline.freeze, "now") with Timeline() as timeline: self.assertRaises(AttributeError, timeline.freeze, -1) self.assertRaises(AttributeError, timeline.freeze, date(1969,1,1)) def test_unfreeze(self): real_day = date.today() with Timeline() as timeline: timeline.freeze(0) self.assertAlmostEquals(time.time(), 0) timeline.unfreeze() timeline.scale(10) time.sleep(1) self.assertEqual(int(time.time()), 1) timeline.forward(timedelta(minutes=2)) self.assertEqual(int(time.time()), 121) timeline.reset() self.assertTrue(int(time.time()) - int(os.popen("date +%s").read().strip()) <= 1) timeline.forward(timedelta(days=1)) self.assertTrue((date.today() - real_day).days == 1) def test_freeze_forward_unfreeze(self): # start at 2012/12/12 0:0:0 test_timeline = Timeline(scale=100, start=datetime(2012,12,12,0,0,0)) # jump forward an hour and freeze with test_timeline.forward(60*60).freeze(): self.assertAlmostEqual((datetime.now() - datetime(2012,12,12,1,0,0)).seconds, 0) # sleep 10 seconds time.sleep(10) # assert no changes self.assertAlmostEqual((datetime.now() - datetime(2012,12,12,1,0,0)).seconds, 0) # unfreeze timeline test_timeline.unfreeze() # ensure unfreeze was at the original freeze point self.assertAlmostEqual((datetime.now() - datetime(2012,12,12,1,0,0)).seconds, 0) # sleep 10 seconds time.sleep(10) # ensure post unfreeze, time moves self.assertAlmostEqual((datetime.now() - datetime(2012,12,12,1,0,0)).seconds, 10) # ensure post unfreeze, forward operations work test_timeline.forward(timedelta(hours=2)) self.assertAlmostEqual(int(timedelta_to_seconds(datetime.now() - datetime(2012,12,12,1,0,0))), 60*60*2 + 10) # reset everything test_timeline.reset() self.assertEquals(int(time.time()), int(os.popen("date +%s").read().strip())) def test_fluent(self): start = datetime.now() with Timeline().scale(10).forward(120): self.assertTrue(int(timedelta_to_seconds(datetime.now() - start)) >= 120) time.sleep(10) self.assertTrue(int(timedelta_to_seconds(datetime.now() - start)) >= 130) self.assertTrue((datetime.now() - start).seconds < 10) def test_decorated(self): start = datetime(2013,1,1,0,0,0) real_start = datetime.now() @Timeline(scale=10, start=start) def _decorated(): time.sleep(10) self.assertTrue(int(timedelta_to_seconds(datetime.now() - start)) >= 10) _decorated() self.assertTrue(((datetime.now() - real_start).seconds < 10)) def test_decorated_with_argument(self): @Timeline() def _decorated(timeline): self.assertTrue(isinstance(timeline, Timeline)) _decorated() @Timeline() def _decorated(timeline=None): self.assertTrue(isinstance(timeline, Timeline)) _decorated() hiro-0.5/tests/test_runners.py000066400000000000000000000033231313575114100165700ustar00rootroot00000000000000""" """ import unittest import time import hiro from hiro.errors import SegmentNotComplete class TestSyncRunner(unittest.TestCase): def test_scale_up_runner(self): def _slow_func(): time.sleep(1) return 1 f = hiro.run_sync(4, _slow_func) self.assertTrue(f.get_execution_time() < 1) self.assertEquals(f.get_response(), 1) def test_scale_up_runner_fail(self): def _slow_func(): time.sleep(1) raise Exception("foo") f = hiro.run_sync(4, _slow_func) self.assertRaises(Exception, f.get_response) self.assertTrue(f.get_execution_time() < 1) class TestASyncRunner(unittest.TestCase): def test_scale_up_runner(self): def _slow_func(): time.sleep(1) f = hiro.run_async(4, _slow_func) self.assertTrue(f.is_running()) f.join() self.assertTrue(f.get_execution_time() < 1) def test_scale_up_runner_fail(self): def _slow_func(): time.sleep(1) raise Exception("foo") f = hiro.run_async(4, _slow_func) self.assertTrue(f.is_running()) f.join() self.assertRaises(Exception, f.get_response) self.assertTrue(f.get_execution_time() < 1) def test_segment_not_complete_error(self): def _slow_func(): time.sleep(1) raise Exception("foo") f = hiro.run_async(4, _slow_func) self.assertRaises(SegmentNotComplete, f.get_execution_time) self.assertRaises(SegmentNotComplete, f.get_response) self.assertTrue(f.is_running()) f.join() self.assertRaises(Exception, f.get_response) self.assertTrue(f.get_execution_time() < 1 ) hiro-0.5/tests/test_utils.py000066400000000000000000000031051313575114100162320ustar00rootroot00000000000000import datetime import unittest import hiro from hiro.errors import InvalidTypeError from hiro.utils import timedelta_to_seconds, time_in_seconds, chained class TestTimeDeltaToSeconds(unittest.TestCase): def test_fractional(self): delta = datetime.timedelta(seconds=1, microseconds=1000) self.assertAlmostEqual(timedelta_to_seconds(delta), 1.001) def test_days(self): delta = datetime.timedelta(days=10) self.assertEqual(timedelta_to_seconds(delta), delta.days * 24 * 60 * 60) class TestTimeInSeconds(unittest.TestCase): def test_passthrough(self): self.assertEqual(time_in_seconds(1), 1) self.assertEqual(time_in_seconds(1.0), 1.0) def test_date(self): d = datetime.date(1970, 1, 1) self.assertEqual(time_in_seconds(d), 0) def test_datetime(self): d = datetime.datetime(1970, 1, 1, 0, 0, 0) self.assertEqual(time_in_seconds(d), 0) def test_invalid_type(self): self.assertRaises( InvalidTypeError, time_in_seconds, "this is a string") class TestChained(unittest.TestCase): class Foo(object): @chained def return_value(self, value=None): return value def setUp(self): self.obj = self.Foo() def test_no_return(self): self.assertTrue(self.obj.return_value() is self.obj) def test_with_return(self): o = object() self.assertTrue(self.obj.return_value(o) is o) def test_kwargs(self): o = object() self.assertTrue(self.obj.return_value(value=o) is o)