exactly


Nameexactly JSON
Version 0.8.9 PyPI version JSON
download
home_pagehttps://github.com/emilkarlen/exactly/wiki
SummaryTests a command line program by executing it in a temporary sandbox directory and inspecting its result.
upload_time2018-04-15 20:59:26
maintainer
docs_urlNone
authorEmil Karlen
requires_python
licenseGPLv3+
keywords test case suite check assert script shell console command line program execute sandbox
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            Tests a command line program by executing it in a temporary sandbox directory and inspecting its result.

Or tests properties of existing files, directories etc.


Supports individual test cases and test suites.

Exactly has a `Wiki
<https://github.com/emilkarlen/exactly/wiki>`_,
and an `introduction by examples
<https://github.com/emilkarlen/exactly/wiki/Exactly-by-example>`_.

It also has a built in help system,
which can, among other things,
generate this `Reference manual
<http://htmlpreview.github.io/?https://raw.githubusercontent.com/wiki/emilkarlen/exactly/Reference.html>`_.


.. contents::


TEST CASES
========================================

A test case is written as a plain text file.


Testing stdin, stdout, stderr, exit code
------------------------------------------------------------

The following checks that your new ``my-contacts-program`` reads a contact list from stdin,
and is able to find the email of a person::

    [setup]

    stdin = -file some-test-contacts.txt

    [act]

    my-contacts-program get-email-of --name 'Pablo Gauss'

    [assert]

    exitcode == 0

    stdout equals <<EOF
    pablo@gauss.org
    EOF

    stderr empty


If the file 'contacts.case' contains this test case, then Exactly can execute it::


    > exactly contacts.case
    PASS


"PASS" means that all assertions were satisfied.

This test assumes that

 * the system under test - ``my-contacts-program`` - is is found in the same directory as the test case file
 * the file "some-test-contacts.txt" (that is referenced from the test case) is found in the same directory as the test case file

The ``home`` and ``act-home`` instructions
can be used to change the directories where Exactly looks for files referenced from the test case.


Testing side effects on files and directories
------------------------------------------------------------

A test case is executed in a temporary sandbox directory,
so files and directories can be created and deleted
without modifying a source code repo.

The following tests a program that classifies
files as either good or bad, by moving them to the
appropriate output directory::

    [setup]

    dir input-files
    dir output-files/good
    dir output-files/bad

    file input-files/a.txt = <<EOF
    GOOD contents
    EOF

    file input-files/b.txt = <<EOF
    bad contents
    EOF

    [act]

    classify-files-by-moving-to-appropriate-dir GOOD .

    [assert]

    dir-contents input-files empty

    exists -file output-files/good/a.txt
    dir-contents  output-files/good num-files == 1

    exists -file output-files/bad/b.txt
    dir-contents  output-files/bad num-files == 1


Testing and transforming the contents of files
------------------------------------------------------------

The ``contents`` instruction tests the contents of a file.
It can also test a transformed version of a file,
by applying a "lines transformer".

Such a "lines transformer" may be given a name
using the ``def`` instruction
to make the test easier to read.

The following test case
tests that "timing lines" are output as part of a log file "log.txt".

The challenge is that the (fictive) log file contains
non-timing lines that we are not interested in,
and that timing lines contains a time stamp of the form
"NN:NN", whos exact value we are also not interested in.

A "lines transformer" is used to extract all timing lines
and to replace "NN:NN" time stamps with the constant string ``TIMESTAMP``::


    [act]

    my-system-under-test-that-writes-log-file

    [assert]

    contents log.txt -transformed-by GET_TIMING_LINES equals <<EOF
    timing TIMESTAMP begin
    timing TIMESTAMP preprocessing
    timing TIMESTAMP validation
    timing TIMESTAMP execution
    timing TIMESTAMP end
    EOF

    [setup]

    def line-matcher      IS_TIMING_LINE     = regex ^timing

    def lines-transformer REPLACE_TIMESTAMPS = replace [0-9]{2}:[0-9]{2} TIMESTAMP

    def lines-transformer GET_TIMING_LINES   = select IS_TIMING_LINE | REPLACE_TIMESTAMPS


The ``-transformed-by`` option does not modify the tested file,
it just applies the assertion to a transformed version of it.


