[
  {
    "path": ".coveragerc",
    "content": "[run]\nomit =\n    */cram/__main__.py\nsource = cram\n"
  },
  {
    "path": ".gitignore",
    "content": "*.orig\n*.rej\n*~\n*.mergebackup\n*.o\n*.so\n*.dll\n*.py[cdo]\n*$py.class\n__pycache__\n*.swp\n*.prof\n\\#*\\#\n.\\#*\n.coverage\n*,cover\nhtmlcov\ntests/*.err\nexamples/*.err\nbuild\ndist\nMANIFEST\ncram.egg-info\n.DS_Store\ntags\ncscope.*\n.idea\n"
  },
  {
    "path": ".hgignore",
    "content": "syntax: glob\n\n*.orig\n*.rej\n*~\n*.mergebackup\n*.o\n*.so\n*.dll\n*.py[cdo]\n*$py.class\n__pycache__\n*.swp\n*.prof\n\\#*\\#\n.\\#*\n.coverage\n*,cover\nhtmlcov\ntests/*.err\nexamples/*.err\nbuild\ndist\nMANIFEST\ncram.egg-info\n.DS_Store\ntags\ncscope.*\n.idea\n\nsyntax: regexp\n^\\.pc/\n^\\.(pydev)?project\n"
  },
  {
    "path": ".hgtags",
    "content": "931859fdd3e0d5af442a3e9b5fe6ac0dbfed2309 0.1\n3c471f7a16b435095b98525e7b851b17e871a2ce 0.2\n3c471f7a16b435095b98525e7b851b17e871a2ce 0.2\n995a287114b0a2a0bcd79b9c5ce8ff98765e7c8a 0.2\n924d14e0636a7ff5815c2412409115a69dfc63f0 0.3\n3ba61fadf306c63ec4bc3254522f286a27ac974a 0.4\n112e96e43892344954a98b0f05a32819f2b6c20d 0.5\n05669fd0420dc0cd52f48bc2f2379a61732d14e0 0.6\ne230eb00d4668508766fc32da154ba46c358ff5f 0.7\n"
  },
  {
    "path": ".pylintrc",
    "content": "[MESSAGES CONTROL]\n# C0330: bad continuation\n# The design check gives mostly useless advice.\n# R0201: method could be a function\n# W0123: eval used\n# W0141: used range\n# W0142: * or ** arguments\n# W0201: attribute defined outside of __init__\n# W0640: unreliable closure/loop variable checking\ndisable=C0330,design,R0201,W0123,W0141,W0142,W0201,W0640\n\n[REPORTS]\nreports=no\n\n[TYPECHECK]\nignored-classes=\ngenerated-members=\n\n[BASIC]\nconst-rgx=(([a-zA-Z_][a-zA-Z0-9_]{2,30})|(__[a-z0-9_]{2,30}__))$\nclass-rgx=[a-zA-Z_][a-zA-Z0-9]{2,30}$\nfunction-rgx=[a-z_][a-z0-9_]{2,30}$\nmethod-rgx=[a-z_][a-z0-9_]{2,30}$\nattr-rgx=[a-z_][a-z0-9_]{0,30}$\nargument-rgx=[a-z_][a-z0-9_]{0,30}$\nvariable-rgx=[a-z_][a-z0-9_]{0,30}$\ninlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$\n\n[CLASSES]\nignore-iface-methods=\ndefining-attr-methods=__init__,__new__\n\n[IMPORTS]\ndeprecated-modules=regsub,TERMIOS,Bastion,rexec\n\n[FORMAT]\nmax-line-length=79\nmax-module-lines=5000\n\n[MISCELLANEOUS]\nnotes=FIXME,XXX,TODO\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: python\n\nmatrix:\n  allow_failures:\n    - python: nightly\n      env: TESTOPTS=--shell=dash\n    - python: pypy\n      env: TESTOPTS=--shell=dash\n    - python: pypy3\n      env: TESTOPTS=--shell=dash\n  include:\n    - python: \"3.5\"\n      env: TESTOPTS=--shell=dash\n    - python: \"3.5\"\n      env: TESTOPTS=--shell=bash\n    - python: \"3.5\"\n      env: TESTOPTS=--shell=zsh\n      addons:\n        apt:\n          packages:\n            - zsh\n    - python: \"3.4\"\n      env: TESTOPTS=--shell=dash\n    - python: \"3.3\"\n      env: TESTOPTS=--shell=dash\n    - python: \"3.2\"\n      env: TESTOPTS=--shell=dash\n    - python: \"2.7\"\n      env: TESTOPTS=--shell=dash\n    - python: \"2.6\"\n      env: TESTOPTS=--shell=dash\n    - env: PYTHON=2.5 TESTOPTS=--shell=dash\n      addons:\n        apt:\n          sources:\n            - deadsnakes\n          packages:\n            - python2.5\n    - env: PYTHON=2.4 TESTOPTS=--shell=dash\n      addons:\n        apt:\n          sources:\n            - deadsnakes\n          packages:\n            - python2.4\n    - python: nightly\n      env: TESTOPTS=--shell=dash\n    - python: pypy\n      env: TESTOPTS=--shell=dash\n    - python: pypy3\n      env: TESTOPTS=--shell=dash\n  fast_finish: true\n\ninstall: |\n  if [ -z \"$PYTHON\" ]\n  then\n    [ \"$TRAVIS_PYTHON_VERSION\" = \"3.2\" ] && pip install coverage==3.7.1\n    pip install -r requirements.txt\n  fi\n\nscript: |\n  if [ -z \"$PYTHON\" ]\n  then\n    make test TESTOPTS=\"$TESTOPTS\"\n  else\n    make quicktest PYTHON=\"python$PYTHON\"\n  fi\n"
  },
  {
    "path": "COPYING.txt",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Free Software Foundation, Inc.,\n 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The licenses for most software are designed to take away your\nfreedom to share and change it.  By contrast, the GNU General Public\nLicense is intended to guarantee your freedom to share and change free\nsoftware--to make sure the software is free for all its users.  This\nGeneral Public License applies to most of the Free Software\nFoundation's software and to any other program whose authors commit to\nusing it.  (Some other Free Software Foundation software is covered by\nthe GNU Lesser General Public License instead.)  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthis service if you wish), that you receive source code or can get it\nif you want it, that you can change the software or use pieces of it\nin new free programs; and that you know you can do these things.\n\n  To protect your rights, we need to make restrictions that forbid\nanyone to deny you these rights or to ask you to surrender the rights.\nThese restrictions translate to certain responsibilities for you if you\ndistribute copies of the software, or if you modify it.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must give the recipients all the rights that\nyou have.  You must make sure that they, too, receive or can get the\nsource code.  And you must show them these terms so they know their\nrights.\n\n  We protect your rights with two steps: (1) copyright the software, and\n(2) offer you this license which gives you legal permission to copy,\ndistribute and/or modify the software.\n\n  Also, for each author's protection and ours, we want to make certain\nthat everyone understands that there is no warranty for this free\nsoftware.  If the software is modified by someone else and passed on, we\nwant its recipients to know that what they have is not the original, so\nthat any problems introduced by others will not reflect on the original\nauthors' reputations.\n\n  Finally, any free program is threatened constantly by software\npatents.  We wish to avoid the danger that redistributors of a free\nprogram will individually obtain patent licenses, in effect making the\nprogram proprietary.  To prevent this, we have made it clear that any\npatent must be licensed for everyone's free use or not licensed at all.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                    GNU GENERAL PUBLIC LICENSE\n   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\n  0. This License applies to any program or other work which contains\na notice placed by the copyright holder saying it may be distributed\nunder the terms of this General Public License.  The \"Program\", below,\nrefers to any such program or work, and a \"work based on the Program\"\nmeans either the Program or any derivative work under copyright law:\nthat is to say, a work containing the Program or a portion of it,\neither verbatim or with modifications and/or translated into another\nlanguage.  (Hereinafter, translation is included without limitation in\nthe term \"modification\".)  Each licensee is addressed as \"you\".\n\nActivities other than copying, distribution and modification are not\ncovered by this License; they are outside its scope.  The act of\nrunning the Program is not restricted, and the output from the Program\nis covered only if its contents constitute a work based on the\nProgram (independent of having been made by running the Program).\nWhether that is true depends on what the Program does.\n\n  1. You may copy and distribute verbatim copies of the Program's\nsource code as you receive it, in any medium, provided that you\nconspicuously and appropriately publish on each copy an appropriate\ncopyright notice and disclaimer of warranty; keep intact all the\nnotices that refer to this License and to the absence of any warranty;\nand give any other recipients of the Program a copy of this License\nalong with the Program.\n\nYou may charge a fee for the physical act of transferring a copy, and\nyou may at your option offer warranty protection in exchange for a fee.\n\n  2. You may modify your copy or copies of the Program or any portion\nof it, thus forming a work based on the Program, and copy and\ndistribute such modifications or work under the terms of Section 1\nabove, provided that you also meet all of these conditions:\n\n    a) You must cause the modified files to carry prominent notices\n    stating that you changed the files and the date of any change.\n\n    b) You must cause any work that you distribute or publish, that in\n    whole or in part contains or is derived from the Program or any\n    part thereof, to be licensed as a whole at no charge to all third\n    parties under the terms of this License.\n\n    c) If the modified program normally reads commands interactively\n    when run, you must cause it, when started running for such\n    interactive use in the most ordinary way, to print or display an\n    announcement including an appropriate copyright notice and a\n    notice that there is no warranty (or else, saying that you provide\n    a warranty) and that users may redistribute the program under\n    these conditions, and telling the user how to view a copy of this\n    License.  (Exception: if the Program itself is interactive but\n    does not normally print such an announcement, your work based on\n    the Program is not required to print an announcement.)\n\nThese requirements apply to the modified work as a whole.  If\nidentifiable sections of that work are not derived from the Program,\nand can be reasonably considered independent and separate works in\nthemselves, then this License, and its terms, do not apply to those\nsections when you distribute them as separate works.  But when you\ndistribute the same sections as part of a whole which is a work based\non the Program, the distribution of the whole must be on the terms of\nthis License, whose permissions for other licensees extend to the\nentire whole, and thus to each and every part regardless of who wrote it.\n\nThus, it is not the intent of this section to claim rights or contest\nyour rights to work written entirely by you; rather, the intent is to\nexercise the right to control the distribution of derivative or\ncollective works based on the Program.\n\nIn addition, mere aggregation of another work not based on the Program\nwith the Program (or with a work based on the Program) on a volume of\na storage or distribution medium does not bring the other work under\nthe scope of this License.\n\n  3. You may copy and distribute the Program (or a work based on it,\nunder Section 2) in object code or executable form under the terms of\nSections 1 and 2 above provided that you also do one of the following:\n\n    a) Accompany it with the complete corresponding machine-readable\n    source code, which must be distributed under the terms of Sections\n    1 and 2 above on a medium customarily used for software interchange; or,\n\n    b) Accompany it with a written offer, valid for at least three\n    years, to give any third party, for a charge no more than your\n    cost of physically performing source distribution, a complete\n    machine-readable copy of the corresponding source code, to be\n    distributed under the terms of Sections 1 and 2 above on a medium\n    customarily used for software interchange; or,\n\n    c) Accompany it with the information you received as to the offer\n    to distribute corresponding source code.  (This alternative is\n    allowed only for noncommercial distribution and only if you\n    received the program in object code or executable form with such\n    an offer, in accord with Subsection b above.)\n\nThe source code for a work means the preferred form of the work for\nmaking modifications to it.  For an executable work, complete source\ncode means all the source code for all modules it contains, plus any\nassociated interface definition files, plus the scripts used to\ncontrol compilation and installation of the executable.  However, as a\nspecial exception, the source code distributed need not include\nanything that is normally distributed (in either source or binary\nform) with the major components (compiler, kernel, and so on) of the\noperating system on which the executable runs, unless that component\nitself accompanies the executable.\n\nIf distribution of executable or object code is made by offering\naccess to copy from a designated place, then offering equivalent\naccess to copy the source code from the same place counts as\ndistribution of the source code, even though third parties are not\ncompelled to copy the source along with the object code.\n\n  4. You may not copy, modify, sublicense, or distribute the Program\nexcept as expressly provided under this License.  Any attempt\notherwise to copy, modify, sublicense or distribute the Program is\nvoid, and will automatically terminate your rights under this License.\nHowever, parties who have received copies, or rights, from you under\nthis License will not have their licenses terminated so long as such\nparties remain in full compliance.\n\n  5. You are not required to accept this License, since you have not\nsigned it.  However, nothing else grants you permission to modify or\ndistribute the Program or its derivative works.  These actions are\nprohibited by law if you do not accept this License.  Therefore, by\nmodifying or distributing the Program (or any work based on the\nProgram), you indicate your acceptance of this License to do so, and\nall its terms and conditions for copying, distributing or modifying\nthe Program or works based on it.\n\n  6. Each time you redistribute the Program (or any work based on the\nProgram), the recipient automatically receives a license from the\noriginal licensor to copy, distribute or modify the Program subject to\nthese terms and conditions.  You may not impose any further\nrestrictions on the recipients' exercise of the rights granted herein.\nYou are not responsible for enforcing compliance by third parties to\nthis License.\n\n  7. If, as a consequence of a court judgment or allegation of patent\ninfringement or for any other reason (not limited to patent issues),\nconditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot\ndistribute so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you\nmay not distribute the Program at all.  For example, if a patent\nlicense would not permit royalty-free redistribution of the Program by\nall those who receive copies directly or indirectly through you, then\nthe only way you could satisfy both it and this License would be to\nrefrain entirely from distribution of the Program.\n\nIf any portion of this section is held invalid or unenforceable under\nany particular circumstance, the balance of the section is intended to\napply and the section as a whole is intended to apply in other\ncircumstances.\n\nIt is not the purpose of this section to induce you to infringe any\npatents or other property right claims or to contest validity of any\nsuch claims; this section has the sole purpose of protecting the\nintegrity of the free software distribution system, which is\nimplemented by public license practices.  Many people have made\ngenerous contributions to the wide range of software distributed\nthrough that system in reliance on consistent application of that\nsystem; it is up to the author/donor to decide if he or she is willing\nto distribute software through any other system and a licensee cannot\nimpose that choice.\n\nThis section is intended to make thoroughly clear what is believed to\nbe a consequence of the rest of this License.\n\n  8. If the distribution and/or use of the Program is restricted in\ncertain countries either by patents or by copyrighted interfaces, the\noriginal copyright holder who places the Program under this License\nmay add an explicit geographical distribution limitation excluding\nthose countries, so that distribution is permitted only in or among\ncountries not thus excluded.  In such case, this License incorporates\nthe limitation as if written in the body of this License.\n\n  9. The Free Software Foundation may publish revised and/or new versions\nof the General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\nEach version is given a distinguishing version number.  If the Program\nspecifies a version number of this License which applies to it and \"any\nlater version\", you have the option of following the terms and conditions\neither of that version or of any later version published by the Free\nSoftware Foundation.  If the Program does not specify a version number of\nthis License, you may choose any version ever published by the Free Software\nFoundation.\n\n  10. If you wish to incorporate parts of the Program into other free\nprograms whose distribution conditions are different, write to the author\nto ask for permission.  For software which is copyrighted by the Free\nSoftware Foundation, write to the Free Software Foundation; we sometimes\nmake exceptions for this.  Our decision will be guided by the two goals\nof preserving the free status of all derivatives of our free software and\nof promoting the sharing and reuse of software generally.\n\n                            NO WARRANTY\n\n  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY\nFOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN\nOTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES\nPROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED\nOR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS\nTO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE\nPROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,\nREPAIR OR CORRECTION.\n\n  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR\nREDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,\nINCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING\nOUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED\nTO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY\nYOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER\nPROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGES.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nconvey the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License along\n    with this program; if not, write to the Free Software Foundation, Inc.,\n    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n\nAlso add information on how to contact you by electronic and paper mail.\n\nIf the program is interactive, make it output a short notice like this\nwhen it starts in an interactive mode:\n\n    Gnomovision version 69, Copyright (C) year name of author\n    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, the commands you use may\nbe called something other than `show w' and `show c'; they could even be\nmouse-clicks or menu items--whatever suits your program.\n\nYou should also get your employer (if you work as a programmer) or your\nschool, if any, to sign a \"copyright disclaimer\" for the program, if\nnecessary.  Here is a sample; alter the names:\n\n  Yoyodyne, Inc., hereby disclaims all copyright interest in the program\n  `Gnomovision' (which makes passes at compilers) written by James Hacker.\n\n  <signature of Ty Coon>, 1 April 1989\n  Ty Coon, President of Vice\n\nThis General Public License does not permit incorporating your program into\nproprietary programs.  If your program is a subroutine library, you may\nconsider it more useful to permit linking proprietary applications with the\nlibrary.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include .coveragerc .pylintrc .travis.yml Makefile MANIFEST.in\ninclude *.md *.rst *.txt contrib/* scripts/*\nexclude contrib/PKGBUILD\nrecursive-include examples *.t\nrecursive-include tests *.py *.sh *.t\n"
  },
  {
    "path": "Makefile",
    "content": "COVERAGE=coverage\nPREFIX=/usr/local\nexport PREFIX\nPYTHON=python3\n\nall: build\n\nbuild:\n\t$(PYTHON) setup.py build\n\ncheck: test\n\nclean:\n\t-$(PYTHON) setup.py clean --all\n\tfind . -not \\( -path '*/.hg/*' -o -path '*/.git/*' \\) \\\n\t\t\\( -name '*.py[cdo]' -o -name '*.err' -o \\\n\t\t-name '*,cover' -o -name __pycache__ \\) -prune \\\n\t\t-exec rm -rf '{}' ';'\n\trm -rf dist build htmlcov\n\trm -f MANIFEST .coverage cram.xml\n\ndist:\n\tTAR_OPTIONS=\"--owner=root --group=root --mode=u+w,go-w,a+rX-s\" \\\n\t\t$(PYTHON) setup.py -q sdist\n\ninstall: build\n\t$(PYTHON) setup.py install --prefix=\"$(PREFIX)\" --force\n\nquicktest:\n\tPYTHON=$(PYTHON) PYTHONPATH=`pwd` scripts/cram $(TESTOPTS) tests\n\ntest:\n\t$(COVERAGE) erase\n\tCOVERAGE=$(COVERAGE) PYTHON=$(PYTHON) PYTHONPATH=`pwd` scripts/cram \\\n\t\t$(TESTOPTS) tests\n\t$(COVERAGE) report --fail-under=100\n\n.PHONY: all build check clean install dist quicktest test\n"
  },
  {
    "path": "NEWS.rst",
    "content": "======\n News\n======\n\nVersion 0.7 (Feb. 24, 2016)\n---------------------------\n\n* Added the ``-d``/``--debug`` flag that disables diffing of\n  expected/actual output and instead passes through script output to\n  ``stdout``/``stderr``.\n\n* Added the ``--shell-opts`` flag for specifying flags to invoke the\n  shell with. By setting ``--shell-opts='-x'`` and ``--debug``\n  together, this can be used to see shell commands as they're run and\n  their output in real time which can be useful for debugging slow or\n  hanging tests.\n\n* Added xUnit XML output support (for better integration of test\n  results with Bamboo and other continuous integration tools).\n\n* Added support for using (esc) on expected out lines that aren't\n  automatically escaped in actual output.\n\n* Added the ``$TESTSHELL`` environment variable. This allows a test to\n  portably check what shell it was invoked with.\n\n* Added an error message for when no tests are found in a directory.\n\n* Changed ``Makefile`` to install into ``/usr/local`` by default.\n\n* Simplified the ``Makefile``'s targets. The targets available now are\n  ``all``, ``build``, ``check``/``test``, ``clean``, ``dist``,\n  ``install``, and ``quicktest`` (for running the test suite without\n  checking test coverage).\n\n* Fixed non-ASCII strings not being escaped with ``(esc)`` on Python 3.\n\n* Fixed a crash on tests that don't have a trailing newline.\n\n* Fixed a crash when using ``set -x`` with zsh.\n\n\nVersion 0.6 (Aug. 1, 2013)\n--------------------------\n\n* Added the long option ``--preserve-env`` for ``-E``.\n\n* Added support for specifying options in ``.cramrc`` (configurable\n  with the ``CRAMRC`` environment variable).\n\n* Added a ``--shell`` option to change the shell tests are run\n  with. Contributed by `Kamil Kisiel`_.\n\n* Added Arch Linux package metadata (in ``contrib/``). Contributed by\n  `Andrey Vlasovskikh`_.\n\n* Fixed shell commands unintentionally inheriting Python's ``SIGPIPE``\n  handler (causing commands that close pipes to print ``broken pipe``\n  messages).\n\n* Fixed ``EPIPE`` under PyPy when applying patches in\n  ``--interactive`` mode.\n\n* Added ``TESTFILE`` test environment variable (set to the name of the\n  current test).\n\n* Fixed GNU patch 2.7 compatibility by using relative paths instead of\n  absolute paths. Contributed by `Douglas Creager`_.\n\n* Fixed name clashes in temporary test directories (e.g., when running\n  two tests with the same name in different folders).\n\n* **Backwards compatibility:** Fixed improper usage of the subprocess\n  library under Python 3. This fixes Python 3.3 support, but breaks\n  support for Python 3.1-3.2.3 due to a bug in Python. If you're using\n  Python 3.0-3.2, you must upgrade to Python 3.2.4 or newer.\n\n.. _Kamil Kisiel: http://kamilkisiel.net/\n.. _Andrey Vlasovskikh: https://twitter.com/vlasovskikh\n.. _Douglas Creager: http://dcreager.net/\n\n\nVersion 0.5 (Jan. 8, 2011)\n--------------------------\n\n* **The test format has changed:** Matching output not ending in a\n  newline now requires the ``(no-eol)`` keyword instead of ending the\n  line in ``%``.\n\n* Matching output containing unprintable characters now requires the\n  ``(esc)`` keyword. Real output containing unprintable characters\n  will automatically receive ``(esc)``.\n\n* If an expected line matches its real output line exactly, special\n  matching like ``(re)`` or ``(glob)`` will be ignored.\n\n* Regular expressions ending in a trailing backslash are now\n  considered invalid.\n\n* Added an ``--indent`` option for changing the default amount of\n  indentation required to specify commands and output.\n\n* Added support for specifying command line options in the ``CRAM``\n  environment variable.\n\n* The ``--quiet`` and ``--verbose`` options can now be used together.\n\n* When running Cram under Python 3, Unicode-specific line break\n  characters will no longer be parsed as newlines.\n\n* Tests are no longer required to end in a trailing newline.\n\n\nVersion 0.4 (Sep. 28, 2010)\n---------------------------\n\n* **The test format has changed:** Output lines containing regular\n  expressions must now end in ``(re)`` or they'll be matched\n  literally. Lines ending with keywords are matched literally first,\n  however.\n\n* Regular expressions are now matched from beginning to end. In other\n  words ``\\d (re)`` is matched as ``^\\d$``.\n\n* In addition to ``(re)``, ``(glob)`` has been added. It supports\n  ``*``, ``?``, and escaping both characters (and backslashes) using\n  ``\\``.\n\n* **Environment settings have changed:** The ``-D`` flag has been\n  removed, ``$TESTDIR`` is now set to the directory containing the\n  ``.t`` file, and ``$CRAMTMP`` is set to the test runner's temporary\n  directory.\n\n* ``-i``/``--interactive`` now requires ``patch(1)``. Instead of\n  ``.err`` files replacing ``.t`` files during merges, diffs are\n  applied using ``patch(1)``. This prevents matching regular\n  expressions and globs from getting clobbered.\n\n* Previous ``.err`` files are now removed when tests pass.\n\n* Cram now exits with return code ``1`` if any tests failed.\n\n* If a test exits with return code ``80``, it's considered a skipped a\n  test. This is useful for intentionally disabling tests when they\n  only work on certain platforms or in certain settings.\n\n* The number of tests, the number of skipped tests, and the number of\n  failed tests are now printed after all tests are finished.\n\n* Added ``-q``/``--quiet`` to suppress diff output.\n\n* Added `contrib/cram.vim`_ syntax file for Vim. Contributed by `Steve\n  Losh`_.\n\n.. _contrib/cram.vim: https://github.com/brodie/cram/blob/0.7/contrib/cram.vim\n.. _Steve Losh: http://stevelosh.com/\n\n\nVersion 0.3 (Sep. 20, 2010)\n---------------------------\n\n* Implemented resetting of common environment variables. This behavior\n  can be disabled using the ``-E`` flag.\n\n* Changed the test runner to first make its own overall random\n  temporary directory, make ``tmp`` inside of it and set ``TMPDIR``,\n  etc. to its path, and run each test with a random temporary working\n  directory inside of that.\n\n* Added ``--keep-tmpdir``. Temporary directories are named by test\n  filename (along with a random string).\n\n* Added ``-i``/``--interactive`` to merge actual output back to into\n  tests interactively.\n\n* Added ability to match command output not ending in a newline by\n  suffixing output in the test with ``%``.\n\n\nVersion 0.2 (Sep. 19, 2010)\n---------------------------\n\n* Changed the test runner to run tests with a random temporary working\n  directory.\n\n\nVersion 0.1 (Sep. 19, 2010)\n---------------------------\n\n* Initial release.\n"
  },
  {
    "path": "README.rst",
    "content": "======================\n Cram: It's test time\n======================\n\nCram is a functional testing framework for command line applications.\nCram tests look like snippets of interactive shell sessions. Cram runs\neach command and compares the command output in the test with the\ncommand's actual output.\n\nHere's a snippet from `Cram's own test suite`_::\n\n    Set up cram alias and example tests:\n\n      $ . \"$TESTDIR\"/setup.sh\n\n    Usage:\n\n      $ cram -h\n      [Uu]sage: cram \\[OPTIONS\\] TESTS\\.\\.\\. (re)\n\n      [Oo]ptions: (re)\n        -h, --help          show this help message and exit\n        -V, --version       show version information and exit\n        -q, --quiet         don't print diffs\n        -v, --verbose       show filenames and test status\n        -i, --interactive   interactively merge changed test output\n        -d, --debug         write script output directly to the terminal\n        -y, --yes           answer yes to all questions\n        -n, --no            answer no to all questions\n        -E, --preserve-env  don't reset common environment variables\n        --keep-tmpdir       keep temporary directories\n        --shell=PATH        shell to use for running tests (default: /bin/sh)\n        --shell-opts=OPTS   arguments to invoke shell with\n        --indent=NUM        number of spaces to use for indentation (default: 2)\n        --xunit-file=PATH   path to write xUnit XML output\n\nThe format in a nutshell:\n\n* Cram tests use the ``.t`` file extension.\n\n* Lines beginning with two spaces, a dollar sign, and a space are run\n  in the shell.\n\n* Lines beginning with two spaces, a greater than sign, and a space\n  allow multi-line commands.\n\n* All other lines beginning with two spaces are considered command\n  output.\n\n* Output lines ending with a space and the keyword ``(re)`` are\n  matched as `Perl-compatible regular expressions`_.\n\n* Lines ending with a space and the keyword ``(glob)`` are matched\n  with a glob-like syntax. The only special characters supported are\n  ``*`` and ``?``. Both characters can be escaped using ``\\``, and the\n  backslash can be escaped itself.\n\n* Output lines ending with either of the above keywords are always\n  first matched literally with actual command output.\n\n* Lines ending with a space and the keyword ``(no-eol)`` will match\n  actual output that doesn't end in a newline.\n\n* Actual output lines containing unprintable characters are escaped\n  and suffixed with a space and the keyword ``(esc)``. Lines matching\n  unprintable output must also contain the keyword.\n\n* Anything else is a comment.\n\n.. _Cram's own test suite: https://github.com/brodie/cram/blob/master/tests/usage.t\n.. _Perl-compatible regular expressions: https://en.wikipedia.org/wiki/Perl_Compatible_Regular_Expressions\n\n\nDownload\n--------\n\n* `cram-0.8.tar.gz`_ (32 KB, requires Python 3.3 or newer)\n\n.. _cram-0.8.tar.gz: https://bitheap.org/cram/cram-0.8.tar.gz\n\n\nInstallation\n------------\n\nInstall Cram using make::\n\n    $ wget https://bitheap.org/cram/cram-0.8.tar.gz\n    $ tar zxvf cram-0.8.tar.gz\n    $ cd cram-0.8\n    $ make install\n\n\nUsage\n-----\n\nCram will print a dot for each passing test. If a test fails, a\n`unified context diff`_ is printed showing the test's expected output\nand the actual output. Skipped tests (empty tests and tests that exit\nwith return code ``80``) are marked with ``s`` instead of a dot.\n\nFor example, if we run Cram on `its own example tests`_::\n\n    .s.!\n    --- examples/fail.t\n    +++ examples/fail.t.err\n    @@ -3,21 +3,22 @@\n       $ echo 1\n       1\n       $ echo 1\n    -  2\n    +  1\n       $ echo 1\n       1\n\n     Invalid regex:\n\n       $ echo 1\n    -  +++ (re)\n    +  1\n\n     Offset regular expression:\n\n       $ printf 'foo\\nbar\\nbaz\\n\\n1\\nA\\n@\\n'\n       foo\n    +  bar\n       baz\n\n       \\d (re)\n       [A-Z] (re)\n    -  #\n    +  @\n    s.\n    # Ran 6 tests, 2 skipped, 1 failed.\n\nCram will also write the test with its actual output to\n``examples/fail.t.err``, allowing you to use other diff tools. This\nfile is automatically removed the next time the test passes.\n\nWhen you're first writing a test, you might just write the commands\nand run the test to see what happens. If you run Cram with ``-i`` or\n``--interactive``, you'll be prompted to merge the actual output back\ninto the test. This makes it easy to quickly prototype new tests.\n\nYou can specify a default set of options by creating a ``.cramrc``\nfile. For example::\n\n    [cram]\n    verbose = True\n    indent = 4\n\nIs the same as invoking Cram with ``--verbose`` and ``--indent=4``.\n\nTo change what configuration file Cram loads, you can set the\n``CRAMRC`` environment variable. You can also specify command line\noptions in the ``CRAM`` environment variable.\n\nNote that the following environment variables are reset before tests\nare run:\n\n* ``TMPDIR``, ``TEMP``, and ``TMP`` are set to the test runner's\n  ``tmp`` directory.\n\n* ``LANG``, ``LC_ALL``, and ``LANGUAGE`` are set to ``C``.\n\n* ``TZ`` is set to ``GMT``.\n\n* ``COLUMNS`` is set to ``80``. (Note: When using ``--shell=zsh``,\n  this cannot be reset. It will reflect the actual terminal's width.)\n\n* ``CDPATH`` and ``GREP_OPTIONS`` are set to an empty string.\n\nCram also provides the following environment variables to tests:\n\n* ``CRAMTMP``, set to the test runner's temporary directory.\n\n* ``TESTDIR``, set to the directory containing the test file.\n\n* ``TESTFILE``, set to the basename of the current test file.\n\n* ``TESTSHELL``, set to the value specified by ``--shell``.\n\nAlso note that care should be taken with commands that close the test\nshell's ``stdin``. For example, if you're trying to invoke ``ssh`` in\na test, try adding the ``-n`` option to prevent it from closing\n``stdin``. Similarly, if you invoke a daemon process that inherits\n``stdout`` and fails to close it, it may cause Cram to hang while\nwaiting for the test shell's ``stdout`` to be fully closed.\n\n.. _unified context diff: https://en.wikipedia.org/wiki/Diff#Unified_format\n.. _its own example tests: https://github.com/brodie/cram/tree/master/examples\n\n\nDevelopment\n-----------\n\nDownload the official development repository using Git_::\n\n    git clone https://github.com/brodie/cram.git\n\nTest Cram using Cram::\n\n    pip install -r requirements.txt\n    make test\n\nVisit GitHub_ if you'd like to fork the project, watch for new changes, or\nreport issues.\n\n.. _Git: http://git-scm.com/\n.. _GitHub: https://github.com/brodie/cram\n"
  },
  {
    "path": "TODO.md",
    "content": "* Add more comments explaining how different parts of the code work.\n\n* Add a man page.\n\n* Implement string substitutions (e.g., --substitute=FOOPORT=123).\n\n* Conditionals (e.g., --define=windows=1, #if windows ... #else ...\n  #endif).\n\n* Support #!/usr/bin/env cram\n\n* Support .cramrc in test directories. Though, if I do this, what happens\n  when there are multiple .cramrc files? Does the deepest one completely\n  override the others? Do they merge together?\n\n* Homebrew formula.\n\n* Debian, Ubuntu, CentOS/RHEL repos.\n\n* Implement a test that does stricter style guide testing.\n\n* Write contributor guidelines.\n\n* Get the test suite running on AppVeyor under MSYS2.\n\n  - http://help.appveyor.com/discussions/suggestions/615-support-for-msys2\n  - https://github.com/behdad/harfbuzz/pull/112/files\n  - https://github.com/khaledhosny/ots/pull/67/files\n  - https://github.com/appveyor/ci/issues/352#issuecomment-138149606\n  - https://github.com/appveyor/ci/issues/597\n  - http://www.appveyor.com\n\n* Get the test suite fully passing with Python.org's Windows\n  distribution.\n\n* Global setup/teardown support.\n\n* Local setup/teardown? This is technically already supported via\n  sourcing scripts and using exit traps, but dedicated syntax might be\n  nice (e.g., #setup ... #endsetup? or maybe just #teardown ...\n  #endteardown or #finally ... #endfinally?).\n\n* Implement -j flag for concurrency.\n\n* Flexible indentation support (with an algorithm similar to Python's\n  for detecting indentation on a per-block basis).\n\n* Some sort of plugin system (one that doesn't require writing plugins\n  in Python) that allows basic extension of Cram's functionality (and\n  possibly even syntax, though perhaps limited to just \"macros\" like\n  #foo, #bar, etc. and matchers like (baz), (quux), etc.).\n\n* Be able to run the Mercurial test suite.\n\n* Write cram plugins for other testing frameworks (nose, py.test,\n  etc.).\n\n* Somehow make it possible to specify tests in Python doc\n  strings (and similar things in other languages like Perl, Ruby,\n  etc.).\n\n* Emacs mode.\n"
  },
  {
    "path": "contrib/PKGBUILD",
    "content": "# Maintainer: Andrey Vlasovskikh <andrey.vlasovskikh@gmail.com>\n\npkgname=cram\npkgver=0.7\npkgrel=1\npkgdesc=\"Functional tests for command line applications\"\narch=(any)\nurl=\"https://bitheap.org/cram/\"\nlicense=('GPL')\ndepends=('python')\nsource=(\"https://pypi.python.org/packages/source/c/cram/cram-$pkgver.tar.gz\")\nmd5sums=('2ea37ada5190526b9bcaac5e4099221c')\n\nbuild() {\n    cd \"$srcdir/$pkgname-$pkgver\"\n    python setup.py install --root=\"$pkgdir/\" --optimize=1\n}\n"
  },
  {
    "path": "contrib/cram.vim",
    "content": "\" Vim syntax file\n\" Language: Cram Tests\n\" Author: Steve Losh (steve@stevelosh.com)\n\"\n\" Add the following line to your ~/.vimrc to enable:\n\" au BufNewFile,BufRead *.t set filetype=cram\n\"\n\" If you want folding you'll need the following line as well:\n\" let cram_fold=1\n\"\n\" You might also want to set the starting foldlevel for Cram files:\n\" autocmd Syntax cram setlocal foldlevel=1\n\nif exists(\"b:current_syntax\")\n  finish\nendif\n\nsyn include @Shell syntax/sh.vim\n\nsyn match cramComment /^[^ ].*$/ contains=@Spell\nsyn region cramOutput start=/^  [^$>]/ start=/^  $/ end=/\\v.(\\n\\n*[^ ])\\@=/me=s end=/^  [$>]/me=e-3 end=/^$/ fold containedin=cramBlock\nsyn match cramCommandStart /^  \\$ / containedin=cramCommand\nsyn region cramCommand start=/^  \\$ /hs=s+4,rs=s+4 end=/^  [^>]/me=e-3 end=/^  $/me=e-2 containedin=cramBlock contains=@Shell keepend\nsyn region cramBlock start=/^  /ms=e-2 end=/\\v.(\\n\\n*[^ ])\\@=/me=s end=/^$/me=e-1 fold keepend\n\nhi link cramCommandStart Keyword\nhi link cramComment Normal\nhi link cramOutput Comment\n\nif exists(\"cram_fold\")\n  setlocal foldmethod=syntax\nendif\n\nsyn sync match cramSync grouphere NONE \"^$\"\nsyn sync maxlines=200\n\n\" It's okay to set tab settings here, because an indent of two spaces is specified\n\" by the file format.\nsetlocal tabstop=2 softtabstop=2 shiftwidth=2 expandtab\n\nlet b:current_syntax = \"cram\"\n"
  },
  {
    "path": "cram/__init__.py",
    "content": "\"\"\"Functional testing framework for command line applications\"\"\"\n\nfrom cram._main import main\nfrom cram._test import test, testfile\n\n__all__ = ['main', 'test', 'testfile']\n"
  },
  {
    "path": "cram/__main__.py",
    "content": "\"\"\"Main module (invoked by \"python3 -m cram\")\"\"\"\n\nimport sys\n\nimport cram\n\ntry:\n    sys.exit(cram.main(sys.argv[1:]))\nexcept (BrokenPipeError, KeyboardInterrupt):\n    pass\n"
  },
  {
    "path": "cram/_cli.py",
    "content": "\"\"\"The command line interface implementation\"\"\"\n\nimport os\nimport sys\n\nfrom cram._process import execute\n\n__all__ = ['runcli']\n\ndef _prompt(question, answers, auto=None):\n    \"\"\"Write a prompt to stdout and ask for answer in stdin.\n\n    answers should be a string, with each character a single\n    answer. An uppercase letter is considered the default answer.\n\n    If an invalid answer is given, this asks again until it gets a\n    valid one.\n\n    If auto is set, the question is answered automatically with the\n    specified value.\n    \"\"\"\n    default = [c for c in answers if c.isupper()]\n    while True:\n        sys.stdout.write('%s [%s] ' % (question, answers))\n        sys.stdout.flush()\n        if auto is not None:\n            sys.stdout.write(auto + '\\n')\n            sys.stdout.flush()\n            return auto\n\n        answer = sys.stdin.readline().strip().lower()\n        if not answer and default:\n            return default[0]\n        elif answer and answer in answers.lower():\n            return answer\n\ndef _log(msg=None, verbosemsg=None, verbose=False):\n    \"\"\"Write msg to standard out and flush.\n\n    If verbose is True, write verbosemsg instead.\n    \"\"\"\n    if verbose:\n        msg = verbosemsg\n    if msg:\n        if isinstance(msg, bytes):\n            sys.stdout.buffer.write(msg)\n        else: # pragma: nocover\n            sys.stdout.write(msg)\n        sys.stdout.flush()\n\ndef _patch(cmd, diff):\n    \"\"\"Run echo [lines from diff] | cmd -p0\"\"\"\n    out, retcode = execute([cmd, '-p0'], stdin=b''.join(diff))\n    return retcode == 0\n\ndef runcli(tests, quiet=False, verbose=False, patchcmd=None, answer=None):\n    \"\"\"Run tests with command line interface input/output.\n\n    tests should be a sequence of 2-tuples containing the following:\n\n        (test path, test function)\n\n    This function yields a new sequence where each test function is wrapped\n    with a function that handles CLI input/output.\n\n    If quiet is True, diffs aren't printed. If verbose is True,\n    filenames and status information are printed.\n\n    If patchcmd is set, a prompt is written to stdout asking if\n    changed output should be merged back into the original test. The\n    answer is read from stdin. If 'y', the test is patched using patch\n    based on the changed output.\n    \"\"\"\n    total, skipped, failed = [0], [0], [0]\n\n    for path, test in tests:\n        def testwrapper():\n            \"\"\"Test function that adds CLI output\"\"\"\n            total[0] += 1\n            _log(None, path + b': ', verbose)\n\n            refout, postout, diff = test()\n            if refout is None:\n                skipped[0] += 1\n                _log('s', 'empty\\n', verbose)\n                return refout, postout, diff\n\n            abspath = os.path.abspath(path)\n            errpath = abspath + b'.err'\n\n            if postout is None:\n                skipped[0] += 1\n                _log('s', 'skipped\\n', verbose)\n            elif not diff:\n                _log('.', 'passed\\n', verbose)\n                if os.path.exists(errpath):\n                    os.remove(errpath)\n            else:\n                failed[0] += 1\n                _log('!', 'failed\\n', verbose)\n                if not quiet:\n                    _log('\\n', None, verbose)\n\n                errfile = open(errpath, 'wb')\n                try:\n                    for line in postout:\n                        errfile.write(line)\n                finally:\n                    errfile.close()\n\n                if not quiet:\n                    origdiff = diff\n                    diff = []\n                    for line in origdiff:\n                        sys.stdout.buffer.write(line)\n                        diff.append(line)\n\n                    if (patchcmd and\n                        _prompt('Accept this change?', 'yN', answer) == 'y'):\n                        if _patch(patchcmd, diff):\n                            _log(None, path + b': merged output\\n', verbose)\n                            os.remove(errpath)\n                        else:\n                            _log(path + b': merge failed\\n')\n\n            return refout, postout, diff\n\n        yield (path, testwrapper)\n\n    if total[0] > 0:\n        _log('\\n', None, verbose)\n        _log('# Ran %s tests, %s skipped, %s failed.\\n'\n             % (total[0], skipped[0], failed[0]))\n"
  },
  {
    "path": "cram/_diff.py",
    "content": "\"\"\"Utilities for diffing test files and their output\"\"\"\n\nimport codecs\nimport difflib\nimport re\n\n__all__ = ['esc', 'glob', 'regex', 'unified_diff']\n\ndef _regex(pattern, s):\n    \"\"\"Match a regular expression or return False if invalid.\n\n    >>> [bool(_regex(r, b'foobar')) for r in (b'foo.*', b'***')]\n    [True, False]\n    \"\"\"\n    try:\n        return re.match(pattern + br'\\Z', s)\n    except re.error:\n        return False\n\ndef _glob(el, l):\n    r\"\"\"Match a glob-like pattern.\n\n    The only supported special characters are * and ?. Escaping is\n    supported.\n\n    >>> bool(_glob(br'\\* \\\\ \\? fo?b*', b'* \\\\ ? foobar'))\n    True\n    \"\"\"\n    i, n = 0, len(el)\n    res = b''\n    while i < n:\n        c = el[i:i + 1]\n        i += 1\n        if c == b'\\\\' and el[i] in b'*?\\\\':\n            res += el[i - 1:i + 1]\n            i += 1\n        elif c == b'*':\n            res += b'.*'\n        elif c == b'?':\n            res += b'.'\n        else:\n            res += re.escape(c)\n    return _regex(res, l)\n\ndef _matchannotation(keyword, matchfunc, el, l):\n    \"\"\"Apply match function based on annotation keyword\"\"\"\n    ann = b' (%s)\\n' % keyword\n    return el.endswith(ann) and matchfunc(el[:-len(ann)], l[:-1])\n\ndef regex(el, l):\n    \"\"\"Apply a regular expression match to a line annotated with '(re)'\"\"\"\n    return _matchannotation(b're', _regex, el, l)\n\ndef glob(el, l):\n    \"\"\"Apply a glob match to a line annotated with '(glob)'\"\"\"\n    return _matchannotation(b'glob', _glob, el, l)\n\ndef esc(el, l):\n    \"\"\"Apply an escape match to a line annotated with '(esc)'\"\"\"\n    ann = b' (esc)\\n'\n\n    if el.endswith(ann):\n        el = codecs.escape_decode(el[:-len(ann)])[0] + b'\\n'\n    if el == l:\n        return True\n\n    if l.endswith(ann):\n        l = codecs.escape_decode(l[:-len(ann)])[0] + b'\\n'\n    return el == l\n\nclass _SequenceMatcher(difflib.SequenceMatcher, object):\n    \"\"\"Like difflib.SequenceMatcher, but supports custom match functions\"\"\"\n    def __init__(self, *args, **kwargs):\n        self._matchers = kwargs.pop('matchers', [])\n        super(_SequenceMatcher, self).__init__(*args, **kwargs)\n\n    def _match(self, el, l):\n        \"\"\"Tests for matching lines using custom matchers\"\"\"\n        for matcher in self._matchers:\n            if matcher(el, l):\n                return True\n        return False\n\n    def find_longest_match(self, alo, ahi, blo, bhi):\n        \"\"\"Find longest matching block in a[alo:ahi] and b[blo:bhi]\"\"\"\n        # SequenceMatcher uses find_longest_match() to slowly whittle down\n        # the differences between a and b until it has each matching block.\n        # Because of this, we can end up doing the same matches many times.\n        matches = []\n        for n, (el, line) in enumerate(zip(self.a[alo:ahi], self.b[blo:bhi])):\n            if el != line and self._match(el, line):\n                # This fools the superclass's method into thinking that the\n                # regex/glob in a is identical to b by replacing a's line (the\n                # expected output) with b's line (the actual output).\n                self.a[alo + n] = line\n                matches.append((n, el))\n        ret = super(_SequenceMatcher, self).find_longest_match(alo, ahi,\n                                                               blo, bhi)\n        # Restore the lines replaced above. Otherwise, the diff output\n        # would seem to imply that the tests never had any regexes/globs.\n        for n, el in matches:\n            self.a[alo + n] = el\n        return ret\n\ndef unified_diff(l1, l2, fromfile=b'', tofile=b'', fromfiledate=b'',\n                 tofiledate=b'', n=3, lineterm=b'\\n', matchers=None):\n    r\"\"\"Compare two sequences of lines; generate the delta as a unified diff.\n\n    This is like difflib.unified_diff(), but allows custom matchers.\n\n    >>> l1 = [b'a\\n', b'? (glob)\\n']\n    >>> l2 = [b'a\\n', b'b\\n']\n    >>> (list(unified_diff(l1, l2, b'f1', b'f2', b'1970-01-01',\n    ...                    b'1970-01-02')) ==\n    ...  [b'--- f1\\t1970-01-01\\n', b'+++ f2\\t1970-01-02\\n',\n    ...   b'@@ -1,2 +1,2 @@\\n', b' a\\n', b'-? (glob)\\n', b'+b\\n'])\n    True\n\n    >>> from cram._diff import glob\n    >>> list(unified_diff(l1, l2, matchers=[glob]))\n    []\n    \"\"\"\n    if matchers is None:\n        matchers = []\n    started = False\n    matcher = _SequenceMatcher(None, l1, l2, matchers=matchers)\n    for group in matcher.get_grouped_opcodes(n):\n        if not started:\n            if fromfiledate:\n                fromdate = b'\\t' + fromfiledate\n            else:\n                fromdate = b''\n            if tofiledate:\n                todate = b'\\t' + tofiledate\n            else:\n                todate = b''\n            yield b'--- ' + fromfile + fromdate + lineterm\n            yield b'+++ ' + tofile + todate + lineterm\n            started = True\n        i1, i2, j1, j2 = group[0][1], group[-1][2], group[0][3], group[-1][4]\n        yield (b'@@ -%d,%d +%d,%d @@' % (i1 + 1, i2 - i1, j1 + 1, j2 - j1) +\n               lineterm)\n        for tag, i1, i2, j1, j2 in group:\n            if tag == 'equal':\n                for line in l1[i1:i2]:\n                    yield b' ' + line\n                continue\n            if tag == 'replace' or tag == 'delete':\n                for line in l1[i1:i2]:\n                    yield b'-' + line\n            if tag == 'replace' or tag == 'insert':\n                for line in l2[j1:j2]:\n                    yield b'+' + line\n"
  },
  {
    "path": "cram/_main.py",
    "content": "\"\"\"Main entry point\"\"\"\n\nimport optparse\nimport os\nimport shlex\nimport shutil\nimport sys\nimport tempfile\n\ntry:\n    import configparser\nexcept ImportError: # pragma: nocover\n    import ConfigParser as configparser\n\nfrom cram._cli import runcli\nfrom cram._run import runtests\nfrom cram._xunit import runxunit\n\ndef _which(cmd):\n    \"\"\"Return the path to cmd or None if not found\"\"\"\n    cmd = os.fsencode(cmd)\n    for p in os.environ['PATH'].split(os.pathsep):\n        path = os.path.join(os.fsencode(p), cmd)\n        if os.path.isfile(path) and os.access(path, os.X_OK):\n            return os.path.abspath(path)\n    return None\n\ndef _expandpath(path):\n    \"\"\"Expands ~ and environment variables in path\"\"\"\n    return os.path.expanduser(os.path.expandvars(path))\n\nclass _OptionParser(optparse.OptionParser):\n    \"\"\"Like optparse.OptionParser, but supports setting values through\n    CRAM= and .cramrc.\"\"\"\n\n    def __init__(self, *args, **kwargs):\n        self._config_opts = {}\n        optparse.OptionParser.__init__(self, *args, **kwargs)\n\n    def add_option(self, *args, **kwargs):\n        option = optparse.OptionParser.add_option(self, *args, **kwargs)\n        if option.dest and option.dest != 'version':\n            key = option.dest.replace('_', '-')\n            self._config_opts[key] = option.action == 'store_true'\n        return option\n\n    def parse_args(self, args=None, values=None):\n        config = configparser.RawConfigParser()\n        config.read(_expandpath(os.environ.get('CRAMRC', '.cramrc')))\n        defaults = {}\n        for key, isbool in self._config_opts.items():\n            try:\n                if isbool:\n                    try:\n                        value = config.getboolean('cram', key)\n                    except ValueError:\n                        value = config.get('cram', key)\n                        self.error('--%s: invalid boolean value: %r'\n                                   % (key, value))\n                else:\n                    value = config.get('cram', key)\n            except (configparser.NoSectionError, configparser.NoOptionError):\n                pass\n            else:\n                defaults[key] = value\n        self.set_defaults(**defaults)\n\n        eargs = os.environ.get('CRAM', '').strip()\n        if eargs:\n            args = args or []\n            args += shlex.split(eargs)\n\n        try:\n            return optparse.OptionParser.parse_args(self, args, values)\n        except optparse.OptionValueError:\n            self.error(str(sys.exc_info()[1]))\n\ndef _parseopts(args):\n    \"\"\"Parse command line arguments\"\"\"\n    p = _OptionParser(usage='cram [OPTIONS] TESTS...', prog='cram')\n    p.add_option('-V', '--version', action='store_true',\n                 help='show version information and exit')\n    p.add_option('-q', '--quiet', action='store_true',\n                 help=\"don't print diffs\")\n    p.add_option('-v', '--verbose', action='store_true',\n                 help='show filenames and test status')\n    p.add_option('-i', '--interactive', action='store_true',\n                 help='interactively merge changed test output')\n    p.add_option('-d', '--debug', action='store_true',\n                 help='write script output directly to the terminal')\n    p.add_option('-y', '--yes', action='store_true',\n                 help='answer yes to all questions')\n    p.add_option('-n', '--no', action='store_true',\n                 help='answer no to all questions')\n    p.add_option('-E', '--preserve-env', action='store_true',\n                 help=\"don't reset common environment variables\")\n    p.add_option('--keep-tmpdir', action='store_true',\n                 help='keep temporary directories')\n    p.add_option('--shell', action='store', default='/bin/sh', metavar='PATH',\n                 help='shell to use for running tests (default: %default)')\n    p.add_option('--shell-opts', action='store', metavar='OPTS',\n                 help='arguments to invoke shell with')\n    p.add_option('--indent', action='store', default=2, metavar='NUM',\n                 type='int', help=('number of spaces to use for indentation '\n                                   '(default: %default)'))\n    p.add_option('--xunit-file', action='store', metavar='PATH',\n                 help='path to write xUnit XML output')\n    opts, paths = p.parse_args(args)\n    paths = [os.fsencode(path) for path in paths]\n    return opts, paths, p.get_usage\n\ndef main(args):\n    \"\"\"Main entry point.\n\n    If you're thinking of using Cram in other Python code (e.g., unit tests),\n    consider using the test() or testfile() functions instead.\n\n    :param args: Script arguments (excluding script name)\n    :type args: str\n    :return: Exit code (non-zero on failure)\n    :rtype: int\n    \"\"\"\n    opts, paths, getusage = _parseopts(args)\n    if opts.version:\n        sys.stdout.write(\"\"\"Cram CLI testing framework (version 0.8)\n\nCopyright (C) 2010-2021 Brodie Rao <brodie@bitheap.org> and others\nThis is free software; see the source for copying conditions. There is NO\nwarranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\"\"\")\n        return\n\n    conflicts = [('--yes', opts.yes, '--no', opts.no),\n                 ('--quiet', opts.quiet, '--interactive', opts.interactive),\n                 ('--debug', opts.debug, '--quiet', opts.quiet),\n                 ('--debug', opts.debug, '--interactive', opts.interactive),\n                 ('--debug', opts.debug, '--verbose', opts.verbose),\n                 ('--debug', opts.debug, '--xunit-file', opts.xunit_file)]\n    for s1, o1, s2, o2 in conflicts:\n        if o1 and o2:\n            sys.stderr.write('options %s and %s are mutually exclusive\\n'\n                             % (s1, s2))\n            return 2\n\n    shellcmd = _which(opts.shell)\n    if not shellcmd:\n        sys.stderr.buffer.write(b'shell not found: %s\\n' %\n                                os.fsencode(opts.shell))\n        return 2\n    shell = [shellcmd]\n    if opts.shell_opts:\n        shell += shlex.split(opts.shell_opts)\n\n    patchcmd = None\n    if opts.interactive:\n        patchcmd = _which('patch')\n        if not patchcmd:\n            sys.stderr.write('patch(1) required for -i\\n')\n            return 2\n\n    if not paths:\n        sys.stdout.write(getusage())\n        return 2\n\n    badpaths = [path for path in paths if not os.path.exists(path)]\n    if badpaths:\n        sys.stderr.buffer.write(b'no such file: %s\\n' % badpaths[0])\n        return 2\n\n    if opts.yes:\n        answer = 'y'\n    elif opts.no:\n        answer = 'n'\n    else:\n        answer = None\n\n    tmpdir = os.environ['CRAMTMP'] = tempfile.mkdtemp('', 'cramtests-')\n    tmpdirb = os.fsencode(tmpdir)\n    proctmp = os.path.join(tmpdir, 'tmp')\n    for s in ('TMPDIR', 'TEMP', 'TMP'):\n        os.environ[s] = proctmp\n\n    os.mkdir(proctmp)\n    try:\n        tests = runtests(paths, tmpdirb, shell, indent=opts.indent,\n                         cleanenv=not opts.preserve_env, debug=opts.debug)\n        if not opts.debug:\n            tests = runcli(tests, quiet=opts.quiet, verbose=opts.verbose,\n                           patchcmd=patchcmd, answer=answer)\n            if opts.xunit_file is not None:\n                tests = runxunit(tests, opts.xunit_file)\n\n        hastests = False\n        failed = False\n        for path, test in tests:\n            hastests = True\n            refout, postout, diff = test()\n            if diff:\n                failed = True\n\n        if not hastests:\n            sys.stderr.write('no tests found\\n')\n            return 2\n\n        return int(failed)\n    finally:\n        if opts.keep_tmpdir:\n            sys.stdout.buffer.write(b'# Kept temporary directory: %s\\n'\n                                    % tmpdirb)\n        else:\n            shutil.rmtree(tmpdir)\n"
  },
  {
    "path": "cram/_process.py",
    "content": "\"\"\"Utilities for running subprocesses\"\"\"\n\nimport os\nimport signal\nimport subprocess\nimport sys\n\n__all__ = ['PIPE', 'STDOUT', 'execute']\n\nPIPE = subprocess.PIPE\nSTDOUT = subprocess.STDOUT\n\ndef _makeresetsigpipe():\n    \"\"\"Make a function to reset SIGPIPE to SIG_DFL (for use in subprocesses).\n\n    Doing subprocess.Popen(..., preexec_fn=makeresetsigpipe()) will prevent\n    Python's SIGPIPE handler (SIG_IGN) from being inherited by the\n    child process.\n    \"\"\"\n    if (sys.platform == 'win32' or\n        getattr(signal, 'SIGPIPE', None) is None): # pragma: nocover\n        return None\n    return lambda: signal.signal(signal.SIGPIPE, signal.SIG_DFL)\n\ndef execute(args, stdin=None, stdout=None, stderr=None, cwd=None, env=None):\n    \"\"\"Run a process and return its output and return code.\n\n    stdin may either be None or a string to send to the process.\n\n    stdout may either be None or PIPE. If set to PIPE, the process's output\n    is returned as a string.\n\n    stderr may either be None or STDOUT. If stdout is set to PIPE and stderr\n    is set to STDOUT, the process's stderr output will be interleaved with\n    stdout and returned as a string.\n\n    cwd sets the process's current working directory.\n\n    env can be set to a dictionary to override the process's environment\n    variables.\n\n    This function returns a 2-tuple of (output, returncode).\n    \"\"\"\n    if sys.platform == 'win32': # pragma: nocover\n        args = [os.fsdecode(arg) for arg in args]\n\n    p = subprocess.Popen(args, stdin=PIPE, stdout=stdout, stderr=stderr,\n                         cwd=cwd, env=env, bufsize=-1,\n                         preexec_fn=_makeresetsigpipe(),\n                         close_fds=os.name == 'posix')\n    out, err = p.communicate(stdin)\n    return out, p.returncode\n"
  },
  {
    "path": "cram/_run.py",
    "content": "\"\"\"The test runner\"\"\"\n\nimport os\nimport sys\n\nfrom cram._test import testfile\n\n__all__ = ['runtests']\n\nif sys.platform == 'win32': # pragma: nocover\n    def _walk(top):\n        top = os.fsdecode(top)\n        for root, dirs, files in os.walk(top):\n            yield (os.fsencode(root),\n                   [os.fsencode(p) for p in dirs],\n                   [os.fsencode(p) for p in files])\nelse:\n    _walk = os.walk\n\ndef _findtests(paths):\n    \"\"\"Yield tests in paths in sorted order\"\"\"\n    for p in paths:\n        if os.path.isdir(p):\n            for root, dirs, files in _walk(p):\n                if os.path.basename(root).startswith(b'.'):\n                    continue\n                for f in sorted(files):\n                    if not f.startswith(b'.') and f.endswith(b'.t'):\n                        yield os.path.normpath(os.path.join(root, f))\n        else:\n            yield os.path.normpath(p)\n\ndef runtests(paths, tmpdir, shell, indent=2, cleanenv=True, debug=False):\n    \"\"\"Run tests and yield results.\n\n    This yields a sequence of 2-tuples containing the following:\n\n        (test path, test function)\n\n    The test function, when called, runs the test in a temporary directory\n    and returns a 3-tuple:\n\n        (list of lines in the test, same list with actual output, diff)\n    \"\"\"\n    cwd = os.getcwd()\n    seen = set()\n    basenames = set()\n    for i, path in enumerate(_findtests(paths)):\n        abspath = os.path.abspath(path)\n        if abspath in seen:\n            continue\n        seen.add(abspath)\n\n        if not os.stat(path).st_size:\n            yield (path, lambda: (None, None, None))\n            continue\n\n        basename = os.path.basename(path)\n        if basename in basenames:\n            basename = basename + b'-%d' % i\n        else:\n            basenames.add(basename)\n\n        def test():\n            \"\"\"Run test file\"\"\"\n            testdir = os.path.join(tmpdir, basename)\n            os.mkdir(testdir)\n            try:\n                os.chdir(testdir)\n                return testfile(abspath, shell, indent=indent,\n                                cleanenv=cleanenv, debug=debug,\n                                testname=path)\n            finally:\n                os.chdir(cwd)\n\n        yield (path, test)\n"
  },
  {
    "path": "cram/_test.py",
    "content": "\"\"\"Utilities for running individual tests\"\"\"\n\nimport itertools\nimport os\nimport re\nimport time\n\nfrom cram._diff import esc, glob, regex, unified_diff\nfrom cram._process import PIPE, STDOUT, execute\n\n__all__ = ['test', 'testfile']\n\n_needescape = re.compile(br'[\\x00-\\x09\\x0b-\\x1f\\x7f-\\xff]').search\n_escapesub = re.compile(br'[\\x00-\\x09\\x0b-\\x1f\\\\\\x7f-\\xff]').sub\n_escapemap = dict((bytes([i]), br'\\x%02x' % i) for i in range(256))\n_escapemap.update({b'\\\\': b'\\\\\\\\', b'\\r': br'\\r', b'\\t': br'\\t'})\n\ndef _escape(s):\n    \"\"\"Like the string-escape codec, but doesn't escape quotes\"\"\"\n    return (_escapesub(lambda m: _escapemap[m.group(0)], s[:-1]) +\n            b' (esc)\\n')\n\ndef test(lines, shell='/bin/sh', indent=2, testname=None, env=None,\n         cleanenv=True, debug=False):\n    r\"\"\"Run test lines and return input, output, and diff.\n\n    This returns a 3-tuple containing the following:\n\n        (list of lines in test, same list with actual output, diff)\n\n    diff is a generator that yields the diff between the two lists.\n\n    If a test exits with return code 80, the actual output is set to\n    None and diff is set to [].\n\n    Note that the TESTSHELL environment variable is available in the\n    test (set to the specified shell). However, the TESTDIR and\n    TESTFILE environment variables are not available. To run actual\n    test files, see testfile().\n\n    Example usage:\n\n    >>> refout, postout, diff = test([b'  $ echo hi\\n',\n    ...                               b'  [a-z]{2} (re)\\n'])\n    >>> refout == [b'  $ echo hi\\n', b'  [a-z]{2} (re)\\n']\n    True\n    >>> postout == [b'  $ echo hi\\n', b'  hi\\n']\n    True\n    >>> bool(diff)\n    False\n\n    lines may also be a single bytes string:\n\n    >>> refout, postout, diff = test(b'  $ echo hi\\n  bye\\n')\n    >>> refout == [b'  $ echo hi\\n', b'  bye\\n']\n    True\n    >>> postout == [b'  $ echo hi\\n', b'  hi\\n']\n    True\n    >>> bool(diff)\n    True\n    >>> (b''.join(diff) ==\n    ...  b'--- \\n+++ \\n@@ -1,2 +1,2 @@\\n   $ echo hi\\n-  bye\\n+  hi\\n')\n    True\n\n    :param lines: Test input\n    :type lines: bytes or collections.Iterable[bytes]\n    :param shell: Shell to run test in\n    :type shell: bytes or str or list[bytes] or list[str]\n    :param indent: Amount of indentation to use for shell commands\n    :type indent: int\n    :param testname: Optional test file name (used in diff output)\n    :type testname: bytes or None\n    :param env: Optional environment variables for the test shell\n    :type env: dict or None\n    :param cleanenv: Whether or not to sanitize the environment\n    :type cleanenv: bool\n    :param debug: Whether or not to run in debug mode (don't capture stdout)\n    :type debug: bool\n    return: Input, output, and diff iterables\n    :rtype: (list[bytes], list[bytes], collections.Iterable[bytes])\n    \"\"\"\n    indent = b' ' * indent\n    cmdline = indent + b'$ '\n    conline = indent + b'> '\n    salt = b'CRAM%.5f' % time.time()\n\n    if env is None:\n        env = os.environ.copy()\n\n    if cleanenv:\n        for s in ('LANG', 'LC_ALL', 'LANGUAGE'):\n            env[s] = 'C'\n        env['TZ'] = 'GMT'\n        env['CDPATH'] = ''\n        env['COLUMNS'] = '80'\n        env['GREP_OPTIONS'] = ''\n\n    if isinstance(lines, bytes):\n        lines = lines.splitlines(True)\n\n    if isinstance(shell, (bytes, str)):\n        shell = [shell]\n    env['TESTSHELL'] = shell[0]\n\n    if debug:\n        stdin = []\n        for line in lines:\n            if not line.endswith(b'\\n'):\n                line += b'\\n'\n            if line.startswith(cmdline):\n                stdin.append(line[len(cmdline):])\n            elif line.startswith(conline):\n                stdin.append(line[len(conline):])\n\n        execute(shell + ['-'], stdin=b''.join(stdin), env=env)\n        return ([], [], [])\n\n    after = {}\n    refout, postout = [], []\n    i = pos = prepos = -1\n    stdin = []\n    for i, line in enumerate(lines):\n        if not line.endswith(b'\\n'):\n            line += b'\\n'\n        refout.append(line)\n        if line.startswith(cmdline):\n            after.setdefault(pos, []).append(line)\n            prepos = pos\n            pos = i\n            stdin.append(b'echo %s %d $?\\n' % (salt, i))\n            stdin.append(line[len(cmdline):])\n        elif line.startswith(conline):\n            after.setdefault(prepos, []).append(line)\n            stdin.append(line[len(conline):])\n        elif not line.startswith(indent):\n            after.setdefault(pos, []).append(line)\n    stdin.append(b'echo %s %d $?\\n' % (salt, i + 1))\n\n    output, retcode = execute(shell + ['-'], stdin=b''.join(stdin),\n                              stdout=PIPE, stderr=STDOUT, env=env)\n    if retcode == 80:\n        return (refout, None, [])\n\n    pos = -1\n    ret = 0\n    for i, line in enumerate(output[:-1].splitlines(True)):\n        out, cmd = line, None\n        if salt in line:\n            out, cmd = line.split(salt, 1)\n\n        if out:\n            if not out.endswith(b'\\n'):\n                out += b' (no-eol)\\n'\n\n            if _needescape(out):\n                out = _escape(out)\n            postout.append(indent + out)\n\n        if cmd:\n            ret = int(cmd.split()[1])\n            if ret != 0:\n                postout.append(indent + b'[%d]\\n' % ret)\n            postout += after.pop(pos, [])\n            pos = int(cmd.split()[0])\n\n    postout += after.pop(pos, [])\n\n    if testname:\n        diffpath = testname\n        errpath = diffpath + b'.err'\n    else:\n        diffpath = errpath = b''\n    diff = unified_diff(refout, postout, diffpath, errpath,\n                        matchers=[esc, glob, regex])\n    for firstline in diff:\n        return refout, postout, itertools.chain([firstline], diff)\n    return refout, postout, []\n\ndef testfile(path, shell='/bin/sh', indent=2, env=None, cleanenv=True,\n             debug=False, testname=None):\n    \"\"\"Run test at path and return input, output, and diff.\n\n    This returns a 3-tuple containing the following:\n\n        (list of lines in test, same list with actual output, diff)\n\n    diff is a generator that yields the diff between the two lists.\n\n    If a test exits with return code 80, the actual output is set to\n    None and diff is set to [].\n\n    Note that the TESTDIR, TESTFILE, and TESTSHELL environment\n    variables are available to use in the test.\n\n    :param path: Path to test file\n    :type path: bytes or str\n    :param shell: Shell to run test in\n    :type shell: bytes or str or list[bytes] or list[str]\n    :param indent: Amount of indentation to use for shell commands\n    :type indent: int\n    :param env: Optional environment variables for the test shell\n    :type env: dict or None\n    :param cleanenv: Whether or not to sanitize the environment\n    :type cleanenv: bool\n    :param debug: Whether or not to run in debug mode (don't capture stdout)\n    :type debug: bool\n    :param testname: Optional test file name (used in diff output)\n    :type testname: bytes or None\n    :return: Input, output, and diff iterables\n    :rtype: (list[bytes], list[bytes], collections.Iterable[bytes])\n    \"\"\"\n    f = open(path, 'rb')\n    try:\n        abspath = os.path.abspath(path)\n        env = env or os.environ.copy()\n        env['TESTDIR'] = os.fsdecode(os.path.dirname(abspath))\n        env['TESTFILE'] = os.fsdecode(os.path.basename(abspath))\n        if testname is None: # pragma: nocover\n            testname = os.path.basename(abspath)\n        return test(f, shell, indent=indent, testname=testname, env=env,\n                    cleanenv=cleanenv, debug=debug)\n    finally:\n        f.close()\n"
  },
  {
    "path": "cram/_xunit.py",
    "content": "\"\"\"xUnit XML output\"\"\"\n\nimport locale\nimport os\nimport re\nimport socket\nimport sys\nimport time\n\n__all__ = ['runxunit']\n\n_widecdataregex = (r'(?:[^\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd'\n                   r'\\U00010000-\\U0010ffff]|]]>)')\n_narrowcdataregex = (r'(?:[^\\x09\\x0a\\x0d\\x20-\\ud7ff\\ue000-\\ufffd]'\n                     r'|]]>)')\n_widequoteattrregex = (r'[^\\x20\\x21\\x23-\\x25\\x27-\\x3b\\x3d'\n                       r'\\x3f-\\ud7ff\\ue000-\\ufffd'\n                       r'\\U00010000-\\U0010ffff]')\n_narrowquoteattrregex = (r'[^\\x20\\x21\\x23-\\x25\\x27-\\x3b\\x3d'\n                         r'\\x3f-\\ud7ff\\ue000-\\ufffd]')\n_replacementchar = '\\N{REPLACEMENT CHARACTER}'\n\nif sys.maxunicode >= 0x10ffff: # pragma: nocover\n    _cdatasub = re.compile(_widecdataregex).sub\n    _quoteattrsub = re.compile(_widequoteattrregex).sub\nelse: # pragma: nocover\n    _cdatasub = re.compile(_narrowcdataregex).sub\n    _quoteattrsub = re.compile(_narrowquoteattrregex).sub\n\ndef _cdatareplace(m):\n    \"\"\"Replace _cdatasub() regex match\"\"\"\n    if m.group(0) == ']]>':\n        return ']]>]]&gt;<![CDATA['\n    else:\n        return _replacementchar\n\ndef _cdata(s):\n    r\"\"\"Escape a string as an XML CDATA block.\n\n    >>> (_cdata('1<\\'2\\'>&\"3\\x00]]>\\t\\r\\n') ==\n    ...  '<![CDATA[1<\\'2\\'>&\\\"3\\ufffd]]>]]&gt;<![CDATA[\\t\\r\\n]]>')\n    True\n    \"\"\"\n    return '<![CDATA[%s]]>' % _cdatasub(_cdatareplace, s)\n\ndef _quoteattrreplace(m):\n    \"\"\"Replace _quoteattrsub() regex match\"\"\"\n    return {'\\t': '&#9;',\n            '\\n': '&#10;',\n            '\\r': '&#13;',\n            '\"': '&quot;',\n            '&': '&amp;',\n            '<': '&lt;',\n            '>': '&gt;'}.get(m.group(0), _replacementchar)\n\ndef _quoteattr(s):\n    r\"\"\"Escape a string for use as an XML attribute value.\n\n    >>> (_quoteattr('1<\\'2\\'>&\"3\\x00]]>\\t\\r\\n') ==\n    ...  '\"1&lt;\\'2\\'&gt;&amp;&quot;3\\ufffd]]&gt;&#9;&#13;&#10;\"')\n    True\n    \"\"\"\n    return '\"%s\"' % _quoteattrsub(_quoteattrreplace, s)\n\ndef _timestamp():\n    \"\"\"Return the current time in ISO 8601 format\"\"\"\n    tm = time.localtime()\n    if tm.tm_isdst == 1: # pragma: nocover\n        tz = time.altzone\n    else: # pragma: nocover\n        tz = time.timezone\n\n    timestamp = time.strftime('%Y-%m-%dT%H:%M:%S', tm)\n    tzhours = int(-tz / 60 / 60)\n    tzmins = int(abs(tz) / 60 % 60)\n    timestamp += '%+03d:%02d' % (tzhours, tzmins)\n    return timestamp\n\ndef runxunit(tests, xmlpath):\n    \"\"\"Run tests with xUnit XML output.\n\n    tests should be a sequence of 2-tuples containing the following:\n\n        (test path, test function)\n\n    This function yields a new sequence where each test function is wrapped\n    with a function that writes test results to an xUnit XML file.\n    \"\"\"\n    suitestart = time.time()\n    timestamp = _timestamp()\n    hostname = socket.gethostname()\n    total, skipped, failed = [0], [0], [0]\n    testcases = []\n\n    for path, test in tests:\n        def testwrapper():\n            \"\"\"Run test and collect XML output\"\"\"\n            total[0] += 1\n\n            start = time.time()\n            refout, postout, diff = test()\n            testtime = time.time() - start\n\n            classname = path.decode(locale.getpreferredencoding(), 'replace')\n            name = os.path.basename(classname)\n\n            if postout is None:\n                skipped[0] += 1\n                testcase = (('  <testcase classname=%(classname)s\\n'\n                             '            name=%(name)s\\n'\n                             '            time=\"%(time).6f\">\\n'\n                             '    <skipped/>\\n'\n                             '  </testcase>\\n') %\n                            {'classname': _quoteattr(classname),\n                             'name': _quoteattr(name),\n                             'time': testtime})\n            elif diff:\n                failed[0] += 1\n                diff = list(diff)\n                diffu = ''.join(l.decode(locale.getpreferredencoding(),\n                                         'replace')\n                                for l in diff)\n                testcase = (('  <testcase classname=%(classname)s\\n'\n                             '            name=%(name)s\\n'\n                             '            time=\"%(time).6f\">\\n'\n                             '    <failure>%(diff)s</failure>\\n'\n                             '  </testcase>\\n') %\n                            {'classname': _quoteattr(classname),\n                             'name': _quoteattr(name),\n                             'time': testtime,\n                             'diff': _cdata(diffu)})\n            else:\n                testcase = (('  <testcase classname=%(classname)s\\n'\n                             '            name=%(name)s\\n'\n                             '            time=\"%(time).6f\"/>\\n') %\n                            {'classname': _quoteattr(classname),\n                             'name': _quoteattr(name),\n                             'time': testtime})\n            testcases.append(testcase)\n\n            return refout, postout, diff\n\n        yield path, testwrapper\n\n    suitetime = time.time() - suitestart\n    header = (('<?xml version=\"1.0\" encoding=\"utf-8\"?>\\n'\n               '<testsuite name=\"cram\"\\n'\n               '           tests=\"%(total)d\"\\n'\n               '           failures=\"%(failed)d\"\\n'\n               '           skipped=\"%(skipped)d\"\\n'\n               '           timestamp=%(timestamp)s\\n'\n               '           hostname=%(hostname)s\\n'\n               '           time=\"%(time).6f\">\\n') %\n              {'total': total[0],\n               'failed': failed[0],\n               'skipped': skipped[0],\n               'timestamp': _quoteattr(timestamp),\n               'hostname': _quoteattr(hostname),\n               'time': suitetime})\n    footer = '</testsuite>\\n'\n\n    xmlfile = open(xmlpath, 'wb')\n    try:\n        xmlfile.write(header.encode('utf-8'))\n        for testcase in testcases:\n            xmlfile.write(testcase.encode('utf-8'))\n        xmlfile.write(footer.encode('utf-8'))\n    finally:\n        xmlfile.close()\n"
  },
  {
    "path": "examples/.hidden/hidden.t",
    "content": "This test is ignored because it's hidden.\n"
  },
  {
    "path": "examples/.hidden.t",
    "content": "This test is ignored because it's hidden.\n"
  },
  {
    "path": "examples/bare.t",
    "content": "  $ true\n"
  },
  {
    "path": "examples/empty.t",
    "content": ""
  },
  {
    "path": "examples/env.t",
    "content": "Check environment variables:\n\n  $ echo \"$LANG\"\n  C\n  $ echo \"$LC_ALL\"\n  C\n  $ echo \"$LANGUAGE\"\n  C\n  $ echo \"$TZ\"\n  GMT\n  $ echo \"$CDPATH\"\n  \n  $ echo \"$GREP_OPTIONS\"\n  \n  $ echo \"$CRAMTMP\"\n  .+ (re)\n  $ echo \"$TESTDIR\"\n  */examples (glob)\n  $ ls \"$TESTDIR\"\n  bare.t\n  empty.t\n  env.t\n  fail.t\n  missingeol.t\n  skip.t\n  test.t\n  $ echo \"$TESTFILE\"\n  env.t\n  $ pwd\n  */cramtests*/env.t (glob)\n"
  },
  {
    "path": "examples/fail.t",
    "content": "Output needing escaping:\n\n  $ printf '\\00\\01\\02\\03\\04\\05\\06\\07\\010\\011\\013\\014\\016\\017\\020\\021\\022\\n'\n  foo\n  $ printf '\\023\\024\\025\\026\\027\\030\\031\\032\\033\\034\\035\\036\\037\\040\\047\\n'\n  bar\n\nWrong output and bad regexes:\n\n  $ echo 1\n  2\n  $ printf '1\\nfoo\\n1\\n'\n  +++ (re)\n  foo\\ (re)\n   (re)\n\nFiller to force a second diff hunk:\n\n\nOffset regular expression:\n\n  $ printf 'foo\\n\\n1\\n'\n  \n  \\d (re)\n"
  },
  {
    "path": "examples/missingeol.t",
    "content": "  $ printf foo\n  foo (no-eol)"
  },
  {
    "path": "examples/skip.t",
    "content": "This test is considered \"skipped\" because it exits with return code\n80. This is useful for skipping tests that only work on certain\nplatforms or in certain settings.\n\n  $ exit 80\n"
  },
  {
    "path": "examples/test.t",
    "content": "Simple commands:\n\n  $ echo foo\n  foo\n  $ printf 'bar\\nbaz\\n' | cat\n  bar\n  baz\n\nMulti-line command:\n\n  $ foo() {\n  >     echo bar\n  > }\n  $ foo\n  bar\n\nRegular expression:\n\n  $ echo foobarbaz\n  foobar.* (re)\n\nGlob:\n\n  $ printf '* \\\\foobarbaz {10}\\n'\n  \\* \\\\fo?bar* {10} (glob)\n\nLiteral match ending in (re) and (glob):\n\n  $ echo 'foo\\Z\\Z\\Z bar (re)'\n  foo\\Z\\Z\\Z bar (re)\n  $ echo 'baz??? quux (glob)'\n  baz??? quux (glob)\n\nExit code:\n\n  $ (exit 1)\n  [1]\n\nWrite to stderr:\n\n  $ echo foo >&2\n  foo\n\nNo newline:\n\n  $ printf foo\n  foo (no-eol)\n  $ printf 'foo\\nbar'\n  foo\n  bar (no-eol)\n  $ printf '  '\n     (no-eol)\n  $ printf '  \\n  '\n    \n     (no-eol)\n  $ echo foo\n  foo\n  $ printf foo\n  foo (no-eol)\n\nEscaped output:\n\n  $ printf '\\00\\01\\02\\03\\04\\05\\06\\07\\010\\011\\013\\014\\016\\017\\020\\021\\022\\n'\n  \\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\t\\x0b\\x0c\\x0e\\x0f\\x10\\x11\\x12 (esc)\n  $ printf '\\023\\024\\025\\026\\027\\030\\031\\032\\033\\034\\035\\036\\037\\040\\047\\n'\n  \\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f ' (esc)\n  $ echo hi\n  \\x68\\x69 (esc)\n  $ echo '(esc) in output (esc)'\n  (esc) in output (esc)\n  $ echo '(esc) in output (esc)'\n  (esc) in output \\x28esc\\x29 (esc)\n\nCommand that closes a pipe:\n\n  $ cat /dev/urandom | head -1 > /dev/null\n\nIf Cram let Python's SIGPIPE handler get inherited by this script, we\nmight see broken pipe messages.\n"
  },
  {
    "path": "requirements.txt",
    "content": "check-manifest\ncoverage\npycodestyle\npyflakes\n"
  },
  {
    "path": "scripts/cram",
    "content": "#!/usr/bin/env python3\nimport sys\n\nimport cram\n\ntry:\n    sys.exit(cram.main(sys.argv[1:]))\nexcept (BrokenPipeError, KeyboardInterrupt):\n    pass\n"
  },
  {
    "path": "setup.cfg",
    "content": "[bdist_wheel]\nuniversal = true\n\n[pycodestyle]\n# E129: indentation between lines in conditions\n# E261: two spaces before inline comment\n# E302/E305: two new lines between functions/etc.\n# E741: ambiguous variable name 'l'\n# W504: line break after binary operator\nignore = E129,E261,E302,E305,E741,W504\n"
  },
  {
    "path": "setup.py",
    "content": "#!/usr/bin/env python\n\"\"\"Installs cram\"\"\"\n\nimport os\nimport sys\nfrom distutils.core import setup\n\nCOMMANDS = {}\nCRAM_DIR = os.path.abspath(os.path.dirname(__file__))\n\ntry:\n    from wheel.bdist_wheel import bdist_wheel\nexcept ImportError:\n    pass\nelse:\n    COMMANDS['bdist_wheel'] = bdist_wheel\n\ndef long_description():\n    \"\"\"Get the long description from the README\"\"\"\n    return open(os.path.join(sys.path[0], 'README.rst')).read()\n\nsetup(\n    author='Brodie Rao',\n    author_email='brodie@bitheap.org',\n    classifiers=[\n        'Development Status :: 5 - Production/Stable',\n        'Environment :: Console',\n        'Intended Audience :: Developers',\n        'License :: OSI Approved :: GNU General Public License (GPL)',\n        ('License :: OSI Approved :: GNU General Public License v2 '\n         'or later (GPLv2+)'),\n        'Natural Language :: English',\n        'Operating System :: OS Independent',\n        'Programming Language :: Python :: 3',\n        'Programming Language :: Unix Shell',\n        'Topic :: Software Development :: Testing',\n    ],\n    cmdclass=COMMANDS,\n    description='Functional tests for command line applications',\n    download_url='https://bitheap.org/cram/cram-0.8.tar.gz',\n    keywords='automatic functional test framework',\n    license='GNU GPLv2 or any later version',\n    long_description=long_description(),\n    name='cram',\n    packages=['cram'],\n    scripts=['scripts/cram'],\n    url='https://bitheap.org/cram/',\n    version='0.8',\n)\n"
  },
  {
    "path": "tests/config.t",
    "content": "Set up cram alias and example tests:\n\n  $ . \"$TESTDIR\"/setup.sh\n\nOptions in .cramrc:\n\n  $ cat > .cramrc <<EOF\n  > [cram]\n  > yes = True\n  > no = 1\n  > indent = 4\n  > EOF\n  $ cram\n  options --yes and --no are mutually exclusive\n  [2]\n  $ mv .cramrc config\n  $ CRAMRC=config cram\n  options --yes and --no are mutually exclusive\n  [2]\n  $ rm config\n\nInvalid option in .cramrc:\n\n  $ cat > .cramrc <<EOF\n  > [cram]\n  > indent = hmm\n  > EOF\n  $ cram\n  [Uu]sage: cram \\[OPTIONS\\] TESTS\\.\\.\\. (re)\n  \n  cram: error: option --indent: invalid integer value: 'hmm'\n  [2]\n  $ rm .cramrc\n  $ cat > .cramrc <<EOF\n  > [cram]\n  > verbose = hmm\n  > EOF\n  $ cram\n  [Uu]sage: cram \\[OPTIONS\\] TESTS\\.\\.\\. (re)\n  \n  cram: error: --verbose: invalid boolean value: 'hmm'\n  [2]\n  $ rm .cramrc\n\nOptions in an environment variable:\n\n  $ CRAM='-y -n' cram\n  options --yes and --no are mutually exclusive\n  [2]\n"
  },
  {
    "path": "tests/debug.t",
    "content": "Set up cram alias and example tests:\n\n  $ . \"$TESTDIR\"/setup.sh\n\nDebug mode:\n\n  $ printf '  $ echo hi\\n  > echo bye' > debug.t\n  $ cram -d -v debug.t\n  options --debug and --verbose are mutually exclusive\n  [2]\n  $ cram -d -q debug.t\n  options --debug and --quiet are mutually exclusive\n  [2]\n  $ cram -d -i debug.t\n  options --debug and --interactive are mutually exclusive\n  [2]\n  $ cram -d --xunit-file==cram.xml debug.t\n  options --debug and --xunit-file are mutually exclusive\n  [2]\n  $ cram -d debug.t\n  hi\n  bye\n  $ cram -d examples/empty.t\n\nDebug mode with extra shell arguments:\n\n  $ cram --shell-opts='-s' -d debug.t\n  hi\n  bye\n\nTest debug mode with set -x:\n\n  $ cat > set-x.t <<EOF\n  >   $ echo 1\n  >   1\n  >   $ set -x\n  >   $ echo 2\n  > EOF\n  $ cram -d set-x.t\n  1\n  \\+.*echo 2 (re)\n  2\n\nTest set -x without debug mode:\n\n  $ cram set-x.t\n  !\n  --- set-x.t\n  +++ set-x.t.err\n  @@ -1,4 +1,8 @@\n     $ echo 1\n     1\n     $ set -x\n  \\+  \\+.*echo  \\(no-eol\\) (re)\n     $ echo 2\n  \\+  \\+.*echo 2 (re)\n  +  2\n  \\+  \\+.*echo  \\(no-eol\\) (re)\n  \n  # Ran 1 tests, 0 skipped, 1 failed.\n  [1]\n\nNote that the \"+ echo  (no-eol)\" lines are artifacts of the echo commands\nthat Cram inserts into the test in order to track output. It'd be nice if\nCram could avoid removing salt/line number/return code information from those\nlines, but it isn't possible to distinguish between set -x output and normal\noutput.\n"
  },
  {
    "path": "tests/dist.t",
    "content": "Skip this test if check-manifest isn't available:\n\n  $ command -v check-manifest > /dev/null || exit 80\n\nConfirm that \"make dist\" isn't going to miss any files:\n\n  $ check-manifest \"$TESTDIR/..\"\n  lists of files in version control and sdist match\n"
  },
  {
    "path": "tests/doctest.t",
    "content": "Set up cram alias and example tests:\n\n  $ . \"$TESTDIR\"/setup.sh\n\nRun doctests:\n\n  $ doctest \"$TESTDIR\"/../cram\n"
  },
  {
    "path": "tests/encoding.t",
    "content": "Set up cram alias and example tests:\n\n  $ . \"$TESTDIR\"/setup.sh\n\nTest with Windows newlines:\n\n  $ printf \"  $ echo hi\\r\\n  hi\\r\\n\" > windows-newlines.t\n  $ cram windows-newlines.t\n  .\n  # Ran 1 tests, 0 skipped, 0 failed.\n\nTest with Latin-1 encoding:\n\n  $ cat > good-latin-1.t <<EOF\n  >   $ printf \"hola se\\361or\\n\"\n  >   hola se\\xf1or (esc)\n  > EOF\n\n  $ cat > bad-latin-1.t <<EOF\n  >   $ printf \"hola se\\361or\\n\"\n  >   hey\n  > EOF\n\n  $ cram good-latin-1.t bad-latin-1.t\n  .!\n  --- bad-latin-1.t\n  +++ bad-latin-1.t.err\n  @@ -1,2 +1,2 @@\n     $ printf \"hola se\\361or\\n\"\n  -  hey\n  +  hola se\\xf1or (esc)\n  \n  # Ran 2 tests, 0 skipped, 1 failed.\n  [1]\n\nTest with UTF-8 encoding:\n\n  $ cat > good-utf-8.t <<EOF\n  >   $ printf \"hola se\\303\\261or\\n\"\n  >   hola se\\xc3\\xb1or (esc)\n  > EOF\n\n  $ cat > bad-utf-8.t <<EOF\n  >   $ printf \"hola se\\303\\261or\\n\"\n  >   hey\n  > EOF\n\n  $ cram good-utf-8.t bad-utf-8.t\n  .!\n  --- bad-utf-8.t\n  +++ bad-utf-8.t.err\n  @@ -1,2 +1,2 @@\n     $ printf \"hola se\\303\\261or\\n\"\n  -  hey\n  +  hola se\\xc3\\xb1or (esc)\n  \n  # Ran 2 tests, 0 skipped, 1 failed.\n  [1]\n\nTest file missing trailing newline:\n\n  $ printf '  $ true' > passing-with-no-newline.t\n  $ cram passing-with-no-newline.t\n  .\n  # Ran 1 tests, 0 skipped, 0 failed.\n\n  $ printf '  $ false' > failing-with-no-newline.t\n  $ cram failing-with-no-newline.t\n  !\n  --- failing-with-no-newline.t\n  +++ failing-with-no-newline.t.err\n  @@ -1,1 +1,2 @@\n     $ false\n  +  [1]\n  \n  # Ran 1 tests, 0 skipped, 1 failed.\n  [1]\n"
  },
  {
    "path": "tests/interactive.t",
    "content": "Set up cram alias and example tests:\n\n  $ . \"$TESTDIR\"/setup.sh\n\nInteractive mode (don't merge):\n\n  $ cram -n -i examples/fail.t\n  !\n  --- examples/fail.t\n  +++ examples/fail.t.err\n  @@ -1,18 +1,18 @@\n   Output needing escaping:\n   \n     $ printf '\\00\\01\\02\\03\\04\\05\\06\\07\\010\\011\\013\\014\\016\\017\\020\\021\\022\\n'\n  -  foo\n  +  \\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\t\\x0b\\x0c\\x0e\\x0f\\x10\\x11\\x12 (esc)\n     $ printf '\\023\\024\\025\\026\\027\\030\\031\\032\\033\\034\\035\\036\\037\\040\\047\\n'\n  -  bar\n  +  \\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f ' (esc)\n   \n   Wrong output and bad regexes:\n   \n     $ echo 1\n  -  2\n  +  1\n     $ printf '1\\nfoo\\n1\\n'\n  -  +++ (re)\n  -  foo\\ (re)\n  -   (re)\n  +  1\n  +  foo\n  +  1\n   \n   Filler to force a second diff hunk:\n   \n  @@ -20,5 +20,6 @@\n   Offset regular expression:\n   \n     $ printf 'foo\\n\\n1\\n'\n  +  foo\n     \n     \\d (re)\n  Accept this change? [yN] n\n  \n  # Ran 1 tests, 0 skipped, 1 failed.\n  [1]\n  $ md5 examples/fail.t examples/fail.t.err\n  .*\\b0f598c2b7b8ca5bcb8880e492ff6b452\\b.* (re)\n  .*\\b7a23dfa85773c77648f619ad0f9df554\\b.* (re)\n\nInteractive mode (merge):\n\n  $ cp examples/fail.t examples/fail.t.orig\n  $ cram -y -i examples/fail.t\n  !\n  --- examples/fail.t\n  +++ examples/fail.t.err\n  @@ -1,18 +1,18 @@\n   Output needing escaping:\n   \n     $ printf '\\00\\01\\02\\03\\04\\05\\06\\07\\010\\011\\013\\014\\016\\017\\020\\021\\022\\n'\n  -  foo\n  +  \\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\t\\x0b\\x0c\\x0e\\x0f\\x10\\x11\\x12 (esc)\n     $ printf '\\023\\024\\025\\026\\027\\030\\031\\032\\033\\034\\035\\036\\037\\040\\047\\n'\n  -  bar\n  +  \\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f ' (esc)\n   \n   Wrong output and bad regexes:\n   \n     $ echo 1\n  -  2\n  +  1\n     $ printf '1\\nfoo\\n1\\n'\n  -  +++ (re)\n  -  foo\\ (re)\n  -   (re)\n  +  1\n  +  foo\n  +  1\n   \n   Filler to force a second diff hunk:\n   \n  @@ -20,5 +20,6 @@\n   Offset regular expression:\n   \n     $ printf 'foo\\n\\n1\\n'\n  +  foo\n     \n     \\d (re)\n  Accept this change? [yN] y\n  patching file examples/fail.t\n  \n  # Ran 1 tests, 0 skipped, 1 failed.\n  [1]\n  $ md5 examples/fail.t\n  .*\\b1d9e5b527f01fbf2d9b1c121d005108c\\b.* (re)\n  $ mv examples/fail.t.orig examples/fail.t\n\nVerbose interactive mode (answer manually and don't merge):\n\n  $ printf 'bad\\nn\\n' | cram -v -i examples/fail.t\n  examples/fail.t: failed\n  --- examples/fail.t\n  +++ examples/fail.t.err\n  @@ -1,18 +1,18 @@\n   Output needing escaping:\n   \n     $ printf '\\00\\01\\02\\03\\04\\05\\06\\07\\010\\011\\013\\014\\016\\017\\020\\021\\022\\n'\n  -  foo\n  +  \\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\t\\x0b\\x0c\\x0e\\x0f\\x10\\x11\\x12 (esc)\n     $ printf '\\023\\024\\025\\026\\027\\030\\031\\032\\033\\034\\035\\036\\037\\040\\047\\n'\n  -  bar\n  +  \\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f ' (esc)\n   \n   Wrong output and bad regexes:\n   \n     $ echo 1\n  -  2\n  +  1\n     $ printf '1\\nfoo\\n1\\n'\n  -  +++ (re)\n  -  foo\\ (re)\n  -   (re)\n  +  1\n  +  foo\n  +  1\n   \n   Filler to force a second diff hunk:\n   \n  @@ -20,5 +20,6 @@\n   Offset regular expression:\n   \n     $ printf 'foo\\n\\n1\\n'\n  +  foo\n     \n     \\d (re)\n  Accept this change? [yN] Accept this change? [yN] # Ran 1 tests, 0 skipped, 1 failed.\n  [1]\n  $ md5 examples/fail.t examples/fail.t.err\n  .*\\b0f598c2b7b8ca5bcb8880e492ff6b452\\b.* (re)\n  .*\\b7a23dfa85773c77648f619ad0f9df554\\b.* (re)\n  $ printf 'bad\\n\\n' | cram -v -i examples/fail.t\n  examples/fail.t: failed\n  --- examples/fail.t\n  +++ examples/fail.t.err\n  @@ -1,18 +1,18 @@\n   Output needing escaping:\n   \n     $ printf '\\00\\01\\02\\03\\04\\05\\06\\07\\010\\011\\013\\014\\016\\017\\020\\021\\022\\n'\n  -  foo\n  +  \\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\t\\x0b\\x0c\\x0e\\x0f\\x10\\x11\\x12 (esc)\n     $ printf '\\023\\024\\025\\026\\027\\030\\031\\032\\033\\034\\035\\036\\037\\040\\047\\n'\n  -  bar\n  +  \\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f ' (esc)\n   \n   Wrong output and bad regexes:\n   \n     $ echo 1\n  -  2\n  +  1\n     $ printf '1\\nfoo\\n1\\n'\n  -  +++ (re)\n  -  foo\\ (re)\n  -   (re)\n  +  1\n  +  foo\n  +  1\n   \n   Filler to force a second diff hunk:\n   \n  @@ -20,5 +20,6 @@\n   Offset regular expression:\n   \n     $ printf 'foo\\n\\n1\\n'\n  +  foo\n     \n     \\d (re)\n  Accept this change? [yN] Accept this change? [yN] # Ran 1 tests, 0 skipped, 1 failed.\n  [1]\n  $ md5 examples/fail.t examples/fail.t.err\n  .*\\b0f598c2b7b8ca5bcb8880e492ff6b452\\b.* (re)\n  .*\\b7a23dfa85773c77648f619ad0f9df554\\b.* (re)\n\nVerbose interactive mode (answer manually and merge):\n\n  $ cp examples/fail.t examples/fail.t.orig\n  $ printf 'bad\\ny\\n' | cram -v -i examples/fail.t\n  examples/fail.t: failed\n  --- examples/fail.t\n  +++ examples/fail.t.err\n  @@ -1,18 +1,18 @@\n   Output needing escaping:\n   \n     $ printf '\\00\\01\\02\\03\\04\\05\\06\\07\\010\\011\\013\\014\\016\\017\\020\\021\\022\\n'\n  -  foo\n  +  \\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\t\\x0b\\x0c\\x0e\\x0f\\x10\\x11\\x12 (esc)\n     $ printf '\\023\\024\\025\\026\\027\\030\\031\\032\\033\\034\\035\\036\\037\\040\\047\\n'\n  -  bar\n  +  \\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f ' (esc)\n   \n   Wrong output and bad regexes:\n   \n     $ echo 1\n  -  2\n  +  1\n     $ printf '1\\nfoo\\n1\\n'\n  -  +++ (re)\n  -  foo\\ (re)\n  -   (re)\n  +  1\n  +  foo\n  +  1\n   \n   Filler to force a second diff hunk:\n   \n  @@ -20,5 +20,6 @@\n   Offset regular expression:\n   \n     $ printf 'foo\\n\\n1\\n'\n  +  foo\n     \n     \\d (re)\n  Accept this change? [yN] Accept this change? [yN] patching file examples/fail.t\n  examples/fail.t: merged output\n  # Ran 1 tests, 0 skipped, 1 failed.\n  [1]\n  $ md5 examples/fail.t\n  .*\\b1d9e5b527f01fbf2d9b1c121d005108c\\b.* (re)\n  $ mv examples/fail.t.orig examples/fail.t\n\nTest missing patch(1) and patch(1) error:\n\n  $ PATH=. cram -i examples/fail.t\n  patch(1) required for -i\n  [2]\n  $ cat > patch <<EOF\n  > #!/bin/sh\n  > echo \"patch failed\" 1>&2\n  > exit 1\n  > EOF\n  $ chmod +x patch\n  $ PATH=. cram -y -i examples/fail.t\n  !\n  --- examples/fail.t\n  +++ examples/fail.t.err\n  @@ -1,18 +1,18 @@\n   Output needing escaping:\n   \n     $ printf '\\00\\01\\02\\03\\04\\05\\06\\07\\010\\011\\013\\014\\016\\017\\020\\021\\022\\n'\n  -  foo\n  +  \\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\t\\x0b\\x0c\\x0e\\x0f\\x10\\x11\\x12 (esc)\n     $ printf '\\023\\024\\025\\026\\027\\030\\031\\032\\033\\034\\035\\036\\037\\040\\047\\n'\n  -  bar\n  +  \\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f ' (esc)\n   \n   Wrong output and bad regexes:\n   \n     $ echo 1\n  -  2\n  +  1\n     $ printf '1\\nfoo\\n1\\n'\n  -  +++ (re)\n  -  foo\\ (re)\n  -   (re)\n  +  1\n  +  foo\n  +  1\n   \n   Filler to force a second diff hunk:\n   \n  @@ -20,5 +20,6 @@\n   Offset regular expression:\n   \n     $ printf 'foo\\n\\n1\\n'\n  +  foo\n     \n     \\d (re)\n  Accept this change? [yN] y\n  patch failed\n  examples/fail.t: merge failed\n  \n  # Ran 1 tests, 0 skipped, 1 failed.\n  [1]\n  $ md5 examples/fail.t examples/fail.t.err\n  .*\\b0f598c2b7b8ca5bcb8880e492ff6b452\\b.* (re)\n  .*\\b7a23dfa85773c77648f619ad0f9df554\\b.* (re)\n  $ rm patch examples/fail.t.err\n"
  },
  {
    "path": "tests/pep8.t",
    "content": "Skip this test if pycodestyle isn't available:\n\n  $ command -v pycodestyle > /dev/null || exit 80\n\nCheck that the Python source code style is PEP 8 compliant:\n\n  $ pycodestyle --config=\"$TESTDIR/..\"/setup.cfg --repeat \"$TESTDIR/..\"\n"
  },
  {
    "path": "tests/pyflakes.t",
    "content": "Skip this test if pyflakes isn't available:\n\n  $ command -v pyflakes > /dev/null || exit 80\n\nCheck that there are no obvious Python source code errors:\n\n  $ pyflakes \"$TESTDIR/..\"\n"
  },
  {
    "path": "tests/run-doctests.py",
    "content": "#!/usr/bin/env python\n\nimport doctest\nimport os\nimport sys\n\ndef _getmodules(pkgdir):\n    \"\"\"Import and yield modules in pkgdir\"\"\"\n    for root, dirs, files in os.walk(pkgdir):\n        if '__pycache__' in dirs:\n            dirs.remove('__pycache__')\n        for fn in files:\n            if not fn.endswith('.py') or fn == '__main__.py':\n                continue\n\n            modname = fn.replace(os.sep, '.')[:-len('.py')]\n            if modname.endswith('.__init__'):\n                modname = modname[:-len('.__init__')]\n            modname = '.'.join(['cram', modname])\n            if '.' in modname:\n                fromlist = [modname.rsplit('.', 1)[1]]\n            else:\n                fromlist = []\n\n            yield __import__(modname, {}, {}, fromlist)\n\ndef rundoctests(pkgdir):\n    \"\"\"Run doctests in the given package directory\"\"\"\n    totalfailures = totaltests = 0\n    for module in _getmodules(pkgdir):\n        failures, tests = doctest.testmod(module)\n        totalfailures += failures\n        totaltests += tests\n    return totalfailures != 0\n\nif __name__ == '__main__':\n    try:\n        sys.exit(rundoctests(sys.argv[1]))\n    except KeyboardInterrupt:\n        pass\n"
  },
  {
    "path": "tests/setup.sh",
    "content": "#!/bin/sh\n\n# Bash doesn't expand aliases by default in non-interactive mode, so\n# we enable it manually if the test is run with --shell=/bin/bash.\n[ \"$TESTSHELL\" = \"/bin/bash\" ] && shopt -s expand_aliases\n\n# The $PYTHON environment variable should be set when running this test\n# from Python.\n[ -n \"$PYTHON\" ] || PYTHON=\"`which python3`\"\n[ -n \"$PYTHONPATH\" ] || PYTHONPATH=\"$TESTDIR/..\" && export PYTHONPATH\nif [ -n \"$COVERAGE\" ]; then\n  if [ -z \"$COVERAGE_FILE\" ]; then\n    COVERAGE_FILE=\"$TESTDIR/../.coverage\"\n    export COVERAGE_FILE\n  fi\n\n  alias cram=\"`which \"$COVERAGE\"` run -a --rcfile=$TESTDIR/../.coveragerc \\\n$TESTDIR/../scripts/cram --shell=$TESTSHELL\"\n  alias doctest=\"`which \"$COVERAGE\"` run -a --rcfile=$TESTDIR/../.coveragerc \\\n$TESTDIR/run-doctests.py\"\nelse\n  PYTHON=\"`command -v \"$PYTHON\" || echo \"$PYTHON\"`\"\n  alias cram=\"$PYTHON $TESTDIR/../scripts/cram --shell=$TESTSHELL\"\n  alias doctest=\"$PYTHON $TESTDIR/run-doctests.py\"\nfi\ncommand -v md5 > /dev/null || alias md5=md5sum\n\n# Copy in example tests\ncp -R \"$TESTDIR\"/../examples .\nfind . -name '*.err' -exec rm '{}' \\;\n"
  },
  {
    "path": "tests/test.t",
    "content": "Set up cram alias and example tests:\n\n  $ . \"$TESTDIR\"/setup.sh\n\nRun cram examples:\n\n  $ cram -q examples examples/fail.t\n  .s.!.s.\n  # Ran 7 tests, 2 skipped, 1 failed.\n  [1]\n  $ md5 examples/fail.t examples/fail.t.err\n  .*\\b0f598c2b7b8ca5bcb8880e492ff6b452\\b.* (re)\n  .*\\b7a23dfa85773c77648f619ad0f9df554\\b.* (re)\n  $ rm examples/fail.t.err\n\nRun examples with bash:\n\n  $ cram --shell=/bin/bash -q examples examples/fail.t\n  .s.!.s.\n  # Ran 7 tests, 2 skipped, 1 failed.\n  [1]\n  $ md5 examples/fail.t examples/fail.t.err\n  .*\\b0f598c2b7b8ca5bcb8880e492ff6b452\\b.* (re)\n  .*\\b7a23dfa85773c77648f619ad0f9df554\\b.* (re)\n  $ rm examples/fail.t.err\n\nVerbose mode:\n\n  $ cram -q -v examples examples/fail.t\n  examples/bare.t: passed\n  examples/empty.t: empty\n  examples/env.t: passed\n  examples/fail.t: failed\n  examples/missingeol.t: passed\n  examples/skip.t: skipped\n  examples/test.t: passed\n  # Ran 7 tests, 2 skipped, 1 failed.\n  [1]\n  $ md5 examples/fail.t examples/fail.t.err\n  .*\\b0f598c2b7b8ca5bcb8880e492ff6b452\\b.* (re)\n  .*\\b7a23dfa85773c77648f619ad0f9df554\\b.* (re)\n  $ rm examples/fail.t.err\n\nTest that a fixed .err file is deleted:\n\n  $ echo \"  $ echo 1\" > fixed.t\n  $ cram fixed.t\n  !\n  --- fixed.t\n  +++ fixed.t.err\n  @@ -1,1 +1,2 @@\n     $ echo 1\n  +  1\n  \n  # Ran 1 tests, 0 skipped, 1 failed.\n  [1]\n  $ cp fixed.t.err fixed.t\n  $ cram fixed.t\n  .\n  # Ran 1 tests, 0 skipped, 0 failed.\n  $ test \\! -f fixed.t.err\n  $ rm fixed.t\n\nDon't sterilize environment:\n\n  $ TZ=foo; export TZ\n  $ CDPATH=foo; export CDPATH\n  $ GREP_OPTIONS=foo; export GREP_OPTIONS\n  $ cram -E examples/env.t\n  !\n  \\-\\-\\- examples/env\\.t\\s* (re)\n  \\+\\+\\+ examples/env\\.t\\.err\\s* (re)\n  @@ -7,11 +7,11 @@\n     $ echo \"$LANGUAGE\"\n     C\n     $ echo \"$TZ\"\n  -  GMT\n  +  foo\n     $ echo \"$CDPATH\"\n  -  \n  +  foo\n     $ echo \"$GREP_OPTIONS\"\n  -  \n  +  foo\n     $ echo \"$CRAMTMP\"\n     .+ (re)\n     $ echo \"$TESTDIR\"\n  \n  # Ran 1 tests, 0 skipped, 1 failed.\n  [1]\n  $ rm examples/env.t.err\n\nNote: We can't set the locale to foo because some shells will issue\nwarnings for invalid locales.\n\nTest --keep-tmpdir:\n\n  $ cram -q --keep-tmpdir examples/test.t | while read line; do\n  >   echo \"$line\" 1>&2\n  >   msg=`echo \"$line\" | cut -d ' ' -f 1-4`\n  >   if [ \"$msg\" = '# Kept temporary directory:' ]; then\n  >     echo \"$line\" | cut -d ' ' -f 5\n  >   fi\n  > done > keeptmp\n  .\n  # Ran 1 tests, 0 skipped, 0 failed.\n  # Kept temporary directory: */cramtests-* (glob)\n  $ ls \"`cat keeptmp`\" | sort\n  test.t\n  tmp\n\nCustom indentation:\n\n  $ cat > indent.t <<EOF\n  > Indented by 4 spaces:\n  > \n  >     $ echo foo\n  >     foo\n  > \n  > Not part of the test:\n  > \n  >   $ echo foo\n  >   bar\n  > EOF\n  $ cram --indent=4 indent.t\n  .\n  # Ran 1 tests, 0 skipped, 0 failed.\n\nTest running tests with the same filename in different directories:\n\n  $ mkdir subdir1 subdir2\n  $ cat > subdir1/test.t <<EOF\n  >   $ echo 1\n  > EOF\n  $ cat > subdir2/test.t <<EOF\n  >   $ echo 2\n  > EOF\n  $ cram subdir1 subdir2\n  !\n  --- subdir1/test.t\n  +++ subdir1/test.t.err\n  @@ -1,1 +1,2 @@\n     $ echo 1\n  +  1\n  !\n  --- subdir2/test.t\n  +++ subdir2/test.t.err\n  @@ -1,1 +1,2 @@\n     $ echo 2\n  +  2\n  \n  # Ran 2 tests, 0 skipped, 2 failed.\n  [1]\n"
  },
  {
    "path": "tests/usage.t",
    "content": "Set up cram alias and example tests:\n\n  $ . \"$TESTDIR\"/setup.sh\n\nUsage:\n\n  $ cram -h\n  [Uu]sage: cram \\[OPTIONS\\] TESTS\\.\\.\\. (re)\n  \n  [Oo]ptions: (re)\n    -h, --help          show this help message and exit\n    -V, --version       show version information and exit\n    -q, --quiet         don't print diffs\n    -v, --verbose       show filenames and test status\n    -i, --interactive   interactively merge changed test output\n    -d, --debug         write script output directly to the terminal\n    -y, --yes           answer yes to all questions\n    -n, --no            answer no to all questions\n    -E, --preserve-env  don't reset common environment variables\n    --keep-tmpdir       keep temporary directories\n    --shell=PATH        shell to use for running tests (default: /bin/sh)\n    --shell-opts=OPTS   arguments to invoke shell with\n    --indent=NUM        number of spaces to use for indentation (default: 2)\n    --xunit-file=PATH   path to write xUnit XML output\n  $ cram -V\n  Cram CLI testing framework (version 0.8)\n  \n  Copyright (C) 2010-2021 Brodie Rao <brodie@bitheap.org> and others\n  This is free software; see the source for copying conditions. There is NO\n  warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n  $ cram\n  [Uu]sage: cram \\[OPTIONS\\] TESTS\\.\\.\\. (re)\n  [2]\n  $ cram -y -n\n  options --yes and --no are mutually exclusive\n  [2]\n  $ cram non-existent also-not-here\n  no such file: non-existent\n  [2]\n  $ mkdir empty\n  $ cram empty\n  no tests found\n  [2]\n  $ cram --shell=./badsh\n  shell not found: ./badsh\n  [2]\n"
  },
  {
    "path": "tests/xunit.t",
    "content": "Set up cram alias and example tests:\n\n  $ . \"$TESTDIR\"/setup.sh\n\nxUnit XML output:\n\n  $ cram -q -v --xunit-file=cram.xml examples\n  examples/bare.t: passed\n  examples/empty.t: empty\n  examples/env.t: passed\n  examples/fail.t: failed\n  examples/missingeol.t: passed\n  examples/skip.t: skipped\n  examples/test.t: passed\n  # Ran 7 tests, 2 skipped, 1 failed.\n  [1]\n  $ cat cram.xml\n  <?xml version=\"1.0\" encoding=\"utf-8\"?>\n  <testsuite name=\"cram\"\n             tests=\"7\"\n             failures=\"1\"\n             skipped=\"2\"\n             timestamp=\"\\d+-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\+\\d{2}:\\d{2}\" (re)\n             hostname=\"[^\"]+\" (re)\n             time=\"\\d+\\.\\d{6}\"> (re)\n    <testcase classname=\"examples/bare.t\"\n              name=\"bare.t\"\n              time=\"\\d+\\.\\d{6}\"/> (re)\n    <testcase classname=\"examples/empty.t\"\n              name=\"empty.t\"\n              time=\"\\d+\\.\\d{6}\"> (re)\n      <skipped/>\n    </testcase>\n    <testcase classname=\"examples/env.t\"\n              name=\"env.t\"\n              time=\"\\d+\\.\\d{6}\"/> (re)\n    <testcase classname=\"examples/fail.t\"\n              name=\"fail.t\"\n              time=\"\\d+\\.\\d{6}\"> (re)\n      <failure><![CDATA[--- examples/fail.t\n  +++ examples/fail.t.err\n  @@ -1,18 +1,18 @@\n   Output needing escaping:\n   \n     $ printf '\\00\\01\\02\\03\\04\\05\\06\\07\\010\\011\\013\\014\\016\\017\\020\\021\\022\\n'\n  -  foo\n  +  \\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\t\\x0b\\x0c\\x0e\\x0f\\x10\\x11\\x12 (esc)\n     $ printf '\\023\\024\\025\\026\\027\\030\\031\\032\\033\\034\\035\\036\\037\\040\\047\\n'\n  -  bar\n  +  \\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f ' (esc)\n   \n   Wrong output and bad regexes:\n   \n     $ echo 1\n  -  2\n  +  1\n     $ printf '1\\nfoo\\n1\\n'\n  -  +++ (re)\n  -  foo\\ (re)\n  -   (re)\n  +  1\n  +  foo\n  +  1\n   \n   Filler to force a second diff hunk:\n   \n  @@ -20,5 +20,6 @@\n   Offset regular expression:\n   \n     $ printf 'foo\\n\\n1\\n'\n  +  foo\n     \n     \\d (re)\n  ]]></failure>\n    </testcase>\n    <testcase classname=\"examples/missingeol.t\"\n              name=\"missingeol.t\"\n              time=\"\\d+\\.\\d{6}\"/> (re)\n    <testcase classname=\"examples/skip.t\"\n              name=\"skip.t\"\n              time=\"\\d+\\.\\d{6}\"> (re)\n      <skipped/>\n    </testcase>\n    <testcase classname=\"examples/test.t\"\n              name=\"test.t\"\n              time=\"\\d+\\.\\d{6}\"/> (re)\n  </testsuite>\n  $ rm cram.xml examples/fail.t.err\n"
  }
]