Using shell commands
--------------------

Shell commands can be used both in the "act" phase (the system under test), and in other phases, using "$".

::

    [setup]

    $ touch file

    [act]

    $ echo ${PATH}

    [assert]

    $ tr ':' '\n' < ../result/stdout | grep '^/usr/local/bin$'


A shell command in the "assert" phase becomes an assertion that depends on the exit code
of the command.


Testing source code files
-------------------------

The ``actor`` instruction can specify an interpreter to test a source code file::

    [conf]

    actor = -file python

    [act]

    my-python-program.py 'an argument'

    [assert]

    stdout equals <<EOF
    Arguments: an argument
    EOF



Experimenting with source code
------------------------------

The "source interpreter" actor treats the contents of the "act" phase as source code.
It's probably most useful as a tool for experimenting::

    [conf]

    actor = -source bash

    [act]

    var='hello world'
    echo ${var:5}

    [assert]

    stdout equals <<EOF
    world
    EOF

or for running a source file in a sandbox::

    > exactly --actor bash my-script.sh
    PASS


This is more useful combined with ``--act`` (see below).


[act] is the default phase
--------------------------


``[act]`` is not needed to indicate what is being checked, since the "act" phase is the default phase.

The following is a valid test case,
and if run by Exactly, it won't remove anything, since it is executed inside a temporary sandbox directory::

    $ rm -rf *


Print output from the tested program
------------------------------------


If ``--act`` is used, the output of the "act" phase (the tested program) will become the output of ``exactly`` -
stdout, stderr and exit code.
::

    $ echo Hello World

    [assert]

    stdout contains Hello

::

    > exactly --act hello-world.case
    Hello World


The test case is executed in a sandbox, as usual.
And all phases are executed, not just the "act" phase.
But the outcome of tha "assert" phase is ignored.


Keeping the sandbox directory for later inspection
--------------------------------------------------


If ``--keep`` is used, the sandbox directory will not be deleted, and its name will be printed.

This can be used to inspect the outcome of the "setup" phase, e.g::

    [setup]

    dir  my-dir
    file my-file.txt

    [act]

    my-prog my-file.txt

    [assert]

    exitcode == 0

::

    > exactly --keep my-test.case
    /tmp/exactly-1strbro1

    > find /tmp/exactly-1strbro1
    /tmp/exactly-1strbro1
    /tmp/exactly-1strbro1/tmp
    /tmp/exactly-1strbro1/tmp/user
    /tmp/exactly-1strbro1/tmp/internal
    /tmp/exactly-1strbro1/testcase
    /tmp/exactly-1strbro1/act
    /tmp/exactly-1strbro1/act/my-dir
    /tmp/exactly-1strbro1/act/my-file.txt
    /tmp/exactly-1strbro1/result
    /tmp/exactly-1strbro1/result/exitcode
    /tmp/exactly-1strbro1/result/stderr
    /tmp/exactly-1strbro1/result/stdout
    /tmp/exactly-1strbro1/log

The ``act/`` directory is the current directory when the test starts.
The ``file`` instruction has put the file ``my-file.txt`` there.

The result of the "act" phase is saved in the ``result/`` directory.

``tmp/user/`` is a directory where the test can put temporary files.

TEST SUITES
========================================


Tests can be grouped in suites::


    first.case
    second.case

or::

    [cases]

    helloworld.case
    *.case
    **/*.case


    [suites]

    subsuite.suite
    *.suite
    pkg/suite.suite
    **/*.suite


If the file ``my-suite.suite`` contains this text, then Exactly can run it::

  > exactly suite my-suite.suite
  ...
  OK


The result of a suite can also be reported as JUnit XML, by using ``--reporter junit``.


HELP
========================================


Exactly has a built in help system.


Use ``exactly --help`` or ``exactly help`` to get brief help.

``exactly help help`` displays a summary of help options.

``exactly help instructions`` lists the instructions that are available in each "phase".

``exactly help htmldoc`` outputs all built in help as html, which serves as Exactly's reference manual.


EXAMPLES
========================================

The ``examples/`` directory of the source distribution contains examples.

A complex example
-----------------

The following test case displays a potpurri of features. (Beware that this test case does not make sense! -
it just displays some of Exactly's features.)
::

    [conf]


    status = SKIP
    # This will cause the test case to not be executed.


    [setup]


    install this-is-an-existing-file-in-same-dir-as-test-case.txt

    dir first/second/third

    file in/a/dir/file-name.txt = <<EOF
    contents of the file
    EOF

    file output-from-git.txt = -stdout $ git status

    file git-branch-info.txt = -stdout
                               $ git status
                               -transformed-by select line-num == 1

    dir root-dir-for-act-phase

    cd root-dir-for-act-phase
    # This will be current directory for the "act" phase.

    stdin = <<EOF
    this will be stdin for the program in the "act" phase
    EOF
    # (It is also possible to have stdin redirected to an existing file.)

    env MY_VAR = 'value of my environment variable'

    env PATH   = '${PATH}:/my-dir'

    env unset VARIABLE_THAT_SHOULD_NOT_BE_SET

    run my-prog--located-in-same-dir-as-test-case--that-does-some-more-setup 'with an argument'

    run -python -existing-file custom-setup.py 'with an argument'

    run -python -c :> print('Setting up things...')


    [act]


    the-system-under-test


    [before-assert]


    cd ..
    # Moves back to the original current directory.

    $ sort root-dir-for-act-phase/output-from-sut.txt > sorted.txt


    [assert]


    exitcode != 0

    stdout equals <<EOF
    This is the expected output from the-system-under-test
    EOF

    stdout -transformed-by REPLACE_TEST_CASE_DIRS
           any line : matches regex 'EXACTLY_ACT:[0-9]+'

    stderr empty

    contents a-file.txt empty

    contents a-second-file.txt ! empty

    contents another-file.txt
             -transformed-by REPLACE_TEST_CASE_DIRS
             equals -file expected-content.txt

    contents file.txt any line : matches regex 'my .* reg ex'

    exists actual-file

    exists -dir actual-file

    cd this-dir-is-where-we-should-be-for-the-following-assertions

    run my-prog--located-in-same-dir-as-test-case--that-does-some-assertions

    run -python -existing-file custom-assertion.py


    file -rel-tmp modified-stdout.txt = -file -rel-result stdout
                                        -transformed-by select line-num >= 10

    contents -rel-tmp modified-stdout.txt
             equals
    <<EOF
    this should be line 10 of original stdout.txt
    this should be line 11 of original stdout.txt
    EOF


    stdout  -transformed-by ( select line-num >= 10 )
            equals
    <<EOF
    this should be line 10 of original stdout.txt
    this should be line 11 of original stdout.txt
    EOF


    [cleanup]


    $ umount my-test-mount-point

    run my-prog-that-removes-database 'my test database'


INSTALLING
========================================


Exactly is written in Python and does not require any external libraries.

Exactly requires Python >= 3.5 (not tested on earlier version of Python 3).

Use ``pip`` or ``pip3`` to install::

    > pip install exactly

or::

    > pip3 install exactly

The program can also be run from a source distribution::

    > python3 src/default-main-program-runner.py


DEVELOPMENT STATUS
========================================


Current version is fully functional, but syntax and semantics are experimental.

Comments are welcome!


AUTHOR
========================================


Emil Karlén

emil@member.fsf.org


THANKS
========================================


The Python IDE
`PyCharm
<https://www.jetbrains.com/pycharm/>`_
from
`JetBrains
<https://www.jetbrains.com/>`_
has greatly helped the development of this software.


DEDICATION
========================================


Aron Karlén

Tommy Karlsson

Götabergsgatan 10, lägenhet 4



            

Raw data

            {
    "maintainer": "", 
    "docs_url": null, 
    "requires_python": "", 
    "maintainer_email": "", 
    "cheesecake_code_kwalitee_id": null, 
    "keywords": "test case suite check assert script shell console command line program execute sandbox", 
    "upload_time": "2018-04-15 20:59:26", 
    "author": "Emil Karlen", 
    "home_page": "https://github.com/emilkarlen/exactly/wiki", 
    "download_url": "https://pypi.python.org/packages/97/29/61c931cacda62e85af354c3670ee342cb5fdb00321d87ece679bdff2f9ea/exactly-0.8.9.tar.gz", 
    "platform": "", 
    "version": "0.8.9", 
    "cheesecake_documentation_id": null, 
    "description": "Tests a command line program by executing it in a temporary sandbox directory and inspecting its result.\n\nOr tests properties of existing files, directories etc.\n\n\nSupports individual test cases and test suites.\n\nExactly has a `Wiki\n<https://github.com/emilkarlen/exactly/wiki>`_,\nand an `introduction by examples\n<https://github.com/emilkarlen/exactly/wiki/Exactly-by-example>`_.\n\nIt also has a built in help system,\nwhich can, among other things,\ngenerate this `Reference manual\n<http://htmlpreview.github.io/?https://raw.githubusercontent.com/wiki/emilkarlen/exactly/Reference.html>`_.\n\n\n.. contents::\n\n\nTEST CASES\n========================================\n\nA test case is written as a plain text file.\n\n\nTesting stdin, stdout, stderr, exit code\n------------------------------------------------------------\n\nThe following checks that your new ``my-contacts-program`` reads a contact list from stdin,\nand is able to find the email of a person::\n\n    [setup]\n\n    stdin = -file some-test-contacts.txt\n\n    [act]\n\n    my-contacts-program get-email-of --name 'Pablo Gauss'\n\n    [assert]\n\n    exitcode == 0\n\n    stdout equals <<EOF\n    pablo@gauss.org\n    EOF\n\n    stderr empty\n\n\nIf the file 'contacts.case' contains this test case, then Exactly can execute it::\n\n\n    > exactly contacts.case\n    PASS\n\n\n\"PASS\" means that all assertions were satisfied.\n\nThis test assumes that\n\n * the system under test - ``my-contacts-program`` - is is found in the same directory as the test case file\n * the file \"some-test-contacts.txt\" (that is referenced from the test case) is found in the same directory as the test case file\n\nThe ``home`` and ``act-home`` instructions\ncan be used to change the directories where Exactly looks for files referenced from the test case.\n\n\nTesting side effects on files and directories\n------------------------------------------------------------\n\nA test case is executed in a temporary sandbox directory,\nso files and directories can be created and deleted\nwithout modifying a source code repo.\n\nThe following tests a program that classifies\nfiles as either good or bad, by moving them to the\nappropriate output directory::\n\n    [setup]\n\n    dir input-files\n    dir output-files/good\n    dir output-files/bad\n\n    file input-files/a.txt = <<EOF\n    GOOD contents\n    EOF\n\n    file input-files/b.txt = <<EOF\n    bad contents\n    EOF\n\n    [act]\n\n    classify-files-by-moving-to-appropriate-dir GOOD .\n\n    [assert]\n\n    dir-contents input-files empty\n\n    exists -file output-files/good/a.txt\n    dir-contents  output-files/good num-files == 1\n\n    exists -file output-files/bad/b.txt\n    dir-contents  output-files/bad num-files == 1\n\n\nTesting and transforming the contents of files\n------------------------------------------------------------\n\nThe ``contents`` instruction tests the contents of a file.\nIt can also test a transformed version of a file,\nby applying a \"lines transformer\".\n\nSuch a \"lines transformer\" may be given a name\nusing the ``def`` instruction\nto make the test easier to read.\n\nThe following test case\ntests that \"timing lines\" are output as part of a log file \"log.txt\".\n\nThe challenge is that the (fictive) log file contains\nnon-timing lines that we are not interested in,\nand that timing lines contains a time stamp of the form\n\"NN:NN\", whos exact value we are also not interested in.\n\nA \"lines transformer\" is used to extract all timing lines\nand to replace \"NN:NN\" time stamps with the constant string ``TIMESTAMP``::\n\n\n    [act]\n\n    my-system-under-test-that-writes-log-file\n\n    [assert]\n\n    contents log.txt -transformed-by GET_TIMING_LINES equals <<EOF\n    timing TIMESTAMP begin\n    timing TIMESTAMP preprocessing\n    timing TIMESTAMP validation\n    timing TIMESTAMP execution\n    timing TIMESTAMP end\n    EOF\n\n    [setup]\n\n    def line-matcher      IS_TIMING_LINE     = regex ^timing\n\n    def lines-transformer REPLACE_TIMESTAMPS = replace [0-9]{2}:[0-9]{2} TIMESTAMP\n\n    def lines-transformer GET_TIMING_LINES   = select IS_TIMING_LINE | REPLACE_TIMESTAMPS\n\n\nThe ``-transformed-by`` option does not modify the tested file,\nit just applies the assertion to a transformed version of it.\n\n\nUsing shell commands\n--------------------\n\nShell commands can be used both in the \"act\" phase (the system under test), and in other phases, using \"$\".\n\n::\n\n    [setup]\n\n    $ touch file\n\n    [act]\n\n    $ echo ${PATH}\n\n    [assert]\n\n    $ tr ':' '\\n' < ../result/stdout | grep '^/usr/local/bin$'\n\n\nA shell command in the \"assert\" phase becomes an assertion that depends on the exit code\nof the command.\n\n\nTesting source code files\n-------------------------\n\nThe ``actor`` instruction can specify an interpreter to test a source code file::\n\n    [conf]\n\n    actor = -file python\n\n    [act]\n\n    my-python-program.py 'an argument'\n\n    [assert]\n\n    stdout equals <<EOF\n    Arguments: an argument\n    EOF\n\n\n\nExperimenting with source code\n------------------------------\n\nThe \"source interpreter\" actor treats the contents of the \"act\" phase as source code.\nIt's probably most useful as a tool for experimenting::\n\n    [conf]\n\n    actor = -source bash\n\n    [act]\n\n    var='hello world'\n    echo ${var:5}\n\n    [assert]\n\n    stdout equals <<EOF\n    world\n    EOF\n\nor for running a source file in a sandbox::\n\n    > exactly --actor bash my-script.sh\n    PASS\n\n\nThis is more useful combined with ``--act`` (see below).\n\n\n[act] is the default phase\n--------------------------\n\n\n``[act]`` is not needed to indicate what is being checked, since the \"act\" phase is the default phase.\n\nThe following is a valid test case,\nand if run by Exactly, it won't remove anything, since it is executed inside a temporary sandbox directory::\n\n    $ rm -rf *\n\n\nPrint output from the tested program\n------------------------------------\n\n\nIf ``--act`` is used, the output of the \"act\" phase (the tested program) will become the output of ``exactly`` -\nstdout, stderr and exit code.\n::\n\n    $ echo Hello World\n\n    [assert]\n\n    stdout contains Hello\n\n::\n\n    > exactly --act hello-world.case\n    Hello World\n\n\nThe test case is executed in a sandbox, as usual.\nAnd all phases are executed, not just the \"act\" phase.\nBut the outcome of tha \"assert\" phase is ignored.\n\n\nKeeping the sandbox directory for later inspection\n--------------------------------------------------\n\n\nIf ``--keep`` is used, the sandbox directory will not be deleted, and its name will be printed.\n\nThis can be used to inspect the outcome of the \"setup\" phase, e.g::\n\n    [setup]\n\n    dir  my-dir\n    file my-file.txt\n\n    [act]\n\n    my-prog my-file.txt\n\n    [assert]\n\n    exitcode == 0\n\n::\n\n    > exactly --keep my-test.case\n    /tmp/exactly-1strbro1\n\n    > find /tmp/exactly-1strbro1\n    /tmp/exactly-1strbro1\n    /tmp/exactly-1strbro1/tmp\n    /tmp/exactly-1strbro1/tmp/user\n    /tmp/exactly-1strbro1/tmp/internal\n    /tmp/exactly-1strbro1/testcase\n    /tmp/exactly-1strbro1/act\n    /tmp/exactly-1strbro1/act/my-dir\n    /tmp/exactly-1strbro1/act/my-file.txt\n    /tmp/exactly-1strbro1/result\n    /tmp/exactly-1strbro1/result/exitcode\n    /tmp/exactly-1strbro1/result/stderr\n    /tmp/exactly-1strbro1/result/stdout\n    /tmp/exactly-1strbro1/log\n\nThe ``act/`` directory is the current directory when the test starts.\nThe ``file`` instruction has put the file ``my-file.txt`` there.\n\nThe result of the \"act\" phase is saved in the ``result/`` directory.\n\n``tmp/user/`` is a directory where the test can put temporary files.\n\nTEST SUITES\n========================================\n\n\nTests can be grouped in suites::\n\n\n    first.case\n    second.case\n\nor::\n\n    [cases]\n\n    helloworld.case\n    *.case\n    **/*.case\n\n\n    [suites]\n\n    subsuite.suite\n    *.suite\n    pkg/suite.suite\n    **/*.suite\n\n\nIf the file ``my-suite.suite`` contains this text, then Exactly can run it::\n\n  > exactly suite my-suite.suite\n  ...\n  OK\n\n\nThe result of a suite can also be reported as JUnit XML, by using ``--reporter junit``.\n\n\nHELP\n========================================\n\n\nExactly has a built in help system.\n\n\nUse ``exactly --help`` or ``exactly help`` to get brief help.\n\n``exactly help help`` displays a summary of help options.\n\n``exactly help instructions`` lists the instructions that are available in each \"phase\".\n\n``exactly help htmldoc`` outputs all built in help as html, which serves as Exactly's reference manual.\n\n\nEXAMPLES\n========================================\n\nThe ``examples/`` directory of the source distribution contains examples.\n\nA complex example\n-----------------\n\nThe following test case displays a potpurri of features. (Beware that this test case does not make sense! -\nit just displays some of Exactly's features.)\n::\n\n    [conf]\n\n\n    status = SKIP\n    # This will cause the test case to not be executed.\n\n\n    [setup]\n\n\n    install this-is-an-existing-file-in-same-dir-as-test-case.txt\n\n    dir first/second/third\n\n    file in/a/dir/file-name.txt = <<EOF\n    contents of the file\n    EOF\n\n    file output-from-git.txt = -stdout $ git status\n\n    file git-branch-info.txt = -stdout\n                               $ git status\n                               -transformed-by select line-num == 1\n\n    dir root-dir-for-act-phase\n\n    cd root-dir-for-act-phase\n    # This will be current directory for the \"act\" phase.\n\n    stdin = <<EOF\n    this will be stdin for the program in the \"act\" phase\n    EOF\n    # (It is also possible to have stdin redirected to an existing file.)\n\n    env MY_VAR = 'value of my environment variable'\n\n    env PATH   = '${PATH}:/my-dir'\n\n    env unset VARIABLE_THAT_SHOULD_NOT_BE_SET\n\n    run my-prog--located-in-same-dir-as-test-case--that-does-some-more-setup 'with an argument'\n\n    run -python -existing-file custom-setup.py 'with an argument'\n\n    run -python -c :> print('Setting up things...')\n\n\n    [act]\n\n\n    the-system-under-test\n\n\n    [before-assert]\n\n\n    cd ..\n    # Moves back to the original current directory.\n\n    $ sort root-dir-for-act-phase/output-from-sut.txt > sorted.txt\n\n\n    [assert]\n\n\n    exitcode != 0\n\n    stdout equals <<EOF\n    This is the expected output from the-system-under-test\n    EOF\n\n    stdout -transformed-by REPLACE_TEST_CASE_DIRS\n           any line : matches regex 'EXACTLY_ACT:[0-9]+'\n\n    stderr empty\n\n    contents a-file.txt empty\n\n    contents a-second-file.txt ! empty\n\n    contents another-file.txt\n             -transformed-by REPLACE_TEST_CASE_DIRS\n             equals -file expected-content.txt\n\n    contents file.txt any line : matches regex 'my .* reg ex'\n\n    exists actual-file\n\n    exists -dir actual-file\n\n    cd this-dir-is-where-we-should-be-for-the-following-assertions\n\n    run my-prog--located-in-same-dir-as-test-case--that-does-some-assertions\n\n    run -python -existing-file custom-assertion.py\n\n\n    file -rel-tmp modified-stdout.txt = -file -rel-result stdout\n                                        -transformed-by select line-num >= 10\n\n    contents -rel-tmp modified-stdout.txt\n             equals\n    <<EOF\n    this should be line 10 of original stdout.txt\n    this should be line 11 of original stdout.txt\n    EOF\n\n\n    stdout  -transformed-by ( select line-num >= 10 )\n            equals\n    <<EOF\n    this should be line 10 of original stdout.txt\n    this should be line 11 of original stdout.txt\n    EOF\n\n\n    [cleanup]\n\n\n    $ umount my-test-mount-point\n\n    run my-prog-that-removes-database 'my test database'\n\n\nINSTALLING\n========================================\n\n\nExactly is written in Python and does not require any external libraries.\n\nExactly requires Python >= 3.5 (not tested on earlier version of Python 3).\n\nUse ``pip`` or ``pip3`` to install::\n\n    > pip install exactly\n\nor::\n\n    > pip3 install exactly\n\nThe program can also be run from a source distribution::\n\n    > python3 src/default-main-program-runner.py\n\n\nDEVELOPMENT STATUS\n========================================\n\n\nCurrent version is fully functional, but syntax and semantics are experimental.\n\nComments are welcome!\n\n\nAUTHOR\n========================================\n\n\nEmil Karl\u00e9n\n\nemil@member.fsf.org\n\n\nTHANKS\n========================================\n\n\nThe Python IDE\n`PyCharm\n<https://www.jetbrains.com/pycharm/>`_\nfrom\n`JetBrains\n<https://www.jetbrains.com/>`_\nhas greatly helped the development of this software.\n\n\nDEDICATION\n========================================\n\n\nAron Karl\u00e9n\n\nTommy Karlsson\n\nG\u00f6tabergsgatan 10, l\u00e4genhet 4\n\n\n", 
    "lcname": "exactly", 
    "bugtrack_url": null, 
    "github": false, 
    "name": "exactly", 
    "license": "GPLv3+", 
    "summary": "Tests a command line program by executing it in a temporary sandbox directory and inspecting its result.", 
    "split_keywords": [
        "test", 
        "case", 
        "suite", 
        "check", 
        "assert", 
        "script", 
        "shell", 
        "console", 
        "command", 
        "line", 
        "program", 
        "execute", 
        "sandbox"
    ], 
    "author_email": "emil@member.fsf.org", 
    "urls": [
        {
            "has_sig": false, 
            "upload_time": "2018-04-15T20:59:23", 
            "comment_text": "", 
            "python_version": "py3", 
            "url": "https://pypi.python.org/packages/98/aa/6ae7a05ae79f1f129626ba57133c34ffb0c9c9687bacd5854b072acc6dff/exactly-0.8.9-py3-none-any.whl", 
            "md5_digest": "629e8304d21d8299951f8ae46a4d245b", 
            "downloads": 0, 
            "filename": "exactly-0.8.9-py3-none-any.whl", 
            "packagetype": "bdist_wheel", 
            "path": "98/aa/6ae7a05ae79f1f129626ba57133c34ffb0c9c9687bacd5854b072acc6dff/exactly-0.8.9-py3-none-any.whl", 
            "digests": {
                "sha256": "12b5a72c0496972d84abda2d62cdb00119b241303080d5d56e2b2288743c730b", 
                "md5": "629e8304d21d8299951f8ae46a4d245b"
            }, 
            "sha256_digest": "12b5a72c0496972d84abda2d62cdb00119b241303080d5d56e2b2288743c730b", 
            "size": 629121
        }, 
        {
            "has_sig": false, 
            "upload_time": "2018-04-15T20:59:26", 
            "comment_text": "", 
            "python_version": "source", 
            "url": "https://pypi.python.org/packages/97/29/61c931cacda62e85af354c3670ee342cb5fdb00321d87ece679bdff2f9ea/exactly-0.8.9.tar.gz", 
            "md5_digest": "4f24944e8ea419b12f3ccd8e25828428", 
            "downloads": 0, 
            "filename": "exactly-0.8.9.tar.gz", 
            "packagetype": "sdist", 
            "path": "97/29/61c931cacda62e85af354c3670ee342cb5fdb00321d87ece679bdff2f9ea/exactly-0.8.9.tar.gz", 
            "digests": {
                "sha256": "4aa6b2ff3eb0b36e836885cf71e267e328c2267abe2600d6604b8dbe74fd24c2", 
                "md5": "4f24944e8ea419b12f3ccd8e25828428"
            }, 
            "sha256_digest": "4aa6b2ff3eb0b36e836885cf71e267e328c2267abe2600d6604b8dbe74fd24c2", 
            "size": 399788
        }
    ], 
    "_id": null, 
    "cheesecake_installability_id": null
}