[
  {
    "path": ".github/workflows/main.yml",
    "content": "name: Run all tests\n\non: [push, pull_request]\n\nenv:\n  PIP: \"env PIP_DISABLE_PIP_VERSION_CHECK=1\n            PYTHONWARNINGS=ignore:DEPRECATION\n            pip --no-cache-dir\"\n\njobs:\n  tests_py2x:\n    runs-on: ubuntu-22.04\n    container:\n      image: python:2.7\n    strategy:\n      fail-fast: false\n      matrix:\n        toxenv: [py27, py27-configparser]\n\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Install dependencies\n      run: $PIP install virtualenv tox\n\n    - name: Run the unit tests\n      run: TOXENV=${{ matrix.toxenv }} tox\n\n    - name: Run the end-to-end tests\n      run: TOXENV=${{ matrix.toxenv }} END_TO_END=1 tox\n\n  tests_py34:\n    runs-on: ubuntu-22.04\n    container:\n      image: ubuntu:20.04\n      env:\n        LANG: C.UTF-8\n\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Install build dependencies\n      run: |\n        apt-get update\n        apt-get install -y build-essential unzip wget \\\n                           libncurses5-dev libgdbm-dev libnss3-dev \\\n                           libreadline-dev zlib1g-dev\n\n    - name: Build OpenSSL 1.0.2 (required by Python 3.4)\n      run: |\n        cd $RUNNER_TEMP\n        wget https://github.com/openssl/openssl/releases/download/OpenSSL_1_0_2u/openssl-1.0.2u.tar.gz\n        tar -xf openssl-1.0.2u.tar.gz\n        cd openssl-1.0.2u\n        ./config --prefix=/usr/local/ssl --openssldir=/usr/local/ssl shared zlib-dynamic\n        make\n        make install\n\n        echo CFLAGS=\"-I/usr/local/ssl/include $CFLAGS\" >> $GITHUB_ENV\n        echo LDFLAGS=\"-L/usr/local/ssl/lib $LDFLAGS\" >> $GITHUB_ENV\n        echo LD_LIBRARY_PATH=\"/usr/local/ssl/lib:$LD_LIBRARY_PATH\" >> $GITHUB_ENV\n\n        ln -s /usr/local/ssl/lib/libssl.so.1.0.0 /usr/lib/libssl.so.1.0.0\n        ln -s /usr/local/ssl/lib/libcrypto.so.1.0.0 /usr/lib/libcrypto.so.1.0.0\n        ldconfig\n\n    - name: Build Python 3.4\n      run: |\n        cd $RUNNER_TEMP\n        wget -O cpython-3.4.10.zip https://github.com/python/cpython/archive/refs/tags/v3.4.10.zip\n        unzip cpython-3.4.10.zip\n        cd cpython-3.4.10\n        ./configure --with-ensurepip=install\n        make\n        make install\n\n        python3.4 --version\n        python3.4 -c 'import ssl'\n        pip3.4 --version\n\n        ln -s /usr/local/bin/python3.4 /usr/local/bin/python\n        ln -s /usr/local/bin/pip3.4 /usr/local/bin/pip\n\n    - name: Install Python dependencies\n      run: |\n        $PIP install virtualenv==20.4.7 tox==3.14.0\n\n    - name: Run the unit tests\n      run: TOXENV=py34 tox\n\n    - name: Run the end-to-end tests\n      run: TOXENV=py34 END_TO_END=1 tox\n\n  tests_py35:\n    runs-on: ubuntu-22.04\n    container:\n      image: python:3.5\n    strategy:\n      fail-fast: false\n\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Install dependencies\n      run: $PIP install virtualenv tox\n\n    - name: Run the unit tests\n      run: TOXENV=py35 tox\n\n    - name: Run the end-to-end tests\n      run: TOXENV=py35 END_TO_END=1 tox\n\n  tests_py36:\n    runs-on: ubuntu-22.04\n    container:\n      image: python:3.6\n    strategy:\n      fail-fast: false\n\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Install dependencies\n      run: $PIP install virtualenv tox\n\n    - name: Run the unit tests\n      run: TOXENV=py36 tox\n\n    - name: Run the end-to-end tests\n      run: TOXENV=py36 END_TO_END=1 tox\n\n  tests_py3x:\n    runs-on: ubuntu-22.04\n    strategy:\n      fail-fast: false\n      matrix:\n        python-version: [3.7, 3.8, 3.9, \"3.10\", 3.11, 3.12, 3.13, 3.14]\n\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v5\n      with:\n        python-version: ${{ matrix.python-version }}\n\n    - name: Install dependencies\n      run: $PIP install virtualenv tox\n\n    - name: Set variable for TOXENV based on Python version\n      id: toxenv\n      run: python -c 'import sys; print(\"TOXENV=py%d%d\" % (sys.version_info.major, sys.version_info.minor))' | tee -a $GITHUB_OUTPUT\n\n    - name: Run the unit tests\n      run: TOXENV=${{steps.toxenv.outputs.TOXENV}} tox\n\n    - name: Run the end-to-end tests\n      run: TOXENV=${{steps.toxenv.outputs.TOXENV}} END_TO_END=1 tox\n\n  coverage_py27:\n    runs-on: ubuntu-22.04\n    container:\n      image: python:2.7\n    strategy:\n      fail-fast: false\n\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Install dependencies\n      run: $PIP install virtualenv tox\n\n    - name: Run unit test coverage\n      run: TOXENV=cover tox\n\n  coverage_py3x:\n    runs-on: ubuntu-22.04\n    strategy:\n      fail-fast: false\n      matrix:\n        python-version: [3.8]\n\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v5\n      with:\n        python-version: ${{ matrix.python-version }}\n\n    - name: Install dependencies\n      run: $PIP install virtualenv tox\n\n    - name: Run unit test coverage\n      run: TOXENV=cover3 tox\n\n  docs:\n    runs-on: ubuntu-22.04\n\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v5\n      with:\n        python-version: \"3.8\"\n\n    - name: Install dependencies\n      run: $PIP install virtualenv tox>=4.0.0\n\n    - name: Build the docs\n      run: TOXENV=docs tox\n"
  },
  {
    "path": ".gitignore",
    "content": "*~\n*.egg\n*.egg-info\n*.log\n*.pyc\n*.pyo\n*.swp\n*.pss\n.DS_Store\n.coverage*\n.eggs/\n.pytest_cache/\n.tox/\nbuild/\ndocs/.build/\ndist/\nenv*/\nvenv*/\nhtmlcov/\ntmp/\ncoverage.xml\nnosetests.xml\n.cache/\n"
  },
  {
    "path": ".readthedocs.yaml",
    "content": "# .readthedocs.yaml\n# Read the Docs configuration file\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details\n\n# Required\nversion: 2\n\n# Set the version of Python and other tools you might need\nbuild:\n  os: ubuntu-22.04\n  tools:\n    python: \"3.11\"\n\n# Build documentation in the docs/ directory with Sphinx\nsphinx:\n  configuration: docs/conf.py\n\n# We recommend specifying your dependencies to enable reproducible builds:\n# https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html\n# python:\n#   install:\n#   - requirements: docs/requirements.txt\n\n"
  },
  {
    "path": "CHANGES.rst",
    "content": "4.4.0.dev0 (Next Release)\n-------------------------\n\n- Fixed a bug where ``supervisord`` would wait 1 second on startup before\n  starting any programs.  Patch by Stepan Blyshchak.\n\n- Fixed a bug where the XML-RPC method ``supervisor.getAllConfigInfo()``\n  did not return the value of the ``autorestart`` program option.\n\n- Fixed a bug where an escaped percent sign (``%%``) could not be used\n  in ``environment=`` in the ``[supervisord]`` section of the config file.\n  The bug did not affect ``[program:x]`` sections, where an escaped\n  percent sign in ``environment=`` already worked.  Patch by yuk1pedia.\n\n- Parsing ``environment=`` in the config file now uses ``shlex`` in POSIX\n  mode instead of legacy mode to allow for escaped quotes in the values.\n  However, on Python 2 before 2.7.13 and Python 3 before 3.5.3, POSIX mode\n  can't be used because of a `bug <https://bugs.python.org/issue21999>`_\n  in ``shlex``.  If ``supervisord`` is run on a Python version with the bug,\n  it will fall back to legacy mode.  Patch by Stefan Friesel.\n\n- The old example scripts in the ``supervisor/scripts/`` directory of\n  the package, which were largely undocumented, had no test coverage, and\n  were last updated over a decade ago, have been removed.\n\n- When importing a plugin fails, the error message printed by ``supervisord``\n  now includes the Python exception message for easier debugging.\n  Patch by Sandro Jäckel.\n\n4.3.0 (2025-08-23)\n------------------\n\n- Fixed a bug where the poller would not unregister a closed\n  file descriptor under some circumstances, which caused excessive\n  polling, resulting in higher CPU usage.  Patch by aftersnow.\n\n- Fixed a bug where restarting ``supervisord`` may have failed with\n  the message ``Error: Another program is already listening\n  on a port that one of our HTTP servers is configured to use.``\n  if an HTTP request was made during restart.  Patch by Julien Le Cléach.\n\n- Fixed a unit test that failed only on Python 3.13.  Only test code was\n  changed; no changes to ``supervisord`` itself.  Patch by Colin Watson.\n\n- On Python 3.8 and later, ``setuptools`` is no longer a runtime\n  dependency.  Patch by Ofek Lev.\n\n- On Python versions before 3.8, ``setuptools`` is still a runtime\n  dependency (for ``pkg_resources``) but it is no longer declared in\n  ``setup.py`` as such.  This is because adding a conditional dependency\n  with an environment marker (``setuptools; python_version < '3.8'``)\n  breaks installation in some scenarios, e.g. ``setup.py install`` or\n  older versions of ``pip``.  Ensure that ``setuptools`` is installed\n  if using Python before 3.8.\n\n4.2.5 (2022-12-23)\n------------------\n\n- Fixed a bug where the XML-RPC method ``supervisor.startProcess()`` would\n  return 500 Internal Server Error instead of an XML-RPC fault response\n  if the command could not be parsed.  Patch by Julien Le Cléach.\n\n- Fixed a bug on Python 2.7 where a ``UnicodeDecodeError`` may have occurred\n  when using the web interface.  Patch by Vinay Sajip.\n\n- Removed use of ``urllib.parse`` functions ``splithost``, ``splitport``, and\n  ``splittype`` deprecated in Python 3.8.\n\n- Removed use of ``asynchat`` and ``asyncore`` deprecated in Python 3.10.\n\n- The return value of the XML-RPC method ``supervisor.getAllConfigInfo()``\n  now includes the ``directory``, ``uid``, and ``serverurl`` of the\n  program.  Patch by Yellmean.\n\n- If a subprocess exits with a unexpected exit code (one not listed in\n  ``exitcodes=`` in a ``[program:x]`` section) then the exit will now be logged\n  at the ``WARN`` level instead of ``INFO``.  Patch by Precy Lee.\n\n- ``supervisorctl shutdown`` now shows an error message if an argument is\n  given.\n\n- File descriptors are now closed using the faster ``os.closerange()`` instead\n  of calling ``os.close()`` in a loop.  Patch by tyong920.\n\n4.2.4 (2021-12-30)\n------------------\n\n- Fixed a bug where the ``--identifier`` command line argument was ignored.\n  It was broken since at least 3.0a7 (released in 2009) and probably earlier.\n  Patch by Julien Le Cléach.\n\n4.2.3 (2021-12-27)\n------------------\n\n- Fixed a race condition where an ``rpcinterface`` extension that subscribed\n  to events would not see the correct process state if it accessed the\n  the ``state`` attribute on a ``Subprocess`` instance immediately in the\n  event callback.  Patch by Chao Wang.\n\n- Added the ``setuptools`` package to the list of dependencies in\n  ``setup.py`` because it is a runtime dependency.  Patch by Louis Sautier.\n\n- The web interface will now return a 404 Not Found response if a log file\n  is missing.  Previously, it would return 410 Gone.  It was changed because\n  410 is intended to mean that the condition is likely to be permanent.  A\n  log file missing is usually temporary, e.g. a process that was never started\n  will not have a log file but will have one as soon as it is started.\n\n4.2.2 (2021-02-26)\n------------------\n\n- Fixed a bug where ``supervisord`` could crash if a subprocess exited\n  immediately before trying to kill it.\n\n- Fixed a bug where the ``stdout_syslog`` and ``stderr_syslog`` options\n  of a ``[program:x]`` section could not be used unless file logging for\n  the same program had also been configured.  The file and syslog options\n  can now be used independently.  Patch by Scott Stroupe.\n\n- Fixed a bug where the ``logfile`` option in the ``[supervisord]``\n  section would not log to syslog when the special filename of\n  ``syslog`` was supplied, as is supported by all other log filename\n  options.  Patch by Franck Cuny.\n\n- Fixed a bug where environment variables defined in ``environment=``\n  in the ``[supervisord]`` section or a ``[program:x]`` section could\n  not be used in ``%(ENV_x)s`` expansions.  Patch by MythRen.\n\n- The  ``supervisorctl signal`` command now allows a signal to be sent\n  when a process is in the ``STOPPING`` state.  Patch by Mike Gould.\n\n- ``supervisorctl`` and ``supervisord`` now print help when given ``-?``\n  in addition to the existing ``-h``/``--help``.\n\n4.2.1 (2020-08-20)\n------------------\n\n- Fixed a bug on Python 3 where a network error could cause ``supervisord``\n  to crash with the error ``<class 'TypeError'>:can't concat str to bytes``.\n  Patch by Vinay Sajip.\n\n- Fixed a bug where a test would fail on systems with glibc 2.3.1 because\n  the default value of SOMAXCONN changed.\n\n4.2.0 (2020-04-30)\n------------------\n\n- When ``supervisord`` is run in the foreground, a new ``--silent`` option\n  suppresses the main log from being echoed to ``stdout`` as it normally\n  would.  Patch by Trevor Foster.\n\n- Parsing ``command=`` now supports a new expansion, ``%(numprocs)d``, that\n  expands to the value of ``numprocs=`` in the same section.  Patch by\n  Santjago Corkez.\n\n- Web UI buttons no longer use background images.  Patch by Dmytro Karpovych.\n\n- The Web UI now has a link to view ``tail -f stderr`` for a process in\n  addition to the existing ``tail -f stdout`` link.  Based on a\n  patch by OuroborosCoding.\n\n- The HTTP server will now send an ``X-Accel-Buffering: no`` header in\n  logtail responses to fix Nginx proxy buffering.  Patch by Weizhao Li.\n\n- When ``supervisord`` reaps an unknown PID, it will now log a description\n  of the ``waitpid`` status.  Patch by Andrey Zelenchuk.\n\n- Fixed a bug introduced in 4.0.3 where ``supervisorctl tail -f foo | grep bar``\n  would fail with the error ``NoneType object has no attribute 'lower'``.  This\n  only occurred on Python 2.7 and only when piped.  Patch by Slawa Pidgorny.\n\n4.1.0 (2019-10-19)\n------------------\n\n- Fixed a bug on Python 3 only where logging to syslog did not work and\n  would log the exception ``TypeError: a bytes-like object is required, not 'str'``\n  to the main ``supervisord`` log file.  Patch by Vinay Sajip and Josh Staley.\n\n- Fixed a Python 3.8 compatibility issue caused by the removal of\n  ``cgi.escape()``.  Patch by Mattia Procopio.\n\n- The ``meld3`` package is no longer a dependency.  A version of ``meld3``\n  is now included within the ``supervisor`` package itself.\n\n4.0.4 (2019-07-15)\n------------------\n\n- Fixed a bug where ``supervisorctl tail <name> stdout`` would actually tail\n  ``stderr``.  Note that ``tail <name>`` without the explicit ``stdout``\n  correctly tailed ``stdout``.  The bug existed since 3.0a3 (released in\n  2007).  Patch by Arseny Hofman.\n\n- Improved the warning message added in 4.0.3 so it is now emitted for\n  both ``tail`` and ``tail -f``.  Patch by Vinay Sajip.\n\n- CVE-2019-12105.  Documentation addition only, no code changes.  This CVE\n  states that ``inet_http_server`` does not use authentication by default\n  (`details <https://github.com/Supervisor/supervisor/issues/1245>`_).  Note that\n  ``inet_http_server`` is not enabled by default, and is also not enabled\n  in the example configuration output by ``echo_supervisord_conf``.  The\n  behavior of the ``inet_http_server`` options have been correctly documented,\n  and have not changed, since the feature was introduced in 2006.  A new\n  `warning message <https://github.com/Supervisor/supervisor/commit/4e334d9cf2a1daff685893e35e72398437df3dcb>`_\n  was added to the documentation.\n\n4.0.3 (2019-05-22)\n------------------\n\n- Fixed an issue on Python 2 where running ``supervisorctl tail -f <name>``\n  would fail with the message\n  ``Cannot connect, error: <type 'exceptions.UnicodeEncodeError'>`` where it\n  may have worked on Supervisor 3.x.  The issue was introduced in Supervisor\n  4.0.0 due to new bytes/strings conversions necessary to add Python 3 support.\n  For ``supervisorctl`` to correctly display logs with Unicode characters, the\n  terminal encoding specified by the environment must support it.  If not, the\n  ``UnicodeEncodeError`` may still occur on either Python 2 or 3.  A new\n  warning message is now printed if a problematic terminal encoding is\n  detected.  Patch by Vinay Sajip.\n\n4.0.2 (2019-04-17)\n------------------\n\n- Fixed a bug where inline comments in the config file were not parsed\n  correctly such that the comments were included as part of the values.\n  This only occurred on Python 2, and only where the environment had an\n  extra ``configparser`` module installed.  The bug was introduced in\n  Supervisor 4.0.0 because of Python 2/3 compatibility code that expected\n  a Python 2 environment to only have a ``ConfigParser`` module.\n\n4.0.1 (2019-04-10)\n------------------\n\n- Fixed an issue on Python 3 where an ``OSError: [Errno 29] Illegal seek``\n  would occur if ``logfile`` in the ``[supervisord]`` section was set to\n  a special file like ``/dev/stdout`` that was not seekable, even if\n  ``logfile_maxbytes = 0`` was set to disable rotation.  The issue only\n  affected the main log and not child logs.  Patch by Martin Falatic.\n\n4.0.0 (2019-04-05)\n------------------\n\n- Support for Python 3 has been added.  On Python 3, Supervisor requires\n  Python 3.4 or later.  Many thanks to Vinay Sajip, Scott Maxwell, Palm Kevin,\n  Tres Seaver, Marc Abramowitz, Son Nguyen, Shane Hathaway, Evan Andrews,\n  and Ethan Hann who all made major contributions to the Python 3 porting\n  effort.  Thanks also to all contributors who submitted issue reports and\n  patches towards this effort.\n\n- Support for Python 2.4, 2.5, and 2.6 has been dropped.  On Python 2,\n  Supervisor now requires Python 2.7.\n\n- The ``supervisor`` package is no longer a namespace package.\n\n- The behavior of the config file expansion ``%(here)s`` has changed.  In\n  previous versions, a bug caused ``%(here)s`` to always expand to the\n  directory of the root config file.  Now, when ``%(here)s`` is used inside\n  a file included via ``[include]``, it will expand to the directory of\n  that file.  Thanks to Alex Eftimie and Zoltan Toth-Czifra for the patches.\n\n- The default value for the config file setting ``exitcodes=``, the expected\n  exit codes of a program, has changed.  In previous versions, it was ``0,2``.\n  This caused issues with Golang programs where ``panic()`` causes the exit\n  code to be ``2``.  The default value for ``exitcodes`` is now ``0``.\n\n- An undocumented feature where multiple ``supervisorctl`` commands could be\n  combined on a single line separated by semicolons has been removed.\n\n- ``supervisorctl`` will now set its exit code to a non-zero value when an\n  error condition occurs.  Previous versions did not set the exit code for\n  most error conditions so it was almost always 0.  Patch by Luke Weber.\n\n- Added new ``stdout_syslog`` and ``stderr_syslog`` options to the config\n  file.  These are boolean options that indicate whether process output will\n  be sent to syslog.  Supervisor can now log to both files and syslog at the\n  same time.  Specifying a log filename of ``syslog`` is still supported\n  but deprecated.  Patch by Jason R. Coombs.\n\n3.4.0 (2019-04-05)\n------------------\n\n- FastCGI programs (``[fcgi-program:x]`` sections) can now be used in\n  groups (``[group:x]``).  Patch by Florian Apolloner.\n\n- Added a new ``socket_backlog`` option to the ``[fcgi-program:x]`` section\n  to set the listen(2) socket backlog.  Patch by Nenad Merdanovic.\n\n- Fixed a bug where ``SupervisorTransport`` (the XML-RPC transport used with\n  Unix domain sockets) did not close the connection when ``close()`` was\n  called on it.  Patch by Jérome Perrin.\n\n- Fixed a bug where ``supervisorctl start <name>`` could hang for a long time\n  if the system clock rolled back.  Patch by Joe LeVeque.\n\n3.3.5 (2018-12-22)\n------------------\n\n- Fixed a race condition where ``supervisord`` would cancel a shutdown\n  already in progress if it received ``SIGHUP``.  Now, ``supervisord`` will\n  ignore ``SIGHUP`` if shutdown is already in progress.  Patch by Livanh.\n\n- Fixed a bug where searching for a relative command ignored changes to\n  ``PATH`` made in ``environment=``.  Based on a patch by dongweiming.\n\n- ``childutils.ProcessCommunicationsProtocol`` now does an explicit\n  ``flush()`` after writing to ``stdout``.\n\n- A more descriptive error message is now emitted if a name in the config\n  file contains a disallowed character.  Patch by Rick van Hattem.\n\n3.3.4 (2018-02-15)\n------------------\n\n- Fixed a bug where rereading the configuration would not detect changes to\n  eventlisteners.  Patch by Michael Ihde.\n\n- Fixed a bug where the warning ``Supervisord is running as root and it is\n  searching for its config file`` may have been incorrectly shown by\n  ``supervisorctl`` if its executable name was changed.\n\n- Fixed a bug where ``supervisord`` would continue starting up if the\n  ``[supervisord]`` section of the config file specified ``user=`` but\n  ``setuid()`` to that user failed.  It will now exit immediately if it\n  cannot drop privileges.\n\n- Fixed a bug in the web interface where redirect URLs did not have a slash\n  between the host and query string, which caused issues when proxying with\n  Nginx.  Patch by Luke Weber.\n\n- When ``supervisord`` successfully drops privileges during startup, it is now\n  logged at the ``INFO`` level instead of ``CRIT``.\n\n- The HTTP server now returns a Content-Type header specifying UTF-8 encoding.\n  This may fix display issues in some browsers.  Patch by Katenkka.\n\n3.3.3 (2017-07-24)\n------------------\n\n- Fixed CVE-2017-11610.  A vulnerability was found where an authenticated\n  client can send a malicious XML-RPC request to ``supervisord`` that will\n  run arbitrary shell commands on the server.  The commands will be run as\n  the same user as ``supervisord``.  Depending on how ``supervisord`` has been\n  configured, this may be root.  See\n  https://github.com/Supervisor/supervisor/issues/964 for details.\n\n3.3.2 (2017-06-03)\n------------------\n\n- Fixed a bug introduced in 3.3.0 where the ``supervisorctl reload`` command\n  would crash ``supervisord`` with the error ``OSError: [Errno 9] Bad file\n  descriptor`` if the ``kqueue`` poller was used.  Patch by Jared Suttles.\n\n- Fixed a bug introduced in 3.3.0 where ``supervisord`` could get stuck in a\n  polling loop after the web interface was used, causing high CPU usage.\n  Patch by Jared Suttles.\n\n- Fixed a bug where if ``supervisord`` attempted to start but aborted due to\n  another running instance of ``supervisord`` with the same config, the\n  pidfile of the running instance would be deleted.  Patch by coldnight.\n\n- Fixed a bug where ``supervisorctl fg`` would swallow most XML-RPC faults.\n  ``fg`` now prints the fault and exits.\n\n- Parsing the config file will now fail with an error message if a process\n  or group name contains a forward slash character (``/``) since it would\n  break the URLs used by the web interface.\n\n- ``supervisorctl reload`` now shows an error message if an argument is\n  given.  Patch by Joel Krauska.\n\n- ``supervisorctl`` commands ``avail``, ``reread``, and ``version`` now show\n  an error message if an argument is given.\n\n3.3.1 (2016-08-02)\n------------------\n\n- Fixed an issue where ``supervisord`` could hang when responding to HTTP\n  requests (including ``supervisorctl`` commands) if the system time was set\n  back after ``supervisord`` was started.\n\n- Zope ``trackrefs``, a debugging tool that was included in the ``tests``\n  directory but hadn't been used for years, has been removed.\n\n3.3.0 (2016-05-14)\n------------------\n\n- ``supervisord`` will now use ``kqueue``, ``poll``, or ``select`` to monitor\n  its file descriptors, in that order, depending on what is available on the\n  system.  Previous versions used ``select`` only and would crash with the error\n  ``ValueError: filedescriptor out of range in select()`` when running a large\n  number of subprocesses (whatever number resulted in enough file descriptors\n  to exceed the fixed-size file descriptor table used by ``select``, which is\n  typically 1024).  Patch by Igor Sobreira.\n\n- ``/etc/supervisor/supervisord.conf`` has been added to the config file search\n  paths.  Many versions of Supervisor packaged for Debian and Ubuntu have\n  included a patch that added this path.  This difference was reported in a\n  number of tickets as a source of confusion and upgrade difficulties, so the\n  path has been added.  Patch by Kelvin Wong.\n\n- Glob patterns in the ``[include]`` section now support the\n  ``host_node_name`` expansion.  Patch by Paul Lockaby.\n\n- Files included via the ``[include]`` section are now logged at the ``INFO``\n  level instead of ``WARN``.  Patch by Daniel Hahler.\n\n3.2.4 (2017-07-24)\n------------------\n\n- Backported from Supervisor 3.3.3:  Fixed CVE-2017-11610.  A vulnerability\n  was found where an authenticated client can send a malicious XML-RPC request\n  to ``supervisord`` that will run arbitrary shell commands on the server.\n  The commands will be run as the same user as ``supervisord``.  Depending on\n  how ``supervisord`` has been configured, this may be root.  See\n  https://github.com/Supervisor/supervisor/issues/964 for details.\n\n3.2.3 (2016-03-19)\n------------------\n\n- 400 Bad Request is now returned if an XML-RPC request is received with\n  invalid body data.  In previous versions, 500 Internal Server Error\n  was returned.\n\n3.2.2 (2016-03-04)\n------------------\n\n- Parsing the config file will now fail with an error message if an\n  ``inet_http_server`` or ``unix_http_server`` section contains a ``username=``\n  but no ``password=``.  In previous versions, ``supervisord`` would start with\n  this invalid configuration but the HTTP server would always return a 500\n  Internal Server Error.  Thanks to Chris Ergatides for reporting this issue.\n\n3.2.1 (2016-02-06)\n------------------\n\n- Fixed a server exception ``OverflowError: int exceeds XML-RPC limits`` that\n  made ``supervisorctl status`` unusable if the system time was far into the\n  future.  The XML-RPC API returns timestamps as XML-RPC integers, but\n  timestamps will exceed the maximum value of an XML-RPC integer in January\n  2038 (\"Year 2038 Problem\").  For now, timestamps exceeding the maximum\n  integer will be capped at the maximum to avoid the exception and retain\n  compatibility with existing API clients.  In a future version of the API,\n  the return type for timestamps will be changed.\n\n3.2.0 (2015-11-30)\n------------------\n\n- Files included via the ``[include]`` section are read in sorted order.  In\n  past versions, the order was undefined.  Patch by Ionel Cristian Mărieș.\n\n- ``supervisorctl start`` and ``supervisorctl stop`` now complete more quickly\n  when handling many processes.  Thanks to Chris McDonough for this patch.\n  See: https://github.com/Supervisor/supervisor/issues/131\n\n- Environment variables are now expanded for all config file options.\n  Patch by Dexter Tad-y.\n\n- Added ``signalProcess``, ``signalProcessGroup``, and ``signalAllProcesses``\n  XML-RPC methods to supervisor RPC interface.  Thanks to Casey Callendrello,\n  Marc Abramowitz, and Moriyoshi Koizumi for the patches.\n\n- Added ``signal`` command to supervisorctl.  Thanks to Moriyoshi Koizumi and\n  Marc Abramowitz for the patches.\n\n- Errors caused by bad values in a config file now show the config section\n  to make debugging easier.  Patch by Marc Abramowitz.\n\n- Setting ``redirect_stderr=true`` in an ``[eventlistener:x]`` section is now\n  disallowed because any messages written to ``stderr`` would interfere\n  with the eventlistener protocol on ``stdout``.\n\n- Fixed a bug where spawning a process could cause ``supervisord`` to crash\n  if an ``IOError`` occurred while setting up logging.  One way this could\n  happen is if a log filename was accidentally set to a directory instead\n  of a file.  Thanks to Grzegorz Nosek for reporting this issue.\n\n- Fixed a bug introduced in 3.1.0 where ``supervisord`` could crash when\n  attempting to display a resource limit error.\n\n- Fixed a bug where ``supervisord`` could crash with the message\n  ``Assertion failed for processname: RUNNING not in STARTING`` if a time\n  change caused the last start time of the process to be in the future.\n  Thanks to Róbert Nagy, Sergey Leschenko, and samhair for the patches.\n\n- A warning is now logged if an eventlistener enters the UNKNOWN state,\n  which usually indicates a bug in the eventlistener.  Thanks to Steve\n  Winton and detailyang for reporting issues that led to this change.\n\n- Errors from the web interface are now logged at the ``ERROR`` level.\n  Previously, they were logged at the ``TRACE`` level and easily\n  missed.  Thanks to Thomas Güttler for reporting this issue.\n\n- Fixed ``DeprecationWarning: Parameters to load are deprecated. Call\n  .resolve and .require separately.`` on setuptools >= 11.3.\n\n- If ``redirect_stderr=true`` and ``stderr_logfile=auto``, no stderr log\n  file will be created.  In previous versions, an empty stderr log file\n  would be created.  Thanks to Łukasz Kożuchowski for the initial patch.\n\n- Fixed an issue in Medusa that would cause ``supervisorctl tail -f`` to\n  disconnect if many other ``supervisorctl`` commands were run in parallel.\n  Patch by Stefan Friesel.\n\n3.1.4 (2017-07-24)\n------------------\n\n- Backported from Supervisor 3.3.3:  Fixed CVE-2017-11610.  A vulnerability\n  was found where an authenticated client can send a malicious XML-RPC request\n  to ``supervisord`` that will run arbitrary shell commands on the server.\n  The commands will be run as the same user as ``supervisord``.  Depending on\n  how ``supervisord`` has been configured, this may be root.  See\n  https://github.com/Supervisor/supervisor/issues/964 for details.\n\n3.1.3 (2014-10-28)\n------------------\n\n- Fixed an XML-RPC bug where the ElementTree-based parser handled strings\n  like ``<value><string>hello</string></value>`` but not strings like\n  ``<value>hello</value>``, which are valid in the XML-RPC spec.  This\n  fixes compatibility with the Apache XML-RPC client for Java and\n  possibly other clients.\n\n3.1.2 (2014-09-07)\n------------------\n\n- Fixed a bug where ``tail group:*`` in ``supervisorctl`` would show a 500\n  Internal Server Error rather than a BAD_NAME fault.\n\n- Fixed a bug where the web interface would show a 500 Internal Server Error\n  instead of an error message for some process start faults.\n\n- Removed medusa files not used by Supervisor.\n\n3.1.1 (2014-08-11)\n------------------\n\n- Fixed a bug where ``supervisorctl tail -f name`` output would stop if log\n  rotation occurred while tailing.\n\n- Prevent a crash when a greater number of file descriptors were attempted to\n  be opened than permitted by the environment when starting a bunch of\n  programs.  Now, instead a spawn error is logged.\n\n- Compute \"channel delay\" properly, fixing symptoms where a supervisorctl\n  start command would hang for a very long time when a process (or many\n  processes) are spewing to their stdout or stderr.  See comments attached to\n  https://github.com/Supervisor/supervisor/pull/263 .\n\n- Added ``docs/conf.py``, ``docs/Makefile``, and ``supervisor/scripts/*.py``\n  to the release package.\n\n3.1.0 (2014-07-29)\n------------------\n\n- The output of the ``start``, ``stop``, ``restart``, and ``clear`` commands\n  in ``supervisorctl`` has been changed to be consistent with the ``status``\n  command.  Previously, the ``status`` command would show a process like\n  ``foo:foo_01`` but starting that process would show ``foo_01: started``\n  (note the group prefix ``foo:`` was missing).  Now, starting the process\n  will show ``foo:foo_01: started``.  Suggested by Chris Wood.\n\n- The ``status`` command in ``supervisorctl`` now supports group name\n  syntax: ``status group:*``.\n\n- The process column in the table output by the ``status`` command in\n  ``supervisorctl`` now expands to fit the widest name.\n\n- The ``update`` command in ``supervisorctl`` now accepts optional group\n  names.  When group names are specified, only those groups will be\n  updated.  Patch by Gary M. Josack.\n\n- Tab completion in ``supervisorctl`` has been improved and now works for\n  more cases.  Thanks to Mathieu Longtin and Marc Abramowitz for the patches.\n\n- Attempting to start or stop a process group in ``supervisorctl`` with the\n  ``group:*`` syntax will now show the same error message as the ``process``\n  syntax if the name does not exist.  Previously, it would show a Python\n  exception.  Patch by George Ang.\n\n- Added new ``PROCESS_GROUP_ADDED`` and ``PROCESS_GROUP_REMOVED`` events.\n  These events are fired when process groups are added or removed from\n  Supervisor's runtime configuration when using the ``add`` and ``remove``\n  commands in ``supervisorctl``.  Patch by Brent Tubbs.\n\n- Stopping a process in the backoff state now changes it to the stopped\n  state.  Previously, an attempt to stop a process in backoff would be\n  ignored.  Patch by Pascal Varet.\n\n- The ``directory`` option is now expanded separately for each process in\n  a homogeneous process group.  This allows each process to have its own\n  working directory.  Patch by Perttu Ranta-aho.\n\n- Removed ``setuptools`` from the ``requires`` list in ``setup.py`` because\n  it caused installation issues on some systems.\n\n- Fixed a bug in Medusa where the HTTP Basic authorizer would cause an\n  exception if the password contained a colon.  Thanks to Thomas Güttler\n  for reporting this issue.\n\n- Fixed an XML-RPC bug where calling supervisor.clearProcessLogs() with a\n  name like ``group:*`` would cause a 500 Internal Server Error rather than\n  returning a BAD_NAME fault.\n\n- Fixed a hang that could occur in ``supervisord`` if log rotation is used\n  and an outside program deletes an active log file.  Patch by Magnus Lycka.\n\n- A warning is now logged if a glob pattern in an ``[include]`` section does\n  not match any files.  Patch by Daniel Hahler.\n\n3.0.1 (2017-07-24)\n------------------\n\n- Backported from Supervisor 3.3.3:  Fixed CVE-2017-11610.  A vulnerability\n  was found where an authenticated client can send a malicious XML-RPC request\n  to ``supervisord`` that will run arbitrary shell commands on the server.\n  The commands will be run as the same user as ``supervisord``.  Depending on\n  how ``supervisord`` has been configured, this may be root.  See\n  https://github.com/Supervisor/supervisor/issues/964 for details.\n\n3.0 (2013-07-30)\n----------------\n\n- Parsing the config file will now fail with an error message if a process\n  or group name contains characters that are not compatible with the\n  eventlistener protocol.\n\n- Fixed a bug where the ``tail -f`` command in ``supervisorctl`` would fail\n  if the combined length of the username and password was over 56 characters.\n\n- Reading the config file now gives a separate error message when the config\n  file exists but can't be read.  Previously, any error reading the file\n  would be reported as \"could not find config file\".  Patch by Jens Rantil.\n\n- Fixed an XML-RPC bug where array elements after the first would be ignored\n  when using the ElementTree-based XML parser.  Patch by Zev Benjamin.\n\n- Fixed the usage message output by ``supervisorctl`` to show the correct\n  default config file path.  Patch by Alek Storm.\n\n3.0b2 (2013-05-28)\n------------------\n\n- The behavior of the program option ``user`` has changed.  In all previous\n  versions, if ``supervisord`` failed to switch to the user, a warning would\n  be sent to the stderr log but the child process would still be spawned.\n  This means that a mistake in the config file could result in a child\n  process being unintentionally spawned as root.  Now, ``supervisord`` will\n  not spawn the child unless it was able to successfully switch to the user.\n  Thanks to Igor Partola for reporting this issue.\n\n- If a user specified in the config file does not exist on the system,\n  ``supervisord`` will now print an error and refuse to start.\n\n- Reverted a change to logging introduced in 3.0b1 that was intended to allow\n  multiple processes to log to the same file with the rotating log handler.\n  The implementation caused supervisord to crash during reload and to leak\n  file handles.  Also, since log rotation options are given on a per-program\n  basis, impossible configurations could be created (conflicting rotation\n  options for the same file).  Given this and that supervisord now has syslog\n  support, it was decided to remove this feature.  A warning was added to the\n  documentation that two processes may not log to the same file.\n\n- Fixed a bug where parsing ``command=`` could cause supervisord to crash if\n  shlex.split() fails, such as a bad quoting.  Patch by Scott Wilson.\n\n- It is now possible to use ``supervisorctl`` on a machine with no\n  ``supervisord.conf`` file by supplying the connection information in\n  command line options.  Patch by Jens Rantil.\n\n- Fixed a bug where supervisord would crash if the syslog handler was used\n  and supervisord received SIGUSR2 (log reopen request).\n\n- Fixed an XML-RPC bug where calling supervisor.getProcessInfo() with a bad\n  name would cause a 500 Internal Server Error rather than the returning\n  a BAD_NAME fault.\n\n- Added a favicon to the web interface.  Patch by Caio Ariede.\n\n- Fixed a test failure due to incorrect handling of daylight savings time\n  in the childutils tests.  Patch by Ildar Hizbulin.\n\n- Fixed a number of pyflakes warnings for unused variables, imports, and\n  dead code.  Patch by Philippe Ombredanne.\n\n3.0b1 (2012-09-10)\n------------------\n\n- Fixed a bug where parsing ``environment=`` did not verify that key/value\n  pairs were correctly separated.  Patch by Martijn Pieters.\n\n- Fixed a bug in the HTTP server code that could cause unnecessary delays\n  when sending large responses.  Patch by Philip Zeyliger.\n\n- When supervisord starts up as root, if the ``-c`` flag was not provided, a\n  warning is now emitted to the console.  Rationale: supervisord looks in the\n  current working directory for a ``supervisord.conf`` file; someone might\n  trick the root user into starting supervisord while cd'ed into a directory\n  that has a rogue ``supervisord.conf``.\n\n- A warning was added to the documentation about the security implications of\n  starting supervisord without the ``-c`` flag.\n\n- Add a boolean program option ``stopasgroup``, defaulting to false.\n  When true, the flag causes supervisor to send the stop signal to the\n  whole process group.  This is useful for programs, such as Flask in debug\n  mode, that do not propagate stop signals to their children, leaving them\n  orphaned.\n\n- Python 2.3 is no longer supported.  The last version that supported Python\n  2.3 is Supervisor 3.0a12.\n\n- Removed the unused \"supervisor_rpc\" entry point from setup.py.\n\n- Fixed a bug in the rotating log handler that would cause unexpected\n  results when two processes were set to log to the same file.  Patch\n  by Whit Morriss.\n\n- Fixed a bug in config file reloading where each reload could leak memory\n  because a list of warning messages would be appended but never cleared.\n  Patch by Philip Zeyliger.\n\n- Added a new Syslog log handler.  Thanks to Denis Bilenko, Nathan L. Smith,\n  and Jason R. Coombs, who each contributed to the patch.\n\n- Put all change history into a single file (CHANGES.txt).\n\n3.0a12 (2011-12-06)\n-------------------\n\n- Released to replace a broken 3.0a11 package where non-Python files were\n  not included in the package.\n\n3.0a11 (2011-12-06)\n-------------------\n\n- Added a new file, ``PLUGINS.rst``, with a listing of third-party plugins\n  for Supervisor.  Contributed by Jens Rantil.\n\n- The ``pid`` command in supervisorctl can now be used to retrieve the PIDs\n  of child processes.  See ``help pid``.  Patch by Gregory Wisniewski.\n\n- Added a new ``host_node_name`` expansion that will be expanded to the\n  value returned by Python's ``platform.node`` (see\n  http://docs.python.org/library/platform.html#platform.node).\n  Patch by Joseph Kondel.\n\n- Fixed a bug in the web interface where pages over 64K would be truncated.\n  Thanks to Drew Perttula and Timothy Jones for reporting this.\n\n- Renamed ``README.txt`` to ``README.rst`` so GitHub renders the file as\n  ReStructuredText.\n\n- The XML-RPC server is now compatible with clients that do not send empty\n  <params> when there are no parameters for the method call.  Thanks to\n  Johannes Becker for reporting this.\n\n- Fixed ``supervisorctl --help`` output to show the correct program name.\n\n- The behavior of the configuration options ``minfds`` and ``minprocs`` has\n  changed.  Previously, if a hard limit was less than ``minfds`` or\n  ``minprocs``, supervisord would unconditionally abort with an error.  Now,\n  supervisord will attempt to raise the hard limit.  This may succeed if\n  supervisord is run as root, otherwise the error is printed as before.\n  Patch by Benoit Sigoure.\n\n- Add a boolean program option ``killasgroup``, defaulting to false,\n  if true when resorting to send SIGKILL to stop/terminate the process\n  send it to its whole process group instead to take care of possible\n  children as well and not leave them behind.  Patch by Samuele Pedroni.\n\n- Environment variables may now be used in the configuration file\n  for options that support string expansion.  Patch by Aleksey Sivokon.\n\n- Fixed a race condition where supervisord might not act on a signal sent\n  to it.  Thanks to Adar Dembo for reporting the issue and supplying the\n  initial patch.\n\n- Updated the output of ``echo_supervisord_conf`` to fix typos and\n  improve comments.  Thanks to Jens Rantil for noticing these.\n\n- Fixed a possible 500 Server Error from the web interface.  This was\n  observed when using Supervisor on a domain socket behind Nginx, where\n  Supervisor would raise an exception because REMOTE_ADDR was not set.\n  Patch by David Bennett.\n\n3.0a10 (2011-03-30)\n-------------------\n\n- Fixed the stylesheet of the web interface so the footer line won't overlap\n  a long process list.  Thanks to Derek DeVries for the patch.\n\n- Allow rpc interface plugins to register new events types.\n\n- Bug fix for FCGI sockets not getting cleaned up when the ``reload`` command\n  is issued from supervisorctl.  Also, the default behavior has changed for\n  FCGI sockets.  They are now closed whenever the number of running processes\n  in a group hits zero.  Previously, the sockets were kept open unless a\n  group-level stop command was issued.\n\n- Better error message when HTTP server cannot reverse-resolve a hostname to\n  an IP address.  Previous behavior: show a socket error.  Current behavior:\n  spit out a suggestion to stdout.\n\n- Environment variables set via ``environment=`` value within\n  ``[supervisord]`` section had no effect.  Thanks to Wyatt Baldwin\n  for a patch.\n\n- Fix bug where stopping process would cause process output that happened\n  after the stop request was issued to be lost.  See\n  https://github.com/Supervisor/supervisor/issues/11.\n\n- Moved 2.X change log entries into ``HISTORY.txt``.\n\n- Converted ``CHANGES.txt`` and ``README.txt`` into proper ReStructuredText\n  and included them in the ``long_description`` in ``setup.py``.\n\n- Added a tox.ini to the package (run via ``tox`` in the package dir).  Tests\n  supervisor on multiple Python versions.\n\n3.0a9 (2010-08-13)\n------------------\n\n- Use rich comparison methods rather than __cmp__ to sort process configs and\n  process group configs to better straddle Python versions.  (thanks to\n  Jonathan Riboux for identifying the problem and supplying an initial\n  patch).\n\n- Fixed test_supervisorctl.test_maintail_dashf test for Python 2.7.  (thanks\n  to Jonathan Riboux for identifying the problem and supplying an initial\n  patch).\n\n- Fixed the way that supervisor.datatypes.url computes a \"good\" URL\n  for compatibility with Python 2.7 and Python >= 2.6.5.  URLs with\n  bogus \"schemes://\" will now be accepted as a version-straddling\n  compromise (before they were rejected before supervisor would\n  start).  (thanks to Jonathan Riboux for identifying the problem\n  and supplying an initial patch).\n\n- Add a ``-v`` / ``--version`` option to supervisord: Print the\n  supervisord version number out to stdout and exit.  (Roger Hoover)\n\n- Import iterparse from xml.etree when available (eg: Python 2.6).  Patch\n  by Sidnei da Silva.\n\n- Fixed the url to the supervisor-users mailing list.  Patch by\n  Sidnei da Silva\n\n- When parsing \"environment=\" in the config file, changes introduced in\n  3.0a8 prevented Supervisor from parsing some characters commonly\n  found in paths unless quoting was used as in this example::\n\n    environment=HOME='/home/auser'\n\n  Supervisor once again allows the above line to be written as::\n\n    environment=HOME=/home/auser\n\n  Alphanumeric characters, \"_\", \"/\", \".\", \"+\", \"-\", \"(\", \")\", and \":\" can all\n  be used as a value without quoting. If any other characters are needed in\n  the value, please quote it as in the first example above.  Thanks to Paul\n  Heideman for reporting this issue.\n\n- Supervisor will now look for its config file in locations relative to the\n  executable path, allowing it to be used more easily in virtual\n  environments.  If sys.argv[0] is ``/path/to/venv/bin/supervisorctl``,\n  supervisor will now look for it's config file in\n  ``/path/to/venv/etc/supervisord.conf`` and\n  ``/path/to/venv/supervisord.conf`` in addition to the other standard\n  locations.  Patch by Chris Rossi.\n\n3.0a8 (2010-01-20)\n------------------\n\n- Don't cleanup file descriptors on first supervisord invocation:\n  this is a lame workaround for Snow Leopard systems that use\n  libdispatch and are receiving \"Illegal instruction\" messages at\n  supervisord startup time.  Restarting supervisord via\n  \"supervisorctl restart\" may still cause a crash on these systems.\n\n- Got rid of Medusa hashbang headers in various files to ease RPM\n  packaging.\n\n- Allow umask to be 000 (patch contributed by Rowan Nairn).\n\n- Fixed a bug introduced in 3.0a7 where supervisorctl wouldn't ask\n  for a username/password combination properly from a\n  password-protected supervisord if it wasn't filled in within the\n  \"[supervisorctl]\" section username/password values.  It now\n  properly asks for a username and password.\n\n- Fixed a bug introduced in 3.0a7 where setup.py would not detect the\n  Python version correctly.  Patch by Daniele Paolella.\n\n- Fixed a bug introduced in 3.0a7 where parsing a string of key/value\n  pairs failed on Python 2.3 due to use of regular expression syntax\n  introduced in Python 2.4.\n\n- Removed the test suite for the ``memmon`` console script, which was\n  moved to the Superlance package in 3.0a7.\n\n- Added release dates to CHANGES.txt.\n\n- Reloading the config for an fcgi process group did not close the fcgi\n  socket - now, the socket is closed whenever the group is stopped as a unit\n  (including during config update). However, if you stop all the processes\n  in a group individually, the socket will remain open to allow for graceful\n  restarts of FCGI daemons.  (Roger Hoover)\n\n- Rereading the config did not pick up changes to the socket parameter in a\n  fcgi-program section.  (Roger Hoover)\n\n- Made a more friendly exception message when a FCGI socket cannot be\n  created.  (Roger Hoover)\n\n- Fixed a bug where the --serverurl option of supervisorctl would not\n  accept a URL with a \"unix\" scheme.  (Jason Kirtland)\n\n- Running the tests now requires the \"mock\" package.  This dependency has\n  been added to \"tests_require\" in setup.py.  (Roger Hoover)\n\n- Added support for setting the ownership and permissions for an FCGI socket.\n  This is done using new \"socket_owner\" and \"socket_mode\" options in an\n  [fcgi-program:x] section.  See the manual for details.  (Roger Hoover)\n\n- Fixed a bug where the FCGI socket reference count was not getting\n  decremented on spawn error.  (Roger Hoover)\n\n- Fixed a Python 2.6 deprecation warning on use of the \"sha\" module.\n\n- Updated ez_setup.py to one that knows about setuptools 0.6c11.\n\n- Running \"supervisorctl shutdown\" no longer dumps a Python backtrace\n  when it can't connect to supervisord on the expected socket.  Thanks\n  to Benjamin Smith for reporting this.\n\n- Removed use of collections.deque in our bundled version of asynchat\n  because it broke compatibility with Python 2.3.\n\n- The sample configuration output by \"echo_supervisord_conf\" now correctly\n  shows the default for \"autorestart\" as \"unexpected\".  Thanks to\n  William Dode for noticing it showed the wrong value.\n\n3.0a7 (2009-05-24)\n------------------\n\n- We now bundle our own patched version of Medusa contributed by Jason\n  Kirtland to allow Supervisor to run on Python 2.6.  This was done\n  because Python 2.6 introduced backwards incompatible changes to\n  asyncore and asynchat in the stdlib.\n\n- The console script ``memmon``, introduced in Supervisor 3.0a4, has\n  been moved to Superlance (http://pypi.python.org/pypi/superlance).\n  The Superlance package contains other useful monitoring tools designed\n  to run under Supervisor.\n\n- Supervisorctl now correctly interprets all of the error codes that can\n  be returned when starting a process.  Patch by Francesc Alted.\n\n- New ``stdout_events_enabled`` and ``stderr_events_enabled`` config options\n  have been added to the ``[program:x]``, ``[fcgi-program:x]``, and\n  ``[eventlistener:x]`` sections.  These enable the emitting of new\n  PROCESS_LOG events for a program.  If unspecified, the default is False.\n\n  If enabled for a subprocess, and data is received from the stdout or\n  stderr of the subprocess while not in the special capture mode used by\n  PROCESS_COMMUNICATION, an event will be emitted.\n\n  Event listeners can subscribe to either PROCESS_LOG_STDOUT or\n  PROCESS_LOG_STDERR individually, or PROCESS_LOG for both.\n\n- Values for subprocess environment variables specified with environment=\n  in supervisord.conf can now be optionally quoted, allowing them to\n  contain commas.  Patch by Tim Godfrey.\n\n- Added a new event type, REMOTE_COMMUNICATION, that is emitted by a new\n  RPC method, supervisor.sendRemoteCommEvent().\n\n- Patch for bug #268 (KeyError on ``here`` expansion for\n  stdout/stderr_logfile) from David E. Kindred.\n\n- Add ``reread``, ``update``, and ``avail`` commands based on Anders\n  Quist's ``online_config_reload.diff`` patch.  This patch extends\n  the \"add\" and \"drop\" commands with automagical behavior::\n\n    In supervisorctl:\n\n      supervisor> status\n      bar                              RUNNING    pid 14864, uptime 18:03:42\n      baz                              RUNNING    pid 23260, uptime 0:10:16\n      foo                              RUNNING    pid 14866, uptime 18:03:42\n      gazonk                           RUNNING    pid 23261, uptime 0:10:16\n      supervisor> avail\n      bar                              in use    auto      999:999\n      baz                              in use    auto      999:999\n      foo                              in use    auto      999:999\n      gazonk                           in use    auto      999:999\n      quux                             avail     auto      999:999\n\n    Now we add this to our conf:\n\n      [group:zegroup]\n      programs=baz,gazonk\n\n    Then we reread conf:\n\n      supervisor> reread\n      baz: disappeared\n      gazonk: disappeared\n      quux: available\n      zegroup: available\n      supervisor> avail\n      bar                              in use    auto      999:999\n      foo                              in use    auto      999:999\n      quux                             avail     auto      999:999\n      zegroup:baz                      avail     auto      999:999\n      zegroup:gazonk                   avail     auto      999:999\n      supervisor> status\n      bar                              RUNNING    pid 14864, uptime 18:04:18\n      baz                              RUNNING    pid 23260, uptime 0:10:52\n      foo                              RUNNING    pid 14866, uptime 18:04:18\n      gazonk                           RUNNING    pid 23261, uptime 0:10:52\n\n    The magic make-it-so command:\n\n      supervisor> update\n      baz: stopped\n      baz: removed process group\n      gazonk: stopped\n      gazonk: removed process group\n      zegroup: added process group\n      quux: added process group\n      supervisor> status\n      bar                              RUNNING    pid 14864, uptime 18:04:43\n      foo                              RUNNING    pid 14866, uptime 18:04:43\n      quux                             RUNNING    pid 23561, uptime 0:00:02\n      zegroup:baz                      RUNNING    pid 23559, uptime 0:00:02\n      zegroup:gazonk                   RUNNING    pid 23560, uptime 0:00:02\n      supervisor> avail\n      bar                              in use    auto      999:999\n      foo                              in use    auto      999:999\n      quux                             in use    auto      999:999\n      zegroup:baz                      in use    auto      999:999\n      zegroup:gazonk                   in use    auto      999:999\n\n- Fix bug with symptom \"KeyError: 'process_name'\" when using a logfile name\n  including documented``process_name`` Python string expansions.\n\n- Tab completions in the supervisorctl shell, and a foreground mode for\n  Supervisor, implemented as a part of GSoC.  The supervisorctl program now\n  has a ``fg`` command, which makes it possible to supply inputs to a\n  process, and see its output/error stream in real time.\n\n- Process config reloading implemented by Anders Quist.  The\n  supervisorctl program now has the commands \"add\" and \"drop\".\n  \"add <programname>\" adds the process group implied by <programname>\n  in the config file.  \"drop <programname>\" removes the process\n  group from the running configuration (it must already be stopped).\n  This makes it possible to add processes to and remove processes from\n  a running supervisord without restarting the supervisord process.\n\n- Fixed a bug where opening the HTTP servers would fail silently\n  for socket errors other than errno.EADDRINUSE.\n\n- Thanks to Dave Peticolas, using \"reload\" against a supervisord\n  that is running in the background no longer causes supervisord\n  to crash.\n\n- Configuration options for logfiles now accept mixed case reserved\n  words (e.g. \"AUTO\" or \"auto\") for consistency with other options.\n\n- childutils.eventdata was buggy, it could not deal with carriage returns\n  in data.  See http://www.plope.com/software/collector/257.  Thanks\n  to Ian Bicking.\n\n- Per-process exitcodes= configuration now will not accept exit\n  codes that are not 8-bit unsigned integers (supervisord will not\n  start when one of the exit codes is outside the range of 0 - 255).\n\n- Per-process ``directory`` value can now contain expandable values like\n  ``%(here)s``. (See http://www.plope.com/software/collector/262).\n\n- Accepted patch from Roger Hoover to allow for a new sort of\n  process group: \"fcgi-program\".  Adding one of these to your\n  supervisord.conf allows you to control fastcgi programs.  FastCGI\n  programs cannot belong to heterogenous groups.\n\n  The configuration for FastCGI programs is the same as regular programs\n  except an additional \"socket\" parameter.  Substitution happens on the\n  socket parameter with the ``here`` and ``program_name`` variables::\n\n   [fcgi-program:fcgi_test]\n   ;socket=tcp://localhost:8002\n   socket=unix:///path/to/fcgi/socket\n\n- Supervisorctl now supports a plugin model for supervisorctl\n  commands.\n\n- Added the ability to retrieve supervisord's own pid through\n  supervisor.getPID() on the XML-RPC interface or a new\n  \"pid\" command on supervisorctl.\n\n3.0a6 (2008-04-07)\n------------------\n\n- The RotatingFileLogger had a race condition in its doRollover\n  method whereby a file might not actually exist despite a call to\n  os.path.exists on the line above a place where we try to remove\n  it.  We catch the exception now and ignore the missing file.\n\n3.0a5 (2008-03-13)\n------------------\n\n- Supervisorctl now supports persistent readline history.  To\n  enable, add \"history_file = <pathname>\" to the ``[supervisorctl]``\n  section in your supervisord.conf file.\n\n- Multiple commands may now be issued on one supervisorctl command\n  line, e.g. \"restart prog; tail -f prog\".  Separate commands with a\n  single semicolon; they will be executed in order as you would\n  expect.\n\n3.0a4 (2008-01-30)\n------------------\n\n- 3.0a3 broke Python 2.3 backwards compatibility.\n\n- On Debian Sarge, one user reported that a call to\n  options.mktempfile would fail with an \"[Errno 9] Bad file\n  descriptor\" at supervisord startup time.  I was unable to\n  reproduce this, but we found a workaround that seemed to work for\n  him and it's included in this release.  See\n  http://www.plope.com/software/collector/252 for more information.\n  Thanks to William Dode.\n\n- The fault ``ALREADY_TERMINATED`` has been removed.  It was only raised by\n  supervisor.sendProcessStdin().  That method now returns ``NOT_RUNNING``\n  for parity with the other methods. (Mike Naberezny)\n\n- The fault TIMED_OUT has been removed.  It was not used.\n\n- Supervisor now depends on meld3 0.6.4, which does not compile its\n  C extensions by default, so there is no more need to faff around\n  with NO_MELD3_EXTENSION_MODULES during installation if you don't\n  have a C compiler or the Python development libraries on your\n  system.\n\n- Instead of making a user root around for the sample.conf file,\n  provide a convenience command \"echo_supervisord_conf\", which he can\n  use to echo the sample.conf to his terminal (and redirect to a file\n  appropriately).  This is a new user convenience (especially one who\n  has no Python experience).\n\n- Added ``numprocs_start`` config option to ``[program:x]`` and\n  ``[eventlistener:x]`` sections.  This is an offset used to compute\n  the first integer that ``numprocs`` will begin to start from.\n  Contributed by Antonio Beamud Montero.\n\n- Added capability for ``[include]`` config section to config format.\n  This section must contain a single key \"files\", which must name a\n  space-separated list of file globs that will be included in\n  supervisor's configuration.  Contributed by Ian Bicking.\n\n- Invoking the ``reload`` supervisorctl command could trigger a bug in\n  supervisord which caused it to crash.  See\n  http://www.plope.com/software/collector/253 .  Thanks to William Dode for\n  a bug report.\n\n- The ``pidproxy`` script was made into a console script.\n\n- The ``password`` value in both the ``[inet_http_server]`` and\n  ``[unix_http_server]`` sections can now optionally be specified as a SHA\n  hexdigest instead of as cleartext.  Values prefixed with ``{SHA}`` will be\n  considered SHA hex digests.  To encrypt a password to a form suitable for\n  pasting into the configuration file using Python, do, e.g.::\n\n     >>> import sha\n     >>> '{SHA}' + sha.new('thepassword').hexdigest()\n     '{SHA}82ab876d1387bfafe46cc1c8a2ef074eae50cb1d'\n\n- The subtypes of the events PROCESS_STATE_CHANGE (and\n  PROCESS_STATE_CHANGE itself) have been removed, replaced with a\n  simpler set of PROCESS_STATE subscribable event types.\n\n  The new event types are:\n\n    PROCESS_STATE_STOPPED\n    PROCESS_STATE_EXITED\n    PROCESS_STATE_STARTING\n    PROCESS_STATE_STOPPING\n    PROCESS_STATE_BACKOFF\n    PROCESS_STATE_FATAL\n    PROCESS_STATE_RUNNING\n    PROCESS_STATE_UNKNOWN\n    PROCESS_STATE # abstract\n\n  PROCESS_STATE_STARTING replaces:\n\n    PROCESS_STATE_CHANGE_STARTING_FROM_STOPPED\n    PROCESS_STATE_CHANGE_STARTING_FROM_BACKOFF\n    PROCESS_STATE_CHANGE_STARTING_FROM_EXITED\n    PROCESS_STATE_CHANGE_STARTING_FROM_FATAL\n\n  PROCESS_STATE_RUNNING replaces\n  PROCESS_STATE_CHANGE_RUNNING_FROM_STARTED\n\n  PROCESS_STATE_BACKOFF replaces\n  PROCESS_STATE_CHANGE_BACKOFF_FROM_STARTING\n\n  PROCESS_STATE_STOPPING replaces:\n\n    PROCESS_STATE_CHANGE_STOPPING_FROM_RUNNING\n    PROCESS_STATE_CHANGE_STOPPING_FROM_STARTING\n\n  PROCESS_STATE_EXITED replaces\n  PROCESS_STATE_CHANGE_EXITED_FROM_RUNNING\n\n  PROCESS_STATE_STOPPED replaces\n  PROCESS_STATE_CHANGE_STOPPED_FROM_STOPPING\n\n  PROCESS_STATE_FATAL replaces\n  PROCESS_STATE_CHANGE_FATAL_FROM_BACKOFF\n\n  PROCESS_STATE_UNKNOWN replaces PROCESS_STATE_CHANGE_TO_UNKNOWN\n\n  PROCESS_STATE replaces PROCESS_STATE_CHANGE\n\n  The PROCESS_STATE_CHANGE_EXITED_OR_STOPPED abstract event is gone.\n\n  All process state changes have at least \"processname\",\n  \"groupname\", and \"from_state\" (the name of the previous state) in\n  their serializations.\n\n  PROCESS_STATE_EXITED additionally has \"expected\" (1 or 0) and \"pid\"\n  (the process id) in its serialization.\n\n  PROCESS_STATE_RUNNING, PROCESS_STATE_STOPPING,\n  PROCESS_STATE_STOPPED additionally have \"pid\" in their\n  serializations.\n\n  PROCESS_STATE_STARTING and PROCESS_STATE_BACKOFF have \"tries\" in\n  their serialization (initially \"0\", bumped +1 each time a start\n  retry happens).\n\n- Remove documentation from README.txt, point people to\n  http://supervisord.org/manual/ .\n\n- The eventlistener request/response protocol has changed.  OK/FAIL\n  must now be wrapped in a RESULT envelope so we can use it for more\n  specialized communications.\n\n  Previously, to signify success, an event listener would write the string\n  ``OK\\n`` to its stdout.  To signify that the event was seen but couldn't\n  be handled by the listener and should be rebuffered, an event listener\n  would write the string ``FAIL\\n`` to its stdout.\n\n  In the new protocol, the listener must write the string::\n\n    RESULT {resultlen}\\n{result}\n\n  For example, to signify OK::\n\n    RESULT 2\\nOK\n\n  To signify FAIL::\n\n    RESULT 4\\nFAIL\n\n  See the scripts/sample_eventlistener.py script for an example.\n\n- To provide a hook point for custom results returned from event\n  handlers (see above) the [eventlistener:x] configuration sections\n  now accept a \"result_handler=\" parameter,\n  e.g. \"result_handler=supervisor.dispatchers:default_handler\" (the\n  default) or \"handler=mypackage:myhandler\".  The keys are pkgutil\n  \"entry point\" specifications (importable Python function names).\n  Result handlers must be callables which accept two arguments: one\n  named \"event\" which represents the event, and the other named\n  \"result\", which represents the listener's result.  A result\n  handler either executes successfully or raises an exception.  If\n  it raises a supervisor.dispatchers.RejectEvent exception, the\n  event will be rebuffered, and the eventhandler will be placed back\n  into the ACKNOWLEDGED state.  If it raises any other exception,\n  the event handler will be placed in the UNKNOWN state.  If it does\n  not raise any exception, the event is considered successfully\n  processed.  A result handler's return value is ignored.  Writing a\n  result handler is a \"in case of emergency break glass\" sort of\n  thing, it is not something to be used for arbitrary business code.\n  In particular, handlers *must not block* for any appreciable\n  amount of time.\n\n  The standard eventlistener result handler\n  (supervisor.dispatchers:default_handler) does nothing if it receives an\n  \"OK\" and will raise a supervisor.dispatchers.RejectEvent exception if it\n  receives any other value.\n\n- Supervisord now emits TICK events, which happen every N seconds.\n  Three types of TICK events are available: TICK_5 (every five\n  seconds), TICK_60 (every minute), TICK_3600 (every hour).  Event\n  listeners may subscribe to one of these types of events to perform\n  every-so-often processing.  TICK events are subtypes of the EVENT\n  type.\n\n- Get rid of OSX platform-specific memory monitor and replace with\n  memmon.py, which works on both Linux and Mac OS.  This script is\n  now a console script named \"memmon\".\n\n- Allow \"web handler\" (the handler which receives http requests from\n  browsers visiting the web UI of supervisor) to deal with POST requests.\n\n- RPC interface methods stopProcess(), stopProcessGroup(), and\n  stopAllProcesses() now take an optional \"wait\" argument that defaults\n  to True for parity with the start methods.\n\n3.0a3 (2007-10-02)\n------------------\n\n- Supervisorctl now reports a better error message when the main supervisor\n  XML-RPC namespace is not registered.  Thanks to Mike Orr for reporting\n  this. (Mike Naberezny)\n\n- Create ``scripts`` directory within supervisor package, move\n  ``pidproxy.py`` there, and place sample event listener and comm event\n  programs within the directory.\n\n- When an event notification is buffered (either because a listener rejected\n  it or because all listeners were busy when we attempted to send it\n  originally), we now rebuffer it in a way that will result in it being\n  retried earlier than it used to be.\n\n- When a listener process exits (unexpectedly) before transitioning from the\n  BUSY state, rebuffer the event that was being processed.\n\n- supervisorctl ``tail`` command now accepts a trailing specifier: ``stderr``\n  or ``stdout``, which respectively, allow a user to tail the stderr or\n  stdout of the named process.  When this specifier is not provided, tail\n  defaults to stdout.\n\n- supervisor ``clear`` command now clears both stderr and stdout logs for the\n  given process.\n\n- When a process encounters a spawn error as a result of a failed execve or\n  when it cannot setuid to a given uid, it now puts this info into the\n  process' stderr log rather than its stdout log.\n\n- The event listener protocol header now contains the ``server`` identifier,\n  the ``pool`` that the event emanated from, and the ``poolserial`` as well\n  as the values it previously contained (version, event name, serial, and\n  length).  The server identifier is taken from the config file options value\n  ``identifier``, the ``pool`` value is the name of the listener pool that\n  this event emanates from, and the ``poolserial`` is a serial number\n  assigned to the event local to the pool that is processing it.\n\n- The event listener protocol header is now a sequence of key-value\n  pairs rather than a list of positional values.  Previously, a\n  representative header looked like::\n\n    SUPERVISOR3.0 PROCESS_COMMUNICATION_STDOUT 30 22\\n\n\n  Now it looks like::\n\n    ver:3.0 server:supervisor serial:21 ...\n\n- Specific event payload serializations have changed.  All event\n  types that deal with processes now include the pid of the process\n  that the event is describing.  In event serialization \"header\"\n  values, we've removed the space between the header name and the\n  value and headers are now separated by a space instead of a line\n  feed.  The names of keys in all event types have had underscores\n  removed.\n\n- Abandon the use of the Python stdlib ``logging`` module for speed\n  and cleanliness purposes.  We've rolled our own.\n\n- Fix crash on start if AUTO logging is used with a max_bytes of\n  zero for a process.\n\n- Improve process communication event performance.\n\n- The process config parameters ``stdout_capturefile`` and\n  ``stderr_capturefile`` are no longer valid.  They have been replaced with\n  the ``stdout_capture_maxbytes`` and ``stderr_capture_maxbytes`` parameters,\n  which are meant to be suffix-multiplied integers.  They both default to\n  zero.  When they are zero, process communication event capturing is not\n  performed.  When either is nonzero, the value represents the maximum number\n  of bytes that will be captured between process event start and end tags.\n  This change was to support the fact that we no longer keep capture data in\n  a separate file, we just use a FIFO in RAM to maintain capture info.  For\n  users whom don't care about process communication events, or whom haven't\n  changed the defaults for ``stdout_capturefile`` or ``stderr_capturefile``,\n  they needn't do anything to their configurations to deal with this change.\n\n- Log message levels have been normalized.  In particular, process\n  stdin/stdout is now logged at ``debug`` level rather than at ``trace``\n  level (``trace`` level is now reserved for output useful typically for\n  debugging supervisor itself).  See \"Supervisor Log Levels\" in the\n  documentation for more info.\n\n- When an event is rebuffered (because all listeners are busy or a\n  listener rejected the event), the rebuffered event is now inserted\n  in the head of the listener event queue.  This doesn't guarantee\n  event emission in natural ordering, because if a listener rejects\n  an event or dies while it's processing an event, it can take an\n  arbitrary amount of time for the event to be rebuffered, and other\n  events may be processed in the meantime.  But if pool listeners\n  never reject an event or don't die while processing an event, this\n  guarantees that events will be emitted in the order that they were\n  received because if all listeners are busy, the rebuffered event\n  will be tried again \"first\" on the next go-around.\n\n- Removed EVENT_BUFFER_OVERFLOW event type.\n\n- The supervisorctl xmlrpc proxy can now communicate with\n  supervisord using a persistent HTTP connection.\n\n- A new module \"supervisor.childutils\" was added.  This module\n  provides utilities for Python scripts which act as children of\n  supervisord.  Most notably, it contains an API method\n  \"getRPCInterface\" allows you to obtain an xmlrpclib ServerProxy\n  that is willing to communicate with the parent supervisor.  It\n  also contains utility functions that allow for parsing of\n  supervisor event listener protocol headers.  A pair of scripts\n  (loop_eventgen.py and loop_listener.py) were added to the script\n  directory that serve as examples about how to use the childutils\n  module.\n\n- A new envvar is added to child process environments:\n  SUPERVISOR_SERVER_URL.  This contains the server URL for the\n  supervisord running the child.\n\n- An ``OK`` URL was added at ``/ok.html`` which just returns the string\n  ``OK`` (can be used for up checks or speed checks via plain-old-HTTP).\n\n- An additional command-line option ``--profile_options`` is accepted\n  by the supervisord script for developer use::\n\n    supervisord -n -c sample.conf --profile_options=cumulative,calls\n\n  The values are sort_stats options that can be passed to the\n  standard Python profiler's PStats sort_stats method.\n\n  When you exit supervisor, it will print Python profiling output to\n  stdout.\n\n- If cElementTree is installed in the Python used to invoke\n  supervisor, an alternate (faster, by about 2X) XML parser will be\n  used to parse XML-RPC request bodies.  cElementTree was added as\n  an \"extras_require\" option in setup.py.\n\n- Added the ability to start, stop, and restart process groups to\n  supervisorctl.  To start a group, use ``start groupname:*``.  To start\n  multiple groups, use ``start groupname1:* groupname2:*``.  Equivalent\n  commands work for \"stop\" and \"restart\". You can mix and match short\n  processnames, fully-specified group:process names, and groupsplats on the\n  same line for any of these commands.\n\n- Added ``directory`` option to process config.  If you set this\n  option, supervisor will chdir to this directory before executing\n  the child program (and thus it will be the child's cwd).\n\n- Added ``umask`` option to process config.  If you set this option,\n  supervisor will set the umask of the child program.  (Thanks to\n  Ian Bicking for the suggestion).\n\n- A pair of scripts ``osx_memmon_eventgen.py`` and `osx_memmon_listener.py``\n  have been added to the scripts directory.  If they are used together as\n  described in their comments, processes which are consuming \"too much\"\n  memory will be restarted.  The ``eventgen`` script only works on OSX (my\n  main development platform) but it should be trivially generalizable to\n  other operating systems.\n\n- The long form ``--configuration`` (-c) command line option for\n  supervisord was broken.  Reported by Mike Orr.  (Mike Naberezny)\n\n- New log level: BLAT (blather).  We log all\n  supervisor-internal-related debugging info here.  Thanks to Mike\n  Orr for the suggestion.\n\n- We now allow supervisor to listen on both a UNIX domain socket and an inet\n  socket instead of making them mutually exclusive.  As a result, the options\n  \"http_port\", \"http_username\", \"http_password\", \"sockchmod\" and \"sockchown\"\n  are no longer part of the ``[supervisord]`` section configuration. These\n  have been supplanted by two other sections: ``[unix_http_server]`` and\n  ``[inet_http_server]``.  You'll need to insert one or the other (depending\n  on whether you want to listen on a UNIX domain socket or a TCP socket\n  respectively) or both into your supervisord.conf file.  These sections have\n  their own options (where applicable) for port, username, password, chmod,\n  and chown.  See README.txt for more information about these sections.\n\n- All supervisord command-line options related to \"http_port\",\n  \"http_username\", \"http_password\", \"sockchmod\" and \"sockchown\" have\n  been removed (see above point for rationale).\n\n- The option that *used* to be ``sockchown`` within the ``[supervisord]``\n  section (and is now named ``chown`` within the ``[unix_http_server]``\n  section) used to accept a dot-separated user.group value.  The separator\n  now must be a colon \":\", e.g. \"user:group\".  Unices allow for dots in\n  usernames, so this change is a bugfix.  Thanks to Ian Bicking for the bug\n  report.\n\n- If a '-c' option is not specified on the command line, both supervisord and\n  supervisorctl will search for one in the paths ``./supervisord.conf`` ,\n  ``./etc/supervisord.conf`` (relative to the current working dir when\n  supervisord or supervisorctl is invoked) or in ``/etc/supervisord.conf``\n  (the old default path).  These paths are searched in order, and supervisord\n  and supervisorctl will use the first one found.  If none are found,\n  supervisor will fail to start.\n\n- The Python string expression ``%(here)s`` (referring to the directory in\n  which the configuration file was found) can be used within the\n  following sections/options within the config file::\n\n      unix_http_server:file\n      supervisor:directory\n      supervisor:logfile\n      supervisor:pidfile\n      supervisor:childlogdir\n      supervisor:environment\n      program:environment\n      program:stdout_logfile\n      program:stderr_logfile\n      program:process_name\n      program:command\n\n- The ``--environment`` aka ``-b`` option was removed from the list of\n  available command-line switches to supervisord (use \"A=1 B=2\n  bin/supervisord\" instead).\n\n- If the socket filename (the tail-end of the unix:// URL) was\n  longer than 64 characters, supervisorctl would fail with an\n  encoding error at startup.\n\n- The ``identifier`` command-line argument was not functional.\n\n- Fixed http://www.plope.com/software/collector/215 (bad error\n  message in supervisorctl when program command not found on PATH).\n\n- Some child processes may not have been shut down properly at\n  supervisor shutdown time.\n\n- Move to ZPL-derived (but not ZPL) license available from\n  http://www.repoze.org/LICENSE.txt; it's slightly less restrictive\n  than the ZPL (no servicemark clause).\n\n- Spurious errors related to unclosed files (\"bad file descriptor\",\n  typically) were evident at supervisord \"reload\" time (when using\n  the \"reload\" command from supervisorctl).\n\n- We no longer bundle ez_setup to bootstrap setuptools installation.\n\n3.0a2 (2007-08-24)\n------------------\n\n- Fixed the README.txt example for defining the supervisor RPC\n  interface in the configuration file.  Thanks to Drew Perttula.\n\n- Fixed a bug where process communication events would not have the\n  proper payload if the payload data was very short.\n\n- when supervisord attempted to kill a process with SIGKILL after\n  the process was not killed within \"stopwaitsecs\" using a \"normal\"\n  kill signal, supervisord would crash with an improper\n  AssertionError.  Thanks to Calvin Hendryx-Parker.\n\n- On Linux, Supervisor would consume too much CPU in an effective\n  \"busywait\" between the time a subprocess exited and the time at\n  which supervisor was notified of its exit status.  Thanks to Drew\n  Perttula.\n\n- RPC interface behavior change: if the RPC method\n  \"sendProcessStdin\" is called against a process that has closed its\n  stdin file descriptor (e.g. it has done the equivalent of\n  \"sys.stdin.close(); os.close(0)\"), we return a NO_FILE fault\n  instead of accepting the data.\n\n- Changed the semantics of the process configuration ``autorestart``\n  parameter with respect to processes which move between the RUNNING and\n  EXITED state.  ``autorestart`` was previously a boolean.  Now it's a\n  trinary, accepting one of ``false``, ``unexpected``, or ``true``.  If it's\n  ``false``, a process will never be automatically restarted from the EXITED\n  state.  If it's ``unexpected``, a process that enters the EXITED state will\n  be automatically restarted if it exited with an exit code that was not\n  named in the process config's ``exitcodes`` list.  If it's ``true``, a\n  process that enters the EXITED state will be automatically restarted\n  unconditionally.  The default is now ``unexpected`` (it was previously\n  ``true``).  The readdition of this feature is a reversion of the behavior\n  change note in the changelog notes for 3.0a1 that asserted we never cared\n  about the process' exit status when determining whether to restart it or\n  not.\n\n- setup.py develop (and presumably setup.py install) would fail under Python\n  2.3.3, because setuptools attempted to import ``splituser`` from urllib2,\n  and it didn't exist.\n\n- It's now possible to use ``setup.py install`` and ``setup.py develop`` on\n  systems which do not have a C compiler if you set the environment variable\n  \"NO_MELD3_EXTENSION_MODULES=1\" in the shell in which you invoke these\n  commands (versions of meld3 > 0.6.1 respect this envvar and do not try to\n  compile optional C extensions when it's set).\n\n- The test suite would fail on Python versions <= 2.3.3 because\n  the \"assertTrue\" and \"assertFalse\" methods of unittest.TestCase\n  didn't exist in those versions.\n\n- The ``supervisorctl`` and ``supervisord`` wrapper scripts were disused in\n  favor of using setuptools' ``console_scripts`` entry point settings.\n\n- Documentation files and the sample configuration file are put into\n  the generated supervisor egg's ``doc`` directory.\n\n- Using the web interface would cause fairly dramatic memory\n  leakage.  We now require a version of meld3 that does not appear\n  to leak memory from its C extensions (0.6.3).\n\n3.0a1 (2007-08-16)\n------------------\n\n- Default config file comment documented 10 secs as default for ``startsecs``\n  value in process config, in reality it was 1 sec.  Thanks to Christoph\n  Zwerschke.\n\n- Make note of subprocess environment behavior in README.txt.\n  Thanks to Christoph Zwerschke.\n\n- New \"strip_ansi\" config file option attempts to strip ANSI escape\n  sequences from logs for smaller/more readable logs (submitted by\n  Mike Naberezny).\n\n- The XML-RPC method supervisor.getVersion() has been renamed for\n  clarity to supervisor.getAPIVersion().  The old name is aliased\n  for compatibility but is deprecated and will be removed in a\n  future version (Mike Naberezny).\n\n- Improved web interface styling (Mike Naberezny, Derek DeVries)\n\n- The XML-RPC method supervisor.startProcess() now checks that\n  the file exists and is executable (Mike Naberezny).\n\n- Two environment variables, \"SUPERVISOR_PROCESS_NAME\" and\n  \"SUPERVISOR_PROCESS_GROUP\" are set in the environment of child\n  processes, representing the name of the process and group in\n  supervisor's configuration.\n\n- Process state map change: a process may now move directly from the\n  STARTING state to the STOPPING state (as a result of a stop\n  request).\n\n- Behavior change: if ``autorestart`` is true, even if a process exits with\n  an \"expected\" exit code, it will still be restarted.  In the immediately\n  prior release of supervisor, this was true anyway, and no one complained,\n  so we're going to consider that the \"officially correct\" behavior from now\n  on.\n\n- Supervisor now logs subprocess stdout and stderr independently.\n  The old program config keys \"logfile\", \"logfile_backups\" and\n  \"logfile_maxbytes\" are superseded by \"stdout_logfile\",\n  \"stdout_logfile_backups\", and \"stdout_logfile_maxbytes\".  Added\n  keys include \"stderr_logfile\", \"stderr_logfile_backups\", and\n  \"stderr_logfile_maxbytes\".  An additional \"redirect_stderr\" key is\n  used to cause program stderr output to be sent to its stdout\n  channel.  The keys \"log_stderr\" and \"log_stdout\" have been\n  removed.\n\n- ``[program:x]`` config file sections now represent \"homogeneous process\n  groups\" instead of single processes.  A \"numprocs\" key in the section\n  represents the number of processes that are in the group.  A \"process_name\"\n  key in the section allows composition of the each process' name within the\n  homogeneous group.\n\n- A new kind of config file section, ``[group:x]`` now exists, allowing users\n  to group heterogeneous processes together into a process group that can be\n  controlled as a unit from a client.\n\n- Supervisord now emits \"events\" at certain points in its normal\n  operation.  These events include supervisor state change events,\n  process state change events, and \"process communication events\".\n\n- A new kind of config file section ``[eventlistener:x]`` now exists.  Each\n  section represents an \"event listener pool\", which is a special kind of\n  homogeneous process group.  Each process in the pool is meant to receive\n  supervisor \"events\" via its stdin and perform some notification (e.g. send\n  a mail, log, make an http request, etc.)\n\n- Supervisord can now capture data between special tokens in\n  subprocess stdout/stderr output and emit a \"process communications\n  event\" as a result.\n\n- Supervisor's XML-RPC interface may be extended arbitrarily by programmers.\n  Additional top-level namespace XML-RPC interfaces can be added using the\n  ``[rpcinterface:foo]`` declaration in the configuration file.\n\n- New ``supervisor``-namespace XML-RPC methods have been added:\n  getAPIVersion (returns the XML-RPC API version, the older\n  \"getVersion\" is now deprecated), \"startProcessGroup\" (starts all\n  processes in a supervisor process group), \"stopProcessGroup\"\n  (stops all processes in a supervisor process group), and\n  \"sendProcessStdin\" (sends data to a process' stdin file\n  descriptor).\n\n- ``supervisor``-namespace XML-RPC methods which previously accepted\n  ony a process name as \"name\" (startProcess, stopProcess,\n  getProcessInfo, readProcessLog, tailProcessLog, and\n  clearProcessLog) now accept a \"name\" which may contain both the\n  process name and the process group name in the form\n  ``groupname:procname``.  For backwards compatibility purposes,\n  \"simple\" names will also be accepted but will be expanded\n  internally (e.g. if \"foo\" is sent as a name, it will be expanded\n  to \"foo:foo\", representing the foo process within the foo process\n  group).\n\n- 2.X versions of supervisorctl will work against supervisor 3.0\n  servers in a degraded fashion, but 3.X versions of supervisorctl\n  will not work at all against supervisor 2.X servers.\n\n2.2b1 (2007-03-31)\n------------------\n\n- Individual program configuration sections can now specify an\n  environment.\n\n- Added a 'version' command to supervisorctl.  This returns the\n  version of the supervisor2 package which the remote supervisord\n  process is using.\n\n2.1 (2007-03-17)\n----------------\n\n- When supervisord was invoked more than once, and its configuration\n  was set up to use a UNIX domain socket as the HTTP server, the\n  socket file would be erased in error.  The symptom of this was\n  that a subsequent invocation of supervisorctl could not find the\n  socket file, so the process could not be controlled (it and all of\n  its subprocesses would need to be killed by hand).\n\n- Close subprocess file descriptors properly when a subprocess exits\n  or otherwise dies.  This should result in fewer \"too many open\n  files to spawn foo\" messages when supervisor is left up for long\n  periods of time.\n\n- When a process was not killable with a \"normal\" signal at shutdown\n  time, too many \"INFO: waiting for x to die\" messages would be sent\n  to the log until we ended up killing the process with a SIGKILL.\n  Now a maximum of one every three seconds is sent up until SIGKILL\n  time.  Thanks to Ian Bicking.\n\n- Add an assertion: we never want to try to marshal None to XML-RPC\n  callers.  Issue 223 in the collector from vgatto indicates that\n  somehow a supervisor XML-RPC method is returning None (which\n  should never happen), but I cannot identify how.  Maybe the\n  assertion will give us more clues if it happens again.\n\n- Supervisor would crash when run under Python 2.5 because the\n  xmlrpclib.Transport class in Python 2.5 changed in a\n  backward-incompatible way.  Thanks to Eric Westra for the bug\n  report and a fix.\n\n- Tests now pass under Python 2.5.\n\n- Better supervisorctl reporting on stop requests that have a FAILED\n  status.\n\n- Removed duplicated code (readLog/readMainLog), thanks to Mike\n  Naberezny.\n\n- Added tailProcessLog command to the XML-RPC API.  It provides a\n  more efficient way to tail logs than readProcessLog().  Use\n  readProcessLog() to read chunks and tailProcessLog() to tail.\n  (thanks to Mike Naberezny).\n\n2.1b1 (2006-08-30)\n------------------\n\n- \"supervisord -h\" and \"supervisorctl -h\" did not work (traceback\n  instead of showing help view (thanks to Damjan from Macedonia for\n  the bug report).\n\n- Processes which started successfully after failing to start\n  initially are no longer reported in BACKOFF state once they are\n  started successfully (thanks to Damjan from Macedonia for the bug\n  report).\n\n- Add new 'maintail' command to supervisorctl shell, which allows\n  you to tail the 'main' supervisor log.  This uses a new\n  readMainLog xmlrpc API.\n\n- Various process-state-transition related changes, all internal.\n  README.txt updated with new state transition map.\n\n- startProcess and startAllProcesses xmlrpc APIs changed: instead of\n  accepting a timeout integer, these accept a wait boolean (timeout\n  is implied by process' \"startsecs\" configuration).  If wait is\n  False, do not wait for startsecs.\n\nKnown issues:\n\n- Code does not match state transition map.  Processes which are\n  configured as autorestarting which start \"successfully\" but\n  subsequently die after 'startsecs' go through the transitions\n  RUNNING -> BACKOFF -> STARTING instead of the correct transitions\n  RUNNING -> EXITED -> STARTING.  This has no real negative effect,\n  but should be fixed for correctness.\n\n2.0 (2006-08-30)\n----------------\n\n- pidfile written in daemon mode had incorrect pid.\n\n- supervisorctl: tail (non -f) did not pass through proper error\n  messages when supplied by the server.\n\n- Log signal name used to kill processes at debug level.\n\n- supervisorctl \"tail -f\" didn't work with supervisorctl sections\n  configured with an absolute unix:// URL\n\n- New \"environment\" config file option allows you to add environment\n  variable values to supervisord environment from config file.\n\n2.0b1 (2006-07-12)\n------------------\n\n- Fundamental rewrite based on 1.0.7, use distutils (only) for\n  installation, use ConfigParser rather than ZConfig, use HTTP for\n  wire protocol, web interface, less lies in supervisorctl.\n\n1.0.7 (2006-07-11)\n------------------\n\n- Don't log a waitpid error if the error value is \"no children\".\n\n- Use select() against child file descriptor pipes and bump up select\n  timeout appropriately.\n\n1.0.6 (2005-11-20)\n------------------\n\n- Various tweaks to make run more effectively on Mac OS X\n  (including fixing tests to run there, no more \"error reading\n  from fd XXX\" in logtail output, reduced disk/CPU usage as a\n  result of not writing to log file unnecessarily on Mac OS).\n\n1.0.5 (2004-07-29)\n------------------\n\n- Short description: In previous releases, managed programs that\n  created voluminous stdout/stderr output could run more slowly\n  than usual when invoked under supervisor, now they do not.\n\n  Long description: The supervisord manages child output by\n  polling pipes related to child process stderr/stdout.  Polling\n  operations are performed in the mainloop, which also performs a\n  'select' on the filedescriptor(s) related to client/server\n  operations.  In prior releases, the select timeout was set to 2\n  seconds.  This release changes the timeout to 1/10th of a second\n  in order to keep up with client stdout/stderr output.\n\n  Gory description: On Linux, at least, there is a pipe buffer\n  size fixed by the kernel of somewhere between 512 - 4096 bytes;\n  when a child process writes enough data to fill the pipe buffer,\n  it will block on further stdout/stderr output until supervisord\n  comes along and clears out the buffer by reading bytes from the\n  pipe within the mainloop.  We now clear these buffers much more\n  quickly than we did before due to the increased frequency of\n  buffer reads in the mainloop; the timeout value of 1/10th of a\n  second seems to be fast enough to clear out the buffers of child\n  process pipes when managing programs on even a very fast system\n  while still enabling the supervisord process to be in a sleeping\n  state for most of the time.\n\n1.0.4 or \"Alpha 4\" (2004-06-30)\n-------------------------------\n\n- Forgot to update version tag in configure.py, so the supervisor version\n  in a3 is listed as \"1.0.1\", where it should be \"1.0.3\".  a4 will be\n  listed as \"1.0.4'.\n\n- Instead of preventing a process from starting if setuid() can't\n  be called (if supervisord is run as nonroot, for example), just log\n  the error and proceed.\n\n1.0.3 or \"Alpha 3\" (2004-05-26)\n-------------------------------\n\n- The daemon could chew up a lot of CPU time trying to select()\n  on real files (I didn't know select() failed to block when a file\n  is at EOF).  Fixed by polling instead of using select().\n\n- Processes could \"leak\" and become zombies due to a bug in\n  reaping dead children.\n\n- supervisord now defaults to daemonizing itself.\n\n- 'daemon' config file option and -d/--daemon command-line option\n  removed from supervisord acceptable options.  In place of these\n  options, we now have a 'nodaemon' config file option and a\n  -n/--nodaemon command-line option.\n\n- logtail now works.\n\n- pidproxy changed slightly to reap children synchronously.\n\n- in alpha2 changelist, supervisord was reported to have a\n  \"noauth\" command-line option.  This was not accurate.  The way\n  to turn off auth on the server is to disinclude the \"passwdfile\"\n  config file option from the server config file.  The client\n  however does indeed still have a noauth option, which prevents\n  it from ever attempting to send authentication credentials to\n  servers.\n\n- ZPL license added for ZConfig to LICENSE.txt\n\n1.0.2 or \"Alpha 2\" (Unreleased)\n-------------------------------\n\n- supervisorctl and supervisord no longer need to run on the same machine\n  due to the addition of internet socket support.\n\n- supervisorctl and supervisord no longer share a common configuration\n  file format.\n\n- supervisorctl now uses a persistent connection to supervisord\n  (as opposed to creating a fresh connection for each command).\n\n- SRP (Secure Remote Password) authentication is now a supported form\n  of access control for supervisord.  In supervisorctl interactive mode,\n  by default, users will be asked for credentials when attempting to\n  talk to a supervisord that requires SRP authentication.\n\n- supervisord has a new command-line option and configuration file\n  option for specifying \"noauth\" mode, which signifies that it\n  should not require authentication from clients.\n\n- supervisorctl has a new command-line option and configuration\n  option for specifying \"noauth\" mode, which signifies that it\n  should never attempt to send authentication info to servers.\n\n- supervisorctl has new commands: open: opens a connection to a new\n  supervisord; close: closes the current connection.\n\n- supervisorctl's \"logtail\" command now retrieves log data from\n  supervisord's log file remotely (as opposed to reading it\n  directly from a common filesystem).  It also no longer emulates\n  \"tail -f\", it just returns <n> lines of the server's log file.\n\n- The supervisord/supervisorctl wire protocol now has protocol versioning\n  and is documented in \"protocol.txt\".\n\n- \"configfile\" command-line override -C changed to -c\n\n- top-level section name for supervisor schema changed to 'supervisord'\n  from 'supervisor'\n\n- Added 'pidproxy' shim program.\n\nKnown issues in alpha 2:\n\n- If supervisorctl loses a connection to a supervisord or if the\n  remote supervisord crashes or shuts down unexpectedly, it is\n  possible that any supervisorctl talking to it will \"hang\"\n  indefinitely waiting for data.  Pressing Ctrl-C will allow you\n  to restart supervisorctl.\n\n- Only one supervisorctl process may talk to a given supervisord\n  process at a time.  If two supervisorctl processes attempt to talk\n  to the same supervisord process, one will \"win\" and the other will\n  be disconnected.\n\n- Sometimes if a pidproxy is used to start a program, the pidproxy\n  program itself will \"leak\".\n\n1.0.0 or \"Alpha 1\" (Unreleased)\n-------------------------------\n\nInitial release.\n"
  },
  {
    "path": "COPYRIGHT.txt",
    "content": "Supervisor is Copyright (c) 2006-2015 Agendaless Consulting and Contributors.\n(http://www.agendaless.com), All Rights Reserved\n\nmedusa was (is?) Copyright (c) Sam Rushing.\n\nhttp_client.py code Copyright (c) by Daniel Krech, http://eikeon.com/.\n"
  },
  {
    "path": "LICENSES.txt",
    "content": "Supervisor is licensed under the following license:\n\n  A copyright notice accompanies this license document that identifies\n  the copyright holders.\n\n  Redistribution and use in source and binary forms, with or without\n  modification, are permitted provided that the following conditions are\n  met:\n\n  1.  Redistributions in source code must retain the accompanying\n      copyright notice, this list of conditions, and the following\n      disclaimer.\n\n  2.  Redistributions in binary form must reproduce the accompanying\n      copyright notice, this list of conditions, and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n\n  3.  Names of the copyright holders must not be used to endorse or\n      promote products derived from this software without prior\n      written permission from the copyright holders.\n\n  4.  If any files are modified, you must cause the modified files to\n      carry prominent notices stating that you changed the files and\n      the date of any change.\n\n  Disclaimer\n\n    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND\n    ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\n    TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\n    PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n    HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\n    TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\n    ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR\n    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF\n    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF\n    SUCH DAMAGE.\n\nhttp_client.py code is based on code by Daniel Krech, which was\nreleased under this license:\n\n  LICENSE AGREEMENT FOR RDFLIB 0.9.0 THROUGH 2.3.1\n  ------------------------------------------------\n  Copyright (c) 2002-2005, Daniel Krech, http://eikeon.com/\n  All rights reserved.\n\n  Redistribution and use in source and binary forms, with or without\n  modification, are permitted provided that the following conditions are\n  met:\n\n    * Redistributions of source code must retain the above copyright\n  notice, this list of conditions and the following disclaimer.\n\n    * Redistributions in binary form must reproduce the above\n  copyright notice, this list of conditions and the following\n  disclaimer in the documentation and/or other materials provided\n  with the distribution.\n\n    * Neither the name of Daniel Krech nor the names of its\n  contributors may be used to endorse or promote products derived\n  from this software without specific prior written permission.\n\n  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n  \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nMedusa, the asynchronous communications framework upon which\nsupervisor's server and client code is based, was created by Sam\nRushing:\n\n  Medusa was once distributed under a 'free for non-commercial use'\n  license, but in May of 2000 Sam Rushing changed the license to be\n  identical to the standard Python license at the time.  The standard\n  Python license has always applied to the core components of Medusa,\n  this change just frees up the rest of the system, including the http\n  server, ftp server, utilities, etc.  Medusa is therefore under the\n  following license:\n\n  ==============================\n  Permission to use, copy, modify, and distribute this software and\n  its documentation for any purpose and without fee is hereby granted,\n  provided that the above copyright notice appear in all copies and\n  that both that copyright notice and this permission notice appear in\n  supporting documentation, and that the name of Sam Rushing not be\n  used in advertising or publicity pertaining to distribution of the\n  software without specific, written prior permission.\n\n  SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,\n  INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN\n  NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR\n  CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS\n  OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,\n  NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION\n  WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n  ==============================\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include CHANGES.rst\ninclude COPYRIGHT.txt\ninclude LICENSES.txt\ninclude README.rst\ninclude tox.ini\ninclude supervisor/version.txt\ninclude supervisor/skel/*.conf\nrecursive-include supervisor/tests/fixtures *.conf *.py\nrecursive-include supervisor/ui *.html *.css *.png *.gif\ninclude docs/Makefile\nrecursive-include docs *.py *.rst *.css *.gif *.png\nrecursive-exclude docs/.build *\n"
  },
  {
    "path": "README.rst",
    "content": "Supervisor\n==========\n\nSupervisor is a client/server system that allows its users to\ncontrol a number of processes on UNIX-like operating systems.\n\nSupported Platforms\n-------------------\n\nSupervisor has been tested and is known to run on Linux (Ubuntu), Mac OS X\n(10.4, 10.5, 10.6), and Solaris (10 for Intel) and FreeBSD 6.1.  It will\nlikely work fine on most UNIX systems.\n\nSupervisor will not run at all under any version of Windows.\n\nSupervisor is intended to work on Python 3 version 3.4 or later\nand on Python 2 version 2.7.\n\nDocumentation\n-------------\n\nYou can view the current Supervisor documentation online `in HTML format\n<http://supervisord.org/>`_ .  This is where you should go for detailed\ninstallation and configuration documentation.\n\nReporting Bugs and Viewing the Source Repository\n------------------------------------------------\n\nPlease report bugs in the `GitHub issue tracker\n<https://github.com/Supervisor/supervisor/issues>`_.\n\nYou can view the source repository for supervisor via\n`https://github.com/Supervisor/supervisor\n<https://github.com/Supervisor/supervisor>`_.\n\nContributing\n------------\n\nWe'll review contributions from the community in\n`pull requests <https://help.github.com/articles/using-pull-requests>`_\non GitHub.\n"
  },
  {
    "path": "docs/.static/repoze.css",
    "content": "@import url('default.css');\nbody {\n    background-color: #006339;\n}\n \ndiv.document {\n    background-color: #dad3bd;\n}\n\ndiv.sphinxsidebar h3, h4, h5, a {\n    color: #127c56 !important;\n}\n\ndiv.related {\n    color: #dad3bd !important;\n    background-color: #00744a;\n}\n \ndiv.related a {\n    color: #dad3bd !important;\n}\n\n/* override the justify text align of the default */\n\ndiv.body p {\n    text-align: left !important;\n}\n\n/* fix google chrome <pre> tag renderings */\n\npre {\n   line-height: normal !important;\n}\n"
  },
  {
    "path": "docs/Makefile",
    "content": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD   = sphinx-build\nPAPER         =\n\n# Internal variables.\nPAPEROPT_a4     = -D latex_paper_size=a4\nPAPEROPT_letter = -D latex_paper_size=letter\nALLSPHINXOPTS   = -d .build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n\n.PHONY: help clean html web pickle htmlhelp latex changes linkcheck\n\nhelp:\n\t@echo \"Please use \\`make <target>' where <target> is one of\"\n\t@echo \"  html      to make standalone HTML files\"\n\t@echo \"  pickle    to make pickle files (usable by e.g. sphinx-web)\"\n\t@echo \"  htmlhelp  to make HTML files and a HTML help project\"\n\t@echo \"  latex     to make LaTeX files, you can set PAPER=a4 or PAPER=letter\"\n\t@echo \"  changes   to make an overview over all changed/added/deprecated items\"\n\t@echo \"  linkcheck to check all external links for integrity\"\n\nclean:\n\t-rm -rf .build/*\n\nhtml:\n\tmkdir -p .build/html .build/doctrees\n\t$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) .build/html\n\t@echo\n\t@echo \"Build finished. The HTML pages are in .build/html.\"\n\npickle:\n\tmkdir -p .build/pickle .build/doctrees\n\t$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) .build/pickle\n\t@echo\n\t@echo \"Build finished; now you can process the pickle files or run\"\n\t@echo \"  sphinx-web .build/pickle\"\n\t@echo \"to start the sphinx-web server.\"\n\nweb: pickle\n\nhtmlhelp:\n\tmkdir -p .build/htmlhelp .build/doctrees\n\t$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) .build/htmlhelp\n\t@echo\n\t@echo \"Build finished; now you can run HTML Help Workshop with the\" \\\n\t      \".hhp project file in .build/htmlhelp.\"\n\nlatex:\n\tmkdir -p .build/latex .build/doctrees\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) .build/latex\n\t@echo\n\t@echo \"Build finished; the LaTeX files are in .build/latex.\"\n\t@echo \"Run \\`make all-pdf' or \\`make all-ps' in that directory to\" \\\n\t      \"run these through (pdf)latex.\"\n\nchanges:\n\tmkdir -p .build/changes .build/doctrees\n\t$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) .build/changes\n\t@echo\n\t@echo \"The overview file is in .build/changes.\"\n\nlinkcheck:\n\tmkdir -p .build/linkcheck .build/doctrees\n\t$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) .build/linkcheck\n\t@echo\n\t@echo \"Link check complete; look for any errors in the above output \" \\\n\t      \"or in .build/linkcheck/output.txt.\"\n"
  },
  {
    "path": "docs/api.rst",
    "content": ".. _xml_rpc:\n\nXML-RPC API Documentation\n=========================\n\nTo use the XML-RPC interface, first make sure you have configured the interface\nfactory properly by setting the default factory. See :ref:`rpcinterface_factories`.\n\nThen you can connect to supervisor's HTTP port\nwith any XML-RPC client library and run commands against it.\n\nAn example of doing this using Python 2's ``xmlrpclib`` client library\nis as follows.\n\n.. code-block:: python\n\n    import xmlrpclib\n    server = xmlrpclib.Server('http://localhost:9001/RPC2')\n\nAn example of doing this using Python 3's ``xmlrpc.client`` library\nis as follows.\n\n.. code-block:: python\n\n    from xmlrpc.client import ServerProxy\n    server = ServerProxy('http://localhost:9001/RPC2')\n\nYou may call methods against :program:`supervisord` and its\nsubprocesses by using the ``supervisor`` namespace.  An example is\nprovided below.\n\n.. code-block:: python\n\n    server.supervisor.getState()\n\nYou can get a list of methods supported by the\n:program:`supervisord` XML-RPC interface by using the XML-RPC\n``system.listMethods`` API:\n\n.. code-block:: python\n\n    server.system.listMethods()\n\nYou can see help on a method by using the ``system.methodHelp`` API\nagainst the method:\n\n.. code-block:: python\n\n    server.system.methodHelp('supervisor.shutdown')\n\nThe :program:`supervisord` XML-RPC interface also supports the\n`XML-RPC multicall API\n<http://web.archive.org/web/20060824100531/http://www.xmlrpc.com/discuss/msgReader$1208>`_.\n\nYou can extend :program:`supervisord` functionality with new XML-RPC\nAPI methods by adding new top-level RPC interfaces as necessary.\nSee :ref:`rpcinterface_factories`.\n\n.. note::\n\n  Any XML-RPC method call may result in a fault response.  This includes errors caused\n  by the client such as bad arguments, and any errors that make :program:`supervisord`\n  unable to fulfill the request.  Many XML-RPC client programs will raise an exception\n  when a fault response is encountered.\n\n.. automodule:: supervisor.rpcinterface\n\nStatus and Control\n------------------\n\n  .. autoclass:: SupervisorNamespaceRPCInterface\n\n    .. automethod:: getAPIVersion\n\n        This API is versioned separately from Supervisor itself. The API version\n        returned by ``getAPIVersion`` only changes when the API changes. Its purpose\n        is to help the client identify with which version of the Supervisor API it\n        is communicating.\n\n        When writing software that communicates with this API, it is highly\n        recommended that you first test the API version for compatibility before\n        making method calls.\n\n        .. note::\n\n          The ``getAPIVersion`` method replaces ``getVersion`` found in Supervisor\n          versions prior to 3.0a1. It is aliased for compatibility but getVersion()\n          is deprecated and support will be dropped from Supervisor in a future\n          version.\n\n    .. automethod:: getSupervisorVersion\n\n    .. automethod:: getIdentification\n\n        This method allows the client to identify with which Supervisor\n        instance it is communicating in the case of environments where\n        multiple Supervisors may be running.\n\n        The identification is a string that must be set in Supervisor’s\n        configuration file. This method simply returns that value back to the\n        client.\n\n    .. automethod:: getState\n\n        This is an internal value maintained by Supervisor that determines what\n        Supervisor believes to be its current operational state.\n\n        Some method calls can alter the current state of the Supervisor. For\n        example, calling the method supervisor.shutdown() while the station is\n        in the RUNNING state places the Supervisor in the SHUTDOWN state while\n        it is shutting down.\n\n        The supervisor.getState() method provides a means for the client to check\n        Supervisor's state, both for informational purposes and to ensure that the\n        methods it intends to call will be permitted.\n\n        The return value is a struct:\n\n        .. code-block:: python\n\n            {'statecode': 1,\n             'statename': 'RUNNING'}\n\n        The possible return values are:\n\n        +---------+----------+----------------------------------------------+\n        |statecode|statename |Description                                   |\n        +=========+==========+==============================================+\n        | 2       |FATAL     |Supervisor has experienced a serious error.   |\n        +---------+----------+----------------------------------------------+\n        | 1       |RUNNING   |Supervisor is working normally.               |\n        +---------+----------+----------------------------------------------+\n        | 0       |RESTARTING|Supervisor is in the process of restarting.   |\n        +---------+----------+----------------------------------------------+\n        | -1      |SHUTDOWN  |Supervisor is in the process of shutting down.|\n        +---------+----------+----------------------------------------------+\n\n        The ``FATAL`` state reports unrecoverable errors, such as internal\n        errors inside Supervisor or system runaway conditions. Once set to\n        ``FATAL``, the Supervisor can never return to any other state without\n        being restarted.\n\n        In the ``FATAL`` state, all future methods except\n        supervisor.shutdown() and supervisor.restart() will automatically fail\n        without being called and the fault ``FATAL_STATE`` will be raised.\n\n        In the ``SHUTDOWN`` or ``RESTARTING`` states, all method calls are\n        ignored and their possible return values are undefined.\n\n    .. automethod:: getPID\n\n    .. automethod:: readLog\n\n        It can either return the entire log, a number of characters from the\n        tail of the log, or a slice of the log specified by the offset and\n        length parameters:\n\n        +--------+---------+------------------------------------------------+\n        | Offset | Length  | Behavior of ``readProcessLog``                 |\n        +========+=========+================================================+\n        |Negative|Not Zero | Bad arguments. This will raise the fault       |\n        |        |         | ``BAD_ARGUMENTS``.                             |\n        +--------+---------+------------------------------------------------+\n        |Negative|Zero     | This will return the tail of the log, or offset|\n        |        |         | number of characters from the end of the log.  |\n        |        |         | For example, if ``offset`` = -4 and ``length`` |\n        |        |         | = 0, then the last four characters will be     |\n        |        |         | returned from the end of the log.              |\n        +--------+---------+------------------------------------------------+\n        |Zero or |Negative | Bad arguments. This will raise the fault       |\n        |Positive|         | ``BAD_ARGUMENTS``.                             |\n        +--------+---------+------------------------------------------------+\n        |Zero or |Zero     | All characters will be returned from the       |\n        |Positive|         | ``offset`` specified.                          |\n        +--------+---------+------------------------------------------------+\n        |Zero or |Positive | A number of characters length will be returned |\n        |Positive|         | from the ``offset``.                           |\n        +--------+---------+------------------------------------------------+\n\n        If the log is empty and the entire log is requested, an empty string\n        is returned.\n\n        If either offset or length is out of range, the fault\n        ``BAD_ARGUMENTS`` will be returned.\n\n        If the log cannot be read, this method will raise either the\n        ``NO_FILE`` error if the file does not exist or the ``FAILED`` error\n        if any other problem was encountered.\n\n        .. note::\n\n          The readLog() method replaces readMainLog() found in Supervisor\n          versions prior to 2.1. It is aliased for compatibility but\n          readMainLog() is deprecated and support will be dropped from\n          Supervisor in a future version.\n\n\n    .. automethod:: clearLog\n\n        If the log cannot be cleared because the log file does not exist, the\n        fault ``NO_FILE`` will be raised. If the log cannot be cleared for any\n        other reason, the fault ``FAILED`` will be raised.\n\n    .. automethod:: shutdown\n\n        This method shuts down the Supervisor daemon. If any processes are running,\n        they are automatically killed without warning.\n\n        Unlike most other methods, if Supervisor is in the ``FATAL`` state,\n        this method will still function.\n\n    .. automethod:: restart\n\n        This method soft restarts the Supervisor daemon. If any processes are\n        running, they are automatically killed without warning. Note that the\n        actual UNIX process for Supervisor cannot restart; only Supervisor’s\n        main program loop. This has the effect of resetting the internal\n        states of Supervisor.\n\n        Unlike most other methods, if Supervisor is in the ``FATAL`` state,\n        this method will still function.\n\n\nProcess Control\n---------------\n\n  .. autoclass:: SupervisorNamespaceRPCInterface\n    :noindex:\n\n    .. automethod:: getProcessInfo\n\n        The return value is a struct:\n\n        .. code-block:: python\n\n            {'name':           'process name',\n             'group':          'group name',\n             'description':    'pid 18806, uptime 0:03:12'\n             'start':          1200361776,\n             'stop':           0,\n             'now':            1200361812,\n             'state':          20,\n             'statename':      'RUNNING',\n             'spawnerr':       '',\n             'exitstatus':     0,\n             'logfile':        '/path/to/stdout-log', # deprecated, b/c only\n             'stdout_logfile': '/path/to/stdout-log',\n             'stderr_logfile': '/path/to/stderr-log',\n             'pid':            1}\n\n        .. describe:: name\n\n            Name of the process\n\n        .. describe:: group\n\n            Name of the process' group\n\n        .. describe:: description\n\n            If process state is running description's value is process_id\n            and uptime. Example \"pid 18806, uptime 0:03:12 \".\n            If process state is stopped description's value is stop time.\n            Example:\"Jun 5 03:16 PM \".\n\n        .. describe:: start\n\n            UNIX timestamp of when the process was started\n\n        .. describe:: stop\n\n            UNIX timestamp of when the process last ended, or 0 if the process\n            has never been stopped.\n\n        .. describe:: now\n\n            UNIX timestamp of the current time, which can be used to calculate\n            process up-time.\n\n        .. describe:: state\n\n            State code, see :ref:`process_states`.\n\n        .. describe:: statename\n\n            String description of `state`, see :ref:`process_states`.\n\n        .. describe:: logfile\n\n            Deprecated alias for ``stdout_logfile``.  This is provided only\n            for compatibility with clients written for Supervisor 2.x and\n            may be removed in the future.  Use ``stdout_logfile`` instead.\n\n        .. describe:: stdout_logfile\n\n            Absolute path and filename to the STDOUT logfile\n\n        .. describe:: stderr_logfile\n\n            Absolute path and filename to the STDERR logfile\n\n        .. describe:: spawnerr\n\n            Description of error that occurred during spawn, or empty string\n            if none.\n\n        .. describe:: exitstatus\n\n            Exit status (errorlevel) of process, or 0 if the process is still\n            running.\n\n        .. describe:: pid\n\n            UNIX process ID (PID) of the process, or 0 if the process is not\n            running.\n\n\n    .. automethod:: getAllProcessInfo\n\n        Each element contains a struct, and this struct contains the exact\n        same elements as the struct returned by ``getProcessInfo``. If the process\n        table is empty, an empty array is returned.\n\n    .. automethod:: getAllConfigInfo\n\n    .. automethod:: startProcess\n\n    .. automethod:: startAllProcesses\n\n    .. automethod:: startProcessGroup\n\n    .. automethod:: stopProcess\n\n    .. automethod:: stopProcessGroup\n\n    .. automethod:: stopAllProcesses\n\n    .. automethod:: signalProcess\n\n    .. automethod:: signalProcessGroup\n\n    .. automethod:: signalAllProcesses\n\n    .. automethod:: sendProcessStdin\n\n    .. automethod:: sendRemoteCommEvent\n\n    .. automethod:: reloadConfig\n\n    .. automethod:: addProcessGroup\n\n    .. automethod:: removeProcessGroup\n\nProcess Logging\n---------------\n\n  .. autoclass:: SupervisorNamespaceRPCInterface\n    :noindex:\n\n    .. automethod:: readProcessStdoutLog\n\n    .. automethod:: readProcessStderrLog\n\n    .. automethod:: tailProcessStdoutLog\n\n    .. automethod:: tailProcessStderrLog\n\n    .. automethod:: clearProcessLogs\n\n    .. automethod:: clearAllProcessLogs\n\n\n.. automodule:: supervisor.xmlrpc\n\nSystem Methods\n--------------\n\n  .. autoclass:: SystemNamespaceRPCInterface\n\n    .. automethod:: listMethods\n\n    .. automethod:: methodHelp\n\n    .. automethod:: methodSignature\n\n    .. automethod:: multicall\n"
  },
  {
    "path": "docs/conf.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# Supervisor documentation build configuration file\n#\n# This file is execfile()d with the current directory set to its containing\n# dir.\n#\n# The contents of this file are pickled, so don't put values in the\n# namespace that aren't pickleable (module imports are okay, they're\n# removed automatically).\n#\n# All configuration values have a default value; values that are commented\n# out serve to show the default value.\n\nimport sys, os\nfrom datetime import date\n\n# If your extensions are in another directory, add it here. If the\n# directory is relative to the documentation root, use os.path.abspath to\n# make it absolute, like shown here.\n#sys.path.append(os.path.abspath('some/directory'))\n\nparent = os.path.dirname(os.path.dirname(__file__))\nsys.path.append(os.path.abspath(parent))\n\nversion_txt = os.path.join(parent, 'supervisor/version.txt')\nsupervisor_version = open(version_txt).read().strip()\n\n# General configuration\n# ---------------------\n\n# Add any Sphinx extension module names here, as strings. They can be\n# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.\nextensions = ['sphinx.ext.autodoc']\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = ['.templates']\n\n# The suffix of source filenames.\nsource_suffix = '.rst'\n\n# The master toctree document.\nmaster_doc = 'index'\n\n# General substitutions.\nproject = 'Supervisor'\nyear = date.today().year\ncopyright = '2004-%d, Agendaless Consulting and Contributors' % year\n\n# The default replacements for |version| and |release|, also used in various\n# other places throughout the built documents.\n#\n# The short X.Y version.\nversion = supervisor_version\n# The full version, including alpha/beta/rc tags.\nrelease = version\n\n# There are two options for replacing |today|: either, you set today to\n# some non-false value, then it is used:\n#today = ''\n# Else, today_fmt is used as the format for a strftime call.\ntoday_fmt = '%B %d, %Y'\n\n# List of documents that shouldn't be included in the build.\n#unused_docs = []\n\n# List of directories, relative to source directories, that shouldn't be\n# searched for source files.\n#exclude_dirs = []\n\n# The reST default role (used for this markup: `text`) to use for all\n# documents.\n#default_role = None\n\n# If true, '()' will be appended to :func: etc. cross-reference text.\n#add_function_parentheses = True\n\n# If true, the current module name will be prepended to all description\n# unit titles (such as .. function::).\n#add_module_names = True\n\n# If true, sectionauthor and moduleauthor directives will be shown in the\n# output. They are ignored by default.\n#show_authors = False\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = 'sphinx'\n\n\n# Options for HTML output\n# -----------------------\n\n# The style sheet to use for HTML and HTML Help pages. A file of that name\n# must exist either in Sphinx' static/ path, or in one of the custom paths\n# given in html_static_path.\nhtml_style = 'repoze.css'\n\n# The name for this set of Sphinx documents.  If None, it defaults to\n# \"<project> v<release> documentation\".\n#html_title = None\n\n# A shorter title for the navigation bar.  Default is the same as\n# html_title.\n#html_short_title = None\n\n# The name of an image file (within the static path) to place at the top of\n# the sidebar.\nhtml_logo = '.static/logo_hi.gif'\n\n# The name of an image file (within the static path) to use as favicon of\n# the docs.  This file should be a Windows icon file (.ico) being 16x16 or\n# 32x32 pixels large.\n#html_favicon = None\n\n# Add any paths that contain custom static files (such as style sheets)\n# here, relative to this directory. They are copied after the builtin\n# static files, so a file named \"default.css\" will overwrite the builtin\n# \"default.css\".\nhtml_static_path = ['.static']\n\n# If not '', a 'Last updated on:' timestamp is inserted at every page\n# bottom, using the given strftime format.\nhtml_last_updated_fmt = '%b %d, %Y'\n\n# If true, SmartyPants will be used to convert quotes and dashes to\n# typographically correct entities.\n#html_use_smartypants = True\n\n# Custom sidebar templates, maps document names to template names.\n#html_sidebars = {}\n\n# Additional templates that should be rendered to pages, maps page names to\n# template names.\n#html_additional_pages = {}\n\n# If false, no module index is generated.\n#html_use_modindex = True\n\n# If false, no index is generated.\n#html_use_index = True\n\n# If true, the index is split into individual pages for each letter.\n#html_split_index = False\n\n# If true, the reST sources are included in the HTML build as\n# _sources/<name>.\n#html_copy_source = True\n\n# If true, an OpenSearch description file will be output, and all pages\n# will contain a <link> tag referring to it.  The value of this option must\n# be the base URL from which the finished HTML is served.\n#html_use_opensearch = ''\n\n# If nonempty, this is the file name suffix for HTML files (e.g. \".xhtml\").\n#html_file_suffix = ''\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = 'supervisor'\n\n\n# Options for LaTeX output\n# ------------------------\n\n# The paper size ('letter' or 'a4').\n#latex_paper_size = 'letter'\n\n# The font size ('10pt', '11pt' or '12pt').\n#latex_font_size = '10pt'\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title,\n#  author, document class [howto/manual]).\nlatex_documents = [\n  ('index', 'supervisor.tex', 'supervisor Documentation',\n   'Supervisor Developers', 'manual'),\n]\n\n# The name of an image file (relative to this directory) to place at the\n# top of the title page.\nlatex_logo = '.static/logo_hi.gif'\n\n# For \"manual\" documents, if this is true, then toplevel headings are\n# parts, not chapters.\n#latex_use_parts = False\n\n# Additional stuff for the LaTeX preamble.\n#latex_preamble = ''\n\n# Documents to append as an appendix to all manuals.\n#latex_appendices = []\n\n# If false, no module index is generated.\n#latex_use_modindex = True\n"
  },
  {
    "path": "docs/configuration.rst",
    "content": "Configuration File\n==================\n\nThe Supervisor configuration file is conventionally named\n:file:`supervisord.conf`.  It is used by both :program:`supervisord`\nand :program:`supervisorctl`.  If either application is started\nwithout the ``-c`` option (the option which is used to tell the\napplication the configuration filename explicitly), the application\nwill look for a file named :file:`supervisord.conf` within the\nfollowing locations, in the specified order.  It will use the first\nfile it finds.\n\n#. :file:`../etc/supervisord.conf` (Relative to the executable)\n\n#. :file:`../supervisord.conf` (Relative to the executable)\n\n#. :file:`$CWD/supervisord.conf`\n\n#. :file:`$CWD/etc/supervisord.conf`\n\n#. :file:`/etc/supervisord.conf`\n\n#. :file:`/etc/supervisor/supervisord.conf` (since Supervisor 3.3.0)\n\n.. note::\n\n  Many versions of Supervisor packaged for Debian and Ubuntu included a patch\n  that added ``/etc/supervisor/supervisord.conf`` to the search paths.  The\n  first PyPI package of Supervisor to include it was Supervisor 3.3.0.\n\nFile Format\n-----------\n\n:file:`supervisord.conf` is a Windows-INI-style (Python ConfigParser)\nfile.  It has sections (each denoted by a ``[header]``) and key / value\npairs within the sections.  The sections and their allowable values\nare described below.\n\nEnvironment Variables\n~~~~~~~~~~~~~~~~~~~~~\n\nEnvironment variables that are present in the environment at the time that\n:program:`supervisord` is started can be used in the configuration file\nusing the Python string expression syntax ``%(ENV_X)s``:\n\n.. code-block:: ini\n\n    [program:example]\n    command=/usr/bin/example --loglevel=%(ENV_LOGLEVEL)s\n\nIn the example above, the expression ``%(ENV_LOGLEVEL)s`` would be expanded\nto the value of the environment variable ``LOGLEVEL``.\n\n.. note::\n\n    In Supervisor 3.2 and later, ``%(ENV_X)s`` expressions are supported in\n    all options.  In prior versions, some options support them, but most\n    do not.  See the documentation for each option below.\n\n\n``[unix_http_server]`` Section Settings\n---------------------------------------\n\nThe :file:`supervisord.conf` file contains a section named\n``[unix_http_server]`` under which configuration parameters for an\nHTTP server that listens on a UNIX domain socket should be inserted.\nIf the configuration file has no ``[unix_http_server]`` section, a\nUNIX domain socket HTTP server will not be started.  The allowable\nconfiguration values are as follows.\n\n``[unix_http_server]`` Section Values\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n``file``\n\n  A path to a UNIX domain socket on which supervisor will listen for\n  HTTP/XML-RPC requests.  :program:`supervisorctl` uses XML-RPC to\n  communicate with :program:`supervisord` over this port.  This option\n  can include the value ``%(here)s``, which expands to the directory\n  in which the :program:`supervisord` configuration file was found.\n\n  *Default*:  None.\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n.. warning::\n\n  The example configuration output by :program:`echo_supervisord_conf` uses\n  ``/tmp/supervisor.sock`` as the socket file.  That path is an example only\n  and will likely need to be changed to a location more appropriate for your\n  system.  Some systems periodically delete older files in ``/tmp``.  If the\n  socket file is deleted, :program:`supervisorctl` will be unable to\n  connect to :program:`supervisord`.\n\n``chmod``\n\n  Change the UNIX permission mode bits of the UNIX domain socket to\n  this value at startup.\n\n  *Default*: ``0700``\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``chown``\n\n  Change the user and group of the socket file to this value.  May be\n  a UNIX username (e.g. ``chrism``) or a UNIX username and group\n  separated by a colon (e.g. ``chrism:wheel``).\n\n  *Default*:  Use the username and group of the user who starts supervisord.\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``username``\n\n  The username required for authentication to this HTTP server.\n\n  *Default*:  No username required.\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``password``\n\n  The password required for authentication to this HTTP server.  This\n  can be a cleartext password, or can be specified as a SHA-1 hash if\n  prefixed by the string ``{SHA}``.  For example,\n  ``{SHA}82ab876d1387bfafe46cc1c8a2ef074eae50cb1d`` is the SHA-stored\n  version of the password \"thepassword\".\n\n  Note that hashed password must be in hex format.\n\n  *Default*:  No password required.\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``[unix_http_server]`` Section Example\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. code-block:: ini\n\n   [unix_http_server]\n   file = /tmp/supervisor.sock\n   chmod = 0777\n   chown= nobody:nogroup\n   username = user\n   password = 123\n\n``[inet_http_server]`` Section Settings\n---------------------------------------\n\nThe :file:`supervisord.conf` file contains a section named\n``[inet_http_server]`` under which configuration parameters for an\nHTTP server that listens on a TCP (internet) socket should be\ninserted.  If the configuration file has no ``[inet_http_server]``\nsection, an inet HTTP server will not be started.  The allowable\nconfiguration values are as follows.\n\n.. warning::\n\n  The inet HTTP server is not enabled by default.  If you choose to enable it,\n  please read the following security warning.  The inet HTTP server is intended\n  for use within a trusted environment only.  It should only be bound to localhost\n  or only accessible from within an isolated, trusted network.  The inet HTTP server\n  does not support any form of encryption.  The inet HTTP server does not use\n  authentication by default (see the ``username=`` and ``password=`` options).\n  The inet HTTP server can be controlled remotely from :program:`supervisorctl`.\n  It also serves a web interface that allows subprocesses to be started or stopped,\n  and subprocess logs to be viewed.  **Never expose the inet HTTP server to the\n  public internet.**\n\n``[inet_http_server]`` Section Values\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n``port``\n\n  A TCP host:port value or (e.g. ``127.0.0.1:9001``) on which\n  supervisor will listen for HTTP/XML-RPC requests.\n  :program:`supervisorctl` will use XML-RPC to communicate with\n  :program:`supervisord` over this port.  To listen on all interfaces\n  in the machine, use ``:9001`` or ``*:9001``.  Please read the security\n  warning above.\n\n  *Default*:  No default.\n\n  *Required*:  Yes.\n\n  *Introduced*: 3.0\n\n``username``\n\n  The username required for authentication to this HTTP server.\n\n  *Default*:  No username required.\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``password``\n\n  The password required for authentication to this HTTP server.  This\n  can be a cleartext password, or can be specified as a SHA-1 hash if\n  prefixed by the string ``{SHA}``.  For example,\n  ``{SHA}82ab876d1387bfafe46cc1c8a2ef074eae50cb1d`` is the SHA-stored\n  version of the password \"thepassword\".\n\n  Note that hashed password must be in hex format.\n\n  *Default*:  No password required.\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``[inet_http_server]`` Section Example\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. code-block:: ini\n\n   [inet_http_server]\n   port = 127.0.0.1:9001\n   username = user\n   password = 123\n\n``[supervisord]`` Section Settings\n----------------------------------\n\nThe :file:`supervisord.conf` file contains a section named\n``[supervisord]`` in which global settings related to the\n:program:`supervisord` process should be inserted.  These are as\nfollows.\n\n``[supervisord]`` Section Values\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n``logfile``\n\n  The path to the activity log of the supervisord process.  This\n  option can include the value ``%(here)s``, which expands to the\n  directory in which the supervisord configuration file was found.\n\n  .. note::\n\n    If ``logfile`` is set to a special file like ``/dev/stdout`` that is\n    not seekable, log rotation must be disabled by setting\n    ``logfile_maxbytes = 0``.\n\n  *Default*:  :file:`$CWD/supervisord.log`\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``logfile_maxbytes``\n\n  The maximum number of bytes that may be consumed by the activity log\n  file before it is rotated (suffix multipliers like \"KB\", \"MB\", and\n  \"GB\" can be used in the value).  Set this value to 0 to indicate an\n  unlimited log size.\n\n  *Default*:  50MB\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``logfile_backups``\n\n  The number of backups to keep around resulting from activity log\n  file rotation.  If set to 0, no backups will be kept.\n\n  *Default*:  10\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``loglevel``\n\n  The logging level, dictating what is written to the supervisord\n  activity log.  One of ``critical``, ``error``, ``warn``, ``info``,\n  ``debug``, ``trace``, or ``blather``.  Note that at log level\n  ``debug``, the supervisord log file will record the stderr/stdout\n  output of its child processes and extended info about process\n  state changes, which is useful for debugging a process which isn't\n  starting properly.  See also: :ref:`activity_log_levels`.\n\n  *Default*:  info\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``pidfile``\n\n  The location in which supervisord keeps its pid file.  This option\n  can include the value ``%(here)s``, which expands to the directory\n  in which the supervisord configuration file was found.\n\n  *Default*:  :file:`$CWD/supervisord.pid`\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``umask``\n\n  The :term:`umask` of the supervisord process.\n\n  *Default*:  ``022``\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``nodaemon``\n\n  If true, supervisord will start in the foreground instead of\n  daemonizing.\n\n  *Default*:  false\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``silent``\n\n  If true and not daemonized, logs will not be directed to stdout.\n\n  *Default*:  false\n\n  *Required*: No.\n\n  *Introduced*: 4.2.0\n\n``minfds``\n\n  The minimum number of file descriptors that must be available before\n  supervisord will start successfully.  A call to setrlimit will be made\n  to attempt to raise the soft and hard limits of the supervisord process to\n  satisfy ``minfds``.  The hard limit may only be raised if supervisord\n  is run as root.  supervisord uses file descriptors liberally, and will\n  enter a failure mode when one cannot be obtained from the OS, so it's\n  useful to be able to specify a minimum value to ensure it doesn't run out\n  of them during execution.  These limits will be inherited by the managed\n  subprocesses.  This option is particularly useful on Solaris,\n  which has a low per-process fd limit by default.\n\n  *Default*:  1024\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``minprocs``\n\n  The minimum number of process descriptors that must be available\n  before supervisord will start successfully.  A call to setrlimit will be\n  made to attempt to raise the soft and hard limits of the supervisord process\n  to satisfy ``minprocs``.  The hard limit may only be raised if supervisord\n  is run as root.  supervisord will enter a failure mode when the OS runs out\n  of process descriptors, so it's useful to ensure that enough process\n  descriptors are available upon :program:`supervisord` startup.\n\n  *Default*:  200\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``nocleanup``\n\n  Prevent supervisord from clearing any existing ``AUTO``\n  child log files at startup time.  Useful for debugging.\n\n  *Default*:  false\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``childlogdir``\n\n  The directory used for ``AUTO`` child log files.  This option can\n  include the value ``%(here)s``, which expands to the directory in\n  which the :program:`supervisord` configuration file was found.\n\n  *Default*: value of Python's :func:`tempfile.gettempdir`\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``user``\n\n  Instruct :program:`supervisord` to switch users to this UNIX user\n  account before doing any meaningful processing.  The user can only\n  be switched if :program:`supervisord` is started as the root user.\n\n  *Default*: do not switch users\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n  *Changed*: 3.3.4.  If :program:`supervisord` can't switch to the\n  specified user, it will write an error message to ``stderr`` and\n  then exit immediately.  In earlier versions, it would continue to\n  run but would log a message at the ``critical`` level.\n\n``directory``\n\n  When :program:`supervisord` daemonizes, switch to this directory.\n  This option can include the value ``%(here)s``, which expands to the\n  directory in which the :program:`supervisord` configuration file was\n  found.\n\n  *Default*: do not cd\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``strip_ansi``\n\n  Strip all ANSI escape sequences from child log files.\n\n  *Default*: false\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``environment``\n\n  A list of key/value pairs in the form ``KEY=\"val\",KEY2=\"val2\"`` that\n  will be placed in the environment of all child processes.  This does\n  not change the environment of :program:`supervisord` itself.  This\n  option can include the value ``%(here)s``, which expands to the\n  directory in which the supervisord configuration file was found.\n  Values containing non-alphanumeric characters should be quoted\n  (e.g. ``KEY=\"val:123\",KEY2=\"val,456\"``).  Otherwise, quoting the\n  values is optional but recommended.  To escape percent characters,\n  simply use two. (e.g. ``URI=\"/first%%20name\"``) **Note** that\n  subprocesses will inherit the environment variables of the shell\n  used to start :program:`supervisord` except for the ones overridden\n  here and within the program's ``environment`` option.  See\n  :ref:`subprocess_environment`.\n\n  *Default*: no values\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``identifier``\n\n  The identifier string for this supervisor process, used by the RPC\n  interface.\n\n  *Default*: supervisor\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``[supervisord]`` Section Example\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. code-block:: ini\n\n   [supervisord]\n   logfile = /tmp/supervisord.log\n   logfile_maxbytes = 50MB\n   logfile_backups=10\n   loglevel = info\n   pidfile = /tmp/supervisord.pid\n   nodaemon = false\n   minfds = 1024\n   minprocs = 200\n   umask = 022\n   user = chrism\n   identifier = supervisor\n   directory = /tmp\n   nocleanup = true\n   childlogdir = /tmp\n   strip_ansi = false\n   environment = KEY1=\"value1\",KEY2=\"value2\"\n\n``[supervisorctl]`` Section Settings\n------------------------------------\n\n  The configuration file may contain settings for the\n  :program:`supervisorctl` interactive shell program.  These options\n  are listed below.\n\n``[supervisorctl]`` Section Values\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n``serverurl``\n\n  The URL that should be used to access the supervisord server,\n  e.g. ``http://localhost:9001``.  For UNIX domain sockets, use\n  ``unix:///absolute/path/to/file.sock``.\n\n  *Default*: ``http://localhost:9001``\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``username``\n\n  The username to pass to the supervisord server for use in\n  authentication.  This should be same as ``username`` from the\n  supervisord server configuration for the port or UNIX domain socket\n  you're attempting to access.\n\n  *Default*: No username\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``password``\n\n  The password to pass to the supervisord server for use in\n  authentication. This should be the cleartext version of ``password``\n  from the supervisord server configuration for the port or UNIX\n  domain socket you're attempting to access.  This value cannot be\n  passed as a SHA hash.  Unlike other passwords specified in this\n  file, it must be provided in cleartext.\n\n  *Default*: No password\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``prompt``\n\n  String used as supervisorctl prompt.\n\n  *Default*: ``supervisor``\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``history_file``\n\n  A path to use as the ``readline`` persistent history file.  If you\n  enable this feature by choosing a path, your supervisorctl commands\n  will be kept in the file, and you can use readline (e.g. arrow-up)\n  to invoke commands you performed in your last supervisorctl session.\n\n  *Default*: No file\n\n  *Required*:  No.\n\n  *Introduced*: 3.0a5\n\n``[supervisorctl]`` Section Example\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. code-block:: ini\n\n   [supervisorctl]\n   serverurl = unix:///tmp/supervisor.sock\n   username = chris\n   password = 123\n   prompt = mysupervisor\n\n.. _programx_section:\n\n``[program:x]`` Section Settings\n--------------------------------\n\nThe configuration file must contain one or more ``program`` sections\nin order for supervisord to know which programs it should start and\ncontrol.  The header value is composite value.  It is the word\n\"program\", followed directly by a colon, then the program name.  A\nheader value of ``[program:foo]`` describes a program with the name of\n\"foo\".  The name is used within client applications that control the\nprocesses that are created as a result of this configuration.  It is\nan error to create a ``program`` section that does not have a name.\nThe name must not include a colon character or a bracket character.\nThe value of the name is used as the value for the\n``%(program_name)s`` string expression expansion within other values\nwhere specified.\n\n.. note::\n\n   A ``[program:x]`` section actually represents a \"homogeneous\n   process group\" to supervisor (as of 3.0).  The members of the group\n   are defined by the combination of the ``numprocs`` and\n   ``process_name`` parameters in the configuration.  By default, if\n   numprocs and process_name are left unchanged from their defaults,\n   the group represented by ``[program:x]`` will be named ``x`` and\n   will have a single process named ``x`` in it.  This provides a\n   modicum of backwards compatibility with older supervisor releases,\n   which did not treat program sections as homogeneous process group\n   definitions.\n\n   But for instance, if you have a ``[program:foo]`` section with a\n   ``numprocs`` of 3 and a ``process_name`` expression of\n   ``%(program_name)s_%(process_num)02d``, the \"foo\" group will\n   contain three processes, named ``foo_00``, ``foo_01``, and\n   ``foo_02``.  This makes it possible to start a number of very\n   similar processes using a single ``[program:x]`` section.  All\n   logfile names, all environment strings, and the command of programs\n   can also contain similar Python string expressions, to pass\n   slightly different parameters to each process.\n\n``[program:x]`` Section Values\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n``command``\n\n  The command that will be run when this program is started.  The\n  command can be either absolute (e.g. ``/path/to/programname``) or\n  relative (e.g. ``programname``).  If it is relative, the\n  supervisord's environment ``$PATH`` will be searched for the\n  executable.  Programs can accept arguments, e.g. ``/path/to/program\n  foo bar``.  The command line can use double quotes to group\n  arguments with spaces in them to pass to the program,\n  e.g. ``/path/to/program/name -p \"foo bar\"``.  Note that the value of\n  ``command`` may include Python string expressions,\n  e.g. ``/path/to/programname --port=80%(process_num)02d`` might\n  expand to ``/path/to/programname --port=8000`` at runtime.  String\n  expressions are evaluated against a dictionary containing the keys\n  ``group_name``, ``host_node_name``, ``program_name``, ``process_num``,\n  ``numprocs``, ``here`` (the directory of the supervisord config file),\n  and all supervisord's environment variables prefixed with ``ENV_``.\n  Controlled programs should themselves not be daemons, as supervisord\n  assumes it is responsible for daemonizing its subprocesses (see\n  :ref:`nondaemonizing_of_subprocesses`).\n\n  .. note::\n\n    The command will be truncated if it looks like a config file comment,\n    e.g. ``command=bash -c 'foo ; bar'`` will be truncated to\n    ``command=bash -c 'foo``.  Quoting will not prevent this behavior,\n    since the configuration file reader does not parse the command like\n    a shell would.\n\n  *Default*: No default.\n\n  *Required*:  Yes.\n\n  *Introduced*: 3.0\n\n  *Changed*: 4.2.0.  Added support for the ``numprocs`` expansion.\n\n``process_name``\n\n  A Python string expression that is used to compose the supervisor\n  process name for this process.  You usually don't need to worry\n  about setting this unless you change ``numprocs``.  The string\n  expression is evaluated against a dictionary that includes\n  ``group_name``, ``host_node_name``, ``process_num``, ``program_name``,\n  and ``here`` (the directory of the supervisord config file).\n\n  *Default*: ``%(program_name)s``\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``numprocs``\n\n  Supervisor will start as many instances of this program as named by\n  numprocs.  Note that if numprocs > 1, the ``process_name``\n  expression must include ``%(process_num)s`` (or any other\n  valid Python string expression that includes ``process_num``) within\n  it.\n\n  *Default*: 1\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``numprocs_start``\n\n  An integer offset that is used to compute the number at which\n  ``process_num`` starts.\n\n  *Default*: 0\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``priority``\n\n  The relative priority of the program in the start and shutdown\n  ordering.  Lower priorities indicate programs that start first and\n  shut down last at startup and when aggregate commands are used in\n  various clients (e.g. \"start all\"/\"stop all\").  Higher priorities\n  indicate programs that start last and shut down first.\n\n  *Default*: 999\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``autostart``\n\n  If true, this program will start automatically when supervisord is\n  started.\n\n  *Default*: true\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``startsecs``\n\n  The total number of seconds which the program needs to stay running\n  after a startup to consider the start successful (moving the process\n  from the ``STARTING`` state to the ``RUNNING`` state).  Set to ``0``\n  to indicate that the program needn't stay running for any particular\n  amount of time.\n\n  .. note::\n\n      Even if a process exits with an \"expected\" exit code (see\n      ``exitcodes``), the start will still be considered a failure\n      if the process exits quicker than ``startsecs``.\n\n  *Default*: 1\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``startretries``\n\n  The number of serial failure attempts that :program:`supervisord`\n  will allow when attempting to start the program before giving up and\n  putting the process into an ``FATAL`` state.\n\n  .. note::\n\n      After each failed restart, process will be put in ``BACKOFF`` state\n      and each retry attempt will take increasingly more time.\n\n      See :ref:`process_states` for explanation of the ``FATAL`` and\n      ``BACKOFF`` states.\n\n  *Default*: 3\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``autorestart``\n\n  Specifies if :program:`supervisord` should automatically restart a\n  process if it exits when it is in the ``RUNNING`` state.  May be\n  one of ``false``, ``unexpected``, or ``true``.  If ``false``, the\n  process will not be autorestarted.  If ``unexpected``, the process\n  will be restarted when the program exits with an exit code that is\n  not one of the exit codes associated with this process' configuration\n  (see ``exitcodes``).  If ``true``, the process will be unconditionally\n  restarted when it exits, without regard to its exit code.\n\n  .. note::\n\n      ``autorestart`` controls whether :program:`supervisord` will\n      autorestart a program if it exits after it has successfully started\n      up (the process is in the ``RUNNING`` state).\n\n      :program:`supervisord` has a different restart mechanism for when the\n      process is starting up (the process is in the ``STARTING`` state).\n      Retries during process startup are controlled by ``startsecs``\n      and ``startretries``.\n\n  *Default*: unexpected\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``exitcodes``\n\n  The list of \"expected\" exit codes for this program used with ``autorestart``.\n  If the ``autorestart`` parameter is set to ``unexpected``, and the process\n  exits in any other way than as a result of a supervisor stop\n  request, :program:`supervisord` will restart the process if it exits\n  with an exit code that is not defined in this list.\n\n  *Default*: 0\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n  .. note::\n\n      In Supervisor versions prior to 4.0, the default was ``0,2``.  In\n      Supervisor 4.0, the default was changed to ``0``.\n\n``stopsignal``\n\n  The signal used to kill the program when a stop is requested.  This can be\n  specified using the signal's name or its number.  It is normally one of:\n  ``TERM``, ``HUP``, ``INT``, ``QUIT``, ``KILL``, ``USR1``, or ``USR2``.\n\n  *Default*: TERM\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``stopwaitsecs``\n\n  The number of seconds to wait for the OS to return a SIGCHLD to\n  :program:`supervisord` after the program has been sent a stopsignal.\n  If this number of seconds elapses before :program:`supervisord`\n  receives a SIGCHLD from the process, :program:`supervisord` will\n  attempt to kill it with a final SIGKILL.\n\n  *Default*: 10\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``stopasgroup``\n\n  If true, the flag causes supervisor to send the stop signal to the\n  whole process group and implies ``killasgroup`` is true.  This is useful\n  for programs, such as Flask in debug mode, that do not propagate\n  stop signals to their children, leaving them orphaned.\n\n  *Default*: false\n\n  *Required*:  No.\n\n  *Introduced*: 3.0b1\n\n``killasgroup``\n\n  If true, when resorting to send SIGKILL to the program to terminate\n  it send it to its whole process group instead, taking care of its\n  children as well, useful e.g with Python programs using\n  :mod:`multiprocessing`.\n\n  *Default*: false\n\n  *Required*:  No.\n\n  *Introduced*: 3.0a11\n\n``user``\n\n  Instruct :program:`supervisord` to use this UNIX user account as the\n  account which runs the program.  The user can only be switched if\n  :program:`supervisord` is run as the root user.  If :program:`supervisord`\n  can't switch to the specified user, the program will not be started.\n\n  .. note::\n\n      The user will be changed using ``setuid`` only.  This does not start\n      a login shell and does not change environment variables like\n      ``USER`` or ``HOME``.  See :ref:`subprocess_environment` for details.\n\n  *Default*: Do not switch users\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``redirect_stderr``\n\n  If true, cause the process' stderr output to be sent back to\n  :program:`supervisord` on its stdout file descriptor (in UNIX shell\n  terms, this is the equivalent of executing ``/the/program 2>&1``).\n\n  .. note::\n\n     Do not set ``redirect_stderr=true`` in an ``[eventlistener:x]`` section.\n     Eventlisteners use ``stdout`` and ``stdin`` to communicate with\n     ``supervisord``.  If ``stderr`` is redirected, output from\n     ``stderr`` will interfere with the eventlistener protocol.\n\n  *Default*: false\n\n  *Required*:  No.\n\n  *Introduced*: 3.0, replaces 2.0's ``log_stdout`` and ``log_stderr``\n\n``stdout_logfile``\n\n  Put process stdout output in this file (and if redirect_stderr is\n  true, also place stderr output in this file).  If ``stdout_logfile``\n  is unset or set to ``AUTO``, supervisor will automatically choose a\n  file location.  If this is set to ``NONE``, supervisord will create\n  no log file.  ``AUTO`` log files and their backups will be deleted\n  when :program:`supervisord` restarts.  The ``stdout_logfile`` value\n  can contain Python string expressions that will evaluated against a\n  dictionary that contains the keys ``group_name``, ``host_node_name``,\n  ``process_num``, ``program_name``, and ``here`` (the directory of the\n  supervisord config file).\n\n  .. note::\n\n     It is not possible for two processes to share a single log file\n     (``stdout_logfile``) when rotation (``stdout_logfile_maxbytes``)\n     is enabled.  This will result in the file being corrupted.\n\n  .. note::\n\n    If ``stdout_logfile`` is set to a special file like ``/dev/stdout``\n    that is not seekable, log rotation must be disabled by setting\n    ``stdout_logfile_maxbytes = 0``.\n\n  *Default*: ``AUTO``\n\n  *Required*:  No.\n\n  *Introduced*: 3.0, replaces 2.0's ``logfile``\n\n``stdout_logfile_maxbytes``\n\n  The maximum number of bytes that may be consumed by\n  ``stdout_logfile`` before it is rotated (suffix multipliers like\n  \"KB\", \"MB\", and \"GB\" can be used in the value).  Set this value to 0\n  to indicate an unlimited log size.\n\n  *Default*: 50MB\n\n  *Required*:  No.\n\n  *Introduced*: 3.0, replaces 2.0's ``logfile_maxbytes``\n\n``stdout_logfile_backups``\n\n  The number of ``stdout_logfile`` backups to keep around resulting\n  from process stdout log file rotation.  If set to 0, no backups\n  will be kept.\n\n  *Default*: 10\n\n  *Required*:  No.\n\n  *Introduced*: 3.0, replaces 2.0's ``logfile_backups``\n\n``stdout_capture_maxbytes``\n\n  Max number of bytes written to capture FIFO when process is in\n  \"stdout capture mode\" (see :ref:`capture_mode`).  Should be an\n  integer (suffix multipliers like \"KB\", \"MB\" and \"GB\" can used in the\n  value).  If this value is 0, process capture mode will be off.\n\n  *Default*: 0\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``stdout_events_enabled``\n\n  If true, PROCESS_LOG_STDOUT events will be emitted when the process\n  writes to its stdout file descriptor.  The events will only be\n  emitted if the file descriptor is not in capture mode at the time\n  the data is received (see :ref:`capture_mode`).\n\n  *Default*: 0\n\n  *Required*:  No.\n\n  *Introduced*: 3.0a7\n\n``stdout_syslog``\n\n  If true, stdout will be directed to syslog along with the process name.\n\n  *Default*: False\n\n  *Required*:  No.\n\n  *Introduced*: 4.0.0\n\n``stderr_logfile``\n\n  Put process stderr output in this file unless ``redirect_stderr`` is\n  true.  Accepts the same value types as ``stdout_logfile`` and may\n  contain the same Python string expressions.\n\n  .. note::\n\n     It is not possible for two processes to share a single log file\n     (``stderr_logfile``) when rotation (``stderr_logfile_maxbytes``)\n     is enabled.  This will result in the file being corrupted.\n\n  .. note::\n\n    If ``stderr_logfile`` is set to a special file like ``/dev/stderr``\n    that is not seekable, log rotation must be disabled by setting\n    ``stderr_logfile_maxbytes = 0``.\n\n  *Default*: ``AUTO``\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``stderr_logfile_maxbytes``\n\n  The maximum number of bytes before logfile rotation for\n  ``stderr_logfile``.  Accepts the same value types as\n  ``stdout_logfile_maxbytes``.\n\n  *Default*: 50MB\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``stderr_logfile_backups``\n\n  The number of backups to keep around resulting from process stderr\n  log file rotation.  If set to 0, no backups will be kept.\n\n  *Default*: 10\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``stderr_capture_maxbytes``\n\n  Max number of bytes written to capture FIFO when process is in\n  \"stderr capture mode\" (see :ref:`capture_mode`).  Should be an\n  integer (suffix multipliers like \"KB\", \"MB\" and \"GB\" can used in the\n  value).  If this value is 0, process capture mode will be off.\n\n  *Default*: 0\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``stderr_events_enabled``\n\n  If true, PROCESS_LOG_STDERR events will be emitted when the process\n  writes to its stderr file descriptor.  The events will only be\n  emitted if the file descriptor is not in capture mode at the time\n  the data is received (see :ref:`capture_mode`).\n\n  *Default*: false\n\n  *Required*:  No.\n\n  *Introduced*: 3.0a7\n\n``stderr_syslog``\n\n  If true, stderr will be directed to syslog along with the process name.\n\n  *Default*: False\n\n  *Required*:  No.\n\n  *Introduced*: 4.0.0\n\n``environment``\n\n  A list of key/value pairs in the form ``KEY=\"val\",KEY2=\"val2\"`` that\n  will be placed in the child process' environment.  The environment\n  string may contain Python string expressions that will be evaluated\n  against a dictionary containing ``group_name``, ``host_node_name``,\n  ``process_num``, ``program_name``, and ``here`` (the directory of the\n  supervisord config file).  Values containing non-alphanumeric characters\n  should be quoted (e.g. ``KEY=\"val:123\",KEY2=\"val,456\"``).  Otherwise,\n  quoting the values is optional but recommended.  **Note** that the\n  subprocess will inherit the environment variables of the shell used to\n  start \"supervisord\" except for the ones overridden here.  See\n  :ref:`subprocess_environment`.\n\n  *Default*: No extra environment\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``directory``\n\n  A file path representing a directory to which :program:`supervisord`\n  should temporarily chdir before exec'ing the child.\n\n  *Default*: No chdir (inherit supervisor's)\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``umask``\n\n  An octal number (e.g. 002, 022) representing the umask of the\n  process.\n\n  *Default*: No special umask (inherit supervisor's)\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``serverurl``\n\n  The URL passed in the environment to the subprocess process as\n  ``SUPERVISOR_SERVER_URL`` (see :mod:`supervisor.childutils`) to\n  allow the subprocess to easily communicate with the internal HTTP\n  server.  If provided, it should have the same syntax and structure\n  as the ``[supervisorctl]`` section option of the same name.  If this\n  is set to AUTO, or is unset, supervisor will automatically construct\n  a server URL, giving preference to a server that listens on UNIX\n  domain sockets over one that listens on an internet socket.\n\n  *Default*: AUTO\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``[program:x]`` Section Example\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. code-block:: ini\n\n   [program:cat]\n   command=/bin/cat\n   process_name=%(program_name)s\n   numprocs=1\n   directory=/tmp\n   umask=022\n   priority=999\n   autostart=true\n   autorestart=unexpected\n   startsecs=10\n   startretries=3\n   exitcodes=0\n   stopsignal=TERM\n   stopwaitsecs=10\n   stopasgroup=false\n   killasgroup=false\n   user=chrism\n   redirect_stderr=false\n   stdout_logfile=/a/path\n   stdout_logfile_maxbytes=1MB\n   stdout_logfile_backups=10\n   stdout_capture_maxbytes=1MB\n   stdout_events_enabled=false\n   stderr_logfile=/a/path\n   stderr_logfile_maxbytes=1MB\n   stderr_logfile_backups=10\n   stderr_capture_maxbytes=1MB\n   stderr_events_enabled=false\n   environment=A=\"1\",B=\"2\"\n   serverurl=AUTO\n\n``[include]`` Section Settings\n------------------------------\n\nThe :file:`supervisord.conf` file may contain a section named\n``[include]``.  If the configuration file contains an ``[include]``\nsection, it must contain a single key named \"files\".  The values in\nthis key specify other configuration files to be included within the\nconfiguration.\n\n.. note::\n\n    The ``[include]`` section is processed only by ``supervisord``.  It is\n    ignored by ``supervisorctl``.\n\n\n``[include]`` Section Values\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n``files``\n\n  A space-separated sequence of file globs.  Each file glob may be\n  absolute or relative.  If the file glob is relative, it is\n  considered relative to the location of the configuration file which\n  includes it.  A \"glob\" is a file pattern which matches a specified\n  pattern according to the rules used by the Unix shell. No tilde\n  expansion is done, but ``*``, ``?``, and character ranges expressed\n  with ``[]`` will be correctly matched.  The string expression is\n  evaluated against a dictionary that includes ``host_node_name``\n  and ``here`` (the directory of the supervisord config file).  Recursive\n  includes from included files are not supported.\n\n  *Default*: No default (required)\n\n  *Required*:  Yes.\n\n  *Introduced*: 3.0\n\n  *Changed*: 3.3.0.  Added support for the ``host_node_name`` expansion.\n\n``[include]`` Section Example\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. code-block:: ini\n\n   [include]\n   files = /an/absolute/filename.conf /an/absolute/*.conf foo.conf config??.conf\n\n``[group:x]`` Section Settings\n------------------------------\n\nIt is often useful to group \"homogeneous\" process groups (aka\n\"programs\") together into a \"heterogeneous\" process group so they can\nbe controlled as a unit from Supervisor's various controller\ninterfaces.\n\nTo place programs into a group so you can treat them as a unit, define\na ``[group:x]`` section in your configuration file.  The group header\nvalue is a composite.  It is the word \"group\", followed directly by a\ncolon, then the group name.  A header value of ``[group:foo]``\ndescribes a group with the name of \"foo\".  The name is used within\nclient applications that control the processes that are created as a\nresult of this configuration.  It is an error to create a ``group``\nsection that does not have a name.  The name must not include a colon\ncharacter or a bracket character.\n\nFor a ``[group:x]``, there must be one or more ``[program:x]``\nsections elsewhere in your configuration file, and the group must\nrefer to them by name in the ``programs`` value.\n\nIf \"homogeneous\" process groups (represented by program sections) are\nplaced into a \"heterogeneous\" group via ``[group:x]`` section's\n``programs`` line, the homogeneous groups that are implied by the\nprogram section will not exist at runtime in supervisor.  Instead, all\nprocesses belonging to each of the homogeneous groups will be placed\ninto the heterogeneous group.  For example, given the following group\nconfiguration:\n\n.. code-block:: ini\n\n   [group:foo]\n   programs=bar,baz\n   priority=999\n\nGiven the above, at supervisord startup, the ``bar`` and ``baz``\nhomogeneous groups will not exist, and the processes that would have\nbeen under them will now be moved into the ``foo`` group.\n\n``[group:x]`` Section Values\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n``programs``\n\n  A comma-separated list of program names.  The programs which are\n  listed become members of the group.\n\n  *Default*: No default (required)\n\n  *Required*:  Yes.\n\n  *Introduced*: 3.0\n\n``priority``\n\n  A priority number analogous to a ``[program:x]`` priority value\n  assigned to the group.\n\n  *Default*: 999\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``[group:x]`` Section Example\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. code-block:: ini\n\n   [group:foo]\n   programs=bar,baz\n   priority=999\n\n\n``[fcgi-program:x]`` Section Settings\n-------------------------------------\n\nSupervisor can manage groups of `FastCGI <http://www.fastcgi.com>`_\nprocesses that all listen on the same socket.  Until now, deployment\nflexibility for FastCGI was limited.  To get full process management,\nyou could use mod_fastcgi under Apache but then you were stuck with\nApache's inefficient concurrency model of one process or thread per\nconnection.  In addition to requiring more CPU and memory resources,\nthe process/thread per connection model can be quickly saturated by a\nslow resource, preventing other resources from being served.  In order\nto take advantage of newer event-driven web servers such as lighttpd\nor nginx which don't include a built-in process manager, you had to\nuse scripts like cgi-fcgi or spawn-fcgi.  These can be used in\nconjunction with a process manager such as supervisord or daemontools\nbut require each FastCGI child process to bind to its own socket.\nThe disadvantages of this are: unnecessarily complicated web server\nconfiguration, ungraceful restarts, and reduced fault tolerance.  With\nfewer sockets to configure, web server configurations are much smaller\nif groups of FastCGI processes can share sockets.  Shared sockets\nallow for graceful restarts because the socket remains bound by the\nparent process while any of the child processes are being restarted.\nFinally, shared sockets are more fault tolerant because if a given\nprocess fails, other processes can continue to serve inbound\nconnections.\n\nWith integrated FastCGI spawning support, Supervisor gives you the\nbest of both worlds.  You get full-featured process management with\ngroups of FastCGI processes sharing sockets without being tied to a\nparticular web server.  It's a clean separation of concerns, allowing\nthe web server and the process manager to each do what they do best.\n\n.. note::\n\n   The socket manager in Supervisor was originally developed to support\n   FastCGI processes but it is not limited to FastCGI.  Other protocols may\n   be used as well with no special configuration.  Any program that can\n   access an open socket from a file descriptor (e.g. with\n   `socket.fromfd <http://docs.python.org/library/socket.html#socket.fromfd>`_\n   in Python) can use the socket manager.  Supervisor will automatically\n   create the socket, bind, and listen before forking the first child in a\n   group.  The socket will be passed to each child on file descriptor\n   number ``0`` (zero).  When the last child in the group exits,\n   Supervisor will close the socket.\n\n.. note::\n\n   Prior to Supervisor 3.4.0, FastCGI programs (``[fcgi-program:x]``)\n   could not be referenced in groups (``[group:x]``).\n\nAll the options available to ``[program:x]`` sections are\nalso respected by ``fcgi-program`` sections.\n\n``[fcgi-program:x]`` Section Values\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n``[fcgi-program:x]`` sections have a few keys which ``[program:x]``\nsections do not have.\n\n``socket``\n\n  The FastCGI socket for this program, either TCP or UNIX domain\n  socket. For TCP sockets, use this format: ``tcp://localhost:9002``.\n  For UNIX domain sockets, use ``unix:///absolute/path/to/file.sock``.\n  String expressions are evaluated against a dictionary containing the\n  keys \"program_name\" and \"here\" (the directory of the supervisord\n  config file).\n\n  *Default*: No default.\n\n  *Required*:  Yes.\n\n  *Introduced*: 3.0\n\n``socket_backlog``\n\n  Sets socket listen(2) backlog.\n\n  *Default*: socket.SOMAXCONN\n\n  *Required*:  No.\n\n  *Introduced*: 3.4.0\n\n``socket_owner``\n\n  For UNIX domain sockets, this parameter can be used to specify the user\n  and group for the FastCGI socket. May be a UNIX username (e.g. chrism)\n  or a UNIX username and group separated by a colon (e.g. chrism:wheel).\n\n  *Default*: Uses the user and group set for the fcgi-program\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``socket_mode``\n\n  For UNIX domain sockets, this parameter can be used to specify the\n  permission mode.\n\n  *Default*: 0700\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\nConsult :ref:`programx_section` for other allowable keys, delta the\nabove constraints and additions.\n\n``[fcgi-program:x]`` Section Example\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. code-block:: ini\n\n   [fcgi-program:fcgiprogramname]\n   command=/usr/bin/example.fcgi\n   socket=unix:///var/run/supervisor/%(program_name)s.sock\n   socket_owner=chrism\n   socket_mode=0700\n   process_name=%(program_name)s_%(process_num)02d\n   numprocs=5\n   directory=/tmp\n   umask=022\n   priority=999\n   autostart=true\n   autorestart=unexpected\n   startsecs=1\n   startretries=3\n   exitcodes=0\n   stopsignal=QUIT\n   stopasgroup=false\n   killasgroup=false\n   stopwaitsecs=10\n   user=chrism\n   redirect_stderr=true\n   stdout_logfile=/a/path\n   stdout_logfile_maxbytes=1MB\n   stdout_logfile_backups=10\n   stdout_events_enabled=false\n   stderr_logfile=/a/path\n   stderr_logfile_maxbytes=1MB\n   stderr_logfile_backups=10\n   stderr_events_enabled=false\n   environment=A=\"1\",B=\"2\"\n   serverurl=AUTO\n\n``[eventlistener:x]`` Section Settings\n--------------------------------------\n\nSupervisor allows specialized homogeneous process groups (\"event\nlistener pools\") to be defined within the configuration file.  These\npools contain processes that are meant to receive and respond to event\nnotifications from supervisor's event system.  See :ref:`events` for\nan explanation of how events work and how to implement programs that\ncan be declared as event listeners.\n\nNote that all the options available to ``[program:x]`` sections are\nrespected by eventlistener sections *except* for ``stdout_capture_maxbytes``.\nEventlisteners cannot emit process communication events on ``stdout``,\nbut can emit on ``stderr`` (see :ref:`capture_mode`).\n\n``[eventlistener:x]`` Section Values\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n``[eventlistener:x]`` sections have a few keys which ``[program:x]``\nsections do not have.\n\n``buffer_size``\n\n  The event listener pool's event queue buffer size.  When a listener\n  pool's event buffer is overflowed (as can happen when an event\n  listener pool cannot keep up with all of the events sent to it), the\n  oldest event in the buffer is discarded.\n\n``events``\n\n  A comma-separated list of event type names that this listener is\n  \"interested\" in receiving notifications for (see\n  :ref:`event_types` for a list of valid event type names).\n\n``result_handler``\n\n  An `entry point object reference\n  <https://packaging.python.org/en/latest/specifications/entry-points/#data-model>`_\n  string that resolves to a Python callable.  The default value is\n  ``supervisor.dispatchers:default_handler``.  Specifying an alternate\n  result handler is a very uncommon thing to need to do, and as a\n  result, how to create one is not documented.\n\nConsult :ref:`programx_section` for other allowable keys, delta the\nabove constraints and additions.\n\n``[eventlistener:x]`` Section Example\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. code-block:: ini\n\n   [eventlistener:theeventlistenername]\n   command=/bin/eventlistener\n   process_name=%(program_name)s_%(process_num)02d\n   numprocs=5\n   events=PROCESS_STATE\n   buffer_size=10\n   directory=/tmp\n   umask=022\n   priority=-1\n   autostart=true\n   autorestart=unexpected\n   startsecs=1\n   startretries=3\n   exitcodes=0\n   stopsignal=QUIT\n   stopwaitsecs=10\n   stopasgroup=false\n   killasgroup=false\n   user=chrism\n   redirect_stderr=false\n   stdout_logfile=/a/path\n   stdout_logfile_maxbytes=1MB\n   stdout_logfile_backups=10\n   stdout_events_enabled=false\n   stderr_logfile=/a/path\n   stderr_logfile_maxbytes=1MB\n   stderr_logfile_backups=10\n   stderr_events_enabled=false\n   environment=A=\"1\",B=\"2\"\n   serverurl=AUTO\n\n``[rpcinterface:x]`` Section Settings\n-------------------------------------\n\nAdding ``rpcinterface:x`` settings in the configuration file is only\nuseful for people who wish to extend supervisor with additional custom\nbehavior.\n\nIn the sample config file (see :ref:`create_config`), there is a section\nwhich is named ``[rpcinterface:supervisor]``.  By default it looks like the\nfollowing.\n\n.. code-block:: ini\n\n   [rpcinterface:supervisor]\n   supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface\n\nThe ``[rpcinterface:supervisor]`` section *must* remain in the\nconfiguration for the standard setup of supervisor to work properly.\nIf you don't want supervisor to do anything it doesn't already do out\nof the box, this is all you need to know about this type of section.\n\nHowever, if you wish to add rpc interface namespaces in order to\ncustomize supervisor, you may add additional ``[rpcinterface:foo]``\nsections, where \"foo\" represents the namespace of the interface (from\nthe web root), and the value named by\n``supervisor.rpcinterface_factory`` is a factory callable which should\nhave a function signature that accepts a single positional argument\n``supervisord`` and as many keyword arguments as required to perform\nconfiguration.  Any extra key/value pairs defined within the\n``[rpcinterface:x]`` section will be passed as keyword arguments to\nthe factory.\n\nHere's an example of a factory function, created in the\n``__init__.py`` file of the Python package ``my.package``.\n\n.. code-block:: python\n\n   from my.package.rpcinterface import AnotherRPCInterface\n\n   def make_another_rpcinterface(supervisord, **config):\n       retries = int(config.get('retries', 0))\n       another_rpc_interface = AnotherRPCInterface(supervisord, retries)\n       return another_rpc_interface\n\nAnd a section in the config file meant to configure it.\n\n.. code-block:: ini\n\n   [rpcinterface:another]\n   supervisor.rpcinterface_factory = my.package:make_another_rpcinterface\n   retries = 1\n\n``[rpcinterface:x]`` Section Values\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n``supervisor.rpcinterface_factory``\n\n  ``entry point object reference`` dotted name to your RPC interface's\n  factory function.\n\n  *Default*: N/A\n\n  *Required*:  No.\n\n  *Introduced*: 3.0\n\n``[rpcinterface:x]`` Section Example\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. code-block:: ini\n\n   [rpcinterface:another]\n   supervisor.rpcinterface_factory = my.package:make_another_rpcinterface\n   retries = 1\n"
  },
  {
    "path": "docs/development.rst",
    "content": "Resources and Development\n=========================\n\nBug Tracker\n-----------\n\nSupervisor has a bugtracker where you may report any bugs or other\nerrors you find.  Please report bugs to the `GitHub issues page\n<https://github.com/supervisor/supervisor/issues>`_.\n\nVersion Control Repository\n--------------------------\n\nYou can also view the `Supervisor version control repository\n<https://github.com/Supervisor/supervisor>`_.\n\nContributing\n------------\n\nWe'll review contributions from the community in\n`pull requests <https://help.github.com/articles/using-pull-requests>`_\non GitHub.\n\nAuthor Information\n------------------\n\nThe following people are responsible for creating Supervisor.\n\nOriginal Author\n~~~~~~~~~~~~~~~\n\n- `Chris McDonough <https://github.com/mcdonc>`_ is the original author of\n  Supervisor.\n\nContributors\n~~~~~~~~~~~~\n\nContributors are tracked on the `GitHub contributions page\n<https://github.com/Supervisor/supervisor/graphs/contributors>`_.  The two lists\nbelow are included for historical reasons.\n\nThis first list recognizes significant contributions that were made\nbefore the repository moved to GitHub.\n\n- Anders Quist: Anders contributed the patch that was the basis for\n  Supervisor’s ability to reload parts of its configuration without\n  restarting.\n\n- Derek DeVries: Derek did the web design of Supervisor’s internal web\n  interface and website logos.\n\n- Guido van Rossum: Guido authored ``zdrun`` and ``zdctl``, the\n  programs from Zope that were the original basis for Supervisor.  He\n  also created Python, the programming language that Supervisor is\n  written in.\n\n- Jason Kirtland: Jason fixed Supervisor to run on Python 2.6 by\n  contributing a patched version of Medusa (a Supervisor dependency)\n  that we now bundle.\n\n- Roger Hoover: Roger added support for spawning FastCGI programs. He\n  has also been one of the most active mailing list users, providing\n  his testing and feedback.\n\n- Siddhant Goel: Siddhant worked on :program:`supervisorctl` as our\n  Google Summer of Code student for 2008. He implemented the ``fg``\n  command and also added tab completion.\n\nThis second list records contributors who signed a legal agreement.\nThe legal agreement was\n`introduced <https://github.com/Supervisor/supervisor/commit/7bdac36e67a91b513a2e53a6098751509a7a9e34>`_\nin January 2014 but later\n`withdrawn <https://github.com/Supervisor/supervisor/commit/79090d521c512634bed03a65147f16cd41456051>`_\nin March 2014.  This list is being preserved in case it is useful\nlater (e.g. if at some point there was a desire to donate the project\nto a foundation that required such agreements).\n\n- Chris McDonough, 2006-06-26\n\n- Siddhant Goel, 2008-06-15\n\n- Chris Rossi, 2010-02-02\n\n- Roger Hoover, 2010-08-17\n\n- Benoit Sigoure, 2011-06-21\n\n- John Szakmeister, 2011-09-06\n\n- Gunnlaugur Þór Briem, 2011-11-26\n\n- Jens Rantil, 2011-11-27\n\n- Michael Blume, 2012-01-09\n\n- Philip Zeyliger, 2012-02-21\n\n- Marcelo Vanzin, 2012-05-03\n\n- Martijn Pieters, 2012-06-04\n\n- Marcin Kuźmiński, 2012-06-21\n\n- Jean Jordaan, 2012-06-28\n\n- Perttu Ranta-aho, 2012-09-27\n\n- Chris Streeter, 2013-03-23\n\n- Caio Ariede, 2013-03-25\n\n- David Birdsong, 2013-04-11\n\n- Lukas Rist, 2013-04-18\n\n- Honza Pokorny, 2013-07-23\n\n- Thúlio Costa, 2013-10-31\n\n- Gary M. Josack, 2013-11-12\n\n- Márk Sági-Kazár, 2013-12-16\n"
  },
  {
    "path": "docs/events.rst",
    "content": ".. _events:\n\nEvents\n======\n\nEvents are an advanced feature of Supervisor introduced in version\n3.0.  You don't need to understand events if you simply want to use\nSupervisor as a mechanism to restart crashed processes or as a system\nto manually control process state.  You do need to understand events\nif you want to use Supervisor as part of a process\nmonitoring/notification framework.\n\nEvent Listeners and Event Notifications\n---------------------------------------\n\nSupervisor provides a way for a specially written program (which it\nruns as a subprocess) called an \"event listener\" to subscribe to\n\"event notifications\".  An event notification implies that something\nhappened related to a subprocess controlled by :program:`supervisord`\nor to :program:`supervisord` itself.  Event notifications are grouped\ninto types in order to make it possible for event listeners to\nsubscribe to a limited subset of event notifications.  Supervisor\ncontinually emits event notifications as its running even if there are\nno listeners configured.  If a listener is configured and subscribed\nto an event type that is emitted during a :program:`supervisord`\nlifetime, that listener will be notified.\n\nThe purpose of the event notification/subscription system is to\nprovide a mechanism for arbitrary code to be run (e.g. send an email,\nmake an HTTP request, etc) when some condition is met.  That condition\nusually has to do with subprocess state.  For instance, you may want\nto notify someone via email when a process crashes and is restarted by\nSupervisor.\n\nThe event notification protocol is based on communication via a\nsubprocess' stdin and stdout.  Supervisor sends specially-formatted\ninput to an event listener process' stdin and expects\nspecially-formatted output from an event listener's stdout, forming a\nrequest-response cycle.  A protocol agreed upon between supervisor and\nthe listener's implementer allows listeners to process event\nnotifications.  Event listeners can be written in any language\nsupported by the platform you're using to run Supervisor.  Although\nevent listeners may be written in any language, there is special\nlibrary support for Python in the form of a\n:mod:`supervisor.childutils` module, which makes creating event\nlisteners in Python slightly easier than in other languages.\n\nConfiguring an Event Listener\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nA supervisor event listener is specified via a ``[eventlistener:x]``\nsection in the configuration file.  Supervisor ``[eventlistener:x]``\nsections are treated almost exactly like supervisor ``[program:x]``\nsection with the respect to the keys allowed in their configuration\nexcept that Supervisor does not respect \"capture mode\" output from\nevent listener processes (ie. event listeners cannot be\n``PROCESS_COMMUNICATIONS_EVENT`` event generators).  Therefore it is\nan error to specify ``stdout_capture_maxbytes`` or\n``stderr_capture_maxbytes`` in the configuration of an eventlistener.\nThere is no artificial constraint on the number of eventlistener\nsections that can be placed into the configuration file.\n\nWhen an ``[eventlistener:x]`` section is defined, it actually defines\na \"pool\", where the number of event listeners in the pool is\ndetermined by the ``numprocs`` value within the section.\n\nThe ``events`` parameter of the ``[eventlistener:x]`` section\nspecifies the events that will be sent to a listener pool.  A\nwell-written event listener will ignore events that it cannot process,\nbut there is no guarantee that a specific event listener won't crash\nas a result of receiving an event type it cannot handle.  Therefore,\ndepending on the listener implementation, it may be important to\nspecify in the configuration that it may receive only certain types of\nevents.  The implementor of the event listener is the only person who\ncan tell you what these are (and therefore what value to put in the\n``events`` configuration).  Examples of eventlistener\nconfigurations that can be placed in ``supervisord.conf`` are as\nfollows.\n\n.. code-block:: ini\n\n   [eventlistener:memmon]\n   command=memmon -a 200MB -m bob@example.com\n   events=TICK_60\n\n.. code-block:: ini\n\n   [eventlistener:mylistener]\n   command=my_custom_listener.py\n   events=PROCESS_STATE,TICK_60\n\n.. note::\n\n   An advanced feature, specifying an alternate \"result handler\" for a\n   pool, can be specified via the ``result_handler`` parameter of an\n   ``[eventlistener:x]`` section in the form of an `entry point object reference\n   <https://packaging.python.org/en/latest/specifications/entry-points/#data-model>`_\n   string.  The default result handler is\n   ``supervisord.dispatchers:default_handler``.  Creating an alternate\n   result handler is not currently documented.\n\nWhen an event notification is sent by supervisor, all event listener\npools which are subscribed to receive events for the event's type\n(filtered by the ``events`` value in the eventlistener\nsection) will be found.  One of the listeners in each listener pool\nwill receive the event notification (any \"available\" listener).\n\nEvery process in an event listener pool is treated equally by\nsupervisor.  If a process in the pool is unavailable (because it is\nalready processing an event, because it has crashed, or because it has\nelected to removed itself from the pool), supervisor will choose\nanother process from the pool.  If the event cannot be sent because\nall listeners in the pool are \"busy\", the event will be buffered and\nnotification will be retried later.  \"Later\" is defined as \"the next\ntime that the :program:`supervisord` select loop executes\".  For\nsatisfactory event processing performance, you should configure a pool\nwith as many event listener processes as appropriate to handle your\nevent load.  This can only be determined empirically for any given\nworkload, there is no \"magic number\" but to help you determine the\noptimal number of listeners in a given pool, Supervisor will emit\nwarning messages to its activity log when an event cannot be sent\nimmediately due to pool congestion.  There is no artificial constraint\nplaced on the number of processes that can be in a pool, it is limited\nonly by your platform constraints.\n\nA listener pool has an event buffer queue.  The queue is sized via the\nlistener pool's ``buffer_size`` config file option.  If the queue is\nfull and supervisor attempts to buffer an event, supervisor will throw\naway the oldest event in the buffer and log an error.\n\nWriting an Event Listener\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\nAn event listener implementation is a program that is willing to\naccept structured input on its stdin stream and produce structured\noutput on its stdout stream.  An event listener implementation should\noperate in \"unbuffered\" mode or should flush its stdout every time it\nneeds to communicate back to the supervisord process.  Event listeners\ncan be written to be long-running or may exit after a single request\n(depending on the implementation and the ``autorestart`` parameter in\nthe eventlistener's configuration).\n\nAn event listener can send arbitrary output to its stderr, which will\nbe logged or ignored by supervisord depending on the stderr-related\nlogfile configuration in its ``[eventlistener:x]`` section.\n\nEvent Notification Protocol\n+++++++++++++++++++++++++++\n\nWhen supervisord sends a notification to an event listener process,\nthe listener will first be sent a single \"header\" line on its\nstdin. The composition of the line is a set of colon-separated tokens\n(each of which represents a key-value pair) separated from each other\nby a single space.  The line is terminated with a ``\\n`` (linefeed)\ncharacter.  The tokens on the line are not guaranteed to be in any\nparticular order.  The types of tokens currently defined are in the\ntable below.\n\nHeader Tokens\n@@@@@@@@@@@@@\n\n=========== =============================================   ===================\nKey         Description                                     Example\n=========== =============================================   ===================\nver         The event system protocol version               3.0\nserver      The identifier of the supervisord sending the\n            event (see config file ``[supervisord]``\n            section ``identifier`` value.\nserial      An integer assigned to each event.  No two      30\n            events generated during the lifetime of\n            a :program:`supervisord` process will have\n            the same serial number.  The value is useful\n            for functional testing and detecting event\n            ordering anomalies.\npool        The name of the event listener pool which       myeventpool\n            generated this event.\npoolserial  An integer assigned to each event by the        30\n            eventlistener pool which it is being sent\n            from.  No two events generated by the same\n            eventlistener pool during the lifetime of a\n            :program:`supervisord` process will have the\n            same ``poolserial`` number.  This value can\n            be used to detect event ordering anomalies.\neventname   The specific event type name (see               TICK_5\n            :ref:`event_types`)\nlen         An integer indicating the number of bytes in    22\n            the event payload, aka the ``PAYLOAD_LENGTH``\n=========== =============================================   ===================\n\nAn example of a complete header line is as follows.\n\n.. code-block:: text\n\n   ver:3.0 server:supervisor serial:21 pool:listener poolserial:10 eventname:PROCESS_COMMUNICATION_STDOUT len:54\n\nDirectly following the linefeed character in the header is the event\npayload.  It consists of ``PAYLOAD_LENGTH`` bytes representing a\nserialization of the event data.  See :ref:`event_types` for the\nspecific event data serialization definitions.\n\nAn example payload for a ``PROCESS_COMMUNICATION_STDOUT`` event\nnotification is as follows.\n\n.. code-block:: text\n\n   processname:foo groupname:bar pid:123\n   This is the data that was sent between the tags\n\nThe payload structure of any given event is determined only by the\nevent's type.\n\nEvent Listener States\n+++++++++++++++++++++\n\nAn event listener process has three possible states that are\nmaintained by supervisord:\n\n=============================   ==============================================\nName                            Description\n=============================   ==============================================\nACKNOWLEDGED                    The event listener has acknowledged (accepted\n                                or rejected) an event send.\nREADY                           Event notifications may be sent to this event\n                                listener\nBUSY                            Event notifications may not be sent to this\n                                event listener.\n=============================   ==============================================\n\nWhen an event listener process first starts, supervisor automatically\nplaces it into the ``ACKNOWLEDGED`` state to allow for startup\nactivities or guard against startup failures (hangs).  Until the\nlistener sends a ``READY\\n`` string to its stdout, it will stay in\nthis state.\n\nWhen supervisor sends an event notification to a listener in the\n``READY`` state, the listener will be placed into the ``BUSY`` state\nuntil it receives an ``OK`` or ``FAIL`` response from the listener, at\nwhich time, the listener will be transitioned back into the\n``ACKNOWLEDGED`` state.\n\nEvent Listener Notification Protocol\n++++++++++++++++++++++++++++++++++++\n\nSupervisor will notify an event listener in the ``READY`` state of an\nevent by sending data to the stdin of the process.  Supervisor will\nnever send anything to the stdin of an event listener process while\nthat process is in the ``BUSY`` or ``ACKNOWLEDGED`` state.  Supervisor\nstarts by sending the header.\n\nOnce it has processed the header, the event listener implementation\nshould read ``PAYLOAD_LENGTH`` bytes from its stdin, perform an\narbitrary action based on the values in the header and the data parsed\nout of the serialization.  It is free to block for an arbitrary amount\nof time while doing this.  Supervisor will continue processing\nnormally as it waits for a response and it will send other events of\nthe same type to other listener processes in the same pool as\nnecessary.\n\nAfter the event listener has processed the event serialization, in\norder to notify supervisord about the result, it should send back a\nresult structure on its stdout.  A result structure is the word\n\"RESULT\", followed by a space, followed by the result length, followed\nby a line feed, followed by the result content.  For example,\n``RESULT 2\\nOK`` is the result \"OK\".  Conventionally, an event\nlistener will use either ``OK`` or ``FAIL`` as the result content.\nThese strings have special meaning to the default result handler.\n\nIf the default result handler receives ``OK`` as result content, it\nwill assume that the listener processed the event notification\nsuccessfully.  If it receives ``FAIL``, it will assume that the\nlistener has failed to process the event, and the event will be\nrebuffered and sent again at a later time.  The event listener may\nreject the event for any reason by returning a ``FAIL`` result.  This\ndoes not indicate a problem with the event data or the event listener.\nOnce an ``OK`` or ``FAIL`` result is received by supervisord, the\nevent listener is placed into the ``ACKNOWLEDGED`` state.\n\nOnce the listener is in the ``ACKNOWLEDGED`` state, it may either exit\n(and subsequently may be restarted by supervisor if its\n``autorestart`` config parameter is ``true``), or it may continue\nrunning.  If it continues to run, in order to be placed back into the\n``READY`` state by supervisord, it must send a ``READY`` token\nfollowed immediately by a line feed to its stdout.\n\nExample Event Listener Implementation\n+++++++++++++++++++++++++++++++++++++\n\nA Python implementation of a \"long-running\" event listener which\naccepts an event notification, prints the header and payload to its\nstderr, and responds with an ``OK`` result, and then subsequently a\n``READY`` is as follows.\n\n.. code-block:: python\n\n   import sys\n\n   def write_stdout(s):\n       # only eventlistener protocol messages may be sent to stdout\n       sys.stdout.write(s)\n       sys.stdout.flush()\n\n   def write_stderr(s):\n       sys.stderr.write(s)\n       sys.stderr.flush()\n\n   def main():\n       while 1:\n           # transition from ACKNOWLEDGED to READY\n           write_stdout('READY\\n')\n\n           # read header line and print it to stderr\n           line = sys.stdin.readline()\n           write_stderr(line)\n\n           # read event payload and print it to stderr\n           headers = dict([ x.split(':') for x in line.split() ])\n           data = sys.stdin.read(int(headers['len']))\n           write_stderr(data)\n\n           # transition from READY to ACKNOWLEDGED\n           write_stdout('RESULT 2\\nOK')\n\n   if __name__ == '__main__':\n       main()\n\nOther sample event listeners are present within the :term:`Superlance`\npackage, including one which can monitor supervisor subprocesses and\nrestart a process if it is using \"too much\" memory.\n\nEvent Listener Error Conditions\n+++++++++++++++++++++++++++++++\n\nIf the event listener process dies while the event is being\ntransmitted to its stdin, or if it dies before sending an result\nstructure back to supervisord, the event is assumed to not be\nprocessed and will be rebuffered by supervisord and sent again later.\n\nIf an event listener sends data to its stdout which supervisor does\nnot recognize as an appropriate response based on the state that the\nevent listener is in, the event listener will be placed into the\n``UNKNOWN`` state, and no further event notifications will be sent to\nit.  If an event was being processed by the listener during this time,\nit will be rebuffered and sent again later.\n\nMiscellaneous\n+++++++++++++\n\nEvent listeners may use the Supervisor XML-RPC interface to call \"back\nin\" to Supervisor.  As such, event listeners can impact the state of a\nSupervisor subprocess as a result of receiving an event notification.\nFor example, you may want to generate an event every few minutes\nrelated to process usage of Supervisor-controlled subprocesses, and if\nany of those processes exceed some memory threshold, you would like\nto restart it.  You would write a program that caused supervisor to\ngenerate ``PROCESS_COMMUNICATION`` events every so often with memory\ninformation in them, and an event listener to perform an action based\non processing the data it receives from these events.\n\n.. _event_types:\n\nEvent Types\n-----------\n\nThe event types are a controlled set, defined by Supervisor itself.\nThere is no way to add an event type without changing\n:program:`supervisord` itself.  This is typically not a problem,\nthough, because metadata is attached to events that can be used by\nevent listeners as additional filter criterion, in conjunction with\nits type.\n\nEvent types that may be subscribed to by event listeners are\npredefined by supervisor and fall into several major categories,\nincluding \"process state change\", \"process communication\", and\n\"supervisor state change\" events. Below are tables describing\nthese event types.\n\nIn the below list, we indicate that some event types have a \"body\"\nwhich is a a *token set*.  A token set consists of a set of characters\nwith space-separated tokens.  Each token represents a key-value pair.\nThe key and value are separated by a colon.  For example:\n\n.. code-block:: text\n\n   processname:cat groupname:cat from_state:STOPPED\n\nToken sets do not have a linefeed or carriage return character at\ntheir end.\n\n``EVENT`` Event Type\n~~~~~~~~~~~~~~~~~~~~\n\nThe base event type.  This event type is abstract.  It will never be\nsent directly.  Subscribing to this event type will cause a subscriber\nto receive all event notifications emitted by Supervisor.\n\n*Name*: ``EVENT``\n\n*Subtype Of*: N/A\n\n*Body Description*: N/A\n\n\n``PROCESS_STATE`` Event Type\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThis process type indicates a process has moved from one state to\nanother.  See :ref:`process_states` for a description of the states\nthat a process moves through during its lifetime.  This event type is\nabstract, it will never be sent directly.  Subscribing to this event\ntype will cause a subscriber to receive event notifications of all the\nevent types that are subtypes of ``PROCESS_STATE``.\n\n*Name*: ``PROCESS_STATE``\n\n*Subtype Of*: ``EVENT``\n\nBody Description\n++++++++++++++++\n\nAll subtypes of ``PROCESS_STATE`` have a body which is a token set.\nAdditionally, each ``PROCESS_STATE`` subtype's token set has a default\nset of key/value pairs: ``processname``, ``groupname``, and\n``from_state``.  ``processname`` represents the process name which\nsupervisor knows this process as. ``groupname`` represents the name of\nthe supervisord group which this process is in.  ``from_state`` is the\nname of the state from which this process is transitioning (the new\nstate is implied by the concrete event type).  Concrete subtypes may\ninclude additional key/value pairs in the token set.\n\n``PROCESS_STATE_STARTING`` Event Type\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n\nIndicates a process has moved from a state to the STARTING state.\n\n*Name*: ``PROCESS_STATE_STARTING``\n\n*Subtype Of*: ``PROCESS_STATE``\n\nBody Description\n++++++++++++++++\n\nThis body is a token set.  It has the default set of key/value pairs\nplus an additional ``tries`` key.  ``tries`` represents the number of\ntimes this process has entered this state before transitioning to\nRUNNING or FATAL (it will never be larger than the \"startretries\"\nparameter of the process).  For example:\n\n.. code-block:: text\n\n   processname:cat groupname:cat from_state:STOPPED tries:0\n\n``PROCESS_STATE_RUNNING`` Event Type\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIndicates a process has moved from the ``STARTING`` state to the\n``RUNNING`` state.  This means that the process has successfully\nstarted as far as Supervisor is concerned.\n\n*Name*: ``PROCESS_STATE_RUNNING``\n\n*Subtype Of*: ``PROCESS_STATE``\n\nBody Description\n++++++++++++++++\n\nThis body is a token set.  It has the default set of key/value pairs\nplus an additional ``pid`` key.  ``pid`` represents the UNIX\nprocess id of the process that was started.  For example:\n\n.. code-block:: text\n\n   processname:cat groupname:cat from_state:STARTING pid:2766\n\n``PROCESS_STATE_BACKOFF`` Event Type\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIndicates a process has moved from the ``STARTING`` state to the\n``BACKOFF`` state.  This means that the process did not successfully\nenter the RUNNING state, and Supervisor is going to try to restart it\nunless it has exceeded its \"startretries\" configuration limit.\n\n*Name*: ``PROCESS_STATE_BACKOFF``\n\n*Subtype Of*: ``PROCESS_STATE``\n\nBody Description\n++++++++++++++++\n\nThis body is a token set.  It has the default set of key/value pairs\nplus an additional ``tries`` key.  ``tries`` represents the number of\ntimes this process has entered this state before transitioning to\n``RUNNING`` or ``FATAL`` (it will never be larger than the\n\"startretries\" parameter of the process).  For example:\n\n.. code-block:: text\n\n   processname:cat groupname:cat from_state:STOPPED tries:0\n\n``PROCESS_STATE_STOPPING`` Event Type\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIndicates a process has moved from either the ``RUNNING`` state or the\n``STARTING`` state to the ``STOPPING`` state.\n\n*Name*: ``PROCESS_STATE_STOPPING``\n\n*Subtype Of*: ``PROCESS_STATE``\n\nBody Description\n++++++++++++++++\n\nThis body is a token set.  It has the default set of key/value pairs\nplus an additional ``pid`` key.  ``pid`` represents the UNIX process\nid of the process that was started.  For example:\n\n.. code-block:: text\n\n   processname:cat groupname:cat from_state:STARTING pid:2766\n\n``PROCESS_STATE_EXITED`` Event Type\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIndicates a process has moved from the ``RUNNING`` state to the\n``EXITED`` state.\n\n*Name*: ``PROCESS_STATE_EXITED``\n\n*Subtype Of*: ``PROCESS_STATE``\n\nBody Description\n++++++++++++++++\n\nThis body is a token set.  It has the default set of key/value pairs\nplus two additional keys: ``pid`` and ``expected``.  ``pid``\nrepresents the UNIX process id of the process that exited.\n``expected`` represents whether the process exited with an expected\nexit code or not.  It will be ``0`` if the exit code was unexpected,\nor ``1`` if the exit code was expected. For example:\n\n.. code-block:: text\n\n   processname:cat groupname:cat from_state:RUNNING expected:0 pid:2766\n\n``PROCESS_STATE_STOPPED`` Event Type\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIndicates a process has moved from the ``STOPPING`` state to the\n``STOPPED`` state.\n\n*Name*: ``PROCESS_STATE_STOPPED``\n\n*Subtype Of*: ``PROCESS_STATE``\n\nBody Description\n++++++++++++++++\n\nThis body is a token set.  It has the default set of key/value pairs\nplus an additional ``pid`` key.  ``pid`` represents the UNIX process\nid of the process that was started.  For example:\n\n.. code-block:: text\n\n   processname:cat groupname:cat from_state:STOPPING pid:2766\n\n``PROCESS_STATE_FATAL`` Event Type\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIndicates a process has moved from the ``BACKOFF`` state to the\n``FATAL`` state.  This means that Supervisor tried ``startretries``\nnumber of times unsuccessfully to start the process, and gave up\nattempting to restart it.\n\n*Name*: ``PROCESS_STATE_FATAL``\n\n*Subtype Of*: ``PROCESS_STATE``\n\nBody Description\n++++++++++++++++\n\nThis event type is a token set with the default key/value pairs.  For\nexample:\n\n.. code-block:: text\n\n   processname:cat groupname:cat from_state:BACKOFF\n\n``PROCESS_STATE_UNKNOWN`` Event Type\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIndicates a process has moved from any state to the ``UNKNOWN`` state\n(indicates an error in :program:`supervisord`).  This state transition\nwill only happen if :program:`supervisord` itself has a programming\nerror.\n\n*Name*: ``PROCESS_STATE_UNKNOWN``\n\n*Subtype Of*: ``PROCESS_STATE``\n\nBody Description\n++++++++++++++++\n\nThis event type is a token set with the default key/value pairs.  For\nexample:\n\n.. code-block:: text\n\n   processname:cat groupname:cat from_state:BACKOFF\n\n``REMOTE_COMMUNICATION`` Event Type\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nAn event type raised when the ``supervisor.sendRemoteCommEvent()``\nmethod is called on Supervisor's RPC interface.  The ``type`` and\n``data`` are arguments of the RPC method.\n\n*Name*: ``REMOTE_COMMUNICATION``\n\n*Subtype Of*: ``EVENT``\n\nBody Description\n++++++++++++++++\n\n.. code-block:: text\n\n   type:type\n   data\n\n``PROCESS_LOG`` Event Type\n~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nAn event type emitted when a process writes to stdout or stderr.  The\nevent will only be emitted if the file descriptor is not in capture\nmode and if ``stdout_events_enabled`` or ``stderr_events_enabled``\nconfig options are set to ``true``.  This event type is abstract, it\nwill never be sent directly.  Subscribing to this event type will\ncause a subscriber to receive event notifications for all subtypes of\n``PROCESS_LOG``.\n\n*Name*: ``PROCESS_LOG``\n\n*Subtype Of*: ``EVENT``\n\n*Body Description*: N/A\n\n``PROCESS_LOG_STDOUT`` Event Type\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIndicates a process has written to its stdout file descriptor.  The\nevent will only be emitted if the file descriptor is not in capture\nmode and if the ``stdout_events_enabled`` config option is set to\n``true``.\n\n*Name*: ``PROCESS_LOG_STDOUT``\n\n*Subtype Of*: ``PROCESS_LOG``\n\nBody Description\n++++++++++++++++\n\n.. code-block:: text\n\n   processname:name groupname:name pid:pid channel:stdout\n   data\n\n``PROCESS_LOG_STDERR`` Event Type\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIndicates a process has written to its stderr file descriptor.  The\nevent will only be emitted if the file descriptor is not in capture\nmode and if the ``stderr_events_enabled`` config option is set to\n``true``.\n\n*Name*: ``PROCESS_LOG_STDERR``\n\n*Subtype Of*: ``PROCESS_LOG``\n\nBody Description\n++++++++++++++++\n\n.. code-block:: text\n\n   processname:name groupname:name pid:pid channel:stderr\n   data\n\n``PROCESS_COMMUNICATION`` Event Type\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nAn event type raised when any process attempts to send information\nbetween ``<!--XSUPERVISOR:BEGIN-->`` and ``<!--XSUPERVISOR:END-->``\ntags in its output.  This event type is abstract, it will never be\nsent directly.  Subscribing to this event type will cause a subscriber\nto receive event notifications for all subtypes of\n``PROCESS_COMMUNICATION``.\n\n*Name*: ``PROCESS_COMMUNICATION``\n\n*Subtype Of*: ``EVENT``\n\n*Body Description*: N/A\n\n``PROCESS_COMMUNICATION_STDOUT`` Event Type\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIndicates a process has sent a message to Supervisor on its stdout\nfile descriptor.\n\n*Name*: ``PROCESS_COMMUNICATION_STDOUT``\n\n*Subtype Of*: ``PROCESS_COMMUNICATION``\n\nBody Description\n++++++++++++++++\n\n.. code-block:: text\n\n   processname:name groupname:name pid:pid\n   data\n\n``PROCESS_COMMUNICATION_STDERR`` Event Type\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIndicates a process has sent a message to Supervisor on its stderr\nfile descriptor.\n\n*Name*: ``PROCESS_COMMUNICATION_STDERR``\n\n*Subtype Of*: ``PROCESS_COMMUNICATION``\n\nBody Description\n++++++++++++++++\n\n.. code-block:: text\n\n   processname:name groupname:name pid:pid\n   data\n\n``SUPERVISOR_STATE_CHANGE`` Event Type\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nAn event type raised when the state of the :program:`supervisord`\nprocess changes.  This type is abstract, it will never be sent\ndirectly.  Subscribing to this event type will cause a subscriber to\nreceive event notifications of all the subtypes of\n``SUPERVISOR_STATE_CHANGE``.\n\n*Name*: ``SUPERVISOR_STATE_CHANGE``\n\n*Subtype Of*: ``EVENT``\n\n*Body Description*: N/A\n\n``SUPERVISOR_STATE_CHANGE_RUNNING`` Event Type\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIndicates that :program:`supervisord` has started.\n\n*Name*: ``SUPERVISOR_STATE_CHANGE_RUNNING``\n\n*Subtype Of*: ``SUPERVISOR_STATE_CHANGE``\n\n*Body Description*: Empty string\n\n``SUPERVISOR_STATE_CHANGE_STOPPING`` Event Type\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIndicates that :program:`supervisord` is stopping.\n\n*Name*: ``SUPERVISOR_STATE_CHANGE_STOPPING``\n\n*Subtype Of*: ``SUPERVISOR_STATE_CHANGE``\n\n*Body Description*: Empty string\n\n``TICK`` Event Type\n~~~~~~~~~~~~~~~~~~~\n\nAn event type that may be subscribed to for event listeners to receive\n\"wake-up\" notifications every N seconds.  This event type is abstract,\nit will never be sent directly.  Subscribing to this event type will\ncause a subscriber to receive event notifications for all subtypes of\n``TICK``.\n\nNote that the only ``TICK`` events available are the ones listed below.\nYou cannot subscribe to an arbitrary ``TICK`` interval. If you need an\ninterval not provided below, you can subscribe to one of the shorter\nintervals given below and keep track of the time between runs in your\nevent listener.\n\n*Name*: ``TICK``\n\n*Subtype Of*: ``EVENT``\n\n*Body Description*: N/A\n\n``TICK_5`` Event Type\n~~~~~~~~~~~~~~~~~~~~~\n\nAn event type that may be subscribed to for event listeners to receive\n\"wake-up\" notifications every 5 seconds.\n\n*Name*: ``TICK_5``\n\n*Subtype Of*: ``TICK``\n\nBody Description\n++++++++++++++++\n\nThis event type is a token set with a single key: \"when\", which\nindicates the epoch time for which the tick was sent.\n\n.. code-block:: text\n\n   when:1201063880\n\n``TICK_60`` Event Type\n~~~~~~~~~~~~~~~~~~~~~~\n\nAn event type that may be subscribed to for event listeners to receive\n\"wake-up\" notifications every 60 seconds.\n\n*Name*: ``TICK_60``\n\n*Subtype Of*: ``TICK``\n\nBody Description\n++++++++++++++++\n\nThis event type is a token set with a single key: \"when\", which\nindicates the epoch time for which the tick was sent.\n\n.. code-block:: text\n\n   when:1201063880\n\n``TICK_3600`` Event Type\n~~~~~~~~~~~~~~~~~~~~~~~~\n\nAn event type that may be subscribed to for event listeners to receive\n\"wake-up\" notifications every 3600 seconds (1 hour).\n\n*Name*: ``TICK_3600``\n\n*Subtype Of*: ``TICK``\n\nBody Description\n++++++++++++++++\n\nThis event type is a token set with a single key: \"when\", which\nindicates the epoch time for which the tick was sent.\n\n.. code-block:: text\n\n   when:1201063880\n\n``PROCESS_GROUP`` Event Type\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nAn event type raised when a process group is added to or removed from\nSupervisor.  This type is abstract, it will never be sent\ndirectly.  Subscribing to this event type will cause a subscriber to\nreceive event notifications of all the subtypes of\n``PROCESS_GROUP``.\n\n*Name*: ``PROCESS_GROUP``\n\n*Subtype Of*: ``EVENT``\n\n*Body Description*: N/A\n\n``PROCESS_GROUP_ADDED`` Event Type\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIndicates that a process group has been added to Supervisor's configuration.\n\n*Name*: ``PROCESS_GROUP_ADDED``\n\n*Subtype Of*: ``PROCESS_GROUP``\n\n*Body Description*: This body is a token set with just a groupname key/value.\n\n.. code-block:: text\n\n   groupname:cat\n\n``PROCESS_GROUP_REMOVED`` Event Type\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIndicates that a process group has been removed from Supervisor's configuration.\n\n*Name*: ``PROCESS_GROUP_REMOVED``\n\n*Subtype Of*: ``PROCESS_GROUP``\n\n*Body Description*: This body is a token set with just a groupname key/value.\n\n.. code-block:: text\n\n   groupname:cat\n"
  },
  {
    "path": "docs/faq.rst",
    "content": "Frequently Asked Questions\n==========================\n\nQ\n  My program never starts and supervisor doesn't indicate any error?\n\nA\n  Make sure the ``x`` bit is set on the executable file you're using in\n  the ``command=`` line of your program section.\n\nQ\n  I am a software author and I want my program to behave differently\n  when it's running under :program:`supervisord`.  How can I tell if\n  my program is running under :program:`supervisord`?\n\nA\n  Supervisor and its subprocesses share an environment variable\n  :envvar:`SUPERVISOR_ENABLED`.  When your program is run under\n  :program:`supervisord`, it can check for the presence of this\n  environment variable to determine whether it is running as a\n  :program:`supervisord` subprocess.\n\nQ\n  My command works fine when I invoke it by hand from a shell prompt,\n  but when I use the same command line in a supervisor program\n  ``command=`` section, the program fails mysteriously.  Why?\n\nA\n  This may be due to your process' dependence on environment variable\n  settings.  See :ref:`subprocess_environment`.\n\nQ\n  How can I make Supervisor restart a process that's using \"too much\"\n  memory automatically?\n\nA\n  The :term:`Superlance` package contains a console script that can be\n  used as a Supervisor event listener named ``memmon`` which helps\n  with this task.  It works on Linux and Mac OS X.\n"
  },
  {
    "path": "docs/glossary.rst",
    "content": ".. _glossary:\n\nGlossary\n========\n\n.. glossary::\n   :sorted:\n\n   daemontools\n     A `process control system by D.J. Bernstein\n     <http://cr.yp.to/daemontools.html>`_.\n\n   runit\n     A `process control system <http://smarden.org/runit/>`_.\n\n   launchd\n     A `process control system used by Apple\n     <http://en.wikipedia.org/wiki/Launchd>`_ as process 1 under Mac\n     OS X.\n\n   umask\n     Abbreviation of *user mask*: sets the file mode creation mask of\n     the current process.  See `http://en.wikipedia.org/wiki/Umask\n     <http://en.wikipedia.org/wiki/Umask>`_.\n\n   Superlance\n     A package which provides various event listener implementations\n     that plug into Supervisor which can help monitor process memory\n     usage and crash status: `https://pypi.org/pypi/superlance/\n     <https://pypi.org/pypi/superlance/>`_.\n\n\n\n"
  },
  {
    "path": "docs/index.rst",
    "content": "Supervisor: A Process Control System\n====================================\n\nSupervisor is a client/server system that allows its users to monitor\nand control a number of processes on UNIX-like operating systems.\n\nIt shares some of the same goals of programs like :term:`launchd`,\n:term:`daemontools`, and :term:`runit`. Unlike some of these programs,\nit is not meant to be run as a substitute for ``init`` as \"process id\n1\". Instead it is meant to be used to control processes related to a\nproject or a customer, and is meant to start like any other program at\nboot time.\n\nNarrative Documentation\n-----------------------\n\n.. toctree::\n   :maxdepth: 2\n\n   introduction.rst\n   installing.rst\n   running.rst\n   configuration.rst\n   subprocess.rst\n   logging.rst\n   events.rst\n   xmlrpc.rst\n   upgrading.rst\n   faq.rst\n   development.rst\n   glossary.rst\n\nAPI Documentation\n-----------------\n\n.. toctree::\n   :maxdepth: 2\n\n   api.rst\n\nPlugins\n-------\n\n.. toctree::\n   :maxdepth: 2\n\n   plugins.rst\n\nIndices and tables\n------------------\n\n* :ref:`genindex`\n* :ref:`modindex`\n* :ref:`search`\n"
  },
  {
    "path": "docs/installing.rst",
    "content": "Installing\n==========\n\nInstallation instructions depend whether the system on which\nyou're attempting to install Supervisor has internet access.\n\nInstalling to A System With Internet Access\n-------------------------------------------\n\nInternet-Installing With Pip\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nSupervisor can be installed with ``pip install``:\n\n.. code-block:: bash\n\n   pip install supervisor\n\nDepending on the permissions of your system's Python, you might need\nto be the root user to install Supervisor successfully using\n``pip``.\n\nYou can also install supervisor in a virtualenv via ``pip``.\n\n.. note::\n\n   If installing on a Python version before 3.8, first ensure that the\n   ``setuptools`` package is installed because it is a runtime\n   dependency of Supervisor.\n\nInternet-Installing Without Pip\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nIf your system does not have ``pip`` installed, you will need to download\nthe Supervisor distribution and install it by hand.  Current and previous\nSupervisor releases may be downloaded from `PyPi\n<https://pypi.org/pypi/supervisor/>`_.  After unpacking the software\narchive, run ``python setup.py install``.  This requires internet access.  It\nwill download and install all distributions depended upon by Supervisor and\nfinally install Supervisor itself.\n\n.. note::\n\n   Depending on the permissions of your system's Python, you might\n   need to be the root user to successfully invoke ``python\n   setup.py install``.\n\n.. note::\n\n   The ``setuptools`` package is required to run ``python setup.py install``.\n   On Python versions before 3.8, ``setuptools`` is also a runtime\n   dependency of Supervisor.\n\nInstalling To A System Without Internet Access\n----------------------------------------------\n\nIf the system that you want to install Supervisor to does not have\nInternet access, you'll need to perform installation slightly\ndifferently.  Since both ``pip`` and ``python setup.py\ninstall`` depend on internet access to perform downloads of dependent\nsoftware, neither will work on machines without internet access until\ndependencies are installed.  To install to a machine which is not\ninternet-connected, obtain the dependencies listed in ``setup.py``\nusing a machine which is internet-connected.\n\nCopy these files to removable media and put them on the target\nmachine.  Install each onto the target machine as per its\ninstructions.  This typically just means unpacking each file and\ninvoking ``python setup.py install`` in the unpacked directory.\nFinally, run supervisor's ``python setup.py install``.\n\n.. note::\n\n   Depending on the permissions of your system's Python, you might\n   need to be the root user to invoke ``python setup.py install``\n   successfully for each package.\n\n.. note::\n\n   The ``setuptools`` package is required to run ``python setup.py install``.\n   On Python versions before 3.8, ``setuptools`` is also a runtime\n   dependency of Supervisor.\n\nInstalling a Distribution Package\n---------------------------------\n\nSome Linux distributions offer a version of Supervisor that is installable\nthrough the system package manager.  These packages are made by third parties,\nnot the Supervisor developers, and often include distribution-specific changes\nto Supervisor.\n\nUse the package management tools of your distribution to check availability;\ne.g. on Ubuntu you can run ``apt-cache show supervisor``, and on CentOS\nyou can run ``yum info supervisor``.\n\nA feature of distribution packages of Supervisor is that they will usually\ninclude integration into the service management infrastructure of the\ndistribution, e.g. allowing ``supervisord`` to automatically start when\nthe system boots.\n\n.. note::\n\n    Distribution packages of Supervisor can lag considerably behind the\n    official Supervisor packages released to PyPI.  For example, Ubuntu\n    12.04 (released April 2012) offered a package based on Supervisor 3.0a8\n    (released January 2010).  Lag is often caused by the software release\n    policy set by a given distribution.\n\n.. note::\n\n    Users reported that the distribution package of Supervisor for Ubuntu 16.04\n    had different behavior than previous versions.  On Ubuntu 10.04, 12.04, and\n    14.04, installing the package will configure the system to start\n    ``supervisord`` when the system boots.  On Ubuntu 16.04, this was not done\n    by the initial release of the package.  The package was fixed later.  See\n    `Ubuntu Bug #1594740 <https://bugs.launchpad.net/ubuntu/+source/supervisor/+bug/1594740>`_\n    for more information.\n\n.. _create_config:\n\nCreating a Configuration File\n-----------------------------\n\nOnce the Supervisor installation has completed, run\n``echo_supervisord_conf``.  This will print a \"sample\" Supervisor\nconfiguration file to your terminal's stdout.\n\nOnce you see the file echoed to your terminal, reinvoke the command as\n``echo_supervisord_conf > /etc/supervisord.conf``. This won't work if\nyou do not have root access.\n\nIf you don't have root access, or you'd rather not put the\n:file:`supervisord.conf` file in :file:`/etc/supervisord.conf`, you\ncan place it in the current directory (``echo_supervisord_conf >\nsupervisord.conf``) and start :program:`supervisord` with the\n``-c`` flag in order to specify the configuration file\nlocation.\n\nFor example, ``supervisord -c supervisord.conf``.  Using the ``-c``\nflag actually is redundant in this case, because\n:program:`supervisord` searches the current directory for a\n:file:`supervisord.conf` before it searches any other locations for\nthe file, but it will work.  See :ref:`running` for more information\nabout the ``-c`` flag.\n\nOnce you have a configuration file on your filesystem, you can\nbegin modifying it to your liking.\n"
  },
  {
    "path": "docs/introduction.rst",
    "content": "Introduction\n============\n\nOverview\n--------\n\nSupervisor is a client/server system that allows its users to control\na number of processes on UNIX-like operating systems.  It was inspired\nby the following:\n\nConvenience\n\n  It is often inconvenient to need to write ``rc.d`` scripts for every\n  single process instance.  ``rc.d`` scripts are a great\n  lowest-common-denominator form of process\n  initialization/autostart/management, but they can be painful to\n  write and maintain.  Additionally, ``rc.d`` scripts cannot\n  automatically restart a crashed process and many programs do not\n  restart themselves properly on a crash.  Supervisord starts\n  processes as its subprocesses, and can be configured to\n  automatically restart them on a crash.  It can also automatically be\n  configured to start processes on its own invocation.\n\nAccuracy\n\n  It's often difficult to get accurate up/down status on processes on\n  UNIX.  Pidfiles often lie.  Supervisord starts processes as\n  subprocesses, so it always knows the true up/down status of its\n  children and can be queried conveniently for this data.\n\nDelegation\n\n  Users who need to control process state often need only to do that.\n  They don't want or need full-blown shell access to the machine on\n  which the processes are running.  Processes which listen on \"low\"\n  TCP ports often need to be started and restarted as the root user (a\n  UNIX misfeature).  It's usually the case that it's perfectly fine to\n  allow \"normal\" people to stop or restart such a process, but\n  providing them with shell access is often impractical, and providing\n  them with root access or sudo access is often impossible.  It's also\n  (rightly) difficult to explain to them why this problem exists.  If\n  supervisord is started as root, it is possible to allow \"normal\"\n  users to control such processes without needing to explain the\n  intricacies of the problem to them.  Supervisorctl allows a very\n  limited form of access to the machine, essentially allowing users to\n  see process status and control supervisord-controlled subprocesses\n  by emitting \"stop\", \"start\", and \"restart\" commands from a simple\n  shell or web UI.\n\nProcess Groups\n\n  Processes often need to be started and stopped in groups, sometimes\n  even in a \"priority order\".  It's often difficult to explain to\n  people how to do this.  Supervisor allows you to assign priorities\n  to processes, and allows user to emit commands via the supervisorctl\n  client like \"start all\", and \"restart all\", which starts them in the\n  preassigned priority order.  Additionally, processes can be grouped\n  into \"process groups\" and a set of logically related processes can\n  be stopped and started as a unit.\n\nFeatures\n--------\n\nSimple\n\n  Supervisor is configured through a simple INI-style config file\n  that’s easy to learn. It provides many per-process options that make\n  your life easier like restarting failed processes and automatic log\n  rotation.\n\nCentralized\n\n  Supervisor provides you with one place to start, stop, and monitor\n  your processes. Processes can be controlled individually or in\n  groups. You can configure Supervisor to provide a local or remote\n  command line and web interface.\n\nEfficient\n\n  Supervisor starts its subprocesses via fork/exec and subprocesses\n  don’t daemonize. The operating system signals Supervisor immediately\n  when a process terminates, unlike some solutions that rely on\n  troublesome PID files and periodic polling to restart failed\n  processes.\n\nExtensible\n\n  Supervisor has a simple event notification protocol that programs\n  written in any language can use to monitor it, and an XML-RPC\n  interface for control. It is also built with extension points that\n  can be leveraged by Python developers.\n\nCompatible\n\n  Supervisor works on just about everything except for Windows. It is\n  tested and supported on Linux, Mac OS X, Solaris, and FreeBSD. It is\n  written entirely in Python, so installation does not require a C\n  compiler.\n\nProven\n\n  While Supervisor is very actively developed today, it is not new\n  software. Supervisor has been around for years and is already in use\n  on many servers.\n\nSupervisor Components\n---------------------\n\n:program:`supervisord`\n\n  The server piece of supervisor is named :program:`supervisord`.  It\n  is responsible for starting child programs at its own invocation,\n  responding to commands from clients, restarting crashed or exited\n  subprocesseses, logging its subprocess ``stdout`` and ``stderr``\n  output, and generating and handling \"events\" corresponding to points\n  in subprocess lifetimes.\n\n  The server process uses a configuration file.  This is typically\n  located in :file:`/etc/supervisord.conf`.  This configuration file\n  is a \"Windows-INI\" style config file.  It is important to keep this\n  file secure via proper filesystem permissions because it may contain\n  unencrypted usernames and passwords.\n\n:program:`supervisorctl`\n\n  The command-line client piece of the supervisor is named\n  :program:`supervisorctl`.  It provides a shell-like interface to the\n  features provided by :program:`supervisord`.  From\n  :program:`supervisorctl`, a user can connect to different\n  :program:`supervisord` processes (one at a time), get status on the\n  subprocesses controlled by, stop and start subprocesses of, and get lists of\n  running processes of a :program:`supervisord`.\n\n  The command-line client talks to the server across a UNIX domain\n  socket or an internet (TCP) socket.  The server can assert that the\n  user of a client should present authentication credentials before it\n  allows them to perform commands.  The client process typically uses\n  the same configuration file as the server but any configuration file\n  with a ``[supervisorctl]`` section in it will work.\n\nWeb Server\n\n  A (sparse) web user interface with functionality comparable to\n  :program:`supervisorctl` may be accessed via a browser if you start\n  :program:`supervisord` against an internet socket.  Visit the server\n  URL (e.g. ``http://localhost:9001/``) to view and control process\n  status through the web interface after activating the configuration\n  file's ``[inet_http_server]`` section.\n\nXML-RPC Interface\n\n  The same HTTP server which serves the web UI serves up an XML-RPC\n  interface that can be used to interrogate and control supervisor and\n  the programs it runs.  See :ref:`xml_rpc`.\n\nPlatform Requirements\n---------------------\n\nSupervisor has been tested and is known to run on Linux (Ubuntu 18.04),\nMac OS X (10.4/10.5/10.6), and Solaris (10 for Intel) and FreeBSD 6.1.\nIt will likely work fine on most UNIX systems.\n\nSupervisor will *not* run at all under any version of Windows.\n\nSupervisor is intended to work on Python 3 version 3.4 or later\nand on Python 2 version 2.7.\n"
  },
  {
    "path": "docs/logging.rst",
    "content": "Logging\n=======\n\nOne of the main tasks that :program:`supervisord` performs is logging.\n:program:`supervisord` logs an activity log detailing what it's doing\nas it runs.  It also logs child process stdout and stderr output to\nother files if configured to do so.\n\nActivity Log\n------------\n\nThe activity log is the place where :program:`supervisord` logs\nmessages about its own health, its subprocess' state changes, any\nmessages that result from events, and debug and informational\nmessages.  The path to the activity log is configured via the\n``logfile`` parameter in the ``[supervisord]`` section of the\nconfiguration file, defaulting to :file:`$CWD/supervisord.log`.  If\nthe value of this option is the special string ``syslog``, the\nactivity log will be routed to the syslog service instead of being\nwritten to a file.  Sample activity log traffic is shown in the\nexample below.  Some lines have been broken to better fit the screen.\n\nSample Activity Log Output\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. code-block:: text\n\n   2007-09-08 14:43:22,886 DEBG 127.0.0.1:Medusa (V1.11) started at Sat Sep  8 14:43:22 2007\n           Hostname: kingfish\n           Port:9001\n   2007-09-08 14:43:22,961 INFO RPC interface 'supervisor' initialized\n   2007-09-08 14:43:22,961 CRIT Running without any HTTP authentication checking\n   2007-09-08 14:43:22,962 INFO supervisord started with pid 27347\n   2007-09-08 14:43:23,965 INFO spawned: 'listener_00' with pid 27349\n   2007-09-08 14:43:23,970 INFO spawned: 'eventgen' with pid 27350\n   2007-09-08 14:43:23,990 INFO spawned: 'grower' with pid 27351\n   2007-09-08 14:43:24,059 DEBG 'listener_00' stderr output:\n    /Users/chrism/projects/supervisor/supervisor2/dev-sandbox/bin/python:\n    can't open file '/Users/chrism/projects/supervisor/supervisor2/src/supervisor/scripts/osx_eventgen_listener.py':\n    [Errno 2] No such file or directory\n   2007-09-08 14:43:24,060 DEBG fd 7 closed, stopped monitoring <PEventListenerDispatcher at 19910168 for\n    <Subprocess at 18892960 with name listener_00 in state STARTING> (stdout)>\n   2007-09-08 14:43:24,060 INFO exited: listener_00 (exit status 2; not expected)\n   2007-09-08 14:43:24,061 DEBG received SIGCHLD indicating a child quit\n\nThe activity log \"level\" is configured in the config file via the\n``loglevel`` parameter in the ``[supervisord]`` ini file section.\nWhen ``loglevel`` is set, messages of the specified priority, plus\nthose with any higher priority are logged to the activity log.  For\nexample, if ``loglevel`` is ``error``, messages of ``error`` and\n``critical`` priority will be logged.  However, if loglevel is\n``warn``, messages of ``warn``, ``error``, and ``critical`` will be\nlogged.\n\n.. _activity_log_levels:\n\nActivity Log Levels\n~~~~~~~~~~~~~~~~~~~\n\nThe below table describes the logging levels in more detail, ordered\nin highest priority to lowest.  The \"Config File Value\" is the string\nprovided to the ``loglevel`` parameter in the ``[supervisord]``\nsection of configuration file and the \"Output Code\" is the code that\nshows up in activity log output lines.\n\n=================   ===========   ============================================\nConfig File Value   Output Code   Description\n=================   ===========   ============================================\ncritical            CRIT          Messages that indicate a condition that\n                                  requires immediate user attention, a\n                                  supervisor state change, or an error in\n                                  supervisor itself.\nerror               ERRO          Messages that indicate a potentially\n                                  ignorable error condition (e.g. unable to\n                                  clear a log directory).\nwarn                WARN          Messages that indicate an anomalous\n                                  condition which isn't an error.\ninfo                INFO          Normal informational output.  This is the\n                                  default log level if none is explicitly\n                                  configured.\ndebug               DEBG          Messages useful for users trying to debug\n                                  process configuration and communications\n                                  behavior (process output, listener state\n                                  changes, event notifications).\ntrace               TRAC          Messages useful for developers trying to\n                                  debug supervisor plugins, and information\n                                  about HTTP and RPC requests and responses.\nblather             BLAT          Messages useful for developers trying to\n                                  debug supervisor itself.\n=================   ===========   ============================================\n\nActivity Log Rotation\n~~~~~~~~~~~~~~~~~~~~~\n\nThe activity log is \"rotated\" by :program:`supervisord` based on the\ncombination of the ``logfile_maxbytes`` and the ``logfile_backups``\nparameters in the ``[supervisord]`` section of the configuration file.\nWhen the activity log reaches ``logfile_maxbytes`` bytes, the current\nlog file is moved to a backup file and a new activity log file is\ncreated.  When this happens, if the number of existing backup files is\ngreater than or equal to ``logfile_backups``, the oldest backup file\nis removed and the backup files are renamed accordingly.  If the file\nbeing written to is named :file:`supervisord.log`, when it exceeds\n``logfile_maxbytes``, it is closed and renamed to\n:file:`supervisord.log.1`, and if files :file:`supervisord.log.1`,\n:file:`supervisord.log.2` etc. exist, then they are renamed to\n:file:`supervisord.log.2`, :file:`supervisord.log.3` etc.\nrespectively.  If ``logfile_maxbytes`` is 0, the logfile is never\nrotated (and thus backups are never made).  If ``logfile_backups`` is\n0, no backups will be kept.\n\nChild Process Logs\n------------------\n\nThe stdout of child processes spawned by supervisor, by default, is\ncaptured for redisplay to users of :program:`supervisorctl` and other\nclients.  If no specific logfile-related configuration is performed in\na ``[program:x]``, ``[fcgi-program:x]``, or ``[eventlistener:x]``\nsection in the configuration file, the following is true:\n\n- :program:`supervisord` will capture the child process' stdout and\n  stderr output into temporary files.  Each stream is captured to a\n  separate file.  This is known as ``AUTO`` log mode.\n\n- ``AUTO`` log files are named automatically and placed in the\n  directory configured as ``childlogdir`` of the ``[supervisord]``\n  section of the config file.\n\n- The size of each ``AUTO`` log file is bounded by the\n  ``{streamname}_logfile_maxbytes`` value of the program section\n  (where {streamname} is \"stdout\" or \"stderr\").  When it reaches that\n  number, it is rotated (like the activity log), based on the\n  ``{streamname}_logfile_backups``.\n\nThe configuration keys that influence child process logging in\n``[program:x]`` and ``[fcgi-program:x]`` sections are these:\n\n``redirect_stderr``, ``stdout_logfile``, ``stdout_logfile_maxbytes``,\n``stdout_logfile_backups``, ``stdout_capture_maxbytes``, ``stdout_syslog``,\n``stderr_logfile``, ``stderr_logfile_maxbytes``,\n``stderr_logfile_backups``, ``stderr_capture_maxbytes``, and\n``stderr_syslog``.\n\n``[eventlistener:x]`` sections may not specify\n``redirect_stderr``, ``stdout_capture_maxbytes``, or\n``stderr_capture_maxbytes``, but otherwise they accept the same values.\n\nThe configuration keys that influence child process logging in the\n``[supervisord]`` config file section are these:\n``childlogdir``, and ``nocleanup``.\n\n.. _capture_mode:\n\nCapture Mode\n~~~~~~~~~~~~\n\nCapture mode is an advanced feature of Supervisor.  You needn't\nunderstand capture mode unless you want to take actions based on data\nparsed from subprocess output.\n\nIf a ``[program:x]`` section in the configuration file defines a\nnon-zero ``stdout_capture_maxbytes`` or ``stderr_capture_maxbytes``\nparameter, each process represented by the program section may emit\nspecial tokens on its stdout or stderr stream (respectively) which\nwill effectively cause supervisor to emit a ``PROCESS_COMMUNICATION``\nevent (see :ref:`events` for a description of events).\n\nThe process communications protocol relies on two tags, one which\ncommands supervisor to enter \"capture mode\" for the stream and one\nwhich commands it to exit.  When a process stream enters \"capture\nmode\", data sent to the stream will be sent to a separate buffer in\nmemory, the \"capture buffer\", which is allowed to contain a maximum of\n``capture_maxbytes`` bytes.  During capture mode, when the buffer's\nlength exceeds ``capture_maxbytes`` bytes, the earliest data in the\nbuffer is discarded to make room for new data.  When a process stream\nexits capture mode, a ``PROCESS_COMMUNICATION`` event subtype is\nemitted by supervisor, which may be intercepted by event listeners.\n\nThe tag to begin \"capture mode\" in a process stream is\n``<!--XSUPERVISOR:BEGIN-->``.  The tag to exit capture mode is\n``<!--XSUPERVISOR:END-->``.  The data between these tags may be\narbitrary, and forms the payload of the ``PROCESS_COMMUNICATION``\nevent.  For example, if a program is set up with a\n``stdout_capture_maxbytes`` of \"1MB\", and it emits the following on\nits stdout stream:\n\n.. code-block:: text\n\n   <!--XSUPERVISOR:BEGIN-->Hello!<!--XSUPERVISOR:END-->\n\nIn this circumstance, :program:`supervisord` will emit a\n``PROCESS_COMMUNICATIONS_STDOUT`` event with data in the payload of\n\"Hello!\".\n\nThe output of processes specified as \"event listeners\"\n(``[eventlistener:x]`` sections) is not processed this way.\nOutput from these processes cannot enter capture mode.\n"
  },
  {
    "path": "docs/plugins.rst",
    "content": "Third Party Applications and Libraries\n======================================\n\nThere are a number of third party applications that can be useful together\nwith Supervisor. This list aims to summarize them and make them easier\nto find.\n\nSee README.rst for information on how to contribute to this list.\n\nDashboards and Tools for Multiple Supervisor Instances\n------------------------------------------------------\n\nThese are tools that can monitor or control a number of Supervisor\ninstances running on different servers.\n\n`cesi <https://github.com/Gamegos/cesi>`_\n    Web-based dashboard written in Python.\n\n`Django-Dashvisor <https://github.com/aleszoulek/django-dashvisor>`_\n    Web-based dashboard written in Python.  Requires Django 1.3 or 1.4.\n\n`Nodervisor <https://github.com/TAKEALOT/nodervisor>`_\n    Web-based dashboard written in Node.js.\n\n`Supervisord-Monitor <https://github.com/mlazarov/supervisord-monitor>`_\n    Web-based dashboard written in PHP.\n\n`Supervisord-Monitor 2 <https://github.com/KoNekoD/supervisord-monitor>`_\n    Modern and adaptive next gen web-based dashboard written in PHP.\n\n`SupervisorUI <https://github.com/luxbet/supervisorui>`_\n    Another Web-based dashboard written in PHP.\n\n`supervisorclusterctl <https://github.com/RobWin/supervisorclusterctl>`_\n    Command line tool for controlling multiple Supervisor instances\n    using Ansible.\n\n`suponoff <https://github.com/GambitResearch/suponoff>`_\n    Web-based dashboard written in Python 3.  Requires Django 1.7 or later.\n\n`Supvisors <https://github.com/julien6387/supvisors>`_\n    Designed for distributed applications, written in Python 3.6. Includes an extended XML-RPC API,\n    a Web-based dashboard and special features such as staged start and stop.\n\n`multivisor <https://github.com/tiagocoutinho/multivisor>`_\n    Centralized supervisor web-based dashboard. The frontend is based on\n    `VueJS <https://vuejs.org>`_. The backend runs a `flask <http://flask.pocoo.org>`_\n    web server. It communicates with each supervisor through a specialized supervisor\n    event-listener based on `zerorpc <http://www.zerorpc.io>`_.\n\n`Dart <https://github.com/plockaby/dart>`_\n    Web-based dashboard and command line tool written in Python using PostgreSQL\n    with a REST API, event monitoring, and configuration management.\n\n`Polyvisor <https://github.com/poly-laboratory/poly-visor>`_\n    Web-based dashboard written in Python using `flask <http://flask.pocoo.org>`_ web server.\n    Frontend based on `Svelte <https://svelte.dev/>`_ result in lightweighted packages. Communicate via supervisor's event-listener.\n    Providing system resource management via visualized charts & easy to config processes configs via web interface.\n\nThird Party Plugins and Libraries for Supervisor\n------------------------------------------------\n\nThese are plugins and libraries that add new functionality to Supervisor.\nThese also includes various event listeners.\n\n`superlance <https://pypi.org/pypi/superlance/>`_\n    Provides set of common eventlisteners that can be used to monitor\n    and, for example, restart when it uses too much memory etc.\n`superhooks <https://pypi.org/project/superhooks/>`_\n    Send Supervisor event notifications to HTTP1.1 webhooks.\n`mr.rubber <https://github.com/collective/mr.rubber>`_\n    An event listener that makes it possible to scale the number of\n    processes to the number of cores on the supervisor host.\n`supervisor-wildcards <https://github.com/aleszoulek/supervisor-wildcards>`_\n    Implements start/stop/restart commands with wildcard support for\n    Supervisor.  These commands run in parallel and can be much faster\n    than the built-in start/stop/restart commands.\n`mr.laforge <https://github.com/fschulze/mr.laforge>`_\n    Lets you easily make sure that ``supervisord`` and specific\n    processes controlled by it are running from within shell and\n    Python scripts. Also adds a ``kill`` command to supervisor that\n    makes it possible to send arbitrary signals to child processes.\n`supervisor_cache <https://github.com/mnaberez/supervisor_cache>`_\n    An extension for Supervisor that provides the ability to cache\n    arbitrary data directly inside a Supervisor instance as key/value\n    pairs. Also serves as a reference for how to write Supervisor\n    extensions.\n`supervisor_twiddler <https://github.com/mnaberez/supervisor_twiddler>`_\n    An RPC extension for Supervisor that allows Supervisor's\n    configuration and state to be manipulated in ways that are not\n    normally possible at runtime.\n`supervisor-stdout <https://github.com/coderanger/supervisor-stdout>`_\n    An event listener that sends process output to supervisord's stdout.\n`supervisor-serialrestart <https://github.com/native2k/supervisor-serialrestart>`_\n    Adds a ``serialrestart`` command to ``supervisorctl`` that restarts\n    processes one after another rather than all at once.\n`supervisor-quick <http://lxyu.github.io/supervisor-quick/>`_\n    Adds ``quickstart``, ``quickstop``, and ``quickrestart`` commands to\n    ``supervisorctl`` that can be faster than the built-in commands.  It\n    works by using the non-blocking mode of the XML-RPC methods and then\n    polling ``supervisord``.  The built-in commands use the blocking mode,\n    which can be slower due to ``supervisord`` implementation details.\n`supervisor-logging <https://github.com/infoxchange/supervisor-logging>`_\n    An event listener that sends process log events to an external\n    Syslog instance (e.g. Logstash).\n`supervisor-logstash-notifier <https://github.com/dohop/supervisor-logstash-notifier>`_\n    An event listener plugin to stream state events to a Logstash instance.\n`supervisor_cgroups <https://github.com/htch/supervisor_cgroups>`_\n    An event listener that enables tying Supervisor processes to a cgroup\n    hierarchy.  It is intended to be used as a replacement for\n    `cgrules.conf <http://linux.die.net/man/5/cgrules.conf>`_.\n`supervisor_checks <https://github.com/vovanec/supervisor_checks>`_\n    Framework to build health checks for Supervisor-based services. Health\n    check applications are supposed to run as event listeners in Supervisor\n    environment. On check failure Supervisor will attempt to restart\n    monitored process.\n`Superfsmon <https://github.com/timakro/superfsmon>`_\n    Watch a directory and restart programs when files change.  It can monitor\n    a directory for changes, filter the file paths by glob patterns or regular\n    expressions and restart Supervisor programs individually or by group.\n\n\nLibraries that integrate Third Party Applications with Supervisor\n-----------------------------------------------------------------\n\nThese are libraries and plugins that makes it easier to use Supervisor\nwith third party applications:\n\n`collective.recipe.supervisor <https://pypi.org/pypi/collective.recipe.supervisor/>`_\n    A buildout recipe to install supervisor.\n`puppet-module-supervisor <https://github.com/plathrop/puppet-module-supervisor>`_\n    Puppet module for configuring the supervisor daemon tool.\n`puppet-supervisord <https://github.com/ajcrowe/puppet-supervisord>`_\n    Puppet module to manage the supervisord process control system.\n`ngx_supervisord <https://github.com/FRiCKLE/ngx_supervisord>`_\n    An nginx module providing API to communicate with supervisord and\n    manage (start/stop) backends on-demand.\n`Supervisord-Nagios-Plugin <https://github.com/Level-Up/Supervisord-Nagios-Plugin>`_\n    A Nagios/Icinga plugin written in Python to monitor individual supervisord processes.\n`nagios-supervisord-processes <https://github.com/blablacar/nagios-supervisord-processes>`_\n    A Nagios/Icinga plugin written in PHP to monitor individual supervisord processes.\n`supervisord-nagios <https://github.com/3dna/supervisord-nagios>`_\n    A plugin for supervisorctl to allow one to perform nagios-style checks\n    against supervisord-managed processes.\n`php-supervisor-event <https://github.com/mtdowling/php-supervisor-event>`_\n    PHP classes for interacting with Supervisor event notifications.\n`PHP5 Supervisor wrapper <https://github.com/yzalis/Supervisor>`_\n    PHP 5 library to manage Supervisor instances as object.\n`Symfony2 SupervisorBundle <https://github.com/yzalis/SupervisorBundle>`_\n    Provide full integration of Supervisor multiple servers management into Symfony2 project.\n`sd-supervisord <https://github.com/robcowie/sd-supervisord>`_\n    `Server Density <http://www.serverdensity.com>`_ plugin for\n    supervisor.\n`node-supervisord <https://github.com/crcn/node-supervisord>`_\n    Node.js client for Supervisor's XML-RPC interface.\n`node-supervisord-eventlistener <https://github.com/sugendran/node-supervisord-eventlistener>`_\n    Node.js implementation of an event listener for Supervisor.\n`ruby-supervisor <https://github.com/schmurfy/ruby-supervisor>`_\n    Ruby client library for Supervisor's XML-RPC interface.\n`Sulphite <https://github.com/jib/sulphite>`_\n    Sends supervisord events to `Graphite <https://github.com/graphite-project/graphite-web>`_.\n`supervisord.tmbundle <https://github.com/countergram/supervisord.tmbundle>`_\n    `TextMate <http://macromates.com/>`_ bundle for supervisord.conf.\n`capistrano-supervisord <https://github.com/yyuu/capistrano-supervisord>`_\n    `Capistrano <https://github.com/capistrano/capistrano>`_ recipe to deploy supervisord based services.\n`capistrano-supervisor <https://github.com/glooby/capistrano-supervisor>`_\n    Another package to control supervisord from `Capistrano <https://github.com/capistrano/capistrano>`_.\n`chef-supervisor <https://github.com/opscode-cookbooks/supervisor>`_\n    `Chef <http://www.opscode.com/chef/>`_ cookbook install and configure supervisord.\n`SupervisorPHP <http://supervisorphp.com>`_\n    Complete Supervisor suite in PHP: Client using XML-RPC interface, event listener and configuration builder implementation, console application and monitor UI.\n`Supervisord-Client <http://search.cpan.org/~skaufman/Supervisord-Client>`_\n    Perl client for the supervisord XML-RPC interface.\n`supervisord4j <https://github.com/satifanie/supervisord4j>`_\n    Java client for Supervisor's XML-RPC interface.\n`Supermann <https://github.com/borntyping/supermann>`_\n    Supermann monitors processes running under Supervisor and sends metrics\n    to `Riemann <http://riemann.io/>`_.\n`gulp-supervisor <https://github.com/leny/gulp-supervisor>`_\n    Run Supervisor as a `Gulp <http://gulpjs.com/>`_ task.\n`Yeebase.Supervisor <https://github.com/yeebase/Yeebase.Supervisor>`_\n    Control and monitor Supervisor from a TYPO3 Flow application.\n`dokku-supervisord <https://github.com/statianzo/dokku-supervisord>`_\n    `Dokku <https://github.com/progrium/dokku>`_ plugin that injects ``supervisord`` to run\n    applications.\n`dokku-logging-supervisord <https://github.com/sehrope/dokku-logging-supervisord>`_\n    `Dokku <https://github.com/progrium/dokku>`_ plugin that injects ``supervisord`` to run\n    applications.  It also redirects ``stdout`` and ``stderr`` from processes to log files\n    (rather than the Docker default per-container JSON files).\n`superslacker <https://github.com/MTSolutions/superslacker>`_\n    Send Supervisor event notifications to `Slack <https://slack.com>`_.\n`supervisor-alert <https://github.com/rahiel/supervisor-alert>`_\n    Send event notifications over `Telegram <https://telegram.org>`_ or to an\n    arbitrary command.\n`supervisor-discord <https://github.com/chaos-a/supervisor-discord>`_\n    Send event notifications to `Discord <https://discord.com>`_ via webhooks.\n"
  },
  {
    "path": "docs/running.rst",
    "content": ".. _running:\n\nRunning Supervisor\n==================\n\nThis section makes reference to a :envvar:`BINDIR` when explaining how\nto run the :command:`supervisord` and :command:`supervisorctl`\ncommands.  This is the \"bindir\" directory that your Python\ninstallation has been configured with.  For example, for an\ninstallation of Python installed via ``./configure\n--prefix=/usr/local/py; make; make install``, :envvar:`BINDIR` would\nbe :file:`/usr/local/py/bin`. Python interpreters on different\nplatforms use a different :envvar:`BINDIR`.  Look at the output of\n``setup.py install`` if you can't figure out where yours is.\n\nAdding a Program\n----------------\n\nBefore :program:`supervisord` will do anything useful for you, you'll\nneed to add at least one ``program`` section to its configuration.\nThe ``program`` section will define a program that is run and managed\nwhen you invoke the :command:`supervisord` command.  To add a program,\nyou'll need to edit the :file:`supervisord.conf` file.\n\nOne of the simplest possible programs to run is the UNIX\n:program:`cat` program.  A ``program`` section that will run ``cat``\nwhen the :program:`supervisord` process starts up is shown below.\n\n.. code-block:: ini\n\n   [program:foo]\n   command=/bin/cat\n\nThis stanza may be cut and pasted into the :file:`supervisord.conf`\nfile.  This is the simplest possible program configuration, because it\nonly names a command.  Program configuration sections have many other\nconfiguration options which aren't shown here.  See\n:ref:`programx_section` for more information.\n\nRunning :program:`supervisord`\n------------------------------\n\nTo start :program:`supervisord`, run :file:`$BINDIR/supervisord`.  The\nresulting process will daemonize itself and detach from the terminal.\nIt keeps an operations log at :file:`$CWD/supervisor.log` by default.\n\nYou may start the :command:`supervisord` executable in the foreground\nby passing the ``-n`` flag on its command line.  This is useful to\ndebug startup problems.\n\n.. warning::\n\n   When :program:`supervisord` starts up, it will search for its\n   configuration file in default locations *including the current working\n   directory*.  If you are security-conscious you will probably want to\n   specify a \"-c\" argument after the :program:`supervisord` command\n   specifying an absolute path to a configuration file to ensure that someone\n   doesn't trick you into running supervisor from within a directory that\n   contains a rogue ``supervisord.conf`` file.  A warning is emitted when\n   supervisor is started as root without this ``-c`` argument.\n\nTo change the set of programs controlled by :program:`supervisord`,\nedit the :file:`supervisord.conf` file and ``kill -HUP`` or otherwise\nrestart the :program:`supervisord` process.  This file has several\nexample program definitions.\n\nThe :command:`supervisord` command accepts a number of command-line\noptions.  Each of these command line options overrides any equivalent\nvalue in the configuration file.\n\n:command:`supervisord` Command-Line Options\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n-c FILE, --configuration=FILE\n\n   The path to a :program:`supervisord` configuration file.\n\n-n, --nodaemon\n\n   Run :program:`supervisord` in the foreground.\n\n-s, --silent\n\n   No output directed to stdout.\n\n-h, --help\n\n   Show :command:`supervisord` command help.\n\n-u USER, --user=USER\n\n   UNIX username or numeric user id.  If :program:`supervisord` is\n   started as the root user, setuid to this user as soon as possible\n   during startup.\n\n-m OCTAL, --umask=OCTAL\n\n   Octal number (e.g. 022) representing the :term:`umask` that should\n   be used by :program:`supervisord` after it starts.\n\n-d PATH, --directory=PATH\n\n   When supervisord is run as a daemon, cd to this directory before\n   daemonizing.\n\n-l FILE, --logfile=FILE\n\n   Filename path to use as the supervisord activity log.\n\n-y BYTES, --logfile_maxbytes=BYTES\n\n   Max size of the supervisord activity log file before a rotation\n   occurs.  The value is suffix-multiplied, e.g \"1\" is one byte, \"1MB\"\n   is 1 megabyte, \"1GB\" is 1 gigabyte.\n\n-z NUM, --logfile_backups=NUM\n\n   Number of backup copies of the supervisord activity log to keep\n   around.  Each logfile will be of size ``logfile_maxbytes``.\n\n-e LEVEL, --loglevel=LEVEL\n\n   The logging level at which supervisor should write to the activity\n   log.  Valid levels are ``trace``, ``debug``, ``info``, ``warn``,\n   ``error``, and ``critical``.\n\n-j FILE, --pidfile=FILE\n\n   The filename to which supervisord should write its pid file.\n\n-i STRING, --identifier=STRING\n\n   Arbitrary string identifier exposed by various client UIs for this\n   instance of supervisor.\n\n-q PATH, --childlogdir=PATH\n\n   A path to a directory (it must already exist) where supervisor will\n   write its ``AUTO`` -mode child process logs.\n\n-k, --nocleanup\n\n   Prevent :program:`supervisord` from performing cleanup (removal of\n   old ``AUTO`` process log files) at startup.\n\n-a NUM, --minfds=NUM\n\n   The minimum number of file descriptors that must be available to\n   the supervisord process before it will start successfully.\n\n-t, --strip_ansi\n\n   Strip ANSI escape sequences from all child log process.\n\n-v, --version\n\n   Print the supervisord version number out to stdout and exit.\n\n--profile_options=LIST\n\n   Comma-separated options list for profiling.  Causes\n   :program:`supervisord` to run under a profiler, and output results\n   based on the options, which is a comma-separated list of the\n   following: ``cumulative``, ``calls``, ``callers``.\n   E.g. ``cumulative,callers``.\n\n--minprocs=NUM\n\n   The minimum number of OS process slots that must be available to\n   the supervisord process before it will start successfully.\n\n\nRunning :program:`supervisorctl`\n--------------------------------\n\nTo start :program:`supervisorctl`, run ``$BINDIR/supervisorctl``.  A\nshell will be presented that will allow you to control the processes\nthat are currently managed by :program:`supervisord`.  Type \"help\" at\nthe prompt to get information about the supported commands.\n\nThe :command:`supervisorctl` executable may be invoked with \"one time\"\ncommands when invoked with arguments from a command line.  An example:\n``supervisorctl stop all``.  If arguments are present on the\ncommand-line, it will prevent the interactive shell from being\ninvoked.  Instead, the command will be executed and ``supervisorctl``\nwill exit with a code of 0 for success or running and non-zero for\nerror. An example: ``supervisorctl status all`` would return non-zero\nif any single process was not running.\n\nIf :command:`supervisorctl` is invoked in interactive mode against a\n:program:`supervisord` that requires authentication, you will be asked\nfor authentication credentials.\n\n:command:`supervisorctl` Command-Line Options\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n-c, --configuration\n\n   Configuration file path (default /etc/supervisord.conf)\n\n-h, --help\n\n   Print usage message and exit\n\n-i, --interactive\n\n   Start an interactive shell after executing commands\n\n-s, --serverurl URL\n\n   URL on which supervisord server is listening (default \"http://localhost:9001\").\n\n-u, --username\n\n   Username to use for authentication with server\n\n-p, --password\n\n   Password to use for authentication with server\n\n-r, --history-file\n\n   Keep a readline history (if readline is available)\n\n`action [arguments]`\n\nActions are commands like \"tail\" or \"stop\".  If -i is specified or no action is\nspecified on the command line, a \"shell\" interpreting actions typed\ninteractively is started.  Use the action \"help\" to find out about available\nactions.\n\n\n:command:`supervisorctl` Actions\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nhelp\n\n   Print a list of available actions\n\nhelp <action>\n\n   Print help for <action>\n\nadd <name> [...]\n\n   Activates any updates in config for process/group\n\nremove <name> [...]\n\n   Removes process/group from active config\n   \nupdate\n\n   Reload config and add/remove as necessary, and will restart affected programs\n   \nupdate all\n\n   Reload config and add/remove as necessary, and will restart affected programs\n\nupdate <gname> [...]\n\n   Update specific groups, and will restart affected programs\n\nclear <name>\n\n   Clear a process' log files.\n\nclear <name> <name>\n\n   Clear multiple process' log files\n\nclear all\n\n   Clear all process' log files\n\nfg <process>\n\n   Connect to a process in foreground mode\n   Press Ctrl+C to exit foreground\n\npid\n\n   Get the PID of supervisord.\n\npid <name>\n\n   Get the PID of a single child process by name.\n\npid all\n\n   Get the PID of every child process, one per line.\n\nreload\n\n   Restarts the remote supervisord\n\nreread\n\n   Reload the daemon's configuration files, without add/remove (no restarts)\n\nrestart <name>\n\n   Restart a process\n   Note: restart does not reread config files. For that, see reread and update.\n\nrestart <gname>:*\n\n   Restart all processes in a group\n   Note: restart does not reread config files. For that, see reread and update.\n\nrestart <name> <name>\n\n   Restart multiple processes or groups\n   Note: restart does not reread config files. For that, see reread and update.\n\nrestart all\n\n   Restart all processes\n   Note: restart does not reread config files. For that, see reread and update.\n\nsignal\n\n   No help on signal\n\nstart <name>\n\n   Start a process\n\nstart <gname>:*\n\n   Start all processes in a group\n\nstart <name> <name>\n\n   Start multiple processes or groups\n\nstart all\n\n   Start all processes\n\nstatus\n\n   Get all process status info.\n\nstatus <name>\n\n   Get status on a single process by name.\n\nstatus <name> <name>\n\n   Get status on multiple named processes.\n\nstop <name>\n\n   Stop a process\n\nstop <gname>:*\n\n   Stop all processes in a group\n\nstop <name> <name>\n\n   Stop multiple processes or groups\n\nstop all\n\n   Stop all processes\n\ntail [-f] <name> [stdout|stderr] (default stdout)\n\n   Output the last part of process logs\n   Ex:\n   tail -f <name>\t\tContinuous tail of named process stdout Ctrl-C to exit.\n   tail -100 <name>\tlast 100 *bytes* of process stdout\n   tail <name> stderr\tlast 1600 *bytes* of process stderr\n\n\nSignals\n-------\n\nThe :program:`supervisord` program may be sent signals which cause it\nto perform certain actions while it's running.\n\nYou can send any of these signals to the single :program:`supervisord`\nprocess id.  This process id can be found in the file represented by\nthe ``pidfile`` parameter in the ``[supervisord]`` section of the\nconfiguration file (by default it's :file:`$CWD/supervisord.pid`).\n\nSignal Handlers\n~~~~~~~~~~~~~~~\n\n``SIGTERM``\n\n  :program:`supervisord` and all its subprocesses will shut down.\n  This may take several seconds.\n\n``SIGINT``\n\n  :program:`supervisord` and all its subprocesses will shut down.\n  This may take several seconds.\n\n``SIGQUIT``\n\n  :program:`supervisord` and all its subprocesses will shut down.\n  This may take several seconds.\n\n``SIGHUP``\n\n  :program:`supervisord` will stop all processes, reload the\n  configuration from the first config file it finds, and start all\n  processes.\n\n``SIGUSR2``\n\n  :program:`supervisord` will close and reopen the main activity log\n  and all child log files.\n\nRuntime Security\n----------------\n\nThe developers have done their best to assure that use of a\n:program:`supervisord` process running as root cannot lead to\nunintended privilege escalation.  But **caveat emptor**.  Supervisor\nis not as paranoid as something like DJ Bernstein's\n:term:`daemontools`, inasmuch as :program:`supervisord` allows for\narbitrary path specifications in its configuration file to which data\nmay be written.  Allowing arbitrary path selections can create\nvulnerabilities from symlink attacks.  Be careful when specifying\npaths in your configuration.  Ensure that the :program:`supervisord`\nconfiguration file cannot be read from or written to by unprivileged\nusers and that all files installed by the supervisor package have\n\"sane\" file permission protection settings.  Additionally, ensure that\nyour ``PYTHONPATH`` is sane and that all Python standard\nlibrary files have adequate file permission protections.\n\nRunning :program:`supervisord` automatically on startup\n-------------------------------------------------------\n\nIf you are using a distribution-packaged version of Supervisor, it should\nalready be integrated into the service management infrastructure of your\ndistribution.\n\nThere are user-contributed scripts for various operating systems at:\nhttps://github.com/Supervisor/initscripts\n\nThere are some answers at Serverfault in case you get stuck:\n`How to automatically start supervisord on Linux (Ubuntu)`__\n\n.. __: http://serverfault.com/questions/96499/how-to-automatically-start-supervisord-on-linux-ubuntu\n"
  },
  {
    "path": "docs/subprocess.rst",
    "content": "Subprocesses\n============\n\n:program:`supervisord`'s primary purpose is to create and manage\nprocesses based on data in its configuration file.  It does this by\ncreating subprocesses.  Each subprocess spawned by supervisor is\nmanaged for the entirety of its lifetime by supervisord\n(:program:`supervisord` is the parent process of each process it\ncreates).  When a child dies, supervisor is notified of its death via\nthe ``SIGCHLD`` signal, and it performs the appropriate operation.\n\n.. _nondaemonizing_of_subprocesses:\n\nNondaemonizing of Subprocesses\n------------------------------\n\nPrograms meant to be run under supervisor should not daemonize\nthemselves.  Instead, they should run in the foreground.  They should\nnot detach from the terminal from which they are started.\n\nThe easiest way to tell if a program will run in the foreground is to\nrun the command that invokes the program from a shell prompt.  If it\ngives you control of the terminal back, but continues running, it's\ndaemonizing itself and that will almost certainly be the wrong way to\nrun it under supervisor.  You want to run a command that essentially\nrequires you to press :kbd:`Ctrl-C` to get control of the terminal\nback.  If it gives you a shell prompt back after running it without\nneeding to press :kbd:`Ctrl-C`, it's not useful under supervisor.  All\nprograms have options to be run in the foreground but there's no\n\"standard way\" to do it; you'll need to read the documentation for\neach program.\n\nBelow are configuration file examples that are known to start\ncommon programs in \"foreground\" mode under Supervisor.\n\nExamples of Program Configurations\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nHere are some \"real world\" program configuration examples:\n\nApache 2.2.6\n++++++++++++\n\n.. code-block:: ini\n\n   [program:apache2]\n   command=/path/to/httpd -c \"ErrorLog /dev/stdout\" -DFOREGROUND\n   redirect_stderr=true\n\nTwo Zope 2.X instances and one ZEO server\n+++++++++++++++++++++++++++++++++++++++++\n\n.. code-block:: ini\n\n   [program:zeo]\n   command=/path/to/runzeo\n   priority=1\n\n   [program:zope1]\n   command=/path/to/instance/home/bin/runzope\n   priority=2\n   redirect_stderr=true\n\n   [program:zope2]\n   command=/path/to/another/instance/home/bin/runzope\n   priority=2\n   redirect_stderr=true\n\nPostgres 8.X\n++++++++++++\n\n.. code-block:: ini\n\n   [program:postgres]\n   command=/path/to/postmaster\n   ; we use the \"fast\" shutdown signal SIGINT\n   stopsignal=INT\n   redirect_stderr=true\n\nOpenLDAP :program:`slapd`\n+++++++++++++++++++++++++\n\n.. code-block:: ini\n\n   [program:slapd]\n   command=/path/to/slapd -f /path/to/slapd.conf -h ldap://0.0.0.0:8888\n   redirect_stderr=true\n\nOther Examples\n~~~~~~~~~~~~~~\n\nOther examples of shell scripts that could be used to start services\nunder :program:`supervisord` can be found at\n`http://thedjbway.b0llix.net/services.html\n<http://thedjbway.b0llix.net/services.html>`_.  These examples are\nactually for :program:`daemontools` but the premise is the same for\nsupervisor.\n\nAnother collection of recipes for starting various programs in the\nforeground is available from `http://smarden.org/runit/runscripts.html\n<http://smarden.org/runit/runscripts.html>`_.\n\n:program:`pidproxy` Program\n---------------------------\n\nSome processes (like :program:`mysqld`) ignore signals sent to the\nactual process which is spawned by :program:`supervisord`.  Instead, a\n\"special\" thread/process is created by these kinds of programs which\nis responsible for handling signals.  This is problematic because\n:program:`supervisord` can only kill a process which it creates\nitself.  If a process created by :program:`supervisord` creates its\nown child processes, :program:`supervisord` cannot kill them.\n\nFortunately, these types of programs typically write a \"pidfile\" which\ncontains the \"special\" process' PID, and is meant to be read and used\nin order to kill the process.  As a workaround for this case, a\nspecial :program:`pidproxy` program can handle startup of these kinds\nof processes.  The :program:`pidproxy` program is a small shim that\nstarts a process, and upon the receipt of a signal, sends the signal\nto the pid provided in a pidfile.  A sample configuration program\nentry for a pidproxy-enabled program is provided below.\n\n.. code-block:: ini\n\n   [program:mysql]\n   command=/path/to/pidproxy /path/to/pidfile /path/to/mysqld_safe\n\nThe :program:`pidproxy` program is put into your configuration's\n``$BINDIR`` when supervisor is installed (it is a \"console script\").\n\n.. _subprocess_environment:\n\nSubprocess Environment\n----------------------\n\nSubprocesses will inherit the environment of the shell used to start\nthe :program:`supervisord` program.  Several environment variables\nwill be set by :program:`supervisord` itself in the child's\nenvironment also, including :envvar:`SUPERVISOR_ENABLED` (a flag\nindicating the process is under supervisor control),\n:envvar:`SUPERVISOR_PROCESS_NAME` (the config-file-specified process\nname for this process) and :envvar:`SUPERVISOR_GROUP_NAME` (the\nconfig-file-specified process group name for the child process).\n\nThese environment variables may be overridden within the\n``[supervisord]`` section config option named ``environment`` (applies\nto all subprocesses) or within the per- ``[program:x]`` section\n``environment`` config option (applies only to the subprocess\nspecified within the ``[program:x]`` section).  These \"environment\"\nsettings are additive.  In other words, each subprocess' environment\nwill consist of:\n\n  The environment variables set within the shell used to start\n  supervisord...\n\n  ... added-to/overridden-by ...\n\n  ... the environment variables set within the \"environment\" global\n      config option ...\n\n   ... added-to/overridden-by ...\n\n   ... supervisor-specific environment variables\n       (:envvar:`SUPERVISOR_ENABLED`,\n       :envvar:`SUPERVISOR_PROCESS_NAME`,\n       :envvar:`SUPERVISOR_GROUP_NAME`) ..\n\n   ... added-to/overridden-by ...\n\n   ... the environment variables set within the per-process\n       \"environment\" config option.\n\nNo shell is executed by :program:`supervisord` when it runs a\nsubprocess, so environment variables such as :envvar:`USER`,\n:envvar:`PATH`, :envvar:`HOME`, :envvar:`SHELL`, :envvar:`LOGNAME`,\netc. are not changed from their defaults or otherwise reassigned.\nThis is particularly important to note when you are running a program\nfrom a :program:`supervisord` run as root with a ``user=`` stanza in\nthe configuration.  Unlike :program:`cron`, :program:`supervisord`\ndoes not attempt to divine and override \"fundamental\" environment\nvariables like :envvar:`USER`, :envvar:`PATH`, :envvar:`HOME`, and\n:envvar:`LOGNAME` when it performs a setuid to the user defined within\nthe ``user=`` program config option.  If you need to set environment\nvariables for a particular program that might otherwise be set by a\nshell invocation for a particular user, you must do it explicitly\nwithin the ``environment=`` program config option.  An\nexample of setting these environment variables is as below.\n\n.. code-block:: ini\n\n   [program:apache2]\n   command=/home/chrism/bin/httpd -c \"ErrorLog /dev/stdout\" -DFOREGROUND\n   user=chrism\n   environment=HOME=\"/home/chrism\",USER=\"chrism\"\n\n.. _process_states:\n\nProcess States\n--------------\n\nA process controlled by supervisord will be in one of the below states\nat any given time.  You may see these state names in various user\ninterface elements in clients.\n\n``STOPPED`` (0)\n\n  The process has been stopped due to a stop request or\n  has never been started.\n\n``STARTING`` (10)\n\n  The process is starting due to a start request.\n\n``RUNNING`` (20)\n\n  The process is running.\n\n``BACKOFF`` (30)\n\n  The process entered the ``STARTING`` state but subsequently exited\n  too quickly (before the time defined in ``startsecs``) to move to\n  the ``RUNNING`` state.\n\n``STOPPING`` (40)\n\n  The process is stopping due to a stop request.\n\n``EXITED`` (100)\n\n  The process exited from the ``RUNNING`` state (expectedly or\n  unexpectedly).\n\n``FATAL`` (200)\n\n  The process could not be started successfully.\n\n``UNKNOWN`` (1000)\n\n  The process is in an unknown state (:program:`supervisord`\n  programming error).\n\nEach process run under supervisor progresses through these states as\nper the following directed graph.\n\n.. figure:: subprocess-transitions.png\n   :alt: Subprocess State Transition Graph\n\n   Subprocess State Transition Graph\n\nA process is in the ``STOPPED`` state if it has been stopped\nadministratively or if it has never been started.\n\nWhen an autorestarting process is in the ``BACKOFF`` state, it will be\nautomatically restarted by :program:`supervisord`.  It will switch\nbetween ``STARTING`` and ``BACKOFF`` states until it becomes evident\nthat it cannot be started because the number of ``startretries`` has\nexceeded the maximum, at which point it will transition to the\n``FATAL`` state.\n\n.. note::\n    Retries will take increasingly more time depending on the number of\n    subsequent attempts made, adding one second each time.\n\n    So if you set ``startretries=3``, :program:`supervisord` will wait one,\n    two and then three seconds between each restart attempt, for a total of\n    6 seconds.\n\nWhen a process is in the ``EXITED`` state, it will\nautomatically restart:\n\n- never if its ``autorestart`` parameter is set to ``false``.\n\n- unconditionally if its ``autorestart`` parameter is set to ``true``.\n\n- conditionally if its ``autorestart`` parameter is set to\n  ``unexpected``.  If it exited with an exit code that doesn't match\n  one of the exit codes defined in the ``exitcodes`` configuration\n  parameter for the process, it will be restarted.\n\nA process automatically transitions from ``EXITED`` to ``RUNNING`` as\na result of being configured to autorestart conditionally or\nunconditionally.  The number of transitions between ``RUNNING`` and\n``EXITED`` is not limited in any way: it is possible to create a\nconfiguration that endlessly restarts an exited process.  This is a\nfeature, not a bug.\n\nAn autorestarted process will never be automatically restarted if it\nends up in the ``FATAL`` state (it must be manually restarted from\nthis state).\n\nA process transitions into the ``STOPPING`` state via an\nadministrative stop request, and will then end up in the\n``STOPPED`` state.\n\nA process that cannot be stopped successfully will stay in the\n``STOPPING`` state forever.  This situation should never be reached\nduring normal operations as it implies that the process did not\nrespond to a final ``SIGKILL`` signal sent to it by supervisor, which\nis \"impossible\" under UNIX.\n\nState transitions which always require user action to invoke are\nthese:\n\n``FATAL``   -> ``STARTING``\n\n``RUNNING`` -> ``STOPPING``\n\nState transitions which typically, but not always, require user\naction to invoke are these, with exceptions noted:\n\n``STOPPED`` -> ``STARTING`` (except at supervisord startup if process\nis configured to autostart)\n\n``EXITED`` -> ``STARTING`` (except if process is configured to\nautorestart)\n\nAll other state transitions are managed by supervisord automatically.\n"
  },
  {
    "path": "docs/upgrading.rst",
    "content": "Upgrading Supervisor 2 to 3\n===========================\n\nThe following is true when upgrading an installation from Supervisor\n2.X to Supervisor 3.X:\n\n#.  In ``[program:x]`` sections, the keys ``logfile``,\n    ``logfile_backups``, ``logfile_maxbytes``, ``log_stderr`` and\n    ``log_stdout`` are no longer valid.  Supervisor2 logged both\n    stderr and stdout to a single log file.  Supervisor 3 logs stderr\n    and stdout to separate log files.  You'll need to rename\n    ``logfile`` to ``stdout_logfile``, ``logfile_backups`` to\n    ``stdout_logfile_backups``, and ``logfile_maxbytes`` to\n    ``stdout_logfile_maxbytes`` at the very least to preserve your\n    configuration.  If you created program sections where\n    ``log_stderr`` was true, to preserve the behavior of sending\n    stderr output to the stdout log, use the ``redirect_stderr``\n    boolean in the program section instead.\n\n#.  The supervisor configuration file must include the following\n    section verbatim for the XML-RPC interface (and thus the web\n    interface and :program:`supervisorctl`) to work properly:\n\n    .. code-block:: ini\n\n       [rpcinterface:supervisor]\n       supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface\n\n#.  The semantics of the ``autorestart`` parameter within\n    ``[program:x]`` sections has changed.  This parameter used to\n    accept only ``true`` or ``false``.  It now accepts an additional\n    value, ``unexpected``, which indicates that the process should\n    restart from the ``EXITED`` state only if its exit code does not\n    match any of those represented by the ``exitcode`` parameter in\n    the process' configuration (implying a process crash).  In\n    addition, the default for ``autorestart`` is now ``unexpected``\n    (it used to be ``true``, which meant restart unconditionally).\n\n#.  We now allow :program:`supervisord` to listen on both a UNIX\n    domain socket and an inet socket instead of making listening on\n    one mutually exclusive with listening on the other.  As a result,\n    the options ``http_port``, ``http_username``, ``http_password``,\n    ``sockchmod`` and ``sockchown`` are no longer part of\n    the ``[supervisord]`` section configuration. These have been\n    supplanted by two other sections: ``[unix_http_server]`` and\n    ``[inet_http_server]``.  You'll need to insert one or the other\n    (depending on whether you want to listen on a UNIX domain socket\n    or a TCP socket respectively) or both into your\n    :file:`supervisord.conf` file.  These sections have their own\n    options (where applicable) for ``port``, ``username``,\n    ``password``, ``chmod``, and ``chown``.\n\n#.  All supervisord command-line options related to ``http_port``,\n    ``http_username``, ``http_password``, ``sockchmod`` and\n    ``sockchown`` have been removed (see above point for rationale).\n\n#. The option that used to be ``sockchown`` within the\n   ``[supervisord]`` section (and is now named ``chown`` within the\n   ``[unix_http_server]`` section) used to accept a dot-separated\n   (``user.group``) value.  The separator now must be a\n   colon, e.g. ``user:group``.  Unices allow for dots in\n   usernames, so this change is a bugfix.\n"
  },
  {
    "path": "docs/xmlrpc.rst",
    "content": "Extending Supervisor's XML-RPC API\n==================================\n\nSupervisor can be extended with new XML-RPC APIs.  Several third-party\nplugins already exist that can be wired into your Supervisor\nconfiguration.  You may additionally write your own.  Extensible\nXML-RPC interfaces is an advanced feature, introduced in version 3.0.\nYou needn't understand it unless you wish to use an existing\nthird-party RPC interface plugin or if you wish to write your own RPC\ninterface plugin.\n\n.. _rpcinterface_factories:\n\nConfiguring XML-RPC Interface Factories\n---------------------------------------\n\nAn additional RPC interface is configured into a supervisor\ninstallation by adding a ``[rpcinterface:x]`` section in the\nSupervisor configuration file.\n\nIn the sample config file, there is a section which is named\n``[rpcinterface:supervisor]``.  By default it looks like this:\n\n.. code-block:: ini\n    \n   [rpcinterface:supervisor]\n   supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface\n\nThis section *must* remain in the configuration for the standard setup\nof supervisor to work properly.  If you don't want supervisor to do\nanything it doesn't already do out of the box, this is all you need to\nknow about this type of section.\n\nHowever, if you wish to add additional XML-RPC interface namespaces to\na configuration of supervisor, you may add additional\n``[rpcinterface:foo]`` sections, where \"foo\" represents the namespace\nof the interface (from the web root), and the value named by\n``supervisor.rpcinterface_factory`` is a factory callable written in\nPython which should have a function signature that accepts a single\npositional argument ``supervisord`` and as many keyword arguments as\nrequired to perform configuration.  Any key/value pairs defined within\nthe ``rpcinterface:foo`` section will be passed as keyword arguments\nto the factory.  Here's an example of a factory function, created in\nthe package ``my.package``.\n\n.. code-block:: python\n\n   def make_another_rpcinterface(supervisord, **config):\n       retries = int(config.get('retries', 0))\n       another_rpc_interface = AnotherRPCInterface(supervisord, retries)\n       return another_rpc_interface\n\nAnd a section in the config file meant to configure it.\n\n.. code-block:: ini\n\n   [rpcinterface:another]\n   supervisor.rpcinterface_factory = my.package:make_another_rpcinterface\n   retries = 1\n\n"
  },
  {
    "path": "setup.cfg",
    "content": "[easy_install]\nzip_ok = false\n\n;Marking a wheel as universal with \"universal = 1\" was deprecated\n;in Setuptools 75.1.0.  Setting \"python_tag = py2.py3\" should do\n;the equivalent on Setuptools 30.3.0 or later.\n;\n;https://github.com/pypa/setuptools/pull/4617\n;https://github.com/pypa/setuptools/pull/4939\n;\n[bdist_wheel]\npython_tag = py2.py3\n"
  },
  {
    "path": "setup.py",
    "content": "##############################################################################\n#\n# Copyright (c) 2006-2015 Agendaless Consulting and Contributors.\n# All Rights Reserved.\n#\n# This software is subject to the provisions of the BSD-like license at\n# http://www.repoze.org/LICENSE.txt.  A copy of the license should accompany\n# this distribution.  THIS SOFTWARE IS PROVIDED \"AS IS\" AND ANY AND ALL\n# EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO,\n# THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND\n# FITNESS FOR A PARTICULAR PURPOSE\n#\n##############################################################################\n\nimport os\nimport sys\n\npy_version = sys.version_info[:2]\n\nif py_version < (2, 7):\n    raise RuntimeError('On Python 2, Supervisor requires Python 2.7 or later')\nelif (3, 0) < py_version < (3, 4):\n    raise RuntimeError('On Python 3, Supervisor requires Python 3.4 or later')\n\n# setuptools is required as a runtime dependency only on Python < 3.8.\n# See the comments in supervisor/compat.py.  An environment marker\n# like \"setuptools; python_version < '3.8'\" is not used here because\n# it breaks installation via \"python setup.py install\".  See also the\n# discussion at: https://github.com/Supervisor/supervisor/issues/1692\nif py_version < (3, 8):\n    try:\n        import pkg_resources\n    except ImportError:\n        raise RuntimeError(\n            \"On Python < 3.8, Supervisor requires setuptools as a runtime\"\n            \" dependency because pkg_resources is used to load plugins\"\n            )\n\nfrom setuptools import setup, find_packages\nhere = os.path.abspath(os.path.dirname(__file__))\ntry:\n    with open(os.path.join(here, 'README.rst'), 'r') as f:\n        README = f.read()\n    with open(os.path.join(here, 'CHANGES.rst'), 'r') as f:\n        CHANGES = f.read()\nexcept Exception:\n    README = \"\"\"\\\nSupervisor is a client/server system that allows its users to\ncontrol a number of processes on UNIX-like operating systems. \"\"\"\n    CHANGES = ''\n\nCLASSIFIERS = [\n    'Development Status :: 5 - Production/Stable',\n    'Environment :: No Input/Output (Daemon)',\n    'Intended Audience :: System Administrators',\n    'Natural Language :: English',\n    'Operating System :: POSIX',\n    'Topic :: System :: Boot',\n    'Topic :: System :: Monitoring',\n    'Topic :: System :: Systems Administration',\n    \"Programming Language :: Python\",\n    \"Programming Language :: Python :: 2\",\n    \"Programming Language :: Python :: 2.7\",\n    \"Programming Language :: Python :: 3\",\n    \"Programming Language :: Python :: 3.4\",\n    \"Programming Language :: Python :: 3.5\",\n    \"Programming Language :: Python :: 3.6\",\n    \"Programming Language :: Python :: 3.7\",\n    \"Programming Language :: Python :: 3.8\",\n    \"Programming Language :: Python :: 3.9\",\n    \"Programming Language :: Python :: 3.10\",\n    \"Programming Language :: Python :: 3.11\",\n    \"Programming Language :: Python :: 3.12\",\n    \"Programming Language :: Python :: 3.13\",\n    \"Programming Language :: Python :: 3.14\",\n]\n\nversion_txt = os.path.join(here, 'supervisor/version.txt')\nwith open(version_txt, 'r') as f:\n    supervisor_version = f.read().strip()\n\ndist = setup(\n    name='supervisor',\n    version=supervisor_version,\n    license='BSD-derived (http://www.repoze.org/LICENSE.txt)',\n    url='http://supervisord.org/',\n    project_urls={\n        'Changelog': 'http://supervisord.org/changelog',\n        'Documentation': 'http://supervisord.org',\n        'Issue Tracker': 'https://github.com/Supervisor/supervisor',\n    },\n    description=\"A system for controlling process state under UNIX\",\n    long_description=README + '\\n\\n' + CHANGES,\n    classifiers=CLASSIFIERS,\n    author=\"Chris McDonough\",\n    author_email=\"chrism@plope.com\",\n    packages=find_packages(),\n    install_requires=[],\n    extras_require={\n        'test': ['pytest', 'pytest-cov']\n    },\n    include_package_data=True,\n    zip_safe=False,\n    entry_points={\n        'console_scripts': [\n            'supervisord = supervisor.supervisord:main',\n            'supervisorctl = supervisor.supervisorctl:main',\n            'echo_supervisord_conf = supervisor.confecho:main',\n            'pidproxy = supervisor.pidproxy:main',\n        ],\n    },\n)\n"
  },
  {
    "path": "supervisor/__init__.py",
    "content": "# this is a package\n"
  },
  {
    "path": "supervisor/childutils.py",
    "content": "import sys\nimport time\n\nfrom supervisor.compat import xmlrpclib\nfrom supervisor.compat import long\nfrom supervisor.compat import as_string\n\nfrom supervisor.xmlrpc import SupervisorTransport\nfrom supervisor.events import ProcessCommunicationEvent\nfrom supervisor.dispatchers import PEventListenerDispatcher\n\ndef getRPCTransport(env):\n    u = env.get('SUPERVISOR_USERNAME', '')\n    p = env.get('SUPERVISOR_PASSWORD', '')\n    return SupervisorTransport(u, p, env['SUPERVISOR_SERVER_URL'])\n\ndef getRPCInterface(env):\n    # dumbass ServerProxy won't allow us to pass in a non-HTTP url,\n    # so we fake the url we pass into it and always use the transport's\n    # 'serverurl' to figure out what to attach to\n    return xmlrpclib.ServerProxy('http://127.0.0.1', getRPCTransport(env))\n\ndef get_headers(line):\n    return dict([ x.split(':') for x in line.split() ])\n\ndef eventdata(payload):\n    headerinfo, data = payload.split('\\n', 1)\n    headers = get_headers(headerinfo)\n    return headers, data\n\ndef get_asctime(now=None):\n    if now is None: # for testing\n        now = time.time() # pragma: no cover\n    msecs = (now - long(now)) * 1000\n    part1 = time.strftime(\"%Y-%m-%d %H:%M:%S\", time.localtime(now))\n    asctime = '%s,%03d' % (part1, msecs)\n    return asctime\n\nclass ProcessCommunicationsProtocol:\n    def send(self, msg, fp=sys.stdout):\n        fp.write(ProcessCommunicationEvent.BEGIN_TOKEN)\n        fp.write(msg)\n        fp.write(ProcessCommunicationEvent.END_TOKEN)\n        fp.flush()\n\n    def stdout(self, msg):\n        return self.send(msg, sys.stdout)\n\n    def stderr(self, msg):\n        return self.send(msg, sys.stderr)\n\npcomm = ProcessCommunicationsProtocol()\n\nclass EventListenerProtocol:\n    def wait(self, stdin=sys.stdin, stdout=sys.stdout):\n        self.ready(stdout)\n        line = stdin.readline()\n        headers = get_headers(line)\n        payload = stdin.read(int(headers['len']))\n        return headers, payload\n\n    def ready(self, stdout=sys.stdout):\n        stdout.write(as_string(PEventListenerDispatcher.READY_FOR_EVENTS_TOKEN))\n        stdout.flush()\n\n    def ok(self, stdout=sys.stdout):\n        self.send('OK', stdout)\n\n    def fail(self, stdout=sys.stdout):\n        self.send('FAIL', stdout)\n\n    def send(self, data, stdout=sys.stdout):\n        resultlen = len(data)\n        result = '%s%s\\n%s' % (as_string(PEventListenerDispatcher.RESULT_TOKEN_START),\n                               str(resultlen),\n                               data)\n        stdout.write(result)\n        stdout.flush()\n\nlistener = EventListenerProtocol()\n"
  },
  {
    "path": "supervisor/compat.py",
    "content": "from __future__ import absolute_import\n\nimport sys\n\nPY2 = sys.version_info[0] == 2\n\nif PY2: # pragma: no cover\n    long = long\n    raw_input = raw_input\n    unicode = unicode\n    unichr = unichr\n    basestring = basestring\n\n    def as_bytes(s, encoding='utf-8'):\n        if isinstance(s, str):\n            return s\n        else:\n            return s.encode(encoding)\n\n    def as_string(s, encoding='utf-8'):\n        if isinstance(s, unicode):\n            return s\n        else:\n            return s.decode(encoding)\n\n    def is_text_stream(stream):\n        try:\n            if isinstance(stream, file):\n                return 'b' not in stream.mode\n        except NameError:  # python 3\n            pass\n\n        try:\n            import _io\n            return isinstance(stream, _io._TextIOBase)\n        except ImportError:\n            import io\n            return isinstance(stream, io.TextIOWrapper)\n\nelse: # pragma: no cover\n    long = int\n    basestring = str\n    raw_input = input\n    unichr = chr\n\n    class unicode(str):\n        def __init__(self, string, encoding, errors):\n            str.__init__(self, string)\n\n    def as_bytes(s, encoding='utf8'):\n        if isinstance(s, bytes):\n            return s\n        else:\n            return s.encode(encoding)\n\n    def as_string(s, encoding='utf8'):\n        if isinstance(s, str):\n            return s\n        else:\n            return s.decode(encoding)\n\n    def is_text_stream(stream):\n        import _io\n        return isinstance(stream, _io._TextIOBase)\n\ntry: # pragma: no cover\n    import xmlrpc.client as xmlrpclib\nexcept ImportError: # pragma: no cover\n    import xmlrpclib\n\ntry: # pragma: no cover\n    import urllib.parse as urlparse\n    import urllib.parse as urllib\nexcept ImportError: # pragma: no cover\n    import urlparse\n    import urllib\n\ntry: # pragma: no cover\n    from hashlib import sha1\nexcept ImportError: # pragma: no cover\n    from sha import new as sha1\n\ntry: # pragma: no cover\n    import syslog\nexcept ImportError: # pragma: no cover\n    syslog = None\n\ntry: # pragma: no cover\n    import ConfigParser\nexcept ImportError: # pragma: no cover\n    import configparser as ConfigParser\n\ntry: # pragma: no cover\n    from StringIO import StringIO\nexcept ImportError: # pragma: no cover\n    from io import StringIO\n\ntry: # pragma: no cover\n    from sys import maxint\nexcept ImportError: # pragma: no cover\n    from sys import maxsize as maxint\n\ntry: # pragma: no cover\n    import http.client as httplib\nexcept ImportError: # pragma: no cover\n    import httplib\n\ntry: # pragma: no cover\n    from base64 import decodebytes as decodestring, encodebytes as encodestring\nexcept ImportError: # pragma: no cover\n    from base64 import decodestring, encodestring\n\ntry: # pragma: no cover\n    from xmlrpc.client import Fault\nexcept ImportError: # pragma: no cover\n    from xmlrpclib import Fault\n\ntry: # pragma: no cover\n    from string import ascii_letters as letters\nexcept ImportError: # pragma: no cover\n    from string import letters\n\ntry: # pragma: no cover\n    from hashlib import md5\nexcept ImportError: # pragma: no cover\n    from md5 import md5\n\ntry: # pragma: no cover\n    import thread\nexcept ImportError: # pragma: no cover\n    import _thread as thread\n\ntry: # pragma: no cover\n    from types import StringTypes\nexcept ImportError: # pragma: no cover\n    StringTypes = (str,)\n\ntry: # pragma: no cover\n    from html import escape\nexcept ImportError: # pragma: no cover\n    from cgi import escape\n\ntry: # pragma: no cover\n    import html.entities as htmlentitydefs\nexcept ImportError: # pragma: no cover\n    import htmlentitydefs\n\ntry: # pragma: no cover\n    from html.parser import HTMLParser\nexcept ImportError: # pragma: no cover\n    from HTMLParser import HTMLParser\n\n# Begin check for working shlex posix mode\n\n# https://github.com/Supervisor/supervisor/issues/328\n# https://github.com/Supervisor/supervisor/issues/873\n# https://bugs.python.org/issue21999\n\nfrom shlex import shlex as _shlex\n\n_shlex_posix_expectations = {\n    'foo=\"\",bar=a': ['foo', '=', '', ',', 'bar', '=', 'a'],\n    \"'')abc\":       ['', ')', 'abc']\n}\n\nshlex_posix_works = all(\n    list(_shlex(_input, posix=True)) == _expected\n    for _input, _expected in _shlex_posix_expectations.items()\n)\n\n# End check for working shlex posix mode\n\n# Begin importlib/setuptools compatibility code\n\n# Supervisor used pkg_resources (a part of setuptools) to load package\n# resources for 15 years, until setuptools 67.5.0 (2023-03-05) deprecated\n# the use of pkg_resources.  On Python 3.8 or later, Supervisor now uses\n# importlib (part of Python 3 stdlib).  Unfortunately, on Python < 3.8,\n# Supervisor needs to use pkg_resources despite its deprecation.  The PyPI\n# backport packages \"importlib-resources\" and \"importlib-metadata\" couldn't\n# be added as dependencies to Supervisor because they require even more\n# dependencies that would likely cause some Supervisor installs to fail.\nfrom warnings import filterwarnings as _fw\n_fw(\"ignore\", message=\"pkg_resources is deprecated as an API\")\n\ntry: # pragma: no cover\n    from importlib.metadata import EntryPoint as _EntryPoint\n\n    def import_spec(spec):\n        return _EntryPoint(None, spec, None).load()\n\nexcept ImportError: # pragma: no cover\n    from pkg_resources import EntryPoint as _EntryPoint\n\n    def import_spec(spec):\n        ep = _EntryPoint.parse(\"x=\" + spec)\n        if hasattr(ep, 'resolve'):\n            # this is available on setuptools >= 10.2\n            return ep.resolve()\n        else:\n            # this causes a DeprecationWarning on setuptools >= 11.3\n            return ep.load(False)\n\ntry: # pragma: no cover\n    import importlib.resources as _importlib_resources\n\n    if hasattr(_importlib_resources, \"files\"):\n        def resource_filename(package, path):\n            return str(_importlib_resources.files(package).joinpath(path))\n\n    else:\n        # fall back to deprecated .path if .files is not available\n        def resource_filename(package, path):\n            with _importlib_resources.path(package, '__init__.py') as p:\n                return str(p.parent.joinpath(path))\n\nexcept ImportError: # pragma: no cover\n    from pkg_resources import resource_filename\n\n# End importlib/setuptools compatibility code\n"
  },
  {
    "path": "supervisor/confecho.py",
    "content": "import sys\nfrom supervisor.compat import as_string\nfrom supervisor.compat import resource_filename\n\n\ndef main(out=sys.stdout):\n    with open(resource_filename(__package__, 'skel/sample.conf'), 'r') as f:\n        out.write(as_string(f.read()))\n"
  },
  {
    "path": "supervisor/datatypes.py",
    "content": "import grp\nimport os\nimport pwd\nimport signal\nimport socket\nimport shlex\n\nfrom supervisor.compat import shlex_posix_works\nfrom supervisor.compat import urlparse\nfrom supervisor.compat import long\nfrom supervisor.loggers import getLevelNumByDescription\n\ndef process_or_group_name(name):\n    \"\"\"Ensures that a process or group name is not created with\n       characters that break the eventlistener protocol or web UI URLs\"\"\"\n    s = str(name).strip()\n    for character in ' :/':\n        if character in s:\n            raise ValueError(\"Invalid name: %r because of character: %r\" % (name, character))\n    return s\n\ndef integer(value):\n    try:\n        return int(value)\n    except (ValueError, OverflowError):\n        return long(value) # why does this help ValueError? (CM)\n\nTRUTHY_STRINGS = ('yes', 'true', 'on', '1')\nFALSY_STRINGS  = ('no', 'false', 'off', '0')\n\ndef boolean(s):\n    \"\"\"Convert a string value to a boolean value.\"\"\"\n    ss = str(s).lower()\n    if ss in TRUTHY_STRINGS:\n        return True\n    elif ss in FALSY_STRINGS:\n        return False\n    else:\n        raise ValueError(\"not a valid boolean value: \" + repr(s))\n\ndef list_of_strings(arg):\n    if not arg:\n        return []\n    try:\n        return [x.strip() for x in arg.split(',')]\n    except:\n        raise ValueError(\"not a valid list of strings: \" + repr(arg))\n\ndef list_of_ints(arg):\n    if not arg:\n        return []\n    else:\n        try:\n            return list(map(int, arg.split(\",\")))\n        except:\n            raise ValueError(\"not a valid list of ints: \" + repr(arg))\n\ndef list_of_exitcodes(arg):\n    try:\n        vals = list_of_ints(arg)\n        for val in vals:\n            if (val > 255) or (val < 0):\n                raise ValueError('Invalid exit code \"%s\"' % val)\n        return vals\n    except:\n        raise ValueError(\"not a valid list of exit codes: \" + repr(arg))\n\ndef dict_of_key_value_pairs(arg):\n    \"\"\" parse KEY=val,KEY2=val2 into {'KEY':'val', 'KEY2':'val2'}\n        Quotes can be used to allow commas in the value\n    \"\"\"\n    lexer = shlex.shlex(str(arg), posix=shlex_posix_works)\n    lexer.wordchars += '/.+-():'\n\n    tokens = list(lexer)\n    tokens_len = len(tokens)\n\n    D = {}\n    i = 0\n    while i < tokens_len:\n        k_eq_v = tokens[i:i+3]\n        if len(k_eq_v) != 3 or k_eq_v[1] != '=':\n            raise ValueError(\n                \"Unexpected end of key/value pairs in value '%s'\" % arg)\n\n        k, v = k_eq_v[0], k_eq_v[2]\n        if not shlex_posix_works:\n            v = v.strip('\\'\"')\n\n        D[k] = v\n        i += 4\n    return D\n\nclass Automatic:\n    pass\n\nclass Syslog:\n    \"\"\"TODO deprecated; remove this special 'syslog' filename in the future\"\"\"\n    pass\n\nLOGFILE_NONES = ('none', 'off', None)\nLOGFILE_AUTOS = (Automatic, 'auto')\nLOGFILE_SYSLOGS = (Syslog, 'syslog')\n\ndef logfile_name(val):\n    if hasattr(val, 'lower'):\n        coerced = val.lower()\n    else:\n        coerced = val\n\n    if coerced in LOGFILE_NONES:\n        return None\n    elif coerced in LOGFILE_AUTOS:\n        return Automatic\n    elif coerced in LOGFILE_SYSLOGS:\n        return Syslog\n    else:\n        return existing_dirpath(val)\n\nclass RangeCheckedConversion:\n    \"\"\"Conversion helper that range checks another conversion.\"\"\"\n\n    def __init__(self, conversion, min=None, max=None):\n        self._min = min\n        self._max = max\n        self._conversion = conversion\n\n    def __call__(self, value):\n        v = self._conversion(value)\n        if self._min is not None and v < self._min:\n            raise ValueError(\"%s is below lower bound (%s)\"\n                             % (repr(v), repr(self._min)))\n        if self._max is not None and v > self._max:\n            raise ValueError(\"%s is above upper bound (%s)\"\n                             % (repr(v), repr(self._max)))\n        return v\n\nport_number = RangeCheckedConversion(integer, min=1, max=0xffff).__call__\n\ndef inet_address(s):\n    # returns (host, port) tuple\n    host = ''\n    if \":\" in s:\n        host, s = s.rsplit(\":\", 1)\n        if not s:\n            raise ValueError(\"no port number specified in %r\" % s)\n        port = port_number(s)\n        host = host.lower()\n    else:\n        try:\n            port = port_number(s)\n        except ValueError:\n            raise ValueError(\"not a valid port number: %r \" %s)\n    if not host or host == '*':\n        host = ''\n    return host, port\n\nclass SocketAddress:\n    def __init__(self, s):\n        # returns (family, address) tuple\n        if \"/\" in s or s.find(os.sep) >= 0 or \":\" not in s:\n            self.family = getattr(socket, \"AF_UNIX\", None)\n            self.address = s\n        else:\n            self.family = socket.AF_INET\n            self.address = inet_address(s)\n\nclass SocketConfig:\n    \"\"\" Abstract base class which provides a uniform abstraction\n    for TCP vs Unix sockets \"\"\"\n    url = '' # socket url\n    addr = None #socket addr\n    backlog = None # socket listen backlog\n\n    def __repr__(self):\n        return '<%s at %s for %s>' % (self.__class__,\n                                      id(self),\n                                      self.url)\n\n    def __str__(self):\n        return str(self.url)\n\n    def __eq__(self, other):\n        if not isinstance(other, SocketConfig):\n            return False\n\n        if self.url != other.url:\n            return False\n\n        return True\n\n    def __ne__(self, other):\n        return not self.__eq__(other)\n\n    def get_backlog(self):\n        return self.backlog\n\n    def addr(self): # pragma: no cover\n        raise NotImplementedError\n\n    def create_and_bind(self): # pragma: no cover\n        raise NotImplementedError\n\nclass InetStreamSocketConfig(SocketConfig):\n    \"\"\" TCP socket config helper \"\"\"\n\n    host = None # host name or ip to bind to\n    port = None # integer port to bind to\n\n    def __init__(self, host, port, **kwargs):\n        self.host = host.lower()\n        self.port = port_number(port)\n        self.url = 'tcp://%s:%d' % (self.host, self.port)\n        self.backlog = kwargs.get('backlog', None)\n\n    def addr(self):\n        return self.host, self.port\n\n    def create_and_bind(self):\n        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n        try:\n            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n            sock.bind(self.addr())\n        except:\n            sock.close()\n            raise\n        return sock\n\nclass UnixStreamSocketConfig(SocketConfig):\n    \"\"\" Unix domain socket config helper \"\"\"\n\n    path = None # Unix domain socket path\n    mode = None # Unix permission mode bits for socket\n    owner = None # Tuple (uid, gid) for Unix ownership of socket\n    sock = None # socket object\n\n    def __init__(self, path, **kwargs):\n        self.path = path\n        self.url = 'unix://%s' % path\n        self.mode = kwargs.get('mode', None)\n        self.owner = kwargs.get('owner', None)\n        self.backlog = kwargs.get('backlog', None)\n\n    def addr(self):\n        return self.path\n\n    def create_and_bind(self):\n        if os.path.exists(self.path):\n            os.unlink(self.path)\n        sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\n        try:\n            sock.bind(self.addr())\n            self._chown()\n            self._chmod()\n        except:\n            sock.close()\n            if os.path.exists(self.path):\n                os.unlink(self.path)\n            raise\n        return sock\n\n    def get_mode(self):\n        return self.mode\n\n    def get_owner(self):\n        return self.owner\n\n    def _chmod(self):\n        if self.mode is not None:\n            try:\n                os.chmod(self.path, self.mode)\n            except Exception as e:\n                raise ValueError(\"Could not change permissions of socket \"\n                                    + \"file: %s\" % e)\n\n    def _chown(self):\n        if self.owner is not None:\n            try:\n                os.chown(self.path, self.owner[0], self.owner[1])\n            except Exception as e:\n                raise ValueError(\"Could not change ownership of socket file: \"\n                                    + \"%s\" % e)\n\ndef colon_separated_user_group(arg):\n    \"\"\" Find a user ID and group ID from a string like 'user:group'.  Returns\n        a tuple (uid, gid).  If the string only contains a user like 'user'\n        then (uid, -1) will be returned.  Raises ValueError if either\n        the user or group can't be resolved to valid IDs on the system. \"\"\"\n    try:\n        parts = arg.split(':', 1)\n        if len(parts) == 1:\n            uid = name_to_uid(parts[0])\n            gid = -1\n        else:\n            uid = name_to_uid(parts[0])\n            gid = name_to_gid(parts[1])\n        return (uid, gid)\n    except:\n        raise ValueError('Invalid user:group definition %s' % arg)\n\ndef name_to_uid(name):\n    \"\"\" Find a user ID from a string containing a user name or ID.\n        Raises ValueError if the string can't be resolved to a valid\n        user ID on the system. \"\"\"\n    try:\n        uid = int(name)\n    except ValueError:\n        try:\n            pwdrec = pwd.getpwnam(name)\n        except KeyError:\n            raise ValueError(\"Invalid user name %s\" % name)\n        uid = pwdrec[2]\n    else:\n        try:\n            pwd.getpwuid(uid) # check if uid is valid\n        except KeyError:\n            raise ValueError(\"Invalid user id %s\" % name)\n    return uid\n\ndef name_to_gid(name):\n    \"\"\" Find a group ID from a string containing a group name or ID.\n        Raises ValueError if the string can't be resolved to a valid\n        group ID on the system. \"\"\"\n    try:\n        gid = int(name)\n    except ValueError:\n        try:\n            grprec = grp.getgrnam(name)\n        except KeyError:\n            raise ValueError(\"Invalid group name %s\" % name)\n        gid = grprec[2]\n    else:\n        try:\n            grp.getgrgid(gid) # check if gid is valid\n        except KeyError:\n            raise ValueError(\"Invalid group id %s\" % name)\n    return gid\n\ndef gid_for_uid(uid):\n    pwrec = pwd.getpwuid(uid)\n    return pwrec[3]\n\ndef octal_type(arg):\n    try:\n        return int(arg, 8)\n    except (TypeError, ValueError):\n        raise ValueError('%s can not be converted to an octal type' % arg)\n\ndef existing_directory(v):\n    nv = os.path.expanduser(v)\n    if os.path.isdir(nv):\n        return nv\n    raise ValueError('%s is not an existing directory' % v)\n\ndef existing_dirpath(v):\n    nv = os.path.expanduser(v)\n    dir = os.path.dirname(nv)\n    if not dir:\n        # relative pathname with no directory component\n        return nv\n    if os.path.isdir(dir):\n        return nv\n    raise ValueError('The directory named as part of the path %s '\n                     'does not exist' % v)\n\ndef logging_level(value):\n    s = str(value).lower()\n    level = getLevelNumByDescription(s)\n    if level is None:\n        raise ValueError('bad logging level name %r' % value)\n    return level\n\nclass SuffixMultiplier:\n    # d is a dictionary of suffixes to integer multipliers.  If no suffixes\n    # match, default is the multiplier.  Matches are case insensitive.  Return\n    # values are in the fundamental unit.\n    def __init__(self, d, default=1):\n        self._d = d\n        self._default = default\n        # all keys must be the same size\n        self._keysz = None\n        for k in d.keys():\n            if self._keysz is None:\n                self._keysz = len(k)\n            else:\n                assert self._keysz == len(k)\n\n    def __call__(self, v):\n        v = v.lower()\n        for s, m in self._d.items():\n            if v[-self._keysz:] == s:\n                return int(v[:-self._keysz]) * m\n        return int(v) * self._default\n\nbyte_size = SuffixMultiplier({'kb': 1024,\n                              'mb': 1024*1024,\n                              'gb': 1024*1024*long(1024),})\n\ndef url(value):\n    scheme, netloc, path, params, query, fragment = urlparse.urlparse(value)\n    if scheme and (netloc or path):\n        return value\n    raise ValueError(\"value %r is not a URL\" % value)\n\n# all valid signal numbers\nSIGNUMS = [ getattr(signal, k) for k in dir(signal) if k.startswith('SIG') ]\n\ndef signal_number(value):\n    try:\n        num = int(value)\n    except (ValueError, TypeError):\n        name = value.strip().upper()\n        if not name.startswith('SIG'):\n            name = 'SIG' + name\n        num = getattr(signal, name, None)\n        if num is None:\n            raise ValueError('value %r is not a valid signal name' % value)\n    if num not in SIGNUMS:\n        raise ValueError('value %r is not a valid signal number' % value)\n    return num\n\nclass RestartWhenExitUnexpected:\n    pass\n\nclass RestartUnconditionally:\n    pass\n\ndef auto_restart(value):\n    value = str(value.lower())\n    computed_value  = value\n    if value in TRUTHY_STRINGS:\n        computed_value = RestartUnconditionally\n    elif value in FALSY_STRINGS:\n        computed_value = False\n    elif value == 'unexpected':\n        computed_value = RestartWhenExitUnexpected\n    if computed_value not in (RestartWhenExitUnexpected,\n                              RestartUnconditionally, False):\n        raise ValueError(\"invalid 'autorestart' value %r\" % value)\n    return computed_value\n\ndef profile_options(value):\n    options = [x.lower() for x in list_of_strings(value) ]\n    sort_options = []\n    callers = False\n    for thing in options:\n        if thing != 'callers':\n            sort_options.append(thing)\n        else:\n            callers = True\n    return sort_options, callers\n"
  },
  {
    "path": "supervisor/dispatchers.py",
    "content": "import errno\nfrom supervisor.medusa.asynchat_25 import find_prefix_at_end\nfrom supervisor.medusa.asyncore_25 import compact_traceback\n\nfrom supervisor.compat import as_string\nfrom supervisor.events import notify\nfrom supervisor.events import EventRejectedEvent\nfrom supervisor.events import ProcessLogStderrEvent\nfrom supervisor.events import ProcessLogStdoutEvent\nfrom supervisor.states import EventListenerStates\nfrom supervisor.states import getEventListenerStateDescription\nfrom supervisor import loggers\n\nclass PDispatcher:\n    \"\"\" Asyncore dispatcher for mainloop, representing a process channel\n    (stdin, stdout, or stderr).  This class is abstract. \"\"\"\n\n    closed = False # True if close() has been called\n\n    def __init__(self, process, channel, fd):\n        self.process = process  # process which \"owns\" this dispatcher\n        self.channel = channel  # 'stderr' or 'stdout'\n        self.fd = fd\n        self.closed = False     # True if close() has been called\n\n    def __repr__(self):\n        return '<%s at %s for %s (%s)>' % (self.__class__.__name__,\n                                           id(self),\n                                           self.process,\n                                           self.channel)\n\n    def readable(self):\n        raise NotImplementedError\n\n    def writable(self):\n        raise NotImplementedError\n\n    def handle_read_event(self):\n        raise NotImplementedError\n\n    def handle_write_event(self):\n        raise NotImplementedError\n\n    def handle_error(self):\n        nil, t, v, tbinfo = compact_traceback()\n\n        self.process.config.options.logger.critical(\n            'uncaptured python exception, closing channel %s (%s:%s %s)' % (\n                repr(self),\n                t,\n                v,\n                tbinfo\n                )\n            )\n        self.close()\n\n    def close(self):\n        if not self.closed:\n            self.process.config.options.logger.debug(\n                'fd %s closed, stopped monitoring %s' % (self.fd, self))\n            self.closed = True\n\n    def flush(self):\n        pass\n\nclass POutputDispatcher(PDispatcher):\n    \"\"\"\n    Dispatcher for one channel (stdout or stderr) of one process.\n    Serves several purposes:\n\n    - capture output sent within <!--XSUPERVISOR:BEGIN--> and\n      <!--XSUPERVISOR:END--> tags and signal a ProcessCommunicationEvent\n      by calling notify(event).\n    - route the output to the appropriate log handlers as specified in the\n      config.\n    \"\"\"\n\n    childlog = None # the current logger (normallog or capturelog)\n    normallog = None # the \"normal\" (non-capture) logger\n    capturelog = None # the logger used while we're in capturemode\n    capturemode = False # are we capturing process event data\n    output_buffer = b'' # data waiting to be logged\n\n    def __init__(self, process, event_type, fd):\n        \"\"\"\n        Initialize the dispatcher.\n\n        `event_type` should be one of ProcessLogStdoutEvent or\n        ProcessLogStderrEvent\n        \"\"\"\n        self.process = process\n        self.event_type = event_type\n        self.fd = fd\n        self.channel = self.event_type.channel\n\n        self._init_normallog()\n        self._init_capturelog()\n\n        self.childlog = self.normallog\n\n        # all code below is purely for minor speedups\n        begintoken = self.event_type.BEGIN_TOKEN\n        endtoken = self.event_type.END_TOKEN\n        self.begintoken_data = (begintoken, len(begintoken))\n        self.endtoken_data = (endtoken, len(endtoken))\n        self.mainlog_level = loggers.LevelsByName.DEBG\n        config = self.process.config\n        self.log_to_mainlog = config.options.loglevel <= self.mainlog_level\n        self.stdout_events_enabled = config.stdout_events_enabled\n        self.stderr_events_enabled = config.stderr_events_enabled\n\n    def _init_normallog(self):\n        \"\"\"\n        Configure the \"normal\" (non-capture) log for this channel of this\n        process.  Sets self.normallog if logging is enabled.\n        \"\"\"\n        config = self.process.config\n        channel = self.channel\n\n        logfile = getattr(config, '%s_logfile' % channel)\n        maxbytes = getattr(config, '%s_logfile_maxbytes' % channel)\n        backups = getattr(config, '%s_logfile_backups' % channel)\n        to_syslog = getattr(config, '%s_syslog' % channel)\n\n        if logfile or to_syslog:\n            self.normallog = config.options.getLogger()\n\n        if logfile:\n            loggers.handle_file(\n                self.normallog,\n                filename=logfile,\n                fmt='%(message)s',\n                rotating=not not maxbytes, # optimization\n                maxbytes=maxbytes,\n                backups=backups\n            )\n\n        if to_syslog:\n            loggers.handle_syslog(\n                self.normallog,\n                fmt=config.name + ' %(message)s'\n            )\n\n    def _init_capturelog(self):\n        \"\"\"\n        Configure the capture log for this process.  This log is used to\n        temporarily capture output when special output is detected.\n        Sets self.capturelog if capturing is enabled.\n        \"\"\"\n        capture_maxbytes = getattr(self.process.config,\n                                   '%s_capture_maxbytes' % self.channel)\n        if capture_maxbytes:\n            self.capturelog = self.process.config.options.getLogger()\n            loggers.handle_boundIO(\n                self.capturelog,\n                fmt='%(message)s',\n                maxbytes=capture_maxbytes,\n                )\n\n    def removelogs(self):\n        for log in (self.normallog, self.capturelog):\n            if log is not None:\n                for handler in log.handlers:\n                    handler.remove()\n                    handler.reopen()\n\n    def reopenlogs(self):\n        for log in (self.normallog, self.capturelog):\n            if log is not None:\n                for handler in log.handlers:\n                    handler.reopen()\n\n    def _log(self, data):\n        if data:\n            config = self.process.config\n            if config.options.strip_ansi:\n                data = stripEscapes(data)\n            if self.childlog:\n                self.childlog.info(data)\n            if self.log_to_mainlog:\n                if not isinstance(data, bytes):\n                    text = data\n                else:\n                    try:\n                        text = data.decode('utf-8')\n                    except UnicodeDecodeError:\n                        text = 'Undecodable: %r' % data\n                msg = '%(name)r %(channel)s output:\\n%(data)s'\n                config.options.logger.log(\n                    self.mainlog_level, msg, name=config.name,\n                    channel=self.channel, data=text)\n            if self.channel == 'stdout':\n                if self.stdout_events_enabled:\n                    notify(\n                        ProcessLogStdoutEvent(self.process,\n                            self.process.pid, data)\n                    )\n            else: # channel == stderr\n                if self.stderr_events_enabled:\n                    notify(\n                        ProcessLogStderrEvent(self.process,\n                            self.process.pid, data)\n                    )\n\n    def record_output(self):\n        if self.capturelog is None:\n            # shortcut trying to find capture data\n            data = self.output_buffer\n            self.output_buffer = b''\n            self._log(data)\n            return\n\n        if self.capturemode:\n            token, tokenlen = self.endtoken_data\n        else:\n            token, tokenlen = self.begintoken_data\n\n        if len(self.output_buffer) <= tokenlen:\n            return # not enough data\n\n        data = self.output_buffer\n        self.output_buffer = b''\n\n        try:\n            before, after = data.split(token, 1)\n        except ValueError:\n            after = None\n            index = find_prefix_at_end(data, token)\n            if index:\n                self.output_buffer = self.output_buffer + data[-index:]\n                data = data[:-index]\n            self._log(data)\n        else:\n            self._log(before)\n            self.toggle_capturemode()\n            self.output_buffer = after\n\n        if after:\n            self.record_output()\n\n    def toggle_capturemode(self):\n        self.capturemode = not self.capturemode\n\n        if self.capturelog is not None:\n            if self.capturemode:\n                self.childlog = self.capturelog\n            else:\n                for handler in self.capturelog.handlers:\n                    handler.flush()\n                data = self.capturelog.getvalue()\n                channel = self.channel\n                procname = self.process.config.name\n                event = self.event_type(self.process, self.process.pid, data)\n                notify(event)\n\n                msg = \"%(procname)r %(channel)s emitted a comm event\"\n                self.process.config.options.logger.debug(msg,\n                                                         procname=procname,\n                                                         channel=channel)\n                for handler in self.capturelog.handlers:\n                    handler.remove()\n                    handler.reopen()\n                self.childlog = self.normallog\n\n    def writable(self):\n        return False\n\n    def readable(self):\n        if self.closed:\n            return False\n        return True\n\n    def handle_read_event(self):\n        data = self.process.config.options.readfd(self.fd)\n        self.output_buffer += data\n        self.record_output()\n        if not data:\n            # if we get no data back from the pipe, it means that the\n            # child process has ended.  See\n            # mail.python.org/pipermail/python-dev/2004-August/046850.html\n            self.close()\n\nclass PEventListenerDispatcher(PDispatcher):\n    \"\"\" An output dispatcher that monitors and changes a process'\n    listener_state \"\"\"\n    childlog = None # the logger\n    state_buffer = b''  # data waiting to be reviewed for state changes\n\n    READY_FOR_EVENTS_TOKEN = b'READY\\n'\n    RESULT_TOKEN_START = b'RESULT '\n    READY_FOR_EVENTS_LEN = len(READY_FOR_EVENTS_TOKEN)\n    RESULT_TOKEN_START_LEN = len(RESULT_TOKEN_START)\n\n    def __init__(self, process, channel, fd):\n        PDispatcher.__init__(self, process, channel, fd)\n        # the initial state of our listener is ACKNOWLEDGED; this is a\n        # \"busy\" state that implies we're awaiting a READY_FOR_EVENTS_TOKEN\n        self.process.listener_state = EventListenerStates.ACKNOWLEDGED\n        self.process.event = None\n        self.result = b''\n        self.resultlen = None\n\n        logfile = getattr(process.config, '%s_logfile' % channel)\n\n        if logfile:\n            maxbytes = getattr(process.config, '%s_logfile_maxbytes' % channel)\n            backups = getattr(process.config, '%s_logfile_backups' % channel)\n            self.childlog = process.config.options.getLogger()\n            loggers.handle_file(\n                self.childlog,\n                logfile,\n                '%(message)s',\n                rotating=not not maxbytes, # optimization\n                maxbytes=maxbytes,\n                backups=backups,\n            )\n\n    def removelogs(self):\n        if self.childlog is not None:\n            for handler in self.childlog.handlers:\n                handler.remove()\n                handler.reopen()\n\n    def reopenlogs(self):\n        if self.childlog is not None:\n            for handler in self.childlog.handlers:\n                handler.reopen()\n\n\n    def writable(self):\n        return False\n\n    def readable(self):\n        if self.closed:\n            return False\n        return True\n\n    def handle_read_event(self):\n        data = self.process.config.options.readfd(self.fd)\n        if data:\n            self.state_buffer += data\n            procname = self.process.config.name\n            msg = '%r %s output:\\n%s' % (procname, self.channel, data)\n            self.process.config.options.logger.debug(msg)\n\n            if self.childlog:\n                if self.process.config.options.strip_ansi:\n                    data = stripEscapes(data)\n                self.childlog.info(data)\n        else:\n            # if we get no data back from the pipe, it means that the\n            # child process has ended.  See\n            # mail.python.org/pipermail/python-dev/2004-August/046850.html\n            self.close()\n\n        self.handle_listener_state_change()\n\n    def handle_listener_state_change(self):\n        data = self.state_buffer\n\n        if not data:\n            return\n\n        process = self.process\n        procname = process.config.name\n        state = process.listener_state\n\n        if state == EventListenerStates.UNKNOWN:\n            # this is a fatal state\n            self.state_buffer = b''\n            return\n\n        if state == EventListenerStates.ACKNOWLEDGED:\n            if len(data) < self.READY_FOR_EVENTS_LEN:\n                # not enough info to make a decision\n                return\n            elif data.startswith(self.READY_FOR_EVENTS_TOKEN):\n                self._change_listener_state(EventListenerStates.READY)\n                tokenlen = self.READY_FOR_EVENTS_LEN\n                self.state_buffer = self.state_buffer[tokenlen:]\n                process.event = None\n            else:\n                self._change_listener_state(EventListenerStates.UNKNOWN)\n                self.state_buffer = b''\n                process.event = None\n            if self.state_buffer:\n                # keep going til its too short\n                self.handle_listener_state_change()\n            else:\n                return\n\n        elif state == EventListenerStates.READY:\n            # the process sent some spurious data, be strict about it\n            self._change_listener_state(EventListenerStates.UNKNOWN)\n            self.state_buffer = b''\n            process.event = None\n            return\n\n        elif state == EventListenerStates.BUSY:\n            if self.resultlen is None:\n                # we haven't begun gathering result data yet\n                pos = data.find(b'\\n')\n                if pos == -1:\n                    # we can't make a determination yet, we dont have a full\n                    # results line\n                    return\n\n                result_line = self.state_buffer[:pos]\n                self.state_buffer = self.state_buffer[pos+1:] # rid LF\n                resultlen = result_line[self.RESULT_TOKEN_START_LEN:]\n                try:\n                    self.resultlen = int(resultlen)\n                except ValueError:\n                    try:\n                        result_line = as_string(result_line)\n                    except UnicodeDecodeError:\n                        result_line = 'Undecodable: %r' % result_line\n                    process.config.options.logger.warn(\n                        '%s: bad result line: \\'%s\\'' % (procname, result_line)\n                        )\n                    self._change_listener_state(EventListenerStates.UNKNOWN)\n                    self.state_buffer = b''\n                    notify(EventRejectedEvent(process, process.event))\n                    process.event = None\n                    return\n\n            else:\n                needed = self.resultlen - len(self.result)\n\n                if needed:\n                    self.result += self.state_buffer[:needed]\n                    self.state_buffer = self.state_buffer[needed:]\n                    needed = self.resultlen - len(self.result)\n\n                if not needed:\n                    self.handle_result(self.result)\n                    self.process.event = None\n                    self.result = b''\n                    self.resultlen = None\n\n            if self.state_buffer:\n                # keep going til its too short\n                self.handle_listener_state_change()\n\n    def handle_result(self, result):\n        process = self.process\n        procname = process.config.name\n        logger = process.config.options.logger\n\n        try:\n            self.process.group.config.result_handler(process.event, result)\n            logger.debug('%s: event was processed' % procname)\n            self._change_listener_state(EventListenerStates.ACKNOWLEDGED)\n        except RejectEvent:\n            logger.warn('%s: event was rejected' % procname)\n            self._change_listener_state(EventListenerStates.ACKNOWLEDGED)\n            notify(EventRejectedEvent(process, process.event))\n        except:\n            logger.warn('%s: event caused an error' % procname)\n            self._change_listener_state(EventListenerStates.UNKNOWN)\n            notify(EventRejectedEvent(process, process.event))\n\n    def _change_listener_state(self, new_state):\n        process = self.process\n        procname = process.config.name\n        old_state = process.listener_state\n\n        msg = '%s: %s -> %s' % (\n            procname,\n            getEventListenerStateDescription(old_state),\n            getEventListenerStateDescription(new_state)\n            )\n        process.config.options.logger.debug(msg)\n\n        process.listener_state = new_state\n        if new_state == EventListenerStates.UNKNOWN:\n            msg = ('%s: has entered the UNKNOWN state and will no longer '\n                   'receive events, this usually indicates the process '\n                   'violated the eventlistener protocol' % procname)\n            process.config.options.logger.warn(msg)\n\nclass PInputDispatcher(PDispatcher):\n    \"\"\" Input (stdin) dispatcher \"\"\"\n\n    def __init__(self, process, channel, fd):\n        PDispatcher.__init__(self, process, channel, fd)\n        self.input_buffer = b''\n\n    def writable(self):\n        if self.input_buffer and not self.closed:\n            return True\n        return False\n\n    def readable(self):\n        return False\n\n    def flush(self):\n        # other code depends on this raising EPIPE if the pipe is closed\n        sent = self.process.config.options.write(self.fd,\n                                                 self.input_buffer)\n        self.input_buffer = self.input_buffer[sent:]\n\n    def handle_write_event(self):\n        if self.input_buffer:\n            try:\n                self.flush()\n            except OSError as why:\n                if why.args[0] == errno.EPIPE:\n                    self.input_buffer = b''\n                    self.close()\n                else:\n                    raise\n\nANSI_ESCAPE_BEGIN = b'\\x1b['\nANSI_TERMINATORS = (b'H', b'f', b'A', b'B', b'C', b'D', b'R', b's', b'u', b'J',\n                    b'K', b'h', b'l', b'p', b'm')\n\ndef stripEscapes(s):\n    \"\"\"\n    Remove all ANSI color escapes from the given string.\n    \"\"\"\n    result = b''\n    show = 1\n    i = 0\n    L = len(s)\n    while i < L:\n        if show == 0 and s[i:i + 1] in ANSI_TERMINATORS:\n            show = 1\n        elif show:\n            n = s.find(ANSI_ESCAPE_BEGIN, i)\n            if n == -1:\n                return result + s[i:]\n            else:\n                result = result + s[i:n]\n                i = n\n                show = 0\n        i += 1\n    return result\n\nclass RejectEvent(Exception):\n    \"\"\" The exception type expected by a dispatcher when a handler wants\n    to reject an event \"\"\"\n\ndef default_handler(event, response):\n    if response != b'OK':\n        raise RejectEvent(response)\n"
  },
  {
    "path": "supervisor/events.py",
    "content": "from supervisor.states import getProcessStateDescription\nfrom supervisor.compat import as_string\n\ncallbacks = []\n\ndef subscribe(type, callback):\n    callbacks.append((type, callback))\n\ndef unsubscribe(type, callback):\n    callbacks.remove((type, callback))\n\ndef notify(event):\n    for type, callback in callbacks:\n        if isinstance(event, type):\n            callback(event)\n\ndef clear():\n    callbacks[:] = []\n\nclass Event:\n    \"\"\" Abstract event type \"\"\"\n    pass\n\nclass ProcessLogEvent(Event):\n    \"\"\" Abstract \"\"\"\n    channel = None\n    def __init__(self, process, pid, data):\n        self.process = process\n        self.pid = pid\n        self.data = data\n\n    def payload(self):\n        groupname = ''\n        if self.process.group is not None:\n            groupname = self.process.group.config.name\n        try:\n            data = as_string(self.data)\n        except UnicodeDecodeError:\n            data = 'Undecodable: %r' % self.data\n        # On Python 2, stuff needs to be in Unicode before invoking the\n        # % operator, otherwise implicit encodings to ASCII can cause\n        # failures\n        fmt = as_string('processname:%s groupname:%s pid:%s channel:%s\\n%s')\n        result = fmt % (as_string(self.process.config.name),\n                        as_string(groupname), self.pid,\n                        as_string(self.channel), data)\n        return result\n\nclass ProcessLogStdoutEvent(ProcessLogEvent):\n    channel = 'stdout'\n\nclass ProcessLogStderrEvent(ProcessLogEvent):\n    channel = 'stderr'\n\nclass ProcessCommunicationEvent(Event):\n    \"\"\" Abstract \"\"\"\n    # event mode tokens\n    BEGIN_TOKEN = b'<!--XSUPERVISOR:BEGIN-->'\n    END_TOKEN   = b'<!--XSUPERVISOR:END-->'\n\n    def __init__(self, process, pid, data):\n        self.process = process\n        self.pid = pid\n        self.data = data\n\n    def payload(self):\n        groupname = ''\n        if self.process.group is not None:\n            groupname = self.process.group.config.name\n        try:\n            data = as_string(self.data)\n        except UnicodeDecodeError:\n            data = 'Undecodable: %r' % self.data\n        return 'processname:%s groupname:%s pid:%s\\n%s' % (\n            self.process.config.name,\n            groupname,\n            self.pid,\n            data)\n\nclass ProcessCommunicationStdoutEvent(ProcessCommunicationEvent):\n    channel = 'stdout'\n\nclass ProcessCommunicationStderrEvent(ProcessCommunicationEvent):\n    channel = 'stderr'\n\nclass RemoteCommunicationEvent(Event):\n    def __init__(self, type, data):\n        self.type = type\n        self.data = data\n\n    def payload(self):\n        return 'type:%s\\n%s' % (self.type, self.data)\n\nclass SupervisorStateChangeEvent(Event):\n    \"\"\" Abstract class \"\"\"\n    def payload(self):\n        return ''\n\nclass SupervisorRunningEvent(SupervisorStateChangeEvent):\n    pass\n\nclass SupervisorStoppingEvent(SupervisorStateChangeEvent):\n    pass\n\nclass EventRejectedEvent: # purposely does not subclass Event\n    def __init__(self, process, event):\n        self.process = process\n        self.event = event\n\nclass ProcessStateEvent(Event):\n    \"\"\" Abstract class, never raised directly \"\"\"\n    frm = None\n    to = None\n    def __init__(self, process, from_state, expected=True):\n        self.process = process\n        self.from_state = from_state\n        self.expected = expected\n        # we eagerly render these so if the process pid, etc changes beneath\n        # us, we stash the values at the time the event was sent\n        self.extra_values = self.get_extra_values()\n\n    def payload(self):\n        groupname = ''\n        if self.process.group is not None:\n            groupname = self.process.group.config.name\n        L = [('processname', self.process.config.name), ('groupname', groupname),\n             ('from_state', getProcessStateDescription(self.from_state))]\n        L.extend(self.extra_values)\n        s = ' '.join( [ '%s:%s' % (name, val) for (name, val) in L ] )\n        return s\n\n    def get_extra_values(self):\n        return []\n\nclass ProcessStateFatalEvent(ProcessStateEvent):\n    pass\n\nclass ProcessStateUnknownEvent(ProcessStateEvent):\n    pass\n\nclass ProcessStateStartingOrBackoffEvent(ProcessStateEvent):\n    def get_extra_values(self):\n        return [('tries', int(self.process.backoff))]\n\nclass ProcessStateBackoffEvent(ProcessStateStartingOrBackoffEvent):\n    pass\n\nclass ProcessStateStartingEvent(ProcessStateStartingOrBackoffEvent):\n    pass\n\nclass ProcessStateExitedEvent(ProcessStateEvent):\n    def get_extra_values(self):\n        return [('expected', int(self.expected)), ('pid', self.process.pid)]\n\nclass ProcessStateRunningEvent(ProcessStateEvent):\n    def get_extra_values(self):\n        return [('pid', self.process.pid)]\n\nclass ProcessStateStoppingEvent(ProcessStateEvent):\n    def get_extra_values(self):\n        return [('pid', self.process.pid)]\n\nclass ProcessStateStoppedEvent(ProcessStateEvent):\n    def get_extra_values(self):\n        return [('pid', self.process.pid)]\n\nclass ProcessGroupEvent(Event):\n    def __init__(self, group):\n        self.group = group\n\n    def payload(self):\n        return 'groupname:%s\\n' % self.group\n\nclass ProcessGroupAddedEvent(ProcessGroupEvent):\n    pass\n\nclass ProcessGroupRemovedEvent(ProcessGroupEvent):\n    pass\n\nclass TickEvent(Event):\n    \"\"\" Abstract \"\"\"\n    def __init__(self, when, supervisord):\n        self.when = when\n        self.supervisord = supervisord\n\n    def payload(self):\n        return 'when:%s' % self.when\n\nclass Tick5Event(TickEvent):\n    period = 5\n\nclass Tick60Event(TickEvent):\n    period = 60\n\nclass Tick3600Event(TickEvent):\n    period = 3600\n\nTICK_EVENTS = [ Tick5Event, Tick60Event, Tick3600Event ] # imported elsewhere\n\nclass EventTypes:\n    EVENT = Event # abstract\n    PROCESS_STATE = ProcessStateEvent # abstract\n    PROCESS_STATE_STOPPED = ProcessStateStoppedEvent\n    PROCESS_STATE_EXITED = ProcessStateExitedEvent\n    PROCESS_STATE_STARTING = ProcessStateStartingEvent\n    PROCESS_STATE_STOPPING = ProcessStateStoppingEvent\n    PROCESS_STATE_BACKOFF = ProcessStateBackoffEvent\n    PROCESS_STATE_FATAL = ProcessStateFatalEvent\n    PROCESS_STATE_RUNNING = ProcessStateRunningEvent\n    PROCESS_STATE_UNKNOWN = ProcessStateUnknownEvent\n    PROCESS_COMMUNICATION = ProcessCommunicationEvent # abstract\n    PROCESS_COMMUNICATION_STDOUT = ProcessCommunicationStdoutEvent\n    PROCESS_COMMUNICATION_STDERR = ProcessCommunicationStderrEvent\n    PROCESS_LOG = ProcessLogEvent\n    PROCESS_LOG_STDOUT = ProcessLogStdoutEvent\n    PROCESS_LOG_STDERR = ProcessLogStderrEvent\n    REMOTE_COMMUNICATION = RemoteCommunicationEvent\n    SUPERVISOR_STATE_CHANGE = SupervisorStateChangeEvent # abstract\n    SUPERVISOR_STATE_CHANGE_RUNNING = SupervisorRunningEvent\n    SUPERVISOR_STATE_CHANGE_STOPPING = SupervisorStoppingEvent\n    TICK = TickEvent # abstract\n    TICK_5 = Tick5Event\n    TICK_60 = Tick60Event\n    TICK_3600 = Tick3600Event\n    PROCESS_GROUP = ProcessGroupEvent # abstract\n    PROCESS_GROUP_ADDED = ProcessGroupAddedEvent\n    PROCESS_GROUP_REMOVED = ProcessGroupRemovedEvent\n\ndef getEventNameByType(requested):\n    for name, typ in EventTypes.__dict__.items():\n        if typ is requested:\n            return name\n\ndef register(name, event):\n    setattr(EventTypes, name, event)\n"
  },
  {
    "path": "supervisor/http.py",
    "content": "import os\nimport stat\nimport time\nimport sys\nimport socket\nimport errno\nimport weakref\nimport traceback\n\ntry:\n    import pwd\nexcept ImportError:  # Windows\n    import getpass as pwd\n\nfrom supervisor.compat import urllib\nfrom supervisor.compat import sha1\nfrom supervisor.compat import as_bytes\nfrom supervisor.compat import as_string\nfrom supervisor.medusa import asyncore_25 as asyncore\nfrom supervisor.medusa import http_date\nfrom supervisor.medusa import http_server\nfrom supervisor.medusa import producers\nfrom supervisor.medusa import filesys\nfrom supervisor.medusa import default_handler\n\nfrom supervisor.medusa.auth_handler import auth_handler\n\nclass NOT_DONE_YET:\n    pass\n\nclass deferring_chunked_producer:\n    \"\"\"A producer that implements the 'chunked' transfer coding for HTTP/1.1.\n    Here is a sample usage:\n            request['Transfer-Encoding'] = 'chunked'\n            request.push (\n                    producers.chunked_producer (your_producer)\n                    )\n            request.done()\n    \"\"\"\n\n    def __init__ (self, producer, footers=None):\n        self.producer = producer\n        self.footers = footers\n        self.delay = 0.1\n\n    def more (self):\n        if self.producer:\n            data = self.producer.more()\n            if data is NOT_DONE_YET:\n                return NOT_DONE_YET\n            elif data:\n                s = '%x' % len(data)\n                return as_bytes(s) + b'\\r\\n' + data + b'\\r\\n'\n            else:\n                self.producer = None\n                if self.footers:\n                    return b'\\r\\n'.join([b'0'] + self.footers) + b'\\r\\n\\r\\n'\n                else:\n                    return b'0\\r\\n\\r\\n'\n        else:\n            return b''\n\nclass deferring_composite_producer:\n    \"\"\"combine a fifo of producers into one\"\"\"\n    def __init__ (self, producers):\n        self.producers = producers\n        self.delay = 0.1\n\n    def more (self):\n        while len(self.producers):\n            p = self.producers[0]\n            d = p.more()\n            if d is NOT_DONE_YET:\n                return NOT_DONE_YET\n            if d:\n                return d\n            else:\n                self.producers.pop(0)\n        else:\n            return b''\n\n\nclass deferring_globbing_producer:\n    \"\"\"\n    'glob' the output from a producer into a particular buffer size.\n    helps reduce the number of calls to send().  [this appears to\n    gain about 30% performance on requests to a single channel]\n    \"\"\"\n\n    def __init__ (self, producer, buffer_size=1<<16):\n        self.producer = producer\n        self.buffer = b''\n        self.buffer_size = buffer_size\n        self.delay = 0.1\n\n    def more (self):\n        while len(self.buffer) < self.buffer_size:\n            data = self.producer.more()\n            if data is NOT_DONE_YET:\n                return NOT_DONE_YET\n            if data:\n                try:\n                    self.buffer = self.buffer + data\n                except TypeError:\n                    self.buffer = as_bytes(self.buffer) + as_bytes(data)\n            else:\n                break\n        r = self.buffer\n        self.buffer = b''\n        return r\n\n\nclass deferring_hooked_producer:\n    \"\"\"\n    A producer that will call <function> when it empties,.\n    with an argument of the number of bytes produced.  Useful\n    for logging/instrumentation purposes.\n    \"\"\"\n\n    def __init__ (self, producer, function):\n        self.producer = producer\n        self.function = function\n        self.bytes = 0\n        self.delay = 0.1\n\n    def more (self):\n        if self.producer:\n            result = self.producer.more()\n            if result is NOT_DONE_YET:\n                return NOT_DONE_YET\n            if not result:\n                self.producer = None\n                self.function (self.bytes)\n            else:\n                self.bytes += len(result)\n            return result\n        else:\n            return b''\n\n\nclass deferring_http_request(http_server.http_request):\n    \"\"\" The medusa http_request class uses the default set of producers in\n    medusa.producers.  We can't use these because they don't know anything\n    about deferred responses, so we override various methods here.  This was\n    added to support tail -f like behavior on the logtail handler \"\"\"\n\n    def done(self, *arg, **kw):\n\n        \"\"\" I didn't want to override this, but there's no way around\n        it in order to support deferreds - CM\n\n        finalize this transaction - send output to the http channel\"\"\"\n\n        # ----------------------------------------\n        # persistent connection management\n        # ----------------------------------------\n\n        #  --- BUCKLE UP! ----\n\n        connection = http_server.get_header(http_server.CONNECTION,self.header)\n        connection = connection.lower()\n\n        close_it = 0\n        wrap_in_chunking = 0\n        globbing = 1\n\n        if self.version == '1.0':\n            if connection == 'keep-alive':\n                if not 'Content-Length' in self:\n                    close_it = 1\n                else:\n                    self['Connection'] = 'Keep-Alive'\n            else:\n                close_it = 1\n        elif self.version == '1.1':\n            if connection == 'close':\n                close_it = 1\n            elif not 'Content-Length' in self:\n                if 'Transfer-Encoding' in self:\n                    if not self['Transfer-Encoding'] == 'chunked':\n                        close_it = 1\n                elif self.use_chunked:\n                    self['Transfer-Encoding'] = 'chunked'\n                    wrap_in_chunking = 1\n                    # globbing slows down tail -f output, so only use it if\n                    # we're not in chunked mode\n                    globbing = 0\n                else:\n                    close_it = 1\n        elif self.version is None:\n            # Although we don't *really* support http/0.9 (because\n            # we'd have to use \\r\\n as a terminator, and it would just\n            # yuck up a lot of stuff) it's very common for developers\n            # to not want to type a version number when using telnet\n            # to debug a server.\n            close_it = 1\n\n        outgoing_header = producers.simple_producer(self.build_reply_header())\n\n        if close_it:\n            self['Connection'] = 'close'\n\n        if wrap_in_chunking:\n            outgoing_producer = deferring_chunked_producer(\n                    deferring_composite_producer(self.outgoing)\n                    )\n            # prepend the header\n            outgoing_producer = deferring_composite_producer(\n                [outgoing_header, outgoing_producer]\n                )\n        else:\n            # prepend the header\n            self.outgoing.insert(0, outgoing_header)\n            outgoing_producer = deferring_composite_producer(self.outgoing)\n\n        # hook logging into the output\n        outgoing_producer = deferring_hooked_producer(outgoing_producer,\n                                                      self.log)\n\n        if globbing:\n            outgoing_producer = deferring_globbing_producer(outgoing_producer)\n\n        self.channel.push_with_producer(outgoing_producer)\n\n        self.channel.current_request = None\n\n        if close_it:\n            self.channel.close_when_done()\n\n    def log (self, bytes):\n        \"\"\" We need to override this because UNIX domain sockets return\n        an empty string for the addr rather than a (host, port) combination \"\"\"\n        if self.channel.addr:\n            host = self.channel.addr[0]\n            port = self.channel.addr[1]\n        else:\n            host = 'localhost'\n            port = 0\n        self.channel.server.logger.log (\n                host,\n                '%d - - [%s] \"%s\" %d %d\\n' % (\n                        port,\n                        self.log_date_string (time.time()),\n                        self.request,\n                        self.reply_code,\n                        bytes\n                        )\n                )\n\n    def cgi_environment(self):\n        env = {}\n\n        # maps request some headers to environment variables.\n        # (those that don't start with 'HTTP_')\n        header2env= {'content-length'    : 'CONTENT_LENGTH',\n                     'content-type'      : 'CONTENT_TYPE',\n                     'connection'        : 'CONNECTION_TYPE'}\n\n        workdir = os.getcwd()\n        (path, params, query, fragment) = self.split_uri()\n\n        if params:\n            path = path + params # undo medusa bug!\n\n        while path and path[0] == '/':\n            path = path[1:]\n        if '%' in path:\n            path = http_server.unquote(path)\n        if query:\n            query = query[1:]\n\n        server = self.channel.server\n        env['REQUEST_METHOD'] = self.command.upper()\n        env['SERVER_PORT'] = str(server.port)\n        env['SERVER_NAME'] = server.server_name\n        env['SERVER_SOFTWARE'] = server.SERVER_IDENT\n        env['SERVER_PROTOCOL'] = \"HTTP/\" + self.version\n        env['channel.creation_time'] = self.channel.creation_time\n        env['SCRIPT_NAME'] = ''\n        env['PATH_INFO'] = '/' + path\n        env['PATH_TRANSLATED'] = os.path.normpath(os.path.join(\n                workdir, env['PATH_INFO']))\n        if query:\n            env['QUERY_STRING'] = query\n        env['GATEWAY_INTERFACE'] = 'CGI/1.1'\n        if self.channel.addr:\n            env['REMOTE_ADDR'] = self.channel.addr[0]\n        else:\n            env['REMOTE_ADDR'] = '127.0.0.1'\n\n        for header in self.header:\n            key,value=header.split(\":\",1)\n            key=key.lower()\n            value=value.strip()\n            if key in header2env and value:\n                env[header2env.get(key)]=value\n            else:\n                key='HTTP_%s' % (\"_\".join(key.split( \"-\"))).upper()\n                if value and key not in env:\n                    env[key]=value\n        return env\n\n    def get_server_url(self):\n        \"\"\" Functionality that medusa's http request doesn't have; set an\n        attribute named 'server_url' on the request based on the Host: header\n        \"\"\"\n        default_port={'http': '80', 'https': '443'}\n        environ = self.cgi_environment()\n        if (environ.get('HTTPS') in ('on', 'ON') or\n            environ.get('SERVER_PORT_SECURE') == \"1\"):\n            # XXX this will currently never be true\n            protocol = 'https'\n        else:\n            protocol = 'http'\n\n        if 'HTTP_HOST' in environ:\n            host = environ['HTTP_HOST'].strip()\n            hostname, port = urllib.splitport(host)\n        else:\n            hostname = environ['SERVER_NAME'].strip()\n            port = environ['SERVER_PORT']\n\n        if port is None or default_port[protocol] == port:\n            host = hostname\n        else:\n            host = hostname + ':' + port\n        server_url = '%s://%s' % (protocol, host)\n        if server_url[-1:]=='/':\n            server_url=server_url[:-1]\n        return server_url\n\nclass deferring_http_channel(http_server.http_channel):\n\n    # use a 4096-byte buffer size instead of the default 65536-byte buffer in\n    # order to spew tail -f output faster (speculative)\n    ac_out_buffer_size = 4096\n\n    delay = 0 # seconds\n    last_writable_check = 0 # timestamp of last writable check; 0 if never\n\n    def writable(self, now=None):\n        if now is None:  # for unit tests\n            now = time.time()\n\n        if self.delay:\n            # we called a deferred producer via this channel (see refill_buffer)\n            elapsed = now - self.last_writable_check\n            if (elapsed > self.delay) or (elapsed < 0):\n                self.last_writable_check = now\n                return True\n            else:\n                return False\n\n        return http_server.http_channel.writable(self)\n\n    def refill_buffer (self):\n        \"\"\" Implement deferreds \"\"\"\n        while 1:\n            if len(self.producer_fifo):\n                p = self.producer_fifo.first()\n                # a 'None' in the producer fifo is a sentinel,\n                # telling us to close the channel.\n                if p is None:\n                    if not self.ac_out_buffer:\n                        self.producer_fifo.pop()\n                        self.close()\n                    return\n                elif isinstance(p, bytes):\n                    self.producer_fifo.pop()\n                    self.ac_out_buffer += p\n                    return\n\n                data = p.more()\n\n                if data is NOT_DONE_YET:\n                    self.delay = p.delay\n                    return\n\n                elif data:\n                    self.ac_out_buffer = self.ac_out_buffer + data\n                    self.delay = False\n                    return\n                else:\n                    self.producer_fifo.pop()\n            else:\n                return\n\n    def found_terminator (self):\n        \"\"\" We only override this to use 'deferring_http_request' class\n        instead of the normal http_request class; it sucks to need to override\n        this \"\"\"\n        if self.current_request:\n            self.current_request.found_terminator()\n        else:\n            # we convert the header to text to facilitate processing.\n            # some of the underlying APIs (such as splitquery)\n            # expect text rather than bytes.\n            header = as_string(self.in_buffer)\n            self.in_buffer = b''\n            lines = header.split('\\r\\n')\n\n            # --------------------------------------------------\n            # crack the request header\n            # --------------------------------------------------\n\n            while lines and not lines[0]:\n                # as per the suggestion of http-1.1 section 4.1, (and\n                # Eric Parker <eparker@zyvex.com>), ignore a leading\n                # blank lines (buggy browsers tack it onto the end of\n                # POST requests)\n                lines = lines[1:]\n\n            if not lines:\n                self.close_when_done()\n                return\n\n            request = lines[0]\n\n            command, uri, version = http_server.crack_request (request)\n            header = http_server.join_headers (lines[1:])\n\n            # unquote path if necessary (thanks to Skip Montanaro for pointing\n            # out that we must unquote in piecemeal fashion).\n            rpath, rquery = http_server.splitquery(uri)\n            if '%' in rpath:\n                if rquery:\n                    uri = http_server.unquote(rpath) + '?' + rquery\n                else:\n                    uri = http_server.unquote(rpath)\n\n            r = deferring_http_request(self, request, command, uri, version,\n                                       header)\n            self.request_counter.increment()\n            self.server.total_requests.increment()\n\n            if command is None:\n                self.log_info ('Bad HTTP request: %s' % repr(request), 'error')\n                r.error (400)\n                return\n\n            # --------------------------------------------------\n            # handler selection and dispatch\n            # --------------------------------------------------\n            for h in self.server.handlers:\n                if h.match (r):\n                    try:\n                        self.current_request = r\n                        # This isn't used anywhere.\n                        # r.handler = h # CYCLE\n                        h.handle_request (r)\n                    except:\n                        self.server.exceptions.increment()\n                        (file, fun, line), t, v, tbinfo = \\\n                               asyncore.compact_traceback()\n                        self.server.log_info(\n                            'Server Error: %s, %s: file: %s line: %s' %\n                            (t,v,file,line),\n                            'error')\n                        try:\n                            r.error (500)\n                        except:\n                            pass\n                    return\n\n            # no handlers, so complain\n            r.error (404)\n\nclass supervisor_http_server(http_server.http_server):\n    channel_class = deferring_http_channel\n    ip = None\n\n    def prebind(self, sock, logger_object):\n        \"\"\" Override __init__ to do logger setup earlier so it can\n        go to our logger object instead of stdout \"\"\"\n        from supervisor.medusa import logger\n\n        if not logger_object:\n            logger_object = logger.file_logger(sys.stdout)\n\n        logger_object = logger.unresolving_logger(logger_object)\n        self.logger = logger_object\n\n        asyncore.dispatcher.__init__ (self)\n        self.set_socket(sock)\n\n        self.handlers = []\n\n        sock.setblocking(0)\n        self.set_reuse_addr()\n\n    def postbind(self):\n        from supervisor.medusa.counter import counter\n        from supervisor.medusa.http_server import VERSION_STRING\n\n        self.listen(1024)\n\n        self.total_clients = counter()\n        self.total_requests = counter()\n        self.exceptions = counter()\n        self.bytes_out = counter()\n        self.bytes_in  = counter()\n\n        self.log_info (\n                'Medusa (V%s) started at %s'\n                '\\n\\tHostname: %s'\n                '\\n\\tPort:%s'\n                '\\n' % (\n                        VERSION_STRING,\n                        time.ctime(time.time()),\n                        self.server_name,\n                        self.port,\n                        )\n                )\n\n    def log_info(self, message, type='info'):\n        ip = ''\n        if getattr(self, 'ip', None) is not None:\n            ip = self.ip\n        self.logger.log(ip, message)\n\nclass supervisor_af_inet_http_server(supervisor_http_server):\n    \"\"\" AF_INET version of supervisor HTTP server \"\"\"\n\n    def __init__(self, ip, port, logger_object):\n        self.ip = ip\n        self.port = port\n        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n        self.prebind(sock, logger_object)\n        self.bind((ip, port))\n\n        if not ip:\n            self.log_info('Computing default hostname', 'warning')\n            hostname = socket.gethostname()\n            try:\n                ip = socket.gethostbyname(hostname)\n            except socket.error:\n                raise ValueError(\n                    'Could not determine IP address for hostname %s, '\n                    'please try setting an explicit IP address in the \"port\" '\n                    'setting of your [inet_http_server] section.  For example, '\n                    'instead of \"port = 9001\", try \"port = 127.0.0.1:9001.\"'\n                    % hostname)\n        try:\n            self.server_name = socket.gethostbyaddr (ip)[0]\n        except socket.error:\n            self.log_info('Cannot do reverse lookup', 'warning')\n            self.server_name = ip       # use the IP address as the \"hostname\"\n\n        self.postbind()\n\nclass supervisor_af_unix_http_server(supervisor_http_server):\n    \"\"\" AF_UNIX version of supervisor HTTP server \"\"\"\n\n    def __init__(self, socketname, sockchmod, sockchown, logger_object):\n        self.ip = socketname\n        self.port = socketname\n\n        # XXX this is insecure.  We really should do something like\n        # http://developer.apple.com/samplecode/CFLocalServer/listing6.html\n        # (see also http://developer.apple.com/technotes/tn2005/tn2083.html#SECUNIXDOMAINSOCKETS)\n        # but it would be very inconvenient for the user to need to get all\n        # the directory setup right.\n\n        tempname = \"%s.%d\" % (socketname, os.getpid())\n\n        try:\n            os.unlink(tempname)\n        except OSError:\n            pass\n\n        while 1:\n            sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\n            try:\n                sock.bind(tempname)\n                os.chmod(tempname, sockchmod)\n                try:\n                    # hard link\n                    os.link(tempname, socketname)\n                except OSError:\n                    # Lock contention, or stale socket.\n                    used = self.checkused(socketname)\n                    if used:\n                        # cooperate with 'openhttpserver' in supervisord\n                        raise socket.error(errno.EADDRINUSE)\n\n                    # Stale socket -- delete, sleep, and try again.\n                    msg = \"Unlinking stale socket %s\\n\" % socketname\n                    sys.stderr.write(msg)\n                    try:\n                        os.unlink(socketname)\n                    except:\n                        pass\n                    sock.close()\n                    time.sleep(.3)\n                    continue\n                else:\n                    try:\n                        os.chown(socketname, sockchown[0], sockchown[1])\n                    except OSError as why:\n                        if why.args[0] == errno.EPERM:\n                            msg = ('Not permitted to chown %s to uid/gid %s; '\n                                   'adjust \"sockchown\" value in config file or '\n                                   'on command line to values that the '\n                                   'current user (%s) can successfully chown')\n                            raise ValueError(msg % (socketname,\n                                                    repr(sockchown),\n                                                    pwd.getpwuid(\n                                                        os.geteuid())[0],\n                                                    ),\n                                             )\n                        else:\n                            raise\n                    self.prebind(sock, logger_object)\n                    break\n\n            finally:\n                try:\n                    os.unlink(tempname)\n                except OSError:\n                    pass\n\n        self.server_name = '<unix domain socket>'\n        self.postbind()\n\n    def checkused(self, socketname):\n        s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\n        try:\n            s.connect(socketname)\n            s.send(as_bytes(\"GET / HTTP/1.0\\r\\n\\r\\n\"))\n            s.recv(1)\n            s.close()\n        except socket.error:\n            return False\n        else:\n            return True\n\nclass tail_f_producer:\n    def __init__(self, request, filename, head):\n        self.request = weakref.ref(request)\n        self.filename = filename\n        self.delay = 0.1\n\n        self._open()\n        sz = self._fsize()\n        if sz >= head:\n            self.sz = sz - head\n\n    def __del__(self):\n        self._close()\n\n    def more(self):\n        self._follow()\n        try:\n            newsz = self._fsize()\n        except (OSError, ValueError):\n            # file descriptor was closed\n            return b''\n        bytes_added = newsz - self.sz\n        if bytes_added < 0:\n            self.sz = 0\n            return \"==> File truncated <==\\n\"\n        if bytes_added > 0:\n            self.file.seek(-bytes_added, 2)\n            bytes = self.file.read(bytes_added)\n            self.sz = newsz\n            return bytes\n        return NOT_DONE_YET\n\n    def _open(self):\n        self.file = open(self.filename, 'rb')\n        self.ino = os.fstat(self.file.fileno())[stat.ST_INO]\n        self.sz = 0\n\n    def _close(self):\n        self.file.close()\n\n    def _follow(self):\n        try:\n            ino = os.stat(self.filename)[stat.ST_INO]\n        except (OSError, ValueError):\n            # file was unlinked\n            return\n\n        if self.ino != ino: # log rotation occurred\n            self._close()\n            self._open()\n\n    def _fsize(self):\n        return os.fstat(self.file.fileno())[stat.ST_SIZE]\n\nclass logtail_handler:\n    IDENT = 'Logtail HTTP Request Handler'\n    path = '/logtail'\n\n    def __init__(self, supervisord):\n        self.supervisord = supervisord\n\n    def match(self, request):\n        return request.uri.startswith(self.path)\n\n    def handle_request(self, request):\n        if request.command != 'GET':\n            request.error (400) # bad request\n            return\n\n        path, params, query, fragment = request.split_uri()\n\n        if '%' in path:\n            path = http_server.unquote(path)\n\n        # strip off all leading slashes\n        while path and path[0] == '/':\n            path = path[1:]\n\n        path, process_name_and_channel = path.split('/', 1)\n\n        try:\n            process_name, channel = process_name_and_channel.split('/', 1)\n        except ValueError:\n            # no channel specified, default channel to stdout\n            process_name = process_name_and_channel\n            channel = 'stdout'\n\n        from supervisor.options import split_namespec\n        group_name, process_name = split_namespec(process_name)\n\n        group = self.supervisord.process_groups.get(group_name)\n        if group is None:\n            request.error(404) # not found\n            return\n\n        process = group.processes.get(process_name)\n        if process is None:\n            request.error(404) # not found\n            return\n\n        logfile = getattr(process.config, '%s_logfile' % channel, None)\n\n        if logfile is None or not os.path.exists(logfile):\n            # we return 404 because no logfile is a temporary condition.\n            # if the process has never been started, no logfile will exist\n            # on disk.  a logfile of None is also a temporary condition,\n            # since the config file can be reloaded.\n            request.error(404) # not found\n            return\n\n        mtime = os.stat(logfile)[stat.ST_MTIME]\n        request['Last-Modified'] = http_date.build_http_date(mtime)\n        request['Content-Type'] = 'text/plain;charset=utf-8'\n        # the lack of a Content-Length header makes the outputter\n        # send a 'Transfer-Encoding: chunked' response\n        request['X-Accel-Buffering'] = 'no'\n        # tell reverse proxy server (e.g., nginx) to disable proxy buffering\n        # (see also http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffering)\n\n        request.push(tail_f_producer(request, logfile, 1024))\n\n        request.done()\n\nclass mainlogtail_handler:\n    IDENT = 'Main Logtail HTTP Request Handler'\n    path = '/mainlogtail'\n\n    def __init__(self, supervisord):\n        self.supervisord = supervisord\n\n    def match(self, request):\n        return request.uri.startswith(self.path)\n\n    def handle_request(self, request):\n        if request.command != 'GET':\n            request.error (400) # bad request\n            return\n\n        logfile = self.supervisord.options.logfile\n\n        if logfile is None or not os.path.exists(logfile):\n            # we return 404 because no logfile is a temporary condition.\n            # even if a log file of None is configured, the config file\n            # may be reloaded, and the new config may have a logfile.\n            request.error(404) # not found\n            return\n\n        mtime = os.stat(logfile)[stat.ST_MTIME]\n        request['Last-Modified'] = http_date.build_http_date(mtime)\n        request['Content-Type'] = 'text/plain;charset=utf-8'\n        # the lack of a Content-Length header makes the outputter\n        # send a 'Transfer-Encoding: chunked' response\n\n        request.push(tail_f_producer(request, logfile, 1024))\n\n        request.done()\n\ndef make_http_servers(options, supervisord):\n    servers = []\n    wrapper = LogWrapper(options.logger)\n\n    for config in options.server_configs:\n        family = config['family']\n\n        if family == socket.AF_INET:\n            host, port = config['host'], config['port']\n            hs = supervisor_af_inet_http_server(host, port,\n                                                logger_object=wrapper)\n        elif family == socket.AF_UNIX:\n            socketname = config['file']\n            sockchmod = config['chmod']\n            sockchown = config['chown']\n            hs = supervisor_af_unix_http_server(socketname,sockchmod, sockchown,\n                                                logger_object=wrapper)\n        else:\n            raise ValueError('Cannot determine socket type %r' % family)\n\n        from supervisor.xmlrpc import supervisor_xmlrpc_handler\n        from supervisor.xmlrpc import SystemNamespaceRPCInterface\n        from supervisor.web import supervisor_ui_handler\n\n        subinterfaces = []\n        for name, factory, d in options.rpcinterface_factories:\n            try:\n                inst = factory(supervisord, **d)\n            except:\n                tb = traceback.format_exc()\n                options.logger.warn(tb)\n                raise ValueError('Could not make %s rpc interface' % name)\n            subinterfaces.append((name, inst))\n            options.logger.info('RPC interface %r initialized' % name)\n\n        subinterfaces.append(('system',\n                              SystemNamespaceRPCInterface(subinterfaces)))\n        xmlrpchandler = supervisor_xmlrpc_handler(supervisord, subinterfaces)\n        tailhandler = logtail_handler(supervisord)\n        maintailhandler = mainlogtail_handler(supervisord)\n        uihandler = supervisor_ui_handler(supervisord)\n        here = os.path.abspath(os.path.dirname(__file__))\n        templatedir = os.path.join(here, 'ui')\n        filesystem = filesys.os_filesystem(templatedir)\n        defaulthandler = default_handler.default_handler(filesystem)\n\n        username = config['username']\n        password = config['password']\n\n        if username:\n            # wrap the xmlrpc handler and tailhandler in an authentication\n            # handler\n            users = {username:password}\n            xmlrpchandler = supervisor_auth_handler(users, xmlrpchandler)\n            tailhandler = supervisor_auth_handler(users, tailhandler)\n            maintailhandler = supervisor_auth_handler(users, maintailhandler)\n            uihandler = supervisor_auth_handler(users, uihandler)\n            defaulthandler = supervisor_auth_handler(users, defaulthandler)\n        else:\n            options.logger.critical(\n                'Server %r running without any HTTP '\n                'authentication checking' % config['section'])\n        # defaulthandler must be consulted last as its match method matches\n        # everything, so it's first here (indicating last checked)\n        hs.install_handler(defaulthandler)\n        hs.install_handler(uihandler)\n        hs.install_handler(maintailhandler)\n        hs.install_handler(tailhandler)\n        hs.install_handler(xmlrpchandler) # last for speed (first checked)\n        servers.append((config, hs))\n\n    return servers\n\nclass LogWrapper:\n    '''Receives log messages from the Medusa servers and forwards\n    them to the Supervisor logger'''\n    def __init__(self, logger):\n        self.logger = logger\n\n    def log(self, msg):\n        '''Medusa servers call this method.  There is no log level so\n        we have to sniff the message.  We want \"Server Error\" messages\n        from medusa.http_server logged as errors at least.'''\n        if msg.endswith('\\n'):\n            msg = msg[:-1]\n        if 'error' in msg.lower():\n            self.logger.error(msg)\n        else:\n            self.logger.trace(msg)\n\nclass encrypted_dictionary_authorizer:\n    def __init__ (self, dict):\n        self.dict = dict\n\n    def authorize(self, auth_info):\n        username, password = auth_info\n        if username in self.dict:\n            stored_password = self.dict[username]\n            if stored_password.startswith('{SHA}'):\n                password_hash = sha1(as_bytes(password)).hexdigest()\n                return stored_password[5:] == password_hash\n            else:\n                return stored_password == password\n        else:\n            return False\n\nclass supervisor_auth_handler(auth_handler):\n    def __init__(self, dict, handler, realm='default'):\n        auth_handler.__init__(self, dict, handler, realm)\n        # override the authorizer with one that knows about SHA hashes too\n        self.authorizer = encrypted_dictionary_authorizer(dict)\n"
  },
  {
    "path": "supervisor/http_client.py",
    "content": "# this code based on Daniel Krech's RDFLib HTTP client code (see rdflib.dev)\n\nimport sys\nimport socket\n\nfrom supervisor.compat import as_bytes\nfrom supervisor.compat import as_string\nfrom supervisor.compat import encodestring\nfrom supervisor.compat import PY2\nfrom supervisor.compat import urlparse\nfrom supervisor.medusa import asynchat_25 as asynchat\n\nCR = b'\\x0d'\nLF = b'\\x0a'\nCRLF = CR+LF\n\nclass Listener(object):\n\n    def status(self, url, status):\n        pass\n\n    def error(self, url, error):\n        sys.stderr.write(\"%s %s\\n\" % (url, error))\n\n    def response_header(self, url, name, value):\n        pass\n\n    def done(self, url):\n        pass\n\n    def feed(self, url, data):\n        try:\n            sdata = as_string(data)\n        except UnicodeDecodeError:\n            sdata = 'Undecodable: %r' % data\n        # We've got Unicode data in sdata now, but writing to stdout sometimes\n        # fails - see issue #1231.\n        try:\n            sys.stdout.write(sdata)\n        except UnicodeEncodeError:\n            if PY2:\n                # This might seem like The Wrong Thing To Do (writing bytes\n                # rather than text to an output stream), but it seems to work\n                # OK for Python 2.7.\n                sys.stdout.write(data)\n            else:\n                s = ('Unable to write Unicode to stdout because it has '\n                     'encoding %s' % sys.stdout.encoding)\n                raise ValueError(s)\n        sys.stdout.flush()\n\n    def close(self, url):\n        pass\n\nclass HTTPHandler(asynchat.async_chat):\n    def __init__(\n        self,\n        listener,\n        username='',\n        password=None,\n        conn=None,\n        map=None\n        ):\n        asynchat.async_chat.__init__(self, conn, map)\n        self.listener = listener\n        self.user_agent = 'Supervisor HTTP Client'\n        self.buffer = b''\n        self.set_terminator(CRLF)\n        self.connected = 0\n        self.part = self.status_line\n        self.chunk_size = 0\n        self.chunk_read = 0\n        self.length_read = 0\n        self.length = 0\n        self.encoding = None\n        self.username = username\n        self.password = password\n        self.url = None\n        self.error_handled = False\n\n    def get(self, serverurl, path=''):\n        if self.url is not None:\n            raise AssertionError('Already doing a get')\n        self.url = serverurl + path\n        scheme, host, path_ignored, params, query, fragment = urlparse.urlparse(\n            self.url)\n        if not scheme in (\"http\", \"unix\"):\n            raise NotImplementedError\n        self.host = host\n        if \":\" in host:\n            hostname, port = host.split(\":\", 1)\n            port = int(port)\n        else:\n            hostname = host\n            port = 80\n\n        self.path = path\n        self.port = port\n\n        if scheme == \"http\":\n            ip = hostname\n            self.create_socket(socket.AF_INET, socket.SOCK_STREAM)\n            self.connect((ip, self.port))\n        elif scheme == \"unix\":\n            socketname = serverurl[7:]\n            self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM)\n            self.connect(socketname)\n\n    def close(self):\n        self.listener.close(self.url)\n        self.connected = 0\n        self.del_channel()\n        self.socket.close()\n        self.url = \"CLOSED\"\n\n    def header(self, name, value):\n        self.push('%s: %s' % (name, value))\n        self.push(CRLF)\n\n    def handle_error(self):\n        if self.error_handled:\n            return\n        if 1 or self.connected:\n            t,v,tb = sys.exc_info()\n            msg = 'Cannot connect, error: %s (%s)' % (t, v)\n            self.listener.error(self.url, msg)\n            self.part = self.ignore\n            self.close()\n            self.error_handled = True\n            del t\n            del v\n            del tb\n\n    def handle_connect(self):\n        self.connected = 1\n        method = \"GET\"\n        version = \"HTTP/1.1\"\n        self.push(\"%s %s %s\" % (method, self.path, version))\n        self.push(CRLF)\n        self.header(\"Host\", self.host)\n\n        self.header('Accept-Encoding', 'chunked')\n        self.header('Accept', '*/*')\n        self.header('User-agent', self.user_agent)\n        if self.password:\n            auth = '%s:%s' % (self.username, self.password)\n            auth = as_string(encodestring(as_bytes(auth))).strip()\n            self.header('Authorization', 'Basic %s' % auth)\n        self.push(CRLF)\n        self.push(CRLF)\n\n\n    def feed(self, data):\n        self.listener.feed(self.url, data)\n\n    def collect_incoming_data(self, bytes):\n        self.buffer = self.buffer + bytes\n        if self.part==self.body:\n            self.feed(self.buffer)\n            self.buffer = b''\n\n    def found_terminator(self):\n        self.part()\n        self.buffer = b''\n\n    def ignore(self):\n        self.buffer = b''\n\n    def status_line(self):\n        line = self.buffer\n\n        version, status, reason = line.split(None, 2)\n        status = int(status)\n        if not version.startswith(b'HTTP/'):\n            raise ValueError(line)\n\n        self.listener.status(self.url, status)\n\n        if status == 200:\n            self.part = self.headers\n        else:\n            self.part = self.ignore\n            msg = 'Cannot read, status code %s' % status\n            self.listener.error(self.url, msg)\n            self.close()\n        return version, status, reason\n\n    def headers(self):\n        line = self.buffer\n        if not line:\n            if self.encoding == b'chunked':\n                self.part = self.chunked_size\n            else:\n                self.part = self.body\n                self.set_terminator(self.length)\n        else:\n            name, value = line.split(b':', 1)\n            if name and value:\n                name = name.lower()\n                value = value.strip()\n                if name == b'transfer-encoding':\n                    self.encoding = value\n                elif name == b'content-length':\n                    self.length = int(value)\n                self.response_header(name, value)\n\n    def response_header(self, name, value):\n        self.listener.response_header(self.url, name, value)\n\n    def body(self):\n        self.done()\n        self.close()\n\n    def done(self):\n        self.listener.done(self.url)\n\n    def chunked_size(self):\n        line = self.buffer\n        if not line:\n            return\n        chunk_size = int(line.split()[0], 16)\n        if chunk_size==0:\n            self.part = self.trailer\n        else:\n            self.set_terminator(chunk_size)\n            self.part = self.chunked_body\n        self.length += chunk_size\n\n    def chunked_body(self):\n        line = self.buffer\n        self.set_terminator(CRLF)\n        self.part = self.chunked_size\n        self.feed(line)\n\n    def trailer(self):\n        # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1\n        # trailer        = *(entity-header CRLF)\n        line = self.buffer\n        if line == CRLF:\n            self.done()\n            self.close()\n"
  },
  {
    "path": "supervisor/loggers.py",
    "content": "\"\"\"\nLogger implementation loosely modeled on PEP 282.  We don't use the\nPEP 282 logger implementation in the stdlib ('logging') because it's\nidiosyncratic and a bit slow for our purposes (we don't use threads).\n\"\"\"\n\n# This module must not depend on any non-stdlib modules to\n# avoid circular import problems\n\nimport os\nimport errno\nimport sys\nimport time\nimport traceback\n\nfrom supervisor.compat import syslog\nfrom supervisor.compat import long\nfrom supervisor.compat import is_text_stream\nfrom supervisor.compat import as_string\n\nclass LevelsByName:\n    CRIT = 50   # messages that probably require immediate user attention\n    ERRO = 40   # messages that indicate a potentially ignorable error condition\n    WARN = 30   # messages that indicate issues which aren't errors\n    INFO = 20   # normal informational output\n    DEBG = 10   # messages useful for users trying to debug configurations\n    TRAC = 5    # messages useful to developers trying to debug plugins\n    BLAT = 3    # messages useful for developers trying to debug supervisor\n\nclass LevelsByDescription:\n    critical = LevelsByName.CRIT\n    error = LevelsByName.ERRO\n    warn = LevelsByName.WARN\n    info = LevelsByName.INFO\n    debug = LevelsByName.DEBG\n    trace = LevelsByName.TRAC\n    blather = LevelsByName.BLAT\n\ndef _levelNumbers():\n    bynumber = {}\n    for name, number in LevelsByName.__dict__.items():\n        if not name.startswith('_'):\n            bynumber[number] = name\n    return bynumber\n\nLOG_LEVELS_BY_NUM = _levelNumbers()\n\ndef getLevelNumByDescription(description):\n    num = getattr(LevelsByDescription, description, None)\n    return num\n\nclass Handler:\n    fmt = '%(message)s'\n    level = LevelsByName.INFO\n\n    def __init__(self, stream=None):\n        self.stream = stream\n        self.closed = False\n\n    def setFormat(self, fmt):\n        self.fmt = fmt\n\n    def setLevel(self, level):\n        self.level = level\n\n    def flush(self):\n        try:\n            self.stream.flush()\n        except IOError as why:\n            # if supervisor output is piped, EPIPE can be raised at exit\n            if why.args[0] != errno.EPIPE:\n                raise\n\n    def close(self):\n        if not self.closed:\n            if hasattr(self.stream, 'fileno'):\n                try:\n                    fd = self.stream.fileno()\n                except IOError:\n                    # on python 3, io.IOBase objects always have fileno()\n                    # but calling it may raise io.UnsupportedOperation\n                    pass\n                else:\n                    if fd < 3: # don't ever close stdout or stderr\n                        return\n            self.stream.close()\n            self.closed = True\n\n    def emit(self, record):\n        try:\n            binary = (self.fmt == '%(message)s' and\n                      isinstance(record.msg, bytes) and\n                      (not record.kw or record.kw == {'exc_info': None}))\n            binary_stream = not is_text_stream(self.stream)\n            if binary:\n                msg = record.msg\n            else:\n                msg = self.fmt % record.asdict()\n                if binary_stream:\n                    msg = msg.encode('utf-8')\n            try:\n                self.stream.write(msg)\n            except UnicodeError:\n                # TODO sort out later\n                # this only occurs because of a test stream type\n                # which deliberately raises an exception the first\n                # time it's called. So just do it again\n                self.stream.write(msg)\n            self.flush()\n        except:\n            self.handleError()\n\n    def handleError(self):\n        ei = sys.exc_info()\n        traceback.print_exception(ei[0], ei[1], ei[2], None, sys.stderr)\n        del ei\n\nclass StreamHandler(Handler):\n    def __init__(self, strm=None):\n        Handler.__init__(self, strm)\n\n    def remove(self):\n        if hasattr(self.stream, 'clear'):\n            self.stream.clear()\n\n    def reopen(self):\n        pass\n\nclass BoundIO:\n    def __init__(self, maxbytes, buf=b''):\n        self.maxbytes = maxbytes\n        self.buf = buf\n\n    def flush(self):\n        pass\n\n    def close(self):\n        self.clear()\n\n    def write(self, b):\n        blen = len(b)\n        if len(self.buf) + blen > self.maxbytes:\n            self.buf = self.buf[blen:]\n        self.buf += b\n\n    def getvalue(self):\n        return self.buf\n\n    def clear(self):\n        self.buf = b''\n\nclass FileHandler(Handler):\n    \"\"\"File handler which supports reopening of logs.\n    \"\"\"\n\n    def __init__(self, filename, mode='ab'):\n        Handler.__init__(self)\n\n        try:\n            self.stream = open(filename, mode)\n        except OSError as e:\n            if mode == 'ab' and e.errno == errno.ESPIPE:\n                # Python 3 can't open special files like\n                # /dev/stdout in 'a' mode due to an implicit seek call\n                # that fails with ESPIPE. Retry in 'w' mode.\n                # See: http://bugs.python.org/issue27805\n                mode = 'wb'\n                self.stream = open(filename, mode)\n            else:\n                raise\n\n        self.baseFilename = filename\n        self.mode = mode\n\n    def reopen(self):\n        self.close()\n        self.stream = open(self.baseFilename, self.mode)\n        self.closed = False\n\n    def remove(self):\n        self.close()\n        try:\n            os.remove(self.baseFilename)\n        except OSError as why:\n            if why.args[0] != errno.ENOENT:\n                raise\n\nclass RotatingFileHandler(FileHandler):\n    def __init__(self, filename, mode='ab', maxBytes=512*1024*1024,\n                 backupCount=10):\n        \"\"\"\n        Open the specified file and use it as the stream for logging.\n\n        By default, the file grows indefinitely. You can specify particular\n        values of maxBytes and backupCount to allow the file to rollover at\n        a predetermined size.\n\n        Rollover occurs whenever the current log file is nearly maxBytes in\n        length. If backupCount is >= 1, the system will successively create\n        new files with the same pathname as the base file, but with extensions\n        \".1\", \".2\" etc. appended to it. For example, with a backupCount of 5\n        and a base file name of \"app.log\", you would get \"app.log\",\n        \"app.log.1\", \"app.log.2\", ... through to \"app.log.5\". The file being\n        written to is always \"app.log\" - when it gets filled up, it is closed\n        and renamed to \"app.log.1\", and if files \"app.log.1\", \"app.log.2\" etc.\n        exist, then they are renamed to \"app.log.2\", \"app.log.3\" etc.\n        respectively.\n\n        If maxBytes is zero, rollover never occurs.\n        \"\"\"\n        if maxBytes > 0:\n            mode = 'ab' # doesn't make sense otherwise!\n        FileHandler.__init__(self, filename, mode)\n        self.maxBytes = maxBytes\n        self.backupCount = backupCount\n        self.counter = 0\n        self.every = 10\n\n    def emit(self, record):\n        \"\"\"\n        Emit a record.\n\n        Output the record to the file, catering for rollover as described\n        in doRollover().\n        \"\"\"\n        FileHandler.emit(self, record)\n        self.doRollover()\n\n    def _remove(self, fn): # pragma: no cover\n        # this is here to service stubbing in unit tests\n        return os.remove(fn)\n\n    def _rename(self, src, tgt): # pragma: no cover\n        # this is here to service stubbing in unit tests\n        return os.rename(src, tgt)\n\n    def _exists(self, fn): # pragma: no cover\n        # this is here to service stubbing in unit tests\n        return os.path.exists(fn)\n\n    def removeAndRename(self, sfn, dfn):\n        if self._exists(dfn):\n            try:\n                self._remove(dfn)\n            except OSError as why:\n                # catch race condition (destination already deleted)\n                if why.args[0] != errno.ENOENT:\n                    raise\n        try:\n            self._rename(sfn, dfn)\n        except OSError as why:\n            # catch exceptional condition (source deleted)\n            # E.g. cleanup script removes active log.\n            if why.args[0] != errno.ENOENT:\n                raise\n\n    def doRollover(self):\n        \"\"\"\n        Do a rollover, as described in __init__().\n        \"\"\"\n        if self.maxBytes <= 0:\n            return\n\n        if not (self.stream.tell() >= self.maxBytes):\n            return\n\n        self.stream.close()\n        if self.backupCount > 0:\n            for i in range(self.backupCount - 1, 0, -1):\n                sfn = \"%s.%d\" % (self.baseFilename, i)\n                dfn = \"%s.%d\" % (self.baseFilename, i + 1)\n                if os.path.exists(sfn):\n                    self.removeAndRename(sfn, dfn)\n            dfn = self.baseFilename + \".1\"\n            self.removeAndRename(self.baseFilename, dfn)\n        self.stream = open(self.baseFilename, 'wb')\n\nclass LogRecord:\n    def __init__(self, level, msg, **kw):\n        self.level = level\n        self.msg = msg\n        self.kw = kw\n        self.dictrepr = None\n\n    def asdict(self):\n        if self.dictrepr is None:\n            now = time.time()\n            msecs = (now - long(now)) * 1000\n            part1 = time.strftime(\"%Y-%m-%d %H:%M:%S\", time.localtime(now))\n            asctime = '%s,%03d' % (part1, msecs)\n            levelname = LOG_LEVELS_BY_NUM[self.level]\n            msg = as_string(self.msg)\n            if self.kw:\n                msg = msg % self.kw\n            self.dictrepr = {'message':msg, 'levelname':levelname,\n                             'asctime':asctime}\n        return self.dictrepr\n\nclass Logger:\n    def __init__(self, level=None, handlers=None):\n        if level is None:\n            level = LevelsByName.INFO\n        self.level = level\n\n        if handlers is None:\n            handlers = []\n        self.handlers = handlers\n\n    def close(self):\n        for handler in self.handlers:\n            handler.close()\n\n    def blather(self, msg, **kw):\n        if LevelsByName.BLAT >= self.level:\n            self.log(LevelsByName.BLAT, msg, **kw)\n\n    def trace(self, msg, **kw):\n        if LevelsByName.TRAC >= self.level:\n            self.log(LevelsByName.TRAC, msg, **kw)\n\n    def debug(self, msg, **kw):\n        if LevelsByName.DEBG >= self.level:\n            self.log(LevelsByName.DEBG, msg, **kw)\n\n    def info(self, msg, **kw):\n        if LevelsByName.INFO >= self.level:\n            self.log(LevelsByName.INFO, msg, **kw)\n\n    def warn(self, msg, **kw):\n        if LevelsByName.WARN >= self.level:\n            self.log(LevelsByName.WARN, msg, **kw)\n\n    def error(self, msg, **kw):\n        if LevelsByName.ERRO >= self.level:\n            self.log(LevelsByName.ERRO, msg, **kw)\n\n    def critical(self, msg, **kw):\n        if LevelsByName.CRIT >= self.level:\n            self.log(LevelsByName.CRIT, msg, **kw)\n\n    def log(self, level, msg, **kw):\n        record = LogRecord(level, msg, **kw)\n        for handler in self.handlers:\n            if level >= handler.level:\n                handler.emit(record)\n\n    def addHandler(self, hdlr):\n        self.handlers.append(hdlr)\n\n    def getvalue(self):\n        raise NotImplementedError\n\nclass SyslogHandler(Handler):\n    def __init__(self):\n        Handler.__init__(self)\n        assert syslog is not None, \"Syslog module not present\"\n\n    def close(self):\n        pass\n\n    def reopen(self):\n        pass\n\n    def _syslog(self, msg): # pragma: no cover\n        # this exists only for unit test stubbing\n        syslog.syslog(msg)\n\n    def emit(self, record):\n        try:\n            params = record.asdict()\n            message = params['message']\n            for line in message.rstrip('\\n').split('\\n'):\n                params['message'] = line\n                msg = self.fmt % params\n                try:\n                    self._syslog(msg)\n                except UnicodeError:\n                    self._syslog(msg.encode(\"UTF-8\"))\n        except:\n            self.handleError()\n\ndef getLogger(level=None):\n    return Logger(level)\n\n_2MB = 1<<21\n\ndef handle_boundIO(logger, fmt, maxbytes=_2MB):\n    \"\"\"Attach a new BoundIO handler to an existing Logger\"\"\"\n    io = BoundIO(maxbytes)\n    handler = StreamHandler(io)\n    handler.setLevel(logger.level)\n    handler.setFormat(fmt)\n    logger.addHandler(handler)\n    logger.getvalue = io.getvalue\n\ndef handle_stdout(logger, fmt):\n    \"\"\"Attach a new StreamHandler with stdout handler to an existing Logger\"\"\"\n    handler = StreamHandler(sys.stdout)\n    handler.setFormat(fmt)\n    handler.setLevel(logger.level)\n    logger.addHandler(handler)\n\ndef handle_syslog(logger, fmt):\n    \"\"\"Attach a new Syslog handler to an existing Logger\"\"\"\n    handler = SyslogHandler()\n    handler.setFormat(fmt)\n    handler.setLevel(logger.level)\n    logger.addHandler(handler)\n\ndef handle_file(logger, filename, fmt, rotating=False, maxbytes=0, backups=0):\n    \"\"\"Attach a new file handler to an existing Logger. If the filename\n    is the magic name of 'syslog' then make it a syslog handler instead.\"\"\"\n    if filename == 'syslog': # TODO remove this\n        handler = SyslogHandler()\n    else:\n        if rotating is False:\n            handler = FileHandler(filename)\n        else:\n            handler = RotatingFileHandler(filename, 'a', maxbytes, backups)\n    handler.setFormat(fmt)\n    handler.setLevel(logger.level)\n    logger.addHandler(handler)\n"
  },
  {
    "path": "supervisor/medusa/CHANGES.txt",
    "content": "PATCHES MADE ONLY TO THIS MEDUSA PACKAGE BUNDLED WITH SUPERVISOR\n* Re-added asyncore.py as asyncore_25.py and asynchat.py as asynchat_25.py\n  and updated Medusa throughout to use these.  The Python 2.6 stdlib version\n  of these modules introduced backward-incompatible changes.\n* Changed imports throughout from \"medusa\" to \"supervisor.medusa\".\n* Removed medusa files not used by Supervisor.\n* Fixed a bug in auth_handler.py where colons could not be used in passwords\n  for HTTP Basic authentication (Supervisor issue #309).\n* Time out connections based on inactivity instead of age (Supervisor issue\n  #651).\n\nVersion 0.5.5:\n\n* [Patch #855389] ADD RNFR & RNTO commands to FTP server (Robin Becker)\n* [Patch #852089] In status_handler, catch any exception raised by the status()\n  method.\n* [Patch #855695] Bugfix for filesys.msdos_date\n* [Patch from Jason Sibre] Improve performance of xmlrpc_handler\n  by avoiding string concatenation\n* [Patch from Jason Sibre] Add interface to http_request for multiple\n  headers with the same name.\n\n\nVersion 0.5.4:\n\n* Open syslog using datagram sockets, and only try streams if that fails\n  (Alxy Sav)\n* Fix bug in http_server.crack_request() (Contributed by Canis Lupus)\n* Incorporate bugfixes to thread/select_trigger.py from ZEO's version \n  (Zope Corporation)\n* Add demo/winFTPserver.py, an FTP server that uses Windows\n  authorization to determine who can access the server. (Contributed\n  by John Abel)\n* Add control files for creating a Debian package of Medusa (python2.2-medusa).\n\n  \nVersion 0.5.3:\n\n* Delete the broken and rather boring dual_server and simple_httpd\n  demo scripts.  start_medusa.py should be sufficient as an example.\n* Fix indentation bug in demo/script_server.py noted by Richard Philips\n* Fix bug in producers.composite_producer, spotted and fixed by Daniel Krech\n* Added test suite for producers.py\n* Fix timestamps in http_server logs\n* Fix unix_user_handler bug, spotted and fixed by Sergio Fernndez.\n* Fix auth_handler bug, spotted and fixed by Sergio Fernndez.\n* Delete unused http_server.fifo class and fifo.py module.\n\nVersion 0.5.2:\n\n* Fix syntax error and missing import in default_handler.py\n* Fix various scripts in demo/\n\nVersion 0.5.1:\n\n* Apply cleanup patch from Donovan Baarda\n* Fix bug reported by Van Gale: counter.py and auth_handler.py did\n  long(...)[:-1] to chop off a trailing L generated in earlier\n  versions of Python.\n* Fix bug in ftp_server.py that I introduced in 0.5\n* Remove some duplicated producer classes\n* Removed work_in_progress/ directory and the 'continuation' module\n* Remove MIME type table code and use the stdlib's mimelib module\n\nVersion 0.5:\n\n* Added a setup.py installation script, which will install all the code\n  under the package name 'medusa'.\n* Added README.txt and CHANGES.txt.\n* Fixed NameError in util/convert_mime_type_table.py \n* Fixed TypeError in test/test_medusa.py\n* Fixed several problems detected by PyChecker\n* Changed demos to use 'from medusa import ...'\n* Rearranged files to reduce the number of subdirectories.\n* Removed or updated uses of the obsolete regsub module\n* Removed asyncore.py and asynchat.py; these modules were added to Python's\n  standard library with version 1.5.2, and Medusa now assumes that they're \n  present.\n* Removed many obsolete files:\n     poll/pollmodule.c, as Python's select module now supports poll()\n     patches/posixmodule.c, as that patch was incorporated in Python\n     old/*, script_handler_demo/*, sendfile/*\n     The old ANNOUNCE files\n* Reindented all files to use four-space indents\n\n\nThe last version of Medusa released by Sam Rushing was medusa-20010416.\n"
  },
  {
    "path": "supervisor/medusa/LICENSE.txt",
    "content": "Medusa was once distributed under a 'free for non-commercial use'\nlicense, but in May of 2000 Sam Rushing changed the license to be\nidentical to the standard Python license at the time.  The standard\nPython license has always applied to the core components of Medusa,\nthis change just frees up the rest of the system, including the http\nserver, ftp server, utilities, etc.  Medusa is therefore under the\nfollowing license:\n\n==============================\nPermission to use, copy, modify, and distribute this software and\nits documentation for any purpose and without fee is hereby granted,\nprovided that the above copyright notice appear in all copies and\nthat both that copyright notice and this permission notice appear in\nsupporting documentation, and that the name of Sam Rushing not be\nused in advertising or publicity pertaining to distribution of the\nsoftware without specific, written prior permission.\n\nSAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,\nINCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN\nNO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR\nCONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS\nOF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,\nNEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION\nWITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n==============================\n\nSam would like to take this opportunity to thank all of the folks who\nsupported Medusa over the years by purchasing commercial licenses.\n\n\n"
  },
  {
    "path": "supervisor/medusa/README.txt",
    "content": "Medusa is a 'server platform' -- it provides a framework for\nimplementing asynchronous socket-based servers (TCP/IP and on Unix,\nUnix domain, sockets).\n\nAn asynchronous socket server is a server that can communicate with many\nother clients simultaneously by multiplexing I/O within a single\nprocess/thread.  In the context of an HTTP server, this means a single\nprocess can serve hundreds or even thousands of clients, depending only on\nthe operating system's configuration and limitations.\n\nThere are several advantages to this approach:\n     \n  o  performance - no fork() or thread() start-up costs per hit.\n\n  o  scalability - the overhead per client can be kept rather small,\n     on the order of several kilobytes of memory.\n\n  o  persistence - a single-process server can easily coordinate the\n     actions of several different connections.  This makes things like\n     proxy servers and gateways easy to implement.  It also makes it\n     possible to share resources like database handles.\n\nMedusa includes HTTP, FTP, and 'monitor' (remote python interpreter)\nservers.  Medusa can simultaneously support several instances of\neither the same or different server types - for example you could\nstart up two HTTP servers, an FTP server, and a monitor server.  Then\nyou could connect to the monitor server to control and manipulate\nmedusa while it is running.\n\nOther servers and clients have been written (SMTP, POP3, NNTP), and\nseveral are in the planning stages.  \n\nMedusa was originally written by Sam Rushing <rushing@nightmare.com>,\nand its original Web page is at <http://www.nightmare.com/medusa/>. After\nSam moved on to other things, A.M. Kuchling <akuchlin@mems-exchange.org> \ntook over maintenance of the Medusa package.\n\n--amk\n\n"
  },
  {
    "path": "supervisor/medusa/TODO.txt",
    "content": "Things to do\n============\n\nBring remaining code up to current standards\nTranslate docs to RST\nWrite README, INSTALL, docs\nWhat should __init__ import?  Anything?  Every single class?\nAdd abo's support for blocking producers\nGet all the producers into the producers module and write tests for them\n\nTest suites for protocols: how could that be implemented?\n\n"
  },
  {
    "path": "supervisor/medusa/__init__.py",
    "content": "\"\"\"medusa.__init__\n\"\"\"\n\n# created 2002/03/19, AMK\n\n__revision__ = \"$Id: __init__.py,v 1.2 2002/03/19 22:49:34 amk Exp $\"\n"
  },
  {
    "path": "supervisor/medusa/asynchat_25.py",
    "content": "# -*- Mode: Python; tab-width: 4 -*-\n#       Id: asynchat.py,v 2.26 2000/09/07 22:29:26 rushing Exp\n#       Author: Sam Rushing <rushing@nightmare.com>\n\n# ======================================================================\n# Copyright 1996 by Sam Rushing\n#\n#                         All Rights Reserved\n#\n# Permission to use, copy, modify, and distribute this software and\n# its documentation for any purpose and without fee is hereby\n# granted, provided that the above copyright notice appear in all\n# copies and that both that copyright notice and this permission\n# notice appear in supporting documentation, and that the name of Sam\n# Rushing not be used in advertising or publicity pertaining to\n# distribution of the software without specific, written prior\n# permission.\n#\n# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,\n# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN\n# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR\n# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS\n# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,\n# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN\n# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n# ======================================================================\n\nr\"\"\"A class supporting chat-style (command/response) protocols.\n\nThis class adds support for 'chat' style protocols - where one side\nsends a 'command', and the other sends a response (examples would be\nthe common internet protocols - smtp, nntp, ftp, etc..).\n\nThe handle_read() method looks at the input stream for the current\n'terminator' (usually '\\r\\n' for single-line responses, '\\r\\n.\\r\\n'\nfor multi-line output), calling self.found_terminator() on its\nreceipt.\n\nfor example:\nSay you build an async nntp client using this class.  At the start\nof the connection, you'll have self.terminator set to '\\r\\n', in\norder to process the single-line greeting.  Just before issuing a\n'LIST' command you'll set it to '\\r\\n.\\r\\n'.  The output of the LIST\ncommand will be accumulated (using your own 'collect_incoming_data'\nmethod) up to the terminator, and then control will be returned to\nyou - by calling your self.found_terminator() method.\n\"\"\"\n\nimport socket\nfrom supervisor.medusa import asyncore_25 as asyncore\nfrom supervisor.compat import long\nfrom supervisor.compat import as_bytes\n\nclass async_chat (asyncore.dispatcher):\n    \"\"\"This is an abstract class.  You must derive from this class, and add\n    the two methods collect_incoming_data() and found_terminator()\"\"\"\n\n    # these are overridable defaults\n\n    ac_in_buffer_size       = 4096\n    ac_out_buffer_size      = 4096\n\n    def __init__ (self, conn=None, map=None):\n        self.ac_in_buffer = b''\n        self.ac_out_buffer = b''\n        self.producer_fifo = fifo()\n        asyncore.dispatcher.__init__ (self, conn, map)\n\n    def collect_incoming_data(self, data):\n        raise NotImplementedError(\"must be implemented in subclass\")\n\n    def found_terminator(self):\n        raise NotImplementedError(\"must be implemented in subclass\")\n\n    def set_terminator (self, term):\n        \"\"\"Set the input delimiter.  Can be a fixed string of any length, an integer, or None\"\"\"\n        self.terminator = term\n\n    def get_terminator (self):\n        return self.terminator\n\n    # grab some more data from the socket,\n    # throw it to the collector method,\n    # check for the terminator,\n    # if found, transition to the next state.\n\n    def handle_read (self):\n        try:\n            data = self.recv (self.ac_in_buffer_size)\n        except socket.error:\n            self.handle_error()\n            return\n\n        self.ac_in_buffer += data\n\n        # Continue to search for self.terminator in self.ac_in_buffer,\n        # while calling self.collect_incoming_data.  The while loop\n        # is necessary because we might read several data+terminator\n        # combos with a single recv(1024).\n\n        while self.ac_in_buffer:\n            lb = len(self.ac_in_buffer)\n            terminator = self.get_terminator()\n            if not terminator:\n                # no terminator, collect it all\n                self.collect_incoming_data (self.ac_in_buffer)\n                self.ac_in_buffer = b''\n            elif isinstance(terminator, int) or isinstance(terminator, long):\n                # numeric terminator\n                n = terminator\n                if lb < n:\n                    self.collect_incoming_data (self.ac_in_buffer)\n                    self.ac_in_buffer = b''\n                    self.terminator -= lb\n                else:\n                    self.collect_incoming_data (self.ac_in_buffer[:n])\n                    self.ac_in_buffer = self.ac_in_buffer[n:]\n                    self.terminator = 0\n                    self.found_terminator()\n            else:\n                # 3 cases:\n                # 1) end of buffer matches terminator exactly:\n                #    collect data, transition\n                # 2) end of buffer matches some prefix:\n                #    collect data to the prefix\n                # 3) end of buffer does not match any prefix:\n                #    collect data\n                terminator_len = len(terminator)\n                index = self.ac_in_buffer.find(terminator)\n                if index != -1:\n                    # we found the terminator\n                    if index > 0:\n                        # don't bother reporting the empty string (source of subtle bugs)\n                        self.collect_incoming_data (self.ac_in_buffer[:index])\n                    self.ac_in_buffer = self.ac_in_buffer[index+terminator_len:]\n                    # This does the Right Thing if the terminator is changed here.\n                    self.found_terminator()\n                else:\n                    # check for a prefix of the terminator\n                    index = find_prefix_at_end (self.ac_in_buffer, terminator)\n                    if index:\n                        if index != lb:\n                            # we found a prefix, collect up to the prefix\n                            self.collect_incoming_data (self.ac_in_buffer[:-index])\n                            self.ac_in_buffer = self.ac_in_buffer[-index:]\n                        break\n                    else:\n                        # no prefix, collect it all\n                        self.collect_incoming_data (self.ac_in_buffer)\n                        self.ac_in_buffer = b''\n\n    def handle_write (self):\n        self.initiate_send ()\n\n    def handle_close (self):\n        self.close()\n\n    def push (self, data):\n        data = as_bytes(data)\n        self.producer_fifo.push(simple_producer(data))\n        self.initiate_send()\n\n    def push_with_producer (self, producer):\n        self.producer_fifo.push (producer)\n        self.initiate_send()\n\n    def readable (self):\n        \"\"\"predicate for inclusion in the readable for select()\"\"\"\n        return len(self.ac_in_buffer) <= self.ac_in_buffer_size\n\n    def writable (self):\n        \"\"\"predicate for inclusion in the writable for select()\"\"\"\n        # return len(self.ac_out_buffer) or len(self.producer_fifo) or (not self.connected)\n        # this is about twice as fast, though not as clear.\n        return not (\n                (self.ac_out_buffer == b'') and\n                self.producer_fifo.is_empty() and\n                self.connected\n                )\n\n    def close_when_done (self):\n        \"\"\"automatically close this channel once the outgoing queue is empty\"\"\"\n        self.producer_fifo.push (None)\n\n    # refill the outgoing buffer by calling the more() method\n    # of the first producer in the queue\n    def refill_buffer (self):\n        while 1:\n            if len(self.producer_fifo):\n                p = self.producer_fifo.first()\n                # a 'None' in the producer fifo is a sentinel,\n                # telling us to close the channel.\n                if p is None:\n                    if not self.ac_out_buffer:\n                        self.producer_fifo.pop()\n                        self.close()\n                    return\n                elif isinstance(p, bytes):\n                    self.producer_fifo.pop()\n                    self.ac_out_buffer += p\n                    return\n                data = p.more()\n                if data:\n                    self.ac_out_buffer = self.ac_out_buffer + data\n                    return\n                else:\n                    self.producer_fifo.pop()\n            else:\n                return\n\n    def initiate_send (self):\n        obs = self.ac_out_buffer_size\n        # try to refill the buffer\n        if len (self.ac_out_buffer) < obs:\n            self.refill_buffer()\n\n        if self.ac_out_buffer and self.connected:\n            # try to send the buffer\n            try:\n                num_sent = self.send (self.ac_out_buffer[:obs])\n                if num_sent:\n                    self.ac_out_buffer = self.ac_out_buffer[num_sent:]\n\n            except socket.error:\n                self.handle_error()\n                return\n\n    def discard_buffers (self):\n        # Emergencies only!\n        self.ac_in_buffer = b''\n        self.ac_out_buffer = b''\n        while self.producer_fifo:\n            self.producer_fifo.pop()\n\n\nclass simple_producer:\n\n    def __init__ (self, data, buffer_size=512):\n        self.data = data\n        self.buffer_size = buffer_size\n\n    def more (self):\n        if len (self.data) > self.buffer_size:\n            result = self.data[:self.buffer_size]\n            self.data = self.data[self.buffer_size:]\n            return result\n        else:\n            result = self.data\n            self.data = b''\n            return result\n\nclass fifo:\n    def __init__ (self, list=None):\n        if not list:\n            self.list = []\n        else:\n            self.list = list\n\n    def __len__ (self):\n        return len(self.list)\n\n    def is_empty (self):\n        return self.list == []\n\n    def first (self):\n        return self.list[0]\n\n    def push (self, data):\n        self.list.append(data)\n\n    def pop (self):\n        if self.list:\n            return 1, self.list.pop(0)\n        else:\n            return 0, None\n\n# Given 'haystack', see if any prefix of 'needle' is at its end.  This\n# assumes an exact match has already been checked.  Return the number of\n# characters matched.\n# for example:\n# f_p_a_e (\"qwerty\\r\", \"\\r\\n\") => 1\n# f_p_a_e (\"qwertydkjf\", \"\\r\\n\") => 0\n# f_p_a_e (\"qwerty\\r\\n\", \"\\r\\n\") => <undefined>\n\n# this could maybe be made faster with a computed regex?\n# [answer: no; circa Python-2.0, Jan 2001]\n# new python:   28961/s\n# old python:   18307/s\n# re:        12820/s\n# regex:     14035/s\n\ndef find_prefix_at_end (haystack, needle):\n    l = len(needle) - 1\n    while l and not haystack.endswith(needle[:l]):\n        l -= 1\n    return l\n"
  },
  {
    "path": "supervisor/medusa/asyncore_25.py",
    "content": "# -*- Mode: Python -*-\n#   Id: asyncore.py,v 2.51 2000/09/07 22:29:26 rushing Exp\n#   Author: Sam Rushing <rushing@nightmare.com>\n\n# ======================================================================\n# Copyright 1996 by Sam Rushing\n#\n#                         All Rights Reserved\n#\n# Permission to use, copy, modify, and distribute this software and\n# its documentation for any purpose and without fee is hereby\n# granted, provided that the above copyright notice appear in all\n# copies and that both that copyright notice and this permission\n# notice appear in supporting documentation, and that the name of Sam\n# Rushing not be used in advertising or publicity pertaining to\n# distribution of the software without specific, written prior\n# permission.\n#\n# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,\n# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN\n# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR\n# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS\n# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,\n# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN\n# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n# ======================================================================\n\n\"\"\"Basic infrastructure for asynchronous socket service clients and servers.\n\nThere are only two ways to have a program on a single processor do \"more\nthan one thing at a time\".  Multi-threaded programming is the simplest and\nmost popular way to do it, but there is another very different technique,\nthat lets you have nearly all the advantages of multi-threading, without\nactually using multiple threads. it's really only practical if your program\nis largely I/O bound. If your program is CPU bound, then preemptive\nscheduled threads are probably what you really need. Network servers are\nrarely CPU-bound, however.\n\nIf your operating system supports the select() system call in its I/O\nlibrary (and nearly all do), then you can use it to juggle multiple\ncommunication channels at once; doing other work while your I/O is taking\nplace in the \"background.\"  Although this strategy can seem strange and\ncomplex, especially at first, it is in many ways easier to understand and\ncontrol than multi-threaded programming. The module documented here solves\nmany of the difficult problems for you, making the task of building\nsophisticated high-performance network servers and clients a snap.\n\"\"\"\n\nimport select\nimport socket\nimport sys\nimport time\n\nimport os\nfrom errno import EALREADY, EINPROGRESS, EWOULDBLOCK, ECONNRESET, \\\n     ENOTCONN, ESHUTDOWN, EINTR, EISCONN, errorcode\n\nfrom supervisor.compat import as_string, as_bytes\n\ntry:\n    socket_map\nexcept NameError:\n    socket_map = {}\n\nclass ExitNow(Exception):\n    pass\n\ndef read(obj):\n    try:\n        obj.handle_read_event()\n    except ExitNow:\n        raise\n    except:\n        obj.handle_error()\n\ndef write(obj):\n    try:\n        obj.handle_write_event()\n    except ExitNow:\n        raise\n    except:\n        obj.handle_error()\n\ndef _exception (obj):\n    try:\n        obj.handle_expt_event()\n    except ExitNow:\n        raise\n    except:\n        obj.handle_error()\n\ndef readwrite(obj, flags):\n    try:\n        if flags & (select.POLLIN | select.POLLPRI):\n            obj.handle_read_event()\n        if flags & select.POLLOUT:\n            obj.handle_write_event()\n        if flags & (select.POLLERR | select.POLLHUP | select.POLLNVAL):\n            obj.handle_expt_event()\n    except ExitNow:\n        raise\n    except:\n        obj.handle_error()\n\ndef poll(timeout=0.0, map=None):\n    if map is None:\n        map = socket_map\n    if map:\n        r = []; w = []; e = []\n        for fd, obj in map.items():\n            is_r = obj.readable()\n            is_w = obj.writable()\n            if is_r:\n                r.append(fd)\n            if is_w:\n                w.append(fd)\n            if is_r or is_w:\n                e.append(fd)\n        if [] == r == w == e:\n            time.sleep(timeout)\n        else:\n            try:\n                r, w, e = select.select(r, w, e, timeout)\n            except select.error as err:\n                if err.args[0] != EINTR:\n                    raise\n                else:\n                    return\n\n        for fd in r:\n            obj = map.get(fd)\n            if obj is None:\n                continue\n            read(obj)\n\n        for fd in w:\n            obj = map.get(fd)\n            if obj is None:\n                continue\n            write(obj)\n\n        for fd in e:\n            obj = map.get(fd)\n            if obj is None:\n                continue\n            _exception(obj)\n\ndef poll2(timeout=0.0, map=None):\n    # Use the poll() support added to the select module in Python 2.0\n    if map is None:\n        map = socket_map\n    if timeout is not None:\n        # timeout is in milliseconds\n        timeout = int(timeout*1000)\n    pollster = select.poll()\n    if map:\n        for fd, obj in map.items():\n            flags = 0\n            if obj.readable():\n                flags |= select.POLLIN | select.POLLPRI\n            if obj.writable():\n                flags |= select.POLLOUT\n            if flags:\n                # Only check for exceptions if object was either readable\n                # or writable.\n                flags |= select.POLLERR | select.POLLHUP | select.POLLNVAL\n                pollster.register(fd, flags)\n        try:\n            r = pollster.poll(timeout)\n        except select.error as err:\n            if err.args[0] != EINTR:\n                raise\n            r = []\n        for fd, flags in r:\n            obj = map.get(fd)\n            if obj is None:\n                continue\n            readwrite(obj, flags)\n\npoll3 = poll2                           # Alias for backward compatibility\n\ndef loop(timeout=30.0, use_poll=False, map=None, count=None):\n    if map is None:\n        map = socket_map\n\n    if use_poll and hasattr(select, 'poll'):\n        poll_fun = poll2\n    else:\n        poll_fun = poll\n\n    if count is None:\n        while map:\n            poll_fun(timeout, map)\n\n    else:\n        while map and count > 0:\n            poll_fun(timeout, map)\n            count -= 1\n\nclass dispatcher:\n\n    debug = False\n    connected = False\n    accepting = False\n    closing = False\n    addr = None\n\n    def __init__(self, sock=None, map=None):\n        if map is None:\n            self._map = socket_map\n        else:\n            self._map = map\n\n        if sock:\n            self.set_socket(sock, map)\n            # I think it should inherit this anyway\n            self.socket.setblocking(0)\n            self.connected = True\n            # XXX Does the constructor require that the socket passed\n            # be connected?\n            try:\n                self.addr = sock.getpeername()\n            except socket.error:\n                # The addr isn't crucial\n                pass\n        else:\n            self.socket = None\n\n    def __repr__(self):\n        status = [self.__class__.__module__+\".\"+self.__class__.__name__]\n        if self.accepting and self.addr:\n            status.append('listening')\n        elif self.connected:\n            status.append('connected')\n        if self.addr is not None:\n            try:\n                status.append('%s:%d' % self.addr)\n            except TypeError:\n                status.append(repr(self.addr))\n        return '<%s at %#x>' % (' '.join(status), id(self))\n\n    def add_channel(self, map=None):\n        #self.log_info('adding channel %s' % self)\n        if map is None:\n            map = self._map\n        map[self._fileno] = self\n\n    def del_channel(self, map=None):\n        fd = self._fileno\n        if map is None:\n            map = self._map\n        if fd in map:\n            #self.log_info('closing channel %d:%s' % (fd, self))\n            del map[fd]\n        self._fileno = None\n\n    def create_socket(self, family, type):\n        self.family_and_type = family, type\n        self.socket = socket.socket(family, type)\n        self.socket.setblocking(0)\n        self._fileno = self.socket.fileno()\n        self.add_channel()\n\n    def set_socket(self, sock, map=None):\n        self.socket = sock\n##        self.__dict__['socket'] = sock\n        self._fileno = sock.fileno()\n        self.add_channel(map)\n\n    def set_reuse_addr(self):\n        # try to re-use a server port if possible\n        try:\n            self.socket.setsockopt(\n                socket.SOL_SOCKET, socket.SO_REUSEADDR,\n                self.socket.getsockopt(socket.SOL_SOCKET,\n                                       socket.SO_REUSEADDR) | 1\n                )\n        except socket.error:\n            pass\n\n    # ==================================================\n    # predicates for select()\n    # these are used as filters for the lists of sockets\n    # to pass to select().\n    # ==================================================\n\n    def readable(self):\n        return True\n\n    def writable(self):\n        return True\n\n    # ==================================================\n    # socket object methods.\n    # ==================================================\n\n    def listen(self, num):\n        self.accepting = True\n        if os.name == 'nt' and num > 5:\n            num = 1\n        return self.socket.listen(num)\n\n    def bind(self, addr):\n        self.addr = addr\n        return self.socket.bind(addr)\n\n    def connect(self, address):\n        self.connected = False\n        err = self.socket.connect_ex(address)\n        # XXX Should interpret Winsock return values\n        if err in (EINPROGRESS, EALREADY, EWOULDBLOCK):\n            return\n        if err in (0, EISCONN):\n            self.addr = address\n            self.connected = True\n            self.handle_connect()\n        else:\n            raise socket.error(err, errorcode[err])\n\n    def accept(self):\n        # XXX can return either an address pair or None\n        try:\n            conn, addr = self.socket.accept()\n            return conn, addr\n        except socket.error as why:\n            if why.args[0] == EWOULDBLOCK:\n                pass\n            else:\n                raise\n\n    def send(self, data):\n        try:\n            result = self.socket.send(data)\n            return result\n        except socket.error as why:\n            if why.args[0] == EWOULDBLOCK:\n                return 0\n            else:\n                raise\n\n    def recv(self, buffer_size):\n        try:\n            data = self.socket.recv(buffer_size)\n            if not data:\n                # a closed connection is indicated by signaling\n                # a read condition, and having recv() return 0.\n                self.handle_close()\n                return b''\n            else:\n                return data\n        except socket.error as why:\n            # winsock sometimes throws ENOTCONN\n            if why.args[0] in [ECONNRESET, ENOTCONN, ESHUTDOWN]:\n                self.handle_close()\n                return b''\n            else:\n                raise\n\n    def close(self):\n        self.del_channel()\n\n        try:\n            self.socket.shutdown(socket.SHUT_RDWR)\n        except socket.error:\n            # must swallow exception from already-closed socket\n            # (at least with Python 3.11.7 on macOS 14.2.1)\n            pass\n\n        # does not raise if called on already-closed socket\n        self.socket.close()  \n\n    # cheap inheritance, used to pass all other attribute\n    # references to the underlying socket object.\n    def __getattr__(self, attr):\n        return getattr(self.socket, attr)\n\n    # log and log_info may be overridden to provide more sophisticated\n    # logging and warning methods. In general, log is for 'hit' logging\n    # and 'log_info' is for informational, warning and error logging.\n\n    def log(self, message):\n        sys.stderr.write('log: %s\\n' % str(message))\n\n    def log_info(self, message, type='info'):\n        if __debug__ or type != 'info':\n            print('%s: %s' % (type, message))\n\n    def handle_read_event(self):\n        if self.accepting:\n            # for an accepting socket, getting a read implies\n            # that we are connected\n            if not self.connected:\n                self.connected = True\n            self.handle_accept()\n        elif not self.connected:\n            self.handle_connect()\n            self.connected = True\n            self.handle_read()\n        else:\n            self.handle_read()\n\n    def handle_write_event(self):\n        # getting a write implies that we are connected\n        if not self.connected:\n            self.handle_connect()\n            self.connected = True\n        self.handle_write()\n\n    def handle_expt_event(self):\n        self.handle_expt()\n\n    def handle_error(self):\n        nil, t, v, tbinfo = compact_traceback()\n\n        # sometimes a user repr method will crash.\n        try:\n            self_repr = repr(self)\n        except:\n            self_repr = '<__repr__(self) failed for object at %0x>' % id(self)\n\n        self.log_info(\n            'uncaptured python exception, closing channel %s (%s:%s %s)' % (\n                self_repr,\n                t,\n                v,\n                tbinfo\n                ),\n            'error'\n            )\n        self.close()\n\n    def handle_expt(self):\n        self.log_info('unhandled exception', 'warning')\n\n    def handle_read(self):\n        self.log_info('unhandled read event', 'warning')\n\n    def handle_write(self):\n        self.log_info('unhandled write event', 'warning')\n\n    def handle_connect(self):\n        self.log_info('unhandled connect event', 'warning')\n\n    def handle_accept(self):\n        self.log_info('unhandled accept event', 'warning')\n\n    def handle_close(self):\n        self.log_info('unhandled close event', 'warning')\n        self.close()\n\n# ---------------------------------------------------------------------------\n# adds simple buffered output capability, useful for simple clients.\n# [for more sophisticated usage use asynchat.async_chat]\n# ---------------------------------------------------------------------------\n\nclass dispatcher_with_send(dispatcher):\n\n    def __init__(self, sock=None, map=None):\n        dispatcher.__init__(self, sock, map)\n        self.out_buffer = b''\n\n    def initiate_send(self):\n        num_sent = dispatcher.send(self, self.out_buffer[:512])\n        self.out_buffer = self.out_buffer[num_sent:]\n\n    def handle_write(self):\n        self.initiate_send()\n\n    def writable(self):\n        return (not self.connected) or len(self.out_buffer)\n\n    def send(self, data):\n        if self.debug:\n            self.log_info('sending %s' % repr(data))\n        self.out_buffer = self.out_buffer + data\n        self.initiate_send()\n\n# ---------------------------------------------------------------------------\n# used for debugging.\n# ---------------------------------------------------------------------------\n\ndef compact_traceback():\n    t, v, tb = sys.exc_info()\n    tbinfo = []\n    assert tb # Must have a traceback\n    while tb:\n        tbinfo.append((\n            tb.tb_frame.f_code.co_filename,\n            tb.tb_frame.f_code.co_name,\n            str(tb.tb_lineno)\n            ))\n        tb = tb.tb_next\n\n    # just to be safe\n    del tb\n\n    file, function, line = tbinfo[-1]\n    info = ' '.join(['[%s|%s|%s]' % x for x in tbinfo])\n    return (file, function, line), t, v, info\n\ndef close_all(map=None):\n    if map is None:\n        map = socket_map\n    for x in map.values():\n        x.socket.close()\n    map.clear()\n\n# Asynchronous File I/O:\n#\n# After a little research (reading man pages on various unixen, and\n# digging through the linux kernel), I've determined that select()\n# isn't meant for doing asynchronous file i/o.\n# Heartening, though - reading linux/mm/filemap.c shows that linux\n# supports asynchronous read-ahead.  So _MOST_ of the time, the data\n# will be sitting in memory for us already when we go to read it.\n#\n# What other OS's (besides NT) support async file i/o?  [VMS?]\n#\n# Regardless, this is useful for pipes, and stdin/stdout...\n\nif os.name == 'posix':\n    import fcntl\n\n    class file_wrapper:\n        # here we override just enough to make a file\n        # look like a socket for the purposes of asyncore.\n\n        def __init__(self, fd):\n            self.fd = fd\n\n        def recv(self, buffersize):\n            return as_string(os.read(self.fd, buffersize))\n\n        def send(self, s):\n            return os.write(self.fd, as_bytes(s))\n\n        read = recv\n        write = send\n\n        def close(self):\n            os.close(self.fd)\n\n        def fileno(self):\n            return self.fd\n\n    class file_dispatcher(dispatcher):\n\n        def __init__(self, fd, map=None):\n            dispatcher.__init__(self, None, map)\n            self.connected = True\n            self.set_file(fd)\n            # set it to non-blocking mode\n            flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0)\n            flags |= os.O_NONBLOCK\n            fcntl.fcntl(fd, fcntl.F_SETFL, flags)\n\n        def set_file(self, fd):\n            self._fileno = fd\n            self.socket = file_wrapper(fd)\n            self.add_channel()\n"
  },
  {
    "path": "supervisor/medusa/auth_handler.py",
    "content": "# -*- Mode: Python -*-\n#\n#       Author: Sam Rushing <rushing@nightmare.com>\n#       Copyright 1996-2000 by Sam Rushing\n#                                                All Rights Reserved.\n#\n\nRCS_ID =  '$Id: auth_handler.py,v 1.6 2002/11/25 19:40:23 akuchling Exp $'\n\n# support for 'basic' authentication.\n\nimport re\nimport sys\nimport time\n\nfrom supervisor.compat import as_string, as_bytes\nfrom supervisor.compat import encodestring, decodestring\nfrom supervisor.compat import long\nfrom supervisor.compat import md5\n\nimport supervisor.medusa.counter as counter\nimport supervisor.medusa.default_handler as default_handler\n\nget_header = default_handler.get_header\n\nimport supervisor.medusa.producers as producers\n\n# This is a 'handler' that wraps an authorization method\n# around access to the resources normally served up by\n# another handler.\n\n# does anyone support digest authentication? (rfc2069)\n\nclass auth_handler:\n    def __init__ (self, dict, handler, realm='default'):\n        self.authorizer = dictionary_authorizer (dict)\n        self.handler = handler\n        self.realm = realm\n        self.pass_count = counter.counter()\n        self.fail_count = counter.counter()\n\n    def match (self, request):\n        # by default, use the given handler's matcher\n        return self.handler.match (request)\n\n    def handle_request (self, request):\n        # authorize a request before handling it...\n        scheme = get_header (AUTHORIZATION, request.header)\n\n        if scheme:\n            scheme = scheme.lower()\n            if scheme == 'basic':\n                cookie = get_header (AUTHORIZATION, request.header, 2)\n                try:\n                    decoded = as_string(decodestring(as_bytes(cookie)))\n                except:\n                    sys.stderr.write('malformed authorization info <%s>\\n' % cookie)\n                    request.error (400)\n                    return\n                auth_info = decoded.split(':', 1)\n                if self.authorizer.authorize (auth_info):\n                    self.pass_count.increment()\n                    request.auth_info = auth_info\n                    self.handler.handle_request (request)\n                else:\n                    self.handle_unauthorized (request)\n            #elif scheme == 'digest':\n            #       print 'digest: ',AUTHORIZATION.group(2)\n            else:\n                sys.stderr.write('unknown/unsupported auth method: %s\\n' % scheme)\n                self.handle_unauthorized(request)\n        else:\n            # list both?  prefer one or the other?\n            # you could also use a 'nonce' here. [see below]\n            #auth = 'Basic realm=\"%s\" Digest realm=\"%s\"' % (self.realm, self.realm)\n            #nonce = self.make_nonce (request)\n            #auth = 'Digest realm=\"%s\" nonce=\"%s\"' % (self.realm, nonce)\n            #request['WWW-Authenticate'] = auth\n            #print 'sending header: %s' % request['WWW-Authenticate']\n            self.handle_unauthorized (request)\n\n    def handle_unauthorized (self, request):\n        # We are now going to receive data that we want to ignore.\n        # to ignore the file data we're not interested in.\n        self.fail_count.increment()\n        request.channel.set_terminator (None)\n        request['Connection'] = 'close'\n        request['WWW-Authenticate'] = 'Basic realm=\"%s\"' % self.realm\n        request.error (401)\n\n    def make_nonce (self, request):\n        \"\"\"A digest-authentication <nonce>, constructed as suggested in RFC 2069\"\"\"\n        ip = request.channel.server.ip\n        now = str(long(time.time()))\n        if now[-1:] == 'L':\n            now = now[:-1]\n        private_key = str (id (self))\n        nonce = ':'.join([ip, now, private_key])\n        return self.apply_hash (nonce)\n\n    def apply_hash (self, s):\n        \"\"\"Apply MD5 to a string <s>, then wrap it in base64 encoding.\"\"\"\n        m = md5()\n        m.update (s)\n        d = m.digest()\n        # base64.encodestring tacks on an extra linefeed.\n        return encodestring (d)[:-1]\n\n    def status (self):\n        # Thanks to mwm@contessa.phone.net (Mike Meyer)\n        r = [\n                producers.simple_producer (\n                        '<li>Authorization Extension : '\n                        '<b>Unauthorized requests:</b> %s<ul>' % self.fail_count\n                        )\n                ]\n        if hasattr (self.handler, 'status'):\n            r.append (self.handler.status())\n        r.append (\n                producers.simple_producer ('</ul>')\n                )\n        return producers.composite_producer(r)\n\nclass dictionary_authorizer:\n    def __init__ (self, dict):\n        self.dict = dict\n\n    def authorize (self, auth_info):\n        [username, password] = auth_info\n        if username in self.dict and self.dict[username] == password:\n            return 1\n        else:\n            return 0\n\nAUTHORIZATION = re.compile (\n        #               scheme  challenge\n        'Authorization: ([^ ]+) (.*)',\n        re.IGNORECASE\n        )\n"
  },
  {
    "path": "supervisor/medusa/counter.py",
    "content": "# -*- Mode: Python -*-\n\n# It is tempting to add an __int__ method to this class, but it's not\n# a good idea.  This class tries to gracefully handle integer\n# overflow, and to hide this detail from both the programmer and the\n# user.  Note that the __str__ method can be relied on for printing out\n# the value of a counter:\n#\n# >>> print 'Total Client: %s' % self.total_clients\n#\n# If you need to do arithmetic with the value, then use the 'as_long'\n# method, the use of long arithmetic is a reminder that the counter\n# will overflow.\n\nfrom supervisor.compat import long\n\nclass counter:\n    \"\"\"general-purpose counter\"\"\"\n\n    def __init__ (self, initial_value=0):\n        self.value = initial_value\n\n    def increment (self, delta=1):\n        result = self.value\n        try:\n            self.value = self.value + delta\n        except OverflowError:\n            self.value = long(self.value) + delta\n        return result\n\n    def decrement (self, delta=1):\n        result = self.value\n        try:\n            self.value = self.value - delta\n        except OverflowError:\n            self.value = long(self.value) - delta\n        return result\n\n    def as_long (self):\n        return long(self.value)\n\n    def __nonzero__ (self):\n        return self.value != 0\n\n    __bool__ = __nonzero__\n\n    def __repr__ (self):\n        return '<counter value=%s at %x>' % (self.value, id(self))\n\n    def __str__ (self):\n        s = str(long(self.value))\n        if s[-1:] == 'L':\n            s = s[:-1]\n        return s\n\n"
  },
  {
    "path": "supervisor/medusa/default_handler.py",
    "content": "# -*- Mode: Python -*-\n#\n#       Author: Sam Rushing <rushing@nightmare.com>\n#       Copyright 1997 by Sam Rushing\n#                                                All Rights Reserved.\n#\n\nRCS_ID = '$Id: default_handler.py,v 1.8 2002/08/01 18:15:45 akuchling Exp $'\n\n# standard python modules\nimport mimetypes\nimport re\nimport stat\n\n# medusa modules\nimport supervisor.medusa.http_date as http_date\nimport supervisor.medusa.http_server as http_server\nimport supervisor.medusa.producers as producers\n\nfrom supervisor.medusa.util import html_repr\n\nunquote = http_server.unquote\n\n# This is the 'default' handler.  it implements the base set of\n# features expected of a simple file-delivering HTTP server.  file\n# services are provided through a 'filesystem' object, the very same\n# one used by the FTP server.\n#\n# You can replace or modify this handler if you want a non-standard\n# HTTP server.  You can also derive your own handler classes from\n# it.\n#\n# support for handling POST requests is available in the derived\n# class <default_with_post_handler>, defined below.\n#\n\nfrom supervisor.medusa.counter import counter\n\nclass default_handler:\n\n    valid_commands = ['GET', 'HEAD']\n\n    IDENT = 'Default HTTP Request Handler'\n\n    # Pathnames that are tried when a URI resolves to a directory name\n    directory_defaults = [\n            'index.html',\n            'default.html'\n            ]\n\n    default_file_producer = producers.file_producer\n\n    def __init__ (self, filesystem):\n        self.filesystem = filesystem\n        # count total hits\n        self.hit_counter = counter()\n        # count file deliveries\n        self.file_counter = counter()\n        # count cache hits\n        self.cache_counter = counter()\n\n    hit_counter = 0\n\n    def __repr__ (self):\n        return '<%s (%s hits) at %x>' % (\n                self.IDENT,\n                self.hit_counter,\n                id (self)\n                )\n\n    # always match, since this is a default\n    def match (self, request):\n        return 1\n\n    # handle a file request, with caching.\n\n    def handle_request (self, request):\n\n        if request.command not in self.valid_commands:\n            request.error (400) # bad request\n            return\n\n        self.hit_counter.increment()\n\n        path, params, query, fragment = request.split_uri()\n\n        if '%' in path:\n            path = unquote (path)\n\n        # strip off all leading slashes\n        while path and path[0] == '/':\n            path = path[1:]\n\n        if self.filesystem.isdir (path):\n            if path and path[-1] != '/':\n                request['Location'] = 'http://%s/%s/' % (\n                        request.channel.server.server_name,\n                        path\n                        )\n                request.error (301)\n                return\n\n            # we could also generate a directory listing here,\n            # may want to move this into another method for that\n            # purpose\n            found = 0\n            if path and path[-1] != '/':\n                path += '/'\n            for default in self.directory_defaults:\n                p = path + default\n                if self.filesystem.isfile (p):\n                    path = p\n                    found = 1\n                    break\n            if not found:\n                request.error (404) # Not Found\n                return\n\n        elif not self.filesystem.isfile (path):\n            request.error (404) # Not Found\n            return\n\n        file_length = self.filesystem.stat (path)[stat.ST_SIZE]\n\n        ims = get_header_match (IF_MODIFIED_SINCE, request.header)\n\n        length_match = 1\n        if ims:\n            length = ims.group (4)\n            if length:\n                try:\n                    length = int(length)\n                    if length != file_length:\n                        length_match = 0\n                except:\n                    pass\n\n        ims_date = 0\n\n        if ims:\n            ims_date = http_date.parse_http_date (ims.group (1))\n\n        try:\n            mtime = self.filesystem.stat (path)[stat.ST_MTIME]\n        except:\n            request.error (404)\n            return\n\n        if length_match and ims_date:\n            if mtime <= ims_date:\n                request.reply_code = 304\n                request.done()\n                self.cache_counter.increment()\n                return\n        try:\n            file = self.filesystem.open (path, 'rb')\n        except IOError:\n            request.error (404)\n            return\n\n        request['Last-Modified'] = http_date.build_http_date (mtime)\n        request['Content-Length'] = file_length\n        self.set_content_type (path, request)\n\n        if request.command == 'GET':\n            request.push (self.default_file_producer (file))\n\n        self.file_counter.increment()\n        request.done()\n\n    def set_content_type (self, path, request):\n        typ, encoding = mimetypes.guess_type(path)\n        if typ is not None:\n            request['Content-Type'] = typ\n        else:\n            # TODO: test a chunk off the front of the file for 8-bit\n            # characters, and use application/octet-stream instead.\n            request['Content-Type'] = 'text/plain'\n\n    def status (self):\n        return producers.simple_producer (\n                '<li>%s' % html_repr (self)\n                + '<ul>'\n                + '  <li><b>Total Hits:</b> %s'                 % self.hit_counter\n                + '  <li><b>Files Delivered:</b> %s'    % self.file_counter\n                + '  <li><b>Cache Hits:</b> %s'                 % self.cache_counter\n                + '</ul>'\n                )\n\n# HTTP/1.0 doesn't say anything about the \"; length=nnnn\" addition\n# to this header.  I suppose its purpose is to avoid the overhead\n# of parsing dates...\nIF_MODIFIED_SINCE = re.compile (\n        'If-Modified-Since: ([^;]+)((; length=([0-9]+)$)|$)',\n        re.IGNORECASE\n        )\n\nUSER_AGENT = re.compile ('User-Agent: (.*)', re.IGNORECASE)\n\nCONTENT_TYPE = re.compile (\n        r'Content-Type: ([^;]+)((; boundary=([A-Za-z0-9\\'\\(\\)+_,./:=?-]+)$)|$)',\n        re.IGNORECASE\n        )\n\nget_header = http_server.get_header\nget_header_match = http_server.get_header_match\n\ndef get_extension (path):\n    dirsep = path.rfind('/')\n    dotsep = path.rfind('.')\n    if dotsep > dirsep:\n        return path[dotsep+1:]\n    else:\n        return ''\n"
  },
  {
    "path": "supervisor/medusa/docs/README.html",
    "content": "<html>\n<body>\n\n<h1> What is Medusa? </h1>\n<hr>\n\n<p>\nMedusa is an architecture for very-high-performance TCP/IP servers\n(like HTTP, FTP, and NNTP).  Medusa is different from most other\nservers because it runs as a single process, multiplexing I/O with its\nvarious client and server connections within a single process/thread.\n\n<p>\nIt is capable of smoother and higher performance than most other\nservers, while placing a dramatically reduced load on the server\nmachine.  The single-process, single-thread model simplifies design\nand enables some new persistence capabilities that are otherwise\ndifficult or impossible to implement.\n\n<p>\nMedusa is supported on any platform that can run Python and includes a\nfunctional implementation of the &lt;socket&gt; and &lt;select&gt;\nmodules.  This includes the majority of Unix implementations.\n\n<p>\nDuring development, it is constantly tested on Linux and Win32\n[Win95/WinNT], but the core asynchronous capability has been shown to\nwork on several other platforms, including the Macintosh.  It might\neven work on VMS.\n\n\n<h2>The Power of Python</h2>\n\n<p>\nA distinguishing feature of Medusa is that it is written entirely in\nPython.  Python (<a href=\"http://www.python.org/\">http://www.python.org/</a>) is a\n'very-high-level' object-oriented language developed by Guido van\nRossum (currently at CNRI).  It is easy to learn, and includes many\nmodern programming features such as storage management, dynamic\ntyping, and an extremely flexible object system.  It also provides\nconvenient interfaces to C and C++.\n\n<p>\nThe rapid prototyping and delivery capabilities are hard to exaggerate;\nfor example\n<ul>\n\n  <li>It took me longer to read the documentation for persistent HTTP\n  connections (the 'Keep-Alive' connection token) than to add the\n  feature to Medusa.\n\n  <li>A simple IRC-like chat server system was written in about 90 minutes.\n\n</ul>\n\n<p> I've heard similar stories from alpha test sites, and other users of\nthe core async library.\n\n<h2>Server Notes</h2>\n\n<p>Both the FTP and HTTP servers use an abstracted 'filesystem object' to\ngain access to a given directory tree.  One possible server extension\ntechnique would be to build behavior into this filesystem object,\nrather than directly into the server: Then the extension could be\nshared with both the FTP and HTTP servers.\n\n<h3>HTTP</h3>\n\n<p>The core HTTP server itself is quite simple - all functionality is\nprovided through 'extensions'.  Extensions can be plugged in\ndynamically. [i.e., you could log in to the server via the monitor\nservice and add or remove an extension on the fly].  The basic\nfile-delivery service is provided by a 'default' extension, which\nmatches all URI's.  You can build more complex behavior by replacing\nor extending this class.\n\n\n<p>The default extension includes support for the 'Connection: Keep-Alive'\ntoken, and will re-use a client channel when requested by the client.\n\n<h3>FTP</h3>\n\n<p>On Unix, the ftp server includes support for 'real' users, so that it\nmay be used as a drop-in replacement for the normal ftp server.  Since\nmost ftp servers on Unix use the 'forking' model, each child process\nchanges its user/group persona after a successful login.  This is a\nappears to be a secure design.\n\n\n<p>Medusa takes a different approach - whenever Medusa performs an\noperation for a particular user [listing a directory, opening a file],\nit temporarily switches to that user's persona _only_ for the duration\nof the operation.  [and each such operation is protected by a\ntry/finally exception handler].\n\n\n<p>To do this  Medusa MUST run  with super-user privileges.  This is a\nHIGHLY experimental   approach, and although   it has  been thoroughly\ntested    on Linux, security problems  may    still exist.  If you are\nconcerned  about the security of your   server machine, AND YOU SHOULD\nBE,  I suggest running  Medusa's ftp  server  in anonymous-only  mode,\nunder an account with limited privileges ('nobody' is usually used for\nthis purpose).\n\n\n<p>I am   very  interested  in any feedback    on  this feature,  most\nespecially   information  on how     the server behaves  on  different\nimplementations of Unix, and of course  any security problems that are\nfound.\n\n<hr>\n\n<h3>Monitor</h3>\n\n<p>The monitor server gives you remote, 'back-door' access to your server\nwhile it is running.  It implements a remote python interpreter.  Once\nconnected to the monitor, you can do just about anything you can do from\nthe normal python interpreter.  You can examine data structures, servers,\nconnection objects.  You can enable or disable extensions, restart the server,\nreload modules, etc...\n\n<p>The monitor server   is protected with an MD5-based  authentication\nsimilar to that proposed in RFC1725 for the POP3 protocol.  The server\nsends the  client a  timestamp,  which  is then  appended to  a secret\npassword.  The resulting md5 digest is  sent back to the server, which\nthen compares this to the  expected result.  Failed login attempts are\nlogged and immediately disconnected.  The  password itself is not sent\nover the network (unless you  have  foolishly transmitted it  yourself\nthrough an insecure telnet or X11 session. 8^)\n\n<p>For this  reason telnet  cannot be used  to connect  to the monitor\nserver when it is in a secure mode (the default).  A client program is\nprovided for this  purpose.  You will  be prompted for a password when\nstarting up the server, and by the monitor client.\n\n<p>For  extra added   security  on   Unix,  the monitor   server  will\neventually be able to use a Unix-domain socket, which can be protected\nbehind a 'firewall' directory (similar to the InterNet News server).\n\n<hr>\n<h2>Performance Notes</h2>\n\n<h3>The <code>select()</code> function</h3>\n\n<p>At  the  heart of  Medusa  is  a single <code>select()</code> loop.\nThis loop   handles all  open  socket connections,  both   servers and\nclients.  It  is  in effect  constantly  asking the  system: 'which of\nthese sockets has activity?'.   Performance  of this system  call  can\nvary widely between operating systems.\n\n<p>There  are also often builtin limitations  to the number of sockets\n('file descriptors')  that a single  process,  or a whole system,  can\nmanipulate at the same time.  Early versions of Linux placed draconian\nlimits (256) that  have since been raised.  Windows  95 has a limit of\n64, while OSF/1 seems to allow up to 4096.\n\n<p>These limits don't affect only Medusa, you will find them described\nin the documentation for other web and ftp servers, too.\n\n<p>The documentation for the Apache web server has some excellent\nnotes on tweaking performance for various Unix implementations.  See\n<a href=\"http://www.apache.org/docs/misc/perf.html\">\nhttp://www.apache.org/docs/misc/perf.html</a>\nfor more information.\n\n<h3>Buffer sizes</h3>\n\n<p>\nThe default buffer sizes  used by Medusa  are  set with a  bias toward\nInternet-based servers: They are  relatively small, so that the buffer\noverhead for each connection is  low.   The assumption is that  Medusa\nwill be talking to a large number of low-bandwidth connections, rather\nthan a smaller number of high bandwidth.\n\n<p>This choice  trades run-time memory use for   efficiency - the down\nside of this is that high-speed local connections  (i.e., over a local\nethernet) will transfer data at a slower rate than necessary.\n\n<p>This parameter can easily be tweaked by  the site designer, and can\nin fact  be adjusted on  a per-server  or  even per-client basis.  For\nexample, you could  have the  FTP server  use larger  buffer sizes for\nconnections from certain domains.\n\n<p>If there's enough interest, I have some rough ideas for how to make\nthese  buffer sizes automatically adjust  to an optimal setting.  Send\nemail if you'd like to see this feature.\n\n<hr>\n\n<p>See <a href=\"medusa.html\">./medusa.html</a> for a brief overview of\nsome of the ideas behind Medusa's design, and for a description of\ncurrent and upcoming features.\n\n<p><h3>Enjoy!</h3>\n\n<hr>\n<br>-Sam Rushing\n<br><a href=\"mailto:rushing@nightmare.com\">rushing@nightmare.com</a>\n\n<!--\n  Local Variables:\n  indent-use-tabs: nil\n  end:\n-->\n\n</body>\n</html>\n"
  },
  {
    "path": "supervisor/medusa/docs/async_blurbs.txt",
    "content": "\n[from the win32 sdk named pipe documentation]\n\n==================================================\nThe simplest server process can use the CreateNamedPipe function to\ncreate a single instance of a pipe, connect to a single client,\ncommunicate with the client, disconnect the pipe, close the pipe\nhandle, and terminate. Typically, however, a server process must\ncommunicate with multiple client processes. A server process can use a\nsingle pipe instance by connecting to and disconnecting from each\nclient in sequence, but performance would be poor. To handle multiple\nclients simultaneously, the server process must create multiple pipe\ninstances.\n\nThere are three basic strategies for servicing multiple pipe instances.\n\n Create multiple threads (and/or processes) with a separate thread\nfor each instance of the pipe. For an example of a multithreaded\nserver process, see Multithreaded Server.\n\n Overlap operations by specifying an OVERLAPPED structure in the\nReadFile, WriteFile, and ConnectNamedPipe functions. For an example of\na server process that uses overlapped operations, see Server Using\nOverlapped Input and Output.\n\n Overlap operations by using the ReadFileEx and WriteFileEx\nfunctions, which specify a completion routine to be executed when the\noperation is complete. For an example of a server process that uses\ncompletion routines, see Server Using Completion Routines.\n\n \nThe multithreaded server strategy is easy to write, because the thread\nfor each instance handles communications for only a single client. The\nsystem allocates processor time to each thread as needed. But each\nthread uses system resources, which is a potential disadvantage for a\nserver that handles a large number of clients. Other complications\noccur if the actions of one client necessitate communications with\nother clients (as for a network game program, where a move by one\nplayer must be communicated to the other players).\n\nWith a single-threaded server, it is easier to coordinate operations\nthat affect multiple clients, and it is easier to protect shared\nresources (for example, a database file) from simultaneous access by\nmultiple clients. The challenge of a single-threaded server is that it\nrequires coordination of overlapped operations in order to allocate\nprocessor time for handling the simultaneous needs of the clients.\n\n==================================================\n"
  },
  {
    "path": "supervisor/medusa/docs/data_flow.html",
    "content": "\n<h1>Data Flow in Medusa</h1>\n\n<img src=\"data_flow.gif\">\n\n<p>Data flow, both input and output, is asynchronous.  This is\nsignified by the <i>request</i> and <i>reply</i> queues in the above\ndiagram.  This means that both requests and replies can get 'backed\nup', and are still handled correctly.  For instance, HTTP/1.1 supports\nthe concept of <i>pipelined requests</i>, where a series of requests\nare sent immediately to a server, and the replies are sent as they are\nprocessed.  With a <i>synchronous</i> request, the client would have\nto wait for a reply to each request before sending the next.</p>\n\n<p>The input data is partitioned into requests by looking for a\n<i>terminator</i>.  A terminator is simply a protocol-specific\ndelimiter - often simply CRLF (carriage-return line-feed), though it\ncan be longer (for example, MIME multi-part boundaries can be\nspecified as terminators).  The protocol handler is notified whenever\na complete request has been received.</p>\n\n<p>The protocol handler then generates a reply, which is enqueued for\noutput back to the client.  Sometimes, instead of queuing the actual\ndata, an object that will generate this data is used, called a\n<i>producer</i>.</p>\n\n<img src=\"producers.gif\">\n\n<p>The use of <code>producers</code> gives the programmer\nextraordinary control over how output is generated and inserted into\nthe output queue.  Though they are simple objects (requiring only a\nsingle method, <i>more()</i>, to be defined), they can be\n<i>composed</i> - simple producers can be wrapped around each other to\ncreate arbitrarily complex behaviors.  [now would be a good time to\nbrowse through some of the producer classes in\n<code>producers.py</code>.]</p>\n\n<p>The HTTP/1.1 producers make an excellent example.  HTTP allows\nreplies to be encoded in various ways - for example a reply consisting\nof dynamically-generated output might use the 'chunked' transfer\nencoding to send data that is compressed on-the-fly.</p>\n\n<img src=\"composing_producers.gif\">\n\n<p>In the diagram, green producers actually generate output, and grey\nones transform it in some manner.  This producer might generate output\nlooking like this:\n\n<pre>\n                            HTTP/1.1 200 OK\n                            Content-Encoding: gzip\n                            Transfer-Encoding: chunked\n              Header ==>    Date: Mon, 04 Aug 1997 21:31:44 GMT\n                            Content-Type: text/html\n                            Server: Medusa/3.0\n                            \n             Chunking ==>   0x200\n            Compression ==> <512 bytes of compressed html>\n                            0x200\n                            <512 bytes of compressed html>\n                            ...\n                            0\n                            \n</pre>\n\n<p>Still more can be done with this output stream: For the purpose of\nefficiency, it makes sense to send output in large, fixed-size chunks:\nThis transformation can be applied by wrapping a 'globbing' producer\naround the whole thing.</p>\n\n<p>An important feature of Medusa's producers is that they are\nactually rather small objects that do not expand into actual output\ndata until the moment they are needed: The <code>async_chat</code>\nclass will only call on a producer for output when the outgoing socket\nhas indicated that it is ready for data.  Thus Medusa is extremely\nefficient when faced with network delays, 'hiccups', and low bandwidth\nclients.\n\n<p>One final note: The mechanisms described above are completely\ngeneral - although the examples given demonstrate application to the\n<code>http</code> protocol, Medusa's asynchronous core has been\napplied to many different protocols, including <code>smtp</code>,\n<code>pop3</code>, <code>ftp</code>, and even <code>dns</code>.\n"
  },
  {
    "path": "supervisor/medusa/docs/programming.html",
    "content": "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">\n<html>\n  <head>\n    <title>Programming in Python with Medusa and the Async Sockets Library</title>\n  </head>\n\n  <body>\n    <h1>Programming in Python with Medusa and the Async Sockets Library</h1>\n\n    <h2>Introduction</h2>\n    <h3>Why Asynchronous?</h3>\n\n    <p>\n      There are only two ways to have a program on a single processor do\n      'more than one thing at a time'.  Multi-threaded programming is\n      the simplest and most popular way to do it, but there is another\n      very different technique, that lets you have nearly all the\n      advantages of multi-threading, without actually using multiple\n      threads.  It's really only practical if your program is <i>I/O\n      bound</i> (I/O is the principle bottleneck).  If your program is\n      CPU bound, then pre-emptive scheduled threads are probably what\n      you really need.  Network servers are rarely CPU-bound, however.\n    </p>\n\n    <p>\n      If your operating system supports the <code>select()</code>\n      system call in its I/O library (and nearly all do), then you can\n      use it to juggle multiple communication channels at once; doing\n      other work while your I/O is taking place in the \"background\".\n      Although this strategy can seem strange and complex (especially\n      at first), it is in many ways easier to understand and control\n      than multi-threaded programming.  The library documented here\n      solves many of the difficult problems for you, making the task\n      of building sophisticated high-performance network servers and\n      clients a snap.\n    </p>\n\n    <h3>Select-based multiplexing in the real world</h3>\n\n    <p>\n      Several well-known Web servers (and other programs) are written using\n      exactly this technique:\n      the <a href=\"http://www.acme.com/software/thttpd/\">thttpd</a>\n      and <a href=\"http://www.zeus.co.uk/\">Zeus</a>,\n      and <a href=\"http://squid.nlanr.net/\">Squid Internet Object Cache</a> servers\n      are excellent examples..\n      <a href=\"http://www.isc.org/inn.html\">The InterNet News server (INN)</a> used\n      this technique for several years before the web exploded.\n    </p>\n\n    <p>\n      An interesting web server comparison chart is available at the\n      <a href=\"http://www.acme.com/software/thttpd/benchmarks.html\">thttpd web site</a>\n    <p>\n\n    <h3>Variations on a Theme: poll() and WaitForMultipleObjects</h3>\n    <p>\n      Of similar (but better) design is the <code>poll()</code> system\n      call.  The main advantage of <code>poll()</code> (for our\n      purposes) is that it does not used fixed-size file-descriptor\n      tables, and is thus more easily scalable than\n      <code>select()</code>.  <code>poll()</code> is only recently becoming\n      widely available, so you need to check for availability on your particular\n      operating system.\n    </p>\n\n    <p>\n      In the Windows world, the Win32 API provides a bewildering array\n      of features for multiplexing.  Although slightly different in\n      semantics, the combination of Event objects and the\n      <code>WaitForMultipleObjects()</code> interface gives\n      essentially the same power as <code>select()</code> on Unix.  A\n      version of this library specific to Win32 has not been written\n      yet, mostly because Win32 also provides <code>select()</code>\n      (at least for sockets).  If such an interface were written, it\n      would have the advantage of allowing us to multiplex on other\n      objects types, like named pipes and files.\n    </p>\n\n    <h3>select()</h3>\n\n    <p>\n      Here's what <code>select()</code> does: you pass in a set of\n      file descriptors, in effect asking the operating system, \"let me\n      know when anything happens to any of these descriptors\".  (A\n      <i>descriptor</i> is simply a numeric handle used by the\n      operating system to keep track of a file, socket, pipe, or other\n      I/O object.  It is usually an index into a system table of some\n      kind).  You can also use a timeout, so that if <i>nothing</i>\n      happens in the allotted period, <code>select()</code> will return\n      control to your program.\n    </p>\n\n    <p>\n      <code>select()</code> takes three <code>fd_set</code> arguments;\n      one for each of the following possible states/events:\n      readability, writability, and exceptional conditions.  The last set\n      is less useful than it sounds; in the context of TCP/IP it refers\n      to the presence of out-of-band (OOB) data.  OOB is a relatively unportable\n      and poorly used feature that you can (and should) ignore unless you really\n      need it.\n    </p>\n\n    <p>\n      So that leaves only two types of events to build our programs\n      around; <i>read</i> events and <i>write</i> events.  As it turns\n      out, this is actually enough to get by with, because other types\n      of events can be implied by the sequencing of these two.  It\n      also keeps the low-level interface as simple as possible -\n      always a good thing in my book.\n    </p>\n\n    <h3>The polling loop</h3>\n    <p>\n      Now that you know what <code>select()</code> does, you're ready\n      for the final piece of the puzzle: the main polling loop.  This\n      is nothing more than a simple while loop that continually calls\n      <code>select()</code> with a timeout (I usually use a 30-second\n      timeout).  Such a program will use virtually no CPU if your\n      server is idle; it spends most of its time letting the operating\n      system do the waiting for it.  This is much more efficient than a\n      <a href=\"http://wombat.doc.ic.ac.uk/foldoc/foldoc.cgi?query=busy-wait\">busy-wait</a>\n      loop.\n    </p>\n    <p> Here is a pseudo-code example of a polling loop:\n    <pre>\nwhile (any_descriptors_left):\n  events = select (descriptors, timeout)\n  for event in events:\n    handle_event (event)\n</pre>\n    <p>\n      If you take a look at the code used by the library, it looks\n      very similar to this. (see the file <code>asyncore.py</code>,\n      the functions poll() and loop()).  Now, on to the magic that must\n      take place to handle the events...\n    </p>\n\n    <h2>The Code</h2>\n    <h3>Blocking vs. Non-Blocking</h3>\n    <p>\n      File descriptors can be in either blocking or non-blocking mode.\n      A descriptor in blocking mode will stop (or 'block') your entire\n      program until the requested event takes place.  For example, if\n      you ask to read 64 bytes from a descriptor attached to a socket\n      which is ultimately connected to a modem deep in the backwaters\n      of the Internet, you may wait a while for those 64 bytes.\n    </p>\n    <p>\n      If you put the descriptor in non-blocking mode, then one of two\n      things might happen: if the data is sitting in a local buffer,\n      it will be returned to you immediately; otherwise you will get\n      back a code (usually <code>EWOULDBLOCK</code>) telling you that\n      the read is in progress, and you should check back later to see\n      if it's done.\n    <p>\n\n    <h3>sockets vs. other kinds of descriptors</h3>\n\n    <p>\n      Although most of our discussion will be about TCP/IP sockets, on\n      Unix you can use <code>select()</code> to multiplex other kinds\n      of communications objects, like pipes and ttys.  (Unfortunately,\n      select() cannot be used to do non-blocking file I/O.  Please\n      correct me if you have information to the contrary!)\n    </p>\n\n    <h3>The socket_map</h3>\n    <p>\n      We use a global dictionary (<code>asyncore.socket_map</code>) to\n      keep track of all the active socket objects.  The keys for this\n      dictionary are the objects themselves.  Nothing is stored in the\n      value slot.  Each time through the loop, this dictionary is scanned.\n      Each object is asked which <code>fd_sets</code> it wants to be in.\n      These sets are then passed on to <code>select()</code>.\n    </p>\n\n    <h3>asyncore.dispatcher</h3>\n    <p>\n      The first class we'll introduce you to is the\n      <code>dispatcher</code> class.  This is a thin wrapper around a\n      low-level socket object.  We have attached a few methods for\n      event-handling to it.  Otherwise, it can be treated as a normal\n      non-blocking socket object.\n    </p>\n    <p>\n      The direct interface between the select loop and the socket object\n      are the <code>handle_read_event</code> and <code>handle_write_event</code>\n      methods.  These are called whenever an object 'fires' that event.\n    </p>\n    <p>\n      The firing of these low-level events can tell us whether certain\n      higher-level events have taken place, depending on the timing\n      and state of the connection.  For example, if we have asked for\n      a socket to connect to another host, we know that the connection\n      has been made when the socket fires a write event (at this point\n      you know that you may write to it with the expectation of\n      success).\n      <br>\n      The implied events are\n    <ul>\n      <li>handle_connect.\n\t<br>implied by a write event.\n      <li>handle_close\n\t<br>implied by a read event with no data available.\n      <li>handle_accept\n\t<br>implied by a read event on a listening socket.\n    </ul>\n    </p>\n    <p>\n      Thus, the set of user-level events is a little larger than simply\n      <code>readable</code> and <code>writeable</code>.  The full set of\n      events your code may handle are:\n    <ul>\n      <li>handle_read\n      <li>handle_write\n      <li>handle_expt (OOB data)\n      <li>handle_connect\n      <li>handle_close\n      <li>handle_accept\n    </ul>\n\n    <p>\n      A quick terminology note: In order to distinguish between\n      low-level socket objects and those based on the async library\n      classes, I call these higher-level objects <i>channels</i>.\n\n    </p>\n    <h3>Enough Gibberish, let's write some code</h3>\n    <p>\n      Ok, that's enough abstract talk.  Let's do something useful and\n      concrete with this stuff.  We'll write a simple HTTP client that\n      demonstrates how easy it is to build a powerful tool in only a few\n      lines of code.\n    </p>\n\n<pre>\n<font color=\"800000\"># -*- Mode: Python; tab-width: 4 -*-</font>\n\n<font color=\"808000\">import</font> asyncore\n<font color=\"808000\">import</font> socket\n<font color=\"808000\">import</font> string\n\n<font color=\"808000\">class</font><font color=\"000080\"> http_client</font> (asyncore.dispatcher):\n\n    <font color=\"808000\">def</font><font color=\"000080\"> __init__</font> (self, host, path):\n        asyncore.dispatcher.__init__ (self)\n        self.path = path\n        self.create_socket (socket.AF_INET, socket.SOCK_STREAM)\n        self.connect ((host, 80))\n\n    <font color=\"808000\">def</font><font color=\"000080\"> handle_connect</font> (self):\n        self.send (<font color=\"008000\">'GET %s HTTP/1.0\\r\\n\\r\\n'</font> % self.path)\n\n    <font color=\"808000\">def</font><font color=\"000080\"> handle_read</font> (self):\n        data = self.recv (8192)\n        <font color=\"808000\">print</font> data\n\n    <font color=\"808000\">def</font><font color=\"000080\"> handle_write</font> (self):\n        <font color=\"808000\">pass</font>\n\n<font color=\"808000\">if</font> __name__ == <font color=\"008000\">'__main__'</font>:\n    <font color=\"808000\">import</font> sys\n    <font color=\"808000\">import</font> urlparse\n    <font color=\"808000\">for</font> url <font color=\"808000\">in</font> sys.argv[1:]:\n        parts = urlparse.urlparse (url)\n        <font color=\"808000\">if</font> parts[0] != <font color=\"008000\">'http'</font>:\n            <font color=\"808000\">raise</font> <font color=\"008000\">ValueError(\"HTTP URL's only, please\")</font>\n        <font color=\"808000\">else</font>:\n            host = parts[1]\n            path = parts[2]\n            http_client (host, path)\n    asyncore.loop()\n\n</pre>\n\n\n<!-- Thanks to Just van Rossum for PyFontify.py! -->\n</pre>\n\n    <p>\n      HTTP is (in theory, at least) a very simple protocol.  You connect to the\n      web server, send the string <code>\"GET /some/path HTTP/1.0\"</code>, and the\n      server will send a short header, followed by the file you asked for.  It will\n      then close the connection.\n    <p>\n      We have defined a single new class, <code>http_client</code>, derived\n      from the abstract class <code>asyncore.dispatcher</code>.  There are three\n      event handlers defined.<ul>\n      <li><code>handle_connect</code>\n\t<br>Once we have made the connection, we send the request string.\n      <li><code>handle_read</code>\n\t<br>As the server sends data back to us, we simply print it out.\n      <li><code>handle_write</code>\n\t<br>Ignore this for the moment, I'm brushing over a technical detail\n\twe'll clean up in a moment.\n    </ul>\n\n    <p>Go ahead and run this demo - giving a single URL as an argument, like this:\n    <p><font color=\"006000\"><code>$ python asynhttp.py http://www.nightmare.com/</code></font>\n    <p>You should see something like this:\n    <p>\n\n<pre>\n[rushing@gnome demo]$ python asynhttp.py http://www.nightmare.com/\nlog: adding channel &lt;http_client  at 80ef3e8&gt;\nHTTP/1.0 200 OK\nServer: Medusa/3.19\nContent-Type: text/html\nContent-Length: 1649\nLast-Modified: Sun, 26 Jul 1998 23:57:51 GMT\nDate: Sat, 16 Jan 1999 13:04:30 GMT\n\n[... body of the file ...]\n\nlog: unhandled close event\nlog: closing channel 4:&lt;http_client connected at 80ef3e8&gt;\n\n</pre>\n\n    <p>\n      The 'log' messages are there to help, they are useful when\n      debugging but you will want to disable them later.  The first log message\n      tells you that a new <code>http_client</code> object has been added to the\n      socket map.  At the end, you'll notice there's a warning that you haven't\n      bothered to handle the <code>close</code> event.  No big deal, for now.\n\n    <p>\n      Now at this point we haven't seen anything <i>revolutionary</i>, but that's\n      because we've only looked at one URL.  Go ahead and add a few other URL's\n      to the argument list; as many as you like - and make sure they're on different\n      hosts...\n\n    <p>\n      <i>Now</i> you begin to see why <code>select()</code> is so powerful.  Depending\n      on your operating system (and its configuration), <code>select()</code> can be\n      fed hundreds, or even thousands of descriptors like this.  (I've recently tested\n      <code>select()</code> on a FreeBSD box with over 10,000 descriptors).\n\n    <p>\n      A really good way to understand <code>select()</code> is to put a print statement\n      into the asyncore.poll() function:\n<pre>\n        [...]\n        (r,w,e) = select.select (r,w,e, timeout)\n        print '---'\n        print 'read', r\n        print 'write', w\n        [...]\n</pre>\n\n    <p>\n      Each time through the loop you will see which channels have fired\n      which events.  If you haven't skipped ahead, you'll also notice a pointless\n      barrage of events, with all your http_client objects in the 'writable' set.\n      This is because we were a bit lazy earlier; sweeping some ugliness under\n      the rug.  Let's fix that now.\n\n    <h3>Buffered Output</h3>\n    <p>\n      In our <code>handle_connect</code>, we cheated a bit by calling\n      <code>send</code> without examining its return code.  In truth,\n      since we are using a non-blocking socket, it's (theoretically)\n      possible that our data didn't get sent.  To do this <i>correctly</i>,\n      we actually need to set up a buffer of outgoing data, and then send\n      as much of the buffer as we can whenever we see a <code>write</code>\n      event:\n\n<pre>\n\n<font color=\"808000\">class</font><font color=\"000080\"> http_client</font> (asyncore.dispatcher):\n\n    <font color=\"808000\">def</font><font color=\"000080\"> __init__</font> (self, host, path):\n        asyncore.dispatcher.__init__ (self)\n        self.path = path\n        self.create_socket (socket.AF_INET, socket.SOCK_STREAM)\n        self.connect ((host, 80))\n        self.buffer = <font color=\"008000\">'GET %s HTTP/1.0\\r\\n\\r\\n'</font> % self.path\n\n    <font color=\"808000\">def</font><font color=\"000080\"> handle_connect</font> (self):\n        <font color=\"808000\">pass</font>\n\n    <font color=\"808000\">def</font><font color=\"000080\"> handle_read</font> (self):\n        data = self.recv (8192)\n        <font color=\"808000\">print</font> data\n\n    <font color=\"808000\">def</font><font color=\"000080\"> writable</font> (self):\n        <font color=\"808000\">return</font> (len(self.buffer) &gt; 0)\n\n    <font color=\"808000\">def</font><font color=\"000080\"> handle_write</font> (self):\n        sent = self.send (self.buffer)\n        self.buffer = self.buffer[sent:]\n</pre>\n\n    <p>\n      The <code>handle_connect</code> method no longer assumes it can\n      send its request string successfully.  We move its work over to\n      <code>handle_write</code>; which trims <code>self.buffer</code>\n      as pieces of it are sent successfully.\n\n    <p>\n      We also introduce the <code>writable</code> method.  Each time\n      through the loop, the set of sockets is scanned, the\n      <code>readable</code> and <code>writable</code> methods of each\n      object are called to see if are interested in those events.  The\n      default methods simply return 1, indicating that by default all\n      channels will be in both sets.  In this case, however, we are only\n      interested in writing as long as we have something to write.  So\n      we override this method, making its behavior dependent on the length\n      of <code>self.buffer</code>.\n\n    <p>\n      If you try the client now (with the print statements in\n      <code>asyncore.poll()</code>), you'll see that\n      <code>select</code> is firing more efficiently.\n\n    <h3>asynchat.py</h3>\n    <p>\n      The dispatcher class is useful, but somewhat limited in\n      capability.  As you might guess, managing input and output\n      buffers manually can get complex, especially if you're working\n      with a protocol more complicated than HTTP.\n\n    <p>\n      The <code>async_chat</code> class does a lot of the heavy\n      lifting for you.  It automatically handles the buffering of both\n      input and output, and provides a \"line terminator\" facility that\n      partitions an input stream into logical lines for you.  It is\n      also carefully designed to support <i>pipelining</i> - a nice\n      feature that we'll explain later.\n\n    <p>\n      There are four new methods to introduce:\n    <ul>\n\n      <li><code>set_terminator (self, &lt;eol-string&gt;)</code>\n\t<br>Set the string used to identify <i>end-of-line</i>.  For most\n\tInternet protocols, this is the string <code>\\r\\n</code>, that is;\n\ta carriage return followed by a line feed.  To turn off input scanning,\n\tuse <code>None</code>\n\n      <li><code>collect_incoming_data (self, data)</code>\n\t<br>Called whenever data is available from\n\ta socket.  Usually, your implementation will accumulate this\n\tdata into a buffer of some kind.\n\n      <li><code>found_terminator (self)</code>\n\t<br>Called whenever an end-of-line marker has been seen.  Typically\n\tyour code will process and clear the input buffer.\n\n      <li><code>push (data)</code>\n\t<br>This is a buffered version of <code>send</code>.  It will place\n\tthe data in an outgoing buffer.\n\n    </ul>\n    <p>\n      These methods build on the underlying capabilities of\n      <code>dispatcher</code> by providing implementations of\n      <code>handle_read</code> <code>handle_write</code>, etc...\n      <code>handle_read</code> collects data into an input buffer, which\n      is continually scanned for the terminator string.  Data in between\n      terminators is feed to your <code>collect_incoming_data</code> method.\n\n    <p>\n      The implementation of <code>handle_write</code> and <code>writable</code>\n      examine an outgoing-data queue, and automatically send data whenever\n      possible.\n\n    <h3>A Proxy Server</h3>\n      In order to demonstrate the <code>async_chat</code> class, we will\n      put together a simple proxy server.  A proxy server combines a server\n      and a client together, in effect sitting between the real server and\n      client.  You can use this to monitor or debug protocol traffic.\n\n<pre>\n\n<font color=\"800000\"># -*- Mode: Python; tab-width: 4 -*-</font>\n\n<font color=\"808000\">import</font> asynchat\n<font color=\"808000\">import</font> asyncore\n<font color=\"808000\">import</font> socket\n<font color=\"808000\">import</font> string\n\n<font color=\"808000\">class</font><font color=\"000080\"> proxy_server</font> (asyncore.dispatcher):\n\n    <font color=\"808000\">def</font><font color=\"000080\"> __init__</font> (self, host, port):\n        asyncore.dispatcher.__init__ (self)\n        self.create_socket (socket.AF_INET, socket.SOCK_STREAM)\n        self.set_reuse_addr()\n        self.there = (host, port)\n        here = (<font color=\"008000\">''</font>, port + 8000)\n        self.bind (here)\n        self.listen (5)\n\n    <font color=\"808000\">def</font><font color=\"000080\"> handle_accept</font> (self):\n        proxy_receiver (self, self.accept())\n\n<font color=\"808000\">class</font><font color=\"000080\"> proxy_sender</font> (asynchat.async_chat):\n\n    <font color=\"808000\">def</font><font color=\"000080\"> __init__</font> (self, receiver, address):\n        asynchat.async_chat.__init__ (self)\n        self.receiver = receiver\n        self.set_terminator (None)\n        self.create_socket (socket.AF_INET, socket.SOCK_STREAM)\n        self.buffer = <font color=\"008000\">''</font>\n        self.set_terminator (<font color=\"008000\">'\\n'</font>)\n        self.connect (address)\n\n    <font color=\"808000\">def</font><font color=\"000080\"> handle_connect</font> (self):\n        <font color=\"808000\">print</font> <font color=\"008000\">'Connected'</font>\n\n    <font color=\"808000\">def</font><font color=\"000080\"> collect_incoming_data</font> (self, data):\n        self.buffer = self.buffer + data\n\n    <font color=\"808000\">def</font><font color=\"000080\"> found_terminator</font> (self):\n        data = self.buffer\n        self.buffer = <font color=\"008000\">''</font>\n        <font color=\"808000\">print</font> <font color=\"008000\">'==&gt; (%d) %s'</font> % (self.id, repr(data))\n        self.receiver.push (data + <font color=\"008000\">'\\n'</font>)\n\n     <font color=\"808000\">def</font><font color=\"000080\"> handle_close</font> (self):\n         self.receiver.close()\n         self.close()\n\n<font color=\"808000\">class</font><font color=\"000080\"> proxy_receiver</font> (asynchat.async_chat):\n\n    channel_counter = 0\n\n    <font color=\"808000\">def</font><font color=\"000080\"> __init__</font> (self, server, (conn, addr)):\n        asynchat.async_chat.__init__ (self, conn)\n        self.set_terminator (<font color=\"008000\">'\\n'</font>)\n        self.server = server\n        self.id = self.channel_counter\n        self.channel_counter = self.channel_counter + 1\n        self.sender = proxy_sender (self, server.there)\n        self.sender.id = self.id\n        self.buffer = <font color=\"008000\">''</font>\n\n    <font color=\"808000\">def</font><font color=\"000080\"> collect_incoming_data</font> (self, data):\n        self.buffer = self.buffer + data\n\n    <font color=\"808000\">def</font><font color=\"000080\"> found_terminator</font> (self):\n        data = self.buffer\n        self.buffer = <font color=\"008000\">''</font>\n        <font color=\"808000\">print</font> <font color=\"008000\">'&lt;== (%d) %s'</font> % (self.id, repr(data))\n        self.sender.push (data + <font color=\"008000\">'\\n'</font>)\n\n     <font color=\"808000\">def</font><font color=\"000080\"> handle_close</font> (self):\n         <font color=\"808000\">print</font> <font color=\"008000\">'Closing'</font>\n         self.sender.close()\n         self.close()\n\n<font color=\"808000\">if</font> __name__ == <font color=\"008000\">'__main__'</font>:\n    <font color=\"808000\">import</font> sys\n    <font color=\"808000\">import</font> string\n    <font color=\"808000\">if</font> len(sys.argv) &lt; 3:\n        <font color=\"808000\">print</font> <font color=\"008000\">'Usage: %s &lt;server-host&gt; &lt;server-port&gt;'</font> % sys.argv[0]\n    <font color=\"808000\">else</font>:\n        ps = proxy_server(sys.argv[1], int(sys.argv[2]))\n        asyncore.loop()\n\n</pre>\n\n    <p>\n      To try out the proxy, find a server (any SMTP, NNTP, or HTTP server should do fine),\n      and give its hostname and port as arguments:\n\n<pre>\npython proxy.py localhost 25\n</pre>\n\n    <p>\n      The proxy server will start up its server on port <code>n +\n      8000</code>, in this case port 8025.  Now, use a telnet program\n      to connect to that port on your server host.  Issue a few\n      commands.  See how the whole session is being echoed by your\n      proxy server.  Try opening up several simultaneous connections\n      through your proxy.  You might also try pointing a real client\n      (a news reader [port 119] or web browser [port 80]) at your proxy.\n\n    <h3>Pipelining</h3>\n    <p>\n      Pipelining refers to a protocol capability.  Normally, a conversation\n      with a server has a back-and-forth quality to it.  The client sends a\n      command, and waits for the response.  If a client needs to send many commands\n      over a high-latency connection, waiting for each response can take a long\n      time.\n    <p>\n      For example, when sending a mail message to many recipients with\n      SMTP, the client will send a series of <code>RCPT</code>\n      commands, one for each recipient.  For each of these commands,\n      the server will send back a reply indicating whether the mailbox\n      specified is valid.  If you want to send a message to several\n      hundred recipients, this can be rather tedious if the round-trip\n      time for each command is long. You'd like to be able to send a\n      bunch of <code>RCPT</code> commands in one batch, and then count\n      off the responses to them as they come.\n\n    <p>\n      I have a favorite visual when explaining the advantages of\n      pipelining.  Imagine each request to the server is a boxcar on a\n      train.  The client is in Los Angeles, and the server is in New\n      York.  Pipelining lets you hook all your cars in one long chain;\n      send them to New York, where they are filled and sent back to you.\n      Without pipelining you have to send one car at a time.\n\n    <p>\n      Not all protocols allow pipelining.  Not all servers support it;\n      Sendmail, for example, does not support pipelining because it tends\n      to fork unpredictably, leaving buffered data in a questionable state.\n      A recent extension to the SMTP protocol allows a server to specify\n      whether it supports pipelining.  HTTP/1.1 explicitly requires that\n      a server support pipelining.\n\n    <p>\n      Servers built on top of <code>async_chat</code> automatically\n      support pipelining.  It is even possible to change the\n      terminator repeatedly when processing data already in the\n      input buffer.  See the <code>handle_read</code> method if you're\n      interested in the gory details.\n\n    <h3>Producers</h3>\n\n    <p>\n      <code>async_chat</code> supports a sophisticated output\n      buffering model, using a queue of data-producing objects.  For\n      most purposes, you will use the <code>push()</code> method to\n      send string data - but for more sophisticated usage you can push\n      a <code>producer</code>\n\n    <p>\n      A <code>producer</code> is a very simple object, requiring only\n      a single method in its implementation, <code>more()</code>.  See\n      the code for <code>simple_producer</code> in\n      <code>asynchat.py</code> for an example.  Many more examples are\n      available in the Medusa distribution, in the file\n      <code>producers.py</code>\n\n  <hr>\n  <address><a href=\"mailto:rushing@nightmare.com\">Samual M. Rushing</a></address>\n<!-- Created: Mon Jan 11 03:53:15 PST 1999 -->\n<!-- hhmts start -->\nLast modified: Fri Apr 30 21:42:52 PDT 1999\n<!-- hhmts end -->\n  </body>\n</html>\n"
  },
  {
    "path": "supervisor/medusa/docs/proxy_notes.txt",
    "content": "\n# we can build 'promises' to produce external data.  Each producer\n# contains a 'promise' to fetch external data (or an error\n# message). writable() for that channel will only return true if the\n# top-most producer is ready.  This state can be flagged by the dns\n# client making a callback.\n\n# So, say 5 proxy requests come in, we can send out DNS queries for\n# them immediately.  If the replies to these come back before the\n# promises get to the front of the queue, so much the better: no\n# resolve delay. 8^)\n#\n# ok, there's still another complication:\n# how to maintain replies in order?\n# say three requests come in, (to different hosts?  can this happen?)\n# yet the connections happen third, second, and first.  We can't buffer\n# the entire request!  We need to be able to specify how much to buffer.\n#\n# ===========================================================================\n#\n# the current setup is a 'pull' model:  whenever the channel fires FD_WRITE,\n# we 'pull' data from the producer fifo.  what we need is a 'push' option/mode,\n# where\n# 1) we only check for FD_WRITE when data is in the buffer\n# 2) whoever is 'pushing' is responsible for calling 'refill_buffer()'\n#\n# what is necessary to support this 'mode'?\n# 1) writable() only fires when data is in the buffer\n# 2) refill_buffer() is only called by the 'pusher'.\n# \n# how would such a mode affect things?  with this mode could we support\n# a true http/1.1 proxy?  [i.e, support <n> pipelined proxy requests, possibly\n# to different hosts, possibly even mixed in with non-proxy requests?]  For\n# example, it would be nice if we could have the proxy automatically apply the\n# 1.1 chunking for 1.0 close-on-eof replies when feeding it to the client. This\n# would let us keep our persistent connection.\n"
  },
  {
    "path": "supervisor/medusa/docs/threads.txt",
    "content": "# -*- Mode: Text; tab-width: 4 -*-\n\n[note, a better solution is now available, see the various modules in\n the 'thread' directory (SMR 990105)]\n\n\t\t  A Workable Approach to Mixing Threads and Medusa.\n---------------------------------------------------------------------------\n\nWhen Medusa receives a request that needs to be handled by a separate\nthread, have the thread remove the socket from Medusa's control, by\ncalling the 'del_channel()' method, and put the socket into\nblocking-mode:\n\n    request.channel.del_channel()\n    request.channel.socket.setblocking (0)\n\nNow your thread is responsible for managing the rest of the HTTP\n'session'.  In particular, you need to send the HTTP response, followed\nby any headers, followed by the response body.\n\nSince the most common need for mixing threads and Medusa is to support\nCGI, there's one final hurdle that should be pointed out: CGI scripts\nsometimes make use of a 'Status:' hack (oops, I meant to say 'header')\nin order to tell the server to return a reply other than '200 OK'.  To\nsupport this it is necessary to scan the output _before_ it is sent.\nHere is a sample 'write' method for a file-like object that performs\nthis scan:\n\nHEADER_LINE = regex.compile ('\\([A-Za-z0-9-]+\\): \\(.*\\)')\n\n\tdef write (self, data):\n\t\tif self.got_header:\n\t\t\tself._write (data)\n\t\telse:\n\t\t\t# CGI scripts may optionally provide extra headers.\n\t\t\t#\n\t\t\t# If they do not, then the output is assumed to be\n\t\t\t# text/html, with an HTTP reply code of '200 OK'.\n\t\t\t#\n\t\t\t# If they do, we need to scan those headers for one in\n\t\t\t# particular: the 'Status:' header, which will tell us\n\t\t\t# to use a different HTTP reply code [like '302 Moved']\n\t\t\t#\n\t\t\tself.buffer = self.buffer + data\n\t\t\tlines = self.buffer.split('\\n')\n\t\t\t# look for something un-header-like\n\t\t\tfor i in range(len(lines)):\n\t\t\t\tif i == (len(lines)-1):\n\t\t\t\t\tif lines[i] == '':\n\t\t\t\t\t\tbreak\n\t\t\t\telif HEADER_LINE.match (lines[i]) == -1:\n\t\t\t\t\t# this is not a header line.\n\t\t\t\t\tself.got_header = 1\n\t\t\t\t\tself.buffer = self.build_header (lines[:i])\n\t\t\t\t\t# rejoin the rest of the data\n\t\t\t\t\tself._write('\\n'.join(lines[i:]))\n\t\t\t\t\tbreak\n"
  },
  {
    "path": "supervisor/medusa/docs/tkinter.txt",
    "content": "\nHere are some notes on combining the Tk Event loop with the async lib\nand/or Medusa.  Many thanks to Aaron Rhodes (alrhodes@cpis.net) for\nthe info!\n\n  > Sam,\n  > \n  > Just wanted to send you a quick message about how I managed to\n  > finally integrate Tkinter with asyncore.  This solution is pretty\n  > straightforward.  From the main tkinter event loop i simply added\n  > a repeating alarm that calls asyncore.poll() every so often. So\n  > the code looks like this:\n  > \n  > in main:\n  > import asyncore\n  > \n  >     self.socket_check()\n  > \n  > ...\n  > \n  > then, socket_check() is:\n  > \n  >     def socket_check(self):\n  >         asyncore.poll(timeout=0.0)\n  >         self.after(100, self.socket_check)\n  > \n  > \n  > This simply causes asyncore to poll all the sockets every 100ms\n  > during the tkinter event loop.  The GUI doesn't block on IO since\n  > all the IO calls are now handled with asyncore.\n\n"
  },
  {
    "path": "supervisor/medusa/filesys.py",
    "content": "# -*- Mode: Python -*-\n#       $Id: filesys.py,v 1.9 2003/12/24 16:10:56 akuchling Exp $\n#       Author: Sam Rushing <rushing@nightmare.com>\n#\n# Generic filesystem interface.\n#\n\n# We want to provide a complete wrapper around any and all\n# filesystem operations.\n\n# this class is really just for documentation,\n# identifying the API for a filesystem object.\n\n# opening files for reading, and listing directories, should\n# return a producer.\n\nfrom supervisor.compat import long\n\nclass abstract_filesystem:\n    def __init__ (self):\n        pass\n\n    def current_directory (self):\n        \"\"\"Return a string representing the current directory.\"\"\"\n        pass\n\n    def listdir (self, path, long=0):\n        \"\"\"Return a listing of the directory at 'path' The empty string\n        indicates the current directory.  If 'long' is set, instead\n        return a list of (name, stat_info) tuples\n        \"\"\"\n        pass\n\n    def open (self, path, mode):\n        \"\"\"Return an open file object\"\"\"\n        pass\n\n    def stat (self, path):\n        \"\"\"Return the equivalent of os.stat() on the given path.\"\"\"\n        pass\n\n    def isdir (self, path):\n        \"\"\"Does the path represent a directory?\"\"\"\n        pass\n\n    def isfile (self, path):\n        \"\"\"Does the path represent a plain file?\"\"\"\n        pass\n\n    def cwd (self, path):\n        \"\"\"Change the working directory.\"\"\"\n        pass\n\n    def cdup (self):\n        \"\"\"Change to the parent of the current directory.\"\"\"\n        pass\n\n\n    def longify (self, path):\n        \"\"\"Return a 'long' representation of the filename\n        [for the output of the LIST command]\"\"\"\n        pass\n\n# standard wrapper around a unix-like filesystem, with a 'false root'\n# capability.\n\n# security considerations: can symbolic links be used to 'escape' the\n# root?  should we allow it?  if not, then we could scan the\n# filesystem on startup, but that would not help if they were added\n# later.  We will probably need to check for symlinks in the cwd method.\n\n# what to do if wd is an invalid directory?\n\nimport os\nimport stat\nimport re\n\ndef safe_stat (path):\n    try:\n        return path, os.stat (path)\n    except:\n        return None\n\nclass os_filesystem:\n    path_module = os.path\n\n    # set this to zero if you want to disable pathname globbing.\n    # [we currently don't glob, anyway]\n    do_globbing = 1\n\n    def __init__ (self, root, wd='/'):\n        self.root = root\n        self.wd = wd\n\n    def current_directory (self):\n        return self.wd\n\n    def isfile (self, path):\n        p = self.normalize (self.path_module.join (self.wd, path))\n        return self.path_module.isfile (self.translate(p))\n\n    def isdir (self, path):\n        p = self.normalize (self.path_module.join (self.wd, path))\n        return self.path_module.isdir (self.translate(p))\n\n    def cwd (self, path):\n        p = self.normalize (self.path_module.join (self.wd, path))\n        translated_path = self.translate(p)\n        if not self.path_module.isdir (translated_path):\n            return 0\n        else:\n            old_dir = os.getcwd()\n            # temporarily change to that directory, in order\n            # to see if we have permission to do so.\n            can = 0\n            try:\n                try:\n                    os.chdir (translated_path)\n                    can = 1\n                    self.wd = p\n                except:\n                    pass\n            finally:\n                if can:\n                    os.chdir (old_dir)\n            return can\n\n    def cdup (self):\n        return self.cwd ('..')\n\n    def listdir (self, path, long=0):\n        p = self.translate (path)\n        # I think we should glob, but limit it to the current\n        # directory only.\n        ld = os.listdir (p)\n        if not long:\n            return list_producer (ld, None)\n        else:\n            old_dir = os.getcwd()\n            try:\n                os.chdir (p)\n                # if os.stat fails we ignore that file.\n                result = [_f for _f in map (safe_stat, ld) if _f]\n            finally:\n                os.chdir (old_dir)\n            return list_producer (result, self.longify)\n\n    # TODO: implement a cache w/timeout for stat()\n    def stat (self, path):\n        p = self.translate (path)\n        return os.stat (p)\n\n    def open (self, path, mode):\n        p = self.translate (path)\n        return open (p, mode)\n\n    def unlink (self, path):\n        p = self.translate (path)\n        return os.unlink (p)\n\n    def mkdir (self, path):\n        p = self.translate (path)\n        return os.mkdir (p)\n\n    def rmdir (self, path):\n        p = self.translate (path)\n        return os.rmdir (p)\n\n    def rename(self, src, dst):\n        return os.rename(self.translate(src),self.translate(dst))\n\n    # utility methods\n    def normalize (self, path):\n        # watch for the ever-sneaky '/+' path element\n        path = re.sub('/+', '/', path)\n        p = self.path_module.normpath (path)\n        # remove 'dangling' cdup's.\n        if len(p) > 2 and p[:3] == '/..':\n            p = '/'\n        return p\n\n    def translate (self, path):\n        # we need to join together three separate\n        # path components, and do it safely.\n        # <real_root>/<current_directory>/<path>\n        # use the operating system's path separator.\n        path = os.sep.join(path.split('/'))\n        p = self.normalize (self.path_module.join (self.wd, path))\n        p = self.normalize (self.path_module.join (self.root, p[1:]))\n        return p\n\n    def longify (self, path_stat_info_tuple):\n        (path, stat_info) = path_stat_info_tuple\n        return unix_longify (path, stat_info)\n\n    def __repr__ (self):\n        return '<unix-style fs root:%s wd:%s>' % (\n                self.root,\n                self.wd\n                )\n\nif os.name == 'posix':\n\n    class unix_filesystem (os_filesystem):\n        pass\n\n    class schizophrenic_unix_filesystem (os_filesystem):\n        PROCESS_UID     = os.getuid()\n        PROCESS_EUID    = os.geteuid()\n        PROCESS_GID     = os.getgid()\n        PROCESS_EGID    = os.getegid()\n\n        def __init__ (self, root, wd='/', persona=(None, None)):\n            os_filesystem.__init__ (self, root, wd)\n            self.persona = persona\n\n        def become_persona (self):\n            if self.persona != (None, None):\n                uid, gid = self.persona\n                # the order of these is important!\n                os.setegid (gid)\n                os.seteuid (uid)\n\n        def become_nobody (self):\n            if self.persona != (None, None):\n                os.seteuid (self.PROCESS_UID)\n                os.setegid (self.PROCESS_GID)\n\n        # cwd, cdup, open, listdir\n        def cwd (self, path):\n            try:\n                self.become_persona()\n                return os_filesystem.cwd (self, path)\n            finally:\n                self.become_nobody()\n\n        def cdup (self):\n            try:\n                self.become_persona()\n                return os_filesystem.cdup (self)\n            finally:\n                self.become_nobody()\n\n        def open (self, filename, mode):\n            try:\n                self.become_persona()\n                return os_filesystem.open (self, filename, mode)\n            finally:\n                self.become_nobody()\n\n        def listdir (self, path, long=0):\n            try:\n                self.become_persona()\n                return os_filesystem.listdir (self, path, long)\n            finally:\n                self.become_nobody()\n\n# For the 'real' root, we could obtain a list of drives, and then\n# use that.  Doesn't win32 provide such a 'real' filesystem?\n# [yes, I think something like this \"\\\\.\\c\\windows\"]\n\nclass msdos_filesystem (os_filesystem):\n    def longify (self, path_stat_info_tuple):\n        (path, stat_info) = path_stat_info_tuple\n        return msdos_longify (path, stat_info)\n\n# A merged filesystem will let you plug other filesystems together.\n# We really need the equivalent of a 'mount' capability - this seems\n# to be the most general idea.  So you'd use a 'mount' method to place\n# another filesystem somewhere in the hierarchy.\n\n# Note: this is most likely how I will handle ~user directories\n# with the http server.\n\nclass merged_filesystem:\n    def __init__ (self, *fsys):\n        pass\n\n# this matches the output of NT's ftp server (when in\n# MSDOS mode) exactly.\n\ndef msdos_longify (file, stat_info):\n    if stat.S_ISDIR (stat_info[stat.ST_MODE]):\n        dir = '<DIR>'\n    else:\n        dir = '     '\n    date = msdos_date (stat_info[stat.ST_MTIME])\n    return '%s       %s %8d %s' % (\n            date,\n            dir,\n            stat_info[stat.ST_SIZE],\n            file\n            )\n\ndef msdos_date (t):\n    try:\n        info = time.gmtime (t)\n    except:\n        info = time.gmtime (0)\n    # year, month, day, hour, minute, second, ...\n    hour = info[3]\n    if hour > 11:\n        merid = 'PM'\n        hour -= 12\n    else:\n        merid = 'AM'\n    return '%02d-%02d-%02d  %02d:%02d%s' % (\n            info[1],\n            info[2],\n            info[0]%100,\n            hour,\n            info[4],\n            merid\n            )\n\nmonths = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',\n                  'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']\n\nmode_table = {\n        '0':'---',\n        '1':'--x',\n        '2':'-w-',\n        '3':'-wx',\n        '4':'r--',\n        '5':'r-x',\n        '6':'rw-',\n        '7':'rwx'\n        }\n\nimport time\n\ndef unix_longify (file, stat_info):\n    # for now, only pay attention to the lower bits\n    mode = ('%o' % stat_info[stat.ST_MODE])[-3:]\n    mode = ''.join([mode_table[x] for x in mode])\n    if stat.S_ISDIR (stat_info[stat.ST_MODE]):\n        dirchar = 'd'\n    else:\n        dirchar = '-'\n    date = ls_date (long(time.time()), stat_info[stat.ST_MTIME])\n    return '%s%s %3d %-8d %-8d %8d %s %s' % (\n            dirchar,\n            mode,\n            stat_info[stat.ST_NLINK],\n            stat_info[stat.ST_UID],\n            stat_info[stat.ST_GID],\n            stat_info[stat.ST_SIZE],\n            date,\n            file\n            )\n\n# Emulate the unix 'ls' command's date field.\n# it has two formats - if the date is more than 180\n# days in the past, then it's like this:\n# Oct 19  1995\n# otherwise, it looks like this:\n# Oct 19 17:33\n\ndef ls_date (now, t):\n    try:\n        info = time.gmtime (t)\n    except:\n        info = time.gmtime (0)\n    # 15,600,000 == 86,400 * 180\n    if (now - t) > 15600000:\n        return '%s %2d  %d' % (\n                months[info[1]-1],\n                info[2],\n                info[0]\n                )\n    else:\n        return '%s %2d %02d:%02d' % (\n                months[info[1]-1],\n                info[2],\n                info[3],\n                info[4]\n                )\n\n# ===========================================================================\n# Producers\n# ===========================================================================\n\nclass list_producer:\n    def __init__ (self, list, func=None):\n        self.list = list\n        self.func = func\n\n    # this should do a pushd/popd\n    def more (self):\n        if not self.list:\n            return ''\n        else:\n            # do a few at a time\n            bunch = self.list[:50]\n            if self.func is not None:\n                bunch = map (self.func, bunch)\n            self.list = self.list[50:]\n            return '\\r\\n'.join(bunch) + '\\r\\n'\n\n"
  },
  {
    "path": "supervisor/medusa/http_date.py",
    "content": "# -*- Mode: Python -*-\n\nimport re\nimport time\n\ndef concat (*args):\n    return ''.join (args)\n\ndef join (seq, field=' '):\n    return field.join (seq)\n\ndef group (s):\n    return '(' + s + ')'\n\nshort_days = ['sun','mon','tue','wed','thu','fri','sat']\nlong_days = ['sunday','monday','tuesday','wednesday','thursday','friday','saturday']\n\nshort_day_reg = group (join (short_days, '|'))\nlong_day_reg = group (join (long_days, '|'))\n\ndaymap = {}\nfor i in range(7):\n    daymap[short_days[i]] = i\n    daymap[long_days[i]] = i\n\nhms_reg = join (3 * [group('[0-9][0-9]')], ':')\n\nmonths = ['jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec']\n\nmonmap = {}\nfor i in range(12):\n    monmap[months[i]] = i+1\n\nmonths_reg = group (join (months, '|'))\n\n# From draft-ietf-http-v11-spec-07.txt/3.3.1\n#       Sun, 06 Nov 1994 08:49:37 GMT  ; RFC 822, updated by RFC 1123\n#       Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036\n#       Sun Nov  6 08:49:37 1994       ; ANSI C's asctime() format\n\n# rfc822 format\nrfc822_date = join (\n        [concat (short_day_reg,','),    # day\n         group('[0-9][0-9]?'),          # date\n         months_reg,                    # month\n         group('[0-9]+'),               # year\n         hms_reg,                       # hour minute second\n         'gmt'\n         ],\n        ' '\n        )\n\nrfc822_reg = re.compile (rfc822_date)\n\ndef unpack_rfc822(m):\n    g = m.group\n    i = int\n    return (\n            i(g(4)),        # year\n            monmap[g(3)],   # month\n            i(g(2)),        # day\n            i(g(5)),        # hour\n            i(g(6)),        # minute\n            i(g(7)),        # second\n            0,\n            0,\n            0\n            )\n\n# rfc850 format\nrfc850_date = join (\n        [concat (long_day_reg,','),\n         join (\n                 [group ('[0-9][0-9]?'),\n                  months_reg,\n                  group ('[0-9]+')\n                  ],\n                 '-'\n                 ),\n         hms_reg,\n         'gmt'\n         ],\n        ' '\n        )\n\nrfc850_reg = re.compile (rfc850_date)\n# they actually unpack the same way\ndef unpack_rfc850(m):\n    g = m.group\n    i = int\n    return (\n            i(g(4)),        # year\n            monmap[g(3)],   # month\n            i(g(2)),        # day\n            i(g(5)),        # hour\n            i(g(6)),        # minute\n            i(g(7)),        # second\n            0,\n            0,\n            0\n            )\n\n# parsedate.parsedate    - ~700/sec.\n# parse_http_date       - ~1333/sec.\n\ndef build_http_date (when):\n    return time.strftime ('%a, %d %b %Y %H:%M:%S GMT', time.gmtime(when))\n\ndef parse_http_date (d):\n    d = d.lower()\n    tz = time.timezone\n    m = rfc850_reg.match (d)\n    if m and m.end() == len(d):\n        retval = int (time.mktime (unpack_rfc850(m)) - tz)\n    else:\n        m = rfc822_reg.match (d)\n        if m and m.end() == len(d):\n            retval = int (time.mktime (unpack_rfc822(m)) - tz)\n        else:\n            return 0\n    # Thanks to Craig Silverstein <csilvers@google.com> for pointing\n    # out the DST discrepancy\n    if time.daylight and time.localtime(retval)[-1] == 1: # DST correction\n        retval += tz - time.altzone\n    return retval\n"
  },
  {
    "path": "supervisor/medusa/http_server.py",
    "content": "# -*- Mode: Python -*-\n#\n#       Author: Sam Rushing <rushing@nightmare.com>\n#       Copyright 1996-2000 by Sam Rushing\n#                                                All Rights Reserved.\n#\nRCS_ID =  '$Id: http_server.py,v 1.12 2004/04/21 15:11:44 akuchling Exp $'\n\n# python modules\nimport re\nimport socket\nimport sys\nimport time\n\nfrom supervisor.compat import as_bytes\n\n# async modules\nimport supervisor.medusa.asyncore_25 as asyncore\nimport supervisor.medusa.asynchat_25 as asynchat\n\n# medusa modules\nimport supervisor.medusa.http_date as http_date\nimport supervisor.medusa.producers as producers\nimport supervisor.medusa.logger as logger\n\nVERSION_STRING = RCS_ID.split()[2]\n\nfrom supervisor.medusa.counter import counter\ntry:\n    from urllib import unquote, splitquery\nexcept ImportError:\n    from urllib.parse import unquote, splitquery\n\n# ===========================================================================\n#                                                       Request Object\n# ===========================================================================\n\nclass http_request:\n\n    # default reply code\n    reply_code = 200\n\n    request_counter = counter()\n\n    # Whether to automatically use chunked encoding when\n    #\n    #   HTTP version is 1.1\n    #   Content-Length is not set\n    #   Chunked encoding is not already in effect\n    #\n    # If your clients are having trouble, you might want to disable this.\n    use_chunked = 1\n\n    # by default, this request object ignores user data.\n    collector = None\n\n    def __init__ (self, *args):\n        # unpack information about the request\n        (self.channel, self.request,\n         self.command, self.uri, self.version,\n         self.header) = args\n\n        self.outgoing = []\n        self.reply_headers = {\n                'Server'        : 'Medusa/%s' % VERSION_STRING,\n                'Date'          : http_date.build_http_date (time.time())\n                }\n\n        # New reply header list (to support multiple\n        # headers with same name)\n        self.__reply_header_list = []\n\n        self.request_number = http_request.request_counter.increment()\n        self._split_uri = None\n        self._header_cache = {}\n\n    # --------------------------------------------------\n    # reply header management\n    # --------------------------------------------------\n    def __setitem__ (self, key, value):\n        self.reply_headers[key] = value\n\n    def __getitem__ (self, key):\n        return self.reply_headers[key]\n\n    def __contains__(self, key):\n        return key in self.reply_headers\n\n    def has_key (self, key):\n        return key in self.reply_headers\n\n    def build_reply_header (self):\n        header_items = ['%s: %s' % item for item in self.reply_headers.items()]\n        result = '\\r\\n'.join (\n            [self.response(self.reply_code)] + header_items) + '\\r\\n\\r\\n'\n        return as_bytes(result)\n\n    ####################################################\n    # multiple reply header management\n    ####################################################\n    # These are intended for allowing multiple occurrences\n    # of the same header.\n    # Usually you can fold such headers together, separating\n    # their contents by a comma (e.g. Accept: text/html, text/plain)\n    # but the big exception is the Set-Cookie header.\n    # dictionary centric.\n    #---------------------------------------------------\n\n    def add_header(self, name, value):\n        \"\"\" Adds a header to the reply headers \"\"\"\n        self.__reply_header_list.append((name, value))\n\n    def clear_headers(self):\n        \"\"\" Clears the reply header list \"\"\"\n\n        # Remove things from the old dict as well\n        self.reply_headers.clear()\n\n        self.__reply_header_list[:] = []\n\n    def remove_header(self, name, value=None):\n        \"\"\" Removes the specified header.\n        If a value is provided, the name and\n        value must match to remove the header.\n        If the value is None, removes all headers\n        with that name.\"\"\"\n\n        found_it = 0\n\n        # Remove things from the old dict as well\n        if (name in self.reply_headers and\n            (value is None or\n             self.reply_headers[name] == value)):\n            del self.reply_headers[name]\n            found_it = 1\n\n\n        removed_headers = []\n        if not value is None:\n            if (name, value) in self.__reply_header_list:\n                removed_headers = [(name, value)]\n                found_it = 1\n        else:\n            for h in self.__reply_header_list:\n                if h[0] == name:\n                    removed_headers.append(h)\n                    found_it = 1\n\n        if not found_it:\n            if value is None:\n                search_value = \"%s\" % name\n            else:\n                search_value = \"%s: %s\" % (name, value)\n\n            raise LookupError(\"Header '%s' not found\" % search_value)\n\n        for h in removed_headers:\n            self.__reply_header_list.remove(h)\n\n\n    def get_reply_headers(self):\n        \"\"\" Get the tuple of headers that will be used\n        for generating reply headers\"\"\"\n        header_tuples = self.__reply_header_list[:]\n\n        # The idea here is to insert the headers from\n        # the old header dict into the new header list,\n        # UNLESS there's already an entry in the list\n        # that would have overwritten the dict entry\n        # if the dict was the only storage...\n        header_names = [n for n,v in header_tuples]\n        for n,v in self.reply_headers.items():\n            if n not in header_names:\n                header_tuples.append((n,v))\n                header_names.append(n)\n        # Ok, that should do it.  Now, if there were any\n        # headers in the dict that weren't in the list,\n        # they should have been copied in.  If the name\n        # was already in the list, we didn't copy it,\n        # because the value from the dict has been\n        # 'overwritten' by the one in the list.\n\n        return header_tuples\n\n    def get_reply_header_text(self):\n        \"\"\" Gets the reply header (including status and\n        additional crlf)\"\"\"\n\n        header_tuples = self.get_reply_headers()\n\n        headers = [self.response(self.reply_code)]\n        headers += [\"%s: %s\" % h for h in header_tuples]\n        return '\\r\\n'.join(headers) + '\\r\\n\\r\\n'\n\n    #---------------------------------------------------\n    # This is the end of the new reply header\n    # management section.\n    ####################################################\n\n\n    # --------------------------------------------------\n    # split a uri\n    # --------------------------------------------------\n\n    # <path>;<params>?<query>#<fragment>\n    path_regex = re.compile (\n    #      path      params    query   fragment\n            r'([^;?#]*)(;[^?#]*)?(\\?[^#]*)?(#.*)?'\n            )\n\n    def split_uri (self):\n        if self._split_uri is None:\n            m = self.path_regex.match (self.uri)\n            if m.end() != len(self.uri):\n                raise ValueError(\"Broken URI\")\n            else:\n                self._split_uri = m.groups()\n        return self._split_uri\n\n    def get_header_with_regex (self, head_reg, group):\n        for line in self.header:\n            m = head_reg.match (line)\n            if m.end() == len(line):\n                return m.group (group)\n        return ''\n\n    def get_header (self, header):\n        header = header.lower()\n        hc = self._header_cache\n        if header not in hc:\n            h = header + ': '\n            hl = len(h)\n            for line in self.header:\n                if line[:hl].lower() == h:\n                    r = line[hl:]\n                    hc[header] = r\n                    return r\n            hc[header] = None\n            return None\n        else:\n            return hc[header]\n\n    # --------------------------------------------------\n    # user data\n    # --------------------------------------------------\n\n    def collect_incoming_data (self, data):\n        if self.collector:\n            self.collector.collect_incoming_data (data)\n        else:\n            self.log_info(\n                    'Dropping %d bytes of incoming request data' % len(data),\n                    'warning'\n                    )\n\n    def found_terminator (self):\n        if self.collector:\n            self.collector.found_terminator()\n        else:\n            self.log_info (\n                    'Unexpected end-of-record for incoming request',\n                    'warning'\n                    )\n\n    def push (self, thing):\n        # Sometimes, text gets pushed by XMLRPC logic for later\n        # processing.\n        if isinstance(thing, str):\n            thing = as_bytes(thing)\n        if isinstance(thing, bytes):\n            thing = producers.simple_producer(thing, buffer_size=len(thing))\n        self.outgoing.append(thing)\n\n    def response (self, code=200):\n        message = self.responses[code]\n        self.reply_code = code\n        return 'HTTP/%s %d %s' % (self.version, code, message)\n\n    def error (self, code):\n        self.reply_code = code\n        message = self.responses[code]\n        s = self.DEFAULT_ERROR_MESSAGE % {\n                'code': code,\n                'message': message,\n                }\n        s = as_bytes(s)\n        self['Content-Length'] = len(s)\n        self['Content-Type'] = 'text/html'\n        # make an error reply\n        self.push(s)\n        self.done()\n\n    # can also be used for empty replies\n    reply_now = error\n\n    def done (self):\n        \"\"\"finalize this transaction - send output to the http channel\"\"\"\n\n        # ----------------------------------------\n        # persistent connection management\n        # ----------------------------------------\n\n        #  --- BUCKLE UP! ----\n\n        connection = get_header(CONNECTION, self.header).lower()\n\n        close_it = 0\n        wrap_in_chunking = 0\n\n        if self.version == '1.0':\n            if connection == 'keep-alive':\n                if 'Content-Length' not in self:\n                    close_it = 1\n                else:\n                    self['Connection'] = 'Keep-Alive'\n            else:\n                close_it = 1\n        elif self.version == '1.1':\n            if connection == 'close':\n                close_it = 1\n            elif 'Content-Length' not in self:\n                if 'Transfer-Encoding' in self:\n                    if not self['Transfer-Encoding'] == 'chunked':\n                        close_it = 1\n                elif self.use_chunked:\n                    self['Transfer-Encoding'] = 'chunked'\n                    wrap_in_chunking = 1\n                else:\n                    close_it = 1\n        elif self.version is None:\n            # Although we don't *really* support http/0.9 (because we'd have to\n            # use \\r\\n as a terminator, and it would just yuck up a lot of stuff)\n            # it's very common for developers to not want to type a version number\n            # when using telnet to debug a server.\n            close_it = 1\n\n        outgoing_header = producers.simple_producer(self.get_reply_header_text())\n\n        if close_it:\n            self['Connection'] = 'close'\n\n        if wrap_in_chunking:\n            outgoing_producer = producers.chunked_producer (\n                    producers.composite_producer (self.outgoing)\n                    )\n            # prepend the header\n            outgoing_producer = producers.composite_producer(\n                [outgoing_header, outgoing_producer]\n                )\n        else:\n            # prepend the header\n            self.outgoing.insert(0, outgoing_header)\n            outgoing_producer = producers.composite_producer (self.outgoing)\n\n        # apply a few final transformations to the output\n        self.channel.push_with_producer (\n                # globbing gives us large packets\n                producers.globbing_producer (\n                        # hooking lets us log the number of bytes sent\n                        producers.hooked_producer (\n                                outgoing_producer,\n                                self.log\n                                )\n                        )\n                )\n\n        self.channel.current_request = None\n\n        if close_it:\n            self.channel.close_when_done()\n\n    def log_date_string (self, when):\n        gmt = time.gmtime(when)\n        if time.daylight and gmt[8]:\n            tz = time.altzone\n        else:\n            tz = time.timezone\n        if tz > 0:\n            neg = 1\n        else:\n            neg = 0\n            tz = -tz\n        h, rem = divmod (tz, 3600)\n        m, rem = divmod (rem, 60)\n        if neg:\n            offset = '-%02d%02d' % (h, m)\n        else:\n            offset = '+%02d%02d' % (h, m)\n\n        return time.strftime ( '%d/%b/%Y:%H:%M:%S ', gmt) + offset\n\n    def log (self, bytes):\n        self.channel.server.logger.log (\n                self.channel.addr[0],\n                '%d - - [%s] \"%s\" %d %d\\n' % (\n                        self.channel.addr[1],\n                        self.log_date_string (time.time()),\n                        self.request,\n                        self.reply_code,\n                        bytes\n                        )\n                )\n\n    responses = {\n            100: \"Continue\",\n            101: \"Switching Protocols\",\n            200: \"OK\",\n            201: \"Created\",\n            202: \"Accepted\",\n            203: \"Non-Authoritative Information\",\n            204: \"No Content\",\n            205: \"Reset Content\",\n            206: \"Partial Content\",\n            300: \"Multiple Choices\",\n            301: \"Moved Permanently\",\n            302: \"Moved Temporarily\",\n            303: \"See Other\",\n            304: \"Not Modified\",\n            305: \"Use Proxy\",\n            400: \"Bad Request\",\n            401: \"Unauthorized\",\n            402: \"Payment Required\",\n            403: \"Forbidden\",\n            404: \"Not Found\",\n            405: \"Method Not Allowed\",\n            406: \"Not Acceptable\",\n            407: \"Proxy Authentication Required\",\n            408: \"Request Time-out\",\n            409: \"Conflict\",\n            410: \"Gone\",\n            411: \"Length Required\",\n            412: \"Precondition Failed\",\n            413: \"Request Entity Too Large\",\n            414: \"Request-URI Too Large\",\n            415: \"Unsupported Media Type\",\n            500: \"Internal Server Error\",\n            501: \"Not Implemented\",\n            502: \"Bad Gateway\",\n            503: \"Service Unavailable\",\n            504: \"Gateway Time-out\",\n            505: \"HTTP Version not supported\"\n            }\n\n    # Default error message\n    DEFAULT_ERROR_MESSAGE = '\\r\\n'.join(\n            ('<head>',\n             '<title>Error response</title>',\n             '</head>',\n             '<body>',\n             '<h1>Error response</h1>',\n             '<p>Error code %(code)d.',\n             '<p>Message: %(message)s.',\n             '</body>',\n             ''\n             )\n            )\n\n    def log_info(self, msg, level):\n        pass\n\n\n# ===========================================================================\n#                                                HTTP Channel Object\n# ===========================================================================\n\nclass http_channel (asynchat.async_chat):\n\n    # use a larger default output buffer\n    ac_out_buffer_size = 1<<16\n\n    current_request = None\n    channel_counter = counter()\n\n    def __init__ (self, server, conn, addr):\n        self.channel_number = http_channel.channel_counter.increment()\n        self.request_counter = counter()\n        asynchat.async_chat.__init__ (self, conn)\n        self.server = server\n        self.addr = addr\n        self.set_terminator (b'\\r\\n\\r\\n')\n        self.in_buffer = b''\n        self.creation_time = int (time.time())\n        self.last_used = self.creation_time\n        self.check_maintenance()\n\n    def __repr__ (self):\n        ar = asynchat.async_chat.__repr__(self)[1:-1]\n        return '<%s channel#: %s requests:%s>' % (\n                ar,\n                self.channel_number,\n                self.request_counter\n                )\n\n    # Channel Counter, Maintenance Interval...\n    maintenance_interval = 500\n\n    def check_maintenance (self):\n        if not self.channel_number % self.maintenance_interval:\n            self.maintenance()\n\n    def maintenance (self):\n        self.kill_zombies()\n\n    # 30-minute zombie timeout.  status_handler also knows how to kill zombies.\n    zombie_timeout = 30 * 60\n\n    def kill_zombies (self):\n        now = int (time.time())\n        for channel in list(asyncore.socket_map.values()):\n            if channel.__class__ == self.__class__:\n                if (now - channel.last_used) > channel.zombie_timeout:\n                    channel.close()\n\n    # --------------------------------------------------\n    # send/recv overrides, good place for instrumentation.\n    # --------------------------------------------------\n\n    # this information needs to get into the request object,\n    # so that it may log correctly.\n    def send (self, data):\n        result = asynchat.async_chat.send (self, data)\n        self.server.bytes_out.increment (len(data))\n        self.last_used = int (time.time())\n        return result\n\n    def recv (self, buffer_size):\n        try:\n            result = asynchat.async_chat.recv (self, buffer_size)\n            self.server.bytes_in.increment (len(result))\n            self.last_used = int (time.time())\n            return result\n        except MemoryError:\n            # --- Save a Trip to Your Service Provider ---\n            # It's possible for a process to eat up all the memory of\n            # the machine, and put it in an extremely wedged state,\n            # where medusa keeps running and can't be shut down.  This\n            # is where MemoryError tends to get thrown, though of\n            # course it could get thrown elsewhere.\n            sys.exit (\"Out of Memory!\")\n\n    def handle_error (self):\n        t, v = sys.exc_info()[:2]\n        if t is SystemExit:\n            raise t(v)\n        else:\n            asynchat.async_chat.handle_error (self)\n\n    def log (self, *args):\n        pass\n\n    # --------------------------------------------------\n    # async_chat methods\n    # --------------------------------------------------\n\n    def collect_incoming_data (self, data):\n        if self.current_request:\n            # we are receiving data (probably POST data) for a request\n            self.current_request.collect_incoming_data (data)\n        else:\n            # we are receiving header (request) data\n            self.in_buffer = self.in_buffer + data\n\n    def found_terminator (self):\n        if self.current_request:\n            self.current_request.found_terminator()\n        else:\n            header = self.in_buffer\n            self.in_buffer = b''\n            lines = header.split(b'\\r\\n')\n\n            # --------------------------------------------------\n            # crack the request header\n            # --------------------------------------------------\n\n            while lines and not lines[0]:\n                # as per the suggestion of http-1.1 section 4.1, (and\n                # Eric Parker <eparker@zyvex.com>), ignore a leading\n                # blank lines (buggy browsers tack it onto the end of\n                # POST requests)\n                lines = lines[1:]\n\n            if not lines:\n                self.close_when_done()\n                return\n\n            request = lines[0]\n\n            command, uri, version = crack_request (request)\n            header = join_headers (lines[1:])\n\n            # unquote path if necessary (thanks to Skip Montanaro for pointing\n            # out that we must unquote in piecemeal fashion).\n            rpath, rquery = splitquery(uri)\n            if '%' in rpath:\n                if rquery:\n                    uri = unquote (rpath) + '?' + rquery\n                else:\n                    uri = unquote (rpath)\n\n            r = http_request (self, request, command, uri, version, header)\n            self.request_counter.increment()\n            self.server.total_requests.increment()\n\n            if command is None:\n                self.log_info ('Bad HTTP request: %s' % repr(request), 'error')\n                r.error (400)\n                return\n\n            # --------------------------------------------------\n            # handler selection and dispatch\n            # --------------------------------------------------\n            for h in self.server.handlers:\n                if h.match (r):\n                    try:\n                        self.current_request = r\n                        # This isn't used anywhere.\n                        # r.handler = h # CYCLE\n                        h.handle_request (r)\n                    except:\n                        self.server.exceptions.increment()\n                        (file, fun, line), t, v, tbinfo = asyncore.compact_traceback()\n                        self.log_info(\n                                        'Server Error: %s, %s: file: %s line: %s' % (t,v,file,line),\n                                        'error')\n                        try:\n                            r.error (500)\n                        except:\n                            pass\n                    return\n\n            # no handlers, so complain\n            r.error (404)\n\n    def writable_for_proxy (self):\n        # this version of writable supports the idea of a 'stalled' producer\n        # [i.e., it's not ready to produce any output yet] This is needed by\n        # the proxy, which will be waiting for the magic combination of\n        # 1) hostname resolved\n        # 2) connection made\n        # 3) data available.\n        if self.ac_out_buffer:\n            return 1\n        elif len(self.producer_fifo):\n            p = self.producer_fifo.first()\n            if hasattr (p, 'stalled'):\n                return not p.stalled()\n            else:\n                return 1\n\n# ===========================================================================\n#                                                HTTP Server Object\n# ===========================================================================\n\nclass http_server (asyncore.dispatcher):\n\n    SERVER_IDENT = 'HTTP Server (V%s)' % VERSION_STRING\n\n    channel_class = http_channel\n\n    def __init__ (self, ip, port, resolver=None, logger_object=None):\n        self.ip = ip\n        self.port = port\n        asyncore.dispatcher.__init__ (self)\n        self.create_socket (socket.AF_INET, socket.SOCK_STREAM)\n\n        self.handlers = []\n\n        if not logger_object:\n            logger_object = logger.file_logger (sys.stdout)\n\n        self.set_reuse_addr()\n        self.bind ((ip, port))\n\n        # lower this to 5 if your OS complains\n        self.listen (1024)\n\n        host, port = self.socket.getsockname()\n        if not ip:\n            self.log_info('Computing default hostname', 'warning')\n            ip = socket.gethostbyname (socket.gethostname())\n        try:\n            self.server_name = socket.gethostbyaddr (ip)[0]\n        except socket.error:\n            self.log_info('Cannot do reverse lookup', 'warning')\n            self.server_name = ip       # use the IP address as the \"hostname\"\n\n        self.server_port = port\n        self.total_clients = counter()\n        self.total_requests = counter()\n        self.exceptions = counter()\n        self.bytes_out = counter()\n        self.bytes_in  = counter()\n\n        if not logger_object:\n            logger_object = logger.file_logger (sys.stdout)\n\n        if resolver:\n            self.logger = logger.resolving_logger (resolver, logger_object)\n        else:\n            self.logger = logger.unresolving_logger (logger_object)\n\n        self.log_info (\n                'Medusa (V%s) started at %s'\n                '\\n\\tHostname: %s'\n                '\\n\\tPort:%d'\n                '\\n' % (\n                        VERSION_STRING,\n                        time.ctime(time.time()),\n                        self.server_name,\n                        port,\n                        )\n                )\n\n    def writable (self):\n        return 0\n\n    def handle_read (self):\n        pass\n\n    def readable (self):\n        return self.accepting\n\n    def handle_connect (self):\n        pass\n\n    def handle_accept (self):\n        self.total_clients.increment()\n        try:\n            conn, addr = self.accept()\n        except socket.error:\n            # linux: on rare occasions we get a bogus socket back from\n            # accept.  socketmodule.c:makesockaddr complains that the\n            # address family is unknown.  We don't want the whole server\n            # to shut down because of this.\n            self.log_info ('warning: server accept() threw an exception', 'warning')\n            return\n        except TypeError:\n            # unpack non-sequence.  this can happen when a read event\n            # fires on a listening socket, but when we call accept()\n            # we get EWOULDBLOCK, so dispatcher.accept() returns None.\n            # Seen on FreeBSD3.\n            self.log_info ('warning: server accept() threw EWOULDBLOCK', 'warning')\n            return\n\n        self.channel_class (self, conn, addr)\n\n    def install_handler (self, handler, back=0):\n        if back:\n            self.handlers.append (handler)\n        else:\n            self.handlers.insert (0, handler)\n\n    def remove_handler (self, handler):\n        self.handlers.remove (handler)\n\n    def status (self):\n        from supervisor.medusa.util import english_bytes\n        def nice_bytes (n):\n            return ''.join(english_bytes (n))\n\n        handler_stats = [_f for _f in map (maybe_status, self.handlers) if _f]\n\n        if self.total_clients:\n            ratio = self.total_requests.as_long() / float(self.total_clients.as_long())\n        else:\n            ratio = 0.0\n\n        return producers.composite_producer (\n                [producers.lines_producer (\n                        ['<h2>%s</h2>'                                                  % self.SERVER_IDENT,\n                        '<br>Listening on: <b>Host:</b> %s'             % self.server_name,\n                        '<b>Port:</b> %d'                                               % self.port,\n                         '<p><ul>'\n                         '<li>Total <b>Clients:</b> %s'                 % self.total_clients,\n                         '<b>Requests:</b> %s'                                  % self.total_requests,\n                         '<b>Requests/Client:</b> %.1f'                 % ratio,\n                         '<li>Total <b>Bytes In:</b> %s'        % (nice_bytes (self.bytes_in.as_long())),\n                         '<b>Bytes Out:</b> %s'                         % (nice_bytes (self.bytes_out.as_long())),\n                         '<li>Total <b>Exceptions:</b> %s'              % self.exceptions,\n                         '</ul><p>'\n                         '<b>Extension List</b><ul>',\n                         ])] + handler_stats + [producers.simple_producer('</ul>')]\n                )\n\ndef maybe_status (thing):\n    if hasattr (thing, 'status'):\n        return thing.status()\n    else:\n        return None\n\nCONNECTION = re.compile ('Connection: (.*)', re.IGNORECASE)\n\n# merge multi-line headers\n# [486dx2: ~500/sec]\ndef join_headers (headers):\n    r = []\n    for i in range(len(headers)):\n        if headers[i][0] in ' \\t':\n            r[-1] = r[-1] + headers[i][1:]\n        else:\n            r.append (headers[i])\n    return r\n\ndef get_header (head_reg, lines, group=1):\n    for line in lines:\n        m = head_reg.match (line)\n        if m and m.end() == len(line):\n            return m.group (group)\n    return ''\n\ndef get_header_match (head_reg, lines):\n    for line in lines:\n        m = head_reg.match (line)\n        if m and m.end() == len(line):\n            return m\n    return ''\n\nREQUEST = re.compile ('([^ ]+) ([^ ]+)(( HTTP/([0-9.]+))$|$)')\n\ndef crack_request (r):\n    m = REQUEST.match (r)\n    if m and m.end() == len(r):\n        if m.group(3):\n            version = m.group(5)\n        else:\n            version = None\n        return m.group(1), m.group(2), version\n    else:\n        return None, None, None\n\nif __name__ == '__main__':\n    if len(sys.argv) < 2:\n        print('usage: %s <root> <port>' % (sys.argv[0]))\n    else:\n        import supervisor.medusa.monitor as monitor\n        import supervisor.medusa.filesys as filesys\n        import supervisor.medusa.default_handler as default_handler\n        import supervisor.medusa.ftp_server as ftp_server\n        import supervisor.medusa.chat_server as chat_server\n        import supervisor.medusa.resolver as resolver\n        rs = resolver.caching_resolver ('127.0.0.1')\n        lg = logger.file_logger (sys.stdout)\n        ms = monitor.secure_monitor_server ('fnord', '127.0.0.1', 9999)\n        fs = filesys.os_filesystem (sys.argv[1])\n        dh = default_handler.default_handler (fs)\n        hs = http_server('', int(sys.argv[2]), rs, lg)\n        hs.install_handler (dh)\n        ftp = ftp_server.ftp_server (\n                ftp_server.dummy_authorizer(sys.argv[1]),\n                port=8021,\n                resolver=rs,\n                logger_object=lg\n                )\n        cs = chat_server.chat_server ('', 7777)\n        if '-p' in sys.argv:\n            def profile_loop ():\n                try:\n                    asyncore.loop()\n                except KeyboardInterrupt:\n                    pass\n            import profile\n            profile.run ('profile_loop()', 'profile.out')\n        else:\n            asyncore.loop()\n"
  },
  {
    "path": "supervisor/medusa/logger.py",
    "content": "# -*- Mode: Python -*-\n\nimport supervisor.medusa.asynchat_25 as asynchat\nimport socket\nimport time         # these three are for the rotating logger\nimport os           # |\nimport stat         # v\n\n#\n# two types of log:\n# 1) file\n#    with optional flushing.  Also, one that rotates the log.\n# 2) socket\n#    dump output directly to a socket connection. [how do we\n#    keep it open?]\n\n#\n# The 'standard' interface to a logging object is simply\n# log_object.log (message)\n#\n\n# a file-like object that captures output, and\n# makes sure to flush it always...  this could\n# be connected to:\n#  o    stdio file\n#  o    low-level file\n#  o    socket channel\n#  o    syslog output...\n\nclass file_logger:\n\n    # pass this either a path or a file object.\n    def __init__ (self, file, flush=1, mode='a'):\n        if isinstance(file, str):\n            if file == '-':\n                import sys\n                self.file = sys.stdout\n            else:\n                self.file = open (file, mode)\n        else:\n            self.file = file\n        self.do_flush = flush\n\n    def __repr__ (self):\n        return '<file logger: %s>' % self.file\n\n    def write (self, data):\n        self.file.write (data)\n        self.maybe_flush()\n\n    def writeline (self, line):\n        self.file.writeline (line)\n        self.maybe_flush()\n\n    def writelines (self, lines):\n        self.file.writelines (lines)\n        self.maybe_flush()\n\n    def maybe_flush (self):\n        if self.do_flush:\n            self.file.flush()\n\n    def flush (self):\n        self.file.flush()\n\n    def softspace (self, *args):\n        pass\n\n    def log (self, message):\n        if message[-1] not in ('\\r', '\\n'):\n            self.write (message + '\\n')\n        else:\n            self.write (message)\n\n# like a file_logger, but it must be attached to a filename.\n# When the log gets too full, or a certain time has passed,\n# it backs up the log and starts a new one.  Note that backing\n# up the log is done via \"mv\" because anything else (cp, gzip)\n# would take time, during which medusa would do nothing else.\n\nclass rotating_file_logger (file_logger):\n\n    # If freq is non-None we back up \"daily\", \"weekly\", or \"monthly\".\n    # Else if maxsize is non-None we back up whenever the log gets\n    # to big.  If both are None we never back up.\n    def __init__ (self, file, freq=None, maxsize=None, flush=1, mode='a'):\n        file_logger.__init__ (self, file, flush, mode)\n        self.filename = file\n        self.mode = mode\n        self.freq = freq\n        self.maxsize = maxsize\n        self.rotate_when = self.next_backup(self.freq)\n\n    def __repr__ (self):\n        return '<rotating-file logger: %s>' % self.file\n\n    # We back up at midnight every 1) day, 2) monday, or 3) 1st of month\n    def next_backup (self, freq):\n        (yr, mo, day, hr, min, sec, wd, jday, dst) = time.localtime(time.time())\n        if freq == 'daily':\n            return time.mktime((yr,mo,day+1, 0,0,0, 0,0,-1))\n        elif freq == 'weekly':\n            return time.mktime((yr,mo,day-wd+7, 0,0,0, 0,0,-1)) # wd(monday)==0\n        elif freq == 'monthly':\n            return time.mktime((yr,mo+1,1, 0,0,0, 0,0,-1))\n        else:\n            return None                  # not a date-based backup\n\n    def maybe_flush (self):              # rotate first if necessary\n        self.maybe_rotate()\n        if self.do_flush:                # from file_logger()\n            self.file.flush()\n\n    def maybe_rotate (self):\n        if self.freq and time.time() > self.rotate_when:\n            self.rotate()\n            self.rotate_when = self.next_backup(self.freq)\n        elif self.maxsize:               # rotate when we get too big\n            try:\n                if os.stat(self.filename)[stat.ST_SIZE] > self.maxsize:\n                    self.rotate()\n            except os.error:             # file not found, probably\n                self.rotate()            # will create a new file\n\n    def rotate (self):\n        (yr, mo, day, hr, min, sec, wd, jday, dst) = time.localtime(time.time())\n        try:\n            self.file.close()\n            newname = '%s.ends%04d%02d%02d' % (self.filename, yr, mo, day)\n            try:\n                open(newname, \"r\").close()      # check if file exists\n                newname += \"-%02d%02d%02d\" % (hr, min, sec)\n            except:                             # YEAR_MONTH_DAY is unique\n                pass\n            os.rename(self.filename, newname)\n            self.file = open(self.filename, self.mode)\n        except:\n            pass\n\n# log to a stream socket, asynchronously\n\nclass socket_logger (asynchat.async_chat):\n\n    def __init__ (self, address):\n        asynchat.async_chat.__init__(self)\n        if isinstance(address, str):\n            self.create_socket (socket.AF_UNIX, socket.SOCK_STREAM)\n        else:\n            self.create_socket (socket.AF_INET, socket.SOCK_STREAM)\n\n        self.connect (address)\n        self.address = address\n\n    def __repr__ (self):\n        return '<socket logger: address=%s>' % self.address\n\n    def log (self, message):\n        if message[-2:] != '\\r\\n':\n            self.socket.push (message + '\\r\\n')\n        else:\n            self.socket.push (message)\n\n# log to multiple places\nclass multi_logger:\n    def __init__ (self, loggers):\n        self.loggers = loggers\n\n    def __repr__ (self):\n        return '<multi logger: %s>' % (repr(self.loggers))\n\n    def log (self, message):\n        for logger in self.loggers:\n            logger.log (message)\n\nclass resolving_logger:\n    \"\"\"Feed (ip, message) combinations into this logger to get a\n    resolved hostname in front of the message.  The message will not\n    be logged until the PTR request finishes (or fails).\"\"\"\n\n    def __init__ (self, resolver, logger):\n        self.resolver = resolver\n        self.logger = logger\n\n    class logger_thunk:\n        def __init__ (self, message, logger):\n            self.message = message\n            self.logger = logger\n\n        def __call__ (self, host, ttl, answer):\n            if not answer:\n                answer = host\n            self.logger.log ('%s:%s' % (answer, self.message))\n\n    def log (self, ip, message):\n        self.resolver.resolve_ptr (\n                ip,\n                self.logger_thunk (\n                        message,\n                        self.logger\n                        )\n                )\n\nclass unresolving_logger:\n    \"\"\"Just in case you don't want to resolve\"\"\"\n    def __init__ (self, logger):\n        self.logger = logger\n\n    def log (self, ip, message):\n        self.logger.log ('%s:%s' % (ip, message))\n\n\ndef strip_eol (line):\n    while line and line[-1] in '\\r\\n':\n        line = line[:-1]\n    return line\n\nclass tail_logger:\n    \"\"\"Keep track of the last <size> log messages\"\"\"\n    def __init__ (self, logger, size=500):\n        self.size = size\n        self.logger = logger\n        self.messages = []\n\n    def log (self, message):\n        self.messages.append (strip_eol (message))\n        if len (self.messages) > self.size:\n            del self.messages[0]\n        self.logger.log (message)\n"
  },
  {
    "path": "supervisor/medusa/producers.py",
    "content": "# -*- Mode: Python -*-\n\nRCS_ID = '$Id: producers.py,v 1.9 2004/04/21 13:56:28 akuchling Exp $'\n\n\"\"\"\nA collection of producers.\nEach producer implements a particular feature:  They can be combined\nin various ways to get interesting and useful behaviors.\n\nFor example, you can feed dynamically-produced output into the compressing\nproducer, then wrap this with the 'chunked' transfer-encoding producer.\n\"\"\"\n\nfrom supervisor.medusa.asynchat_25 import find_prefix_at_end\nfrom supervisor.compat import as_bytes\n\nclass simple_producer:\n    \"\"\"producer for a string\"\"\"\n    def __init__ (self, data, buffer_size=1024):\n        self.data = data\n        self.buffer_size = buffer_size\n\n    def more (self):\n        if len (self.data) > self.buffer_size:\n            result = self.data[:self.buffer_size]\n            self.data = self.data[self.buffer_size:]\n            return result\n        else:\n            result = self.data\n            self.data = b''\n            return result\n\nclass scanning_producer:\n    \"\"\"like simple_producer, but more efficient for large strings\"\"\"\n    def __init__ (self, data, buffer_size=1024):\n        self.data = data\n        self.buffer_size = buffer_size\n        self.pos = 0\n\n    def more (self):\n        if self.pos < len(self.data):\n            lp = self.pos\n            rp = min (\n                    len(self.data),\n                    self.pos + self.buffer_size\n                    )\n            result = self.data[lp:rp]\n            self.pos += len(result)\n            return result\n        else:\n            return b''\n\nclass lines_producer:\n    \"\"\"producer for a list of lines\"\"\"\n\n    def __init__ (self, lines):\n        self.lines = lines\n\n    def more (self):\n        if self.lines:\n            chunk = self.lines[:50]\n            self.lines = self.lines[50:]\n            return '\\r\\n'.join(chunk) + '\\r\\n'\n        else:\n            return ''\n\nclass buffer_list_producer:\n    \"\"\"producer for a list of strings\"\"\"\n\n    # i.e., data == ''.join(buffers)\n\n    def __init__ (self, buffers):\n        self.index = 0\n        self.buffers = buffers\n\n    def more (self):\n        if self.index >= len(self.buffers):\n            return b''\n        else:\n            data = self.buffers[self.index]\n            self.index += 1\n            return data\n\nclass file_producer:\n    \"\"\"producer wrapper for file[-like] objects\"\"\"\n\n    # match http_channel's outgoing buffer size\n    out_buffer_size = 1<<16\n\n    def __init__ (self, file):\n        self.done = 0\n        self.file = file\n\n    def more (self):\n        if self.done:\n            return b''\n        else:\n            data = self.file.read (self.out_buffer_size)\n            if not data:\n                self.file.close()\n                del self.file\n                self.done = 1\n                return b''\n            else:\n                return data\n\n# A simple output producer.  This one does not [yet] have\n# the safety feature builtin to the monitor channel:  runaway\n# output will not be caught.\n\n# don't try to print from within any of the methods\n# of this object.\n\nclass output_producer:\n    \"\"\"Acts like an output file; suitable for capturing sys.stdout\"\"\"\n    def __init__ (self):\n        self.data = b''\n\n    def write (self, data):\n        lines = data.split('\\n')\n        data = '\\r\\n'.join(lines)\n        self.data += data\n\n    def writeline (self, line):\n        self.data = self.data + line + '\\r\\n'\n\n    def writelines (self, lines):\n        self.data = self.data + '\\r\\n'.join(lines) + '\\r\\n'\n\n    def flush (self):\n        pass\n\n    def softspace (self, *args):\n        pass\n\n    def more (self):\n        if self.data:\n            result = self.data[:512]\n            self.data = self.data[512:]\n            return result\n        else:\n            return ''\n\nclass composite_producer:\n    \"\"\"combine a fifo of producers into one\"\"\"\n    def __init__ (self, producers):\n        self.producers = producers\n\n    def more (self):\n        while len(self.producers):\n            p = self.producers[0]\n            d = p.more()\n            if d:\n                return d\n            else:\n                self.producers.pop(0)\n        else:\n            return b''\n\n\nclass globbing_producer:\n    \"\"\"\n    'glob' the output from a producer into a particular buffer size.\n    helps reduce the number of calls to send().  [this appears to\n    gain about 30% performance on requests to a single channel]\n    \"\"\"\n\n    def __init__ (self, producer, buffer_size=1<<16):\n        self.producer = producer\n        self.buffer = b''\n        self.buffer_size = buffer_size\n\n    def more (self):\n        while len(self.buffer) < self.buffer_size:\n            data = self.producer.more()\n            if data:\n                self.buffer = self.buffer + data\n            else:\n                break\n        r = self.buffer\n        self.buffer = b''\n        return r\n\n\nclass hooked_producer:\n    \"\"\"\n    A producer that will call <function> when it empties,.\n    with an argument of the number of bytes produced.  Useful\n    for logging/instrumentation purposes.\n    \"\"\"\n\n    def __init__ (self, producer, function):\n        self.producer = producer\n        self.function = function\n        self.bytes = 0\n\n    def more (self):\n        if self.producer:\n            result = self.producer.more()\n            if not result:\n                self.producer = None\n                self.function (self.bytes)\n            else:\n                self.bytes += len(result)\n            return result\n        else:\n            return ''\n\n# HTTP 1.1 emphasizes that an advertised Content-Length header MUST be\n# correct.  In the face of Strange Files, it is conceivable that\n# reading a 'file' may produce an amount of data not matching that\n# reported by os.stat() [text/binary mode issues, perhaps the file is\n# being appended to, etc..]  This makes the chunked encoding a True\n# Blessing, and it really ought to be used even with normal files.\n# How beautifully it blends with the concept of the producer.\n\nclass chunked_producer:\n    \"\"\"A producer that implements the 'chunked' transfer coding for HTTP/1.1.\n    Here is a sample usage:\n            request['Transfer-Encoding'] = 'chunked'\n            request.push (\n                    producers.chunked_producer (your_producer)\n                    )\n            request.done()\n    \"\"\"\n\n    def __init__ (self, producer, footers=None):\n        self.producer = producer\n        self.footers = footers\n\n    def more (self):\n        if self.producer:\n            data = self.producer.more()\n            if data:\n                s = '%x' % len(data)\n                return as_bytes(s) + b'\\r\\n' + data + b'\\r\\n'\n            else:\n                self.producer = None\n                if self.footers:\n                    return b'\\r\\n'.join([b'0'] + self.footers) + b'\\r\\n\\r\\n'\n                else:\n                    return b'0\\r\\n\\r\\n'\n        else:\n            return b''\n\ntry:\n    import zlib\nexcept ImportError:\n    zlib = None\n\nclass compressed_producer:\n    \"\"\"\n    Compress another producer on-the-fly, using ZLIB\n    \"\"\"\n\n    # Note: It's not very efficient to have the server repeatedly\n    # compressing your outgoing files: compress them ahead of time, or\n    # use a compress-once-and-store scheme.  However, if you have low\n    # bandwidth and low traffic, this may make more sense than\n    # maintaining your source files compressed.\n    #\n    # Can also be used for compressing dynamically-produced output.\n\n    def __init__ (self, producer, level=5):\n        self.producer = producer\n        self.compressor = zlib.compressobj (level)\n\n    def more (self):\n        if self.producer:\n            cdata = b''\n            # feed until we get some output\n            while not cdata:\n                data = self.producer.more()\n                if not data:\n                    self.producer = None\n                    return self.compressor.flush()\n                else:\n                    cdata = self.compressor.compress (data)\n            return cdata\n        else:\n            return b''\n\nclass escaping_producer:\n\n    \"\"\"A producer that escapes a sequence of characters\"\"\"\n    # Common usage: escaping the CRLF.CRLF sequence in SMTP, NNTP, etc...\n\n    def __init__ (self, producer, esc_from='\\r\\n.', esc_to='\\r\\n..'):\n        self.producer = producer\n        self.esc_from = esc_from\n        self.esc_to = esc_to\n        self.buffer = b''\n        self.find_prefix_at_end = find_prefix_at_end\n\n    def more (self):\n        esc_from = self.esc_from\n        esc_to   = self.esc_to\n\n        buffer = self.buffer + self.producer.more()\n\n        if buffer:\n            buffer = buffer.replace(esc_from, esc_to)\n            i = self.find_prefix_at_end (buffer, esc_from)\n            if i:\n                # we found a prefix\n                self.buffer = buffer[-i:]\n                return buffer[:-i]\n            else:\n                # no prefix, return it all\n                self.buffer = b''\n                return buffer\n        else:\n            return buffer\n"
  },
  {
    "path": "supervisor/medusa/util.py",
    "content": "from supervisor.compat import escape\n\ndef html_repr (object):\n    so = escape (repr (object))\n    if hasattr (object, 'hyper_respond'):\n        return '<a href=\"/status/object/%d/\">%s</a>' % (id (object), so)\n    else:\n        return so\n\n    # for example, tera, giga, mega, kilo\n# p_d (n, (1024, 1024, 1024, 1024))\n# smallest divider goes first - for example\n# minutes, hours, days\n# p_d (n, (60, 60, 24))\n\ndef progressive_divide (n, parts):\n    result = []\n    for part in parts:\n        n, rem = divmod (n, part)\n        result.append (rem)\n    result.append (n)\n    return result\n\n# b,k,m,g,t\ndef split_by_units (n, units, dividers, format_string):\n    divs = progressive_divide (n, dividers)\n    result = []\n    for i in range(len(units)):\n        if divs[i]:\n            result.append (format_string % (divs[i], units[i]))\n    result.reverse()\n    if not result:\n        return [format_string % (0, units[0])]\n    else:\n        return result\n\ndef english_bytes (n):\n    return split_by_units (\n            n,\n            ('','K','M','G','T'),\n            (1024, 1024, 1024, 1024, 1024),\n            '%d %sB'\n            )\n\ndef english_time (n):\n    return split_by_units (\n            n,\n            ('secs', 'mins', 'hours', 'days', 'weeks', 'years'),\n            (         60,     60,      24,     7,       52),\n            '%d %s'\n            )\n"
  },
  {
    "path": "supervisor/medusa/xmlrpc_handler.py",
    "content": "# -*- Mode: Python -*-\n\n# See http://www.xml-rpc.com/\n#     http://www.pythonware.com/products/xmlrpc/\n\n# Based on \"xmlrpcserver.py\" by Fredrik Lundh (fredrik@pythonware.com)\n\nVERSION = \"$Id: xmlrpc_handler.py,v 1.6 2004/04/21 14:09:24 akuchling Exp $\"\n\nfrom supervisor.compat import as_string\n\nimport supervisor.medusa.http_server as http_server\ntry:\n    import xmlrpclib\nexcept:\n    import xmlrpc.client as xmlrpclib\n\nimport sys\n\nclass xmlrpc_handler:\n\n    def match (self, request):\n        # Note: /RPC2 is not required by the spec, so you may override this method.\n        if request.uri[:5] == '/RPC2':\n            return 1\n        else:\n            return 0\n\n    def handle_request (self, request):\n        if request.command == 'POST':\n            request.collector = collector (self, request)\n        else:\n            request.error (400)\n\n    def continue_request (self, data, request):\n        params, method = xmlrpclib.loads (data)\n        try:\n            # generate response\n            try:\n                response = self.call (method, params)\n                if type(response) != type(()):\n                    response = (response,)\n            except:\n                # report exception back to server\n                response = xmlrpclib.dumps (\n                        xmlrpclib.Fault (1, \"%s:%s\" % (sys.exc_info()[0], sys.exc_info()[1]))\n                        )\n            else:\n                response = xmlrpclib.dumps (response, methodresponse=1)\n        except:\n            # internal error, report as HTTP server error\n            request.error (500)\n        else:\n            # got a valid XML RPC response\n            request['Content-Type'] = 'text/xml'\n            request.push (response)\n            request.done()\n\n    def call (self, method, params):\n        # override this method to implement RPC methods\n        raise Exception(\"NotYetImplemented\")\n\nclass collector:\n\n    \"\"\"gathers input for POST and PUT requests\"\"\"\n\n    def __init__ (self, handler, request):\n\n        self.handler = handler\n        self.request = request\n        self.data = []\n\n        # make sure there's a content-length header\n        cl = request.get_header ('content-length')\n\n        if not cl:\n            request.error (411)\n        else:\n            cl = int(cl)\n            # using a 'numeric' terminator\n            self.request.channel.set_terminator (cl)\n\n    def collect_incoming_data (self, data):\n        self.data.append(data)\n\n    def found_terminator (self):\n        # set the terminator back to the default\n        self.request.channel.set_terminator (b'\\r\\n\\r\\n')\n        # convert the data back to text for processing\n        data = as_string(b''.join(self.data))\n        self.handler.continue_request (data, self.request)\n\nif __name__ == '__main__':\n\n    class rpc_demo (xmlrpc_handler):\n\n        def call (self, method, params):\n            print('method=\"%s\" params=%s' % (method, params))\n            return \"Sure, that works\"\n\n    import supervisor.medusa.asyncore_25 as asyncore\n\n    hs = http_server.http_server ('', 8000)\n    rpc = rpc_demo()\n    hs.install_handler (rpc)\n\n    asyncore.loop()\n"
  },
  {
    "path": "supervisor/options.py",
    "content": "import socket\nimport getopt\nimport os\nimport sys\nimport tempfile\nimport errno\nimport signal\nimport re\nimport pwd\nimport grp\nimport resource\nimport stat\nimport glob\nimport platform\nimport warnings\nimport fcntl\n\nfrom supervisor.compat import PY2\nfrom supervisor.compat import ConfigParser\nfrom supervisor.compat import as_bytes, as_string\nfrom supervisor.compat import xmlrpclib\nfrom supervisor.compat import StringIO\nfrom supervisor.compat import basestring\nfrom supervisor.compat import import_spec\n\nfrom supervisor.medusa import asyncore_25 as asyncore\n\nfrom supervisor.datatypes import process_or_group_name\nfrom supervisor.datatypes import boolean\nfrom supervisor.datatypes import integer\nfrom supervisor.datatypes import name_to_uid\nfrom supervisor.datatypes import gid_for_uid\nfrom supervisor.datatypes import existing_dirpath\nfrom supervisor.datatypes import byte_size\nfrom supervisor.datatypes import signal_number\nfrom supervisor.datatypes import list_of_exitcodes\nfrom supervisor.datatypes import dict_of_key_value_pairs\nfrom supervisor.datatypes import logfile_name\nfrom supervisor.datatypes import list_of_strings\nfrom supervisor.datatypes import octal_type\nfrom supervisor.datatypes import existing_directory\nfrom supervisor.datatypes import logging_level\nfrom supervisor.datatypes import colon_separated_user_group\nfrom supervisor.datatypes import inet_address\nfrom supervisor.datatypes import InetStreamSocketConfig\nfrom supervisor.datatypes import UnixStreamSocketConfig\nfrom supervisor.datatypes import url\nfrom supervisor.datatypes import Automatic\nfrom supervisor.datatypes import Syslog\nfrom supervisor.datatypes import auto_restart\nfrom supervisor.datatypes import profile_options\n\nfrom supervisor import loggers\nfrom supervisor import states\nfrom supervisor import xmlrpc\nfrom supervisor import poller\n\ndef _read_version_txt():\n    mydir = os.path.abspath(os.path.dirname(__file__))\n    version_txt = os.path.join(mydir, 'version.txt')\n    with open(version_txt, 'r') as f:\n        return f.read().strip()\nVERSION = _read_version_txt()\n\ndef normalize_path(v):\n    return os.path.normpath(os.path.abspath(os.path.expanduser(v)))\n\nclass Dummy:\n    pass\n\nclass Options:\n    stderr = sys.stderr\n    stdout = sys.stdout\n    exit = sys.exit\n    warnings = warnings\n\n    uid = gid = None\n\n    progname = sys.argv[0]\n    configfile = None\n    schemadir = None\n    configroot = None\n    here = None\n\n    # Class variable deciding whether positional arguments are allowed.\n    # If you want positional arguments, set this to 1 in your subclass.\n    positional_args_allowed = 0\n\n    def __init__(self, require_configfile=True):\n        \"\"\"Constructor.\n\n        Params:\n        require_configfile -- whether we should fail on no config file.\n        \"\"\"\n        self.names_list = []\n        self.short_options = []\n        self.long_options = []\n        self.options_map = {}\n        self.default_map = {}\n        self.required_map = {}\n        self.environ_map = {}\n        self.attr_priorities = {}\n        self.require_configfile = require_configfile\n        self.add(None, None, \"h\", \"help\", self.help)\n        self.add(None, None, \"?\", None, self.help)\n        self.add(\"configfile\", None, \"c:\", \"configuration=\")\n\n        here = os.path.dirname(os.path.dirname(sys.argv[0]))\n        searchpaths = [os.path.join(here, 'etc', 'supervisord.conf'),\n                       os.path.join(here, 'supervisord.conf'),\n                       'supervisord.conf',\n                       'etc/supervisord.conf',\n                       '/etc/supervisord.conf',\n                       '/etc/supervisor/supervisord.conf',\n                       ]\n        self.searchpaths = searchpaths\n\n        self.environ_expansions = {}\n        for k, v in os.environ.items():\n            self.environ_expansions['ENV_%s' % k] = v\n\n    def default_configfile(self):\n        \"\"\"Return the name of the found config file or print usage/exit.\"\"\"\n        config = None\n        for path in self.searchpaths:\n            if os.path.exists(path):\n                config = path\n                break\n        if config is None and self.require_configfile:\n            self.usage('No config file found at default paths (%s); '\n                       'use the -c option to specify a config file '\n                       'at a different path' % ', '.join(self.searchpaths))\n        return config\n\n    def help(self, dummy):\n        \"\"\"Print a long help message to stdout and exit(0).\n\n        Occurrences of \"%s\" in are replaced by self.progname.\n        \"\"\"\n        help = self.doc + \"\\n\"\n        if help.find(\"%s\") > 0:\n            help = help.replace(\"%s\", self.progname)\n        self.stdout.write(help)\n        self.exit(0)\n\n    def usage(self, msg):\n        \"\"\"Print a brief error message to stderr and exit(2).\"\"\"\n        self.stderr.write(\"Error: %s\\n\" % str(msg))\n        self.stderr.write(\"For help, use %s -h\\n\" % self.progname)\n        self.exit(2)\n\n    def add(self,\n            name=None,                  # attribute name on self\n            confname=None,              # dotted config path name\n            short=None,                 # short option name\n            long=None,                  # long option name\n            handler=None,               # handler (defaults to string)\n            default=None,               # default value\n            required=None,              # message if not provided\n            flag=None,                  # if not None, flag value\n            env=None,                   # if not None, environment variable\n            ):\n        \"\"\"Add information about a configuration option.\n\n        This can take several forms:\n\n        add(name, confname)\n            Configuration option 'confname' maps to attribute 'name'\n        add(name, None, short, long)\n            Command line option '-short' or '--long' maps to 'name'\n        add(None, None, short, long, handler)\n            Command line option calls handler\n        add(name, None, short, long, handler)\n            Assign handler return value to attribute 'name'\n\n        In addition, one of the following keyword arguments may be given:\n\n        default=...  -- if not None, the default value\n        required=... -- if nonempty, an error message if no value provided\n        flag=...     -- if not None, flag value for command line option\n        env=...      -- if not None, name of environment variable that\n                        overrides the configuration file or default\n        \"\"\"\n        if flag is not None:\n            if handler is not None:\n                raise ValueError(\"use at most one of flag= and handler=\")\n            if not long and not short:\n                raise ValueError(\"flag= requires a command line flag\")\n            if short and short.endswith(\":\"):\n                raise ValueError(\"flag= requires a command line flag\")\n            if long and long.endswith(\"=\"):\n                raise ValueError(\"flag= requires a command line flag\")\n            handler = lambda arg, flag=flag: flag\n\n        if short and long:\n            if short.endswith(\":\") != long.endswith(\"=\"):\n                raise ValueError(\"inconsistent short/long options: %r %r\" % (\n                    short, long))\n\n        if short:\n            if short[0] == \"-\":\n                raise ValueError(\"short option should not start with '-'\")\n            key, rest = short[:1], short[1:]\n            if rest not in (\"\", \":\"):\n                raise ValueError(\"short option should be 'x' or 'x:'\")\n            key = \"-\" + key\n            if key in self.options_map:\n                raise ValueError(\"duplicate short option key '%s'\" % key)\n            self.options_map[key] = (name, handler)\n            self.short_options.append(short)\n\n        if long:\n            if long[0] == \"-\":\n                raise ValueError(\"long option should not start with '-'\")\n            key = long\n            if key[-1] == \"=\":\n                key = key[:-1]\n            key = \"--\" + key\n            if key in self.options_map:\n                raise ValueError(\"duplicate long option key '%s'\" % key)\n            self.options_map[key] = (name, handler)\n            self.long_options.append(long)\n\n        if env:\n            self.environ_map[env] = (name, handler)\n\n        if name:\n            if not hasattr(self, name):\n                setattr(self, name, None)\n            self.names_list.append((name, confname))\n            if default is not None:\n                self.default_map[name] = default\n            if required:\n                self.required_map[name] = required\n\n    def _set(self, attr, value, prio):\n        current = self.attr_priorities.get(attr, -1)\n        if prio >= current:\n            setattr(self, attr, value)\n            self.attr_priorities[attr] = prio\n\n    def realize(self, args=None, doc=None, progname=None):\n        \"\"\"Realize a configuration.\n\n        Optional arguments:\n\n        args     -- the command line arguments, less the program name\n                    (default is sys.argv[1:])\n\n        doc      -- usage message (default is __main__.__doc__)\n        \"\"\"\n        # Provide dynamic default method arguments\n        if args is None:\n            args = sys.argv[1:]\n        if progname is None:\n            progname = sys.argv[0]\n        if doc is None:\n            try:\n                import __main__\n                doc = __main__.__doc__\n            except Exception:\n                pass\n        self.progname = progname\n        self.doc = doc\n\n        self.options = []\n        self.args = []\n\n        # Call getopt\n        try:\n            self.options, self.args = getopt.getopt(\n                args, \"\".join(self.short_options), self.long_options)\n        except getopt.error as exc:\n            self.usage(str(exc))\n\n        # Check for positional args\n        if self.args and not self.positional_args_allowed:\n            self.usage(\"positional arguments are not supported: %s\" % (str(self.args)))\n\n        # Process options returned by getopt\n        for opt, arg in self.options:\n            name, handler = self.options_map[opt]\n            if handler is not None:\n                try:\n                    arg = handler(arg)\n                except ValueError as msg:\n                    self.usage(\"invalid value for %s %r: %s\" % (opt, arg, msg))\n            if name and arg is not None:\n                if getattr(self, name) is not None:\n                    self.usage(\"conflicting command line option %r\" % opt)\n                self._set(name, arg, 2)\n\n        # Process environment variables\n        for envvar in self.environ_map.keys():\n            name, handler = self.environ_map[envvar]\n            if envvar in os.environ:\n                value = os.environ[envvar]\n                if handler is not None:\n                    try:\n                        value = handler(value)\n                    except ValueError as msg:\n                        self.usage(\"invalid environment value for %s %r: %s\"\n                                   % (envvar, value, msg))\n                if name and value is not None:\n                    self._set(name, value, 1)\n\n        if self.configfile is None:\n            self.configfile = self.default_configfile()\n\n        self.process_config()\n\n    def process_config(self, do_usage=True):\n        \"\"\"Process configuration data structure.\n\n        This includes reading config file if necessary, setting defaults etc.\n        \"\"\"\n        if self.configfile:\n            self.process_config_file(do_usage)\n\n        # Copy config options to attributes of self.  This only fills\n        # in options that aren't already set from the command line.\n        for name, confname in self.names_list:\n            if confname:\n                parts = confname.split(\".\")\n                obj = self.configroot\n                for part in parts:\n                    if obj is None:\n                        break\n                    # Here AttributeError is not a user error!\n                    obj = getattr(obj, part)\n                self._set(name, obj, 0)\n\n        # Process defaults\n        for name, value in self.default_map.items():\n            if getattr(self, name) is None:\n                setattr(self, name, value)\n\n        # Process required options\n        for name, message in self.required_map.items():\n            if getattr(self, name) is None:\n                self.usage(message)\n\n    def process_config_file(self, do_usage):\n        # Process config file\n        if not hasattr(self.configfile, 'read'):\n            self.here = os.path.abspath(os.path.dirname(self.configfile))\n        try:\n            self.read_config(self.configfile)\n        except ValueError as msg:\n            if do_usage:\n                # if this is not called from an RPC method, run usage and exit.\n                self.usage(str(msg))\n            else:\n                # if this is called from an RPC method, raise an error\n                raise ValueError(msg)\n\n    def exists(self, path):\n        return os.path.exists(path)\n\n    def open(self, fn, mode='r'):\n        return open(fn, mode)\n\n    def get_plugins(self, parser, factory_key, section_prefix):\n        factories = []\n\n        for section in parser.sections():\n            if not section.startswith(section_prefix):\n                continue\n\n            name = section.split(':', 1)[1]\n            factory_spec = parser.saneget(section, factory_key, None)\n            if factory_spec is None:\n                raise ValueError('section [%s] does not specify a %s'  %\n                                 (section, factory_key))\n            try:\n                factory = self.import_spec(factory_spec)\n            except (AttributeError, ImportError) as e:\n                raise ValueError('%s cannot be resolved within [%s]: %s' % (\n                    factory_spec, section, e))\n\n            extras = {}\n            for k in parser.options(section):\n                if k != factory_key:\n                    extras[k] = parser.saneget(section, k)\n            factories.append((name, factory, extras))\n\n        return factories\n\n    def import_spec(self, spec):\n        \"\"\"On failure, raises either AttributeError or ImportError\"\"\"\n        return import_spec(spec)\n\n\nclass ServerOptions(Options):\n    user = None\n    sockchown = None\n    sockchmod = None\n    logfile = None\n    loglevel = None\n    pidfile = None\n    passwdfile = None\n    nodaemon = None\n    silent = None\n    httpservers = ()\n    unlink_pidfile = False\n    unlink_socketfiles = False\n    mood = states.SupervisorStates.RUNNING\n\n    def __init__(self):\n        Options.__init__(self)\n        self.configroot = Dummy()\n        self.configroot.supervisord = Dummy()\n\n        self.add(None, None, \"v\", \"version\", self.version)\n        self.add(\"nodaemon\", \"supervisord.nodaemon\", \"n\", \"nodaemon\", flag=1,\n                 default=0)\n        self.add(\"user\", \"supervisord.user\", \"u:\", \"user=\")\n        self.add(\"umask\", \"supervisord.umask\", \"m:\", \"umask=\",\n                 octal_type, default='022')\n        self.add(\"directory\", \"supervisord.directory\", \"d:\", \"directory=\",\n                 existing_directory)\n        self.add(\"logfile\", \"supervisord.logfile\", \"l:\", \"logfile=\",\n                 existing_dirpath, default=\"supervisord.log\")\n        self.add(\"logfile_maxbytes\", \"supervisord.logfile_maxbytes\",\n                 \"y:\", \"logfile_maxbytes=\", byte_size,\n                 default=50 * 1024 * 1024) # 50MB\n        self.add(\"logfile_backups\", \"supervisord.logfile_backups\",\n                 \"z:\", \"logfile_backups=\", integer, default=10)\n        self.add(\"loglevel\", \"supervisord.loglevel\", \"e:\", \"loglevel=\",\n                 logging_level, default=\"info\")\n        self.add(\"pidfile\", \"supervisord.pidfile\", \"j:\", \"pidfile=\",\n                 existing_dirpath, default=\"supervisord.pid\")\n        self.add(\"identifier\", \"supervisord.identifier\", \"i:\", \"identifier=\",\n                 str, default=\"supervisor\")\n        self.add(\"childlogdir\", \"supervisord.childlogdir\", \"q:\", \"childlogdir=\",\n                 existing_directory, default=tempfile.gettempdir())\n        self.add(\"minfds\", \"supervisord.minfds\",\n                 \"a:\", \"minfds=\", int, default=1024)\n        self.add(\"minprocs\", \"supervisord.minprocs\",\n                 \"\", \"minprocs=\", int, default=200)\n        self.add(\"nocleanup\", \"supervisord.nocleanup\",\n                 \"k\", \"nocleanup\", flag=1, default=0)\n        self.add(\"strip_ansi\", \"supervisord.strip_ansi\",\n                 \"t\", \"strip_ansi\", flag=1, default=0)\n        self.add(\"profile_options\", \"supervisord.profile_options\",\n                 \"\", \"profile_options=\", profile_options, default=None)\n        self.add(\"silent\", \"supervisord.silent\",\n                 \"s\", \"silent\", flag=1, default=0)\n        self.pidhistory = {}\n        self.process_group_configs = []\n        self.parse_criticals = []\n        self.parse_warnings = []\n        self.parse_infos = []\n        self.signal_receiver = SignalReceiver()\n        self.poller = poller.Poller(self)\n\n    def version(self, dummy):\n        \"\"\"Print version to stdout and exit(0).\n        \"\"\"\n        self.stdout.write('%s\\n' % VERSION)\n        self.exit(0)\n\n    # TODO: not covered by any test, but used by dispatchers\n    def getLogger(self, *args, **kwargs):\n        return loggers.getLogger(*args, **kwargs)\n\n    def default_configfile(self):\n        if os.getuid() == 0:\n            self.warnings.warn(\n                'Supervisord is running as root and it is searching '\n                'for its configuration file in default locations '\n                '(including its current working directory); you '\n                'probably want to specify a \"-c\" argument specifying an '\n                'absolute path to a configuration file for improved '\n                'security.'\n                )\n        return Options.default_configfile(self)\n\n    def realize(self, *arg, **kw):\n        Options.realize(self, *arg, **kw)\n        section = self.configroot.supervisord\n\n        # Additional checking of user option; set uid and gid\n        if self.user is not None:\n            try:\n                uid = name_to_uid(self.user)\n            except ValueError as msg:\n                self.usage(msg) # invalid user\n            self.uid = uid\n            self.gid = gid_for_uid(uid)\n\n        if not self.loglevel:\n            self.loglevel = section.loglevel\n\n        if self.logfile:\n            logfile = self.logfile\n        else:\n            logfile = section.logfile\n\n        if logfile != 'syslog':\n            # if the value for logfile is \"syslog\", we don't want to\n            # normalize the path to something like $CWD/syslog.log, but\n            # instead use the syslog service.\n            self.logfile = normalize_path(logfile)\n\n        if self.pidfile:\n            pidfile = self.pidfile\n        else:\n            pidfile = section.pidfile\n\n        self.pidfile = normalize_path(pidfile)\n\n        self.rpcinterface_factories = section.rpcinterface_factories\n\n        self.serverurl = None\n\n        self.server_configs = sconfigs = section.server_configs\n\n        # we need to set a fallback serverurl that process.spawn can use\n\n        # prefer a unix domain socket\n        for config in [ config for config in sconfigs if\n                        config['family'] is socket.AF_UNIX ]:\n            path = config['file']\n            self.serverurl = 'unix://%s' % path\n            break\n\n        # fall back to an inet socket\n        if self.serverurl is None:\n            for config in [ config for config in sconfigs if\n                            config['family'] is socket.AF_INET]:\n                host = config['host']\n                port = config['port']\n                if not host:\n                    host = 'localhost'\n                self.serverurl = 'http://%s:%s' % (host, port)\n\n        # self.serverurl may still be None if no servers at all are\n        # configured in the config file\n\n    def process_config(self, do_usage=True):\n        Options.process_config(self, do_usage=do_usage)\n\n        new = self.configroot.supervisord.process_group_configs\n        self.process_group_configs = new\n\n    def read_config(self, fp):\n        # Clear parse messages, since we may be re-reading the\n        # config a second time after a reload.\n        self.parse_criticals = []\n        self.parse_warnings = []\n        self.parse_infos = []\n\n        section = self.configroot.supervisord\n        need_close = False\n        if not hasattr(fp, 'read'):\n            if not self.exists(fp):\n                raise ValueError(\"could not find config file %s\" % fp)\n            try:\n                fp = self.open(fp, 'r')\n                need_close = True\n            except (IOError, OSError):\n                raise ValueError(\"could not read config file %s\" % fp)\n\n        parser = UnhosedConfigParser()\n        parser.expansions = self.environ_expansions\n        try:\n            try:\n                parser.read_file(fp)\n            except AttributeError:\n                parser.readfp(fp)\n        except ConfigParser.ParsingError as why:\n            raise ValueError(str(why))\n        finally:\n            if need_close:\n                fp.close()\n\n        host_node_name = platform.node()\n        expansions = {'here':self.here,\n                      'host_node_name':host_node_name}\n        expansions.update(self.environ_expansions)\n        if parser.has_section('include'):\n            parser.expand_here(self.here)\n            if not parser.has_option('include', 'files'):\n                raise ValueError(\".ini file has [include] section, but no \"\n                \"files setting\")\n            files = parser.get('include', 'files')\n            files = expand(files, expansions, 'include.files')\n            files = files.split()\n            if hasattr(fp, 'name'):\n                base = os.path.dirname(os.path.abspath(fp.name))\n            else:\n                base = '.'\n            for pattern in files:\n                pattern = os.path.join(base, pattern)\n                filenames = glob.glob(pattern)\n                if not filenames:\n                    self.parse_warnings.append(\n                        'No file matches via include \"%s\"' % pattern)\n                    continue\n                for filename in sorted(filenames):\n                    self.parse_infos.append(\n                        'Included extra file \"%s\" during parsing' % filename)\n                    try:\n                        parser.read(filename)\n                    except ConfigParser.ParsingError as why:\n                        raise ValueError(str(why))\n                    else:\n                        parser.expand_here(\n                            os.path.abspath(os.path.dirname(filename))\n                        )\n\n        sections = parser.sections()\n        if not 'supervisord' in sections:\n            raise ValueError('.ini file does not include supervisord section')\n\n        common_expansions = {'here':self.here}\n        def get(opt, default, **kwargs):\n            expansions = kwargs.get('expansions', {})\n            expansions.update(common_expansions)\n            kwargs['expansions'] = expansions\n            return parser.getdefault(opt, default, **kwargs)\n\n        section.minfds = integer(get('minfds', 1024))\n        section.minprocs = integer(get('minprocs', 200))\n\n        directory = get('directory', None)\n        if directory is None:\n            section.directory = None\n        else:\n            section.directory = existing_directory(directory)\n\n        section.user = get('user', None)\n        section.umask = octal_type(get('umask', '022'))\n        section.logfile = existing_dirpath(get('logfile', 'supervisord.log'))\n        section.logfile_maxbytes = byte_size(get('logfile_maxbytes', '50MB'))\n        section.logfile_backups = integer(get('logfile_backups', 10))\n        section.loglevel = logging_level(get('loglevel', 'info'))\n        section.pidfile = existing_dirpath(get('pidfile', 'supervisord.pid'))\n        section.identifier = get('identifier', 'supervisor')\n        section.nodaemon = boolean(get('nodaemon', 'false'))\n        section.silent = boolean(get('silent', 'false'))\n\n        tempdir = tempfile.gettempdir()\n        section.childlogdir = existing_directory(get('childlogdir', tempdir))\n        section.nocleanup = boolean(get('nocleanup', 'false'))\n        section.strip_ansi = boolean(get('strip_ansi', 'false'))\n\n        environ_str = get('environment', '', do_expand=False)\n        environ_str = expand(environ_str, expansions, 'environment')\n        section.environment = dict_of_key_value_pairs(environ_str)\n\n        # extend expansions for global from [supervisord] environment definition\n        for k, v in section.environment.items():\n            self.environ_expansions['ENV_%s' % k ] = v\n\n        # Process rpcinterface plugins before groups to allow custom events to\n        # be registered.\n        section.rpcinterface_factories = self.get_plugins(\n            parser,\n            'supervisor.rpcinterface_factory',\n            'rpcinterface:'\n            )\n        section.process_group_configs = self.process_groups_from_parser(parser)\n        for group in section.process_group_configs:\n            for proc in group.process_configs:\n                env = section.environment.copy()\n                env.update(proc.environment)\n                proc.environment = env\n        section.server_configs = self.server_configs_from_parser(parser)\n        section.profile_options = None\n        return section\n\n    def process_groups_from_parser(self, parser):\n        groups = []\n        all_sections = parser.sections()\n        homogeneous_exclude = []\n\n        common_expansions = {'here':self.here}\n        def get(section, opt, default, **kwargs):\n            expansions = kwargs.get('expansions', {})\n            expansions.update(common_expansions)\n            kwargs['expansions'] = expansions\n            return parser.saneget(section, opt, default, **kwargs)\n\n        # process heterogeneous groups\n        for section in all_sections:\n            if not section.startswith('group:'):\n                continue\n            group_name = process_or_group_name(section.split(':', 1)[1])\n            programs = list_of_strings(get(section, 'programs', None))\n            priority = integer(get(section, 'priority', 999))\n            group_processes = []\n            for program in programs:\n                program_section = \"program:%s\" % program\n                fcgi_section = \"fcgi-program:%s\" % program\n                if not program_section in all_sections and not fcgi_section in all_sections:\n                    raise ValueError(\n                        '[%s] names unknown program or fcgi-program %s' % (section, program))\n                if program_section in all_sections and fcgi_section in all_sections:\n                     raise ValueError(\n                        '[%s] name %s is ambiguous (exists as program and fcgi-program)' %\n                        (section, program))\n                section = program_section if program_section in all_sections else fcgi_section\n                homogeneous_exclude.append(section)\n                processes = self.processes_from_section(parser, section,\n                                                        group_name, ProcessConfig)\n\n                group_processes.extend(processes)\n            groups.append(\n                ProcessGroupConfig(self, group_name, priority, group_processes)\n                )\n\n        # process \"normal\" homogeneous groups\n        for section in all_sections:\n            if ( (not section.startswith('program:') )\n                 or section in homogeneous_exclude ):\n                continue\n            program_name = process_or_group_name(section.split(':', 1)[1])\n            priority = integer(get(section, 'priority', 999))\n            processes=self.processes_from_section(parser, section, program_name,\n                                                  ProcessConfig)\n            groups.append(\n                ProcessGroupConfig(self, program_name, priority, processes)\n                )\n\n        # process \"event listener\" homogeneous groups\n        for section in all_sections:\n            if not section.startswith('eventlistener:'):\n                continue\n            pool_name = section.split(':', 1)[1]\n\n            # give listeners a \"high\" default priority so they are started first\n            # and stopped last at mainloop exit\n            priority = integer(get(section, 'priority', -1))\n\n            buffer_size = integer(get(section, 'buffer_size', 10))\n            if buffer_size < 1:\n                raise ValueError('[%s] section sets invalid buffer_size (%d)' %\n                    (section, buffer_size))\n\n            result_handler = get(section, 'result_handler',\n                                       'supervisor.dispatchers:default_handler')\n            try:\n                result_handler = self.import_spec(result_handler)\n            except (AttributeError, ImportError) as e:\n                raise ValueError('%s cannot be resolved within [%s]: %s' % (\n                    result_handler, section, e))\n\n            pool_event_names = [x.upper() for x in\n                                list_of_strings(get(section, 'events', ''))]\n            pool_event_names = set(pool_event_names)\n            if not pool_event_names:\n                raise ValueError('[%s] section requires an \"events\" line' %\n                                 section)\n\n            from supervisor.events import EventTypes\n            pool_events = []\n            for pool_event_name in pool_event_names:\n                pool_event = getattr(EventTypes, pool_event_name, None)\n                if pool_event is None:\n                    raise ValueError('Unknown event type %s in [%s] events' %\n                                     (pool_event_name, section))\n                pool_events.append(pool_event)\n\n            redirect_stderr = boolean(get(section, 'redirect_stderr', 'false'))\n            if redirect_stderr:\n                raise ValueError('[%s] section sets redirect_stderr=true '\n                    'but this is not allowed because it will interfere '\n                    'with the eventlistener protocol' % section)\n\n            processes=self.processes_from_section(parser, section, pool_name,\n                                                  EventListenerConfig)\n\n            groups.append(\n                EventListenerPoolConfig(self, pool_name, priority, processes,\n                                        buffer_size, pool_events,\n                                        result_handler)\n                )\n\n        # process fastcgi homogeneous groups\n        for section in all_sections:\n            if ( (not section.startswith('fcgi-program:') )\n                 or section in homogeneous_exclude ):\n                continue\n            program_name = process_or_group_name(section.split(':', 1)[1])\n            priority = integer(get(section, 'priority', 999))\n            fcgi_expansions = {'program_name': program_name}\n\n            # find proc_uid from \"user\" option\n            proc_user = get(section, 'user', None)\n            if proc_user is None:\n                proc_uid = None\n            else:\n                proc_uid = name_to_uid(proc_user)\n\n            socket_backlog = get(section, 'socket_backlog', None)\n\n            if socket_backlog is not None:\n                socket_backlog = integer(socket_backlog)\n                if (socket_backlog < 1 or socket_backlog > 65535):\n                    raise ValueError('Invalid socket_backlog value %s'\n                                                            % socket_backlog)\n\n            socket_owner = get(section, 'socket_owner', None)\n            if socket_owner is not None:\n                try:\n                    socket_owner = colon_separated_user_group(socket_owner)\n                except ValueError:\n                    raise ValueError('Invalid socket_owner value %s'\n                                                                % socket_owner)\n\n            socket_mode = get(section, 'socket_mode', None)\n            if socket_mode is not None:\n                try:\n                    socket_mode = octal_type(socket_mode)\n                except (TypeError, ValueError):\n                    raise ValueError('Invalid socket_mode value %s'\n                                                                % socket_mode)\n\n            socket = get(section, 'socket', None, expansions=fcgi_expansions)\n            if not socket:\n                raise ValueError('[%s] section requires a \"socket\" line' %\n                                 section)\n\n            try:\n                socket_config = self.parse_fcgi_socket(socket, proc_uid,\n                                                    socket_owner, socket_mode,\n                                                    socket_backlog)\n            except ValueError as e:\n                raise ValueError('%s in [%s] socket' % (str(e), section))\n\n            processes=self.processes_from_section(parser, section, program_name,\n                                                  FastCGIProcessConfig)\n            groups.append(\n                FastCGIGroupConfig(self, program_name, priority, processes,\n                                   socket_config)\n                )\n\n        groups.sort()\n        return groups\n\n    def parse_fcgi_socket(self, sock, proc_uid, socket_owner, socket_mode,\n            socket_backlog):\n        if sock.startswith('unix://'):\n            path = sock[7:]\n            #Check it's an absolute path\n            if not os.path.isabs(path):\n                raise ValueError(\"Unix socket path %s is not an absolute path\",\n                                 path)\n            path = normalize_path(path)\n\n            if socket_owner is None:\n                uid = os.getuid()\n                if proc_uid is not None and proc_uid != uid:\n                    socket_owner = (proc_uid, gid_for_uid(proc_uid))\n\n            if socket_mode is None:\n                socket_mode = 0o700\n\n            return UnixStreamSocketConfig(path, owner=socket_owner,\n                                                mode=socket_mode,\n                                                backlog=socket_backlog)\n\n        if socket_owner is not None or socket_mode is not None:\n            raise ValueError(\"socket_owner and socket_mode params should\"\n                    + \" only be used with a Unix domain socket\")\n\n        m = re.match(r'tcp://([^\\s:]+):(\\d+)$', sock)\n        if m:\n            host = m.group(1)\n            port = int(m.group(2))\n            return InetStreamSocketConfig(host, port,\n                    backlog=socket_backlog)\n\n        raise ValueError(\"Bad socket format %s\", sock)\n\n    def processes_from_section(self, parser, section, group_name,\n                               klass=None):\n        try:\n            return self._processes_from_section(\n                parser, section, group_name, klass)\n        except ValueError as e:\n            filename = parser.section_to_file.get(section, self.configfile)\n            raise ValueError('%s in section %r (file: %r)'\n                             % (e, section, filename))\n\n    def _processes_from_section(self, parser, section, group_name,\n                                klass=None):\n        if klass is None:\n            klass = ProcessConfig\n        programs = []\n\n        program_name = process_or_group_name(section.split(':', 1)[1])\n        host_node_name = platform.node()\n        common_expansions = {'here':self.here,\n                      'program_name':program_name,\n                      'host_node_name':host_node_name,\n                      'group_name':group_name}\n        def get(section, opt, *args, **kwargs):\n            expansions = kwargs.get('expansions', {})\n            expansions.update(common_expansions)\n            kwargs['expansions'] = expansions\n            return parser.saneget(section, opt, *args, **kwargs)\n\n        priority = integer(get(section, 'priority', 999))\n        autostart = boolean(get(section, 'autostart', 'true'))\n        autorestart = auto_restart(get(section, 'autorestart', 'unexpected'))\n        startsecs = integer(get(section, 'startsecs', 1))\n        startretries = integer(get(section, 'startretries', 3))\n        stopsignal = signal_number(get(section, 'stopsignal', 'TERM'))\n        stopwaitsecs = integer(get(section, 'stopwaitsecs', 10))\n        stopasgroup = boolean(get(section, 'stopasgroup', 'false'))\n        killasgroup = boolean(get(section, 'killasgroup', stopasgroup))\n        exitcodes = list_of_exitcodes(get(section, 'exitcodes', '0'))\n        # see also redirect_stderr check in process_groups_from_parser()\n        redirect_stderr = boolean(get(section, 'redirect_stderr','false'))\n        numprocs = integer(get(section, 'numprocs', 1))\n        numprocs_start = integer(get(section, 'numprocs_start', 0))\n        environment_str = get(section, 'environment', '', do_expand=False)\n        stdout_cmaxbytes = byte_size(get(section,'stdout_capture_maxbytes','0'))\n        stdout_events = boolean(get(section, 'stdout_events_enabled','false'))\n        stderr_cmaxbytes = byte_size(get(section,'stderr_capture_maxbytes','0'))\n        stderr_events = boolean(get(section, 'stderr_events_enabled','false'))\n        serverurl = get(section, 'serverurl', None)\n        if serverurl and serverurl.strip().upper() == 'AUTO':\n            serverurl = None\n\n        # find uid from \"user\" option\n        user = get(section, 'user', None)\n        if user is None:\n            uid = None\n        else:\n            uid = name_to_uid(user)\n\n        umask = get(section, 'umask', None)\n        if umask is not None:\n            umask = octal_type(umask)\n\n        process_name = process_or_group_name(\n            get(section, 'process_name', '%(program_name)s', do_expand=False))\n\n        if numprocs > 1:\n            if not '%(process_num)' in process_name:\n                # process_name needs to include process_num when we\n                # represent a group of processes\n                raise ValueError(\n                    '%(process_num) must be present within process_name when '\n                    'numprocs > 1')\n\n        if stopasgroup and not killasgroup:\n            raise ValueError(\n                \"Cannot set stopasgroup=true and killasgroup=false\"\n                )\n\n        for process_num in range(numprocs_start, numprocs + numprocs_start):\n            expansions = common_expansions\n            expansions.update({'process_num': process_num, 'numprocs': numprocs})\n            expansions.update(self.environ_expansions)\n\n            environment = dict_of_key_value_pairs(\n                expand(environment_str, expansions, 'environment'))\n\n            # extend expansions for process from [program:x] environment definition\n            for k, v in environment.items():\n                expansions['ENV_%s' % k] = v\n\n            directory = get(section, 'directory', None)\n\n            logfiles = {}\n\n            for k in ('stdout', 'stderr'):\n                lf_key = '%s_logfile' % k\n                lf_val = get(section, lf_key, Automatic)\n                if isinstance(lf_val, basestring):\n                    lf_val = expand(lf_val, expansions, lf_key)\n                lf_val = logfile_name(lf_val)\n                logfiles[lf_key] = lf_val\n\n                bu_key = '%s_logfile_backups' % k\n                backups = integer(get(section, bu_key, 10))\n                logfiles[bu_key] = backups\n\n                mb_key = '%s_logfile_maxbytes' % k\n                maxbytes = byte_size(get(section, mb_key, '50MB'))\n                logfiles[mb_key] = maxbytes\n\n                sy_key = '%s_syslog' % k\n                syslog = boolean(get(section, sy_key, False))\n                logfiles[sy_key] = syslog\n\n                # rewrite deprecated \"syslog\" magic logfile into the equivalent\n                # TODO remove this in a future version\n                if lf_val is Syslog:\n                    self.parse_warnings.append(\n                        'For [%s], %s=syslog but this is deprecated and will '\n                        'be removed.  Use %s=true to enable syslog instead.' % (\n                        section, lf_key, sy_key))\n                    logfiles[lf_key] = lf_val = None\n                    logfiles[sy_key] = True\n\n                if lf_val is Automatic and not maxbytes:\n                    self.parse_warnings.append(\n                        'For [%s], AUTO logging used for %s without '\n                        'rollover, set maxbytes > 0 to avoid filling up '\n                        'filesystem unintentionally' % (section, lf_key))\n\n            if redirect_stderr:\n                if logfiles['stderr_logfile'] not in (Automatic, None):\n                    self.parse_warnings.append(\n                        'For [%s], redirect_stderr=true but stderr_logfile has '\n                        'also been set to a filename, the filename has been '\n                        'ignored' % section)\n                # never create an stderr logfile when redirected\n                logfiles['stderr_logfile'] = None\n\n            command = get(section, 'command', None, expansions=expansions)\n            if command is None:\n                raise ValueError(\n                    'program section %s does not specify a command' % section)\n\n            pconfig = klass(\n                self,\n                name=expand(process_name, expansions, 'process_name'),\n                command=command,\n                directory=directory,\n                umask=umask,\n                priority=priority,\n                autostart=autostart,\n                autorestart=autorestart,\n                startsecs=startsecs,\n                startretries=startretries,\n                uid=uid,\n                stdout_logfile=logfiles['stdout_logfile'],\n                stdout_capture_maxbytes = stdout_cmaxbytes,\n                stdout_events_enabled = stdout_events,\n                stdout_logfile_backups=logfiles['stdout_logfile_backups'],\n                stdout_logfile_maxbytes=logfiles['stdout_logfile_maxbytes'],\n                stdout_syslog=logfiles['stdout_syslog'],\n                stderr_logfile=logfiles['stderr_logfile'],\n                stderr_capture_maxbytes = stderr_cmaxbytes,\n                stderr_events_enabled = stderr_events,\n                stderr_logfile_backups=logfiles['stderr_logfile_backups'],\n                stderr_logfile_maxbytes=logfiles['stderr_logfile_maxbytes'],\n                stderr_syslog=logfiles['stderr_syslog'],\n                stopsignal=stopsignal,\n                stopwaitsecs=stopwaitsecs,\n                stopasgroup=stopasgroup,\n                killasgroup=killasgroup,\n                exitcodes=exitcodes,\n                redirect_stderr=redirect_stderr,\n                environment=environment,\n                serverurl=serverurl)\n\n            programs.append(pconfig)\n\n        programs.sort() # asc by priority\n        return programs\n\n    def _parse_servernames(self, parser, stype):\n        options = []\n        for section in parser.sections():\n            if section.startswith(stype):\n                parts = section.split(':', 1)\n                if len(parts) > 1:\n                    name = parts[1]\n                else:\n                    name = None # default sentinel\n                options.append((name, section))\n        return options\n\n    def _parse_username_and_password(self, parser, section):\n        get = parser.saneget\n        username = get(section, 'username', None)\n        password = get(section, 'password', None)\n        if username is not None or password is not None:\n            if username is None or password is None:\n                raise ValueError(\n                    'Section [%s] contains incomplete authentication: '\n                    'If a username or a password is specified, both the '\n                    'username and password must be specified' % section)\n        return {'username':username, 'password':password}\n\n    def server_configs_from_parser(self, parser):\n        configs = []\n        inet_serverdefs = self._parse_servernames(parser, 'inet_http_server')\n        for name, section in inet_serverdefs:\n            config = {}\n            get = parser.saneget\n            config.update(self._parse_username_and_password(parser, section))\n            config['name'] = name\n            config['family'] = socket.AF_INET\n            port = get(section, 'port', None)\n            if port is None:\n                raise ValueError('section [%s] has no port value' % section)\n            host, port = inet_address(port)\n            config['host'] = host\n            config['port'] = port\n            config['section'] = section\n            configs.append(config)\n\n        unix_serverdefs = self._parse_servernames(parser, 'unix_http_server')\n        for name, section in unix_serverdefs:\n            config = {}\n            get = parser.saneget\n            sfile = get(section, 'file', None, expansions={'here': self.here})\n            if sfile is None:\n                raise ValueError('section [%s] has no file value' % section)\n            sfile = sfile.strip()\n            config['name'] = name\n            config['family'] = socket.AF_UNIX\n            config['file'] = normalize_path(sfile)\n            config.update(self._parse_username_and_password(parser, section))\n            chown = get(section, 'chown', None)\n            if chown is not None:\n                try:\n                    chown = colon_separated_user_group(chown)\n                except ValueError:\n                    raise ValueError('Invalid sockchown value %s' % chown)\n            else:\n                chown = (-1, -1)\n            config['chown'] = chown\n            chmod = get(section, 'chmod', None)\n            if chmod is not None:\n                try:\n                    chmod = octal_type(chmod)\n                except (TypeError, ValueError):\n                    raise ValueError('Invalid chmod value %s' % chmod)\n            else:\n                chmod = 0o700\n            config['chmod'] = chmod\n            config['section'] = section\n            configs.append(config)\n\n        return configs\n\n    def daemonize(self):\n        self.poller.before_daemonize()\n        self._daemonize()\n        self.poller.after_daemonize()\n\n    def _daemonize(self):\n        # To daemonize, we need to become the leader of our own session\n        # (process) group.  If we do not, signals sent to our\n        # parent process will also be sent to us.   This might be bad because\n        # signals such as SIGINT can be sent to our parent process during\n        # normal (uninteresting) operations such as when we press Ctrl-C in the\n        # parent terminal window to escape from a logtail command.\n        # To disassociate ourselves from our parent's session group we use\n        # os.setsid.  It means \"set session id\", which has the effect of\n        # disassociating a process from is current session and process group\n        # and setting itself up as a new session leader.\n        #\n        # Unfortunately we cannot call setsid if we're already a session group\n        # leader, so we use \"fork\" to make a copy of ourselves that is\n        # guaranteed to not be a session group leader.\n        #\n        # We also change directories, set stderr and stdout to null, and\n        # change our umask.\n        #\n        # This explanation was (gratefully) garnered from\n        # http://www.cems.uwe.ac.uk/~irjohnso/coursenotes/lrc/system/daemons/d3.htm\n\n        pid = os.fork()\n        if pid != 0:\n            # Parent\n            self.logger.blather(\"supervisord forked; parent exiting\")\n            os._exit(0)\n        # Child\n        self.logger.info(\"daemonizing the supervisord process\")\n        if self.directory:\n            try:\n                os.chdir(self.directory)\n            except OSError as err:\n                self.logger.critical(\"can't chdir into %r: %s\"\n                                     % (self.directory, err))\n            else:\n                self.logger.info(\"set current directory: %r\"\n                                 % self.directory)\n        os.close(0)\n        self.stdin = sys.stdin = sys.__stdin__ = open(\"/dev/null\")\n        os.close(1)\n        self.stdout = sys.stdout = sys.__stdout__ = open(\"/dev/null\", \"w\")\n        os.close(2)\n        self.stderr = sys.stderr = sys.__stderr__ = open(\"/dev/null\", \"w\")\n        os.setsid()\n        os.umask(self.umask)\n        # XXX Stevens, in his Advanced Unix book, section 13.3 (page\n        # 417) recommends calling umask(0) and closing unused\n        # file descriptors.  In his Network Programming book, he\n        # additionally recommends ignoring SIGHUP and forking again\n        # after the setsid() call, for obscure SVR4 reasons.\n\n    def write_pidfile(self):\n        pid = os.getpid()\n        try:\n            with open(self.pidfile, 'w') as f:\n                f.write('%s\\n' % pid)\n        except (IOError, OSError):\n            self.logger.critical('could not write pidfile %s' % self.pidfile)\n        else:\n            self.unlink_pidfile = True\n            self.logger.info('supervisord started with pid %s' % pid)\n\n    def cleanup(self):\n        for config, server in self.httpservers:\n            if config['family'] == socket.AF_UNIX:\n                if self.unlink_socketfiles:\n                    socketname = config['file']\n                    self._try_unlink(socketname)\n        if self.unlink_pidfile:\n            self._try_unlink(self.pidfile)\n        self.poller.close()\n\n    def _try_unlink(self, path):\n        try:\n            os.unlink(path)\n        except OSError:\n            pass\n\n    def close_httpservers(self):\n        dispatcher_servers = []\n        for config, server in self.httpservers:\n            server.close()\n            # server._map is a reference to the asyncore socket_map\n            for dispatcher in self.get_socket_map().values():\n                dispatcher_server = getattr(dispatcher, 'server', None)\n                if dispatcher_server is server:\n                    dispatcher_servers.append(dispatcher)\n        for server in dispatcher_servers:\n            # TODO: try to remove this entirely.\n            # For unknown reasons, sometimes an http_channel\n            # dispatcher in the socket map related to servers\n            # remains open *during a reload*.  If one of these\n            # exists at this point, we need to close it by hand\n            # (thus removing it from the asyncore.socket_map).  If\n            # we don't do this, 'cleanup_fds' will cause its file\n            # descriptor to be closed, but it will still remain in\n            # the socket_map, and eventually its file descriptor\n            # will be passed to # select(), which will bomb.  See\n            # also https://web.archive.org/web/20160729222427/http://www.plope.com/software/collector/253\n            server.close()\n\n    def close_logger(self):\n        self.logger.close()\n\n    def setsignals(self):\n        receive = self.signal_receiver.receive\n        signal.signal(signal.SIGTERM, receive)\n        signal.signal(signal.SIGINT, receive)\n        signal.signal(signal.SIGQUIT, receive)\n        signal.signal(signal.SIGHUP, receive)\n        signal.signal(signal.SIGCHLD, receive)\n        signal.signal(signal.SIGUSR2, receive)\n\n    def get_signal(self):\n        return self.signal_receiver.get_signal()\n\n    def openhttpservers(self, supervisord):\n        try:\n            self.httpservers = self.make_http_servers(supervisord)\n            self.unlink_socketfiles = True\n        except socket.error as why:\n            if why.args[0] == errno.EADDRINUSE:\n                self.usage('Another program is already listening on '\n                           'a port that one of our HTTP servers is '\n                           'configured to use.  Shut this program '\n                           'down first before starting supervisord.')\n            else:\n                help = 'Cannot open an HTTP server: socket.error reported'\n                errorname = errno.errorcode.get(why.args[0])\n                if errorname is None:\n                    self.usage('%s %s' % (help, why.args[0]))\n                else:\n                    self.usage('%s errno.%s (%d)' %\n                               (help, errorname, why.args[0]))\n        except ValueError as why:\n            self.usage(why.args[0])\n\n    def get_autochildlog_name(self, name, identifier, channel):\n        prefix='%s-%s---%s-' % (name, channel, identifier)\n        logfile = self.mktempfile(\n            suffix='.log',\n            prefix=prefix,\n            dir=self.childlogdir)\n        return logfile\n\n    def clear_autochildlogdir(self):\n        # must be called after realize()\n        childlogdir = self.childlogdir\n        fnre = re.compile(r'.+?---%s-\\S+\\.log\\.{0,1}\\d{0,4}' % self.identifier)\n        try:\n            filenames = os.listdir(childlogdir)\n        except (IOError, OSError):\n            self.logger.warn('Could not clear childlog dir')\n            return\n\n        for filename in filenames:\n            if fnre.match(filename):\n                pathname = os.path.join(childlogdir, filename)\n                try:\n                    self.remove(pathname)\n                except (OSError, IOError):\n                    self.logger.warn('Failed to clean up %r' % pathname)\n\n    def get_socket_map(self):\n        return asyncore.socket_map\n\n    def cleanup_fds(self):\n        # try to close any leaked file descriptors (for reload)\n        start = 5\n        os.closerange(start, self.minfds)\n\n    def kill(self, pid, signal):\n        os.kill(pid, signal)\n\n    def waitpid(self):\n        # Need pthread_sigmask here to avoid concurrent sigchld, but Python\n        # doesn't offer in Python < 3.4.  There is still a race condition here;\n        # we can get a sigchld while we're sitting in the waitpid call.\n        # However, AFAICT, if waitpid is interrupted by SIGCHLD, as long as we\n        # call waitpid again (which happens every so often during the normal\n        # course in the mainloop), we'll eventually reap the child that we\n        # tried to reap during the interrupted call. At least on Linux, this\n        # appears to be true, or at least stopping 50 processes at once never\n        # left zombies laying around.\n        try:\n            pid, sts = os.waitpid(-1, os.WNOHANG)\n        except OSError as exc:\n            code = exc.args[0]\n            if code not in (errno.ECHILD, errno.EINTR):\n                self.logger.critical(\n                    'waitpid error %r; '\n                    'a process may not be cleaned up properly' % code\n                    )\n            if code == errno.EINTR:\n                self.logger.blather('EINTR during reap')\n            pid, sts = None, None\n        return pid, sts\n\n    def drop_privileges(self, user):\n        \"\"\"Drop privileges to become the specified user, which may be a\n        username or uid.  Called for supervisord startup and when spawning\n        subprocesses.  Returns None on success or a string error message if\n        privileges could not be dropped.\"\"\"\n        if user is None:\n            return \"No user specified to setuid to!\"\n\n        # get uid for user, which can be a number or username\n        try:\n            uid = int(user)\n        except ValueError:\n            try:\n                pwrec = pwd.getpwnam(user)\n            except KeyError:\n                return \"Can't find username %r\" % user\n            uid = pwrec[2]\n        else:\n            try:\n                pwrec = pwd.getpwuid(uid)\n            except KeyError:\n                return \"Can't find uid %r\" % uid\n\n        current_uid = os.getuid()\n\n        if current_uid == uid:\n            # do nothing and return successfully if the uid is already the\n            # current one.  this allows a supervisord running as an\n            # unprivileged user \"foo\" to start a process where the config\n            # has \"user=foo\" (same user) in it.\n            return\n\n        if current_uid != 0:\n            return \"Can't drop privilege as nonroot user\"\n\n        gid = pwrec[3]\n        if hasattr(os, 'setgroups'):\n            user = pwrec[0]\n            groups = [grprec[2] for grprec in grp.getgrall() if user in\n                      grprec[3]]\n\n            # always put our primary gid first in this list, otherwise we can\n            # lose group info since sometimes the first group in the setgroups\n            # list gets overwritten on the subsequent setgid call (at least on\n            # freebsd 9 with python 2.7 - this will be safe though for all unix\n            # /python version combos)\n            groups.insert(0, gid)\n            try:\n                os.setgroups(groups)\n            except OSError:\n                return 'Could not set groups of effective user'\n        try:\n            os.setgid(gid)\n        except OSError:\n            return 'Could not set group id of effective user'\n        os.setuid(uid)\n\n    def set_uid_or_exit(self):\n        \"\"\"Set the uid of the supervisord process.  Called during supervisord\n        startup only.  No return value.  Exits the process via usage() if\n        privileges could not be dropped.\"\"\"\n        if self.uid is None:\n            if os.getuid() == 0:\n                self.parse_criticals.append('Supervisor is running as root.  '\n                        'Privileges were not dropped because no user is '\n                        'specified in the config file.  If you intend to run '\n                        'as root, you can set user=root in the config file '\n                        'to avoid this message.')\n        else:\n            msg = self.drop_privileges(self.uid)\n            if msg is None:\n                self.parse_infos.append('Set uid to user %s succeeded' %\n                                        self.uid)\n            else:  # failed to drop privileges\n                self.usage(msg)\n\n    def set_rlimits_or_exit(self):\n        \"\"\"Set the rlimits of the supervisord process.  Called during\n        supervisord startup only.  No return value.  Exits the process via\n        usage() if any rlimits could not be set.\"\"\"\n        limits = []\n        if hasattr(resource, 'RLIMIT_NOFILE'):\n            limits.append(\n                {\n                'msg':('The minimum number of file descriptors required '\n                       'to run this process is %(min_limit)s as per the \"minfds\" '\n                       'command-line argument or config file setting. '\n                       'The current environment will only allow you '\n                       'to open %(hard)s file descriptors.  Either raise '\n                       'the number of usable file descriptors in your '\n                       'environment (see README.rst) or lower the '\n                       'minfds setting in the config file to allow '\n                       'the process to start.'),\n                'min':self.minfds,\n                'resource':resource.RLIMIT_NOFILE,\n                'name':'RLIMIT_NOFILE',\n                })\n        if hasattr(resource, 'RLIMIT_NPROC'):\n            limits.append(\n                {\n                'msg':('The minimum number of available processes required '\n                       'to run this program is %(min_limit)s as per the \"minprocs\" '\n                       'command-line argument or config file setting. '\n                       'The current environment will only allow you '\n                       'to open %(hard)s processes.  Either raise '\n                       'the number of usable processes in your '\n                       'environment (see README.rst) or lower the '\n                       'minprocs setting in the config file to allow '\n                       'the program to start.'),\n                'min':self.minprocs,\n                'resource':resource.RLIMIT_NPROC,\n                'name':'RLIMIT_NPROC',\n                })\n\n        for limit in limits:\n            min_limit = limit['min']\n            res = limit['resource']\n            msg = limit['msg']\n            name = limit['name']\n            name = name # name is used below by locals()\n\n            soft, hard = resource.getrlimit(res)\n\n            if (soft < min_limit) and (soft != -1): # -1 means unlimited\n                if (hard < min_limit) and (hard != -1):\n                    # setrlimit should increase the hard limit if we are\n                    # root, if not then setrlimit raises and we print usage\n                    hard = min_limit\n\n                try:\n                    resource.setrlimit(res, (min_limit, hard))\n                    self.parse_infos.append('Increased %(name)s limit to '\n                                '%(min_limit)s' % locals())\n                except (resource.error, ValueError):\n                    self.usage(msg % locals())\n\n    def make_logger(self):\n        # must be called after realize() and after supervisor does setuid()\n        format = '%(asctime)s %(levelname)s %(message)s\\n'\n        self.logger = loggers.getLogger(self.loglevel)\n        if self.nodaemon and not self.silent:\n            loggers.handle_stdout(self.logger, format)\n        loggers.handle_file(\n            self.logger,\n            self.logfile,\n            format,\n            rotating=not not self.logfile_maxbytes,\n            maxbytes=self.logfile_maxbytes,\n            backups=self.logfile_backups,\n        )\n        for msg in self.parse_criticals:\n            self.logger.critical(msg)\n        for msg in self.parse_warnings:\n            self.logger.warn(msg)\n        for msg in self.parse_infos:\n            self.logger.info(msg)\n\n    def make_http_servers(self, supervisord):\n        from supervisor.http import make_http_servers\n        return make_http_servers(self, supervisord)\n\n    def close_fd(self, fd):\n        try:\n            os.close(fd)\n        except OSError:\n            pass\n\n    def fork(self):\n        return os.fork()\n\n    def dup2(self, frm, to):\n        return os.dup2(frm, to)\n\n    def setpgrp(self):\n        return os.setpgrp()\n\n    def stat(self, filename):\n        return os.stat(filename)\n\n    def write(self, fd, data):\n        return os.write(fd, as_bytes(data))\n\n    def execve(self, filename, argv, env):\n        return os.execve(filename, argv, env)\n\n    def mktempfile(self, suffix, prefix, dir):\n        # set os._urandomfd as a hack around bad file descriptor bug\n        # seen in the wild, see\n        # https://web.archive.org/web/20160729044005/http://www.plope.com/software/collector/252\n        os._urandomfd = None\n        fd, filename = tempfile.mkstemp(suffix, prefix, dir)\n        os.close(fd)\n        return filename\n\n    def remove(self, path):\n        os.remove(path)\n\n    def _exit(self, code):\n        os._exit(code)\n\n    def setumask(self, mask):\n        os.umask(mask)\n\n    def get_path(self):\n        \"\"\"Return a list corresponding to $PATH, or a default.\"\"\"\n        path = [\"/bin\", \"/usr/bin\", \"/usr/local/bin\"]\n        if \"PATH\" in os.environ:\n            p = os.environ[\"PATH\"]\n            if p:\n                path = p.split(os.pathsep)\n        return path\n\n    def get_pid(self):\n        return os.getpid()\n\n    def check_execv_args(self, filename, argv, st):\n        if st is None:\n            raise NotFound(\"can't find command %r\" % filename)\n\n        elif stat.S_ISDIR(st[stat.ST_MODE]):\n            raise NotExecutable(\"command at %r is a directory\" % filename)\n\n        elif not (stat.S_IMODE(st[stat.ST_MODE]) & 0o111):\n            raise NotExecutable(\"command at %r is not executable\" % filename)\n\n        elif not os.access(filename, os.X_OK):\n            raise NoPermission(\"no permission to run command %r\" % filename)\n\n    def reopenlogs(self):\n        self.logger.info('supervisord logreopen')\n        for handler in self.logger.handlers:\n            if hasattr(handler, 'reopen'):\n                handler.reopen()\n\n    def readfd(self, fd):\n        try:\n            data = os.read(fd, 2 << 16) # 128K\n        except OSError as why:\n            if why.args[0] not in (errno.EWOULDBLOCK, errno.EBADF, errno.EINTR):\n                raise\n            data = b''\n        return data\n\n    def chdir(self, dir):\n        os.chdir(dir)\n\n    def make_pipes(self, stderr=True):\n        \"\"\" Create pipes for parent to child stdin/stdout/stderr\n        communications.  Open fd in non-blocking mode so we can read them\n        in the mainloop without blocking.  If stderr is False, don't\n        create a pipe for stderr. \"\"\"\n\n        pipes = {'child_stdin':None,\n                 'stdin':None,\n                 'stdout':None,\n                 'child_stdout':None,\n                 'stderr':None,\n                 'child_stderr':None}\n        try:\n            stdin, child_stdin = os.pipe()\n            pipes['child_stdin'], pipes['stdin'] = stdin, child_stdin\n            stdout, child_stdout = os.pipe()\n            pipes['stdout'], pipes['child_stdout'] = stdout, child_stdout\n            if stderr:\n                stderr, child_stderr = os.pipe()\n                pipes['stderr'], pipes['child_stderr'] = stderr, child_stderr\n            for fd in (pipes['stdout'], pipes['stderr'], pipes['stdin']):\n                if fd is not None:\n                    flags = fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NDELAY\n                    fcntl.fcntl(fd, fcntl.F_SETFL, flags)\n            return pipes\n        except OSError:\n            for fd in pipes.values():\n                if fd is not None:\n                    self.close_fd(fd)\n            raise\n\n    def close_parent_pipes(self, pipes):\n        for fdname in ('stdin', 'stdout', 'stderr'):\n            fd = pipes.get(fdname)\n            if fd is not None:\n                self.close_fd(fd)\n\n    def close_child_pipes(self, pipes):\n        for fdname in ('child_stdin', 'child_stdout', 'child_stderr'):\n            fd = pipes.get(fdname)\n            if fd is not None:\n                self.close_fd(fd)\n\nclass ClientOptions(Options):\n    positional_args_allowed = 1\n\n    interactive = None\n    prompt = None\n    serverurl = None\n    username = None\n    password = None\n    history_file = None\n\n    def __init__(self):\n        Options.__init__(self, require_configfile=False)\n        self.configroot = Dummy()\n        self.configroot.supervisorctl = Dummy()\n        self.configroot.supervisorctl.interactive = None\n        self.configroot.supervisorctl.prompt = 'supervisor'\n        self.configroot.supervisorctl.serverurl = None\n        self.configroot.supervisorctl.username = None\n        self.configroot.supervisorctl.password = None\n        self.configroot.supervisorctl.history_file = None\n\n        from supervisor.supervisorctl import DefaultControllerPlugin\n        default_factory = ('default', DefaultControllerPlugin, {})\n        # we always add the default factory. If you want to a supervisorctl\n        # without the default plugin, please write your own supervisorctl.\n        self.plugin_factories = [default_factory]\n\n        self.add(\"interactive\", \"supervisorctl.interactive\", \"i\",\n                 \"interactive\", flag=1, default=0)\n        self.add(\"prompt\", \"supervisorctl.prompt\", default=\"supervisor\")\n        self.add(\"serverurl\", \"supervisorctl.serverurl\", \"s:\", \"serverurl=\",\n                 url, default=\"http://localhost:9001\")\n        self.add(\"username\", \"supervisorctl.username\", \"u:\", \"username=\")\n        self.add(\"password\", \"supervisorctl.password\", \"p:\", \"password=\")\n        self.add(\"history\", \"supervisorctl.history_file\", \"r:\", \"history_file=\")\n\n    def realize(self, *arg, **kw):\n        Options.realize(self, *arg, **kw)\n        if not self.args:\n            self.interactive = 1\n\n    def read_config(self, fp):\n        section = self.configroot.supervisorctl\n        need_close = False\n        if not hasattr(fp, 'read'):\n            self.here = os.path.dirname(normalize_path(fp))\n            if not self.exists(fp):\n                raise ValueError(\"could not find config file %s\" % fp)\n            try:\n                fp = self.open(fp, 'r')\n                need_close = True\n            except (IOError, OSError):\n                raise ValueError(\"could not read config file %s\" % fp)\n\n        parser = UnhosedConfigParser()\n        parser.expansions = self.environ_expansions\n        parser.mysection = 'supervisorctl'\n        try:\n            parser.read_file(fp)\n        except AttributeError:\n            parser.readfp(fp)\n        if need_close:\n            fp.close()\n        sections = parser.sections()\n        if not 'supervisorctl' in sections:\n            raise ValueError('.ini file does not include supervisorctl section')\n        serverurl = parser.getdefault('serverurl', 'http://localhost:9001',\n            expansions={'here': self.here})\n        if serverurl.startswith('unix://'):\n            path = normalize_path(serverurl[7:])\n            serverurl = 'unix://%s' % path\n        section.serverurl = serverurl\n\n        # The defaults used below are really set in __init__ (since\n        # section==self.configroot.supervisorctl)\n        section.prompt = parser.getdefault('prompt', section.prompt)\n        section.username = parser.getdefault('username', section.username)\n        section.password = parser.getdefault('password', section.password)\n        history_file = parser.getdefault('history_file', section.history_file,\n            expansions={'here': self.here})\n\n        if history_file:\n            history_file = normalize_path(history_file)\n            section.history_file = history_file\n            self.history_file = history_file\n        else:\n            section.history_file = None\n            self.history_file = None\n\n        self.plugin_factories += self.get_plugins(\n            parser,\n            'supervisor.ctl_factory',\n            'ctlplugin:'\n            )\n\n        return section\n\n    # TODO: not covered by any test, but used by supervisorctl\n    def getServerProxy(self):\n        return xmlrpclib.ServerProxy(\n            # dumbass ServerProxy won't allow us to pass in a non-HTTP url,\n            # so we fake the url we pass into it and always use the transport's\n            # 'serverurl' to figure out what to attach to\n            'http://127.0.0.1',\n            transport = xmlrpc.SupervisorTransport(self.username,\n                                                   self.password,\n                                                   self.serverurl)\n            )\n\n_marker = []\n\nclass UnhosedConfigParser(ConfigParser.RawConfigParser):\n    mysection = 'supervisord'\n\n    def __init__(self, *args, **kwargs):\n        # inline_comment_prefixes and strict were added in Python 3 but their\n        # defaults make RawConfigParser behave differently than it did on\n        # Python 2.  We make it work like 2 by default for backwards compat.\n        if not PY2:\n            if 'inline_comment_prefixes' not in kwargs:\n                kwargs['inline_comment_prefixes'] = (';', '#')\n\n            if 'strict' not in kwargs:\n                kwargs['strict'] = False\n\n        ConfigParser.RawConfigParser.__init__(self, *args, **kwargs)\n\n        self.section_to_file = {}\n        self.expansions = {}\n\n    def read_string(self, string, source='<string>'):\n        '''Parse configuration data from a string.  This is intended\n        to be used in tests only.  We add this method for Py 2/3 compat.'''\n        try:\n            return ConfigParser.RawConfigParser.read_string(\n                self, string, source) # Python 3.2 or later\n        except AttributeError:\n            return self.readfp(StringIO(string))\n\n    def read(self, filenames, **kwargs):\n        '''Attempt to read and parse a list of filenames, returning a list\n        of filenames which were successfully parsed.  This is a method of\n        RawConfigParser that is overridden to build self.section_to_file,\n        which is a mapping of section names to the files they came from.\n        '''\n        if isinstance(filenames, basestring):  # RawConfigParser compat\n            filenames = [filenames]\n\n        ok_filenames = []\n        for filename in filenames:\n            sections_orig = self._sections.copy()\n\n            ok_filenames.extend(\n                ConfigParser.RawConfigParser.read(self, [filename], **kwargs))\n\n            diff = frozenset(self._sections) - frozenset(sections_orig)\n            for section in diff:\n                self.section_to_file[section] = filename\n        return ok_filenames\n\n    def saneget(self, section, option, default=_marker, do_expand=True,\n                expansions={}):\n        try:\n            optval = self.get(section, option)\n        except ConfigParser.NoOptionError:\n            if default is _marker:\n                raise\n            else:\n                optval = default\n\n        if do_expand and isinstance(optval, basestring):\n            combined_expansions = dict(\n                list(self.expansions.items()) + list(expansions.items()))\n\n            optval = expand(optval, combined_expansions,\n                           \"%s.%s\" % (section, option))\n\n        return optval\n\n    def getdefault(self, option, default=_marker, expansions={}, **kwargs):\n        return self.saneget(self.mysection, option, default=default,\n                            expansions=expansions, **kwargs)\n\n    def expand_here(self, here):\n        HERE_FORMAT = '%(here)s'\n        for section in self.sections():\n            for key, value in self.items(section):\n                if HERE_FORMAT in value:\n                    assert here is not None, \"here has not been set to a path\"\n                    value = value.replace(HERE_FORMAT, here)\n                    self.set(section, key, value)\n\n\nclass Config(object):\n    def __ne__(self, other):\n        return not self.__eq__(other)\n\n    def __lt__(self, other):\n        if self.priority == other.priority:\n            return self.name < other.name\n\n        return self.priority < other.priority\n\n    def __le__(self, other):\n        if self.priority == other.priority:\n            return self.name <= other.name\n\n        return self.priority <= other.priority\n\n    def __gt__(self, other):\n        if self.priority == other.priority:\n            return self.name > other.name\n\n        return self.priority > other.priority\n\n    def __ge__(self, other):\n        if self.priority == other.priority:\n            return self.name >= other.name\n\n        return self.priority >= other.priority\n\n    def __repr__(self):\n        return '<%s instance at %s named %s>' % (self.__class__, id(self),\n                                                 self.name)\n\nclass ProcessConfig(Config):\n    req_param_names = [\n        'name', 'uid', 'command', 'directory', 'umask', 'priority',\n        'autostart', 'autorestart', 'startsecs', 'startretries',\n        'stdout_logfile', 'stdout_capture_maxbytes',\n        'stdout_events_enabled', 'stdout_syslog',\n        'stdout_logfile_backups', 'stdout_logfile_maxbytes',\n        'stderr_logfile', 'stderr_capture_maxbytes',\n        'stderr_logfile_backups', 'stderr_logfile_maxbytes',\n        'stderr_events_enabled', 'stderr_syslog',\n        'stopsignal', 'stopwaitsecs', 'stopasgroup', 'killasgroup',\n        'exitcodes', 'redirect_stderr' ]\n    optional_param_names = [ 'environment', 'serverurl' ]\n\n    def __init__(self, options, **params):\n        self.options = options\n        for name in self.req_param_names:\n            setattr(self, name, params[name])\n        for name in self.optional_param_names:\n            setattr(self, name, params.get(name, None))\n\n    def __eq__(self, other):\n        if not isinstance(other, ProcessConfig):\n            return False\n\n        for name in self.req_param_names + self.optional_param_names:\n            if Automatic in [getattr(self, name), getattr(other, name)] :\n                continue\n            if getattr(self, name) != getattr(other, name):\n                return False\n\n        return True\n\n    def get_path(self):\n        '''Return a list corresponding to $PATH that is configured to be set\n        in the process environment, or the system default.'''\n        if self.environment is not None:\n            path = self.environment.get('PATH')\n            if path is not None:\n                return path.split(os.pathsep)\n        return self.options.get_path()\n\n    def create_autochildlogs(self):\n        # temporary logfiles which are erased at start time\n        get_autoname = self.options.get_autochildlog_name\n        sid = self.options.identifier\n        name = self.name\n        if self.stdout_logfile is Automatic:\n            self.stdout_logfile = get_autoname(name, sid, 'stdout')\n        if self.stderr_logfile is Automatic:\n            self.stderr_logfile = get_autoname(name, sid, 'stderr')\n\n    def make_process(self, group=None):\n        from supervisor.process import Subprocess\n        process = Subprocess(self)\n        process.group = group\n        return process\n\n    def make_dispatchers(self, proc):\n        use_stderr = not self.redirect_stderr\n        p = self.options.make_pipes(use_stderr)\n        stdout_fd,stderr_fd,stdin_fd = p['stdout'],p['stderr'],p['stdin']\n        dispatchers = {}\n        from supervisor.dispatchers import POutputDispatcher\n        from supervisor.dispatchers import PInputDispatcher\n        from supervisor import events\n        if stdout_fd is not None:\n            etype = events.ProcessCommunicationStdoutEvent\n            dispatchers[stdout_fd] = POutputDispatcher(proc, etype, stdout_fd)\n        if stderr_fd is not None:\n            etype = events.ProcessCommunicationStderrEvent\n            dispatchers[stderr_fd] = POutputDispatcher(proc,etype, stderr_fd)\n        if stdin_fd is not None:\n            dispatchers[stdin_fd] = PInputDispatcher(proc, 'stdin', stdin_fd)\n        return dispatchers, p\n\nclass EventListenerConfig(ProcessConfig):\n    def make_dispatchers(self, proc):\n        # always use_stderr=True for eventlisteners because mixing stderr\n        # messages into stdout would break the eventlistener protocol\n        use_stderr = True\n        p = self.options.make_pipes(use_stderr)\n        stdout_fd,stderr_fd,stdin_fd = p['stdout'],p['stderr'],p['stdin']\n        dispatchers = {}\n        from supervisor.dispatchers import PEventListenerDispatcher\n        from supervisor.dispatchers import PInputDispatcher\n        from supervisor.dispatchers import POutputDispatcher\n        from supervisor import events\n        if stdout_fd is not None:\n            dispatchers[stdout_fd] = PEventListenerDispatcher(proc, 'stdout',\n                                                              stdout_fd)\n        if stderr_fd is not None:\n            etype = events.ProcessCommunicationStderrEvent\n            dispatchers[stderr_fd] = POutputDispatcher(proc, etype, stderr_fd)\n        if stdin_fd is not None:\n            dispatchers[stdin_fd] = PInputDispatcher(proc, 'stdin', stdin_fd)\n        return dispatchers, p\n\nclass FastCGIProcessConfig(ProcessConfig):\n\n    def make_process(self, group=None):\n        if group is None:\n            raise NotImplementedError('FastCGI programs require a group')\n        from supervisor.process import FastCGISubprocess\n        process = FastCGISubprocess(self)\n        process.group = group\n        return process\n\n    def make_dispatchers(self, proc):\n        dispatchers, p = ProcessConfig.make_dispatchers(self, proc)\n        #FastCGI child processes expect the FastCGI socket set to\n        #file descriptor 0, so supervisord cannot use stdin\n        #to communicate with the child process\n        stdin_fd = p['stdin']\n        if stdin_fd is not None:\n            dispatchers[stdin_fd].close()\n        return dispatchers, p\n\nclass ProcessGroupConfig(Config):\n    def __init__(self, options, name, priority, process_configs):\n        self.options = options\n        self.name = name\n        self.priority = priority\n        self.process_configs = process_configs\n\n    def __eq__(self, other):\n        if not isinstance(other, ProcessGroupConfig):\n            return False\n\n        if self.name != other.name:\n            return False\n        if self.priority != other.priority:\n            return False\n        if self.process_configs != other.process_configs:\n            return False\n\n        return True\n\n    def after_setuid(self):\n        for config in self.process_configs:\n            config.create_autochildlogs()\n\n    def make_group(self):\n        from supervisor.process import ProcessGroup\n        return ProcessGroup(self)\n\nclass EventListenerPoolConfig(Config):\n    def __init__(self, options, name, priority, process_configs, buffer_size,\n                 pool_events, result_handler):\n        self.options = options\n        self.name = name\n        self.priority = priority\n        self.process_configs = process_configs\n        self.buffer_size = buffer_size\n        self.pool_events = pool_events\n        self.result_handler = result_handler\n\n    def __eq__(self, other):\n        if not isinstance(other, EventListenerPoolConfig):\n            return False\n\n        if ((self.name == other.name) and\n            (self.priority == other.priority) and\n            (self.process_configs == other.process_configs) and\n            (self.buffer_size == other.buffer_size) and\n            (self.pool_events == other.pool_events) and\n            (self.result_handler == other.result_handler)):\n            return True\n\n        return False\n\n    def after_setuid(self):\n        for config in self.process_configs:\n            config.create_autochildlogs()\n\n    def make_group(self):\n        from supervisor.process import EventListenerPool\n        return EventListenerPool(self)\n\nclass FastCGIGroupConfig(ProcessGroupConfig):\n    def __init__(self, options, name, priority, process_configs, socket_config):\n        ProcessGroupConfig.__init__(\n            self,\n            options,\n            name,\n            priority,\n            process_configs,\n            )\n        self.socket_config = socket_config\n\n    def __eq__(self, other):\n        if not isinstance(other, FastCGIGroupConfig):\n            return False\n\n        if self.socket_config != other.socket_config:\n            return False\n\n        return ProcessGroupConfig.__eq__(self, other)\n\n    def make_group(self):\n        from supervisor.process import FastCGIProcessGroup\n        return FastCGIProcessGroup(self)\n\ndef readFile(filename, offset, length):\n    \"\"\" Read length bytes from the file named by filename starting at\n    offset \"\"\"\n\n    absoffset = abs(offset)\n    abslength = abs(length)\n\n    try:\n        with open(filename, 'rb') as f:\n            if absoffset != offset:\n                # negative offset returns offset bytes from tail of the file\n                if length:\n                    raise ValueError('BAD_ARGUMENTS')\n                f.seek(0, 2)\n                sz = f.tell()\n                pos = int(sz - absoffset)\n                if pos < 0:\n                    pos = 0\n                f.seek(pos)\n                data = f.read(absoffset)\n            else:\n                if abslength != length:\n                    raise ValueError('BAD_ARGUMENTS')\n                if length == 0:\n                    f.seek(offset)\n                    data = f.read()\n                else:\n                    f.seek(offset)\n                    data = f.read(length)\n    except (OSError, IOError):\n        raise ValueError('FAILED')\n\n    return data\n\ndef tailFile(filename, offset, length):\n    \"\"\"\n    Read length bytes from the file named by filename starting at\n    offset, automatically increasing offset and setting overflow\n    flag if log size has grown beyond (offset + length).  If length\n    bytes are not available, as many bytes as are available are returned.\n    \"\"\"\n\n    try:\n        with open(filename, 'rb') as f:\n            overflow = False\n            f.seek(0, 2)\n            sz = f.tell()\n\n            if sz > (offset + length):\n                overflow = True\n                offset = sz - 1\n\n            if (offset + length) > sz:\n                if offset > (sz - 1):\n                    length = 0\n                offset = sz - length\n\n            if offset < 0:\n                offset = 0\n            if length < 0:\n                length = 0\n\n            if length == 0:\n                data = b''\n            else:\n                f.seek(offset)\n                data = f.read(length)\n\n            offset = sz\n            return [as_string(data), offset, overflow]\n    except (OSError, IOError):\n        return ['', offset, False]\n\n# Helpers for dealing with signals and exit status\n\ndef decode_wait_status(sts):\n    \"\"\"Decode the status returned by wait() or waitpid().\n\n    Return a tuple (exitstatus, message) where exitstatus is the exit\n    status, or -1 if the process was killed by a signal; and message\n    is a message telling what happened.  It is the caller's\n    responsibility to display the message.\n    \"\"\"\n    if os.WIFEXITED(sts):\n        es = os.WEXITSTATUS(sts) & 0xffff\n        msg = \"exit status %s\" % es\n        return es, msg\n    elif os.WIFSIGNALED(sts):\n        sig = os.WTERMSIG(sts)\n        msg = \"terminated by %s\" % signame(sig)\n        if hasattr(os, \"WCOREDUMP\"):\n            iscore = os.WCOREDUMP(sts)\n        else:\n            iscore = sts & 0x80\n        if iscore:\n            msg += \" (core dumped)\"\n        return -1, msg\n    else:\n        msg = \"unknown termination cause 0x%04x\" % sts\n        return -1, msg\n\n_signames = None\n\ndef signame(sig):\n    \"\"\"Return a symbolic name for a signal.\n\n    Return \"signal NNN\" if there is no corresponding SIG name in the\n    signal module.\n    \"\"\"\n\n    if _signames is None:\n        _init_signames()\n    return _signames.get(sig) or \"signal %d\" % sig\n\ndef _init_signames():\n    global _signames\n    d = {}\n    for k, v in signal.__dict__.items():\n        k_startswith = getattr(k, \"startswith\", None)\n        if k_startswith is None:\n            continue\n        if k_startswith(\"SIG\") and not k_startswith(\"SIG_\"):\n            d[v] = k\n    _signames = d\n\nclass SignalReceiver:\n    def __init__(self):\n        self._signals_recvd = []\n\n    def receive(self, sig, frame):\n        if sig not in self._signals_recvd:\n            self._signals_recvd.append(sig)\n\n    def get_signal(self):\n        if self._signals_recvd:\n            sig = self._signals_recvd.pop(0)\n        else:\n            sig = None\n        return sig\n\n# miscellaneous utility functions\n\ndef expand(s, expansions, name):\n    try:\n        return s % expansions\n    except KeyError as ex:\n        available = list(expansions.keys())\n        available.sort()\n        raise ValueError(\n            'Format string %r for %r contains names (%s) which cannot be '\n            'expanded. Available names: %s' %\n            (s, name, str(ex), \", \".join(available)))\n    except Exception as ex:\n        raise ValueError(\n            'Format string %r for %r is badly formatted: %s' %\n            (s, name, str(ex))\n        )\n\ndef make_namespec(group_name, process_name):\n    # we want to refer to the process by its \"short name\" (a process named\n    # process1 in the group process1 has a name \"process1\").  This is for\n    # backwards compatibility\n    if group_name == process_name:\n        name = process_name\n    else:\n        name = '%s:%s' % (group_name, process_name)\n    return name\n\ndef split_namespec(namespec):\n    names = namespec.split(':', 1)\n    if len(names) == 2:\n        # group and process name differ\n        group_name, process_name = names\n        if not process_name or process_name == '*':\n            process_name = None\n    else:\n        # group name is same as process name\n        group_name, process_name = namespec, namespec\n    return group_name, process_name\n\n# exceptions\n\nclass ProcessException(Exception):\n    \"\"\" Specialized exceptions used when attempting to start a process \"\"\"\n\nclass BadCommand(ProcessException):\n    \"\"\" Indicates the command could not be parsed properly. \"\"\"\n\nclass NotExecutable(ProcessException):\n    \"\"\" Indicates that the filespec cannot be executed because its path\n    resolves to a file which is not executable, or which is a directory. \"\"\"\n\nclass NotFound(ProcessException):\n    \"\"\" Indicates that the filespec cannot be executed because it could not\n    be found \"\"\"\n\nclass NoPermission(ProcessException):\n    \"\"\" Indicates that the file cannot be executed because the supervisor\n    process does not possess the appropriate UNIX filesystem permission\n    to execute the file. \"\"\"\n"
  },
  {
    "path": "supervisor/pidproxy.py",
    "content": "#!/usr/bin/env python -u\n\n\"\"\"pidproxy -- run command and proxy signals to it via its pidfile.\n\nThis executable runs a command and then monitors a pidfile.  When this\nexecutable receives a signal, it sends the same signal to the pid\nin the pidfile.\n\nUsage: %s <pidfile name> <command> [<cmdarg1> ...]\n\"\"\"\n\nimport os\nimport sys\nimport signal\nimport time\n\nclass PidProxy:\n    pid = None\n\n    def __init__(self, args):\n        try:\n            self.pidfile, cmdargs = args[1], args[2:]\n            self.abscmd = os.path.abspath(cmdargs[0])\n            self.cmdargs = cmdargs\n        except (ValueError, IndexError):\n            self.usage()\n            sys.exit(1)\n\n    def go(self):\n        self.setsignals()\n        self.pid = os.spawnv(os.P_NOWAIT, self.abscmd, self.cmdargs)\n        while 1:\n            time.sleep(5)\n            try:\n                pid = os.waitpid(-1, os.WNOHANG)[0]\n            except OSError:\n                pid = None\n            if pid:\n                break\n\n    def usage(self):\n        print(__doc__ % sys.argv[0])\n\n    def setsignals(self):\n        signal.signal(signal.SIGTERM, self.passtochild)\n        signal.signal(signal.SIGHUP, self.passtochild)\n        signal.signal(signal.SIGINT, self.passtochild)\n        signal.signal(signal.SIGUSR1, self.passtochild)\n        signal.signal(signal.SIGUSR2, self.passtochild)\n        signal.signal(signal.SIGQUIT, self.passtochild)\n        signal.signal(signal.SIGCHLD, self.reap)\n\n    def reap(self, sig, frame):\n        # do nothing, we reap our child synchronously\n        pass\n\n    def passtochild(self, sig, frame):\n        try:\n            with open(self.pidfile, 'r') as f:\n                pid = int(f.read().strip())\n        except:\n            print(\"Can't read child pidfile %s!\" % self.pidfile)\n            return\n        os.kill(pid, sig)\n        if sig in [signal.SIGTERM, signal.SIGINT, signal.SIGQUIT]:\n            sys.exit(0)\n\ndef main():\n    pp = PidProxy(sys.argv)\n    pp.go()\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "supervisor/poller.py",
    "content": "import select\nimport errno\n\nclass BasePoller:\n\n    def __init__(self, options):\n        self.options = options\n        self.initialize()\n\n    def initialize(self):\n        pass\n\n    def register_readable(self, fd):\n        raise NotImplementedError\n\n    def register_writable(self, fd):\n        raise NotImplementedError\n\n    def unregister_readable(self, fd):\n        raise NotImplementedError\n\n    def unregister_writable(self, fd):\n        raise NotImplementedError\n\n    def poll(self, timeout):\n        raise NotImplementedError\n\n    def before_daemonize(self):\n        pass\n\n    def after_daemonize(self):\n        pass\n\n    def close(self):\n        pass\n\n\nclass SelectPoller(BasePoller):\n\n    def initialize(self):\n        self._select = select\n        self._init_fdsets()\n\n    def register_readable(self, fd):\n        self.readables.add(fd)\n\n    def register_writable(self, fd):\n        self.writables.add(fd)\n\n    def unregister_readable(self, fd):\n        self.readables.discard(fd)\n\n    def unregister_writable(self, fd):\n        self.writables.discard(fd)\n\n    def unregister_all(self):\n        self._init_fdsets()\n\n    def poll(self, timeout):\n        try:\n            r, w, x = self._select.select(\n                self.readables,\n                self.writables,\n                [], timeout\n                )\n        except select.error as err:\n            if err.args[0] == errno.EINTR:\n                self.options.logger.blather('EINTR encountered in poll')\n                return [], []\n            if err.args[0] == errno.EBADF:\n                self.options.logger.blather('EBADF encountered in poll')\n                self.unregister_all()\n                return [], []\n            raise\n        return r, w\n\n    def _init_fdsets(self):\n        self.readables = set()\n        self.writables = set()\n\nclass PollPoller(BasePoller):\n\n    def initialize(self):\n        self._poller = select.poll()\n        self.READ = select.POLLIN | select.POLLPRI | select.POLLHUP\n        self.WRITE = select.POLLOUT\n        self.readables = set()\n        self.writables = set()\n\n    def register_readable(self, fd):\n        self._poller.register(fd, self.READ)\n        self.readables.add(fd)\n\n    def register_writable(self, fd):\n        self._poller.register(fd, self.WRITE)\n        self.writables.add(fd)\n\n    def unregister_readable(self, fd):\n        self.readables.discard(fd)\n        self._poller.unregister(fd)\n        if fd in self.writables:\n            self._poller.register(fd, self.WRITE)\n\n    def unregister_writable(self, fd):\n        self.writables.discard(fd)\n        self._poller.unregister(fd)\n        if fd in self.readables:\n            self._poller.register(fd, self.READ)\n\n    def poll(self, timeout):\n        fds = self._poll_fds(timeout)\n        readables, writables = [], []\n        for fd, eventmask in fds:\n            if self._ignore_invalid(fd, eventmask):\n                continue\n            if eventmask & self.READ:\n                readables.append(fd)\n            if eventmask & self.WRITE:\n                writables.append(fd)\n        return readables, writables\n\n    def _poll_fds(self, timeout):\n        try:\n            return self._poller.poll(timeout * 1000)\n        except select.error as err:\n            if err.args[0] == errno.EINTR:\n                self.options.logger.blather('EINTR encountered in poll')\n                return []\n            raise\n\n    def _ignore_invalid(self, fd, eventmask):\n        if eventmask & select.POLLNVAL:\n            # POLLNVAL means `fd` value is invalid, not open.\n            # When a process quits it's `fd`s are closed so there\n            # is no more reason to keep this `fd` registered\n            # If the process restarts it's `fd`s are registered again\n            self._poller.unregister(fd)\n            self.readables.discard(fd)\n            self.writables.discard(fd)\n            return True\n        return False\n\nclass KQueuePoller(BasePoller):\n    '''\n    Wrapper for select.kqueue()/kevent()\n    '''\n\n    max_events = 1000\n\n    def initialize(self):\n        self._kqueue = select.kqueue()\n        self.readables = set()\n        self.writables = set()\n\n    def register_readable(self, fd):\n        self.readables.add(fd)\n        kevent = select.kevent(fd, filter=select.KQ_FILTER_READ,\n                               flags=select.KQ_EV_ADD)\n        self._kqueue_control(fd, kevent)\n\n    def register_writable(self, fd):\n        self.writables.add(fd)\n        kevent = select.kevent(fd, filter=select.KQ_FILTER_WRITE,\n                               flags=select.KQ_EV_ADD)\n        self._kqueue_control(fd, kevent)\n\n    def unregister_readable(self, fd):\n        kevent = select.kevent(fd, filter=select.KQ_FILTER_READ,\n                               flags=select.KQ_EV_DELETE)\n        self.readables.discard(fd)\n        self._kqueue_control(fd, kevent)\n\n    def unregister_writable(self, fd):\n        kevent = select.kevent(fd, filter=select.KQ_FILTER_WRITE,\n                               flags=select.KQ_EV_DELETE)\n        self.writables.discard(fd)\n        self._kqueue_control(fd, kevent)\n\n    def _kqueue_control(self, fd, kevent):\n        try:\n            self._kqueue.control([kevent], 0)\n        except OSError as error:\n            if error.errno == errno.EBADF:\n                self.options.logger.blather('EBADF encountered in kqueue. '\n                                            'Invalid file descriptor %s' % fd)\n            else:\n                raise\n\n    def poll(self, timeout):\n        readables, writables = [], []\n\n        try:\n            kevents = self._kqueue.control(None, self.max_events, timeout)\n        except OSError as error:\n            if error.errno == errno.EINTR:\n                self.options.logger.blather('EINTR encountered in poll')\n                return readables, writables\n            raise\n\n        for kevent in kevents:\n            if kevent.filter == select.KQ_FILTER_READ:\n                readables.append(kevent.ident)\n            if kevent.filter == select.KQ_FILTER_WRITE:\n                writables.append(kevent.ident)\n\n        return readables, writables\n\n    def before_daemonize(self):\n        self.close()\n\n    def after_daemonize(self):\n        self._kqueue = select.kqueue()\n        for fd in self.readables:\n            self.register_readable(fd)\n        for fd in self.writables:\n            self.register_writable(fd)\n\n    def close(self):\n        self._kqueue.close()\n        self._kqueue = None\n\ndef implements_poll():\n    return hasattr(select, 'poll')\n\ndef implements_kqueue():\n    return hasattr(select, 'kqueue')\n\nif implements_kqueue():\n    Poller = KQueuePoller\nelif implements_poll():\n    Poller = PollPoller\nelse:\n    Poller = SelectPoller\n"
  },
  {
    "path": "supervisor/process.py",
    "content": "import errno\nimport functools\nimport os\nimport signal\nimport shlex\nimport time\nimport traceback\n\nfrom supervisor.compat import maxint\nfrom supervisor.compat import as_bytes\nfrom supervisor.compat import as_string\nfrom supervisor.compat import PY2\n\nfrom supervisor.medusa import asyncore_25 as asyncore\n\nfrom supervisor.states import ProcessStates\nfrom supervisor.states import SupervisorStates\nfrom supervisor.states import getProcessStateDescription\nfrom supervisor.states import STOPPED_STATES\n\nfrom supervisor.options import decode_wait_status\nfrom supervisor.options import signame\nfrom supervisor.options import ProcessException, BadCommand\n\nfrom supervisor.dispatchers import EventListenerStates\n\nfrom supervisor import events\n\nfrom supervisor.datatypes import RestartUnconditionally\n\nfrom supervisor.socket_manager import SocketManager\n\n@functools.total_ordering\nclass Subprocess(object):\n\n    \"\"\"A class to manage a subprocess.\"\"\"\n\n    # Initial state; overridden by instance variables\n\n    pid = 0 # Subprocess pid; 0 when not running\n    config = None # ProcessConfig instance\n    state = None # process state code\n    listener_state = None # listener state code (if we're an event listener)\n    event = None # event currently being processed (if we're an event listener)\n    laststart = 0 # Last time the subprocess was started; 0 if never\n    laststop = 0  # Last time the subprocess was stopped; 0 if never\n    laststopreport = 0 # Last time \"waiting for x to stop\" logged, to throttle\n    delay = 0 # If nonzero, delay starting or killing until this time\n    administrative_stop = False # true if process has been stopped by an admin\n    system_stop = False # true if process has been stopped by the system\n    killing = False # true if we are trying to kill this process\n    backoff = 0 # backoff counter (to startretries)\n    dispatchers = None # asyncore output dispatchers (keyed by fd)\n    pipes = None # map of channel name to file descriptor #\n    exitstatus = None # status attached to dead process by finish()\n    spawnerr = None # error message attached by spawn() if any\n    group = None # ProcessGroup instance if process is in the group\n\n    def __init__(self, config):\n        \"\"\"Constructor.\n\n        Argument is a ProcessConfig instance.\n        \"\"\"\n        self.config = config\n        self.dispatchers = {}\n        self.pipes = {}\n        self.state = ProcessStates.STOPPED\n\n    def removelogs(self):\n        for dispatcher in self.dispatchers.values():\n            if hasattr(dispatcher, 'removelogs'):\n                dispatcher.removelogs()\n\n    def reopenlogs(self):\n        for dispatcher in self.dispatchers.values():\n            if hasattr(dispatcher, 'reopenlogs'):\n                dispatcher.reopenlogs()\n\n    def drain(self):\n        for dispatcher in self.dispatchers.values():\n            # note that we *must* call readable() for every\n            # dispatcher, as it may have side effects for a given\n            # dispatcher (eg. call handle_listener_state_change for\n            # event listener processes)\n            if dispatcher.readable():\n                dispatcher.handle_read_event()\n            if dispatcher.writable():\n                dispatcher.handle_write_event()\n\n    def write(self, chars):\n        if not self.pid or self.killing:\n            raise OSError(errno.EPIPE, \"Process already closed\")\n\n        stdin_fd = self.pipes['stdin']\n        if stdin_fd is None:\n            raise OSError(errno.EPIPE, \"Process has no stdin channel\")\n\n        dispatcher = self.dispatchers[stdin_fd]\n        if dispatcher.closed:\n            raise OSError(errno.EPIPE, \"Process' stdin channel is closed\")\n\n        dispatcher.input_buffer += chars\n        dispatcher.flush() # this must raise EPIPE if the pipe is closed\n\n    def get_execv_args(self):\n        \"\"\"Internal: turn a program name into a file name, using $PATH,\n        make sure it exists / is executable, raising a ProcessException\n        if not \"\"\"\n        try:\n            commandargs = shlex.split(self.config.command)\n        except ValueError as e:\n            raise BadCommand(\"can't parse command %r: %s\" % \\\n                (self.config.command, str(e)))\n\n        if commandargs:\n            program = commandargs[0]\n        else:\n            raise BadCommand(\"command is empty\")\n\n        if \"/\" in program:\n            filename = program\n            try:\n                st = self.config.options.stat(filename)\n            except OSError:\n                st = None\n\n        else:\n            path = self.config.get_path()\n            found = None\n            st = None\n            for dir in path:\n                found = os.path.join(dir, program)\n                try:\n                    st = self.config.options.stat(found)\n                except OSError:\n                    pass\n                else:\n                    break\n            if st is None:\n                filename = program\n            else:\n                filename = found\n\n        # check_execv_args will raise a ProcessException if the execv\n        # args are bogus, we break it out into a separate options\n        # method call here only to service unit tests\n        self.config.options.check_execv_args(filename, commandargs, st)\n\n        return filename, commandargs\n\n    event_map = {\n        ProcessStates.BACKOFF: events.ProcessStateBackoffEvent,\n        ProcessStates.FATAL:   events.ProcessStateFatalEvent,\n        ProcessStates.UNKNOWN: events.ProcessStateUnknownEvent,\n        ProcessStates.STOPPED: events.ProcessStateStoppedEvent,\n        ProcessStates.EXITED:  events.ProcessStateExitedEvent,\n        ProcessStates.RUNNING: events.ProcessStateRunningEvent,\n        ProcessStates.STARTING: events.ProcessStateStartingEvent,\n        ProcessStates.STOPPING: events.ProcessStateStoppingEvent,\n        }\n\n    def change_state(self, new_state, expected=True):\n        old_state = self.state\n        if new_state is old_state:\n            # exists for unit tests\n            return False\n\n        self.state = new_state\n        if new_state == ProcessStates.BACKOFF:\n            now = time.time()\n            self.backoff += 1\n            self.delay = now + self.backoff\n\n        event_class = self.event_map.get(new_state)\n        if event_class is not None:\n            event = event_class(self, old_state, expected)\n            events.notify(event)\n\n    def _assertInState(self, *states):\n        if self.state not in states:\n            current_state = getProcessStateDescription(self.state)\n            allowable_states = ' '.join(map(getProcessStateDescription, states))\n            processname = as_string(self.config.name)\n            raise AssertionError('Assertion failed for %s: %s not in %s' %  (\n                processname, current_state, allowable_states))\n\n    def record_spawnerr(self, msg):\n        self.spawnerr = msg\n        self.config.options.logger.info(\"spawnerr: %s\" % msg)\n\n    def spawn(self):\n        \"\"\"Start the subprocess.  It must not be running already.\n\n        Return the process id.  If the fork() call fails, return None.\n        \"\"\"\n        options = self.config.options\n        processname = as_string(self.config.name)\n\n        if self.pid:\n            msg = 'process \\'%s\\' already running' % processname\n            options.logger.warn(msg)\n            return\n\n        self.killing = False\n        self.spawnerr = None\n        self.exitstatus = None\n        self.system_stop = False\n        self.administrative_stop = False\n\n        self.laststart = time.time()\n\n        self._assertInState(ProcessStates.EXITED, ProcessStates.FATAL,\n                            ProcessStates.BACKOFF, ProcessStates.STOPPED)\n\n        self.change_state(ProcessStates.STARTING)\n\n        try:\n            filename, argv = self.get_execv_args()\n        except ProcessException as what:\n            self.record_spawnerr(what.args[0])\n            self._assertInState(ProcessStates.STARTING)\n            self.change_state(ProcessStates.BACKOFF)\n            return\n\n        try:\n            self.dispatchers, self.pipes = self.config.make_dispatchers(self)\n        except (OSError, IOError) as why:\n            code = why.args[0]\n            if code == errno.EMFILE:\n                # too many file descriptors open\n                msg = 'too many open files to spawn \\'%s\\'' % processname\n            else:\n                msg = 'unknown error making dispatchers for \\'%s\\': %s' % (\n                      processname, errno.errorcode.get(code, code))\n            self.record_spawnerr(msg)\n            self._assertInState(ProcessStates.STARTING)\n            self.change_state(ProcessStates.BACKOFF)\n            return\n\n        try:\n            pid = options.fork()\n        except OSError as why:\n            code = why.args[0]\n            if code == errno.EAGAIN:\n                # process table full\n                msg  = ('Too many processes in process table to spawn \\'%s\\'' %\n                        processname)\n            else:\n                msg = 'unknown error during fork for \\'%s\\': %s' % (\n                      processname, errno.errorcode.get(code, code))\n            self.record_spawnerr(msg)\n            self._assertInState(ProcessStates.STARTING)\n            self.change_state(ProcessStates.BACKOFF)\n            options.close_parent_pipes(self.pipes)\n            options.close_child_pipes(self.pipes)\n            return\n\n        if pid != 0:\n            return self._spawn_as_parent(pid)\n\n        else:\n            return self._spawn_as_child(filename, argv)\n\n    def _spawn_as_parent(self, pid):\n        # Parent\n        self.pid = pid\n        options = self.config.options\n        options.close_child_pipes(self.pipes)\n        options.logger.info('spawned: \\'%s\\' with pid %s' % (as_string(self.config.name), pid))\n        self.spawnerr = None\n        self.delay = time.time() + self.config.startsecs\n        options.pidhistory[pid] = self\n        return pid\n\n    def _prepare_child_fds(self):\n        options = self.config.options\n        options.dup2(self.pipes['child_stdin'], 0)\n        options.dup2(self.pipes['child_stdout'], 1)\n        if self.config.redirect_stderr:\n            options.dup2(self.pipes['child_stdout'], 2)\n        else:\n            options.dup2(self.pipes['child_stderr'], 2)\n        for i in range(3, options.minfds):\n            options.close_fd(i)\n\n    def _spawn_as_child(self, filename, argv):\n        options = self.config.options\n        try:\n            # prevent child from receiving signals sent to the\n            # parent by calling os.setpgrp to create a new process\n            # group for the child; this prevents, for instance,\n            # the case of child processes being sent a SIGINT when\n            # running supervisor in foreground mode and Ctrl-C in\n            # the terminal window running supervisord is pressed.\n            # Presumably it also prevents HUP, etc received by\n            # supervisord from being sent to children.\n            options.setpgrp()\n\n            self._prepare_child_fds()\n            # sending to fd 2 will put this output in the stderr log\n\n            # set user\n            setuid_msg = self.set_uid()\n            if setuid_msg:\n                uid = self.config.uid\n                msg = \"couldn't setuid to %s: %s\\n\" % (uid, setuid_msg)\n                options.write(2, \"supervisor: \" + msg)\n                return # finally clause will exit the child process\n\n            # set environment\n            env = os.environ.copy()\n            env['SUPERVISOR_ENABLED'] = '1'\n            serverurl = self.config.serverurl\n            if serverurl is None: # unset\n                serverurl = self.config.options.serverurl # might still be None\n            if serverurl:\n                env['SUPERVISOR_SERVER_URL'] = serverurl\n            env['SUPERVISOR_PROCESS_NAME'] = self.config.name\n            if self.group:\n                env['SUPERVISOR_GROUP_NAME'] = self.group.config.name\n            if self.config.environment is not None:\n                env.update(self.config.environment)\n\n            # change directory\n            cwd = self.config.directory\n            try:\n                if cwd is not None:\n                    options.chdir(cwd)\n            except OSError as why:\n                code = errno.errorcode.get(why.args[0], why.args[0])\n                msg = \"couldn't chdir to %s: %s\\n\" % (cwd, code)\n                options.write(2, \"supervisor: \" + msg)\n                return # finally clause will exit the child process\n\n            # set umask, then execve\n            try:\n                if self.config.umask is not None:\n                    options.setumask(self.config.umask)\n                options.execve(filename, argv, env)\n            except OSError as why:\n                code = errno.errorcode.get(why.args[0], why.args[0])\n                msg = \"couldn't exec %s: %s\\n\" % (argv[0], code)\n                options.write(2, \"supervisor: \" + msg)\n            except:\n                (file, fun, line), t,v,tbinfo = asyncore.compact_traceback()\n                error = '%s, %s: file: %s line: %s' % (t, v, file, line)\n                msg = \"couldn't exec %s: %s\\n\" % (filename, error)\n                options.write(2, \"supervisor: \" + msg)\n\n            # this point should only be reached if execve failed.\n            # the finally clause will exit the child process.\n\n        finally:\n            options.write(2, \"supervisor: child process was not spawned\\n\")\n            options._exit(127) # exit process with code for spawn failure\n\n    def _check_and_adjust_for_system_clock_rollback(self, test_time):\n        \"\"\"\n        Check if system clock has rolled backward beyond test_time. If so, set\n        affected timestamps to test_time.\n        \"\"\"\n        if self.state == ProcessStates.STARTING:\n            if test_time < self.laststart:\n                self.laststart = test_time;\n            if self.delay > 0 and test_time < (self.delay - self.config.startsecs):\n                self.delay = test_time + self.config.startsecs\n        elif self.state == ProcessStates.RUNNING:\n            if test_time > self.laststart and test_time < (self.laststart + self.config.startsecs):\n                self.laststart = test_time - self.config.startsecs\n        elif self.state == ProcessStates.STOPPING:\n            if test_time < self.laststopreport:\n                self.laststopreport = test_time;\n            if self.delay > 0 and test_time < (self.delay - self.config.stopwaitsecs):\n                self.delay = test_time + self.config.stopwaitsecs\n        elif self.state == ProcessStates.BACKOFF:\n            if self.delay > 0 and test_time < (self.delay - self.backoff):\n                self.delay = test_time + self.backoff\n\n    def stop(self):\n        \"\"\" Administrative stop \"\"\"\n        self.administrative_stop = True\n        self.laststopreport = 0\n        return self.kill(self.config.stopsignal)\n\n    def stop_report(self):\n        \"\"\" Log a 'waiting for x to stop' message with throttling. \"\"\"\n        if self.state == ProcessStates.STOPPING:\n            now = time.time()\n\n            self._check_and_adjust_for_system_clock_rollback(now)\n\n            if now > (self.laststopreport + 2): # every 2 seconds\n                self.config.options.logger.info(\n                    'waiting for %s to stop' % as_string(self.config.name))\n                self.laststopreport = now\n\n    def give_up(self):\n        self.delay = 0\n        self.backoff = 0\n        self.system_stop = True\n        self._assertInState(ProcessStates.BACKOFF)\n        self.change_state(ProcessStates.FATAL)\n\n    def kill(self, sig):\n        \"\"\"Send a signal to the subprocess with the intention to kill\n        it (to make it exit).  This may or may not actually kill it.\n\n        Return None if the signal was sent, or an error message string\n        if an error occurred or if the subprocess is not running.\n        \"\"\"\n        now = time.time()\n        options = self.config.options\n\n        processname = as_string(self.config.name)\n        # If the process is in BACKOFF and we want to stop or kill it, then\n        # BACKOFF -> STOPPED.  This is needed because if startretries is a\n        # large number and the process isn't starting successfully, the stop\n        # request would be blocked for a long time waiting for the retries.\n        if self.state == ProcessStates.BACKOFF:\n            msg = (\"Attempted to kill %s, which is in BACKOFF state.\" %\n                   processname)\n            options.logger.debug(msg)\n            self.change_state(ProcessStates.STOPPED)\n            return None\n\n        if not self.pid:\n            msg = (\"attempted to kill %s with sig %s but it wasn't running\" %\n                   (processname, signame(sig)))\n            options.logger.debug(msg)\n            return msg\n\n        # If we're in the stopping state, then we've already sent the stop\n        # signal and this is the kill signal\n        if self.state == ProcessStates.STOPPING:\n            killasgroup = self.config.killasgroup\n        else:\n            killasgroup = self.config.stopasgroup\n\n        as_group = \"\"\n        if killasgroup:\n            as_group = \"process group \"\n\n        options.logger.debug('killing %s (pid %s) %swith signal %s'\n                             % (processname,\n                                self.pid,\n                                as_group,\n                                signame(sig))\n                             )\n\n        # RUNNING/STARTING/STOPPING -> STOPPING\n        self.killing = True\n        self.delay = now + self.config.stopwaitsecs\n        # we will already be in the STOPPING state if we're doing a\n        # SIGKILL as a result of overrunning stopwaitsecs\n        self._assertInState(ProcessStates.RUNNING,\n                            ProcessStates.STARTING,\n                            ProcessStates.STOPPING)\n        self.change_state(ProcessStates.STOPPING)\n\n        pid = self.pid\n        if killasgroup:\n            # send to the whole process group instead\n            pid = -self.pid\n\n        try:\n            try:\n                options.kill(pid, sig)\n            except OSError as exc:\n                if exc.errno == errno.ESRCH:\n                    msg = (\"unable to signal %s (pid %s), it probably just exited \"\n                           \"on its own: %s\" % (processname, self.pid, str(exc)))\n                    options.logger.debug(msg)\n                    # we could change the state here but we intentionally do\n                    # not.  we will do it during normal SIGCHLD processing.\n                    return None\n                raise\n        except:\n            tb = traceback.format_exc()\n            msg = 'unknown problem killing %s (%s):%s' % (processname,\n                                                          self.pid, tb)\n            options.logger.critical(msg)\n            self.change_state(ProcessStates.UNKNOWN)\n            self.killing = False\n            self.delay = 0\n            return msg\n\n        return None\n\n    def signal(self, sig):\n        \"\"\"Send a signal to the subprocess, without intending to kill it.\n\n        Return None if the signal was sent, or an error message string\n        if an error occurred or if the subprocess is not running.\n        \"\"\"\n        options = self.config.options\n        processname = as_string(self.config.name)\n        if not self.pid:\n            msg = (\"attempted to send %s sig %s but it wasn't running\" %\n                   (processname, signame(sig)))\n            options.logger.debug(msg)\n            return msg\n\n        options.logger.debug('sending %s (pid %s) sig %s'\n                             % (processname,\n                                self.pid,\n                                signame(sig))\n                             )\n\n        self._assertInState(ProcessStates.RUNNING,\n                            ProcessStates.STARTING,\n                            ProcessStates.STOPPING)\n\n        try:\n            try:\n                options.kill(self.pid, sig)\n            except OSError as exc:\n                if exc.errno == errno.ESRCH:\n                    msg = (\"unable to signal %s (pid %s), it probably just now exited \"\n                           \"on its own: %s\" % (processname, self.pid,\n                           str(exc)))\n                    options.logger.debug(msg)\n                    # we could change the state here but we intentionally do\n                    # not.  we will do it during normal SIGCHLD processing.\n                    return None\n                raise\n        except:\n            tb = traceback.format_exc()\n            msg = 'unknown problem sending sig %s (%s):%s' % (\n                                processname, self.pid, tb)\n            options.logger.critical(msg)\n            self.change_state(ProcessStates.UNKNOWN)\n            return msg\n\n        return None\n\n    def finish(self, pid, sts):\n        \"\"\" The process was reaped and we need to report and manage its state\n        \"\"\"\n        self.drain()\n\n        es, msg = decode_wait_status(sts)\n\n        now = time.time()\n\n        self._check_and_adjust_for_system_clock_rollback(now)\n\n        self.laststop = now\n        processname = as_string(self.config.name)\n\n        if now > self.laststart:\n            too_quickly = now - self.laststart < self.config.startsecs\n        else:\n            too_quickly = False\n            self.config.options.logger.warn(\n                \"process \\'%s\\' (%s) laststart time is in the future, don't \"\n                \"know how long process was running so assuming it did \"\n                \"not exit too quickly\" % (processname, self.pid))\n\n        exit_expected = es in self.config.exitcodes\n\n        if self.killing:\n            # likely the result of a stop request\n            # implies STOPPING -> STOPPED\n            self.killing = False\n            self.delay = 0\n            self.exitstatus = es\n\n            msg = \"stopped: %s (%s)\" % (processname, msg)\n            self._assertInState(ProcessStates.STOPPING)\n            self.change_state(ProcessStates.STOPPED)\n            if exit_expected:\n                self.config.options.logger.info(msg)\n            else:\n                self.config.options.logger.warn(msg)\n\n\n        elif too_quickly:\n            # the program did not stay up long enough to make it to RUNNING\n            # implies STARTING -> BACKOFF\n            self.exitstatus = None\n            self.spawnerr = 'Exited too quickly (process log may have details)'\n            msg = \"exited: %s (%s)\" % (processname, msg + \"; not expected\")\n            self._assertInState(ProcessStates.STARTING)\n            self.change_state(ProcessStates.BACKOFF)\n            self.config.options.logger.warn(msg)\n\n        else:\n            # this finish was not the result of a stop request, the\n            # program was in the RUNNING state but exited\n            # implies RUNNING -> EXITED normally but see next comment\n            self.delay = 0\n            self.backoff = 0\n            self.exitstatus = es\n\n            # if the process was STARTING but a system time change causes\n            # self.laststart to be in the future, the normal STARTING->RUNNING\n            # transition can be subverted so we perform the transition here.\n            if self.state == ProcessStates.STARTING:\n                self.change_state(ProcessStates.RUNNING)\n\n            self._assertInState(ProcessStates.RUNNING)\n\n            if exit_expected:\n                # expected exit code\n                msg = \"exited: %s (%s)\" % (processname, msg + \"; expected\")\n                self.change_state(ProcessStates.EXITED, expected=True)\n                self.config.options.logger.info(msg)\n            else:\n                # unexpected exit code\n                self.spawnerr = 'Bad exit code %s' % es\n                msg = \"exited: %s (%s)\" % (processname, msg + \"; not expected\")\n                self.change_state(ProcessStates.EXITED, expected=False)\n                self.config.options.logger.warn(msg)\n\n        self.pid = 0\n        self.config.options.close_parent_pipes(self.pipes)\n        self.pipes = {}\n        self.dispatchers = {}\n\n        # if we died before we processed the current event (only happens\n        # if we're an event listener), notify the event system that this\n        # event was rejected so it can be processed again.\n        if self.event is not None:\n            # Note: this should only be true if we were in the BUSY\n            # state when finish() was called.\n            events.notify(events.EventRejectedEvent(self, self.event))\n            self.event = None\n\n    def set_uid(self):\n        if self.config.uid is None:\n            return\n        msg = self.config.options.drop_privileges(self.config.uid)\n        return msg\n\n    def __lt__(self, other):\n        return self.config.priority < other.config.priority\n\n    def __eq__(self, other):\n        # sort by priority\n        return self.config.priority == other.config.priority\n\n    def __repr__(self):\n        # repr can't return anything other than a native string,\n        # but the name might be unicode - a problem on Python 2.\n        name = self.config.name\n        if PY2:\n            name = as_string(name).encode('unicode-escape')\n        return '<Subprocess at %s with name %s in state %s>' % (\n            id(self),\n            name,\n            getProcessStateDescription(self.get_state()))\n\n    def get_state(self):\n        return self.state\n\n    def transition(self):\n        now = time.time()\n        state = self.state\n\n        self._check_and_adjust_for_system_clock_rollback(now)\n\n        logger = self.config.options.logger\n\n        if self.config.options.mood > SupervisorStates.RESTARTING:\n            # dont start any processes if supervisor is shutting down\n            if state == ProcessStates.EXITED:\n                if self.config.autorestart:\n                    if self.config.autorestart is RestartUnconditionally:\n                        # EXITED -> STARTING\n                        self.spawn()\n                    else: # autorestart is RestartWhenExitUnexpected\n                        if self.exitstatus not in self.config.exitcodes:\n                            # EXITED -> STARTING\n                            self.spawn()\n            elif state == ProcessStates.STOPPED and not self.laststart:\n                if self.config.autostart:\n                    # STOPPED -> STARTING\n                    self.spawn()\n            elif state == ProcessStates.BACKOFF:\n                if self.backoff <= self.config.startretries:\n                    if now > self.delay:\n                        # BACKOFF -> STARTING\n                        self.spawn()\n\n        processname = as_string(self.config.name)\n        if state == ProcessStates.STARTING:\n            if now - self.laststart > self.config.startsecs:\n                # STARTING -> RUNNING if the proc has started\n                # successfully and it has stayed up for at least\n                # proc.config.startsecs,\n                self.delay = 0\n                self.backoff = 0\n                self._assertInState(ProcessStates.STARTING)\n                self.change_state(ProcessStates.RUNNING)\n                msg = (\n                    'entered RUNNING state, process has stayed up for '\n                    '> than %s seconds (startsecs)' % self.config.startsecs)\n                logger.info('success: %s %s' % (processname, msg))\n\n        if state == ProcessStates.BACKOFF:\n            if self.backoff > self.config.startretries:\n                # BACKOFF -> FATAL if the proc has exceeded its number\n                # of retries\n                self.give_up()\n                msg = ('entered FATAL state, too many start retries too '\n                       'quickly')\n                logger.info('gave up: %s %s' % (processname, msg))\n\n        elif state == ProcessStates.STOPPING:\n            time_left = self.delay - now\n            if time_left <= 0:\n                # kill processes which are taking too long to stop with a final\n                # sigkill.  if this doesn't kill it, the process will be stuck\n                # in the STOPPING state forever.\n                self.config.options.logger.warn(\n                    'killing \\'%s\\' (%s) with SIGKILL' % (processname,\n                                                          self.pid))\n                self.kill(signal.SIGKILL)\n\nclass FastCGISubprocess(Subprocess):\n    \"\"\"Extends Subprocess class to handle FastCGI subprocesses\"\"\"\n\n    def __init__(self, config):\n        Subprocess.__init__(self, config)\n        self.fcgi_sock = None\n\n    def before_spawn(self):\n        \"\"\"\n        The FastCGI socket needs to be created by the parent before we fork\n        \"\"\"\n        if self.group is None:\n            raise NotImplementedError('No group set for FastCGISubprocess')\n        if not hasattr(self.group, 'socket_manager'):\n            raise NotImplementedError('No SocketManager set for '\n                                      '%s:%s' % (self.group, dir(self.group)))\n        self.fcgi_sock = self.group.socket_manager.get_socket()\n\n    def spawn(self):\n        \"\"\"\n        Overrides Subprocess.spawn() so we can hook in before it happens\n        \"\"\"\n        self.before_spawn()\n        pid = Subprocess.spawn(self)\n        if pid is None:\n            #Remove object reference to decrement the reference count on error\n            self.fcgi_sock = None\n        return pid\n\n    def after_finish(self):\n        \"\"\"\n        Releases reference to FastCGI socket when process is reaped\n        \"\"\"\n        #Remove object reference to decrement the reference count\n        self.fcgi_sock = None\n\n    def finish(self, pid, sts):\n        \"\"\"\n        Overrides Subprocess.finish() so we can hook in after it happens\n        \"\"\"\n        retval = Subprocess.finish(self, pid, sts)\n        self.after_finish()\n        return retval\n\n    def _prepare_child_fds(self):\n        \"\"\"\n        Overrides Subprocess._prepare_child_fds()\n        The FastCGI socket needs to be set to file descriptor 0 in the child\n        \"\"\"\n        sock_fd = self.fcgi_sock.fileno()\n\n        options = self.config.options\n        options.dup2(sock_fd, 0)\n        options.dup2(self.pipes['child_stdout'], 1)\n        if self.config.redirect_stderr:\n            options.dup2(self.pipes['child_stdout'], 2)\n        else:\n            options.dup2(self.pipes['child_stderr'], 2)\n        for i in range(3, options.minfds):\n            options.close_fd(i)\n\n@functools.total_ordering\nclass ProcessGroupBase(object):\n    def __init__(self, config):\n        self.config = config\n        self.processes = {}\n        for pconfig in self.config.process_configs:\n            self.processes[pconfig.name] = pconfig.make_process(self)\n\n\n    def __lt__(self, other):\n        return self.config.priority < other.config.priority\n\n    def __eq__(self, other):\n        return self.config.priority == other.config.priority\n\n    def __repr__(self):\n        # repr can't return anything other than a native string,\n        # but the name might be unicode - a problem on Python 2.\n        name = self.config.name\n        if PY2:\n            name = as_string(name).encode('unicode-escape')\n        return '<%s instance at %s named %s>' % (self.__class__, id(self),\n                                                 name)\n\n    def removelogs(self):\n        for process in self.processes.values():\n            process.removelogs()\n\n    def reopenlogs(self):\n        for process in self.processes.values():\n            process.reopenlogs()\n\n    def stop_all(self):\n        processes = list(self.processes.values())\n        processes.sort()\n        processes.reverse() # stop in desc priority order\n\n        for proc in processes:\n            state = proc.get_state()\n            if state == ProcessStates.RUNNING:\n                # RUNNING -> STOPPING\n                proc.stop()\n            elif state == ProcessStates.STARTING:\n                # STARTING -> STOPPING\n                proc.stop()\n            elif state == ProcessStates.BACKOFF:\n                # BACKOFF -> FATAL\n                proc.give_up()\n\n    def get_unstopped_processes(self):\n        \"\"\" Processes which aren't in a state that is considered 'stopped' \"\"\"\n        return [ x for x in self.processes.values() if x.get_state() not in\n                 STOPPED_STATES ]\n\n    def get_dispatchers(self):\n        dispatchers = {}\n        for process in self.processes.values():\n            dispatchers.update(process.dispatchers)\n        return dispatchers\n\n    def before_remove(self):\n        pass\n\nclass ProcessGroup(ProcessGroupBase):\n    def transition(self):\n        for proc in self.processes.values():\n            proc.transition()\n\nclass FastCGIProcessGroup(ProcessGroup):\n\n    def __init__(self, config, **kwargs):\n        ProcessGroup.__init__(self, config)\n        sockManagerKlass = kwargs.get('socketManager', SocketManager)\n        self.socket_manager = sockManagerKlass(config.socket_config,\n                                               logger=config.options.logger)\n        # It's not required to call get_socket() here but we want\n        # to fail early during start up if there is a config error\n        try:\n            self.socket_manager.get_socket()\n        except Exception as e:\n            raise ValueError(\n                'Could not create FastCGI socket %s: %s' % (\n                    self.socket_manager.config(), e)\n                )\n\nclass EventListenerPool(ProcessGroupBase):\n    def __init__(self, config):\n        ProcessGroupBase.__init__(self, config)\n        self.event_buffer = []\n        self.serial = -1\n        self.last_dispatch = 0\n        self.dispatch_throttle = 0 # in seconds: .00195 is an interesting one\n        self._subscribe()\n\n    def handle_rejected(self, event):\n        process = event.process\n        procs = self.processes.values()\n        if process in procs: # this is one of our processes\n            # rebuffer the event\n            self._acceptEvent(event.event, head=True)\n\n    def transition(self):\n        processes = self.processes.values()\n        dispatch_capable = False\n        for process in processes:\n            process.transition()\n            # this is redundant, we do it in _dispatchEvent too, but we\n            # want to reduce function call overhead\n            if process.state == ProcessStates.RUNNING:\n                if process.listener_state == EventListenerStates.READY:\n                    dispatch_capable = True\n        if dispatch_capable:\n            if self.dispatch_throttle:\n                now = time.time()\n\n                if now < self.last_dispatch:\n                    # The system clock appears to have moved backward\n                    # Reset self.last_dispatch accordingly\n                    self.last_dispatch = now;\n\n                if now - self.last_dispatch < self.dispatch_throttle:\n                    return\n            self.dispatch()\n\n    def before_remove(self):\n        self._unsubscribe()\n\n    def dispatch(self):\n        while self.event_buffer:\n            # dispatch the oldest event\n            event = self.event_buffer.pop(0)\n            ok = self._dispatchEvent(event)\n            if not ok:\n                # if we can't dispatch an event, rebuffer it and stop trying\n                # to process any further events in the buffer\n                self._acceptEvent(event, head=True)\n                break\n        self.last_dispatch = time.time()\n\n    def _acceptEvent(self, event, head=False):\n        # events are required to be instances\n        # this has a side effect to fail with an attribute error on 'old style'\n        # classes\n        processname = as_string(self.config.name)\n        if not hasattr(event, 'serial'):\n            event.serial = new_serial(GlobalSerial)\n        if not hasattr(event, 'pool_serials'):\n            event.pool_serials = {}\n        if self.config.name not in event.pool_serials:\n            event.pool_serials[self.config.name] = new_serial(self)\n        else:\n            self.config.options.logger.debug(\n                'rebuffering event %s for pool %s (buf size=%d, max=%d)' % (\n                (event.serial, processname, len(self.event_buffer),\n                self.config.buffer_size)))\n\n        if len(self.event_buffer) >= self.config.buffer_size:\n            if self.event_buffer:\n                # discard the oldest event\n                discarded_event = self.event_buffer.pop(0)\n                self.config.options.logger.error(\n                    'pool %s event buffer overflowed, discarding event %s' % (\n                    (processname, discarded_event.serial)))\n        if head:\n            self.event_buffer.insert(0, event)\n        else:\n            self.event_buffer.append(event)\n\n    def _dispatchEvent(self, event):\n        pool_serial = event.pool_serials[self.config.name]\n\n        for process in self.processes.values():\n            if process.state != ProcessStates.RUNNING:\n                continue\n            if process.listener_state == EventListenerStates.READY:\n                processname = as_string(process.config.name)\n                payload = event.payload()\n                try:\n                    event_type = event.__class__\n                    serial = event.serial\n                    envelope = self._eventEnvelope(event_type, serial,\n                                                   pool_serial, payload)\n                    process.write(as_bytes(envelope))\n                except OSError as why:\n                    if why.args[0] != errno.EPIPE:\n                        raise\n\n                    self.config.options.logger.debug(\n                        'epipe occurred while sending event %s '\n                        'to listener %s, listener state unchanged' % (\n                        event.serial, processname))\n                    continue\n\n                process.listener_state = EventListenerStates.BUSY\n                process.event = event\n                self.config.options.logger.debug(\n                    'event %s sent to listener %s' % (\n                    event.serial, processname))\n                return True\n\n        return False\n\n    def _eventEnvelope(self, event_type, serial, pool_serial, payload):\n        event_name = events.getEventNameByType(event_type)\n        payload_len = len(payload)\n        D = {\n            'ver':'3.0',\n            'sid':self.config.options.identifier,\n            'serial':serial,\n            'pool_name':self.config.name,\n            'pool_serial':pool_serial,\n            'event_name':event_name,\n            'len':payload_len,\n            'payload':payload,\n             }\n        return ('ver:%(ver)s server:%(sid)s serial:%(serial)s '\n                'pool:%(pool_name)s poolserial:%(pool_serial)s '\n                'eventname:%(event_name)s len:%(len)s\\n%(payload)s' % D)\n\n    def _subscribe(self):\n        for event_type in self.config.pool_events:\n            events.subscribe(event_type, self._acceptEvent)\n        events.subscribe(events.EventRejectedEvent, self.handle_rejected)\n\n    def _unsubscribe(self):\n        for event_type in self.config.pool_events:\n            events.unsubscribe(event_type, self._acceptEvent)\n        events.unsubscribe(events.EventRejectedEvent, self.handle_rejected)\n\n\nclass GlobalSerial(object):\n    def __init__(self):\n        self.serial = -1\n\nGlobalSerial = GlobalSerial() # singleton\n\ndef new_serial(inst):\n    if inst.serial == maxint:\n        inst.serial = -1\n    inst.serial += 1\n    return inst.serial\n"
  },
  {
    "path": "supervisor/rpcinterface.py",
    "content": "import os\nimport time\nimport datetime\nimport errno\nimport types\n\nfrom supervisor.compat import as_string\nfrom supervisor.compat import as_bytes\nfrom supervisor.compat import unicode\n\nfrom supervisor.datatypes import (\n    Automatic,\n    RestartWhenExitUnexpected,\n    signal_number,\n    )\n\nfrom supervisor.options import readFile\nfrom supervisor.options import tailFile\nfrom supervisor.options import BadCommand\nfrom supervisor.options import NotExecutable\nfrom supervisor.options import NotFound\nfrom supervisor.options import NoPermission\nfrom supervisor.options import make_namespec\nfrom supervisor.options import split_namespec\nfrom supervisor.options import VERSION\n\nfrom supervisor.events import notify\nfrom supervisor.events import RemoteCommunicationEvent\n\nfrom supervisor.http import NOT_DONE_YET\nfrom supervisor.xmlrpc import (\n    capped_int,\n    Faults,\n    RPCError,\n    )\n\nfrom supervisor.states import SupervisorStates\nfrom supervisor.states import getSupervisorStateDescription\nfrom supervisor.states import ProcessStates\nfrom supervisor.states import getProcessStateDescription\nfrom supervisor.states import (\n    RUNNING_STATES,\n    STOPPED_STATES,\n    SIGNALLABLE_STATES\n    )\n\nAPI_VERSION  = '3.0'\n\nclass SupervisorNamespaceRPCInterface:\n    def __init__(self, supervisord):\n        self.supervisord = supervisord\n\n    def _update(self, text):\n        self.update_text = text # for unit tests, mainly\n        if ( isinstance(self.supervisord.options.mood, int) and\n             self.supervisord.options.mood < SupervisorStates.RUNNING ):\n            raise RPCError(Faults.SHUTDOWN_STATE)\n\n    # RPC API methods\n\n    def getAPIVersion(self):\n        \"\"\" Return the version of the RPC API used by supervisord\n\n        @return string version id\n        \"\"\"\n        self._update('getAPIVersion')\n        return API_VERSION\n\n    getVersion = getAPIVersion # b/w compatibility with releases before 3.0\n\n    def getSupervisorVersion(self):\n        \"\"\" Return the version of the supervisor package in use by supervisord\n\n        @return string version id\n        \"\"\"\n        self._update('getSupervisorVersion')\n        return VERSION\n\n    def getIdentification(self):\n        \"\"\" Return identifying string of supervisord\n\n        @return string identifier identifying string\n        \"\"\"\n        self._update('getIdentification')\n        return self.supervisord.options.identifier\n\n    def getState(self):\n        \"\"\" Return current state of supervisord as a struct\n\n        @return struct A struct with keys int statecode, string statename\n        \"\"\"\n        self._update('getState')\n\n        state = self.supervisord.options.mood\n        statename = getSupervisorStateDescription(state)\n        data =  {\n            'statecode':state,\n            'statename':statename,\n            }\n        return data\n\n    def getPID(self):\n        \"\"\" Return the PID of supervisord\n\n        @return int PID\n        \"\"\"\n        self._update('getPID')\n        return self.supervisord.options.get_pid()\n\n    def readLog(self, offset, length):\n        \"\"\" Read length bytes from the main log starting at offset\n\n        @param int offset         offset to start reading from.\n        @param int length         number of bytes to read from the log.\n        @return string result     Bytes of log\n        \"\"\"\n        self._update('readLog')\n\n        logfile = self.supervisord.options.logfile\n\n        if logfile is None or not os.path.exists(logfile):\n            raise RPCError(Faults.NO_FILE, logfile)\n\n        try:\n            return as_string(readFile(logfile, int(offset), int(length)))\n        except ValueError as inst:\n            why = inst.args[0]\n            raise RPCError(getattr(Faults, why))\n\n    readMainLog = readLog # b/w compatibility with releases before 2.1\n\n    def clearLog(self):\n        \"\"\" Clear the main log.\n\n        @return boolean result always returns True unless error\n        \"\"\"\n        self._update('clearLog')\n\n        logfile = self.supervisord.options.logfile\n        if logfile is None or not self.supervisord.options.exists(logfile):\n            raise RPCError(Faults.NO_FILE)\n\n        # there is a race condition here, but ignore it.\n        try:\n            self.supervisord.options.remove(logfile)\n        except (OSError, IOError):\n            raise RPCError(Faults.FAILED)\n\n        for handler in self.supervisord.options.logger.handlers:\n            if hasattr(handler, 'reopen'):\n                self.supervisord.options.logger.info('reopening log file')\n                handler.reopen()\n        return True\n\n    def shutdown(self):\n        \"\"\" Shut down the supervisor process\n\n        @return boolean result always returns True unless error\n        \"\"\"\n        self._update('shutdown')\n        self.supervisord.options.mood = SupervisorStates.SHUTDOWN\n        return True\n\n    def restart(self):\n        \"\"\" Restart the supervisor process\n\n        @return boolean result  always return True unless error\n        \"\"\"\n        self._update('restart')\n\n        self.supervisord.options.mood = SupervisorStates.RESTARTING\n        return True\n\n    def reloadConfig(self):\n        \"\"\"\n        Reload the configuration.\n\n        The result contains three arrays containing names of process\n        groups:\n\n        * `added` gives the process groups that have been added\n        * `changed` gives the process groups whose contents have\n          changed\n        * `removed` gives the process groups that are no longer\n          in the configuration\n\n        @return array result  [[added, changed, removed]]\n\n        \"\"\"\n        self._update('reloadConfig')\n        try:\n            self.supervisord.options.process_config(do_usage=False)\n        except ValueError as msg:\n            raise RPCError(Faults.CANT_REREAD, msg)\n\n        added, changed, removed = self.supervisord.diff_to_active()\n\n        added = [group.name for group in added]\n        changed = [group.name for group in changed]\n        removed = [group.name for group in removed]\n        return [[added, changed, removed]] # cannot return len > 1, apparently\n\n    def addProcessGroup(self, name):\n        \"\"\" Update the config for a running process from config file.\n\n        @param string name         name of process group to add\n        @return boolean result     true if successful\n        \"\"\"\n        self._update('addProcessGroup')\n\n        for config in self.supervisord.options.process_group_configs:\n            if config.name == name:\n                result = self.supervisord.add_process_group(config)\n                if not result:\n                    raise RPCError(Faults.ALREADY_ADDED, name)\n                return True\n        raise RPCError(Faults.BAD_NAME, name)\n\n    def removeProcessGroup(self, name):\n        \"\"\" Remove a stopped process from the active configuration.\n\n        @param string name         name of process group to remove\n        @return boolean result     Indicates whether the removal was successful\n        \"\"\"\n        self._update('removeProcessGroup')\n        if name not in self.supervisord.process_groups:\n            raise RPCError(Faults.BAD_NAME, name)\n\n        result = self.supervisord.remove_process_group(name)\n        if not result:\n            raise RPCError(Faults.STILL_RUNNING, name)\n        return True\n\n    def _getAllProcesses(self, lexical=False):\n        # if lexical is true, return processes sorted in lexical order,\n        # otherwise, sort in priority order\n        all_processes = []\n\n        if lexical:\n            group_names = list(self.supervisord.process_groups.keys())\n            group_names.sort()\n            for group_name in group_names:\n                group = self.supervisord.process_groups[group_name]\n                process_names = list(group.processes.keys())\n                process_names.sort()\n                for process_name in process_names:\n                    process = group.processes[process_name]\n                    all_processes.append((group, process))\n        else:\n            groups = list(self.supervisord.process_groups.values())\n            groups.sort() # asc by priority\n\n            for group in groups:\n                processes = list(group.processes.values())\n                processes.sort() # asc by priority\n                for process in processes:\n                    all_processes.append((group, process))\n\n        return all_processes\n\n    def _getGroupAndProcess(self, name):\n        # get process to start from name\n        group_name, process_name = split_namespec(name)\n\n        group = self.supervisord.process_groups.get(group_name)\n        if group is None:\n            raise RPCError(Faults.BAD_NAME, name)\n\n        if process_name is None:\n            return group, None\n\n        process = group.processes.get(process_name)\n        if process is None:\n            raise RPCError(Faults.BAD_NAME, name)\n\n        return group, process\n\n    def startProcess(self, name, wait=True):\n        \"\"\" Start a process\n\n        @param string name Process name (or ``group:name``, or ``group:*``)\n        @param boolean wait Wait for process to be fully started\n        @return boolean result     Always true unless error\n\n        \"\"\"\n        self._update('startProcess')\n        group, process = self._getGroupAndProcess(name)\n        if process is None:\n            group_name, process_name = split_namespec(name)\n            return self.startProcessGroup(group_name, wait)\n\n        # test filespec, don't bother trying to spawn if we know it will\n        # eventually fail\n        try:\n            filename, argv = process.get_execv_args()\n        except NotFound as why:\n            raise RPCError(Faults.NO_FILE, why.args[0])\n        except (BadCommand, NotExecutable, NoPermission) as why:\n            raise RPCError(Faults.NOT_EXECUTABLE, why.args[0])\n\n        if process.get_state() in RUNNING_STATES:\n            raise RPCError(Faults.ALREADY_STARTED, name)\n\n        if process.get_state() == ProcessStates.UNKNOWN:\n            raise RPCError(Faults.FAILED,\n                           \"%s is in an unknown process state\" % name)\n\n        process.spawn()\n\n        # We call reap() in order to more quickly obtain the side effects of\n        # process.finish(), which reap() eventually ends up calling.  This\n        # might be the case if the spawn() was successful but then the process\n        # died before its startsecs elapsed or it exited with an unexpected\n        # exit code. In particular, finish() may set spawnerr, which we can\n        # check and immediately raise an RPCError, avoiding the need to\n        # defer by returning a callback.\n\n        self.supervisord.reap()\n\n        if process.spawnerr:\n            raise RPCError(Faults.SPAWN_ERROR, name)\n\n        # We call process.transition() in order to more quickly obtain its\n        # side effects.  In particular, it might set the process' state from\n        # STARTING->RUNNING if the process has a startsecs==0.\n        process.transition()\n\n        if wait and process.get_state() != ProcessStates.RUNNING:\n            # by default, this branch will almost always be hit for processes\n            # with default startsecs configurations, because the default number\n            # of startsecs for a process is \"1\", and the process will not have\n            # entered the RUNNING state yet even though we've called\n            # transition() on it.  This is because a process is not considered\n            # RUNNING until it has stayed up > startsecs.\n\n            def onwait():\n                if process.spawnerr:\n                    raise RPCError(Faults.SPAWN_ERROR, name)\n\n                state = process.get_state()\n\n                if state not in (ProcessStates.STARTING, ProcessStates.RUNNING):\n                    raise RPCError(Faults.ABNORMAL_TERMINATION, name)\n\n                if state == ProcessStates.RUNNING:\n                    return True\n\n                return NOT_DONE_YET\n\n            onwait.delay = 0.05\n            onwait.rpcinterface = self\n            return onwait # deferred\n\n        return True\n\n    def startProcessGroup(self, name, wait=True):\n        \"\"\" Start all processes in the group named 'name'\n\n        @param string name     The group name\n        @param boolean wait    Wait for each process to be fully started\n        @return array result   An array of process status info structs\n        \"\"\"\n        self._update('startProcessGroup')\n\n        group = self.supervisord.process_groups.get(name)\n\n        if group is None:\n            raise RPCError(Faults.BAD_NAME, name)\n\n        processes = list(group.processes.values())\n        processes.sort()\n        processes = [ (group, process) for process in processes ]\n\n        startall = make_allfunc(processes, isNotRunning, self.startProcess,\n                                wait=wait)\n\n        startall.delay = 0.05\n        startall.rpcinterface = self\n        return startall # deferred\n\n    def startAllProcesses(self, wait=True):\n        \"\"\" Start all processes listed in the configuration file\n\n        @param boolean wait    Wait for each process to be fully started\n        @return array result   An array of process status info structs\n        \"\"\"\n        self._update('startAllProcesses')\n\n        processes = self._getAllProcesses()\n        startall = make_allfunc(processes, isNotRunning, self.startProcess,\n                                wait=wait)\n\n        startall.delay = 0.05\n        startall.rpcinterface = self\n        return startall # deferred\n\n    def stopProcess(self, name, wait=True):\n        \"\"\" Stop a process named by name\n\n        @param string name  The name of the process to stop (or 'group:name')\n        @param boolean wait        Wait for the process to be fully stopped\n        @return boolean result     Always return True unless error\n        \"\"\"\n        self._update('stopProcess')\n\n        group, process = self._getGroupAndProcess(name)\n\n        if process is None:\n            group_name, process_name = split_namespec(name)\n            return self.stopProcessGroup(group_name, wait)\n\n        if process.get_state() not in RUNNING_STATES:\n            raise RPCError(Faults.NOT_RUNNING, name)\n\n        msg = process.stop()\n        if msg is not None:\n            raise RPCError(Faults.FAILED, msg)\n\n        # We'll try to reap any killed child. FWIW, reap calls waitpid, and\n        # then, if waitpid returns a pid, calls finish() on the process with\n        # that pid, which drains any I/O from the process' dispatchers and\n        # changes the process' state.  I chose to call reap without once=True\n        # because we don't really care if we reap more than one child.  Even if\n        # we only reap one child. we may not even be reaping the child that we\n        # just stopped (this is all async, and process.stop() may not work, and\n        # we'll need to wait for SIGKILL during process.transition() as the\n        # result of normal select looping).\n\n        self.supervisord.reap()\n\n        if wait and process.get_state() not in STOPPED_STATES:\n\n            def onwait():\n                # process will eventually enter a stopped state by\n                # virtue of the supervisord.reap() method being called\n                # during normal operations\n                process.stop_report()\n                if process.get_state() not in STOPPED_STATES:\n                    return NOT_DONE_YET\n                return True\n\n            onwait.delay = 0\n            onwait.rpcinterface = self\n            return onwait # deferred\n\n        return True\n\n    def stopProcessGroup(self, name, wait=True):\n        \"\"\" Stop all processes in the process group named 'name'\n\n        @param string name     The group name\n        @param boolean wait    Wait for each process to be fully stopped\n        @return array result   An array of process status info structs\n        \"\"\"\n        self._update('stopProcessGroup')\n\n        group = self.supervisord.process_groups.get(name)\n\n        if group is None:\n            raise RPCError(Faults.BAD_NAME, name)\n\n        processes = list(group.processes.values())\n        processes.sort()\n        processes = [ (group, process) for process in processes ]\n\n        killall = make_allfunc(processes, isRunning, self.stopProcess,\n                               wait=wait)\n\n        killall.delay = 0.05\n        killall.rpcinterface = self\n        return killall # deferred\n\n    def stopAllProcesses(self, wait=True):\n        \"\"\" Stop all processes in the process list\n\n        @param  boolean wait   Wait for each process to be fully stopped\n        @return array result   An array of process status info structs\n        \"\"\"\n        self._update('stopAllProcesses')\n\n        processes = self._getAllProcesses()\n\n        killall = make_allfunc(processes, isRunning, self.stopProcess,\n                               wait=wait)\n\n        killall.delay = 0.05\n        killall.rpcinterface = self\n        return killall # deferred\n\n    def signalProcess(self, name, signal):\n        \"\"\" Send an arbitrary UNIX signal to the process named by name\n\n        @param string name    Name of the process to signal (or 'group:name')\n        @param string signal  Signal to send, as name ('HUP') or number ('1')\n        @return boolean\n        \"\"\"\n\n        self._update('signalProcess')\n\n        group, process = self._getGroupAndProcess(name)\n\n        if process is None:\n            group_name, process_name = split_namespec(name)\n            return self.signalProcessGroup(group_name, signal=signal)\n\n        try:\n            sig = signal_number(signal)\n        except ValueError:\n            raise RPCError(Faults.BAD_SIGNAL, signal)\n\n        if process.get_state() not in SIGNALLABLE_STATES:\n            raise RPCError(Faults.NOT_RUNNING, name)\n\n        msg = process.signal(sig)\n\n        if not msg is None:\n            raise RPCError(Faults.FAILED, msg)\n\n        return True\n\n    def signalProcessGroup(self, name, signal):\n        \"\"\" Send a signal to all processes in the group named 'name'\n\n        @param string name    The group name\n        @param string signal  Signal to send, as name ('HUP') or number ('1')\n        @return array\n        \"\"\"\n\n        group = self.supervisord.process_groups.get(name)\n        self._update('signalProcessGroup')\n\n        if group is None:\n            raise RPCError(Faults.BAD_NAME, name)\n\n        processes = list(group.processes.values())\n        processes.sort()\n        processes = [(group, process) for process in processes]\n\n        sendall = make_allfunc(processes, isSignallable, self.signalProcess,\n                               signal=signal)\n        result = sendall()\n        self._update('signalProcessGroup')\n\n        return result\n\n    def signalAllProcesses(self, signal):\n        \"\"\" Send a signal to all processes in the process list\n\n        @param string signal  Signal to send, as name ('HUP') or number ('1')\n        @return array         An array of process status info structs\n        \"\"\"\n        processes = self._getAllProcesses()\n        signalall = make_allfunc(processes, isSignallable, self.signalProcess,\n            signal=signal)\n        result = signalall()\n        self._update('signalAllProcesses')\n        return result\n\n    def getAllConfigInfo(self):\n        \"\"\" Get info about all available process configurations. Each struct\n        represents a single process (i.e. groups get flattened).\n\n        @return array result  An array of process config info structs\n        \"\"\"\n        self._update('getAllConfigInfo')\n\n        configinfo = []\n        for gconfig in self.supervisord.options.process_group_configs:\n            inuse = gconfig.name in self.supervisord.process_groups\n            for pconfig in gconfig.process_configs:\n                d = {'autostart': pconfig.autostart,\n                     'autorestart': pconfig.autorestart,\n                     'directory': pconfig.directory,\n                     'uid': pconfig.uid,\n                     'command': pconfig.command,\n                     'exitcodes': pconfig.exitcodes,\n                     'group': gconfig.name,\n                     'group_prio': gconfig.priority,\n                     'inuse': inuse,\n                     'killasgroup': pconfig.killasgroup,\n                     'name': pconfig.name,\n                     'process_prio': pconfig.priority,\n                     'redirect_stderr': pconfig.redirect_stderr,\n                     'startretries': pconfig.startretries,\n                     'startsecs': pconfig.startsecs,\n                     'stdout_capture_maxbytes': pconfig.stdout_capture_maxbytes,\n                     'stdout_events_enabled': pconfig.stdout_events_enabled,\n                     'stdout_logfile': pconfig.stdout_logfile,\n                     'stdout_logfile_backups': pconfig.stdout_logfile_backups,\n                     'stdout_logfile_maxbytes': pconfig.stdout_logfile_maxbytes,\n                     'stdout_syslog': pconfig.stdout_syslog,\n                     'stopsignal': int(pconfig.stopsignal), # enum on py3\n                     'stopwaitsecs': pconfig.stopwaitsecs,\n                     'stderr_capture_maxbytes': pconfig.stderr_capture_maxbytes,\n                     'stderr_events_enabled': pconfig.stderr_events_enabled,\n                     'stderr_logfile': pconfig.stderr_logfile,\n                     'stderr_logfile_backups': pconfig.stderr_logfile_backups,\n                     'stderr_logfile_maxbytes': pconfig.stderr_logfile_maxbytes,\n                     'stderr_syslog': pconfig.stderr_syslog,\n                     'serverurl': pconfig.serverurl,\n                    }\n\n                # no support for these types in xml-rpc\n                for k, v in d.items():\n                    if v is Automatic:\n                        d[k] = \"auto\"\n                    elif v is None:\n                        d[k] = \"none\"\n                    elif v is RestartWhenExitUnexpected:\n                        d[k] = \"unexpected\"\n\n                configinfo.append(d)\n\n        configinfo.sort(key=lambda r: r['name'])\n        return configinfo\n\n    def _interpretProcessInfo(self, info):\n        state = info['state']\n\n        if state == ProcessStates.RUNNING:\n            start = info['start']\n            now = info['now']\n            start_dt = datetime.datetime(*time.gmtime(start)[:6])\n            now_dt = datetime.datetime(*time.gmtime(now)[:6])\n            uptime = now_dt - start_dt\n            if _total_seconds(uptime) < 0: # system time set back\n                uptime = datetime.timedelta(0)\n            desc = 'pid %s, uptime %s' % (info['pid'], uptime)\n\n        elif state in (ProcessStates.FATAL, ProcessStates.BACKOFF):\n            desc = info['spawnerr']\n            if not desc:\n                desc = 'unknown error (try \"tail %s\")' % info['name']\n\n        elif state in (ProcessStates.STOPPED, ProcessStates.EXITED):\n            if info['start']:\n                stop = info['stop']\n                stop_dt = datetime.datetime(*time.localtime(stop)[:7])\n                desc = stop_dt.strftime('%b %d %I:%M %p')\n            else:\n                desc = 'Not started'\n\n        else:\n            desc = ''\n\n        return desc\n\n    def getProcessInfo(self, name):\n        \"\"\" Get info about a process named name\n\n        @param string name The name of the process (or 'group:name')\n        @return struct result     A structure containing data about the process\n        \"\"\"\n        self._update('getProcessInfo')\n\n        group, process = self._getGroupAndProcess(name)\n\n        if process is None:\n            raise RPCError(Faults.BAD_NAME, name)\n\n        # TODO timestamps are returned as xml-rpc integers for b/c but will\n        # saturate the xml-rpc integer type in jan 2038 (\"year 2038 problem\").\n        # future api versions should return timestamps as a different type.\n        start = capped_int(process.laststart)\n        stop = capped_int(process.laststop)\n        now = capped_int(self._now())\n\n        state = process.get_state()\n        spawnerr = process.spawnerr or ''\n        exitstatus = process.exitstatus or 0\n        stdout_logfile = process.config.stdout_logfile or ''\n        stderr_logfile = process.config.stderr_logfile or ''\n\n        info = {\n            'name':process.config.name,\n            'group':group.config.name,\n            'start':start,\n            'stop':stop,\n            'now':now,\n            'state':state,\n            'statename':getProcessStateDescription(state),\n            'spawnerr':spawnerr,\n            'exitstatus':exitstatus,\n            'logfile':stdout_logfile, # b/c alias\n            'stdout_logfile':stdout_logfile,\n            'stderr_logfile':stderr_logfile,\n            'pid':process.pid,\n            }\n\n        description = self._interpretProcessInfo(info)\n        info['description'] = description\n        return info\n\n    def _now(self): # pragma: no cover\n        # this is here to service stubbing in unit tests\n        return time.time()\n\n    def getAllProcessInfo(self):\n        \"\"\" Get info about all processes\n\n        @return array result  An array of process status results\n        \"\"\"\n        self._update('getAllProcessInfo')\n\n        all_processes = self._getAllProcesses(lexical=True)\n\n        output = []\n        for group, process in all_processes:\n            name = make_namespec(group.config.name, process.config.name)\n            output.append(self.getProcessInfo(name))\n        return output\n\n    def _readProcessLog(self, name, offset, length, channel):\n        group, process = self._getGroupAndProcess(name)\n\n        if process is None:\n            raise RPCError(Faults.BAD_NAME, name)\n\n        logfile = getattr(process.config, '%s_logfile' % channel)\n\n        if logfile is None or not os.path.exists(logfile):\n            raise RPCError(Faults.NO_FILE, logfile)\n\n        try:\n            return as_string(readFile(logfile, int(offset), int(length)))\n        except ValueError as inst:\n            why = inst.args[0]\n            raise RPCError(getattr(Faults, why))\n\n    def readProcessStdoutLog(self, name, offset, length):\n        \"\"\" Read length bytes from name's stdout log starting at offset\n\n        @param string name        the name of the process (or 'group:name')\n        @param int offset         offset to start reading from.\n        @param int length         number of bytes to read from the log.\n        @return string result     Bytes of log\n        \"\"\"\n        self._update('readProcessStdoutLog')\n        return self._readProcessLog(name, offset, length, 'stdout')\n\n    readProcessLog = readProcessStdoutLog # b/c alias\n\n    def readProcessStderrLog(self, name, offset, length):\n        \"\"\" Read length bytes from name's stderr log starting at offset\n\n        @param string name        the name of the process (or 'group:name')\n        @param int offset         offset to start reading from.\n        @param int length         number of bytes to read from the log.\n        @return string result     Bytes of log\n        \"\"\"\n        self._update('readProcessStderrLog')\n        return self._readProcessLog(name, offset, length, 'stderr')\n\n    def _tailProcessLog(self, name, offset, length, channel):\n        group, process = self._getGroupAndProcess(name)\n\n        if process is None:\n            raise RPCError(Faults.BAD_NAME, name)\n\n        logfile = getattr(process.config, '%s_logfile' % channel)\n\n        if logfile is None or not os.path.exists(logfile):\n            return ['', 0, False]\n\n        return tailFile(logfile, int(offset), int(length))\n\n    def tailProcessStdoutLog(self, name, offset, length):\n        \"\"\"\n        Provides a more efficient way to tail the (stdout) log than\n        readProcessStdoutLog().  Use readProcessStdoutLog() to read\n        chunks and tailProcessStdoutLog() to tail.\n\n        Requests (length) bytes from the (name)'s log, starting at\n        (offset).  If the total log size is greater than (offset +\n        length), the overflow flag is set and the (offset) is\n        automatically increased to position the buffer at the end of\n        the log.  If less than (length) bytes are available, the\n        maximum number of available bytes will be returned.  (offset)\n        returned is always the last offset in the log +1.\n\n        @param string name         the name of the process (or 'group:name')\n        @param int offset          offset to start reading from\n        @param int length          maximum number of bytes to return\n        @return array result       [string bytes, int offset, bool overflow]\n        \"\"\"\n        self._update('tailProcessStdoutLog')\n        return self._tailProcessLog(name, offset, length, 'stdout')\n\n    tailProcessLog = tailProcessStdoutLog # b/c alias\n\n    def tailProcessStderrLog(self, name, offset, length):\n        \"\"\"\n        Provides a more efficient way to tail the (stderr) log than\n        readProcessStderrLog().  Use readProcessStderrLog() to read\n        chunks and tailProcessStderrLog() to tail.\n\n        Requests (length) bytes from the (name)'s log, starting at\n        (offset).  If the total log size is greater than (offset +\n        length), the overflow flag is set and the (offset) is\n        automatically increased to position the buffer at the end of\n        the log.  If less than (length) bytes are available, the\n        maximum number of available bytes will be returned.  (offset)\n        returned is always the last offset in the log +1.\n\n        @param string name         the name of the process (or 'group:name')\n        @param int offset          offset to start reading from\n        @param int length          maximum number of bytes to return\n        @return array result       [string bytes, int offset, bool overflow]\n        \"\"\"\n        self._update('tailProcessStderrLog')\n        return self._tailProcessLog(name, offset, length, 'stderr')\n\n    def clearProcessLogs(self, name):\n        \"\"\" Clear the stdout and stderr logs for the named process and\n        reopen them.\n\n        @param string name   The name of the process (or 'group:name')\n        @return boolean result      Always True unless error\n        \"\"\"\n        self._update('clearProcessLogs')\n\n        group, process = self._getGroupAndProcess(name)\n\n        if process is None:\n            raise RPCError(Faults.BAD_NAME, name)\n\n        try:\n            # implies a reopen\n            process.removelogs()\n        except (IOError, OSError):\n            raise RPCError(Faults.FAILED, name)\n\n        return True\n\n    clearProcessLog = clearProcessLogs # b/c alias\n\n    def clearAllProcessLogs(self):\n        \"\"\" Clear all process log files\n\n        @return array result   An array of process status info structs\n        \"\"\"\n        self._update('clearAllProcessLogs')\n        results  = []\n        callbacks = []\n\n        all_processes = self._getAllProcesses()\n\n        for group, process in all_processes:\n            callbacks.append((group, process, self.clearProcessLog))\n\n        def clearall():\n            if not callbacks:\n                return results\n\n            group, process, callback = callbacks.pop(0)\n            name = make_namespec(group.config.name, process.config.name)\n            try:\n                callback(name)\n            except RPCError as e:\n                results.append(\n                    {'name':process.config.name,\n                     'group':group.config.name,\n                     'status':e.code,\n                     'description':e.text})\n            else:\n                results.append(\n                    {'name':process.config.name,\n                     'group':group.config.name,\n                     'status':Faults.SUCCESS,\n                     'description':'OK'}\n                    )\n\n            if callbacks:\n                return NOT_DONE_YET\n\n            return results\n\n        clearall.delay = 0.05\n        clearall.rpcinterface = self\n        return clearall # deferred\n\n    def sendProcessStdin(self, name, chars):\n        \"\"\" Send a string of chars to the stdin of the process name.\n        If non-7-bit data is sent (unicode), it is encoded to utf-8\n        before being sent to the process' stdin.  If chars is not a\n        string or is not unicode, raise INCORRECT_PARAMETERS.  If the\n        process is not running, raise NOT_RUNNING.  If the process'\n        stdin cannot accept input (e.g. it was closed by the child\n        process), raise NO_FILE.\n\n        @param string name        The process name to send to (or 'group:name')\n        @param string chars       The character data to send to the process\n        @return boolean result    Always return True unless error\n        \"\"\"\n        self._update('sendProcessStdin')\n\n        if not isinstance(chars, (str, bytes, unicode)):\n            raise RPCError(Faults.INCORRECT_PARAMETERS, chars)\n\n        chars = as_bytes(chars)\n\n        group, process = self._getGroupAndProcess(name)\n\n        if process is None:\n            raise RPCError(Faults.BAD_NAME, name)\n\n        if not process.pid or process.killing:\n            raise RPCError(Faults.NOT_RUNNING, name)\n\n        try:\n            process.write(chars)\n        except OSError as why:\n            if why.args[0] == errno.EPIPE:\n                raise RPCError(Faults.NO_FILE, name)\n            else:\n                raise\n\n        return True\n\n    def sendRemoteCommEvent(self, type, data):\n        \"\"\" Send an event that will be received by event listener\n        subprocesses subscribing to the RemoteCommunicationEvent.\n\n        @param  string  type  String for the \"type\" key in the event header\n        @param  string  data  Data for the event body\n        @return boolean       Always return True unless error\n        \"\"\"\n        if isinstance(type, unicode):\n            type = type.encode('utf-8')\n        if isinstance(data, unicode):\n            data = data.encode('utf-8')\n\n        notify(\n            RemoteCommunicationEvent(type, data)\n        )\n\n        return True\n\ndef _total_seconds(timedelta):\n    return ((timedelta.days * 86400 + timedelta.seconds) * 10**6 +\n                timedelta.microseconds) / 10**6\n\ndef make_allfunc(processes, predicate, func, **extra_kwargs):\n    \"\"\" Return a closure representing a function that calls a\n    function for every process, and returns a result \"\"\"\n\n    callbacks = []\n    results = []\n\n    def allfunc(\n        processes=processes,\n        predicate=predicate,\n        func=func,\n        extra_kwargs=extra_kwargs,\n        callbacks=callbacks, # used only to fool scoping, never passed by caller\n        results=results, # used only to fool scoping, never passed by caller\n        ):\n\n        if not callbacks:\n\n            for group, process in processes:\n                name = make_namespec(group.config.name, process.config.name)\n                if predicate(process):\n                    try:\n                        callback = func(name, **extra_kwargs)\n                    except RPCError as e:\n                        results.append({'name':process.config.name,\n                                        'group':group.config.name,\n                                        'status':e.code,\n                                        'description':e.text})\n                        continue\n                    if isinstance(callback, types.FunctionType):\n                        callbacks.append((group, process, callback))\n                    else:\n                        results.append(\n                            {'name':process.config.name,\n                             'group':group.config.name,\n                             'status':Faults.SUCCESS,\n                             'description':'OK'}\n                            )\n\n        if not callbacks:\n            return results\n\n        for struct in callbacks[:]:\n\n            group, process, cb = struct\n\n            try:\n                value = cb()\n            except RPCError as e:\n                results.append(\n                    {'name':process.config.name,\n                     'group':group.config.name,\n                     'status':e.code,\n                     'description':e.text})\n                callbacks.remove(struct)\n            else:\n                if value is not NOT_DONE_YET:\n                    results.append(\n                        {'name':process.config.name,\n                         'group':group.config.name,\n                         'status':Faults.SUCCESS,\n                         'description':'OK'}\n                        )\n                    callbacks.remove(struct)\n\n        if callbacks:\n            return NOT_DONE_YET\n\n        return results\n\n    # XXX the above implementation has a weakness inasmuch as the\n    # first call into each individual process callback will always\n    # return NOT_DONE_YET, so they need to be called twice.  The\n    # symptom of this is that calling this method causes the\n    # client to block for much longer than it actually requires to\n    # kill all of the running processes.  After the first call to\n    # the killit callback, the process is actually dead, but the\n    # above killall method processes the callbacks one at a time\n    # during the select loop, which, because there is no output\n    # from child processes after e.g. stopAllProcesses is called,\n    # is not busy, so hits the timeout for each callback.  I\n    # attempted to make this better, but the only way to make it\n    # better assumes totally synchronous reaping of child\n    # processes, which requires infrastructure changes to\n    # supervisord that are scary at the moment as it could take a\n    # while to pin down all of the platform differences and might\n    # require a C extension to the Python signal module to allow\n    # the setting of ignore flags to signals.\n    return allfunc\n\ndef isRunning(process):\n    return process.get_state() in RUNNING_STATES\n\ndef isNotRunning(process):\n    return not isRunning(process)\n\ndef isSignallable(process):\n    if process.get_state() in SIGNALLABLE_STATES:\n        return True\n\n# this is not used in code but referenced via an entry point in the conf file\ndef make_main_rpcinterface(supervisord):\n    return SupervisorNamespaceRPCInterface(supervisord)\n\n"
  },
  {
    "path": "supervisor/skel/sample.conf",
    "content": "; Sample supervisor config file.\n;\n; For more information on the config file, please see:\n; http://supervisord.org/configuration.html\n;\n; Notes:\n;  - Shell expansion (\"~\" or \"$HOME\") is not supported.  Environment\n;    variables can be expanded using this syntax: \"%(ENV_HOME)s\".\n;  - Quotes around values are not supported, except in the case of\n;    the environment= options as shown below.\n;  - Comments must have a leading space: \"a=b ;comment\" not \"a=b;comment\".\n;  - Command will be truncated if it looks like a config file comment, e.g.\n;    \"command=bash -c 'foo ; bar'\" will truncate to \"command=bash -c 'foo \".\n;\n; Warning:\n;  Paths throughout this example file use /tmp because it is available on most\n;  systems.  You will likely need to change these to locations more appropriate\n;  for your system.  Some systems periodically delete older files in /tmp.\n;  Notably, if the socket file defined in the [unix_http_server] section below\n;  is deleted, supervisorctl will be unable to connect to supervisord.\n\n[unix_http_server]\nfile=/tmp/supervisor.sock   ; the path to the socket file\n;chmod=0700                 ; socket file mode (default 0700)\n;chown=nobody:nogroup       ; socket file uid:gid owner\n;username=user              ; default is no username (open server)\n;password=123               ; default is no password (open server)\n\n; Security Warning:\n;  The inet HTTP server is not enabled by default.  The inet HTTP server is\n;  enabled by uncommenting the [inet_http_server] section below.  The inet\n;  HTTP server is intended for use within a trusted environment only.  It\n;  should only be bound to localhost or only accessible from within an\n;  isolated, trusted network.  The inet HTTP server does not support any\n;  form of encryption.  The inet HTTP server does not use authentication\n;  by default (see the username= and password= options to add authentication).\n;  Never expose the inet HTTP server to the public internet.\n\n;[inet_http_server]         ; inet (TCP) server disabled by default\n;port=127.0.0.1:9001        ; ip_address:port specifier, *:port for all iface\n;username=user              ; default is no username (open server)\n;password=123               ; default is no password (open server)\n\n[supervisord]\nlogfile=/tmp/supervisord.log ; main log file; default $CWD/supervisord.log\nlogfile_maxbytes=50MB        ; max main logfile bytes b4 rotation; default 50MB\nlogfile_backups=10           ; # of main logfile backups; 0 means none, default 10\nloglevel=info                ; log level; default info; others: debug,warn,trace\npidfile=/tmp/supervisord.pid ; supervisord pidfile; default supervisord.pid\nnodaemon=false               ; start in foreground if true; default false\nsilent=false                 ; no logs to stdout if true; default false\nminfds=1024                  ; min. avail startup file descriptors; default 1024\nminprocs=200                 ; min. avail process descriptors;default 200\n;umask=022                   ; process file creation umask; default 022\n;user=supervisord            ; setuid to this UNIX account at startup; recommended if root\n;identifier=supervisor       ; supervisord identifier, default is 'supervisor'\n;directory=/tmp              ; default is not to cd during start\n;nocleanup=true              ; don't clean up tempfiles at start; default false\n;childlogdir=/tmp            ; 'AUTO' child log dir, default $TEMP\n;environment=KEY=\"value\"     ; key value pairs to add to environment\n;strip_ansi=false            ; strip ansi escape codes in logs; def. false\n\n; The rpcinterface:supervisor section must remain in the config file for\n; RPC (supervisorctl/web interface) to work.  Additional interfaces may be\n; added by defining them in separate [rpcinterface:x] sections.\n\n[rpcinterface:supervisor]\nsupervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface\n\n; The supervisorctl section configures how supervisorctl will connect to\n; supervisord.  configure it match the settings in either the unix_http_server\n; or inet_http_server section.\n\n[supervisorctl]\nserverurl=unix:///tmp/supervisor.sock ; use a unix:// URL  for a unix socket\n;serverurl=http://127.0.0.1:9001 ; use an http:// url to specify an inet socket\n;username=chris              ; should be same as in [*_http_server] if set\n;password=123                ; should be same as in [*_http_server] if set\n;prompt=mysupervisor         ; cmd line prompt (default \"supervisor\")\n;history_file=~/.sc_history  ; use readline history if available\n\n; The sample program section below shows all possible program subsection values.\n; Create one or more 'real' program: sections to be able to control them under\n; supervisor.\n\n;[program:theprogramname]\n;command=/bin/cat              ; the program (relative uses PATH, can take args)\n;process_name=%(program_name)s ; process_name expr (default %(program_name)s)\n;numprocs=1                    ; number of processes copies to start (def 1)\n;directory=/tmp                ; directory to cwd to before exec (def no cwd)\n;umask=022                     ; umask for process (default None)\n;priority=999                  ; the relative start priority (default 999)\n;autostart=true                ; start at supervisord start (default: true)\n;startsecs=1                   ; # of secs prog must stay up to be running (def. 1)\n;startretries=3                ; max # of serial start failures when starting (default 3)\n;autorestart=unexpected        ; when to restart if exited after running (def: unexpected)\n;exitcodes=0                   ; 'expected' exit codes used with autorestart (default 0)\n;stopsignal=QUIT               ; signal used to kill process (default TERM)\n;stopwaitsecs=10               ; max num secs to wait b4 SIGKILL (default 10)\n;stopasgroup=false             ; send stop signal to the UNIX process group (default false)\n;killasgroup=false             ; SIGKILL the UNIX process group (def false)\n;user=chrism                   ; setuid to this UNIX account to run the program\n;redirect_stderr=true          ; redirect proc stderr to stdout (default false)\n;stdout_logfile=/a/path        ; stdout log path, NONE for none; default AUTO\n;stdout_logfile_maxbytes=1MB   ; max # logfile bytes b4 rotation (default 50MB)\n;stdout_logfile_backups=10     ; # of stdout logfile backups (0 means none, default 10)\n;stdout_capture_maxbytes=1MB   ; number of bytes in 'capturemode' (default 0)\n;stdout_events_enabled=false   ; emit events on stdout writes (default false)\n;stdout_syslog=false           ; send stdout to syslog with process name (default false)\n;stderr_logfile=/a/path        ; stderr log path, NONE for none; default AUTO\n;stderr_logfile_maxbytes=1MB   ; max # logfile bytes b4 rotation (default 50MB)\n;stderr_logfile_backups=10     ; # of stderr logfile backups (0 means none, default 10)\n;stderr_capture_maxbytes=1MB   ; number of bytes in 'capturemode' (default 0)\n;stderr_events_enabled=false   ; emit events on stderr writes (default false)\n;stderr_syslog=false           ; send stderr to syslog with process name (default false)\n;environment=A=\"1\",B=\"2\"       ; process environment additions (def no adds)\n;serverurl=AUTO                ; override serverurl computation (childutils)\n\n; The sample eventlistener section below shows all possible eventlistener\n; subsection values.  Create one or more 'real' eventlistener: sections to be\n; able to handle event notifications sent by supervisord.\n\n;[eventlistener:theeventlistenername]\n;command=/bin/eventlistener    ; the program (relative uses PATH, can take args)\n;process_name=%(program_name)s ; process_name expr (default %(program_name)s)\n;numprocs=1                    ; number of processes copies to start (def 1)\n;events=EVENT                  ; event notif. types to subscribe to (req'd)\n;buffer_size=10                ; event buffer queue size (default 10)\n;directory=/tmp                ; directory to cwd to before exec (def no cwd)\n;umask=022                     ; umask for process (default None)\n;priority=-1                   ; the relative start priority (default -1)\n;autostart=true                ; start at supervisord start (default: true)\n;startsecs=1                   ; # of secs prog must stay up to be running (def. 1)\n;startretries=3                ; max # of serial start failures when starting (default 3)\n;autorestart=unexpected        ; autorestart if exited after running (def: unexpected)\n;exitcodes=0                   ; 'expected' exit codes used with autorestart (default 0)\n;stopsignal=QUIT               ; signal used to kill process (default TERM)\n;stopwaitsecs=10               ; max num secs to wait b4 SIGKILL (default 10)\n;stopasgroup=false             ; send stop signal to the UNIX process group (default false)\n;killasgroup=false             ; SIGKILL the UNIX process group (def false)\n;user=chrism                   ; setuid to this UNIX account to run the program\n;redirect_stderr=false         ; redirect_stderr=true is not allowed for eventlisteners\n;stdout_logfile=/a/path        ; stdout log path, NONE for none; default AUTO\n;stdout_logfile_maxbytes=1MB   ; max # logfile bytes b4 rotation (default 50MB)\n;stdout_logfile_backups=10     ; # of stdout logfile backups (0 means none, default 10)\n;stdout_events_enabled=false   ; emit events on stdout writes (default false)\n;stdout_syslog=false           ; send stdout to syslog with process name (default false)\n;stderr_logfile=/a/path        ; stderr log path, NONE for none; default AUTO\n;stderr_logfile_maxbytes=1MB   ; max # logfile bytes b4 rotation (default 50MB)\n;stderr_logfile_backups=10     ; # of stderr logfile backups (0 means none, default 10)\n;stderr_events_enabled=false   ; emit events on stderr writes (default false)\n;stderr_syslog=false           ; send stderr to syslog with process name (default false)\n;environment=A=\"1\",B=\"2\"       ; process environment additions\n;serverurl=AUTO                ; override serverurl computation (childutils)\n\n; The sample group section below shows all possible group values.  Create one\n; or more 'real' group: sections to create \"heterogeneous\" process groups.\n\n;[group:thegroupname]\n;programs=progname1,progname2  ; each refers to 'x' in [program:x] definitions\n;priority=999                  ; the relative start priority (default 999)\n\n; The [include] section can just contain the \"files\" setting.  This\n; setting can list multiple files (separated by whitespace or\n; newlines).  It can also contain wildcards.  The filenames are\n; interpreted as relative to this file.  Included files *cannot*\n; include files themselves.\n\n;[include]\n;files = relative/directory/*.ini\n"
  },
  {
    "path": "supervisor/socket_manager.py",
    "content": "import socket\n\nclass Proxy:\n    \"\"\" Class for wrapping a shared resource object and getting\n        notified when it's deleted\n    \"\"\"\n\n    def __init__(self, object, **kwargs):\n        self.object = object\n        self.on_delete = kwargs.get('on_delete', None)\n\n    def __del__(self):\n        if self.on_delete:\n            self.on_delete()\n\n    def __getattr__(self, name):\n        return getattr(self.object, name)\n\n    def _get(self):\n        return self.object\n\nclass ReferenceCounter:\n    \"\"\" Class for tracking references to a shared resource\n    \"\"\"\n\n    def __init__(self, **kwargs):\n        self.on_non_zero = kwargs['on_non_zero']\n        self.on_zero = kwargs['on_zero']\n        self.ref_count = 0\n\n    def get_count(self):\n        return self.ref_count\n\n    def increment(self):\n        if self.ref_count == 0:\n            self.on_non_zero()\n        self.ref_count += 1\n\n    def decrement(self):\n        if self.ref_count <= 0:\n            raise Exception('Illegal operation: cannot decrement below zero')\n        self.ref_count -= 1\n        if self.ref_count == 0:\n            self.on_zero()\n\nclass SocketManager:\n    \"\"\" Class for managing sockets in servers that create/bind/listen\n        before forking multiple child processes to accept()\n        Sockets are managed at the process group level and referenced counted\n        at the process level b/c that's really the only place to hook in\n    \"\"\"\n\n    def __init__(self, socket_config, **kwargs):\n        self.logger = kwargs.get('logger', None)\n        self.socket = None\n        self.prepared = False\n        self.socket_config = socket_config\n        self.ref_ctr = ReferenceCounter(\n            on_zero=self._close, on_non_zero=self._prepare_socket\n            )\n\n    def __repr__(self):\n        return '<%s at %s for %s>' % (self.__class__,\n                                      id(self),\n                                      self.socket_config.url)\n\n    def config(self):\n        return self.socket_config\n\n    def is_prepared(self):\n        return self.prepared\n\n    def get_socket(self):\n        self.ref_ctr.increment()\n        self._require_prepared()\n        return Proxy(self.socket, on_delete=self.ref_ctr.decrement)\n\n    def get_socket_ref_count(self):\n        self._require_prepared()\n        return self.ref_ctr.get_count()\n\n    def _require_prepared(self):\n        if not self.prepared:\n            raise Exception('Socket has not been prepared')\n\n    def _prepare_socket(self):\n        if not self.prepared:\n            if self.logger:\n                self.logger.info('Creating socket %s' % self.socket_config)\n            self.socket = self.socket_config.create_and_bind()\n            if self.socket_config.get_backlog():\n                self.socket.listen(self.socket_config.get_backlog())\n            else:\n                self.socket.listen(socket.SOMAXCONN)\n            self.prepared = True\n\n    def _close(self):\n        self._require_prepared()\n        if self.logger:\n            self.logger.info('Closing socket %s' % self.socket_config)\n        self.socket.close()\n        self.prepared = False\n"
  },
  {
    "path": "supervisor/states.py",
    "content": "# This module must not depend on any other non-stdlib module to prevent\n# circular import problems.\n\nclass ProcessStates:\n    STOPPED = 0\n    STARTING = 10\n    RUNNING = 20\n    BACKOFF = 30\n    STOPPING = 40\n    EXITED = 100\n    FATAL = 200\n    UNKNOWN = 1000\n\nSTOPPED_STATES = (ProcessStates.STOPPED,\n                  ProcessStates.EXITED,\n                  ProcessStates.FATAL,\n                  ProcessStates.UNKNOWN)\n\nRUNNING_STATES = (ProcessStates.RUNNING,\n                  ProcessStates.BACKOFF,\n                  ProcessStates.STARTING)\n\nSIGNALLABLE_STATES = (ProcessStates.RUNNING,\n                     ProcessStates.STARTING,\n                     ProcessStates.STOPPING)\n\ndef getProcessStateDescription(code):\n    return _process_states_by_code.get(code)\n\n\nclass SupervisorStates:\n    FATAL = 2\n    RUNNING = 1\n    RESTARTING = 0\n    SHUTDOWN = -1\n\ndef getSupervisorStateDescription(code):\n    return _supervisor_states_by_code.get(code)\n\n\nclass EventListenerStates:\n    READY = 10 # the process ready to be sent an event from supervisor\n    BUSY = 20 # event listener is processing an event sent to it by supervisor\n    ACKNOWLEDGED = 30 # the event listener processed an event\n    UNKNOWN = 40 # the event listener is in an unknown state\n\ndef getEventListenerStateDescription(code):\n    return _eventlistener_states_by_code.get(code)\n\n\n# below is an optimization for internal use in this module only\ndef _names_by_code(states):\n    d = {}\n    for name in states.__dict__:\n        if not name.startswith('__'):\n            code = getattr(states, name)\n            d[code] = name\n    return d\n_process_states_by_code = _names_by_code(ProcessStates)\n_supervisor_states_by_code = _names_by_code(SupervisorStates)\n_eventlistener_states_by_code = _names_by_code(EventListenerStates)\n"
  },
  {
    "path": "supervisor/supervisorctl.py",
    "content": "#!/usr/bin/env python -u\n\n\"\"\"supervisorctl -- control applications run by supervisord from the cmd line.\n\nUsage: %s [options] [action [arguments]]\n\nOptions:\n-c/--configuration FILENAME -- configuration file path (searches if not given)\n-h/--help -- print usage message and exit\n-i/--interactive -- start an interactive shell after executing commands\n-s/--serverurl URL -- URL on which supervisord server is listening\n     (default \"http://localhost:9001\").\n-u/--username USERNAME -- username to use for authentication with server\n-p/--password PASSWORD -- password to use for authentication with server\n-r/--history-file -- keep a readline history (if readline is available)\n\naction [arguments] -- see below\n\nActions are commands like \"tail\" or \"stop\".  If -i is specified or no action is\nspecified on the command line, a \"shell\" interpreting actions typed\ninteractively is started.  Use the action \"help\" to find out about available\nactions.\n\"\"\"\n\nimport cmd\nimport errno\nimport getpass\nimport socket\nimport sys\nimport threading\n\nfrom supervisor.compat import xmlrpclib\nfrom supervisor.compat import urlparse\nfrom supervisor.compat import unicode\nfrom supervisor.compat import raw_input\nfrom supervisor.compat import as_string\n\nfrom supervisor.medusa import asyncore_25 as asyncore\n\nfrom supervisor.options import ClientOptions\nfrom supervisor.options import make_namespec\nfrom supervisor.options import split_namespec\nfrom supervisor import xmlrpc\nfrom supervisor import states\nfrom supervisor import http_client\n\nclass LSBInitExitStatuses:\n    SUCCESS = 0\n    GENERIC = 1\n    INVALID_ARGS = 2\n    UNIMPLEMENTED_FEATURE = 3\n    INSUFFICIENT_PRIVILEGES = 4\n    NOT_INSTALLED = 5\n    NOT_RUNNING = 7\n\nclass LSBStatusExitStatuses:\n    NOT_RUNNING = 3\n    UNKNOWN = 4\n\nDEAD_PROGRAM_FAULTS = (xmlrpc.Faults.SPAWN_ERROR,\n                       xmlrpc.Faults.ABNORMAL_TERMINATION,\n                       xmlrpc.Faults.NOT_RUNNING)\n\nclass fgthread(threading.Thread):\n    \"\"\" A subclass of threading.Thread, with a kill() method.\n    To be used for foreground output/error streaming.\n    http://mail.python.org/pipermail/python-list/2004-May/260937.html\n    \"\"\"\n\n    def __init__(self, program, ctl):\n        threading.Thread.__init__(self)\n        self.killed = False\n        self.program = program\n        self.ctl = ctl\n        self.listener = http_client.Listener()\n        self.output_handler = http_client.HTTPHandler(self.listener,\n                                                      self.ctl.options.username,\n                                                      self.ctl.options.password)\n        self.error_handler = http_client.HTTPHandler(self.listener,\n                                                     self.ctl.options.username,\n                                                     self.ctl.options.password)\n\n    def start(self): # pragma: no cover\n        # Start the thread\n        self.__run_backup = self.run\n        self.run = self.__run\n        threading.Thread.start(self)\n\n    def run(self): # pragma: no cover\n        self.output_handler.get(self.ctl.options.serverurl,\n                                '/logtail/%s/stdout' % self.program)\n        self.error_handler.get(self.ctl.options.serverurl,\n                               '/logtail/%s/stderr' % self.program)\n        asyncore.loop()\n\n    def __run(self): # pragma: no cover\n        # Hacked run function, which installs the trace\n        sys.settrace(self.globaltrace)\n        self.__run_backup()\n        self.run = self.__run_backup\n\n    def globaltrace(self, frame, why, arg):\n        if why == 'call':\n            return self.localtrace\n        else:\n            return None\n\n    def localtrace(self, frame, why, arg):\n        if self.killed:\n            if why == 'line':\n                raise SystemExit()\n        return self.localtrace\n\n    def kill(self):\n        self.output_handler.close()\n        self.error_handler.close()\n        self.killed = True\n\nclass Controller(cmd.Cmd):\n\n    def __init__(self, options, completekey='tab', stdin=None,\n                 stdout=None):\n        self.options = options\n        self.prompt = self.options.prompt + '> '\n        self.options.plugins = []\n        self.vocab = ['help']\n        self._complete_info = None\n        self.exitstatus = LSBInitExitStatuses.SUCCESS\n        cmd.Cmd.__init__(self, completekey, stdin, stdout)\n        for name, factory, kwargs in self.options.plugin_factories:\n            plugin = factory(self, **kwargs)\n            for a in dir(plugin):\n                if a.startswith('do_') and callable(getattr(plugin, a)):\n                    self.vocab.append(a[3:])\n            self.options.plugins.append(plugin)\n            plugin.name = name\n\n    def emptyline(self):\n        # We don't want a blank line to repeat the last command.\n        return\n\n    def default(self, line):\n        self.output('*** Unknown syntax: %s' % line)\n        self.exitstatus = LSBInitExitStatuses.GENERIC\n\n    def exec_cmdloop(self, args, options):\n        try:\n            import readline\n            delims = readline.get_completer_delims()\n            delims = delims.replace(':', '')  # \"group:process\" as one word\n            delims = delims.replace('*', '')  # \"group:*\" as one word\n            delims = delims.replace('-', '')  # names with \"-\" as one word\n            readline.set_completer_delims(delims)\n\n            if options.history_file:\n                try:\n                    readline.read_history_file(options.history_file)\n                except IOError:\n                    pass\n\n                def save():\n                    try:\n                        readline.write_history_file(options.history_file)\n                    except IOError:\n                        pass\n\n                import atexit\n                atexit.register(save)\n        except ImportError:\n            pass\n        try:\n            self.cmdqueue.append('status')\n            self.cmdloop()\n        except KeyboardInterrupt:\n            self.output('')\n            pass\n\n    def set_exitstatus_from_xmlrpc_fault(self, faultcode, ignored_faultcode=None):\n        if faultcode in (ignored_faultcode, xmlrpc.Faults.SUCCESS):\n            pass\n        elif faultcode in DEAD_PROGRAM_FAULTS:\n            self.exitstatus = LSBInitExitStatuses.NOT_RUNNING\n        else:\n            self.exitstatus = LSBInitExitStatuses.GENERIC\n\n    def onecmd(self, line):\n        \"\"\" Override the onecmd method to:\n          - catch and print all exceptions\n          - call 'do_foo' on plugins rather than ourself\n        \"\"\"\n        cmd, arg, line = self.parseline(line)\n        if not line:\n            return self.emptyline()\n        if cmd is None:\n            return self.default(line)\n        self._complete_info = None\n        self.lastcmd = line\n\n        if cmd == '':\n            return self.default(line)\n        else:\n            do_func = self._get_do_func(cmd)\n            if do_func is None:\n                return self.default(line)\n            try:\n                try:\n                    return do_func(arg)\n                except xmlrpclib.ProtocolError as e:\n                    if e.errcode == 401:\n                        if self.options.interactive:\n                            self.output('Server requires authentication')\n                            username = raw_input('Username:')\n                            password = getpass.getpass(prompt='Password:')\n                            self.output('')\n                            self.options.username = username\n                            self.options.password = password\n                            return self.onecmd(line)\n                        else:\n                            self.output('Server requires authentication')\n                            self.exitstatus = LSBInitExitStatuses.GENERIC\n                    else:\n                        self.exitstatus = LSBInitExitStatuses.GENERIC\n                        raise\n                do_func(arg)\n            except Exception:\n                (file, fun, line), t, v, tbinfo = asyncore.compact_traceback()\n                error = 'error: %s, %s: file: %s line: %s' % (t, v, file, line)\n                self.output(error)\n                self.exitstatus = LSBInitExitStatuses.GENERIC\n\n    def _get_do_func(self, cmd):\n        func_name = 'do_' + cmd\n        func = getattr(self, func_name, None)\n        if not func:\n            for plugin in self.options.plugins:\n                func = getattr(plugin, func_name, None)\n                if func is not None:\n                    break\n        return func\n\n    def output(self, message):\n        if isinstance(message, unicode):\n            message = message.encode('utf-8')\n        self.stdout.write(message + '\\n')\n\n    def get_supervisor(self):\n        return self.get_server_proxy('supervisor')\n\n    def get_server_proxy(self, namespace=None):\n        proxy = self.options.getServerProxy()\n        if namespace is None:\n            return proxy\n        else:\n            return getattr(proxy, namespace)\n\n    def upcheck(self):\n        try:\n            supervisor = self.get_supervisor()\n            api = supervisor.getVersion() # deprecated\n            from supervisor import rpcinterface\n            if api != rpcinterface.API_VERSION:\n                self.output(\n                    'Sorry, this version of supervisorctl expects to '\n                    'talk to a server with API version %s, but the '\n                    'remote version is %s.' % (rpcinterface.API_VERSION, api))\n                self.exitstatus = LSBInitExitStatuses.NOT_INSTALLED\n                return False\n        except xmlrpclib.Fault as e:\n            if e.faultCode == xmlrpc.Faults.UNKNOWN_METHOD:\n                self.output(\n                    'Sorry, supervisord responded but did not recognize '\n                    'the supervisor namespace commands that supervisorctl '\n                    'uses to control it.  Please check that the '\n                    '[rpcinterface:supervisor] section is enabled in the '\n                    'configuration file (see sample.conf).')\n                self.exitstatus = LSBInitExitStatuses.UNIMPLEMENTED_FEATURE\n                return False\n            self.exitstatus = LSBInitExitStatuses.GENERIC\n            raise\n        except socket.error as e:\n            if e.args[0] == errno.ECONNREFUSED:\n                self.output('%s refused connection' % self.options.serverurl)\n                self.exitstatus = LSBInitExitStatuses.INSUFFICIENT_PRIVILEGES\n                return False\n            elif e.args[0] == errno.ENOENT:\n                self.output('%s no such file' % self.options.serverurl)\n                self.exitstatus = LSBInitExitStatuses.NOT_RUNNING\n                return False\n            self.exitstatus = LSBInitExitStatuses.GENERIC\n            raise\n        return True\n\n    def complete(self, text, state, line=None):\n        \"\"\"Completer function that Cmd will register with readline using\n        readline.set_completer().  This function will be called by readline\n        as complete(text, state) where text is a fragment to complete and\n        state is an integer (0..n).  Each call returns a string with a new\n        completion.  When no more are available, None is returned.\"\"\"\n        if line is None: # line is only set in tests\n            import readline\n            line = readline.get_line_buffer()\n\n        matches = []\n        # blank line completes to action list\n        if not line.strip():\n            matches = self._complete_actions(text)\n        else:\n            words = line.split()\n            action = words[0]\n            # incomplete action completes to action list\n            if len(words) == 1 and not line.endswith(' '):\n                matches = self._complete_actions(text)\n            # actions that accept an action name\n            elif action in ('help'):\n                matches = self._complete_actions(text)\n            # actions that accept a group name\n            elif action in ('add', 'remove', 'update'):\n                matches = self._complete_groups(text)\n            # actions that accept a process name\n            elif action in ('clear', 'fg', 'pid', 'restart', 'signal',\n                            'start', 'status', 'stop', 'tail'):\n                matches = self._complete_processes(text)\n        if len(matches) > state:\n            return matches[state]\n\n    def _complete_actions(self, text):\n        \"\"\"Build a completion list of action names matching text\"\"\"\n        return [ a + ' ' for a in self.vocab if a.startswith(text)]\n\n    def _complete_groups(self, text):\n        \"\"\"Build a completion list of group names matching text\"\"\"\n        groups = []\n        for info in self._get_complete_info():\n            if info['group'] not in groups:\n                groups.append(info['group'])\n        return [ g + ' ' for g in groups if g.startswith(text) ]\n\n    def _complete_processes(self, text):\n        \"\"\"Build a completion list of process names matching text\"\"\"\n        processes = []\n        for info in self._get_complete_info():\n            if ':' in text or info['name'] != info['group']:\n                processes.append('%s:%s' % (info['group'], info['name']))\n                if '%s:*' % info['group'] not in processes:\n                    processes.append('%s:*' % info['group'])\n            else:\n                processes.append(info['name'])\n        return [ p + ' ' for p in processes if p.startswith(text) ]\n\n    def _get_complete_info(self):\n        \"\"\"Get all process info used for completion.  We cache this between\n        commands to reduce XML-RPC calls because readline may call\n        complete() many times if the user hits tab only once.\"\"\"\n        if self._complete_info is None:\n            self._complete_info = self.get_supervisor().getAllProcessInfo()\n        return self._complete_info\n\n    def do_help(self, arg):\n        if arg.strip() == 'help':\n            self.help_help()\n        else:\n            for plugin in self.options.plugins:\n                plugin.do_help(arg)\n\n    def help_help(self):\n        self.output(\"help\\t\\tPrint a list of available actions\")\n        self.output(\"help <action>\\tPrint help for <action>\")\n\n    def do_EOF(self, arg):\n        self.output('')\n        return 1\n\n    def help_EOF(self):\n        self.output(\"To quit, type ^D or use the quit command\")\n\ndef get_names(inst):\n    names = []\n    classes = [inst.__class__]\n    while classes:\n        aclass = classes.pop(0)\n        if aclass.__bases__:\n            classes = classes + list(aclass.__bases__)\n        names = names + dir(aclass)\n    return names\n\nclass ControllerPluginBase:\n    name = 'unnamed'\n\n    def __init__(self, controller):\n        self.ctl = controller\n\n    def _doc_header(self):\n        return \"%s commands (type help <topic>):\" % self.name\n    doc_header = property(_doc_header)\n\n    def do_help(self, arg):\n        if arg:\n            # XXX check arg syntax\n            try:\n                func = getattr(self, 'help_' + arg)\n            except AttributeError:\n                try:\n                    doc = getattr(self, 'do_' + arg).__doc__\n                    if doc:\n                        self.ctl.output(doc)\n                        return\n                except AttributeError:\n                    pass\n                self.ctl.output(self.ctl.nohelp % (arg,))\n                return\n            func()\n        else:\n            names = get_names(self)\n            cmds_doc = []\n            cmds_undoc = []\n            help = {}\n            for name in names:\n                if name[:5] == 'help_':\n                    help[name[5:]]=1\n            names.sort()\n            # There can be duplicates if routines overridden\n            prevname = ''\n            for name in names:\n                if name[:3] == 'do_':\n                    if name == prevname:\n                        continue\n                    prevname = name\n                    cmd=name[3:]\n                    if cmd in help:\n                        cmds_doc.append(cmd)\n                        del help[cmd]\n                    elif getattr(self, name).__doc__:\n                        cmds_doc.append(cmd)\n                    else:\n                        cmds_undoc.append(cmd)\n            self.ctl.output('')\n            self.ctl.print_topics(self.doc_header, cmds_doc, 15, 80)\n\ndef not_all_langs():\n    enc = getattr(sys.stdout, 'encoding', None) or ''\n    return None if enc.lower().startswith('utf') else sys.stdout.encoding\n\ndef check_encoding(ctl):\n    problematic_enc = not_all_langs()\n    if problematic_enc:\n        ctl.output('Warning: sys.stdout.encoding is set to %s, so Unicode '\n                   'output may fail. Check your LANG and PYTHONIOENCODING '\n                   'environment settings.' % problematic_enc)\n\nclass DefaultControllerPlugin(ControllerPluginBase):\n    name = 'default'\n    listener = None # for unit tests\n    def _tailf(self, path):\n        check_encoding(self.ctl)\n        self.ctl.output('==> Press Ctrl-C to exit <==')\n\n        username = self.ctl.options.username\n        password = self.ctl.options.password\n        handler = None\n        try:\n            # Python's urllib2 (at least as of Python 2.4.2) isn't up\n            # to this task; it doesn't actually implement a proper\n            # HTTP/1.1 client that deals with chunked responses (it\n            # always sends a Connection: close header).  We use a\n            # homegrown client based on asyncore instead.  This makes\n            # me sad.\n            if self.listener is None:\n                listener = http_client.Listener()\n            else:\n                listener = self.listener # for unit tests\n            handler = http_client.HTTPHandler(listener, username, password)\n            handler.get(self.ctl.options.serverurl, path)\n            asyncore.loop()\n        except KeyboardInterrupt:\n            if handler:\n                handler.close()\n            self.ctl.output('')\n            return\n\n    def do_tail(self, arg):\n        if not self.ctl.upcheck():\n            return\n\n        args = arg.split()\n\n        if len(args) < 1:\n            self.ctl.output('Error: too few arguments')\n            self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n            self.help_tail()\n            return\n\n        elif len(args) > 3:\n            self.ctl.output('Error: too many arguments')\n            self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n            self.help_tail()\n            return\n\n        modifier = None\n\n        if args[0].startswith('-'):\n            modifier = args.pop(0)\n\n        if len(args) == 1:\n            name = args[-1]\n            channel = 'stdout'\n        else:\n            if args:\n                name = args[0]\n                channel = args[-1].lower()\n                if channel not in ('stderr', 'stdout'):\n                    self.ctl.output('Error: bad channel %r' % channel)\n                    self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n                    return\n            else:\n                self.ctl.output('Error: tail requires process name')\n                self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n                return\n\n        bytes = 1600\n\n        if modifier is not None:\n            what = modifier[1:]\n            if what == 'f':\n                bytes = None\n            else:\n                try:\n                    bytes = int(what)\n                except:\n                    self.ctl.output('Error: bad argument %s' % modifier)\n                    self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n                    return\n\n        supervisor = self.ctl.get_supervisor()\n\n        if bytes is None:\n            return self._tailf('/logtail/%s/%s' % (name, channel))\n\n        else:\n            check_encoding(self.ctl)\n            try:\n                if channel == 'stdout':\n                    output = supervisor.readProcessStdoutLog(name,\n                                                             -bytes, 0)\n                else:\n                    output = supervisor.readProcessStderrLog(name,\n                                                             -bytes, 0)\n            except xmlrpclib.Fault as e:\n                self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n                template = '%s: ERROR (%s)'\n                if e.faultCode == xmlrpc.Faults.NO_FILE:\n                    self.ctl.output(template % (name, 'no log file'))\n                elif e.faultCode == xmlrpc.Faults.FAILED:\n                    self.ctl.output(template % (name,\n                                             'unknown error reading log'))\n                elif e.faultCode == xmlrpc.Faults.BAD_NAME:\n                    self.ctl.output(template % (name,\n                                             'no such process name'))\n                else:\n                    raise\n            else:\n                self.ctl.output(output)\n\n    def help_tail(self):\n        self.ctl.output(\n            \"tail [-f] <name> [stdout|stderr] (default stdout)\\n\"\n            \"Ex:\\n\"\n            \"tail -f <name>\\t\\tContinuous tail of named process stdout\\n\"\n            \"\\t\\t\\tCtrl-C to exit.\\n\"\n            \"tail -100 <name>\\tlast 100 *bytes* of process stdout\\n\"\n            \"tail <name> stderr\\tlast 1600 *bytes* of process stderr\"\n            )\n\n    def do_maintail(self, arg):\n        if not self.ctl.upcheck():\n            return\n\n        args = arg.split()\n\n        if len(args) > 1:\n            self.ctl.output('Error: too many arguments')\n            self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n            self.help_maintail()\n            return\n\n        elif len(args) == 1:\n            if args[0].startswith('-'):\n                what = args[0][1:]\n                if what == 'f':\n                    path = '/mainlogtail'\n                    return self._tailf(path)\n                try:\n                    what = int(what)\n                except:\n                    self.ctl.output('Error: bad argument %s' % args[0])\n                    self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n                    return\n                else:\n                    bytes = what\n            else:\n                self.ctl.output('Error: bad argument %s' % args[0])\n                self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n                return\n\n        else:\n            bytes = 1600\n\n        supervisor = self.ctl.get_supervisor()\n\n        try:\n            output = supervisor.readLog(-bytes, 0)\n        except xmlrpclib.Fault as e:\n            self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n            template = '%s: ERROR (%s)'\n            if e.faultCode == xmlrpc.Faults.NO_FILE:\n                self.ctl.output(template % ('supervisord', 'no log file'))\n            elif e.faultCode == xmlrpc.Faults.FAILED:\n                self.ctl.output(template % ('supervisord',\n                                         'unknown error reading log'))\n            else:\n                raise\n        else:\n            self.ctl.output(output)\n\n    def help_maintail(self):\n        self.ctl.output(\n            \"maintail -f \\tContinuous tail of supervisor main log file\"\n            \" (Ctrl-C to exit)\\n\"\n            \"maintail -100\\tlast 100 *bytes* of supervisord main log file\\n\"\n            \"maintail\\tlast 1600 *bytes* of supervisor main log file\\n\"\n            )\n\n    def do_quit(self, arg):\n        return self.ctl.do_EOF(arg)\n\n    def help_quit(self):\n        self.ctl.output(\"quit\\tExit the supervisor shell.\")\n\n    do_exit = do_quit\n\n    def help_exit(self):\n        self.ctl.output(\"exit\\tExit the supervisor shell.\")\n\n    def _show_statuses(self, process_infos):\n        namespecs, maxlen = [], 30\n        for i, info in enumerate(process_infos):\n            namespecs.append(make_namespec(info['group'], info['name']))\n            if len(namespecs[i]) > maxlen:\n                maxlen = len(namespecs[i])\n\n        template = '%(namespec)-' + str(maxlen+3) + 's%(state)-10s%(desc)s'\n        for i, info in enumerate(process_infos):\n            line = template % {'namespec': namespecs[i],\n                               'state': info['statename'],\n                               'desc': info['description']}\n            self.ctl.output(line)\n\n    def do_status(self, arg):\n        # XXX In case upcheck fails, we override the exitstatus which\n        # should only return 4 for do_status\n        # TODO review this\n        if not self.ctl.upcheck():\n            self.ctl.exitstatus = LSBStatusExitStatuses.UNKNOWN\n            return\n\n        supervisor = self.ctl.get_supervisor()\n        all_infos = supervisor.getAllProcessInfo()\n\n        names = as_string(arg).split()\n        if not names or \"all\" in names:\n            matching_infos = all_infos\n        else:\n            matching_infos = []\n\n            for name in names:\n                bad_name = True\n                group_name, process_name = split_namespec(name)\n\n                for info in all_infos:\n                    matched = info['group'] == group_name\n                    if process_name is not None:\n                        matched = matched and info['name'] == process_name\n\n                    if matched:\n                        bad_name = False\n                        matching_infos.append(info)\n\n                if bad_name:\n                    if process_name is None:\n                        msg = \"%s: ERROR (no such group)\" % group_name\n                    else:\n                        msg = \"%s: ERROR (no such process)\" % name\n                    self.ctl.output(msg)\n                    self.ctl.exitstatus = LSBStatusExitStatuses.UNKNOWN\n        self._show_statuses(matching_infos)\n\n        for info in matching_infos:\n            if info['state'] in states.STOPPED_STATES:\n                self.ctl.exitstatus = LSBStatusExitStatuses.NOT_RUNNING\n\n    def help_status(self):\n        self.ctl.output(\"status <name>\\t\\tGet status for a single process\")\n        self.ctl.output(\"status <gname>:*\\tGet status for all \"\n                        \"processes in a group\")\n        self.ctl.output(\"status <name> <name>\\tGet status for multiple named \"\n                        \"processes\")\n        self.ctl.output(\"status\\t\\t\\tGet all process status info\")\n\n    def do_pid(self, arg):\n        supervisor = self.ctl.get_supervisor()\n        if not self.ctl.upcheck():\n            return\n        names = arg.split()\n        if not names:\n            pid = supervisor.getPID()\n            self.ctl.output(str(pid))\n        elif 'all' in names:\n            for info in supervisor.getAllProcessInfo():\n                self.ctl.output(str(info['pid']))\n        else:\n            for name in names:\n                try:\n                    info = supervisor.getProcessInfo(name)\n                except xmlrpclib.Fault as e:\n                    self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n                    if e.faultCode == xmlrpc.Faults.BAD_NAME:\n                        self.ctl.output('No such process %s' % name)\n                    else:\n                        raise\n                else:\n                    pid = info['pid']\n                    self.ctl.output(str(pid))\n                    if pid == 0:\n                        self.ctl.exitstatus = LSBInitExitStatuses.NOT_RUNNING\n\n    def help_pid(self):\n        self.ctl.output(\"pid\\t\\t\\tGet the PID of supervisord.\")\n        self.ctl.output(\"pid <name>\\t\\tGet the PID of a single \"\n            \"child process by name.\")\n        self.ctl.output(\"pid all\\t\\t\\tGet the PID of every child \"\n            \"process, one per line.\")\n\n    def _startresult(self, result):\n        name = make_namespec(result['group'], result['name'])\n        code = result['status']\n        template = '%s: ERROR (%s)'\n        if code == xmlrpc.Faults.BAD_NAME:\n            return template % (name, 'no such process')\n        elif code == xmlrpc.Faults.NO_FILE:\n            return template % (name, 'no such file')\n        elif code == xmlrpc.Faults.NOT_EXECUTABLE:\n            return template % (name, 'file is not executable')\n        elif code == xmlrpc.Faults.ALREADY_STARTED:\n            return template % (name, 'already started')\n        elif code == xmlrpc.Faults.SPAWN_ERROR:\n            return template % (name, 'spawn error')\n        elif code == xmlrpc.Faults.ABNORMAL_TERMINATION:\n            return template % (name, 'abnormal termination')\n        elif code == xmlrpc.Faults.SUCCESS:\n            return '%s: started' % name\n        # assertion\n        raise ValueError('Unknown result code %s for %s' % (code, name))\n\n    def do_start(self, arg):\n        if not self.ctl.upcheck():\n            return\n\n        names = arg.split()\n        supervisor = self.ctl.get_supervisor()\n\n        if not names:\n            self.ctl.output(\"Error: start requires a process name\")\n            self.ctl.exitstatus = LSBInitExitStatuses.INVALID_ARGS\n            self.help_start()\n            return\n\n        if 'all' in names:\n            results = supervisor.startAllProcesses()\n            for result in results:\n                self.ctl.output(self._startresult(result))\n                self.ctl.set_exitstatus_from_xmlrpc_fault(result['status'], xmlrpc.Faults.ALREADY_STARTED)\n        else:\n            for name in names:\n                group_name, process_name = split_namespec(name)\n                if process_name is None:\n                    try:\n                        results = supervisor.startProcessGroup(group_name)\n                        for result in results:\n                            self.ctl.output(self._startresult(result))\n                            self.ctl.set_exitstatus_from_xmlrpc_fault(result['status'], xmlrpc.Faults.ALREADY_STARTED)\n                    except xmlrpclib.Fault as e:\n                        if e.faultCode == xmlrpc.Faults.BAD_NAME:\n                            error = \"%s: ERROR (no such group)\" % group_name\n                            self.ctl.output(error)\n                            self.ctl.exitstatus = LSBInitExitStatuses.INVALID_ARGS\n                        else:\n                            self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n                            raise\n                else:\n                    try:\n                        result = supervisor.startProcess(name)\n                    except xmlrpclib.Fault as e:\n                        error = {'status': e.faultCode,\n                                  'name': process_name,\n                                  'group': group_name,\n                                  'description': e.faultString}\n                        self.ctl.output(self._startresult(error))\n                        self.ctl.set_exitstatus_from_xmlrpc_fault(error['status'], xmlrpc.Faults.ALREADY_STARTED)\n                    else:\n                        name = make_namespec(group_name, process_name)\n                        self.ctl.output('%s: started' % name)\n\n    def help_start(self):\n        self.ctl.output(\"start <name>\\t\\tStart a process\")\n        self.ctl.output(\"start <gname>:*\\t\\tStart all processes in a group\")\n        self.ctl.output(\n            \"start <name> <name>\\tStart multiple processes or groups\")\n        self.ctl.output(\"start all\\t\\tStart all processes\")\n\n    def _signalresult(self, result, success='signalled'):\n        name = make_namespec(result['group'], result['name'])\n        code = result['status']\n        fault_string = result['description']\n        template = '%s: ERROR (%s)'\n        if code == xmlrpc.Faults.BAD_NAME:\n            return template % (name, 'no such process')\n        elif code == xmlrpc.Faults.BAD_SIGNAL:\n            return template % (name, 'bad signal name')\n        elif code == xmlrpc.Faults.NOT_RUNNING:\n            return template % (name, 'not running')\n        elif code == xmlrpc.Faults.SUCCESS:\n            return '%s: %s' % (name, success)\n        elif code == xmlrpc.Faults.FAILED:\n            return fault_string\n        # assertion\n        raise ValueError('Unknown result code %s for %s' % (code, name))\n\n    def _stopresult(self, result):\n        return self._signalresult(result, success='stopped')\n\n    def do_stop(self, arg):\n        if not self.ctl.upcheck():\n            return\n\n        names = arg.split()\n        supervisor = self.ctl.get_supervisor()\n\n        if not names:\n            self.ctl.output('Error: stop requires a process name')\n            self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n            self.help_stop()\n            return\n\n        if 'all' in names:\n            results = supervisor.stopAllProcesses()\n            for result in results:\n                self.ctl.output(self._stopresult(result))\n                self.ctl.set_exitstatus_from_xmlrpc_fault(result['status'], xmlrpc.Faults.NOT_RUNNING)\n        else:\n            for name in names:\n                group_name, process_name = split_namespec(name)\n                if process_name is None:\n                    try:\n                        results = supervisor.stopProcessGroup(group_name)\n\n                        for result in results:\n                            self.ctl.output(self._stopresult(result))\n                            self.ctl.set_exitstatus_from_xmlrpc_fault(result['status'], xmlrpc.Faults.NOT_RUNNING)\n                    except xmlrpclib.Fault as e:\n                        self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n                        if e.faultCode == xmlrpc.Faults.BAD_NAME:\n                            error = \"%s: ERROR (no such group)\" % group_name\n                            self.ctl.output(error)\n                        else:\n                            raise\n                else:\n                    try:\n                        supervisor.stopProcess(name)\n                    except xmlrpclib.Fault as e:\n                        error = {'status': e.faultCode,\n                                 'name': process_name,\n                                 'group': group_name,\n                                 'description':e.faultString}\n                        self.ctl.output(self._stopresult(error))\n                        self.ctl.set_exitstatus_from_xmlrpc_fault(error['status'], xmlrpc.Faults.NOT_RUNNING)\n                    else:\n                        name = make_namespec(group_name, process_name)\n                        self.ctl.output('%s: stopped' % name)\n\n    def help_stop(self):\n        self.ctl.output(\"stop <name>\\t\\tStop a process\")\n        self.ctl.output(\"stop <gname>:*\\t\\tStop all processes in a group\")\n        self.ctl.output(\"stop <name> <name>\\tStop multiple processes or groups\")\n        self.ctl.output(\"stop all\\t\\tStop all processes\")\n\n    def do_signal(self, arg):\n        if not self.ctl.upcheck():\n            return\n\n        args = arg.split()\n        if len(args) < 2:\n            self.ctl.output(\n                'Error: signal requires a signal name and a process name')\n            self.help_signal()\n            self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n            return\n\n        sig = args[0]\n        names = args[1:]\n        supervisor = self.ctl.get_supervisor()\n\n        if 'all' in names:\n            results = supervisor.signalAllProcesses(sig)\n\n            for result in results:\n                self.ctl.output(self._signalresult(result))\n                self.ctl.set_exitstatus_from_xmlrpc_fault(result['status'])\n        else:\n            for name in names:\n                group_name, process_name = split_namespec(name)\n                if process_name is None:\n                    try:\n                        results = supervisor.signalProcessGroup(\n                            group_name, sig\n                            )\n                        for result in results:\n                            self.ctl.output(self._signalresult(result))\n                            self.ctl.set_exitstatus_from_xmlrpc_fault(result['status'])\n                    except xmlrpclib.Fault as e:\n                        if e.faultCode == xmlrpc.Faults.BAD_NAME:\n                            error = \"%s: ERROR (no such group)\" % group_name\n                            self.ctl.output(error)\n                            self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n                        else:\n                            raise\n                else:\n                    try:\n                        supervisor.signalProcess(name, sig)\n                    except xmlrpclib.Fault as e:\n                        error = {'status': e.faultCode,\n                                 'name': process_name,\n                                 'group': group_name,\n                                 'description':e.faultString}\n                        self.ctl.output(self._signalresult(error))\n                        self.ctl.set_exitstatus_from_xmlrpc_fault(error['status'])\n                    else:\n                        name = make_namespec(group_name, process_name)\n                        self.ctl.output('%s: signalled' % name)\n\n    def help_signal(self):\n        self.ctl.output(\"signal <signal name> <name>\\t\\tSignal a process\")\n        self.ctl.output(\"signal <signal name> <gname>:*\\t\\tSignal all processes in a group\")\n        self.ctl.output(\"signal <signal name> <name> <name>\\tSignal multiple processes or groups\")\n        self.ctl.output(\"signal <signal name> all\\t\\tSignal all processes\")\n\n    def do_restart(self, arg):\n        if not self.ctl.upcheck():\n            return\n\n        names = arg.split()\n\n        if not names:\n            self.ctl.output('Error: restart requires a process name')\n            self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n            self.help_restart()\n            return\n\n        self.do_stop(arg)\n        self.do_start(arg)\n\n    def help_restart(self):\n        self.ctl.output(\"restart <name>\\t\\tRestart a process\")\n        self.ctl.output(\"restart <gname>:*\\tRestart all processes in a group\")\n        self.ctl.output(\"restart <name> <name>\\tRestart multiple processes or \"\n                     \"groups\")\n        self.ctl.output(\"restart all\\t\\tRestart all processes\")\n        self.ctl.output(\"Note: restart does not reread config files. For that,\"\n                        \" see reread and update.\")\n\n    def do_shutdown(self, arg):\n        if arg:\n            self.ctl.output('Error: shutdown accepts no arguments')\n            self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n            self.help_shutdown()\n            return\n\n        if self.ctl.options.interactive:\n            yesno = raw_input('Really shut the remote supervisord process '\n                              'down y/N? ')\n            really = yesno.lower().startswith('y')\n        else:\n            really = 1\n\n        if really:\n            supervisor = self.ctl.get_supervisor()\n            try:\n                supervisor.shutdown()\n            except xmlrpclib.Fault as e:\n                if e.faultCode == xmlrpc.Faults.SHUTDOWN_STATE:\n                    self.ctl.output('ERROR: already shutting down')\n                else:\n                    self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n                    raise\n            except socket.error as e:\n                self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n                if e.args[0] == errno.ECONNREFUSED:\n                    msg = 'ERROR: %s refused connection (already shut down?)'\n                    self.ctl.output(msg % self.ctl.options.serverurl)\n                elif e.args[0] == errno.ENOENT:\n                    msg = 'ERROR: %s no such file (already shut down?)'\n                    self.ctl.output(msg % self.ctl.options.serverurl)\n                else:\n                    raise\n            else:\n                self.ctl.output('Shut down')\n\n    def help_shutdown(self):\n        self.ctl.output(\"shutdown \\tShut the remote supervisord down.\")\n\n    def do_reload(self, arg):\n        if arg:\n            self.ctl.output('Error: reload accepts no arguments')\n            self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n            self.help_reload()\n            return\n\n        if self.ctl.options.interactive:\n            yesno = raw_input('Really restart the remote supervisord process '\n                              'y/N? ')\n            really = yesno.lower().startswith('y')\n        else:\n            really = 1\n        if really:\n            supervisor = self.ctl.get_supervisor()\n            try:\n                supervisor.restart()\n            except xmlrpclib.Fault as e:\n                self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n                if e.faultCode == xmlrpc.Faults.SHUTDOWN_STATE:\n                    self.ctl.output('ERROR: already shutting down')\n                else:\n                    raise\n            else:\n                self.ctl.output('Restarted supervisord')\n\n    def help_reload(self):\n        self.ctl.output(\"reload \\t\\tRestart the remote supervisord.\")\n\n    def _formatChanges(self, added_changed_dropped_tuple):\n        added, changed, dropped = added_changed_dropped_tuple\n        changedict = {}\n        for n, t in [(added, 'available'),\n                     (changed, 'changed'),\n                     (dropped, 'disappeared')]:\n            changedict.update(dict(zip(n, [t] * len(n))))\n\n        if changedict:\n            names = list(changedict.keys())\n            names.sort()\n            for name in names:\n                self.ctl.output(\"%s: %s\" % (name, changedict[name]))\n        else:\n            self.ctl.output(\"No config updates to processes\")\n\n    def _formatConfigInfo(self, configinfo):\n        name = make_namespec(configinfo['group'], configinfo['name'])\n        formatted = { 'name': name }\n        if configinfo['inuse']:\n            formatted['inuse'] = 'in use'\n        else:\n            formatted['inuse'] = 'avail'\n        if configinfo['autostart']:\n            formatted['autostart'] = 'auto'\n        else:\n            formatted['autostart'] = 'manual'\n        formatted['priority'] = \"%s:%s\" % (configinfo['group_prio'],\n                                           configinfo['process_prio'])\n\n        template = '%(name)-32s %(inuse)-9s %(autostart)-9s %(priority)s'\n        return template % formatted\n\n    def do_avail(self, arg):\n        if arg:\n            self.ctl.output('Error: avail accepts no arguments')\n            self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n            self.help_avail()\n            return\n\n        supervisor = self.ctl.get_supervisor()\n        try:\n            configinfo = supervisor.getAllConfigInfo()\n        except xmlrpclib.Fault as e:\n            self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n            if e.faultCode == xmlrpc.Faults.SHUTDOWN_STATE:\n                self.ctl.output('ERROR: supervisor shutting down')\n            else:\n                raise\n        else:\n            for pinfo in configinfo:\n                self.ctl.output(self._formatConfigInfo(pinfo))\n\n    def help_avail(self):\n        self.ctl.output(\"avail\\t\\t\\tDisplay all configured processes\")\n\n    def do_reread(self, arg):\n        if arg:\n            self.ctl.output('Error: reread accepts no arguments')\n            self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n            self.help_reread()\n            return\n\n        supervisor = self.ctl.get_supervisor()\n        try:\n            result = supervisor.reloadConfig()\n        except xmlrpclib.Fault as e:\n            self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n            if e.faultCode == xmlrpc.Faults.SHUTDOWN_STATE:\n                self.ctl.output('ERROR: supervisor shutting down')\n            elif e.faultCode == xmlrpc.Faults.CANT_REREAD:\n                self.ctl.output(\"ERROR: %s\" % e.faultString)\n            else:\n                raise\n        else:\n            self._formatChanges(result[0])\n\n    def help_reread(self):\n        self.ctl.output(\"reread \\t\\t\\tReload the daemon's configuration files without add/remove\")\n\n    def do_add(self, arg):\n        names = arg.split()\n\n        supervisor = self.ctl.get_supervisor()\n        for name in names:\n            try:\n                supervisor.addProcessGroup(name)\n            except xmlrpclib.Fault as e:\n                if e.faultCode == xmlrpc.Faults.SHUTDOWN_STATE:\n                    self.ctl.output('ERROR: shutting down')\n                    self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n                elif e.faultCode == xmlrpc.Faults.ALREADY_ADDED:\n                    self.ctl.output('ERROR: process group already active')\n                elif e.faultCode == xmlrpc.Faults.BAD_NAME:\n                    self.ctl.output(\"ERROR: no such process/group: %s\" % name)\n                    self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n                else:\n                    self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n                    raise\n            else:\n                self.ctl.output(\"%s: added process group\" % name)\n\n    def help_add(self):\n        self.ctl.output(\"add <name> [...]\\tActivates any updates in config \"\n                        \"for process/group\")\n\n    def do_remove(self, arg):\n        names = arg.split()\n\n        supervisor = self.ctl.get_supervisor()\n        for name in names:\n            try:\n                supervisor.removeProcessGroup(name)\n            except xmlrpclib.Fault as e:\n                self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n                if e.faultCode == xmlrpc.Faults.STILL_RUNNING:\n                    self.ctl.output('ERROR: process/group still running: %s'\n                                      % name)\n                elif e.faultCode == xmlrpc.Faults.BAD_NAME:\n                    self.ctl.output(\"ERROR: no such process/group: %s\" % name)\n                else:\n                    raise\n            else:\n                self.ctl.output(\"%s: removed process group\" % name)\n\n    def help_remove(self):\n        self.ctl.output(\"remove <name> [...]\\tRemoves process/group from \"\n                        \"active config\")\n\n    def do_update(self, arg):\n        def log(name, message):\n            self.ctl.output(\"%s: %s\" % (name, message))\n\n        supervisor = self.ctl.get_supervisor()\n        try:\n            result = supervisor.reloadConfig()\n        except xmlrpclib.Fault as e:\n            self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n            if e.faultCode == xmlrpc.Faults.SHUTDOWN_STATE:\n                self.ctl.output('ERROR: already shutting down')\n                return\n            else:\n                raise\n\n        added, changed, removed = result[0]\n        valid_gnames = set(arg.split())\n\n        # If all is specified treat it as if nothing was specified.\n        if \"all\" in valid_gnames:\n            valid_gnames = set()\n\n        # If any gnames are specified we need to verify that they are\n        # valid in order to print a useful error message.\n        if valid_gnames:\n            groups = set()\n            for info in supervisor.getAllProcessInfo():\n                groups.add(info['group'])\n            # New gnames would not currently exist in this set so\n            # add those as well.\n            groups.update(added)\n\n            for gname in valid_gnames:\n                if gname not in groups:\n                    self.ctl.output('ERROR: no such group: %s' % gname)\n                    self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n\n        for gname in removed:\n            if valid_gnames and gname not in valid_gnames:\n                continue\n            results = supervisor.stopProcessGroup(gname)\n            log(gname, \"stopped\")\n\n            fails = [res for res in results\n                     if res['status'] == xmlrpc.Faults.FAILED]\n            if fails:\n                self.ctl.output(\"%s: %s\" % (gname, \"has problems; not removing\"))\n                self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n                continue\n            supervisor.removeProcessGroup(gname)\n            log(gname, \"removed process group\")\n\n        for gname in changed:\n            if valid_gnames and gname not in valid_gnames:\n                continue\n            supervisor.stopProcessGroup(gname)\n            log(gname, \"stopped\")\n\n            supervisor.removeProcessGroup(gname)\n            supervisor.addProcessGroup(gname)\n            log(gname, \"updated process group\")\n\n        for gname in added:\n            if valid_gnames and gname not in valid_gnames:\n                continue\n            supervisor.addProcessGroup(gname)\n            log(gname, \"added process group\")\n\n    def help_update(self):\n        self.ctl.output(\"update\\t\\t\\tReload config and add/remove as necessary, and will restart affected programs\")\n        self.ctl.output(\"update all\\t\\tReload config and add/remove as necessary, and will restart affected programs\")\n        self.ctl.output(\"update <gname> [...]\\tUpdate specific groups\")\n\n    def _clearresult(self, result):\n        name = make_namespec(result['group'], result['name'])\n        code = result['status']\n        template = '%s: ERROR (%s)'\n        if code == xmlrpc.Faults.BAD_NAME:\n            return template % (name, 'no such process')\n        elif code == xmlrpc.Faults.FAILED:\n            return template % (name, 'failed')\n        elif code == xmlrpc.Faults.SUCCESS:\n            return '%s: cleared' % name\n        raise ValueError('Unknown result code %s for %s' % (code, name))\n\n    def do_clear(self, arg):\n        if not self.ctl.upcheck():\n            return\n\n        names = arg.split()\n\n        if not names:\n            self.ctl.output('Error: clear requires a process name')\n            self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n            self.help_clear()\n            return\n\n        supervisor = self.ctl.get_supervisor()\n\n        if 'all' in names:\n            results = supervisor.clearAllProcessLogs()\n            for result in results:\n                self.ctl.output(self._clearresult(result))\n                self.ctl.set_exitstatus_from_xmlrpc_fault(result['status'])\n        else:\n            for name in names:\n                group_name, process_name = split_namespec(name)\n                try:\n                    supervisor.clearProcessLogs(name)\n                except xmlrpclib.Fault as e:\n                    error = {'status': e.faultCode,\n                             'name': process_name,\n                             'group': group_name,\n                             'description': e.faultString}\n                    self.ctl.output(self._clearresult(error))\n                    self.ctl.set_exitstatus_from_xmlrpc_fault(error['status'])\n                else:\n                    name = make_namespec(group_name, process_name)\n                    self.ctl.output('%s: cleared' % name)\n\n    def help_clear(self):\n        self.ctl.output(\"clear <name>\\t\\tClear a process' log files.\")\n        self.ctl.output(\n            \"clear <name> <name>\\tClear multiple process' log files\")\n        self.ctl.output(\"clear all\\t\\tClear all process' log files\")\n\n    def do_open(self, arg):\n        url = arg.strip()\n        parts = urlparse.urlparse(url)\n        if parts[0] not in ('unix', 'http'):\n            self.ctl.output('ERROR: url must be http:// or unix://')\n            self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n            return\n        self.ctl.options.serverurl = url\n        # TODO review this\n        old_exitstatus = self.ctl.exitstatus\n        self.do_status('')\n        self.ctl.exitstatus = old_exitstatus\n\n    def help_open(self):\n        self.ctl.output(\"open <url>\\tConnect to a remote supervisord process.\")\n        self.ctl.output(\"\\t\\t(for UNIX domain socket, use unix:///socket/path)\")\n\n    def do_version(self, arg):\n        if arg:\n            self.ctl.output('Error: version accepts no arguments')\n            self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n            self.help_version()\n            return\n\n        if not self.ctl.upcheck():\n            return\n        supervisor = self.ctl.get_supervisor()\n        self.ctl.output(supervisor.getSupervisorVersion())\n\n    def help_version(self):\n        self.ctl.output(\n            \"version\\t\\t\\tShow the version of the remote supervisord \"\n            \"process\")\n\n    def do_fg(self, arg):\n        if not self.ctl.upcheck():\n            return\n\n        names = arg.split()\n        if not names:\n            self.ctl.output('ERROR: no process name supplied')\n            self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n            self.help_fg()\n            return\n        if len(names) > 1:\n            self.ctl.output('ERROR: too many process names supplied')\n            self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n            return\n\n        name = names[0]\n        supervisor = self.ctl.get_supervisor()\n\n        try:\n            info = supervisor.getProcessInfo(name)\n        except xmlrpclib.Fault as e:\n            if e.faultCode == xmlrpc.Faults.BAD_NAME:\n                self.ctl.output('ERROR: bad process name supplied')\n                self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n            else:\n                self.ctl.output('ERROR: ' + str(e))\n            return\n\n        if info['state'] != states.ProcessStates.RUNNING:\n            self.ctl.output('ERROR: process not running')\n            self.ctl.exitstatus = LSBInitExitStatuses.GENERIC\n            return\n\n        self.ctl.output('==> Press Ctrl-C to exit <==')\n\n        a = None\n        try:\n            # this thread takes care of the output/error messages\n            a = fgthread(name, self.ctl)\n            a.start()\n\n            # this takes care of the user input\n            while True:\n                inp = raw_input() + '\\n'\n                try:\n                    supervisor.sendProcessStdin(name, inp)\n                except xmlrpclib.Fault as e:\n                    if e.faultCode == xmlrpc.Faults.NOT_RUNNING:\n                        self.ctl.output('Process got killed')\n                    else:\n                        self.ctl.output('ERROR: ' + str(e))\n                    self.ctl.output('Exiting foreground')\n                    a.kill()\n                    return\n\n                info = supervisor.getProcessInfo(name)\n                if info['state'] != states.ProcessStates.RUNNING:\n                    self.ctl.output('Process got killed')\n                    self.ctl.output('Exiting foreground')\n                    a.kill()\n                    return\n        except (KeyboardInterrupt, EOFError):\n            self.ctl.output('Exiting foreground')\n            if a:\n                a.kill()\n\n    def help_fg(self,args=None):\n        self.ctl.output('fg <process>\\tConnect to a process in foreground mode')\n        self.ctl.output(\"\\t\\tCtrl-C to exit\")\n\n\ndef main(args=None, options=None):\n    if options is None:\n        options = ClientOptions()\n\n    options.realize(args, doc=__doc__)\n    c = Controller(options)\n\n    if options.args:\n        c.onecmd(\" \".join(options.args))\n        sys.exit(c.exitstatus)\n\n    if options.interactive:\n        c.exec_cmdloop(args, options)\n        sys.exit(0)  # exitstatus always 0 for interactive mode\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "supervisor/supervisord.py",
    "content": "#!/usr/bin/env python\n\n\"\"\"supervisord -- run a set of applications as daemons.\n\nUsage: %s [options]\n\nOptions:\n-c/--configuration FILENAME -- configuration file path (searches if not given)\n-n/--nodaemon -- run in the foreground (same as 'nodaemon=true' in config file)\n-s/--silent -- no logs to stdout (maps to 'silent=true' in config file)\n-h/--help -- print this usage message and exit\n-v/--version -- print supervisord version number and exit\n-u/--user USER -- run supervisord as this user (or numeric uid)\n-m/--umask UMASK -- use this umask for daemon subprocess (default is 022)\n-d/--directory DIRECTORY -- directory to chdir to when daemonized\n-l/--logfile FILENAME -- use FILENAME as logfile path\n-y/--logfile_maxbytes BYTES -- use BYTES to limit the max size of logfile\n-z/--logfile_backups NUM -- number of backups to keep when max bytes reached\n-e/--loglevel LEVEL -- use LEVEL as log level (debug,info,warn,error,critical)\n-j/--pidfile FILENAME -- write a pid file for the daemon process to FILENAME\n-i/--identifier STR -- identifier used for this instance of supervisord\n-q/--childlogdir DIRECTORY -- the log directory for child process logs\n-k/--nocleanup --  prevent the process from performing cleanup (removal of\n                   old automatic child log files) at startup.\n-a/--minfds NUM -- the minimum number of file descriptors for start success\n-t/--strip_ansi -- strip ansi escape codes from process output\n--minprocs NUM  -- the minimum number of processes available for start success\n--profile_options OPTIONS -- run supervisord under profiler and output\n                             results based on OPTIONS, which  is a comma-sep'd\n                             list of 'cumulative', 'calls', and/or 'callers',\n                             e.g. 'cumulative,callers')\n\"\"\"\n\nimport os\nimport time\nimport signal\n\nfrom supervisor.medusa import asyncore_25 as asyncore\n\nfrom supervisor.compat import as_string\nfrom supervisor.options import ServerOptions\nfrom supervisor.options import decode_wait_status\nfrom supervisor.options import signame\nfrom supervisor import events\nfrom supervisor.states import SupervisorStates\nfrom supervisor.states import getProcessStateDescription\n\nclass Supervisor:\n    stopping = False # set after we detect that we are handling a stop request\n    lastshutdownreport = 0 # throttle for delayed process error reports at stop\n    process_groups = None # map of process group name to process group object\n    stop_groups = None # list used for priority ordered shutdown\n\n    def __init__(self, options):\n        self.options = options\n        self.process_groups = {}\n        self.ticks = {}\n\n    def main(self):\n        if not self.options.first:\n            # prevent crash on libdispatch-based systems, at least for the\n            # first request\n            self.options.cleanup_fds()\n\n        self.options.set_uid_or_exit()\n\n        if self.options.first:\n            self.options.set_rlimits_or_exit()\n\n        # this sets the options.logger object\n        # delay logger instantiation until after setuid\n        self.options.make_logger()\n\n        if not self.options.nocleanup:\n            # clean up old automatic logs\n            self.options.clear_autochildlogdir()\n\n        self.run()\n\n    def run(self):\n        self.process_groups = {} # clear\n        self.stop_groups = None # clear\n        events.clear()\n        try:\n            for config in self.options.process_group_configs:\n                self.add_process_group(config)\n            self.options.openhttpservers(self)\n            self.options.setsignals()\n            if (not self.options.nodaemon) and self.options.first:\n                self.options.daemonize()\n            # writing pid file needs to come *after* daemonizing or pid\n            # will be wrong\n            self.options.write_pidfile()\n            self.runforever()\n        finally:\n            self.options.cleanup()\n\n    def diff_to_active(self):\n        new = self.options.process_group_configs\n        cur = [group.config for group in self.process_groups.values()]\n\n        curdict = dict(zip([cfg.name for cfg in cur], cur))\n        newdict = dict(zip([cfg.name for cfg in new], new))\n\n        added   = [cand for cand in new if cand.name not in curdict]\n        removed = [cand for cand in cur if cand.name not in newdict]\n\n        changed = [cand for cand in new\n                   if cand != curdict.get(cand.name, cand)]\n\n        return added, changed, removed\n\n    def add_process_group(self, config):\n        name = config.name\n        if name not in self.process_groups:\n            config.after_setuid()\n            self.process_groups[name] = config.make_group()\n            events.notify(events.ProcessGroupAddedEvent(name))\n            return True\n        return False\n\n    def remove_process_group(self, name):\n        if self.process_groups[name].get_unstopped_processes():\n            return False\n        self.process_groups[name].before_remove()\n        del self.process_groups[name]\n        events.notify(events.ProcessGroupRemovedEvent(name))\n        return True\n\n    def get_process_map(self):\n        process_map = {}\n        for group in self.process_groups.values():\n            process_map.update(group.get_dispatchers())\n        return process_map\n\n    def shutdown_report(self):\n        unstopped = []\n\n        for group in self.process_groups.values():\n            unstopped.extend(group.get_unstopped_processes())\n\n        if unstopped:\n            # throttle 'waiting for x to die' reports\n            now = time.time()\n            if now > (self.lastshutdownreport + 3): # every 3 secs\n                names = [ as_string(p.config.name) for p in unstopped ]\n                namestr = ', '.join(names)\n                self.options.logger.info('waiting for %s to die' % namestr)\n                self.lastshutdownreport = now\n                for proc in unstopped:\n                    state = getProcessStateDescription(proc.get_state())\n                    self.options.logger.blather(\n                        '%s state: %s' % (proc.config.name, state))\n        return unstopped\n\n    def ordered_stop_groups_phase_1(self):\n        if self.stop_groups:\n            # stop the last group (the one with the \"highest\" priority)\n            self.stop_groups[-1].stop_all()\n\n    def ordered_stop_groups_phase_2(self):\n        # after phase 1 we've transitioned and reaped, let's see if we\n        # can remove the group we stopped from the stop_groups queue.\n        if self.stop_groups:\n            # pop the last group (the one with the \"highest\" priority)\n            group = self.stop_groups.pop()\n            if group.get_unstopped_processes():\n                # if any processes in the group aren't yet in a\n                # stopped state, we're not yet done shutting this\n                # group down, so push it back on to the end of the\n                # stop group queue\n                self.stop_groups.append(group)\n\n    def runforever(self):\n        events.notify(events.SupervisorRunningEvent())\n        timeout = 1 # this cannot be fewer than the smallest TickEvent (5)\n        first_poll = True\n\n        socket_map = self.options.get_socket_map()\n\n        while 1:\n            combined_map = {}\n            combined_map.update(socket_map)\n            combined_map.update(self.get_process_map())\n\n            pgroups = list(self.process_groups.values())\n            pgroups.sort()\n\n            if self.options.mood < SupervisorStates.RUNNING:\n                if not self.stopping:\n                    # first time, set the stopping flag, do a\n                    # notification and set stop_groups\n                    self.stopping = True\n                    self.stop_groups = pgroups[:]\n                    events.notify(events.SupervisorStoppingEvent())\n\n                self.ordered_stop_groups_phase_1()\n\n                if not self.shutdown_report():\n                    # if there are no unstopped processes (we're done\n                    # killing everything), it's OK to shutdown or reload\n                    raise asyncore.ExitNow\n\n            for fd, dispatcher in combined_map.items():\n                if dispatcher.readable():\n                    self.options.poller.register_readable(fd)\n                if dispatcher.writable():\n                    self.options.poller.register_writable(fd)\n\n            if first_poll:\n                # initial timeout of 0 avoids delaying supervisord startup\n                r, w = self.options.poller.poll(0)\n                first_poll = False\n            else:\n                r, w = self.options.poller.poll(timeout)\n\n            for fd in r:\n                if fd in combined_map:\n                    try:\n                        dispatcher = combined_map[fd]\n                        self.options.logger.blather(\n                            'read event caused by %(dispatcher)r',\n                            dispatcher=dispatcher)\n                        dispatcher.handle_read_event()\n                        if not dispatcher.readable():\n                            self.options.poller.unregister_readable(fd)\n                    except asyncore.ExitNow:\n                        raise\n                    except:\n                        combined_map[fd].handle_error()\n                else:\n                    # if the fd is not in combined_map, we should unregister it. otherwise,\n                    # it will be polled every time, which may cause 100% cpu usage\n                    self.options.logger.blather('unexpected read event from fd %r' % fd)\n                    try:\n                        self.options.poller.unregister_readable(fd)\n                    except:\n                        pass\n\n            for fd in w:\n                if fd in combined_map:\n                    try:\n                        dispatcher = combined_map[fd]\n                        self.options.logger.blather(\n                            'write event caused by %(dispatcher)r',\n                            dispatcher=dispatcher)\n                        dispatcher.handle_write_event()\n                        if not dispatcher.writable():\n                            self.options.poller.unregister_writable(fd)\n                    except asyncore.ExitNow:\n                        raise\n                    except:\n                        combined_map[fd].handle_error()\n                else:\n                    self.options.logger.blather('unexpected write event from fd %r' % fd)\n                    try:\n                        self.options.poller.unregister_writable(fd)\n                    except:\n                        pass\n\n            for group in pgroups:\n                group.transition()\n\n            self.reap()\n            self.handle_signal()\n            self.tick()\n\n            if self.options.mood < SupervisorStates.RUNNING:\n                self.ordered_stop_groups_phase_2()\n\n            if self.options.test:\n                break\n\n    def tick(self, now=None):\n        \"\"\" Send one or more 'tick' events when the timeslice related to\n        the period for the event type rolls over \"\"\"\n        if now is None:\n            # now won't be None in unit tests\n            now = time.time()\n        for event in events.TICK_EVENTS:\n            period = event.period\n            last_tick = self.ticks.get(period)\n            if last_tick is None:\n                # we just started up\n                last_tick = self.ticks[period] = timeslice(period, now)\n            this_tick = timeslice(period, now)\n            if this_tick != last_tick:\n                self.ticks[period] = this_tick\n                events.notify(event(this_tick, self))\n\n    def reap(self, once=False, recursionguard=0):\n        if recursionguard == 100:\n            return\n        pid, sts = self.options.waitpid()\n        if pid:\n            process = self.options.pidhistory.get(pid, None)\n            if process is None:\n                _, msg = decode_wait_status(sts)\n                self.options.logger.info('reaped unknown pid %s (%s)' % (pid, msg))\n            else:\n                process.finish(pid, sts)\n                del self.options.pidhistory[pid]\n            if not once:\n                # keep reaping until no more kids to reap, but don't recurse\n                # infinitely\n                self.reap(once=False, recursionguard=recursionguard+1)\n\n    def handle_signal(self):\n        sig = self.options.get_signal()\n        if sig:\n            if sig in (signal.SIGTERM, signal.SIGINT, signal.SIGQUIT):\n                self.options.logger.warn(\n                    'received %s indicating exit request' % signame(sig))\n                self.options.mood = SupervisorStates.SHUTDOWN\n            elif sig == signal.SIGHUP:\n                if self.options.mood == SupervisorStates.SHUTDOWN:\n                    self.options.logger.warn(\n                        'ignored %s indicating restart request (shutdown in progress)' % signame(sig))\n                else:\n                    self.options.logger.warn(\n                        'received %s indicating restart request' % signame(sig))\n                    self.options.mood = SupervisorStates.RESTARTING\n            elif sig == signal.SIGCHLD:\n                self.options.logger.debug(\n                    'received %s indicating a child quit' % signame(sig))\n            elif sig == signal.SIGUSR2:\n                self.options.logger.info(\n                    'received %s indicating log reopen request' % signame(sig))\n                self.options.reopenlogs()\n                for group in self.process_groups.values():\n                    group.reopenlogs()\n            else:\n                self.options.logger.blather(\n                    'received %s indicating nothing' % signame(sig))\n\n    def get_state(self):\n        return self.options.mood\n\ndef timeslice(period, when):\n    return int(when - (when % period))\n\n# profile entry point\ndef profile(cmd, globals, locals, sort_order, callers): # pragma: no cover\n    try:\n        import cProfile as profile\n    except ImportError:\n        import profile\n    import pstats\n    import tempfile\n    fd, fn = tempfile.mkstemp()\n    try:\n        profile.runctx(cmd, globals, locals, fn)\n        stats = pstats.Stats(fn)\n        stats.strip_dirs()\n        # calls,time,cumulative and cumulative,calls,time are useful\n        stats.sort_stats(*sort_order or ('cumulative', 'calls', 'time'))\n        if callers:\n            stats.print_callers(.3)\n        else:\n            stats.print_stats(.3)\n    finally:\n        os.remove(fn)\n\n\n# Main program\ndef main(args=None, test=False):\n    assert os.name == \"posix\", \"This code makes Unix-specific assumptions\"\n    # if we hup, restart by making a new Supervisor()\n    first = True\n    while 1:\n        options = ServerOptions()\n        options.realize(args, doc=__doc__)\n        options.first = first\n        options.test = test\n        if options.profile_options:\n            sort_order, callers = options.profile_options\n            profile('go(options)', globals(), locals(), sort_order, callers)\n        else:\n            go(options)\n        options.close_httpservers()\n        options.close_logger()\n        first = False\n        if test or (options.mood < SupervisorStates.RESTARTING):\n            break\n\ndef go(options): # pragma: no cover\n    d = Supervisor(options)\n    try:\n        d.main()\n    except asyncore.ExitNow:\n        pass\n\nif __name__ == \"__main__\": # pragma: no cover\n    main()\n"
  },
  {
    "path": "supervisor/templating.py",
    "content": "# This file was originally based on the meld3 package version 2.0.0\n# (https://pypi.org/project/meld3/2.0.0/).  The meld3 package is not\n# called out separately in Supervisor's license or copyright files\n# because meld3 had the same authors, copyright, and license as\n# Supervisor at the time this file was bundled with Supervisor.\n\nimport email\nimport re\n\nfrom xml.etree.ElementTree import (\n    Comment,\n    ElementPath,\n    ProcessingInstruction,\n    QName,\n    TreeBuilder,\n    XMLParser,\n    parse as et_parse\n    )\n\nfrom supervisor.compat import (\n    PY2,\n    htmlentitydefs,\n    HTMLParser,\n    StringIO,\n    StringTypes,\n    unichr,\n    as_bytes,\n    as_string,\n    )\n\nAUTOCLOSE = \"p\", \"li\", \"tr\", \"th\", \"td\", \"head\", \"body\"\nIGNOREEND = \"img\", \"hr\", \"meta\", \"link\", \"br\"\n_BLANK = as_bytes('', encoding='latin1')\n_SPACE = as_bytes(' ', encoding='latin1')\n_EQUAL = as_bytes('=', encoding='latin1')\n_QUOTE = as_bytes('\"', encoding='latin1')\n_OPEN_TAG_START = as_bytes(\"<\", encoding='latin1')\n_CLOSE_TAG_START = as_bytes(\"</\", encoding='latin1')\n_OPEN_TAG_END = _CLOSE_TAG_END = as_bytes(\">\", encoding='latin1')\n_SELF_CLOSE = as_bytes(\" />\", encoding='latin1')\n_OMITTED_TEXT = as_bytes(' [...]\\n', encoding='latin1')\n_COMMENT_START = as_bytes('<!-- ', encoding='latin1')\n_COMMENT_END = as_bytes(' -->', encoding='latin1')\n_PI_START = as_bytes('<?', encoding='latin1')\n_PI_END = as_bytes('?>', encoding='latin1')\n_AMPER_ESCAPED = as_bytes('&amp;', encoding='latin1')\n_LT = as_bytes('<', encoding='latin1')\n_LT_ESCAPED = as_bytes('&lt;', encoding='latin1')\n_QUOTE_ESCAPED = as_bytes(\"&quot;\", encoding='latin1')\n_XML_PROLOG_BEGIN = as_bytes('<?xml version=\"1.0\"', encoding='latin1')\n_ENCODING = as_bytes('encoding', encoding='latin1')\n_XML_PROLOG_END = as_bytes('?>\\n', encoding='latin1')\n_DOCTYPE_BEGIN = as_bytes('<!DOCTYPE', encoding='latin1')\n_PUBLIC = as_bytes('PUBLIC', encoding='latin1')\n_DOCTYPE_END = as_bytes('>\\n', encoding='latin1')\n\nif PY2:\n    def encode(text, encoding):\n        return text.encode(encoding)\nelse:\n    def encode(text, encoding):\n        if not isinstance(text, bytes):\n            text = text.encode(encoding)\n        return text\n\n# replace element factory\ndef Replace(text, structure=False):\n    element = _MeldElementInterface(Replace, {})\n    element.text = text\n    element.structure = structure\n    return element\n\nclass PyHelper:\n    def findmeld(self, node, name, default=None):\n        iterator = self.getiterator(node)\n        for element in iterator:\n            val = element.attrib.get(_MELD_ID)\n            if val == name:\n                return element\n        return default\n\n    def clone(self, node, parent=None):\n        element = _MeldElementInterface(node.tag, node.attrib.copy())\n        element.text = node.text\n        element.tail = node.tail\n        element.structure = node.structure\n        if parent is not None:\n            # avoid calling self.append to reduce function call overhead\n            parent._children.append(element)\n            element.parent = parent\n        for child in node._children:\n            self.clone(child, element)\n        return element\n\n    def _bfclone(self, nodes, parent):\n        L = []\n        for node in nodes:\n            element = _MeldElementInterface(node.tag, node.attrib.copy())\n            element.parent = parent\n            element.text = node.text\n            element.tail = node.tail\n            element.structure = node.structure\n            if node._children:\n                self._bfclone(node._children, element)\n            L.append(element)\n        parent._children = L\n\n    def bfclone(self, node, parent=None):\n        element = _MeldElementInterface(node.tag, node.attrib.copy())\n        element.text = node.text\n        element.tail = node.tail\n        element.structure = node.structure\n        element.parent = parent\n        if parent is not None:\n            parent._children.append(element)\n        if node._children:\n            self._bfclone(node._children, element)\n        return element\n\n    def getiterator(self, node, tag=None):\n        nodes = []\n        if tag == \"*\":\n            tag = None\n        if tag is None or node.tag == tag:\n            nodes.append(node)\n        for element in node._children:\n            nodes.extend(self.getiterator(element, tag))\n        return nodes\n\n    def content(self, node, text, structure=False):\n        node.text = None\n        replacenode = Replace(text, structure)\n        replacenode.parent = node\n        replacenode.text = text\n        replacenode.structure = structure\n        node._children = [replacenode]\n\nhelper = PyHelper()\n\n_MELD_NS_URL  = 'https://github.com/Supervisor/supervisor'\n_MELD_PREFIX  = '{%s}' % _MELD_NS_URL\n_MELD_LOCAL   = 'id'\n_MELD_ID      = '%s%s' % (_MELD_PREFIX, _MELD_LOCAL)\n_MELD_SHORT_ID = 'meld:%s' % _MELD_LOCAL\n_XHTML_NS_URL = 'http://www.w3.org/1999/xhtml'\n_XHTML_PREFIX = '{%s}' % _XHTML_NS_URL\n_XHTML_PREFIX_LEN = len(_XHTML_PREFIX)\n\n\n_marker = []\n\nclass doctype:\n    # lookup table for ease of use in external code\n    html_strict  = ('HTML', '-//W3C//DTD HTML 4.01//EN',\n                    'http://www.w3.org/TR/html4/strict.dtd')\n    html         = ('HTML', '-//W3C//DTD HTML 4.01 Transitional//EN',\n                   'http://www.w3.org/TR/html4/loose.dtd')\n    xhtml_strict = ('html', '-//W3C//DTD XHTML 1.0 Strict//EN',\n                    'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd')\n    xhtml        = ('html', '-//W3C//DTD XHTML 1.0 Transitional//EN',\n                    'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd')\n\nclass _MeldElementInterface:\n    parent = None\n    attrib = None\n    text   = None\n    tail   = None\n    structure = None\n\n    # overrides to reduce MRU lookups\n    def __init__(self, tag, attrib):\n        self.tag = tag\n        self.attrib = attrib\n        self._children = []\n\n    def __repr__(self):\n        return \"<MeldElement %s at %x>\" % (self.tag, id(self))\n\n    def __len__(self):\n        return len(self._children)\n\n    def __getitem__(self, index):\n        return self._children[index]\n\n    def __getslice__(self, start, stop):\n        return self._children[start:stop]\n\n    def getchildren(self):\n        return self._children\n\n    def find(self, path):\n        return ElementPath.find(self, path)\n\n    def findtext(self, path, default=None):\n        return ElementPath.findtext(self, path, default)\n\n    def findall(self, path):\n        return ElementPath.findall(self, path)\n\n    def clear(self):\n        self.attrib.clear()\n        self._children = []\n        self.text = self.tail = None\n\n    def get(self, key, default=None):\n        return self.attrib.get(key, default)\n\n    def set(self, key, value):\n        self.attrib[key] = value\n\n    def keys(self):\n        return list(self.attrib.keys())\n\n    def items(self):\n        return list(self.attrib.items())\n\n    def getiterator(self, *ignored_args, **ignored_kw):\n        # we ignore any tag= passed in to us, originally because it was too\n        # painfail to support in the old C extension, now for b/w compat\n        return helper.getiterator(self)\n\n    # overrides to support parent pointers and factories\n\n    def __setitem__(self, index, element):\n        if isinstance(index, slice):\n            for e in element:\n                e.parent = self\n        else:\n            element.parent = self\n\n        self._children[index] = element\n\n    # TODO: Can __setslice__ be removed now?\n    def __setslice__(self, start, stop, elements):\n        for element in elements:\n            element.parent = self\n        self._children[start:stop] = list(elements)\n\n    def append(self, element):\n        self._children.append(element)\n        element.parent = self\n\n    def insert(self, index, element):\n        self._children.insert(index, element)\n        element.parent = self\n\n    def __delitem__(self, index):\n        if isinstance(index, slice):\n            for ob in self._children[index]:\n                ob.parent = None\n        else:\n            self._children[index].parent = None\n\n        ob = self._children[index]\n        del self._children[index]\n\n    # TODO: Can __delslice__ be removed now?\n    def __delslice__(self, start, stop):\n        obs = self._children[start:stop]\n        for ob in obs:\n            ob.parent = None\n        del self._children[start:stop]\n\n    def remove(self, element):\n        self._children.remove(element)\n        element.parent = None\n\n    def makeelement(self, tag, attrib):\n        return self.__class__(tag, attrib)\n\n    # meld-specific\n\n    def __mod__(self, other):\n        \"\"\" Fill in the text values of meld nodes in tree; only\n        support dictionarylike operand (sequence operand doesn't seem\n        to make sense here)\"\"\"\n        return self.fillmelds(**other)\n\n    def fillmelds(self, **kw):\n        \"\"\" Fill in the text values of meld nodes in tree using the\n        keyword arguments passed in; use the keyword keys as meld ids\n        and the keyword values as text that should fill in the node\n        text on which that meld id is found.  Return a list of keys\n        from **kw that were not able to be found anywhere in the tree.\n        Never raises an exception. \"\"\"\n        unfilled = []\n        for k in kw:\n            node = self.findmeld(k)\n            if node is None:\n                unfilled.append(k)\n            else:\n                node.text = kw[k]\n        return unfilled\n\n    def fillmeldhtmlform(self, **kw):\n        \"\"\" Perform magic to 'fill in' HTML form element values from a\n        dictionary.  Unlike 'fillmelds', the type of element being\n        'filled' is taken into consideration.\n\n        Perform a 'findmeld' on each key in the dictionary and use the\n        value that corresponds to the key to perform mutation of the\n        tree, changing data in what is presumed to be one or more HTML\n        form elements according to the following rules::\n\n          If the found element is an 'input group' (its meld id ends\n          with the string ':inputgroup'), set the 'checked' attribute\n          on the appropriate subelement which has a 'value' attribute\n          which matches the dictionary value.  Also remove the\n          'checked' attribute from every other 'input' subelement of\n          the input group.  If no input subelement's value matches the\n          dictionary value, this key is treated as 'unfilled'.\n\n          If the found element is an 'input type=text', 'input\n          type=hidden', 'input type=submit', 'input type=password',\n          'input type=reset' or 'input type=file' element, replace its\n          'value' attribute with the value.\n\n          If the found element is an 'input type=checkbox' or 'input\n          type='radio' element, set its 'checked' attribute to true if\n          the dict value is true, or remove its 'checked' attribute if\n          the dict value is false.\n\n          If the found element is a 'select' element and the value\n          exists in the 'value=' attribute of one of its 'option'\n          subelements, change that option's 'selected' attribute to\n          true and mark all other option elements as unselected.  If\n          the select element does not contain an option with a value\n          that matches the dictionary value, do nothing and return\n          this key as unfilled.\n\n          If the found element is a 'textarea' or any other kind of\n          element, replace its text with the value.\n\n          If the element corresponding to the key is not found,\n          do nothing and treat the key as 'unfilled'.\n\n        Return a list of 'unfilled' keys, representing meld ids\n        present in the dictionary but not present in the element tree\n        or meld ids which could not be filled due to the lack of any\n        matching subelements for 'select' nodes or 'inputgroup' nodes.\n        \"\"\"\n\n        unfilled = []\n\n        for k in kw:\n            node = self.findmeld(k)\n\n            if node is None:\n                unfilled.append(k)\n                continue\n\n            val = kw[k]\n\n            if k.endswith(':inputgroup'):\n                # an input group is a list of input type=\"checkbox\" or\n                # input type=\"radio\" elements that can be treated as a group\n                # because they attempt to specify the same value\n\n                found = []\n                unfound = []\n\n                for child in node.findall('input'):\n                    input_type = child.attrib.get('type', '').lower()\n                    if input_type not in ('checkbox', 'radio'):\n                        continue\n\n                    input_val = child.attrib.get('value', '')\n\n                    if val == input_val:\n                        found.append(child)\n                    else:\n                        unfound.append(child)\n\n                if not found:\n                    unfilled.append(k)\n\n                else:\n                    for option in found:\n                        option.attrib['checked'] = 'checked'\n                    for option in unfound:\n                        try:\n                            del option.attrib['checked']\n                        except KeyError:\n                            pass\n            else:\n\n                tag = node.tag.lower()\n\n                if tag == 'input':\n\n                    input_type = node.attrib.get('type', 'text').lower()\n\n                    # fill in value attrib for most input types\n                    if input_type in ('hidden', 'submit', 'text',\n                                      'password', 'reset', 'file'):\n                        node.attrib['value'] = val\n\n                    # unless it's a checkbox or radio attribute, then we\n                    # fill in its checked attribute\n                    elif input_type in ('checkbox', 'radio'):\n                        if val:\n                            node.attrib['checked'] = 'checked'\n                        else:\n                            try:\n                                del node.attrib['checked']\n                            except KeyError:\n                                pass\n                    else:\n\n                        unfilled.append(k)\n\n                elif tag == 'select':\n                    # if the node is a select node, we want to select\n                    # the value matching val, otherwise it's unfilled\n\n                    found = []\n                    unfound = []\n\n                    for option in node.findall('option'):\n                        if option.attrib.get('value', '') == val:\n                            found.append(option)\n                        else:\n                            unfound.append(option)\n                    if not found:\n                        unfilled.append(k)\n                    else:\n                        for option in found:\n                            option.attrib['selected'] = 'selected'\n                        for option in unfound:\n                            try:\n                                del option.attrib['selected']\n                            except KeyError:\n                                pass\n                else:\n                    node.text = kw[k]\n\n        return unfilled\n\n    def findmeld(self, name, default=None):\n        \"\"\" Find a node in the tree that has a 'meld id' corresponding\n        to 'name'. Iterate over all subnodes recursively looking for a\n        node which matches.  If we can't find the node, return None.\"\"\"\n        # this could be faster if we indexed all the meld nodes in the\n        # tree; we just walk the whole hierarchy now.\n        result = helper.findmeld(self, name)\n        if result is None:\n            return default\n        return result\n\n    def findmelds(self):\n        \"\"\" Find all nodes that have a meld id attribute and return\n        the found nodes in a list\"\"\"\n        return self.findwithattrib(_MELD_ID)\n\n    def findwithattrib(self, attrib, value=None):\n        \"\"\" Find all nodes that have an attribute named 'attrib'.  If\n        'value' is not None, omit nodes on which the attribute value\n        does not compare equally to 'value'. Return the found nodes in\n        a list.\"\"\"\n        iterator = helper.getiterator(self)\n        elements = []\n        for element in iterator:\n            attribval = element.attrib.get(attrib)\n            if attribval is not None:\n                if value is None:\n                    elements.append(element)\n                else:\n                    if value == attribval:\n                        elements.append(element)\n        return elements\n\n    # ZPT-alike methods\n    def repeat(self, iterable, childname=None):\n        \"\"\"repeats an element with values from an iterable.  If\n        'childname' is not None, repeat the element on which the\n        repeat is called, otherwise find the child element with a\n        'meld:id' matching 'childname' and repeat that.  The element\n        is repeated within its parent element (nodes that are created\n        as a result of a repeat share the same parent).  This method\n        returns an iterable; the value of each iteration is a\n        two-sequence in the form (newelement, data).  'newelement' is\n        a clone of the template element (including clones of its\n        children) which has already been seated in its parent element\n        in the template. 'data' is a value from the passed in\n        iterable.  Changing 'newelement' (typically based on values\n        from 'data') mutates the element 'in place'.\"\"\"\n        if childname:\n            element = self.findmeld(childname)\n        else:\n            element = self\n\n        parent = element.parent\n        # creating a list is faster than yielding a generator (py 2.4)\n        L = []\n        first = True\n        for thing in iterable:\n            if first is True:\n                clone = element\n            else:\n                clone = helper.bfclone(element, parent)\n            L.append((clone, thing))\n            first = False\n        return L\n\n    def replace(self, text, structure=False):\n        \"\"\" Replace this element with a Replace node in our parent with\n        the text 'text' and return the index of our position in\n        our parent.  If we have no parent, do nothing, and return None.\n        Pass the 'structure' flag to the replace node so it can do the right\n        thing at render time. \"\"\"\n        parent = self.parent\n        i = self.deparent()\n        if i is not None:\n            # reduce function call overhead by not calling self.insert\n            node = Replace(text, structure)\n            parent._children.insert(i, node)\n            node.parent = parent\n            return i\n\n    def content(self, text, structure=False):\n        \"\"\" Delete this node's children and append a Replace node that\n        contains text.  Always return None.  Pass the 'structure' flag\n        to the replace node so it can do the right thing at render\n        time.\"\"\"\n        helper.content(self, text, structure)\n\n    def attributes(self, **kw):\n        \"\"\" Set attributes on this node. \"\"\"\n        for k, v in kw.items():\n            # prevent this from getting to the parser if possible\n            if not isinstance(k, StringTypes):\n                raise ValueError('do not set non-stringtype as key: %s' % k)\n            if not isinstance(v, StringTypes):\n                raise ValueError('do not set non-stringtype as val: %s' % v)\n            self.attrib[k] = kw[k]\n\n    # output methods\n    def write_xmlstring(self, encoding=None, doctype=None, fragment=False,\n                        declaration=True, pipeline=False):\n        data = []\n        write = data.append\n        if not fragment:\n            if declaration:\n                _write_declaration(write, encoding)\n            if doctype:\n                _write_doctype(write, doctype)\n        _write_xml(write, self, encoding, {}, pipeline)\n        return _BLANK.join(data)\n\n    def write_xml(self, file, encoding=None, doctype=None,\n                  fragment=False, declaration=True, pipeline=False):\n        \"\"\" Write XML to 'file' (which can be a filename or filelike object)\n\n        encoding    - encoding string (if None, 'utf-8' encoding is assumed)\n                      Must be a recognizable Python encoding type.\n        doctype     - 3-tuple indicating name, pubid, system of doctype.\n                      The default is to prevent a doctype from being emitted.\n        fragment    - True if a 'fragment' should be emitted for this node (no\n                      declaration, no doctype).  This causes both the\n                      'declaration' and 'doctype' parameters to become ignored\n                      if provided.\n        declaration - emit an xml declaration header (including an encoding\n                      if it's not None).  The default is to emit the\n                      doctype.\n        pipeline    - preserve 'meld' namespace identifiers in output\n                      for use in pipelining\n        \"\"\"\n        if not hasattr(file, \"write\"):\n            file = open(file, \"wb\")\n        data = self.write_xmlstring(encoding, doctype, fragment, declaration,\n                                    pipeline)\n        file.write(data)\n\n    def write_htmlstring(self, encoding=None, doctype=doctype.html,\n                         fragment=False):\n        data = []\n        write = data.append\n        if encoding is None:\n            encoding = 'utf8'\n        if not fragment:\n            if doctype:\n                _write_doctype(write, doctype)\n        _write_html(write, self, encoding, {})\n        joined = _BLANK.join(data)\n        return joined\n\n    def write_html(self, file, encoding=None, doctype=doctype.html,\n                   fragment=False):\n        \"\"\" Write HTML to 'file' (which can be a filename or filelike object)\n\n        encoding    - encoding string (if None, 'utf-8' encoding is assumed).\n                      Unlike XML output, this is not used in a declaration,\n                      but it is used to do actual character encoding during\n                      output.  Must be a recognizable Python encoding type.\n        doctype     - 3-tuple indicating name, pubid, system of doctype.\n                      The default is the value of doctype.html (HTML 4.0\n                      'loose')\n        fragment    - True if a \"fragment\" should be omitted (no doctype).\n                      This overrides any provided \"doctype\" parameter if\n                      provided.\n\n        Namespace'd elements and attributes have their namespaces removed\n        during output when writing HTML, so pipelining cannot be performed.\n\n        HTML is not valid XML, so an XML declaration header is never emitted.\n        \"\"\"\n        if not hasattr(file, \"write\"):\n            file = open(file, \"wb\")\n        page = self.write_htmlstring(encoding, doctype, fragment)\n        file.write(page)\n\n    def write_xhtmlstring(self, encoding=None, doctype=doctype.xhtml,\n                          fragment=False, declaration=False, pipeline=False):\n        data = []\n        write = data.append\n        if not fragment:\n            if declaration:\n                _write_declaration(write, encoding)\n            if doctype:\n                _write_doctype(write, doctype)\n        _write_xml(write, self, encoding, {}, pipeline, xhtml=True)\n        return _BLANK.join(data)\n\n    def write_xhtml(self, file, encoding=None, doctype=doctype.xhtml,\n                    fragment=False, declaration=False, pipeline=False):\n        \"\"\" Write XHTML to 'file' (which can be a filename or filelike object)\n\n        encoding    - encoding string (if None, 'utf-8' encoding is assumed)\n                      Must be a recognizable Python encoding type.\n        doctype     - 3-tuple indicating name, pubid, system of doctype.\n                      The default is the value of doctype.xhtml (XHTML\n                      'loose').\n        fragment    - True if a 'fragment' should be emitted for this node (no\n                      declaration, no doctype).  This causes both the\n                      'declaration' and 'doctype' parameters to be ignored.\n        declaration - emit an xml declaration header (including an encoding\n                      string if 'encoding' is not None)\n        pipeline    - preserve 'meld' namespace identifiers in output\n                      for use in pipelining\n        \"\"\"\n        if not hasattr(file, \"write\"):\n            file = open(file, \"wb\")\n        page = self.write_xhtmlstring(encoding, doctype, fragment, declaration,\n                                      pipeline)\n        file.write(page)\n\n    def clone(self, parent=None):\n        \"\"\" Create a clone of an element.  If parent is not None,\n        append the element to the parent.  Recurse as necessary to create\n        a deep clone of the element. \"\"\"\n        return helper.bfclone(self, parent)\n\n    def deparent(self):\n        \"\"\" Remove ourselves from our parent node (de-parent) and return\n        the index of the parent which was deleted. \"\"\"\n        i = self.parentindex()\n        if i is not None:\n            del self.parent[i]\n            return i\n\n    def parentindex(self):\n        \"\"\" Return the parent node index in which we live \"\"\"\n        parent = self.parent\n        if parent is not None:\n            return parent._children.index(self)\n\n    def shortrepr(self, encoding=None):\n        data = []\n        _write_html(data.append, self, encoding, {}, maxdepth=2)\n        return _BLANK.join(data)\n\n    def diffmeld(self, other):\n        \"\"\" Compute the meld element differences from this node (the\n        source) to 'other' (the target).  Return a dictionary of\n        sequences in the form {'unreduced:\n               {'added':[], 'removed':[], 'moved':[]},\n                               'reduced':\n               {'added':[], 'removed':[], 'moved':[]},}\n                               \"\"\"\n        srcelements = self.findmelds()\n        tgtelements = other.findmelds()\n        srcids = [ x.meldid() for x in srcelements ]\n        tgtids = [ x.meldid() for x in tgtelements ]\n\n        removed = []\n        for srcelement in srcelements:\n            if srcelement.meldid() not in tgtids:\n                removed.append(srcelement)\n\n        added = []\n        for tgtelement in tgtelements:\n            if tgtelement.meldid() not in srcids:\n                added.append(tgtelement)\n\n        moved = []\n        for srcelement in srcelements:\n            srcid = srcelement.meldid()\n            if srcid in tgtids:\n                i = tgtids.index(srcid)\n                tgtelement = tgtelements[i]\n                if not sharedlineage(srcelement, tgtelement):\n                    moved.append(tgtelement)\n\n        unreduced = {'added':added, 'removed':removed, 'moved':moved}\n\n        moved_reduced = diffreduce(moved)\n        added_reduced = diffreduce(added)\n        removed_reduced = diffreduce(removed)\n\n        reduced = {'moved':moved_reduced, 'added':added_reduced,\n                   'removed':removed_reduced}\n\n        return {'unreduced':unreduced,\n                'reduced':reduced}\n\n    def meldid(self):\n        return self.attrib.get(_MELD_ID)\n\n    def lineage(self):\n        L = []\n        parent = self\n        while parent is not None:\n            L.append(parent)\n            parent = parent.parent\n        return L\n\nclass MeldTreeBuilder(TreeBuilder):\n    def __init__(self):\n        TreeBuilder.__init__(self, element_factory=_MeldElementInterface)\n        self.meldids = {}\n\n    def start(self, tag, attrs):\n        elem = TreeBuilder.start(self, tag, attrs)\n        for key, value in attrs.items():\n            if key == _MELD_ID:\n                if value in self.meldids:\n                    raise ValueError('Repeated meld id \"%s\" in source' %\n                                     value)\n                self.meldids[value] = 1\n                break\n        return elem\n\n    def comment(self, data):\n        self.start(Comment, {})\n        self.data(data)\n        self.end(Comment)\n\n    def doctype(self, name, pubid, system):\n        pass\n\nclass HTMLXMLParser(HTMLParser):\n    \"\"\" A mostly-cut-and-paste of ElementTree's HTMLTreeBuilder that\n    does special meld3 things (like preserve comments and munge meld\n    ids).  Subclassing is not possible due to private attributes. :-(\"\"\"\n\n    def __init__(self, builder=None, encoding=None):\n        self.__stack = []\n        if builder is None:\n            builder = MeldTreeBuilder()\n        self.builder = builder\n        self.encoding = encoding or \"iso-8859-1\"\n        try:\n            # ``convert_charrefs`` was added in Python 3.4.  Set it to avoid\n            # \"DeprecationWarning: The value of convert_charrefs will become\n            # True in 3.5. You are encouraged to set the value explicitly.\"\n            HTMLParser.__init__(self, convert_charrefs=False)\n        except TypeError:\n            HTMLParser.__init__(self)\n        self.meldids = {}\n\n    def close(self):\n        HTMLParser.close(self)\n        self.meldids = {}\n        return self.builder.close()\n\n    def handle_starttag(self, tag, attrs):\n        if tag == \"meta\":\n            # look for encoding directives\n            http_equiv = content = None\n            for k, v in attrs:\n                if k == \"http-equiv\":\n                    http_equiv = v.lower()\n                elif k == \"content\":\n                    content = v\n            if http_equiv == \"content-type\" and content:\n                # use email to parse the http header\n                msg = email.message_from_string(\n                    \"%s: %s\\n\\n\" % (http_equiv, content)\n                    )\n                encoding = msg.get_param(\"charset\")\n                if encoding:\n                    self.encoding = encoding\n        if tag in AUTOCLOSE:\n            if self.__stack and self.__stack[-1] == tag:\n                self.handle_endtag(tag)\n        self.__stack.append(tag)\n        attrib = {}\n        if attrs:\n            for k, v in attrs:\n                if k == _MELD_SHORT_ID:\n                    k = _MELD_ID\n                    if self.meldids.get(v):\n                        raise ValueError('Repeated meld id \"%s\" in source' %\n                                         v)\n                    self.meldids[v] = 1\n                else:\n                    k = k.lower()\n                attrib[k] = v\n        self.builder.start(tag, attrib)\n        if tag in IGNOREEND:\n            self.__stack.pop()\n            self.builder.end(tag)\n\n    def handle_endtag(self, tag):\n        if tag in IGNOREEND:\n            return\n        lasttag = self.__stack.pop()\n        if tag != lasttag and lasttag in AUTOCLOSE:\n            self.handle_endtag(lasttag)\n        self.builder.end(tag)\n\n    def handle_charref(self, char):\n        if char[:1] == \"x\":\n            char = int(char[1:], 16)\n        else:\n            char = int(char)\n        self.builder.data(unichr(char))\n\n    def handle_entityref(self, name):\n        entity = htmlentitydefs.entitydefs.get(name)\n        if entity:\n            if len(entity) == 1:\n                entity = ord(entity)\n            else:\n                entity = int(entity[2:-1])\n            self.builder.data(unichr(entity))\n        else:\n            self.unknown_entityref(name)\n\n    def handle_data(self, data):\n        if isinstance(data, bytes):\n            data = as_string(data, self.encoding)\n        self.builder.data(data)\n\n    def unknown_entityref(self, name):\n        pass # ignore by default; override if necessary\n\n    def handle_comment(self, data):\n        self.builder.start(Comment, {})\n        self.builder.data(data)\n        self.builder.end(Comment)\n\ndef do_parse(source, parser):\n    root = et_parse(source, parser=parser).getroot()\n    iterator = root.getiterator()\n    for p in iterator:\n        for c in p:\n            c.parent = p\n    return root\n\ndef parse_xml(source):\n    \"\"\" Parse source (a filelike object) into an element tree.  If\n    html is true, use a parser that can resolve somewhat ambiguous\n    HTML into XHTML.  Otherwise use a 'normal' parser only.\"\"\"\n    builder = MeldTreeBuilder()\n    parser = XMLParser(target=builder)\n    return do_parse(source, parser)\n\ndef parse_html(source, encoding=None):\n    builder = MeldTreeBuilder()\n    parser = HTMLXMLParser(builder, encoding)\n    return do_parse(source, parser)\n\ndef parse_xmlstring(text):\n    source = StringIO(text)\n    return parse_xml(source)\n\ndef parse_htmlstring(text, encoding=None):\n    source = StringIO(text)\n    return parse_html(source, encoding)\n\nattrib_needs_escaping = re.compile(r'[&\"<]').search\ncdata_needs_escaping = re.compile(r'[&<]').search\n\ndef _both_case(mapping):\n    # Add equivalent upper-case keys to mapping.\n    lc_keys = list(mapping.keys())\n    for k in lc_keys:\n        mapping[k.upper()] = mapping[k]\n\n\n_HTMLTAGS_UNBALANCED    = {'area':1, 'base':1, 'basefont':1, 'br':1, 'col':1,\n                           'frame':1, 'hr':1, 'img':1, 'input':1, 'isindex':1,\n                           'link':1, 'meta':1, 'param':1}\n_both_case(_HTMLTAGS_UNBALANCED)\n\n_HTMLTAGS_NOESCAPE      = {'script':1, 'style':1}\n_both_case(_HTMLTAGS_NOESCAPE)\n\n_HTMLATTRS_BOOLEAN      = {'selected':1, 'checked':1, 'compact':1, 'declare':1,\n                           'defer':1, 'disabled':1, 'ismap':1, 'multiple':1,\n                           'nohref':1, 'noresize':1, 'noshade':1, 'nowrap':1}\n_both_case(_HTMLATTRS_BOOLEAN)\n\ndef _write_html(write, node, encoding, namespaces, depth=-1, maxdepth=None):\n    \"\"\" Walk 'node', calling 'write' with bytes(?).\n    \"\"\"\n    if encoding is None:\n        encoding = 'utf-8'\n\n    tag  = node.tag\n    tail = node.tail\n    text = node.text\n    tail = node.tail\n\n    to_write = _BLANK\n\n    if tag is Replace:\n        if not node.structure:\n            if cdata_needs_escaping(text):\n                text = _escape_cdata(text)\n        write(encode(text, encoding))\n\n    elif tag is Comment:\n        if cdata_needs_escaping(text):\n            text = _escape_cdata(text)\n        write(encode('<!-- ' + text + ' -->', encoding))\n\n    elif tag is ProcessingInstruction:\n        if cdata_needs_escaping(text):\n            text = _escape_cdata(text)\n        write(encode('<!-- ' + text + ' -->', encoding))\n\n    else:\n        xmlns_items = [] # new namespaces in this scope\n        try:\n            if tag[:1] == \"{\":\n                if tag[:_XHTML_PREFIX_LEN] == _XHTML_PREFIX:\n                    tag = tag[_XHTML_PREFIX_LEN:]\n                else:\n                    tag, xmlns = fixtag(tag, namespaces)\n                    if xmlns:\n                        xmlns_items.append(xmlns)\n        except TypeError:\n            _raise_serialization_error(tag)\n\n        to_write += _OPEN_TAG_START + encode(tag, encoding)\n\n        attrib = node.attrib\n\n        if attrib is not None:\n            if len(attrib) > 1:\n                attrib_keys = list(attrib.keys())\n                attrib_keys.sort()\n            else:\n                attrib_keys = attrib\n            for k in attrib_keys:\n                try:\n                    if k[:1] == \"{\":\n                        continue\n                except TypeError:\n                    _raise_serialization_error(k)\n                if k in _HTMLATTRS_BOOLEAN:\n                    to_write += _SPACE + encode(k, encoding)\n                else:\n                    v = attrib[k]\n                    to_write += _encode_attrib(k, v, encoding)\n\n        for k, v in xmlns_items:\n            to_write += _encode_attrib(k, v, encoding)\n\n        to_write += _OPEN_TAG_END\n\n        if text is not None and text:\n            if tag in _HTMLTAGS_NOESCAPE:\n                to_write += encode(text, encoding)\n            elif cdata_needs_escaping(text):\n                to_write += _escape_cdata(text)\n            else:\n                to_write += encode(text,encoding)\n\n        write(to_write)\n\n        for child in node._children:\n            if maxdepth is not None:\n                depth = depth + 1\n                if depth < maxdepth:\n                    _write_html(write, child, encoding, namespaces, depth,\n                                maxdepth)\n                elif depth == maxdepth and text:\n                    write(_OMITTED_TEXT)\n\n            else:\n                _write_html(write, child, encoding, namespaces, depth, maxdepth)\n\n        if text or node._children or tag not in _HTMLTAGS_UNBALANCED:\n            write(_CLOSE_TAG_START + encode(tag, encoding) + _CLOSE_TAG_END)\n\n    if tail:\n        if cdata_needs_escaping(tail):\n            write(_escape_cdata(tail))\n        else:\n            write(encode(tail,encoding))\n\ndef _write_xml(write, node, encoding, namespaces, pipeline, xhtml=False):\n    \"\"\" Write XML to a file \"\"\"\n    if encoding is None:\n        encoding = 'utf-8'\n    tag = node.tag\n    if tag is Comment:\n        write(_COMMENT_START +\n              _escape_cdata(node.text, encoding) +\n              _COMMENT_END)\n    elif tag is ProcessingInstruction:\n        write(_PI_START +\n              _escape_cdata(node.text, encoding) +\n              _PI_END)\n    elif tag is Replace:\n        if node.structure:\n            # this may produce invalid xml\n            write(encode(node.text, encoding))\n        else:\n            write(_escape_cdata(node.text, encoding))\n    else:\n        if xhtml:\n            if tag[:_XHTML_PREFIX_LEN] == _XHTML_PREFIX:\n                tag = tag[_XHTML_PREFIX_LEN:]\n        if node.attrib:\n            items = list(node.attrib.items())\n        else:\n            items = []  # must always be sortable.\n        xmlns_items = [] # new namespaces in this scope\n        try:\n            if tag[:1] == \"{\":\n                tag, xmlns = fixtag(tag, namespaces)\n                if xmlns:\n                    xmlns_items.append(xmlns)\n        except TypeError:\n            _raise_serialization_error(tag)\n        write(_OPEN_TAG_START + encode(tag, encoding))\n        if items or xmlns_items:\n            items.sort() # lexical order\n            for k, v in items:\n                try:\n                    if k[:1] == \"{\":\n                        if not pipeline:\n                            if k == _MELD_ID:\n                                continue\n                        k, xmlns = fixtag(k, namespaces)\n                        if xmlns: xmlns_items.append(xmlns)\n                    if not pipeline:\n                        # special-case for HTML input\n                        if k == 'xmlns:meld':\n                            continue\n                except TypeError:\n                    _raise_serialization_error(k)\n                write(_encode_attrib(k, v, encoding))\n            for k, v in xmlns_items:\n                write(_encode_attrib(k, v, encoding))\n        if node.text or node._children:\n            write(_OPEN_TAG_END)\n            if node.text:\n                write(_escape_cdata(node.text, encoding))\n            for n in node._children:\n                _write_xml(write, n, encoding, namespaces, pipeline, xhtml)\n            write(_CLOSE_TAG_START + encode(tag, encoding) + _CLOSE_TAG_END)\n        else:\n            write(_SELF_CLOSE)\n        for k, v in xmlns_items:\n            del namespaces[v]\n    if node.tail:\n        write(_escape_cdata(node.tail, encoding))\n\ndef _encode_attrib(k, v, encoding):\n    return _BLANK.join((_SPACE,\n                        encode(k, encoding),\n                        _EQUAL,\n                        _QUOTE,\n                        _escape_attrib(v, encoding),\n                        _QUOTE,\n                       ))\n\n# overrides to elementtree to increase speed and get entity quoting correct.\n\n# negative lookahead assertion\n_NONENTITY_RE = re.compile(as_bytes(r'&(?!([#\\w]*;))', encoding='latin1'))\n\ndef _escape_cdata(text, encoding=None):\n    # Return escaped character data as bytes.\n    try:\n        if encoding:\n            try:\n                encoded = encode(text, encoding)\n            except UnicodeError:\n                return _encode_entity(text)\n        else:\n            encoded = as_bytes(text, encoding='latin1')\n        encoded = _NONENTITY_RE.sub(_AMPER_ESCAPED, encoded)\n        encoded = encoded.replace(_LT, _LT_ESCAPED)\n        return encoded\n    except (TypeError, AttributeError):\n        _raise_serialization_error(text)\n\ndef _escape_attrib(text, encoding):\n    # Return escaped attribute value as bytes.\n    try:\n        if encoding:\n            try:\n                encoded = encode(text, encoding)\n            except UnicodeError:\n                return _encode_entity(text)\n        else:\n            encoded = as_bytes(text, encoding='latin1')\n        # don't requote properly-quoted entities\n        encoded = _NONENTITY_RE.sub(_AMPER_ESCAPED, encoded)\n        encoded = encoded.replace(_LT, _LT_ESCAPED)\n        encoded = encoded.replace(_QUOTE, _QUOTE_ESCAPED)\n        return encoded\n    except (TypeError, AttributeError):\n        _raise_serialization_error(text)\n\n# utility functions\n\ndef _write_declaration(write, encoding):\n    # Write as bytes.\n    if not encoding:\n        write(_XML_PROLOG_BEGIN + _XML_PROLOG_END)\n    else:\n        write(_XML_PROLOG_BEGIN +\n              _SPACE +\n              _ENCODING +\n              _EQUAL +\n              _QUOTE +\n              as_bytes(encoding, encoding='latin1') +\n              _QUOTE +\n              _XML_PROLOG_END)\n\ndef _write_doctype(write, doctype):\n    # Write as bytes.\n    try:\n        name, pubid, system = doctype\n    except (ValueError, TypeError):\n        raise ValueError(\"doctype must be supplied as a 3-tuple in the form \"\n                         \"(name, pubid, system) e.g. '%s'\" % doctype.xhtml)\n    write(_DOCTYPE_BEGIN + _SPACE + as_bytes(name, encoding='latin1') +\n          _SPACE + _PUBLIC + _SPACE +\n          _QUOTE + as_bytes(pubid, encoding='latin1') + _QUOTE + _SPACE +\n          _QUOTE + as_bytes(system, encoding='latin1') + _QUOTE +\n          _DOCTYPE_END)\n\n_XML_DECL_RE = re.compile(r'<\\?xml .*?\\?>')\n_BEGIN_TAG_RE = re.compile(r'<[^/?!]?\\w+')\n\ndef insert_doctype(data, doctype=doctype.xhtml):\n    # jam an html doctype declaration into 'data' if it\n    # doesn't already contain a doctype declaration\n    match = _XML_DECL_RE.search(data)\n    dt_string = '<!DOCTYPE %s PUBLIC \"%s\" \"%s\">' % doctype\n    if match is not None:\n        start, end = match.span(0)\n        before = data[:start]\n        tag = data[start:end]\n        after = data[end:]\n        return before + tag + dt_string + after\n    else:\n        return dt_string + data\n\ndef insert_meld_ns_decl(data):\n    match = _BEGIN_TAG_RE.search(data)\n    if match is not None:\n        start, end = match.span(0)\n        before = data[:start]\n        tag = data[start:end] + ' xmlns:meld=\"%s\"' % _MELD_NS_URL\n        after = data[end:]\n        data =  before + tag + after\n    return data\n\ndef prefeed(data, doctype=doctype.xhtml):\n    if data.find('<!DOCTYPE') == -1:\n        data = insert_doctype(data, doctype)\n    if data.find('xmlns:meld') == -1:\n        data = insert_meld_ns_decl(data)\n    return data\n\ndef sharedlineage(srcelement, tgtelement):\n    srcparent = srcelement.parent\n    tgtparent = tgtelement.parent\n    srcparenttag = getattr(srcparent, 'tag', None)\n    tgtparenttag = getattr(tgtparent, 'tag', None)\n    if srcparenttag != tgtparenttag:\n        return False\n    elif tgtparenttag is None and srcparenttag is None:\n        return True\n    elif tgtparent and srcparent:\n        return sharedlineage(srcparent, tgtparent)\n    return False\n\ndef diffreduce(elements):\n    # each element in 'elements' should all have non-None meldids, and should\n    # be preordered in depth-first traversal order\n    reduced = []\n    for element in elements:\n        parent = element.parent\n        if parent is None:\n            reduced.append(element)\n            continue\n        if parent in reduced:\n            continue\n        reduced.append(element)\n    return reduced\n\ndef intersection(S1, S2):\n    L = []\n    for element in S1:\n        if element in S2:\n            L.append(element)\n    return L\n\ndef melditerator(element, meldid=None, _MELD_ID=_MELD_ID):\n    nodeid = element.attrib.get(_MELD_ID)\n    if nodeid is not None:\n        if meldid is None or nodeid == meldid:\n            yield element\n    for child in element._children:\n        for el2 in melditerator(child, meldid):\n            nodeid = el2.attrib.get(_MELD_ID)\n            if nodeid is not None:\n                if meldid is None or nodeid == meldid:\n                    yield el2\n\n#-----------------------------------------------------------------------------\n# Begin fork from Python 2.6.8 stdlib:\n#       - xml.elementtree.ElementTree._raise_serialization_error\n#       - xml.elementtree.ElementTree._encode_entity\n#       - xml.elementtree.ElementTree._namespace_map\n#       - xml.elementtree.ElementTree.fixtag\n#-----------------------------------------------------------------------------\n\n_NON_ASCII_MIN = as_string('\\xc2\\x80', 'utf-8')        # u'\\u0080'\n_NON_ASCII_MAX = as_string('\\xef\\xbf\\xbf', 'utf-8')    # u'\\uffff'\n\n_escape_map = {\n    \"&\": \"&amp;\",\n    \"<\": \"&lt;\",\n    \">\": \"&gt;\",\n    '\"': \"&quot;\",\n}\n\n_namespace_map = {\n    # \"well-known\" namespace prefixes\n    \"http://www.w3.org/XML/1998/namespace\": \"xml\",\n    \"http://www.w3.org/1999/xhtml\": \"html\",\n    \"http://www.w3.org/1999/02/22-rdf-syntax-ns#\": \"rdf\",\n    \"http://schemas.xmlsoap.org/wsdl/\": \"wsdl\",\n}\n\ndef _encode(s, encoding):\n    try:\n        return s.encode(encoding)\n    except AttributeError:\n        return s\n\ndef _raise_serialization_error(text):\n    raise TypeError(\n        \"cannot serialize %r (type %s)\" % (text, type(text).__name__)\n        )\n\n_pattern = None\ndef _encode_entity(text):\n    # map reserved and non-ascii characters to numerical entities\n    global _pattern\n    if _pattern is None:\n        _ptxt = r'[&<>\\\"' + _NON_ASCII_MIN + '-' + _NON_ASCII_MAX + ']+'\n        #_pattern = re.compile(eval(r'u\"[&<>\\\"\\u0080-\\uffff]+\"'))\n        _pattern = re.compile(_ptxt)\n\n    def _escape_entities(m):\n        out = []\n        append = out.append\n        for char in m.group():\n            text = _escape_map.get(char)\n            if text is None:\n                text = \"&#%d;\" % ord(char)\n            append(text)\n        return ''.join(out)\n    try:\n        return _encode(_pattern.sub(_escape_entities, text), \"ascii\")\n    except TypeError:\n        _raise_serialization_error(text)\n\ndef fixtag(tag, namespaces):\n    # given a decorated tag (of the form {uri}tag), return prefixed\n    # tag and namespace declaration, if any\n    if isinstance(tag, QName):\n        tag = tag.text\n    namespace_uri, tag = tag[1:].split(\"}\", 1)\n    prefix = namespaces.get(namespace_uri)\n    if prefix is None:\n        prefix = _namespace_map.get(namespace_uri)\n        if prefix is None:\n            prefix = \"ns%d\" % len(namespaces)\n        namespaces[namespace_uri] = prefix\n        if prefix == \"xml\":\n            xmlns = None\n        else:\n            xmlns = (\"xmlns:%s\" % prefix, namespace_uri)\n    else:\n        xmlns = None\n    return \"%s:%s\" % (prefix, tag), xmlns\n\n#-----------------------------------------------------------------------------\n# End fork from Python 2.6.8 stdlib\n#-----------------------------------------------------------------------------\n"
  },
  {
    "path": "supervisor/tests/__init__.py",
    "content": "# this is a package\n"
  },
  {
    "path": "supervisor/tests/base.py",
    "content": "_NOW = 1151365354\n_TIMEFORMAT = '%b %d %I:%M %p'\n\nimport functools\n\nfrom supervisor.compat import Fault\nfrom supervisor.compat import as_bytes\n\n# mock is imported here for py2/3 compat.  we only declare mock as a dependency\n# via tests_require so it is not available on all supervisor installs.  the\n# modules imported in supervisor.compat must always be available.\n\ntry: # pragma: no cover\n    from unittest.mock import Mock, patch, sentinel\nexcept ImportError: # pragma: no cover\n    from mock import Mock, patch, sentinel\n\ntry: # pragma: no cover\n    import unittest.mock as mock\nexcept ImportError: # pragma: no cover\n    import mock\n\nclass DummyOptions:\n    loglevel = 20\n    minfds = 5\n\n    chdir_exception = None\n    fork_exception = None\n    execv_exception = None\n    kill_exception = None\n    make_pipes_exception = None\n    remove_exception = None\n    write_exception = None\n\n    def __init__(self):\n        self.identifier = 'supervisor'\n        self.childlogdir = '/tmp'\n        self.uid = 999\n        self.logger = self.getLogger()\n        self.backofflimit = 10\n        self.logfile = '/tmp/logfile'\n        self.nocleanup = False\n        self.strip_ansi = False\n        self.pidhistory = {}\n        self.process_group_configs = []\n        self.nodaemon = False\n        self.socket_map = {}\n        self.mood = 1\n        self.mustreopen = False\n        self.realizeargs = None\n        self.fds_cleaned_up = False\n        self.rlimit_set = False\n        self.setuid_called = False\n        self.httpservers_opened = False\n        self.signals_set = False\n        self.daemonized = False\n        self.make_logger_messages = None\n        self.autochildlogdir_cleared = False\n        self.cleaned_up = False\n        self.pidfile_written = False\n        self.directory = None\n        self.waitpid_return = None, None\n        self.kills = {}\n        self._signal = None\n        self.parent_pipes_closed = None\n        self.child_pipes_closed = None\n        self.forkpid = 0\n        self.pgrp_set = None\n        self.duped = {}\n        self.written = {}\n        self.fds_closed = []\n        self._exitcode = None\n        self.execve_called = False\n        self.execv_args = None\n        self.setuid_msg = None\n        self.privsdropped = None\n        self.logs_reopened = False\n        self.write_accept = None\n        self.tempfile_name = '/foo/bar'\n        self.removed = []\n        self.existing = []\n        self.openreturn = None\n        self.readfd_result = ''\n        self.parse_criticals = []\n        self.parse_warnings = []\n        self.parse_infos = []\n        self.serverurl = 'http://localhost:9001'\n        self.changed_directory = False\n        self.umaskset = None\n        self.poller = DummyPoller(self)\n        self.silent = False\n\n    def getLogger(self, *args, **kw):\n        logger = DummyLogger()\n        logger.handlers = [DummyLogger()]\n        logger.args = args, kw\n        return logger\n\n    def realize(self, args, **kw):\n        self.realizeargs = args\n        self.realizekw = kw\n\n    def process_config(self, do_usage=True):\n        pass\n\n    def cleanup_fds(self):\n        self.fds_cleaned_up = True\n\n    def set_rlimits_or_exit(self):\n        self.rlimits_set = True\n        self.parse_infos.append('rlimits_set')\n\n    def set_uid_or_exit(self):\n        self.setuid_called = True\n        self.parse_criticals.append('setuid_called')\n\n    def openhttpservers(self, supervisord):\n        self.httpservers_opened = True\n\n    def daemonize(self):\n        self.daemonized = True\n\n    def setsignals(self):\n        self.signals_set = True\n\n    def get_signal(self):\n        return self._signal\n\n    def get_socket_map(self):\n        return self.socket_map\n\n    def make_logger(self):\n        pass\n\n    def clear_autochildlogdir(self):\n        self.autochildlogdir_cleared = True\n\n    def get_autochildlog_name(self, *ignored):\n        return self.tempfile_name\n\n    def cleanup(self):\n        self.cleaned_up = True\n\n    def write_pidfile(self):\n        self.pidfile_written = True\n\n    def waitpid(self):\n        return self.waitpid_return\n\n    def kill(self, pid, sig):\n        if self.kill_exception is not None:\n            raise self.kill_exception\n        self.kills[pid] = sig\n\n    def stat(self, filename):\n        import os\n        return os.stat(filename)\n\n    def get_path(self):\n        return [\"/bin\", \"/usr/bin\", \"/usr/local/bin\"]\n\n    def get_pid(self):\n        import os\n        return os.getpid()\n\n    def check_execv_args(self, filename, argv, st):\n        if filename == '/bad/filename':\n            from supervisor.options import NotFound\n            raise NotFound('bad filename')\n\n    def make_pipes(self, stderr=True):\n        if self.make_pipes_exception is not None:\n            raise self.make_pipes_exception\n        pipes = {'child_stdin': 3, 'stdin': 4, 'stdout': 5, 'child_stdout': 6}\n        if stderr:\n            pipes['stderr'], pipes['child_stderr'] = (7, 8)\n        else:\n            pipes['stderr'], pipes['child_stderr'] = None, None\n        return pipes\n\n    def write(self, fd, chars):\n        if self.write_exception is not None:\n            raise self.write_exception\n        if self.write_accept:\n            chars = chars[self.write_accept]\n        data = self.written.setdefault(fd, '')\n        data += chars\n        self.written[fd] = data\n        return len(chars)\n\n    def fork(self):\n        if self.fork_exception is not None:\n            raise self.fork_exception\n        return self.forkpid\n\n    def close_fd(self, fd):\n        self.fds_closed.append(fd)\n\n    def close_parent_pipes(self, pipes):\n        self.parent_pipes_closed = pipes\n\n    def close_child_pipes(self, pipes):\n        self.child_pipes_closed = pipes\n\n    def setpgrp(self):\n        self.pgrp_set = True\n\n    def dup2(self, frm, to):\n        self.duped[frm] = to\n\n    def _exit(self, code):\n        self._exitcode = code\n\n    def execve(self, filename, argv, environment):\n        self.execve_called = True\n        if self.execv_exception is not None:\n            raise self.execv_exception\n        self.execv_args = (filename, argv)\n        self.execv_environment = environment\n\n    def drop_privileges(self, uid):\n        if self.setuid_msg:\n            return self.setuid_msg\n        self.privsdropped = uid\n\n    def readfd(self, fd):\n        return self.readfd_result\n\n    def reopenlogs(self):\n        self.logs_reopened = True\n\n    def mktempfile(self, prefix, suffix, dir):\n        return self.tempfile_name\n\n    def remove(self, path):\n        if self.remove_exception is not None:\n            raise self.remove_exception\n        self.removed.append(path)\n\n    def exists(self, path):\n        if path in self.existing:\n            return True\n        return False\n\n    def open(self, name, mode='r'):\n        if self.openreturn:\n            return self.openreturn\n        return open(name, mode)\n\n    def chdir(self, dir):\n        if self.chdir_exception is not None:\n            raise self.chdir_exception\n        self.changed_directory = True\n\n    def setumask(self, mask):\n        self.umaskset = mask\n\nclass DummyLogger:\n    level = None\n\n    def __init__(self):\n        self.reopened = False\n        self.removed = False\n        self.closed = False\n        self.data = []\n\n    def info(self, msg, **kw):\n        if kw:\n            msg = msg % kw\n        self.data.append(msg)\n    warn = debug = critical = trace = error = blather = info\n\n    def log(self, level, msg, **kw):\n        if kw:\n            msg = msg % kw\n        self.data.append(msg)\n\n    def addHandler(self, handler):\n        handler.close()\n\n    def reopen(self):\n        self.reopened = True\n    def close(self):\n        self.closed = True\n    def remove(self):\n        self.removed = True\n    def flush(self):\n        self.flushed = True\n    def getvalue(self):\n        return ''.join(self.data)\n\nclass DummySupervisor:\n    def __init__(self, options=None, state=None, process_groups=None):\n        if options is None:\n            self.options = DummyOptions()\n        else:\n            self.options = options\n        if state is None:\n            from supervisor.supervisord import SupervisorStates\n            self.options.mood = SupervisorStates.RUNNING\n        else:\n            self.options.mood = state\n        if process_groups is None:\n            self.process_groups = {}\n        else:\n            self.process_groups = process_groups\n\n    def get_state(self):\n        return self.options.mood\n\nclass DummySocket:\n    bind_called = False\n    bind_addr = None\n    listen_called = False\n    listen_backlog = None\n    close_called = False\n\n    def __init__(self, fd):\n        self.fd = fd\n\n    def fileno(self):\n        return self.fd\n\n    def bind(self, addr):\n        self.bind_called = True\n        self.bind_addr = addr\n\n    def listen(self, backlog):\n        self.listen_called = True\n        self.listen_backlog = backlog\n\n    def close(self):\n        self.close_called = True\n\n    def __str__(self):\n        return 'dummy socket'\n\nclass DummySocketConfig:\n    def __init__(self, fd, backlog=128):\n        self.fd = fd\n        self.backlog = backlog\n        self.url = 'unix:///sock'\n\n    def addr(self):\n        return 'dummy addr'\n\n    def __eq__(self, other):\n        return self.fd == other.fd\n\n    def __ne__(self, other):\n        return not self.__eq__(other)\n\n    def get_backlog(self):\n        return self.backlog\n\n    def create_and_bind(self):\n        return DummySocket(self.fd)\n\nclass DummySocketManager:\n    def __init__(self, config, **kwargs):\n        self._config = config\n\n    def config(self):\n        return self._config\n\n    def get_socket(self):\n        return DummySocket(self._config.fd)\n\n@functools.total_ordering\nclass DummyProcess(object):\n    write_exception = None\n\n    # Initial state; overridden by instance variables\n    pid = 0 # Subprocess pid; 0 when not running\n    laststart = 0 # Last time the subprocess was started; 0 if never\n    laststop = 0  # Last time the subprocess was stopped; 0 if never\n    delay = 0 # If nonzero, delay starting or killing until this time\n    administrative_stop = False # true if the process stopped by an admin\n    system_stop = False # true if the process has been stopped by the system\n    killing = False # flag determining whether we are trying to kill this proc\n    backoff = 0 # backoff counter (to backofflimit)\n    waitstatus = None\n    exitstatus = None\n    pipes = None\n    rpipes = None\n    dispatchers = None\n    stdout_logged = ''\n    stderr_logged = ''\n    spawnerr = None\n    stdout_buffer = '' # buffer of characters from child stdout output to log\n    stderr_buffer = '' # buffer of characters from child stderr output to log\n    stdin_buffer = '' # buffer of characters to send to child process' stdin\n    listener_state = None\n    group = None\n    sent_signal = None\n\n    def __init__(self, config, state=None):\n        self.config = config\n        self.logsremoved = False\n        self.stop_called = False\n        self.stop_report_called = True\n        self.backoff_secs = None\n        self.spawned = False\n        if state is None:\n            from supervisor.process import ProcessStates\n            state = ProcessStates.RUNNING\n        self.state = state\n        self.error_at_clear = False\n        self.killed_with = None\n        self.drained = False\n        self.stdout_buffer = as_bytes('')\n        self.stderr_buffer = as_bytes('')\n        self.stdout_logged = as_bytes('')\n        self.stderr_logged = as_bytes('')\n        self.stdin_buffer = as_bytes('')\n        self.pipes = {}\n        self.rpipes = {}\n        self.dispatchers = {}\n        self.finished = None\n        self.logs_reopened = False\n        self.execv_arg_exception = None\n        self.input_fd_drained = None\n        self.output_fd_drained = None\n        self.transitioned = False\n\n    def reopenlogs(self):\n        self.logs_reopened = True\n\n    def removelogs(self):\n        if self.error_at_clear:\n            raise IOError('whatever')\n        self.logsremoved = True\n\n    def get_state(self):\n        return self.state\n\n    def stop(self):\n        self.stop_called = True\n        self.killing = False\n        from supervisor.process import ProcessStates\n        self.state = ProcessStates.STOPPED\n\n    def stop_report(self):\n        self.stop_report_called = True\n\n    def kill(self, signal):\n        self.killed_with = signal\n\n    def signal(self, signal):\n        self.sent_signal = signal\n\n\n    def spawn(self):\n        self.spawned = True\n        from supervisor.process import ProcessStates\n        self.state = ProcessStates.RUNNING\n\n    def drain(self):\n        self.drained = True\n\n    def readable_fds(self):\n        return []\n\n    def record_output(self):\n        self.stdout_logged += self.stdout_buffer\n        self.stdout_buffer = ''\n\n        self.stderr_logged += self.stderr_buffer\n        self.stderr_buffer = ''\n\n    def finish(self, pid, sts):\n        self.finished = pid, sts\n\n    def give_up(self):\n        from supervisor.process import ProcessStates\n        self.state = ProcessStates.FATAL\n\n    def get_execv_args(self):\n        if self.execv_arg_exception:\n            raise self.execv_arg_exception('whatever')\n        import shlex\n        commandargs = shlex.split(self.config.command)\n        program = commandargs[0]\n        return program, commandargs\n\n    def drain_output_fd(self, fd):\n        self.output_fd_drained = fd\n\n    def drain_input_fd(self, fd):\n        self.input_fd_drained = fd\n\n    def write(self, chars):\n        if self.write_exception is not None:\n            raise self.write_exception\n        self.stdin_buffer += chars\n\n    def transition(self):\n        self.transitioned = True\n\n    def __eq__(self, other):\n        return self.config.priority == other.config.priority\n\n    def __lt__(self, other):\n        return self.config.priority < other.config.priority\n\nclass DummyPConfig:\n    def __init__(self, options, name, command, directory=None, umask=None,\n                 priority=999, autostart=True,\n                 autorestart=True, startsecs=10, startretries=999,\n                 uid=None, stdout_logfile=None, stdout_capture_maxbytes=0,\n                 stdout_events_enabled=False,\n                 stdout_logfile_backups=0, stdout_logfile_maxbytes=0,\n                 stdout_syslog=False,\n                 stderr_logfile=None, stderr_capture_maxbytes=0,\n                 stderr_events_enabled=False,\n                 stderr_logfile_backups=0, stderr_logfile_maxbytes=0,\n                 stderr_syslog=False,\n                 redirect_stderr=False,\n                 stopsignal=None, stopwaitsecs=10, stopasgroup=False, killasgroup=False,\n                 exitcodes=(0,), environment=None, serverurl=None):\n        self.options = options\n        self.name = name\n        self.command = command\n        self.priority = priority\n        self.autostart = autostart\n        self.autorestart = autorestart\n        self.startsecs = startsecs\n        self.startretries = startretries\n        self.uid = uid\n        self.stdout_logfile = stdout_logfile\n        self.stdout_capture_maxbytes = stdout_capture_maxbytes\n        self.stdout_events_enabled = stdout_events_enabled\n        self.stdout_logfile_backups = stdout_logfile_backups\n        self.stdout_logfile_maxbytes = stdout_logfile_maxbytes\n        self.stdout_syslog = stdout_syslog\n        self.stderr_logfile = stderr_logfile\n        self.stderr_capture_maxbytes = stderr_capture_maxbytes\n        self.stderr_events_enabled = stderr_events_enabled\n        self.stderr_logfile_backups = stderr_logfile_backups\n        self.stderr_logfile_maxbytes = stderr_logfile_maxbytes\n        self.stderr_syslog = stderr_syslog\n        self.redirect_stderr = redirect_stderr\n        if stopsignal is None:\n            import signal\n            stopsignal = signal.SIGTERM\n        self.stopsignal = stopsignal\n        self.stopwaitsecs = stopwaitsecs\n        self.stopasgroup = stopasgroup\n        self.killasgroup = killasgroup\n        self.exitcodes = exitcodes\n        self.environment = environment\n        self.directory = directory\n        self.umask = umask\n        self.autochildlogs_created = False\n        self.serverurl = serverurl\n\n    def get_path(self):\n        return [\"/bin\", \"/usr/bin\", \"/usr/local/bin\"]\n\n    def create_autochildlogs(self):\n        self.autochildlogs_created = True\n\n    def make_process(self, group=None):\n        process = DummyProcess(self)\n        process.group = group\n        return process\n\n    def make_dispatchers(self, proc):\n        use_stderr = not self.redirect_stderr\n        pipes = self.options.make_pipes(use_stderr)\n        stdout_fd,stderr_fd,stdin_fd = (pipes['stdout'],pipes['stderr'],\n                                        pipes['stdin'])\n        dispatchers = {}\n        if stdout_fd is not None:\n            dispatchers[stdout_fd] = DummyDispatcher(readable=True)\n        if stderr_fd is not None:\n            dispatchers[stderr_fd] = DummyDispatcher(readable=True)\n        if stdin_fd is not None:\n            dispatchers[stdin_fd] = DummyDispatcher(writable=True)\n        return dispatchers, pipes\n\ndef makeExecutable(file, substitutions=None):\n    import os\n    import sys\n    import tempfile\n\n    if substitutions is None:\n        substitutions = {}\n    data = open(file).read()\n    last = os.path.split(file)[1]\n\n    substitutions['PYTHON'] = sys.executable\n    for key in substitutions.keys():\n        data = data.replace('<<%s>>' % key.upper(), substitutions[key])\n\n    with tempfile.NamedTemporaryFile(prefix=last, delete=False) as f:\n        tmpnam = f.name\n        f.write(data)\n    os.chmod(tmpnam, 0o755)\n    return tmpnam\n\ndef makeSpew(unkillable=False):\n    import os\n    here = os.path.dirname(__file__)\n    if not unkillable:\n        return makeExecutable(os.path.join(here, 'fixtures/spew.py'))\n    return makeExecutable(os.path.join(here, 'fixtures/unkillable_spew.py'))\n\nclass DummyMedusaServerLogger:\n    def __init__(self):\n        self.logged = []\n    def log(self, category, msg):\n        self.logged.append((category, msg))\n\nclass DummyMedusaServer:\n    def __init__(self):\n        self.logger = DummyMedusaServerLogger()\n\nclass DummyMedusaChannel:\n    def __init__(self):\n        self.server = DummyMedusaServer()\n        self.producer = None\n\n    def push_with_producer(self, producer):\n        self.producer = producer\n\n    def close_when_done(self):\n        pass\n\n    def set_terminator(self, terminator):\n        pass\n\nclass DummyRequest(object):\n    command = 'GET'\n    _error = None\n    _done = False\n    version = '1.0'\n    def __init__(self, path, params, query, fragment, env=None):\n        self.args = path, params, query, fragment\n        self.producers = []\n        self.headers = {}\n        self.header = []\n        self.outgoing = []\n        self.channel = DummyMedusaChannel()\n        if env is None:\n            self.env = {}\n        else:\n            self.env = env\n\n    def split_uri(self):\n        return self.args\n\n    def error(self, code):\n        self._error = code\n\n    def push(self, producer):\n        self.producers.append(producer)\n\n    def __setitem__(self, header, value):\n        self.headers[header] = value\n\n    def __getitem__(self, header):\n        return self.headers[header]\n\n    def __delitem__(self, header):\n        del self.headers[header]\n\n    def has_key(self, header):\n        return header in self.headers\n\n    def __contains__(self, item):\n        return item in self.headers\n\n    def done(self):\n        self._done = True\n\n    def build_reply_header(self):\n        return ''\n\n    def log(self, *arg, **kw):\n        pass\n\n    def cgi_environment(self):\n        return self.env\n\n    def get_server_url(self):\n        return 'http://example.com'\n\n\nclass DummyRPCInterfaceFactory:\n    def __init__(self, supervisord, **config):\n        self.supervisord = supervisord\n        self.config = config\n\nclass DummyRPCServer:\n    def __init__(self):\n        self.supervisor = DummySupervisorRPCNamespace()\n        self.system = DummySystemRPCNamespace()\n\nclass DummySystemRPCNamespace:\n    pass\n\nclass DummySupervisorRPCNamespace:\n    _restartable = True\n    _restarted = False\n    _shutdown = False\n    _readlog_error = False\n\n\n    from supervisor.process import ProcessStates\n    all_process_info = [\n        {\n        'name':'foo',\n        'group':'foo',\n        'pid':11,\n        'state':ProcessStates.RUNNING,\n        'statename':'RUNNING',\n        'start':_NOW - 100,\n        'stop':0,\n        'spawnerr':'',\n        'now':_NOW,\n        'description':'foo description',\n        },\n        {\n        'name':'bar',\n        'group':'bar',\n        'pid':12,\n        'state':ProcessStates.FATAL,\n        'statename':'FATAL',\n        'start':_NOW - 100,\n        'stop':_NOW - 50,\n        'spawnerr':'screwed',\n        'now':_NOW,\n        'description':'bar description',\n        },\n        {\n        'name':'baz_01',\n        'group':'baz',\n        'pid':13,\n        'state':ProcessStates.STOPPED,\n        'statename':'STOPPED',\n        'start':_NOW - 100,\n        'stop':_NOW - 25,\n        'spawnerr':'',\n        'now':_NOW,\n        'description':'baz description',\n        },\n        ]\n\n    def getAPIVersion(self):\n        return '3.0'\n\n    getVersion = getAPIVersion # deprecated\n\n    def getPID(self):\n        return 42\n\n    def _read_log(self, channel, name, offset, length):\n        from supervisor import xmlrpc\n        if name == 'BAD_NAME':\n            raise Fault(xmlrpc.Faults.BAD_NAME, 'BAD_NAME')\n        elif name == 'FAILED':\n            raise Fault(xmlrpc.Faults.FAILED, 'FAILED')\n        elif name == 'NO_FILE':\n            raise Fault(xmlrpc.Faults.NO_FILE, 'NO_FILE')\n        a = (channel + ' line\\n') * 10\n        return a[offset:]\n\n    def readProcessStdoutLog(self, name, offset, length):\n        return self._read_log('stdout', name, offset, length)\n    readProcessLog = readProcessStdoutLog\n\n    def readProcessStderrLog(self, name, offset, length):\n        return self._read_log('stderr', name, offset, length)\n\n    def getAllProcessInfo(self):\n        return self.all_process_info\n\n    def getProcessInfo(self, name):\n        from supervisor import xmlrpc\n        for i in self.all_process_info:\n            if i['name']==name:\n                info=i\n                return info\n        if name == 'BAD_NAME':\n            raise Fault(xmlrpc.Faults.BAD_NAME, 'BAD_NAME')\n        if name == 'FAILED':\n            raise Fault(xmlrpc.Faults.FAILED, 'FAILED')\n        if name == 'NO_FILE':\n            raise Fault(xmlrpc.Faults.NO_FILE, 'NO_FILE')\n\n    def startProcess(self, name):\n        from supervisor import xmlrpc\n        if name == 'BAD_NAME:BAD_NAME':\n            raise Fault(xmlrpc.Faults.BAD_NAME, 'BAD_NAME:BAD_NAME')\n        if name == 'BAD_NAME':\n            raise Fault(xmlrpc.Faults.BAD_NAME, 'BAD_NAME')\n        if name == 'NO_FILE':\n            raise Fault(xmlrpc.Faults.NO_FILE, 'NO_FILE')\n        if name == 'NOT_EXECUTABLE':\n            raise Fault(xmlrpc.Faults.NOT_EXECUTABLE, 'NOT_EXECUTABLE')\n        if name == 'ALREADY_STARTED':\n            raise Fault(xmlrpc.Faults.ALREADY_STARTED, 'ALREADY_STARTED')\n        if name == 'SPAWN_ERROR':\n            raise Fault(xmlrpc.Faults.SPAWN_ERROR, 'SPAWN_ERROR')\n        if name == 'ABNORMAL_TERMINATION':\n            raise Fault(xmlrpc.Faults.ABNORMAL_TERMINATION,\n                        'ABNORMAL_TERMINATION')\n        return True\n\n    def startProcessGroup(self, name):\n        from supervisor import xmlrpc\n        from supervisor.compat import Fault\n        if name == 'BAD_NAME':\n            raise Fault(xmlrpc.Faults.BAD_NAME, 'BAD_NAME')\n        return [\n            {'name':'foo_00', 'group':'foo',\n             'status': xmlrpc.Faults.SUCCESS,\n             'description': 'OK'},\n            {'name':'foo_01', 'group':'foo',\n             'status':xmlrpc.Faults.SUCCESS,\n             'description': 'OK'},\n            ]\n\n    def startAllProcesses(self):\n        from supervisor import xmlrpc\n        return [\n            {'name':'foo', 'group':'foo',\n             'status': xmlrpc.Faults.SUCCESS,\n             'description': 'OK'},\n            {'name':'foo2', 'group':'foo2',\n             'status':xmlrpc.Faults.SUCCESS,\n             'description': 'OK'},\n            {'name':'failed', 'group':'failed_group',\n             'status':xmlrpc.Faults.SPAWN_ERROR,\n             'description':'SPAWN_ERROR'}\n            ]\n\n    def stopProcessGroup(self, name):\n        from supervisor import xmlrpc\n        from supervisor.compat import Fault\n        if name == 'BAD_NAME':\n            raise Fault(xmlrpc.Faults.BAD_NAME, 'BAD_NAME')\n        return [\n            {'name':'foo_00', 'group':'foo',\n             'status': xmlrpc.Faults.SUCCESS,\n             'description': 'OK'},\n            {'name':'foo_01', 'group':'foo',\n             'status':xmlrpc.Faults.SUCCESS,\n             'description': 'OK'},\n            ]\n\n    def stopProcess(self, name):\n        from supervisor import xmlrpc\n        if name == 'BAD_NAME:BAD_NAME':\n            raise Fault(xmlrpc.Faults.BAD_NAME, 'BAD_NAME:BAD_NAME')\n        if name == 'BAD_NAME':\n            raise Fault(xmlrpc.Faults.BAD_NAME, 'BAD_NAME')\n        if name == 'NOT_RUNNING':\n            raise Fault(xmlrpc.Faults.NOT_RUNNING, 'NOT_RUNNING')\n        if name == 'FAILED':\n            raise Fault(xmlrpc.Faults.FAILED, 'FAILED')\n\n        return True\n\n    def stopAllProcesses(self):\n        from supervisor import xmlrpc\n        return [\n            {'name':'foo','group':'foo',\n             'status': xmlrpc.Faults.SUCCESS,\n             'description': 'OK'},\n            {'name':'foo2', 'group':'foo2',\n             'status':xmlrpc.Faults.SUCCESS,'description': 'OK'},\n            {'name':'failed', 'group':'failed_group',\n             'status':xmlrpc.Faults.BAD_NAME,\n             'description':'FAILED'}\n            ]\n\n    def restart(self):\n        if self._restartable:\n            self._restarted = True\n            return\n        from supervisor import xmlrpc\n        raise Fault(xmlrpc.Faults.SHUTDOWN_STATE, '')\n\n    def shutdown(self):\n        if self._restartable:\n            self._shutdown = True\n            return\n        from supervisor import xmlrpc\n        raise Fault(xmlrpc.Faults.SHUTDOWN_STATE, '')\n\n    def reloadConfig(self):\n        return [[['added'], ['changed'], ['removed']]]\n\n    def addProcessGroup(self, name):\n        from supervisor import xmlrpc\n        if name == 'ALREADY_ADDED':\n            raise Fault(xmlrpc.Faults.ALREADY_ADDED, '')\n        if name == 'BAD_NAME':\n            raise Fault(xmlrpc.Faults.BAD_NAME, '')\n        if name == 'FAILED':\n            raise Fault(xmlrpc.Faults.FAILED, '')\n        if name == 'SHUTDOWN_STATE':\n            raise Fault(xmlrpc.Faults.SHUTDOWN_STATE, '')\n        if hasattr(self, 'processes'):\n            self.processes.append(name)\n        else:\n            self.processes = [name]\n\n    def removeProcessGroup(self, name):\n        from supervisor import xmlrpc\n        if name == 'STILL_RUNNING':\n            raise Fault(xmlrpc.Faults.STILL_RUNNING, '')\n        if name == 'BAD_NAME':\n            raise Fault(xmlrpc.Faults.BAD_NAME, '')\n        if name == 'FAILED':\n            raise Fault(xmlrpc.Faults.FAILED, '')\n        self.processes.remove(name)\n\n    def clearProcessStdoutLog(self, name):\n        from supervisor import xmlrpc\n        if name == 'BAD_NAME':\n            raise Fault(xmlrpc.Faults.BAD_NAME, 'BAD_NAME')\n        return True\n\n    clearProcessLog = clearProcessStdoutLog\n    clearProcessStderrLog = clearProcessStdoutLog\n    clearProcessLogs = clearProcessStdoutLog\n\n    def clearAllProcessLogs(self):\n        from supervisor import xmlrpc\n        return [\n            {'name':'foo',\n             'group':'foo',\n             'status':xmlrpc.Faults.SUCCESS,\n             'description': 'OK'},\n            {'name':'foo2',\n             'group':'foo2',\n             'status':xmlrpc.Faults.SUCCESS,\n             'description': 'OK'},\n            {'name':'failed',\n             'group':'failed_group',\n             'status':xmlrpc.Faults.FAILED,\n             'description':'FAILED'}\n            ]\n\n    def raiseError(self):\n        raise ValueError('error')\n\n    def getXmlRpcUnmarshallable(self):\n        return {'stdout_logfile': None}  # None is unmarshallable\n\n    def getSupervisorVersion(self):\n        return '3000'\n\n    def readLog(self, whence, offset):\n        if self._readlog_error:\n            raise Fault(self._readlog_error, '')\n        return 'mainlogdata'\n\n    def signalProcessGroup(self, name, signal):\n        from supervisor import xmlrpc\n        if name == 'BAD_NAME':\n            raise Fault(xmlrpc.Faults.BAD_NAME, 'BAD_NAME')\n        return [\n            {'name':'foo_00',\n             'group':'foo',\n             'status': xmlrpc.Faults.SUCCESS,\n             'description': 'OK'},\n            {'name':'foo_01',\n             'group':'foo',\n             'status':xmlrpc.Faults.SUCCESS,\n             'description': 'OK'},\n            ]\n\n    def signalProcess(self, name, signal):\n        from supervisor import xmlrpc\n        if signal == 'BAD_SIGNAL':\n            raise Fault(xmlrpc.Faults.BAD_SIGNAL, 'BAD_SIGNAL')\n        if name == 'BAD_NAME:BAD_NAME':\n            raise Fault(xmlrpc.Faults.BAD_NAME, 'BAD_NAME:BAD_NAME')\n        if name == 'BAD_NAME':\n            raise Fault(xmlrpc.Faults.BAD_NAME, 'BAD_NAME')\n        if name == 'NOT_RUNNING':\n            raise Fault(xmlrpc.Faults.NOT_RUNNING, 'NOT_RUNNING')\n        if name == 'FAILED':\n            raise Fault(xmlrpc.Faults.FAILED, 'FAILED')\n\n        return True\n\n    def signalAllProcesses(self, signal):\n        from supervisor import xmlrpc\n        return [\n            {'name':'foo',\n             'group':'foo',\n             'status': xmlrpc.Faults.SUCCESS,\n             'description': 'OK'},\n            {'name':'foo2',\n             'group':'foo2',\n             'status':xmlrpc.Faults.SUCCESS,\n             'description': 'OK'},\n            {'name':'failed',\n             'group':'failed_group',\n             'status':xmlrpc.Faults.BAD_NAME,\n             'description':'FAILED'}\n            ]\n\nclass DummyPGroupConfig:\n    def __init__(self, options, name='whatever', priority=999, pconfigs=None):\n        self.options = options\n        self.name = name\n        self.priority = priority\n        if pconfigs is None:\n            pconfigs = []\n        self.process_configs = pconfigs\n        self.after_setuid_called = False\n        self.pool_events = []\n        self.buffer_size = 10\n\n    def after_setuid(self):\n        self.after_setuid_called = True\n\n    def make_group(self):\n        return DummyProcessGroup(self)\n\n    def __repr__(self):\n        return '<%s instance at %s named %s>' % (self.__class__, id(self),\n                                                 self.name)\n\nclass DummyFCGIGroupConfig(DummyPGroupConfig):\n    def __init__(self, options, name='whatever', priority=999, pconfigs=None, socket_config=DummySocketConfig(1)):\n        DummyPGroupConfig.__init__(self, options, name, priority, pconfigs)\n        self.socket_config = socket_config\n\n@functools.total_ordering\nclass DummyProcessGroup(object):\n    def __init__(self, config):\n        self.config = config\n        self.transitioned = False\n        self.all_stopped = False\n        self.dispatchers = {}\n        self.unstopped_processes = []\n        self.before_remove_called = False\n\n    def transition(self):\n        self.transitioned = True\n\n    def before_remove(self):\n        self.before_remove_called = True\n\n    def stop_all(self):\n        self.all_stopped = True\n\n    def get_unstopped_processes(self):\n        return self.unstopped_processes\n\n    def get_dispatchers(self):\n        return self.dispatchers\n\n    def __lt__(self, other):\n        return self.config.priority < other.config.priority\n\n    def __eq__(self, other):\n        return self.config.priority == other.config.priority\n\n    def reopenlogs(self):\n        self.logs_reopened = True\n\nclass DummyFCGIProcessGroup(DummyProcessGroup):\n\n    def __init__(self, config):\n        DummyProcessGroup.__init__(self, config)\n        self.socket_manager = DummySocketManager(config.socket_config)\n\nclass PopulatedDummySupervisor(DummySupervisor):\n    def __init__(self, options, group_name, *pconfigs):\n        DummySupervisor.__init__(self, options)\n        self.process_groups = {}\n        processes = {}\n        self.group_name = group_name\n        gconfig = DummyPGroupConfig(options, group_name, pconfigs=pconfigs)\n        pgroup = DummyProcessGroup(gconfig)\n        self.process_groups[group_name] = pgroup\n        for pconfig in pconfigs:\n            process = DummyProcess(pconfig)\n            processes[pconfig.name] = process\n        pgroup.processes = processes\n\n    def set_procattr(self, process_name, attr_name, val, group_name=None):\n        if group_name is None:\n            group_name = self.group_name\n        process = self.process_groups[group_name].processes[process_name]\n        setattr(process, attr_name, val)\n\n    def reap(self):\n        self.reaped = True\n\nclass DummyDispatcher:\n    flush_exception = None\n\n    write_event_handled = False\n    read_event_handled = False\n    error_handled = False\n    logs_reopened = False\n    logs_removed = False\n    closed = False\n    flushed = False\n\n    def __init__(self, readable=False, writable=False, error=False):\n        self._readable = readable\n        self._writable = writable\n        self._error = error\n        self.input_buffer = ''\n        if readable:\n            # only readable dispatchers should have these methods\n            def reopenlogs():\n                self.logs_reopened = True\n            self.reopenlogs = reopenlogs\n            def removelogs():\n                self.logs_removed = True\n            self.removelogs = removelogs\n\n    def readable(self):\n        return self._readable\n    def writable(self):\n        return self._writable\n    def handle_write_event(self):\n        if self._error:\n            raise self._error\n        self.write_event_handled = True\n    def handle_read_event(self):\n        if self._error:\n            raise self._error\n        self.read_event_handled = True\n    def handle_error(self):\n        self.error_handled = True\n    def close(self):\n        self.closed = True\n    def flush(self):\n        if self.flush_exception:\n            raise self.flush_exception\n        self.flushed = True\n\nclass DummyStream:\n    def __init__(self, error=None, fileno=20):\n        self.error = error\n        self.closed = False\n        self.flushed = False\n        self.written = b''\n        self._fileno = fileno\n    def close(self):\n        if self.error:\n            raise self.error\n        self.closed = True\n    def flush(self):\n        if self.error:\n            raise self.error\n        self.flushed = True\n    def write(self, msg):\n        if self.error:\n            error = self.error\n            self.error = None\n            raise error\n        self.written += as_bytes(msg)\n    def seek(self, num, whence=0):\n        pass\n    def tell(self):\n        return len(self.written)\n    def fileno(self):\n        return self._fileno\n\nclass DummyEvent:\n    def __init__(self, serial='abc'):\n        if serial is not None:\n            self.serial = serial\n\n    def payload(self):\n        return 'dummy event'\n\nclass DummyPoller:\n    def __init__(self, options):\n        self.result = [], []\n        self.closed = False\n\n    def register_readable(self, fd):\n        pass\n\n    def register_writable(self, fd):\n        pass\n\n    def poll(self, timeout):\n        return self.result\n\n    def close(self):\n        self.closed = True\n\ndef dummy_handler(event, result):\n    pass\n\ndef rejecting_handler(event, result):\n    from supervisor.dispatchers import RejectEvent\n    raise RejectEvent(result)\n\ndef exception_handler(event, result):\n    raise ValueError(result)\n\ndef lstrip(s):\n    strings = [x.strip() for x in s.split('\\n')]\n    return '\\n'.join(strings)\n"
  },
  {
    "path": "supervisor/tests/fixtures/donothing.conf",
    "content": "[supervisord]\nlogfile=/tmp/donothing.log ; (main log file;default $CWD/supervisord.log)\npidfile=/tmp/donothing.pid ; (supervisord pidfile;default supervisord.pid)\nnodaemon=true              ; (start in foreground if true;default false)\n\n"
  },
  {
    "path": "supervisor/tests/fixtures/example/included.conf",
    "content": "[supervisord]\nchildlogdir = %(here)s\n"
  },
  {
    "path": "supervisor/tests/fixtures/hello.sh",
    "content": "#!/bin/bash\nn=0\nwhile [ $n -lt 10 ]; do\n    let n=n+1\n    echo \"The Øresund bridge ends in Malmö - $n\"\n    sleep 1\ndone\n"
  },
  {
    "path": "supervisor/tests/fixtures/include.conf",
    "content": "[include]\nfiles = ./example/included.conf\n\n[supervisord]\nlogfile = %(here)s\n"
  },
  {
    "path": "supervisor/tests/fixtures/issue-1054.conf",
    "content": "[supervisord]\nloglevel = debug\nlogfile=/tmp/issue-1054.log\npidfile=/tmp/issue-1054.pid\nnodaemon = true\n\n[unix_http_server]\nfile=/tmp/issue-1054.sock     ; the path to the socket file\n\n[supervisorctl]\nserverurl=unix:///tmp/issue-1054.sock ; use a unix:// URL  for a unix socket\n\n[rpcinterface:supervisor]\nsupervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface\n\n[program:cat]\ncommand = /bin/cat\nstartsecs = 0\n\n"
  },
  {
    "path": "supervisor/tests/fixtures/issue-1170a.conf",
    "content": "[supervisord]\nnodaemon=true                ; start in foreground if true; default false\nloglevel=debug                ; log level; default info; others: debug,warn,trace\nlogfile=/tmp/issue-1170a.log  ; main log file; default $CWD/supervisord.log\npidfile=/tmp/issue-1170a.pid  ; supervisord pidfile; default supervisord.pid\nenvironment=FOO=\"set from [supervisord] section\"\n\n[program:echo]\ncommand=bash -c \"echo '%(ENV_FOO)s'\"\nstartsecs=0\nstartretries=0\nautorestart=false\n"
  },
  {
    "path": "supervisor/tests/fixtures/issue-1170b.conf",
    "content": "[supervisord]\nnodaemon=true                ; start in foreground if true; default false\nloglevel=debug                ; log level; default info; others: debug,warn,trace\nlogfile=/tmp/issue-1170b.log  ; main log file; default $CWD/supervisord.log\npidfile=/tmp/issue-1170b.pid  ; supervisord pidfile; default supervisord.pid\nenvironment=FOO=\"set from [supervisord] section\"\n\n[program:echo]\ncommand=bash -c \"echo '%(ENV_FOO)s'\"\nenvironment=FOO=\"set from [program] section\"\nstartsecs=0\nstartretries=0\nautorestart=false\n"
  },
  {
    "path": "supervisor/tests/fixtures/issue-1170c.conf",
    "content": "[supervisord]\nnodaemon=true                ; start in foreground if true; default false\nloglevel=debug                ; log level; default info; others: debug,warn,trace\nlogfile=/tmp/issue-1170c.log  ; main log file; default $CWD/supervisord.log\npidfile=/tmp/issue-1170c.pid  ; supervisord pidfile; default supervisord.pid\nenvironment=FOO=\"set from [supervisord] section\"\n\n[eventlistener:echo]\ncommand=bash -c \"echo '%(ENV_FOO)s' >&2\"\nenvironment=FOO=\"set from [eventlistener] section\"\nevents=PROCESS_STATE_FATAL\nstartsecs=0\nstartretries=0\nautorestart=false\n"
  },
  {
    "path": "supervisor/tests/fixtures/issue-1224.conf",
    "content": "[supervisord]\nnodaemon = true\npidfile = /tmp/issue-1224.pid\nnodaemon = true\nlogfile = /dev/stdout\nlogfile_maxbytes = 0\n\n[program:cat]\ncommand = /bin/cat\nstartsecs = 0\n\n"
  },
  {
    "path": "supervisor/tests/fixtures/issue-1231a.conf",
    "content": "[supervisord]\nloglevel=info                ; log level; default info; others: debug,warn,trace\nlogfile=/tmp/issue-1231a.log ; main log file; default $CWD/supervisord.log\npidfile=/tmp/issue-1231a.pid ; supervisord pidfile; default supervisord.pid\nnodaemon=true                ; start in foreground if true; default false\n\n[rpcinterface:supervisor]\nsupervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface\n\n[unix_http_server]\nfile=/tmp/issue-1231a.sock   ; the path to the socket file\n\n[supervisorctl]\nserverurl=unix:///tmp/issue-1231a.sock  ; use a unix:// URL  for a unix socket\n\n[program:hello]\ncommand=python %(here)s/test_1231.py\n"
  },
  {
    "path": "supervisor/tests/fixtures/issue-1231b.conf",
    "content": "[supervisord]\nloglevel=info                ; log level; default info; others: debug,warn,trace\nlogfile=/tmp/issue-1231b.log ; main log file; default $CWD/supervisord.log\npidfile=/tmp/issue-1231b.pid ; supervisord pidfile; default supervisord.pid\nnodaemon=true                ; start in foreground if true; default false\n\n[rpcinterface:supervisor]\nsupervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface\n\n[unix_http_server]\nfile=/tmp/issue-1231b.sock   ; the path to the socket file\n\n[supervisorctl]\nserverurl=unix:///tmp/issue-1231b.sock  ; use a unix:// URL  for a unix socket\n\n[program:hello]\ncommand=python %(here)s/test_1231.py\n"
  },
  {
    "path": "supervisor/tests/fixtures/issue-1231c.conf",
    "content": "[supervisord]\nloglevel=info                ; log level; default info; others: debug,warn,trace\nlogfile=/tmp/issue-1231c.log ; main log file; default $CWD/supervisord.log\npidfile=/tmp/issue-1231c.pid ; supervisord pidfile; default supervisord.pid\nnodaemon=true                ; start in foreground if true; default false\n\n[rpcinterface:supervisor]\nsupervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface\n\n[unix_http_server]\nfile=/tmp/issue-1231c.sock   ; the path to the socket file\n\n[supervisorctl]\nserverurl=unix:///tmp/issue-1231c.sock  ; use a unix:// URL  for a unix socket\n\n[program:hello]\ncommand=python %(here)s/test_1231.py\n"
  },
  {
    "path": "supervisor/tests/fixtures/issue-1298.conf",
    "content": "[supervisord]\nnodaemon=true                ; start in foreground if true; default false\n\n[rpcinterface:supervisor]\nsupervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface\n\n[unix_http_server]\nfile=/tmp/issue-1298.sock    ; the path to the socket file\n\n[supervisorctl]\nserverurl=unix:///tmp/issue-1298.sock   ; use a unix:// URL  for a unix socket\n\n[program:spew]\ncommand=python %(here)s/spew.py\n"
  },
  {
    "path": "supervisor/tests/fixtures/issue-1483a.conf",
    "content": "[supervisord]\nloglevel=info                ; log level; default info; others: debug,warn,trace\nlogfile=/tmp/issue-1483a.log ; main log file; default $CWD/supervisord.log\npidfile=/tmp/issue-1483a.pid ; supervisord pidfile; default supervisord.pid\nnodaemon=true                ; start in foreground if true; default false\n\n[rpcinterface:supervisor]\nsupervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface\n\n[unix_http_server]\nfile=/tmp/issue-1483a.sock   ; the path to the socket file\n"
  },
  {
    "path": "supervisor/tests/fixtures/issue-1483b.conf",
    "content": "[supervisord]\nloglevel=info                ; log level; default info; others: debug,warn,trace\nlogfile=/tmp/issue-1483b.log ; main log file; default $CWD/supervisord.log\npidfile=/tmp/issue-1483b.pid ; supervisord pidfile; default supervisord.pid\nnodaemon=true                ; start in foreground if true; default false\nidentifier=from_config_file\n\n[rpcinterface:supervisor]\nsupervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface\n\n[unix_http_server]\nfile=/tmp/issue-1483b.sock   ; the path to the socket file\n"
  },
  {
    "path": "supervisor/tests/fixtures/issue-1483c.conf",
    "content": "[supervisord]\nloglevel=info                ; log level; default info; others: debug,warn,trace\nlogfile=/tmp/issue-1483c.log ; main log file; default $CWD/supervisord.log\npidfile=/tmp/issue-1483c.pid ; supervisord pidfile; default supervisord.pid\nnodaemon=true                ; start in foreground if true; default false\nidentifier=from_config_file\n\n[rpcinterface:supervisor]\nsupervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface\n\n[unix_http_server]\nfile=/tmp/issue-1483c.sock   ; the path to the socket file\n"
  },
  {
    "path": "supervisor/tests/fixtures/issue-1596.conf",
    "content": "[supervisord]\nloglevel=info                ; log level; default info; others: debug,warn,trace\nlogfile=/tmp/issue-1596.log  ; main log file; default $CWD/supervisord.log\npidfile=/tmp/issue-1596.pid  ; supervisord pidfile; default supervisord.pid\nnodaemon=true                ; start in foreground if true; default false\nidentifier=from_config_file\n\n[rpcinterface:supervisor]\nsupervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface\n\n[unix_http_server]\nfile=/tmp/issue-1596.sock    ; the path to the socket file\n"
  },
  {
    "path": "supervisor/tests/fixtures/issue-291a.conf",
    "content": "[supervisord]\nloglevel=debug               ; log level; default info; others: debug,warn,trace\nlogfile=/tmp/issue-291a.log  ; main log file; default $CWD/supervisord.log\npidfile=/tmp/issue-291a.pid  ; supervisord pidfile; default supervisord.pid\nnodaemon=true                ; start in foreground if true; default false\n\n[program:print_env]\ncommand=python %(here)s/print_env.py\nstartsecs=0\nautorestart=false\n"
  },
  {
    "path": "supervisor/tests/fixtures/issue-550.conf",
    "content": "[supervisord]\nloglevel=info                ; log level; default info; others: debug,warn,trace\nlogfile=/tmp/issue-550.log   ; main log file; default $CWD/supervisord.log\npidfile=/tmp/issue-550.pid   ; supervisord pidfile; default supervisord.pid\nnodaemon=true                ; start in foreground if true; default false\nenvironment=THIS_SHOULD=BE_IN_CHILD_ENV\n\n[rpcinterface:supervisor]\nsupervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface\n\n[unix_http_server]\nfile=/tmp/issue-550.sock     ; the path to the socket file\n\n[supervisorctl]\nserverurl=unix:///tmp/issue-550.sock ; use a unix:// URL  for a unix socket\n\n[program:print_env]\ncommand=python %(here)s/print_env.py\nstartsecs=0\nstartretries=0\nautorestart=false\n"
  },
  {
    "path": "supervisor/tests/fixtures/issue-565.conf",
    "content": "[supervisord]\nloglevel=info                ; log level; default info; others: debug,warn,trace\nlogfile=/tmp/issue-565.log   ; main log file; default $CWD/supervisord.log\npidfile=/tmp/issue-565.pid   ; supervisord pidfile; default supervisord.pid\nnodaemon=true                ; start in foreground if true; default false\n\n[rpcinterface:supervisor]\nsupervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface\n\n[unix_http_server]\nfile=/tmp/issue-565.sock     ; the path to the socket file\n\n[supervisorctl]\nserverurl=unix:///tmp/issue-565.sock ; use a unix:// URL  for a unix socket\n\n[program:hello]\ncommand=bash %(here)s/hello.sh\nstdout_events_enabled=true\nstartretries=0\nautorestart=false\n\n[eventlistener:listener]\ncommand=python %(here)s/listener.py\nevents=PROCESS_LOG\nstartretries=0\nautorestart=false\n"
  },
  {
    "path": "supervisor/tests/fixtures/issue-638.conf",
    "content": "[supervisord]\nloglevel=debug               ; log level; default info; others: debug,warn,trace\nlogfile=/tmp/issue-638.log   ; main log file; default $CWD/supervisord.log\npidfile=/tmp/issue-638.pid   ; supervisord pidfile; default supervisord.pid\nnodaemon=true                ; start in foreground if true; default false\n\n[program:produce-unicode-error]\ncommand=bash -c 'echo -e \"\\x88\"'\nstartretries=0\nautorestart=false\n\n"
  },
  {
    "path": "supervisor/tests/fixtures/issue-663.conf",
    "content": "[supervisord]\nloglevel=debug\nlogfile=/tmp/issue-663.log\npidfile=/tmp/issue-663.pid\nnodaemon=true\n\n[eventlistener:listener]\ncommand=python %(here)s/listener.py\nevents=TICK_5\nstartretries=0\nautorestart=false\n\n"
  },
  {
    "path": "supervisor/tests/fixtures/issue-664.conf",
    "content": "[supervisord]\nloglevel=debug\nlogfile=/tmp/issue-664.log\npidfile=/tmp/issue-664.pid\nnodaemon=true\n\n[rpcinterface:supervisor]\nsupervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface\n\n[unix_http_server]\nfile=/tmp/issue-664.sock     ; the path to the socket file\n\n[supervisorctl]\nserverurl=unix:///tmp/issue-664.sock ; use a unix:// URL  for a unix socket\n\n[program:test_öäü]\ncommand = /bin/cat\nstartretries = 0\nautorestart = false\n\n"
  },
  {
    "path": "supervisor/tests/fixtures/issue-733.conf",
    "content": "[supervisord]\nloglevel=debug               ; log level; default info; others: debug,warn,trace\nlogfile=/tmp/issue-733.log   ; main log file; default $CWD/supervisord.log\npidfile=/tmp/issue-733.pid   ; supervisord pidfile; default supervisord.pid\nnodaemon=true                ; start in foreground if true; default false\n\n;\n;This command does not exist so the process will enter the FATAL state.\n;\n[program:nonexistent]\ncommand=%(here)s/nonexistent\nstartsecs=0\nstartretries=0\nautorestart=false\n\n;\n;The one-line eventlistener below will cause supervisord to exit when any process\n;enters the FATAL state.  Based on:\n;https://github.com/Supervisor/supervisor/issues/733#issuecomment-781254766\n;\n;Differences from that example:\n;  1. $PPID is used instead of a hardcoded PID 1.  Child processes are always forked\n;     from supervisord, so their PPID is the PID of supervisord.\n;  2. \"printf\" is used instead of \"echo\".  The result \"OK\" must not have a newline\n;     or else the protocol will be violated and supervisord will log a warning.\n;\n[eventlistener:fatalexit]\nevents=PROCESS_STATE_FATAL\ncommand=sh -c 'while true; do printf \"READY\\n\"; read line; kill -15 $PPID; printf \"RESULT 2\\n\"; printf \"OK\"; done'\nstartsecs=0\nstartretries=0\n"
  },
  {
    "path": "supervisor/tests/fixtures/issue-835.conf",
    "content": "[supervisord]\nloglevel = debug\nlogfile=/tmp/issue-835.log\npidfile=/tmp/issue-835.pid\nnodaemon = true\n\n[rpcinterface:supervisor]\nsupervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface\n\n[unix_http_server]\nfile=/tmp/issue-835.sock   ; the path to the socket file\n\n[program:cat]\ncommand = /bin/cat\nstartretries = 0\nautorestart = false\n\n"
  },
  {
    "path": "supervisor/tests/fixtures/issue-836.conf",
    "content": "[supervisord]\nloglevel = debug\nlogfile=/tmp/supervisord.log\npidfile=/tmp/supervisord.pid\nnodaemon = true\n\n[rpcinterface:supervisor]\nsupervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface\n\n[unix_http_server]\nfile=/tmp/issue-565.sock     ; the path to the socket file\n\n[supervisorctl]\nserverurl=unix:///tmp/issue-565.sock ; use a unix:// URL  for a unix socket\n\n[program:cat]\ncommand = /bin/cat\nstartretries = 0\nautorestart = false\n\n"
  },
  {
    "path": "supervisor/tests/fixtures/issue-986.conf",
    "content": "[supervisord]\nloglevel=debug               ; log level; default info; others: debug,warn,trace\nlogfile=/tmp/issue-986.log   ; main log file; default $CWD/supervisord.log\npidfile=/tmp/issue-986.pid   ; supervisord pidfile; default supervisord.pid\nnodaemon=true                ; start in foreground if true; default false\n\n[rpcinterface:supervisor]\nsupervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface\n\n[unix_http_server]\nfile=/tmp/issue-986.sock   ; the path to the socket file\n\n[supervisorctl]\nserverurl=unix:///tmp/issue-986.sock  ; use a unix:// URL  for a unix socket\n\n[program:echo]\ncommand=bash -c \"echo 'dhcrelay -d -q -a %%h:%%p %%P -i Vlan1000 192.168.0.1'\"\nstartsecs=0\nautorestart=false\n"
  },
  {
    "path": "supervisor/tests/fixtures/listener.py",
    "content": "\nimport sys\n\ndef write_and_flush(stream, s):\n    stream.write(s)\n    stream.flush()\n\ndef write_stdout(s):\n    # only eventlistener protocol messages may be sent to stdout\n    sys.stdout.write(s)\n    sys.stdout.flush()\n\ndef write_stderr(s):\n    sys.stderr.write(s)\n    sys.stderr.flush()\n\ndef main():\n    stdin = sys.stdin\n    stdout = sys.stdout\n    stderr = sys.stderr\n    while True:\n        # transition from ACKNOWLEDGED to READY\n        write_and_flush(stdout, 'READY\\n')\n\n        # read header line and print it to stderr\n        line = stdin.readline()\n        write_and_flush(stderr, line)\n\n        # read event payload and print it to stderr\n        headers = dict([ x.split(':') for x in line.split() ])\n        data = stdin.read(int(headers['len']))\n        write_and_flush(stderr, data)\n\n        # transition from READY to ACKNOWLEDGED\n        write_and_flush(stdout, 'RESULT 2\\nOK')\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "supervisor/tests/fixtures/print_env.py",
    "content": "#!<<PYTHON>>\nimport os\n\nfor k, v in os.environ.items():\n    print(\"%s=%s\" % (k,v))\n"
  },
  {
    "path": "supervisor/tests/fixtures/spew.py",
    "content": "#!<<PYTHON>>\nimport sys\nimport time\n\ncounter = 0\n\nwhile counter < 30000:\n    sys.stdout.write(\"more spewage %d\\n\" % counter)\n    sys.stdout.flush()\n    time.sleep(0.01)\n    counter += 1\n"
  },
  {
    "path": "supervisor/tests/fixtures/test_1231.py",
    "content": "# -*- coding: utf-8 -*-\nimport logging\nimport random\nimport sys\nimport time\n\ndef main():\n    logging.basicConfig(level=logging.INFO, stream=sys.stdout,\n                        format='%(levelname)s [%(asctime)s] %(message)s',\n                        datefmt='%m-%d|%H:%M:%S')\n    i = 1\n    while i < 500:\n        delay = random.randint(400, 1200)\n        time.sleep(delay / 1000.0)\n        logging.info('%d - hash=57d94b…381088', i)\n        i += 1\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "supervisor/tests/fixtures/unkillable_spew.py",
    "content": "#!<<PYTHON>>\nimport time\nimport signal\nsignal.signal(signal.SIGTERM, signal.SIG_IGN)\n\ncounter = 0\n\nwhile 1:\n   time.sleep(0.01)\n   print(\"more spewage %s\" % counter)\n   counter += 1\n   \n"
  },
  {
    "path": "supervisor/tests/test_childutils.py",
    "content": "from io import BytesIO\nimport sys\nimport time\nimport unittest\nfrom supervisor.compat import StringIO\nfrom supervisor.compat import as_string\n\nclass ChildUtilsTests(unittest.TestCase):\n    def test_getRPCInterface(self):\n        from supervisor.childutils import getRPCInterface\n        rpc = getRPCInterface({'SUPERVISOR_SERVER_URL':'http://localhost:9001'})\n        # we can't really test this thing; its a magic object\n        self.assertTrue(rpc is not None)\n\n    def test_getRPCTransport_no_uname_pass(self):\n        from supervisor.childutils import getRPCTransport\n        t = getRPCTransport({'SUPERVISOR_SERVER_URL':'http://localhost:9001'})\n        self.assertEqual(t.username, '')\n        self.assertEqual(t.password, '')\n        self.assertEqual(t.serverurl, 'http://localhost:9001')\n\n    def test_getRPCTransport_with_uname_pass(self):\n        from supervisor.childutils import getRPCTransport\n        env = {'SUPERVISOR_SERVER_URL':'http://localhost:9001',\n               'SUPERVISOR_USERNAME':'chrism',\n               'SUPERVISOR_PASSWORD':'abc123'}\n        t = getRPCTransport(env)\n        self.assertEqual(t.username, 'chrism')\n        self.assertEqual(t.password, 'abc123')\n        self.assertEqual(t.serverurl, 'http://localhost:9001')\n\n    def test_get_headers(self):\n        from supervisor.childutils import get_headers\n        line = 'a:1 b:2'\n        result = get_headers(line)\n        self.assertEqual(result, {'a':'1', 'b':'2'})\n\n    def test_eventdata(self):\n        from supervisor.childutils import eventdata\n        payload = 'a:1 b:2\\nthedata\\n'\n        headers, data = eventdata(payload)\n        self.assertEqual(headers, {'a':'1', 'b':'2'})\n        self.assertEqual(data, 'thedata\\n')\n\n    def test_get_asctime(self):\n        from supervisor.childutils import get_asctime\n        timestamp = time.mktime((2009, 1, 18, 22, 14, 7, 0, 0, -1))\n        result = get_asctime(timestamp)\n        self.assertEqual(result, '2009-01-18 22:14:07,000')\n\nclass TestProcessCommunicationsProtocol(unittest.TestCase):\n    def test_send(self):\n        from supervisor.childutils import pcomm\n        stdout = BytesIO()\n        pcomm.send(b'hello', stdout)\n        from supervisor.events import ProcessCommunicationEvent\n        begin = ProcessCommunicationEvent.BEGIN_TOKEN\n        end = ProcessCommunicationEvent.END_TOKEN\n        self.assertEqual(stdout.getvalue(), begin + b'hello' + end)\n\n    def test_stdout(self):\n        from supervisor.childutils import pcomm\n        old = sys.stdout\n        try:\n            io = sys.stdout = BytesIO()\n            pcomm.stdout(b'hello')\n            from supervisor.events import ProcessCommunicationEvent\n            begin = ProcessCommunicationEvent.BEGIN_TOKEN\n            end = ProcessCommunicationEvent.END_TOKEN\n            self.assertEqual(io.getvalue(), begin + b'hello' + end)\n        finally:\n            sys.stdout = old\n\n    def test_stderr(self):\n        from supervisor.childutils import pcomm\n        old = sys.stderr\n        try:\n            io = sys.stderr = BytesIO()\n            pcomm.stderr(b'hello')\n            from supervisor.events import ProcessCommunicationEvent\n            begin = ProcessCommunicationEvent.BEGIN_TOKEN\n            end = ProcessCommunicationEvent.END_TOKEN\n            self.assertEqual(io.getvalue(), begin + b'hello' + end)\n        finally:\n            sys.stderr = old\n\nclass TestEventListenerProtocol(unittest.TestCase):\n    def test_wait(self):\n        from supervisor.childutils import listener\n        class Dummy:\n            def readline(self):\n                return 'len:5'\n            def read(self, *ignored):\n                return 'hello'\n        stdin = Dummy()\n        stdout = StringIO()\n        headers, payload = listener.wait(stdin, stdout)\n        self.assertEqual(headers, {'len':'5'})\n        self.assertEqual(payload, 'hello')\n        self.assertEqual(stdout.getvalue(), 'READY\\n')\n\n    def test_token(self):\n        from supervisor.childutils import listener\n        from supervisor.dispatchers import PEventListenerDispatcher\n        token = as_string(PEventListenerDispatcher.READY_FOR_EVENTS_TOKEN)\n        stdout = StringIO()\n        listener.ready(stdout)\n        self.assertEqual(stdout.getvalue(), token)\n\n    def test_ok(self):\n        from supervisor.childutils import listener\n        from supervisor.dispatchers import PEventListenerDispatcher\n        begin = as_string(PEventListenerDispatcher.RESULT_TOKEN_START)\n        stdout = StringIO()\n        listener.ok(stdout)\n        self.assertEqual(stdout.getvalue(), begin + '2\\nOK')\n\n    def test_fail(self):\n        from supervisor.childutils import listener\n        from supervisor.dispatchers import PEventListenerDispatcher\n        begin = as_string(PEventListenerDispatcher.RESULT_TOKEN_START)\n        stdout = StringIO()\n        listener.fail(stdout)\n        self.assertEqual(stdout.getvalue(), begin + '4\\nFAIL')\n\n    def test_send(self):\n        from supervisor.childutils import listener\n        from supervisor.dispatchers import PEventListenerDispatcher\n        begin = as_string(PEventListenerDispatcher.RESULT_TOKEN_START)\n        stdout = StringIO()\n        msg = 'the body data ya fool\\n'\n        listener.send(msg, stdout)\n        expected = '%s%s\\n%s' % (begin, len(msg), msg)\n        self.assertEqual(stdout.getvalue(), expected)\n"
  },
  {
    "path": "supervisor/tests/test_confecho.py",
    "content": "\"\"\"Test suite for supervisor.confecho\"\"\"\n\nimport unittest\nfrom supervisor.compat import StringIO\nfrom supervisor import confecho\n\nclass TopLevelFunctionTests(unittest.TestCase):\n    def test_main_writes_data_out_that_looks_like_a_config_file(self):\n        sio = StringIO()\n        confecho.main(out=sio)\n\n        output = sio.getvalue()\n        self.assertTrue(\"[supervisord]\" in output)\n"
  },
  {
    "path": "supervisor/tests/test_datatypes.py",
    "content": "\"\"\"Test suite for supervisor.datatypes\"\"\"\n\nimport os\nimport signal\nimport socket\nimport tempfile\nimport unittest\n\nfrom supervisor.tests.base import Mock, patch, sentinel\nfrom supervisor.compat import maxint\nfrom supervisor.compat import shlex_posix_works\n\nfrom supervisor import datatypes\n\nclass ProcessOrGroupName(unittest.TestCase):\n    def _callFUT(self, arg):\n        return datatypes.process_or_group_name(arg)\n\n    def test_strips_surrounding_whitespace(self):\n        name = \" foo\\t\"\n        self.assertEqual(self._callFUT(name), \"foo\")\n\n    def test_disallows_inner_spaces_for_eventlistener_protocol(self):\n        name = \"foo bar\"\n        self.assertRaises(ValueError, self._callFUT, name)\n\n    def test_disallows_colons_for_eventlistener_protocol(self):\n        name = \"foo:bar\"\n        self.assertRaises(ValueError, self._callFUT, name)\n\n    def test_disallows_slashes_for_web_ui_urls(self):\n        name = \"foo/bar\"\n        self.assertRaises(ValueError, self._callFUT, name)\n\nclass IntegerTests(unittest.TestCase):\n    def _callFUT(self, arg):\n        return datatypes.integer(arg)\n\n    def test_converts_numeric(self):\n        self.assertEqual(self._callFUT('1'), 1)\n\n    def test_converts_numeric_overflowing_int(self):\n        self.assertEqual(self._callFUT(str(maxint+1)), maxint+1)\n\n    def test_raises_for_non_numeric(self):\n        self.assertRaises(ValueError, self._callFUT, 'abc')\n\nclass BooleanTests(unittest.TestCase):\n    def _callFUT(self, arg):\n        return datatypes.boolean(arg)\n\n    def test_returns_true_for_truthy_values(self):\n        for s in datatypes.TRUTHY_STRINGS:\n            self.assertEqual(self._callFUT(s), True)\n\n    def test_returns_true_for_upper_truthy_values(self):\n        for s in map(str.upper, datatypes.TRUTHY_STRINGS):\n            self.assertEqual(self._callFUT(s), True)\n\n    def test_returns_false_for_falsy_values(self):\n        for s in datatypes.FALSY_STRINGS:\n            self.assertEqual(self._callFUT(s), False)\n\n    def test_returns_false_for_upper_falsy_values(self):\n        for s in map(str.upper, datatypes.FALSY_STRINGS):\n            self.assertEqual(self._callFUT(s), False)\n\n    def test_braises_value_error_for_bad_value(self):\n        self.assertRaises(ValueError,\n                          self._callFUT, 'not-a-value')\n\nclass ListOfStringsTests(unittest.TestCase):\n    def _callFUT(self, arg):\n        return datatypes.list_of_strings(arg)\n\n    def test_returns_empty_list_for_empty_string(self):\n        self.assertEqual(self._callFUT(''), [])\n\n    def test_returns_list_of_strings_by_comma_split(self):\n        self.assertEqual(self._callFUT('foo,bar'), ['foo', 'bar'])\n\n    def test_returns_strings_with_whitespace_stripped(self):\n        self.assertEqual(self._callFUT(' foo , bar '), ['foo', 'bar'])\n\n    def test_raises_value_error_when_comma_split_fails(self):\n        self.assertRaises(ValueError,\n                          self._callFUT, 42)\n\nclass ListOfIntsTests(unittest.TestCase):\n    def _callFUT(self, arg):\n        return datatypes.list_of_ints(arg)\n\n    def test_returns_empty_list_for_empty_string(self):\n        self.assertEqual(self._callFUT(''), [])\n\n    def test_returns_list_of_ints_by_comma_split(self):\n        self.assertEqual(self._callFUT('1,42'), [1,42])\n\n    def test_returns_ints_even_if_whitespace_in_string(self):\n        self.assertEqual(self._callFUT(' 1 , 42 '), [1,42])\n\n    def test_raises_value_error_when_comma_split_fails(self):\n        self.assertRaises(ValueError,\n                          self._callFUT, 42)\n\n    def test_raises_value_error_when_one_value_is_bad(self):\n        self.assertRaises(ValueError,\n                          self._callFUT, '1, bad, 42')\n\nclass ListOfExitcodesTests(unittest.TestCase):\n    def _callFUT(self, arg):\n        return datatypes.list_of_exitcodes(arg)\n\n    def test_returns_list_of_ints_from_csv(self):\n        self.assertEqual(self._callFUT('1,2,3'), [1,2,3])\n\n    def test_returns_list_of_ints_from_one(self):\n        self.assertEqual(self._callFUT('1'), [1])\n\n    def test_raises_for_invalid_exitcode_values(self):\n        self.assertRaises(ValueError, self._callFUT, 'a,b,c')\n        self.assertRaises(ValueError, self._callFUT, '1024')\n        self.assertRaises(ValueError, self._callFUT, '-1,1')\n\nclass DictOfKeyValuePairsTests(unittest.TestCase):\n    def _callFUT(self, arg):\n        return datatypes.dict_of_key_value_pairs(arg)\n\n    def test_returns_empty_dict_for_empty_str(self):\n        actual = self._callFUT('')\n        self.assertEqual({}, actual)\n\n    def test_returns_dict_from_single_pair_str(self):\n        actual = self._callFUT('foo=bar')\n        expected = {'foo': 'bar'}\n        self.assertEqual(actual, expected)\n\n    def test_returns_dict_from_multi_pair_str(self):\n        actual = self._callFUT('foo=bar,baz=qux')\n        expected = {'foo': 'bar', 'baz': 'qux'}\n        self.assertEqual(actual, expected)\n\n    def test_returns_dict_even_if_whitespace(self):\n        actual = self._callFUT(' foo = bar , baz = qux ')\n        expected = {'foo': 'bar', 'baz': 'qux'}\n        self.assertEqual(actual, expected)\n\n    def test_returns_dict_even_if_newlines(self):\n        actual = self._callFUT('foo\\n=\\nbar\\n,\\nbaz\\n=\\nqux')\n        expected = {'foo': 'bar', 'baz': 'qux'}\n        self.assertEqual(actual, expected)\n\n    def test_handles_commas_inside_apostrophes(self):\n        actual = self._callFUT(\"foo='bar,baz',baz='q,ux'\")\n        expected = {'foo': 'bar,baz', 'baz': 'q,ux'}\n        self.assertEqual(actual, expected)\n\n    def test_handles_commas_inside_quotes(self):\n        actual = self._callFUT('foo=\"bar,baz\",baz=\"q,ux\"')\n        expected = {'foo': 'bar,baz', 'baz': 'q,ux'}\n        self.assertEqual(actual, expected)\n\n    def test_handles_newlines_inside_quotes(self):\n        actual = datatypes.dict_of_key_value_pairs('foo=\"a\\nb\\nc\"')\n        expected = {'foo': 'a\\nb\\nc'}\n        self.assertEqual(actual, expected)\n\n    def test_handles_quotes_inside_quotes(self):\n        func = lambda: datatypes.dict_of_key_value_pairs('foo=\"\\'\\\\\"\"')\n\n        if shlex_posix_works:\n            actual = func()\n            expected = {'foo': '\\'\"'}\n            self.assertEqual(actual, expected)\n        else:\n            self.assertRaises(ValueError, func)\n\n    def test_handles_empty_inside_quotes(self):\n        actual = datatypes.dict_of_key_value_pairs('foo=\"\"')\n        expected = {'foo': ''}\n        self.assertEqual(actual, expected)\n\n    def test_handles_empty_inside_quotes_with_second_unquoted_pair(self):\n        actual = datatypes.dict_of_key_value_pairs('foo=\"\",bar=a')\n        expected = {'foo': '', 'bar': 'a'}\n        self.assertEqual(actual, expected)\n\n    def test_handles_unquoted_non_alphanum(self):\n        actual = self._callFUT(\n            'HOME=/home/auser,FOO=/.foo+(1.2)-_/,'\n            'SUPERVISOR_SERVER_URL=http://127.0.0.1:9001')\n        expected = {'HOME': '/home/auser', 'FOO': '/.foo+(1.2)-_/',\n                    'SUPERVISOR_SERVER_URL': 'http://127.0.0.1:9001'}\n        self.assertEqual(actual, expected)\n\n    def test_allows_trailing_comma(self):\n        actual = self._callFUT('foo=bar,')\n        expected = {'foo': 'bar'}\n        self.assertEqual(actual, expected)\n\n    def test_raises_value_error_on_too_short(self):\n        self.assertRaises(ValueError,\n                          self._callFUT, 'foo')\n        self.assertRaises(ValueError,\n                          self._callFUT, 'foo=')\n        self.assertRaises(ValueError,\n                          self._callFUT, 'foo=bar,baz')\n        self.assertRaises(ValueError,\n                          self._callFUT, 'foo=bar,baz=')\n\n    def test_raises_when_comma_is_missing(self):\n        kvp = 'KEY1=no-comma KEY2=ends-with-comma,'\n        self.assertRaises(ValueError,\n                          self._callFUT, kvp)\n\nclass LogfileNameTests(unittest.TestCase):\n    def _callFUT(self, arg):\n        return datatypes.logfile_name(arg)\n\n    def test_returns_none_for_none_values(self):\n        for thing in datatypes.LOGFILE_NONES:\n            actual = self._callFUT(thing)\n            self.assertEqual(actual, None)\n\n    def test_returns_none_for_uppered_none_values(self):\n        for thing in datatypes.LOGFILE_NONES:\n            if hasattr(thing, 'upper'):\n                thing = thing.upper()\n            actual = self._callFUT(thing)\n            self.assertEqual(actual, None)\n\n    def test_returns_automatic_for_auto_values(self):\n        for thing in datatypes.LOGFILE_AUTOS:\n            actual = self._callFUT(thing)\n            self.assertEqual(actual, datatypes.Automatic)\n\n    def test_returns_automatic_for_uppered_auto_values(self):\n        for thing in datatypes.LOGFILE_AUTOS:\n            if hasattr(thing, 'upper'):\n                thing = thing.upper()\n            actual = self._callFUT(thing)\n            self.assertEqual(actual, datatypes.Automatic)\n\n    def test_returns_syslog_for_syslog_values(self):\n        for thing in datatypes.LOGFILE_SYSLOGS:\n            actual = self._callFUT(thing)\n            self.assertEqual(actual, datatypes.Syslog)\n\n    def test_returns_syslog_for_uppered_syslog_values(self):\n        for thing in datatypes.LOGFILE_SYSLOGS:\n            if hasattr(thing, 'upper'):\n                thing = thing.upper()\n            actual = self._callFUT(thing)\n            self.assertEqual(actual, datatypes.Syslog)\n\n    def test_returns_existing_dirpath_for_other_values(self):\n        func = datatypes.existing_dirpath\n        datatypes.existing_dirpath = lambda path: path\n        try:\n            path = '/path/to/logfile/With/Case/Preserved'\n            actual = self._callFUT(path)\n            self.assertEqual(actual, path)\n        finally:\n            datatypes.existing_dirpath = func\n\nclass RangeCheckedConversionTests(unittest.TestCase):\n    def _getTargetClass(self):\n        return datatypes.RangeCheckedConversion\n\n    def _makeOne(self, conversion, min=None, max=None):\n        return self._getTargetClass()(conversion, min, max)\n\n    def test_below_lower_bound(self):\n        conversion = self._makeOne(lambda *arg: -1, 0)\n        self.assertRaises(ValueError, conversion, None)\n\n    def test_above_upper_lower_bound(self):\n        conversion = self._makeOne(lambda *arg: 1, 0, 0)\n        self.assertRaises(ValueError, conversion, None)\n\n    def test_passes(self):\n        conversion = self._makeOne(lambda *arg: 0, 0, 0)\n        self.assertEqual(conversion(0), 0)\n\n\nclass NameToGidTests(unittest.TestCase):\n    def _callFUT(self, arg):\n        return datatypes.name_to_gid(arg)\n\n    @patch(\"grp.getgrnam\", Mock(return_value=[0,0,42]))\n    def test_gets_gid_from_group_name(self):\n        gid = self._callFUT(\"foo\")\n        self.assertEqual(gid, 42)\n\n    @patch(\"grp.getgrgid\", Mock(return_value=[0,0,42]))\n    def test_gets_gid_from_group_id(self):\n        gid = self._callFUT(\"42\")\n        self.assertEqual(gid, 42)\n\n    @patch(\"grp.getgrnam\", Mock(side_effect=KeyError(\"bad group name\")))\n    def test_raises_for_bad_group_name(self):\n        self.assertRaises(ValueError, self._callFUT, \"foo\")\n\n    @patch(\"grp.getgrgid\", Mock(side_effect=KeyError(\"bad group id\")))\n    def test_raises_for_bad_group_id(self):\n        self.assertRaises(ValueError, self._callFUT, \"42\")\n\nclass NameToUidTests(unittest.TestCase):\n    def _callFUT(self, arg):\n        return datatypes.name_to_uid(arg)\n\n    @patch(\"pwd.getpwnam\", Mock(return_value=[0,0,42]))\n    def test_gets_uid_from_username(self):\n        uid = self._callFUT(\"foo\")\n        self.assertEqual(uid, 42)\n\n    @patch(\"pwd.getpwuid\", Mock(return_value=[0,0,42]))\n    def test_gets_uid_from_user_id(self):\n        uid = self._callFUT(\"42\")\n        self.assertEqual(uid, 42)\n\n    @patch(\"pwd.getpwnam\", Mock(side_effect=KeyError(\"bad username\")))\n    def test_raises_for_bad_username(self):\n        self.assertRaises(ValueError, self._callFUT, \"foo\")\n\n    @patch(\"pwd.getpwuid\", Mock(side_effect=KeyError(\"bad user id\")))\n    def test_raises_for_bad_user_id(self):\n        self.assertRaises(ValueError, self._callFUT, \"42\")\n\nclass OctalTypeTests(unittest.TestCase):\n    def _callFUT(self, arg):\n        return datatypes.octal_type(arg)\n\n    def test_success(self):\n        self.assertEqual(self._callFUT('10'), 8)\n\n    def test_raises_for_non_numeric(self):\n        try:\n            self._callFUT('bad')\n            self.fail()\n        except ValueError as e:\n            expected = 'bad can not be converted to an octal type'\n            self.assertEqual(e.args[0], expected)\n\n    def test_raises_for_unconvertable_numeric(self):\n        try:\n            self._callFUT('1.2')\n            self.fail()\n        except ValueError as e:\n            expected = '1.2 can not be converted to an octal type'\n            self.assertEqual(e.args[0], expected)\n\nclass ExistingDirectoryTests(unittest.TestCase):\n    def _callFUT(self, arg):\n        return datatypes.existing_directory(arg)\n\n    def test_dir_exists(self):\n        path = os.path.dirname(__file__)\n        self.assertEqual(path, self._callFUT(path))\n\n    def test_dir_does_not_exist(self):\n        path = os.path.join(os.path.dirname(__file__), 'nonexistent')\n        try:\n            self._callFUT(path)\n            self.fail()\n        except ValueError as e:\n            expected = \"%s is not an existing directory\" % path\n            self.assertEqual(e.args[0], expected)\n\n    def test_not_a_directory(self):\n        path = __file__\n        try:\n            self._callFUT(path)\n            self.fail()\n        except ValueError as e:\n            expected = \"%s is not an existing directory\" % path\n            self.assertEqual(e.args[0], expected)\n\n    def test_expands_home(self):\n        home = os.path.expanduser('~')\n        if os.path.exists(home):\n            path = self._callFUT('~')\n            self.assertEqual(home, path)\n\nclass ExistingDirpathTests(unittest.TestCase):\n    def _callFUT(self, arg):\n        return datatypes.existing_dirpath(arg)\n\n    def test_returns_existing_dirpath(self):\n        self.assertEqual(self._callFUT(__file__), __file__)\n\n    def test_returns_dirpath_if_relative(self):\n        self.assertEqual(self._callFUT('foo'), 'foo')\n\n    def test_raises_if_dir_does_not_exist(self):\n        path = os.path.join(os.path.dirname(__file__), 'nonexistent', 'foo')\n        try:\n            self._callFUT(path)\n            self.fail()\n        except ValueError as e:\n            expected = ('The directory named as part of the path %s '\n                        'does not exist' % path)\n            self.assertEqual(e.args[0], expected)\n\n    def test_raises_if_exists_but_not_a_dir(self):\n        path = os.path.join(os.path.dirname(__file__),\n                            os.path.basename(__file__), 'foo')\n        try:\n            self._callFUT(path)\n            self.fail()\n        except ValueError as e:\n            expected = ('The directory named as part of the path %s '\n                        'does not exist' % path)\n            self.assertEqual(e.args[0], expected)\n\n    def test_expands_home(self):\n        home = os.path.expanduser('~')\n        if os.path.exists(home):\n            path = self._callFUT('~/foo')\n            self.assertEqual(os.path.join(home, 'foo'), path)\n\nclass LoggingLevelTests(unittest.TestCase):\n    def _callFUT(self, arg):\n        return datatypes.logging_level(arg)\n\n    def test_returns_level_from_name_case_insensitive(self):\n        from supervisor.loggers import LevelsByName\n        self.assertEqual(self._callFUT(\"wArN\"), LevelsByName.WARN)\n\n    def test_raises_for_bad_level_name(self):\n        self.assertRaises(ValueError,\n                          self._callFUT, \"foo\")\n\nclass UrlTests(unittest.TestCase):\n    def _callFUT(self, arg):\n        return datatypes.url(arg)\n\n    def test_accepts_urlparse_recognized_scheme_with_netloc(self):\n        good_url = 'http://localhost:9001'\n        self.assertEqual(self._callFUT(good_url), good_url)\n\n    def test_rejects_urlparse_recognized_scheme_but_no_netloc(self):\n        bad_url = 'http://'\n        self.assertRaises(ValueError, self._callFUT, bad_url)\n\n    def test_accepts_unix_scheme_with_path(self):\n        good_url = \"unix://somepath\"\n        self.assertEqual(good_url, self._callFUT(good_url))\n\n    def test_rejects_unix_scheme_with_no_slashes_or_path(self):\n        bad_url = \"unix:\"\n        self.assertRaises(ValueError, self._callFUT, bad_url)\n\n    def test_rejects_unix_scheme_with_slashes_but_no_path(self):\n        bad_url = \"unix://\"\n        self.assertRaises(ValueError, self._callFUT, bad_url)\n\nclass InetStreamSocketConfigTests(unittest.TestCase):\n    def _getTargetClass(self):\n        return datatypes.InetStreamSocketConfig\n\n    def _makeOne(self, *args, **kw):\n        return self._getTargetClass()(*args, **kw)\n\n    def test_url(self):\n        conf = self._makeOne('127.0.0.1', 8675)\n        self.assertEqual(conf.url, 'tcp://127.0.0.1:8675')\n\n    def test___str__(self):\n        cfg = self._makeOne('localhost', 65531)\n        self.assertEqual(str(cfg), 'tcp://localhost:65531')\n\n    def test_repr(self):\n        conf = self._makeOne('127.0.0.1', 8675)\n        s = repr(conf)\n        self.assertTrue('supervisor.datatypes.InetStreamSocketConfig' in s)\n        self.assertTrue(s.endswith('for tcp://127.0.0.1:8675>'), s)\n\n    def test_addr(self):\n        conf = self._makeOne('127.0.0.1', 8675)\n        addr = conf.addr()\n        self.assertEqual(addr, ('127.0.0.1', 8675))\n\n    def test_port_as_string(self):\n        conf = self._makeOne('localhost', '5001')\n        addr = conf.addr()\n        self.assertEqual(addr, ('localhost', 5001))\n\n    def test_create_and_bind(self):\n        conf = self._makeOne('127.0.0.1', 8675)\n        sock = conf.create_and_bind()\n        reuse = sock.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR)\n        self.assertTrue(reuse)\n        self.assertEqual(conf.addr(), sock.getsockname()) #verifies that bind was called\n        sock.close()\n\n    def test_same_urls_are_equal(self):\n        conf1 = self._makeOne('localhost', 5001)\n        conf2 = self._makeOne('localhost', 5001)\n        self.assertTrue(conf1 == conf2)\n        self.assertFalse(conf1 != conf2)\n\n    def test_diff_urls_are_not_equal(self):\n        conf1 = self._makeOne('localhost', 5001)\n        conf2 = self._makeOne('localhost', 5002)\n        self.assertTrue(conf1 != conf2)\n        self.assertFalse(conf1 == conf2)\n\n    def test_diff_objs_are_not_equal(self):\n        conf1 = self._makeOne('localhost', 5001)\n        conf2 = 'blah'\n        self.assertTrue(conf1 != conf2)\n        self.assertFalse(conf1 == conf2)\n\nclass UnixStreamSocketConfigTests(unittest.TestCase):\n    def _getTargetClass(self):\n        return datatypes.UnixStreamSocketConfig\n\n    def _makeOne(self, *args, **kw):\n        return self._getTargetClass()(*args, **kw)\n\n    def test_url(self):\n        conf = self._makeOne('/tmp/foo.sock')\n        self.assertEqual(conf.url, 'unix:///tmp/foo.sock')\n\n    def test___str__(self):\n        cfg = self._makeOne('foo/bar')\n        self.assertEqual(str(cfg), 'unix://foo/bar')\n\n    def test_repr(self):\n        conf = self._makeOne('/tmp/foo.sock')\n        s = repr(conf)\n        self.assertTrue('supervisor.datatypes.UnixStreamSocketConfig' in s)\n        self.assertTrue(s.endswith('for unix:///tmp/foo.sock>'), s)\n\n    def test_get_addr(self):\n        conf = self._makeOne('/tmp/foo.sock')\n        addr = conf.addr()\n        self.assertEqual(addr, '/tmp/foo.sock')\n\n    def test_create_and_bind(self):\n        (tf_fd, tf_name) = tempfile.mkstemp()\n        owner = (sentinel.uid, sentinel.gid)\n        mode = sentinel.mode\n        conf = self._makeOne(tf_name, owner=owner, mode=mode)\n\n        # Patch os.chmod and os.chown functions with mocks\n        # objects so that the test does not depend on\n        # any specific system users or permissions\n        chown_mock = Mock()\n        chmod_mock = Mock()\n        @patch('os.chown', chown_mock)\n        @patch('os.chmod', chmod_mock)\n        def call_create_and_bind(conf):\n            return conf.create_and_bind()\n\n        sock = call_create_and_bind(conf)\n        self.assertTrue(os.path.exists(tf_name))\n        # verifies that bind was called\n        self.assertEqual(conf.addr(), sock.getsockname())\n        sock.close()\n        self.assertTrue(os.path.exists(tf_name))\n        os.unlink(tf_name)\n        # Verify that os.chown was called with correct args\n        self.assertEqual(1, chown_mock.call_count)\n        path_arg = chown_mock.call_args[0][0]\n        uid_arg = chown_mock.call_args[0][1]\n        gid_arg = chown_mock.call_args[0][2]\n        self.assertEqual(tf_name, path_arg)\n        self.assertEqual(owner[0], uid_arg)\n        self.assertEqual(owner[1], gid_arg)\n        # Verify that os.chmod was called with correct args\n        self.assertEqual(1, chmod_mock.call_count)\n        path_arg = chmod_mock.call_args[0][0]\n        mode_arg = chmod_mock.call_args[0][1]\n        self.assertEqual(tf_name, path_arg)\n        self.assertEqual(mode, mode_arg)\n\n    def test_create_and_bind_when_chown_fails(self):\n        (tf_fd, tf_name) = tempfile.mkstemp()\n        owner = (sentinel.uid, sentinel.gid)\n        mode = sentinel.mode\n        conf = self._makeOne(tf_name, owner=owner, mode=mode)\n\n        @patch('os.chown', Mock(side_effect=OSError(\"msg\")))\n        @patch('os.chmod', Mock())\n        def call_create_and_bind(conf):\n            return conf.create_and_bind()\n\n        try:\n            call_create_and_bind(conf)\n            self.fail()\n        except ValueError as e:\n            expected = \"Could not change ownership of socket file: msg\"\n            self.assertEqual(e.args[0], expected)\n            self.assertFalse(os.path.exists(tf_name))\n\n    def test_create_and_bind_when_chmod_fails(self):\n        (tf_fd, tf_name) = tempfile.mkstemp()\n        owner = (sentinel.uid, sentinel.gid)\n        mode = sentinel.mode\n        conf = self._makeOne(tf_name, owner=owner, mode=mode)\n\n        @patch('os.chown', Mock())\n        @patch('os.chmod', Mock(side_effect=OSError(\"msg\")))\n        def call_create_and_bind(conf):\n            return conf.create_and_bind()\n\n        try:\n            call_create_and_bind(conf)\n            self.fail()\n        except ValueError as e:\n            expected = \"Could not change permissions of socket file: msg\"\n            self.assertEqual(e.args[0], expected)\n            self.assertFalse(os.path.exists(tf_name))\n\n    def test_same_paths_are_equal(self):\n        conf1 = self._makeOne('/tmp/foo.sock')\n        conf2 = self._makeOne('/tmp/foo.sock')\n        self.assertTrue(conf1 == conf2)\n        self.assertFalse(conf1 != conf2)\n\n    def test_diff_paths_are_not_equal(self):\n        conf1 = self._makeOne('/tmp/foo.sock')\n        conf2 = self._makeOne('/tmp/bar.sock')\n        self.assertTrue(conf1 != conf2)\n        self.assertFalse(conf1 == conf2)\n\n    def test_diff_objs_are_not_equal(self):\n        conf1 = self._makeOne('/tmp/foo.sock')\n        conf2 = 'blah'\n        self.assertTrue(conf1 != conf2)\n        self.assertFalse(conf1 == conf2)\n\nclass InetAddressTests(unittest.TestCase):\n    def _callFUT(self, s):\n        return datatypes.inet_address(s)\n\n    def test_no_port_number(self):\n        self.assertRaises(ValueError, self._callFUT, 'a:')\n\n    def test_bad_port_number(self):\n        self.assertRaises(ValueError, self._callFUT, 'a')\n\n    def test_default_host(self):\n        host, port = self._callFUT('*:9001')\n        self.assertEqual(host, '')\n        self.assertEqual(port, 9001)\n\n    def test_hostname_and_port(self):\n        host, port = self._callFUT('localhost:9001')\n        self.assertEqual(host, 'localhost')\n        self.assertEqual(port, 9001)\n\n    def test_ipv4_address_and_port(self):\n        host, port = self._callFUT('127.0.0.1:9001')\n        self.assertEqual(host, '127.0.0.1')\n        self.assertEqual(port, 9001)\n\n    def test_ipv6_address_and_port(self):\n        host, port = self._callFUT('2001:db8:ff:55:0:0:0:138:9001')\n        self.assertEqual(host, '2001:db8:ff:55:0:0:0:138')\n        self.assertEqual(port, 9001)\n\nclass SocketAddressTests(unittest.TestCase):\n    def _getTargetClass(self):\n        return datatypes.SocketAddress\n\n    def _makeOne(self, s):\n        return self._getTargetClass()(s)\n\n    def test_unix_socket(self):\n        addr = self._makeOne('/foo/bar')\n        self.assertEqual(addr.family, socket.AF_UNIX)\n        self.assertEqual(addr.address, '/foo/bar')\n\n    def test_inet_socket(self):\n        addr = self._makeOne('localhost:8080')\n        self.assertEqual(addr.family, socket.AF_INET)\n        self.assertEqual(addr.address, ('localhost', 8080))\n\nclass ColonSeparatedUserGroupTests(unittest.TestCase):\n    def _callFUT(self, arg):\n        return datatypes.colon_separated_user_group(arg)\n\n    def test_ok_username(self):\n        self.assertEqual(self._callFUT('root')[0], 0)\n\n    def test_missinguser_username(self):\n        self.assertRaises(ValueError,\n                          self._callFUT, 'godihopethisuserdoesntexist')\n\n    def test_missinguser_username_and_groupname(self):\n        self.assertRaises(ValueError,\n                          self._callFUT, 'godihopethisuserdoesntexist:foo')\n\n    def test_separated_user_group_returns_both(self):\n        name_to_uid = Mock(return_value=12)\n        name_to_gid = Mock(return_value=34)\n\n        @patch(\"supervisor.datatypes.name_to_uid\", name_to_uid)\n        @patch(\"supervisor.datatypes.name_to_gid\", name_to_gid)\n        def colon_separated(value):\n            return self._callFUT(value)\n\n        uid, gid = colon_separated(\"foo:bar\")\n        name_to_uid.assert_called_with(\"foo\")\n        self.assertEqual(12, uid)\n        name_to_gid.assert_called_with(\"bar\")\n        self.assertEqual(34, gid)\n\n    def test_separated_user_group_returns_user_only(self):\n        name_to_uid = Mock(return_value=42)\n\n        @patch(\"supervisor.datatypes.name_to_uid\", name_to_uid)\n        def colon_separated(value):\n            return self._callFUT(value)\n\n        uid, gid = colon_separated(\"foo\")\n        name_to_uid.assert_called_with(\"foo\")\n        self.assertEqual(42, uid)\n        self.assertEqual(-1, gid)\n\nclass SignalNumberTests(unittest.TestCase):\n    def _callFUT(self, arg):\n        return datatypes.signal_number(arg)\n\n    def test_converts_number(self):\n        self.assertEqual(self._callFUT(signal.SIGTERM), signal.SIGTERM)\n\n    def test_converts_name(self):\n        self.assertEqual(self._callFUT(' term '), signal.SIGTERM)\n\n    def test_converts_signame(self):\n        self.assertEqual(self._callFUT('SIGTERM'), signal.SIGTERM)\n\n    def test_raises_for_bad_number(self):\n        try:\n            self._callFUT('12345678')\n            self.fail()\n        except ValueError as e:\n            expected = \"value '12345678' is not a valid signal number\"\n            self.assertEqual(e.args[0], expected)\n\n    def test_raises_for_bad_name(self):\n        try:\n            self._callFUT('BADSIG')\n            self.fail()\n        except ValueError as e:\n            expected = \"value 'BADSIG' is not a valid signal name\"\n            self.assertEqual(e.args[0], expected)\n\nclass AutoRestartTests(unittest.TestCase):\n    def _callFUT(self, arg):\n        return datatypes.auto_restart(arg)\n\n    def test_converts_truthy(self):\n        for s in datatypes.TRUTHY_STRINGS:\n            result = self._callFUT(s)\n            self.assertEqual(result, datatypes.RestartUnconditionally)\n\n    def test_converts_falsy(self):\n        for s in datatypes.FALSY_STRINGS:\n            self.assertFalse(self._callFUT(s))\n\n    def test_converts_unexpected(self):\n        for s in ('unexpected', 'UNEXPECTED'):\n            result = self._callFUT(s)\n            self.assertEqual(result, datatypes.RestartWhenExitUnexpected)\n\n    def test_raises_for_bad_value(self):\n        try:\n            self._callFUT('bad')\n            self.fail()\n        except ValueError as e:\n            self.assertEqual(e.args[0], \"invalid 'autorestart' value 'bad'\")\n\nclass ProfileOptionsTests(unittest.TestCase):\n    def _callFUT(self, arg):\n        return datatypes.profile_options(arg)\n\n    def test_empty(self):\n        sort_options, callers = self._callFUT('')\n        self.assertEqual([], sort_options)\n        self.assertFalse(callers)\n\n    def test_without_callers(self):\n        sort_options, callers = self._callFUT('CUMULATIVE,calls')\n        self.assertEqual(['cumulative', 'calls'], sort_options)\n        self.assertFalse(callers)\n\n    def test_with_callers(self):\n        sort_options, callers = self._callFUT('cumulative, callers')\n        self.assertEqual(['cumulative'], sort_options)\n        self.assertTrue(callers)\n"
  },
  {
    "path": "supervisor/tests/test_dispatchers.py",
    "content": "import unittest\nimport os\n\nfrom supervisor.compat import as_bytes\n\nfrom supervisor.tests.base import DummyOptions\nfrom supervisor.tests.base import DummyProcess\nfrom supervisor.tests.base import DummyPConfig\nfrom supervisor.tests.base import DummyLogger\nfrom supervisor.tests.base import DummyEvent\n\nclass PDispatcherTests(unittest.TestCase):\n    def setUp(self):\n        from supervisor.events import clear\n        clear()\n\n    def tearDown(self):\n        from supervisor.events import clear\n        clear()\n\n    def _getTargetClass(self):\n        from supervisor.dispatchers import PDispatcher\n        return PDispatcher\n\n    def _makeOne(self, process=None, channel='stdout', fd=0):\n        return self._getTargetClass()(process, channel, fd)\n\n    def test_readable(self):\n        inst = self._makeOne()\n        self.assertRaises(NotImplementedError, inst.readable)\n\n    def test_writable(self):\n        inst = self._makeOne()\n        self.assertRaises(NotImplementedError, inst.writable)\n\n    def test_flush(self):\n        inst = self._makeOne()\n        self.assertEqual(inst.flush(), None)\n\nclass POutputDispatcherTests(unittest.TestCase):\n    def setUp(self):\n        from supervisor.events import clear\n        clear()\n\n    def tearDown(self):\n        from supervisor.events import clear\n        clear()\n\n    def _getTargetClass(self):\n        from supervisor.dispatchers import POutputDispatcher\n        return POutputDispatcher\n\n    def _makeOne(self, process, channel='stdout'):\n        from supervisor import events\n        events = {'stdout': events.ProcessCommunicationStdoutEvent,\n                  'stderr': events.ProcessCommunicationStderrEvent}\n        # dispatcher derives its channel from event class\n        return self._getTargetClass()(process, events[channel], 0)\n\n    def test_writable(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1')\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        self.assertEqual(dispatcher.writable(), False)\n\n    def test_readable_open(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1')\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        dispatcher.closed = False\n        self.assertEqual(dispatcher.readable(), True)\n\n    def test_readable_closed(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1')\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        dispatcher.closed = True\n        self.assertEqual(dispatcher.readable(), False)\n\n    def test_handle_write_event(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1')\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        self.assertRaises(NotImplementedError, dispatcher.handle_write_event)\n\n    def test_handle_read_event(self):\n        options = DummyOptions()\n        options.readfd_result = b'abc'\n        config = DummyPConfig(options, 'process1', '/bin/process1',\n                              stdout_capture_maxbytes=100)\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        self.assertEqual(dispatcher.handle_read_event(), None)\n        self.assertEqual(dispatcher.output_buffer, b'abc')\n\n    def test_handle_read_event_no_data_closes(self):\n        options = DummyOptions()\n        options.readfd_result = b''\n        config = DummyPConfig(options, 'process1', '/bin/process1',\n                              stdout_capture_maxbytes=100)\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        self.assertFalse(dispatcher.closed)\n        self.assertEqual(dispatcher.handle_read_event(), None)\n        self.assertEqual(dispatcher.output_buffer, b'')\n        self.assertTrue(dispatcher.closed)\n\n    def test_handle_error(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'test', '/test')\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        try:\n            raise ValueError('foo')\n        except:\n            dispatcher.handle_error()\n        result = options.logger.data[0]\n        self.assertTrue(result.startswith(\n            'uncaptured python exception, closing channel'),result)\n\n    def test_toggle_capturemode_sends_event(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1',\n                              stdout_logfile='/tmp/foo',\n                              stdout_capture_maxbytes=500)\n        process = DummyProcess(config)\n        process.pid = 4000\n        dispatcher = self._makeOne(process)\n        dispatcher.capturemode = True\n        dispatcher.capturelog.getvalue = lambda: 'hallooo'\n        L = []\n        def doit(event):\n            L.append(event)\n        from supervisor import events\n        events.subscribe(events.EventTypes.PROCESS_COMMUNICATION, doit)\n        dispatcher.toggle_capturemode()\n        self.assertEqual(len(L), 1)\n        event = L[0]\n        self.assertEqual(event.process, process)\n        self.assertEqual(event.pid, 4000)\n        self.assertEqual(event.data, 'hallooo')\n\n    def test_removelogs(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1',\n                              stdout_logfile='/tmp/foo')\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        dispatcher.removelogs()\n        self.assertEqual(dispatcher.normallog.handlers[0].reopened, True)\n        self.assertEqual(dispatcher.normallog.handlers[0].removed, True)\n        self.assertEqual(dispatcher.childlog.handlers[0].reopened, True)\n        self.assertEqual(dispatcher.childlog.handlers[0].removed, True)\n\n    def test_reopenlogs(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1',\n                              stdout_logfile='/tmp/foo')\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        dispatcher.reopenlogs()\n        self.assertEqual(dispatcher.childlog.handlers[0].reopened, True)\n        self.assertEqual(dispatcher.normallog.handlers[0].reopened, True)\n\n    def test_record_output_log_non_capturemode(self):\n        # stdout/stderr goes to the process log and the main log,\n        # in non-capturemode, the data length doesn't matter\n        options = DummyOptions()\n        from supervisor import loggers\n        options.loglevel = loggers.LevelsByName.TRAC\n        config = DummyPConfig(options, 'process1', '/bin/process1',\n                              stdout_logfile='/tmp/foo')\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        dispatcher.output_buffer = 'a'\n        dispatcher.record_output()\n        self.assertEqual(dispatcher.childlog.data, ['a'])\n        self.assertEqual(options.logger.data[0],\n             \"'process1' stdout output:\\na\")\n        self.assertEqual(dispatcher.output_buffer, b'')\n\n    def test_record_output_emits_stdout_event_when_enabled(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1',\n                              stdout_events_enabled=True)\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process, 'stdout')\n        dispatcher.output_buffer = b'hello from stdout'\n\n        L = []\n        def doit(event):\n            L.append(event)\n        from supervisor import events\n        events.subscribe(events.EventTypes.PROCESS_LOG_STDOUT, doit)\n        dispatcher.record_output()\n\n        self.assertEqual(len(L), 1)\n        event = L[0]\n        self.assertEqual(event.process, process)\n        self.assertEqual(event.data, b'hello from stdout')\n\n    def test_record_output_does_not_emit_stdout_event_when_disabled(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1',\n                              stdout_events_enabled=False)\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process, 'stdout')\n        dispatcher.output_buffer = b'hello from stdout'\n\n        L = []\n        def doit(event):\n            L.append(event)\n        from supervisor import events\n        events.subscribe(events.EventTypes.PROCESS_LOG_STDOUT, doit)\n        dispatcher.record_output()\n\n        self.assertEqual(len(L), 0)\n\n    def test_record_output_emits_stderr_event_when_enabled(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1',\n                              stderr_events_enabled=True)\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process, 'stderr')\n        dispatcher.output_buffer = b'hello from stderr'\n\n        L = []\n        def doit(event):\n            L.append(event)\n        from supervisor import events\n        events.subscribe(events.EventTypes.PROCESS_LOG_STDERR, doit)\n        dispatcher.record_output()\n\n        self.assertEqual(len(L), 1)\n        event = L[0]\n        self.assertEqual(event.process, process)\n        self.assertEqual(event.data, b'hello from stderr')\n\n    def test_record_output_does_not_emit_stderr_event_when_disabled(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1',\n                              stderr_events_enabled=False)\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process, 'stderr')\n        dispatcher.output_buffer = b'hello from stderr'\n\n        L = []\n        def doit(event):\n            L.append(event)\n        from supervisor import events\n        events.subscribe(events.EventTypes.PROCESS_LOG_STDERR, doit)\n        dispatcher.record_output()\n\n        self.assertEqual(len(L), 0)\n\n    def test_record_output_capturemode_string_longer_than_token(self):\n        # stdout/stderr goes to the process log and the main log,\n        # in capturemode, the length of the data needs to be longer\n        # than the capture token to make it out.\n        options = DummyOptions()\n        from supervisor import loggers\n        options.loglevel = loggers.LevelsByName.TRAC\n        config = DummyPConfig(options, 'process1', '/bin/process1',\n                              stdout_logfile='/tmp/foo',\n                              stdout_capture_maxbytes=100)\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        dispatcher.output_buffer = b'stdout string longer than a token'\n        dispatcher.record_output()\n        self.assertEqual(dispatcher.childlog.data,\n                         [b'stdout string longer than a token'])\n        self.assertEqual(options.logger.data[0],\n             \"'process1' stdout output:\\nstdout string longer than a token\")\n\n    def test_record_output_capturemode_string_not_longer_than_token(self):\n        # stdout/stderr goes to the process log and the main log,\n        # in capturemode, the length of the data needs to be longer\n        # than the capture token to make it out.\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1',\n                              stdout_logfile='/tmp/foo',\n                              stdout_capture_maxbytes=100)\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        dispatcher.output_buffer = 'a'\n        dispatcher.record_output()\n        self.assertEqual(dispatcher.childlog.data, [])\n        self.assertEqual(dispatcher.output_buffer, 'a')\n\n    def test_stdout_capturemode_single_buffer(self):\n        # mike reported that comm events that took place within a single\n        # output buffer were broken 8/20/2007\n        from supervisor.events import ProcessCommunicationEvent\n        from supervisor.events import subscribe\n        events = []\n        def doit(event):\n            events.append(event)\n        subscribe(ProcessCommunicationEvent, doit)\n        BEGIN_TOKEN = ProcessCommunicationEvent.BEGIN_TOKEN\n        END_TOKEN = ProcessCommunicationEvent.END_TOKEN\n        data = BEGIN_TOKEN + b'hello' + END_TOKEN\n        options = DummyOptions()\n        from supervisor.loggers import getLogger\n        options.getLogger = getLogger # actually use real logger\n        logfile = '/tmp/log'\n        config = DummyPConfig(options, 'process1', '/bin/process1',\n                              stdout_logfile=logfile,\n                              stdout_capture_maxbytes=1000)\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n\n        try:\n            dispatcher.output_buffer = data\n            dispatcher.record_output()\n            self.assertEqual(os.path.getsize(logfile), 0)\n            self.assertEqual(len(dispatcher.output_buffer), 0)\n            self.assertEqual(len(events), 1)\n\n            event = events[0]\n            from supervisor.events import ProcessCommunicationStdoutEvent\n            self.assertEqual(event.__class__, ProcessCommunicationStdoutEvent)\n            self.assertEqual(event.process, process)\n            self.assertEqual(event.channel, 'stdout')\n            self.assertEqual(event.data, b'hello')\n\n        finally:\n            try:\n                dispatcher.capturelog.close()\n                dispatcher.childlog.close()\n                os.remove(logfile)\n            except (OSError, IOError):\n                pass\n\n    def test_stdout_capturemode_multiple_buffers(self):\n        from supervisor.events import ProcessCommunicationEvent\n        from supervisor.events import subscribe\n        events = []\n        def doit(event):\n            events.append(event)\n        subscribe(ProcessCommunicationEvent, doit)\n        import string\n        # ascii_letters for python 3\n        letters = as_bytes(getattr(string, \"letters\", string.ascii_letters))\n        digits = as_bytes(string.digits) * 4\n        BEGIN_TOKEN = ProcessCommunicationEvent.BEGIN_TOKEN\n        END_TOKEN = ProcessCommunicationEvent.END_TOKEN\n        data = (letters +  BEGIN_TOKEN + digits + END_TOKEN + letters)\n\n        # boundaries that split tokens\n        colon = b':'\n        broken = data.split(colon)\n        first = broken[0] + colon\n        second = broken[1] + colon\n        third = broken[2]\n\n        options = DummyOptions()\n        from supervisor.loggers import getLogger\n        options.getLogger = getLogger # actually use real logger\n        logfile = '/tmp/log'\n        config = DummyPConfig(options, 'process1', '/bin/process1',\n                              stdout_logfile=logfile,\n                              stdout_capture_maxbytes=10000)\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        try:\n            dispatcher.output_buffer = first\n            dispatcher.record_output()\n            [ x.flush() for x in dispatcher.childlog.handlers ]\n            with open(logfile, 'rb') as f:\n                self.assertEqual(f.read(), letters)\n            self.assertEqual(dispatcher.output_buffer, first[len(letters):])\n            self.assertEqual(len(events), 0)\n\n            dispatcher.output_buffer += second\n            dispatcher.record_output()\n            self.assertEqual(len(events), 0)\n            [ x.flush() for x in dispatcher.childlog.handlers ]\n            with open(logfile, 'rb') as f:\n                self.assertEqual(f.read(), letters)\n            self.assertEqual(dispatcher.output_buffer, first[len(letters):])\n            self.assertEqual(len(events), 0)\n\n            dispatcher.output_buffer += third\n            dispatcher.record_output()\n            [ x.flush() for x in dispatcher.childlog.handlers ]\n            with open(logfile, 'rb') as f:\n                self.assertEqual(f.read(), letters * 2)\n            self.assertEqual(len(events), 1)\n            event = events[0]\n            from supervisor.events import ProcessCommunicationStdoutEvent\n            self.assertEqual(event.__class__, ProcessCommunicationStdoutEvent)\n            self.assertEqual(event.process, process)\n            self.assertEqual(event.channel, 'stdout')\n            self.assertEqual(event.data, digits)\n\n        finally:\n            try:\n                dispatcher.capturelog.close()\n                dispatcher.childlog.close()\n                os.remove(logfile)\n            except (OSError, IOError):\n                pass\n\n    def test_strip_ansi(self):\n        options = DummyOptions()\n        options.strip_ansi = True\n        config = DummyPConfig(options, 'process1', '/bin/process1',\n                              stdout_logfile='/tmp/foo')\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        ansi = b'\\x1b[34mHello world... this is longer than a token!\\x1b[0m'\n        noansi = b'Hello world... this is longer than a token!'\n\n        dispatcher.output_buffer = ansi\n        dispatcher.record_output()\n        self.assertEqual(len(dispatcher.childlog.data), 1)\n        self.assertEqual(dispatcher.childlog.data[0], noansi)\n\n        options.strip_ansi = False\n\n        dispatcher.output_buffer = ansi\n        dispatcher.record_output()\n        self.assertEqual(len(dispatcher.childlog.data), 2)\n        self.assertEqual(dispatcher.childlog.data[1], ansi)\n\n    def test_ctor_no_logfiles(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1')\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        self.assertEqual(dispatcher.process, process)\n        self.assertEqual(dispatcher.channel, 'stdout')\n        self.assertEqual(dispatcher.fd, 0)\n        self.assertEqual(dispatcher.capturelog, None)\n        self.assertEqual(dispatcher.normallog, None)\n        self.assertEqual(dispatcher.childlog, None)\n\n    def test_ctor_logfile_only(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1',\n                              stdout_logfile='/tmp/foo')\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        self.assertEqual(dispatcher.process, process)\n        self.assertEqual(dispatcher.channel, 'stdout')\n        self.assertEqual(dispatcher.fd, 0)\n        self.assertEqual(dispatcher.capturelog, None)\n        self.assertEqual(dispatcher.normallog.__class__, DummyLogger)\n        self.assertEqual(dispatcher.childlog, dispatcher.normallog)\n\n    def test_ctor_capturelog_only(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1',\n                              stdout_capture_maxbytes=300)\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        self.assertEqual(dispatcher.process, process)\n        self.assertEqual(dispatcher.channel, 'stdout')\n        self.assertEqual(dispatcher.fd, 0)\n        self.assertEqual(dispatcher.capturelog.__class__, DummyLogger)\n        self.assertEqual(dispatcher.normallog, None)\n        self.assertEqual(dispatcher.childlog, None)\n\n    def test_ctor_stdout_logfile_is_empty_string(self):\n        from supervisor.datatypes import logfile_name\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1',\n                              stdout_logfile=logfile_name(''))\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        self.assertEqual(dispatcher.process, process)\n        self.assertEqual(dispatcher.channel, 'stdout')\n        self.assertEqual(dispatcher.fd, 0)\n        self.assertEqual(dispatcher.normallog, None)\n\n    def test_ctor_stdout_logfile_none_and_stdout_syslog_false(self):\n        from supervisor.datatypes import boolean, logfile_name\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1',\n                              stdout_logfile=logfile_name('NONE'),\n                              stdout_syslog=boolean('false'))\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        self.assertEqual(dispatcher.process, process)\n        self.assertEqual(dispatcher.channel, 'stdout')\n        self.assertEqual(dispatcher.fd, 0)\n        self.assertEqual(dispatcher.normallog, None)\n\n    def test_ctor_stdout_logfile_none_and_stdout_syslog_true(self):\n        from supervisor.datatypes import boolean, logfile_name\n        from supervisor.loggers import LevelsByName, SyslogHandler\n        from supervisor.options import ServerOptions\n        options = ServerOptions() # need real options to get a real logger\n        options.loglevel = LevelsByName.TRAC\n        config = DummyPConfig(options, 'process1', '/bin/process1',\n                              stdout_logfile=logfile_name('NONE'),\n                              stdout_syslog=boolean('true'))\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        self.assertEqual(dispatcher.process, process)\n        self.assertEqual(dispatcher.channel, 'stdout')\n        self.assertEqual(dispatcher.fd, 0)\n        self.assertEqual(len(dispatcher.normallog.handlers), 1)\n        self.assertEqual(dispatcher.normallog.handlers[0].__class__,\n            SyslogHandler)\n\n    def test_ctor_stdout_logfile_str_and_stdout_syslog_false(self):\n        from supervisor.datatypes import boolean, logfile_name\n        from supervisor.loggers import FileHandler, LevelsByName\n        from supervisor.options import ServerOptions\n        options = ServerOptions() # need real options to get a real logger\n        options.loglevel = LevelsByName.TRAC\n        config = DummyPConfig(options, 'process1', '/bin/process1',\n                              stdout_logfile=logfile_name('/tmp/foo'),\n                              stdout_syslog=boolean('false'))\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        self.assertEqual(dispatcher.process, process)\n        self.assertEqual(dispatcher.channel, 'stdout')\n        self.assertEqual(dispatcher.fd, 0)\n        self.assertEqual(len(dispatcher.normallog.handlers), 1)\n        self.assertEqual(dispatcher.normallog.handlers[0].__class__, FileHandler)\n        dispatcher.normallog.close()\n\n    def test_ctor_stdout_logfile_str_and_stdout_syslog_true(self):\n        from supervisor.datatypes import boolean, logfile_name\n        from supervisor.loggers import FileHandler, LevelsByName, SyslogHandler\n        from supervisor.options import ServerOptions\n        options = ServerOptions() # need real options to get a real logger\n        options.loglevel = LevelsByName.TRAC\n        config = DummyPConfig(options, 'process1', '/bin/process1',\n                              stdout_logfile=logfile_name('/tmp/foo'),\n                              stdout_syslog=boolean('true'))\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        self.assertEqual(dispatcher.process, process)\n        self.assertEqual(dispatcher.channel, 'stdout')\n        self.assertEqual(dispatcher.fd, 0)\n        self.assertEqual(len(dispatcher.normallog.handlers), 2)\n        self.assertTrue(any(isinstance(h, FileHandler) for h in\n            dispatcher.normallog.handlers))\n        self.assertTrue(any(isinstance(h, SyslogHandler) for h in\n            dispatcher.normallog.handlers))\n        dispatcher.normallog.close()\n\n    def test_repr(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1')\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        drepr = repr(dispatcher)\n        self.assertTrue('POutputDispatcher' in drepr)\n        self.assertNotEqual(\n            drepr.find('supervisor.tests.base.DummyProcess'),\n            -1)\n        self.assertTrue(drepr.endswith('(stdout)>'), drepr)\n\n    def test_close(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1')\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        dispatcher.close()\n        self.assertEqual(dispatcher.closed, True)\n        dispatcher.close() # make sure we don't error if we try to close twice\n        self.assertEqual(dispatcher.closed, True)\n\n\nclass PInputDispatcherTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.dispatchers import PInputDispatcher\n        return PInputDispatcher\n\n    def _makeOne(self, process):\n        channel = 'stdin'\n        return self._getTargetClass()(process, channel, 0)\n\n    def test_writable_open_nodata(self):\n        process = DummyProcess(None)\n        dispatcher = self._makeOne(process)\n        dispatcher.input_buffer = 'a'\n        dispatcher.closed = False\n        self.assertEqual(dispatcher.writable(), True)\n\n    def test_writable_open_withdata(self):\n        process = DummyProcess(None)\n        dispatcher = self._makeOne(process)\n        dispatcher.input_buffer = ''\n        dispatcher.closed = False\n        self.assertEqual(dispatcher.writable(), False)\n\n    def test_writable_closed_nodata(self):\n        process = DummyProcess(None)\n        dispatcher = self._makeOne(process)\n        dispatcher.input_buffer = 'a'\n        dispatcher.closed = True\n        self.assertEqual(dispatcher.writable(), False)\n\n    def test_writable_closed_withdata(self):\n        process = DummyProcess(None)\n        dispatcher = self._makeOne(process)\n        dispatcher.input_buffer = ''\n        dispatcher.closed = True\n        self.assertEqual(dispatcher.writable(), False)\n\n    def test_readable(self):\n        process = DummyProcess(None)\n        dispatcher = self._makeOne(process)\n        self.assertEqual(dispatcher.readable(), False)\n\n    def test_handle_write_event(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1')\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        dispatcher.input_buffer = 'halloooo'\n        self.assertEqual(dispatcher.handle_write_event(), None)\n        self.assertEqual(options.written[0], 'halloooo')\n\n    def test_handle_write_event_nodata(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'test', '/test')\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        self.assertEqual(dispatcher.input_buffer, b'')\n        dispatcher.handle_write_event()\n        self.assertEqual(dispatcher.input_buffer, b'')\n        self.assertEqual(options.written, {})\n\n    def test_handle_write_event_epipe_raised(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'test', '/test')\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        dispatcher.input_buffer = 'halloooo'\n        import errno\n        options.write_exception = OSError(errno.EPIPE,\n                                          os.strerror(errno.EPIPE))\n        dispatcher.handle_write_event()\n        self.assertEqual(dispatcher.input_buffer, b'')\n        self.assertTrue(options.logger.data[0].startswith(\n            'fd 0 closed, stopped monitoring'))\n        self.assertTrue(options.logger.data[0].endswith('(stdin)>'))\n\n    def test_handle_write_event_uncaught_raised(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'test', '/test')\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        dispatcher.input_buffer = 'halloooo'\n        import errno\n        options.write_exception = OSError(errno.EBADF,\n                                          os.strerror(errno.EBADF))\n        self.assertRaises(OSError, dispatcher.handle_write_event)\n\n    def test_handle_write_event_over_os_limit(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'test', '/test')\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        options.write_accept = 1\n        dispatcher.input_buffer = 'a' * 50\n        dispatcher.handle_write_event()\n        self.assertEqual(len(dispatcher.input_buffer), 49)\n        self.assertEqual(options.written[0], 'a')\n\n    def test_handle_read_event(self):\n        process = DummyProcess(None)\n        dispatcher = self._makeOne(process)\n        self.assertRaises(NotImplementedError, dispatcher.handle_read_event)\n\n    def test_handle_error(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'test', '/test')\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        try:\n            raise ValueError('foo')\n        except:\n            dispatcher.handle_error()\n        result = options.logger.data[0]\n        self.assertTrue(result.startswith(\n            'uncaptured python exception, closing channel'),result)\n\n    def test_repr(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1')\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        drepr = repr(dispatcher)\n        self.assertTrue('PInputDispatcher' in drepr)\n        self.assertNotEqual(\n            drepr.find('supervisor.tests.base.DummyProcess'),\n            -1)\n        self.assertTrue(drepr.endswith('(stdin)>'), drepr)\n\n    def test_close(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1')\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        dispatcher.close()\n        self.assertEqual(dispatcher.closed, True)\n        dispatcher.close() # make sure we don't error if we try to close twice\n        self.assertEqual(dispatcher.closed, True)\n\nclass PEventListenerDispatcherTests(unittest.TestCase):\n    def setUp(self):\n        from supervisor.events import clear\n        clear()\n\n    def tearDown(self):\n        from supervisor.events import clear\n        clear()\n\n    def _getTargetClass(self):\n        from supervisor.dispatchers import PEventListenerDispatcher\n        return PEventListenerDispatcher\n\n    def _makeOne(self, process):\n        channel = 'stdout'\n        return self._getTargetClass()(process, channel, 0)\n\n    def test_writable(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1')\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        self.assertEqual(dispatcher.writable(), False)\n\n    def test_readable_open(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1')\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        dispatcher.closed = False\n        self.assertEqual(dispatcher.readable(), True)\n\n    def test_readable_closed(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1')\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        dispatcher.closed = True\n        self.assertEqual(dispatcher.readable(), False)\n\n    def test_handle_write_event(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1')\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        self.assertRaises(NotImplementedError, dispatcher.handle_write_event)\n\n    def test_handle_read_event_calls_handle_listener_state_change(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1',\n                              stdout_logfile='/tmp/foo')\n        process = DummyProcess(config)\n        from supervisor.dispatchers import EventListenerStates\n        process.listener_state = EventListenerStates.ACKNOWLEDGED\n        dispatcher = self._makeOne(process)\n        options.readfd_result = dispatcher.READY_FOR_EVENTS_TOKEN\n        self.assertEqual(dispatcher.handle_read_event(), None)\n        self.assertEqual(process.listener_state, EventListenerStates.READY)\n        self.assertEqual(dispatcher.state_buffer, b'')\n        self.assertEqual(len(dispatcher.childlog.data), 1)\n        self.assertEqual(dispatcher.childlog.data[0],\n                         dispatcher.READY_FOR_EVENTS_TOKEN)\n\n    def test_handle_read_event_nodata(self):\n        options = DummyOptions()\n        options.readfd_result = ''\n        config = DummyPConfig(options, 'process1', '/bin/process1')\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        self.assertEqual(dispatcher.handle_read_event(), None)\n        self.assertEqual(dispatcher.state_buffer, b'')\n        from supervisor.dispatchers import EventListenerStates\n        self.assertEqual(dispatcher.process.listener_state,\n                         EventListenerStates.ACKNOWLEDGED)\n\n    def test_handle_read_event_logging_nologs(self):\n        options = DummyOptions()\n        options.readfd_result = b'supercalifragilisticexpialidocious'\n        config = DummyPConfig(options, 'process1', '/bin/process1')\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        # just make sure there are no errors if a child logger doesnt\n        # exist\n        self.assertEqual(dispatcher.handle_read_event(), None)\n        self.assertEqual(dispatcher.childlog, None)\n\n    def test_handle_read_event_logging_childlog(self):\n        options = DummyOptions()\n        options.readfd_result = b'supercalifragilisticexpialidocious'\n        config = DummyPConfig(options, 'process1', '/bin/process1',\n                              stdout_logfile='/tmp/foo')\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        self.assertEqual(dispatcher.handle_read_event(), None)\n        self.assertEqual(len(dispatcher.childlog.data), 1)\n        self.assertEqual(dispatcher.childlog.data[0],\n                         b'supercalifragilisticexpialidocious')\n\n    def test_handle_listener_state_change_from_unknown(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1')\n        process = DummyProcess(config)\n        from supervisor.dispatchers import EventListenerStates\n        dispatcher = self._makeOne(process)\n        process.listener_state = EventListenerStates.UNKNOWN\n        dispatcher.state_buffer = b'whatever'\n        self.assertEqual(dispatcher.handle_listener_state_change(), None)\n        self.assertEqual(dispatcher.state_buffer, b'')\n        self.assertEqual(options.logger.data, [])\n        self.assertEqual(process.listener_state, EventListenerStates.UNKNOWN)\n\n    def test_handle_listener_state_change_acknowledged_to_ready(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1')\n        process = DummyProcess(config)\n        from supervisor.dispatchers import EventListenerStates\n        dispatcher = self._makeOne(process)\n        process.listener_state = EventListenerStates.ACKNOWLEDGED\n        dispatcher.state_buffer = b'READY\\n'\n        self.assertEqual(dispatcher.handle_listener_state_change(), None)\n        self.assertEqual(dispatcher.state_buffer, b'')\n        self.assertEqual(options.logger.data[0],\n                         'process1: ACKNOWLEDGED -> READY')\n        self.assertEqual(process.listener_state, EventListenerStates.READY)\n\n    def test_handle_listener_state_change_acknowledged_gobbles(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1')\n        process = DummyProcess(config)\n        from supervisor.dispatchers import EventListenerStates\n        dispatcher = self._makeOne(process)\n        process.listener_state = EventListenerStates.ACKNOWLEDGED\n        dispatcher.state_buffer = b'READY\\ngarbage\\n'\n        self.assertEqual(dispatcher.handle_listener_state_change(), None)\n        self.assertEqual(dispatcher.state_buffer, b'')\n        self.assertEqual(options.logger.data[0],\n                         'process1: ACKNOWLEDGED -> READY')\n        self.assertEqual(options.logger.data[1],\n                         'process1: READY -> UNKNOWN')\n        self.assertEqual(process.listener_state, EventListenerStates.UNKNOWN)\n\n    def test_handle_listener_state_change_acknowledged_to_insufficient(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1')\n        process = DummyProcess(config)\n        from supervisor.dispatchers import EventListenerStates\n        dispatcher = self._makeOne(process)\n        process.listener_state = EventListenerStates.ACKNOWLEDGED\n        dispatcher.state_buffer = b'RE'\n        self.assertEqual(dispatcher.handle_listener_state_change(), None)\n        self.assertEqual(dispatcher.state_buffer, b'RE')\n        self.assertEqual(options.logger.data, [])\n        self.assertEqual(process.listener_state,\n                         EventListenerStates.ACKNOWLEDGED)\n\n    def test_handle_listener_state_change_acknowledged_to_unknown(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1')\n        process = DummyProcess(config)\n        from supervisor.dispatchers import EventListenerStates\n        dispatcher = self._makeOne(process)\n        process.listener_state = EventListenerStates.ACKNOWLEDGED\n        dispatcher.state_buffer = b'bogus data yo'\n        self.assertEqual(dispatcher.handle_listener_state_change(), None)\n        self.assertEqual(dispatcher.state_buffer, b'')\n        self.assertEqual(options.logger.data[0],\n                         'process1: ACKNOWLEDGED -> UNKNOWN')\n        self.assertEqual(options.logger.data[1],\n                         'process1: has entered the UNKNOWN state and will '\n                         'no longer receive events, this usually indicates '\n                         'the process violated the eventlistener protocol')\n        self.assertEqual(process.listener_state, EventListenerStates.UNKNOWN)\n\n    def test_handle_listener_state_change_ready_to_unknown(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1')\n        process = DummyProcess(config)\n        from supervisor.dispatchers import EventListenerStates\n        dispatcher = self._makeOne(process)\n        process.listener_state = EventListenerStates.READY\n        dispatcher.state_buffer = b'bogus data yo'\n        self.assertEqual(dispatcher.handle_listener_state_change(), None)\n        self.assertEqual(dispatcher.state_buffer, b'')\n        self.assertEqual(options.logger.data[0],\n                         'process1: READY -> UNKNOWN')\n        self.assertEqual(options.logger.data[1],\n                         'process1: has entered the UNKNOWN state and will '\n                         'no longer receive events, this usually indicates '\n                         'the process violated the eventlistener protocol')\n        self.assertEqual(process.listener_state, EventListenerStates.UNKNOWN)\n\n    def test_handle_listener_state_change_busy_to_insufficient(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1')\n        process = DummyProcess(config)\n        from supervisor.dispatchers import EventListenerStates\n        dispatcher = self._makeOne(process)\n        process.listener_state = EventListenerStates.BUSY\n        dispatcher.state_buffer = b'bogus data yo'\n        self.assertEqual(dispatcher.handle_listener_state_change(), None)\n        self.assertEqual(dispatcher.state_buffer, b'bogus data yo')\n        self.assertEqual(process.listener_state, EventListenerStates.BUSY)\n\n    def test_handle_listener_state_change_busy_to_acknowledged_procd(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1')\n        process = DummyProcess(config)\n        from supervisor.dispatchers import EventListenerStates\n        dispatcher = self._makeOne(process)\n        process.listener_state = EventListenerStates.BUSY\n        class Dummy:\n            pass\n        process.group = Dummy()\n        process.group.config = Dummy()\n        from supervisor.dispatchers import default_handler\n        process.group.config.result_handler = default_handler\n        dispatcher.state_buffer = b'RESULT 2\\nOKabc'\n        self.assertEqual(dispatcher.handle_listener_state_change(), None)\n        self.assertEqual(dispatcher.state_buffer, b'abc')\n        self.assertEqual(options.logger.data[0],\n                         'process1: event was processed')\n        self.assertEqual(options.logger.data[1],\n                         'process1: BUSY -> ACKNOWLEDGED')\n        self.assertEqual(process.listener_state,\n                         EventListenerStates.ACKNOWLEDGED)\n\n    def test_handle_listener_state_change_busy_to_acknowledged_rejected(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1')\n        process = DummyProcess(config)\n        from supervisor.dispatchers import EventListenerStates\n        dispatcher = self._makeOne(process)\n        process.listener_state = EventListenerStates.BUSY\n        class Dummy:\n            pass\n        process.group = Dummy()\n        process.group.config = Dummy()\n        from supervisor.dispatchers import default_handler\n        process.group.config.result_handler = default_handler\n        dispatcher.state_buffer = b'RESULT 4\\nFAILabc'\n        self.assertEqual(dispatcher.handle_listener_state_change(), None)\n        self.assertEqual(dispatcher.state_buffer, b'abc')\n        self.assertEqual(options.logger.data[0],\n                         'process1: event was rejected')\n        self.assertEqual(options.logger.data[1],\n                         'process1: BUSY -> ACKNOWLEDGED')\n        self.assertEqual(process.listener_state,\n                         EventListenerStates.ACKNOWLEDGED)\n\n    def test_handle_listener_state_change_busy_to_unknown(self):\n        from supervisor.events import EventRejectedEvent\n        from supervisor.events import subscribe\n        events = []\n        def doit(event):\n            events.append(event)\n        subscribe(EventRejectedEvent, doit)\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1')\n        process = DummyProcess(config)\n        from supervisor.dispatchers import EventListenerStates\n        dispatcher = self._makeOne(process)\n        process.listener_state = EventListenerStates.BUSY\n        current_event = DummyEvent()\n        process.event = current_event\n        dispatcher.state_buffer = b'bogus data\\n'\n        self.assertEqual(dispatcher.handle_listener_state_change(), None)\n        self.assertEqual(dispatcher.state_buffer, b'')\n        self.assertEqual(options.logger.data[0],\n                \"process1: bad result line: 'bogus data'\")\n        self.assertEqual(options.logger.data[1],\n                'process1: BUSY -> UNKNOWN')\n        self.assertEqual(options.logger.data[2],\n                         'process1: has entered the UNKNOWN state and will '\n                         'no longer receive events, this usually indicates '\n                         'the process violated the eventlistener protocol')\n        self.assertEqual(process.listener_state,\n                         EventListenerStates.UNKNOWN)\n        self.assertEqual(events[0].process, process)\n        self.assertEqual(events[0].event, current_event)\n\n    def test_handle_listener_state_busy_gobbles(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1')\n        process = DummyProcess(config)\n        from supervisor.dispatchers import EventListenerStates\n        dispatcher = self._makeOne(process)\n        process.listener_state = EventListenerStates.BUSY\n        class Dummy:\n            pass\n        process.group = Dummy()\n        process.group.config = Dummy()\n        from supervisor.dispatchers import default_handler\n        process.group.config.result_handler = default_handler\n        dispatcher.state_buffer = b'RESULT 2\\nOKbogus data\\n'\n        self.assertEqual(dispatcher.handle_listener_state_change(), None)\n        self.assertEqual(dispatcher.state_buffer, b'')\n        self.assertEqual(options.logger.data[0],\n                         'process1: event was processed')\n        self.assertEqual(options.logger.data[1],\n                         'process1: BUSY -> ACKNOWLEDGED')\n        self.assertEqual(options.logger.data[2],\n                         'process1: ACKNOWLEDGED -> UNKNOWN')\n        self.assertEqual(options.logger.data[3],\n                         'process1: has entered the UNKNOWN state and will '\n                         'no longer receive events, this usually indicates '\n                         'the process violated the eventlistener protocol')\n        self.assertEqual(process.listener_state,\n                         EventListenerStates.UNKNOWN)\n\n    def test_handle_result_accept(self):\n        from supervisor.events import subscribe\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1')\n        process = DummyProcess(config)\n        L = []\n        def doit(event):\n            L.append(event)\n        from supervisor import events\n        subscribe(events.EventRejectedEvent, doit)\n        from supervisor.dispatchers import EventListenerStates\n        dispatcher = self._makeOne(process)\n        def handle(event, result):\n            pass\n        class Dummy:\n            pass\n        process.group = Dummy()\n        process.group.config = Dummy()\n        process.group.config.result_handler = handle\n        process.listener_state = EventListenerStates.BUSY\n        dispatcher.handle_result('foo')\n        self.assertEqual(len(L), 0)\n        self.assertEqual(process.listener_state,\n                         EventListenerStates.ACKNOWLEDGED)\n        self.assertEqual(options.logger.data[0],\n                         'process1: event was processed')\n        self.assertEqual(options.logger.data[1],\n                         'process1: BUSY -> ACKNOWLEDGED')\n\n    def test_handle_result_rejectevent(self):\n        from supervisor.events import subscribe\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1')\n        process = DummyProcess(config)\n        L = []\n        def doit(event):\n            L.append(event)\n        from supervisor import events\n        subscribe(events.EventRejectedEvent, doit)\n        from supervisor.dispatchers import EventListenerStates\n        dispatcher = self._makeOne(process)\n        def rejected(event, result):\n            from supervisor.dispatchers import RejectEvent\n            raise RejectEvent(result)\n        class Dummy:\n            pass\n        process.group = Dummy()\n        process.group.config = Dummy()\n        process.group.config.result_handler = rejected\n        process.listener_state = EventListenerStates.BUSY\n        dispatcher.handle_result('foo')\n        self.assertEqual(len(L), 1)\n        self.assertEqual(L[0].__class__, events.EventRejectedEvent)\n        self.assertEqual(process.listener_state,\n                         EventListenerStates.ACKNOWLEDGED)\n        self.assertEqual(options.logger.data[0],\n                         'process1: event was rejected')\n        self.assertEqual(options.logger.data[1],\n                         'process1: BUSY -> ACKNOWLEDGED')\n\n    def test_handle_result_exception(self):\n        from supervisor.events import subscribe\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1')\n        process = DummyProcess(config)\n        L = []\n        def doit(event):\n            L.append(event)\n        from supervisor import events\n        subscribe(events.EventRejectedEvent, doit)\n        from supervisor.dispatchers import EventListenerStates\n        dispatcher = self._makeOne(process)\n        def exception(event, result):\n            raise ValueError\n        class Dummy:\n            pass\n        process.group = Dummy()\n        process.group.config = Dummy()\n        process.group.config.result_handler = exception\n        process.group.result_handler = exception\n        process.listener_state = EventListenerStates.BUSY\n        dispatcher.handle_result('foo')\n        self.assertEqual(len(L), 1)\n        self.assertEqual(L[0].__class__, events.EventRejectedEvent)\n        self.assertEqual(process.listener_state,\n                         EventListenerStates.UNKNOWN)\n        self.assertEqual(options.logger.data[0],\n                         'process1: event caused an error')\n        self.assertEqual(options.logger.data[1],\n                         'process1: BUSY -> UNKNOWN')\n        self.assertEqual(options.logger.data[2],\n                         'process1: has entered the UNKNOWN state and will '\n                         'no longer receive events, this usually indicates '\n                         'the process violated the eventlistener protocol')\n\n    def test_handle_error(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'test', '/test')\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        try:\n            raise ValueError('foo')\n        except:\n            dispatcher.handle_error()\n        result = options.logger.data[0]\n        self.assertTrue(result.startswith(\n            'uncaptured python exception, closing channel'),result)\n\n    def test_removelogs(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1',\n                              stdout_logfile='/tmp/foo')\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        dispatcher.removelogs()\n        self.assertEqual(dispatcher.childlog.handlers[0].reopened, True)\n        self.assertEqual(dispatcher.childlog.handlers[0].removed, True)\n\n    def test_reopenlogs(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1',\n                              stdout_logfile='/tmp/foo')\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        dispatcher.reopenlogs()\n        self.assertEqual(dispatcher.childlog.handlers[0].reopened, True)\n\n    def test_strip_ansi(self):\n        options = DummyOptions()\n        options.strip_ansi = True\n        config = DummyPConfig(options, 'process1', '/bin/process1',\n                              stdout_logfile='/tmp/foo')\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        ansi = b'\\x1b[34mHello world... this is longer than a token!\\x1b[0m'\n        noansi = b'Hello world... this is longer than a token!'\n\n        options.readfd_result = ansi\n        dispatcher.handle_read_event()\n        self.assertEqual(len(dispatcher.childlog.data), 1)\n        self.assertEqual(dispatcher.childlog.data[0], noansi)\n\n        options.strip_ansi = False\n\n        options.readfd_result = ansi\n        dispatcher.handle_read_event()\n        self.assertEqual(len(dispatcher.childlog.data), 2)\n        self.assertEqual(dispatcher.childlog.data[1], ansi)\n\n    def test_ctor_nologfiles(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1')\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        self.assertEqual(dispatcher.process, process)\n        self.assertEqual(dispatcher.channel, 'stdout')\n        self.assertEqual(dispatcher.fd, 0)\n        self.assertEqual(dispatcher.childlog, None)\n\n    def test_ctor_logfile_only(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1',\n                              stdout_logfile='/tmp/foo')\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        self.assertEqual(dispatcher.process, process)\n        self.assertEqual(dispatcher.channel, 'stdout')\n        self.assertEqual(dispatcher.fd, 0)\n        self.assertEqual(dispatcher.childlog.__class__, DummyLogger)\n\n    def test_repr(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1')\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        drepr = repr(dispatcher)\n        self.assertTrue('PEventListenerDispatcher' in drepr)\n        self.assertNotEqual(\n            drepr.find('supervisor.tests.base.DummyProcess'),\n            -1)\n        self.assertTrue(drepr.endswith('(stdout)>'), drepr)\n\n    def test_close(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'process1', '/bin/process1')\n        process = DummyProcess(config)\n        dispatcher = self._makeOne(process)\n        dispatcher.close()\n        self.assertEqual(dispatcher.closed, True)\n        dispatcher.close() # make sure we don't error if we try to close twice\n        self.assertEqual(dispatcher.closed, True)\n\n\nclass stripEscapeTests(unittest.TestCase):\n    def _callFUT(self, s):\n        from supervisor.dispatchers import stripEscapes\n        return stripEscapes(s)\n\n    def test_zero_length_string(self):\n        self.assertEqual(self._callFUT(b''), b'')\n\n    def test_ansi(self):\n        ansi = b'\\x1b[34mHello world... this is longer than a token!\\x1b[0m'\n        noansi = b'Hello world... this is longer than a token!'\n        self.assertEqual(self._callFUT(ansi), noansi)\n\n    def test_noansi(self):\n        noansi = b'Hello world... this is longer than a token!'\n        self.assertEqual(self._callFUT(noansi), noansi)\n"
  },
  {
    "path": "supervisor/tests/test_end_to_end.py",
    "content": "# ~*~ coding: utf-8 ~*~\nfrom __future__ import unicode_literals\n\nimport os\nimport signal\nimport sys\nimport unittest\nfrom supervisor.compat import resource_filename\nfrom supervisor.compat import xmlrpclib\nfrom supervisor.xmlrpc import SupervisorTransport\n\n# end-to-test tests are slow so only run them when asked\nif 'END_TO_END' in os.environ:\n    import pexpect\n    BaseTestCase = unittest.TestCase\nelse:\n    BaseTestCase = object\n\n\nclass EndToEndTests(BaseTestCase):\n\n    def test_issue_291a_percent_signs_in_original_env_are_preserved(self):\n        \"\"\"When an environment variable whose value contains a percent sign is\n        present in the environment before supervisord starts, the value is\n        passed to the child without the percent sign being mangled.\"\"\"\n        key = \"SUPERVISOR_TEST_1441B\"\n        val = \"foo_%s_%_%%_%%%_%2_bar\"\n        filename = resource_filename(__package__, 'fixtures/issue-291a.conf')\n        args = ['-m', 'supervisor.supervisord', '-c', filename]\n        try:\n            os.environ[key] = val\n            supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')\n            self.addCleanup(supervisord.kill, signal.SIGINT)\n            supervisord.expect_exact(key + \"=\" + val)\n        finally:\n            del os.environ[key]\n\n    def test_issue_550(self):\n        \"\"\"When an environment variable is set in the [supervisord] section,\n        it should be put into the environment of the subprocess.\"\"\"\n        filename = resource_filename(__package__, 'fixtures/issue-550.conf')\n        args = ['-m', 'supervisor.supervisord', '-c', filename]\n        supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')\n        self.addCleanup(supervisord.kill, signal.SIGINT)\n        supervisord.expect_exact('success: print_env entered RUNNING state')\n        supervisord.expect_exact('exited: print_env (exit status 0; expected)')\n\n        args = ['-m', 'supervisor.supervisorctl', '-c', filename, 'tail -100000', 'print_env']\n        supervisorctl = pexpect.spawn(sys.executable, args, encoding='utf-8')\n        self.addCleanup(supervisorctl.kill, signal.SIGINT)\n        supervisorctl.expect_exact(\"THIS_SHOULD=BE_IN_CHILD_ENV\")\n        supervisorctl.expect(pexpect.EOF)\n\n    def test_issue_565(self):\n        \"\"\"When a log file has Unicode characters in it, 'supervisorctl\n        tail -f name' should still work.\"\"\"\n        filename = resource_filename(__package__, 'fixtures/issue-565.conf')\n        args = ['-m', 'supervisor.supervisord', '-c', filename]\n        supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')\n        self.addCleanup(supervisord.kill, signal.SIGINT)\n        supervisord.expect_exact('success: hello entered RUNNING state')\n\n        args = ['-m', 'supervisor.supervisorctl', '-c', filename, 'tail', '-f', 'hello']\n        supervisorctl = pexpect.spawn(sys.executable, args, encoding='utf-8')\n        self.addCleanup(supervisorctl.kill, signal.SIGINT)\n\n        for i in range(1, 4):\n            line = 'The Øresund bridge ends in Malmö - %d' % i\n            supervisorctl.expect_exact(line, timeout=30)\n\n    def test_issue_638(self):\n        \"\"\"When a process outputs something on its stdout or stderr file\n        descriptor that is not valid UTF-8, supervisord should not crash.\"\"\"\n        filename = resource_filename(__package__, 'fixtures/issue-638.conf')\n        args = ['-m', 'supervisor.supervisord', '-c', filename]\n        supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')\n        self.addCleanup(supervisord.kill, signal.SIGINT)\n        is_py2 = sys.version_info[0] < 3\n        if is_py2:\n            b_prefix = ''\n        else:\n            b_prefix = 'b'\n        supervisord.expect_exact(r\"Undecodable: %s'\\x88\\n'\" % b_prefix, timeout=30)\n        supervisord.expect('received SIGCH?LD indicating a child quit', timeout=30)\n        if is_py2:\n            # need to investigate why this message is only printed under 2.x\n            supervisord.expect_exact('gave up: produce-unicode-error entered FATAL state, '\n                                     'too many start retries too quickly', timeout=60)\n\n    def test_issue_663(self):\n        \"\"\"When Supervisor is run on Python 3, the eventlistener protocol\n        should work.\"\"\"\n        filename = resource_filename(__package__, 'fixtures/issue-663.conf')\n        args = ['-m', 'supervisor.supervisord', '-c', filename]\n        supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')\n        self.addCleanup(supervisord.kill, signal.SIGINT)\n        for i in range(2):\n            supervisord.expect_exact('OKREADY', timeout=60)\n            supervisord.expect_exact('BUSY -> ACKNOWLEDGED', timeout=30)\n\n    def test_issue_664(self):\n        \"\"\"When a subprocess name has Unicode characters, 'supervisord'\n        should not send incomplete XML-RPC responses and 'supervisorctl\n        status' should work.\"\"\"\n        filename = resource_filename(__package__, 'fixtures/issue-664.conf')\n        args = ['-m', 'supervisor.supervisord', '-c', filename]\n        supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')\n        self.addCleanup(supervisord.kill, signal.SIGINT)\n        supervisord.expect_exact('test_öäü entered RUNNING state', timeout=60)\n\n        args = ['-m', 'supervisor.supervisorctl', '-c', filename, 'status']\n        supervisorctl = pexpect.spawn(sys.executable, args, encoding='utf-8')\n        self.addCleanup(supervisorctl.kill, signal.SIGINT)\n        try:\n            supervisorctl.expect('test_öäü\\\\s+RUNNING', timeout=30)\n            seen = True\n        except pexpect.ExceptionPexpect:\n            seen = False\n        self.assertTrue(seen)\n\n    def test_issue_733(self):\n        \"\"\"When a subprocess enters the FATAL state, a one-line eventlistener\n        can be used to signal supervisord to shut down.\"\"\"\n        filename = resource_filename(__package__, 'fixtures/issue-733.conf')\n        args = ['-m', 'supervisor.supervisord', '-c', filename]\n        supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')\n        self.addCleanup(supervisord.kill, signal.SIGINT)\n        supervisord.expect_exact('gave up: nonexistent entered FATAL state')\n        supervisord.expect_exact('received SIGTERM indicating exit request')\n        supervisord.expect(pexpect.EOF)\n\n    def test_issue_835(self):\n        filename = resource_filename(__package__, 'fixtures/issue-835.conf')\n        args = ['-m', 'supervisor.supervisord', '-c', filename]\n        supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')\n        self.addCleanup(supervisord.kill, signal.SIGINT)\n        supervisord.expect_exact('cat entered RUNNING state', timeout=60)\n        transport = SupervisorTransport('', '', 'unix:///tmp/issue-835.sock')\n        server = xmlrpclib.ServerProxy('http://anything/RPC2', transport)\n        try:\n            for s in ('The Øresund bridge ends in Malmö', 'hello'):\n                result = server.supervisor.sendProcessStdin('cat', s)\n                self.assertTrue(result)\n                supervisord.expect_exact(s, timeout=30)\n        finally:\n            transport.connection.close()\n\n    def test_issue_836(self):\n        filename = resource_filename(__package__, 'fixtures/issue-836.conf')\n        args = ['-m', 'supervisor.supervisord', '-c', filename]\n        supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')\n        self.addCleanup(supervisord.kill, signal.SIGINT)\n        supervisord.expect_exact('cat entered RUNNING state', timeout=60)\n        args = ['-m', 'supervisor.supervisorctl', '-c', filename, 'fg', 'cat']\n        supervisorctl = pexpect.spawn(sys.executable, args, encoding='utf-8')\n        self.addCleanup(supervisorctl.kill, signal.SIGINT)\n\n        try:\n            for s in ('Hi', 'Hello', 'The Øresund bridge ends in Malmö'):\n                supervisorctl.sendline(s)\n                supervisord.expect_exact(s, timeout=60)\n                supervisorctl.expect_exact(s) # echoed locally\n                supervisorctl.expect_exact(s) # sent back by supervisord\n            seen = True\n        except pexpect.ExceptionPexpect:\n            seen = False\n        self.assertTrue(seen)\n\n    def test_issue_986_command_string_with_double_percent(self):\n        \"\"\"A percent sign can be used in a command= string without being\n        expanded if it is escaped by a second percent sign.\"\"\"\n        filename = resource_filename(__package__, 'fixtures/issue-986.conf')\n        args = ['-m', 'supervisor.supervisord', '-c', filename]\n        supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')\n        self.addCleanup(supervisord.kill, signal.SIGINT)\n        supervisord.expect_exact('dhcrelay -d -q -a %h:%p %P -i Vlan1000 192.168.0.1')\n\n    def test_issue_1054(self):\n        \"\"\"When run on Python 3, the 'supervisorctl avail' command\n        should work.\"\"\"\n        filename = resource_filename(__package__, 'fixtures/issue-1054.conf')\n        args = ['-m', 'supervisor.supervisord', '-c', filename]\n        supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')\n        self.addCleanup(supervisord.kill, signal.SIGINT)\n        supervisord.expect_exact('cat entered RUNNING state', timeout=60)\n        args = ['-m', 'supervisor.supervisorctl', '-c', filename, 'avail']\n        supervisorctl = pexpect.spawn(sys.executable, args, encoding='utf-8')\n        try:\n            supervisorctl.expect('cat\\\\s+in use\\\\s+auto', timeout=30)\n            seen = True\n        except pexpect.ExceptionPexpect:\n            seen = False\n        self.assertTrue(seen)\n\n    def test_issue_1170a(self):\n        \"\"\"When the [supervisord] section has a variable defined in\n        environment=, that variable should be able to be used in an\n        %(ENV_x) expansion in a [program] section.\"\"\"\n        filename = resource_filename(__package__, 'fixtures/issue-1170a.conf')\n        args = ['-m', 'supervisor.supervisord', '-c', filename]\n        supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')\n        self.addCleanup(supervisord.kill, signal.SIGINT)\n        supervisord.expect_exact(\"set from [supervisord] section\")\n\n    def test_issue_1170b(self):\n        \"\"\"When the [supervisord] section has a variable defined in\n        environment=, and a variable by the same name is defined in\n        enviroment= of a [program] section, the one in the [program]\n        section should be used.\"\"\"\n        filename = resource_filename(__package__, 'fixtures/issue-1170b.conf')\n        args = ['-m', 'supervisor.supervisord', '-c', filename]\n        supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')\n        self.addCleanup(supervisord.kill, signal.SIGINT)\n        supervisord.expect_exact(\"set from [program] section\")\n\n    def test_issue_1170c(self):\n        \"\"\"When the [supervisord] section has a variable defined in\n        environment=, and a variable by the same name is defined in\n        enviroment= of an [eventlistener] section, the one in the\n        [eventlistener] section should be used.\"\"\"\n        filename = resource_filename(__package__, 'fixtures/issue-1170c.conf')\n        args = ['-m', 'supervisor.supervisord', '-c', filename]\n        supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')\n        self.addCleanup(supervisord.kill, signal.SIGINT)\n        supervisord.expect_exact(\"set from [eventlistener] section\")\n\n    def test_issue_1224(self):\n        \"\"\"When the main log file does not need rotation (logfile_maxbyte=0)\n        then the non-rotating logger will be used to avoid an\n        IllegalSeekError in the case that the user has configured a\n        non-seekable file like /dev/stdout.\"\"\"\n        filename = resource_filename(__package__, 'fixtures/issue-1224.conf')\n        args = ['-m', 'supervisor.supervisord', '-c', filename]\n        supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')\n        self.addCleanup(supervisord.kill, signal.SIGINT)\n        supervisord.expect_exact('cat entered RUNNING state', timeout=60)\n\n    def test_issue_1231a(self):\n        \"\"\"When 'supervisorctl tail -f name' is run and the log contains\n        unicode, it should not fail.\"\"\"\n        filename = resource_filename(__package__, 'fixtures/issue-1231a.conf')\n        args = ['-m', 'supervisor.supervisord', '-c', filename]\n        supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')\n        self.addCleanup(supervisord.kill, signal.SIGINT)\n        supervisord.expect_exact('success: hello entered RUNNING state')\n\n        args = ['-m', 'supervisor.supervisorctl', '-c', filename, 'tail', '-f', 'hello']\n        supervisorctl = pexpect.spawn(sys.executable, args, encoding='utf-8')\n        self.addCleanup(supervisorctl.kill, signal.SIGINT)\n\n        for i in range(1, 4):\n            line = '%d - hash=57d94b…381088' % i\n            supervisorctl.expect_exact(line, timeout=30)\n\n    def test_issue_1231b(self):\n        \"\"\"When 'supervisorctl tail -f name' is run and the log contains\n        unicode, it should not fail.\"\"\"\n        filename = resource_filename(__package__, 'fixtures/issue-1231b.conf')\n        args = ['-m', 'supervisor.supervisord', '-c', filename]\n        supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')\n        self.addCleanup(supervisord.kill, signal.SIGINT)\n        supervisord.expect_exact('success: hello entered RUNNING state')\n\n        args = ['-m', 'supervisor.supervisorctl', '-c', filename, 'tail', '-f', 'hello']\n        env = os.environ.copy()\n        env['LANG'] = 'oops'\n        supervisorctl = pexpect.spawn(sys.executable, args, encoding='utf-8',\n                                      env=env)\n        self.addCleanup(supervisorctl.kill, signal.SIGINT)\n\n        # For Python 3 < 3.7, LANG=oops leads to warnings because of the\n        # stdout encoding. For 3.7 (and presumably later), the encoding is\n        # utf-8 when LANG=oops.\n        if sys.version_info[:2] < (3, 7):\n            supervisorctl.expect('Warning: sys.stdout.encoding is set to ',\n                                 timeout=30)\n            supervisorctl.expect('Unicode output may fail.', timeout=30)\n\n        for i in range(1, 4):\n            line = '%d - hash=57d94b…381088' % i\n            try:\n                supervisorctl.expect_exact(line, timeout=30)\n            except pexpect.exceptions.EOF:\n                self.assertIn('Unable to write Unicode to stdout because it '\n                              'has encoding ',\n                              supervisorctl.before)\n                break\n\n    def test_issue_1231c(self):\n        \"\"\"When 'supervisorctl tail -f name' is run and the log contains\n        unicode, it should not fail.\"\"\"\n        filename = resource_filename(__package__, 'fixtures/issue-1231c.conf')\n        args = ['-m', 'supervisor.supervisord', '-c', filename]\n        supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')\n        self.addCleanup(supervisord.kill, signal.SIGINT)\n        supervisord.expect_exact('success: hello entered RUNNING state')\n\n        args = ['-m', 'supervisor.supervisorctl', '-c', filename, 'tail', 'hello']\n        env = os.environ.copy()\n        env['LANG'] = 'oops'\n        supervisorctl = pexpect.spawn(sys.executable, args, encoding='utf-8',\n                                      env=env)\n        self.addCleanup(supervisorctl.kill, signal.SIGINT)\n\n        # For Python 3 < 3.7, LANG=oops leads to warnings because of the\n        # stdout encoding. For 3.7 (and presumably later), the encoding is\n        # utf-8 when LANG=oops.\n        if sys.version_info[:2] < (3, 7):\n            supervisorctl.expect('Warning: sys.stdout.encoding is set to ',\n                                 timeout=30)\n            supervisorctl.expect('Unicode output may fail.', timeout=30)\n\n    def test_issue_1251(self):\n        \"\"\"When -? is given to supervisord or supervisorctl, help should be\n        displayed like -h does.\"\"\"\n        args = ['-m', 'supervisor.supervisord', '-?']\n        supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')\n        self.addCleanup(supervisord.kill, signal.SIGINT)\n        supervisord.expect_exact(\"supervisord -- run a set of applications\")\n        supervisord.expect_exact(\"-l/--logfile FILENAME -- use FILENAME as\")\n        supervisord.expect(pexpect.EOF)\n\n        args = ['-m', 'supervisor.supervisorctl', '-?']\n        supervisorctl = pexpect.spawn(sys.executable, args, encoding='utf-8')\n        self.addCleanup(supervisorctl.kill, signal.SIGINT)\n        supervisorctl.expect_exact(\"supervisorctl -- control applications\")\n        supervisorctl.expect_exact(\"-i/--interactive -- start an interactive\")\n        supervisorctl.expect(pexpect.EOF)\n\n    def test_issue_1298(self):\n        \"\"\"When the output of 'supervisorctl tail -f worker' is piped such as\n        'supervisor tail -f worker | grep something', 'supervisorctl' should\n        not crash.\"\"\"\n        filename = resource_filename(__package__, 'fixtures/issue-1298.conf')\n        args = ['-m', 'supervisor.supervisord', '-c', filename]\n        supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')\n        self.addCleanup(supervisord.kill, signal.SIGINT)\n        supervisord.expect_exact('success: spew entered RUNNING state')\n\n        cmd = \"'%s' -m supervisor.supervisorctl -c '%s' tail -f spew | /bin/cat -u\" % (\n            sys.executable, filename\n            )\n        bash = pexpect.spawn('/bin/sh', ['-c', cmd], encoding='utf-8')\n        self.addCleanup(bash.kill, signal.SIGINT)\n        bash.expect('spewage 2', timeout=30)\n\n    def test_issue_1418_pidproxy_cmd_with_no_args(self):\n        \"\"\"When pidproxy is given a command to run that has no arguments, it\n        runs that command.\"\"\"\n        args = ['-m', 'supervisor.pidproxy', 'nonexistent-pidfile', \"/bin/echo\"]\n        pidproxy = pexpect.spawn(sys.executable, args, encoding='utf-8')\n        self.addCleanup(pidproxy.kill, signal.SIGINT)\n        pidproxy.expect(pexpect.EOF)\n        self.assertEqual(pidproxy.before.strip(), \"\")\n\n    def test_issue_1418_pidproxy_cmd_with_args(self):\n        \"\"\"When pidproxy is given a command to run that has arguments, it\n        runs that command.\"\"\"\n        args = ['-m', 'supervisor.pidproxy', 'nonexistent-pidfile', \"/bin/echo\", \"1\", \"2\"]\n        pidproxy = pexpect.spawn(sys.executable, args, encoding='utf-8')\n        self.addCleanup(pidproxy.kill, signal.SIGINT)\n        pidproxy.expect(pexpect.EOF)\n        self.assertEqual(pidproxy.before.strip(), \"1 2\")\n\n    def test_issue_1483a_identifier_default(self):\n        \"\"\"When no identifier is supplied on the command line or in the config\n        file, the default is used.\"\"\"\n        filename = resource_filename(__package__, 'fixtures/issue-1483a.conf')\n        args = ['-m', 'supervisor.supervisord', '-c', filename]\n        supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')\n        self.addCleanup(supervisord.kill, signal.SIGINT)\n        supervisord.expect_exact('supervisord started with pid')\n\n        from supervisor.compat import xmlrpclib\n        from supervisor.xmlrpc import SupervisorTransport\n        transport = SupervisorTransport('', '', 'unix:///tmp/issue-1483a.sock')\n        try:\n            server = xmlrpclib.ServerProxy('http://transport.ignores.host/RPC2', transport)\n            ident = server.supervisor.getIdentification()\n        finally:\n            transport.close()\n        self.assertEqual(ident, \"supervisor\")\n\n    def test_issue_1483b_identifier_from_config_file(self):\n        \"\"\"When the identifier is supplied in the config file only, that\n        identifier is used instead of the default.\"\"\"\n        filename = resource_filename(__package__, 'fixtures/issue-1483b.conf')\n        args = ['-m', 'supervisor.supervisord', '-c', filename]\n        supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')\n        self.addCleanup(supervisord.kill, signal.SIGINT)\n        supervisord.expect_exact('supervisord started with pid')\n\n        from supervisor.compat import xmlrpclib\n        from supervisor.xmlrpc import SupervisorTransport\n        transport = SupervisorTransport('', '', 'unix:///tmp/issue-1483b.sock')\n        try:\n            server = xmlrpclib.ServerProxy('http://transport.ignores.host/RPC2', transport)\n            ident = server.supervisor.getIdentification()\n        finally:\n            transport.close()\n        self.assertEqual(ident, \"from_config_file\")\n\n    def test_issue_1483c_identifier_from_command_line(self):\n        \"\"\"When an identifier is supplied in both the config file and on the\n        command line, the one from the command line is used.\"\"\"\n        filename = resource_filename(__package__, 'fixtures/issue-1483c.conf')\n        args = ['-m', 'supervisor.supervisord', '-c', filename, '-i', 'from_command_line']\n        supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')\n        self.addCleanup(supervisord.kill, signal.SIGINT)\n        supervisord.expect_exact('supervisord started with pid')\n\n        from supervisor.compat import xmlrpclib\n        from supervisor.xmlrpc import SupervisorTransport\n        transport = SupervisorTransport('', '', 'unix:///tmp/issue-1483c.sock')\n        try:\n            server = xmlrpclib.ServerProxy('http://transport.ignores.host/RPC2', transport)\n            ident = server.supervisor.getIdentification()\n        finally:\n            transport.close()\n        self.assertEqual(ident, \"from_command_line\")\n\n    def test_pull_request_1578_echo_supervisord_conf(self):\n        \"\"\"The command echo_supervisord_conf, whose implementation depends on\n        importlib.resources to work, should print the example config.\"\"\"\n        args = ['-c', 'from supervisor import confecho; confecho.main()']\n        echo_supervisord_conf = pexpect.spawn(sys.executable, args, encoding='utf-8')\n        self.addCleanup(echo_supervisord_conf.kill, signal.SIGKILL)\n        echo_supervisord_conf.expect_exact('Sample supervisor config file')\n\n    def test_issue_1596_asyncore_close_does_not_crash(self):\n        \"\"\"If the socket is already closed when socket.shutdown(socket.SHUT_RDWR)\n        is called in the close() method of an asyncore dispatcher, an exception\n        will be raised (at least with Python 3.11.7 on macOS 14.2.1).  If it is\n        not caught in that method, supervisord will crash.\"\"\"\n        filename = resource_filename(__package__, 'fixtures/issue-1596.conf')\n        args = ['-m', 'supervisor.supervisord', '-c', filename]\n        supervisord = pexpect.spawn(sys.executable, args, encoding='utf-8')\n        self.addCleanup(supervisord.kill, signal.SIGINT)\n        supervisord.expect_exact('supervisord started with pid')\n\n        from supervisor.compat import xmlrpclib\n        from supervisor.xmlrpc import SupervisorTransport\n\n        socket_url = 'unix:///tmp/issue-1596.sock'\n        dummy_url = 'http://transport.ignores.host/RPC2'\n\n        # supervisord will crash after close() if it has the bug\n        t1 = SupervisorTransport('', '', socket_url)\n        s1 = xmlrpclib.ServerProxy(dummy_url, t1)\n        s1.system.listMethods()\n        t1.close()\n\n        # this call will only succeed if supervisord did not crash\n        t2 = SupervisorTransport('', '', socket_url)\n        s2 = xmlrpclib.ServerProxy(dummy_url, t2)\n        s2.system.listMethods()\n        t2.close()\n"
  },
  {
    "path": "supervisor/tests/test_events.py",
    "content": "import unittest\n\nfrom supervisor.tests.base import DummyOptions\nfrom supervisor.tests.base import DummyPConfig\nfrom supervisor.tests.base import DummyProcess\nfrom supervisor.tests.base import DummyEvent\n\nclass EventSubscriptionNotificationTests(unittest.TestCase):\n    def setUp(self):\n        from supervisor import events\n        events.callbacks[:] = []\n\n    def tearDown(self):\n        from supervisor import events\n        events.callbacks[:] = []\n\n    def test_subscribe(self):\n        from supervisor import events\n        events.subscribe(None, None)\n        self.assertEqual(events.callbacks, [(None, None)])\n\n    def test_unsubscribe(self):\n        from supervisor import events\n        events.callbacks[:] = [(1, 1), (2, 2), (3, 3)]\n        events.unsubscribe(2, 2)\n        self.assertEqual(events.callbacks, [(1, 1), (3, 3)])\n\n    def test_clear(self):\n        from supervisor import events\n        events.callbacks[:] = [(None, None)]\n        events.clear()\n        self.assertEqual(events.callbacks, [])\n\n    def test_notify_true(self):\n        from supervisor import events\n        L = []\n        def callback(event):\n            L.append(1)\n        events.callbacks[:] = [(DummyEvent, callback)]\n        events.notify(DummyEvent())\n        self.assertEqual(L, [1])\n\n    def test_notify_false(self):\n        from supervisor import events\n        L = []\n        def callback(event):\n            L.append(1)\n        class AnotherEvent:\n            pass\n        events.callbacks[:] = [(AnotherEvent, callback)]\n        events.notify(DummyEvent())\n        self.assertEqual(L, [])\n\n    def test_notify_via_subclass(self):\n        from supervisor import events\n        L = []\n        def callback(event):\n            L.append(1)\n        class ASubclassEvent(DummyEvent):\n            pass\n        events.callbacks[:] = [(DummyEvent, callback)]\n        events.notify(ASubclassEvent())\n        self.assertEqual(L, [1])\n\n\nclass TestEventTypes(unittest.TestCase):\n    def test_ProcessLogEvent_attributes(self):\n        from supervisor.events import ProcessLogEvent\n        inst = ProcessLogEvent(1, 2, 3)\n        self.assertEqual(inst.process, 1)\n        self.assertEqual(inst.pid, 2)\n        self.assertEqual(inst.data, 3)\n\n    def test_ProcessLogEvent_inheritance(self):\n        from supervisor.events import ProcessLogEvent\n        from supervisor.events import Event\n        self.assertTrue(\n            issubclass(ProcessLogEvent, Event)\n        )\n\n    def test_ProcessLogStdoutEvent_attributes(self):\n        from supervisor.events import ProcessLogStdoutEvent\n        inst = ProcessLogStdoutEvent(1, 2, 3)\n        self.assertEqual(inst.process, 1)\n        self.assertEqual(inst.pid, 2)\n        self.assertEqual(inst.data, 3)\n        self.assertEqual(inst.channel, 'stdout')\n\n    def test_ProcessLogStdoutEvent_inheritance(self):\n        from supervisor.events import ProcessLogStdoutEvent\n        from supervisor.events import ProcessLogEvent\n        self.assertTrue(\n            issubclass(ProcessLogStdoutEvent, ProcessLogEvent)\n        )\n\n    def test_ProcessLogStderrEvent_attributes(self):\n        from supervisor.events import ProcessLogStderrEvent\n        inst = ProcessLogStderrEvent(1, 2, 3)\n        self.assertEqual(inst.process, 1)\n        self.assertEqual(inst.pid, 2)\n        self.assertEqual(inst.data, 3)\n        self.assertEqual(inst.channel, 'stderr')\n\n    def test_ProcessLogStderrEvent_inheritance(self):\n        from supervisor.events import ProcessLogStderrEvent\n        from supervisor.events import ProcessLogEvent\n        self.assertTrue(\n            issubclass(ProcessLogStderrEvent, ProcessLogEvent)\n        )\n\n    def test_ProcessCommunicationEvent_attributes(self):\n        from supervisor.events import ProcessCommunicationEvent\n        inst = ProcessCommunicationEvent(1, 2, 3)\n        self.assertEqual(inst.process, 1)\n        self.assertEqual(inst.pid, 2)\n        self.assertEqual(inst.data, 3)\n\n    def test_ProcessCommunicationEvent_inheritance(self):\n        from supervisor.events import ProcessCommunicationEvent\n        from supervisor.events import Event\n        self.assertTrue(\n            issubclass(ProcessCommunicationEvent, Event)\n        )\n\n    def test_ProcessCommunicationStdoutEvent_attributes(self):\n        from supervisor.events import ProcessCommunicationStdoutEvent\n        inst = ProcessCommunicationStdoutEvent(1, 2, 3)\n        self.assertEqual(inst.process, 1)\n        self.assertEqual(inst.pid, 2)\n        self.assertEqual(inst.data, 3)\n        self.assertEqual(inst.channel, 'stdout')\n\n    def test_ProcessCommunicationStdoutEvent_inheritance(self):\n        from supervisor.events import ProcessCommunicationStdoutEvent\n        from supervisor.events import ProcessCommunicationEvent\n        self.assertTrue(\n            issubclass(ProcessCommunicationStdoutEvent,\n                       ProcessCommunicationEvent)\n        )\n\n    def test_ProcessCommunicationStderrEvent_attributes(self):\n        from supervisor.events import ProcessCommunicationStderrEvent\n        inst = ProcessCommunicationStderrEvent(1, 2, 3)\n        self.assertEqual(inst.process, 1)\n        self.assertEqual(inst.pid, 2)\n        self.assertEqual(inst.data, 3)\n        self.assertEqual(inst.channel, 'stderr')\n\n    def test_ProcessCommunicationStderrEvent_inheritance(self):\n        from supervisor.events import ProcessCommunicationStderrEvent\n        from supervisor.events import ProcessCommunicationEvent\n        self.assertTrue(\n            issubclass(ProcessCommunicationStderrEvent,\n                       ProcessCommunicationEvent)\n        )\n\n    def test_RemoteCommunicationEvent_attributes(self):\n        from supervisor.events import RemoteCommunicationEvent\n        inst = RemoteCommunicationEvent(1, 2)\n        self.assertEqual(inst.type, 1)\n        self.assertEqual(inst.data, 2)\n\n    def test_RemoteCommunicationEvent_inheritance(self):\n        from supervisor.events import RemoteCommunicationEvent\n        from supervisor.events import Event\n        self.assertTrue(\n            issubclass(RemoteCommunicationEvent, Event)\n        )\n\n    def test_EventRejectedEvent_attributes(self):\n        from supervisor.events import EventRejectedEvent\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')\n        process = DummyProcess(pconfig1)\n        rejected_event = DummyEvent()\n        event = EventRejectedEvent(process, rejected_event)\n        self.assertEqual(event.process, process)\n        self.assertEqual(event.event, rejected_event)\n\n    def test_EventRejectedEvent_does_not_inherit_from_event(self):\n        from supervisor.events import EventRejectedEvent\n        from supervisor.events import Event\n        self.assertFalse(\n            issubclass(EventRejectedEvent, Event)\n        )\n\n    def test_all_SupervisorStateChangeEvents(self):\n        from supervisor import events\n        for klass in (\n            events.SupervisorStateChangeEvent,\n            events.SupervisorRunningEvent,\n            events.SupervisorStoppingEvent\n            ):\n            self._test_one_SupervisorStateChangeEvent(klass)\n\n    def _test_one_SupervisorStateChangeEvent(self, klass):\n        from supervisor.events import SupervisorStateChangeEvent\n        self.assertTrue(issubclass(klass, SupervisorStateChangeEvent))\n\n    def test_all_ProcessStateEvents(self):\n        from supervisor import events\n        for klass in (\n            events.ProcessStateEvent,\n            events.ProcessStateStoppedEvent,\n            events.ProcessStateExitedEvent,\n            events.ProcessStateFatalEvent,\n            events.ProcessStateBackoffEvent,\n            events.ProcessStateRunningEvent,\n            events.ProcessStateUnknownEvent,\n            events.ProcessStateStoppingEvent,\n            events.ProcessStateStartingEvent,\n            ):\n            self._test_one_ProcessStateEvent(klass)\n\n    def _test_one_ProcessStateEvent(self, klass):\n        from supervisor.states import ProcessStates\n        from supervisor.events import ProcessStateEvent\n        self.assertTrue(issubclass(klass, ProcessStateEvent))\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')\n        process = DummyProcess(pconfig1)\n        inst = klass(process, ProcessStates.STARTING)\n        self.assertEqual(inst.process, process)\n        self.assertEqual(inst.from_state, ProcessStates.STARTING)\n        self.assertEqual(inst.expected, True)\n\n    def test_all_TickEvents(self):\n        from supervisor import events\n        for klass in (\n           events.TickEvent,\n           events.Tick5Event,\n           events.Tick60Event,\n           events.Tick3600Event\n           ):\n           self._test_one_TickEvent(klass)\n\n    def _test_one_TickEvent(self, klass):\n        from supervisor.events import TickEvent\n        self.assertTrue(issubclass(klass, TickEvent))\n\n        inst = klass(1, 2)\n        self.assertEqual(inst.when, 1)\n        self.assertEqual(inst.supervisord, 2)\n\n    def test_ProcessGroupAddedEvent_attributes(self):\n        from supervisor.events import ProcessGroupAddedEvent\n        inst = ProcessGroupAddedEvent('myprocess')\n        self.assertEqual(inst.group, 'myprocess')\n\n    def test_ProcessGroupRemovedEvent_attributes(self):\n        from supervisor.events import ProcessGroupRemovedEvent\n        inst = ProcessGroupRemovedEvent('myprocess')\n        self.assertEqual(inst.group, 'myprocess')\n\nclass TestSerializations(unittest.TestCase):\n    def _deserialize(self, serialization):\n        data = serialization.split('\\n')\n        headerdata = data[0]\n        payload = ''\n        headers = {}\n        if len(data) > 1:\n            payload = data[1]\n        if headerdata:\n            try:\n                headers = dict( [ x.split(':',1) for x in\n                                  headerdata.split()] )\n            except ValueError:\n                raise AssertionError('headerdata %r could not be deserialized' %\n                                     headerdata)\n        return headers, payload\n\n    def test_plog_stdout_event(self):\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')\n        process1 = DummyProcess(pconfig1)\n        from supervisor.events import ProcessLogStdoutEvent\n        class DummyGroup:\n            config = pconfig1\n        process1.group = DummyGroup\n        event = ProcessLogStdoutEvent(process1, 1, 'yo')\n        headers, payload = self._deserialize(event.payload())\n        self.assertEqual(headers['processname'], 'process1', headers)\n        self.assertEqual(headers['groupname'], 'process1', headers)\n        self.assertEqual(headers['pid'], '1', headers)\n        self.assertEqual(payload, 'yo')\n\n    def test_plog_stderr_event(self):\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')\n        process1 = DummyProcess(pconfig1)\n        from supervisor.events import ProcessLogStderrEvent\n        class DummyGroup:\n            config = pconfig1\n        process1.group = DummyGroup\n        event = ProcessLogStderrEvent(process1, 1, 'yo')\n        headers, payload = self._deserialize(event.payload())\n        self.assertEqual(headers['processname'], 'process1', headers)\n        self.assertEqual(headers['groupname'], 'process1', headers)\n        self.assertEqual(headers['pid'], '1', headers)\n        self.assertEqual(payload, 'yo')\n\n    def test_pcomm_stdout_event(self):\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')\n        process1 = DummyProcess(pconfig1)\n        from supervisor.events import ProcessCommunicationStdoutEvent\n        class DummyGroup:\n            config = pconfig1\n        process1.group = DummyGroup\n        event = ProcessCommunicationStdoutEvent(process1, 1, 'yo')\n        headers, payload = self._deserialize(event.payload())\n        self.assertEqual(headers['processname'], 'process1', headers)\n        self.assertEqual(headers['groupname'], 'process1', headers)\n        self.assertEqual(headers['pid'], '1', headers)\n        self.assertEqual(payload, 'yo')\n\n    def test_pcomm_stderr_event(self):\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')\n        process1 = DummyProcess(pconfig1)\n        class DummyGroup:\n            config = pconfig1\n        process1.group = DummyGroup\n        from supervisor.events import ProcessCommunicationStderrEvent\n        event = ProcessCommunicationStderrEvent(process1, 1, 'yo')\n        headers, payload = self._deserialize(event.payload())\n        self.assertEqual(headers['processname'], 'process1', headers)\n        self.assertEqual(headers['groupname'], 'process1', headers)\n        self.assertEqual(headers['pid'], '1', headers)\n        self.assertEqual(payload, 'yo')\n\n    def test_remote_comm_event(self):\n        from supervisor.events import RemoteCommunicationEvent\n        event = RemoteCommunicationEvent('foo', 'bar')\n        headers, payload = self._deserialize(event.payload())\n        self.assertEqual(headers['type'], 'foo', headers)\n        self.assertEqual(payload, 'bar')\n\n    def test_process_group_added_event(self):\n        from supervisor.events import ProcessGroupAddedEvent\n        event = ProcessGroupAddedEvent('foo')\n        headers, payload = self._deserialize(event.payload())\n        self.assertEqual(headers['groupname'], 'foo')\n        self.assertEqual(payload, '')\n\n    def test_process_group_removed_event(self):\n        from supervisor.events import ProcessGroupRemovedEvent\n        event = ProcessGroupRemovedEvent('foo')\n        headers, payload = self._deserialize(event.payload())\n        self.assertEqual(headers['groupname'], 'foo')\n        self.assertEqual(payload, '')\n\n    def test_process_state_events_without_extra_values(self):\n        from supervisor.states import ProcessStates\n        from supervisor import events\n        for klass in (\n            events.ProcessStateFatalEvent,\n            events.ProcessStateUnknownEvent,\n            ):\n            options = DummyOptions()\n            pconfig1 = DummyPConfig(options, 'process1', 'process1',\n                                    '/bin/process1')\n            class DummyGroup:\n                config = pconfig1\n            process1 = DummyProcess(pconfig1)\n            process1.group = DummyGroup\n            event = klass(process1, ProcessStates.STARTING)\n            headers, payload = self._deserialize(event.payload())\n            self.assertEqual(len(headers), 3)\n            self.assertEqual(headers['processname'], 'process1')\n            self.assertEqual(headers['groupname'], 'process1')\n            self.assertEqual(headers['from_state'], 'STARTING')\n            self.assertEqual(payload, '')\n\n    def test_process_state_events_with_pid(self):\n        from supervisor.states import ProcessStates\n        from supervisor import events\n        for klass in (\n            events.ProcessStateRunningEvent,\n            events.ProcessStateStoppedEvent,\n            events.ProcessStateStoppingEvent,\n            ):\n            options = DummyOptions()\n            pconfig1 = DummyPConfig(options, 'process1', 'process1',\n                                    '/bin/process1')\n            class DummyGroup:\n                config = pconfig1\n            process1 = DummyProcess(pconfig1)\n            process1.group = DummyGroup\n            process1.pid = 1\n            event = klass(process1, ProcessStates.STARTING)\n            headers, payload = self._deserialize(event.payload())\n            self.assertEqual(len(headers), 4)\n            self.assertEqual(headers['processname'], 'process1')\n            self.assertEqual(headers['groupname'], 'process1')\n            self.assertEqual(headers['from_state'], 'STARTING')\n            self.assertEqual(headers['pid'], '1')\n            self.assertEqual(payload, '')\n\n    def test_process_state_events_starting_and_backoff(self):\n        from supervisor.states import ProcessStates\n        from supervisor import events\n        for klass in (\n            events.ProcessStateStartingEvent,\n            events.ProcessStateBackoffEvent,\n            ):\n            options = DummyOptions()\n            pconfig1 = DummyPConfig(options, 'process1', 'process1',\n                                    '/bin/process1')\n            class DummyGroup:\n                config = pconfig1\n            process1 = DummyProcess(pconfig1)\n            process1.group = DummyGroup\n            event = klass(process1, ProcessStates.STARTING)\n            headers, payload = self._deserialize(event.payload())\n            self.assertEqual(len(headers), 4)\n            self.assertEqual(headers['processname'], 'process1')\n            self.assertEqual(headers['groupname'], 'process1')\n            self.assertEqual(headers['from_state'], 'STARTING')\n            self.assertEqual(headers['tries'], '0')\n            self.assertEqual(payload, '')\n            process1.backoff = 1\n            event = klass(process1, ProcessStates.STARTING)\n            headers, payload = self._deserialize(event.payload())\n            self.assertEqual(headers['tries'], '1')\n            process1.backoff = 2\n            event = klass(process1, ProcessStates.STARTING)\n            headers, payload = self._deserialize(event.payload())\n            self.assertEqual(headers['tries'], '2')\n\n    def test_process_state_exited_event_expected(self):\n        from supervisor import events\n        from supervisor.states import ProcessStates\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')\n        process1 = DummyProcess(pconfig1)\n        class DummyGroup:\n            config = pconfig1\n        process1.group = DummyGroup\n        process1.pid = 1\n        event = events.ProcessStateExitedEvent(process1,\n                                               ProcessStates.STARTING,\n                                               expected=True)\n        headers, payload = self._deserialize(event.payload())\n        self.assertEqual(len(headers), 5)\n        self.assertEqual(headers['processname'], 'process1')\n        self.assertEqual(headers['groupname'], 'process1')\n        self.assertEqual(headers['pid'], '1')\n        self.assertEqual(headers['from_state'], 'STARTING')\n        self.assertEqual(headers['expected'], '1')\n        self.assertEqual(payload, '')\n\n    def test_process_state_exited_event_unexpected(self):\n        from supervisor import events\n        from supervisor.states import ProcessStates\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')\n        process1 = DummyProcess(pconfig1)\n        class DummyGroup:\n            config = pconfig1\n        process1.group = DummyGroup\n        process1.pid = 1\n        event = events.ProcessStateExitedEvent(process1,\n                                               ProcessStates.STARTING,\n                                               expected=False)\n        headers, payload = self._deserialize(event.payload())\n        self.assertEqual(len(headers), 5)\n        self.assertEqual(headers['processname'], 'process1')\n        self.assertEqual(headers['groupname'], 'process1')\n        self.assertEqual(headers['pid'], '1')\n        self.assertEqual(headers['from_state'], 'STARTING')\n        self.assertEqual(headers['expected'], '0')\n        self.assertEqual(payload, '')\n\n    def test_supervisor_sc_event(self):\n        from supervisor import events\n        event = events.SupervisorRunningEvent()\n        headers, payload = self._deserialize(event.payload())\n        self.assertEqual(headers, {})\n        self.assertEqual(payload, '')\n\n    def test_tick_events(self):\n        from supervisor import events\n        for klass in (\n            events.Tick5Event,\n            events.Tick60Event,\n            events.Tick3600Event,\n            ):\n            event = klass(1, 2)\n            headers, payload = self._deserialize(event.payload())\n            self.assertEqual(headers, {'when':'1'})\n            self.assertEqual(payload, '')\n\nclass TestUtilityFunctions(unittest.TestCase):\n    def test_getEventNameByType(self):\n        from supervisor import events\n        for name, value in events.EventTypes.__dict__.items():\n            self.assertEqual(events.getEventNameByType(value), name)\n\n    def test_register(self):\n        from supervisor import events\n        self.assertFalse(hasattr(events.EventTypes, 'FOO'))\n        class FooEvent(events.Event):\n            pass\n        try:\n            events.register('FOO', FooEvent)\n            self.assertTrue(events.EventTypes.FOO is FooEvent)\n        finally:\n            del events.EventTypes.FOO\n"
  },
  {
    "path": "supervisor/tests/test_http.py",
    "content": "import base64\nimport os\nimport stat\nimport socket\nimport tempfile\nimport unittest\n\nfrom supervisor.compat import as_bytes\nfrom supervisor.compat import as_string\nfrom supervisor.compat import sha1\n\nfrom supervisor.tests.base import DummySupervisor\nfrom supervisor.tests.base import PopulatedDummySupervisor\nfrom supervisor.tests.base import DummyRPCInterfaceFactory\nfrom supervisor.tests.base import DummyPConfig\nfrom supervisor.tests.base import DummyOptions\nfrom supervisor.tests.base import DummyRequest\nfrom supervisor.tests.base import DummyLogger\n\nfrom supervisor.http import NOT_DONE_YET\n\nclass HandlerTests:\n    def _makeOne(self, supervisord):\n        return self._getTargetClass()(supervisord)\n\n    def test_match(self):\n        class FakeRequest:\n            def __init__(self, uri):\n                self.uri = uri\n        supervisor = DummySupervisor()\n        handler = self._makeOne(supervisor)\n        self.assertEqual(handler.match(FakeRequest(handler.path)), True)\n\nclass LogtailHandlerTests(HandlerTests, unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.http import logtail_handler\n        return logtail_handler\n\n    def test_handle_request_stdout_logfile_none(self):\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'process1', '/bin/process1', priority=1,\n                               stdout_logfile='/tmp/process1.log')\n        supervisord = PopulatedDummySupervisor(options, 'process1', pconfig)\n        handler = self._makeOne(supervisord)\n        request = DummyRequest('/logtail/process1', None, None, None)\n        handler.handle_request(request)\n        self.assertEqual(request._error, 404)\n\n    def test_handle_request_stdout_logfile_missing(self):\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', 'foo', 'it/is/missing')\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        handler = self._makeOne(supervisord)\n        request = DummyRequest('/logtail/foo', None, None, None)\n        handler.handle_request(request)\n        self.assertEqual(request._error, 404)\n\n    def test_handle_request(self):\n        with tempfile.NamedTemporaryFile() as f:\n            t = f.name\n            options = DummyOptions()\n            pconfig = DummyPConfig(options, 'foo', 'foo', stdout_logfile=t)\n            supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n            handler = self._makeOne(supervisord)\n            request = DummyRequest('/logtail/foo', None, None, None)\n            handler.handle_request(request)\n            self.assertEqual(request._error, None)\n            from supervisor.medusa import http_date\n            self.assertEqual(request.headers['Last-Modified'],\n                http_date.build_http_date(os.stat(t)[stat.ST_MTIME]))\n            self.assertEqual(request.headers['Content-Type'], 'text/plain;charset=utf-8')\n            self.assertEqual(request.headers['X-Accel-Buffering'], 'no')\n            self.assertEqual(len(request.producers), 1)\n            self.assertEqual(request._done, True)\n\nclass MainLogTailHandlerTests(HandlerTests, unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.http import mainlogtail_handler\n        return mainlogtail_handler\n\n    def test_handle_request_stdout_logfile_none(self):\n        supervisor = DummySupervisor()\n        handler = self._makeOne(supervisor)\n        request = DummyRequest('/mainlogtail', None, None, None)\n        handler.handle_request(request)\n        self.assertEqual(request._error, 404)\n\n    def test_handle_request_stdout_logfile_missing(self):\n        supervisor = DummySupervisor()\n        supervisor.options.logfile = '/not/there'\n        request = DummyRequest('/mainlogtail', None, None, None)\n        handler = self._makeOne(supervisor)\n        handler.handle_request(request)\n        self.assertEqual(request._error, 404)\n\n    def test_handle_request(self):\n        supervisor = DummySupervisor()\n        with tempfile.NamedTemporaryFile() as f:\n            t = f.name\n            supervisor.options.logfile = t\n            handler = self._makeOne(supervisor)\n            request = DummyRequest('/mainlogtail', None, None, None)\n            handler.handle_request(request)\n            self.assertEqual(request._error, None)\n            from supervisor.medusa import http_date\n            self.assertEqual(request.headers['Last-Modified'],\n                http_date.build_http_date(os.stat(t)[stat.ST_MTIME]))\n            self.assertEqual(request.headers['Content-Type'], 'text/plain;charset=utf-8')\n            self.assertEqual(len(request.producers), 1)\n            self.assertEqual(request._done, True)\n\n\nclass TailFProducerTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.http import tail_f_producer\n        return tail_f_producer\n\n    def _makeOne(self, request, filename, head):\n        return self._getTargetClass()(request, filename, head)\n\n    def test_handle_more(self):\n        request = DummyRequest('/logtail/foo', None, None, None)\n        from supervisor import http\n        f = tempfile.NamedTemporaryFile()\n        f.write(b'a' * 80)\n        f.flush()\n        producer = self._makeOne(request, f.name, 80)\n        result = producer.more()\n        self.assertEqual(result, b'a' * 80)\n        f.write(as_bytes(b'w' * 100))\n        f.flush()\n        result = producer.more()\n        self.assertEqual(result, b'w' * 100)\n        result = producer.more()\n        self.assertEqual(result, http.NOT_DONE_YET)\n        f.truncate(0)\n        f.flush()\n        result = producer.more()\n        self.assertEqual(result, '==> File truncated <==\\n')\n\n    def test_handle_more_fd_closed(self):\n        request = DummyRequest('/logtail/foo', None, None, None)\n        with tempfile.NamedTemporaryFile() as f:\n            f.write(as_bytes('a' * 80))\n            f.flush()\n            producer = self._makeOne(request, f.name, 80)\n            producer.file.close()\n            result = producer.more()\n        self.assertEqual(result, producer.more())\n\n    def test_handle_more_follow_file_recreated(self):\n        request = DummyRequest('/logtail/foo', None, None, None)\n        f = tempfile.NamedTemporaryFile()\n        f.write(as_bytes('a' * 80))\n        f.flush()\n        producer = self._makeOne(request, f.name, 80)\n        result = producer.more()\n        self.assertEqual(result, b'a' * 80)\n        f.close()\n        f2 = open(f.name, 'wb')\n        try:\n            f2.write(as_bytes(b'b' * 80))\n            f2.close()\n            result = producer.more()\n        finally:\n            os.unlink(f2.name)\n        self.assertEqual(result, b'b' * 80)\n\n    def test_handle_more_follow_file_gone(self):\n        request = DummyRequest('/logtail/foo', None, None, None)\n        with tempfile.NamedTemporaryFile(delete=False) as f:\n            filename = f.name\n            f.write(b'a' * 80)\n        try:\n            producer = self._makeOne(request, f.name, 80)\n        finally:\n            os.unlink(f.name)\n        result = producer.more()\n        self.assertEqual(result, b'a' * 80)\n        with open(filename, 'wb') as f:\n            f.write(as_bytes(b'b' * 80))\n        try:\n            result = producer.more() # should open in new file\n            self.assertEqual(result, b'b' * 80)\n        finally:\n             os.unlink(f.name)\n\nclass DeferringChunkedProducerTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.http import deferring_chunked_producer\n        return deferring_chunked_producer\n\n    def _makeOne(self, producer, footers=None):\n        return self._getTargetClass()(producer, footers)\n\n    def test_more_not_done_yet(self):\n        wrapped = DummyProducer(NOT_DONE_YET)\n        producer = self._makeOne(wrapped)\n        self.assertEqual(producer.more(), NOT_DONE_YET)\n\n    def test_more_string(self):\n        wrapped = DummyProducer(b'hello')\n        producer = self._makeOne(wrapped)\n        self.assertEqual(producer.more(), b'5\\r\\nhello\\r\\n')\n\n    def test_more_nodata(self):\n        wrapped = DummyProducer()\n        producer = self._makeOne(wrapped, footers=[b'a', b'b'])\n        self.assertEqual(producer.more(), b'0\\r\\na\\r\\nb\\r\\n\\r\\n')\n\n    def test_more_nodata_footers(self):\n        wrapped = DummyProducer(b'')\n        producer = self._makeOne(wrapped, footers=[b'a', b'b'])\n        self.assertEqual(producer.more(), b'0\\r\\na\\r\\nb\\r\\n\\r\\n')\n\n    def test_more_nodata_nofooters(self):\n        wrapped = DummyProducer(b'')\n        producer = self._makeOne(wrapped)\n        self.assertEqual(producer.more(), b'0\\r\\n\\r\\n')\n\n    def test_more_noproducer(self):\n        producer = self._makeOne(None)\n        self.assertEqual(producer.more(), b'')\n\nclass DeferringCompositeProducerTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.http import deferring_composite_producer\n        return deferring_composite_producer\n\n    def _makeOne(self, producers):\n        return self._getTargetClass()(producers)\n\n    def test_more_not_done_yet(self):\n        wrapped = DummyProducer(NOT_DONE_YET)\n        producer = self._makeOne([wrapped])\n        self.assertEqual(producer.more(), NOT_DONE_YET)\n\n    def test_more_string(self):\n        wrapped1 = DummyProducer('hello')\n        wrapped2 = DummyProducer('goodbye')\n        producer = self._makeOne([wrapped1, wrapped2])\n        self.assertEqual(producer.more(), 'hello')\n        self.assertEqual(producer.more(), 'goodbye')\n        self.assertEqual(producer.more(), b'')\n\n    def test_more_nodata(self):\n        wrapped = DummyProducer()\n        producer = self._makeOne([wrapped])\n        self.assertEqual(producer.more(), b'')\n\nclass DeferringGlobbingProducerTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.http import deferring_globbing_producer\n        return deferring_globbing_producer\n\n    def _makeOne(self, producer, buffer_size=1<<16):\n        return self._getTargetClass()(producer, buffer_size)\n\n    def test_more_not_done_yet(self):\n        wrapped = DummyProducer(NOT_DONE_YET)\n        producer = self._makeOne(wrapped)\n        self.assertEqual(producer.more(), NOT_DONE_YET)\n\n    def test_more_string(self):\n        wrapped = DummyProducer('hello', 'there', 'guy')\n        producer = self._makeOne(wrapped, buffer_size=1)\n        self.assertEqual(producer.more(), b'hello')\n\n        wrapped = DummyProducer('hello', 'there', 'guy')\n        producer = self._makeOne(wrapped, buffer_size=50)\n        self.assertEqual(producer.more(), b'hellothereguy')\n\n    def test_more_nodata(self):\n        wrapped = DummyProducer()\n        producer = self._makeOne(wrapped)\n        self.assertEqual(producer.more(), b'')\n\nclass DeferringHookedProducerTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.http import deferring_hooked_producer\n        return deferring_hooked_producer\n\n    def _makeOne(self, producer, function):\n        return self._getTargetClass()(producer, function)\n\n    def test_more_not_done_yet(self):\n        wrapped = DummyProducer(NOT_DONE_YET)\n        producer = self._makeOne(wrapped, None)\n        self.assertEqual(producer.more(), NOT_DONE_YET)\n\n    def test_more_string(self):\n        wrapped = DummyProducer('hello')\n        L = []\n        def callback(bytes):\n            L.append(bytes)\n        producer = self._makeOne(wrapped, callback)\n        self.assertEqual(producer.more(), 'hello')\n        self.assertEqual(L, [])\n        producer.more()\n        self.assertEqual(L, [5])\n\n    def test_more_nodata(self):\n        wrapped = DummyProducer()\n        L = []\n        def callback(bytes):\n            L.append(bytes)\n        producer = self._makeOne(wrapped, callback)\n        self.assertEqual(producer.more(), b'')\n        self.assertEqual(L, [0])\n\n    def test_more_noproducer(self):\n        producer = self._makeOne(None, None)\n        self.assertEqual(producer.more(), b'')\n\nclass DeferringHttpRequestTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.http import deferring_http_request\n        return deferring_http_request\n\n    def _makeOne(\n        self,\n        channel=None,\n        req='GET / HTTP/1.0',\n        command='GET',\n        uri='/',\n        version='1.0',\n        header=(),\n        ):\n        return self._getTargetClass()(\n            channel, req, command, uri, version, header\n            )\n\n    def _makeChannel(self):\n        class Channel:\n            closed = False\n            def close_when_done(self):\n                self.closed = True\n            def push_with_producer(self, producer):\n                self.producer = producer\n        return Channel()\n\n    def test_done_http_10_nokeepalive(self):\n        channel = self._makeChannel()\n        inst = self._makeOne(channel=channel, version='1.0')\n        inst.done()\n        self.assertTrue(channel.closed)\n\n    def test_done_http_10_keepalive_no_content_length(self):\n        channel = self._makeChannel()\n        inst = self._makeOne(\n            channel=channel,\n            version='1.0',\n            header=['Connection: Keep-Alive'],\n            )\n\n        inst.done()\n        self.assertTrue(channel.closed)\n\n    def test_done_http_10_keepalive_and_content_length(self):\n        channel = self._makeChannel()\n        inst = self._makeOne(\n            channel=channel,\n            version='1.0',\n            header=['Connection: Keep-Alive'],\n            )\n        inst.reply_headers['Content-Length'] = 1\n        inst.done()\n        self.assertEqual(inst['Connection'], 'Keep-Alive')\n        self.assertFalse(channel.closed)\n\n    def test_done_http_11_connection_close(self):\n        channel = self._makeChannel()\n        inst = self._makeOne(\n            channel=channel,\n            version='1.1',\n            header=['Connection: close']\n            )\n        inst.done()\n        self.assertTrue(channel.closed)\n\n    def test_done_http_11_unknown_transfer_encoding(self):\n        channel = self._makeChannel()\n        inst = self._makeOne(\n            channel=channel,\n            version='1.1',\n            )\n        inst.reply_headers['Transfer-Encoding'] = 'notchunked'\n        inst.done()\n        self.assertTrue(channel.closed)\n\n    def test_done_http_11_chunked_transfer_encoding(self):\n        channel = self._makeChannel()\n        inst = self._makeOne(\n            channel=channel,\n            version='1.1',\n            )\n        inst.reply_headers['Transfer-Encoding'] = 'chunked'\n        inst.done()\n        self.assertFalse(channel.closed)\n\n    def test_done_http_11_use_chunked(self):\n        channel = self._makeChannel()\n        inst = self._makeOne(\n            channel=channel,\n            version='1.1',\n            )\n        inst.use_chunked = True\n        inst.done()\n        self.assertTrue('Transfer-Encoding' in inst)\n        self.assertFalse(channel.closed)\n\n    def test_done_http_11_wo_content_length_no_te_no_use_chunked_close(self):\n        channel = self._makeChannel()\n        inst = self._makeOne(\n            channel=channel,\n            version='1.1',\n            )\n        inst.use_chunked = False\n        inst.done()\n        self.assertTrue(channel.closed)\n\n    def test_done_http_09(self):\n        channel = self._makeChannel()\n        inst = self._makeOne(\n            channel=channel,\n            version=None,\n            )\n        inst.done()\n        self.assertTrue(channel.closed)\n\nclass DeferringHttpChannelTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.http import deferring_http_channel\n        return deferring_http_channel\n\n    def _makeOne(self):\n        return self._getTargetClass()(\n            server=None,\n            conn=None,\n            addr=None\n            )\n\n    def test_defaults_delay_and_last_writable_check_time(self):\n        channel = self._makeOne()\n        self.assertEqual(channel.delay, 0)\n        self.assertEqual(channel.last_writable_check, 0)\n\n    def test_writable_with_delay_is_False_if_elapsed_lt_delay(self):\n        channel = self._makeOne()\n        channel.delay = 2\n        channel.last_writable_check = _NOW\n        later = _NOW + 1\n        self.assertFalse(channel.writable(now=later))\n        self.assertEqual(channel.last_writable_check, _NOW)\n\n    def test_writable_with_delay_is_False_if_elapsed_eq_delay(self):\n        channel = self._makeOne()\n        channel.delay = 2\n        channel.last_writable_check = _NOW\n        later = _NOW + channel.delay\n        self.assertFalse(channel.writable(now=later))\n        self.assertEqual(channel.last_writable_check, _NOW)\n\n    def test_writable_with_delay_is_True_if_elapsed_gt_delay(self):\n        channel = self._makeOne()\n        channel.delay = 2\n        channel.last_writable_check = _NOW\n        later = _NOW + channel.delay + 0.1\n        self.assertTrue(channel.writable(now=later))\n        self.assertEqual(channel.last_writable_check, later)\n\n    def test_writable_with_delay_is_True_if_system_time_goes_backwards(self):\n        channel = self._makeOne()\n        channel.delay = 2\n        channel.last_writable_check = _NOW\n        later = _NOW - 3600 # last check was in the future\n        self.assertTrue(channel.writable(now=later))\n        self.assertEqual(channel.last_writable_check, later)\n\n_NOW = 1470085990\n\nclass EncryptedDictionaryAuthorizedTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.http import encrypted_dictionary_authorizer\n        return encrypted_dictionary_authorizer\n\n    def _makeOne(self, dict):\n        return self._getTargetClass()(dict)\n\n    def test_authorize_baduser(self):\n        authorizer = self._makeOne({})\n        self.assertFalse(authorizer.authorize(('foo', 'bar')))\n\n    def test_authorize_gooduser_badpassword(self):\n        authorizer = self._makeOne({'foo':'password'})\n        self.assertFalse(authorizer.authorize(('foo', 'bar')))\n\n    def test_authorize_gooduser_goodpassword(self):\n        authorizer = self._makeOne({'foo':'password'})\n        self.assertTrue(authorizer.authorize(('foo', 'password')))\n\n    def test_authorize_gooduser_goodpassword_with_colon(self):\n        authorizer = self._makeOne({'foo':'pass:word'})\n        self.assertTrue(authorizer.authorize(('foo', 'pass:word')))\n\n    def test_authorize_gooduser_badpassword_sha(self):\n        password = '{SHA}' + sha1(as_bytes('password')).hexdigest()\n        authorizer = self._makeOne({'foo':password})\n        self.assertFalse(authorizer.authorize(('foo', 'bar')))\n\n    def test_authorize_gooduser_goodpassword_sha(self):\n        password = '{SHA}' + sha1(as_bytes('password')).hexdigest()\n        authorizer = self._makeOne({'foo':password})\n        self.assertTrue(authorizer.authorize(('foo', 'password')))\n\nclass SupervisorAuthHandlerTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.http import supervisor_auth_handler\n        return supervisor_auth_handler\n\n    def _makeOne(self, dict, handler):\n        return self._getTargetClass()(dict, handler)\n\n    def test_ctor(self):\n        handler = self._makeOne({'a':1}, None)\n        from supervisor.http import encrypted_dictionary_authorizer\n        self.assertEqual(handler.authorizer.__class__,\n                         encrypted_dictionary_authorizer)\n\n    def test_handle_request_authorizes_good_credentials(self):\n        request = DummyRequest('/logtail/process1', None, None, None)\n        encoded = base64.b64encode(as_bytes(\"user:password\"))\n        request.header = [\"Authorization: Basic %s\" % as_string(encoded)]\n        handler = DummyHandler()\n        auth_handler = self._makeOne({'user':'password'}, handler)\n        auth_handler.handle_request(request)\n        self.assertTrue(handler.handled_request)\n\n    def test_handle_request_authorizes_good_password_with_colon(self):\n        request = DummyRequest('/logtail/process1', None, None, None)\n        # password contains colon\n        encoded = base64.b64encode(as_bytes(\"user:pass:word\"))\n        request.header = [\"Authorization: Basic %s\" % as_string(encoded)]\n        handler = DummyHandler()\n        auth_handler = self._makeOne({'user':'pass:word'}, handler)\n        auth_handler.handle_request(request)\n        self.assertTrue(handler.handled_request)\n\n    def test_handle_request_does_not_authorize_bad_credentials(self):\n        request = DummyRequest('/logtail/process1', None, None, None)\n        encoded = base64.b64encode(as_bytes(\"wrong:wrong\"))\n        request.header = [\"Authorization: Basic %s\" % as_string(encoded)]\n        handler = DummyHandler()\n        auth_handler = self._makeOne({'user':'password'}, handler)\n        auth_handler.handle_request(request)\n        self.assertFalse(handler.handled_request)\n\nclass LogWrapperTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.http import LogWrapper\n        return LogWrapper\n\n    def _makeOne(self, logger):\n        return self._getTargetClass()(logger)\n\n    def test_strips_trailing_newlines_from_msgs(self):\n        logger = DummyLogger()\n        log_wrapper = self._makeOne(logger)\n        log_wrapper.log(\"foo\\n\")\n        logdata = logger.data\n        self.assertEqual(len(logdata), 1)\n        self.assertEqual(logdata[0], \"foo\")\n\n    def test_logs_msgs_with_error_at_error_level(self):\n        logger = DummyLogger()\n        log_wrapper = self._makeOne(logger)\n        errors = []\n        logger.error = errors.append\n        log_wrapper.log(\"Server Error\")\n        self.assertEqual(len(errors), 1)\n        self.assertEqual(errors[0], \"Server Error\")\n\n    def test_logs_other_messages_at_trace_level(self):\n        logger = DummyLogger()\n        log_wrapper = self._makeOne(logger)\n        traces = []\n        logger.trace = traces.append\n        log_wrapper.log(\"GET /\")\n        self.assertEqual(len(traces), 1)\n        self.assertEqual(traces[0], \"GET /\")\n\nclass TopLevelFunctionTests(unittest.TestCase):\n    def _make_http_servers(self, sconfigs):\n        options = DummyOptions()\n        options.server_configs = sconfigs\n        options.rpcinterface_factories = [('dummy',DummyRPCInterfaceFactory,{})]\n        supervisord = DummySupervisor()\n        from supervisor.http import make_http_servers\n        servers = make_http_servers(options, supervisord)\n        try:\n            for config, s in servers:\n                s.close()\n                socketfile = config.get('file')\n                if socketfile is not None:\n                    os.unlink(socketfile)\n        finally:\n            from supervisor.medusa.asyncore_25 import socket_map\n            socket_map.clear()\n        return servers\n\n    def test_make_http_servers_socket_type_error(self):\n        config = {'family':999, 'host':'localhost', 'port':17735,\n                  'username':None, 'password':None,\n                  'section':'inet_http_server'}\n        try:\n            self._make_http_servers([config])\n            self.fail('nothing raised')\n        except ValueError as exc:\n            self.assertEqual(exc.args[0], 'Cannot determine socket type 999')\n\n    def test_make_http_servers_noauth(self):\n        with tempfile.NamedTemporaryFile(delete=True) as f:\n            socketfile = f.name\n        self.assertFalse(os.path.exists(socketfile))\n\n        inet = {'family':socket.AF_INET, 'host':'localhost', 'port':17735,\n                'username':None, 'password':None, 'section':'inet_http_server'}\n        unix = {'family':socket.AF_UNIX, 'file':socketfile, 'chmod':0o700,\n                'chown':(-1, -1), 'username':None, 'password':None,\n                'section':'unix_http_server'}\n        servers = self._make_http_servers([inet, unix])\n        self.assertEqual(len(servers), 2)\n\n        inetdata = servers[0]\n        self.assertEqual(inetdata[0], inet)\n        server = inetdata[1]\n        idents = [\n            'Supervisor XML-RPC Handler',\n            'Logtail HTTP Request Handler',\n            'Main Logtail HTTP Request Handler',\n            'Supervisor Web UI HTTP Request Handler',\n            'Default HTTP Request Handler'\n            ]\n        self.assertEqual([x.IDENT for x in server.handlers], idents)\n\n        unixdata = servers[1]\n        self.assertEqual(unixdata[0], unix)\n        server = unixdata[1]\n        self.assertEqual([x.IDENT for x in server.handlers], idents)\n\n    def test_make_http_servers_withauth(self):\n        with tempfile.NamedTemporaryFile(delete=True) as f:\n            socketfile = f.name\n        self.assertFalse(os.path.exists(socketfile))\n\n        inet = {'family':socket.AF_INET, 'host':'localhost', 'port':17736,\n                'username':'username', 'password':'password',\n                'section':'inet_http_server'}\n        unix = {'family':socket.AF_UNIX, 'file':socketfile, 'chmod':0o700,\n                'chown':(-1, -1), 'username':'username', 'password':'password',\n                'section':'unix_http_server'}\n        servers = self._make_http_servers([inet, unix])\n        self.assertEqual(len(servers), 2)\n        from supervisor.http import supervisor_auth_handler\n        for config, server in servers:\n            for handler in server.handlers:\n                self.assertTrue(isinstance(handler, supervisor_auth_handler),\n                                handler)\n\nclass DummyHandler:\n    def __init__(self):\n        self.handled_request = False\n\n    def handle_request(self, request):\n        self.handled_request = True\n\nclass DummyProducer:\n    def __init__(self, *data):\n        self.data = list(data)\n\n    def more(self):\n        if self.data:\n            return self.data.pop(0)\n        else:\n            return b''\n"
  },
  {
    "path": "supervisor/tests/test_http_client.py",
    "content": "import socket\nimport sys\nimport unittest\n\nfrom supervisor.compat import as_bytes\nfrom supervisor.compat import StringIO\n\nclass ListenerTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.http_client import Listener\n        return Listener\n\n    def _makeOne(self):\n        return self._getTargetClass()()\n\n    def test_status(self):\n        inst = self._makeOne()\n        self.assertEqual(inst.status(None, None), None)\n\n    def test_error(self):\n        inst = self._makeOne()\n        try:\n            old_stderr = sys.stderr\n            stderr = StringIO()\n            sys.stderr = stderr\n            self.assertEqual(inst.error('url', 'error'), None)\n            self.assertEqual(stderr.getvalue(), 'url error\\n')\n        finally:\n            sys.stderr = old_stderr\n\n    def test_response_header(self):\n        inst = self._makeOne()\n        self.assertEqual(inst.response_header(None, None, None), None)\n\n    def test_done(self):\n        inst = self._makeOne()\n        self.assertEqual(inst.done(None), None)\n\n    def test_feed(self):\n        inst = self._makeOne()\n        try:\n            old_stdout = sys.stdout\n            stdout = StringIO()\n            sys.stdout = stdout\n            inst.feed('url', 'data')\n            self.assertEqual(stdout.getvalue(), 'data')\n        finally:\n            sys.stdout = old_stdout\n\n    def test_close(self):\n        inst = self._makeOne()\n        self.assertEqual(inst.close(None), None)\n\nclass HTTPHandlerTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.http_client import HTTPHandler\n        return HTTPHandler\n\n    def _makeOne(self, listener=None, username='', password=None):\n        if listener is None:\n            listener = self._makeListener()\n        socket_map = {}\n        return self._getTargetClass()(\n            listener,\n            username,\n            password,\n            map=socket_map,\n            )\n\n    def _makeListener(self):\n        listener = DummyListener()\n        return listener\n\n    def test_get_url_not_None(self):\n        inst = self._makeOne()\n        inst.url = 'abc'\n        self.assertRaises(AssertionError, inst.get, 'abc')\n\n    def test_get_bad_scheme(self):\n        inst = self._makeOne()\n        self.assertRaises(\n            NotImplementedError,\n            inst.get,\n            'nothttp://localhost',\n            '/abc'\n            )\n\n    def test_get_implied_port_80(self):\n        inst = self._makeOne()\n        sockets = []\n        connects = []\n        inst.create_socket = lambda *arg: sockets.append(arg)\n        inst.connect = lambda tup: connects.append(tup)\n        inst.get('http://localhost', '/abc/def')\n        self.assertEqual(inst.port, 80)\n        self.assertEqual(sockets, [(socket.AF_INET, socket.SOCK_STREAM)])\n        self.assertEqual(connects, [('localhost', 80)])\n\n    def test_get_explicit_port(self):\n        inst = self._makeOne()\n        sockets = []\n        connects = []\n        inst.create_socket = lambda *arg: sockets.append(arg)\n        inst.connect = lambda tup: connects.append(tup)\n        inst.get('http://localhost:8080', '/abc/def')\n        self.assertEqual(inst.port, 8080)\n        self.assertEqual(sockets, [(socket.AF_INET, socket.SOCK_STREAM)])\n        self.assertEqual(connects, [('localhost', 8080)])\n\n    def test_get_explicit_unix_domain_socket(self):\n        inst = self._makeOne()\n        sockets = []\n        connects = []\n        inst.create_socket = lambda *arg: sockets.append(arg)\n        inst.connect = lambda tup: connects.append(tup)\n        inst.get('unix:///a/b/c', '')\n        self.assertEqual(sockets, [(socket.AF_UNIX, socket.SOCK_STREAM)])\n        self.assertEqual(connects, ['/a/b/c'])\n\n    def test_close(self):\n        inst = self._makeOne()\n        dels = []\n        inst.del_channel = lambda: dels.append(True)\n        inst.socket = DummySocket()\n        inst.close()\n        self.assertEqual(inst.listener.closed, None)\n        self.assertEqual(inst.connected, 0)\n        self.assertEqual(dels, [True])\n        self.assertTrue(inst.socket.closed)\n        self.assertEqual(inst.url, 'CLOSED')\n\n    def test_header(self):\n        from supervisor.http_client import CRLF\n        inst = self._makeOne()\n        pushes = []\n        inst.push = lambda val: pushes.append(val)\n        inst.header('name', 'val')\n        self.assertEqual(pushes, ['name: val', CRLF])\n\n    def test_handle_error_already_handled(self):\n        inst = self._makeOne()\n        inst.error_handled = True\n        self.assertEqual(inst.handle_error(), None)\n\n    def test_handle_error(self):\n        inst = self._makeOne()\n        closed = []\n        inst.close = lambda: closed.append(True)\n        inst.url = 'foo'\n        self.assertEqual(inst.handle_error(), None)\n        self.assertEqual(inst.listener.error_url, 'foo')\n        self.assertEqual(\n            inst.listener.error_msg,\n            'Cannot connect, error: None (None)',\n            )\n        self.assertEqual(closed, [True])\n        self.assertTrue(inst.error_handled)\n\n    def test_handle_connect_no_password(self):\n        inst = self._makeOne()\n        pushed = []\n        inst.push = lambda val: pushed.append(as_bytes(val))\n        inst.path = '/'\n        inst.host = 'localhost'\n        inst.handle_connect()\n        self.assertTrue(inst.connected)\n        self.assertEqual(\n            pushed,\n            [b'GET / HTTP/1.1',\n             b'\\r\\n',\n             b'Host: localhost',\n             b'\\r\\n',\n             b'Accept-Encoding: chunked',\n             b'\\r\\n',\n             b'Accept: */*',\n             b'\\r\\n',\n             b'User-agent: Supervisor HTTP Client',\n             b'\\r\\n',\n             b'\\r\\n',\n             b'\\r\\n']\n            )\n\n    def test_handle_connect_with_password(self):\n        inst = self._makeOne()\n        pushed = []\n        inst.push = lambda val: pushed.append(as_bytes(val))\n        inst.path = '/'\n        inst.host = 'localhost'\n        inst.password = 'password'\n        inst.username = 'username'\n        inst.handle_connect()\n        self.assertTrue(inst.connected)\n        self.assertEqual(\n            pushed,\n             [b'GET / HTTP/1.1',\n              b'\\r\\n',\n              b'Host: localhost',\n              b'\\r\\n',\n              b'Accept-Encoding: chunked',\n              b'\\r\\n',\n              b'Accept: */*',\n              b'\\r\\n',\n              b'User-agent: Supervisor HTTP Client',\n              b'\\r\\n',\n              b'Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=',\n              b'\\r\\n',\n              b'\\r\\n',\n              b'\\r\\n'],\n            )\n\n    def test_feed(self):\n        inst = self._makeOne()\n        inst.feed('data')\n        self.assertEqual(inst.listener.fed_data, ['data'])\n\n    def test_collect_incoming_data_part_is_body(self):\n        inst = self._makeOne()\n        inst.part = inst.body\n        inst.buffer = 'abc'\n        inst.collect_incoming_data('foo')\n        self.assertEqual(inst.listener.fed_data, ['abcfoo'])\n        self.assertEqual(inst.buffer, b'')\n\n    def test_collect_incoming_data_part_is_not_body(self):\n        inst = self._makeOne()\n        inst.part = None\n        inst.buffer = 'abc'\n        inst.collect_incoming_data('foo')\n        self.assertEqual(inst.listener.fed_data, [])\n        self.assertEqual(inst.buffer, 'abcfoo')\n\n    def test_found_terminator(self):\n        inst = self._makeOne()\n        parted = []\n        inst.part = lambda: parted.append(True)\n        inst.buffer = None\n        inst.found_terminator()\n        self.assertEqual(parted, [True])\n        self.assertEqual(inst.buffer, b'')\n\n    def test_ignore(self):\n        inst = self._makeOne()\n        inst.buffer = None\n        inst.ignore()\n        self.assertEqual(inst.buffer, b'')\n\n    def test_status_line_not_startswith_http(self):\n        inst = self._makeOne()\n        inst.buffer = b'NOTHTTP/1.0 200 OK'\n        self.assertRaises(ValueError, inst.status_line)\n\n    def test_status_line_200(self):\n        inst = self._makeOne()\n        inst.buffer = b'HTTP/1.0 200 OK'\n        version, status, reason = inst.status_line()\n        self.assertEqual(version, b'HTTP/1.0')\n        self.assertEqual(status, 200)\n        self.assertEqual(reason, b'OK')\n        self.assertEqual(inst.part, inst.headers)\n\n    def test_status_line_not_200(self):\n        inst = self._makeOne()\n        inst.buffer = b'HTTP/1.0 201 OK'\n        closed = []\n        inst.close = lambda: closed.append(True)\n        version, status, reason = inst.status_line()\n        self.assertEqual(version, b'HTTP/1.0')\n        self.assertEqual(status, 201)\n        self.assertEqual(reason, b'OK')\n        self.assertEqual(inst.part, inst.ignore)\n        self.assertEqual(\n            inst.listener.error_msg,\n            'Cannot read, status code 201'\n            )\n        self.assertEqual(closed, [True])\n\n    def test_headers_empty_line_nonchunked(self):\n        inst = self._makeOne()\n        inst.buffer = b''\n        inst.encoding = b'not chunked'\n        inst.length = 3\n        terms = []\n        inst.set_terminator = lambda L: terms.append(L)\n        inst.headers()\n        self.assertEqual(inst.part, inst.body)\n        self.assertEqual(terms, [3])\n\n    def test_headers_empty_line_chunked(self):\n        inst = self._makeOne()\n        inst.buffer = b''\n        inst.encoding = b'chunked'\n        inst.headers()\n        self.assertEqual(inst.part, inst.chunked_size)\n\n    def test_headers_nonempty_line_no_name_no_value(self):\n        inst = self._makeOne()\n        inst.buffer = b':'\n        self.assertEqual(inst.headers(), None)\n\n    def test_headers_nonempty_line_transfer_encoding(self):\n        inst = self._makeOne()\n        inst.buffer = b'Transfer-Encoding: chunked'\n        responses = []\n        inst.response_header = lambda n, v: responses.append((n, v))\n        inst.headers()\n        self.assertEqual(inst.encoding, b'chunked')\n        self.assertEqual(responses, [(b'transfer-encoding', b'chunked')])\n\n    def test_headers_nonempty_line_content_length(self):\n        inst = self._makeOne()\n        inst.buffer = b'Content-Length: 3'\n        responses = []\n        inst.response_header = lambda n, v: responses.append((n, v))\n        inst.headers()\n        self.assertEqual(inst.length, 3)\n        self.assertEqual(responses, [(b'content-length', b'3')])\n\n    def test_headers_nonempty_line_arbitrary(self):\n        inst = self._makeOne()\n        inst.buffer = b'X-Test: abc'\n        responses = []\n        inst.response_header = lambda n, v: responses.append((n, v))\n        inst.headers()\n        self.assertEqual(responses, [(b'x-test', b'abc')])\n\n    def test_response_header(self):\n        inst = self._makeOne()\n        inst.response_header(b'a', b'b')\n        self.assertEqual(inst.listener.response_header_name, b'a')\n        self.assertEqual(inst.listener.response_header_value, b'b')\n\n    def test_body(self):\n        inst = self._makeOne()\n        closed = []\n        inst.close = lambda: closed.append(True)\n        inst.body()\n        self.assertEqual(closed, [True])\n        self.assertTrue(inst.listener.done)\n\n    def test_done(self):\n        inst = self._makeOne()\n        inst.done()\n        self.assertTrue(inst.listener.done)\n\n    def test_chunked_size_empty_line(self):\n        inst = self._makeOne()\n        inst.buffer = b''\n        inst.length = 1\n        self.assertEqual(inst.chunked_size(), None)\n        self.assertEqual(inst.length, 1)\n\n    def test_chunked_size_zero_size(self):\n        inst = self._makeOne()\n        inst.buffer = b'0'\n        inst.length = 1\n        self.assertEqual(inst.chunked_size(), None)\n        self.assertEqual(inst.length, 1)\n        self.assertEqual(inst.part, inst.trailer)\n\n    def test_chunked_size_nonzero_size(self):\n        inst = self._makeOne()\n        inst.buffer = b'10'\n        inst.length = 1\n        terms = []\n        inst.set_terminator = lambda sz: terms.append(sz)\n        self.assertEqual(inst.chunked_size(), None)\n        self.assertEqual(inst.part, inst.chunked_body)\n        self.assertEqual(inst.length, 17)\n        self.assertEqual(terms, [16])\n\n    def test_chunked_body(self):\n        from supervisor.http_client import CRLF\n        inst = self._makeOne()\n        inst.buffer = b'buffer'\n        terms = []\n        lines = []\n        inst.set_terminator = lambda v: terms.append(v)\n        inst.feed = lambda v: lines.append(v)\n        inst.chunked_body()\n        self.assertEqual(terms, [CRLF])\n        self.assertEqual(lines, [b'buffer'])\n        self.assertEqual(inst.part, inst.chunked_size)\n\n    def test_trailer_line_not_crlf(self):\n        inst = self._makeOne()\n        inst.buffer = b''\n        self.assertEqual(inst.trailer(), None)\n\n    def test_trailer_line_crlf(self):\n        from supervisor.http_client import CRLF\n        inst = self._makeOne()\n        inst.buffer = CRLF\n        dones = []\n        closes = []\n        inst.done = lambda: dones.append(True)\n        inst.close = lambda: closes.append(True)\n        self.assertEqual(inst.trailer(), None)\n        self.assertEqual(dones, [True])\n        self.assertEqual(closes, [True])\n\nclass DummyListener(object):\n    closed = None\n    error_url = None\n    error_msg = None\n    done = False\n    def __init__(self):\n        self.fed_data = []\n\n    def close(self, url):\n        self.closed = url\n\n    def error(self, url, msg):\n        self.error_url = url\n        self.error_msg = msg\n\n    def feed(self, url, data):\n        self.fed_data.append(data)\n\n    def status(self, url, int):\n        self.status_url = url\n        self.status_int = int\n\n    def response_header(self, url, name, value):\n        self.response_header_name = name\n        self.response_header_value = value\n\n    def done(self, url):\n        self.done = True\n\nclass DummySocket(object):\n    closed = False\n    def close(self):\n        self.closed = True\n"
  },
  {
    "path": "supervisor/tests/test_loggers.py",
    "content": "# -*- coding: utf-8 -*-\nimport errno\nimport sys\nimport unittest\nimport tempfile\nimport shutil\nimport os\nimport syslog\n\nfrom supervisor.compat import PY2\nfrom supervisor.compat import as_string\nfrom supervisor.compat import StringIO\nfrom supervisor.compat import unicode\n\nfrom supervisor.tests.base import mock\nfrom supervisor.tests.base import DummyStream\n\nclass LevelTests(unittest.TestCase):\n    def test_LOG_LEVELS_BY_NUM_doesnt_include_builtins(self):\n        from supervisor import loggers\n        for level_name in loggers.LOG_LEVELS_BY_NUM.values():\n            self.assertFalse(level_name.startswith('_'))\n\nclass HandlerTests:\n    def setUp(self):\n        self.basedir = tempfile.mkdtemp()\n        self.filename = os.path.join(self.basedir, 'thelog')\n\n    def tearDown(self):\n        try:\n            shutil.rmtree(self.basedir)\n        except OSError:\n            pass\n\n    def _makeOne(self, *arg, **kw):\n        klass = self._getTargetClass()\n        return klass(*arg, **kw)\n\n    def _makeLogRecord(self, msg):\n        from supervisor import loggers\n        record = loggers.LogRecord(\n            level=loggers.LevelsByName.INFO,\n            msg=msg,\n            exc_info=None\n            )\n        return record\n\nclass BareHandlerTests(HandlerTests, unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.loggers import Handler\n        return Handler\n\n    def test_flush_stream_flush_raises_IOError_EPIPE(self):\n        stream = DummyStream(error=IOError(errno.EPIPE))\n        inst = self._makeOne(stream=stream)\n        self.assertEqual(inst.flush(), None) # does not raise\n\n    def test_flush_stream_flush_raises_IOError_not_EPIPE(self):\n        stream = DummyStream(error=IOError(errno.EALREADY))\n        inst = self._makeOne(stream=stream)\n        self.assertRaises(IOError, inst.flush) # non-EPIPE IOError raises\n\n    def test_close_already_closed(self):\n        stream = DummyStream()\n        inst = self._makeOne(stream=stream)\n        inst.closed = True\n        self.assertEqual(inst.close(), None)\n\n    def test_close_stream_fileno_above_3(self):\n        stream = DummyStream(fileno=50)\n        inst = self._makeOne(stream=stream)\n        self.assertEqual(inst.close(), None)\n        self.assertTrue(inst.closed)\n        self.assertTrue(inst.stream.closed)\n\n    def test_close_stream_fileno_below_3(self):\n        stream = DummyStream(fileno=0)\n        inst = self._makeOne(stream=stream)\n        self.assertEqual(inst.close(), None)\n        self.assertFalse(inst.closed)\n        self.assertFalse(inst.stream.closed)\n\n    def test_close_stream_handles_fileno_unsupported_operation(self):\n        # on python 2, StringIO does not have fileno()\n        # on python 3, StringIO has fileno() but calling it raises\n        stream = StringIO()\n        inst = self._makeOne(stream=stream)\n        inst.close() # shouldn't raise\n        self.assertTrue(inst.closed)\n\n    def test_close_stream_handles_fileno_ioerror(self):\n        stream = DummyStream()\n        def raise_ioerror():\n            raise IOError()\n        stream.fileno = raise_ioerror\n        inst = self._makeOne(stream=stream)\n        inst.close() # shouldn't raise\n        self.assertTrue(inst.closed)\n\n    def test_emit_gardenpath(self):\n        stream = DummyStream()\n        inst = self._makeOne(stream=stream)\n        record = self._makeLogRecord(b'foo')\n        inst.emit(record)\n        self.assertEqual(stream.flushed, True)\n        self.assertEqual(stream.written, b'foo')\n\n    def test_emit_unicode_error(self):\n        stream = DummyStream(error=UnicodeError)\n        inst = self._makeOne(stream=stream)\n        record = self._makeLogRecord(b'foo')\n        inst.emit(record)\n        self.assertEqual(stream.flushed, True)\n        self.assertEqual(stream.written, b'foo')\n\n    def test_emit_other_error(self):\n        stream = DummyStream(error=ValueError)\n        inst = self._makeOne(stream=stream)\n        handled = []\n        inst.handleError = lambda: handled.append(True)\n        record = self._makeLogRecord(b'foo')\n        inst.emit(record)\n        self.assertEqual(stream.flushed, False)\n        self.assertEqual(stream.written, b'')\n\nclass FileHandlerTests(HandlerTests, unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.loggers import FileHandler\n        return FileHandler\n\n    def test_ctor(self):\n        handler = self._makeOne(self.filename)\n        self.assertTrue(os.path.exists(self.filename), self.filename)\n        self.assertEqual(handler.mode, 'ab')\n        self.assertEqual(handler.baseFilename, self.filename)\n        self.assertEqual(handler.stream.name, self.filename)\n        handler.close()\n\n    def test_close(self):\n        handler = self._makeOne(self.filename)\n        handler.stream.close()\n        handler.stream = DummyStream()\n        handler.close()\n        self.assertEqual(handler.stream.closed, True)\n\n    def test_close_raises(self):\n        handler = self._makeOne(self.filename)\n        handler.stream.close()\n        handler.stream = DummyStream(OSError)\n        self.assertRaises(OSError, handler.close)\n        self.assertEqual(handler.stream.closed, False)\n\n    def test_reopen(self):\n        handler = self._makeOne(self.filename)\n        handler.stream.close()\n        stream = DummyStream()\n        handler.stream = stream\n        handler.reopen()\n        self.assertEqual(stream.closed, True)\n        self.assertEqual(handler.stream.name, self.filename)\n        handler.close()\n\n    def test_reopen_raises(self):\n        handler = self._makeOne(self.filename)\n        handler.stream.close()\n        stream = DummyStream()\n        handler.stream = stream\n        handler.baseFilename = os.path.join(self.basedir, 'notthere', 'a.log')\n        self.assertRaises(IOError, handler.reopen)\n        self.assertEqual(stream.closed, True)\n\n    def test_remove_exists(self):\n        handler = self._makeOne(self.filename)\n        self.assertTrue(os.path.exists(self.filename), self.filename)\n        handler.remove()\n        self.assertFalse(os.path.exists(self.filename), self.filename)\n\n    def test_remove_doesntexist(self):\n        handler = self._makeOne(self.filename)\n        os.remove(self.filename)\n        self.assertFalse(os.path.exists(self.filename), self.filename)\n        handler.remove() # should not raise\n        self.assertFalse(os.path.exists(self.filename), self.filename)\n\n    def test_remove_raises(self):\n        handler = self._makeOne(self.filename)\n        os.remove(self.filename)\n        os.mkdir(self.filename)\n        self.assertTrue(os.path.exists(self.filename), self.filename)\n        self.assertRaises(OSError, handler.remove)\n\n    def test_emit_ascii_noerror(self):\n        handler = self._makeOne(self.filename)\n        record = self._makeLogRecord(b'hello!')\n        handler.emit(record)\n        handler.close()\n        with open(self.filename, 'rb') as f:\n            self.assertEqual(f.read(), b'hello!')\n\n    def test_emit_unicode_noerror(self):\n        handler = self._makeOne(self.filename)\n        record = self._makeLogRecord(b'fi\\xc3\\xad')\n        handler.emit(record)\n        handler.close()\n        with open(self.filename, 'rb') as f:\n            self.assertEqual(f.read(), b'fi\\xc3\\xad')\n\n    def test_emit_error(self):\n        handler = self._makeOne(self.filename)\n        handler.stream.close()\n        handler.stream = DummyStream(error=OSError)\n        record = self._makeLogRecord(b'hello!')\n        try:\n            old_stderr = sys.stderr\n            dummy_stderr = DummyStream()\n            sys.stderr = dummy_stderr\n            handler.emit(record)\n        finally:\n            sys.stderr = old_stderr\n\n        self.assertTrue(dummy_stderr.written.endswith(b'OSError\\n'),\n                        dummy_stderr.written)\n\nif os.path.exists('/dev/stdout'):\n    StdoutTestsBase = FileHandlerTests\nelse:\n    # Skip the stdout tests on platforms that don't have /dev/stdout.\n    StdoutTestsBase = object\n\nclass StdoutTests(StdoutTestsBase):\n    def test_ctor_with_dev_stdout(self):\n        handler = self._makeOne('/dev/stdout')\n        # Modes 'w' and 'a' have the same semantics when applied to\n        # character device files and fifos.\n        self.assertTrue(handler.mode in ['wb', 'ab'], handler.mode)\n        self.assertEqual(handler.baseFilename, '/dev/stdout')\n        self.assertEqual(handler.stream.name, '/dev/stdout')\n        handler.close()\n\nclass RotatingFileHandlerTests(FileHandlerTests):\n\n    def _getTargetClass(self):\n        from supervisor.loggers import RotatingFileHandler\n        return RotatingFileHandler\n\n    def test_ctor(self):\n        handler = self._makeOne(self.filename)\n        self.assertEqual(handler.mode, 'ab')\n        self.assertEqual(handler.maxBytes, 512*1024*1024)\n        self.assertEqual(handler.backupCount, 10)\n        handler.close()\n\n    def test_emit_does_rollover(self):\n        handler = self._makeOne(self.filename, maxBytes=10, backupCount=2)\n        record = self._makeLogRecord(b'a' * 4)\n\n        handler.emit(record) # 4 bytes\n        self.assertFalse(os.path.exists(self.filename + '.1'))\n        self.assertFalse(os.path.exists(self.filename + '.2'))\n\n        handler.emit(record) # 8 bytes\n        self.assertFalse(os.path.exists(self.filename + '.1'))\n        self.assertFalse(os.path.exists(self.filename + '.2'))\n\n        handler.emit(record) # 12 bytes, do rollover\n        self.assertTrue(os.path.exists(self.filename + '.1'))\n        self.assertFalse(os.path.exists(self.filename + '.2'))\n\n        handler.emit(record) # 16 bytes\n        self.assertTrue(os.path.exists(self.filename + '.1'))\n        self.assertFalse(os.path.exists(self.filename + '.2'))\n\n        handler.emit(record) # 20 bytes\n        self.assertTrue(os.path.exists(self.filename + '.1'))\n        self.assertFalse(os.path.exists(self.filename + '.2'))\n\n        handler.emit(record) # 24 bytes, do rollover\n        self.assertTrue(os.path.exists(self.filename + '.1'))\n        self.assertTrue(os.path.exists(self.filename + '.2'))\n\n        handler.emit(record) # 28 bytes\n        handler.close()\n        self.assertTrue(os.path.exists(self.filename + '.1'))\n        self.assertTrue(os.path.exists(self.filename + '.2'))\n\n        with open(self.filename, 'rb') as f:\n            self.assertEqual(f.read(), b'a' * 4)\n\n        with open(self.filename+'.1', 'rb') as f:\n            self.assertEqual(f.read(), b'a' * 12)\n\n        with open(self.filename+'.2', 'rb') as f:\n            self.assertEqual(f.read(), b'a' * 12)\n\n    def test_current_logfile_removed(self):\n        handler = self._makeOne(self.filename, maxBytes=6, backupCount=1)\n        record = self._makeLogRecord(b'a' * 4)\n\n        handler.emit(record) # 4 bytes\n        self.assertTrue(os.path.exists(self.filename))\n        self.assertFalse(os.path.exists(self.filename + '.1'))\n\n        # Someone removes the active log file! :-(\n        os.unlink(self.filename)\n        self.assertFalse(os.path.exists(self.filename))\n\n        handler.emit(record) # 8 bytes, do rollover\n        handler.close()\n        self.assertTrue(os.path.exists(self.filename))\n        self.assertFalse(os.path.exists(self.filename + '.1'))\n\n    def test_removeAndRename_destination_does_not_exist(self):\n        inst = self._makeOne(self.filename)\n        renames = []\n        removes = []\n        inst._remove = lambda v: removes.append(v)\n        inst._exists = lambda v: False\n        inst._rename = lambda s, t: renames.append((s, t))\n        inst.removeAndRename('foo', 'bar')\n        self.assertEqual(renames, [('foo', 'bar')])\n        self.assertEqual(removes, [])\n        inst.close()\n\n    def test_removeAndRename_destination_exists(self):\n        inst = self._makeOne(self.filename)\n        renames = []\n        removes = []\n        inst._remove = lambda v: removes.append(v)\n        inst._exists = lambda v: True\n        inst._rename = lambda s, t: renames.append((s, t))\n        inst.removeAndRename('foo', 'bar')\n        self.assertEqual(renames, [('foo', 'bar')])\n        self.assertEqual(removes, ['bar'])\n        inst.close()\n\n    def test_removeAndRename_remove_raises_ENOENT(self):\n        def remove(fn):\n            raise OSError(errno.ENOENT)\n        inst = self._makeOne(self.filename)\n        renames = []\n        inst._remove = remove\n        inst._exists = lambda v: True\n        inst._rename = lambda s, t: renames.append((s, t))\n        inst.removeAndRename('foo', 'bar')\n        self.assertEqual(renames, [('foo', 'bar')])\n        inst.close()\n\n    def test_removeAndRename_remove_raises_other_than_ENOENT(self):\n        def remove(fn):\n            raise OSError(errno.EAGAIN)\n        inst = self._makeOne(self.filename)\n        inst._remove = remove\n        inst._exists = lambda v: True\n        self.assertRaises(OSError, inst.removeAndRename, 'foo', 'bar')\n        inst.close()\n\n    def test_removeAndRename_rename_raises_ENOENT(self):\n        def rename(s, d):\n            raise OSError(errno.ENOENT)\n        inst = self._makeOne(self.filename)\n        inst._rename = rename\n        inst._exists = lambda v: False\n        self.assertEqual(inst.removeAndRename('foo', 'bar'), None)\n        inst.close()\n\n    def test_removeAndRename_rename_raises_other_than_ENOENT(self):\n        def rename(s, d):\n            raise OSError(errno.EAGAIN)\n        inst = self._makeOne(self.filename)\n        inst._rename = rename\n        inst._exists = lambda v: False\n        self.assertRaises(OSError, inst.removeAndRename, 'foo', 'bar')\n        inst.close()\n\n    def test_doRollover_maxbytes_lte_zero(self):\n        inst = self._makeOne(self.filename)\n        inst.maxBytes = 0\n        self.assertEqual(inst.doRollover(), None)\n        inst.close()\n\n\nclass BoundIOTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.loggers import BoundIO\n        return BoundIO\n\n    def _makeOne(self, maxbytes, buf=''):\n        klass = self._getTargetClass()\n        return klass(maxbytes, buf)\n\n    def test_write_overflow(self):\n        io = self._makeOne(1, b'a')\n        io.write(b'b')\n        self.assertEqual(io.buf, b'b')\n\n    def test_getvalue(self):\n        io = self._makeOne(1, b'a')\n        self.assertEqual(io.getvalue(), b'a')\n\n    def test_clear(self):\n        io = self._makeOne(1, b'a')\n        io.clear()\n        self.assertEqual(io.buf, b'')\n\n    def test_close(self):\n        io = self._makeOne(1, b'a')\n        io.close()\n        self.assertEqual(io.buf, b'')\n\nclass LoggerTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.loggers import Logger\n        return Logger\n\n    def _makeOne(self, level=None, handlers=None):\n        klass = self._getTargetClass()\n        return klass(level, handlers)\n\n    def test_blather(self):\n        from supervisor.loggers import LevelsByName\n        handler = DummyHandler(LevelsByName.BLAT)\n        logger = self._makeOne(LevelsByName.BLAT, (handler,))\n        logger.blather('hello')\n        self.assertEqual(len(handler.records), 1)\n        logger.level = LevelsByName.TRAC\n        logger.blather('hello')\n        self.assertEqual(len(handler.records), 1)\n\n    def test_trace(self):\n        from supervisor.loggers import LevelsByName\n        handler = DummyHandler(LevelsByName.TRAC)\n        logger = self._makeOne(LevelsByName.TRAC, (handler,))\n        logger.trace('hello')\n        self.assertEqual(len(handler.records), 1)\n        logger.level = LevelsByName.DEBG\n        logger.trace('hello')\n        self.assertEqual(len(handler.records), 1)\n\n    def test_debug(self):\n        from supervisor.loggers import LevelsByName\n        handler = DummyHandler(LevelsByName.DEBG)\n        logger = self._makeOne(LevelsByName.DEBG, (handler,))\n        logger.debug('hello')\n        self.assertEqual(len(handler.records), 1)\n        logger.level = LevelsByName.INFO\n        logger.debug('hello')\n        self.assertEqual(len(handler.records), 1)\n\n    def test_info(self):\n        from supervisor.loggers import LevelsByName\n        handler = DummyHandler(LevelsByName.INFO)\n        logger = self._makeOne(LevelsByName.INFO, (handler,))\n        logger.info('hello')\n        self.assertEqual(len(handler.records), 1)\n        logger.level = LevelsByName.WARN\n        logger.info('hello')\n        self.assertEqual(len(handler.records), 1)\n\n    def test_warn(self):\n        from supervisor.loggers import LevelsByName\n        handler = DummyHandler(LevelsByName.WARN)\n        logger = self._makeOne(LevelsByName.WARN, (handler,))\n        logger.warn('hello')\n        self.assertEqual(len(handler.records), 1)\n        logger.level = LevelsByName.ERRO\n        logger.warn('hello')\n        self.assertEqual(len(handler.records), 1)\n\n    def test_error(self):\n        from supervisor.loggers import LevelsByName\n        handler = DummyHandler(LevelsByName.ERRO)\n        logger = self._makeOne(LevelsByName.ERRO, (handler,))\n        logger.error('hello')\n        self.assertEqual(len(handler.records), 1)\n        logger.level = LevelsByName.CRIT\n        logger.error('hello')\n        self.assertEqual(len(handler.records), 1)\n\n    def test_critical(self):\n        from supervisor.loggers import LevelsByName\n        handler = DummyHandler(LevelsByName.CRIT)\n        logger = self._makeOne(LevelsByName.CRIT, (handler,))\n        logger.critical('hello')\n        self.assertEqual(len(handler.records), 1)\n\n    def test_close(self):\n        from supervisor.loggers import LevelsByName\n        handler = DummyHandler(LevelsByName.CRIT)\n        logger = self._makeOne(LevelsByName.CRIT, (handler,))\n        logger.close()\n        self.assertEqual(handler.closed, True)\n\n    def test_getvalue(self):\n        from supervisor.loggers import LevelsByName\n        handler = DummyHandler(LevelsByName.CRIT)\n        logger = self._makeOne(LevelsByName.CRIT, (handler,))\n        self.assertRaises(NotImplementedError, logger.getvalue)\n\n\nclass MockSysLog(mock.Mock):\n    def __call__(self, *args, **kwargs):\n        message = args[-1]\n        if sys.version_info < (3, 0) and isinstance(message, unicode):\n            # Python 2.x raises a UnicodeEncodeError when attempting to\n            #  transmit unicode characters that don't encode in the\n            #  default encoding.\n            message.encode()\n        super(MockSysLog, self).__call__(*args, **kwargs)\n\nclass SyslogHandlerTests(HandlerTests, unittest.TestCase):\n    def setUp(self):\n        pass\n\n    def tearDown(self):\n        pass\n\n    def _getTargetClass(self):\n        return __import__('supervisor.loggers').loggers.SyslogHandler\n\n    def _makeOne(self):\n        return self._getTargetClass()()\n\n    def test_emit_record_asdict_raises(self):\n        class Record(object):\n            def asdict(self):\n                raise TypeError\n        record = Record()\n        handler = self._makeOne()\n        handled = []\n        handler.handleError = lambda: handled.append(True)\n        handler.emit(record)\n        self.assertEqual(handled, [True])\n\n\n    @mock.patch('syslog.syslog', MockSysLog())\n    def test_emit_ascii_noerror(self):\n        handler = self._makeOne()\n        record = self._makeLogRecord(b'hello!')\n        handler.emit(record)\n        syslog.syslog.assert_called_with('hello!')\n        record = self._makeLogRecord('hi!')\n        handler.emit(record)\n        syslog.syslog.assert_called_with('hi!')\n\n    @mock.patch('syslog.syslog', MockSysLog())\n    def test_close(self):\n        handler = self._makeOne()\n        handler.close()  # no-op for syslog\n\n    @mock.patch('syslog.syslog', MockSysLog())\n    def test_reopen(self):\n        handler = self._makeOne()\n        handler.reopen()  # no-op for syslog\n\n    if PY2:\n        @mock.patch('syslog.syslog', MockSysLog())\n        def test_emit_unicode_noerror(self):\n            handler = self._makeOne()\n            inp = as_string('fií')\n            record = self._makeLogRecord(inp)\n            handler.emit(record)\n            syslog.syslog.assert_called_with('fi\\xc3\\xad')\n        def test_emit_unicode_witherror(self):\n            handler = self._makeOne()\n            called = []\n            def fake_syslog(msg):\n                if not called:\n                    called.append(msg)\n                    raise UnicodeError\n            handler._syslog = fake_syslog\n            record = self._makeLogRecord(as_string('fií'))\n            handler.emit(record)\n            self.assertEqual(called, [as_string('fi\\xc3\\xad')])\n    else:\n        @mock.patch('syslog.syslog', MockSysLog())\n        def test_emit_unicode_noerror(self):\n            handler = self._makeOne()\n            record = self._makeLogRecord('fií')\n            handler.emit(record)\n            syslog.syslog.assert_called_with('fií')\n        def test_emit_unicode_witherror(self):\n            handler = self._makeOne()\n            called = []\n            def fake_syslog(msg):\n                if not called:\n                    called.append(msg)\n                    raise UnicodeError\n            handler._syslog = fake_syslog\n            record = self._makeLogRecord('fií')\n            handler.emit(record)\n            self.assertEqual(called, ['fií'])\n\nclass DummyHandler:\n    close = False\n    def __init__(self, level):\n        self.level = level\n        self.records = []\n    def emit(self, record):\n        self.records.append(record)\n    def close(self):\n        self.closed = True\n"
  },
  {
    "path": "supervisor/tests/test_options.py",
    "content": "\"\"\"Test suite for supervisor.options\"\"\"\n\nimport os\nimport sys\nimport tempfile\nimport socket\nimport unittest\nimport signal\nimport shutil\nimport errno\nimport platform\n\nfrom supervisor.compat import StringIO\nfrom supervisor.compat import as_bytes\n\nfrom supervisor.tests.base import Mock, sentinel, patch\nfrom supervisor.loggers import LevelsByName\n\nfrom supervisor.tests.base import DummySupervisor\nfrom supervisor.tests.base import DummyLogger\nfrom supervisor.tests.base import DummyOptions\nfrom supervisor.tests.base import DummyPoller\nfrom supervisor.tests.base import DummyPConfig\nfrom supervisor.tests.base import DummyProcess\nfrom supervisor.tests.base import DummySocketConfig\nfrom supervisor.tests.base import lstrip\n\n\nclass OptionTests(unittest.TestCase):\n\n    def _getTargetClass(self):\n        from supervisor.options import Options\n        return Options\n\n    def _makeOptions(self, read_error=False):\n        Options = self._getTargetClass()\n        from supervisor.datatypes import integer\n\n        class MyOptions(Options):\n            master = {\n                'other': 41 }\n            def __init__(self, read_error=read_error):\n                self.read_error = read_error\n                Options.__init__(self)\n                class Foo(object): pass\n                self.configroot = Foo()\n\n            def read_config(self, fp):\n                if self.read_error:\n                    raise ValueError(self.read_error)\n                # Pretend we read it from file:\n                self.configroot.__dict__.update(self.default_map)\n                self.configroot.__dict__.update(self.master)\n\n        options = MyOptions()\n        options.configfile = StringIO()\n        options.add(name='anoption', confname='anoption',\n                    short='o', long='option', default='default')\n        options.add(name='other', confname='other', env='OTHER',\n                    short='p:', long='other=', handler=integer)\n        return options\n\n    def test_add_flag_not_None_handler_not_None(self):\n        cls = self._getTargetClass()\n        inst = cls()\n        self.assertRaises(ValueError, inst.add, flag=True, handler=True)\n\n    def test_add_flag_not_None_long_false_short_false(self):\n        cls = self._getTargetClass()\n        inst = cls()\n        self.assertRaises(\n            ValueError,\n            inst.add,\n            flag=True,\n            long=False,\n            short=False,\n            )\n\n    def test_add_flag_not_None_short_endswith_colon(self):\n        cls = self._getTargetClass()\n        inst = cls()\n        self.assertRaises(\n            ValueError,\n            inst.add,\n            flag=True,\n            long=False,\n            short=\":\",\n            )\n\n    def test_add_flag_not_None_long_endswith_equal(self):\n        cls = self._getTargetClass()\n        inst = cls()\n        self.assertRaises(\n            ValueError,\n            inst.add,\n            flag=True,\n            long='=',\n            short=False,\n            )\n\n    def test_add_inconsistent_short_long_options(self):\n        cls = self._getTargetClass()\n        inst = cls()\n        self.assertRaises(\n            ValueError,\n            inst.add,\n            long='=',\n            short='abc',\n            )\n\n    def test_add_short_option_startswith_dash(self):\n        cls = self._getTargetClass()\n        inst = cls()\n        self.assertRaises(\n            ValueError,\n            inst.add,\n            long=False,\n            short='-abc',\n            )\n\n    def test_add_short_option_too_long(self):\n        cls = self._getTargetClass()\n        inst = cls()\n        self.assertRaises(\n            ValueError,\n            inst.add,\n            long=False,\n            short='abc',\n            )\n\n    def test_add_duplicate_short_option_key(self):\n        cls = self._getTargetClass()\n        inst = cls()\n        inst.options_map = {'-a':True}\n        self.assertRaises(\n            ValueError,\n            inst.add,\n            long=False,\n            short='a',\n            )\n\n    def test_add_long_option_startswith_dash(self):\n        cls = self._getTargetClass()\n        inst = cls()\n        self.assertRaises(\n            ValueError,\n            inst.add,\n            long='-abc',\n            short=False,\n            )\n\n    def test_add_duplicate_long_option_key(self):\n        cls = self._getTargetClass()\n        inst = cls()\n        inst.options_map = {'--abc':True}\n        self.assertRaises(\n            ValueError,\n            inst.add,\n            long='abc',\n            short=False,\n            )\n\n    def test_searchpaths(self):\n        options = self._makeOptions()\n        self.assertEqual(len(options.searchpaths), 6)\n        self.assertEqual(options.searchpaths[-4:], [\n            'supervisord.conf',\n            'etc/supervisord.conf',\n            '/etc/supervisord.conf',\n            '/etc/supervisor/supervisord.conf',\n            ])\n\n    def test_options_and_args_order(self):\n        # Only config file exists\n        options = self._makeOptions()\n        options.realize([])\n        self.assertEqual(options.anoption, 'default')\n        self.assertEqual(options.other, 41)\n\n        # Env should trump config\n        options = self._makeOptions()\n        os.environ['OTHER'] = '42'\n        options.realize([])\n        self.assertEqual(options.other, 42)\n\n        # Opt should trump both env (still set) and config\n        options = self._makeOptions()\n        options.realize(['-p', '43'])\n        self.assertEqual(options.other, 43)\n        del os.environ['OTHER']\n\n    def test_config_reload(self):\n        options = self._makeOptions()\n        options.realize([])\n        self.assertEqual(options.other, 41)\n        options.master['other'] = 42\n        options.process_config()\n        self.assertEqual(options.other, 42)\n\n    def test_config_reload_do_usage_false(self):\n        options = self._makeOptions(read_error='error')\n        self.assertRaises(ValueError, options.process_config,\n                          False)\n\n    def test_config_reload_do_usage_true(self):\n        options = self._makeOptions(read_error='error')\n        exitcodes = []\n        options.exit = lambda x: exitcodes.append(x)\n        options.stderr = options.stdout = StringIO()\n        options.configroot.anoption = 1\n        options.configroot.other = 1\n        options.process_config(do_usage=True)\n        self.assertEqual(exitcodes, [2])\n\n    def test__set(self):\n        from supervisor.options import Options\n        options = Options()\n        options._set('foo', 'bar', 0)\n        self.assertEqual(options.foo, 'bar')\n        self.assertEqual(options.attr_priorities['foo'], 0)\n        options._set('foo', 'baz', 1)\n        self.assertEqual(options.foo, 'baz')\n        self.assertEqual(options.attr_priorities['foo'], 1)\n        options._set('foo', 'gazonk', 0)\n        self.assertEqual(options.foo, 'baz')\n        self.assertEqual(options.attr_priorities['foo'], 1)\n        options._set('foo', 'gazonk', 1)\n        self.assertEqual(options.foo, 'gazonk')\n\n    def test_missing_default_config(self):\n        options = self._makeOptions()\n        options.searchpaths = []\n        exitcodes = []\n        options.exit = lambda x: exitcodes.append(x)\n        options.stderr = StringIO()\n        options.default_configfile()\n        self.assertEqual(exitcodes, [2])\n        msg = \"Error: No config file found at default paths\"\n        self.assertTrue(options.stderr.getvalue().startswith(msg))\n\n    def test_default_config(self):\n        options = self._makeOptions()\n        with tempfile.NamedTemporaryFile() as f:\n            options.searchpaths = [f.name]\n            config = options.default_configfile()\n            self.assertEqual(config, f.name)\n\n    def test_help(self):\n        options = self._makeOptions()\n        exitcodes = []\n        options.exit = lambda x: exitcodes.append(x)\n        options.stdout = StringIO()\n        options.progname = 'test_help'\n        options.doc = 'A sample docstring for %s'\n        options.help('')\n        self.assertEqual(exitcodes, [0])\n        msg = 'A sample docstring for test_help\\n'\n        self.assertEqual(options.stdout.getvalue(), msg)\n\nclass ClientOptionsTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.options import ClientOptions\n        return ClientOptions\n\n    def _makeOne(self):\n        return self._getTargetClass()()\n\n    def test_no_config_file(self):\n        \"\"\"Making sure config file is not required.\"\"\"\n        instance = self._makeOne()\n        instance.searchpaths = []\n        exitcodes = []\n        instance.exit = lambda x: exitcodes.append(x)\n\n        instance.realize(args=['-s', 'http://localhost:9001', '-u', 'chris',\n                               '-p', '123'])\n\n        self.assertEqual(exitcodes, [])\n        self.assertEqual(instance.interactive, 1)\n        self.assertEqual(instance.serverurl, 'http://localhost:9001')\n        self.assertEqual(instance.username, 'chris')\n        self.assertEqual(instance.password, '123')\n\n    def test_options(self):\n        tempdir = tempfile.gettempdir()\n        s = lstrip(\"\"\"[supervisorctl]\n        serverurl=http://localhost:9001\n        username=chris\n        password=123\n        prompt=mysupervisor\n        history_file=%s/sc_history\n        \"\"\" % tempdir)\n\n        fp = StringIO(s)\n        instance = self._makeOne()\n        instance.configfile = fp\n        instance.realize(args=[])\n        self.assertEqual(instance.interactive, True)\n        history_file = os.path.join(tempdir, 'sc_history')\n        self.assertEqual(instance.history_file, history_file)\n        options = instance.configroot.supervisorctl\n        self.assertEqual(options.prompt, 'mysupervisor')\n        self.assertEqual(options.serverurl, 'http://localhost:9001')\n        self.assertEqual(options.username, 'chris')\n        self.assertEqual(options.password, '123')\n        self.assertEqual(options.history_file, history_file)\n\n    def test_options_ignores_space_prefixed_inline_comments(self):\n        text = lstrip(\"\"\"\n        [supervisorctl]\n        serverurl=http://127.0.0.1:9001 ; use an http:// url to specify an inet socket\n        \"\"\")\n        instance = self._makeOne()\n        instance.configfile = StringIO(text)\n        instance.realize(args=[])\n        options = instance.configroot.supervisorctl\n        self.assertEqual(options.serverurl, 'http://127.0.0.1:9001')\n\n    def test_options_ignores_tab_prefixed_inline_comments(self):\n        text = lstrip(\"\"\"\n        [supervisorctl]\n        serverurl=http://127.0.0.1:9001\\t;use an http:// url to specify an inet socket\n        \"\"\")\n        instance = self._makeOne()\n        instance.configfile = StringIO(text)\n        instance.realize(args=[])\n        options = instance.configroot.supervisorctl\n        self.assertEqual(options.serverurl, 'http://127.0.0.1:9001')\n\n    def test_options_parses_as_nonstrict_for_py2_py3_compat(self):\n        text = lstrip(\"\"\"\n        [supervisorctl]\n        serverurl=http://localhost:9001 ;duplicate\n\n        [supervisorctl]\n        serverurl=http://localhost:9001 ;duplicate\n        \"\"\")\n        instance = self._makeOne()\n        instance.configfile = StringIO(text)\n        instance.realize(args=[])\n        # should not raise configparser.DuplicateSectionError on py3\n\n    def test_options_with_environment_expansions(self):\n        s = lstrip(\"\"\"\n        [supervisorctl]\n        serverurl=http://localhost:%(ENV_SERVER_PORT)s\n        username=%(ENV_CLIENT_USER)s\n        password=%(ENV_CLIENT_PASS)s\n        prompt=%(ENV_CLIENT_PROMPT)s\n        history_file=/path/to/histdir/.supervisorctl%(ENV_CLIENT_HIST_EXT)s\n        \"\"\")\n\n        fp = StringIO(s)\n        instance = self._makeOne()\n        instance.environ_expansions = {'ENV_HOME': tempfile.gettempdir(),\n                                       'ENV_USER': 'johndoe',\n                                       'ENV_SERVER_PORT': '9210',\n                                       'ENV_CLIENT_USER': 'someuser',\n                                       'ENV_CLIENT_PASS': 'passwordhere',\n                                       'ENV_CLIENT_PROMPT': 'xsupervisor',\n                                       'ENV_CLIENT_HIST_EXT': '.hist',\n                                      }\n        instance.configfile = fp\n        instance.realize(args=[])\n        self.assertEqual(instance.interactive, True)\n        options = instance.configroot.supervisorctl\n        self.assertEqual(options.prompt, 'xsupervisor')\n        self.assertEqual(options.serverurl, 'http://localhost:9210')\n        self.assertEqual(options.username, 'someuser')\n        self.assertEqual(options.password, 'passwordhere')\n        self.assertEqual(options.history_file, '/path/to/histdir/.supervisorctl.hist')\n\n    def test_options_supervisorctl_section_expands_here(self):\n        instance = self._makeOne()\n        text = lstrip('''\n        [supervisorctl]\n        history_file=%(here)s/sc_history\n        serverurl=unix://%(here)s/supervisord.sock\n        ''')\n        here = tempfile.mkdtemp()\n        supervisord_conf = os.path.join(here, 'supervisord.conf')\n        with open(supervisord_conf, 'w') as f:\n            f.write(text)\n        try:\n            instance.configfile = supervisord_conf\n            instance.realize(args=[])\n        finally:\n            shutil.rmtree(here, ignore_errors=True)\n        options = instance.configroot.supervisorctl\n        self.assertEqual(options.history_file,\n           os.path.join(here, 'sc_history'))\n        self.assertEqual(options.serverurl,\n           'unix://' + os.path.join(here, 'supervisord.sock'))\n\n    def test_read_config_not_found(self):\n        nonexistent = os.path.join(os.path.dirname(__file__), 'nonexistent')\n        instance = self._makeOne()\n        try:\n            instance.read_config(nonexistent)\n            self.fail(\"nothing raised\")\n        except ValueError as exc:\n            self.assertTrue(\"could not find config file\" in exc.args[0])\n\n    def test_read_config_unreadable(self):\n        instance = self._makeOne()\n        def dummy_open(fn, mode):\n            raise IOError(errno.EACCES, 'Permission denied: %s' % fn)\n        instance.open = dummy_open\n\n        try:\n            instance.read_config(__file__)\n            self.fail(\"expected exception\")\n        except ValueError as exc:\n            self.assertTrue(\"could not read config file\" in exc.args[0])\n\n    def test_read_config_no_supervisord_section_raises_valueerror(self):\n        instance = self._makeOne()\n        try:\n            instance.read_config(StringIO())\n            self.fail(\"nothing raised\")\n        except ValueError as exc:\n            self.assertEqual(exc.args[0],\n                \".ini file does not include supervisorctl section\")\n\n    def test_options_unixsocket_cli(self):\n        fp = StringIO('[supervisorctl]')\n        instance = self._makeOne()\n        instance.configfile = fp\n        instance.realize(args=['--serverurl', 'unix:///dev/null'])\n        self.assertEqual(instance.serverurl, 'unix:///dev/null')\n\n    def test_options_unixsocket_configfile(self):\n        s = lstrip(\"\"\"[supervisorctl]\n        serverurl=unix:///dev/null\n        \"\"\")\n        fp = StringIO(s)\n        instance = self._makeOne()\n        instance.configfile = fp\n        instance.realize(args=[])\n        self.assertEqual(instance.serverurl, 'unix:///dev/null')\n\nclass ServerOptionsTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.options import ServerOptions\n        return ServerOptions\n\n    def _makeOne(self):\n        return self._getTargetClass()()\n\n    def test_version(self):\n        from supervisor.options import VERSION\n        options = self._makeOne()\n        options.stdout = StringIO()\n        self.assertRaises(SystemExit, options.version, None)\n        self.assertEqual(options.stdout.getvalue(), VERSION + '\\n')\n\n    def test_options(self):\n        s = lstrip(\"\"\"\n        [supervisord]\n        directory=%(tempdir)s\n        backofflimit=10\n        user=root\n        umask=022\n        logfile=supervisord.log\n        logfile_maxbytes=1000MB\n        logfile_backups=5\n        loglevel=error\n        pidfile=supervisord.pid\n        nodaemon=true\n        silent=true\n        identifier=fleeb\n        childlogdir=%(tempdir)s\n        nocleanup=true\n        minfds=2048\n        minprocs=300\n        environment=FAKE_ENV_VAR=/some/path\n\n        [inet_http_server]\n        port=127.0.0.1:8999\n        username=chrism\n        password=foo\n\n        [program:cat1]\n        command=/bin/cat\n        priority=1\n        autostart=true\n        user=root\n        stdout_logfile=/tmp/cat.log\n        stopsignal=KILL\n        stopwaitsecs=5\n        startsecs=5\n        startretries=10\n        directory=/tmp\n        umask=002\n\n        [program:cat2]\n        priority=2\n        command=/bin/cat\n        autostart=true\n        autorestart=false\n        stdout_logfile_maxbytes = 1024\n        stdout_logfile_backups = 2\n        stdout_logfile = /tmp/cat2.log\n\n        [program:cat3]\n        priority=3\n        process_name = replaced\n        command=/bin/cat\n        autorestart=true\n        exitcodes=0,1,127\n        stopasgroup=true\n        killasgroup=true\n\n        [program:cat4]\n        priority=4\n        process_name = fleeb_%%(process_num)s\n        numprocs = 2\n        command = /bin/cat\n        autorestart=unexpected\n\n        [program:cat5]\n        priority=5\n        process_name = foo_%%(process_num)02d\n        numprocs = 2\n        numprocs_start = 1\n        command = /bin/cat\n        directory = /some/path/foo_%%(process_num)02d\n        \"\"\" % {'tempdir':tempfile.gettempdir()})\n\n        from supervisor import datatypes\n\n        fp = StringIO(s)\n        instance = self._makeOne()\n        instance.configfile = fp\n        instance.realize(args=[])\n        options = instance.configroot.supervisord\n        self.assertEqual(options.directory, tempfile.gettempdir())\n        self.assertEqual(options.umask, 0o22)\n        self.assertEqual(options.logfile, 'supervisord.log')\n        self.assertEqual(options.logfile_maxbytes, 1000 * 1024 * 1024)\n        self.assertEqual(options.logfile_backups, 5)\n        self.assertEqual(options.loglevel, 40)\n        self.assertEqual(options.pidfile, 'supervisord.pid')\n        self.assertEqual(options.nodaemon, True)\n        self.assertEqual(options.silent, True)\n        self.assertEqual(options.identifier, 'fleeb')\n        self.assertEqual(options.childlogdir, tempfile.gettempdir())\n        self.assertEqual(len(options.server_configs), 1)\n        self.assertEqual(options.server_configs[0]['family'], socket.AF_INET)\n        self.assertEqual(options.server_configs[0]['host'], '127.0.0.1')\n        self.assertEqual(options.server_configs[0]['port'], 8999)\n        self.assertEqual(options.server_configs[0]['username'], 'chrism')\n        self.assertEqual(options.server_configs[0]['password'], 'foo')\n        self.assertEqual(options.nocleanup, True)\n        self.assertEqual(options.minfds, 2048)\n        self.assertEqual(options.minprocs, 300)\n        self.assertEqual(options.nocleanup, True)\n        self.assertEqual(len(options.process_group_configs), 5)\n        self.assertEqual(options.environment, dict(FAKE_ENV_VAR='/some/path'))\n\n        cat1 = options.process_group_configs[0]\n        self.assertEqual(cat1.name, 'cat1')\n        self.assertEqual(cat1.priority, 1)\n        self.assertEqual(len(cat1.process_configs), 1)\n\n        proc1 = cat1.process_configs[0]\n        self.assertEqual(proc1.name, 'cat1')\n        self.assertEqual(proc1.command, '/bin/cat')\n        self.assertEqual(proc1.priority, 1)\n        self.assertEqual(proc1.autostart, True)\n        self.assertEqual(proc1.autorestart, datatypes.RestartWhenExitUnexpected)\n        self.assertEqual(proc1.startsecs, 5)\n        self.assertEqual(proc1.startretries, 10)\n        self.assertEqual(proc1.uid, 0)\n        self.assertEqual(proc1.stdout_logfile, '/tmp/cat.log')\n        self.assertEqual(proc1.stopsignal, signal.SIGKILL)\n        self.assertEqual(proc1.stopwaitsecs, 5)\n        self.assertEqual(proc1.stopasgroup, False)\n        self.assertEqual(proc1.killasgroup, False)\n        self.assertEqual(proc1.stdout_logfile_maxbytes,\n                         datatypes.byte_size('50MB'))\n        self.assertEqual(proc1.stdout_logfile_backups, 10)\n        self.assertEqual(proc1.exitcodes, [0])\n        self.assertEqual(proc1.directory, '/tmp')\n        self.assertEqual(proc1.umask, 2)\n        self.assertEqual(proc1.environment, dict(FAKE_ENV_VAR='/some/path'))\n\n        cat2 = options.process_group_configs[1]\n        self.assertEqual(cat2.name, 'cat2')\n        self.assertEqual(cat2.priority, 2)\n        self.assertEqual(len(cat2.process_configs), 1)\n\n        proc2 = cat2.process_configs[0]\n        self.assertEqual(proc2.name, 'cat2')\n        self.assertEqual(proc2.command, '/bin/cat')\n        self.assertEqual(proc2.priority, 2)\n        self.assertEqual(proc2.autostart, True)\n        self.assertEqual(proc2.autorestart, False)\n        self.assertEqual(proc2.uid, None)\n        self.assertEqual(proc2.stdout_logfile, '/tmp/cat2.log')\n        self.assertEqual(proc2.stopsignal, signal.SIGTERM)\n        self.assertEqual(proc2.stopasgroup, False)\n        self.assertEqual(proc2.killasgroup, False)\n        self.assertEqual(proc2.stdout_logfile_maxbytes, 1024)\n        self.assertEqual(proc2.stdout_logfile_backups, 2)\n        self.assertEqual(proc2.exitcodes, [0])\n        self.assertEqual(proc2.directory, None)\n\n        cat3 = options.process_group_configs[2]\n        self.assertEqual(cat3.name, 'cat3')\n        self.assertEqual(cat3.priority, 3)\n        self.assertEqual(len(cat3.process_configs), 1)\n\n        proc3 = cat3.process_configs[0]\n        self.assertEqual(proc3.name, 'replaced')\n        self.assertEqual(proc3.command, '/bin/cat')\n        self.assertEqual(proc3.priority, 3)\n        self.assertEqual(proc3.autostart, True)\n        self.assertEqual(proc3.autorestart, datatypes.RestartUnconditionally)\n        self.assertEqual(proc3.uid, None)\n        self.assertEqual(proc3.stdout_logfile, datatypes.Automatic)\n        self.assertEqual(proc3.stdout_logfile_maxbytes,\n                         datatypes.byte_size('50MB'))\n        self.assertEqual(proc3.stdout_logfile_backups, 10)\n        self.assertEqual(proc3.exitcodes, [0,1,127])\n        self.assertEqual(proc3.stopsignal, signal.SIGTERM)\n        self.assertEqual(proc3.stopasgroup, True)\n        self.assertEqual(proc3.killasgroup, True)\n\n        cat4 = options.process_group_configs[3]\n        self.assertEqual(cat4.name, 'cat4')\n        self.assertEqual(cat4.priority, 4)\n        self.assertEqual(len(cat4.process_configs), 2)\n\n        proc4_a = cat4.process_configs[0]\n        self.assertEqual(proc4_a.name, 'fleeb_0')\n        self.assertEqual(proc4_a.command, '/bin/cat')\n        self.assertEqual(proc4_a.priority, 4)\n        self.assertEqual(proc4_a.autostart, True)\n        self.assertEqual(proc4_a.autorestart,\n                         datatypes.RestartWhenExitUnexpected)\n        self.assertEqual(proc4_a.uid, None)\n        self.assertEqual(proc4_a.stdout_logfile, datatypes.Automatic)\n        self.assertEqual(proc4_a.stdout_logfile_maxbytes,\n                         datatypes.byte_size('50MB'))\n        self.assertEqual(proc4_a.stdout_logfile_backups, 10)\n        self.assertEqual(proc4_a.exitcodes, [0])\n        self.assertEqual(proc4_a.stopsignal, signal.SIGTERM)\n        self.assertEqual(proc4_a.stopasgroup, False)\n        self.assertEqual(proc4_a.killasgroup, False)\n        self.assertEqual(proc4_a.directory, None)\n\n        proc4_b = cat4.process_configs[1]\n        self.assertEqual(proc4_b.name, 'fleeb_1')\n        self.assertEqual(proc4_b.command, '/bin/cat')\n        self.assertEqual(proc4_b.priority, 4)\n        self.assertEqual(proc4_b.autostart, True)\n        self.assertEqual(proc4_b.autorestart,\n                         datatypes.RestartWhenExitUnexpected)\n        self.assertEqual(proc4_b.uid, None)\n        self.assertEqual(proc4_b.stdout_logfile, datatypes.Automatic)\n        self.assertEqual(proc4_b.stdout_logfile_maxbytes,\n                         datatypes.byte_size('50MB'))\n        self.assertEqual(proc4_b.stdout_logfile_backups, 10)\n        self.assertEqual(proc4_b.exitcodes, [0])\n        self.assertEqual(proc4_b.stopsignal, signal.SIGTERM)\n        self.assertEqual(proc4_b.stopasgroup, False)\n        self.assertEqual(proc4_b.killasgroup, False)\n        self.assertEqual(proc4_b.directory, None)\n\n        cat5 = options.process_group_configs[4]\n        self.assertEqual(cat5.name, 'cat5')\n        self.assertEqual(cat5.priority, 5)\n        self.assertEqual(len(cat5.process_configs), 2)\n\n        proc5_a = cat5.process_configs[0]\n        self.assertEqual(proc5_a.name, 'foo_01')\n        self.assertEqual(proc5_a.directory, '/some/path/foo_01')\n\n        proc5_b = cat5.process_configs[1]\n        self.assertEqual(proc5_b.name, 'foo_02')\n        self.assertEqual(proc5_b.directory, '/some/path/foo_02')\n\n        here = os.path.abspath(os.getcwd())\n        self.assertEqual(instance.uid, 0)\n        self.assertEqual(instance.gid, 0)\n        self.assertEqual(instance.directory, tempfile.gettempdir())\n        self.assertEqual(instance.umask, 0o22)\n        self.assertEqual(instance.logfile, os.path.join(here,'supervisord.log'))\n        self.assertEqual(instance.logfile_maxbytes, 1000 * 1024 * 1024)\n        self.assertEqual(instance.logfile_backups, 5)\n        self.assertEqual(instance.loglevel, 40)\n        self.assertEqual(instance.pidfile, os.path.join(here,'supervisord.pid'))\n        self.assertEqual(instance.nodaemon, True)\n        self.assertEqual(instance.silent, True)\n        self.assertEqual(instance.passwdfile, None)\n        self.assertEqual(instance.identifier, 'fleeb')\n        self.assertEqual(instance.childlogdir, tempfile.gettempdir())\n\n        self.assertEqual(len(instance.server_configs), 1)\n        self.assertEqual(instance.server_configs[0]['family'], socket.AF_INET)\n        self.assertEqual(instance.server_configs[0]['host'], '127.0.0.1')\n        self.assertEqual(instance.server_configs[0]['port'], 8999)\n        self.assertEqual(instance.server_configs[0]['username'], 'chrism')\n        self.assertEqual(instance.server_configs[0]['password'], 'foo')\n\n        self.assertEqual(instance.nocleanup, True)\n        self.assertEqual(instance.minfds, 2048)\n        self.assertEqual(instance.minprocs, 300)\n\n    def test_options_ignores_space_prefixed_inline_comments(self):\n        text = lstrip(\"\"\"\n        [supervisord]\n        logfile=/tmp/supervisord.log ;(main log file;default $CWD/supervisord.log)\n        minfds=123 ; (min. avail startup file descriptors;default 1024)\n        \"\"\")\n        instance = self._makeOne()\n        instance.configfile = StringIO(text)\n        instance.realize(args=[])\n        options = instance.configroot.supervisord\n        self.assertEqual(options.logfile, \"/tmp/supervisord.log\")\n        self.assertEqual(options.minfds, 123)\n\n    def test_options_ignores_tab_prefixed_inline_comments(self):\n        text = lstrip(\"\"\"\n        [supervisord]\n        logfile=/tmp/supervisord.log\\t;(main log file;default $CWD/supervisord.log)\n        minfds=123\\t; (min. avail startup file descriptors;default 1024)\n        \"\"\")\n        instance = self._makeOne()\n        instance.configfile = StringIO(text)\n        instance.realize(args=[])\n        options = instance.configroot.supervisord\n        self.assertEqual(options.minfds, 123)\n\n    def test_options_parses_as_nonstrict_for_py2_py3_compat(self):\n        text = lstrip(\"\"\"\n        [supervisord]\n\n        [program:duplicate]\n        command=/bin/cat\n\n        [program:duplicate]\n        command=/bin/cat\n        \"\"\")\n        instance = self._makeOne()\n        instance.configfile = StringIO(text)\n        instance.realize(args=[])\n        # should not raise configparser.DuplicateSectionError on py3\n\n    def test_reload(self):\n        text = lstrip(\"\"\"\\\n        [supervisord]\n        user=root\n\n        [program:one]\n        command = /bin/cat\n\n        [program:two]\n        command = /bin/dog\n\n        [program:four]\n        command = /bin/sheep\n\n        [group:thegroup]\n        programs = one,two\n        \"\"\")\n\n        instance = self._makeOne()\n        instance.configfile = StringIO(text)\n        instance.realize(args=[])\n\n        section = instance.configroot.supervisord\n\n        self.assertEqual(len(section.process_group_configs), 2)\n\n        cat = section.process_group_configs[0]\n        self.assertEqual(len(cat.process_configs), 1)\n\n        cat = section.process_group_configs[1]\n        self.assertEqual(len(cat.process_configs), 2)\n        self.assertTrue(section.process_group_configs is\n                        instance.process_group_configs)\n\n        text = lstrip(\"\"\"\\\n        [supervisord]\n        user=root\n\n        [program:one]\n        command = /bin/cat\n\n        [program:three]\n        command = /bin/pig\n\n        [group:thegroup]\n        programs = three\n        \"\"\")\n        instance.configfile = StringIO(text)\n        instance.process_config(do_usage=False)\n\n        section = instance.configroot.supervisord\n\n        self.assertEqual(len(section.process_group_configs), 2)\n\n        cat = section.process_group_configs[0]\n        self.assertEqual(len(cat.process_configs), 1)\n        proc = cat.process_configs[0]\n        self.assertEqual(proc.name, 'one')\n        self.assertEqual(proc.command, '/bin/cat')\n        self.assertTrue(section.process_group_configs is\n                        instance.process_group_configs)\n\n        cat = section.process_group_configs[1]\n        self.assertEqual(len(cat.process_configs), 1)\n        proc = cat.process_configs[0]\n        self.assertEqual(proc.name, 'three')\n        self.assertEqual(proc.command, '/bin/pig')\n\n    def test_reload_clears_parse_messages(self):\n        instance = self._makeOne()\n        old_msg = \"Message from a prior config read\"\n        instance.parse_criticals = [old_msg]\n        instance.parse_warnings = [old_msg]\n        instance.parse_infos = [old_msg]\n\n        text = lstrip(\"\"\"\\\n        [supervisord]\n        user=root\n\n        [program:cat]\n        command = /bin/cat\n        \"\"\")\n        instance.configfile = StringIO(text)\n        instance.realize(args=[])\n        self.assertFalse(old_msg in instance.parse_criticals)\n        self.assertFalse(old_msg in instance.parse_warnings)\n        self.assertFalse(old_msg in instance.parse_infos)\n\n    def test_reload_clears_parse_infos(self):\n        instance = self._makeOne()\n        old_info = \"Info from a prior config read\"\n        instance.infos = [old_info]\n\n        text = lstrip(\"\"\"\\\n        [supervisord]\n        user=root\n\n        [program:cat]\n        command = /bin/cat\n        \"\"\")\n        instance.configfile = StringIO(text)\n        instance.realize(args=[])\n        self.assertFalse(old_info in instance.parse_infos)\n\n    def test_read_config_not_found(self):\n        nonexistent = os.path.join(os.path.dirname(__file__), 'nonexistent')\n        instance = self._makeOne()\n        try:\n            instance.read_config(nonexistent)\n            self.fail(\"nothing raised\")\n        except ValueError as exc:\n            self.assertTrue(\"could not find config file\" in exc.args[0])\n\n    def test_read_config_unreadable(self):\n        instance = self._makeOne()\n        def dummy_open(fn, mode):\n            raise IOError(errno.EACCES, 'Permission denied: %s' % fn)\n        instance.open = dummy_open\n\n        try:\n            instance.read_config(__file__)\n            self.fail(\"nothing raised\")\n        except ValueError as exc:\n            self.assertTrue(\"could not read config file\" in exc.args[0])\n\n    def test_read_config_malformed_config_file_raises_valueerror(self):\n        instance = self._makeOne()\n\n        with tempfile.NamedTemporaryFile(mode=\"w+\") as f:\n            try:\n                f.write(\"[supervisord]\\njunk\")\n                f.flush()\n                instance.read_config(f.name)\n                self.fail(\"nothing raised\")\n            except ValueError as exc:\n                self.assertTrue('contains parsing errors:' in exc.args[0])\n                self.assertTrue(f.name in exc.args[0])\n\n    def test_read_config_logfile_with_nonexistent_dirpath(self):\n        instance = self._makeOne()\n        logfile_with_nonexistent_dir = os.path.join(\n            os.path.dirname(__file__), \"nonexistent\",\n            \"supervisord.log\"\n            )\n        text = lstrip(\"\"\"\\\n        [supervisord]\n        logfile=%s\n        \"\"\" % logfile_with_nonexistent_dir)\n        try:\n            instance.read_config(StringIO(text))\n            self.fail(\"nothing raised\")\n        except ValueError as exc:\n            self.assertEqual(exc.args[0],\n                \"The directory named as part of the path %s does not exist\" %\n                logfile_with_nonexistent_dir\n            )\n\n    def test_read_config_no_supervisord_section_raises_valueerror(self):\n        instance = self._makeOne()\n        try:\n            instance.read_config(StringIO())\n            self.fail(\"nothing raised\")\n        except ValueError as exc:\n            self.assertEqual(exc.args[0],\n                \".ini file does not include supervisord section\")\n\n    def test_read_config_include_with_no_files_raises_valueerror(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [supervisord]\n\n        [include]\n        ;no files=\n        \"\"\")\n        try:\n            instance.read_config(StringIO(text))\n            self.fail(\"nothing raised\")\n        except ValueError as exc:\n            self.assertEqual(exc.args[0],\n                \".ini file has [include] section, but no files setting\")\n\n    def test_read_config_include_with_no_matching_files_logs_warning(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [supervisord]\n\n        [include]\n        files=nonexistent/*\n        \"\"\")\n        instance.read_config(StringIO(text))\n        self.assertEqual(instance.parse_warnings,\n                         ['No file matches via include \"./nonexistent/*\"'])\n\n    def test_read_config_include_reads_extra_files(self):\n        dirname = tempfile.mkdtemp()\n        conf_d = os.path.join(dirname, \"conf.d\")\n        os.mkdir(conf_d)\n\n        supervisord_conf = os.path.join(dirname, \"supervisord.conf\")\n        text = lstrip(\"\"\"\\\n        [supervisord]\n\n        [include]\n        files=%s/conf.d/*.conf %s/conf.d/*.ini\n        \"\"\" % (dirname, dirname))\n        with open(supervisord_conf, 'w') as f:\n            f.write(text)\n\n        conf_file = os.path.join(conf_d, \"a.conf\")\n        with open(conf_file, 'w') as f:\n            f.write(\"[inet_http_server]\\nport=8000\\n\")\n\n        ini_file = os.path.join(conf_d, \"a.ini\")\n        with open(ini_file, 'w') as f:\n            f.write(\"[unix_http_server]\\nfile=/tmp/file\\n\")\n\n        instance = self._makeOne()\n        try:\n            instance.read_config(supervisord_conf)\n        finally:\n            shutil.rmtree(dirname, ignore_errors=True)\n        options = instance.configroot.supervisord\n        self.assertEqual(len(options.server_configs), 2)\n        msg = 'Included extra file \"%s\" during parsing' % conf_file\n        self.assertTrue(msg in instance.parse_infos)\n        msg = 'Included extra file \"%s\" during parsing' % ini_file\n        self.assertTrue(msg in instance.parse_infos)\n\n    def test_read_config_include_reads_files_in_sorted_order(self):\n        dirname = tempfile.mkdtemp()\n        conf_d = os.path.join(dirname, \"conf.d\")\n        os.mkdir(conf_d)\n\n        supervisord_conf = os.path.join(dirname, \"supervisord.conf\")\n        text = lstrip(\"\"\"\\\n        [supervisord]\n\n        [include]\n        files=%s/conf.d/*.conf\n        \"\"\" % dirname)\n        with open(supervisord_conf, 'w') as f:\n            f.write(text)\n\n        from supervisor.compat import letters\n        a_z = letters[:26]\n        for letter in reversed(a_z):\n            filename = os.path.join(conf_d, \"%s.conf\" % letter)\n            with open(filename, \"w\") as f:\n                f.write(\"[program:%s]\\n\"\n                        \"command=/bin/%s\\n\" % (letter, letter))\n\n        instance = self._makeOne()\n        try:\n            instance.read_config(supervisord_conf)\n        finally:\n            shutil.rmtree(dirname, ignore_errors=True)\n        expected_msgs = []\n        for letter in sorted(a_z):\n            filename = os.path.join(conf_d, \"%s.conf\" % letter)\n            expected_msgs.append(\n                'Included extra file \"%s\" during parsing' % filename)\n        self.assertEqual(instance.parse_infos, expected_msgs)\n\n    def test_read_config_include_extra_file_malformed(self):\n        dirname = tempfile.mkdtemp()\n        conf_d = os.path.join(dirname, \"conf.d\")\n        os.mkdir(conf_d)\n\n        supervisord_conf = os.path.join(dirname, \"supervisord.conf\")\n        text = lstrip(\"\"\"\\\n        [supervisord]\n\n        [include]\n        files=%s/conf.d/*.conf\n        \"\"\" % dirname)\n        with open(supervisord_conf, 'w') as f:\n            f.write(text)\n\n        malformed_file = os.path.join(conf_d, \"a.conf\")\n        with open(malformed_file, 'w') as f:\n            f.write(\"[inet_http_server]\\njunk\\n\")\n\n        instance = self._makeOne()\n        try:\n            instance.read_config(supervisord_conf)\n            self.fail(\"nothing raised\")\n        except ValueError as exc:\n            self.assertTrue('contains parsing errors:' in exc.args[0])\n            self.assertTrue(malformed_file in exc.args[0])\n            msg = 'Included extra file \"%s\" during parsing' % malformed_file\n            self.assertTrue(msg in instance.parse_infos)\n        finally:\n            shutil.rmtree(dirname, ignore_errors=True)\n\n    def test_read_config_include_expands_host_node_name(self):\n        dirname = tempfile.mkdtemp()\n        conf_d = os.path.join(dirname, \"conf.d\")\n        os.mkdir(conf_d)\n\n        supervisord_conf = os.path.join(dirname, \"supervisord.conf\")\n        text = lstrip(\"\"\"\\\n        [supervisord]\n\n        [include]\n        files=%s/conf.d/%s.conf\n        \"\"\" % (dirname, \"%(host_node_name)s\"))\n        with open(supervisord_conf, 'w') as f:\n            f.write(text)\n\n        conf_file = os.path.join(conf_d, \"%s.conf\" % platform.node())\n        with open(conf_file, 'w') as f:\n            f.write(\"[inet_http_server]\\nport=8000\\n\")\n\n        instance = self._makeOne()\n        try:\n            instance.read_config(supervisord_conf)\n        finally:\n            shutil.rmtree(dirname, ignore_errors=True)\n        options = instance.configroot.supervisord\n        self.assertEqual(len(options.server_configs), 1)\n        msg = 'Included extra file \"%s\" during parsing' % conf_file\n        self.assertTrue(msg in instance.parse_infos)\n\n    def test_read_config_include_expands_here(self):\n        conf = os.path.join(\n            os.path.abspath(os.path.dirname(__file__)), 'fixtures',\n            'include.conf')\n        root_here = os.path.dirname(conf)\n        include_here = os.path.join(root_here, 'example')\n        parser = self._makeOne()\n        parser.configfile = conf\n        parser.process_config_file(True)\n        section = parser.configroot.supervisord\n        self.assertEqual(section.logfile, root_here)\n        self.assertEqual(section.childlogdir, include_here)\n\n    def test_readFile_failed(self):\n        from supervisor.options import readFile\n        try:\n            readFile('/notthere', 0, 10)\n        except ValueError as inst:\n            self.assertEqual(inst.args[0], 'FAILED')\n        else:\n            raise AssertionError(\"Didn't raise\")\n\n    def test_get_pid(self):\n        instance = self._makeOne()\n        self.assertEqual(os.getpid(), instance.get_pid())\n\n    def test_get_signal_delegates_to_signal_receiver(self):\n        instance = self._makeOne()\n        instance.signal_receiver.receive(signal.SIGTERM, None)\n        instance.signal_receiver.receive(signal.SIGCHLD, None)\n        self.assertEqual(instance.get_signal(), signal.SIGTERM)\n        self.assertEqual(instance.get_signal(), signal.SIGCHLD)\n        self.assertEqual(instance.get_signal(), None)\n\n    def test_check_execv_args_cant_find_command(self):\n        instance = self._makeOne()\n        from supervisor.options import NotFound\n        self.assertRaises(NotFound, instance.check_execv_args,\n                          '/not/there', None, None)\n\n    def test_check_execv_args_notexecutable(self):\n        instance = self._makeOne()\n        from supervisor.options import NotExecutable\n        self.assertRaises(NotExecutable,\n                          instance.check_execv_args, '/etc/passwd',\n                          ['etc/passwd'], os.stat('/etc/passwd'))\n\n    def test_check_execv_args_isdir(self):\n        instance = self._makeOne()\n        from supervisor.options import NotExecutable\n        self.assertRaises(NotExecutable,\n                          instance.check_execv_args, '/',\n                          ['/'], os.stat('/'))\n\n    def test_realize_positional_args_not_supported(self):\n        instance = self._makeOne()\n\n        recorder = []\n        def record_usage(message):\n            recorder.append(message)\n        instance.usage = record_usage\n\n        instance.configfile=StringIO('[supervisord]')\n        args = ['foo', 'bar']\n        instance.realize(args=args)\n        self.assertEqual(len(recorder), 1)\n        self.assertEqual(recorder[0],\n            'positional arguments are not supported: %s' % args)\n\n    def test_realize_getopt_error(self):\n        instance = self._makeOne()\n\n        recorder = []\n        def record_usage(message):\n            recorder.append(message)\n        instance.usage = record_usage\n\n        instance.configfile=StringIO('[supervisord]')\n        instance.realize(args=[\"--bad=1\"])\n        self.assertEqual(len(recorder), 1)\n        self.assertEqual(recorder[0], \"option --bad not recognized\")\n\n    def test_realize_prefers_identifier_from_args(self):\n        text = lstrip(\"\"\"\n        [supervisord]\n        identifier=from_config_file\n        \"\"\")\n        instance = self._makeOne()\n        instance.configfile = StringIO(text)\n        instance.realize(args=['-i', 'from_args'])\n        self.assertEqual(instance.identifier, \"from_args\")\n\n    def test_options_afunix(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [unix_http_server]\n        file=/tmp/supvtest.sock\n        username=johndoe\n        password=passwordhere\n\n        [supervisord]\n        ; ...\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        instance.configfile = StringIO(text)\n        instance.read_config(StringIO(text))\n        instance.realize(args=[])\n        # unix_http_server\n        options = instance.configroot.supervisord\n        self.assertEqual(options.server_configs[0]['family'], socket.AF_UNIX)\n        self.assertEqual(options.server_configs[0]['file'], '/tmp/supvtest.sock')\n        self.assertEqual(options.server_configs[0]['chmod'], 448) # defaults\n        self.assertEqual(options.server_configs[0]['chown'], (-1,-1)) # defaults\n\n    def test_options_afunix_chxxx_values_valid(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [unix_http_server]\n        file=/tmp/supvtest.sock\n        username=johndoe\n        password=passwordhere\n        chmod=0755\n\n        [supervisord]\n        ; ...\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        instance.configfile = StringIO(text)\n        instance.read_config(StringIO(text))\n        instance.realize(args=[])\n        # unix_http_server\n        options = instance.configroot.supervisord\n        self.assertEqual(options.server_configs[0]['family'], socket.AF_UNIX)\n        self.assertEqual(options.server_configs[0]['file'], '/tmp/supvtest.sock')\n        self.assertEqual(options.server_configs[0]['chmod'], 493)\n\n    def test_options_afunix_chmod_bad(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [supervisord]\n\n        [unix_http_server]\n        file=/tmp/file\n        chmod=NaN\n        \"\"\")\n        instance.configfile = StringIO(text)\n        try:\n            instance.read_config(StringIO(text))\n            self.fail(\"nothing raised\")\n        except ValueError as exc:\n            self.assertEqual(exc.args[0],\n                \"Invalid chmod value NaN\")\n\n    def test_options_afunix_chown_bad(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [supervisord]\n\n        [unix_http_server]\n        file=/tmp/file\n        chown=thisisnotavaliduser\n        \"\"\")\n        instance.configfile = StringIO(text)\n        try:\n            instance.read_config(StringIO(text))\n            self.fail(\"nothing raised\")\n        except ValueError as exc:\n            self.assertEqual(exc.args[0],\n                \"Invalid sockchown value thisisnotavaliduser\")\n\n    def test_options_afunix_no_file(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [supervisord]\n\n        [unix_http_server]\n        ;no file=\n        \"\"\")\n        instance.configfile = StringIO(text)\n        try:\n            instance.read_config(StringIO(text))\n            self.fail(\"nothing raised\")\n        except ValueError as exc:\n            self.assertEqual(exc.args[0],\n                \"section [unix_http_server] has no file value\")\n\n    def test_options_afunix_username_without_password(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [supervisord]\n\n        [unix_http_server]\n        file=/tmp/supvtest.sock\n        username=usernamehere\n        ;no password=\n        chmod=0755\n        \"\"\")\n        instance.configfile = StringIO(text)\n        try:\n            instance.read_config(StringIO(text))\n            self.fail(\"nothing raised\")\n        except ValueError as exc:\n            self.assertEqual(exc.args[0],\n                'Section [unix_http_server] contains incomplete '\n                'authentication: If a username or a password is '\n                'specified, both the username and password must '\n                'be specified')\n\n    def test_options_afunix_password_without_username(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [supervisord]\n\n        [unix_http_server]\n        file=/tmp/supvtest.sock\n        ;no username=\n        password=passwordhere\n        chmod=0755\n        \"\"\")\n        instance.configfile = StringIO(text)\n        try:\n            instance.read_config(StringIO(text))\n            self.fail(\"nothing raised\")\n        except ValueError as exc:\n            self.assertEqual(exc.args[0],\n                'Section [unix_http_server] contains incomplete '\n                'authentication: If a username or a password is '\n                'specified, both the username and password must '\n                'be specified')\n\n    def test_options_afunix_file_expands_here(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [supervisord]\n\n        [unix_http_server]\n        file=%(here)s/supervisord.sock\n        \"\"\")\n        here = tempfile.mkdtemp()\n        supervisord_conf = os.path.join(here, 'supervisord.conf')\n        with open(supervisord_conf, 'w') as f:\n            f.write(text)\n        try:\n            instance.configfile = supervisord_conf\n            instance.realize(args=[])\n        finally:\n            shutil.rmtree(here, ignore_errors=True)\n        options = instance.configroot.supervisord\n        # unix_http_server\n        serverconf = options.server_configs[0]\n        self.assertEqual(serverconf['family'], socket.AF_UNIX)\n        self.assertEqual(serverconf['file'],\n            os.path.join(here, 'supervisord.sock'))\n\n    def test_options_afinet_username_without_password(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [supervisord]\n\n        [inet_http_server]\n        file=/tmp/supvtest.sock\n        username=usernamehere\n        ;no password=\n        chmod=0755\n        \"\"\")\n        instance.configfile = StringIO(text)\n        try:\n            instance.read_config(StringIO(text))\n            self.fail(\"nothing raised\")\n        except ValueError as exc:\n            self.assertEqual(exc.args[0],\n                'Section [inet_http_server] contains incomplete '\n                'authentication: If a username or a password is '\n                'specified, both the username and password must '\n                'be specified')\n\n    def test_options_afinet_password_without_username(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [supervisord]\n\n        [inet_http_server]\n        password=passwordhere\n        ;no username=\n        \"\"\")\n        instance.configfile = StringIO(text)\n        try:\n            instance.read_config(StringIO(text))\n            self.fail(\"nothing raised\")\n        except ValueError as exc:\n            self.assertEqual(exc.args[0],\n                'Section [inet_http_server] contains incomplete '\n                'authentication: If a username or a password is '\n                'specified, both the username and password must '\n                'be specified')\n\n    def test_options_afinet_no_port(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [supervisord]\n\n        [inet_http_server]\n        ;no port=\n        \"\"\")\n        instance.configfile = StringIO(text)\n        try:\n            instance.read_config(StringIO(text))\n            self.fail(\"nothing raised\")\n        except ValueError as exc:\n            self.assertEqual(exc.args[0],\n                \"section [inet_http_server] has no port value\")\n\n    def test_cleanup_afunix_unlink(self):\n        with tempfile.NamedTemporaryFile(delete=False) as f:\n            fn = f.name\n            f.write(b'foo')\n        instance = self._makeOne()\n        instance.unlink_socketfiles = True\n        class Server:\n            pass\n        instance.httpservers = [({'family':socket.AF_UNIX, 'file':fn},\n                                 Server())]\n        instance.pidfile = ''\n        instance.cleanup()\n        self.assertFalse(os.path.exists(fn))\n\n    def test_cleanup_afunix_nounlink(self):\n        with tempfile.NamedTemporaryFile(delete=False) as f:\n            fn = f.name\n            f.write(b'foo')\n        try:\n            instance = self._makeOne()\n            class Server:\n                pass\n            instance.httpservers = [({'family':socket.AF_UNIX, 'file':fn},\n                                     Server())]\n            instance.pidfile = ''\n            instance.unlink_socketfiles = False\n            instance.cleanup()\n            self.assertTrue(os.path.exists(fn))\n        finally:\n            try:\n                os.unlink(fn)\n            except OSError:\n                pass\n\n    def test_cleanup_afunix_ignores_oserror_enoent(self):\n        notfound = os.path.join(os.path.dirname(__file__), 'notfound')\n        with tempfile.NamedTemporaryFile(delete=False) as f:\n            socketname = f.name\n            f.write(b'foo')\n        try:\n            instance = self._makeOne()\n            instance.unlink_socketfiles = True\n            class Server:\n                pass\n            instance.httpservers = [\n                ({'family': socket.AF_UNIX, 'file': notfound}, Server()),\n                ({'family': socket.AF_UNIX, 'file': socketname}, Server()),\n            ]\n            instance.pidfile = ''\n            instance.cleanup()\n            self.assertFalse(os.path.exists(socketname))\n        finally:\n            try:\n                os.unlink(socketname)\n            except OSError:\n                pass\n\n    def test_cleanup_removes_pidfile(self):\n        with tempfile.NamedTemporaryFile(delete=False) as f:\n            pidfile = f.name\n            f.write(b'2')\n        try:\n            instance = self._makeOne()\n            instance.pidfile = pidfile\n            instance.logger = DummyLogger()\n            instance.write_pidfile()\n            self.assertTrue(instance.unlink_pidfile)\n            instance.cleanup()\n            self.assertFalse(os.path.exists(pidfile))\n        finally:\n            try:\n                os.unlink(pidfile)\n            except OSError:\n                pass\n\n    def test_cleanup_pidfile_ignores_oserror_enoent(self):\n        notfound = os.path.join(os.path.dirname(__file__), 'notfound')\n        instance = self._makeOne()\n        instance.pidfile = notfound\n        instance.cleanup() # shouldn't raise\n\n    def test_cleanup_does_not_remove_pidfile_from_another_supervisord(self):\n        with tempfile.NamedTemporaryFile(delete=False) as f:\n            pidfile = f.name\n            f.write(b'1234')\n\n        try:\n            instance = self._makeOne()\n            # pidfile exists but unlink_pidfile indicates we did not write it.\n            # pidfile must be from another instance of supervisord and\n            # shouldn't be removed.\n            instance.pidfile = pidfile\n            self.assertFalse(instance.unlink_pidfile)\n            instance.cleanup()\n            self.assertTrue(os.path.exists(pidfile))\n        finally:\n            try:\n                os.unlink(pidfile)\n            except OSError:\n                pass\n\n    def test_cleanup_closes_poller(self):\n        with tempfile.NamedTemporaryFile(delete=False) as f:\n            pidfile = f.name\n            f.write(b'2')\n        try:\n            instance = self._makeOne()\n            instance.pidfile = pidfile\n\n            poller = DummyPoller({})\n            instance.poller = poller\n            self.assertFalse(poller.closed)\n            instance.cleanup()\n            self.assertTrue(poller.closed)\n        finally:\n            try:\n                os.unlink(pidfile)\n            except OSError:\n                pass\n\n    @patch('os.closerange', Mock())\n    def test_cleanup_fds_closes_5_upto_minfds(self):\n        instance = self._makeOne()\n        instance.minfds = 10\n\n        def f():\n            instance.cleanup_fds()\n        f()\n        os.closerange.assert_called_with(5, 10)\n\n    def test_close_httpservers(self):\n        instance = self._makeOne()\n        class Server:\n            closed = False\n            def close(self):\n                self.closed = True\n        server = Server()\n        instance.httpservers = [({}, server)]\n        instance.close_httpservers()\n        self.assertEqual(server.closed, True)\n\n    def test_close_logger(self):\n        instance = self._makeOne()\n        logger = DummyLogger()\n        instance.logger = logger\n        instance.close_logger()\n        self.assertEqual(logger.closed, True)\n\n    def test_close_parent_pipes(self):\n        instance = self._makeOne()\n        closed = []\n        def close_fd(fd):\n            closed.append(fd)\n        instance.close_fd = close_fd\n        pipes = {'stdin': 0, 'stdout': 1, 'stderr': 2,\n                 'child_stdin': 3, 'child_stdout': 4, 'child_stderr': 5}\n        instance.close_parent_pipes(pipes)\n        self.assertEqual(sorted(closed), [0, 1, 2])\n\n    def test_close_parent_pipes_ignores_fd_of_none(self):\n        instance = self._makeOne()\n        closed = []\n        def close_fd(fd):\n            closed.append(fd)\n        instance.close_fd = close_fd\n        pipes = {'stdin': None}\n        instance.close_parent_pipes(pipes)\n        self.assertEqual(closed, [])\n\n    def test_close_child_pipes(self):\n        instance = self._makeOne()\n        closed = []\n        def close_fd(fd):\n            closed.append(fd)\n        instance.close_fd = close_fd\n        pipes = {'stdin': 0, 'stdout': 1, 'stderr': 2,\n                 'child_stdin': 3, 'child_stdout': 4, 'child_stderr': 5}\n        instance.close_child_pipes(pipes)\n        self.assertEqual(sorted(closed), [3, 4, 5])\n\n    def test_close_child_pipes_ignores_fd_of_none(self):\n        instance = self._makeOne()\n        closed = []\n        def close_fd(fd):\n            closed.append(fd)\n        instance.close_fd = close_fd\n        pipes = {'child_stdin': None}\n        instance.close_parent_pipes(pipes)\n        self.assertEqual(sorted(closed), [])\n\n    def test_reopenlogs(self):\n        instance = self._makeOne()\n        logger = DummyLogger()\n        logger.handlers = [DummyLogger()]\n        instance.logger = logger\n        instance.reopenlogs()\n        self.assertEqual(logger.handlers[0].reopened, True)\n        self.assertEqual(logger.data[0], 'supervisord logreopen')\n\n    def test_write_pidfile_ok(self):\n        with tempfile.NamedTemporaryFile(delete=True) as f:\n            fn = f.name\n        self.assertFalse(os.path.exists(fn))\n\n        try:\n            instance = self._makeOne()\n            instance.logger = DummyLogger()\n            instance.pidfile = fn\n            instance.write_pidfile()\n            self.assertTrue(os.path.exists(fn))\n            with open(fn, 'r') as f:\n                pid = int(f.read().strip())\n            self.assertEqual(pid, os.getpid())\n            msg = instance.logger.data[0]\n            self.assertTrue(msg.startswith('supervisord started with pid'))\n            self.assertTrue(instance.unlink_pidfile)\n        finally:\n            try:\n                os.unlink(fn)\n            except OSError:\n                pass\n\n    def test_write_pidfile_fail(self):\n        fn = '/cannot/possibly/exist'\n        instance = self._makeOne()\n        instance.logger = DummyLogger()\n        instance.pidfile = fn\n        instance.write_pidfile()\n        msg = instance.logger.data[0]\n        self.assertTrue(msg.startswith('could not write pidfile'))\n        self.assertFalse(instance.unlink_pidfile)\n\n    def test_close_fd(self):\n        instance = self._makeOne()\n        innie, outie = os.pipe()\n        os.read(innie, 0) # we can read it while its open\n        os.write(outie, as_bytes('foo')) # we can write to it while its open\n        instance.close_fd(innie)\n        self.assertRaises(OSError, os.read, innie, 0)\n        instance.close_fd(outie)\n        self.assertRaises(OSError, os.write, outie, as_bytes('foo'))\n\n    @patch('os.close', Mock(side_effect=OSError))\n    def test_close_fd_ignores_oserror(self):\n        instance = self._makeOne()\n        instance.close_fd(0) # shouldn't raise\n\n    def test_processes_from_section(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [program:foo]\n        command = /bin/cat\n        priority = 1\n        autostart = false\n        autorestart = false\n        startsecs = 100\n        startretries = 100\n        user = root\n        stdout_logfile = NONE\n        stdout_logfile_backups = 1\n        stdout_logfile_maxbytes = 100MB\n        stdout_events_enabled = true\n        stopsignal = KILL\n        stopwaitsecs = 100\n        killasgroup = true\n        exitcodes = 1,4\n        redirect_stderr = false\n        environment = KEY1=val1,KEY2=val2,KEY3=%(process_num)s\n        numprocs = 2\n        process_name = %(group_name)s_%(program_name)s_%(process_num)02d\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        pconfigs = instance.processes_from_section(config, 'program:foo', 'bar')\n        self.assertEqual(len(pconfigs), 2)\n        pconfig = pconfigs[0]\n        self.assertEqual(pconfig.name, 'bar_foo_00')\n        self.assertEqual(pconfig.command, '/bin/cat')\n        self.assertEqual(pconfig.autostart, False)\n        self.assertEqual(pconfig.autorestart, False)\n        self.assertEqual(pconfig.startsecs, 100)\n        self.assertEqual(pconfig.startretries, 100)\n        self.assertEqual(pconfig.uid, 0)\n        self.assertEqual(pconfig.stdout_logfile, None)\n        self.assertEqual(pconfig.stdout_capture_maxbytes, 0)\n        self.assertEqual(pconfig.stdout_logfile_maxbytes, 104857600)\n        self.assertEqual(pconfig.stdout_events_enabled, True)\n        self.assertEqual(pconfig.stopsignal, signal.SIGKILL)\n        self.assertEqual(pconfig.stopasgroup, False)\n        self.assertEqual(pconfig.killasgroup, True)\n        self.assertEqual(pconfig.stopwaitsecs, 100)\n        self.assertEqual(pconfig.exitcodes, [1,4])\n        self.assertEqual(pconfig.redirect_stderr, False)\n        self.assertEqual(pconfig.environment,\n                         {'KEY1':'val1', 'KEY2':'val2', 'KEY3':'0'})\n\n    def test_processes_from_section_environment_with_escaped_chars(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [program:foo]\n        command = /bin/foo\n        environment=VAR_WITH_P=\"some_value_%%_end\"\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        pconfigs = instance.processes_from_section(config, 'program:foo', 'bar')\n        expected = {'VAR_WITH_P': 'some_value_%_end'}\n        self.assertEqual(pconfigs[0].environment, expected)\n\n    def test_processes_from_section_host_node_name_expansion(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [program:foo]\n        command = /bin/foo --host=%(host_node_name)s\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        pconfigs = instance.processes_from_section(config, 'program:foo', 'bar')\n        expected = \"/bin/foo --host=\" + platform.node()\n        self.assertEqual(pconfigs[0].command, expected)\n\n    def test_processes_from_section_process_num_expansion(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [program:foo]\n        process_name = foo_%(process_num)d\n        command = /bin/foo --num=%(process_num)d\n        directory = /tmp/foo_%(process_num)d\n        stderr_logfile = /tmp/foo_%(process_num)d_stderr\n        stdout_logfile = /tmp/foo_%(process_num)d_stdout\n        environment = NUM=%(process_num)d\n        numprocs = 2\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        pconfigs = instance.processes_from_section(config, 'program:foo', 'bar')\n        self.assertEqual(len(pconfigs), 2)\n        for num in (0, 1):\n            self.assertEqual(pconfigs[num].name, 'foo_%d' % num)\n            self.assertEqual(pconfigs[num].command, \"/bin/foo --num=%d\" % num)\n            self.assertEqual(pconfigs[num].directory, '/tmp/foo_%d' % num)\n            self.assertEqual(pconfigs[num].stderr_logfile,\n                '/tmp/foo_%d_stderr' % num)\n            self.assertEqual(pconfigs[num].stdout_logfile,\n                '/tmp/foo_%d_stdout' % num)\n            self.assertEqual(pconfigs[num].environment, {'NUM': '%d' % num})\n\n    def test_processes_from_section_numprocs_expansion(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [program:foo]\n        process_name = foo_%(process_num)d\n        command = /bin/foo --numprocs=%(numprocs)d\n        numprocs = 2\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        pconfigs = instance.processes_from_section(config, 'program:foo', 'bar')\n        self.assertEqual(len(pconfigs), 2)\n        for num in (0, 1):\n            self.assertEqual(pconfigs[num].name, 'foo_%d' % num)\n            self.assertEqual(pconfigs[num].command, \"/bin/foo --numprocs=%d\" % 2)\n\n    def test_processes_from_section_expands_directory(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [program:foo]\n        command = /bin/cat\n        directory = /tmp/%(ENV_FOO)s\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.expansions = {'ENV_FOO': 'bar'}\n        config.read_string(text)\n        pconfigs = instance.processes_from_section(config, 'program:foo', 'bar')\n        self.assertEqual(pconfigs[0].directory, '/tmp/bar')\n\n    def test_processes_from_section_environment_variables_expansion(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [program:foo]\n        command = /bin/foo --path='%(ENV_PATH)s'\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        pconfigs = instance.processes_from_section(config, 'program:foo', 'bar')\n        expected = \"/bin/foo --path='%s'\" % os.environ['PATH']\n        self.assertEqual(pconfigs[0].command, expected)\n\n    def test_processes_from_section_expands_env_in_environment(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [program:foo]\n        command = /bin/foo\n        environment = PATH='/foo/bar:%(ENV_PATH)s'\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        pconfigs = instance.processes_from_section(config, 'program:foo', 'bar')\n        expected = \"/foo/bar:%s\" % os.environ['PATH']\n        self.assertEqual(pconfigs[0].environment['PATH'], expected)\n\n    def test_processes_from_section_redirect_stderr_with_filename(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [program:foo]\n        command = /bin/foo\n        redirect_stderr = true\n        stderr_logfile = /tmp/logfile\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        pconfigs = instance.processes_from_section(config, 'program:foo', 'bar')\n        self.assertEqual(instance.parse_warnings[0],\n            'For [program:foo], redirect_stderr=true but stderr_logfile has '\n            'also been set to a filename, the filename has been ignored')\n        self.assertEqual(pconfigs[0].stderr_logfile, None)\n\n    def test_processes_from_section_rewrites_stdout_logfile_of_syslog(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [program:foo]\n        command = /bin/foo\n        stdout_logfile = syslog\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        pconfigs = instance.processes_from_section(config, 'program:foo', 'bar')\n        self.assertEqual(instance.parse_warnings[0],\n            'For [program:foo], stdout_logfile=syslog but this is deprecated '\n            'and will be removed.  Use stdout_syslog=true to enable syslog '\n            'instead.')\n        self.assertEqual(pconfigs[0].stdout_logfile, None)\n        self.assertEqual(pconfigs[0].stdout_syslog, True)\n\n    def test_processes_from_section_rewrites_stderr_logfile_of_syslog(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [program:foo]\n        command = /bin/foo\n        stderr_logfile = syslog\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        pconfigs = instance.processes_from_section(config, 'program:foo', 'bar')\n        self.assertEqual(instance.parse_warnings[0],\n            'For [program:foo], stderr_logfile=syslog but this is deprecated '\n            'and will be removed.  Use stderr_syslog=true to enable syslog '\n            'instead.')\n        self.assertEqual(pconfigs[0].stderr_logfile, None)\n        self.assertEqual(pconfigs[0].stderr_syslog, True)\n\n    def test_processes_from_section_redirect_stderr_with_auto(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [program:foo]\n        command = /bin/foo\n        redirect_stderr = true\n        stderr_logfile = auto\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        pconfigs = instance.processes_from_section(config, 'program:foo', 'bar')\n        self.assertEqual(instance.parse_warnings, [])\n        self.assertEqual(pconfigs[0].stderr_logfile, None)\n\n    def test_processes_from_section_accepts_number_for_stopsignal(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [program:foo]\n        command = /bin/foo\n        stopsignal = %d\n        \"\"\" % signal.SIGQUIT)\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        pconfigs = instance.processes_from_section(config, 'program:foo', 'bar')\n        self.assertEqual(instance.parse_warnings, [])\n        self.assertEqual(pconfigs[0].stopsignal, signal.SIGQUIT)\n\n    def test_options_with_environment_expansions(self):\n        text = lstrip(\"\"\"\\\n        [supervisord]\n        logfile = %(ENV_HOME)s/supervisord.log\n        logfile_maxbytes = %(ENV_SUPD_LOGFILE_MAXBYTES)s\n        logfile_backups = %(ENV_SUPD_LOGFILE_BACKUPS)s\n        loglevel = %(ENV_SUPD_LOGLEVEL)s\n        nodaemon = %(ENV_SUPD_NODAEMON)s\n        minfds = %(ENV_SUPD_MINFDS)s\n        minprocs = %(ENV_SUPD_MINPROCS)s\n        umask = %(ENV_SUPD_UMASK)s\n        identifier = supervisor_%(ENV_USER)s\n        nocleanup = %(ENV_SUPD_NOCLEANUP)s\n        childlogdir = %(ENV_HOME)s\n        strip_ansi = %(ENV_SUPD_STRIP_ANSI)s\n        environment = GLOBAL_ENV_VAR=%(ENV_SUPR_ENVIRONMENT_VALUE)s\n\n        [inet_http_server]\n        port=*:%(ENV_HTSRV_PORT)s\n        username=%(ENV_HTSRV_USER)s\n        password=%(ENV_HTSRV_PASS)s\n\n        [program:cat1]\n        command=%(ENV_CAT1_COMMAND)s --logdir=%(ENV_CAT1_COMMAND_LOGDIR)s\n        priority=%(ENV_CAT1_PRIORITY)s\n        autostart=%(ENV_CAT1_AUTOSTART)s\n        user=%(ENV_CAT1_USER)s\n        stdout_logfile=%(ENV_CAT1_STDOUT_LOGFILE)s\n        stdout_logfile_maxbytes = %(ENV_CAT1_STDOUT_LOGFILE_MAXBYTES)s\n        stdout_logfile_backups = %(ENV_CAT1_STDOUT_LOGFILE_BACKUPS)s\n        stopsignal=%(ENV_CAT1_STOPSIGNAL)s\n        stopwaitsecs=%(ENV_CAT1_STOPWAIT)s\n        startsecs=%(ENV_CAT1_STARTWAIT)s\n        startretries=%(ENV_CAT1_STARTRETRIES)s\n        directory=%(ENV_CAT1_DIR)s\n        umask=%(ENV_CAT1_UMASK)s\n        environment = PROGRAM_ENV_VAR=%(ENV_CAT1_ENVIRONMENT_VALUE)s\n        \"\"\")\n        from supervisor import datatypes\n        from supervisor.options import UnhosedConfigParser\n        instance = self._makeOne()\n        instance.environ_expansions = {\n            'ENV_HOME': tempfile.gettempdir(),\n            'ENV_USER': 'johndoe',\n            'ENV_HTSRV_PORT': '9210',\n            'ENV_HTSRV_USER': 'someuser',\n            'ENV_HTSRV_PASS': 'passwordhere',\n            'ENV_SUPR_ENVIRONMENT_VALUE': 'from_supervisord_section',\n            'ENV_SUPD_LOGFILE_MAXBYTES': '51MB',\n            'ENV_SUPD_LOGFILE_BACKUPS': '10',\n            'ENV_SUPD_LOGLEVEL': 'info',\n            'ENV_SUPD_NODAEMON': 'false',\n            'ENV_SUPD_SILENT': 'false',\n            'ENV_SUPD_MINFDS': '1024',\n            'ENV_SUPD_MINPROCS': '200',\n            'ENV_SUPD_UMASK': '002',\n            'ENV_SUPD_NOCLEANUP': 'true',\n            'ENV_SUPD_STRIP_ANSI': 'false',\n            'ENV_CAT1_COMMAND': '/bin/customcat',\n            'ENV_CAT1_COMMAND_LOGDIR': '/path/to/logs',\n            'ENV_CAT1_PRIORITY': '3',\n            'ENV_CAT1_AUTOSTART': 'true',\n            'ENV_CAT1_ENVIRONMENT_VALUE': 'from_program_section',\n            'ENV_CAT1_USER': 'root', # resolved to uid\n            'ENV_CAT1_STDOUT_LOGFILE': '/tmp/cat.log',\n            'ENV_CAT1_STDOUT_LOGFILE_MAXBYTES': '78KB',\n            'ENV_CAT1_STDOUT_LOGFILE_BACKUPS': '2',\n            'ENV_CAT1_STOPSIGNAL': 'KILL',\n            'ENV_CAT1_STOPWAIT': '5',\n            'ENV_CAT1_STARTWAIT': '5',\n            'ENV_CAT1_STARTRETRIES': '10',\n            'ENV_CAT1_DIR': '/tmp',\n            'ENV_CAT1_UMASK': '002',\n           }\n        config = UnhosedConfigParser()\n        config.expansions = instance.environ_expansions\n        config.read_string(text)\n        instance.configfile = StringIO(text)\n        instance.read_config(StringIO(text))\n        instance.realize(args=[])\n        # supervisord\n        self.assertEqual(instance.logfile,\n                         '%(ENV_HOME)s/supervisord.log' % config.expansions)\n        self.assertEqual(instance.identifier,\n                         'supervisor_%(ENV_USER)s' % config.expansions)\n        self.assertEqual(instance.logfile_maxbytes, 53477376)\n        self.assertEqual(instance.logfile_backups, 10)\n        self.assertEqual(instance.loglevel, LevelsByName.INFO)\n        self.assertEqual(instance.nodaemon, False)\n        self.assertEqual(instance.silent, False)\n        self.assertEqual(instance.minfds, 1024)\n        self.assertEqual(instance.minprocs, 200)\n        self.assertEqual(instance.nocleanup, True)\n        self.assertEqual(instance.childlogdir, config.expansions['ENV_HOME'])\n        self.assertEqual(instance.strip_ansi, False)\n        # inet_http_server\n        options = instance.configroot.supervisord\n        self.assertEqual(options.server_configs[0]['family'], socket.AF_INET)\n        self.assertEqual(options.server_configs[0]['host'], '')\n        self.assertEqual(options.server_configs[0]['port'], 9210)\n        self.assertEqual(options.server_configs[0]['username'], 'someuser')\n        self.assertEqual(options.server_configs[0]['password'], 'passwordhere')\n        # cat1\n        cat1 = options.process_group_configs[0]\n        self.assertEqual(cat1.name, 'cat1')\n        self.assertEqual(cat1.priority, 3)\n        self.assertEqual(len(cat1.process_configs), 1)\n        proc1 = cat1.process_configs[0]\n        self.assertEqual(proc1.name, 'cat1')\n        self.assertEqual(proc1.command,\n                         '/bin/customcat --logdir=/path/to/logs')\n        self.assertEqual(proc1.priority, 3)\n        self.assertEqual(proc1.autostart, True)\n        self.assertEqual(proc1.autorestart, datatypes.RestartWhenExitUnexpected)\n        self.assertEqual(proc1.startsecs, 5)\n        self.assertEqual(proc1.startretries, 10)\n        self.assertEqual(proc1.uid, 0)\n        self.assertEqual(proc1.stdout_logfile, '/tmp/cat.log')\n        self.assertEqual(proc1.stopsignal, signal.SIGKILL)\n        self.assertEqual(proc1.stopwaitsecs, 5)\n        self.assertEqual(proc1.stopasgroup, False)\n        self.assertEqual(proc1.killasgroup, False)\n        self.assertEqual(proc1.stdout_logfile_maxbytes,\n                         datatypes.byte_size('78KB'))\n        self.assertEqual(proc1.stdout_logfile_backups, 2)\n        self.assertEqual(proc1.exitcodes, [0])\n        self.assertEqual(proc1.directory, '/tmp')\n        self.assertEqual(proc1.umask, 2)\n        expected_env = {\n            'GLOBAL_ENV_VAR': 'from_supervisord_section',\n            'PROGRAM_ENV_VAR': 'from_program_section'\n            }\n        self.assertEqual(proc1.environment, expected_env)\n\n    def test_options_supervisord_section_expands_here(self):\n        instance = self._makeOne()\n        text = lstrip('''\\\n        [supervisord]\n        childlogdir=%(here)s\n        directory=%(here)s\n        logfile=%(here)s/supervisord.log\n        pidfile=%(here)s/supervisord.pid\n        ''')\n        here = tempfile.mkdtemp()\n        supervisord_conf = os.path.join(here, 'supervisord.conf')\n        with open(supervisord_conf, 'w') as f:\n            f.write(text)\n        try:\n            instance.configfile = supervisord_conf\n            instance.realize(args=[])\n        finally:\n            shutil.rmtree(here, ignore_errors=True)\n        self.assertEqual(instance.childlogdir,\n            os.path.join(here))\n        self.assertEqual(instance.directory,\n            os.path.join(here))\n        self.assertEqual(instance.logfile,\n            os.path.join(here, 'supervisord.log'))\n        self.assertEqual(instance.pidfile,\n            os.path.join(here, 'supervisord.pid'))\n\n    def test_options_program_section_expands_env_from_supervisord_sect(self):\n        instance = self._makeOne()\n        text = lstrip('''\n        [supervisord]\n        environment=CMD=/bin/from/supervisord/section\n\n        [program:cmd]\n        command=%(ENV_CMD)s\n        ''')\n        here = tempfile.mkdtemp()\n        supervisord_conf = os.path.join(here, 'supervisord.conf')\n        with open(supervisord_conf, 'w') as f:\n            f.write(text)\n        try:\n            instance.configfile = supervisord_conf\n            instance.realize(args=[])\n        finally:\n            shutil.rmtree(here, ignore_errors=True)\n        options = instance.configroot.supervisord\n        group = options.process_group_configs[0]\n        self.assertEqual(group.name, 'cmd')\n        proc = group.process_configs[0]\n        self.assertEqual(proc.command,\n            os.path.join(here, '/bin/from/supervisord/section'))\n\n    def test_options_program_section_expands_env_from_program_sect(self):\n        instance = self._makeOne()\n        text = lstrip('''\n        [supervisord]\n        environment=CMD=/bin/from/supervisord/section\n\n        [program:cmd]\n        command=%(ENV_CMD)s\n        environment=CMD=/bin/from/program/section\n        ''')\n        here = tempfile.mkdtemp()\n        supervisord_conf = os.path.join(here, 'supervisord.conf')\n        with open(supervisord_conf, 'w') as f:\n            f.write(text)\n        try:\n            instance.configfile = supervisord_conf\n            instance.realize(args=[])\n        finally:\n            shutil.rmtree(here, ignore_errors=True)\n        options = instance.configroot.supervisord\n        group = options.process_group_configs[0]\n        self.assertEqual(group.name, 'cmd')\n        proc = group.process_configs[0]\n        self.assertEqual(proc.command,\n            os.path.join(here, '/bin/from/program/section'))\n\n    def test_options_program_section_expands_here(self):\n        instance = self._makeOne()\n        text = lstrip('''\n        [supervisord]\n\n        [program:cat]\n        command=%(here)s/bin/cat\n        directory=%(here)s/thedirectory\n        environment=FOO=%(here)s/foo\n        serverurl=unix://%(here)s/supervisord.sock\n        stdout_logfile=%(here)s/stdout.log\n        stderr_logfile=%(here)s/stderr.log\n        ''')\n        here = tempfile.mkdtemp()\n        supervisord_conf = os.path.join(here, 'supervisord.conf')\n        with open(supervisord_conf, 'w') as f:\n            f.write(text)\n        try:\n            instance.configfile = supervisord_conf\n            instance.realize(args=[])\n        finally:\n            shutil.rmtree(here, ignore_errors=True)\n        options = instance.configroot.supervisord\n        group = options.process_group_configs[0]\n        self.assertEqual(group.name, 'cat')\n        proc = group.process_configs[0]\n        self.assertEqual(proc.directory,\n            os.path.join(here, 'thedirectory'))\n        self.assertEqual(proc.command,\n            os.path.join(here, 'bin/cat'))\n        self.assertEqual(proc.environment,\n            {'FOO': os.path.join(here, 'foo')})\n        self.assertEqual(proc.serverurl,\n            'unix://' + os.path.join(here, 'supervisord.sock'))\n        self.assertEqual(proc.stdout_logfile,\n            os.path.join(here, 'stdout.log'))\n        self.assertEqual(proc.stderr_logfile,\n            os.path.join(here, 'stderr.log'))\n\n    def test_options_eventlistener_section_expands_here(self):\n        instance = self._makeOne()\n        text = lstrip('''\n        [supervisord]\n\n        [eventlistener:memmon]\n        events=TICK_60\n        command=%(here)s/bin/memmon\n        directory=%(here)s/thedirectory\n        environment=FOO=%(here)s/foo\n        serverurl=unix://%(here)s/supervisord.sock\n        stdout_logfile=%(here)s/stdout.log\n        stderr_logfile=%(here)s/stderr.log\n        ''')\n        here = tempfile.mkdtemp()\n        supervisord_conf = os.path.join(here, 'supervisord.conf')\n        with open(supervisord_conf, 'w') as f:\n            f.write(text)\n        try:\n            instance.configfile = supervisord_conf\n            instance.realize(args=[])\n        finally:\n            shutil.rmtree(here, ignore_errors=True)\n        options = instance.configroot.supervisord\n        group = options.process_group_configs[0]\n        self.assertEqual(group.name, 'memmon')\n        proc = group.process_configs[0]\n        self.assertEqual(proc.directory,\n            os.path.join(here, 'thedirectory'))\n        self.assertEqual(proc.command,\n            os.path.join(here, 'bin/memmon'))\n        self.assertEqual(proc.environment,\n            {'FOO': os.path.join(here, 'foo')})\n        self.assertEqual(proc.serverurl,\n            'unix://' + os.path.join(here, 'supervisord.sock'))\n        self.assertEqual(proc.stdout_logfile,\n            os.path.join(here, 'stdout.log'))\n        self.assertEqual(proc.stderr_logfile,\n            os.path.join(here, 'stderr.log'))\n\n    def test_options_expands_combined_expansions(self):\n        instance = self._makeOne()\n        text = lstrip('''\n        [supervisord]\n        logfile = %(here)s/%(ENV_LOGNAME)s.log\n\n        [program:cat]\n        ''')\n        text += ('command = %(here)s/bin/cat --foo=%(ENV_FOO)s '\n                 '--num=%(process_num)d --node=%(host_node_name)s')\n\n        here = tempfile.mkdtemp()\n        supervisord_conf = os.path.join(here, 'supervisord.conf')\n        with open(supervisord_conf, 'w') as f:\n            f.write(text)\n        try:\n            instance.environ_expansions = {\n                'ENV_LOGNAME': 'mainlog',\n                'ENV_FOO': 'bar'\n                }\n            instance.configfile = supervisord_conf\n            instance.realize(args=[])\n        finally:\n            shutil.rmtree(here, ignore_errors=True)\n\n        section = instance.configroot.supervisord\n        self.assertEqual(section.logfile,\n            os.path.join(here, 'mainlog.log'))\n\n        cat_group = section.process_group_configs[0]\n        cat_0 = cat_group.process_configs[0]\n        expected = '%s --foo=bar --num=0 --node=%s' % (\n            os.path.join(here, 'bin/cat'),\n            platform.node()\n            )\n        self.assertEqual(cat_0.command, expected)\n\n    def test_options_error_handler_shows_main_filename(self):\n        dirname = tempfile.mkdtemp()\n        supervisord_conf = os.path.join(dirname, 'supervisord.conf')\n        text = lstrip('''\n        [supervisord]\n\n        [program:cat]\n        command = /bin/cat\n        stopsignal = NOTASIGNAL\n        ''')\n        with open(supervisord_conf, 'w') as f:\n            f.write(text)\n\n        instance = self._makeOne()\n        try:\n            instance.configfile = supervisord_conf\n            try:\n                instance.process_config(do_usage=False)\n                self.fail('nothing raised')\n            except ValueError as e:\n                self.assertEqual(str(e.args[0]),\n                    \"value 'NOTASIGNAL' is not a valid signal name \"\n                    \"in section 'program:cat' (file: %r)\" % supervisord_conf)\n        finally:\n            shutil.rmtree(dirname, ignore_errors=True)\n\n    def test_options_error_handler_shows_included_filename(self):\n        dirname = tempfile.mkdtemp()\n        supervisord_conf = os.path.join(dirname, \"supervisord.conf\")\n        text = lstrip(\"\"\"\\\n        [supervisord]\n\n        [include]\n        files=%s/conf.d/*.conf\n        \"\"\" % dirname)\n        with open(supervisord_conf, 'w') as f:\n            f.write(text)\n\n        conf_d = os.path.join(dirname, \"conf.d\")\n        os.mkdir(conf_d)\n        included_conf = os.path.join(conf_d, \"included.conf\")\n        text = lstrip('''\\\n        [program:cat]\n        command = /bin/cat\n        stopsignal = NOTASIGNAL\n        ''')\n        with open(included_conf, 'w') as f:\n            f.write(text)\n\n        instance = self._makeOne()\n        try:\n            instance.configfile = supervisord_conf\n            try:\n                instance.process_config(do_usage=False)\n                self.fail('nothing raised')\n            except ValueError as e:\n                self.assertEqual(str(e.args[0]),\n                    \"value 'NOTASIGNAL' is not a valid signal name \"\n                    \"in section 'program:cat' (file: %r)\" % included_conf)\n        finally:\n            shutil.rmtree(dirname, ignore_errors=True)\n\n    def test_processes_from_section_bad_program_name_spaces(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [program:spaces are bad]\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        self.assertRaises(ValueError, instance.processes_from_section,\n                          config, 'program:spaces are bad', None)\n\n    def test_processes_from_section_bad_program_name_colons(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [program:colons:are:bad]\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        self.assertRaises(ValueError, instance.processes_from_section,\n                          config, 'program:colons:are:bad', None)\n\n    def test_processes_from_section_no_procnum_in_processname(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [program:foo]\n        command = /bin/cat\n        numprocs = 2\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        self.assertRaises(ValueError, instance.processes_from_section,\n                          config, 'program:foo', None)\n\n    def test_processes_from_section_no_command(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [program:foo]\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        try:\n            instance.processes_from_section(config, 'program:foo', None)\n            self.fail('nothing raised')\n        except ValueError as exc:\n            self.assertTrue(exc.args[0].startswith(\n                'program section program:foo does not specify a command'))\n\n    def test_processes_from_section_missing_replacement_in_process_name(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [program:foo]\n        command = /bin/cat\n        process_name = %(not_there)s\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        self.assertRaises(ValueError, instance.processes_from_section,\n                          config, 'program:foo', None)\n\n    def test_processes_from_section_bad_expression_in_process_name(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [program:foo]\n        command = /bin/cat\n        process_name = %(program_name)\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        self.assertRaises(ValueError, instance.processes_from_section,\n                          config, 'program:foo', None)\n\n    def test_processes_from_section_bad_chars_in_process_name(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [program:foo]\n        command = /bin/cat\n        process_name = colons:are:bad\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        self.assertRaises(ValueError, instance.processes_from_section,\n                          config, 'program:foo', None)\n\n    def test_processes_from_section_stopasgroup_implies_killasgroup(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [program:foo]\n        command = /bin/cat\n        process_name = %(program_name)s\n        stopasgroup = true\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        pconfigs = instance.processes_from_section(config, 'program:foo', 'bar')\n        self.assertEqual(len(pconfigs), 1)\n        pconfig = pconfigs[0]\n        self.assertEqual(pconfig.stopasgroup, True)\n        self.assertEqual(pconfig.killasgroup, True)\n\n    def test_processes_from_section_killasgroup_mismatch_w_stopasgroup(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [program:foo]\n        command = /bin/cat\n        process_name = %(program_name)s\n        stopasgroup = true\n        killasgroup = false\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        self.assertRaises(ValueError, instance.processes_from_section,\n                          config, 'program:foo', None)\n\n    def test_processes_from_section_unexpected_end_of_key_value_pairs(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [program:foo]\n        command = /bin/cat\n        environment = KEY1=val1,KEY2=val2,KEY3\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        try:\n            instance.processes_from_section(config, 'program:foo', None)\n        except ValueError as e:\n            self.assertTrue(\n                \"Unexpected end of key/value pairs in value \"\n                \"'KEY1=val1,KEY2=val2,KEY3' in section 'program:foo'\"\n                in str(e))\n        else:\n            self.fail('instance.processes_from_section should '\n                      'raise a ValueError')\n\n    def test_processes_from_section_shows_conf_filename_on_valueerror(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [program:foo]\n        ;no command\n        \"\"\")\n        with tempfile.NamedTemporaryFile(mode=\"w+\") as f:\n            try:\n                f.write(text)\n                f.flush()\n                from supervisor.options import UnhosedConfigParser\n                config = UnhosedConfigParser()\n                config.read(f.name)\n                instance.processes_from_section(config, 'program:foo', None)\n            except ValueError as e:\n                self.assertEqual(e.args[0],\n                    \"program section program:foo does not specify a command \"\n                    \"in section 'program:foo' (file: %r)\" % f.name)\n            else:\n                self.fail('nothing raised')\n\n    def test_processes_from_section_autolog_without_rollover(self):\n        instance = self._makeOne()\n        text = lstrip(\"\"\"\\\n        [program:foo]\n        command = /bin/foo\n        stdout_logfile = AUTO\n        stdout_logfile_maxbytes = 0\n        stderr_logfile = AUTO\n        stderr_logfile_maxbytes = 0\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        instance.logger = DummyLogger()\n        config.read_string(text)\n        instance.processes_from_section(config, 'program:foo', None)\n        self.assertEqual(instance.parse_warnings[0],\n             'For [program:foo], AUTO logging used for stdout_logfile '\n             'without rollover, set maxbytes > 0 to avoid filling up '\n              'filesystem unintentionally')\n        self.assertEqual(instance.parse_warnings[1],\n             'For [program:foo], AUTO logging used for stderr_logfile '\n             'without rollover, set maxbytes > 0 to avoid filling up '\n              'filesystem unintentionally')\n\n    def test_homogeneous_process_groups_from_parser(self):\n        text = lstrip(\"\"\"\\\n        [program:many]\n        process_name = %(program_name)s_%(process_num)s\n        command = /bin/cat\n        numprocs = 2\n        priority = 1\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        instance = self._makeOne()\n        gconfigs = instance.process_groups_from_parser(config)\n        self.assertEqual(len(gconfigs), 1)\n        gconfig = gconfigs[0]\n        self.assertEqual(gconfig.name, 'many')\n        self.assertEqual(gconfig.priority, 1)\n        self.assertEqual(len(gconfig.process_configs), 2)\n\n    def test_event_listener_pools_from_parser(self):\n        text = lstrip(\"\"\"\\\n        [eventlistener:dog]\n        events=PROCESS_COMMUNICATION\n        process_name = %(program_name)s_%(process_num)s\n        command = /bin/dog\n        numprocs = 2\n        priority = 1\n\n        [eventlistener:cat]\n        events=PROCESS_COMMUNICATION\n        process_name = %(program_name)s_%(process_num)s\n        command = /bin/cat\n        numprocs = 3\n\n        [eventlistener:biz]\n        events=PROCESS_COMMUNICATION\n        process_name = %(program_name)s_%(process_num)s\n        command = /bin/biz\n        numprocs = 2\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        from supervisor.dispatchers import default_handler\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        instance = self._makeOne()\n        gconfigs = instance.process_groups_from_parser(config)\n        self.assertEqual(len(gconfigs), 3)\n\n        gconfig1 = gconfigs[0]\n        self.assertEqual(gconfig1.name, 'biz')\n        self.assertEqual(gconfig1.result_handler, default_handler)\n        self.assertEqual(len(gconfig1.process_configs), 2)\n\n        gconfig1 = gconfigs[1]\n        self.assertEqual(gconfig1.name, 'cat')\n        self.assertEqual(gconfig1.priority, -1)\n        self.assertEqual(gconfig1.result_handler, default_handler)\n        self.assertEqual(len(gconfig1.process_configs), 3)\n\n        gconfig1 = gconfigs[2]\n        self.assertEqual(gconfig1.name, 'dog')\n        self.assertEqual(gconfig1.priority, 1)\n        self.assertEqual(gconfig1.result_handler, default_handler)\n        self.assertEqual(len(gconfig1.process_configs), 2)\n\n    def test_event_listener_pools_from_parser_with_environment_expansions(self):\n        text = lstrip(\"\"\"\\\n        [eventlistener:dog]\n        events=PROCESS_COMMUNICATION\n        process_name = %(ENV_EL1_PROCNAME)s_%(program_name)s_%(process_num)s\n        command = %(ENV_EL1_COMMAND)s\n        numprocs = %(ENV_EL1_NUMPROCS)s\n        priority = %(ENV_EL1_PRIORITY)s\n\n        [eventlistener:cat]\n        events=PROCESS_COMMUNICATION\n        process_name = %(program_name)s_%(process_num)s\n        command = /bin/cat\n        numprocs = 3\n\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        from supervisor.dispatchers import default_handler\n        instance = self._makeOne()\n        instance.environ_expansions = {'ENV_HOME': tempfile.gettempdir(),\n                                       'ENV_USER': 'johndoe',\n                                       'ENV_EL1_PROCNAME': 'myeventlistener',\n                                       'ENV_EL1_COMMAND': '/bin/dog',\n                                       'ENV_EL1_NUMPROCS': '2',\n                                       'ENV_EL1_PRIORITY': '1',\n                                      }\n        config = UnhosedConfigParser()\n        config.expansions = instance.environ_expansions\n        config.read_string(text)\n        gconfigs = instance.process_groups_from_parser(config)\n        self.assertEqual(len(gconfigs), 2)\n\n        gconfig0 = gconfigs[0]\n        self.assertEqual(gconfig0.name, 'cat')\n        self.assertEqual(gconfig0.priority, -1)\n        self.assertEqual(gconfig0.result_handler, default_handler)\n        self.assertEqual(len(gconfig0.process_configs), 3)\n\n        gconfig1 = gconfigs[1]\n        self.assertEqual(gconfig1.name, 'dog')\n        self.assertEqual(gconfig1.priority, 1)\n        self.assertEqual(gconfig1.result_handler, default_handler)\n        self.assertEqual(len(gconfig1.process_configs), 2)\n        dog0 = gconfig1.process_configs[0]\n        self.assertEqual(dog0.name, 'myeventlistener_dog_0')\n        self.assertEqual(dog0.command, '/bin/dog')\n        self.assertEqual(dog0.priority, 1)\n        dog1 = gconfig1.process_configs[1]\n        self.assertEqual(dog1.name, 'myeventlistener_dog_1')\n        self.assertEqual(dog1.command, '/bin/dog')\n        self.assertEqual(dog1.priority, 1)\n\n    def test_event_listener_pool_disallows_buffer_size_zero(self):\n        text = lstrip(\"\"\"\\\n        [eventlistener:dog]\n        events=EVENT\n        command = /bin/dog\n        buffer_size = 0\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        instance = self._makeOne()\n        try:\n            instance.process_groups_from_parser(config)\n            self.fail('nothing raised')\n        except ValueError as exc:\n            self.assertEqual(exc.args[0], '[eventlistener:dog] section sets '\n                'invalid buffer_size (0)')\n\n    def test_event_listener_pool_disallows_redirect_stderr(self):\n        text = lstrip(\"\"\"\\\n        [eventlistener:dog]\n        events=PROCESS_COMMUNICATION\n        command = /bin/dog\n        redirect_stderr = True\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        instance = self._makeOne()\n        try:\n            instance.process_groups_from_parser(config)\n            self.fail('nothing raised')\n        except ValueError as exc:\n            self.assertEqual(exc.args[0], '[eventlistener:dog] section sets '\n                'redirect_stderr=true but this is not allowed because it '\n                'will interfere with the eventlistener protocol')\n\n    def test_event_listener_pool_with_event_result_handler(self):\n        text = lstrip(\"\"\"\\\n        [eventlistener:dog]\n        events=PROCESS_COMMUNICATION\n        command = /bin/dog\n        result_handler = supervisor.tests.base:dummy_handler\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        from supervisor.tests.base import dummy_handler\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        instance = self._makeOne()\n        gconfigs = instance.process_groups_from_parser(config)\n        self.assertEqual(len(gconfigs), 1)\n\n        gconfig1 = gconfigs[0]\n        self.assertEqual(gconfig1.result_handler, dummy_handler)\n\n    def test_event_listener_pool_result_handler_unimportable_ImportError(self):\n        text = lstrip(\"\"\"\\\n        [eventlistener:cat]\n        events=PROCESS_COMMUNICATION\n        command = /bin/cat\n        result_handler = thisishopefullynotanimportablepackage:nonexistent\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        instance = self._makeOne()\n        try:\n            instance.process_groups_from_parser(config)\n            self.fail('nothing raised')\n        except ValueError as exc:\n            possible_msgs = (\n                # py27, py34\n                \"thisishopefullynotanimportablepackage:nonexistent cannot be \"\n                \"resolved within [eventlistener:cat]: No module named \"\n                \"thisishopefullynotanimportablepackage\"\n\n                # py35+\n                \"thisishopefullynotanimportablepackage:nonexistent cannot be \"\n                \"resolved within [eventlistener:cat]: No module named \"\n                \"'thisishopefullynotanimportablepackage'\"\n            )\n            self.assertTrue(exc.args[0] in possible_msgs, exc.args[0])\n\n    def test_event_listener_pool_result_handler_unimportable_AttributeError(self):\n        text = lstrip(\"\"\"\\\n        [eventlistener:cat]\n        events=PROCESS_COMMUNICATION\n        command = /bin/cat\n        result_handler = supervisor.tests.base:nonexistent\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        instance = self._makeOne()\n        try:\n            instance.process_groups_from_parser(config)\n            self.fail('nothing raised')\n        except ValueError as exc:\n            possible_msgs = (\n                # py27, py34\n                \"supervisor.tests.base:nonexistent cannot be resolved \"\n                \"within [eventlistener:cat]: 'module' object \"\n                \"has no attribute 'nonexistent'\",\n\n                # py35+\n                \"supervisor.tests.base:nonexistent cannot be resolved \"\n                \"within [eventlistener:cat]: module 'supervisor.tests.base' \"\n                \"has no attribute 'nonexistent'\"\n            )\n            self.assertTrue(exc.args[0] in possible_msgs, exc.args[0])\n\n    def test_event_listener_pool_noeventsline(self):\n        text = lstrip(\"\"\"\\\n        [eventlistener:dog]\n        process_name = %(program_name)s_%(process_num)s\n        command = /bin/dog\n        numprocs = 2\n        priority = 1\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        instance = self._makeOne()\n        self.assertRaises(ValueError,instance.process_groups_from_parser,config)\n\n    def test_event_listener_pool_unknown_eventtype(self):\n        text = lstrip(\"\"\"\\\n        [eventlistener:dog]\n        events=PROCESS_COMMUNICATION,THIS_EVENT_TYPE_DOESNT_EXIST\n        process_name = %(program_name)s_%(process_num)s\n        command = /bin/dog\n        numprocs = 2\n        priority = 1\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        instance = self._makeOne()\n        self.assertRaises(ValueError,instance.process_groups_from_parser,config)\n\n    def test_fcgi_programs_from_parser(self):\n        from supervisor.options import FastCGIGroupConfig\n        from supervisor.options import FastCGIProcessConfig\n        text = lstrip(\"\"\"\\\n        [fcgi-program:foo]\n        socket = unix:///tmp/%(program_name)s.sock\n        socket_owner = testuser:testgroup\n        socket_mode = 0666\n        socket_backlog = 32676\n        process_name = %(program_name)s_%(process_num)s\n        command = /bin/foo\n        numprocs = 2\n        priority = 1\n\n        [fcgi-program:bar]\n        socket = unix:///tmp/%(program_name)s.sock\n        process_name = %(program_name)s_%(process_num)s\n        command = /bin/bar\n        user = testuser\n        numprocs = 3\n\n        [fcgi-program:flub]\n        socket = unix:///tmp/%(program_name)s.sock\n        command = /bin/flub\n\n        [fcgi-program:cub]\n        socket = tcp://localhost:6000\n        command = /bin/cub\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        instance = self._makeOne()\n\n        #Patch pwd and grp module functions to give us sentinel\n        #uid/gid values so that the test does not depend on\n        #any specific system users\n        pwd_mock = Mock()\n        pwd_mock.return_value = (None, None, sentinel.uid, sentinel.gid)\n        grp_mock = Mock()\n        grp_mock.return_value = (None, None, sentinel.gid)\n        @patch('pwd.getpwuid', pwd_mock)\n        @patch('pwd.getpwnam', pwd_mock)\n        @patch('grp.getgrnam', grp_mock)\n        def get_process_groups(instance, config):\n            return instance.process_groups_from_parser(config)\n\n        gconfigs = get_process_groups(instance, config)\n\n        exp_owner = (sentinel.uid, sentinel.gid)\n\n        self.assertEqual(len(gconfigs), 4)\n\n        gconf_foo = gconfigs[0]\n        self.assertEqual(gconf_foo.__class__, FastCGIGroupConfig)\n        self.assertEqual(gconf_foo.name, 'foo')\n        self.assertEqual(gconf_foo.priority, 1)\n        self.assertEqual(gconf_foo.socket_config.url,\n                                'unix:///tmp/foo.sock')\n        self.assertEqual(exp_owner, gconf_foo.socket_config.get_owner())\n        self.assertEqual(0o666, gconf_foo.socket_config.get_mode())\n        self.assertEqual(32676, gconf_foo.socket_config.get_backlog())\n        self.assertEqual(len(gconf_foo.process_configs), 2)\n        pconfig_foo = gconf_foo.process_configs[0]\n        self.assertEqual(pconfig_foo.__class__, FastCGIProcessConfig)\n\n        gconf_bar = gconfigs[1]\n        self.assertEqual(gconf_bar.name, 'bar')\n        self.assertEqual(gconf_bar.priority, 999)\n        self.assertEqual(gconf_bar.socket_config.url,\n                         'unix:///tmp/bar.sock')\n        self.assertEqual(exp_owner, gconf_bar.socket_config.get_owner())\n        self.assertEqual(0o700, gconf_bar.socket_config.get_mode())\n        self.assertEqual(len(gconf_bar.process_configs), 3)\n\n        gconf_cub = gconfigs[2]\n        self.assertEqual(gconf_cub.name, 'cub')\n        self.assertEqual(gconf_cub.socket_config.url,\n                         'tcp://localhost:6000')\n        self.assertEqual(len(gconf_cub.process_configs), 1)\n\n        gconf_flub = gconfigs[3]\n        self.assertEqual(gconf_flub.name, 'flub')\n        self.assertEqual(gconf_flub.socket_config.url,\n                         'unix:///tmp/flub.sock')\n        self.assertEqual(None, gconf_flub.socket_config.get_owner())\n        self.assertEqual(0o700, gconf_flub.socket_config.get_mode())\n        self.assertEqual(len(gconf_flub.process_configs), 1)\n\n    def test_fcgi_programs_from_parser_with_environment_expansions(self):\n        from supervisor.options import FastCGIGroupConfig\n        from supervisor.options import FastCGIProcessConfig\n        text = lstrip(\"\"\"\\\n        [fcgi-program:foo]\n        socket = unix:///tmp/%(program_name)s%(ENV_FOO_SOCKET_EXT)s\n        socket_owner = %(ENV_FOO_SOCKET_USER)s:testgroup\n        socket_mode = %(ENV_FOO_SOCKET_MODE)s\n        socket_backlog = %(ENV_FOO_SOCKET_BACKLOG)s\n        process_name = %(ENV_FOO_PROCESS_PREFIX)s_%(program_name)s_%(process_num)s\n        command = /bin/foo --arg1=%(ENV_FOO_COMMAND_ARG1)s\n        numprocs = %(ENV_FOO_NUMPROCS)s\n        priority = %(ENV_FOO_PRIORITY)s\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        instance = self._makeOne()\n        instance.environ_expansions = {'ENV_HOME': '/tmp',\n                                       'ENV_SERVER_PORT': '9210',\n                                       'ENV_FOO_SOCKET_EXT': '.usock',\n                                       'ENV_FOO_SOCKET_USER': 'testuser',\n                                       'ENV_FOO_SOCKET_MODE': '0666',\n                                       'ENV_FOO_SOCKET_BACKLOG': '32676',\n                                       'ENV_FOO_PROCESS_PREFIX': 'fcgi-',\n                                       'ENV_FOO_COMMAND_ARG1': 'bar',\n                                       'ENV_FOO_NUMPROCS': '2',\n                                       'ENV_FOO_PRIORITY': '1',\n                                      }\n        config = UnhosedConfigParser()\n        config.expansions = instance.environ_expansions\n        config.read_string(text)\n\n        # Patch pwd and grp module functions to give us sentinel\n        # uid/gid values so that the test does not depend on\n        # any specific system users\n        pwd_mock = Mock()\n        pwd_mock.return_value = (None, None, sentinel.uid, sentinel.gid)\n        grp_mock = Mock()\n        grp_mock.return_value = (None, None, sentinel.gid)\n        @patch('pwd.getpwuid', pwd_mock)\n        @patch('pwd.getpwnam', pwd_mock)\n        @patch('grp.getgrnam', grp_mock)\n        def get_process_groups(instance, config):\n            return instance.process_groups_from_parser(config)\n\n        gconfigs = get_process_groups(instance, config)\n\n        exp_owner = (sentinel.uid, sentinel.gid)\n\n        self.assertEqual(len(gconfigs), 1)\n\n        gconf_foo = gconfigs[0]\n        self.assertEqual(gconf_foo.__class__, FastCGIGroupConfig)\n        self.assertEqual(gconf_foo.name, 'foo')\n        self.assertEqual(gconf_foo.priority, 1)\n        self.assertEqual(gconf_foo.socket_config.url,\n                                'unix:///tmp/foo.usock')\n        self.assertEqual(exp_owner, gconf_foo.socket_config.get_owner())\n        self.assertEqual(0o666, gconf_foo.socket_config.get_mode())\n        self.assertEqual(32676, gconf_foo.socket_config.get_backlog())\n        self.assertEqual(len(gconf_foo.process_configs), 2)\n        pconfig_foo = gconf_foo.process_configs[0]\n        self.assertEqual(pconfig_foo.__class__, FastCGIProcessConfig)\n        self.assertEqual(pconfig_foo.command, '/bin/foo --arg1=bar')\n\n    def test_fcgi_program_no_socket(self):\n        text = lstrip(\"\"\"\\\n        [fcgi-program:foo]\n        process_name = %(program_name)s_%(process_num)s\n        command = /bin/foo\n        numprocs = 2\n        priority = 1\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        instance = self._makeOne()\n        self.assertRaises(ValueError,instance.process_groups_from_parser,config)\n\n    def test_fcgi_program_unknown_socket_protocol(self):\n        text = lstrip(\"\"\"\\\n        [fcgi-program:foo]\n        socket=junk://blah\n        process_name = %(program_name)s_%(process_num)s\n        command = /bin/foo\n        numprocs = 2\n        priority = 1\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        instance = self._makeOne()\n        self.assertRaises(ValueError,instance.process_groups_from_parser,config)\n\n    def test_fcgi_program_rel_unix_sock_path(self):\n        text = lstrip(\"\"\"\\\n        [fcgi-program:foo]\n        socket=unix://relative/path\n        process_name = %(program_name)s_%(process_num)s\n        command = /bin/foo\n        numprocs = 2\n        priority = 1\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        instance = self._makeOne()\n        self.assertRaises(ValueError,instance.process_groups_from_parser,config)\n\n    def test_fcgi_program_bad_tcp_sock_format(self):\n        text = lstrip(\"\"\"\\\n        [fcgi-program:foo]\n        socket=tcp://missingport\n        process_name = %(program_name)s_%(process_num)s\n        command = /bin/foo\n        numprocs = 2\n        priority = 1\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        instance = self._makeOne()\n        self.assertRaises(ValueError,instance.process_groups_from_parser,config)\n\n    def test_fcgi_program_bad_expansion_proc_num(self):\n        text = lstrip(\"\"\"\\\n        [fcgi-program:foo]\n        socket=unix:///tmp/%(process_num)s.sock\n        process_name = %(program_name)s_%(process_num)s\n        command = /bin/foo\n        numprocs = 2\n        priority = 1\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        instance = self._makeOne()\n        self.assertRaises(ValueError,instance.process_groups_from_parser,config)\n\n    def test_fcgi_program_socket_owner_set_for_tcp(self):\n        text = lstrip(\"\"\"\\\n        [fcgi-program:foo]\n        socket=tcp://localhost:8000\n        socket_owner=nobody:nobody\n        command = /bin/foo\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        instance = self._makeOne()\n        self.assertRaises(ValueError,instance.process_groups_from_parser,config)\n\n    def test_fcgi_program_socket_mode_set_for_tcp(self):\n        text = lstrip(\"\"\"\\\n        [fcgi-program:foo]\n        socket = tcp://localhost:8000\n        socket_mode = 0777\n        command = /bin/foo\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        instance = self._makeOne()\n        self.assertRaises(ValueError,instance.process_groups_from_parser,config)\n\n    def test_fcgi_program_bad_socket_owner(self):\n        text = lstrip(\"\"\"\\\n        [fcgi-program:foo]\n        socket = unix:///tmp/foo.sock\n        socket_owner = sometotaljunkuserthatshouldnobethere\n        command = /bin/foo\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        instance = self._makeOne()\n        self.assertRaises(ValueError,instance.process_groups_from_parser,config)\n\n    def test_fcgi_program_bad_socket_mode(self):\n        text = lstrip(\"\"\"\\\n        [fcgi-program:foo]\n        socket = unix:///tmp/foo.sock\n        socket_mode = junk\n        command = /bin/foo\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        instance = self._makeOne()\n        self.assertRaises(ValueError,instance.process_groups_from_parser,config)\n\n    def test_fcgi_program_bad_socket_backlog(self):\n        text = lstrip(\"\"\"\\\n        [fcgi-program:foo]\n        socket = unix:///tmp/foo.sock\n        socket_backlog = -1\n        command = /bin/foo\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        instance = self._makeOne()\n        self.assertRaises(ValueError,instance.process_groups_from_parser,config)\n\n    def test_heterogeneous_process_groups_from_parser(self):\n        text = lstrip(\"\"\"\\\n        [program:one]\n        command = /bin/cat\n\n        [program:two]\n        command = /bin/cat\n\n        [group:thegroup]\n        programs = one,two\n        priority = 5\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        instance = self._makeOne()\n        gconfigs = instance.process_groups_from_parser(config)\n        self.assertEqual(len(gconfigs), 1)\n        gconfig = gconfigs[0]\n        self.assertEqual(gconfig.name, 'thegroup')\n        self.assertEqual(gconfig.priority, 5)\n        self.assertEqual(len(gconfig.process_configs), 2)\n\n    def test_mixed_process_groups_from_parser1(self):\n        text = lstrip(\"\"\"\\\n        [program:one]\n        command = /bin/cat\n\n        [program:two]\n        command = /bin/cat\n\n        [program:many]\n        process_name = %(program_name)s_%(process_num)s\n        command = /bin/cat\n        numprocs = 2\n        priority = 1\n\n        [group:thegroup]\n        programs = one,two\n        priority = 5\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        instance = self._makeOne()\n        gconfigs = instance.process_groups_from_parser(config)\n        self.assertEqual(len(gconfigs), 2)\n\n        manyconfig = gconfigs[0]\n        self.assertEqual(manyconfig.name, 'many')\n        self.assertEqual(manyconfig.priority, 1)\n        self.assertEqual(len(manyconfig.process_configs), 2)\n\n        gconfig = gconfigs[1]\n        self.assertEqual(gconfig.name, 'thegroup')\n        self.assertEqual(gconfig.priority, 5)\n        self.assertEqual(len(gconfig.process_configs), 2)\n\n    def test_mixed_process_groups_from_parser2(self):\n        text = lstrip(\"\"\"\\\n        [program:one]\n        command = /bin/cat\n\n        [program:two]\n        command = /bin/cat\n\n        [program:many]\n        process_name = %(program_name)s_%(process_num)s\n        command = /bin/cat\n        numprocs = 2\n        priority = 1\n\n        [group:thegroup]\n        programs = one,two, many\n        priority = 5\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        instance = self._makeOne()\n        gconfigs = instance.process_groups_from_parser(config)\n        self.assertEqual(len(gconfigs), 1)\n\n        gconfig = gconfigs[0]\n        self.assertEqual(gconfig.name, 'thegroup')\n        self.assertEqual(gconfig.priority, 5)\n        self.assertEqual(len(gconfig.process_configs), 4)\n\n    def test_mixed_process_groups_from_parser3(self):\n        text = lstrip(\"\"\"\\\n        [program:one]\n        command = /bin/cat\n\n        [fcgi-program:two]\n        command = /bin/cat\n\n        [program:many]\n        process_name = %(program_name)s_%(process_num)s\n        command = /bin/cat\n        numprocs = 2\n        priority = 1\n\n        [fcgi-program:more]\n        process_name = %(program_name)s_%(process_num)s\n        command = /bin/cat\n        numprocs = 2\n        priority = 1\n\n        [group:thegroup]\n        programs = one,two,many,more\n        priority = 5\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        instance = self._makeOne()\n        gconfigs = instance.process_groups_from_parser(config)\n        self.assertEqual(len(gconfigs), 1)\n\n        gconfig = gconfigs[0]\n        self.assertEqual(gconfig.name, 'thegroup')\n        self.assertEqual(gconfig.priority, 5)\n        self.assertEqual(len(gconfig.process_configs), 6)\n\n    def test_ambiguous_process_in_heterogeneous_group(self):\n        text = lstrip(\"\"\"\\\n        [program:one]\n        command = /bin/cat\n\n        [fcgi-program:one]\n        command = /bin/cat\n\n        [group:thegroup]\n        programs = one\"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        instance = self._makeOne()\n        self.assertRaises(ValueError, instance.process_groups_from_parser,\n                          config)\n\n    def test_unknown_program_in_heterogeneous_group(self):\n        text = lstrip(\"\"\"\\\n        [program:one]\n        command = /bin/cat\n\n        [group:foo]\n        programs = notthere\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        instance = self._makeOne()\n        self.assertRaises(ValueError, instance.process_groups_from_parser,\n                          config)\n\n    def test_rpcinterfaces_from_parser(self):\n        text = lstrip(\"\"\"\\\n        [rpcinterface:dummy]\n        supervisor.rpcinterface_factory = %s\n        foo = bar\n        baz = qux\n        \"\"\" % __name__)\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        instance = self._makeOne()\n        factories = instance.get_plugins(config,\n                                         'supervisor.rpcinterface_factory',\n                                         'rpcinterface:')\n        self.assertEqual(len(factories), 1)\n        factory = factories[0]\n        self.assertEqual(factory[0], 'dummy')\n        self.assertEqual(factory[1], sys.modules[__name__])\n        self.assertEqual(factory[2], {'foo':'bar', 'baz':'qux'})\n\n    def test_rpcinterfaces_from_parser_factory_expansions(self):\n        text = lstrip(\"\"\"\\\n        [rpcinterface:dummy]\n        supervisor.rpcinterface_factory = %(factory)s\n        foo = %(pet)s\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        instance = self._makeOne()\n        config = UnhosedConfigParser()\n        config.expansions = {'factory': __name__, 'pet': 'cat'}\n        config.read_string(text)\n        factories = instance.get_plugins(config,\n                                         'supervisor.rpcinterface_factory',\n                                         'rpcinterface:')\n        self.assertEqual(len(factories), 1)\n        factory = factories[0]\n        self.assertEqual(factory[0], 'dummy')\n        self.assertEqual(factory[1], sys.modules[__name__])\n        self.assertEqual(factory[2], {'foo': 'cat'})\n\n    def test_rpcinterfaces_from_parser_factory_missing(self):\n        text = lstrip(\"\"\"\\\n        [rpcinterface:dummy]\n        # note: no supervisor.rpcinterface_factory here\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        instance = self._makeOne()\n        try:\n            instance.get_plugins(config,\n                                 'supervisor.rpcinterface_factory',\n                                 'rpcinterface:')\n            self.fail('nothing raised')\n        except ValueError as exc:\n            self.assertEqual(exc.args[0], 'section [rpcinterface:dummy] '\n                'does not specify a supervisor.rpcinterface_factory')\n\n    def test_rpcinterfaces_from_parser_factory_not_importable(self):\n        text = lstrip(\"\"\"\\\n        [rpcinterface:dummy]\n        supervisor.rpcinterface_factory = nonexistent\n        \"\"\")\n        from supervisor.options import UnhosedConfigParser\n        config = UnhosedConfigParser()\n        config.read_string(text)\n        instance = self._makeOne()\n        try:\n            instance.get_plugins(config,\n                                 'supervisor.rpcinterface_factory',\n                                 'rpcinterface:')\n            self.fail('nothing raised')\n        except ValueError as exc:\n            possible_msgs = (\n                # py27, py34\n                \"nonexistent cannot be resolved within [rpcinterface:dummy]: \"\n                \"No module named nonexistent\",\n\n                # py35+\n                \"nonexistent cannot be resolved within [rpcinterface:dummy]: \"\n                \"No module named 'nonexistent'\",\n            )\n            self.assertTrue(exc.args[0] in possible_msgs, exc.args[0])\n\n    def test_clear_autochildlogdir(self):\n        dn = tempfile.mkdtemp()\n        try:\n            instance = self._makeOne()\n            instance.childlogdir = dn\n            sid = 'supervisor'\n            instance.identifier = sid\n            logfn = instance.get_autochildlog_name('foo', sid,'stdout')\n            first = logfn + '.1'\n            second = logfn + '.2'\n            f1 = open(first, 'w')\n            f2 = open(second, 'w')\n            instance.clear_autochildlogdir()\n            self.assertFalse(os.path.exists(logfn))\n            self.assertFalse(os.path.exists(first))\n            self.assertFalse(os.path.exists(second))\n            f1.close()\n            f2.close()\n        finally:\n            shutil.rmtree(dn, ignore_errors=True)\n\n    def test_clear_autochildlogdir_listdir_oserror(self):\n        instance = self._makeOne()\n        instance.childlogdir = '/tmp/this/cant/possibly/existjjjj'\n        instance.logger = DummyLogger()\n        instance.clear_autochildlogdir()\n        self.assertEqual(instance.logger.data, ['Could not clear childlog dir'])\n\n    def test_clear_autochildlogdir_unlink_oserror(self):\n        dirname = tempfile.mkdtemp()\n        instance = self._makeOne()\n        instance.childlogdir = dirname\n        ident = instance.identifier\n        filename = os.path.join(dirname, 'cat-stdout---%s-ayWAp9.log' % ident)\n        with open(filename, 'w') as f:\n            f.write(\"log\")\n        def raise_oserror(*args):\n            raise OSError(errno.ENOENT)\n        instance.remove = raise_oserror\n        instance.logger = DummyLogger()\n        instance.clear_autochildlogdir()\n        self.assertEqual(instance.logger.data,\n            [\"Failed to clean up '%s'\" % filename])\n\n    def test_openhttpservers_reports_friendly_usage_when_eaddrinuse(self):\n        supervisord = DummySupervisor()\n        instance = self._makeOne()\n\n        def raise_eaddrinuse(supervisord):\n            raise socket.error(errno.EADDRINUSE)\n        instance.make_http_servers = raise_eaddrinuse\n\n        recorder = []\n        def record_usage(message):\n            recorder.append(message)\n        instance.usage = record_usage\n\n        instance.openhttpservers(supervisord)\n        self.assertEqual(len(recorder), 1)\n        expected = 'Another program is already listening'\n        self.assertTrue(recorder[0].startswith(expected))\n\n    def test_openhttpservers_reports_socket_error_with_errno(self):\n        supervisord = DummySupervisor()\n        instance = self._makeOne()\n\n        def make_http_servers(supervisord):\n            raise socket.error(errno.EPERM)\n        instance.make_http_servers = make_http_servers\n\n        recorder = []\n        def record_usage(message):\n            recorder.append(message)\n        instance.usage = record_usage\n\n        instance.openhttpservers(supervisord)\n        self.assertEqual(len(recorder), 1)\n        expected = ('Cannot open an HTTP server: socket.error '\n                    'reported errno.EPERM (%d)' % errno.EPERM)\n        self.assertEqual(recorder[0], expected)\n\n    def test_openhttpservers_reports_other_socket_errors(self):\n        supervisord = DummySupervisor()\n        instance = self._makeOne()\n\n        def make_http_servers(supervisord):\n            raise socket.error('uh oh')\n        instance.make_http_servers = make_http_servers\n\n        recorder = []\n        def record_usage(message):\n            recorder.append(message)\n        instance.usage = record_usage\n\n        instance.openhttpservers(supervisord)\n        self.assertEqual(len(recorder), 1)\n        expected = ('Cannot open an HTTP server: socket.error '\n                    'reported uh oh')\n        self.assertEqual(recorder[0], expected)\n\n    def test_openhttpservers_reports_value_errors(self):\n        supervisord = DummySupervisor()\n        instance = self._makeOne()\n\n        def make_http_servers(supervisord):\n            raise ValueError('not prefixed with help')\n        instance.make_http_servers = make_http_servers\n\n        recorder = []\n        def record_usage(message):\n            recorder.append(message)\n        instance.usage = record_usage\n\n        instance.openhttpservers(supervisord)\n        self.assertEqual(len(recorder), 1)\n        expected = 'not prefixed with help'\n        self.assertEqual(recorder[0], expected)\n\n    def test_openhttpservers_does_not_catch_other_exception_types(self):\n        supervisord = DummySupervisor()\n        instance = self._makeOne()\n\n        def make_http_servers(supervisord):\n            raise OverflowError\n        instance.make_http_servers = make_http_servers\n\n        # this scenario probably means a bug in supervisor.  we dump\n        # all the gory details on the poor user for troubleshooting\n        self.assertRaises(OverflowError,\n                          instance.openhttpservers, supervisord)\n\n    def test_drop_privileges_user_none(self):\n        instance = self._makeOne()\n        msg = instance.drop_privileges(None)\n        self.assertEqual(msg, \"No user specified to setuid to!\")\n\n    @patch('pwd.getpwuid', Mock(return_value=[\"foo\", None, 12, 34]))\n    @patch('os.getuid', Mock(return_value=12))\n    def test_drop_privileges_nonroot_same_user(self):\n        instance = self._makeOne()\n        msg = instance.drop_privileges(os.getuid())\n        self.assertEqual(msg, None) # no error if same user\n\n    @patch('pwd.getpwuid', Mock(return_value=[\"foo\", None, 55, 34]))\n    @patch('os.getuid', Mock(return_value=12))\n    def test_drop_privileges_nonroot_different_user(self):\n        instance = self._makeOne()\n        msg = instance.drop_privileges(42)\n        self.assertEqual(msg, \"Can't drop privilege as nonroot user\")\n\n    def test_daemonize_notifies_poller_before_and_after_fork(self):\n        instance = self._makeOne()\n        instance._daemonize = lambda: None\n        instance.poller = Mock()\n        instance.daemonize()\n        instance.poller.before_daemonize.assert_called_once_with()\n        instance.poller.after_daemonize.assert_called_once_with()\n\n    def test_options_environment_of_supervisord_with_escaped_chars(self):\n        text = lstrip(\"\"\"\n        [supervisord]\n        environment=VAR_WITH_P=\"some_value_%%_end\"\n        \"\"\")\n\n        instance = self._makeOne()\n        instance.configfile = StringIO(text)\n        instance.realize(args=[])\n        options = instance.configroot.supervisord\n        self.assertEqual(options.environment, dict(VAR_WITH_P=\"some_value_%_end\"))\n\n\nclass ProcessConfigTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.options import ProcessConfig\n        return ProcessConfig\n\n    def _makeOne(self, *arg, **kw):\n        defaults = {}\n        for name in ('name', 'command', 'directory', 'umask',\n                     'priority', 'autostart', 'autorestart',\n                     'startsecs', 'startretries', 'uid',\n                     'stdout_logfile', 'stdout_capture_maxbytes',\n                     'stdout_events_enabled', 'stdout_syslog',\n                     'stderr_logfile', 'stderr_capture_maxbytes',\n                     'stderr_events_enabled', 'stderr_syslog',\n                     'stopsignal', 'stopwaitsecs', 'stopasgroup',\n                     'killasgroup', 'exitcodes', 'redirect_stderr',\n                     'environment'):\n            defaults[name] = name\n        for name in ('stdout_logfile_backups', 'stdout_logfile_maxbytes',\n                     'stderr_logfile_backups', 'stderr_logfile_maxbytes'):\n            defaults[name] = 10\n        defaults.update(kw)\n        return self._getTargetClass()(*arg, **defaults)\n\n    def test_get_path_env_is_None_delegates_to_options(self):\n        options = DummyOptions()\n        instance = self._makeOne(options, environment=None)\n        self.assertEqual(instance.get_path(), options.get_path())\n\n    def test_get_path_env_dict_with_no_PATH_delegates_to_options(self):\n        options = DummyOptions()\n        instance = self._makeOne(options, environment={'FOO': '1'})\n        self.assertEqual(instance.get_path(), options.get_path())\n\n    def test_get_path_env_dict_with_PATH_uses_it(self):\n        options = DummyOptions()\n        instance = self._makeOne(options, environment={'PATH': '/a:/b:/c'})\n        self.assertNotEqual(instance.get_path(), options.get_path())\n        self.assertEqual(instance.get_path(), ['/a', '/b', '/c'])\n\n    def test_create_autochildlogs(self):\n        options = DummyOptions()\n        instance = self._makeOne(options)\n        from supervisor.datatypes import Automatic\n        instance.stdout_logfile = Automatic\n        instance.stderr_logfile = Automatic\n        instance.create_autochildlogs()\n        self.assertEqual(instance.stdout_logfile, options.tempfile_name)\n        self.assertEqual(instance.stderr_logfile, options.tempfile_name)\n\n    def test_make_process(self):\n        options = DummyOptions()\n        instance = self._makeOne(options)\n        process = instance.make_process()\n        from supervisor.process import Subprocess\n        self.assertEqual(process.__class__, Subprocess)\n        self.assertEqual(process.group, None)\n\n    def test_make_process_with_group(self):\n        options = DummyOptions()\n        instance = self._makeOne(options)\n        process = instance.make_process('abc')\n        from supervisor.process import Subprocess\n        self.assertEqual(process.__class__, Subprocess)\n        self.assertEqual(process.group, 'abc')\n\n    def test_make_dispatchers_stderr_not_redirected(self):\n        options = DummyOptions()\n        instance = self._makeOne(options)\n        with tempfile.NamedTemporaryFile() as stdout_logfile:\n            with tempfile.NamedTemporaryFile() as stderr_logfile:\n                instance.stdout_logfile = stdout_logfile.name\n                instance.stderr_logfile = stderr_logfile.name\n                instance.redirect_stderr = False\n                process1 = DummyProcess(instance)\n                dispatchers, pipes = instance.make_dispatchers(process1)\n                self.assertEqual(dispatchers[5].channel, 'stdout')\n                from supervisor.events import ProcessCommunicationStdoutEvent\n                self.assertEqual(dispatchers[5].event_type,\n                                 ProcessCommunicationStdoutEvent)\n                self.assertEqual(pipes['stdout'], 5)\n                self.assertEqual(dispatchers[7].channel, 'stderr')\n                from supervisor.events import ProcessCommunicationStderrEvent\n                self.assertEqual(dispatchers[7].event_type,\n                                 ProcessCommunicationStderrEvent)\n                self.assertEqual(pipes['stderr'], 7)\n\n    def test_make_dispatchers_stderr_redirected(self):\n        options = DummyOptions()\n        instance = self._makeOne(options)\n        with tempfile.NamedTemporaryFile() as stdout_logfile:\n            instance.stdout_logfile = stdout_logfile.name\n            process1 = DummyProcess(instance)\n            dispatchers, pipes = instance.make_dispatchers(process1)\n            self.assertEqual(dispatchers[5].channel, 'stdout')\n            self.assertEqual(pipes['stdout'], 5)\n            self.assertEqual(pipes['stderr'], None)\n\nclass EventListenerConfigTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.options import EventListenerConfig\n        return EventListenerConfig\n\n    def _makeOne(self, *arg, **kw):\n        defaults = {}\n        for name in ('name', 'command', 'directory', 'umask',\n                     'priority', 'autostart', 'autorestart',\n                     'startsecs', 'startretries', 'uid',\n                     'stdout_logfile', 'stdout_capture_maxbytes',\n                     'stdout_events_enabled', 'stdout_syslog',\n                     'stderr_logfile', 'stderr_capture_maxbytes',\n                     'stderr_events_enabled', 'stderr_syslog',\n                     'stopsignal', 'stopwaitsecs', 'stopasgroup',\n                     'killasgroup', 'exitcodes', 'redirect_stderr',\n                     'environment'):\n            defaults[name] = name\n        for name in ('stdout_logfile_backups', 'stdout_logfile_maxbytes',\n                     'stderr_logfile_backups', 'stderr_logfile_maxbytes'):\n            defaults[name] = 10\n        defaults.update(kw)\n        return self._getTargetClass()(*arg, **defaults)\n\n    def test_make_dispatchers(self):\n        options = DummyOptions()\n        instance = self._makeOne(options)\n        with tempfile.NamedTemporaryFile() as stdout_logfile:\n            with tempfile.NamedTemporaryFile() as stderr_logfile:\n                instance.stdout_logfile = stdout_logfile.name\n                instance.stderr_logfile = stderr_logfile.name\n                instance.redirect_stderr = False\n                process1 = DummyProcess(instance)\n                dispatchers, pipes = instance.make_dispatchers(process1)\n                self.assertEqual(dispatchers[4].channel, 'stdin')\n                self.assertEqual(dispatchers[4].closed, False)\n                self.assertEqual(dispatchers[5].channel, 'stdout')\n                from supervisor.states import EventListenerStates\n                self.assertEqual(dispatchers[5].process.listener_state,\n                                 EventListenerStates.ACKNOWLEDGED)\n                self.assertEqual(pipes['stdout'], 5)\n                self.assertEqual(dispatchers[7].channel, 'stderr')\n                from supervisor.events import ProcessCommunicationStderrEvent\n                self.assertEqual(dispatchers[7].event_type,\n                                 ProcessCommunicationStderrEvent)\n                self.assertEqual(pipes['stderr'], 7)\n\n\nclass FastCGIProcessConfigTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.options import FastCGIProcessConfig\n        return FastCGIProcessConfig\n\n    def _makeOne(self, *arg, **kw):\n        defaults = {}\n        for name in ('name', 'command', 'directory', 'umask',\n                     'priority', 'autostart', 'autorestart',\n                     'startsecs', 'startretries', 'uid',\n                     'stdout_logfile', 'stdout_capture_maxbytes',\n                     'stdout_events_enabled', 'stdout_syslog',\n                     'stderr_logfile', 'stderr_capture_maxbytes',\n                     'stderr_events_enabled', 'stderr_syslog',\n                     'stopsignal', 'stopwaitsecs', 'stopasgroup',\n                     'killasgroup', 'exitcodes', 'redirect_stderr',\n                     'environment'):\n            defaults[name] = name\n        for name in ('stdout_logfile_backups', 'stdout_logfile_maxbytes',\n                     'stderr_logfile_backups', 'stderr_logfile_maxbytes'):\n            defaults[name] = 10\n        defaults.update(kw)\n        return self._getTargetClass()(*arg, **defaults)\n\n    def test_make_process(self):\n        options = DummyOptions()\n        instance = self._makeOne(options)\n        self.assertRaises(NotImplementedError, instance.make_process)\n\n    def test_make_process_with_group(self):\n        options = DummyOptions()\n        instance = self._makeOne(options)\n        process = instance.make_process('abc')\n        from supervisor.process import FastCGISubprocess\n        self.assertEqual(process.__class__, FastCGISubprocess)\n        self.assertEqual(process.group, 'abc')\n\n    def test_make_dispatchers(self):\n        options = DummyOptions()\n        instance = self._makeOne(options)\n        with tempfile.NamedTemporaryFile() as stdout_logfile:\n            with tempfile.NamedTemporaryFile() as stderr_logfile:\n                instance.stdout_logfile = stdout_logfile.name\n                instance.stderr_logfile = stderr_logfile.name\n                instance.redirect_stderr = False\n                process1 = DummyProcess(instance)\n                dispatchers, pipes = instance.make_dispatchers(process1)\n                self.assertEqual(dispatchers[4].channel, 'stdin')\n                self.assertEqual(dispatchers[4].closed, True)\n                self.assertEqual(dispatchers[5].channel, 'stdout')\n                from supervisor.events import ProcessCommunicationStdoutEvent\n                self.assertEqual(dispatchers[5].event_type,\n                                 ProcessCommunicationStdoutEvent)\n                self.assertEqual(pipes['stdout'], 5)\n                self.assertEqual(dispatchers[7].channel, 'stderr')\n                from supervisor.events import ProcessCommunicationStderrEvent\n                self.assertEqual(dispatchers[7].event_type,\n                                 ProcessCommunicationStderrEvent)\n                self.assertEqual(pipes['stderr'], 7)\n\nclass ProcessGroupConfigTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.options import ProcessGroupConfig\n        return ProcessGroupConfig\n\n    def _makeOne(self, options, name, priority, pconfigs):\n        return self._getTargetClass()(options, name, priority, pconfigs)\n\n    def test_ctor(self):\n        options = DummyOptions()\n        instance = self._makeOne(options, 'whatever', 999, [])\n        self.assertEqual(instance.options, options)\n        self.assertEqual(instance.name, 'whatever')\n        self.assertEqual(instance.priority, 999)\n        self.assertEqual(instance.process_configs, [])\n\n    def test_after_setuid(self):\n        options = DummyOptions()\n        pconfigs = [DummyPConfig(options, 'process1', '/bin/process1')]\n        instance = self._makeOne(options, 'whatever', 999, pconfigs)\n        instance.after_setuid()\n        self.assertEqual(pconfigs[0].autochildlogs_created, True)\n\n    def test_make_group(self):\n        options = DummyOptions()\n        pconfigs = [DummyPConfig(options, 'process1', '/bin/process1')]\n        instance = self._makeOne(options, 'whatever', 999, pconfigs)\n        group = instance.make_group()\n        from supervisor.process import ProcessGroup\n        self.assertEqual(group.__class__, ProcessGroup)\n\nclass EventListenerPoolConfigTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.options import EventListenerPoolConfig\n        return EventListenerPoolConfig\n\n    def _makeOne(self, options, name, priority, process_configs, buffer_size,\n                 pool_events, result_handler):\n        return self._getTargetClass()(options, name, priority,\n                                      process_configs, buffer_size,\n                                      pool_events, result_handler)\n\n    def test_after_setuid(self):\n        options = DummyOptions()\n        pconfigs = [DummyPConfig(options, 'process1', '/bin/process1')]\n        instance = self._makeOne(options, 'name', 999, pconfigs, 1, [], None)\n        instance.after_setuid()\n        self.assertEqual(pconfigs[0].autochildlogs_created, True)\n\n    def test_make_group(self):\n        options = DummyOptions()\n        pconfigs = [DummyPConfig(options, 'process1', '/bin/process1')]\n        instance = self._makeOne(options, 'name', 999, pconfigs, 1, [], None)\n        group = instance.make_group()\n        from supervisor.process import EventListenerPool\n        self.assertEqual(group.__class__, EventListenerPool)\n\nclass FastCGIGroupConfigTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.options import FastCGIGroupConfig\n        return FastCGIGroupConfig\n\n    def _makeOne(self, *args, **kw):\n        return self._getTargetClass()(*args, **kw)\n\n    def test_ctor(self):\n        options = DummyOptions()\n        sock_config = DummySocketConfig(6)\n        instance = self._makeOne(options, 'whatever', 999, [], sock_config)\n        self.assertEqual(instance.options, options)\n        self.assertEqual(instance.name, 'whatever')\n        self.assertEqual(instance.priority, 999)\n        self.assertEqual(instance.process_configs, [])\n        self.assertEqual(instance.socket_config, sock_config)\n\n    def test_same_sockets_are_equal(self):\n        options = DummyOptions()\n        sock_config1 = DummySocketConfig(6)\n        instance1 = self._makeOne(options, 'whatever', 999, [], sock_config1)\n\n        sock_config2 = DummySocketConfig(6)\n        instance2 = self._makeOne(options, 'whatever', 999, [], sock_config2)\n\n        self.assertTrue(instance1 == instance2)\n        self.assertFalse(instance1 != instance2)\n\n    def test_diff_sockets_are_not_equal(self):\n        options = DummyOptions()\n        sock_config1 = DummySocketConfig(6)\n        instance1 = self._makeOne(options, 'whatever', 999, [], sock_config1)\n\n        sock_config2 = DummySocketConfig(7)\n        instance2 = self._makeOne(options, 'whatever', 999, [], sock_config2)\n\n        self.assertTrue(instance1 != instance2)\n        self.assertFalse(instance1 == instance2)\n\n    def test_make_group(self):\n        options = DummyOptions()\n        sock_config = DummySocketConfig(6)\n        instance = self._makeOne(options, 'name', 999, [], sock_config)\n        group = instance.make_group()\n        from supervisor.process import FastCGIProcessGroup\n        self.assertEqual(group.__class__, FastCGIProcessGroup)\n\nclass SignalReceiverTests(unittest.TestCase):\n    def test_returns_None_initially(self):\n        from supervisor.options import SignalReceiver\n        sr = SignalReceiver()\n        self.assertEqual(sr.get_signal(), None)\n\n    def test_returns_signals_in_order_received(self):\n        from supervisor.options import SignalReceiver\n        sr = SignalReceiver()\n        sr.receive(signal.SIGTERM, 'frame')\n        sr.receive(signal.SIGCHLD, 'frame')\n        self.assertEqual(sr.get_signal(), signal.SIGTERM)\n        self.assertEqual(sr.get_signal(), signal.SIGCHLD)\n        self.assertEqual(sr.get_signal(), None)\n\n    def test_does_not_queue_duplicate_signals(self):\n        from supervisor.options import SignalReceiver\n        sr = SignalReceiver()\n        sr.receive(signal.SIGTERM, 'frame')\n        sr.receive(signal.SIGTERM, 'frame')\n        self.assertEqual(sr.get_signal(), signal.SIGTERM)\n        self.assertEqual(sr.get_signal(), None)\n\n    def test_queues_again_after_being_emptied(self):\n        from supervisor.options import SignalReceiver\n        sr = SignalReceiver()\n        sr.receive(signal.SIGTERM, 'frame')\n        self.assertEqual(sr.get_signal(), signal.SIGTERM)\n        self.assertEqual(sr.get_signal(), None)\n        sr.receive(signal.SIGCHLD, 'frame')\n        self.assertEqual(sr.get_signal(), signal.SIGCHLD)\n        self.assertEqual(sr.get_signal(), None)\n\nclass UnhosedConfigParserTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.options import UnhosedConfigParser\n        return UnhosedConfigParser\n\n    def _makeOne(self, *args, **kw):\n        return self._getTargetClass()(*args, **kw)\n\n    def test_saneget_no_default(self):\n        parser = self._makeOne()\n        parser.read_string(\"[supervisord]\\n\")\n        from supervisor.compat import ConfigParser\n        self.assertRaises(ConfigParser.NoOptionError,\n            parser.saneget, \"supervisord\", \"missing\")\n\n    def test_saneget_with_default(self):\n        parser = self._makeOne()\n        parser.read_string(\"[supervisord]\\n\")\n        result = parser.saneget(\"supervisord\", \"missing\", default=\"abc\")\n        self.assertEqual(result, \"abc\")\n\n    def test_saneget_with_default_and_expand(self):\n        parser = self._makeOne()\n        parser.expansions = {'pet': 'dog'}\n        parser.read_string(\"[supervisord]\\n\")\n        result = parser.saneget(\"supervisord\", \"foo\", default=\"%(pet)s\")\n        self.assertEqual(result, \"dog\")\n\n    def test_saneget_with_default_no_expand(self):\n        parser = self._makeOne()\n        parser.expansions = {'pet': 'dog'}\n        parser.read_string(\"[supervisord]\\n\")\n        result = parser.saneget(\"supervisord\", \"foo\",\n            default=\"%(pet)s\", do_expand=False)\n        self.assertEqual(result, \"%(pet)s\")\n\n    def test_saneget_no_default_no_expand(self):\n        parser = self._makeOne()\n        parser.read_string(\"[supervisord]\\nfoo=%(pet)s\\n\")\n        result = parser.saneget(\"supervisord\", \"foo\", do_expand=False)\n        self.assertEqual(result, \"%(pet)s\")\n\n    def test_saneget_expands_instance_expansions(self):\n        parser = self._makeOne()\n        parser.expansions = {'pet': 'dog'}\n        parser.read_string(\"[supervisord]\\nfoo=%(pet)s\\n\")\n        result = parser.saneget(\"supervisord\", \"foo\")\n        self.assertEqual(result, \"dog\")\n\n    def test_saneget_expands_arg_expansions(self):\n        parser = self._makeOne()\n        parser.expansions = {'pet': 'dog'}\n        parser.read_string(\"[supervisord]\\nfoo=%(pet)s\\n\")\n        result = parser.saneget(\"supervisord\", \"foo\",\n            expansions={'pet': 'cat'})\n        self.assertEqual(result, \"cat\")\n\n    def test_getdefault_does_saneget_with_mysection(self):\n        parser = self._makeOne()\n        parser.read_string(\"[%s]\\nfoo=bar\\n\" % parser.mysection)\n        self.assertEqual(parser.getdefault(\"foo\"), \"bar\")\n\n    def test_read_filenames_as_string(self):\n        parser = self._makeOne()\n        with tempfile.NamedTemporaryFile(mode=\"w+\") as f:\n            f.write(\"[foo]\\n\")\n            f.flush()\n            ok_filenames = parser.read(f.name)\n        self.assertEqual(ok_filenames, [f.name])\n\n    def test_read_filenames_as_list(self):\n        parser = self._makeOne()\n        with tempfile.NamedTemporaryFile(mode=\"w+\") as f:\n            f.write(\"[foo]\\n\")\n            f.flush()\n            ok_filenames = parser.read([f.name])\n        self.assertEqual(ok_filenames, [f.name])\n\n    def test_read_returns_ok_filenames_like_rawconfigparser(self):\n        nonexistent = os.path.join(os.path.dirname(__file__), \"nonexistent\")\n        parser = self._makeOne()\n        with tempfile.NamedTemporaryFile(mode=\"w+\") as f:\n            f.write(\"[foo]\\n\")\n            f.flush()\n            ok_filenames = parser.read([nonexistent, f.name])\n        self.assertEqual(ok_filenames, [f.name])\n\n    def test_read_section_to_file_initially_empty(self):\n        parser = self._makeOne()\n        self.assertEqual(parser.section_to_file, {})\n\n    def test_read_section_to_file_read_one_file(self):\n        parser = self._makeOne()\n        with tempfile.NamedTemporaryFile(mode=\"w+\") as f:\n            f.write(\"[foo]\\n\")\n            f.flush()\n            parser.read([f.name])\n        self.assertEqual(parser.section_to_file['foo'], f.name)\n\n    def test_read_section_to_file_read_multiple_files(self):\n        parser = self._makeOne()\n        with tempfile.NamedTemporaryFile(mode=\"w+\") as f1:\n            with tempfile.NamedTemporaryFile(mode=\"w+\") as f2:\n                f1.write(\"[foo]\\n\")\n                f1.flush()\n                f2.write(\"[bar]\\n\")\n                f2.flush()\n                parser.read([f1.name, f2.name])\n        self.assertEqual(parser.section_to_file['foo'], f1.name)\n        self.assertEqual(parser.section_to_file['bar'], f2.name)\n\nclass UtilFunctionsTests(unittest.TestCase):\n    def test_make_namespec(self):\n        from supervisor.options import make_namespec\n        self.assertEqual(make_namespec('group', 'process'), 'group:process')\n        self.assertEqual(make_namespec('process', 'process'), 'process')\n\n    def test_split_namespec(self):\n        from supervisor.options import split_namespec\n        s = split_namespec\n        self.assertEqual(s('process:group'), ('process', 'group'))\n        self.assertEqual(s('process'), ('process', 'process'))\n        self.assertEqual(s('group:'), ('group', None))\n        self.assertEqual(s('group:*'), ('group', None))\n"
  },
  {
    "path": "supervisor/tests/test_pidproxy.py",
    "content": "import os\nimport unittest\n\nclass PidProxyTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.pidproxy import PidProxy\n        return PidProxy\n\n    def _makeOne(self, *arg, **kw):\n        return self._getTargetClass()(*arg, **kw)\n\n    def test_ctor_parses_args(self):\n        args = [\"pidproxy.py\", \"/path/to/pidfile\", \"./cmd\", \"-arg1\", \"-arg2\"]\n        pp = self._makeOne(args)\n        self.assertEqual(pp.pidfile, \"/path/to/pidfile\")\n        self.assertEqual(pp.abscmd, os.path.abspath(\"./cmd\"))\n        self.assertEqual(pp.cmdargs, [\"./cmd\", \"-arg1\", \"-arg2\"])\n"
  },
  {
    "path": "supervisor/tests/test_poller.py",
    "content": "import unittest\nimport errno\nimport select\nfrom supervisor.tests.base import Mock\n\nfrom supervisor.poller import SelectPoller, PollPoller, KQueuePoller\nfrom supervisor.poller import implements_poll, implements_kqueue\nfrom supervisor.tests.base import DummyOptions\n\n# this base class is used instead of unittest.TestCase to hide\n# a TestCase subclass from test runner when the implementation is\n# not available\nSkipTestCase = object\n\nclass BasePollerTests(unittest.TestCase):\n    def _makeOne(self, options):\n        from supervisor.poller import BasePoller\n        return BasePoller(options)\n\n    def test_register_readable(self):\n        inst = self._makeOne(None)\n        self.assertRaises(NotImplementedError, inst.register_readable, None)\n\n    def test_register_writable(self):\n        inst = self._makeOne(None)\n        self.assertRaises(NotImplementedError, inst.register_writable, None)\n\n    def test_unregister_readable(self):\n        inst = self._makeOne(None)\n        self.assertRaises(NotImplementedError, inst.unregister_readable, None)\n\n    def test_unregister_writable(self):\n        inst = self._makeOne(None)\n        self.assertRaises(NotImplementedError, inst.unregister_writable, None)\n\n    def test_poll(self):\n        inst = self._makeOne(None)\n        self.assertRaises(NotImplementedError, inst.poll, None)\n\n    def test_before_daemonize(self):\n        inst = self._makeOne(None)\n        self.assertEqual(inst.before_daemonize(), None)\n\n    def test_after_daemonize(self):\n        inst = self._makeOne(None)\n        self.assertEqual(inst.after_daemonize(), None)\n\nclass SelectPollerTests(unittest.TestCase):\n\n    def _makeOne(self, options):\n        return SelectPoller(options)\n\n    def test_register_readable(self):\n        poller = self._makeOne(DummyOptions())\n        poller.register_readable(6)\n        poller.register_readable(7)\n        self.assertEqual(sorted(poller.readables), [6,7])\n\n    def test_register_writable(self):\n        poller = self._makeOne(DummyOptions())\n        poller.register_writable(6)\n        poller.register_writable(7)\n        self.assertEqual(sorted(poller.writables), [6,7])\n\n    def test_unregister_readable(self):\n        poller = self._makeOne(DummyOptions())\n        poller.register_readable(6)\n        poller.register_readable(7)\n        poller.register_writable(8)\n        poller.register_writable(9)\n        poller.unregister_readable(6)\n        poller.unregister_readable(9)\n        poller.unregister_readable(100)  # not registered, ignore error\n        self.assertEqual(list(poller.readables), [7])\n        self.assertEqual(list(poller.writables), [8, 9])\n\n    def test_unregister_writable(self):\n        poller = self._makeOne(DummyOptions())\n        poller.register_readable(6)\n        poller.register_readable(7)\n        poller.register_writable(8)\n        poller.register_writable(6)\n        poller.unregister_writable(7)\n        poller.unregister_writable(6)\n        poller.unregister_writable(100)  # not registered, ignore error\n        self.assertEqual(list(poller.readables), [6, 7])\n        self.assertEqual(list(poller.writables), [8])\n\n    def test_poll_returns_readables_and_writables(self):\n        _select = DummySelect(result={'readables': [6],\n                                      'writables': [8]})\n        poller = self._makeOne(DummyOptions())\n        poller._select = _select\n        poller.register_readable(6)\n        poller.register_readable(7)\n        poller.register_writable(8)\n        readables, writables = poller.poll(1)\n        self.assertEqual(readables, [6])\n        self.assertEqual(writables, [8])\n\n    def test_poll_ignores_eintr(self):\n        _select = DummySelect(error=errno.EINTR)\n        options = DummyOptions()\n        poller = self._makeOne(options)\n        poller._select = _select\n        poller.register_readable(6)\n        poller.poll(1)\n        self.assertEqual(options.logger.data[0], 'EINTR encountered in poll')\n\n    def test_poll_ignores_ebadf(self):\n        _select = DummySelect(error=errno.EBADF)\n        options = DummyOptions()\n        poller = self._makeOne(options)\n        poller._select = _select\n        poller.register_readable(6)\n        poller.poll(1)\n        self.assertEqual(options.logger.data[0], 'EBADF encountered in poll')\n        self.assertEqual(list(poller.readables), [])\n        self.assertEqual(list(poller.writables), [])\n\n    def test_poll_uncaught_exception(self):\n        _select = DummySelect(error=errno.EPERM)\n        options = DummyOptions()\n        poller = self._makeOne(options)\n        poller._select = _select\n        poller.register_readable(6)\n        self.assertRaises(select.error, poller.poll, 1)\n\nif implements_kqueue():\n    KQueuePollerTestsBase = unittest.TestCase\nelse:\n    KQueuePollerTestsBase = SkipTestCase\n\nclass KQueuePollerTests(KQueuePollerTestsBase):\n\n    def _makeOne(self, options):\n        return KQueuePoller(options)\n\n    def test_register_readable(self):\n        kqueue = DummyKQueue()\n        poller = self._makeOne(DummyOptions())\n        poller._kqueue = kqueue\n        poller.register_readable(6)\n        self.assertEqual(list(poller.readables), [6])\n        self.assertEqual(len(kqueue.registered_kevents), 1)\n        self.assertReadEventAdded(kqueue.registered_kevents[0], 6)\n\n    def test_register_writable(self):\n        kqueue = DummyKQueue()\n        poller = self._makeOne(DummyOptions())\n        poller._kqueue = kqueue\n        poller.register_writable(7)\n        self.assertEqual(list(poller.writables), [7])\n        self.assertEqual(len(kqueue.registered_kevents), 1)\n        self.assertWriteEventAdded(kqueue.registered_kevents[0], 7)\n\n    def test_unregister_readable(self):\n        kqueue = DummyKQueue()\n        poller = self._makeOne(DummyOptions())\n        poller._kqueue = kqueue\n        poller.register_writable(7)\n        poller.register_readable(8)\n        poller.unregister_readable(7)\n        poller.unregister_readable(8)\n        poller.unregister_readable(100)  # not registered, ignore error\n        self.assertEqual(list(poller.writables), [7])\n        self.assertEqual(list(poller.readables), [])\n        self.assertWriteEventAdded(kqueue.registered_kevents[0], 7)\n        self.assertReadEventAdded(kqueue.registered_kevents[1], 8)\n        self.assertReadEventDeleted(kqueue.registered_kevents[2], 7)\n        self.assertReadEventDeleted(kqueue.registered_kevents[3], 8)\n\n    def test_unregister_writable(self):\n        kqueue = DummyKQueue()\n        poller = self._makeOne(DummyOptions())\n        poller._kqueue = kqueue\n        poller.register_writable(7)\n        poller.register_readable(8)\n        poller.unregister_writable(7)\n        poller.unregister_writable(8)\n        poller.unregister_writable(100)  # not registered, ignore error\n        self.assertEqual(list(poller.writables), [])\n        self.assertEqual(list(poller.readables), [8])\n        self.assertWriteEventAdded(kqueue.registered_kevents[0], 7)\n        self.assertReadEventAdded(kqueue.registered_kevents[1], 8)\n        self.assertWriteEventDeleted(kqueue.registered_kevents[2], 7)\n        self.assertWriteEventDeleted(kqueue.registered_kevents[3], 8)\n\n    def test_poll_returns_readables_and_writables(self):\n        kqueue = DummyKQueue(result=[(6, select.KQ_FILTER_READ),\n                                     (7, select.KQ_FILTER_READ),\n                                     (8, select.KQ_FILTER_WRITE)])\n        poller = self._makeOne(DummyOptions())\n        poller._kqueue = kqueue\n        poller.register_readable(6)\n        poller.register_readable(7)\n        poller.register_writable(8)\n        readables, writables = poller.poll(1000)\n        self.assertEqual(readables, [6,7])\n        self.assertEqual(writables, [8])\n\n    def test_poll_ignores_eintr(self):\n        kqueue = DummyKQueue(raise_errno_poll=errno.EINTR)\n        options = DummyOptions()\n        poller = self._makeOne(options)\n        poller._kqueue = kqueue\n        poller.register_readable(6)\n        poller.poll(1000)\n        self.assertEqual(options.logger.data[0], 'EINTR encountered in poll')\n\n    def test_register_readable_and_writable_ignores_ebadf(self):\n        _kqueue = DummyKQueue(raise_errno_register=errno.EBADF)\n        options = DummyOptions()\n        poller = self._makeOne(options)\n        poller._kqueue = _kqueue\n        poller.register_readable(6)\n        poller.register_writable(7)\n        self.assertEqual(options.logger.data[0],\n                         'EBADF encountered in kqueue. Invalid file descriptor 6')\n        self.assertEqual(options.logger.data[1],\n                         'EBADF encountered in kqueue. Invalid file descriptor 7')\n\n    def test_register_uncaught_exception(self):\n        _kqueue = DummyKQueue(raise_errno_register=errno.ENOMEM)\n        options = DummyOptions()\n        poller = self._makeOne(options)\n        poller._kqueue = _kqueue\n        self.assertRaises(OSError, poller.register_readable, 5)\n\n    def test_poll_uncaught_exception(self):\n        kqueue = DummyKQueue(raise_errno_poll=errno.EINVAL)\n        options = DummyOptions()\n        poller = self._makeOne(options)\n        poller._kqueue = kqueue\n        poller.register_readable(6)\n        self.assertRaises(OSError, poller.poll, 1000)\n\n    def test_before_daemonize_closes_kqueue(self):\n        mock_kqueue = Mock()\n        options = DummyOptions()\n        poller = self._makeOne(options)\n        poller._kqueue = mock_kqueue\n        poller.before_daemonize()\n        mock_kqueue.close.assert_called_once_with()\n        self.assertEqual(poller._kqueue, None)\n\n    def test_after_daemonize_restores_kqueue(self):\n        options = DummyOptions()\n        poller = self._makeOne(options)\n        poller.readables = [1]\n        poller.writables = [3]\n        poller.register_readable = Mock()\n        poller.register_writable = Mock()\n        poller.after_daemonize()\n        self.assertTrue(isinstance(poller._kqueue, select.kqueue))\n        poller.register_readable.assert_called_with(1)\n        poller.register_writable.assert_called_with(3)\n\n    def test_close_closes_kqueue(self):\n        mock_kqueue = Mock()\n        options = DummyOptions()\n        poller = self._makeOne(options)\n        poller._kqueue = mock_kqueue\n        poller.close()\n        mock_kqueue.close.assert_called_once_with()\n        self.assertEqual(poller._kqueue, None)\n\n    def assertReadEventAdded(self, kevent, fd):\n        self.assertKevent(kevent, fd, select.KQ_FILTER_READ, select.KQ_EV_ADD)\n\n    def assertWriteEventAdded(self, kevent, fd):\n        self.assertKevent(kevent, fd, select.KQ_FILTER_WRITE, select.KQ_EV_ADD)\n\n    def assertReadEventDeleted(self, kevent, fd):\n        self.assertKevent(kevent, fd, select.KQ_FILTER_READ, select.KQ_EV_DELETE)\n\n    def assertWriteEventDeleted(self, kevent, fd):\n        self.assertKevent(kevent, fd, select.KQ_FILTER_WRITE, select.KQ_EV_DELETE)\n\n    def assertKevent(self, kevent, ident, filter, flags):\n        self.assertEqual(kevent.ident, ident)\n        self.assertEqual(kevent.filter, filter)\n        self.assertEqual(kevent.flags, flags)\n\nif implements_poll():\n    PollerPollTestsBase = unittest.TestCase\nelse:\n    PollerPollTestsBase = SkipTestCase\n\nclass PollerPollTests(PollerPollTestsBase):\n\n    def _makeOne(self, options):\n        return PollPoller(options)\n\n    def test_register_readable(self):\n        select_poll = DummySelectPoll()\n        poller = self._makeOne(DummyOptions())\n        poller._poller = select_poll\n        poller.register_readable(6)\n        poller.register_readable(7)\n        self.assertEqual(select_poll.registered_as_readable, [6,7])\n\n    def test_register_writable(self):\n        select_poll = DummySelectPoll()\n        poller = self._makeOne(DummyOptions())\n        poller._poller = select_poll\n        poller.register_writable(6)\n        self.assertEqual(select_poll.registered_as_writable, [6])\n\n    def test_poll_returns_readables_and_writables(self):\n        select_poll = DummySelectPoll(result=[(6, select.POLLIN),\n                                              (7, select.POLLPRI),\n                                              (8, select.POLLOUT),\n                                              (9, select.POLLHUP)])\n        poller = self._makeOne(DummyOptions())\n        poller._poller = select_poll\n        poller.register_readable(6)\n        poller.register_readable(7)\n        poller.register_writable(8)\n        poller.register_readable(9)\n        readables, writables = poller.poll(1000)\n        self.assertEqual(readables, [6,7,9])\n        self.assertEqual(writables, [8])\n\n    def test_poll_ignores_eintr(self):\n        select_poll = DummySelectPoll(error=errno.EINTR)\n        options = DummyOptions()\n        poller = self._makeOne(options)\n        poller._poller = select_poll\n        poller.register_readable(9)\n        poller.poll(1000)\n        self.assertEqual(options.logger.data[0], 'EINTR encountered in poll')\n\n    def test_poll_uncaught_exception(self):\n        select_poll = DummySelectPoll(error=errno.EBADF)\n        options = DummyOptions()\n        poller = self._makeOne(options)\n        poller._poller = select_poll\n        poller.register_readable(9)\n        self.assertRaises(select.error, poller.poll, 1000)\n\n    def test_poll_ignores_and_unregisters_closed_fd(self):\n        select_poll = DummySelectPoll(result=[(6, select.POLLNVAL),\n                                              (7, select.POLLPRI)])\n        poller = self._makeOne(DummyOptions())\n        poller._poller = select_poll\n        poller.register_readable(6)\n        poller.register_readable(7)\n        readables, writables = poller.poll(1000)\n        self.assertEqual(readables, [7])\n        self.assertEqual(select_poll.unregistered, [6])\n\nclass DummySelect(object):\n    '''\n    Fake implementation of select.select()\n    '''\n    def __init__(self, result=None, error=None):\n        result = result or {}\n        self.readables = result.get('readables', [])\n        self.writables = result.get('writables', [])\n        self.error = error\n\n    def select(self, r, w, x, timeout):\n        if self.error:\n            raise select.error(self.error)\n        return self.readables, self.writables, []\n\nclass DummySelectPoll(object):\n    '''\n    Fake implementation of select.poll()\n    '''\n    def __init__(self, result=None, error=None):\n        self.result = result or []\n        self.error = error\n        self.registered_as_readable = []\n        self.registered_as_writable = []\n        self.unregistered = []\n\n    def register(self, fd, eventmask):\n        if eventmask == select.POLLIN | select.POLLPRI | select.POLLHUP:\n            self.registered_as_readable.append(fd)\n        elif eventmask == select.POLLOUT:\n            self.registered_as_writable.append(fd)\n        else:\n            raise ValueError(\"Registered a fd on unknown eventmask: '{0}'\".format(eventmask))\n\n    def unregister(self, fd):\n        self.unregistered.append(fd)\n\n    def poll(self, timeout):\n        if self.error:\n            raise select.error(self.error)\n        return self.result\n\n\nclass DummyKQueue(object):\n    '''\n    Fake implementation of select.kqueue()\n    '''\n    def __init__(self, result=None, raise_errno_poll=None, raise_errno_register=None):\n        self.result = result or []\n        self.errno_poll = raise_errno_poll\n        self.errno_register = raise_errno_register\n        self.registered_kevents = []\n        self.registered_flags = []\n\n    def control(self, kevents, max_events, timeout=None):\n        if kevents is None:    # being called on poll()\n            self.assert_max_events_on_poll(max_events)\n            self.raise_error(self.errno_poll)\n            return self.build_result()\n\n        self.assert_max_events_on_register(max_events)\n        self.raise_error(self.errno_register)\n        self.registered_kevents.extend(kevents)\n\n    def raise_error(self, err):\n        if not err: return\n        ex = OSError()\n        ex.errno = err\n        raise ex\n\n    def build_result(self):\n        return [FakeKEvent(ident, filter) for ident,filter in self.result]\n\n    def assert_max_events_on_poll(self, max_events):\n        assert max_events == KQueuePoller.max_events, (\n            \"`max_events` parameter of `kqueue.control() should be %d\"\n            % KQueuePoller.max_events)\n\n    def assert_max_events_on_register(self, max_events):\n        assert max_events == 0, (\n            \"`max_events` parameter of `kqueue.control()` should be 0 on register\")\n\nclass FakeKEvent(object):\n    def __init__(self, ident, filter):\n        self.ident = ident\n        self.filter = filter\n"
  },
  {
    "path": "supervisor/tests/test_process.py",
    "content": "import errno\nimport os\nimport signal\nimport tempfile\nimport time\nimport unittest\n\nfrom supervisor.compat import as_bytes\nfrom supervisor.compat import maxint\n\nfrom supervisor.tests.base import Mock, patch, sentinel\nfrom supervisor.tests.base import DummyOptions\nfrom supervisor.tests.base import DummyPConfig\nfrom supervisor.tests.base import DummyProcess\nfrom supervisor.tests.base import DummyPGroupConfig\nfrom supervisor.tests.base import DummyDispatcher\nfrom supervisor.tests.base import DummyEvent\nfrom supervisor.tests.base import DummyFCGIGroupConfig\nfrom supervisor.tests.base import DummySocketConfig\nfrom supervisor.tests.base import DummyProcessGroup\nfrom supervisor.tests.base import DummyFCGIProcessGroup\n\nfrom supervisor.process import Subprocess\nfrom supervisor.options import BadCommand\n\nclass SubprocessTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.process import Subprocess\n        return Subprocess\n\n    def _makeOne(self, *arg, **kw):\n        return self._getTargetClass()(*arg, **kw)\n\n    def tearDown(self):\n        from supervisor.events import clear\n        clear()\n\n    def test_getProcessStateDescription(self):\n        from supervisor.states import ProcessStates\n        from supervisor.process import getProcessStateDescription\n        for statename, code in ProcessStates.__dict__.items():\n            if not statename.startswith(\"__\"):\n                self.assertEqual(getProcessStateDescription(code), statename)\n\n    def test_ctor(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'cat', 'bin/cat',\n                              stdout_logfile='/tmp/temp123.log',\n                              stderr_logfile='/tmp/temp456.log')\n        instance = self._makeOne(config)\n        self.assertEqual(instance.config, config)\n        self.assertEqual(instance.config.options, options)\n        self.assertEqual(instance.laststart, 0)\n        self.assertEqual(instance.pid, 0)\n        self.assertEqual(instance.laststart, 0)\n        self.assertEqual(instance.laststop, 0)\n        self.assertEqual(instance.delay, 0)\n        self.assertFalse(instance.administrative_stop)\n        self.assertFalse(instance.killing)\n        self.assertEqual(instance.backoff, 0)\n        self.assertEqual(instance.pipes, {})\n        self.assertEqual(instance.dispatchers, {})\n        self.assertEqual(instance.spawnerr, None)\n\n    def test_repr(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'cat', 'bin/cat')\n        instance = self._makeOne(config)\n        s = repr(instance)\n        self.assertTrue(s.startswith('<Subprocess at'))\n        self.assertTrue(s.endswith('with name cat in state STOPPED>'))\n\n    def test_reopenlogs(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'test', '/test')\n        instance = self._makeOne(config)\n        instance.dispatchers = {0:DummyDispatcher(readable=True),\n                                1:DummyDispatcher(writable=True)}\n        instance.reopenlogs()\n        self.assertEqual(instance.dispatchers[0].logs_reopened, True)\n        self.assertEqual(instance.dispatchers[1].logs_reopened, False)\n\n    def test_removelogs(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'test', '/test')\n        instance = self._makeOne(config)\n        instance.dispatchers = {0:DummyDispatcher(readable=True),\n                                1:DummyDispatcher(writable=True)}\n        instance.removelogs()\n        self.assertEqual(instance.dispatchers[0].logs_removed, True)\n        self.assertEqual(instance.dispatchers[1].logs_removed, False)\n\n    def test_drain(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'test', '/test',\n                              stdout_logfile='/tmp/foo',\n                              stderr_logfile='/tmp/bar')\n        instance = self._makeOne(config)\n        instance.dispatchers = {0:DummyDispatcher(readable=True),\n                                1:DummyDispatcher(writable=True)}\n        instance.drain()\n        self.assertTrue(instance.dispatchers[0].read_event_handled)\n        self.assertTrue(instance.dispatchers[1].write_event_handled)\n\n    def test_get_execv_args_bad_command_extraquote(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'extraquote', 'extraquote\"')\n        instance = self._makeOne(config)\n        self.assertRaises(BadCommand, instance.get_execv_args)\n\n    def test_get_execv_args_bad_command_empty(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'empty', '')\n        instance = self._makeOne(config)\n        self.assertRaises(BadCommand, instance.get_execv_args)\n\n    def test_get_execv_args_bad_command_whitespaceonly(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'whitespaceonly', ' \\t ')\n        instance = self._makeOne(config)\n        self.assertRaises(BadCommand, instance.get_execv_args)\n\n    def test_get_execv_args_abs_missing(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'notthere', '/notthere')\n        instance = self._makeOne(config)\n        args = instance.get_execv_args()\n        self.assertEqual(args, ('/notthere', ['/notthere']))\n\n    def test_get_execv_args_abs_withquotes_missing(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'notthere', '/notthere \"an argument\"')\n        instance = self._makeOne(config)\n        args = instance.get_execv_args()\n        self.assertEqual(args, ('/notthere', ['/notthere', 'an argument']))\n\n    def test_get_execv_args_rel_missing(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'notthere', 'notthere')\n        instance = self._makeOne(config)\n        args = instance.get_execv_args()\n        self.assertEqual(args, ('notthere', ['notthere']))\n\n    def test_get_execv_args_rel_withquotes_missing(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'notthere', 'notthere \"an argument\"')\n        instance = self._makeOne(config)\n        args = instance.get_execv_args()\n        self.assertEqual(args, ('notthere', ['notthere', 'an argument']))\n\n    def test_get_execv_args_abs(self):\n        executable = '/bin/sh foo'\n        options = DummyOptions()\n        config = DummyPConfig(options, 'sh', executable)\n        instance = self._makeOne(config)\n        args = instance.get_execv_args()\n        self.assertEqual(len(args), 2)\n        self.assertEqual(args[0], '/bin/sh')\n        self.assertEqual(args[1], ['/bin/sh', 'foo'])\n\n    def test_get_execv_args_rel(self):\n        executable = 'sh foo'\n        options = DummyOptions()\n        config = DummyPConfig(options, 'sh', executable)\n        instance = self._makeOne(config)\n        args = instance.get_execv_args()\n        self.assertEqual(len(args), 2)\n        self.assertEqual(args[0], '/bin/sh')\n        self.assertEqual(args[1], ['sh', 'foo'])\n\n    def test_get_execv_args_rel_searches_using_pconfig_path(self):\n        with tempfile.NamedTemporaryFile() as f:\n            dirname, basename = os.path.split(f.name)\n            executable = '%s foo' % basename\n            options = DummyOptions()\n            config = DummyPConfig(options, 'sh', executable)\n            config.get_path = lambda: [ dirname ]\n            instance = self._makeOne(config)\n            args = instance.get_execv_args()\n            self.assertEqual(args[0], f.name)\n            self.assertEqual(args[1], [basename, 'foo'])\n\n    def test_record_spawnerr(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'test', '/test')\n        instance = self._makeOne(config)\n        instance.record_spawnerr('foo')\n        self.assertEqual(instance.spawnerr, 'foo')\n        self.assertEqual(options.logger.data[0], 'spawnerr: foo')\n\n    def test_spawn_already_running(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'sh', '/bin/sh')\n        instance = self._makeOne(config)\n        instance.pid = True\n        from supervisor.states import ProcessStates\n        instance.state = ProcessStates.RUNNING\n        result = instance.spawn()\n        self.assertEqual(result, None)\n        self.assertEqual(options.logger.data[0], \"process 'sh' already running\")\n        self.assertEqual(instance.state, ProcessStates.RUNNING)\n\n    def test_spawn_fail_check_execv_args(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'bad', '/bad/filename')\n        instance = self._makeOne(config)\n        from supervisor.states import ProcessStates\n        instance.state = ProcessStates.BACKOFF\n        from supervisor import events\n        L = []\n        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))\n        result = instance.spawn()\n        self.assertEqual(result, None)\n        self.assertEqual(instance.spawnerr, 'bad filename')\n        self.assertEqual(options.logger.data[0], \"spawnerr: bad filename\")\n        self.assertTrue(instance.delay)\n        self.assertTrue(instance.backoff)\n        from supervisor.states import ProcessStates\n        self.assertEqual(instance.state, ProcessStates.BACKOFF)\n        self.assertEqual(len(L), 2)\n        event1 = L[0]\n        event2 = L[1]\n        self.assertEqual(event1.__class__, events.ProcessStateStartingEvent)\n        self.assertEqual(event2.__class__, events.ProcessStateBackoffEvent)\n\n    def test_spawn_fail_make_pipes_emfile(self):\n        options = DummyOptions()\n        options.make_pipes_exception = OSError(errno.EMFILE,\n                                               os.strerror(errno.EMFILE))\n        config = DummyPConfig(options, 'good', '/good/filename')\n        instance = self._makeOne(config)\n        from supervisor.states import ProcessStates\n        instance.state = ProcessStates.BACKOFF\n        from supervisor import events\n        L = []\n        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))\n        result = instance.spawn()\n        self.assertEqual(result, None)\n        self.assertEqual(instance.spawnerr,\n                         \"too many open files to spawn 'good'\")\n        self.assertEqual(options.logger.data[0],\n                         \"spawnerr: too many open files to spawn 'good'\")\n        self.assertTrue(instance.delay)\n        self.assertTrue(instance.backoff)\n        from supervisor.states import ProcessStates\n        self.assertEqual(instance.state, ProcessStates.BACKOFF)\n        self.assertEqual(len(L), 2)\n        event1, event2 = L\n        self.assertEqual(event1.__class__, events.ProcessStateStartingEvent)\n        self.assertEqual(event2.__class__, events.ProcessStateBackoffEvent)\n\n    def test_spawn_fail_make_pipes_other(self):\n        options = DummyOptions()\n        options.make_pipes_exception = OSError(errno.EPERM,\n                                               os.strerror(errno.EPERM))\n        config = DummyPConfig(options, 'good', '/good/filename')\n        instance = self._makeOne(config)\n        from supervisor.states import ProcessStates\n        instance.state = ProcessStates.BACKOFF\n        from supervisor import events\n        L = []\n        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))\n        result = instance.spawn()\n        self.assertEqual(result, None)\n        msg = \"unknown error making dispatchers for 'good': EPERM\"\n        self.assertEqual(instance.spawnerr, msg)\n        self.assertEqual(options.logger.data[0], \"spawnerr: %s\" % msg)\n        self.assertTrue(instance.delay)\n        self.assertTrue(instance.backoff)\n        from supervisor.states import ProcessStates\n        self.assertEqual(instance.state, ProcessStates.BACKOFF)\n        self.assertEqual(len(L), 2)\n        event1, event2 = L\n        self.assertEqual(event1.__class__, events.ProcessStateStartingEvent)\n        self.assertEqual(event2.__class__, events.ProcessStateBackoffEvent)\n\n    def test_spawn_fail_make_dispatchers_eisdir(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, name='cat', command='/bin/cat',\n                              stdout_logfile='/a/directory') # not a file\n        def raise_eisdir(envelope):\n            raise IOError(errno.EISDIR)\n        config.make_dispatchers = raise_eisdir\n        instance = self._makeOne(config)\n        from supervisor.states import ProcessStates\n        instance.state = ProcessStates.BACKOFF\n        from supervisor import events\n        L = []\n        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))\n        result = instance.spawn()\n        self.assertEqual(result, None)\n        msg = \"unknown error making dispatchers for 'cat': EISDIR\"\n        self.assertEqual(instance.spawnerr, msg)\n        self.assertEqual(options.logger.data[0], \"spawnerr: %s\" % msg)\n        self.assertTrue(instance.delay)\n        self.assertTrue(instance.backoff)\n        from supervisor.states import ProcessStates\n        self.assertEqual(instance.state, ProcessStates.BACKOFF)\n        self.assertEqual(len(L), 2)\n        event1, event2 = L\n        self.assertEqual(event1.__class__, events.ProcessStateStartingEvent)\n        self.assertEqual(event2.__class__, events.ProcessStateBackoffEvent)\n\n    def test_spawn_fork_fail_eagain(self):\n        options = DummyOptions()\n        options.fork_exception = OSError(errno.EAGAIN,\n                                         os.strerror(errno.EAGAIN))\n        config = DummyPConfig(options, 'good', '/good/filename')\n        instance = self._makeOne(config)\n        from supervisor.states import ProcessStates\n        instance.state = ProcessStates.BACKOFF\n        from supervisor import events\n        L = []\n        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))\n        result = instance.spawn()\n        self.assertEqual(result, None)\n        msg = \"Too many processes in process table to spawn 'good'\"\n        self.assertEqual(instance.spawnerr, msg)\n        self.assertEqual(options.logger.data[0], \"spawnerr: %s\" % msg)\n        self.assertEqual(len(options.parent_pipes_closed), 6)\n        self.assertEqual(len(options.child_pipes_closed), 6)\n        self.assertTrue(instance.delay)\n        self.assertTrue(instance.backoff)\n        from supervisor.states import ProcessStates\n        self.assertEqual(instance.state, ProcessStates.BACKOFF)\n        self.assertEqual(len(L), 2)\n        event1, event2 = L\n        self.assertEqual(event1.__class__, events.ProcessStateStartingEvent)\n        self.assertEqual(event2.__class__, events.ProcessStateBackoffEvent)\n\n    def test_spawn_fork_fail_other(self):\n        options = DummyOptions()\n        options.fork_exception = OSError(errno.EPERM,\n                                         os.strerror(errno.EPERM))\n        config = DummyPConfig(options, 'good', '/good/filename')\n        instance = self._makeOne(config)\n        from supervisor.states import ProcessStates\n        instance.state = ProcessStates.BACKOFF\n        from supervisor import events\n        L = []\n        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))\n        result = instance.spawn()\n        self.assertEqual(result, None)\n        msg = \"unknown error during fork for 'good': EPERM\"\n        self.assertEqual(instance.spawnerr, msg)\n        self.assertEqual(options.logger.data[0], \"spawnerr: %s\" % msg)\n        self.assertEqual(len(options.parent_pipes_closed), 6)\n        self.assertEqual(len(options.child_pipes_closed), 6)\n        self.assertTrue(instance.delay)\n        self.assertTrue(instance.backoff)\n        from supervisor.states import ProcessStates\n        self.assertEqual(instance.state, ProcessStates.BACKOFF)\n        self.assertEqual(len(L), 2)\n        event1, event2 = L\n        self.assertEqual(event1.__class__, events.ProcessStateStartingEvent)\n        self.assertEqual(event2.__class__, events.ProcessStateBackoffEvent)\n\n    def test_spawn_as_child_setuid_ok(self):\n        options = DummyOptions()\n        options.forkpid = 0\n        config = DummyPConfig(options, 'good', '/good/filename', uid=1)\n        instance = self._makeOne(config)\n        result = instance.spawn()\n        self.assertEqual(result, None)\n        self.assertEqual(options.parent_pipes_closed, None)\n        self.assertEqual(options.child_pipes_closed, None)\n        self.assertEqual(options.pgrp_set, True)\n        self.assertEqual(len(options.duped), 3)\n        self.assertEqual(len(options.fds_closed), options.minfds - 3)\n        self.assertEqual(options.privsdropped, 1)\n        self.assertEqual(options.execv_args,\n                         ('/good/filename', ['/good/filename']) )\n        self.assertEqual(options.execve_called, True)\n        # if the real execve() succeeds, the code that writes the\n        # \"was not spawned\" message won't be reached.  this assertion\n        # is to test that no other errors were written.\n        self.assertEqual(options.written,\n             {2: \"supervisor: child process was not spawned\\n\"})\n\n    def test_spawn_as_child_setuid_fail(self):\n        options = DummyOptions()\n        options.forkpid = 0\n        options.setuid_msg = 'failure reason'\n        config = DummyPConfig(options, 'good', '/good/filename', uid=1)\n        instance = self._makeOne(config)\n        result = instance.spawn()\n        self.assertEqual(result, None)\n        self.assertEqual(options.parent_pipes_closed, None)\n        self.assertEqual(options.child_pipes_closed, None)\n        self.assertEqual(options.pgrp_set, True)\n        self.assertEqual(len(options.duped), 3)\n        self.assertEqual(len(options.fds_closed), options.minfds - 3)\n        self.assertEqual(options.written,\n             {2: \"supervisor: couldn't setuid to 1: failure reason\\n\"\n                 \"supervisor: child process was not spawned\\n\"})\n        self.assertEqual(options.privsdropped, None)\n        self.assertEqual(options.execve_called, False)\n        self.assertEqual(options._exitcode, 127)\n\n    def test_spawn_as_child_cwd_ok(self):\n        options = DummyOptions()\n        options.forkpid = 0\n        config = DummyPConfig(options, 'good', '/good/filename',\n                              directory='/tmp')\n        instance = self._makeOne(config)\n        result = instance.spawn()\n        self.assertEqual(result, None)\n        self.assertEqual(options.parent_pipes_closed, None)\n        self.assertEqual(options.child_pipes_closed, None)\n        self.assertEqual(options.pgrp_set, True)\n        self.assertEqual(len(options.duped), 3)\n        self.assertEqual(len(options.fds_closed), options.minfds - 3)\n        self.assertEqual(options.execv_args,\n                         ('/good/filename', ['/good/filename']) )\n        self.assertEqual(options.changed_directory, True)\n        self.assertEqual(options.execve_called, True)\n        # if the real execve() succeeds, the code that writes the\n        # \"was not spawned\" message won't be reached.  this assertion\n        # is to test that no other errors were written.\n        self.assertEqual(options.written,\n             {2: \"supervisor: child process was not spawned\\n\"})\n\n    def test_spawn_as_child_sets_umask(self):\n        options = DummyOptions()\n        options.forkpid = 0\n        config = DummyPConfig(options, 'good', '/good/filename', umask=2)\n        instance = self._makeOne(config)\n        result = instance.spawn()\n        self.assertEqual(result, None)\n        self.assertEqual(options.execv_args,\n                         ('/good/filename', ['/good/filename']) )\n        self.assertEqual(options.umaskset, 2)\n        self.assertEqual(options.execve_called, True)\n        # if the real execve() succeeds, the code that writes the\n        # \"was not spawned\" message won't be reached.  this assertion\n        # is to test that no other errors were written.\n        self.assertEqual(options.written,\n             {2: \"supervisor: child process was not spawned\\n\"})\n\n    def test_spawn_as_child_cwd_fail(self):\n        options = DummyOptions()\n        options.forkpid = 0\n        options.chdir_exception = OSError(errno.ENOENT,\n                                          os.strerror(errno.ENOENT))\n        config = DummyPConfig(options, 'good', '/good/filename',\n                              directory='/tmp')\n        instance = self._makeOne(config)\n        result = instance.spawn()\n        self.assertEqual(result, None)\n        self.assertEqual(options.parent_pipes_closed, None)\n        self.assertEqual(options.child_pipes_closed, None)\n        self.assertEqual(options.pgrp_set, True)\n        self.assertEqual(len(options.duped), 3)\n        self.assertEqual(len(options.fds_closed), options.minfds - 3)\n        self.assertEqual(options.execv_args, None)\n        out = {2: \"supervisor: couldn't chdir to /tmp: ENOENT\\n\"\n                  \"supervisor: child process was not spawned\\n\"}\n        self.assertEqual(options.written, out)\n        self.assertEqual(options._exitcode, 127)\n        self.assertEqual(options.changed_directory, False)\n        self.assertEqual(options.execve_called, False)\n\n    def test_spawn_as_child_execv_fail_oserror(self):\n        options = DummyOptions()\n        options.forkpid = 0\n        options.execv_exception = OSError(errno.EPERM,\n                                          os.strerror(errno.EPERM))\n        config = DummyPConfig(options, 'good', '/good/filename')\n        instance = self._makeOne(config)\n        result = instance.spawn()\n        self.assertEqual(result, None)\n        self.assertEqual(options.parent_pipes_closed, None)\n        self.assertEqual(options.child_pipes_closed, None)\n        self.assertEqual(options.pgrp_set, True)\n        self.assertEqual(len(options.duped), 3)\n        self.assertEqual(len(options.fds_closed), options.minfds - 3)\n        out = {2: \"supervisor: couldn't exec /good/filename: EPERM\\n\"\n                  \"supervisor: child process was not spawned\\n\"}\n        self.assertEqual(options.written, out)\n        self.assertEqual(options.privsdropped, None)\n        self.assertEqual(options._exitcode, 127)\n\n    def test_spawn_as_child_execv_fail_runtime_error(self):\n        options = DummyOptions()\n        options.forkpid = 0\n        options.execv_exception = RuntimeError(errno.ENOENT)\n        config = DummyPConfig(options, 'good', '/good/filename')\n        instance = self._makeOne(config)\n        result = instance.spawn()\n        self.assertEqual(result, None)\n        self.assertEqual(options.parent_pipes_closed, None)\n        self.assertEqual(options.child_pipes_closed, None)\n        self.assertEqual(options.pgrp_set, True)\n        self.assertEqual(len(options.duped), 3)\n        self.assertEqual(len(options.fds_closed), options.minfds - 3)\n        msg = options.written[2] # dict, 2 is fd #\n        head = \"supervisor: couldn't exec /good/filename:\"\n        self.assertTrue(msg.startswith(head))\n        self.assertTrue(\"RuntimeError\" in msg)\n        self.assertEqual(options.privsdropped, None)\n        self.assertEqual(options._exitcode, 127)\n\n    def test_spawn_as_child_uses_pconfig_environment(self):\n        options = DummyOptions()\n        options.forkpid = 0\n        config = DummyPConfig(options, 'cat', '/bin/cat',\n                              environment={'_TEST_':'1'})\n        instance = self._makeOne(config)\n        result = instance.spawn()\n        self.assertEqual(result, None)\n        self.assertEqual(options.execv_args, ('/bin/cat', ['/bin/cat']) )\n        self.assertEqual(options.execv_environment['_TEST_'], '1')\n\n    def test_spawn_as_child_environment_supervisor_envvars(self):\n        options = DummyOptions()\n        options.forkpid = 0\n        config = DummyPConfig(options, 'cat', '/bin/cat')\n        instance = self._makeOne(config)\n        class Dummy:\n            name = 'dummy'\n        instance.group = Dummy()\n        instance.group.config = Dummy()\n        result = instance.spawn()\n        self.assertEqual(result, None)\n        self.assertEqual(options.execv_args, ('/bin/cat', ['/bin/cat']) )\n        self.assertEqual(\n            options.execv_environment['SUPERVISOR_ENABLED'], '1')\n        self.assertEqual(\n            options.execv_environment['SUPERVISOR_PROCESS_NAME'], 'cat')\n        self.assertEqual(\n            options.execv_environment['SUPERVISOR_GROUP_NAME'], 'dummy')\n        self.assertEqual(\n            options.execv_environment['SUPERVISOR_SERVER_URL'],\n            'http://localhost:9001')\n\n    def test_spawn_as_child_stderr_redirected(self):\n        options = DummyOptions()\n        options.forkpid = 0\n        config = DummyPConfig(options, 'good', '/good/filename', uid=1)\n        config.redirect_stderr = True\n        instance = self._makeOne(config)\n        result = instance.spawn()\n        self.assertEqual(result, None)\n        self.assertEqual(options.parent_pipes_closed, None)\n        self.assertEqual(options.child_pipes_closed, None)\n        self.assertEqual(options.pgrp_set, True)\n        self.assertEqual(len(options.duped), 2)\n        self.assertEqual(len(options.fds_closed), options.minfds - 3)\n        self.assertEqual(options.privsdropped, 1)\n        self.assertEqual(options.execv_args,\n                         ('/good/filename', ['/good/filename']) )\n        self.assertEqual(options.execve_called, True)\n        # if the real execve() succeeds, the code that writes the\n        # \"was not spawned\" message won't be reached.  this assertion\n        # is to test that no other errors were written.\n        self.assertEqual(options.written,\n             {2: \"supervisor: child process was not spawned\\n\"})\n\n    def test_spawn_as_parent(self):\n        options = DummyOptions()\n        options.forkpid = 10\n        config = DummyPConfig(options, 'good', '/good/filename')\n        instance = self._makeOne(config)\n        result = instance.spawn()\n        self.assertEqual(result, 10)\n        self.assertEqual(instance.dispatchers[4].__class__, DummyDispatcher)\n        self.assertEqual(instance.dispatchers[5].__class__, DummyDispatcher)\n        self.assertEqual(instance.dispatchers[7].__class__, DummyDispatcher)\n        self.assertEqual(instance.pipes['stdin'], 4)\n        self.assertEqual(instance.pipes['stdout'], 5)\n        self.assertEqual(instance.pipes['stderr'], 7)\n        self.assertEqual(options.parent_pipes_closed, None)\n        self.assertEqual(len(options.child_pipes_closed), 6)\n        self.assertEqual(options.logger.data[0], \"spawned: 'good' with pid 10\")\n        self.assertEqual(instance.spawnerr, None)\n        self.assertTrue(instance.delay)\n        self.assertEqual(instance.config.options.pidhistory[10], instance)\n        from supervisor.states import ProcessStates\n        self.assertEqual(instance.state, ProcessStates.STARTING)\n\n    def test_spawn_redirect_stderr(self):\n        options = DummyOptions()\n        options.forkpid = 10\n        config = DummyPConfig(options, 'good', '/good/filename',\n                              redirect_stderr=True)\n        instance = self._makeOne(config)\n        result = instance.spawn()\n        self.assertEqual(result, 10)\n        self.assertEqual(instance.dispatchers[4].__class__, DummyDispatcher)\n        self.assertEqual(instance.dispatchers[5].__class__, DummyDispatcher)\n        self.assertEqual(instance.pipes['stdin'], 4)\n        self.assertEqual(instance.pipes['stdout'], 5)\n        self.assertEqual(instance.pipes['stderr'], None)\n\n    def test_write(self):\n        executable = '/bin/cat'\n        options = DummyOptions()\n        config = DummyPConfig(options, 'output', executable)\n        instance = self._makeOne(config)\n        sent = 'a' * (1 << 13)\n        self.assertRaises(OSError, instance.write, sent)\n        options.forkpid = 1\n        instance.spawn()\n        instance.write(sent)\n        stdin_fd = instance.pipes['stdin']\n        self.assertEqual(sent, instance.dispatchers[stdin_fd].input_buffer)\n        instance.killing = True\n        self.assertRaises(OSError, instance.write, sent)\n\n    def test_write_dispatcher_closed(self):\n        executable = '/bin/cat'\n        options = DummyOptions()\n        config = DummyPConfig(options, 'output', executable)\n        instance = self._makeOne(config)\n        sent = 'a' * (1 << 13)\n        self.assertRaises(OSError, instance.write, sent)\n        options.forkpid = 1\n        instance.spawn()\n        stdin_fd = instance.pipes['stdin']\n        instance.dispatchers[stdin_fd].close()\n        self.assertRaises(OSError, instance.write, sent)\n\n    def test_write_stdin_fd_none(self):\n        executable = '/bin/cat'\n        options = DummyOptions()\n        config = DummyPConfig(options, 'output', executable)\n        instance = self._makeOne(config)\n        options.forkpid = 1\n        instance.spawn()\n        stdin_fd = instance.pipes['stdin']\n        instance.dispatchers[stdin_fd].close()\n        instance.pipes['stdin'] = None\n        try:\n            instance.write('foo')\n            self.fail('nothing raised')\n        except OSError as exc:\n            self.assertEqual(exc.args[0], errno.EPIPE)\n            self.assertEqual(exc.args[1], 'Process has no stdin channel')\n\n    def test_write_dispatcher_flush_raises_epipe(self):\n        executable = '/bin/cat'\n        options = DummyOptions()\n        config = DummyPConfig(options, 'output', executable)\n        instance = self._makeOne(config)\n        sent = 'a' * (1 << 13)\n        self.assertRaises(OSError, instance.write, sent)\n        options.forkpid = 1\n        instance.spawn()\n        stdin_fd = instance.pipes['stdin']\n        instance.dispatchers[stdin_fd].flush_exception = OSError(errno.EPIPE,\n                                                    os.strerror(errno.EPIPE))\n        self.assertRaises(OSError, instance.write, sent)\n\n    def _dont_test_spawn_and_kill(self):\n        # this is a functional test\n        from supervisor.tests.base import makeSpew\n        try:\n            sigchlds = []\n            def sighandler(*args):\n                sigchlds.append(True)\n            signal.signal(signal.SIGCHLD, sighandler)\n            executable = makeSpew()\n            options = DummyOptions()\n            config = DummyPConfig(options, 'spew', executable)\n            instance = self._makeOne(config)\n            result = instance.spawn()\n            msg = options.logger.data[0]\n            self.assertTrue(msg.startswith(\"spawned: 'spew' with pid\"))\n            self.assertEqual(len(instance.pipes), 6)\n            self.assertTrue(instance.pid)\n            self.assertEqual(instance.pid, result)\n            origpid = instance.pid\n            while 1:\n                try:\n                    data = os.popen('ps').read()\n                    break\n                except IOError as why:\n                    if why.args[0] != errno.EINTR:\n                        raise\n                        # try again ;-)\n            time.sleep(0.1) # arbitrary, race condition possible\n            self.assertTrue(data.find(as_bytes(repr(origpid))) != -1 )\n            msg = instance.kill(signal.SIGTERM)\n            time.sleep(0.1) # arbitrary, race condition possible\n            self.assertEqual(msg, None)\n            pid, sts = os.waitpid(-1, os.WNOHANG)\n            data = os.popen('ps').read()\n            self.assertEqual(data.find(as_bytes(repr(origpid))), -1) # dubious\n            self.assertNotEqual(sigchlds, [])\n        finally:\n            try:\n                os.remove(executable)\n            except:\n                pass\n            signal.signal(signal.SIGCHLD, signal.SIG_DFL)\n\n    def test_stop(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'test', '/test')\n        instance = self._makeOne(config)\n        instance.pid = 11\n        dispatcher = DummyDispatcher(writable=True)\n        instance.dispatchers = {'foo':dispatcher}\n        from supervisor.states import ProcessStates\n        instance.state = ProcessStates.RUNNING\n        instance.laststopreport = time.time()\n        instance.stop()\n        self.assertTrue(instance.administrative_stop)\n        self.assertEqual(instance.laststopreport, 0)\n        self.assertTrue(instance.delay)\n        self.assertEqual(options.logger.data[0], 'killing test (pid 11) with '\n                         'signal SIGTERM')\n        self.assertTrue(instance.killing)\n        self.assertEqual(options.kills[11], signal.SIGTERM)\n\n    def test_stop_not_in_stoppable_state_error(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'test', '/test')\n        instance = self._makeOne(config)\n        instance.pid = 11\n        dispatcher = DummyDispatcher(writable=True)\n        instance.dispatchers = {'foo':dispatcher}\n        from supervisor.states import ProcessStates\n        instance.state = ProcessStates.STOPPED\n        try:\n            instance.stop()\n            self.fail('nothing raised')\n        except AssertionError as exc:\n            self.assertEqual(exc.args[0], 'Assertion failed for test: '\n                'STOPPED not in RUNNING STARTING STOPPING')\n\n    def test_stop_report_logs_nothing_if_not_stopping_state(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'test', '/test')\n        instance = self._makeOne(config)\n        instance.pid = 11\n        dispatcher = DummyDispatcher(writable=True)\n        instance.dispatchers = {'foo':dispatcher}\n        from supervisor.states import ProcessStates\n        instance.state = ProcessStates.STOPPED\n        instance.stop_report()\n        self.assertEqual(len(options.logger.data), 0)\n\n    def test_stop_report_logs_throttled_by_laststopreport(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'test', '/test')\n        instance = self._makeOne(config)\n        instance.pid = 11\n        dispatcher = DummyDispatcher(writable=True)\n        instance.dispatchers = {'foo':dispatcher}\n        from supervisor.states import ProcessStates\n        instance.state = ProcessStates.STOPPING\n        self.assertEqual(instance.laststopreport, 0)\n        instance.stop_report()\n        self.assertEqual(len(options.logger.data), 1)\n        self.assertEqual(options.logger.data[0], 'waiting for test to stop')\n        self.assertNotEqual(instance.laststopreport, 0)\n        instance.stop_report()\n        self.assertEqual(len(options.logger.data), 1) # throttled\n\n    def test_stop_report_laststopreport_in_future(self):\n        future_time = time.time() + 3600 # 1 hour into the future\n        options = DummyOptions()\n        config = DummyPConfig(options, 'test', '/test')\n        instance = self._makeOne(config)\n        instance.pid = 11\n        dispatcher = DummyDispatcher(writable=True)\n        instance.dispatchers = {'foo':dispatcher}\n        from supervisor.states import ProcessStates\n        instance.state = ProcessStates.STOPPING\n        instance.laststopreport = future_time\n\n        # This iteration of stop_report() should reset instance.laststopreport\n        # to the current time\n        instance.stop_report()\n\n        # No logging should have taken place\n        self.assertEqual(len(options.logger.data), 0)\n\n        # Ensure instance.laststopreport has rolled backward\n        self.assertTrue(instance.laststopreport < future_time)\n\n        # Sleep for 2 seconds\n        time.sleep(2)\n\n        # This iteration of stop_report() should actually trigger the report\n        instance.stop_report()\n\n        self.assertEqual(len(options.logger.data), 1)\n        self.assertEqual(options.logger.data[0], 'waiting for test to stop')\n        self.assertNotEqual(instance.laststopreport, 0)\n        instance.stop_report()\n        self.assertEqual(len(options.logger.data), 1) # throttled\n\n    def test_give_up(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'test', '/test')\n        instance = self._makeOne(config)\n        L = []\n        from supervisor.states import ProcessStates\n        from supervisor import events\n        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))\n        instance.state = ProcessStates.BACKOFF\n        instance.give_up()\n        self.assertTrue(instance.system_stop)\n        self.assertFalse(instance.delay)\n        self.assertFalse(instance.backoff)\n        self.assertEqual(instance.state, ProcessStates.FATAL)\n        self.assertEqual(len(L), 1)\n        event = L[0]\n        self.assertEqual(event.__class__, events.ProcessStateFatalEvent)\n\n    def test_kill_nopid(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'test', '/test')\n        instance = self._makeOne(config)\n        instance.kill(signal.SIGTERM)\n        self.assertEqual(options.logger.data[0],\n              'attempted to kill test with sig SIGTERM but it wasn\\'t running')\n        self.assertFalse(instance.killing)\n\n    def test_kill_from_starting(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'test', '/test')\n        instance = self._makeOne(config)\n        instance.pid = 11\n        L = []\n        from supervisor.states import ProcessStates\n        from supervisor import events\n        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))\n        instance.state = ProcessStates.STARTING\n        instance.kill(signal.SIGTERM)\n        self.assertEqual(options.logger.data[0], 'killing test (pid 11) with '\n                         'signal SIGTERM')\n        self.assertTrue(instance.killing)\n        self.assertEqual(options.kills[11], signal.SIGTERM)\n        self.assertEqual(len(L), 1)\n        event = L[0]\n        self.assertEqual(event.__class__, events.ProcessStateStoppingEvent)\n\n    def test_kill_from_running(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'test', '/test')\n        instance = self._makeOne(config)\n        instance.pid = 11\n        L = []\n        from supervisor.states import ProcessStates\n        from supervisor import events\n        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))\n        instance.state = ProcessStates.RUNNING\n        instance.kill(signal.SIGTERM)\n        self.assertEqual(options.logger.data[0], 'killing test (pid 11) with '\n                         'signal SIGTERM')\n        self.assertTrue(instance.killing)\n        self.assertEqual(options.kills[11], signal.SIGTERM)\n        self.assertEqual(len(L), 1)\n        event = L[0]\n        self.assertEqual(event.__class__, events.ProcessStateStoppingEvent)\n\n    def test_kill_from_running_error(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'test', '/test')\n        options.kill_exception = OSError(errno.EPERM,\n                                         os.strerror(errno.EPERM))\n        instance = self._makeOne(config)\n        L = []\n        from supervisor.states import ProcessStates\n        from supervisor import events\n        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))\n        instance.pid = 11\n        instance.state = ProcessStates.RUNNING\n        instance.kill(signal.SIGTERM)\n        self.assertEqual(options.logger.data[0], 'killing test (pid 11) with '\n                         'signal SIGTERM')\n        self.assertTrue(options.logger.data[1].startswith(\n            'unknown problem killing test'))\n        self.assertTrue('Traceback' in options.logger.data[1])\n        self.assertFalse(instance.killing)\n        self.assertEqual(instance.pid, 11) # unchanged\n        self.assertEqual(instance.state, ProcessStates.UNKNOWN)\n        self.assertEqual(len(L), 2)\n        event1 = L[0]\n        event2 = L[1]\n        self.assertEqual(event1.__class__, events.ProcessStateStoppingEvent)\n        self.assertEqual(event2.__class__, events.ProcessStateUnknownEvent)\n\n    def test_kill_from_running_error_ESRCH(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'test', '/test')\n        options.kill_exception = OSError(errno.ESRCH,\n                                         os.strerror(errno.ESRCH))\n        instance = self._makeOne(config)\n        L = []\n        from supervisor.states import ProcessStates\n        from supervisor import events\n        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))\n        instance.pid = 11\n        instance.state = ProcessStates.RUNNING\n        instance.kill(signal.SIGTERM)\n        self.assertEqual(options.logger.data[0], 'killing test (pid 11) with '\n            'signal SIGTERM')\n        self.assertEqual(options.logger.data[1], 'unable to signal test (pid 11), '\n            'it probably just exited on its own: %s' %\n            str(options.kill_exception))\n        self.assertTrue(instance.killing)\n        self.assertEqual(instance.pid, 11) # unchanged\n        self.assertEqual(instance.state, ProcessStates.STOPPING)\n        self.assertEqual(len(L), 1)\n        event1 = L[0]\n        self.assertEqual(event1.__class__, events.ProcessStateStoppingEvent)\n\n    def test_kill_from_stopping(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'test', '/test')\n        instance = self._makeOne(config)\n        instance.pid = 11\n        L = []\n        from supervisor.states import ProcessStates\n        from supervisor import events\n        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))\n        instance.state = ProcessStates.STOPPING\n        instance.kill(signal.SIGKILL)\n        self.assertEqual(options.logger.data[0], 'killing test (pid 11) with '\n                         'signal SIGKILL')\n        self.assertTrue(instance.killing)\n        self.assertEqual(options.kills[11], signal.SIGKILL)\n        self.assertEqual(L, []) # no event because we didn't change state\n\n    def test_kill_from_backoff(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'test', '/test')\n        instance = self._makeOne(config)\n        L = []\n        from supervisor.states import ProcessStates\n        from supervisor import events\n        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))\n        instance.state = ProcessStates.BACKOFF\n        instance.kill(signal.SIGKILL)\n        self.assertEqual(options.logger.data[0],\n                         'Attempted to kill test, which is in BACKOFF state.')\n        self.assertFalse(instance.killing)\n        event = L[0]\n        self.assertEqual(event.__class__, events.ProcessStateStoppedEvent)\n\n    def test_kill_from_stopping_w_killasgroup(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'test', '/test', killasgroup=True)\n        instance = self._makeOne(config)\n        instance.pid = 11\n        L = []\n        from supervisor.states import ProcessStates\n        from supervisor import events\n        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))\n        instance.state = ProcessStates.STOPPING\n        instance.kill(signal.SIGKILL)\n        self.assertEqual(options.logger.data[0], 'killing test (pid 11) '\n                         'process group with signal SIGKILL')\n        self.assertTrue(instance.killing)\n        self.assertEqual(options.kills[-11], signal.SIGKILL)\n        self.assertEqual(L, []) # no event because we didn't change state\n\n    def test_stopasgroup(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'test', '/test', stopasgroup=True)\n        instance = self._makeOne(config)\n        instance.pid = 11\n        L = []\n        from supervisor.states import ProcessStates\n        from supervisor import events\n        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))\n        instance.state = ProcessStates.RUNNING\n        instance.kill(signal.SIGTERM)\n        self.assertEqual(options.logger.data[0], 'killing test (pid 11) '\n                         'process group with signal SIGTERM')\n        self.assertTrue(instance.killing)\n        self.assertEqual(options.kills[-11], signal.SIGTERM)\n        self.assertEqual(len(L), 1)\n        event = L[0]\n        self.assertEqual(event.__class__, events.ProcessStateStoppingEvent)\n        self.assertEqual(event.extra_values, [('pid', 11)])\n        self.assertEqual(event.from_state, ProcessStates.RUNNING)\n\n    def test_signal_from_stopped(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'test', '/test')\n        instance = self._makeOne(config)\n        from supervisor.states import ProcessStates\n        instance.state = ProcessStates.STOPPED\n        instance.signal(signal.SIGWINCH)\n        self.assertEqual(options.logger.data[0], \"attempted to send test sig SIGWINCH \"\n                                                 \"but it wasn't running\")\n        self.assertEqual(len(options.kills), 0)\n\n    def test_signal_from_running(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'test', '/test')\n        instance = self._makeOne(config)\n        instance.pid = 11\n        from supervisor.states import ProcessStates\n        instance.state = ProcessStates.RUNNING\n        instance.signal(signal.SIGWINCH)\n        self.assertEqual(options.logger.data[0], 'sending test (pid 11) sig SIGWINCH')\n        self.assertEqual(len(options.kills), 1)\n        self.assertTrue(instance.pid in options.kills)\n        self.assertEqual(options.kills[instance.pid], signal.SIGWINCH)\n\n    def test_signal_from_running_error_ESRCH(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'test', '/test')\n        options.kill_exception = OSError(errno.ESRCH,\n                                         os.strerror(errno.ESRCH))\n        instance = self._makeOne(config)\n        L = []\n        from supervisor.states import ProcessStates\n        from supervisor import events\n        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))\n        instance.pid = 11\n        instance.state = ProcessStates.RUNNING\n        instance.signal(signal.SIGWINCH)\n        self.assertEqual(options.logger.data[0],\n            'sending test (pid 11) sig SIGWINCH')\n        self.assertEqual(options.logger.data[1], 'unable to signal test (pid 11), '\n            'it probably just now exited on its own: %s' %\n            str(options.kill_exception))\n        self.assertFalse(instance.killing)\n        self.assertEqual(instance.state, ProcessStates.RUNNING) # unchanged\n        self.assertEqual(instance.pid, 11) # unchanged\n        self.assertEqual(len(L), 0)\n\n    def test_signal_from_running_error(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'test', '/test')\n        options.kill_exception = OSError(errno.EPERM,\n                                         os.strerror(errno.EPERM))\n        instance = self._makeOne(config)\n        L = []\n        from supervisor.states import ProcessStates\n        from supervisor import events\n        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))\n        instance.pid = 11\n        instance.state = ProcessStates.RUNNING\n        instance.signal(signal.SIGWINCH)\n        self.assertEqual(options.logger.data[0],\n            'sending test (pid 11) sig SIGWINCH')\n        self.assertTrue(options.logger.data[1].startswith(\n            'unknown problem sending sig test (11)'))\n        self.assertTrue('Traceback' in options.logger.data[1])\n        self.assertFalse(instance.killing)\n        self.assertEqual(instance.state, ProcessStates.UNKNOWN)\n        self.assertEqual(instance.pid, 11) # unchanged\n        self.assertEqual(len(L), 1)\n        event = L[0]\n        self.assertEqual(event.__class__, events.ProcessStateUnknownEvent)\n\n    def test_finish_stopping_state(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'notthere', '/notthere',\n                              stdout_logfile='/tmp/foo')\n        instance = self._makeOne(config)\n        instance.waitstatus = (123, 1) # pid, waitstatus\n        instance.config.options.pidhistory[123] = instance\n        instance.killing = True\n        pipes = {'stdout':'','stderr':''}\n        instance.pipes = pipes\n        from supervisor.states import ProcessStates\n        from supervisor import events\n        instance.state = ProcessStates.STOPPING\n        L = []\n        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))\n        instance.pid = 123\n        instance.finish(123, 1)\n        self.assertFalse(instance.killing)\n        self.assertEqual(instance.pid, 0)\n        self.assertEqual(options.parent_pipes_closed, pipes)\n        self.assertEqual(instance.pipes, {})\n        self.assertEqual(instance.dispatchers, {})\n        self.assertEqual(options.logger.data[0], 'stopped: notthere '\n                         '(terminated by SIGHUP)')\n        self.assertEqual(instance.exitstatus, -1)\n        self.assertEqual(len(L), 1)\n        event = L[0]\n        self.assertEqual(event.__class__, events.ProcessStateStoppedEvent)\n        self.assertEqual(event.extra_values, [('pid', 123)])\n        self.assertEqual(event.from_state, ProcessStates.STOPPING)\n\n    def test_finish_running_state_exit_expected(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'notthere', '/notthere',\n                              stdout_logfile='/tmp/foo')\n        instance = self._makeOne(config)\n        instance.config.options.pidhistory[123] = instance\n        pipes = {'stdout':'','stderr':''}\n        instance.pipes = pipes\n        instance.config.exitcodes =[-1]\n        from supervisor.states import ProcessStates\n        from supervisor import events\n        instance.state = ProcessStates.RUNNING\n        L = []\n        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))\n        instance.pid = 123\n        instance.finish(123, 1)\n        self.assertFalse(instance.killing)\n        self.assertEqual(instance.pid, 0)\n        self.assertEqual(options.parent_pipes_closed, pipes)\n        self.assertEqual(instance.pipes, {})\n        self.assertEqual(instance.dispatchers, {})\n        self.assertEqual(options.logger.data[0],\n                         'exited: notthere (terminated by SIGHUP; expected)')\n        self.assertEqual(instance.exitstatus, -1)\n        self.assertEqual(len(L), 1)\n        event = L[0]\n        self.assertEqual(event.__class__,\n                         events.ProcessStateExitedEvent)\n        self.assertEqual(event.expected, True)\n        self.assertEqual(event.extra_values, [('expected', True), ('pid', 123)])\n        self.assertEqual(event.from_state, ProcessStates.RUNNING)\n\n    def test_finish_starting_state_laststart_in_future(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'notthere', '/notthere',\n                              stdout_logfile='/tmp/foo')\n        instance = self._makeOne(config)\n        instance.config.options.pidhistory[123] = instance\n        pipes = {'stdout':'','stderr':''}\n        instance.pipes = pipes\n        instance.config.exitcodes =[-1]\n        instance.laststart = time.time() + 3600 # 1 hour into the future\n        from supervisor.states import ProcessStates\n        from supervisor import events\n        instance.state = ProcessStates.STARTING\n        L = []\n        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))\n        instance.pid = 123\n        instance.finish(123, 1)\n        self.assertFalse(instance.killing)\n        self.assertEqual(instance.pid, 0)\n        self.assertEqual(options.parent_pipes_closed, pipes)\n        self.assertEqual(instance.pipes, {})\n        self.assertEqual(instance.dispatchers, {})\n        self.assertEqual(options.logger.data[0],\n                         \"process 'notthere' (123) laststart time is in the \"\n                         \"future, don't know how long process was running so \"\n                         \"assuming it did not exit too quickly\")\n        self.assertEqual(options.logger.data[1],\n                         'exited: notthere (terminated by SIGHUP; expected)')\n        self.assertEqual(instance.exitstatus, -1)\n        self.assertEqual(len(L), 2)\n        event = L[0]\n        self.assertEqual(event.__class__, events.ProcessStateRunningEvent)\n        self.assertEqual(event.expected, True)\n        self.assertEqual(event.extra_values, [('pid', 123)])\n        self.assertEqual(event.from_state, ProcessStates.STARTING)\n        event = L[1]\n        self.assertEqual(event.__class__, events.ProcessStateExitedEvent)\n        self.assertEqual(event.expected, True)\n        self.assertEqual(event.extra_values, [('expected', True), ('pid', 123)])\n        self.assertEqual(event.from_state, ProcessStates.RUNNING)\n\n    def test_finish_starting_state_exited_too_quickly(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'notthere', '/notthere',\n                              stdout_logfile='/tmp/foo', startsecs=10)\n        instance = self._makeOne(config)\n        instance.config.options.pidhistory[123] = instance\n        pipes = {'stdout':'','stderr':''}\n        instance.pipes = pipes\n        instance.config.exitcodes =[-1]\n        instance.laststart = time.time()\n        from supervisor.states import ProcessStates\n        from supervisor import events\n        instance.state = ProcessStates.STARTING\n        L = []\n        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))\n        instance.pid = 123\n        instance.finish(123, 1)\n        self.assertFalse(instance.killing)\n        self.assertEqual(instance.pid, 0)\n        self.assertEqual(options.parent_pipes_closed, pipes)\n        self.assertEqual(instance.pipes, {})\n        self.assertEqual(instance.dispatchers, {})\n        self.assertEqual(options.logger.data[0],\n                      'exited: notthere (terminated by SIGHUP; not expected)')\n        self.assertEqual(instance.exitstatus, None)\n        self.assertEqual(len(L), 1)\n        event = L[0]\n        self.assertEqual(event.__class__, events.ProcessStateBackoffEvent)\n        self.assertEqual(event.from_state, ProcessStates.STARTING)\n\n    # This tests the case where the process has stayed alive longer than\n    # startsecs (i.e., long enough to enter the RUNNING state), however the\n    # system clock has since rolled backward such that the current time is\n    # greater than laststart but less than startsecs.\n    def test_finish_running_state_exited_too_quickly_due_to_clock_rollback(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'notthere', '/notthere',\n                              stdout_logfile='/tmp/foo', startsecs=10)\n        instance = self._makeOne(config)\n        instance.config.options.pidhistory[123] = instance\n        pipes = {'stdout':'','stderr':''}\n        instance.pipes = pipes\n        instance.config.exitcodes =[-1]\n        instance.laststart = time.time()\n        from supervisor.states import ProcessStates\n        from supervisor import events\n        instance.state = ProcessStates.RUNNING\n        L = []\n        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))\n        instance.pid = 123\n        instance.finish(123, 1)\n        self.assertFalse(instance.killing)\n        self.assertEqual(instance.pid, 0)\n        self.assertEqual(options.parent_pipes_closed, pipes)\n        self.assertEqual(instance.pipes, {})\n        self.assertEqual(instance.dispatchers, {})\n        self.assertEqual(options.logger.data[0],\n                         'exited: notthere (terminated by SIGHUP; expected)')\n        self.assertEqual(instance.exitstatus, -1)\n        self.assertEqual(len(L), 1)\n        event = L[0]\n        self.assertEqual(event.__class__,\n                         events.ProcessStateExitedEvent)\n        self.assertEqual(event.expected, True)\n        self.assertEqual(event.extra_values, [('expected', True), ('pid', 123)])\n        self.assertEqual(event.from_state, ProcessStates.RUNNING)\n\n    def test_finish_running_state_laststart_in_future(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'notthere', '/notthere',\n                              stdout_logfile='/tmp/foo')\n        instance = self._makeOne(config)\n        instance.config.options.pidhistory[123] = instance\n        pipes = {'stdout':'','stderr':''}\n        instance.pipes = pipes\n        instance.config.exitcodes =[-1]\n        instance.laststart = time.time() + 3600 # 1 hour into the future\n        from supervisor.states import ProcessStates\n        from supervisor import events\n        instance.state = ProcessStates.RUNNING\n        L = []\n        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))\n        instance.pid = 123\n        instance.finish(123, 1)\n        self.assertFalse(instance.killing)\n        self.assertEqual(instance.pid, 0)\n        self.assertEqual(options.parent_pipes_closed, pipes)\n        self.assertEqual(instance.pipes, {})\n        self.assertEqual(instance.dispatchers, {})\n        self.assertEqual(options.logger.data[0],\n                         \"process 'notthere' (123) laststart time is in the \"\n                         \"future, don't know how long process was running so \"\n                         \"assuming it did not exit too quickly\")\n        self.assertEqual(options.logger.data[1],\n                         'exited: notthere (terminated by SIGHUP; expected)')\n        self.assertEqual(instance.exitstatus, -1)\n        self.assertEqual(len(L), 1)\n        event = L[0]\n        self.assertEqual(event.__class__,\n                         events.ProcessStateExitedEvent)\n        self.assertEqual(event.expected, True)\n        self.assertEqual(event.extra_values, [('expected', True), ('pid', 123)])\n        self.assertEqual(event.from_state, ProcessStates.RUNNING)\n\n    def test_finish_with_current_event_sends_rejected(self):\n        from supervisor import events\n        L = []\n        events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))\n        events.subscribe(events.EventRejectedEvent, lambda x: L.append(x))\n        options = DummyOptions()\n        config = DummyPConfig(options, 'notthere', '/notthere',\n                              stdout_logfile='/tmp/foo', startsecs=10)\n        instance = self._makeOne(config)\n        from supervisor.states import ProcessStates\n        instance.state = ProcessStates.RUNNING\n        event = DummyEvent()\n        instance.event = event\n        instance.finish(123, 1)\n        self.assertEqual(len(L), 2)\n        event1, event2 = L\n        self.assertEqual(event1.__class__,\n                         events.ProcessStateExitedEvent)\n        self.assertEqual(event2.__class__, events.EventRejectedEvent)\n        self.assertEqual(event2.process, instance)\n        self.assertEqual(event2.event, event)\n        self.assertEqual(instance.event, None)\n\n    def test_set_uid_no_uid(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'test', '/test')\n        instance = self._makeOne(config)\n        instance.set_uid()\n        self.assertEqual(options.privsdropped, None)\n\n    def test_set_uid(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'test', '/test', uid=1)\n        instance = self._makeOne(config)\n        msg = instance.set_uid()\n        self.assertEqual(options.privsdropped, 1)\n        self.assertEqual(msg, None)\n\n    def test_cmp_bypriority(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'notthere', '/notthere',\n                              stdout_logfile='/tmp/foo',\n                              priority=1)\n        instance = self._makeOne(config)\n\n        config = DummyPConfig(options, 'notthere1', '/notthere',\n                              stdout_logfile='/tmp/foo',\n                              priority=2)\n        instance1 = self._makeOne(config)\n\n        config = DummyPConfig(options, 'notthere2', '/notthere',\n                              stdout_logfile='/tmp/foo',\n                              priority=3)\n        instance2 = self._makeOne(config)\n\n        L = [instance2, instance, instance1]\n        L.sort()\n\n        self.assertEqual(L, [instance, instance1, instance2])\n\n    def test_transition_stopped_to_starting_supervisor_stopping(self):\n        from supervisor import events\n        emitted_events = []\n        events.subscribe(events.ProcessStateEvent, emitted_events.append)\n        from supervisor.states import ProcessStates, SupervisorStates\n        options = DummyOptions()\n        options.mood = SupervisorStates.SHUTDOWN\n\n        # this should not be spawned, as supervisor is shutting down\n        pconfig = DummyPConfig(options, 'process', 'process','/bin/process')\n        process = self._makeOne(pconfig)\n        process.laststart = 0\n        process.state = ProcessStates.STOPPED\n        process.transition()\n        self.assertEqual(process.state, ProcessStates.STOPPED)\n        self.assertEqual(emitted_events, [])\n\n    def test_transition_stopped_to_starting_supervisor_running(self):\n        from supervisor import events\n        emitted_events_with_states = []\n        def subscriber(e):\n            emitted_events_with_states.append((e, e.process.state))\n        events.subscribe(events.ProcessStateEvent, subscriber)\n        from supervisor.states import ProcessStates, SupervisorStates\n        options = DummyOptions()\n        options.mood = SupervisorStates.RUNNING\n\n        pconfig = DummyPConfig(options, 'process', 'process','/bin/process')\n        process = self._makeOne(pconfig)\n        process.laststart = 0\n        process.state = ProcessStates.STOPPED\n        process.transition()\n        self.assertEqual(process.state, ProcessStates.STARTING)\n        self.assertEqual(len(emitted_events_with_states), 1)\n        event, state_when_event_emitted = emitted_events_with_states[0]\n        self.assertEqual(event.__class__, events.ProcessStateStartingEvent)\n        self.assertEqual(event.from_state, ProcessStates.STOPPED)\n        self.assertEqual(state_when_event_emitted, ProcessStates.STARTING)\n\n    def test_transition_exited_to_starting_supervisor_stopping(self):\n        from supervisor import events\n        emitted_events = []\n        events.subscribe(events.ProcessStateEvent, emitted_events.append)\n        from supervisor.states import ProcessStates, SupervisorStates\n        options = DummyOptions()\n        options.mood = SupervisorStates.SHUTDOWN\n\n        # this should not be spawned, as supervisor is shutting down\n        pconfig = DummyPConfig(options, 'process', 'process','/bin/process')\n        from supervisor.datatypes import RestartUnconditionally\n        pconfig.autorestart = RestartUnconditionally\n        process = self._makeOne(pconfig)\n        process.laststart = 1\n        process.system_stop = True\n        process.state = ProcessStates.EXITED\n        process.transition()\n        self.assertEqual(process.state, ProcessStates.EXITED)\n        self.assertTrue(process.system_stop)\n        self.assertEqual(emitted_events, [])\n\n    def test_transition_exited_to_starting_uncond_supervisor_running(self):\n        from supervisor import events\n        emitted_events_with_states = []\n        def subscriber(e):\n            emitted_events_with_states.append((e, e.process.state))\n        events.subscribe(events.ProcessStateEvent, subscriber)\n        from supervisor.states import ProcessStates\n        options = DummyOptions()\n\n        pconfig = DummyPConfig(options, 'process', 'process','/bin/process')\n        from supervisor.datatypes import RestartUnconditionally\n        pconfig.autorestart = RestartUnconditionally\n        process = self._makeOne(pconfig)\n        process.laststart = 1\n        process.state = ProcessStates.EXITED\n        process.transition()\n        self.assertEqual(process.state, ProcessStates.STARTING)\n        self.assertEqual(len(emitted_events_with_states), 1)\n        event, state_when_event_emitted = emitted_events_with_states[0]\n        self.assertEqual(event.__class__, events.ProcessStateStartingEvent)\n        self.assertEqual(event.from_state, ProcessStates.EXITED)\n        self.assertEqual(state_when_event_emitted, ProcessStates.STARTING)\n\n    def test_transition_exited_to_starting_condit_supervisor_running(self):\n        from supervisor import events\n        emitted_events_with_states = []\n        def subscriber(e):\n            emitted_events_with_states.append((e, e.process.state))\n        events.subscribe(events.ProcessStateEvent, subscriber)\n        from supervisor.states import ProcessStates\n        options = DummyOptions()\n\n        pconfig = DummyPConfig(options, 'process', 'process','/bin/process')\n        from supervisor.datatypes import RestartWhenExitUnexpected\n        pconfig.autorestart = RestartWhenExitUnexpected\n        process = self._makeOne(pconfig)\n        process.laststart = 1\n        process.state = ProcessStates.EXITED\n        process.exitstatus = 'bogus'\n        process.transition()\n        self.assertEqual(process.state, ProcessStates.STARTING)\n        self.assertEqual(len(emitted_events_with_states), 1)\n        event, state_when_event_emitted = emitted_events_with_states[0]\n        self.assertEqual(event.__class__, events.ProcessStateStartingEvent)\n        self.assertEqual(event.from_state, ProcessStates.EXITED)\n        self.assertEqual(state_when_event_emitted, ProcessStates.STARTING)\n\n    def test_transition_exited_to_starting_condit_fls_supervisor_running(self):\n        from supervisor import events\n        emitted_events = []\n        events.subscribe(events.ProcessStateEvent, emitted_events.append)\n        from supervisor.states import ProcessStates\n        options = DummyOptions()\n\n        pconfig = DummyPConfig(options, 'process', 'process','/bin/process')\n        from supervisor.datatypes import RestartWhenExitUnexpected\n        pconfig.autorestart = RestartWhenExitUnexpected\n        process = self._makeOne(pconfig)\n        process.laststart = 1\n        process.state = ProcessStates.EXITED\n        process.exitstatus = 0\n        process.transition()\n        self.assertEqual(process.state, ProcessStates.EXITED)\n        self.assertEqual(emitted_events, [])\n\n    def test_transition_backoff_to_starting_supervisor_stopping(self):\n        from supervisor import events\n        emitted_events = []\n        events.subscribe(events.ProcessStateEvent, lambda x: emitted_events.append(x))\n        from supervisor.states import ProcessStates, SupervisorStates\n        options = DummyOptions()\n        options.mood = SupervisorStates.SHUTDOWN\n\n        pconfig = DummyPConfig(options, 'process', 'process','/bin/process')\n        process = self._makeOne(pconfig)\n        process.laststart = 1\n        process.delay = 0\n        process.backoff = 0\n        process.state = ProcessStates.BACKOFF\n        process.transition()\n        self.assertEqual(process.state, ProcessStates.BACKOFF)\n        self.assertEqual(emitted_events, [])\n\n    def test_transition_backoff_to_starting_supervisor_running(self):\n        from supervisor import events\n        emitted_events_with_states = []\n        def subscriber(e):\n            emitted_events_with_states.append((e, e.process.state))\n        events.subscribe(events.ProcessStateEvent, subscriber)\n        from supervisor.states import ProcessStates, SupervisorStates\n        options = DummyOptions()\n        options.mood = SupervisorStates.RUNNING\n\n        pconfig = DummyPConfig(options, 'process', 'process','/bin/process')\n        process = self._makeOne(pconfig)\n        process.laststart = 1\n        process.delay = 0\n        process.backoff = 0\n        process.state = ProcessStates.BACKOFF\n        process.transition()\n        self.assertEqual(process.state, ProcessStates.STARTING)\n        self.assertEqual(len(emitted_events_with_states), 1)\n        event, state_when_event_emitted = emitted_events_with_states[0]\n        self.assertEqual(event.__class__, events.ProcessStateStartingEvent)\n        self.assertEqual(event.from_state, ProcessStates.BACKOFF)\n        self.assertEqual(state_when_event_emitted, ProcessStates.STARTING)\n\n    def test_transition_backoff_to_starting_supervisor_running_notyet(self):\n        from supervisor import events\n        emitted_events = []\n        events.subscribe(events.ProcessStateEvent, lambda x: emitted_events.append(x))\n        from supervisor.states import ProcessStates, SupervisorStates\n        options = DummyOptions()\n        options.mood = SupervisorStates.RUNNING\n\n        pconfig = DummyPConfig(options, 'process', 'process','/bin/process')\n        process = self._makeOne(pconfig)\n        process.laststart = 1\n        process.delay = maxint\n        process.backoff = 0\n        process.state = ProcessStates.BACKOFF\n        process.transition()\n        self.assertEqual(process.state, ProcessStates.BACKOFF)\n        self.assertEqual(emitted_events, [])\n\n    def test_transition_starting_to_running(self):\n        from supervisor import events\n        emitted_events_with_states = []\n        def subscriber(e):\n            emitted_events_with_states.append((e, e.process.state))\n        events.subscribe(events.ProcessStateEvent, subscriber)\n        from supervisor.states import ProcessStates\n\n        options = DummyOptions()\n\n        # this should go from STARTING to RUNNING via transition()\n        pconfig = DummyPConfig(options, 'process', 'process','/bin/process')\n        process = self._makeOne(pconfig)\n        process.backoff = 1\n        process.delay = 1\n        process.system_stop = False\n        process.laststart = 1\n        process.pid = 1\n        process.stdout_buffer = 'abc'\n        process.stderr_buffer = 'def'\n        process.state = ProcessStates.STARTING\n        process.transition()\n\n        # this implies RUNNING\n        self.assertEqual(process.backoff, 0)\n        self.assertEqual(process.delay, 0)\n        self.assertFalse(process.system_stop)\n        self.assertEqual(options.logger.data[0],\n                         'success: process entered RUNNING state, process has '\n                         'stayed up for > than 10 seconds (startsecs)')\n        self.assertEqual(len(emitted_events_with_states), 1)\n        event, state_when_event_emitted = emitted_events_with_states[0]\n        self.assertEqual(event.__class__, events.ProcessStateRunningEvent)\n        self.assertEqual(event.from_state, ProcessStates.STARTING)\n        self.assertEqual(state_when_event_emitted, ProcessStates.RUNNING)\n\n    def test_transition_starting_to_running_laststart_in_future(self):\n        from supervisor import events\n        emitted_events_with_states = []\n        def subscriber(e):\n            emitted_events_with_states.append((e, e.process.state))\n        events.subscribe(events.ProcessStateEvent, subscriber)\n        from supervisor.states import ProcessStates\n\n        future_time = time.time() + 3600 # 1 hour into the future\n        options = DummyOptions()\n        test_startsecs = 2\n\n        # this should go from STARTING to RUNNING via transition()\n        pconfig = DummyPConfig(options, 'process', 'process','/bin/process',\n                               startsecs=test_startsecs)\n        process = self._makeOne(pconfig)\n        process.backoff = 1\n        process.delay = 1\n        process.system_stop = False\n        process.laststart = future_time\n        process.pid = 1\n        process.stdout_buffer = 'abc'\n        process.stderr_buffer = 'def'\n        process.state = ProcessStates.STARTING\n\n        # This iteration of transition() should reset process.laststart\n        # to the current time\n        process.transition()\n\n        # Process state should still be STARTING\n        self.assertEqual(process.state, ProcessStates.STARTING)\n\n        # Ensure process.laststart has rolled backward\n        self.assertTrue(process.laststart < future_time)\n\n        # Sleep for (startsecs + 1)\n        time.sleep(test_startsecs + 1)\n\n        # This iteration of transition() should actually trigger the state\n        # transition to RUNNING\n        process.transition()\n\n        # this implies RUNNING\n        self.assertEqual(process.backoff, 0)\n        self.assertEqual(process.delay, 0)\n        self.assertFalse(process.system_stop)\n        self.assertEqual(process.state, ProcessStates.RUNNING)\n        self.assertEqual(options.logger.data[0],\n                         'success: process entered RUNNING state, process has '\n                         'stayed up for > than {} seconds (startsecs)'.format(test_startsecs))\n        self.assertEqual(len(emitted_events_with_states), 1)\n        event, state_when_event_emitted = emitted_events_with_states[0]\n        self.assertEqual(event.__class__, events.ProcessStateRunningEvent)\n        self.assertEqual(event.from_state, ProcessStates.STARTING)\n        self.assertEqual(state_when_event_emitted, ProcessStates.RUNNING)\n\n    def test_transition_backoff_to_starting_delay_in_future(self):\n        from supervisor import events\n        emitted_events_with_states = []\n        def subscriber(e):\n            emitted_events_with_states.append((e, e.process.state))\n        events.subscribe(events.ProcessStateEvent, subscriber)\n        from supervisor.states import ProcessStates\n\n        future_time = time.time() + 3600 # 1 hour into the future\n        options = DummyOptions()\n\n        pconfig = DummyPConfig(options, 'process', 'process','/bin/process')\n        process = self._makeOne(pconfig)\n        process.laststart = 1\n        process.delay = future_time\n        process.backoff = 0\n        process.state = ProcessStates.BACKOFF\n\n        # This iteration of transition() should reset process.delay\n        # to the current time\n        process.transition()\n\n        # Process state should still be BACKOFF\n        self.assertEqual(process.state, ProcessStates.BACKOFF)\n\n        # Ensure process.delay has rolled backward\n        self.assertTrue(process.delay < future_time)\n\n        # This iteration of transition() should actually trigger the state\n        # transition to STARTING\n        process.transition()\n\n        self.assertEqual(process.state, ProcessStates.STARTING)\n        self.assertEqual(len(emitted_events_with_states), 1)\n        event, state_when_event_emitted = emitted_events_with_states[0]\n        self.assertEqual(event.__class__, events.ProcessStateStartingEvent)\n        self.assertEqual(event.from_state, ProcessStates.BACKOFF)\n        self.assertEqual(state_when_event_emitted, ProcessStates.STARTING)\n\n    def test_transition_backoff_to_fatal(self):\n        from supervisor import events\n        emitted_events_with_states = []\n        def subscriber(e):\n            emitted_events_with_states.append((e, e.process.state))\n        events.subscribe(events.ProcessStateEvent, subscriber)\n        from supervisor.states import ProcessStates\n        options = DummyOptions()\n\n        # this should go from BACKOFF to FATAL via transition()\n        pconfig = DummyPConfig(options, 'process', 'process','/bin/process')\n        process = self._makeOne(pconfig)\n        process.laststart = 1\n        process.backoff = 10000\n        process.delay = 1\n        process.system_stop = False\n        process.stdout_buffer = 'abc'\n        process.stderr_buffer = 'def'\n        process.state = ProcessStates.BACKOFF\n\n        process.transition()\n\n        # this implies FATAL\n        self.assertEqual(process.backoff, 0)\n        self.assertEqual(process.delay, 0)\n        self.assertTrue(process.system_stop)\n        self.assertEqual(options.logger.data[0],\n                         'gave up: process entered FATAL state, too many start'\n                         ' retries too quickly')\n        self.assertEqual(len(emitted_events_with_states), 1)\n        event, state_when_event_emitted = emitted_events_with_states[0]\n        self.assertEqual(event.__class__, events.ProcessStateFatalEvent)\n        self.assertEqual(event.from_state, ProcessStates.BACKOFF)\n        self.assertEqual(state_when_event_emitted, ProcessStates.FATAL)\n\n    def test_transition_stops_unkillable_notyet(self):\n        from supervisor import events\n        emitted_events = []\n        events.subscribe(events.ProcessStateEvent, emitted_events.append)\n        from supervisor.states import ProcessStates\n        options = DummyOptions()\n\n        pconfig = DummyPConfig(options, 'process', 'process','/bin/process')\n        process = self._makeOne(pconfig)\n        process.delay = maxint\n        process.state = ProcessStates.STOPPING\n\n        process.transition()\n        self.assertEqual(process.state, ProcessStates.STOPPING)\n        self.assertEqual(emitted_events, [])\n\n    def test_transition_stops_unkillable(self):\n        from supervisor import events\n        emitted_events = []\n        events.subscribe(events.ProcessStateEvent, emitted_events.append)\n        from supervisor.states import ProcessStates\n        options = DummyOptions()\n\n        pconfig = DummyPConfig(options, 'process', 'process','/bin/process')\n        process = self._makeOne(pconfig)\n        process.delay = 0\n        process.pid = 1\n        process.killing = False\n        process.state = ProcessStates.STOPPING\n\n        process.transition()\n        self.assertTrue(process.killing)\n        self.assertNotEqual(process.delay, 0)\n        self.assertEqual(process.state, ProcessStates.STOPPING)\n        self.assertEqual(options.logger.data[0],\n                         \"killing 'process' (1) with SIGKILL\")\n        self.assertEqual(options.kills[1], signal.SIGKILL)\n        self.assertEqual(emitted_events, [])\n\n    def test_change_state_doesnt_notify_if_no_state_change(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'test', '/test')\n        instance = self._makeOne(config)\n        instance.state = 10\n        self.assertEqual(instance.change_state(10), False)\n\n    def test_change_state_sets_backoff_and_delay(self):\n        from supervisor.states import ProcessStates\n        options = DummyOptions()\n        config = DummyPConfig(options, 'test', '/test')\n        instance = self._makeOne(config)\n        instance.state = 10\n        instance.change_state(ProcessStates.BACKOFF)\n        self.assertEqual(instance.backoff, 1)\n        self.assertTrue(instance.delay > 0)\n\nclass FastCGISubprocessTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.process import FastCGISubprocess\n        return FastCGISubprocess\n\n    def _makeOne(self, *arg, **kw):\n        return self._getTargetClass()(*arg, **kw)\n\n    def tearDown(self):\n        from supervisor.events import clear\n        clear()\n\n    def test_no_group(self):\n        options = DummyOptions()\n        options.forkpid = 0\n        config = DummyPConfig(options, 'good', '/good/filename', uid=1)\n        instance = self._makeOne(config)\n        self.assertRaises(NotImplementedError, instance.spawn)\n\n    def test_no_socket_manager(self):\n        options = DummyOptions()\n        options.forkpid = 0\n        config = DummyPConfig(options, 'good', '/good/filename', uid=1)\n        instance = self._makeOne(config)\n        instance.group = DummyProcessGroup(DummyPGroupConfig(options))\n        self.assertRaises(NotImplementedError, instance.spawn)\n\n    def test_prepare_child_fds(self):\n        options = DummyOptions()\n        options.forkpid = 0\n        config = DummyPConfig(options, 'good', '/good/filename', uid=1)\n        instance = self._makeOne(config)\n        sock_config = DummySocketConfig(7)\n        gconfig = DummyFCGIGroupConfig(options, 'whatever', 999, None,\n                                       sock_config)\n        instance.group = DummyFCGIProcessGroup(gconfig)\n        result = instance.spawn()\n        self.assertEqual(result, None)\n        self.assertEqual(len(options.duped), 3)\n        self.assertEqual(options.duped[7], 0)\n        self.assertEqual(options.duped[instance.pipes['child_stdout']], 1)\n        self.assertEqual(options.duped[instance.pipes['child_stderr']], 2)\n        self.assertEqual(len(options.fds_closed), options.minfds - 3)\n\n    def test_prepare_child_fds_stderr_redirected(self):\n        options = DummyOptions()\n        options.forkpid = 0\n        config = DummyPConfig(options, 'good', '/good/filename', uid=1)\n        config.redirect_stderr = True\n        instance = self._makeOne(config)\n        sock_config = DummySocketConfig(13)\n        gconfig = DummyFCGIGroupConfig(options, 'whatever', 999, None,\n                                       sock_config)\n        instance.group = DummyFCGIProcessGroup(gconfig)\n        result = instance.spawn()\n        self.assertEqual(result, None)\n        self.assertEqual(len(options.duped), 2)\n        self.assertEqual(options.duped[13], 0)\n        self.assertEqual(len(options.fds_closed), options.minfds - 3)\n\n    def test_before_spawn_gets_socket_ref(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'good', '/good/filename', uid=1)\n        instance = self._makeOne(config)\n        sock_config = DummySocketConfig(7)\n        gconfig = DummyFCGIGroupConfig(options, 'whatever', 999, None,\n                                       sock_config)\n        instance.group = DummyFCGIProcessGroup(gconfig)\n        self.assertTrue(instance.fcgi_sock is None)\n        instance.before_spawn()\n        self.assertFalse(instance.fcgi_sock is None)\n\n    def test_after_finish_removes_socket_ref(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'good', '/good/filename', uid=1)\n        instance = self._makeOne(config)\n        instance.fcgi_sock = 'hello'\n        instance.after_finish()\n        self.assertTrue(instance.fcgi_sock is None)\n\n    #Patch Subprocess.finish() method for this test to verify override\n    @patch.object(Subprocess, 'finish', Mock(return_value=sentinel.finish_result))\n    def test_finish_override(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'good', '/good/filename', uid=1)\n        instance = self._makeOne(config)\n        instance.after_finish = Mock()\n        result = instance.finish(sentinel.pid, sentinel.sts)\n        self.assertEqual(sentinel.finish_result, result,\n                        'FastCGISubprocess.finish() did not pass thru result')\n        self.assertEqual(1, instance.after_finish.call_count,\n                            'FastCGISubprocess.after_finish() not called once')\n        finish_mock = Subprocess.finish\n        self.assertEqual(1, finish_mock.call_count,\n                            'Subprocess.finish() not called once')\n        pid_arg = finish_mock.call_args[0][1]\n        sts_arg = finish_mock.call_args[0][2]\n        self.assertEqual(sentinel.pid, pid_arg,\n                            'Subprocess.finish() pid arg was not passed')\n        self.assertEqual(sentinel.sts, sts_arg,\n                            'Subprocess.finish() sts arg was not passed')\n\n    #Patch Subprocess.spawn() method for this test to verify override\n    @patch.object(Subprocess, 'spawn', Mock(return_value=sentinel.ppid))\n    def test_spawn_override_success(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'good', '/good/filename', uid=1)\n        instance = self._makeOne(config)\n        instance.before_spawn = Mock()\n        result = instance.spawn()\n        self.assertEqual(sentinel.ppid, result,\n                        'FastCGISubprocess.spawn() did not pass thru result')\n        self.assertEqual(1, instance.before_spawn.call_count,\n                            'FastCGISubprocess.before_spawn() not called once')\n        spawn_mock = Subprocess.spawn\n        self.assertEqual(1, spawn_mock.call_count,\n                            'Subprocess.spawn() not called once')\n\n    #Patch Subprocess.spawn() method for this test to verify error handling\n    @patch.object(Subprocess, 'spawn', Mock(return_value=None))\n    def test_spawn_error(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'good', '/good/filename', uid=1)\n        instance = self._makeOne(config)\n        instance.before_spawn = Mock()\n        instance.fcgi_sock = 'nuke me on error'\n        result = instance.spawn()\n        self.assertEqual(None, result,\n                        'FastCGISubprocess.spawn() did return None on error')\n        self.assertEqual(1, instance.before_spawn.call_count,\n                            'FastCGISubprocess.before_spawn() not called once')\n        self.assertEqual(None, instance.fcgi_sock,\n                'FastCGISubprocess.spawn() did not remove sock ref on error')\n\nclass ProcessGroupBaseTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.process import ProcessGroupBase\n        return ProcessGroupBase\n\n    def _makeOne(self, *args, **kw):\n        return self._getTargetClass()(*args, **kw)\n\n    def test_get_unstopped_processes(self):\n        options = DummyOptions()\n        from supervisor.states import ProcessStates\n        pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')\n        process1 = DummyProcess(pconfig1, state=ProcessStates.STOPPING)\n        gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1])\n        group = self._makeOne(gconfig)\n        group.processes = { 'process1': process1 }\n        unstopped = group.get_unstopped_processes()\n        self.assertEqual(unstopped, [process1])\n\n    def test_before_remove(self):\n        options = DummyOptions()\n        from supervisor.states import ProcessStates\n        pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')\n        process1 = DummyProcess(pconfig1, state=ProcessStates.STOPPING)\n        gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1])\n        group = self._makeOne(gconfig)\n        group.processes = { 'process1': process1 }\n        group.before_remove()  # shouldn't raise\n\n    def test_stop_all(self):\n        from supervisor.states import ProcessStates\n        options = DummyOptions()\n\n        pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')\n        process1 = DummyProcess(pconfig1, state=ProcessStates.STOPPED)\n\n        pconfig2 = DummyPConfig(options, 'process2', 'process2','/bin/process2')\n        process2 = DummyProcess(pconfig2, state=ProcessStates.RUNNING)\n\n        pconfig3 = DummyPConfig(options, 'process3', 'process3','/bin/process3')\n        process3 = DummyProcess(pconfig3, state=ProcessStates.STARTING)\n        pconfig4 = DummyPConfig(options, 'process4', 'process4','/bin/process4')\n        process4 = DummyProcess(pconfig4, state=ProcessStates.BACKOFF)\n        process4.delay = 1000\n        process4.backoff = 10\n        gconfig = DummyPGroupConfig(\n            options,\n            pconfigs=[pconfig1, pconfig2, pconfig3, pconfig4])\n        group = self._makeOne(gconfig)\n        group.processes = {'process1': process1, 'process2': process2,\n                           'process3':process3, 'process4':process4}\n\n        group.stop_all()\n        self.assertEqual(process1.stop_called, False)\n        self.assertEqual(process2.stop_called, True)\n        self.assertEqual(process3.stop_called, True)\n        self.assertEqual(process4.stop_called, False)\n        self.assertEqual(process4.state, ProcessStates.FATAL)\n\n    def test_get_dispatchers(self):\n        options = DummyOptions()\n        from supervisor.states import ProcessStates\n        pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')\n        process1 = DummyProcess(pconfig1, state=ProcessStates.STOPPING)\n        process1.dispatchers = {4:None}\n        pconfig2 = DummyPConfig(options, 'process2', 'process2','/bin/process2')\n        process2 = DummyProcess(pconfig2, state=ProcessStates.STOPPING)\n        process2.dispatchers = {5:None}\n        gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1, pconfig2])\n        group = self._makeOne(gconfig)\n        group.processes = { 'process1': process1, 'process2': process2 }\n        result= group.get_dispatchers()\n        self.assertEqual(result, {4:None, 5:None})\n\n    def test_reopenlogs(self):\n        options = DummyOptions()\n        from supervisor.states import ProcessStates\n        pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')\n        process1 = DummyProcess(pconfig1, state=ProcessStates.STOPPING)\n        gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1])\n        group = self._makeOne(gconfig)\n        group.processes = {'process1': process1}\n        group.reopenlogs()\n        self.assertEqual(process1.logs_reopened, True)\n\n    def test_removelogs(self):\n        options = DummyOptions()\n        from supervisor.states import ProcessStates\n        pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')\n        process1 = DummyProcess(pconfig1, state=ProcessStates.STOPPING)\n        gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1])\n        group = self._makeOne(gconfig)\n        group.processes = {'process1': process1}\n        group.removelogs()\n        self.assertEqual(process1.logsremoved, True)\n\n    def test_ordering_and_comparison(self):\n        options = DummyOptions()\n        gconfig1 = DummyPGroupConfig(options)\n        group1 = self._makeOne(gconfig1)\n\n        gconfig2 = DummyPGroupConfig(options)\n        group2 = self._makeOne(gconfig2)\n\n        config3 = DummyPGroupConfig(options)\n        group3 = self._makeOne(config3)\n\n        group1.config.priority = 5\n        group2.config.priority = 1\n        group3.config.priority = 5\n\n        L = [group1, group2]\n        L.sort()\n\n        self.assertEqual(L, [group2, group1])\n        self.assertNotEqual(group1, group2)\n        self.assertEqual(group1, group3)\n\nclass ProcessGroupTests(ProcessGroupBaseTests):\n    def _getTargetClass(self):\n        from supervisor.process import ProcessGroup\n        return ProcessGroup\n\n    def test_repr(self):\n        options = DummyOptions()\n        gconfig = DummyPGroupConfig(options)\n        group = self._makeOne(gconfig)\n        s = repr(group)\n        self.assertTrue('supervisor.process.ProcessGroup' in s)\n        self.assertTrue(s.endswith('named whatever>'), s)\n\n    def test_transition(self):\n        options = DummyOptions()\n        from supervisor.states import ProcessStates\n        pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')\n        process1 = DummyProcess(pconfig1, state=ProcessStates.STOPPING)\n        gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1])\n        group = self._makeOne(gconfig)\n        group.processes = {'process1': process1}\n        group.transition()\n        self.assertEqual(process1.transitioned, True)\n\nclass FastCGIProcessGroupTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.process import FastCGIProcessGroup\n        return FastCGIProcessGroup\n\n    def _makeOne(self, config, **kwargs):\n        cls = self._getTargetClass()\n        return cls(config, **kwargs)\n\n    def test___init__without_socket_error(self):\n        options = DummyOptions()\n        gconfig = DummyPGroupConfig(options)\n        gconfig.socket_config = None\n        class DummySocketManager(object):\n            def __init__(self, config, logger): pass\n            def get_socket(self): pass\n        self._makeOne(gconfig, socketManager=DummySocketManager)\n        # doesn't fail with exception\n\n    def test___init__with_socket_error(self):\n        options = DummyOptions()\n        gconfig = DummyPGroupConfig(options)\n        gconfig.socket_config = None\n        class DummySocketManager(object):\n            def __init__(self, config, logger): pass\n            def get_socket(self):\n                raise KeyError(5)\n            def config(self):\n                return 'config'\n        self.assertRaises(\n            ValueError,\n            self._makeOne, gconfig, socketManager=DummySocketManager\n            )\n\nclass EventListenerPoolTests(ProcessGroupBaseTests):\n    def setUp(self):\n        from supervisor.events import clear\n        clear()\n\n    def tearDown(self):\n        from supervisor.events import clear\n        clear()\n\n    def _getTargetClass(self):\n        from supervisor.process import EventListenerPool\n        return EventListenerPool\n\n    def test_ctor(self):\n        options = DummyOptions()\n        gconfig = DummyPGroupConfig(options)\n        class EventType:\n            pass\n        gconfig.pool_events = (EventType,)\n        pool = self._makeOne(gconfig)\n        from supervisor import events\n        self.assertEqual(len(events.callbacks), 2)\n        self.assertEqual(events.callbacks[0],\n            (EventType, pool._acceptEvent))\n        self.assertEqual(events.callbacks[1],\n            (events.EventRejectedEvent, pool.handle_rejected))\n        self.assertEqual(pool.serial, -1)\n\n    def test_before_remove_unsubscribes_from_events(self):\n        options = DummyOptions()\n        gconfig = DummyPGroupConfig(options)\n        class EventType:\n            pass\n        gconfig.pool_events = (EventType,)\n        pool = self._makeOne(gconfig)\n        from supervisor import events\n        self.assertEqual(len(events.callbacks), 2)\n        pool.before_remove()\n        self.assertEqual(len(events.callbacks), 0)\n\n    def test__eventEnvelope(self):\n        options = DummyOptions()\n        options.identifier = 'thesupervisorname'\n        gconfig = DummyPGroupConfig(options)\n        gconfig.name = 'thepoolname'\n        pool = self._makeOne(gconfig)\n        from supervisor import events\n        result = pool._eventEnvelope(\n            events.EventTypes.PROCESS_COMMUNICATION_STDOUT, 80, 20, 'payload\\n')\n        header, payload = result.split('\\n', 1)\n        headers = header.split()\n        self.assertEqual(headers[0], 'ver:3.0')\n        self.assertEqual(headers[1], 'server:thesupervisorname')\n        self.assertEqual(headers[2], 'serial:80')\n        self.assertEqual(headers[3], 'pool:thepoolname')\n        self.assertEqual(headers[4], 'poolserial:20')\n        self.assertEqual(headers[5], 'eventname:PROCESS_COMMUNICATION_STDOUT')\n        self.assertEqual(headers[6], 'len:8')\n        self.assertEqual(payload, 'payload\\n')\n\n    def test_handle_rejected_no_overflow(self):\n        options = DummyOptions()\n        gconfig = DummyPGroupConfig(options)\n        pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')\n        process1 = DummyProcess(pconfig1)\n        gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1])\n        pool = self._makeOne(gconfig)\n        pool.processes = {'process1': process1}\n        pool.event_buffer = [None, None]\n        class DummyEvent1:\n            serial = 'abc'\n        class DummyEvent2:\n            process = process1\n            event = DummyEvent1()\n        dummyevent = DummyEvent2()\n        dummyevent.serial = 1\n        pool.handle_rejected(dummyevent)\n        self.assertEqual(pool.event_buffer, [dummyevent.event, None, None])\n\n    def test_handle_rejected_event_buffer_overflowed(self):\n        options = DummyOptions()\n        gconfig = DummyPGroupConfig(options)\n        pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')\n        process1 = DummyProcess(pconfig1)\n        gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1])\n        gconfig.buffer_size = 3\n        pool = self._makeOne(gconfig)\n        pool.processes = {'process1': process1}\n        class DummyEvent:\n            def __init__(self, serial):\n                self.serial = serial\n        class DummyRejectedEvent:\n            def __init__(self, serial):\n                self.process = process1\n                self.event = DummyEvent(serial)\n        event_a = DummyEvent('a')\n        event_b = DummyEvent('b')\n        event_c = DummyEvent('c')\n        rej_event = DummyRejectedEvent('rejected')\n        pool.event_buffer = [event_a, event_b, event_c]\n        pool.handle_rejected(rej_event)\n        serials = [ x.serial for x in pool.event_buffer ]\n        # we popped a, and we inserted the rejected event into the 1st pos\n        self.assertEqual(serials, ['rejected', 'b', 'c'])\n        self.assertEqual(pool.config.options.logger.data[0],\n            'pool whatever event buffer overflowed, discarding event a')\n\n    def test_dispatch_pipe_error(self):\n        options = DummyOptions()\n        gconfig = DummyPGroupConfig(options)\n        pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')\n        from supervisor.states import EventListenerStates\n        gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1])\n        pool = self._makeOne(gconfig)\n        process1 = pool.processes['process1']\n        process1.write_exception = OSError(errno.EPIPE,\n                                           os.strerror(errno.EPIPE))\n        process1.listener_state = EventListenerStates.READY\n        event = DummyEvent()\n        pool._acceptEvent(event)\n        pool.dispatch()\n        self.assertEqual(process1.listener_state, EventListenerStates.READY)\n        self.assertEqual(pool.event_buffer, [event])\n        self.assertEqual(options.logger.data[0],\n            'epipe occurred while sending event abc to listener '\n            'process1, listener state unchanged')\n        self.assertEqual(options.logger.data[1],\n            'rebuffering event abc for pool whatever (buf size=0, max=10)')\n\n    def test__acceptEvent_attaches_pool_serial_and_serial(self):\n        from supervisor.process import GlobalSerial\n        options = DummyOptions()\n        gconfig = DummyPGroupConfig(options)\n        pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')\n        gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1])\n        pool = self._makeOne(gconfig)\n        process1 = pool.processes['process1']\n        from supervisor.states import EventListenerStates\n        process1.listener_state = EventListenerStates.READY\n        event = DummyEvent(None)\n        pool._acceptEvent(event)\n        self.assertEqual(event.serial, GlobalSerial.serial)\n        self.assertEqual(event.pool_serials['whatever'], pool.serial)\n\n    def test_repr(self):\n        options = DummyOptions()\n        gconfig = DummyPGroupConfig(options)\n        pool = self._makeOne(gconfig)\n        s = repr(pool)\n        self.assertTrue('supervisor.process.EventListenerPool' in s)\n        self.assertTrue(s.endswith('named whatever>'))\n\n    def test_transition_nobody_ready(self):\n        options = DummyOptions()\n        from supervisor.states import ProcessStates\n        pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')\n        process1 = DummyProcess(pconfig1, state=ProcessStates.STARTING)\n        gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1])\n        pool = self._makeOne(gconfig)\n        pool.processes = {'process1': process1}\n        event = DummyEvent()\n        event.serial = 'a'\n        from supervisor.states import EventListenerStates\n        process1.listener_state = EventListenerStates.BUSY\n        pool._acceptEvent(event)\n        pool.transition()\n        self.assertEqual(process1.transitioned, True)\n        self.assertEqual(pool.event_buffer, [event])\n\n    def test_transition_event_proc_not_running(self):\n        options = DummyOptions()\n        from supervisor.states import ProcessStates\n        pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')\n        process1 = DummyProcess(pconfig1, state=ProcessStates.STARTING)\n        gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1])\n        pool = self._makeOne(gconfig)\n        pool.processes = {'process1': process1}\n        event = DummyEvent()\n        from supervisor.states import EventListenerStates\n        event.serial = 1\n        process1.listener_state = EventListenerStates.READY\n        pool._acceptEvent(event)\n        pool.transition()\n        self.assertEqual(process1.transitioned, True)\n        self.assertEqual(pool.event_buffer, [event])\n        self.assertEqual(process1.stdin_buffer, b'')\n        self.assertEqual(process1.listener_state, EventListenerStates.READY)\n\n    def test_transition_event_proc_running(self):\n        options = DummyOptions()\n        from supervisor.states import ProcessStates\n        pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')\n        process1 = DummyProcess(pconfig1, state=ProcessStates.RUNNING)\n        gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1])\n        pool = self._makeOne(gconfig)\n        pool.processes = {'process1': process1}\n        event = DummyEvent()\n        from supervisor.states import EventListenerStates\n        process1.listener_state = EventListenerStates.READY\n        class DummyGroup:\n            config = gconfig\n        process1.group = DummyGroup\n        pool._acceptEvent(event)\n        pool.transition()\n        self.assertEqual(process1.transitioned, True)\n        self.assertEqual(pool.event_buffer, [])\n        header, payload = process1.stdin_buffer.split(b'\\n', 1)\n        self.assertEqual(payload, b'dummy event', payload)\n        self.assertEqual(process1.listener_state, EventListenerStates.BUSY)\n        self.assertEqual(process1.event, event)\n\n    def test_transition_event_proc_running_with_dispatch_throttle_notyet(self):\n        options = DummyOptions()\n        from supervisor.states import ProcessStates\n        pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')\n        process1 = DummyProcess(pconfig1, state=ProcessStates.RUNNING)\n        gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1])\n        pool = self._makeOne(gconfig)\n        pool.dispatch_throttle = 5\n        pool.last_dispatch = time.time()\n        pool.processes = {'process1': process1}\n        event = DummyEvent()\n        from supervisor.states import EventListenerStates\n        process1.listener_state = EventListenerStates.READY\n        class DummyGroup:\n            config = gconfig\n        process1.group = DummyGroup\n        pool._acceptEvent(event)\n        pool.transition()\n        self.assertEqual(process1.transitioned, True)\n        self.assertEqual(pool.event_buffer, [event]) # not popped\n\n    def test_transition_event_proc_running_with_dispatch_throttle_ready(self):\n        options = DummyOptions()\n        from supervisor.states import ProcessStates\n        pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')\n        process1 = DummyProcess(pconfig1, state=ProcessStates.RUNNING)\n        gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1])\n        pool = self._makeOne(gconfig)\n        pool.dispatch_throttle = 5\n        pool.last_dispatch = time.time() - 1000\n        pool.processes = {'process1': process1}\n        event = DummyEvent()\n        from supervisor.states import EventListenerStates\n        process1.listener_state = EventListenerStates.READY\n        class DummyGroup:\n            config = gconfig\n        process1.group = DummyGroup\n        pool._acceptEvent(event)\n        pool.transition()\n        self.assertEqual(process1.transitioned, True)\n        self.assertEqual(pool.event_buffer, [])\n        header, payload = process1.stdin_buffer.split(b'\\n', 1)\n        self.assertEqual(payload, b'dummy event', payload)\n        self.assertEqual(process1.listener_state, EventListenerStates.BUSY)\n        self.assertEqual(process1.event, event)\n\n    def test_transition_event_proc_running_with_dispatch_throttle_last_dispatch_in_future(self):\n        future_time = time.time() + 3600 # 1 hour into the future\n        options = DummyOptions()\n        from supervisor.states import ProcessStates\n        pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')\n        process1 = DummyProcess(pconfig1, state=ProcessStates.RUNNING)\n        gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1])\n        pool = self._makeOne(gconfig)\n        pool.dispatch_throttle = 5\n        pool.last_dispatch = future_time\n        pool.processes = {'process1': process1}\n        event = DummyEvent()\n        from supervisor.states import EventListenerStates\n        process1.listener_state = EventListenerStates.READY\n        class DummyGroup:\n            config = gconfig\n        process1.group = DummyGroup\n        pool._acceptEvent(event)\n        pool.transition()\n\n        self.assertEqual(process1.transitioned, True)\n        self.assertEqual(pool.event_buffer, [event]) # not popped\n\n        # Ensure pool.last_dispatch has been rolled backward\n        self.assertTrue(pool.last_dispatch < future_time)\n\n    def test__dispatchEvent_notready(self):\n        options = DummyOptions()\n        from supervisor.states import ProcessStates\n        pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')\n        process1 = DummyProcess(pconfig1, state=ProcessStates.STOPPED)\n        gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1])\n        pool = self._makeOne(gconfig)\n        pool.processes = {'process1': process1}\n        event = DummyEvent()\n        pool._acceptEvent(event)\n        self.assertEqual(pool._dispatchEvent(event), False)\n\n    def test__dispatchEvent_proc_write_raises_non_EPIPE_OSError(self):\n        options = DummyOptions()\n        from supervisor.states import ProcessStates\n        pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')\n        process1 = DummyProcess(pconfig1, state=ProcessStates.RUNNING)\n        def raise_epipe(envelope):\n            raise OSError(errno.EAGAIN)\n        process1.write = raise_epipe\n        gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1])\n        pool = self._makeOne(gconfig)\n        pool.processes = {'process1': process1}\n        event = DummyEvent()\n        from supervisor.states import EventListenerStates\n        process1.listener_state = EventListenerStates.READY\n        class DummyGroup:\n            config = gconfig\n        process1.group = DummyGroup\n        pool._acceptEvent(event)\n        self.assertRaises(OSError, pool._dispatchEvent, event)\n\nclass test_new_serial(unittest.TestCase):\n    def _callFUT(self, inst):\n        from supervisor.process import new_serial\n        return new_serial(inst)\n\n    def test_inst_serial_is_maxint(self):\n        from supervisor.compat import maxint\n        class Inst(object):\n            def __init__(self):\n                self.serial = maxint\n        inst = Inst()\n        result = self._callFUT(inst)\n        self.assertEqual(inst.serial, 0)\n        self.assertEqual(result, 0)\n\n    def test_inst_serial_is_not_maxint(self):\n        class Inst(object):\n            def __init__(self):\n                self.serial = 1\n        inst = Inst()\n        result = self._callFUT(inst)\n        self.assertEqual(inst.serial, 2)\n        self.assertEqual(result, 2)\n"
  },
  {
    "path": "supervisor/tests/test_rpcinterfaces.py",
    "content": "# -*- coding: utf-8 -*-\nimport unittest\nimport operator\nimport os\nimport time\nimport errno\n\nfrom supervisor.tests.base import DummyOptions\nfrom supervisor.tests.base import DummySupervisor\nfrom supervisor.tests.base import DummyProcess\nfrom supervisor.tests.base import DummyPConfig\nfrom supervisor.tests.base import DummyPGroupConfig\nfrom supervisor.tests.base import DummyProcessGroup\nfrom supervisor.tests.base import PopulatedDummySupervisor\nfrom supervisor.tests.base import _NOW\nfrom supervisor.tests.base import _TIMEFORMAT\n\nfrom supervisor.compat import as_string, PY2\nfrom supervisor.datatypes import Automatic\nfrom supervisor.datatypes import RestartWhenExitUnexpected\n\nclass TestBase(unittest.TestCase):\n    def setUp(self):\n        pass\n\n    def tearDown(self):\n        pass\n\n    def _assertRPCError(self, code, callable, *args, **kw):\n        from supervisor import xmlrpc\n        try:\n            callable(*args, **kw)\n        except xmlrpc.RPCError as inst:\n            self.assertEqual(inst.code, code)\n        else:\n            raise AssertionError(\"Didn't raise\")\n\nclass MainXMLRPCInterfaceTests(TestBase):\n\n    def _getTargetClass(self):\n        from supervisor import xmlrpc\n        return xmlrpc.RootRPCInterface\n\n    def _makeOne(self, *args, **kw):\n        return self._getTargetClass()(*args, **kw)\n\n    def test_ctor(self):\n        interface = self._makeOne([('supervisor', None)])\n        self.assertEqual(interface.supervisor, None)\n\n    def test_traverse(self):\n        dummy = DummyRPCInterface()\n        interface = self._makeOne([('dummy', dummy)])\n        from supervisor import xmlrpc\n        self._assertRPCError(xmlrpc.Faults.UNKNOWN_METHOD,\n                             xmlrpc.traverse, interface, 'notthere.hello', [])\n        self._assertRPCError(xmlrpc.Faults.UNKNOWN_METHOD,\n                             xmlrpc.traverse, interface,\n                             'supervisor._readFile', [])\n        self._assertRPCError(xmlrpc.Faults.INCORRECT_PARAMETERS,\n                             xmlrpc.traverse, interface,\n                             'dummy.hello', [1])\n        self.assertEqual(xmlrpc.traverse(\n            interface, 'dummy.hello', []), 'Hello!')\n\nclass SupervisorNamespaceXMLRPCInterfaceTests(TestBase):\n    def _getTargetClass(self):\n        from supervisor import rpcinterface\n        return rpcinterface.SupervisorNamespaceRPCInterface\n\n    def _makeOne(self, *args, **kw):\n        return self._getTargetClass()(*args, **kw)\n\n    def test_update(self):\n        from supervisor import xmlrpc\n        from supervisor.supervisord import SupervisorStates\n        supervisord = DummySupervisor()\n        interface = self._makeOne(supervisord)\n        interface._update('foo')\n        self.assertEqual(interface.update_text, 'foo')\n        supervisord.options.mood = SupervisorStates.SHUTDOWN\n        self._assertRPCError(xmlrpc.Faults.SHUTDOWN_STATE, interface._update,\n                             'foo')\n\n    def test_getAPIVersion(self):\n        from supervisor import rpcinterface\n        supervisord = DummySupervisor()\n        interface = self._makeOne(supervisord)\n        version = interface.getAPIVersion()\n        self.assertEqual(version, rpcinterface.API_VERSION)\n        self.assertEqual(interface.update_text, 'getAPIVersion')\n\n    def test_getAPIVersion_aliased_to_deprecated_getVersion(self):\n        supervisord = DummySupervisor()\n        interface = self._makeOne(supervisord)\n        self.assertEqual(interface.getAPIVersion, interface.getVersion)\n\n    def test_getSupervisorVersion(self):\n        supervisord = DummySupervisor()\n        interface = self._makeOne(supervisord)\n        version = interface.getSupervisorVersion()\n        from supervisor import options\n        self.assertEqual(version, options.VERSION)\n        self.assertEqual(interface.update_text, 'getSupervisorVersion')\n\n\n    def test_getIdentification(self):\n        supervisord = DummySupervisor()\n        interface = self._makeOne(supervisord)\n        identifier = interface.getIdentification()\n        self.assertEqual(identifier, supervisord.options.identifier)\n        self.assertEqual(interface.update_text, 'getIdentification')\n\n    def test_getState(self):\n        from supervisor.states import getSupervisorStateDescription\n        supervisord = DummySupervisor()\n        interface = self._makeOne(supervisord)\n        stateinfo = interface.getState()\n        statecode = supervisord.options.mood\n        statename = getSupervisorStateDescription(statecode)\n        self.assertEqual(stateinfo['statecode'], statecode)\n        self.assertEqual(stateinfo['statename'], statename)\n        self.assertEqual(interface.update_text, 'getState')\n\n    def test_getPID(self):\n        options = DummyOptions()\n        supervisord = DummySupervisor(options)\n        interface = self._makeOne(supervisord)\n        self.assertEqual(interface.getPID(), options.get_pid())\n        self.assertEqual(interface.update_text, 'getPID')\n\n    def test_readLog_aliased_to_deprecated_readMainLog(self):\n        supervisord = DummySupervisor()\n        interface = self._makeOne(supervisord)\n        self.assertEqual(interface.readMainLog, interface.readLog)\n\n    def test_readLog_unreadable(self):\n        from supervisor import xmlrpc\n        supervisord = DummySupervisor()\n        interface = self._makeOne(supervisord)\n        self._assertRPCError(xmlrpc.Faults.NO_FILE, interface.readLog,\n                             offset=0, length=1)\n\n    def test_readLog_badargs(self):\n        from supervisor import xmlrpc\n        supervisord = DummySupervisor()\n        interface = self._makeOne(supervisord)\n        try:\n            logfile = supervisord.options.logfile\n            with open(logfile, 'w+') as f:\n                f.write('x' * 2048)\n            self._assertRPCError(xmlrpc.Faults.BAD_ARGUMENTS,\n                                 interface.readLog, offset=-1, length=1)\n            self._assertRPCError(xmlrpc.Faults.BAD_ARGUMENTS,\n                                 interface.readLog, offset=-1,\n                                 length=-1)\n        finally:\n            os.remove(logfile)\n\n    def test_readLog(self):\n        supervisord = DummySupervisor()\n        interface = self._makeOne(supervisord)\n        logfile = supervisord.options.logfile\n        try:\n            with open(logfile, 'w+') as f:\n                f.write('x' * 2048)\n                f.write('y' * 2048)\n            data = interface.readLog(offset=0, length=0)\n            self.assertEqual(interface.update_text, 'readLog')\n            self.assertEqual(data, ('x' * 2048) + ('y' * 2048))\n            data = interface.readLog(offset=2048, length=0)\n            self.assertEqual(data, 'y' * 2048)\n            data = interface.readLog(offset=0, length=2048)\n            self.assertEqual(data, 'x' * 2048)\n            data = interface.readLog(offset=-4, length=0)\n            self.assertEqual(data, 'y' * 4)\n        finally:\n            os.remove(logfile)\n\n    def test_clearLog_unreadable(self):\n        from supervisor import xmlrpc\n        supervisord = DummySupervisor()\n        interface = self._makeOne(supervisord)\n        self._assertRPCError(xmlrpc.Faults.NO_FILE, interface.clearLog)\n\n    def test_clearLog_unremoveable(self):\n        from supervisor import xmlrpc\n        options = DummyOptions()\n        options.existing = [options.logfile]\n        options.remove_exception = OSError(errno.EPERM,\n                                           os.strerror(errno.EPERM))\n        supervisord = DummySupervisor(options)\n        interface = self._makeOne(supervisord)\n        self.assertRaises(xmlrpc.RPCError, interface.clearLog)\n        self.assertEqual(interface.update_text, 'clearLog')\n\n    def test_clearLog(self):\n        options = DummyOptions()\n        options.existing = [options.logfile]\n        supervisord = DummySupervisor(options)\n        interface = self._makeOne(supervisord)\n        result = interface.clearLog()\n        self.assertEqual(interface.update_text, 'clearLog')\n        self.assertEqual(result, True)\n        self.assertEqual(options.removed[0], options.logfile)\n        for handler in supervisord.options.logger.handlers:\n            self.assertEqual(handler.reopened, True)\n\n    def test_shutdown(self):\n        supervisord = DummySupervisor()\n        interface = self._makeOne(supervisord)\n        value = interface.shutdown()\n        self.assertEqual(value, True)\n        self.assertEqual(supervisord.options.mood, -1)\n\n    def test_restart(self):\n        supervisord = DummySupervisor()\n        interface = self._makeOne(supervisord)\n        value = interface.restart()\n        self.assertEqual(value, True)\n        self.assertEqual(supervisord.options.mood, 0)\n\n    def test_reloadConfig(self):\n        options = DummyOptions()\n        supervisord = DummySupervisor(options)\n        interface = self._makeOne(supervisord)\n\n        changes = [ [DummyPGroupConfig(options, 'added')],\n                    [DummyPGroupConfig(options, 'changed')],\n                    [DummyPGroupConfig(options, 'dropped')] ]\n\n        supervisord.diff_to_active = lambda : changes\n\n        value = interface.reloadConfig()\n        self.assertEqual(value, [[['added'], ['changed'], ['dropped']]])\n\n    def test_reloadConfig_process_config_raises_ValueError(self):\n        from supervisor import xmlrpc\n        options = DummyOptions()\n        def raise_exc(*arg, **kw):\n            raise ValueError('foo')\n        options.process_config = raise_exc\n        supervisord = DummySupervisor(options)\n        interface = self._makeOne(supervisord)\n        self._assertRPCError(xmlrpc.Faults.CANT_REREAD, interface.reloadConfig)\n\n    def test_addProcessGroup(self):\n        from supervisor.supervisord import Supervisor\n        from supervisor import xmlrpc\n        options = DummyOptions()\n        supervisord = Supervisor(options)\n        pconfig = DummyPConfig(options, 'foo', __file__, autostart=False)\n        gconfig = DummyPGroupConfig(options, 'group1', pconfigs=[pconfig])\n        supervisord.options.process_group_configs = [gconfig]\n\n        interface = self._makeOne(supervisord)\n\n        result = interface.addProcessGroup('group1')\n        self.assertTrue(result)\n        self.assertEqual(list(supervisord.process_groups.keys()), ['group1'])\n\n        self._assertRPCError(xmlrpc.Faults.ALREADY_ADDED,\n                             interface.addProcessGroup, 'group1')\n        self.assertEqual(list(supervisord.process_groups.keys()), ['group1'])\n\n        self._assertRPCError(xmlrpc.Faults.BAD_NAME,\n                             interface.addProcessGroup, 'asdf')\n        self.assertEqual(list(supervisord.process_groups.keys()), ['group1'])\n\n    def test_removeProcessGroup(self):\n        from supervisor.supervisord import Supervisor\n        options = DummyOptions()\n        supervisord = Supervisor(options)\n        pconfig = DummyPConfig(options, 'foo', __file__, autostart=False)\n        gconfig = DummyPGroupConfig(options, 'group1', pconfigs=[pconfig])\n        supervisord.options.process_group_configs = [gconfig]\n\n        interface = self._makeOne(supervisord)\n\n        interface.addProcessGroup('group1')\n        result = interface.removeProcessGroup('group1')\n        self.assertTrue(result)\n        self.assertEqual(list(supervisord.process_groups.keys()), [])\n\n    def test_removeProcessGroup_bad_name(self):\n        from supervisor.supervisord import Supervisor\n        from supervisor import xmlrpc\n        options = DummyOptions()\n        supervisord = Supervisor(options)\n        pconfig = DummyPConfig(options, 'foo', __file__, autostart=False)\n        gconfig = DummyPGroupConfig(options, 'group1', pconfigs=[pconfig])\n        supervisord.options.process_group_configs = [gconfig]\n\n        interface = self._makeOne(supervisord)\n\n        self._assertRPCError(xmlrpc.Faults.BAD_NAME,\n                             interface.removeProcessGroup, 'asdf')\n\n    def test_removeProcessGroup_still_running(self):\n        from supervisor.supervisord import Supervisor\n        from supervisor import xmlrpc\n        options = DummyOptions()\n        supervisord = Supervisor(options)\n        pconfig = DummyPConfig(options, 'foo', __file__, autostart=False)\n        gconfig = DummyPGroupConfig(options, 'group1', pconfigs=[pconfig])\n        supervisord.options.process_group_configs = [gconfig]\n        process = DummyProcessGroup(gconfig)\n        process.unstopped_processes = [123]\n        supervisord.process_groups = {'group1':process}\n        interface = self._makeOne(supervisord)\n        self._assertRPCError(xmlrpc.Faults.STILL_RUNNING,\n                             interface.removeProcessGroup, 'group1')\n\n    def test_startProcess_already_started(self):\n        from supervisor import xmlrpc\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', __file__, autostart=False)\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        supervisord.set_procattr('foo', 'pid', 10)\n        interface = self._makeOne(supervisord)\n        self._assertRPCError(\n            xmlrpc.Faults.ALREADY_STARTED,\n            interface.startProcess, 'foo'\n            )\n\n    def test_startProcess_unknown_state(self):\n        from supervisor import xmlrpc\n        from supervisor.states import ProcessStates\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', __file__, autostart=False)\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        supervisord.set_procattr('foo', 'pid', 10)\n        supervisord.set_procattr('foo', 'state', ProcessStates.UNKNOWN)\n        interface = self._makeOne(supervisord)\n        self._assertRPCError(\n            xmlrpc.Faults.FAILED,\n            interface.startProcess, 'foo'\n            )\n        process = supervisord.process_groups['foo'].processes['foo']\n        self.assertEqual(process.spawned, False)\n\n    def test_startProcess_bad_group_name(self):\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', __file__, autostart=False)\n        supervisord = PopulatedDummySupervisor(options, 'group1', pconfig)\n        interface = self._makeOne(supervisord)\n        from supervisor import xmlrpc\n        self._assertRPCError(xmlrpc.Faults.BAD_NAME,\n                             interface.startProcess, 'group2:foo')\n\n    def test_startProcess_bad_process_name(self):\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', __file__, autostart=False)\n        supervisord = PopulatedDummySupervisor(options, 'group1', pconfig)\n        interface = self._makeOne(supervisord)\n        from supervisor import xmlrpc\n        self._assertRPCError(xmlrpc.Faults.BAD_NAME,\n                             interface.startProcess, 'group1:bar')\n\n    def test_startProcess_file_not_found(self):\n        options = DummyOptions()\n        pconfig  = DummyPConfig(options, 'foo', '/foo/bar', autostart=False)\n        from supervisor.options import NotFound\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        process = supervisord.process_groups['foo'].processes['foo']\n        process.execv_arg_exception = NotFound\n        interface = self._makeOne(supervisord)\n        from supervisor import xmlrpc\n        self._assertRPCError(xmlrpc.Faults.NO_FILE,\n                             interface.startProcess, 'foo')\n\n    def test_startProcess_bad_command(self):\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', '/foo/bar', autostart=False)\n        from supervisor.options import BadCommand\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        process = supervisord.process_groups['foo'].processes['foo']\n        process.execv_arg_exception = BadCommand\n        interface = self._makeOne(supervisord)\n        from supervisor import xmlrpc\n        self._assertRPCError(xmlrpc.Faults.NOT_EXECUTABLE,\n                             interface.startProcess, 'foo')\n\n    def test_startProcess_file_not_executable(self):\n        options = DummyOptions()\n        pconfig  = DummyPConfig(options, 'foo', '/foo/bar', autostart=False)\n        from supervisor.options import NotExecutable\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        process = supervisord.process_groups['foo'].processes['foo']\n        process.execv_arg_exception = NotExecutable\n        interface = self._makeOne(supervisord)\n        from supervisor import xmlrpc\n        self._assertRPCError(xmlrpc.Faults.NOT_EXECUTABLE,\n                             interface.startProcess, 'foo')\n\n    def test_startProcess_spawnerr(self):\n        from supervisor import xmlrpc\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', __file__, autostart=False)\n        from supervisor.process import ProcessStates\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        supervisord.set_procattr('foo', 'state', ProcessStates.STOPPED)\n        process = supervisord.process_groups['foo'].processes['foo']\n        process.spawnerr = 'abc'\n        interface = self._makeOne(supervisord)\n        self._assertRPCError(\n            xmlrpc.Faults.SPAWN_ERROR,\n            interface.startProcess,\n            'foo'\n            )\n\n    def test_startProcess(self):\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', __file__, autostart=False,\n                               startsecs=.01)\n        from supervisor.process import ProcessStates\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        supervisord.set_procattr('foo', 'state', ProcessStates.STOPPED)\n        interface = self._makeOne(supervisord)\n        result = interface.startProcess('foo')\n        process = supervisord.process_groups['foo'].processes['foo']\n        self.assertEqual(process.spawned, True)\n        self.assertEqual(interface.update_text, 'startProcess')\n        process.state = ProcessStates.RUNNING\n        self.assertEqual(result, True)\n\n    def test_startProcess_spawnerr_in_onwait(self):\n        from supervisor import xmlrpc\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', __file__, autostart=False)\n        from supervisor.process import ProcessStates\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        supervisord.set_procattr('foo', 'state', ProcessStates.STOPPED)\n        process = supervisord.process_groups['foo'].processes['foo']\n        def spawn():\n            process.spawned = True\n            process.state = ProcessStates.STARTING\n        def transition():\n            process.spawnerr = 'abc'\n        process.spawn = spawn\n        process.transition = transition\n        interface = self._makeOne(supervisord)\n        callback = interface.startProcess('foo')\n        self._assertRPCError(xmlrpc.Faults.SPAWN_ERROR, callback)\n\n    def test_startProcess_success_in_onwait(self):\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', __file__, autostart=False)\n        from supervisor.process import ProcessStates\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        supervisord.set_procattr('foo', 'state', ProcessStates.STOPPED)\n        process = supervisord.process_groups['foo'].processes['foo']\n        def spawn():\n            process.spawned = True\n            process.state = ProcessStates.STARTING\n        process.spawn = spawn\n        interface = self._makeOne(supervisord)\n        callback = interface.startProcess('foo')\n        process.state = ProcessStates.RUNNING\n        self.assertEqual(callback(), True)\n\n    def test_startProcess_nowait(self):\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', __file__, autostart=False)\n        from supervisor.process import ProcessStates\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        supervisord.set_procattr('foo', 'state', ProcessStates.STOPPED)\n        interface = self._makeOne(supervisord)\n        result = interface.startProcess('foo', wait=False)\n        self.assertEqual(result, True)\n        process = supervisord.process_groups['foo'].processes['foo']\n        self.assertEqual(process.spawned, True)\n        self.assertEqual(interface.update_text, 'startProcess')\n\n    def test_startProcess_abnormal_term_process_not_running(self):\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', __file__, autostart=False)\n        from supervisor.process import ProcessStates\n        from supervisor import http\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        supervisord.set_procattr('foo', 'state', ProcessStates.STOPPED)\n        interface = self._makeOne(supervisord)\n        process = supervisord.process_groups['foo'].processes['foo']\n        def spawn():\n            process.spawned = True\n            process.state = ProcessStates.STARTING\n        process.spawn = spawn\n        callback = interface.startProcess('foo', 100) # milliseconds\n        result = callback()\n        self.assertEqual(result, http.NOT_DONE_YET)\n        self.assertEqual(process.spawned, True)\n        self.assertEqual(interface.update_text, 'startProcess')\n        process.state = ProcessStates.BACKOFF\n        from supervisor import xmlrpc\n        self._assertRPCError(xmlrpc.Faults.ABNORMAL_TERMINATION, callback)\n\n    def test_startProcess_splat_calls_startProcessGroup(self):\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', __file__, autostart=False,\n                               startsecs=.01)\n        pconfig2 = DummyPConfig(options, 'process2', __file__, priority=2,\n                                startsecs=.01)\n        supervisord = PopulatedDummySupervisor(options, 'foo',\n                                               pconfig1, pconfig2)\n        from supervisor.process import ProcessStates\n        supervisord.set_procattr('process1', 'state', ProcessStates.STOPPED)\n        supervisord.set_procattr('process2', 'state', ProcessStates.STOPPED)\n        interface = self._makeOne(supervisord)\n        interface.startProcess('foo:*')\n        self.assertEqual(interface.update_text, 'startProcessGroup')\n\n    def test_startProcessGroup(self):\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', __file__, priority=1,\n                                startsecs=.01)\n        pconfig2 = DummyPConfig(options, 'process2', __file__, priority=2,\n                                startsecs=.01)\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig1,\n                                               pconfig2)\n        from supervisor.process import ProcessStates\n        from supervisor.xmlrpc import Faults\n        supervisord.set_procattr('process1', 'state', ProcessStates.STOPPED)\n        supervisord.set_procattr('process2', 'state', ProcessStates.STOPPED)\n        interface = self._makeOne(supervisord)\n        callback = interface.startProcessGroup('foo')\n\n        self.assertEqual(\n            callback(),\n            [{'group': 'foo',\n              'status': Faults.SUCCESS,\n              'description': 'OK',\n              'name': 'process1'},\n             {'group': 'foo',\n              'status': Faults.SUCCESS,\n              'description': 'OK',\n              'name': 'process2'}\n             ]\n            )\n        self.assertEqual(interface.update_text, 'startProcess')\n\n        process1 = supervisord.process_groups['foo'].processes['process1']\n        self.assertEqual(process1.spawned, True)\n        process2 = supervisord.process_groups['foo'].processes['process2']\n        self.assertEqual(process2.spawned, True)\n\n    def test_startProcessGroup_nowait(self):\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', __file__, priority=1,\n                                startsecs=.01)\n        pconfig2 = DummyPConfig(options, 'process2', __file__, priority=2,\n                                startsecs=.01)\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig1,\n                                               pconfig2)\n        from supervisor.process import ProcessStates\n        supervisord.set_procattr('process1', 'state', ProcessStates.STOPPED)\n        supervisord.set_procattr('process2', 'state', ProcessStates.STOPPED)\n        interface = self._makeOne(supervisord)\n        callback = interface.startProcessGroup('foo', wait=False)\n        from supervisor.xmlrpc import Faults\n\n        self.assertEqual(\n            callback(),\n            [{'group': 'foo',\n              'status': Faults.SUCCESS,\n              'description': 'OK',\n              'name': 'process1'},\n             {'group': 'foo',\n              'status': Faults.SUCCESS,\n              'description': 'OK',\n              'name': 'process2'}\n             ]\n            )\n\n    def test_startProcessGroup_badname(self):\n        from supervisor import xmlrpc\n        supervisord = DummySupervisor()\n        interface = self._makeOne(supervisord)\n        self._assertRPCError(xmlrpc.Faults.BAD_NAME,\n                             interface.startProcessGroup, 'foo')\n\n\n    def test_startAllProcesses(self):\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', __file__, priority=1,\n                                startsecs=.01)\n        pconfig2 = DummyPConfig(options, 'process2', __file__, priority=2,\n                                startsecs=.01)\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig1,\n                                               pconfig2)\n        from supervisor.process import ProcessStates\n        supervisord.set_procattr('process1', 'state', ProcessStates.STOPPED)\n        supervisord.set_procattr('process2', 'state', ProcessStates.STOPPED)\n        interface = self._makeOne(supervisord)\n        callback = interface.startAllProcesses()\n\n        from supervisor.xmlrpc import Faults\n\n        self.assertEqual(\n            callback(),\n            [{'group': 'foo',\n              'status': Faults.SUCCESS,\n              'description': 'OK',\n              'name': 'process1'},\n             {'group': 'foo',\n              'status': Faults.SUCCESS,\n              'description': 'OK',\n              'name': 'process2'}\n             ]\n            )\n\n        self.assertEqual(interface.update_text, 'startProcess')\n\n        process1 = supervisord.process_groups['foo'].processes['process1']\n        self.assertEqual(process1.spawned, True)\n        process2 = supervisord.process_groups['foo'].processes['process2']\n        self.assertEqual(process2.spawned, True)\n\n    def test_startAllProcesses_nowait(self):\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', __file__, priority=1,\n                                startsecs=.01)\n        pconfig2 = DummyPConfig(options, 'process2', __file__, priority=2,\n                                startsecs=.01)\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig1,\n                                               pconfig2)\n        from supervisor.process import ProcessStates\n        supervisord.set_procattr('process1', 'state', ProcessStates.STOPPED)\n        supervisord.set_procattr('process2', 'state', ProcessStates.STOPPED)\n        interface = self._makeOne(supervisord)\n        callback = interface.startAllProcesses(wait=False)\n        from supervisor.xmlrpc import Faults\n\n        self.assertEqual(\n            callback(),\n            [{'group': 'foo',\n              'status': Faults.SUCCESS,\n              'description': 'OK',\n              'name': 'process1'},\n             {'group': 'foo',\n              'status': Faults.SUCCESS,\n              'description': 'OK',\n              'name': 'process2'}\n             ]\n            )\n\n    def test_stopProcess(self):\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo')\n        from supervisor.process import ProcessStates\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        supervisord.set_procattr('foo', 'state', ProcessStates.RUNNING)\n        interface = self._makeOne(supervisord)\n        result = interface.stopProcess('foo')\n        self.assertTrue(result)\n        self.assertEqual(interface.update_text, 'stopProcess')\n        process = supervisord.process_groups['foo'].processes['foo']\n        self.assertEqual(process.backoff, 0)\n        self.assertEqual(process.delay, 0)\n        self.assertFalse(process.killing)\n        self.assertEqual(process.state, ProcessStates.STOPPED)\n        self.assertTrue(process.stop_report_called)\n        self.assertEqual(len(supervisord.process_groups['foo'].processes), 1)\n        self.assertEqual(interface.update_text, 'stopProcess')\n\n    def test_stopProcess_nowait(self):\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', __file__)\n        from supervisor.process import ProcessStates\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        supervisord.set_procattr('foo', 'state', ProcessStates.RUNNING)\n        interface = self._makeOne(supervisord)\n        result = interface.stopProcess('foo', wait=False)\n        self.assertEqual(result, True)\n        process = supervisord.process_groups['foo'].processes['foo']\n        self.assertEqual(process.stop_called, True)\n        self.assertTrue(process.stop_report_called)\n        self.assertEqual(interface.update_text, 'stopProcess')\n\n    def test_stopProcess_success_in_onwait(self):\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo')\n        from supervisor.process import ProcessStates\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        process = supervisord.process_groups['foo'].processes['foo']\n        L = [ ProcessStates.RUNNING,\n              ProcessStates.STOPPING,\n              ProcessStates.STOPPED ]\n        def get_state():\n            return L.pop(0)\n        process.get_state = get_state\n        interface = self._makeOne(supervisord)\n        callback = interface.stopProcess('foo')\n        self.assertEqual(interface.update_text, 'stopProcess')\n        self.assertTrue(callback())\n\n    def test_stopProcess_NDY_in_onwait(self):\n        from supervisor.http import NOT_DONE_YET\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo')\n        from supervisor.process import ProcessStates\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        process = supervisord.process_groups['foo'].processes['foo']\n        L = [ ProcessStates.RUNNING,\n              ProcessStates.STOPPING,\n              ProcessStates.STOPPING ]\n        def get_state():\n            return L.pop(0)\n        process.get_state = get_state\n        interface = self._makeOne(supervisord)\n        callback = interface.stopProcess('foo')\n        self.assertEqual(callback(), NOT_DONE_YET)\n        self.assertEqual(interface.update_text, 'stopProcess')\n\n    def test_stopProcess_bad_name(self):\n        from supervisor.xmlrpc import Faults\n        supervisord = DummySupervisor()\n        interface = self._makeOne(supervisord)\n        self._assertRPCError(Faults.BAD_NAME,\n                             interface.stopProcess, 'foo')\n\n    def test_stopProcess_not_running(self):\n        from supervisor.states import ProcessStates\n        from supervisor.xmlrpc import Faults\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo')\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        supervisord.set_procattr('foo', 'state', ProcessStates.EXITED)\n        interface = self._makeOne(supervisord)\n        self._assertRPCError(Faults.NOT_RUNNING, interface.stopProcess, 'foo')\n\n    def test_stopProcess_failed(self):\n        from supervisor.xmlrpc import Faults\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo')\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        supervisord.set_procattr('foo', 'stop', lambda: 'unstoppable')\n        interface = self._makeOne(supervisord)\n        self._assertRPCError(Faults.FAILED, interface.stopProcess, 'foo')\n\n    def test_stopProcessGroup(self):\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', '/bin/foo', priority=1)\n        pconfig2 = DummyPConfig(options, 'process2', '/bin/foo2', priority=2)\n        from supervisor.process import ProcessStates\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig1,\n                                               pconfig2)\n        supervisord.set_procattr('process1', 'state', ProcessStates.RUNNING)\n        supervisord.set_procattr('process2', 'state', ProcessStates.RUNNING)\n        interface = self._makeOne(supervisord)\n        callback = interface.stopProcessGroup('foo')\n        self.assertEqual(interface.update_text, 'stopProcessGroup')\n        from supervisor import http\n        value = http.NOT_DONE_YET\n        while 1:\n            value = callback()\n            if value is not http.NOT_DONE_YET:\n                break\n\n        from supervisor.xmlrpc import Faults\n        self.assertEqual(value, [\n            {'status': Faults.SUCCESS,\n             'group':'foo',\n             'name': 'process1',\n             'description': 'OK'},\n\n            {'status': Faults.SUCCESS,\n             'group':'foo',\n             'name': 'process2',\n             'description': 'OK'},\n            ] )\n        process1 = supervisord.process_groups['foo'].processes['process1']\n        self.assertEqual(process1.stop_called, True)\n        process2 = supervisord.process_groups['foo'].processes['process2']\n        self.assertEqual(process2.stop_called, True)\n\n    def test_stopProcessGroup_nowait(self):\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', __file__, priority=1)\n        pconfig2 = DummyPConfig(options, 'process2', __file__, priority=2)\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig1,\n                                               pconfig2)\n        from supervisor.process import ProcessStates\n        supervisord.set_procattr('process1', 'state', ProcessStates.RUNNING)\n        supervisord.set_procattr('process2', 'state', ProcessStates.RUNNING)\n        interface = self._makeOne(supervisord)\n        callback = interface.stopProcessGroup('foo', wait=False)\n        from supervisor.xmlrpc import Faults\n\n        self.assertEqual(\n            callback(),\n            [\n                {'name': 'process1',\n                 'description': 'OK',\n                 'group': 'foo',\n                 'status': Faults.SUCCESS},\n                {'name': 'process2',\n                 'description': 'OK',\n                 'group': 'foo',\n                 'status': Faults.SUCCESS}\n                ]\n            )\n\n    def test_stopProcessGroup_badname(self):\n        from supervisor import xmlrpc\n        supervisord = DummySupervisor()\n        interface = self._makeOne(supervisord)\n        self._assertRPCError(xmlrpc.Faults.BAD_NAME,\n                             interface.stopProcessGroup, 'foo')\n\n    def test_stopProcess_splat_calls_stopProcessGroup(self):\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', __file__, autostart=False,\n                               startsecs=.01)\n        pconfig2 = DummyPConfig(options, 'process2', __file__, priority=2,\n                                startsecs=.01)\n        supervisord = PopulatedDummySupervisor(options, 'foo',\n                                               pconfig1, pconfig2)\n        from supervisor.process import ProcessStates\n        supervisord.set_procattr('process1', 'state', ProcessStates.STOPPED)\n        supervisord.set_procattr('process2', 'state', ProcessStates.STOPPED)\n        interface = self._makeOne(supervisord)\n        interface.stopProcess('foo:*')\n        self.assertEqual(interface.update_text, 'stopProcessGroup')\n\n    def test_stopAllProcesses(self):\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', '/bin/foo', priority=1)\n        pconfig2 = DummyPConfig(options, 'process2', '/bin/foo2', priority=2)\n        from supervisor.process import ProcessStates\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig1,\n                                               pconfig2)\n        supervisord.set_procattr('process1', 'state', ProcessStates.RUNNING)\n        supervisord.set_procattr('process2', 'state', ProcessStates.RUNNING)\n        interface = self._makeOne(supervisord)\n        callback = interface.stopAllProcesses()\n        self.assertEqual(interface.update_text, 'stopAllProcesses')\n        from supervisor import http\n        value = http.NOT_DONE_YET\n        while 1:\n            value = callback()\n            if value is not http.NOT_DONE_YET:\n                break\n\n        from supervisor.xmlrpc import Faults\n        self.assertEqual(value, [\n            {'status': Faults.SUCCESS,\n             'group':'foo',\n             'name': 'process1',\n             'description': 'OK'},\n\n            {'status': Faults.SUCCESS,\n             'group':'foo',\n             'name': 'process2',\n             'description': 'OK'},\n            ] )\n        process1 = supervisord.process_groups['foo'].processes['process1']\n        self.assertEqual(process1.stop_called, True)\n        process2 = supervisord.process_groups['foo'].processes['process2']\n        self.assertEqual(process2.stop_called, True)\n\n    def test_stopAllProcesses_nowait(self):\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', __file__, priority=1)\n        pconfig2 = DummyPConfig(options, 'process2', __file__, priority=2)\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig1,\n                                               pconfig2)\n        from supervisor.process import ProcessStates\n        supervisord.set_procattr('process1', 'state', ProcessStates.RUNNING)\n        supervisord.set_procattr('process2', 'state', ProcessStates.RUNNING)\n        interface = self._makeOne(supervisord)\n        callback = interface.stopAllProcesses(wait=False)\n        from supervisor.xmlrpc import Faults\n\n        self.assertEqual(\n            callback(),\n            [{'group': 'foo',\n              'status': Faults.SUCCESS,\n              'description': 'OK',\n              'name': 'process1'},\n             {'group': 'foo',\n              'status': Faults.SUCCESS,\n              'description': 'OK',\n              'name': 'process2'}\n             ]\n            )\n\n    def test_signalProcess_with_signal_number(self):\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo')\n        from supervisor.process import ProcessStates\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        supervisord.set_procattr('foo', 'state', ProcessStates.RUNNING)\n\n        interface = self._makeOne(supervisord)\n        result = interface.signalProcess('foo', 10)\n\n        self.assertEqual(interface.update_text, 'signalProcess')\n        self.assertEqual(result, True)\n        p = supervisord.process_groups[supervisord.group_name].processes['foo']\n        self.assertEqual(p.sent_signal, 10)\n\n    def test_signalProcess_with_signal_name(self):\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo')\n        from supervisor.process import ProcessStates\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        supervisord.set_procattr('foo', 'state', ProcessStates.RUNNING)\n\n        from supervisor.datatypes import signal_number\n        signame = 'quit'\n        signum = signal_number(signame)\n\n        interface = self._makeOne(supervisord)\n        result = interface.signalProcess('foo', signame)\n\n        self.assertEqual(interface.update_text, 'signalProcess')\n        self.assertEqual(result, True)\n        p = supervisord.process_groups[supervisord.group_name].processes['foo']\n        self.assertEqual(p.sent_signal, signum)\n\n    def test_signalProcess_stopping(self):\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo')\n        from supervisor.process import ProcessStates\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        supervisord.set_procattr('foo', 'state', ProcessStates.STOPPING)\n\n        interface = self._makeOne(supervisord)\n        result = interface.signalProcess('foo', 10)\n\n        self.assertEqual(interface.update_text, 'signalProcess')\n        self.assertEqual(result, True)\n        p = supervisord.process_groups[supervisord.group_name].processes['foo']\n        self.assertEqual(p.sent_signal, 10)\n\n    def test_signalProcess_badsignal(self):\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo')\n        from supervisor.process import ProcessStates\n        from supervisor import xmlrpc\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        supervisord.set_procattr('foo', 'state', ProcessStates.RUNNING)\n        interface = self._makeOne(supervisord)\n        self._assertRPCError(\n            xmlrpc.Faults.BAD_SIGNAL, interface.signalProcess, 'foo', 155\n            )\n\n    def test_signalProcess_notrunning(self):\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo')\n        from supervisor.process import ProcessStates\n        from supervisor import xmlrpc\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        supervisord.set_procattr('foo', 'state', ProcessStates.STOPPED)\n        interface = self._makeOne(supervisord)\n        self._assertRPCError(\n            xmlrpc.Faults.NOT_RUNNING, interface.signalProcess, 'foo', 10\n            )\n\n\n    def test_signalProcess_signal_returns_message(self):\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo')\n        from supervisor.process import ProcessStates\n        from supervisor import xmlrpc\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        supervisord.set_procattr('foo', 'state', ProcessStates.RUNNING)\n        def signalreturn(sig):\n            return 'msg'\n        pgroup = supervisord.process_groups[supervisord.group_name]\n        proc = pgroup.processes['foo']\n        proc.signal = signalreturn\n        interface = self._makeOne(supervisord)\n        self._assertRPCError(\n            xmlrpc.Faults.FAILED, interface.signalProcess, 'foo', 10\n            )\n\n\n    def test_signalProcess_withgroup(self):\n        \"\"\" Test that sending foo:* works \"\"\"\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', '/bin/foo')\n        pconfig2 = DummyPConfig(options, 'process2', '/bin/foo2')\n        from supervisor.process import ProcessStates\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig1,\n                                               pconfig2)\n        supervisord.set_procattr('process1', 'state', ProcessStates.RUNNING)\n        supervisord.set_procattr('process2', 'state', ProcessStates.STOPPING)\n        interface = self._makeOne(supervisord)\n        result = interface.signalProcess('foo:*', 10)\n        self.assertEqual(interface.update_text, 'signalProcessGroup')\n\n        # Sort so we get deterministic results despite hash randomization\n        result = sorted(result, key=operator.itemgetter('name'))\n\n        from supervisor.xmlrpc import Faults\n        self.assertEqual(result, [\n            {'status': Faults.SUCCESS,\n             'group': 'foo',\n             'name': 'process1',\n             'description': 'OK'},\n\n            {'status': Faults.SUCCESS,\n             'group':'foo',\n             'name': 'process2',\n             'description': 'OK'},\n            ] )\n        process1 = supervisord.process_groups['foo'].processes['process1']\n        self.assertEqual(process1.sent_signal, 10)\n        process2 = supervisord.process_groups['foo'].processes['process2']\n        self.assertEqual(process2.sent_signal, 10)\n\n\n    def test_signalProcessGroup_with_signal_number(self):\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', '/bin/foo')\n        pconfig2 = DummyPConfig(options, 'process2', '/bin/foo2')\n        from supervisor.process import ProcessStates\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig1,\n                                               pconfig2)\n        supervisord.set_procattr('process1', 'state', ProcessStates.RUNNING)\n        supervisord.set_procattr('process2', 'state', ProcessStates.RUNNING)\n        interface = self._makeOne(supervisord)\n        result = interface.signalProcessGroup('foo', 10)\n        self.assertEqual(interface.update_text, 'signalProcessGroup')\n        # Sort so we get deterministic results despite hash randomization\n        result = sorted(result, key=operator.itemgetter('name'))\n\n        from supervisor.xmlrpc import Faults\n        self.assertEqual(result, [\n            {'status': Faults.SUCCESS,\n             'group': 'foo',\n             'name': 'process1',\n             'description': 'OK'},\n\n            {'status': Faults.SUCCESS,\n             'group': 'foo',\n             'name': 'process2',\n             'description': 'OK'},\n            ] )\n        process1 = supervisord.process_groups['foo'].processes['process1']\n        self.assertEqual(process1.sent_signal, 10)\n        process2 = supervisord.process_groups['foo'].processes['process2']\n        self.assertEqual(process2.sent_signal, 10)\n\n    def test_signalProcessGroup_with_signal_name(self):\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', '/bin/foo')\n        from supervisor.process import ProcessStates\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig1)\n        supervisord.set_procattr('process1', 'state', ProcessStates.RUNNING)\n\n        from supervisor.datatypes import signal_number\n        signame = 'term'\n        signum = signal_number(signame)\n\n        interface = self._makeOne(supervisord)\n        result = interface.signalProcessGroup('foo', signame)\n\n        self.assertEqual(interface.update_text, 'signalProcessGroup')\n        from supervisor.xmlrpc import Faults\n        self.assertEqual(result, [\n            {'status': Faults.SUCCESS,\n             'group': 'foo',\n             'name': 'process1',\n             'description': 'OK'},\n            ] )\n\n        process1 = supervisord.process_groups['foo'].processes['process1']\n        self.assertEqual(process1.sent_signal, signum)\n\n    def test_signalProcessGroup_nosuchgroup(self):\n        from supervisor import xmlrpc\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', '/bin/foo')\n        pconfig2 = DummyPConfig(options, 'process2', '/bin/foo2')\n        from supervisor.process import ProcessStates\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig1,\n                                               pconfig2)\n        supervisord.set_procattr('process1', 'state', ProcessStates.RUNNING)\n        supervisord.set_procattr('process2', 'state', ProcessStates.RUNNING)\n        interface = self._makeOne(supervisord)\n        self._assertRPCError(xmlrpc.Faults.BAD_NAME,\n            interface.signalProcessGroup, 'bar', 10\n            )\n\n    def test_signalAllProcesses_with_signal_number(self):\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', '/bin/foo')\n        pconfig2 = DummyPConfig(options, 'process2', '/bin/foo2')\n        from supervisor.process import ProcessStates\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig1,\n                                               pconfig2)\n        supervisord.set_procattr('process1', 'state', ProcessStates.RUNNING)\n        supervisord.set_procattr('process2', 'state', ProcessStates.RUNNING)\n        interface = self._makeOne(supervisord)\n        result = interface.signalAllProcesses(10)\n        self.assertEqual(interface.update_text, 'signalAllProcesses')\n\n        # Sort so we get deterministic results despite hash randomization\n        result = sorted(result, key=operator.itemgetter('name'))\n\n        from supervisor.xmlrpc import Faults\n        self.assertEqual(result, [\n            {'status': Faults.SUCCESS,\n             'group': 'foo',\n             'name': 'process1',\n             'description': 'OK'},\n\n            {'status': Faults.SUCCESS,\n             'group': 'foo',\n             'name': 'process2',\n             'description': 'OK'},\n            ] )\n        process1 = supervisord.process_groups['foo'].processes['process1']\n        self.assertEqual(process1.sent_signal, 10)\n        process2 = supervisord.process_groups['foo'].processes['process2']\n        self.assertEqual(process2.sent_signal, 10)\n\n    def test_signalAllProcesses_with_signal_name(self):\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', '/bin/foo')\n        from supervisor.process import ProcessStates\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig1)\n        supervisord.set_procattr('process1', 'state', ProcessStates.RUNNING)\n\n        from supervisor.datatypes import signal_number\n        signame = 'hup'\n        signum = signal_number(signame)\n\n        interface = self._makeOne(supervisord)\n        result = interface.signalAllProcesses(signame)\n        self.assertEqual(interface.update_text, 'signalAllProcesses')\n\n        from supervisor.xmlrpc import Faults\n        self.assertEqual(result, [\n            {'status': Faults.SUCCESS,\n             'group': 'foo',\n             'name': 'process1',\n             'description': 'OK'},\n            ] )\n        process1 = supervisord.process_groups['foo'].processes['process1']\n        self.assertEqual(process1.sent_signal, signum)\n\n    def test_getAllConfigInfo(self):\n        options = DummyOptions()\n        supervisord = DummySupervisor(options, 'foo')\n\n        pconfig1 = DummyPConfig(options, 'process1', __file__,\n                                autorestart=False,\n                                stdout_logfile=Automatic,\n                                stderr_logfile=Automatic,\n                                )\n        pconfig2 = DummyPConfig(options, 'process2', __file__,\n                                autorestart=RestartWhenExitUnexpected,\n                                stdout_logfile=None,\n                                stderr_logfile=None,\n                                )\n        gconfig = DummyPGroupConfig(options, 'group1', pconfigs=[pconfig1, pconfig2])\n        supervisord.process_groups = {'group1': DummyProcessGroup(gconfig)}\n        supervisord.options.process_group_configs = [gconfig]\n\n        interface = self._makeOne(supervisord)\n        configs = interface.getAllConfigInfo()\n        self.assertEqual(configs[0]['autostart'], True)\n        self.assertEqual(configs[0]['autorestart'], False)\n        self.assertEqual(configs[0]['stopwaitsecs'], 10)\n        self.assertEqual(configs[0]['stdout_events_enabled'], False)\n        self.assertEqual(configs[0]['stderr_events_enabled'], False)\n        self.assertEqual(configs[0]['group'], 'group1')\n        self.assertEqual(configs[0]['stdout_capture_maxbytes'], 0)\n        self.assertEqual(configs[0]['name'], 'process1')\n        self.assertEqual(configs[0]['stopsignal'], 15)\n        self.assertEqual(configs[0]['stderr_syslog'], False)\n        self.assertEqual(configs[0]['stdout_logfile_maxbytes'], 0)\n        self.assertEqual(configs[0]['group_prio'], 999)\n        self.assertEqual(configs[0]['killasgroup'], False)\n        self.assertEqual(configs[0]['process_prio'], 999)\n        self.assertEqual(configs[0]['stdout_syslog'], False)\n        self.assertEqual(configs[0]['stderr_logfile_maxbytes'], 0)\n        self.assertEqual(configs[0]['startsecs'], 10)\n        self.assertEqual(configs[0]['redirect_stderr'], False)\n        self.assertEqual(configs[0]['stdout_logfile'], 'auto')\n        self.assertEqual(configs[0]['exitcodes'], (0,))\n        self.assertEqual(configs[0]['stderr_capture_maxbytes'], 0)\n        self.assertEqual(configs[0]['startretries'], 999)\n        self.assertEqual(configs[0]['stderr_logfile_maxbytes'], 0)\n        self.assertEqual(configs[0]['inuse'], True)\n        self.assertEqual(configs[0]['stderr_logfile'], 'auto')\n        self.assertEqual(configs[0]['stdout_logfile_backups'], 0)\n        assert 'test_rpcinterfaces.py' in configs[0]['command']\n\n        self.assertEqual(configs[1]['autostart'], True)\n        self.assertEqual(configs[1]['autorestart'], \"unexpected\")\n        self.assertEqual(configs[1]['stopwaitsecs'], 10)\n        self.assertEqual(configs[1]['stdout_events_enabled'], False)\n        self.assertEqual(configs[1]['stderr_events_enabled'], False)\n        self.assertEqual(configs[1]['group'], 'group1')\n        self.assertEqual(configs[1]['stdout_capture_maxbytes'], 0)\n        self.assertEqual(configs[1]['name'], 'process2')\n        self.assertEqual(configs[1]['stopsignal'], 15)\n        self.assertEqual(configs[1]['stderr_syslog'], False)\n        self.assertEqual(configs[1]['stdout_logfile_maxbytes'], 0)\n        self.assertEqual(configs[1]['group_prio'], 999)\n        self.assertEqual(configs[1]['killasgroup'], False)\n        self.assertEqual(configs[1]['process_prio'], 999)\n        self.assertEqual(configs[1]['stdout_syslog'], False)\n        self.assertEqual(configs[1]['stderr_logfile_maxbytes'], 0)\n        self.assertEqual(configs[1]['startsecs'], 10)\n        self.assertEqual(configs[1]['redirect_stderr'], False)\n        self.assertEqual(configs[1]['stdout_logfile'], 'none')\n        self.assertEqual(configs[1]['exitcodes'], (0,))\n        self.assertEqual(configs[1]['stderr_capture_maxbytes'], 0)\n        self.assertEqual(configs[1]['startretries'], 999)\n        self.assertEqual(configs[1]['stderr_logfile_maxbytes'], 0)\n        self.assertEqual(configs[1]['inuse'], True)\n        self.assertEqual(configs[1]['stderr_logfile'], 'none')\n        self.assertEqual(configs[1]['stdout_logfile_backups'], 0)\n        assert 'test_rpcinterfaces.py' in configs[0]['command']\n\n    def test_getAllConfigInfo_filters_types_not_compatible_with_xmlrpc(self):\n        options = DummyOptions()\n        supervisord = DummySupervisor(options, 'foo')\n\n        pconfig1 = DummyPConfig(options, 'process1', __file__)\n        pconfig2 = DummyPConfig(options, 'process2', __file__)\n        gconfig = DummyPGroupConfig(options, 'group1', pconfigs=[pconfig1, pconfig2])\n        supervisord.process_groups = {'group1': DummyProcessGroup(gconfig)}\n        supervisord.options.process_group_configs = [gconfig]\n        interface = self._makeOne(supervisord)\n\n        unmarshallables = [type(None)]\n\n        try:\n            from enum import Enum\n            unmarshallables.append(Enum)\n        except ImportError: # python 2\n            pass\n\n        for typ in unmarshallables:\n            for config in interface.getAllConfigInfo():\n                for k, v in config.items():\n                    self.assertFalse(isinstance(v, typ), k)\n\n    def test__interpretProcessInfo(self):\n        supervisord = DummySupervisor()\n        interface = self._makeOne(supervisord)\n        start = _NOW -100\n        stop  = _NOW -1\n        from supervisor.process import ProcessStates\n        running = {'name':'running',\n                   'pid':1,\n                   'state':ProcessStates.RUNNING,\n                   'start':start,\n                   'stop':stop,\n                   'now':_NOW}\n\n        description = interface._interpretProcessInfo(running)\n        self.assertEqual(description, 'pid 1, uptime 0:01:40')\n\n        fatal = {'name':'fatal',\n                 'pid':2,\n                 'state':ProcessStates.FATAL,\n                 'start':start,\n                 'stop':stop,\n                 'now':_NOW,\n                 'spawnerr':'Hosed'}\n\n        description = interface._interpretProcessInfo(fatal)\n        self.assertEqual(description, 'Hosed')\n\n        fatal2 = {'name':'fatal',\n                  'pid':2,\n                  'state':ProcessStates.FATAL,\n                  'start':start,\n                  'stop':stop,\n                  'now':_NOW,\n                  'spawnerr':'',}\n\n        description = interface._interpretProcessInfo(fatal2)\n        self.assertEqual(description, 'unknown error (try \"tail fatal\")')\n\n        stopped = {'name':'stopped',\n                   'pid':3,\n                   'state':ProcessStates.STOPPED,\n                   'start':start,\n                   'stop':stop,\n                   'now':_NOW,\n                   'spawnerr':'',}\n\n        description = interface._interpretProcessInfo(stopped)\n        from datetime import datetime\n        stoptime = datetime(*time.localtime(stop)[:7])\n        self.assertEqual(description, stoptime.strftime(_TIMEFORMAT))\n\n        stopped2 = {'name':'stopped',\n                   'pid':3,\n                   'state':ProcessStates.STOPPED,\n                   'start':0,\n                   'stop':stop,\n                   'now':_NOW,\n                   'spawnerr':'',}\n\n        description = interface._interpretProcessInfo(stopped2)\n        self.assertEqual(description, 'Not started')\n\n    def test__interpretProcessInfo_doesnt_report_negative_uptime(self):\n        supervisord = DummySupervisor()\n        interface = self._makeOne(supervisord)\n        from supervisor.process import ProcessStates\n        running = {'name': 'running',\n                   'pid': 42,\n                   'state': ProcessStates.RUNNING,\n                   'start': _NOW + 10, # started in the future\n                   'stop': None,\n                   'now': _NOW}\n        description = interface._interpretProcessInfo(running)\n        self.assertEqual(description, 'pid 42, uptime 0:00:00')\n\n    def test_getProcessInfo(self):\n        from supervisor.process import ProcessStates\n\n        options = DummyOptions()\n        config = DummyPConfig(options, 'foo', '/bin/foo',\n                              stdout_logfile='/tmp/fleeb.bar')\n        process = DummyProcess(config)\n        process.pid = 111\n        process.laststart = 10\n        process.laststop = 11\n        pgroup_config = DummyPGroupConfig(options, name='foo')\n        pgroup = DummyProcessGroup(pgroup_config)\n        pgroup.processes = {'foo':process}\n        supervisord = DummySupervisor(process_groups={'foo':pgroup})\n        interface = self._makeOne(supervisord)\n        data = interface.getProcessInfo('foo')\n\n        self.assertEqual(interface.update_text, 'getProcessInfo')\n        self.assertEqual(data['logfile'], '/tmp/fleeb.bar')\n        self.assertEqual(data['stdout_logfile'], '/tmp/fleeb.bar')\n        self.assertEqual(data['stderr_logfile'], '')\n        self.assertEqual(data['name'], 'foo')\n        self.assertEqual(data['pid'], 111)\n        self.assertEqual(data['start'], 10)\n        self.assertEqual(data['stop'], 11)\n        self.assertEqual(data['state'], ProcessStates.RUNNING)\n        self.assertEqual(data['statename'], 'RUNNING')\n        self.assertEqual(data['exitstatus'], 0)\n        self.assertEqual(data['spawnerr'], '')\n        self.assertTrue(data['description'].startswith('pid 111'))\n\n    def test_getProcessInfo_logfile_NONE(self):\n        options = DummyOptions()\n        config = DummyPConfig(options, 'foo', '/bin/foo',\n                              stdout_logfile=None)\n        process = DummyProcess(config)\n        process.pid = 111\n        process.laststart = 10\n        process.laststop = 11\n        pgroup_config = DummyPGroupConfig(options, name='foo')\n        pgroup = DummyProcessGroup(pgroup_config)\n        pgroup.processes = {'foo':process}\n        supervisord = DummySupervisor(process_groups={'foo':pgroup})\n        interface = self._makeOne(supervisord)\n        data = interface.getProcessInfo('foo')\n        self.assertEqual(data['logfile'], '')\n        self.assertEqual(data['stdout_logfile'], '')\n\n    def test_getProcessInfo_unknown_state(self):\n        from supervisor.states import ProcessStates\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo')\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        supervisord.set_procattr('foo', 'state', ProcessStates.UNKNOWN)\n        interface = self._makeOne(supervisord)\n        data = interface.getProcessInfo('foo')\n        self.assertEqual(data['statename'], 'UNKNOWN')\n        self.assertEqual(data['description'], '')\n\n    def test_getProcessInfo_bad_name_when_bad_process(self):\n        from supervisor import xmlrpc\n        supervisord = DummySupervisor()\n        interface = self._makeOne(supervisord)\n        self._assertRPCError(xmlrpc.Faults.BAD_NAME,\n                             interface.getProcessInfo, 'nonexistent')\n\n    def test_getProcessInfo_bad_name_when_no_process(self):\n        from supervisor import xmlrpc\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo')\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        interface = self._makeOne(supervisord)\n        self._assertRPCError(xmlrpc.Faults.BAD_NAME,\n                             interface.getProcessInfo, 'foo:')\n\n    def test_getProcessInfo_caps_timestamps_exceeding_xmlrpc_maxint(self):\n        from supervisor.compat import xmlrpclib\n        options = DummyOptions()\n        config = DummyPConfig(options, 'foo', '/bin/foo',\n                              stdout_logfile=None)\n        process = DummyProcess(config)\n        process.laststart = float(xmlrpclib.MAXINT + 1)\n        process.laststop = float(xmlrpclib.MAXINT + 1)\n        pgroup_config = DummyPGroupConfig(options, name='foo')\n        pgroup = DummyProcessGroup(pgroup_config)\n        pgroup.processes = {'foo':process}\n        supervisord = DummySupervisor(process_groups={'foo':pgroup})\n        interface = self._makeOne(supervisord)\n        interface._now = lambda: float(xmlrpclib.MAXINT + 1)\n        data = interface.getProcessInfo('foo')\n        self.assertEqual(data['start'], xmlrpclib.MAXINT)\n        self.assertEqual(data['stop'], xmlrpclib.MAXINT)\n        self.assertEqual(data['now'], xmlrpclib.MAXINT)\n\n    def test_getAllProcessInfo(self):\n        from supervisor.process import ProcessStates\n        options = DummyOptions()\n\n        p1config = DummyPConfig(options, 'process1', '/bin/process1',\n                                priority=1,\n                                stdout_logfile='/tmp/process1.log')\n\n        p2config = DummyPConfig(options, 'process2', '/bin/process2',\n                                priority=2,\n                                stdout_logfile='/tmp/process2.log')\n\n        supervisord = PopulatedDummySupervisor(options, 'gname', p1config,\n                                               p2config)\n        supervisord.set_procattr('process1', 'pid', 111)\n        supervisord.set_procattr('process1', 'laststart', 10)\n        supervisord.set_procattr('process1', 'laststop', 11)\n        supervisord.set_procattr('process1', 'state', ProcessStates.RUNNING)\n\n        supervisord.set_procattr('process2', 'pid', 0)\n        supervisord.set_procattr('process2', 'laststart', 20)\n        supervisord.set_procattr('process2', 'laststop', 11)\n        supervisord.set_procattr('process2', 'state', ProcessStates.STOPPED)\n\n        interface = self._makeOne(supervisord)\n\n        info = interface.getAllProcessInfo()\n\n        self.assertEqual(interface.update_text, 'getProcessInfo')\n        self.assertEqual(len(info), 2)\n\n        p1info = info[0]\n        self.assertEqual(p1info['logfile'], '/tmp/process1.log')\n        self.assertEqual(p1info['stdout_logfile'], '/tmp/process1.log')\n        self.assertEqual(p1info['stderr_logfile'], '')\n        self.assertEqual(p1info['name'], 'process1')\n        self.assertEqual(p1info['pid'], 111)\n        self.assertEqual(p1info['start'], 10)\n        self.assertEqual(p1info['stop'], 11)\n        self.assertEqual(p1info['state'], ProcessStates.RUNNING)\n        self.assertEqual(p1info['statename'], 'RUNNING')\n        self.assertEqual(p1info['exitstatus'], 0)\n        self.assertEqual(p1info['spawnerr'], '')\n        self.assertEqual(p1info['group'], 'gname')\n        self.assertTrue(p1info['description'].startswith('pid 111'))\n\n        p2info = info[1]\n        process2 = supervisord.process_groups['gname'].processes['process2']\n        self.assertEqual(p2info['logfile'], '/tmp/process2.log')\n        self.assertEqual(p2info['stdout_logfile'], '/tmp/process2.log')\n        self.assertEqual(p1info['stderr_logfile'], '')\n        self.assertEqual(p2info['name'], 'process2')\n        self.assertEqual(p2info['pid'], 0)\n        self.assertEqual(p2info['start'], process2.laststart)\n        self.assertEqual(p2info['stop'], 11)\n        self.assertEqual(p2info['state'], ProcessStates.STOPPED)\n        self.assertEqual(p2info['statename'], 'STOPPED')\n        self.assertEqual(p2info['exitstatus'], 0)\n        self.assertEqual(p2info['spawnerr'], '')\n        self.assertEqual(p1info['group'], 'gname')\n\n        from datetime import datetime\n        starttime = datetime(*time.localtime(process2.laststart)[:7])\n        self.assertEqual(p2info['description'],\n                            starttime.strftime(_TIMEFORMAT))\n\n    def test_readProcessStdoutLog_unreadable(self):\n        from supervisor import xmlrpc\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'process1', '/bin/process1', priority=1,\n                               stdout_logfile='/tmp/process1.log')\n        supervisord = PopulatedDummySupervisor(options, 'process1', pconfig)\n        interface = self._makeOne(supervisord)\n        self._assertRPCError(xmlrpc.Faults.NO_FILE,\n                             interface.readProcessStdoutLog,\n                             'process1', offset=0, length=1)\n\n    def test_readProcessStdoutLog_badargs(self):\n        from supervisor import xmlrpc\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'process1', '/bin/process1', priority=1,\n                              stdout_logfile='/tmp/process1.log')\n        supervisord = PopulatedDummySupervisor(options, 'process1', pconfig)\n        interface = self._makeOne(supervisord)\n        process = supervisord.process_groups['process1'].processes['process1']\n        logfile = process.config.stdout_logfile\n\n        try:\n            with open(logfile, 'w+') as f:\n                f.write('x' * 2048)\n            self._assertRPCError(xmlrpc.Faults.BAD_ARGUMENTS,\n                                 interface.readProcessStdoutLog,\n                                 'process1', offset=-1, length=1)\n            self._assertRPCError(xmlrpc.Faults.BAD_ARGUMENTS,\n                                 interface.readProcessStdoutLog, 'process1',\n                                 offset=-1, length=-1)\n        finally:\n            os.remove(logfile)\n\n    def test_readProcessStdoutLog_bad_name_no_process(self):\n        from supervisor import xmlrpc\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'process1', '/bin/process1', priority=1,\n                               stdout_logfile='/tmp/process1.log')\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        interface = self._makeOne(supervisord)\n        self._assertRPCError(xmlrpc.Faults.BAD_NAME,\n                             interface.readProcessStdoutLog,\n                             'foo:*', offset=0, length=1)\n\n    def test_readProcessStdoutLog(self):\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo',\n                              stdout_logfile='/tmp/fooooooo')\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        interface = self._makeOne(supervisord)\n        process = supervisord.process_groups['foo'].processes['foo']\n        logfile = process.config.stdout_logfile\n        try:\n            with open(logfile, 'w+') as f:\n                f.write('x' * 2048)\n                f.write('y' * 2048)\n            data = interface.readProcessStdoutLog('foo', offset=0, length=0)\n            self.assertEqual(interface.update_text, 'readProcessStdoutLog')\n            self.assertEqual(data, ('x' * 2048) + ('y' * 2048))\n            data = interface.readProcessStdoutLog('foo', offset=2048, length=0)\n            self.assertEqual(data, 'y' * 2048)\n            data = interface.readProcessStdoutLog('foo', offset=0, length=2048)\n            self.assertEqual(data, 'x' * 2048)\n            data = interface.readProcessStdoutLog('foo', offset=-4, length=0)\n            self.assertEqual(data, 'y' * 4)\n        finally:\n            os.remove(logfile)\n\n    def test_readProcessLogAliasedTo_readProcessStdoutLog(self):\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo')\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        interface = self._makeOne(supervisord)\n        self.assertEqual(interface.readProcessLog,\n                         interface.readProcessStdoutLog)\n\n    def test_readProcessStderrLog_unreadable(self):\n        from supervisor import xmlrpc\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'process1', '/bin/process1', priority=1,\n                               stderr_logfile='/tmp/process1.log')\n        supervisord = PopulatedDummySupervisor(options, 'process1', pconfig)\n        interface = self._makeOne(supervisord)\n        self._assertRPCError(xmlrpc.Faults.NO_FILE,\n                             interface.readProcessStderrLog,\n                             'process1', offset=0, length=1)\n\n    def test_readProcessStderrLog_badargs(self):\n        from supervisor import xmlrpc\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'process1', '/bin/process1', priority=1,\n                              stderr_logfile='/tmp/process1.log')\n        supervisord = PopulatedDummySupervisor(options, 'process1', pconfig)\n        interface = self._makeOne(supervisord)\n        process = supervisord.process_groups['process1'].processes['process1']\n        logfile = process.config.stderr_logfile\n\n        try:\n            with open(logfile, 'w+') as f:\n                f.write('x' * 2048)\n            self._assertRPCError(xmlrpc.Faults.BAD_ARGUMENTS,\n                                 interface.readProcessStderrLog,\n                                 'process1', offset=-1, length=1)\n            self._assertRPCError(xmlrpc.Faults.BAD_ARGUMENTS,\n                                 interface.readProcessStderrLog, 'process1',\n                                 offset=-1, length=-1)\n        finally:\n            os.remove(logfile)\n\n    def test_readProcessStderrLog_bad_name_no_process(self):\n        from supervisor import xmlrpc\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'process1', '/bin/process1', priority=1,\n                               stdout_logfile='/tmp/process1.log')\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        interface = self._makeOne(supervisord)\n        self._assertRPCError(xmlrpc.Faults.BAD_NAME,\n                             interface.readProcessStderrLog,\n                             'foo:*', offset=0, length=1)\n\n    def test_readProcessStderrLog(self):\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo',\n                              stderr_logfile='/tmp/fooooooo')\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        interface = self._makeOne(supervisord)\n        process = supervisord.process_groups['foo'].processes['foo']\n        logfile = process.config.stderr_logfile\n        try:\n            with open(logfile, 'w+') as f:\n                f.write('x' * 2048)\n                f.write('y' * 2048)\n            data = interface.readProcessStderrLog('foo', offset=0, length=0)\n            self.assertEqual(interface.update_text, 'readProcessStderrLog')\n            self.assertEqual(data, ('x' * 2048) + ('y' * 2048))\n            data = interface.readProcessStderrLog('foo', offset=2048, length=0)\n            self.assertEqual(data, 'y' * 2048)\n            data = interface.readProcessStderrLog('foo', offset=0, length=2048)\n            self.assertEqual(data, 'x' * 2048)\n            data = interface.readProcessStderrLog('foo', offset=-4, length=0)\n            self.assertEqual(data, 'y' * 4)\n        finally:\n            os.remove(logfile)\n\n    def test_tailProcessStdoutLog_bad_name(self):\n        from supervisor import xmlrpc\n        supervisord = DummySupervisor()\n        interface = self._makeOne(supervisord)\n        self._assertRPCError(xmlrpc.Faults.BAD_NAME,\n                             interface.tailProcessStdoutLog, 'BAD_NAME', 0, 10)\n\n    def test_tailProcessStdoutLog_bad_name_no_process(self):\n        from supervisor import xmlrpc\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'process1', '/bin/process1', priority=1,\n                               stdout_logfile='/tmp/process1.log')\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        interface = self._makeOne(supervisord)\n        self._assertRPCError(xmlrpc.Faults.BAD_NAME,\n                             interface.tailProcessStdoutLog, 'foo:*', 0, 10)\n\n    def test_tailProcessStdoutLog_all(self):\n        # test entire log is returned when offset==0 and logsize < length\n        from supervisor.compat import letters\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo',\n                               stdout_logfile='/tmp/fooooooo')\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        interface = self._makeOne(supervisord)\n        process = supervisord.process_groups['foo'].processes['foo']\n        logfile = process.config.stdout_logfile\n        try:\n            with open(logfile, 'w+') as f:\n                f.write(letters)\n\n            data, offset, overflow = interface.tailProcessStdoutLog('foo',\n                                                        offset=0,\n                                                        length=len(letters))\n            self.assertEqual(interface.update_text, 'tailProcessStdoutLog')\n            self.assertEqual(overflow, False)\n            self.assertEqual(offset, len(letters))\n            self.assertEqual(data, letters)\n        finally:\n            os.remove(logfile)\n\n    def test_tailProcessStdoutLog_none(self):\n        # test nothing is returned when offset <= logsize\n        from supervisor.compat import letters\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo',\n                               stdout_logfile='/tmp/fooooooo')\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        interface = self._makeOne(supervisord)\n        process = supervisord.process_groups['foo'].processes['foo']\n        logfile = process.config.stdout_logfile\n        try:\n            with open(logfile, 'w+') as f:\n                f.write(letters)\n\n            # offset==logsize\n            data, offset, overflow = interface.tailProcessStdoutLog('foo',\n                                                        offset=len(letters),\n                                                        length=100)\n            self.assertEqual(interface.update_text, 'tailProcessStdoutLog')\n            self.assertEqual(overflow, False)\n            self.assertEqual(offset, len(letters))\n            self.assertEqual(data, '')\n\n            # offset > logsize\n            data, offset, overflow = interface.tailProcessStdoutLog('foo',\n                                                        offset=len(letters)+5,\n                                                        length=100)\n            self.assertEqual(interface.update_text, 'tailProcessStdoutLog')\n            self.assertEqual(overflow, False)\n            self.assertEqual(offset, len(letters))\n            self.assertEqual(data, '')\n        finally:\n            os.remove(logfile)\n\n    def test_tailProcessStdoutLog_overflow(self):\n        # test buffer overflow occurs when logsize > offset+length\n        from supervisor.compat import letters\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo',\n                              stdout_logfile='/tmp/fooooooo')\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        interface = self._makeOne(supervisord)\n        process = supervisord.process_groups['foo'].processes['foo']\n        logfile = process.config.stdout_logfile\n        try:\n            with open(logfile, 'w+') as f:\n                f.write(letters)\n\n            data, offset, overflow = interface.tailProcessStdoutLog('foo',\n                                                        offset=0, length=5)\n            self.assertEqual(interface.update_text, 'tailProcessStdoutLog')\n            self.assertEqual(overflow, True)\n            self.assertEqual(offset, len(letters))\n            self.assertEqual(data, letters[-5:])\n        finally:\n            os.remove(logfile)\n\n    def test_tailProcessStdoutLog_unreadable(self):\n        # test nothing is returned if the log doesn't exist yet\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo',\n                               stdout_logfile='/tmp/fooooooo')\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        interface = self._makeOne(supervisord)\n\n        data, offset, overflow = interface.tailProcessStdoutLog('foo',\n                                                    offset=0, length=100)\n        self.assertEqual(interface.update_text, 'tailProcessStdoutLog')\n        self.assertEqual(overflow, False)\n        self.assertEqual(offset, 0)\n        self.assertEqual(data, '')\n\n    def test_tailProcessLogAliasedTo_tailProcessStdoutLog(self):\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo')\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        interface = self._makeOne(supervisord)\n        self.assertEqual(interface.tailProcessLog,\n                         interface.tailProcessStdoutLog)\n\n    def test_tailProcessStderrLog_bad_name(self):\n        from supervisor import xmlrpc\n        supervisord = DummySupervisor()\n        interface = self._makeOne(supervisord)\n        self._assertRPCError(xmlrpc.Faults.BAD_NAME,\n                             interface.tailProcessStderrLog, 'BAD_NAME', 0, 10)\n\n    def test_tailProcessStderrLog_bad_name_no_process(self):\n        from supervisor import xmlrpc\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'process1', '/bin/process1', priority=1,\n                               stdout_logfile='/tmp/process1.log')\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        interface = self._makeOne(supervisord)\n        self._assertRPCError(xmlrpc.Faults.BAD_NAME,\n                             interface.tailProcessStderrLog, 'foo:*', 0, 10)\n\n    def test_tailProcessStderrLog_all(self):\n        # test entire log is returned when offset==0 and logsize < length\n        from supervisor.compat import letters\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo',\n                               stderr_logfile='/tmp/fooooooo')\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        interface = self._makeOne(supervisord)\n        process = supervisord.process_groups['foo'].processes['foo']\n        logfile = process.config.stderr_logfile\n        try:\n            with open(logfile, 'w+') as f:\n                f.write(letters)\n\n            data, offset, overflow = interface.tailProcessStderrLog('foo',\n                                                        offset=0,\n                                                        length=len(letters))\n            self.assertEqual(interface.update_text, 'tailProcessStderrLog')\n            self.assertEqual(overflow, False)\n            self.assertEqual(offset, len(letters))\n            self.assertEqual(data, letters)\n        finally:\n            os.remove(logfile)\n\n    def test_tailProcessStderrLog_none(self):\n        # test nothing is returned when offset <= logsize\n        from supervisor.compat import letters\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo',\n                               stderr_logfile='/tmp/fooooooo')\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        interface = self._makeOne(supervisord)\n        process = supervisord.process_groups['foo'].processes['foo']\n        logfile = process.config.stderr_logfile\n        try:\n            with open(logfile, 'w+') as f:\n                f.write(letters)\n\n            # offset==logsize\n            data, offset, overflow = interface.tailProcessStderrLog('foo',\n                                                        offset=len(letters),\n                                                        length=100)\n            self.assertEqual(interface.update_text, 'tailProcessStderrLog')\n            self.assertEqual(overflow, False)\n            self.assertEqual(offset, len(letters))\n            self.assertEqual(data, '')\n\n            # offset > logsize\n            data, offset, overflow = interface.tailProcessStderrLog('foo',\n                                                        offset=len(letters)+5,\n                                                        length=100)\n            self.assertEqual(interface.update_text, 'tailProcessStderrLog')\n            self.assertEqual(overflow, False)\n            self.assertEqual(offset, len(letters))\n            self.assertEqual(data, '')\n        finally:\n            os.remove(logfile)\n\n    def test_tailProcessStderrLog_overflow(self):\n        # test buffer overflow occurs when logsize > offset+length\n        from supervisor.compat import letters\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo',\n                              stderr_logfile='/tmp/fooooooo')\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        interface = self._makeOne(supervisord)\n        process = supervisord.process_groups['foo'].processes['foo']\n        logfile = process.config.stderr_logfile\n        try:\n            with open(logfile, 'w+') as f:\n                f.write(letters)\n\n            data, offset, overflow = interface.tailProcessStderrLog('foo',\n                                                        offset=0, length=5)\n            self.assertEqual(interface.update_text, 'tailProcessStderrLog')\n            self.assertEqual(overflow, True)\n            self.assertEqual(offset, len(letters))\n            self.assertEqual(data, letters[-5:])\n        finally:\n            os.remove(logfile)\n\n    def test_tailProcessStderrLog_unreadable(self):\n        # test nothing is returned if the log doesn't exist yet\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo',\n                               stderr_logfile='/tmp/fooooooo')\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        interface = self._makeOne(supervisord)\n\n        data, offset, overflow = interface.tailProcessStderrLog('foo',\n                                                    offset=0, length=100)\n        self.assertEqual(interface.update_text, 'tailProcessStderrLog')\n        self.assertEqual(overflow, False)\n        self.assertEqual(offset, 0)\n        self.assertEqual(data, '')\n\n    def test_clearProcessLogs_bad_name_no_group(self):\n        from supervisor import xmlrpc\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', 'foo')\n        process = DummyProcess(pconfig)\n        pgroup = DummyProcessGroup(None)\n        pgroup.processes = {'foo': process}\n        supervisord = DummySupervisor(process_groups={'foo':pgroup})\n        interface = self._makeOne(supervisord)\n        self._assertRPCError(xmlrpc.Faults.BAD_NAME,\n                             interface.clearProcessLogs,\n                             'badgroup')\n\n    def test_clearProcessLogs_bad_name_no_process(self):\n        from supervisor import xmlrpc\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', 'foo')\n        process = DummyProcess(pconfig)\n        pgroup = DummyProcessGroup(None)\n        pgroup.processes = {'foo': process}\n        supervisord = DummySupervisor(process_groups={'foo':pgroup})\n        interface = self._makeOne(supervisord)\n        self._assertRPCError(xmlrpc.Faults.BAD_NAME,\n                             interface.clearProcessLogs,\n                             'foo:*')\n\n    def test_clearProcessLogs(self):\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', 'foo')\n        process = DummyProcess(pconfig)\n        pgroup = DummyProcessGroup(None)\n        pgroup.processes = {'foo': process}\n        supervisord = DummySupervisor(process_groups={'foo':pgroup})\n        interface = self._makeOne(supervisord)\n        interface.clearProcessLogs('foo')\n        self.assertEqual(process.logsremoved, True)\n\n    def test_clearProcessLogs_failed(self):\n        from supervisor import xmlrpc\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', 'foo')\n        process = DummyProcess(pconfig)\n        pgroup = DummyProcessGroup(None)\n        pgroup.processes = {'foo': process}\n        process.error_at_clear = True\n        supervisord = DummySupervisor(process_groups={'foo':pgroup})\n        interface = self._makeOne(supervisord)\n        self.assertRaises(xmlrpc.RPCError, interface.clearProcessLogs, 'foo')\n\n    def test_clearProcessLogAliasedTo_clearProcessLogs(self):\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo')\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig)\n        interface = self._makeOne(supervisord)\n        self.assertEqual(interface.clearProcessLog,\n                         interface.clearProcessLogs)\n\n    def test_clearAllProcessLogs(self):\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', 'foo', priority=1)\n        pconfig2 = DummyPConfig(options, 'process2', 'bar', priority=2)\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig1,\n                                               pconfig2)\n        interface = self._makeOne(supervisord)\n        callback = interface.clearAllProcessLogs()\n        callback()\n        results = callback()\n        from supervisor import xmlrpc\n        self.assertEqual(results[0],\n                         {'name':'process1',\n                          'group':'foo',\n                          'status':xmlrpc.Faults.SUCCESS,\n                          'description':'OK'})\n        self.assertEqual(results[1],\n                         {'name':'process2',\n                          'group':'foo',\n                          'status':xmlrpc.Faults.SUCCESS,\n                          'description':'OK'})\n        process1 = supervisord.process_groups['foo'].processes['process1']\n        self.assertEqual(process1.logsremoved, True)\n        process2 = supervisord.process_groups['foo'].processes['process2']\n        self.assertEqual(process2.logsremoved, True)\n\n    def test_clearAllProcessLogs_onefails(self):\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', 'foo', priority=1)\n        pconfig2 = DummyPConfig(options, 'process2', 'bar', priority=2)\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig1,\n                                               pconfig2)\n        supervisord.set_procattr('process1', 'error_at_clear', True)\n        interface = self._makeOne(supervisord)\n        callback = interface.clearAllProcessLogs()\n        callback()\n        results = callback()\n        process1 = supervisord.process_groups['foo'].processes['process1']\n        self.assertEqual(process1.logsremoved, False)\n        process2 = supervisord.process_groups['foo'].processes['process2']\n        self.assertEqual(process2.logsremoved, True)\n        self.assertEqual(len(results), 2)\n        from supervisor import xmlrpc\n        self.assertEqual(results[0],\n                         {'name':'process1',\n                          'group':'foo',\n                          'status':xmlrpc.Faults.FAILED,\n                          'description':'FAILED: foo:process1'})\n        self.assertEqual(results[1],\n                         {'name':'process2',\n                          'group':'foo',\n                          'status':xmlrpc.Faults.SUCCESS,\n                          'description':'OK'})\n\n    def test_clearAllProcessLogs_no_processes(self):\n        supervisord = DummySupervisor()\n        self.assertEqual(supervisord.process_groups, {})\n        interface = self._makeOne(supervisord)\n        callback = interface.clearAllProcessLogs()\n        results = callback()\n        self.assertEqual(results, [])\n\n    def test_sendProcessStdin_raises_incorrect_params_when_not_chars(self):\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', 'foo')\n        supervisord = PopulatedDummySupervisor(options, 'foo', pconfig1)\n        interface   = self._makeOne(supervisord)\n        thing_not_chars = 42\n        from supervisor import xmlrpc\n        self._assertRPCError(xmlrpc.Faults.INCORRECT_PARAMETERS,\n                             interface.sendProcessStdin,\n                             'process1', thing_not_chars)\n\n    def test_sendProcessStdin_raises_bad_name_when_bad_process(self):\n        supervisord = DummySupervisor()\n        interface = self._makeOne(supervisord)\n        from supervisor import xmlrpc\n        self._assertRPCError(xmlrpc.Faults.BAD_NAME,\n                             interface.sendProcessStdin,\n                             'nonexistent', 'chars for stdin')\n\n    def test_sendProcessStdin_raises_bad_name_when_no_process(self):\n        options = DummyOptions()\n        supervisord = PopulatedDummySupervisor(options, 'foo')\n        interface = self._makeOne(supervisord)\n        from supervisor import xmlrpc\n        self._assertRPCError(xmlrpc.Faults.BAD_NAME,\n                             interface.sendProcessStdin,\n                             'foo:*', 'chars for stdin')\n\n    def test_sendProcessStdin_raises_not_running_when_not_process_pid(self):\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', 'foo')\n        supervisord = PopulatedDummySupervisor(options, 'process1', pconfig1)\n        supervisord.set_procattr('process1', 'pid', 0)\n        interface = self._makeOne(supervisord)\n        from supervisor import xmlrpc\n        self._assertRPCError(xmlrpc.Faults.NOT_RUNNING,\n                            interface.sendProcessStdin,\n                            'process1', 'chars for stdin')\n\n    def test_sendProcessStdin_raises_not_running_when_killing(self):\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', 'foo')\n        supervisord = PopulatedDummySupervisor(options, 'process1', pconfig1)\n        supervisord.set_procattr('process1', 'pid', 42)\n        supervisord.set_procattr('process1', 'killing', True)\n        interface   = self._makeOne(supervisord)\n        from supervisor import xmlrpc\n        self._assertRPCError(xmlrpc.Faults.NOT_RUNNING,\n                             interface.sendProcessStdin,\n                             'process1', 'chars for stdin')\n\n    def test_sendProcessStdin_raises_no_file_when_write_raises_epipe(self):\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', 'foo')\n        supervisord = PopulatedDummySupervisor(options, 'process1', pconfig1)\n        supervisord.set_procattr('process1', 'pid', 42)\n        supervisord.set_procattr('process1', 'killing', False)\n        supervisord.set_procattr('process1', 'write_exception',\n            OSError(errno.EPIPE, os.strerror(errno.EPIPE)))\n        interface   = self._makeOne(supervisord)\n        from supervisor import xmlrpc\n        self._assertRPCError(xmlrpc.Faults.NO_FILE,\n                             interface.sendProcessStdin,\n                             'process1', 'chars for stdin')\n\n    def test_sendProcessStdin_reraises_other_oserrors(self):\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', 'foo')\n        supervisord = PopulatedDummySupervisor(options, 'process1', pconfig1)\n        supervisord.set_procattr('process1', 'pid', 42)\n        supervisord.set_procattr('process1', 'killing', False)\n        supervisord.set_procattr('process1', 'write_exception',\n            OSError(errno.EINTR, os.strerror(errno.EINTR)))\n        interface   = self._makeOne(supervisord)\n        self.assertRaises(OSError,\n                          interface.sendProcessStdin,\n                          'process1', 'chars for stdin')\n\n    def test_sendProcessStdin_writes_chars_and_returns_true(self):\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', 'foo')\n        supervisord = PopulatedDummySupervisor(options, 'process1', pconfig1)\n        supervisord.set_procattr('process1', 'pid', 42)\n        interface   = self._makeOne(supervisord)\n        chars = b'chars for stdin'\n        self.assertTrue(interface.sendProcessStdin('process1', chars))\n        self.assertEqual('sendProcessStdin', interface.update_text)\n        process1 = supervisord.process_groups['process1'].processes['process1']\n        self.assertEqual(process1.stdin_buffer, chars)\n\n    def test_sendProcessStdin_unicode_encoded_to_utf8(self):\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', 'foo')\n        supervisord = PopulatedDummySupervisor(options, 'process1', pconfig1)\n        supervisord.set_procattr('process1', 'pid', 42)\n        interface   = self._makeOne(supervisord)\n        interface.sendProcessStdin('process1', as_string(b'fi\\xc3\\xad'))\n        process1 = supervisord.process_groups['process1'].processes['process1']\n        self.assertEqual(process1.stdin_buffer, b'fi\\xc3\\xad')\n\n    def test_sendRemoteCommEvent_notifies_subscribers(self):\n        options = DummyOptions()\n        supervisord = DummySupervisor(options)\n        interface = self._makeOne(supervisord)\n\n        from supervisor import events\n        L = []\n        def callback(event):\n            L.append(event)\n\n        try:\n            events.callbacks[:] = [(events.RemoteCommunicationEvent, callback)]\n            result = interface.sendRemoteCommEvent('foo', 'bar')\n        finally:\n            events.callbacks[:] = []\n            events.clear()\n\n        self.assertTrue(result)\n        self.assertEqual(len(L), 1)\n        event = L[0]\n        self.assertEqual(event.type, 'foo')\n        self.assertEqual(event.data, 'bar')\n\n    def test_sendRemoteCommEvent_unicode_encoded_to_utf8(self):\n        options = DummyOptions()\n        supervisord = DummySupervisor(options)\n        interface = self._makeOne(supervisord)\n\n        from supervisor import events\n        L = []\n        def callback(event):\n            L.append(event)\n\n        try:\n            events.callbacks[:] = [(events.RemoteCommunicationEvent, callback)]\n            result = interface.sendRemoteCommEvent(\n                as_string('fií once'),\n                as_string('fií twice'),\n                )\n        finally:\n            events.callbacks[:] = []\n            events.clear()\n\n        self.assertTrue(result)\n        self.assertEqual(len(L), 1)\n        event = L[0]\n        if PY2:\n            self.assertEqual(event.type, 'fi\\xc3\\xad once')\n            self.assertEqual(event.data, 'fi\\xc3\\xad twice')\n        else:\n            self.assertEqual(event.type, 'fií once')\n            self.assertEqual(event.data, 'fií twice')\n\n\nclass SystemNamespaceXMLRPCInterfaceTests(TestBase):\n    def _getTargetClass(self):\n        from supervisor import xmlrpc\n        return xmlrpc.SystemNamespaceRPCInterface\n\n    def _makeOne(self):\n        from supervisor import rpcinterface\n        supervisord = DummySupervisor()\n        supervisor = rpcinterface.SupervisorNamespaceRPCInterface(supervisord)\n        return self._getTargetClass()(\n            [('supervisor', supervisor),\n             ]\n            )\n\n    def test_ctor(self):\n        interface = self._makeOne()\n        self.assertTrue(interface.namespaces['supervisor'])\n        self.assertTrue(interface.namespaces['system'])\n\n    def test_listMethods(self):\n        interface = self._makeOne()\n        methods = interface.listMethods()\n        methods.sort()\n        keys = list(interface._listMethods().keys())\n        keys.sort()\n        self.assertEqual(methods, keys)\n\n    def test_methodSignature(self):\n        from supervisor import xmlrpc\n        interface = self._makeOne()\n        self._assertRPCError(xmlrpc.Faults.SIGNATURE_UNSUPPORTED,\n                             interface.methodSignature,\n                             ['foo.bar'])\n        result = interface.methodSignature('system.methodSignature')\n        self.assertEqual(result, ['array', 'string'])\n\n    def test_allMethodDocs(self):\n        from supervisor import xmlrpc\n        # belt-and-suspenders test for docstring-as-typing parsing correctness\n        # and documentation validity vs. implementation\n        _RPCTYPES = ['int', 'double', 'string', 'boolean', 'dateTime.iso8601',\n                     'base64', 'binary', 'array', 'struct']\n        interface = self._makeOne()\n        methods = interface._listMethods()\n        for k in methods.keys():\n            # if a method doesn't have a @return value, an RPCError is raised.\n            # Detect that here.\n            try:\n                interface.methodSignature(k)\n            except xmlrpc.RPCError:\n                raise AssertionError('methodSignature for %s raises '\n                                     'RPCError (missing @return doc?)' % k)\n\n            # we want to test that the number of arguments implemented in\n            # the function is the same as the number of arguments implied by\n            # the doc @params, and that they show up in the same order.\n            ns_name, method_name = k.split('.', 1)\n            namespace = interface.namespaces[ns_name]\n            meth = getattr(namespace, method_name)\n            try:\n                code = meth.func_code\n            except Exception:\n                code = meth.__code__\n            argnames = code.co_varnames[1:code.co_argcount]\n            parsed = xmlrpc.gettags(str(meth.__doc__))\n\n            plines = []\n            ptypes = []\n            pnames = []\n            ptexts = []\n\n            rlines = []\n            rtypes = []\n            rnames = []\n            rtexts = []\n\n            for thing in parsed:\n                if thing[1] == 'param': # tag name\n                    plines.append(thing[0]) # doc line number\n                    ptypes.append(thing[2]) # data type\n                    pnames.append(thing[3]) # function name\n                    ptexts.append(thing[4])  # description\n                elif thing[1] == 'return': # tag name\n                    rlines.append(thing[0]) # doc line number\n                    rtypes.append(thing[2]) # data type\n                    rnames.append(thing[3]) # function name\n                    rtexts.append(thing[4])  # description\n                elif thing[1] is not None:\n                    raise AssertionError(\n                        'unknown tag type %s for %s, parsed %s' % (thing[1],\n                                                                   k,\n                                                                   parsed))\n            # param tokens\n\n            if len(argnames) != len(pnames):\n                raise AssertionError('Incorrect documentation '\n                                     '(%s args, %s doc params) in %s'\n                                     % (len(argnames), len(pnames), k))\n            for docline in plines:\n                self.assertTrue(type(docline) == int, (docline,\n                                                       type(docline),\n                                                       k,\n                                                       parsed))\n            for doctype in ptypes:\n                self.assertTrue(doctype in _RPCTYPES, doctype)\n            for x in range(len(pnames)):\n                if pnames[x] != argnames[x]:\n                    msg = 'Name wrong: (%s vs. %s in %s)\\n%s' % (pnames[x],\n                                                                 argnames[x],\n                                                                 k,\n                                                                 parsed)\n                    raise AssertionError(msg)\n            for doctext in ptexts:\n                self.assertTrue(type(doctext) == type(''), doctext)\n\n            # result tokens\n\n            if len(rlines) > 1:\n                raise AssertionError(\n                    'Duplicate @return values in docs for %s' % k)\n            for docline in rlines:\n                self.assertTrue(type(docline) == int, (docline,\n                                                       type(docline), k))\n            for doctype in rtypes:\n                self.assertTrue(doctype in _RPCTYPES, doctype)\n            for docname in rnames:\n                self.assertTrue(type(docname) == type(''), (docname,\n                                                            type(docname),\n                                                            k))\n            for doctext in rtexts:\n                self.assertTrue(type(doctext) == type(''), (doctext,\n                                                            type(doctext), k))\n\n    def test_multicall_simplevals(self):\n        interface = self._makeOne()\n        results = interface.multicall([\n            {'methodName':'system.methodHelp', 'params':['system.methodHelp']},\n            {'methodName':'system.listMethods', 'params':[]},\n            ])\n        self.assertEqual(results[0], interface.methodHelp('system.methodHelp'))\n        self.assertEqual(results[1], interface.listMethods())\n\n    def test_multicall_recursion_guard(self):\n        from supervisor import xmlrpc\n        interface = self._makeOne()\n        results = interface.multicall([\n            {'methodName': 'system.multicall', 'params': []},\n        ])\n\n        e = xmlrpc.RPCError(xmlrpc.Faults.INCORRECT_PARAMETERS,\n                'Recursive system.multicall forbidden')\n        recursion_fault = {'faultCode': e.code, 'faultString': e.text}\n        self.assertEqual(results, [recursion_fault])\n\n    def test_multicall_nested_callback(self):\n        from supervisor import http\n        interface = self._makeOne()\n        callback = interface.multicall([\n            {'methodName':'supervisor.stopAllProcesses'}])\n        results = http.NOT_DONE_YET\n        while results is http.NOT_DONE_YET:\n            results = callback()\n        self.assertEqual(results[0], [])\n\n    def test_methodHelp(self):\n        from supervisor import xmlrpc\n        interface = self._makeOne()\n        self._assertRPCError(xmlrpc.Faults.SIGNATURE_UNSUPPORTED,\n                             interface.methodHelp,\n                             ['foo.bar'])\n        help = interface.methodHelp('system.methodHelp')\n        self.assertEqual(help, interface.methodHelp.__doc__)\n\nclass Test_make_allfunc(unittest.TestCase):\n    def _callFUT(self, processes, predicate, func, **extra_kwargs):\n        from supervisor.rpcinterface import make_allfunc\n        return make_allfunc(processes, predicate, func, **extra_kwargs)\n\n    def test_rpcerror_nocallbacks(self):\n        from supervisor import xmlrpc\n        def cb(name, **kw):\n            raise xmlrpc.RPCError(xmlrpc.Faults.FAILED)\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', 'foo')\n        proc = DummyProcess(pconfig1)\n        group = DummyProcessGroup(pconfig1)\n        def pred(proc):\n            return True\n        af = self._callFUT([(group, proc)], pred, cb)\n        result = af()\n        self.assertEqual(result,\n            [{'description': 'FAILED',\n            'group': 'process1',\n            'name': 'process1',\n            'status': xmlrpc.Faults.FAILED}])\n\n    def test_func_callback_normal_return_val(self):\n        def cb(name, **kw):\n            return lambda: 1\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', 'foo')\n        proc = DummyProcess(pconfig1)\n        group = DummyProcessGroup(pconfig1)\n        def pred(proc):\n            return True\n        af = self._callFUT([(group, proc)], pred, cb)\n        result = af()\n        self.assertEqual(\n            result,\n            [{'group': 'process1',\n              'description': 'OK',\n              'status': 80, 'name': 'process1'}]\n            )\n\n    def test_func_callback_raises_RPCError(self):\n        from supervisor import xmlrpc\n        def cb(name, **kw):\n            def inner():\n                raise xmlrpc.RPCError(xmlrpc.Faults.FAILED)\n            return inner\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', 'foo')\n        proc = DummyProcess(pconfig1)\n        group = DummyProcessGroup(pconfig1)\n        def pred(proc):\n            return True\n        af = self._callFUT([(group, proc)], pred, cb)\n        result = af()\n        self.assertEqual(\n            result,\n            [{'description': 'FAILED',\n              'group': 'process1',\n              'status': 30,\n              'name': 'process1'}]\n            )\n\n    def test_func_callback_returns_NOT_DONE_YET(self):\n        from supervisor.http import NOT_DONE_YET\n        def cb(name, **kw):\n            def inner():\n                return NOT_DONE_YET\n            return inner\n        options = DummyOptions()\n        pconfig1 = DummyPConfig(options, 'process1', 'foo')\n        proc = DummyProcess(pconfig1)\n        group = DummyProcessGroup(pconfig1)\n        def pred(proc):\n            return True\n        af = self._callFUT([(group, proc)], pred, cb)\n        result = af()\n        self.assertEqual(\n            result,\n            NOT_DONE_YET,\n            )\n\nclass Test_make_main_rpcinterface(unittest.TestCase):\n    def _callFUT(self, supervisord):\n        from supervisor.rpcinterface import make_main_rpcinterface\n        return make_main_rpcinterface(supervisord)\n\n    def test_it(self):\n        inst = self._callFUT(None)\n        self.assertEqual(\n            inst.__class__.__name__,\n            'SupervisorNamespaceRPCInterface'\n            )\n\n\nclass DummyRPCInterface:\n    def hello(self):\n        return 'Hello!'\n"
  },
  {
    "path": "supervisor/tests/test_socket_manager.py",
    "content": "\"\"\"Test suite for supervisor.socket_manager\"\"\"\n\nimport gc\nimport os\nimport unittest\nimport socket\nimport tempfile\n\ntry:\n    import __pypy__\nexcept ImportError:\n    __pypy__ = None\n\nfrom supervisor.tests.base import DummySocketConfig\nfrom supervisor.tests.base import DummyLogger\nfrom supervisor.datatypes import UnixStreamSocketConfig\nfrom supervisor.datatypes import InetStreamSocketConfig\n\nclass Subject:\n\n    def __init__(self):\n        self.value = 5\n\n    def getValue(self):\n        return self.value\n\n    def setValue(self, val):\n        self.value = val\n\nclass ProxyTest(unittest.TestCase):\n\n    def setUp(self):\n        self.on_deleteCalled = False\n\n    def _getTargetClass(self):\n        from supervisor.socket_manager import Proxy\n        return Proxy\n\n    def _makeOne(self, *args, **kw):\n        return self._getTargetClass()(*args, **kw)\n\n    def setOnDeleteCalled(self):\n        self.on_deleteCalled = True\n\n    def test_proxy_getattr(self):\n        proxy = self._makeOne(Subject())\n        self.assertEqual(5, proxy.getValue())\n\n    def test_on_delete(self):\n        proxy = self._makeOne(Subject(), on_delete=self.setOnDeleteCalled)\n        self.assertEqual(5, proxy.getValue())\n        proxy = None\n        gc_collect()\n        self.assertTrue(self.on_deleteCalled)\n\nclass ReferenceCounterTest(unittest.TestCase):\n\n    def setUp(self):\n        self.running = False\n\n    def start(self):\n        self.running = True\n\n    def stop(self):\n        self.running = False\n\n    def _getTargetClass(self):\n        from supervisor.socket_manager import ReferenceCounter\n        return ReferenceCounter\n\n    def _makeOne(self, *args, **kw):\n        return self._getTargetClass()(*args, **kw)\n\n    def test_incr_and_decr(self):\n        ctr = self._makeOne(on_zero=self.stop,on_non_zero=self.start)\n        self.assertFalse(self.running)\n        ctr.increment()\n        self.assertTrue(self.running)\n        self.assertEqual(1, ctr.get_count())\n        ctr.increment()\n        self.assertTrue(self.running)\n        self.assertEqual(2, ctr.get_count())\n        ctr.decrement()\n        self.assertTrue(self.running)\n        self.assertEqual(1, ctr.get_count())\n        ctr.decrement()\n        self.assertFalse(self.running)\n        self.assertEqual(0, ctr.get_count())\n\n    def test_decr_at_zero_raises_error(self):\n        ctr = self._makeOne(on_zero=self.stop,on_non_zero=self.start)\n        self.assertRaises(Exception, ctr.decrement)\n\nclass SocketManagerTest(unittest.TestCase):\n\n    def tearDown(self):\n        gc_collect()\n\n    def _getTargetClass(self):\n        from supervisor.socket_manager import SocketManager\n        return SocketManager\n\n    def _makeOne(self, *args, **kw):\n        return self._getTargetClass()(*args, **kw)\n\n    def test_repr(self):\n        conf = DummySocketConfig(2)\n        sock_manager = self._makeOne(conf)\n        expected = \"<%s at %s for %s>\" % (\n            sock_manager.__class__, id(sock_manager), conf.url)\n        self.assertEqual(repr(sock_manager), expected)\n\n    def test_get_config(self):\n        conf = DummySocketConfig(2)\n        sock_manager = self._makeOne(conf)\n        self.assertEqual(conf, sock_manager.config())\n\n    def test_tcp_w_hostname(self):\n        conf = InetStreamSocketConfig('localhost', 51041)\n        sock_manager = self._makeOne(conf)\n        self.assertEqual(sock_manager.socket_config, conf)\n        sock = sock_manager.get_socket()\n        self.assertEqual(sock.getsockname(), ('127.0.0.1', 51041))\n\n    def test_tcp_w_ip(self):\n        conf = InetStreamSocketConfig('127.0.0.1', 51041)\n        sock_manager = self._makeOne(conf)\n        self.assertEqual(sock_manager.socket_config, conf)\n        sock = sock_manager.get_socket()\n        self.assertEqual(sock.getsockname(), ('127.0.0.1', 51041))\n\n    def test_unix(self):\n        (tf_fd, tf_name) = tempfile.mkstemp()\n        conf = UnixStreamSocketConfig(tf_name)\n        sock_manager = self._makeOne(conf)\n        self.assertEqual(sock_manager.socket_config, conf)\n        sock = sock_manager.get_socket()\n        self.assertEqual(sock.getsockname(), tf_name)\n        sock = None\n        os.close(tf_fd)\n\n    def test_socket_lifecycle(self):\n        conf = DummySocketConfig(2)\n        sock_manager = self._makeOne(conf)\n        # Assert that sockets are created on demand\n        self.assertFalse(sock_manager.is_prepared())\n        # Get two socket references\n        sock = sock_manager.get_socket()\n        self.assertTrue(sock_manager.is_prepared()) #socket created on demand\n        sock_id = id(sock._get())\n        sock2 = sock_manager.get_socket()\n        sock2_id = id(sock2._get())\n        # Assert that they are not the same proxy object\n        self.assertNotEqual(sock, sock2)\n        # Assert that they are the same underlying socket\n        self.assertEqual(sock_id, sock2_id)\n        # Socket not actually closed yet b/c ref ct is 2\n        self.assertEqual(2, sock_manager.get_socket_ref_count())\n        self.assertTrue(sock_manager.is_prepared())\n        self.assertFalse(sock_manager.socket.close_called)\n        sock = None\n        gc_collect()\n        # Socket not actually closed yet b/c ref ct is 1\n        self.assertTrue(sock_manager.is_prepared())\n        self.assertFalse(sock_manager.socket.close_called)\n        sock2 = None\n        gc_collect()\n        # Socket closed\n        self.assertFalse(sock_manager.is_prepared())\n        self.assertTrue(sock_manager.socket.close_called)\n\n        # Get a new socket reference\n        sock3 = sock_manager.get_socket()\n        self.assertTrue(sock_manager.is_prepared())\n        sock3_id = id(sock3._get())\n        # Assert that it is not the same socket\n        self.assertNotEqual(sock_id, sock3_id)\n        # Drop ref ct to zero\n        del sock3\n        gc_collect()\n        # Now assert that socket is closed\n        self.assertFalse(sock_manager.is_prepared())\n        self.assertTrue(sock_manager.socket.close_called)\n\n    def test_logging(self):\n        conf = DummySocketConfig(1)\n        logger = DummyLogger()\n        sock_manager = self._makeOne(conf, logger=logger)\n        # socket open\n        sock = sock_manager.get_socket()\n        self.assertEqual(len(logger.data), 1)\n        self.assertEqual('Creating socket %s' % repr(conf), logger.data[0])\n        # socket close\n        del sock\n        gc_collect()\n        self.assertEqual(len(logger.data), 2)\n        self.assertEqual('Closing socket %s' % repr(conf), logger.data[1])\n\n    def test_prepare_socket(self):\n        conf = DummySocketConfig(1)\n        sock_manager = self._makeOne(conf)\n        sock = sock_manager.get_socket()\n        self.assertTrue(sock_manager.is_prepared())\n        self.assertFalse(sock.bind_called)\n        self.assertTrue(sock.listen_called)\n        self.assertFalse(sock.close_called)\n\n    def test_prepare_socket_uses_configured_backlog(self):\n        conf = DummySocketConfig(1, backlog=42)\n        sock_manager = self._makeOne(conf)\n        sock = sock_manager.get_socket()\n        self.assertTrue(sock_manager.is_prepared())\n        self.assertEqual(sock.listen_backlog, conf.get_backlog())\n\n    def test_prepare_socket_uses_somaxconn_if_no_backlog_configured(self):\n        conf = DummySocketConfig(1, backlog=None)\n        sock_manager = self._makeOne(conf)\n        sock = sock_manager.get_socket()\n        self.assertTrue(sock_manager.is_prepared())\n        self.assertEqual(sock.listen_backlog, socket.SOMAXCONN)\n\n    def test_tcp_socket_already_taken(self):\n        conf = InetStreamSocketConfig('127.0.0.1', 51041)\n        sock_manager = self._makeOne(conf)\n        sock = sock_manager.get_socket()\n        sock_manager2 = self._makeOne(conf)\n        self.assertRaises(socket.error, sock_manager2.get_socket)\n        del sock\n\n    def test_unix_bad_sock(self):\n        conf = UnixStreamSocketConfig('/notthere/foo.sock')\n        sock_manager = self._makeOne(conf)\n        self.assertRaises(socket.error, sock_manager.get_socket)\n\n    def test_close_requires_prepared_socket(self):\n        conf = InetStreamSocketConfig('127.0.0.1', 51041)\n        sock_manager = self._makeOne(conf)\n        self.assertFalse(sock_manager.is_prepared())\n        try:\n            sock_manager._close()\n            self.fail()\n        except Exception as e:\n            self.assertEqual(e.args[0], 'Socket has not been prepared')\n\ndef gc_collect():\n    if __pypy__ is not None:\n        gc.collect()\n        gc.collect()\n        gc.collect()\n"
  },
  {
    "path": "supervisor/tests/test_states.py",
    "content": "\"\"\"Test suite for supervisor.states\"\"\"\n\nimport unittest\nfrom supervisor import states\n\nclass TopLevelProcessStateTests(unittest.TestCase):\n    def test_module_has_process_states(self):\n        self.assertTrue(hasattr(states, 'ProcessStates'))\n    \n    def test_stopped_states_do_not_overlap_with_running_states(self):\n        for state in states.STOPPED_STATES:\n            self.assertFalse(state in states.RUNNING_STATES)\n\n    def test_running_states_do_not_overlap_with_stopped_states(self):\n        for state in states.RUNNING_STATES:\n            self.assertFalse(state in states.STOPPED_STATES)\n\n    def test_getProcessStateDescription_returns_string_when_found(self):\n        state = states.ProcessStates.STARTING\n        self.assertEqual(states.getProcessStateDescription(state),\n            'STARTING')\n\n    def test_getProcessStateDescription_returns_None_when_not_found(self):\n        self.assertEqual(states.getProcessStateDescription(3.14159),\n            None)\n\nclass TopLevelSupervisorStateTests(unittest.TestCase):\n    def test_module_has_supervisor_states(self):\n        self.assertTrue(hasattr(states, 'SupervisorStates'))\n\n    def test_getSupervisorStateDescription_returns_string_when_found(self):\n        state = states.SupervisorStates.RUNNING\n        self.assertEqual(states.getSupervisorStateDescription(state),\n            'RUNNING')\n\n    def test_getSupervisorStateDescription_returns_None_when_not_found(self):\n        self.assertEqual(states.getSupervisorStateDescription(3.14159),\n            None)\n\nclass TopLevelEventListenerStateTests(unittest.TestCase):\n    def test_module_has_eventlistener_states(self):\n        self.assertTrue(hasattr(states, 'EventListenerStates'))\n\n    def test_getEventListenerStateDescription_returns_string_when_found(self):\n        state = states.EventListenerStates.ACKNOWLEDGED\n        self.assertEqual(states.getEventListenerStateDescription(state),\n            'ACKNOWLEDGED')\n\n    def test_getEventListenerStateDescription_returns_None_when_not_found(self):\n        self.assertEqual(states.getEventListenerStateDescription(3.14159),\n            None)\n"
  },
  {
    "path": "supervisor/tests/test_supervisorctl.py",
    "content": "import unittest\nfrom supervisor import xmlrpc\nfrom supervisor.compat import StringIO\nfrom supervisor.compat import xmlrpclib\nfrom supervisor.supervisorctl import LSBInitExitStatuses, LSBStatusExitStatuses\nfrom supervisor.tests.base import DummyRPCServer\n\nclass fgthread_Tests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.supervisorctl import fgthread\n        return fgthread\n\n    def _makeOne(self, program, ctl):\n        return self._getTargetClass()(program, ctl)\n\n    def test_ctor(self):\n        options = DummyClientOptions()\n        ctl = DummyController(options)\n        inst = self._makeOne(None, ctl)\n        self.assertEqual(inst.killed, False)\n\n    def test_globaltrace_call(self):\n        options = DummyClientOptions()\n        ctl = DummyController(options)\n        inst = self._makeOne(None, ctl)\n        result = inst.globaltrace(None, 'call', None)\n        self.assertEqual(result, inst.localtrace)\n\n    def test_globaltrace_noncall(self):\n        options = DummyClientOptions()\n        ctl = DummyController(options)\n        inst = self._makeOne(None, ctl)\n        result = inst.globaltrace(None, None, None)\n        self.assertEqual(result, None)\n\n    def test_localtrace_killed_whyline(self):\n        options = DummyClientOptions()\n        ctl = DummyController(options)\n        inst = self._makeOne(None, ctl)\n        inst.killed = True\n        try:\n            inst.localtrace(None, 'line', None)\n        except SystemExit as e:\n            self.assertEqual(e.code, None)\n        else:\n            self.fail(\"No exception thrown. Excepted SystemExit\")\n\n    def test_localtrace_killed_not_whyline(self):\n        options = DummyClientOptions()\n        ctl = DummyController(options)\n        inst = self._makeOne(None, ctl)\n        inst.killed = True\n        result = inst.localtrace(None, None, None)\n        self.assertEqual(result, inst.localtrace)\n\n    def test_kill(self):\n        options = DummyClientOptions()\n        ctl = DummyController(options)\n        inst = self._makeOne(None, ctl)\n        inst.killed = True\n        class DummyCloseable(object):\n            def close(self):\n                self.closed = True\n        inst.output_handler = DummyCloseable()\n        inst.error_handler = DummyCloseable()\n        inst.kill()\n        self.assertTrue(inst.killed)\n        self.assertTrue(inst.output_handler.closed)\n        self.assertTrue(inst.error_handler.closed)\n\nclass ControllerTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.supervisorctl import Controller\n        return Controller\n\n    def _makeOne(self, options):\n        return self._getTargetClass()(options)\n\n\n    def test_ctor(self):\n        options = DummyClientOptions()\n        controller = self._makeOne(options)\n        self.assertEqual(controller.prompt, options.prompt + '> ')\n\n    def test__upcheck(self):\n        options = DummyClientOptions()\n        controller = self._makeOne(options)\n        result = controller.upcheck()\n        self.assertEqual(result, True)\n\n    def test__upcheck_wrong_server_version(self):\n        options = DummyClientOptions()\n        options._server.supervisor.getVersion = lambda *x: '1.0'\n        controller = self._makeOne(options)\n        controller.stdout = StringIO()\n        result = controller.upcheck()\n        self.assertEqual(result, False)\n        self.assertEqual(controller.stdout.getvalue(),\n                         'Sorry, this version of supervisorctl expects'\n                         ' to talk to a server with API version 3.0, but'\n                         ' the remote version is 1.0.\\n')\n\n    def test__upcheck_unknown_method(self):\n        options = DummyClientOptions()\n        from supervisor.xmlrpc import Faults\n        def getVersion():\n            raise xmlrpclib.Fault(Faults.UNKNOWN_METHOD, 'duh')\n        options._server.supervisor.getVersion = getVersion\n        controller = self._makeOne(options)\n        controller.stdout = StringIO()\n        result = controller.upcheck()\n        self.assertEqual(result, False)\n        self.assertEqual(controller.stdout.getvalue(),\n                         'Sorry, supervisord responded but did not recognize'\n                         ' the supervisor namespace commands that'\n                         ' supervisorctl uses to control it.  Please check'\n                         ' that the [rpcinterface:supervisor] section is'\n                         ' enabled in the configuration file'\n                         ' (see sample.conf).\\n')\n\n    def test__upcheck_reraises_other_xmlrpc_faults(self):\n        options = DummyClientOptions()\n        from supervisor.xmlrpc import Faults\n        def f(*arg, **kw):\n            raise xmlrpclib.Fault(Faults.FAILED, '')\n        options._server.supervisor.getVersion = f\n        controller = self._makeOne(options)\n        controller.stdout = StringIO()\n        self.assertRaises(xmlrpclib.Fault, controller.upcheck)\n        self.assertEqual(controller.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test__upcheck_catches_socket_error_ECONNREFUSED(self):\n        options = DummyClientOptions()\n        import socket\n        import errno\n        def raise_fault(*arg, **kw):\n            raise socket.error(errno.ECONNREFUSED, 'nobody home')\n        options._server.supervisor.getVersion = raise_fault\n\n        controller = self._makeOne(options)\n        controller.stdout = StringIO()\n\n        result = controller.upcheck()\n        self.assertEqual(result, False)\n\n        output = controller.stdout.getvalue()\n        self.assertTrue('refused connection' in output)\n        self.assertEqual(controller.exitstatus, LSBInitExitStatuses.INSUFFICIENT_PRIVILEGES)\n\n    def test__upcheck_catches_socket_error_ENOENT(self):\n        options = DummyClientOptions()\n        import socket\n        import errno\n        def raise_fault(*arg, **kw):\n            raise socket.error(errno.ENOENT, 'nobody home')\n        options._server.supervisor.getVersion = raise_fault\n\n        controller = self._makeOne(options)\n        controller.stdout = StringIO()\n\n        result = controller.upcheck()\n        self.assertEqual(result, False)\n\n        output = controller.stdout.getvalue()\n        self.assertTrue('no such file' in output)\n        self.assertEqual(controller.exitstatus, LSBInitExitStatuses.NOT_RUNNING)\n\n    def test__upcheck_reraises_other_socket_faults(self):\n        options = DummyClientOptions()\n        import socket\n        import errno\n        def f(*arg, **kw):\n            raise socket.error(errno.EBADF, '')\n        options._server.supervisor.getVersion = f\n        controller = self._makeOne(options)\n        controller.stdout = StringIO()\n        self.assertRaises(socket.error, controller.upcheck)\n\n    def test_onecmd(self):\n        options = DummyClientOptions()\n        controller = self._makeOne(options)\n        controller.stdout = StringIO()\n        plugin = DummyPlugin()\n        controller.options.plugins = (plugin,)\n        result = controller.onecmd('help')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.helped, True)\n\n    def test_onecmd_empty_does_not_repeat_previous_cmd(self):\n        options = DummyClientOptions()\n        controller = self._makeOne(options)\n        controller.stdout = StringIO()\n        plugin = DummyPlugin()\n        controller.options.plugins = (plugin,)\n        plugin.helped = False\n        controller.onecmd('help')\n        self.assertTrue(plugin.helped)\n        plugin.helped = False\n        controller.onecmd('')\n        self.assertFalse(plugin.helped)\n\n    def test_onecmd_clears_completion_cache(self):\n        options = DummyClientOptions()\n        controller = self._makeOne(options)\n        controller.stdout = StringIO()\n        controller._complete_info = {}\n        controller.onecmd('help')\n        self.assertEqual(controller._complete_info, None)\n\n    def test_onecmd_bad_command_error(self):\n        options = DummyClientOptions()\n        controller = self._makeOne(options)\n        controller.stdout = StringIO()\n        controller.onecmd(\"badcmd\")\n        self.assertEqual(controller.stdout.getvalue(),\n            \"*** Unknown syntax: badcmd\\n\")\n        self.assertEqual(controller.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_complete_action_empty(self):\n        options = DummyClientOptions()\n        controller = self._makeOne(options)\n        controller.stdout=StringIO()\n        controller.vocab = ['help']\n        result = controller.complete('', 0, line='')\n        self.assertEqual(result, 'help ')\n        result = controller.complete('', 1, line='')\n        self.assertEqual(result, None)\n\n    def test_complete_action_partial(self):\n        options = DummyClientOptions()\n        controller = self._makeOne(options)\n        controller.stdout=StringIO()\n        controller.vocab = ['help']\n        result = controller.complete('h', 0, line='h')\n        self.assertEqual(result, 'help ')\n        result = controller.complete('h', 1, line='h')\n        self.assertEqual(result, None)\n\n    def test_complete_action_whole(self):\n        options = DummyClientOptions()\n        controller = self._makeOne(options)\n        controller.stdout=StringIO()\n        controller.vocab = ['help']\n        result = controller.complete('help', 0, line='help')\n        self.assertEqual(result, 'help ')\n\n    def test_complete_unknown_action_uncompletable(self):\n        options = DummyClientOptions()\n        controller = self._makeOne(options)\n        controller.stdout=StringIO()\n        result = controller.complete('bad', 0, line='bad')\n        self.assertEqual(result, None)\n\n    def test_complete_unknown_action_arg_uncompletable(self):\n        options = DummyClientOptions()\n        controller = self._makeOne(options)\n        controller.stdout=StringIO()\n        controller.vocab = ['help', 'add']\n        result = controller.complete('', 1, line='bad ')\n        self.assertEqual(result, None)\n\n    def test_complete_help_empty(self):\n        options = DummyClientOptions()\n        controller = self._makeOne(options)\n        controller.stdout=StringIO()\n        controller.vocab = ['help', 'start']\n        result = controller.complete('', 0, line='help ')\n        self.assertEqual(result, 'help ')\n        result = controller.complete('', 1, line='help ')\n        self.assertEqual(result, 'start ')\n        result = controller.complete('', 2, line='help ')\n        self.assertEqual(result, None)\n\n    def test_complete_help_action(self):\n        options = DummyClientOptions()\n        controller = self._makeOne(options)\n        controller.stdout=StringIO()\n        controller.vocab = ['help', 'start']\n        result = controller.complete('he', 0, line='help he')\n        self.assertEqual(result, 'help ')\n        result = controller.complete('he', 1, line='help he')\n        self.assertEqual(result, None)\n\n    def test_complete_start_empty(self):\n        options = DummyClientOptions()\n        controller = self._makeOne(options)\n        controller.stdout=StringIO()\n        controller.vocab = ['help', 'start']\n        result = controller.complete('', 0, line='start ')\n        self.assertEqual(result, 'foo ')\n        result = controller.complete('', 1, line='start ')\n        self.assertEqual(result, 'bar ')\n        result = controller.complete('', 2, line='start ')\n        self.assertEqual(result, 'baz:baz_01 ')\n        result = controller.complete('', 3, line='start ')\n        self.assertEqual(result, 'baz:* ')\n        result = controller.complete('', 4, line='start ')\n        self.assertEqual(result, None)\n\n    def test_complete_start_no_colon(self):\n        options = DummyClientOptions()\n        controller = self._makeOne(options)\n        controller.stdout=StringIO()\n        controller.vocab = ['help', 'start']\n        result = controller.complete('f', 0, line='start f')\n        self.assertEqual(result, 'foo ')\n        result = controller.complete('f', 1, line='start f')\n        self.assertEqual(result, None)\n\n    def test_complete_start_with_colon(self):\n        options = DummyClientOptions()\n        controller = self._makeOne(options)\n        controller.stdout=StringIO()\n        controller.vocab = ['help', 'start']\n        result = controller.complete('foo:', 0, line='start foo:')\n        self.assertEqual(result, 'foo:foo ')\n        result = controller.complete('foo:', 1, line='start foo:')\n        self.assertEqual(result, 'foo:* ')\n        result = controller.complete('foo:', 2, line='start foo:')\n        self.assertEqual(result, None)\n\n    def test_complete_start_uncompletable(self):\n        options = DummyClientOptions()\n        controller = self._makeOne(options)\n        controller.stdout=StringIO()\n        controller.vocab = ['help', 'start']\n        result = controller.complete('bad', 0, line='start bad')\n        self.assertEqual(result, None)\n\n    def test_complete_caches_process_info(self):\n        options = DummyClientOptions()\n        controller = self._makeOne(options)\n        controller.stdout=StringIO()\n        controller.vocab = ['help', 'start']\n        result = controller.complete('', 0, line='start ')\n        self.assertNotEqual(result, None)\n        def f(*arg, **kw):\n            raise Exception(\"should not have called getAllProcessInfo\")\n        controller.options._server.supervisor.getAllProcessInfo = f\n        controller.complete('', 1, line='start ')\n\n    def test_complete_add_empty(self):\n        options = DummyClientOptions()\n        controller = self._makeOne(options)\n        controller.stdout=StringIO()\n        controller.vocab = ['help', 'add']\n        result = controller.complete('', 0, line='add ')\n        self.assertEqual(result, 'foo ')\n        result = controller.complete('', 1, line='add ')\n        self.assertEqual(result, 'bar ')\n        result = controller.complete('', 2, line='add ')\n        self.assertEqual(result, 'baz ')\n        result = controller.complete('', 3, line='add ')\n        self.assertEqual(result, None)\n\n    def test_complete_add_uncompletable(self):\n        options = DummyClientOptions()\n        controller = self._makeOne(options)\n        controller.stdout=StringIO()\n        controller.vocab = ['help', 'add']\n        result = controller.complete('bad', 0, line='add bad')\n        self.assertEqual(result, None)\n\n    def test_complete_add_group(self):\n        options = DummyClientOptions()\n        controller = self._makeOne(options)\n        controller.stdout=StringIO()\n        controller.vocab = ['help', 'add']\n        result = controller.complete('f', 0, line='add f')\n        self.assertEqual(result, 'foo ')\n        result = controller.complete('f', 1, line='add f')\n        self.assertEqual(result, None)\n\n    def test_complete_reload_arg_uncompletable(self):\n        options = DummyClientOptions()\n        controller = self._makeOne(options)\n        controller.stdout=StringIO()\n        controller.vocab = ['help', 'reload']\n        result = controller.complete('', 1, line='reload ')\n        self.assertEqual(result, None)\n\n    def test_nohelp(self):\n        options = DummyClientOptions()\n        controller = self._makeOne(options)\n        self.assertEqual(controller.nohelp, '*** No help on %s')\n\n    def test_do_help(self):\n        options = DummyClientOptions()\n        controller = self._makeOne(options)\n        controller.stdout = StringIO()\n        results = controller.do_help('')\n        helpval = controller.stdout.getvalue()\n        self.assertEqual(results, None)\n        self.assertEqual(helpval, 'foo helped')\n\n    def test_do_help_for_help(self):\n        options = DummyClientOptions()\n        controller = self._makeOne(options)\n        controller.stdout = StringIO()\n        results = controller.do_help(\"help\")\n        self.assertEqual(results, None)\n        helpval = controller.stdout.getvalue()\n        self.assertTrue(\"help\\t\\tPrint a list\" in helpval)\n\n    def test_get_supervisor_returns_serverproxy_supervisor_namespace(self):\n        options = DummyClientOptions()\n        controller = self._makeOne(options)\n\n        proxy = controller.get_supervisor()\n        expected = options.getServerProxy().supervisor\n        self.assertEqual(proxy, expected)\n\n    def test_get_server_proxy_with_no_args_returns_serverproxy(self):\n        options = DummyClientOptions()\n        controller = self._makeOne(options)\n\n        proxy = controller.get_server_proxy()\n        expected = options.getServerProxy()\n        self.assertEqual(proxy, expected)\n\n    def test_get_server_proxy_with_namespace_returns_that_namespace(self):\n        options = DummyClientOptions()\n        controller = self._makeOne(options)\n\n        proxy = controller.get_server_proxy('system')\n        expected = options.getServerProxy().system\n        self.assertEqual(proxy, expected)\n\n    def test_real_controller_initialization(self):\n        from supervisor.options import ClientOptions\n        args = [] # simulating starting without parameters\n        options = ClientOptions()\n\n        # No default config file search in case they would exist\n        self.assertTrue(len(options.searchpaths) > 0)\n        options.searchpaths = []\n\n        options.realize(args, doc=__doc__)\n        self._makeOne(options) # should not raise\n\n\nclass TestControllerPluginBase(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.supervisorctl import ControllerPluginBase\n        return ControllerPluginBase\n\n    def _makeOne(self, *arg, **kw):\n        klass = self._getTargetClass()\n        options = DummyClientOptions()\n        ctl = DummyController(options)\n        plugin = klass(ctl, *arg, **kw)\n        return plugin\n\n    def test_do_help_noarg(self):\n        plugin = self._makeOne()\n        result = plugin.do_help(None)\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(), '\\n')\n        self.assertEqual(len(plugin.ctl.topics_printed), 1)\n        topics = plugin.ctl.topics_printed[0]\n        self.assertEqual(topics[0], 'unnamed commands (type help <topic>):')\n        self.assertEqual(topics[1], [])\n        self.assertEqual(topics[2], 15)\n        self.assertEqual(topics[3], 80)\n\n    def test_do_help_witharg(self):\n        plugin = self._makeOne()\n        result = plugin.do_help('foo')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(), 'no help on foo\\n')\n        self.assertEqual(len(plugin.ctl.topics_printed), 0)\n\nclass TestDefaultControllerPlugin(unittest.TestCase):\n\n    def _getTargetClass(self):\n        from supervisor.supervisorctl import DefaultControllerPlugin\n        return DefaultControllerPlugin\n\n    def _makeOne(self, *arg, **kw):\n        klass = self._getTargetClass()\n        options = DummyClientOptions()\n        ctl = DummyController(options)\n        plugin = klass(ctl, *arg, **kw)\n        return plugin\n\n    def test_tail_toofewargs(self):\n        plugin = self._makeOne()\n        result = plugin.do_tail('')\n        self.assertEqual(result, None)\n        lines = plugin.ctl.stdout.getvalue().split('\\n')\n        self.assertEqual(lines[0], 'Error: too few arguments')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_tail_toomanyargs(self):\n        plugin = self._makeOne()\n        result = plugin.do_tail('one two three four')\n        self.assertEqual(result, None)\n        lines = plugin.ctl.stdout.getvalue().split('\\n')\n        self.assertEqual(lines[0], 'Error: too many arguments')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_tail_f_noprocname(self):\n        plugin = self._makeOne()\n        result = plugin.do_tail('-f')\n        self.assertEqual(result, None)\n        lines = plugin.ctl.stdout.getvalue().split('\\n')\n        self.assertEqual(lines[0], 'Error: tail requires process name')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_tail_bad_modifier(self):\n        plugin = self._makeOne()\n        result = plugin.do_tail('-z foo')\n        self.assertEqual(result, None)\n        lines = plugin.ctl.stdout.getvalue().split('\\n')\n        self.assertEqual(lines[0], 'Error: bad argument -z')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_tail_defaults(self):\n        plugin = self._makeOne()\n        result = plugin.do_tail('foo')\n        self.assertEqual(result, None)\n        lines = plugin.ctl.stdout.getvalue().split('\\n')\n        self.assertEqual(len(lines), 12)\n        self.assertEqual(lines[0], 'stdout line')\n\n    def test_tail_no_file(self):\n        plugin = self._makeOne()\n        result = plugin.do_tail('NO_FILE')\n        self.assertEqual(result, None)\n        lines = plugin.ctl.stdout.getvalue().split('\\n')\n        self.assertEqual(len(lines), 2)\n        self.assertEqual(lines[0], 'NO_FILE: ERROR (no log file)')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_tail_failed(self):\n        plugin = self._makeOne()\n        result = plugin.do_tail('FAILED')\n        self.assertEqual(result, None)\n        lines = plugin.ctl.stdout.getvalue().split('\\n')\n        self.assertEqual(len(lines), 2)\n        self.assertEqual(lines[0], 'FAILED: ERROR (unknown error reading log)')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_tail_bad_name(self):\n        plugin = self._makeOne()\n        result = plugin.do_tail('BAD_NAME')\n        self.assertEqual(result, None)\n        lines = plugin.ctl.stdout.getvalue().split('\\n')\n        self.assertEqual(len(lines), 2)\n        self.assertEqual(lines[0], 'BAD_NAME: ERROR (no such process name)')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_tail_bytesmodifier(self):\n        plugin = self._makeOne()\n        result = plugin.do_tail('-10 foo')\n        self.assertEqual(result, None)\n        lines = plugin.ctl.stdout.getvalue().split('\\n')\n        self.assertEqual(len(lines), 3)\n        self.assertEqual(lines[0], 'dout line')\n\n    def test_tail_explicit_channel_stdout_nomodifier(self):\n        plugin = self._makeOne()\n        result = plugin.do_tail('foo stdout')\n        self.assertEqual(result, None)\n        lines = plugin.ctl.stdout.getvalue().split('\\n')\n        self.assertEqual(len(lines), 12)\n        self.assertEqual(lines[0], 'stdout line')\n\n    def test_tail_explicit_channel_stderr_nomodifier(self):\n        plugin = self._makeOne()\n        result = plugin.do_tail('foo stderr')\n        self.assertEqual(result, None)\n        lines = plugin.ctl.stdout.getvalue().split('\\n')\n        self.assertEqual(len(lines), 12)\n        self.assertEqual(lines[0], 'stderr line')\n\n    def test_tail_explicit_channel_unrecognized(self):\n        plugin = self._makeOne()\n        result = plugin.do_tail('foo fudge')\n        self.assertEqual(result, None)\n        value = plugin.ctl.stdout.getvalue().strip()\n        self.assertEqual(value, \"Error: bad channel 'fudge'\")\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_tail_upcheck_failed(self):\n        plugin = self._makeOne()\n        plugin.ctl.upcheck = lambda: False\n        called = []\n        def f(*arg, **kw):\n            called.append(True)\n        plugin.ctl.options._server.supervisor.readProcessStdoutLog = f\n        plugin.do_tail('foo')\n        self.assertEqual(called, [])\n\n    def test_status_help(self):\n        plugin = self._makeOne()\n        plugin.help_status()\n        out = plugin.ctl.stdout.getvalue()\n        self.assertTrue(\"status <name>\" in out)\n\n    def test_status_upcheck_failed(self):\n        plugin = self._makeOne()\n        plugin.ctl.upcheck = lambda: False\n        called = []\n        def f(*arg, **kw):\n            called.append(True)\n        plugin.ctl.options._server.supervisor.getAllProcessInfo = f\n        plugin.do_status('')\n        self.assertEqual(called, [])\n\n    def test_status_table_process_column_min_width(self):\n        plugin = self._makeOne()\n        result = plugin.do_status('')\n        self.assertEqual(result, None)\n        lines = plugin.ctl.stdout.getvalue().split(\"\\n\")\n        self.assertEqual(lines[0].index(\"RUNNING\"), 33)\n\n    def test_status_table_process_column_expands(self):\n        plugin = self._makeOne()\n        options = plugin.ctl.options\n        def f(*arg, **kw):\n            from supervisor.states import ProcessStates\n            return [{'name': 'foo'*50, # long name\n                     'group':'foo',\n                     'pid': 11,\n                     'state': ProcessStates.RUNNING,\n                     'statename': 'RUNNING',\n                     'start': 0,\n                     'stop': 0,\n                     'spawnerr': '',\n                     'now': 0,\n                     'description':'foo description'},\n                    {\n                    'name': 'bar', # short name\n                    'group': 'bar',\n                    'pid': 12,\n                    'state': ProcessStates.FATAL,\n                    'statename': 'RUNNING',\n                    'start': 0,\n                    'stop': 0,\n                    'spawnerr': '',\n                    'now': 0,\n                    'description': 'bar description',\n                    }]\n        options._server.supervisor.getAllProcessInfo = f\n        self.assertEqual(plugin.do_status(''), None)\n        lines = plugin.ctl.stdout.getvalue().split(\"\\n\")\n        self.assertEqual(lines[0].index(\"RUNNING\"), 157)\n        self.assertEqual(lines[1].index(\"RUNNING\"), 157)\n\n    def test_status_all_processes_no_arg(self):\n        plugin = self._makeOne()\n        result = plugin.do_status('')\n        self.assertEqual(result, None)\n        value = plugin.ctl.stdout.getvalue().split('\\n')\n        self.assertEqual(value[0].split(None, 2),\n                         ['foo', 'RUNNING', 'foo description'])\n        self.assertEqual(value[1].split(None, 2),\n                         ['bar', 'FATAL', 'bar description'])\n        self.assertEqual(value[2].split(None, 2),\n                         ['baz:baz_01', 'STOPPED', 'baz description'])\n        self.assertEqual(plugin.ctl.exitstatus, LSBStatusExitStatuses.NOT_RUNNING)\n\n\n    def test_status_success(self):\n        plugin = self._makeOne()\n        result = plugin.do_status('foo')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.SUCCESS)\n        value = plugin.ctl.stdout.getvalue().split('\\n')\n        self.assertEqual(value[0].split(None, 2),\n                         ['foo', 'RUNNING', 'foo description'])\n\n    def test_status_unknown_process(self):\n        plugin = self._makeOne()\n        result = plugin.do_status('unknownprogram')\n        self.assertEqual(result, None)\n        value = plugin.ctl.stdout.getvalue()\n        self.assertEqual(\"unknownprogram: ERROR (no such process)\\n\", value)\n        self.assertEqual(plugin.ctl.exitstatus, LSBStatusExitStatuses.UNKNOWN)\n\n    def test_status_all_processes_all_arg(self):\n        plugin = self._makeOne()\n        result = plugin.do_status('all')\n        self.assertEqual(result, None)\n        value = plugin.ctl.stdout.getvalue().split('\\n')\n        self.assertEqual(value[0].split(None, 2),\n                         ['foo', 'RUNNING', 'foo description'])\n        self.assertEqual(value[1].split(None, 2),\n                         ['bar', 'FATAL', 'bar description'])\n        self.assertEqual(value[2].split(None, 2),\n                         ['baz:baz_01', 'STOPPED', 'baz description'])\n        self.assertEqual(plugin.ctl.exitstatus, LSBStatusExitStatuses.NOT_RUNNING)\n\n    def test_status_process_name(self):\n        plugin = self._makeOne()\n        result = plugin.do_status('foo')\n        self.assertEqual(result, None)\n        value = plugin.ctl.stdout.getvalue().strip()\n        self.assertEqual(value.split(None, 2),\n                         ['foo', 'RUNNING', 'foo description'])\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.SUCCESS)\n\n    def test_status_group_name(self):\n        plugin = self._makeOne()\n        result = plugin.do_status('baz:*')\n        self.assertEqual(result, None)\n        value = plugin.ctl.stdout.getvalue().split('\\n')\n        self.assertEqual(value[0].split(None, 2),\n                         ['baz:baz_01', 'STOPPED', 'baz description'])\n        self.assertEqual(plugin.ctl.exitstatus, LSBStatusExitStatuses.NOT_RUNNING)\n\n    def test_status_mixed_names(self):\n        plugin = self._makeOne()\n        result = plugin.do_status('foo baz:*')\n        self.assertEqual(result, None)\n        value = plugin.ctl.stdout.getvalue().split('\\n')\n        self.assertEqual(value[0].split(None, 2),\n                         ['foo', 'RUNNING', 'foo description'])\n        self.assertEqual(value[1].split(None, 2),\n                         ['baz:baz_01', 'STOPPED', 'baz description'])\n        self.assertEqual(plugin.ctl.exitstatus, LSBStatusExitStatuses.NOT_RUNNING)\n\n    def test_status_bad_group_name(self):\n        plugin = self._makeOne()\n        result = plugin.do_status('badgroup:*')\n        self.assertEqual(result, None)\n        value = plugin.ctl.stdout.getvalue().split('\\n')\n        self.assertEqual(value[0], \"badgroup: ERROR (no such group)\")\n        self.assertEqual(plugin.ctl.exitstatus, LSBStatusExitStatuses.UNKNOWN)\n\n    def test_status_bad_process_name(self):\n        plugin = self._makeOne()\n        result = plugin.do_status('badprocess')\n        self.assertEqual(result, None)\n        value = plugin.ctl.stdout.getvalue().split('\\n')\n        self.assertEqual(value[0], \"badprocess: ERROR (no such process)\")\n        self.assertEqual(plugin.ctl.exitstatus, LSBStatusExitStatuses.UNKNOWN)\n\n    def test_status_bad_process_name_with_group(self):\n        plugin = self._makeOne()\n        result = plugin.do_status('badgroup:badprocess')\n        self.assertEqual(result, None)\n        value = plugin.ctl.stdout.getvalue().split('\\n')\n        self.assertEqual(value[0], \"badgroup:badprocess: \"\n                                   \"ERROR (no such process)\")\n        self.assertEqual(plugin.ctl.exitstatus, LSBStatusExitStatuses.UNKNOWN)\n\n    def test_start_help(self):\n        plugin = self._makeOne()\n        plugin.help_start()\n        out = plugin.ctl.stdout.getvalue()\n        self.assertTrue(\"start <name>\" in out)\n\n    def test_start_fail(self):\n        plugin = self._makeOne()\n        result = plugin.do_start('')\n        self.assertEqual(result, None)\n        expected = \"Error: start requires a process name\"\n        self.assertEqual(plugin.ctl.stdout.getvalue().split('\\n')[0], expected)\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.INVALID_ARGS)\n\n    def test_start_badname(self):\n        plugin = self._makeOne()\n        result = plugin.do_start('BAD_NAME')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'BAD_NAME: ERROR (no such process)\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_start_no_file(self):\n        plugin = self._makeOne()\n        result = plugin.do_start('NO_FILE')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'NO_FILE: ERROR (no such file)\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_start_not_executable(self):\n        plugin = self._makeOne()\n        result = plugin.do_start('NOT_EXECUTABLE')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'NOT_EXECUTABLE: ERROR (file is not executable)\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_start_alreadystarted(self):\n        plugin = self._makeOne()\n        result = plugin.do_start('ALREADY_STARTED')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'ALREADY_STARTED: ERROR (already started)\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.SUCCESS)\n\n    def test_start_spawnerror(self):\n        plugin = self._makeOne()\n        result = plugin.do_start('SPAWN_ERROR')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'SPAWN_ERROR: ERROR (spawn error)\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.NOT_RUNNING)\n\n    def test_start_abnormaltermination(self):\n        plugin = self._makeOne()\n        result = plugin.do_start('ABNORMAL_TERMINATION')\n        self.assertEqual(result, None)\n        expected = 'ABNORMAL_TERMINATION: ERROR (abnormal termination)\\n'\n        self.assertEqual(plugin.ctl.stdout.getvalue(), expected)\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.NOT_RUNNING)\n\n    def test_start_one_success(self):\n        plugin = self._makeOne()\n        result = plugin.do_start('foo')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'foo: started\\n')\n\n    def test_start_one_with_group_name_success(self):\n        plugin = self._makeOne()\n        result = plugin.do_start('foo:foo')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'foo: started\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.SUCCESS)\n\n    def test_start_many(self):\n        plugin = self._makeOne()\n        result = plugin.do_start('foo bar')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'foo: started\\nbar: started\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.SUCCESS)\n\n    def test_start_group(self):\n        plugin = self._makeOne()\n        result = plugin.do_start('foo:')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'foo:foo_00: started\\n'\n                         'foo:foo_01: started\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.SUCCESS)\n\n\n    def test_start_group_bad_name(self):\n        plugin = self._makeOne()\n        result = plugin.do_start('BAD_NAME:')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'BAD_NAME: ERROR (no such group)\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.INVALID_ARGS)\n\n    def test_start_all(self):\n        plugin = self._makeOne()\n        result = plugin.do_start('all')\n        self.assertEqual(result, None)\n\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'foo: started\\n'\n                         'foo2: started\\n'\n                         'failed_group:failed: ERROR (spawn error)\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.NOT_RUNNING)\n\n    def test_start_upcheck_failed(self):\n        plugin = self._makeOne()\n        plugin.ctl.upcheck = lambda: False\n        called = []\n        def f(*arg, **kw):\n            called.append(True)\n        supervisor = plugin.ctl.options._server.supervisor\n        supervisor.startAllProcesses = f\n        supervisor.startProcessGroup = f\n        plugin.do_start('foo')\n        self.assertEqual(called, [])\n\n    def test_stop_help(self):\n        plugin = self._makeOne()\n        plugin.help_stop()\n        out = plugin.ctl.stdout.getvalue()\n        self.assertTrue(\"stop <name>\" in out)\n\n    def test_stop_fail(self):\n        plugin = self._makeOne()\n        result = plugin.do_stop('')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue().split('\\n')[0],\n                         \"Error: stop requires a process name\")\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_stop_badname(self):\n        plugin = self._makeOne()\n        result = plugin.do_stop('BAD_NAME')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'BAD_NAME: ERROR (no such process)\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_stop_notrunning(self):\n        plugin = self._makeOne()\n        result = plugin.do_stop('NOT_RUNNING')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'NOT_RUNNING: ERROR (not running)\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.SUCCESS)\n\n    def test_stop_failed(self):\n        plugin = self._makeOne()\n        result = plugin.do_stop('FAILED')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(), 'FAILED\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_stop_one_success(self):\n        plugin = self._makeOne()\n        result = plugin.do_stop('foo')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'foo: stopped\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.SUCCESS)\n\n    def test_stop_one_with_group_name_success(self):\n        plugin = self._makeOne()\n        result = plugin.do_stop('foo:foo')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'foo: stopped\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.SUCCESS)\n\n    def test_stop_many(self):\n        plugin = self._makeOne()\n        result = plugin.do_stop('foo bar')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'foo: stopped\\n'\n                         'bar: stopped\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.SUCCESS)\n\n    def test_stop_group(self):\n        plugin = self._makeOne()\n        result = plugin.do_stop('foo:')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'foo:foo_00: stopped\\n'\n                         'foo:foo_01: stopped\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.SUCCESS)\n\n    def test_stop_group_bad_name(self):\n        plugin = self._makeOne()\n        result = plugin.do_stop('BAD_NAME:')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'BAD_NAME: ERROR (no such group)\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_stop_all(self):\n        plugin = self._makeOne()\n        result = plugin.do_stop('all')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'foo: stopped\\n'\n                         'foo2: stopped\\n'\n                         'failed_group:failed: ERROR (no such process)\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_stop_upcheck_failed(self):\n        plugin = self._makeOne()\n        plugin.ctl.upcheck = lambda: False\n        called = []\n        def f(*arg, **kw):\n            called.append(True)\n        supervisor = plugin.ctl.options._server.supervisor\n        supervisor.stopAllProcesses = f\n        supervisor.stopProcessGroup = f\n        plugin.do_stop('foo')\n        self.assertEqual(called, [])\n\n    def test_signal_help(self):\n        plugin = self._makeOne()\n        plugin.help_signal()\n        out = plugin.ctl.stdout.getvalue()\n        self.assertTrue(\"signal <signal name> <name>\" in out)\n\n    def test_signal_fail_no_arg(self):\n        plugin = self._makeOne()\n        result = plugin.do_signal('')\n        self.assertEqual(result, None)\n        msg = 'Error: signal requires a signal name and a process name'\n        self.assertEqual(plugin.ctl.stdout.getvalue().split('\\n')[0], msg)\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_signal_fail_one_arg(self):\n        plugin = self._makeOne()\n        result = plugin.do_signal('hup')\n        self.assertEqual(result, None)\n        msg = 'Error: signal requires a signal name and a process name'\n        self.assertEqual(plugin.ctl.stdout.getvalue().split('\\n')[0], msg)\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_signal_bad_signal(self):\n        plugin = self._makeOne()\n        result = plugin.do_signal('BAD_SIGNAL foo')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'foo: ERROR (bad signal name)\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_signal_bad_name(self):\n        plugin = self._makeOne()\n        result = plugin.do_signal('HUP BAD_NAME')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'BAD_NAME: ERROR (no such process)\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_signal_bad_group(self):\n        plugin = self._makeOne()\n        result = plugin.do_signal('HUP BAD_NAME:')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'BAD_NAME: ERROR (no such group)\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_signal_not_running(self):\n        plugin = self._makeOne()\n        result = plugin.do_signal('HUP NOT_RUNNING')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'NOT_RUNNING: ERROR (not running)\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.NOT_RUNNING)\n\n    def test_signal_failed(self):\n        plugin = self._makeOne()\n        result = plugin.do_signal('HUP FAILED')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(), 'FAILED\\n')\n        self.assertEqual(plugin.ctl.exitstatus, 1)\n\n    def test_signal_one_success(self):\n        plugin = self._makeOne()\n        result = plugin.do_signal('HUP foo')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(), 'foo: signalled\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.SUCCESS)\n\n    def test_signal_many(self):\n        plugin = self._makeOne()\n        result = plugin.do_signal('HUP foo bar')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'foo: signalled\\n'\n                         'bar: signalled\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.SUCCESS)\n\n    def test_signal_group(self):\n        plugin = self._makeOne()\n        result = plugin.do_signal('HUP foo:')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'foo:foo_00: signalled\\n'\n                         'foo:foo_01: signalled\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.SUCCESS)\n\n    def test_signal_all(self):\n        plugin = self._makeOne()\n        result = plugin.do_signal('HUP all')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'foo: signalled\\n'\n                         'foo2: signalled\\n'\n                         'failed_group:failed: ERROR (no such process)\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_signal_upcheck_failed(self):\n        plugin = self._makeOne()\n        plugin.ctl.upcheck = lambda: False\n        called = []\n        def f(*arg, **kw):\n            called.append(True)\n        supervisor = plugin.ctl.options._server.supervisor\n        supervisor.signalAllProcesses = f\n        supervisor.signalProcessGroup = f\n        plugin.do_signal('term foo')\n        self.assertEqual(called, [])\n\n    def test_restart_help(self):\n        plugin = self._makeOne()\n        plugin.help_restart()\n        out = plugin.ctl.stdout.getvalue()\n        self.assertTrue(\"restart <name>\" in out)\n\n    def test_restart_fail(self):\n        plugin = self._makeOne()\n        result = plugin.do_restart('')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue().split('\\n')[0],\n                         'Error: restart requires a process name')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_restart_one(self):\n        plugin = self._makeOne()\n        result = plugin.do_restart('foo')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'foo: stopped\\nfoo: started\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.SUCCESS)\n\n    def test_restart_all(self):\n        plugin = self._makeOne()\n        result = plugin.do_restart('all')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'foo: stopped\\nfoo2: stopped\\n'\n                         'failed_group:failed: ERROR (no such process)\\n'\n                         'foo: started\\nfoo2: started\\n'\n                         'failed_group:failed: ERROR (spawn error)\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.NOT_RUNNING)\n\n    def test_restart_upcheck_failed(self):\n        plugin = self._makeOne()\n        plugin.ctl.upcheck = lambda: False\n        called = []\n        def f(*arg, **kw):\n            called.append(True)\n        supervisor = plugin.ctl.options._server.supervisor\n        supervisor.stopAllProcesses = f\n        supervisor.stopProcessGroup = f\n        plugin.do_restart('foo')\n        self.assertEqual(called, [])\n\n    def test_clear_help(self):\n        plugin = self._makeOne()\n        plugin.help_clear()\n        out = plugin.ctl.stdout.getvalue()\n        self.assertTrue(\"clear <name>\" in out)\n\n    def test_clear_fail(self):\n        plugin = self._makeOne()\n        result = plugin.do_clear('')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue().split('\\n')[0],\n                         \"Error: clear requires a process name\")\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_clear_badname(self):\n        plugin = self._makeOne()\n        result = plugin.do_clear('BAD_NAME')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'BAD_NAME: ERROR (no such process)\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_clear_one_success(self):\n        plugin = self._makeOne()\n        result = plugin.do_clear('foo')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'foo: cleared\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.SUCCESS)\n\n    def test_clear_one_with_group_success(self):\n        plugin = self._makeOne()\n        result = plugin.do_clear('foo:foo')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'foo: cleared\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.SUCCESS)\n\n    def test_clear_many(self):\n        plugin = self._makeOne()\n        result = plugin.do_clear('foo bar')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'foo: cleared\\nbar: cleared\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.SUCCESS)\n\n    def test_clear_all(self):\n        plugin = self._makeOne()\n        result = plugin.do_clear('all')\n        self.assertEqual(result, None)\n\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'foo: cleared\\n'\n                         'foo2: cleared\\n'\n                         'failed_group:failed: ERROR (failed)\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_clear_upcheck_failed(self):\n        plugin = self._makeOne()\n        plugin.ctl.upcheck = lambda: False\n        called = []\n        def f(*arg, **kw):\n            called.append(True)\n        supervisor = plugin.ctl.options._server.supervisor\n        supervisor.clearAllProcessLogs = f\n        supervisor.clearProcessLogs = f\n        plugin.do_clear('foo')\n        self.assertEqual(called, [])\n\n    def test_open_help(self):\n        plugin = self._makeOne()\n        plugin.help_open()\n        out = plugin.ctl.stdout.getvalue()\n        self.assertTrue(\"open <url>\" in out)\n\n    def test_open_fail(self):\n        plugin = self._makeOne()\n        result = plugin.do_open('badname')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'ERROR: url must be http:// or unix://\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_open_succeed(self):\n        plugin = self._makeOne()\n        result = plugin.do_open('http://localhost:9002')\n        self.assertEqual(result, None)\n        value = plugin.ctl.stdout.getvalue().split('\\n')\n        self.assertEqual(value[0].split(None, 2),\n                         ['foo', 'RUNNING', 'foo description'])\n        self.assertEqual(value[1].split(None, 2),\n                         ['bar', 'FATAL', 'bar description'])\n        self.assertEqual(value[2].split(None, 2),\n                         ['baz:baz_01', 'STOPPED', 'baz description'])\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.SUCCESS)\n\n    def test_version_help(self):\n        plugin = self._makeOne()\n        plugin.help_version()\n        out = plugin.ctl.stdout.getvalue()\n        self.assertTrue(\"Show the version of the remote supervisord\" in out)\n\n    def test_version(self):\n        plugin = self._makeOne()\n        plugin.do_version(None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(), '3000\\n')\n\n    def test_version_arg(self):\n        plugin = self._makeOne()\n        result = plugin.do_version('bad')\n        self.assertEqual(result, None)\n        val = plugin.ctl.stdout.getvalue()\n        self.assertTrue(val.startswith('Error: version accepts no arguments'), val)\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_version_upcheck_failed(self):\n        plugin = self._makeOne()\n        plugin.ctl.upcheck = lambda: False\n        called = []\n        def f(*arg, **kw):\n            called.append(True)\n        plugin.ctl.options._server.supervisor.getSupervisorVersion = f\n        plugin.do_version('')\n        self.assertEqual(called, [])\n\n    def test_reload_help(self):\n        plugin = self._makeOne()\n        plugin.help_reload()\n        out = plugin.ctl.stdout.getvalue()\n        self.assertTrue(\"Restart the remote supervisord\" in out)\n\n    def test_reload_fail(self):\n        plugin = self._makeOne()\n        options = plugin.ctl.options\n        options._server.supervisor._restartable = False\n        result = plugin.do_reload('')\n        self.assertEqual(result, None)\n        self.assertEqual(options._server.supervisor._restarted, False)\n\n    def test_reload(self):\n        plugin = self._makeOne()\n        options = plugin.ctl.options\n        result = plugin.do_reload('')\n        self.assertEqual(result, None)\n        self.assertEqual(options._server.supervisor._restarted, True)\n\n    def test_reload_arg(self):\n        plugin = self._makeOne()\n        result = plugin.do_reload('bad')\n        self.assertEqual(result, None)\n        val = plugin.ctl.stdout.getvalue()\n        self.assertTrue(val.startswith('Error: reload accepts no arguments'), val)\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_shutdown_help(self):\n        plugin = self._makeOne()\n        plugin.help_shutdown()\n        out = plugin.ctl.stdout.getvalue()\n        self.assertTrue(\"Shut the remote supervisord down\" in out)\n\n    def test_shutdown_with_arg_shows_error(self):\n        plugin = self._makeOne()\n        options = plugin.ctl.options\n        result = plugin.do_shutdown('bad')\n        self.assertEqual(result, None)\n        self.assertEqual(options._server.supervisor._shutdown, False)\n        val = plugin.ctl.stdout.getvalue()\n        self.assertTrue(val.startswith('Error: shutdown accepts no arguments'), val)\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_shutdown(self):\n        plugin = self._makeOne()\n        options = plugin.ctl.options\n        result = plugin.do_shutdown('')\n        self.assertEqual(result, None)\n        self.assertEqual(options._server.supervisor._shutdown, True)\n\n    def test_shutdown_catches_xmlrpc_fault_shutdown_state(self):\n        plugin = self._makeOne()\n        from supervisor import xmlrpc\n\n        def raise_fault(*arg, **kw):\n            raise xmlrpclib.Fault(xmlrpc.Faults.SHUTDOWN_STATE, 'bye')\n        plugin.ctl.options._server.supervisor.shutdown = raise_fault\n\n        result = plugin.do_shutdown('')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'ERROR: already shutting down\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.SUCCESS)\n\n    def test_shutdown_reraises_other_xmlrpc_faults(self):\n        plugin = self._makeOne()\n        from supervisor import xmlrpc\n\n        def raise_fault(*arg, **kw):\n            raise xmlrpclib.Fault(xmlrpc.Faults.CANT_REREAD, 'ouch')\n        plugin.ctl.options._server.supervisor.shutdown = raise_fault\n\n        self.assertRaises(xmlrpclib.Fault,\n                          plugin.do_shutdown, '')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_shutdown_catches_socket_error_ECONNREFUSED(self):\n        plugin = self._makeOne()\n        import socket\n        import errno\n\n        def raise_fault(*arg, **kw):\n            raise socket.error(errno.ECONNREFUSED, 'nobody home')\n        plugin.ctl.options._server.supervisor.shutdown = raise_fault\n\n        result = plugin.do_shutdown('')\n        self.assertEqual(result, None)\n\n        output = plugin.ctl.stdout.getvalue()\n        self.assertTrue('refused connection (already shut down?)' in output)\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_shutdown_catches_socket_error_ENOENT(self):\n        plugin = self._makeOne()\n        import socket\n        import errno\n\n        def raise_fault(*arg, **kw):\n            raise socket.error(errno.ENOENT, 'no file')\n        plugin.ctl.options._server.supervisor.shutdown = raise_fault\n\n        result = plugin.do_shutdown('')\n        self.assertEqual(result, None)\n\n        output = plugin.ctl.stdout.getvalue()\n        self.assertTrue('no such file (already shut down?)' in output)\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_shutdown_reraises_other_socket_errors(self):\n        plugin = self._makeOne()\n        import socket\n        import errno\n\n        def raise_fault(*arg, **kw):\n            raise socket.error(errno.EPERM, 'denied')\n        plugin.ctl.options._server.supervisor.shutdown = raise_fault\n\n        self.assertRaises(socket.error,\n                          plugin.do_shutdown, '')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test__formatChanges(self):\n        plugin = self._makeOne()\n        # Don't explode, plz\n        plugin._formatChanges([['added'], ['changed'], ['removed']])\n        plugin._formatChanges([[], [], []])\n\n    def test_reread_help(self):\n        plugin = self._makeOne()\n        plugin.help_reread()\n        out = plugin.ctl.stdout.getvalue()\n        self.assertTrue(\"Reload the daemon's configuration files\" in out)\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.SUCCESS)\n\n    def test_reread(self):\n        plugin = self._makeOne()\n        calls = []\n        plugin._formatChanges = lambda x: calls.append(x)\n        result = plugin.do_reread(None)\n        self.assertEqual(result, None)\n        self.assertEqual(calls[0], [['added'], ['changed'], ['removed']])\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.SUCCESS)\n\n    def test_reread_arg(self):\n        plugin = self._makeOne()\n        result = plugin.do_reread('bad')\n        self.assertEqual(result, None)\n        val = plugin.ctl.stdout.getvalue()\n        self.assertTrue(val.startswith('Error: reread accepts no arguments'), val)\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_reread_cant_reread(self):\n        plugin = self._makeOne()\n        from supervisor import xmlrpc\n        def reloadConfig(*arg, **kw):\n            raise xmlrpclib.Fault(xmlrpc.Faults.CANT_REREAD, 'cant')\n        plugin.ctl.options._server.supervisor.reloadConfig = reloadConfig\n        plugin.do_reread(None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'ERROR: cant\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_reread_shutdown_state(self):\n        plugin = self._makeOne()\n        from supervisor import xmlrpc\n        def reloadConfig(*arg, **kw):\n            raise xmlrpclib.Fault(xmlrpc.Faults.SHUTDOWN_STATE, '')\n        plugin.ctl.options._server.supervisor.reloadConfig = reloadConfig\n        plugin.do_reread(None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'ERROR: supervisor shutting down\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_reread_reraises_other_faults(self):\n        plugin = self._makeOne()\n        from supervisor import xmlrpc\n        def reloadConfig(*arg, **kw):\n            raise xmlrpclib.Fault(xmlrpc.Faults.FAILED, '')\n        plugin.ctl.options._server.supervisor.reloadConfig = reloadConfig\n        self.assertRaises(xmlrpclib.Fault, plugin.do_reread, '')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test__formatConfigInfo(self):\n        info = { 'group': 'group1',\n                 'name': 'process1',\n                 'inuse': True,\n                 'autostart': True,\n                 'process_prio': 999,\n                 'group_prio': 999 }\n        plugin = self._makeOne()\n        result = plugin._formatConfigInfo(info)\n        self.assertTrue('in use' in result)\n        info = { 'group': 'group1',\n                 'name': 'process1',\n                 'inuse': False,\n                 'autostart': False,\n                 'process_prio': 999,\n                 'group_prio': 999 }\n        result = plugin._formatConfigInfo(info)\n        self.assertTrue('avail' in result)\n\n    def test_avail_help(self):\n        plugin = self._makeOne()\n        plugin.help_avail()\n        out = plugin.ctl.stdout.getvalue()\n        self.assertTrue(\"Display all configured\" in out)\n\n    def test_avail(self):\n        calls = []\n        plugin = self._makeOne()\n\n        class FakeSupervisor(object):\n            def getAllConfigInfo(self):\n                return [{ 'group': 'group1', 'name': 'process1',\n                          'inuse': False, 'autostart': False,\n                          'process_prio': 999, 'group_prio': 999 }]\n\n        plugin.ctl.get_supervisor = lambda : FakeSupervisor()\n        plugin.ctl.output = calls.append\n        result = plugin.do_avail('')\n        self.assertEqual(result, None)\n\n    def test_avail_arg(self):\n        plugin = self._makeOne()\n        result = plugin.do_avail('bad')\n        self.assertEqual(result, None)\n        val = plugin.ctl.stdout.getvalue()\n        self.assertTrue(val.startswith('Error: avail accepts no arguments'), val)\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_avail_shutdown_state(self):\n        plugin = self._makeOne()\n        supervisor = plugin.ctl.options._server.supervisor\n\n        def getAllConfigInfo():\n            from supervisor import xmlrpc\n            raise xmlrpclib.Fault(xmlrpc.Faults.SHUTDOWN_STATE, '')\n        supervisor.getAllConfigInfo = getAllConfigInfo\n\n        result = plugin.do_avail('')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'ERROR: supervisor shutting down\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_avail_reraises_other_faults(self):\n        plugin = self._makeOne()\n        supervisor = plugin.ctl.options._server.supervisor\n\n        def getAllConfigInfo():\n            from supervisor import xmlrpc\n            raise xmlrpclib.Fault(xmlrpc.Faults.FAILED, '')\n        supervisor.getAllConfigInfo = getAllConfigInfo\n\n        self.assertRaises(xmlrpclib.Fault, plugin.do_avail, '')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_add_help(self):\n        plugin = self._makeOne()\n        plugin.help_add()\n        out = plugin.ctl.stdout.getvalue()\n        self.assertTrue(\"add <name>\" in out)\n\n    def test_add(self):\n        plugin = self._makeOne()\n        result = plugin.do_add('foo')\n        self.assertEqual(result, None)\n        supervisor = plugin.ctl.options._server.supervisor\n        self.assertEqual(supervisor.processes, ['foo'])\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.SUCCESS)\n\n    def test_add_already_added(self):\n        plugin = self._makeOne()\n        result = plugin.do_add('ALREADY_ADDED')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'ERROR: process group already active\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.SUCCESS)\n\n    def test_add_bad_name(self):\n        plugin = self._makeOne()\n        result = plugin.do_add('BAD_NAME')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'ERROR: no such process/group: BAD_NAME\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_add_shutdown_state(self):\n        plugin = self._makeOne()\n        result = plugin.do_add('SHUTDOWN_STATE')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'ERROR: shutting down\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_add_reraises_other_faults(self):\n        plugin = self._makeOne()\n        self.assertRaises(xmlrpclib.Fault, plugin.do_add, 'FAILED')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_remove_help(self):\n        plugin = self._makeOne()\n        plugin.help_remove()\n        out = plugin.ctl.stdout.getvalue()\n        self.assertTrue(\"remove <name>\" in out)\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.SUCCESS)\n\n    def test_remove(self):\n        plugin = self._makeOne()\n        supervisor = plugin.ctl.options._server.supervisor\n        supervisor.processes = ['foo']\n        result = plugin.do_remove('foo')\n        self.assertEqual(result, None)\n        self.assertEqual(supervisor.processes, [])\n\n    def test_remove_bad_name(self):\n        plugin = self._makeOne()\n        supervisor = plugin.ctl.options._server.supervisor\n        supervisor.processes = ['foo']\n        result = plugin.do_remove('BAD_NAME')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'ERROR: no such process/group: BAD_NAME\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_remove_still_running(self):\n        plugin = self._makeOne()\n        supervisor = plugin.ctl.options._server.supervisor\n        supervisor.processes = ['foo']\n        result = plugin.do_remove('STILL_RUNNING')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'ERROR: process/group still running: STILL_RUNNING\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_remove_reraises_other_faults(self):\n        plugin = self._makeOne()\n        self.assertRaises(xmlrpclib.Fault, plugin.do_remove, 'FAILED')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_update_help(self):\n        plugin = self._makeOne()\n        plugin.help_update()\n        out = plugin.ctl.stdout.getvalue()\n        self.assertTrue(\"Reload config and add/remove\" in out)\n\n    def test_update_not_on_shutdown(self):\n        plugin = self._makeOne()\n        supervisor = plugin.ctl.options._server.supervisor\n        def reloadConfig():\n            from supervisor import xmlrpc\n            raise xmlrpclib.Fault(xmlrpc.Faults.SHUTDOWN_STATE, 'blah')\n        supervisor.reloadConfig = reloadConfig\n        supervisor.processes = ['removed']\n        plugin.do_update('')\n        self.assertEqual(supervisor.processes, ['removed'])\n\n    def test_update_added_procs(self):\n        plugin = self._makeOne()\n        supervisor = plugin.ctl.options._server.supervisor\n\n        def reloadConfig():\n            return [[['new_proc'], [], []]]\n        supervisor.reloadConfig = reloadConfig\n\n        result = plugin.do_update('')\n        self.assertEqual(result, None)\n        self.assertEqual(supervisor.processes, ['new_proc'])\n\n    def test_update_with_gname(self):\n        plugin = self._makeOne()\n        supervisor = plugin.ctl.options._server.supervisor\n\n        def reloadConfig():\n            return [[['added1', 'added2'], ['changed'], ['removed']]]\n        supervisor.reloadConfig = reloadConfig\n        supervisor.processes = ['changed', 'removed']\n\n        plugin.do_update('changed')\n        self.assertEqual(sorted(supervisor.processes),\n                         sorted(['changed', 'removed']))\n\n        plugin.do_update('added1 added2')\n        self.assertEqual(sorted(supervisor.processes),\n                         sorted(['changed', 'removed', 'added1', 'added2']))\n\n        plugin.do_update('removed')\n        self.assertEqual(sorted(supervisor.processes),\n                         sorted(['changed', 'added1', 'added2']))\n\n        supervisor.processes = ['changed', 'removed']\n        plugin.do_update('removed added1')\n        self.assertEqual(sorted(supervisor.processes),\n                         sorted(['changed', 'added1']))\n\n        supervisor.processes = ['changed', 'removed']\n        plugin.do_update('all')\n        self.assertEqual(sorted(supervisor.processes),\n                         sorted(['changed', 'added1', 'added2']))\n\n\n    def test_update_changed_procs(self):\n        from supervisor import xmlrpc\n\n        plugin = self._makeOne()\n        supervisor = plugin.ctl.options._server.supervisor\n\n        calls = []\n        def reloadConfig():\n            return [[[], ['changed_group'], []]]\n        supervisor.reloadConfig = reloadConfig\n        supervisor.startProcess = lambda x: calls.append(('start', x))\n\n        supervisor.addProcessGroup('changed_group') # fake existence\n        results = [{'name':        'changed_process',\n                    'group':       'changed_group',\n                    'status':      xmlrpc.Faults.SUCCESS,\n                    'description': 'blah'}]\n        def stopProcessGroup(name):\n            calls.append(('stop', name))\n            return results\n        supervisor.stopProcessGroup = stopProcessGroup\n\n        plugin.do_update('')\n        self.assertEqual(calls, [('stop', 'changed_group')])\n\n        supervisor.addProcessGroup('changed_group') # fake existence\n        calls[:] = []\n        results[:] = [{'name':        'changed_process1',\n                       'group':       'changed_group',\n                       'status':      xmlrpc.Faults.NOT_RUNNING,\n                       'description': 'blah'},\n                      {'name':        'changed_process2',\n                       'group':       'changed_group',\n                       'status':      xmlrpc.Faults.FAILED,\n                       'description': 'blah'}]\n\n        plugin.do_update('')\n        self.assertEqual(calls, [('stop', 'changed_group')])\n\n        supervisor.addProcessGroup('changed_group') # fake existence\n        calls[:] = []\n        results[:] = [{'name':        'changed_process1',\n                       'group':       'changed_group',\n                       'status':      xmlrpc.Faults.FAILED,\n                       'description': 'blah'},\n                      {'name':        'changed_process2',\n                       'group':       'changed_group',\n                       'status':      xmlrpc.Faults.SUCCESS,\n                       'description': 'blah'}]\n\n        plugin.do_update('')\n        self.assertEqual(calls, [('stop', 'changed_group')])\n\n    def test_update_removed_procs(self):\n        from supervisor import xmlrpc\n\n        plugin = self._makeOne()\n        supervisor = plugin.ctl.options._server.supervisor\n\n        def reloadConfig():\n            return [[[], [], ['removed_group']]]\n        supervisor.reloadConfig = reloadConfig\n\n        results = [{'name':        'removed_process',\n                    'group':       'removed_group',\n                    'status':      xmlrpc.Faults.SUCCESS,\n                    'description': 'blah'}]\n        supervisor.processes = ['removed_group']\n\n        def stopProcessGroup(name):\n            return results\n        supervisor.stopProcessGroup = stopProcessGroup\n\n        plugin.do_update('')\n        self.assertEqual(supervisor.processes, [])\n\n        results[:] = [{'name':        'removed_process',\n                       'group':       'removed_group',\n                       'status':      xmlrpc.Faults.NOT_RUNNING,\n                       'description': 'blah'}]\n        supervisor.processes = ['removed_group']\n\n        plugin.do_update('')\n        self.assertEqual(supervisor.processes, [])\n\n        results[:] = [{'name':        'removed_process',\n                       'group':       'removed_group',\n                       'status':      xmlrpc.Faults.FAILED,\n                       'description': 'blah'}]\n        supervisor.processes = ['removed_group']\n\n        plugin.do_update('')\n        self.assertEqual(supervisor.processes, ['removed_group'])\n\n    def test_update_reraises_other_faults(self):\n        plugin = self._makeOne()\n        supervisor = plugin.ctl.options._server.supervisor\n\n        def reloadConfig():\n            from supervisor import xmlrpc\n            raise xmlrpclib.Fault(xmlrpc.Faults.FAILED, 'FAILED')\n        supervisor.reloadConfig = reloadConfig\n\n        self.assertRaises(xmlrpclib.Fault, plugin.do_update, '')\n        self.assertEqual(plugin.ctl.exitstatus, 1)\n\n    def test_pid_help(self):\n        plugin = self._makeOne()\n        plugin.help_pid()\n        out = plugin.ctl.stdout.getvalue()\n        self.assertTrue(\"pid <name>\" in out)\n\n    def test_pid_supervisord(self):\n        plugin = self._makeOne()\n        result = plugin.do_pid('')\n        self.assertEqual(result, None)\n        options = plugin.ctl.options\n        lines = plugin.ctl.stdout.getvalue().split('\\n')\n        self.assertEqual(len(lines), 2)\n        self.assertEqual(lines[0], str(options._server.supervisor.getPID()))\n\n    def test_pid_allprocesses(self):\n        plugin = self._makeOne()\n        result = plugin.do_pid('all')\n        self.assertEqual(result, None)\n        value = plugin.ctl.stdout.getvalue().strip()\n        self.assertEqual(value.split(), ['11', '12', '13'])\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.SUCCESS)\n\n    def test_pid_badname(self):\n        plugin = self._makeOne()\n        result = plugin.do_pid('BAD_NAME')\n        self.assertEqual(result, None)\n        value = plugin.ctl.stdout.getvalue().strip()\n        self.assertEqual(value, 'No such process BAD_NAME')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_pid_oneprocess(self):\n        plugin = self._makeOne()\n        result = plugin.do_pid('foo')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue().strip(), '11')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.SUCCESS)\n\n    def test_pid_oneprocess_not_running(self):\n        plugin = self._makeOne()\n        options = plugin.ctl.options\n        def f(*arg, **kw):\n            from supervisor.states import ProcessStates\n            return {'name': 'foo',\n                     'group':'foo',\n                     'pid': 0,\n                     'state': ProcessStates.STOPPED,\n                     'statename': 'STOPPED',\n                     'start': 0,\n                     'stop': 0,\n                     'spawnerr': '',\n                     'now': 0,\n                     'description':'foo description'\n                    }\n        options._server.supervisor.getProcessInfo = f\n        result = plugin.do_pid('foo')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue().strip(), '0')\n        self.assertEqual(plugin.ctl.exitstatus,\n                         LSBInitExitStatuses.NOT_RUNNING)\n\n    def test_pid_upcheck_failed(self):\n        plugin = self._makeOne()\n        plugin.ctl.upcheck = lambda: False\n        called = []\n        def f(*arg, **kw):\n            called.append(True)\n        plugin.ctl.options._server.supervisor.getPID = f\n        plugin.do_pid('')\n        self.assertEqual(called, [])\n\n    def test_maintail_help(self):\n        plugin = self._makeOne()\n        plugin.help_maintail()\n        out = plugin.ctl.stdout.getvalue()\n        self.assertTrue(\"tail of supervisor main log file\" in out)\n\n    def test_maintail_toomanyargs(self):\n        plugin = self._makeOne()\n        result = plugin.do_maintail('foo bar')\n        self.assertEqual(result, None)\n        val = plugin.ctl.stdout.getvalue()\n        self.assertTrue(val.startswith('Error: too many'), val)\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_maintail_minus_string_fails(self):\n        plugin = self._makeOne()\n        result = plugin.do_maintail('-wrong')\n        self.assertEqual(result, None)\n        val = plugin.ctl.stdout.getvalue()\n        self.assertTrue(val.startswith('Error: bad argument -wrong'), val)\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_maintail_wrong(self):\n        plugin = self._makeOne()\n        result = plugin.do_maintail('wrong')\n        self.assertEqual(result, None)\n        val = plugin.ctl.stdout.getvalue()\n        self.assertTrue(val.startswith('Error: bad argument wrong'), val)\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def _dont_test_maintail_dashf(self):\n        # https://github.com/Supervisor/supervisor/issues/285\n        # TODO: Refactor so we can test more of maintail -f than just a\n        # connect error, and fix this test so it passes on FreeBSD.\n        plugin = self._makeOne()\n        plugin.listener = DummyListener()\n        result = plugin.do_maintail('-f')\n        self.assertEqual(result, None)\n        errors = plugin.listener.errors\n        self.assertEqual(len(errors), 1)\n        error = errors[0]\n        self.assertEqual(plugin.listener.closed,\n                         'http://localhost:65532/mainlogtail')\n        self.assertEqual(error[0],\n                         'http://localhost:65532/mainlogtail')\n        self.assertTrue('Cannot connect' in error[1])\n\n    def test_maintail_bad_modifier(self):\n        plugin = self._makeOne()\n        result = plugin.do_maintail('-z')\n        self.assertEqual(result, None)\n        lines = plugin.ctl.stdout.getvalue().split('\\n')\n        self.assertEqual(lines[0], 'Error: bad argument -z')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_maintail_nobytes(self):\n        plugin = self._makeOne()\n        result = plugin.do_maintail('')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(), 'mainlogdata\\n')\n\n    def test_maintail_dashbytes(self):\n        plugin = self._makeOne()\n        result = plugin.do_maintail('-100')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(), 'mainlogdata\\n')\n\n    def test_maintail_readlog_error_nofile(self):\n        plugin = self._makeOne()\n        supervisor_rpc = plugin.ctl.get_supervisor()\n        from supervisor import xmlrpc\n        supervisor_rpc._readlog_error = xmlrpc.Faults.NO_FILE\n        result = plugin.do_maintail('-100')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'supervisord: ERROR (no log file)\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_maintail_readlog_error_failed(self):\n        plugin = self._makeOne()\n        supervisor_rpc = plugin.ctl.get_supervisor()\n        from supervisor import xmlrpc\n        supervisor_rpc._readlog_error = xmlrpc.Faults.FAILED\n        result = plugin.do_maintail('-100')\n        self.assertEqual(result, None)\n        self.assertEqual(plugin.ctl.stdout.getvalue(),\n                         'supervisord: ERROR (unknown error reading log)\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_maintail_upcheck_failed(self):\n        plugin = self._makeOne()\n        plugin.ctl.upcheck = lambda: False\n        called = []\n        def f(*arg, **kw):\n            called.append(True)\n        plugin.ctl.options._server.supervisor.readLog = f\n        plugin.do_maintail('')\n        self.assertEqual(called, [])\n\n    def test_fg_help(self):\n        plugin = self._makeOne()\n        plugin.help_fg()\n        out = plugin.ctl.stdout.getvalue()\n        self.assertTrue(\"fg <process>\" in out)\n\n    def test_fg_too_few_args(self):\n        plugin = self._makeOne()\n        result = plugin.do_fg('')\n        self.assertEqual(result, None)\n        lines = plugin.ctl.stdout.getvalue().split('\\n')\n        self.assertEqual(lines[0], 'ERROR: no process name supplied')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_fg_too_many_args(self):\n        plugin = self._makeOne()\n        result = plugin.do_fg('foo bar')\n        self.assertEqual(result, None)\n        line = plugin.ctl.stdout.getvalue()\n        self.assertEqual(line, 'ERROR: too many process names supplied\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_fg_badprocname(self):\n        plugin = self._makeOne()\n        result = plugin.do_fg('BAD_NAME')\n        self.assertEqual(result, None)\n        line = plugin.ctl.stdout.getvalue()\n        self.assertEqual(line, 'ERROR: bad process name supplied\\n')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_fg_procnotrunning(self):\n        plugin = self._makeOne()\n        result = plugin.do_fg('bar')\n        self.assertEqual(result, None)\n        line = plugin.ctl.stdout.getvalue()\n        self.assertEqual(line, 'ERROR: process not running\\n')\n        result = plugin.do_fg('baz_01')\n        lines = plugin.ctl.stdout.getvalue().split('\\n')\n        self.assertEqual(result, None)\n        self.assertEqual(lines[-2], 'ERROR: process not running')\n        self.assertEqual(plugin.ctl.exitstatus, LSBInitExitStatuses.GENERIC)\n\n    def test_fg_upcheck_failed(self):\n        plugin = self._makeOne()\n        plugin.ctl.upcheck = lambda: False\n        called = []\n        def f(*arg, **kw):\n            called.append(True)\n        plugin.ctl.options._server.supervisor.getProcessInfo = f\n        plugin.do_fg('foo')\n        self.assertEqual(called, [])\n\n    def test_exit_help(self):\n        plugin = self._makeOne()\n        plugin.help_exit()\n        out = plugin.ctl.stdout.getvalue()\n        self.assertTrue(\"Exit the supervisor shell\" in out)\n\n    def test_quit_help(self):\n        plugin = self._makeOne()\n        plugin.help_quit()\n        out = plugin.ctl.stdout.getvalue()\n        self.assertTrue(\"Exit the supervisor shell\" in out)\n\nclass DummyListener:\n    def __init__(self):\n        self.errors = []\n    def error(self, url, msg):\n        self.errors.append((url, msg))\n    def close(self, url):\n        self.closed = url\n\nclass DummyPluginFactory:\n    def __init__(self, ctl, **kw):\n        self.ctl = ctl\n\n    def do_help(self, arg):\n        self.ctl.stdout.write('foo helped')\n\nclass DummyClientOptions:\n    def __init__(self):\n        self.prompt = 'supervisor'\n        self.serverurl = 'http://localhost:65532'\n        self.username = 'chrism'\n        self.password = '123'\n        self.history_file = None\n        self.plugins = ()\n        self._server = DummyRPCServer()\n        self.interactive = False\n        self.plugin_factories = [('dummy', DummyPluginFactory, {})]\n\n    def getServerProxy(self):\n        return self._server\n\nclass DummyController:\n    nohelp = 'no help on %s'\n    def __init__(self, options):\n        self.options = options\n        self.topics_printed = []\n        self.stdout = StringIO()\n        self.exitstatus = LSBInitExitStatuses.SUCCESS\n\n    def upcheck(self):\n        return True\n\n    def get_supervisor(self):\n        return self.get_server_proxy('supervisor')\n\n    def get_server_proxy(self, namespace=None):\n        proxy = self.options.getServerProxy()\n        if namespace is None:\n            return proxy\n        else:\n            return getattr(proxy, namespace)\n\n    def output(self, data):\n        self.stdout.write(data + '\\n')\n\n    def print_topics(self, doc_headers, cmds_doc, rows, cols):\n        self.topics_printed.append((doc_headers, cmds_doc, rows, cols))\n\n    def set_exitstatus_from_xmlrpc_fault(self, faultcode, ignored_faultcode=None):\n        from supervisor.supervisorctl import DEAD_PROGRAM_FAULTS\n        if faultcode in (ignored_faultcode, xmlrpc.Faults.SUCCESS):\n            pass\n        elif faultcode in DEAD_PROGRAM_FAULTS:\n            self.exitstatus = LSBInitExitStatuses.NOT_RUNNING\n        else:\n            self.exitstatus = LSBInitExitStatuses.GENERIC\n\nclass DummyPlugin:\n    def __init__(self, controller=None):\n        self.ctl = controller\n\n    def do_help(self, arg):\n        self.helped = True\n"
  },
  {
    "path": "supervisor/tests/test_supervisord.py",
    "content": "import unittest\nimport time\nimport signal\nimport sys\nimport os\nimport tempfile\nimport shutil\n\nfrom supervisor.states import ProcessStates\nfrom supervisor.states import SupervisorStates\n\nfrom supervisor.tests.base import DummyOptions\nfrom supervisor.tests.base import DummyPConfig\nfrom supervisor.tests.base import DummyPGroupConfig\nfrom supervisor.tests.base import DummyProcess\nfrom supervisor.tests.base import DummyProcessGroup\nfrom supervisor.tests.base import DummyDispatcher\n\nfrom supervisor.compat import StringIO\n\ntry:\n    import pstats\nexcept ImportError: # pragma: no cover\n    # Debian-packaged pythons may not have the pstats module\n    # unless the \"python-profiler\" package is installed.\n    pstats = None\n\nclass EntryPointTests(unittest.TestCase):\n    def test_main_noprofile(self):\n        from supervisor.supervisord import main\n        conf = os.path.join(\n            os.path.abspath(os.path.dirname(__file__)), 'fixtures',\n            'donothing.conf')\n        new_stdout = StringIO()\n        new_stdout.fileno = lambda: 1\n        old_stdout = sys.stdout\n        try:\n            tempdir = tempfile.mkdtemp()\n            log = os.path.join(tempdir, 'log')\n            pid = os.path.join(tempdir, 'pid')\n            sys.stdout = new_stdout\n            main(args=['-c', conf, '-l', log, '-j', pid, '-n'],\n                 test=True)\n        finally:\n            sys.stdout = old_stdout\n            shutil.rmtree(tempdir)\n        output = new_stdout.getvalue()\n        self.assertTrue('supervisord started' in output, output)\n\n    if pstats:\n        def test_main_profile(self):\n            from supervisor.supervisord import main\n            conf = os.path.join(\n                os.path.abspath(os.path.dirname(__file__)), 'fixtures',\n                'donothing.conf')\n            new_stdout = StringIO()\n            new_stdout.fileno = lambda: 1\n            old_stdout = sys.stdout\n            try:\n                tempdir = tempfile.mkdtemp()\n                log = os.path.join(tempdir, 'log')\n                pid = os.path.join(tempdir, 'pid')\n                sys.stdout = new_stdout\n                main(args=['-c', conf, '-l', log, '-j', pid, '-n',\n                           '--profile_options=cumulative,calls'], test=True)\n            finally:\n                sys.stdout = old_stdout\n                shutil.rmtree(tempdir)\n            output = new_stdout.getvalue()\n            self.assertTrue('cumulative time, call count' in output, output)\n\n    def test_silent_off(self):\n        from supervisor.supervisord import main\n        conf = os.path.join(\n            os.path.abspath(os.path.dirname(__file__)), 'fixtures',\n            'donothing.conf')\n        new_stdout = StringIO()\n        new_stdout.fileno = lambda: 1\n        old_stdout = sys.stdout\n\n        try:\n            tempdir = tempfile.mkdtemp()\n            log = os.path.join(tempdir, 'log')\n            pid = os.path.join(tempdir, 'pid')\n            sys.stdout = new_stdout\n            main(args=['-c', conf, '-l', log, '-j', pid, '-n'], test=True)\n        finally:\n            sys.stdout = old_stdout\n            shutil.rmtree(tempdir)\n        output = new_stdout.getvalue()\n        self.assertGreater(len(output), 0)\n\n    def test_silent_on(self):\n        from supervisor.supervisord import main\n        conf = os.path.join(\n            os.path.abspath(os.path.dirname(__file__)), 'fixtures',\n            'donothing.conf')\n        new_stdout = StringIO()\n        new_stdout.fileno = lambda: 1\n        old_stdout = sys.stdout\n\n        try:\n            tempdir = tempfile.mkdtemp()\n            log = os.path.join(tempdir, 'log')\n            pid = os.path.join(tempdir, 'pid')\n            sys.stdout = new_stdout\n            main(args=['-c', conf, '-l', log, '-j', pid, '-n', '-s'], test=True)\n        finally:\n            sys.stdout = old_stdout\n            shutil.rmtree(tempdir)\n        output = new_stdout.getvalue()\n        self.assertEqual(len(output), 0)\n\nclass SupervisordTests(unittest.TestCase):\n    def tearDown(self):\n        from supervisor.events import clear\n        clear()\n\n    def _getTargetClass(self):\n        from supervisor.supervisord import Supervisor\n        return Supervisor\n\n    def _makeOne(self, options):\n        return self._getTargetClass()(options)\n\n    def test_main_first(self):\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo', '/tmp')\n        gconfigs = [DummyPGroupConfig(options, 'foo', pconfigs=[pconfig])]\n        options.process_group_configs = gconfigs\n        options.test = True\n        options.first = True\n        supervisord = self._makeOne(options)\n        supervisord.main()\n        self.assertEqual(options.fds_cleaned_up, False)\n        self.assertEqual(options.rlimits_set, True)\n        self.assertEqual(options.parse_criticals, ['setuid_called'])\n        self.assertEqual(options.parse_warnings, [])\n        self.assertEqual(options.parse_infos, ['rlimits_set'])\n        self.assertEqual(options.autochildlogdir_cleared, True)\n        self.assertEqual(len(supervisord.process_groups), 1)\n        self.assertEqual(supervisord.process_groups['foo'].config.options,\n                         options)\n        self.assertEqual(options.httpservers_opened, True)\n        self.assertEqual(options.signals_set, True)\n        self.assertEqual(options.daemonized, True)\n        self.assertEqual(options.pidfile_written, True)\n        self.assertEqual(options.cleaned_up, True)\n\n    def test_main_notfirst(self):\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo', '/tmp')\n        gconfigs = [DummyPGroupConfig(options, 'foo', pconfigs=[pconfig])]\n        options.process_group_configs = gconfigs\n        options.test = True\n        options.first = False\n        supervisord = self._makeOne(options)\n        supervisord.main()\n        self.assertEqual(options.fds_cleaned_up, True)\n        self.assertFalse(hasattr(options, 'rlimits_set'))\n        self.assertEqual(options.parse_criticals, ['setuid_called'])\n        self.assertEqual(options.parse_warnings, [])\n        self.assertEqual(options.parse_infos, [])\n        self.assertEqual(options.autochildlogdir_cleared, True)\n        self.assertEqual(len(supervisord.process_groups), 1)\n        self.assertEqual(supervisord.process_groups['foo'].config.options,\n                         options)\n        self.assertEqual(options.httpservers_opened, True)\n        self.assertEqual(options.signals_set, True)\n        self.assertEqual(options.daemonized, False)\n        self.assertEqual(options.pidfile_written, True)\n        self.assertEqual(options.cleaned_up, True)\n\n    def test_reap(self):\n        options = DummyOptions()\n        options.waitpid_return = 1, 1\n        pconfig = DummyPConfig(options, 'process', '/bin/foo', '/tmp')\n        process = DummyProcess(pconfig)\n        process.drained = False\n        process.killing = True\n        process.laststop = None\n        process.waitstatus = None, None\n        options.pidhistory = {1:process}\n        supervisord = self._makeOne(options)\n\n        supervisord.reap(once=True)\n        self.assertEqual(process.finished, (1,1))\n\n    def test_reap_recursionguard(self):\n        options = DummyOptions()\n        supervisord = self._makeOne(options)\n        result = supervisord.reap(once=True, recursionguard=100)\n        self.assertEqual(result, None)\n\n    def test_reap_more_than_once(self):\n        options = DummyOptions()\n        options.waitpid_return = 1, 1\n        pconfig = DummyPConfig(options, 'process', '/bin/foo', '/tmp')\n        process = DummyProcess(pconfig)\n        process.drained = False\n        process.killing = True\n        process.laststop = None\n        process.waitstatus = None, None\n        options.pidhistory = {1:process}\n        supervisord = self._makeOne(options)\n\n        supervisord.reap(recursionguard=99)\n        self.assertEqual(process.finished, (1,1))\n\n    def test_reap_unknown_pid(self):\n        options = DummyOptions()\n        options.waitpid_return = 2, 0 # pid, status\n        pconfig = DummyPConfig(options, 'process', '/bin/foo', '/tmp')\n        process = DummyProcess(pconfig)\n        process.drained = False\n        process.killing = True\n        process.laststop = None\n        process.waitstatus = None, None\n        options.pidhistory = {1: process}\n        supervisord = self._makeOne(options)\n\n        supervisord.reap(once=True)\n        self.assertEqual(process.finished, None)\n        self.assertEqual(options.logger.data[0],\n                         'reaped unknown pid 2 (exit status 0)')\n\n    def test_handle_sigterm(self):\n        options = DummyOptions()\n        options._signal = signal.SIGTERM\n        supervisord = self._makeOne(options)\n        supervisord.handle_signal()\n        self.assertEqual(supervisord.options.mood,\n                         SupervisorStates.SHUTDOWN)\n        self.assertEqual(options.logger.data[0],\n                         'received SIGTERM indicating exit request')\n\n    def test_handle_sigint(self):\n        options = DummyOptions()\n        options._signal = signal.SIGINT\n        supervisord = self._makeOne(options)\n        supervisord.handle_signal()\n        self.assertEqual(supervisord.options.mood,\n                         SupervisorStates.SHUTDOWN)\n        self.assertEqual(options.logger.data[0],\n                         'received SIGINT indicating exit request')\n\n    def test_handle_sigquit(self):\n        options = DummyOptions()\n        options._signal = signal.SIGQUIT\n        supervisord = self._makeOne(options)\n        supervisord.handle_signal()\n        self.assertEqual(supervisord.options.mood,\n                         SupervisorStates.SHUTDOWN)\n        self.assertEqual(options.logger.data[0],\n                         'received SIGQUIT indicating exit request')\n\n    def test_handle_sighup_in_running_state(self):\n        options = DummyOptions()\n        options._signal = signal.SIGHUP\n        supervisord = self._makeOne(options)\n        self.assertEqual(supervisord.options.mood,\n                         SupervisorStates.RUNNING)\n        supervisord.handle_signal()\n        self.assertEqual(supervisord.options.mood,\n                         SupervisorStates.RESTARTING)\n        self.assertEqual(options.logger.data[0],\n                         'received SIGHUP indicating restart request')\n\n    def test_handle_sighup_in_shutdown_state(self):\n        options = DummyOptions()\n        options._signal = signal.SIGHUP\n        supervisord = self._makeOne(options)\n        supervisord.options.mood = SupervisorStates.SHUTDOWN\n        self.assertEqual(supervisord.options.mood,\n                         SupervisorStates.SHUTDOWN)\n        supervisord.handle_signal()\n        self.assertEqual(supervisord.options.mood,\n                         SupervisorStates.SHUTDOWN) # unchanged\n        self.assertEqual(options.logger.data[0],\n                         'ignored SIGHUP indicating restart request '\n                         '(shutdown in progress)')\n\n    def test_handle_sigchld(self):\n        options = DummyOptions()\n        options._signal = signal.SIGCHLD\n        supervisord = self._makeOne(options)\n        supervisord.handle_signal()\n        self.assertEqual(supervisord.options.mood,\n                         SupervisorStates.RUNNING)\n        # supervisor.options.signame(signal.SIGCHLD) may return \"SIGCLD\"\n        # on linux or other systems where SIGCHLD = SIGCLD.\n        msgs = ('received SIGCHLD indicating a child quit',\n                'received SIGCLD indicating a child quit')\n        self.assertTrue(options.logger.data[0] in msgs)\n\n    def test_handle_sigusr2(self):\n        options = DummyOptions()\n        options._signal = signal.SIGUSR2\n        pconfig1 = DummyPConfig(options, 'process1', '/bin/foo', '/tmp')\n        process1 = DummyProcess(pconfig1, state=ProcessStates.STOPPING)\n        process1.delay = time.time() - 1\n        supervisord = self._makeOne(options)\n        pconfigs = [DummyPConfig(options, 'foo', '/bin/foo', '/tmp')]\n        options.process_group_configs = DummyPGroupConfig(\n            options, 'foo',\n            pconfigs=pconfigs)\n        dummypgroup = DummyProcessGroup(options)\n        supervisord.process_groups = {None:dummypgroup}\n        supervisord.handle_signal()\n        self.assertEqual(supervisord.options.mood,\n                         SupervisorStates.RUNNING)\n        self.assertEqual(options.logs_reopened, True)\n        self.assertEqual(options.logger.data[0],\n                         'received SIGUSR2 indicating log reopen request')\n        self.assertEqual(dummypgroup.logs_reopened, True)\n\n    def test_handle_unknown_signal(self):\n        options = DummyOptions()\n        options._signal = signal.SIGUSR1\n        supervisord = self._makeOne(options)\n        supervisord.handle_signal()\n        self.assertEqual(supervisord.options.mood,\n                         SupervisorStates.RUNNING)\n        self.assertEqual(options.logger.data[0],\n                         'received SIGUSR1 indicating nothing')\n\n    def test_get_state(self):\n        options = DummyOptions()\n        supervisord = self._makeOne(options)\n        self.assertEqual(supervisord.get_state(), SupervisorStates.RUNNING)\n\n    def test_diff_to_active_finds_groups_added(self):\n        options = DummyOptions()\n        supervisord = self._makeOne(options)\n\n        pconfig = DummyPConfig(options, 'process1', '/bin/foo', '/tmp')\n        group1 = DummyPGroupConfig(options, 'group1', pconfigs=[pconfig])\n\n        # the active configuration has no groups\n        # diffing should find that group1 has been added\n        supervisord.options.process_group_configs = [group1]\n        added, changed, removed = supervisord.diff_to_active()\n        self.assertEqual(added, [group1])\n        self.assertEqual(changed, [])\n        self.assertEqual(removed, [])\n\n    def test_diff_to_active_finds_groups_removed(self):\n        options = DummyOptions()\n        supervisord = self._makeOne(options)\n\n        pconfig = DummyPConfig(options, 'process1', '/bin/process1', '/tmp')\n        group1 = DummyPGroupConfig(options, 'group1', pconfigs=[pconfig])\n\n        pconfig = DummyPConfig(options, 'process2', '/bin/process2', '/tmp')\n        group2 = DummyPGroupConfig(options, 'group2', pconfigs=[pconfig])\n\n        # set up supervisord with an active configuration of group1 and group2\n        supervisord.options.process_group_configs = [group1, group2]\n        supervisord.add_process_group(group1)\n        supervisord.add_process_group(group2)\n\n        # diffing should find that group2 has been removed\n        supervisord.options.process_group_configs = [group1]\n        added, changed, removed = supervisord.diff_to_active()\n        self.assertEqual(added, [])\n        self.assertEqual(changed, [])\n        self.assertEqual(removed, [group2])\n\n    def test_diff_to_active_changed(self):\n        from supervisor.options import ProcessConfig, ProcessGroupConfig\n\n        options = DummyOptions()\n        supervisord = self._makeOne(options)\n\n        def make_pconfig(name, command, **params):\n            result = {\n                'name': name, 'command': command,\n                'directory': None, 'umask': None, 'priority': 999, 'autostart': True,\n                'autorestart': True, 'startsecs': 10, 'startretries': 999,\n                'uid': None, 'stdout_logfile': None, 'stdout_capture_maxbytes': 0,\n                'stdout_events_enabled': False,\n                'stdout_logfile_backups': 0, 'stdout_logfile_maxbytes': 0,\n                'stdout_syslog': False,\n                'stderr_logfile': None, 'stderr_capture_maxbytes': 0,\n                'stderr_events_enabled': False,\n                'stderr_logfile_backups': 0, 'stderr_logfile_maxbytes': 0,\n                'stderr_syslog': False,\n                'redirect_stderr': False,\n                'stopsignal': None, 'stopwaitsecs': 10,\n                'stopasgroup': False,\n                'killasgroup': False,\n                'exitcodes': (0,), 'environment': None, 'serverurl': None,\n            }\n            result.update(params)\n            return ProcessConfig(options, **result)\n\n        def make_gconfig(name, pconfigs):\n            return ProcessGroupConfig(options, name, 25, pconfigs)\n\n        pconfig = make_pconfig('process1', 'process1', uid='new')\n        group1 = make_gconfig('group1', [pconfig])\n\n        pconfig = make_pconfig('process2', 'process2')\n        group2 = make_gconfig('group2', [pconfig])\n        new = [group1, group2]\n\n        pconfig = make_pconfig('process1', 'process1', uid='old')\n        group3 = make_gconfig('group1', [pconfig])\n\n        pconfig = make_pconfig('process2', 'process2')\n        group4 = make_gconfig('group2', [pconfig])\n        supervisord.add_process_group(group3)\n        supervisord.add_process_group(group4)\n\n        supervisord.options.process_group_configs = new\n        added, changed, removed = supervisord.diff_to_active()\n        self.assertEqual(added, [])\n        self.assertEqual(removed, [])\n        self.assertEqual(changed, [group1])\n\n        options = DummyOptions()\n        supervisord = self._makeOne(options)\n\n        pconfig1 = make_pconfig('process1', 'process1')\n        pconfig2 = make_pconfig('process2', 'process2')\n        group1 = make_gconfig('group1', [pconfig1, pconfig2])\n        new = [group1]\n\n        supervisord.add_process_group(make_gconfig('group1', [pconfig1]))\n\n        supervisord.options.process_group_configs = new\n        added, changed, removed = supervisord.diff_to_active()\n        self.assertEqual(added, [])\n        self.assertEqual(removed, [])\n        self.assertEqual(changed, [group1])\n\n    def test_diff_to_active_changed_eventlistener(self):\n        from supervisor.events import EventTypes\n        from supervisor.options import EventListenerConfig, EventListenerPoolConfig\n\n        options = DummyOptions()\n        supervisord = self._makeOne(options)\n\n        def make_pconfig(name, command, **params):\n            result = {\n                'name': name, 'command': command,\n                'directory': None, 'umask': None, 'priority': 999, 'autostart': True,\n                'autorestart': True, 'startsecs': 10, 'startretries': 999,\n                'uid': None, 'stdout_logfile': None, 'stdout_capture_maxbytes': 0,\n                'stdout_events_enabled': False,\n                'stdout_logfile_backups': 0, 'stdout_logfile_maxbytes': 0,\n                'stdout_syslog': False,\n                'stderr_logfile': None, 'stderr_capture_maxbytes': 0,\n                'stderr_events_enabled': False,\n                'stderr_logfile_backups': 0, 'stderr_logfile_maxbytes': 0,\n                'stderr_syslog': False,\n                'redirect_stderr': False,\n                'stopsignal': None, 'stopwaitsecs': 10,\n                'stopasgroup': False,\n                'killasgroup': False,\n                'exitcodes': (0,), 'environment': None, 'serverurl': None,\n            }\n            result.update(params)\n            return EventListenerConfig(options, **result)\n\n        def make_econfig(*pool_event_names):\n            result = []\n            for pool_event_name in pool_event_names:\n                result.append(getattr(EventTypes, pool_event_name, None))\n            return result\n\n        def make_gconfig(name, pconfigs, pool_events, result_handler='supervisor.dispatchers:default_handler'):\n            return EventListenerPoolConfig(options, name, 25, pconfigs, 10, pool_events, result_handler)\n\n\t    # Test that changing an eventlistener's command is detected by diff_to_active\n        pconfig = make_pconfig('process1', 'process1-new')\n        econfig = make_econfig(\"TICK_60\")\n        group1 = make_gconfig('group1', [pconfig], econfig)\n\n        pconfig = make_pconfig('process2', 'process2')\n        econfig = make_econfig(\"TICK_3600\")\n        group2 = make_gconfig('group2', [pconfig], econfig)\n        new = [group1, group2]\n\n        pconfig = make_pconfig('process1', 'process1-old')\n        econfig = make_econfig(\"TICK_60\")\n        group3 = make_gconfig('group1', [pconfig], econfig)\n\n        pconfig = make_pconfig('process2', 'process2')\n        econfig = make_econfig(\"TICK_3600\")\n        group4 = make_gconfig('group2', [pconfig], econfig)\n        supervisord.add_process_group(group3)\n        supervisord.add_process_group(group4)\n\n        supervisord.options.process_group_configs = new\n        added, changed, removed = supervisord.diff_to_active()\n        self.assertEqual(added, [])\n        self.assertEqual(removed, [])\n        self.assertEqual(changed, [group1])\n\n        # Test that changing an eventlistener's event is detected by diff_to_active\n        options = DummyOptions()\n        supervisord = self._makeOne(options)\n\n        pconfig = make_pconfig('process1', 'process1')\n        econfig = make_econfig(\"TICK_60\")\n        group1 = make_gconfig('group1', [pconfig], econfig)\n\n        pconfig = make_pconfig('process2', 'process2')\n        econfig = make_econfig(\"TICK_3600\")\n        group2 = make_gconfig('group2', [pconfig], econfig)\n        new = [group1, group2]\n\n        pconfig = make_pconfig('process1', 'process1')\n        econfig = make_econfig(\"TICK_5\")\n        group3 = make_gconfig('group1', [pconfig], econfig)\n\n        pconfig = make_pconfig('process2', 'process2')\n        econfig = make_econfig(\"TICK_3600\")\n        group4 = make_gconfig('group2', [pconfig], econfig)\n        supervisord.add_process_group(group3)\n        supervisord.add_process_group(group4)\n\n        supervisord.options.process_group_configs = new\n        added, changed, removed = supervisord.diff_to_active()\n        self.assertEqual(added, [])\n        self.assertEqual(removed, [])\n        self.assertEqual(changed, [group1])\n\n        # Test that changing an eventlistener's result_handler is detected by diff_to_active\n        options = DummyOptions()\n        supervisord = self._makeOne(options)\n\n        pconfig = make_pconfig('process1', 'process1')\n        econfig = make_econfig(\"TICK_60\")\n        group1 = make_gconfig('group1', [pconfig], econfig, 'new-result-handler')\n\n        pconfig = make_pconfig('process2', 'process2')\n        econfig = make_econfig(\"TICK_3600\")\n        group2 = make_gconfig('group2', [pconfig], econfig)\n        new = [group1, group2]\n\n        pconfig = make_pconfig('process1', 'process1')\n        econfig = make_econfig(\"TICK_60\")\n        group3 = make_gconfig('group1', [pconfig], econfig, 'old-result-handler')\n\n        pconfig = make_pconfig('process2', 'process2')\n        econfig = make_econfig(\"TICK_3600\")\n        group4 = make_gconfig('group2', [pconfig], econfig)\n        supervisord.add_process_group(group3)\n        supervisord.add_process_group(group4)\n\n        supervisord.options.process_group_configs = new\n        added, changed, removed = supervisord.diff_to_active()\n        self.assertEqual(added, [])\n        self.assertEqual(removed, [])\n        self.assertEqual(changed, [group1])\n\n    def test_add_process_group(self):\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo', '/tmp')\n        gconfig = DummyPGroupConfig(options, 'foo', pconfigs=[pconfig])\n        options.process_group_configs = [gconfig]\n        supervisord = self._makeOne(options)\n\n        self.assertEqual(supervisord.process_groups, {})\n\n        result = supervisord.add_process_group(gconfig)\n        self.assertEqual(list(supervisord.process_groups.keys()), ['foo'])\n        self.assertTrue(result)\n\n        group = supervisord.process_groups['foo']\n        result = supervisord.add_process_group(gconfig)\n        self.assertEqual(group, supervisord.process_groups['foo'])\n        self.assertTrue(not result)\n\n    def test_add_process_group_emits_event(self):\n        from supervisor import events\n        L = []\n        def callback(event):\n            L.append(1)\n        events.subscribe(events.ProcessGroupAddedEvent, callback)\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo', '/tmp')\n        gconfig = DummyPGroupConfig(options, 'foo', pconfigs=[pconfig])\n        options.process_group_configs = [gconfig]\n        supervisord = self._makeOne(options)\n\n        supervisord.add_process_group(gconfig)\n\n        options.test = True\n        supervisord.runforever()\n        self.assertEqual(L, [1])\n\n    def test_remove_process_group(self):\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo', '/tmp')\n        gconfig = DummyPGroupConfig(options, 'foo', pconfigs=[pconfig])\n        supervisord = self._makeOne(options)\n\n        self.assertRaises(KeyError, supervisord.remove_process_group, 'asdf')\n\n        supervisord.add_process_group(gconfig)\n        group = supervisord.process_groups['foo']\n        result = supervisord.remove_process_group('foo')\n        self.assertTrue(group.before_remove_called)\n        self.assertEqual(supervisord.process_groups, {})\n        self.assertTrue(result)\n\n        supervisord.add_process_group(gconfig)\n        supervisord.process_groups['foo'].unstopped_processes = [DummyProcess(None)]\n        result = supervisord.remove_process_group('foo')\n        self.assertEqual(list(supervisord.process_groups.keys()), ['foo'])\n        self.assertTrue(not result)\n\n    def test_remove_process_group_event(self):\n        from supervisor import events\n        L = []\n        def callback(event):\n            L.append(1)\n        events.subscribe(events.ProcessGroupRemovedEvent, callback)\n        options = DummyOptions()\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo', '/tmp')\n        gconfig = DummyPGroupConfig(options, 'foo', pconfigs=[pconfig])\n        options.process_group_configs = [gconfig]\n        supervisord = self._makeOne(options)\n\n        supervisord.add_process_group(gconfig)\n        supervisord.process_groups['foo'].stopped_processes = [DummyProcess(None)]\n        supervisord.remove_process_group('foo')\n        options.test = True\n        supervisord.runforever()\n\n        self.assertEqual(L, [1])\n\n    def test_runforever_emits_generic_startup_event(self):\n        from supervisor import events\n        L = []\n        def callback(event):\n            L.append(1)\n        events.subscribe(events.SupervisorStateChangeEvent, callback)\n        options = DummyOptions()\n        supervisord = self._makeOne(options)\n        options.test = True\n        supervisord.runforever()\n        self.assertEqual(L, [1])\n\n    def test_runforever_emits_generic_specific_event(self):\n        from supervisor import events\n        L = []\n        def callback(event):\n            L.append(2)\n        events.subscribe(events.SupervisorRunningEvent, callback)\n        options = DummyOptions()\n        options.test = True\n        supervisord = self._makeOne(options)\n        supervisord.runforever()\n        self.assertEqual(L, [2])\n\n    def test_runforever_calls_tick(self):\n        options = DummyOptions()\n        options.test = True\n        supervisord = self._makeOne(options)\n        self.assertEqual(len(supervisord.ticks), 0)\n        supervisord.runforever()\n        self.assertEqual(len(supervisord.ticks), 3)\n\n    def test_runforever_poll_dispatchers(self):\n        options = DummyOptions()\n        options.poller.result = [6], [7, 8]\n        supervisord = self._makeOne(options)\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo',)\n        gconfig = DummyPGroupConfig(options, pconfigs=[pconfig])\n        pgroup = DummyProcessGroup(gconfig)\n        readable = DummyDispatcher(readable=True)\n        writable = DummyDispatcher(writable=True)\n        error = DummyDispatcher(writable=True, error=OSError)\n        pgroup.dispatchers = {6:readable, 7:writable, 8:error}\n        supervisord.process_groups = {'foo': pgroup}\n        options.test = True\n        supervisord.runforever()\n        self.assertEqual(pgroup.transitioned, True)\n        self.assertEqual(readable.read_event_handled, True)\n        self.assertEqual(writable.write_event_handled, True)\n        self.assertEqual(error.error_handled, True)\n\n    def test_runforever_select_dispatcher_exitnow_via_read(self):\n        options = DummyOptions()\n        options.poller.result = [6], []\n        supervisord = self._makeOne(options)\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo',)\n        gconfig = DummyPGroupConfig(options, pconfigs=[pconfig])\n        pgroup = DummyProcessGroup(gconfig)\n        from supervisor.medusa import asyncore_25 as asyncore\n        exitnow = DummyDispatcher(readable=True, error=asyncore.ExitNow)\n        pgroup.dispatchers = {6:exitnow}\n        supervisord.process_groups = {'foo': pgroup}\n        options.test = True\n        self.assertRaises(asyncore.ExitNow, supervisord.runforever)\n\n    def test_runforever_select_dispatcher_exitnow_via_write(self):\n        options = DummyOptions()\n        options.poller.result = [], [6]\n        supervisord = self._makeOne(options)\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo',)\n        gconfig = DummyPGroupConfig(options, pconfigs=[pconfig])\n        pgroup = DummyProcessGroup(gconfig)\n        from supervisor.medusa import asyncore_25 as asyncore\n        exitnow = DummyDispatcher(readable=True, error=asyncore.ExitNow)\n        pgroup.dispatchers = {6:exitnow}\n        supervisord.process_groups = {'foo': pgroup}\n        options.test = True\n        self.assertRaises(asyncore.ExitNow, supervisord.runforever)\n\n    def test_runforever_select_dispatcher_handle_error_via_read(self):\n        options = DummyOptions()\n        options.poller.result = [6], []\n        supervisord = self._makeOne(options)\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo',)\n        gconfig = DummyPGroupConfig(options, pconfigs=[pconfig])\n        pgroup = DummyProcessGroup(gconfig)\n        notimpl = DummyDispatcher(readable=True, error=NotImplementedError)\n        pgroup.dispatchers = {6:notimpl}\n        supervisord.process_groups = {'foo': pgroup}\n        options.test = True\n        supervisord.runforever()\n        self.assertEqual(notimpl.error_handled, True)\n\n    def test_runforever_select_dispatcher_handle_error_via_write(self):\n        options = DummyOptions()\n        options.poller.result = [], [6]\n        supervisord = self._makeOne(options)\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo',)\n        gconfig = DummyPGroupConfig(options, pconfigs=[pconfig])\n        pgroup = DummyProcessGroup(gconfig)\n        notimpl = DummyDispatcher(readable=True, error=NotImplementedError)\n        pgroup.dispatchers = {6:notimpl}\n        supervisord.process_groups = {'foo': pgroup}\n        options.test = True\n        supervisord.runforever()\n        self.assertEqual(notimpl.error_handled, True)\n\n    def test_runforever_stopping_emits_events(self):\n        options = DummyOptions()\n        supervisord = self._makeOne(options)\n        gconfig = DummyPGroupConfig(options)\n        pgroup = DummyProcessGroup(gconfig)\n        supervisord.process_groups = {'foo': pgroup}\n        supervisord.options.mood = SupervisorStates.SHUTDOWN\n        L = []\n        def callback(event):\n            L.append(event)\n        from supervisor import events\n        events.subscribe(events.SupervisorStateChangeEvent, callback)\n        from supervisor.medusa import asyncore_25 as asyncore\n        options.test = True\n        self.assertRaises(asyncore.ExitNow, supervisord.runforever)\n        self.assertTrue(pgroup.all_stopped)\n        self.assertTrue(isinstance(L[0], events.SupervisorRunningEvent))\n        self.assertTrue(isinstance(L[0], events.SupervisorStateChangeEvent))\n        self.assertTrue(isinstance(L[1], events.SupervisorStoppingEvent))\n        self.assertTrue(isinstance(L[1], events.SupervisorStateChangeEvent))\n\n    def test_exit(self):\n        options = DummyOptions()\n        supervisord = self._makeOne(options)\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo',)\n        gconfig = DummyPGroupConfig(options, pconfigs=[pconfig])\n        pgroup = DummyProcessGroup(gconfig)\n        L = []\n        def callback():\n            L.append(1)\n        supervisord.process_groups = {'foo': pgroup}\n        supervisord.options.mood = SupervisorStates.RESTARTING\n        supervisord.options.test = True\n        from supervisor.medusa import asyncore_25 as asyncore\n        self.assertRaises(asyncore.ExitNow, supervisord.runforever)\n        self.assertEqual(pgroup.all_stopped, True)\n\n    def test_exit_delayed(self):\n        options = DummyOptions()\n        supervisord = self._makeOne(options)\n        pconfig = DummyPConfig(options, 'foo', '/bin/foo',)\n        process = DummyProcess(pconfig)\n        gconfig = DummyPGroupConfig(options, pconfigs=[pconfig])\n        pgroup = DummyProcessGroup(gconfig)\n        pgroup.unstopped_processes = [process]\n        L = []\n        def callback():\n            L.append(1)\n        supervisord.process_groups = {'foo': pgroup}\n        supervisord.options.mood = SupervisorStates.RESTARTING\n        supervisord.options.test = True\n        supervisord.runforever()\n        self.assertNotEqual(supervisord.lastshutdownreport, 0)\n\n    def test_getSupervisorStateDescription(self):\n        from supervisor.states import getSupervisorStateDescription\n        result = getSupervisorStateDescription(SupervisorStates.RUNNING)\n        self.assertEqual(result, 'RUNNING')\n\n    def test_tick(self):\n        from supervisor import events\n        L = []\n        def callback(event):\n            L.append(event)\n        events.subscribe(events.TickEvent, callback)\n        options = DummyOptions()\n        supervisord = self._makeOne(options)\n\n        supervisord.tick(now=0)\n        self.assertEqual(supervisord.ticks[5], 0)\n        self.assertEqual(supervisord.ticks[60], 0)\n        self.assertEqual(supervisord.ticks[3600], 0)\n        self.assertEqual(len(L), 0)\n\n        supervisord.tick(now=6)\n        self.assertEqual(supervisord.ticks[5], 5)\n        self.assertEqual(supervisord.ticks[60], 0)\n        self.assertEqual(supervisord.ticks[3600], 0)\n        self.assertEqual(len(L), 1)\n        self.assertEqual(L[-1].__class__, events.Tick5Event)\n\n        supervisord.tick(now=61)\n        self.assertEqual(supervisord.ticks[5], 60)\n        self.assertEqual(supervisord.ticks[60], 60)\n        self.assertEqual(supervisord.ticks[3600], 0)\n        self.assertEqual(len(L), 3)\n        self.assertEqual(L[-1].__class__, events.Tick60Event)\n\n        supervisord.tick(now=3601)\n        self.assertEqual(supervisord.ticks[5], 3600)\n        self.assertEqual(supervisord.ticks[60], 3600)\n        self.assertEqual(supervisord.ticks[3600], 3600)\n        self.assertEqual(len(L), 6)\n        self.assertEqual(L[-1].__class__, events.Tick3600Event)\n"
  },
  {
    "path": "supervisor/tests/test_templating.py",
    "content": "# This file was originally based on the meld3 package version 2.0.0\n# (https://pypi.org/project/meld3/2.0.0/).  The meld3 package is not\n# called out separately in Supervisor's license or copyright files\n# because meld3 had the same authors, copyright, and license as\n# Supervisor at the time this file was bundled with Supervisor.\n\nimport unittest\nimport re\n\n_SIMPLE_XML = r\"\"\"<?xml version=\"1.0\"?>\n<root xmlns:meld=\"https://github.com/Supervisor/supervisor\">\n  <list meld:id=\"list\">\n    <item meld:id=\"item\">\n       <name meld:id=\"name\">Name</name>\n       <description meld:id=\"description\">Description</description>\n    </item>\n  </list>\n</root>\"\"\"\n\n_SIMPLE_XHTML = r\"\"\"<html xmlns=\"http://www.w3.org/1999/xhtml\"\n      xmlns:meld=\"https://github.com/Supervisor/supervisor\">\n   <body meld:id=\"body\">Hello!</body>\n</html>\"\"\"\n\n_EMPTYTAGS_HTML = \"\"\"<html>\n  <body>\n    <area/><base/><basefont/><br/><col/><frame/><hr/><img/><input/><isindex/>\n    <link/><meta/><param/>\n  </body>\n</html>\"\"\"\n\n_BOOLEANATTRS_XHTML= \"\"\"<html>\n  <body>\n  <tag selected=\"true\"/>\n  <tag checked=\"true\"/>\n  <tag compact=\"true\"/>\n  <tag declare=\"true\"/>\n  <tag defer=\"true\"/>\n  <tag disabled=\"true\"/>\n  <tag ismap=\"true\"/>\n  <tag multiple=\"true\"/>\n  <tag nohref=\"true\"/>\n  <tag noresize=\"true\"/>\n  <tag noshade=\"true\"/>\n  <tag nowrap=\"true\"/>\n  </body>\n</html>\"\"\"\n\n_ENTITIES_XHTML= r\"\"\"\n<html>\n<head></head>\n<body>\n  <!-- test entity references -->\n  <p>&nbsp;</p>\n</body>\n</html>\"\"\"\n\n_COMPLEX_XHTML = r\"\"\"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\"\n      xmlns:meld=\"https://github.com/Supervisor/supervisor\"\n      xmlns:bar=\"http://foo/bar\">\n  <head>\n    <meta content=\"text/html; charset=ISO-8859-1\" http-equiv=\"content-type\" />\n    <title meld:id=\"title\">This will be escaped in html output: &amp;</title>\n    <script>this won't be escaped in html output: &amp;</script>\n    <script type=\"text/javascript\">\n            //<![CDATA[\n              // this won't be escaped in html output\n              function match(a,b) {\n                 if (a < b && a > 0) then { return 1 }\n                }\n             //]]>\n    </script>\n    <style>this won't be escaped in html output: &amp;</style>\n  </head>\n  <!-- a comment -->\n  <body>\n    <div bar:baz=\"slab\"/>\n    <div meld:id=\"content_well\">\n      <form meld:id=\"form1\" action=\".\" method=\"POST\">\n      <img src=\"foo.gif\"/>\n      <table border=\"0\" meld:id=\"table1\">\n        <tbody meld:id=\"tbody\">\n          <tr meld:id=\"tr\" class=\"foo\">\n            <td meld:id=\"td1\">Name</td>\n            <td meld:id=\"td2\">Description</td>\n          </tr>\n        </tbody>\n      </table>\n      <input type=\"submit\" name=\"submit\" value=\" Next \"/>\n      </form>\n    </div>\n  </body>\n</html>\"\"\"\n\n_NVU_HTML = \"\"\"<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n<html>\n<head>\n  <meta content=\"text/html; charset=ISO-8859-1\" http-equiv=\"content-type\">\n  <title meld:id=\"title\">test doc</title>\n</head>\n<body>\n <!-- comment! -->\n Oh yeah...<br>\n<br>\n<table style=\"text-align: left; width: 100px;\" border=\"1\" cellpadding=\"2\" cellspacing=\"2\">\n  <tbody>\n    <tr>\n      <td>Yup</td>\n      <td>More </td>\n      <td>Stuff</td>\n      <td>Oh Yeah</td>\n    </tr>\n    <tr>\n      <td>1</td>\n      <td>2</td>\n      <td>3</td>\n      <td>4</td>\n    </tr>\n    <tr>\n      <td></td>\n      <td></td>\n      <td></td>\n      <td></td>\n    </tr>\n  </tbody>\n</table>\n<br>\n<a href=\".\">And an image...</a><br>\n<br>\n<img style=\"width: 2048px; height: 1536px;\" alt=\"dumb\" src=\"IMG_0102.jpg\">\n</body>\n</html>\"\"\"\n\n_FILLMELDFORM_HTML = \"\"\"\\\n<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n<html>\n<head>\n <title>Emergency Contacts</title>\n</head>\n<body>\n   <div class=\"header\">Emergency Contacts</div>\n\n   <form action=\".\" method=\"POST\">\n    <table>\n\n      <tbody meld:id=\"tbody\">\n\n\t<tr>\n\t  <th>Title</th>\n\t  <td>\n\t    <input type=\"text\" name=\"honorific\" size=\"6\" value=\"\"\n\t\t   meld:id=\"honorific\"/>\n\t  </td>\n\t</tr>\n\n\t<tr>\n\t  <th>First Name</th>\n\t  <td>\n\t    <input type=\"text\" name=\"firstname\" size=\"20\" value=\"\"\n\t\t   meld:id=\"firstname\"/>\n\t  </td>\n\t</tr>\n\n\t<tr>\n\t  <th>Middle Name</th>\n\t  <td>\n\t    <input type=\"text\" name=\"middlename\" size=\"15\" value=\"\"\n\t\t   meld:id=\"middlename\"/>\n\t  </td>\n\t</tr>\n\n\t<tr>\n\t  <th>Last Name</th>\n\t  <td>\n\t    <input type=\"text\" name=\"lastname\" size=\"20\" value=\"\"\n\t\t   meld:id=\"lastname\"/>\n\t  </td>\n\t</tr>\n\n\t<tr>\n\n\t  <th>Suffix</th>\n\t  <td style=\"width: 554px;\">\n          <select name=\"suffix\" meld:id=\"suffix\">\n\t      <option value=\"Jr.\">Jr.</option>\n\t      <option value=\"Sr.\">Sr.</option>\n\t      <option value=\"III\">III</option>\n\t    </select>\n          </td>\n\n        </tr>\n\n\t<tr>\n\t  <th>Address 1</th>\n\t  <td>\n\t    <input type=\"text\" name=\"address1\" size=\"30\" value=\"\"\n\t\t   meld:id=\"address1\"/>\n\t  </td>\n\t</tr>\n\n\t<tr>\n\t  <th>Address 2</th>\n\t  <td>\n\t    <input type=\"text\" name=\"address2\" size=\"30\" value=\"\"\n\t\t   meld:id=\"address2\"/>\n\t  </td>\n\t</tr>\n\n\t<tr>\n\t  <th>City</th>\n\t  <td>\n\t    <input type=\"text\" name=\"city\" size=\"20\" value=\"\"\n\t\t   meld:id=\"city\"/>\n\t  </td>\n\t</tr>\n\n\t<tr>\n\t  <th>State</th>\n\t  <td>\n\t    <input type=\"text\" name=\"state\" size=\"5\" value=\"\"\n\t\t   meld:id=\"state\"/>\n\t  </td>\n\t</tr>\n\n\t<tr>\n\t  <th>ZIP</th>\n\t  <td>\n\t    <input type=\"text\" name=\"zip\" size=\"8\" value=\"\"\n\t\t   meld:id=\"zip\"/>\n\t  </td>\n\t</tr>\n\n\t<tr>\n\t  <th>Home Phone</th>\n\t  <td>\n\t    <input type=\"text\" name=\"homephone\" size=\"12\" value=\"\"\n\t\t   meld:id=\"homephone\"/>\n\t  </td>\n\t</tr>\n\n\t<tr>\n\t  <th>Cell/Mobile Phone</th>\n\t  <td>\n\t    <input type=\"text\" name=\"cellphone\" size=\"12\" value=\"\"\n\t\t   meld:id=\"cellphone\"/>\n\t  </td>\n\t</tr>\n\n\t<tr>\n\t  <th>Email Address</th>\n\t  <td>\n\t    <input type=\"text\" name=\"email\" size=\"20\" value=\"\"\n\t\t   meld:id=\"email\"/>\n\t  </td>\n\t</tr>\n\n\t<tr>\n\t  <th>Over 18? (Checkbox Boolean)</th>\n\t  <td>\n            <input type=\"checkbox\" name=\"over18\" meld:id=\"over18\"\n                   value=\"true\" checked=\"true\"/>\n\t  </td>\n\t</tr>\n\n\t<tr>\n\n\t  <th>Mail OK? (Checkbox Ternary)</th>\n          <td style=\"width: 554px;\" meld:id=\"mailok:inputgroup\">\n            <input type=\"hidden\" name=\"mailok:default\"\n                   value=\"false\"/>\n            <input type=\"checkbox\" name=\"mailok\"\n                   value=\"true\" checked/>\n            <input type=\"checkbox\" name=\"mailok\"\n                   value=\"false\"/>\n          </td>\n\n        </tr>\n\n\t<tr>\n\n\t  <th>Favorite Color (Radio)</th>\n          <td style=\"width: 554px;\" meld:id=\"favorite_color:inputgroup\">\n            Red   <input type=\"radio\" name=\"favorite_color\"\n                         value=\"Red\"/>\n            Green <input type=\"radio\" name=\"favorite_color\"\n                         value=\"Green\"/>\n            Blue  <input type=\"radio\" name=\"favorite_color\"\n                         value=\"Blue\"/>\n          </td>\n\n        </tr>\n\n\t<tr>\n\t  <th></th>\n\t  <td>\n\t    <input type=\"submit\" value=\" Update \" name=\"edit:method\" />\n\t  </td>\n\t</tr>\n\n      </tbody>\n    </table>\n    </form>\n\n<p><a href=\"..\">Return to list</a></p>\n</body>\n</html>\n\"\"\"\n\nclass MeldAPITests(unittest.TestCase):\n    def _makeElement(self, string):\n        from supervisor.templating import parse_xmlstring\n        return parse_xmlstring(string)\n\n    def _makeElementFromHTML(self, string):\n        from supervisor.templating import parse_htmlstring\n        return parse_htmlstring(string)\n\n    def test_findmeld(self):\n        root = self._makeElement(_SIMPLE_XML)\n        item = root.findmeld('item')\n        self.assertEqual(item.tag, 'item')\n        name = root.findmeld('name')\n        self.assertEqual(name.text, 'Name')\n\n    def test_findmeld_default(self):\n        root = self._makeElement(_SIMPLE_XML)\n        item = root.findmeld('item')\n        self.assertEqual(item.tag, 'item')\n        unknown = root.findmeld('unknown', 'foo')\n        self.assertEqual(unknown, 'foo')\n        self.assertEqual(root.findmeld('unknown'), None)\n\n    def test_repeat_nochild(self):\n        root = self._makeElement(_SIMPLE_XML)\n        item = root.findmeld('item')\n        self.assertEqual(item.tag, 'item')\n        data = [{'name':'Jeff Buckley', 'description':'ethereal'},\n                {'name':'Slipknot', 'description':'heavy'}]\n        for element, d in item.repeat(data):\n            element.findmeld('name').text = d['name']\n            element.findmeld('description').text = d['description']\n        self.assertEqual(item[0].text, 'Jeff Buckley')\n        self.assertEqual(item[1].text, 'ethereal')\n\n    def test_repeat_child(self):\n        root = self._makeElement(_SIMPLE_XML)\n        list = root.findmeld('list')\n        self.assertEqual(list.tag, 'list')\n        data = [{'name':'Jeff Buckley', 'description':'ethereal'},\n                {'name':'Slipknot', 'description':'heavy'}]\n        for element, d in list.repeat(data, 'item'):\n            element.findmeld('name').text = d['name']\n            element.findmeld('description').text = d['description']\n        self.assertEqual(list[0][0].text, 'Jeff Buckley')\n        self.assertEqual(list[0][1].text, 'ethereal')\n        self.assertEqual(list[1][0].text, 'Slipknot')\n        self.assertEqual(list[1][1].text, 'heavy')\n\n    def test_mod(self):\n        root = self._makeElement(_SIMPLE_XML)\n        root % {'description':'foo', 'name':'bar'}\n        name = root.findmeld('name')\n        self.assertEqual(name.text, 'bar')\n        desc = root.findmeld('description')\n        self.assertEqual(desc.text, 'foo')\n\n    def test_fillmelds(self):\n        root = self._makeElement(_SIMPLE_XML)\n        unfilled = root.fillmelds(**{'description':'foo', 'jammyjam':'a'})\n        desc = root.findmeld('description')\n        self.assertEqual(desc.text, 'foo')\n        self.assertEqual(unfilled, ['jammyjam'])\n\n    def test_fillmeldhtmlform(self):\n        data = [\n            {'honorific':'Mr.', 'firstname':'Chris', 'middlename':'Phillips',\n             'lastname':'McDonough', 'address1':'802 Caroline St.',\n             'address2':'Apt. 2B', 'city':'Fredericksburg', 'state': 'VA',\n             'zip':'22401', 'homephone':'555-1212', 'cellphone':'555-1313',\n             'email':'user@example.com', 'suffix':'Sr.', 'over18':True,\n             'mailok:inputgroup':'true', 'favorite_color:inputgroup':'Green'},\n            {'honorific':'Mr.', 'firstname':'Fred', 'middlename':'',\n             'lastname':'Rogers', 'address1':'1 Imaginary Lane',\n             'address2':'Apt. 3A', 'city':'Never Never Land', 'state': 'LA',\n             'zip':'00001', 'homephone':'555-1111', 'cellphone':'555-4444',\n             'email':'fred@neighborhood.com', 'suffix':'Jr.', 'over18':False,\n             'mailok:inputgroup':'false','favorite_color:inputgroup':'Yellow',},\n            {'firstname':'Fred', 'middlename':'',\n             'lastname':'Rogers', 'address1':'1 Imaginary Lane',\n             'address2':'Apt. 3A', 'city':'Never Never Land', 'state': 'LA',\n             'zip':'00001', 'homephone':'555-1111', 'cellphone':'555-4444',\n             'email':'fred@neighborhood.com', 'suffix':'IV', 'over18':False,\n             'mailok:inputgroup':'false', 'favorite_color:inputgroup':'Blue',\n             'notthere':1,},\n            ]\n        root = self._makeElementFromHTML(_FILLMELDFORM_HTML)\n\n        clone = root.clone()\n        unfilled = clone.fillmeldhtmlform(**data[0])\n        self.assertEqual(unfilled, [])\n        self.assertEqual(clone.findmeld('honorific').attrib['value'], 'Mr.')\n        self.assertEqual(clone.findmeld('firstname').attrib['value'], 'Chris')\n        middlename = clone.findmeld('middlename')\n        self.assertEqual(middlename.attrib['value'], 'Phillips')\n        suffix = clone.findmeld('suffix')\n        self.assertEqual(suffix[1].attrib['selected'], 'selected')\n        self.assertEqual(clone.findmeld('over18').attrib['checked'], 'checked')\n        mailok = clone.findmeld('mailok:inputgroup')\n        self.assertEqual(mailok[1].attrib['checked'], 'checked')\n        favoritecolor = clone.findmeld('favorite_color:inputgroup')\n        self.assertEqual(favoritecolor[1].attrib['checked'], 'checked')\n\n        clone = root.clone()\n        unfilled = clone.fillmeldhtmlform(**data[1])\n        self.assertEqual(unfilled, ['favorite_color:inputgroup'])\n        self.assertEqual(clone.findmeld('over18').attrib.get('checked'), None)\n        mailok = clone.findmeld('mailok:inputgroup')\n        self.assertEqual(mailok[2].attrib['checked'], 'checked')\n        self.assertEqual(mailok[1].attrib.get('checked'), None)\n\n        clone = root.clone()\n        unfilled = clone.fillmeldhtmlform(**data[2])\n        self.assertEqual(sorted(unfilled), ['notthere', 'suffix'])\n        self.assertEqual(clone.findmeld('honorific').text, None)\n        favoritecolor = clone.findmeld('favorite_color:inputgroup')\n        self.assertEqual(favoritecolor[2].attrib['checked'], 'checked')\n        self.assertEqual(favoritecolor[1].attrib.get('checked'), None)\n\n    def test_replace_removes_all_elements(self):\n        from supervisor.templating import Replace\n        root = self._makeElement(_SIMPLE_XML)\n        L = root.findmeld('list')\n        L.replace('this is a textual replacement')\n        R = root[0]\n        self.assertEqual(R.tag, Replace)\n        self.assertEqual(len(root.getchildren()), 1)\n\n    def test_replace_replaces_the_right_element(self):\n        from supervisor.templating import Replace\n        root = self._makeElement(_SIMPLE_XML)\n        D = root.findmeld('description')\n        D.replace('this is a textual replacement')\n        self.assertEqual(len(root.getchildren()), 1)\n        L = root[0]\n        self.assertEqual(L.tag, 'list')\n        self.assertEqual(len(L.getchildren()), 1)\n        I = L[0]\n        self.assertEqual(I.tag, 'item')\n        self.assertEqual(len(I.getchildren()), 2)\n        N = I[0]\n        self.assertEqual(N.tag, 'name')\n        self.assertEqual(len(N.getchildren()), 0)\n        D = I[1]\n        self.assertEqual(D.tag, Replace)\n        self.assertEqual(D.text, 'this is a textual replacement')\n        self.assertEqual(len(D.getchildren()), 0)\n        self.assertEqual(D.structure, False)\n\n    def test_content(self):\n        from supervisor.templating import Replace\n        root = self._makeElement(_SIMPLE_XML)\n        D = root.findmeld('description')\n        D.content('this is a textual replacement')\n        self.assertEqual(len(root.getchildren()), 1)\n        L = root[0]\n        self.assertEqual(L.tag, 'list')\n        self.assertEqual(len(L.getchildren()), 1)\n        I = L[0]\n        self.assertEqual(I.tag, 'item')\n        self.assertEqual(len(I.getchildren()), 2)\n        N = I[0]\n        self.assertEqual(N.tag, 'name')\n        self.assertEqual(len(N.getchildren()), 0)\n        D = I[1]\n        self.assertEqual(D.tag, 'description')\n        self.assertEqual(D.text, None)\n        self.assertEqual(len(D.getchildren()), 1)\n        T = D[0]\n        self.assertEqual(T.tag, Replace)\n        self.assertEqual(T.text, 'this is a textual replacement')\n        self.assertEqual(T.structure, False)\n\n    def test_attributes(self):\n        from supervisor.templating import _MELD_ID\n        root = self._makeElement(_COMPLEX_XHTML)\n        D = root.findmeld('form1')\n        D.attributes(foo='bar', baz='1', g='2', action='#')\n        self.assertEqual(D.attrib, {\n            'foo':'bar', 'baz':'1', 'g':'2',\n            'method':'POST', 'action':'#',\n            _MELD_ID: 'form1'})\n\n    def test_attributes_unicode(self):\n        from supervisor.templating import _MELD_ID\n        from supervisor.compat import as_string\n        root = self._makeElement(_COMPLEX_XHTML)\n        D = root.findmeld('form1')\n        D.attributes(foo=as_string('bar', encoding='latin1'),\n                     action=as_string('#', encoding='latin1'))\n        self.assertEqual(D.attrib, {\n            'foo':as_string('bar', encoding='latin1'),\n            'method':'POST', 'action': as_string('#', encoding='latin1'),\n            _MELD_ID: 'form1'})\n\n    def test_attributes_nonstringtype_raises(self):\n        root = self._makeElement('<root></root>')\n        self.assertRaises(ValueError, root.attributes, foo=1)\n\nclass MeldElementInterfaceTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.templating import _MeldElementInterface\n        return _MeldElementInterface\n\n    def _makeOne(self, *arg, **kw):\n        klass = self._getTargetClass()\n        return klass(*arg, **kw)\n\n    def test_repeat(self):\n        root = self._makeOne('root', {})\n        from supervisor.templating import _MELD_ID\n        item = self._makeOne('item', {_MELD_ID:'item'})\n        record = self._makeOne('record', {_MELD_ID:'record'})\n        name = self._makeOne('name', {_MELD_ID:'name'})\n        description = self._makeOne('description', {_MELD_ID:'description'})\n        record.append(name)\n        record.append(description)\n        item.append(record)\n        root.append(item)\n\n        data = [{'name':'Jeff Buckley', 'description':'ethereal'},\n                {'name':'Slipknot', 'description':'heavy'}]\n\n        for element, d in item.repeat(data):\n            element.findmeld('name').text = d['name']\n            element.findmeld('description').text = d['description']\n        self.assertEqual(len(root), 2)\n        item1 = root[0]\n        self.assertEqual(len(item1), 1)\n\n        record1 = item1[0]\n        self.assertEqual(len(record1), 2)\n\n        name1 = record1[0]\n        desc1 = record1[1]\n        self.assertEqual(name1.text, 'Jeff Buckley')\n        self.assertEqual(desc1.text, 'ethereal')\n\n        item2 = root[1]\n        self.assertEqual(len(item2), 1)\n        record2 = item2[0]\n        self.assertEqual(len(record2), 2)\n        name2 = record2[0]\n        desc2 = record2[1]\n        self.assertEqual(name2.text, 'Slipknot')\n        self.assertEqual(desc2.text, 'heavy')\n\n    def test_content_simple_nostructure(self):\n        el = self._makeOne('div', {'id':'thediv'})\n        el.content('hello')\n        self.assertEqual(len(el._children), 1)\n        replacenode = el._children[0]\n        self.assertEqual(replacenode.parent, el)\n        self.assertEqual(replacenode.text, 'hello')\n        self.assertEqual(replacenode.structure, False)\n        from supervisor.templating import Replace\n        self.assertEqual(replacenode.tag, Replace)\n\n    def test_content_simple_structure(self):\n        el = self._makeOne('div', {'id':'thediv'})\n        el.content('hello', structure=True)\n        self.assertEqual(len(el._children), 1)\n        replacenode = el._children[0]\n        self.assertEqual(replacenode.parent, el)\n        self.assertEqual(replacenode.text, 'hello')\n        self.assertEqual(replacenode.structure, True)\n        from supervisor.templating import Replace\n        self.assertEqual(replacenode.tag, Replace)\n\n    def test_findmeld_simple(self):\n        from supervisor.templating import _MELD_ID\n        el = self._makeOne('div', {_MELD_ID:'thediv'})\n        self.assertEqual(el.findmeld('thediv'), el)\n\n    def test_findmeld_simple_oneleveldown(self):\n        from supervisor.templating import _MELD_ID\n        el = self._makeOne('div', {_MELD_ID:'thediv'})\n        span = self._makeOne('span', {_MELD_ID:'thespan'})\n        el.append(span)\n        self.assertEqual(el.findmeld('thespan'), span)\n\n    def test_findmeld_simple_twolevelsdown(self):\n        from supervisor.templating import _MELD_ID\n        el = self._makeOne('div', {_MELD_ID:'thediv'})\n        span = self._makeOne('span', {_MELD_ID:'thespan'})\n        a = self._makeOne('a', {_MELD_ID:'thea'})\n        span.append(a)\n        el.append(span)\n        self.assertEqual(el.findmeld('thea'), a)\n\n    def test_ctor(self):\n        iface = self._makeOne('div', {'id':'thediv'})\n        self.assertEqual(iface.parent, None)\n        self.assertEqual(iface.tag, 'div')\n        self.assertEqual(iface.attrib, {'id':'thediv'})\n\n    def test_getiterator_simple(self):\n        div = self._makeOne('div', {'id':'thediv'})\n        iterator = div.getiterator()\n        self.assertEqual(len(iterator), 1)\n        self.assertEqual(iterator[0], div)\n\n    def test_getiterator(self):\n        div = self._makeOne('div', {'id':'thediv'})\n        span = self._makeOne('span', {})\n        span2 = self._makeOne('span', {'id':'2'})\n        span3 = self._makeOne('span3', {'id':'3'})\n        span3.text = 'abc'\n        span3.tail = '  '\n        div.append(span)\n        span.append(span2)\n        span2.append(span3)\n\n        it = div.getiterator()\n        self.assertEqual(len(it), 4)\n        self.assertEqual(it[0], div)\n        self.assertEqual(it[1], span)\n        self.assertEqual(it[2], span2)\n        self.assertEqual(it[3], span3)\n\n    def test_getiterator_tag_ignored(self):\n        div = self._makeOne('div', {'id':'thediv'})\n        span = self._makeOne('span', {})\n        span2 = self._makeOne('span', {'id':'2'})\n        span3 = self._makeOne('span3', {'id':'3'})\n        span3.text = 'abc'\n        span3.tail = '  '\n        div.append(span)\n        span.append(span2)\n        span2.append(span3)\n\n        it = div.getiterator(tag='div')\n        self.assertEqual(len(it), 4)\n        self.assertEqual(it[0], div)\n        self.assertEqual(it[1], span)\n        self.assertEqual(it[2], span2)\n        self.assertEqual(it[3], span3)\n\n    def test_append(self):\n        div = self._makeOne('div', {'id':'thediv'})\n        span = self._makeOne('span', {})\n        div.append(span)\n        self.assertEqual(div[0].tag, 'span')\n        self.assertEqual(span.parent, div)\n\n    def test__setitem__(self):\n        div = self._makeOne('div', {'id':'thediv'})\n        span = self._makeOne('span', {})\n        span2 = self._makeOne('span', {'id':'2'})\n        div.append(span)\n        div[0] = span2\n        self.assertEqual(div[0].tag, 'span')\n        self.assertEqual(div[0].attrib, {'id':'2'})\n        self.assertEqual(div[0].parent, div)\n\n    def test_insert(self):\n        div = self._makeOne('div', {'id':'thediv'})\n        span = self._makeOne('span', {})\n        span2 = self._makeOne('span', {'id':'2'})\n        div.append(span)\n        div.insert(0, span2)\n        self.assertEqual(div[0].tag, 'span')\n        self.assertEqual(div[0].attrib, {'id':'2'})\n        self.assertEqual(div[0].parent, div)\n        self.assertEqual(div[1].tag, 'span')\n        self.assertEqual(div[1].attrib, {})\n        self.assertEqual(div[1].parent, div)\n\n    def test_clone_simple(self):\n        div = self._makeOne('div', {'id':'thediv'})\n        div.text = 'abc'\n        div.tail = '   '\n        span = self._makeOne('span', {})\n        div.append(span)\n        div.clone()\n\n    def test_clone(self):\n        div = self._makeOne('div', {'id':'thediv'})\n        span = self._makeOne('span', {})\n        span2 = self._makeOne('span', {'id':'2'})\n        span3 = self._makeOne('span3', {'id':'3'})\n        span3.text = 'abc'\n        span3.tail = '  '\n        div.append(span)\n        span.append(span2)\n        span2.append(span3)\n\n        div2 = div.clone()\n        self.assertEqual(div.tag, div2.tag)\n        self.assertEqual(div.attrib, div2.attrib)\n        self.assertEqual(div[0].tag, div2[0].tag)\n        self.assertEqual(div[0].attrib, div2[0].attrib)\n        self.assertEqual(div[0][0].tag, div2[0][0].tag)\n        self.assertEqual(div[0][0].attrib, div2[0][0].attrib)\n        self.assertEqual(div[0][0][0].tag, div2[0][0][0].tag)\n        self.assertEqual(div[0][0][0].attrib, div2[0][0][0].attrib)\n        self.assertEqual(div[0][0][0].text, div2[0][0][0].text)\n        self.assertEqual(div[0][0][0].tail, div2[0][0][0].tail)\n\n        self.assertNotEqual(id(div), id(div2))\n        self.assertNotEqual(id(div[0]), id(div2[0]))\n        self.assertNotEqual(id(div[0][0]), id(div2[0][0]))\n        self.assertNotEqual(id(div[0][0][0]), id(div2[0][0][0]))\n\n    def test_deparent_noparent(self):\n        div = self._makeOne('div', {})\n        self.assertEqual(div.parent, None)\n        div.deparent()\n        self.assertEqual(div.parent, None)\n\n    def test_deparent_withparent(self):\n        parent = self._makeOne('parent', {})\n        self.assertEqual(parent.parent, None)\n        child = self._makeOne('child', {})\n        parent.append(child)\n        self.assertEqual(parent.parent, None)\n        self.assertEqual(child.parent, parent)\n        self.assertEqual(parent[0], child)\n        child.deparent()\n        self.assertEqual(child.parent, None)\n        self.assertRaises(IndexError, parent.__getitem__, 0)\n\n    def test_setslice(self):\n        parent = self._makeOne('parent', {})\n        child1 = self._makeOne('child1', {})\n        child2 = self._makeOne('child2', {})\n        child3 = self._makeOne('child3', {})\n        children = (child1, child2, child3)\n        parent[0:2] = children\n        self.assertEqual(child1.parent, parent)\n        self.assertEqual(child2.parent, parent)\n        self.assertEqual(child3.parent, parent)\n        self.assertEqual(parent._children, list(children))\n\n    def test_delslice(self):\n        parent = self._makeOne('parent', {})\n        child1 = self._makeOne('child1', {})\n        child2 = self._makeOne('child2', {})\n        child3 = self._makeOne('child3', {})\n        children = (child1, child2, child3)\n        parent[0:2] = children\n        del parent[0:2]\n        self.assertEqual(child1.parent, None)\n        self.assertEqual(child2.parent, None)\n        self.assertEqual(child3.parent, parent)\n        self.assertEqual(len(parent._children), 1)\n\n    def test_remove(self):\n        parent = self._makeOne('parent', {})\n        child1 = self._makeOne('child1', {})\n        parent.append(child1)\n        parent.remove(child1)\n        self.assertEqual(child1.parent, None)\n        self.assertEqual(len(parent._children), 0)\n\n    def test_lineage(self):\n        from supervisor.templating import _MELD_ID\n        div1 = self._makeOne('div', {_MELD_ID:'div1'})\n        span1 = self._makeOne('span', {_MELD_ID:'span1'})\n        span2 = self._makeOne('span', {_MELD_ID:'span2'})\n        span3 = self._makeOne('span', {_MELD_ID:'span3'})\n        span4 = self._makeOne('span', {_MELD_ID:'span4'})\n        span5 = self._makeOne('span', {_MELD_ID:'span5'})\n        span6 = self._makeOne('span', {_MELD_ID:'span6'})\n        unknown = self._makeOne('span', {})\n        div2 = self._makeOne('div2', {_MELD_ID:'div2'})\n        div1.append(span1)\n        span1.append(span2)\n        span2.append(span3)\n        span3.append(unknown)\n        unknown.append(span4)\n        span4.append(span5)\n        span5.append(span6)\n        div1.append(div2)\n        def ids(L):\n            return [ x.meldid() for x in L ]\n        self.assertEqual(ids(div1.lineage()), ['div1'])\n        self.assertEqual(ids(span1.lineage()), ['span1', 'div1'])\n        self.assertEqual(ids(span2.lineage()), ['span2', 'span1', 'div1'])\n        self.assertEqual(ids(span3.lineage()), ['span3', 'span2', 'span1',\n                                                    'div1'])\n        self.assertEqual(ids(unknown.lineage()), [None, 'span3', 'span2',\n                                                  'span1',\n                                                  'div1'])\n        self.assertEqual(ids(span4.lineage()), ['span4', None, 'span3',\n                                                'span2',\n                                                'span1','div1'])\n\n        self.assertEqual(ids(span5.lineage()), ['span5', 'span4', None,\n                                                'span3', 'span2',\n                                                'span1','div1'])\n        self.assertEqual(ids(span6.lineage()), ['span6', 'span5', 'span4',\n                                                None,'span3', 'span2',\n                                                    'span1','div1'])\n        self.assertEqual(ids(div2.lineage()), ['div2', 'div1'])\n\n\n    def test_shortrepr(self):\n        from supervisor.compat import as_bytes\n        div = self._makeOne('div', {'id':'div1'})\n        span = self._makeOne('span', {})\n        span2 = self._makeOne('span', {'id':'2'})\n        div2 = self._makeOne('div2', {'id':'div2'})\n        div.append(span)\n        span.append(span2)\n        div.append(div2)\n        r = div.shortrepr()\n        self.assertEqual(r,\n            as_bytes('<div id=\"div1\"><span><span id=\"2\"></span></span>'\n                     '<div2 id=\"div2\"></div2></div>', encoding='latin1'))\n\n    def test_shortrepr2(self):\n        from supervisor.templating import parse_xmlstring\n        from supervisor.compat import as_bytes\n        root = parse_xmlstring(_COMPLEX_XHTML)\n        r = root.shortrepr()\n        self.assertEqual(r,\n            as_bytes('<html>\\n'\n               '  <head>\\n'\n               '    <meta content=\"text/html; charset=ISO-8859-1\" '\n                         'http-equiv=\"content-type\">\\n'\n               '     [...]\\n</head>\\n'\n               '  <!--  a comment  -->\\n'\n               '   [...]\\n'\n               '</html>', encoding='latin1'))\n\n    def test_diffmeld1(self):\n        from supervisor.templating import parse_xmlstring\n        from supervisor.templating import _MELD_ID\n        root = parse_xmlstring(_COMPLEX_XHTML)\n        clone = root.clone()\n        div = self._makeOne('div', {_MELD_ID:'newdiv'})\n        clone.append(div)\n        tr = clone.findmeld('tr')\n        tr.deparent()\n        title = clone.findmeld('title')\n        title.deparent()\n        clone.append(title)\n\n        # unreduced\n        diff = root.diffmeld(clone)\n        changes = diff['unreduced']\n        addedtags = [ x.attrib[_MELD_ID] for x in changes['added'] ]\n        removedtags = [x.attrib[_MELD_ID] for x in changes['removed'] ]\n        movedtags = [ x.attrib[_MELD_ID] for x in changes['moved'] ]\n        addedtags.sort()\n        removedtags.sort()\n        movedtags.sort()\n        self.assertEqual(addedtags,['newdiv'])\n        self.assertEqual(removedtags,['td1', 'td2', 'tr'])\n        self.assertEqual(movedtags, ['title'])\n\n        # reduced\n        changes = diff['reduced']\n        addedtags = [ x.attrib[_MELD_ID] for x in changes['added'] ]\n        removedtags = [x.attrib[_MELD_ID] for x in changes['removed'] ]\n        movedtags = [ x.attrib[_MELD_ID] for x in changes['moved'] ]\n        addedtags.sort()\n        removedtags.sort()\n        movedtags.sort()\n        self.assertEqual(addedtags,['newdiv'])\n        self.assertEqual(removedtags,['tr'])\n        self.assertEqual(movedtags, ['title'])\n\n    def test_diffmeld2(self):\n        source = \"\"\"\n        <root>\n          <a meld:id=\"a\">\n             <b meld:id=\"b\"></b>\n          </a>\n        </root>\"\"\"\n        target = \"\"\"\n        <root>\n          <a meld:id=\"a\"></a>\n          <b meld:id=\"b\"></b>\n        </root>\n        \"\"\"\n        from supervisor.templating import parse_htmlstring\n        source_root = parse_htmlstring(source)\n        target_root = parse_htmlstring(target)\n        changes = source_root.diffmeld(target_root)\n\n        # unreduced\n        actual = [x.meldid() for x in changes['unreduced']['moved']]\n        expected = ['b']\n        self.assertEqual(expected, actual)\n\n        actual = [x.meldid() for x in changes['unreduced']['added']]\n        expected = []\n        self.assertEqual(expected, actual)\n\n        actual = [x.meldid() for x in changes['unreduced']['removed']]\n        expected = []\n        self.assertEqual(expected, actual)\n\n        # reduced\n        actual = [x.meldid() for x in changes['reduced']['moved']]\n        expected = ['b']\n        self.assertEqual(expected, actual)\n\n        actual = [x.meldid() for x in changes['reduced']['added']]\n        expected = []\n        self.assertEqual(expected, actual)\n\n        actual = [x.meldid() for x in changes['reduced']['removed']]\n        expected = []\n        self.assertEqual(expected, actual)\n\n\n    def test_diffmeld3(self):\n        source = \"\"\"\n        <root>\n          <a meld:id=\"a\">\n             <b meld:id=\"b\">\n               <c meld:id=\"c\"></c>\n             </b>\n          </a>\n          <z meld:id=\"z\">\n            <y meld:id=\"y\"></y>\n          </z>\n        </root>\n        \"\"\"\n        target = \"\"\"\n        <root>\n          <b meld:id=\"b\">\n            <c meld:id=\"c\"></c>\n          </b>\n          <a meld:id=\"a\"></a>\n          <d meld:id=\"d\">\n             <e meld:id=\"e\"></e>\n          </d>\n        </root>\n        \"\"\"\n        from supervisor.templating import parse_htmlstring\n        source_root = parse_htmlstring(source)\n        target_root = parse_htmlstring(target)\n        changes = source_root.diffmeld(target_root)\n\n        # unreduced\n        actual = [x.meldid() for x in changes['unreduced']['moved']]\n        expected = ['b', 'c']\n        self.assertEqual(expected, actual)\n\n        actual = [x.meldid() for x in changes['unreduced']['added']]\n        expected = ['d', 'e']\n        self.assertEqual(expected, actual)\n\n        actual = [x.meldid() for x in changes['unreduced']['removed']]\n        expected = ['z', 'y']\n        self.assertEqual(expected, actual)\n\n        # reduced\n        actual = [x.meldid() for x in changes['reduced']['moved']]\n        expected = ['b']\n        self.assertEqual(expected, actual)\n\n        actual = [x.meldid() for x in changes['reduced']['added']]\n        expected = ['d']\n        self.assertEqual(expected, actual)\n\n        actual = [x.meldid() for x in changes['reduced']['removed']]\n        expected = ['z']\n        self.assertEqual(expected, actual)\n\n    def test_diffmeld4(self):\n        source = \"\"\"\n        <root>\n          <a meld:id=\"a\">\n             <b meld:id=\"b\">\n               <c meld:id=\"c\">\n                 <d meld:id=\"d\"></d>\n               </c>\n             </b>\n          </a>\n          <z meld:id=\"z\">\n            <y meld:id=\"y\"></y>\n          </z>\n        </root>\n        \"\"\"\n        target = \"\"\"\n        <root>\n          <p>\n            <a meld:id=\"a\">\n               <b meld:id=\"b\"></b>\n            </a>\n          </p>\n          <p>\n            <m meld:id=\"m\">\n              <n meld:id=\"n\"></n>\n            </m>\n          </p>\n        </root>\n        \"\"\"\n        from supervisor.templating import parse_htmlstring\n        source_root = parse_htmlstring(source)\n        target_root = parse_htmlstring(target)\n        changes = source_root.diffmeld(target_root)\n\n        # unreduced\n        actual = [x.meldid() for x in changes['unreduced']['moved']]\n        expected = ['a', 'b']\n        self.assertEqual(expected, actual)\n\n        actual = [x.meldid() for x in changes['unreduced']['added']]\n        expected = ['m', 'n']\n        self.assertEqual(expected, actual)\n\n        actual = [x.meldid() for x in changes['unreduced']['removed']]\n        expected = ['c', 'd', 'z', 'y']\n        self.assertEqual(expected, actual)\n\n        # reduced\n        actual = [x.meldid() for x in changes['reduced']['moved']]\n        expected = ['a']\n        self.assertEqual(expected, actual)\n\n        actual = [x.meldid() for x in changes['reduced']['added']]\n        expected = ['m']\n        self.assertEqual(expected, actual)\n\n        actual = [x.meldid() for x in changes['reduced']['removed']]\n        expected = ['c', 'z']\n        self.assertEqual(expected, actual)\n\n    def test_diffmeld5(self):\n        source = \"\"\"\n        <root>\n          <a meld:id=\"a\">\n             <b meld:id=\"b\">\n               <c meld:id=\"c\">\n                 <d meld:id=\"d\"></d>\n               </c>\n             </b>\n          </a>\n          <z meld:id=\"z\">\n            <y meld:id=\"y\"></y>\n          </z>\n        </root>\n        \"\"\"\n        target = \"\"\"\n        <root>\n          <p>\n            <a meld:id=\"a\">\n               <b meld:id=\"b\">\n                 <p>\n                   <c meld:id=\"c\">\n                     <d meld:id=\"d\"></d>\n                   </c>\n                 </p>\n               </b>\n            </a>\n          </p>\n          <z meld:id=\"z\">\n            <y meld:id=\"y\"></y>\n          </z>\n        </root>\n        \"\"\"\n        from supervisor.templating import parse_htmlstring\n        source_root = parse_htmlstring(source)\n        target_root = parse_htmlstring(target)\n        changes = source_root.diffmeld(target_root)\n\n        # unreduced\n        actual = [x.meldid() for x in changes['unreduced']['moved']]\n        expected = ['a', 'b', 'c', 'd']\n        self.assertEqual(expected, actual)\n\n        actual = [x.meldid() for x in changes['unreduced']['added']]\n        expected = []\n        self.assertEqual(expected, actual)\n\n        actual = [x.meldid() for x in changes['unreduced']['removed']]\n        expected = []\n        self.assertEqual(expected, actual)\n\n        # reduced\n        actual = [x.meldid() for x in changes['reduced']['moved']]\n        expected = ['a', 'c']\n        self.assertEqual(expected, actual)\n\n        actual = [x.meldid() for x in changes['reduced']['added']]\n        expected = []\n        self.assertEqual(expected, actual)\n\n        actual = [x.meldid() for x in changes['reduced']['removed']]\n        expected = []\n        self.assertEqual(expected, actual)\n\n\n\nclass ParserTests(unittest.TestCase):\n    def _parse(self, *args):\n        from supervisor.templating import parse_xmlstring\n        root = parse_xmlstring(*args)\n        return root\n\n    def _parse_html(self, *args):\n        from supervisor.templating import parse_htmlstring\n        root = parse_htmlstring(*args)\n        return root\n\n    def test_parse_simple_xml(self):\n        from supervisor.templating import _MELD_ID\n        root = self._parse(_SIMPLE_XML)\n        self.assertEqual(root.tag, 'root')\n        self.assertEqual(root.parent, None)\n        l1st = root[0]\n        self.assertEqual(l1st.tag, 'list')\n        self.assertEqual(l1st.parent, root)\n        self.assertEqual(l1st.attrib[_MELD_ID], 'list')\n        item = l1st[0]\n        self.assertEqual(item.tag, 'item')\n        self.assertEqual(item.parent, l1st)\n        self.assertEqual(item.attrib[_MELD_ID], 'item')\n        name = item[0]\n        description = item[1]\n        self.assertEqual(name.tag, 'name')\n        self.assertEqual(name.parent, item)\n        self.assertEqual(name.attrib[_MELD_ID], 'name')\n        self.assertEqual(description.tag, 'description')\n        self.assertEqual(description.parent, item)\n        self.assertEqual(description.attrib[_MELD_ID], 'description')\n\n    def test_parse_simple_xhtml(self):\n        xhtml_ns = '{http://www.w3.org/1999/xhtml}%s'\n        from supervisor.templating import _MELD_ID\n\n        root = self._parse(_SIMPLE_XHTML)\n        self.assertEqual(root.tag, xhtml_ns % 'html')\n        self.assertEqual(root.attrib, {})\n        self.assertEqual(root.parent, None)\n        body = root[0]\n        self.assertEqual(body.tag, xhtml_ns % 'body')\n        self.assertEqual(body.attrib[_MELD_ID], 'body')\n        self.assertEqual(body.parent, root)\n\n    def test_parse_complex_xhtml(self):\n        xhtml_ns = '{http://www.w3.org/1999/xhtml}%s'\n        from supervisor.templating import _MELD_ID\n        root = self._parse(_COMPLEX_XHTML)\n        self.assertEqual(root.tag, xhtml_ns % 'html')\n        self.assertEqual(root.attrib, {})\n        self.assertEqual(root.parent, None)\n        head = root[0]\n        self.assertEqual(head.tag, xhtml_ns % 'head')\n        self.assertEqual(head.attrib, {})\n        self.assertEqual(head.parent, root)\n        meta = head[0]\n        self.assertEqual(meta.tag, xhtml_ns % 'meta')\n        self.assertEqual(meta.attrib['content'],\n                         'text/html; charset=ISO-8859-1')\n        self.assertEqual(meta.parent, head)\n        title = head[1]\n        self.assertEqual(title.tag, xhtml_ns % 'title')\n        self.assertEqual(title.attrib[_MELD_ID], 'title')\n        self.assertEqual(title.parent, head)\n\n        body = root[2]\n        self.assertEqual(body.tag, xhtml_ns % 'body')\n        self.assertEqual(body.attrib, {})\n        self.assertEqual(body.parent, root)\n\n        div1 = body[0]\n        self.assertEqual(div1.tag, xhtml_ns % 'div')\n        self.assertEqual(div1.attrib, {'{http://foo/bar}baz': 'slab'})\n        self.assertEqual(div1.parent, body)\n\n        div2 = body[1]\n        self.assertEqual(div2.tag, xhtml_ns % 'div')\n        self.assertEqual(div2.attrib[_MELD_ID], 'content_well')\n        self.assertEqual(div2.parent, body)\n\n        form = div2[0]\n        self.assertEqual(form.tag, xhtml_ns % 'form')\n        self.assertEqual(form.attrib[_MELD_ID], 'form1')\n        self.assertEqual(form.attrib['action'], '.')\n        self.assertEqual(form.attrib['method'], 'POST')\n        self.assertEqual(form.parent, div2)\n\n        img = form[0]\n        self.assertEqual(img.tag, xhtml_ns % 'img')\n        self.assertEqual(img.parent, form)\n\n        table = form[1]\n        self.assertEqual(table.tag, xhtml_ns % 'table')\n        self.assertEqual(table.attrib[_MELD_ID], 'table1')\n        self.assertEqual(table.attrib['border'], '0')\n        self.assertEqual(table.parent, form)\n\n        tbody = table[0]\n        self.assertEqual(tbody.tag, xhtml_ns % 'tbody')\n        self.assertEqual(tbody.attrib[_MELD_ID], 'tbody')\n        self.assertEqual(tbody.parent, table)\n\n        tr = tbody[0]\n        self.assertEqual(tr.tag, xhtml_ns % 'tr')\n        self.assertEqual(tr.attrib[_MELD_ID], 'tr')\n        self.assertEqual(tr.attrib['class'], 'foo')\n        self.assertEqual(tr.parent, tbody)\n\n        td1 = tr[0]\n        self.assertEqual(td1.tag, xhtml_ns % 'td')\n        self.assertEqual(td1.attrib[_MELD_ID], 'td1')\n        self.assertEqual(td1.parent, tr)\n\n        td2 = tr[1]\n        self.assertEqual(td2.tag, xhtml_ns % 'td')\n        self.assertEqual(td2.attrib[_MELD_ID], 'td2')\n        self.assertEqual(td2.parent, tr)\n\n    def test_nvu_html(self):\n        from supervisor.templating import _MELD_ID\n        from supervisor.templating import Comment\n        root = self._parse_html(_NVU_HTML)\n        self.assertEqual(root.tag, 'html')\n        self.assertEqual(root.attrib, {})\n        self.assertEqual(root.parent, None)\n        head = root[0]\n        self.assertEqual(head.tag, 'head')\n        self.assertEqual(head.attrib, {})\n        self.assertEqual(head.parent, root)\n        meta = head[0]\n        self.assertEqual(meta.tag, 'meta')\n        self.assertEqual(meta.attrib['content'],\n                         'text/html; charset=ISO-8859-1')\n        title = head[1]\n        self.assertEqual(title.tag, 'title')\n        self.assertEqual(title.attrib[_MELD_ID], 'title')\n        self.assertEqual(title.parent, head)\n\n        body = root[1]\n        self.assertEqual(body.tag, 'body')\n        self.assertEqual(body.attrib, {})\n        self.assertEqual(body.parent, root)\n\n        comment = body[0]\n        self.assertEqual(comment.tag, Comment)\n\n        table = body[3]\n        self.assertEqual(table.tag, 'table')\n        self.assertEqual(table.attrib, {'style':\n                                        'text-align: left; width: 100px;',\n                                        'border':'1',\n                                        'cellpadding':'2',\n                                        'cellspacing':'2'})\n        self.assertEqual(table.parent, body)\n        href = body[5]\n        self.assertEqual(href.tag, 'a')\n        img = body[8]\n        self.assertEqual(img.tag, 'img')\n\n\n    def test_dupe_meldids_fails_parse_xml(self):\n        meld_ns = \"https://github.com/Supervisor/supervisor\"\n        repeated = ('<html xmlns:meld=\"%s\" meld:id=\"repeat\">'\n                    '<body meld:id=\"repeat\"/></html>' % meld_ns)\n        self.assertRaises(ValueError, self._parse, repeated)\n\n    def test_dupe_meldids_fails_parse_html(self):\n        meld_ns = \"https://github.com/Supervisor/supervisor\"\n        repeated = ('<html xmlns:meld=\"%s\" meld:id=\"repeat\">'\n                    '<body meld:id=\"repeat\"/></html>' % meld_ns)\n        self.assertRaises(ValueError, self._parse_html, repeated)\n\nclass UtilTests(unittest.TestCase):\n\n    def test_insert_xhtml_doctype(self):\n        from supervisor.templating import insert_doctype\n        orig = '<root></root>'\n        actual = insert_doctype(orig)\n        expected = '<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><root></root>'\n        self.assertEqual(actual, expected)\n\n    def test_insert_doctype_after_xmldecl(self):\n        from supervisor.templating import insert_doctype\n        orig = '<?xml version=\"1.0\" encoding=\"latin-1\"?><root></root>'\n        actual = insert_doctype(orig)\n        expected = '<?xml version=\"1.0\" encoding=\"latin-1\"?><!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><root></root>'\n        self.assertEqual(actual, expected)\n\n    def test_insert_meld_ns_decl(self):\n        from supervisor.templating import insert_meld_ns_decl\n        orig = '<?xml version=\"1.0\" encoding=\"latin-1\"?><root></root>'\n        actual = insert_meld_ns_decl(orig)\n        expected = '<?xml version=\"1.0\" encoding=\"latin-1\"?><root xmlns:meld=\"https://github.com/Supervisor/supervisor\"></root>'\n        self.assertEqual(actual, expected)\n\n    def test_prefeed_preserves_existing_meld_ns(self):\n        from supervisor.templating import prefeed\n        orig = '<?xml version=\"1.0\" encoding=\"latin-1\"?><root xmlns:meld=\"#\"></root>'\n        actual = prefeed(orig)\n        expected = '<?xml version=\"1.0\" encoding=\"latin-1\"?><!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><root xmlns:meld=\"#\"></root>'\n        self.assertEqual(actual, expected)\n\n    def test_prefeed_preserves_existing_doctype(self):\n        from supervisor.templating import prefeed\n        orig = '<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\"><root xmlns:meld=\"https://github.com/Supervisor/supervisor\"></root>'\n        actual = prefeed(orig)\n        self.assertEqual(actual, orig)\n\nclass WriterTests(unittest.TestCase):\n    def _parse(self, xml):\n        from supervisor.templating import parse_xmlstring\n        root = parse_xmlstring(xml)\n        return root\n\n    def _parse_html(self, xml):\n        from supervisor.templating import parse_htmlstring\n        root = parse_htmlstring(xml)\n        return root\n\n    def _write(self, fn, **kw):\n        try:\n            from io import BytesIO\n        except: # python 2.5\n            from StringIO import StringIO as BytesIO\n        out = BytesIO()\n        fn(out, **kw)\n        out.seek(0)\n        actual = out.read()\n        return actual\n\n    def _write_xml(self, node, **kw):\n        return self._write(node.write_xml, **kw)\n\n    def _write_html(self, node, **kw):\n        return self._write(node.write_html, **kw)\n\n    def _write_xhtml(self, node, **kw):\n        return self._write(node.write_xhtml, **kw)\n\n    def assertNormalizedXMLEqual(self, a, b):\n        from supervisor.compat import as_string\n        a = normalize_xml(as_string(a, encoding='latin1'))\n        b = normalize_xml(as_string(b, encoding='latin1'))\n        self.assertEqual(a, b)\n\n    def assertNormalizedHTMLEqual(self, a, b):\n        from supervisor.compat import as_string\n        a = normalize_xml(as_string(a, encoding='latin1'))\n        b = normalize_xml(as_string(b, encoding='latin1'))\n        self.assertEqual(a, b)\n\n    def test_write_simple_xml(self):\n        root = self._parse(_SIMPLE_XML)\n        actual = self._write_xml(root)\n        expected = \"\"\"<?xml version=\"1.0\"?><root>\n  <list>\n    <item>\n       <name>Name</name>\n       <description>Description</description>\n    </item>\n  </list>\n</root>\"\"\"\n        self.assertNormalizedXMLEqual(actual, expected)\n\n        for el, data in root.findmeld('item').repeat(((1,2),)):\n            el.findmeld('name').text = str(data[0])\n            el.findmeld('description').text = str(data[1])\n        actual = self._write_xml(root)\n        expected = \"\"\"<?xml version=\"1.0\"?><root>\n  <list>\n    <item>\n       <name>1</name>\n       <description>2</description>\n    </item>\n  </list>\n</root>\"\"\"\n        self.assertNormalizedXMLEqual(actual, expected)\n\n    def test_write_simple_xhtml(self):\n        root = self._parse(_SIMPLE_XHTML)\n        actual = self._write_xhtml(root)\n        expected = \"\"\"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><html><body>Hello!</body></html>\"\"\"\n        self.assertNormalizedXMLEqual(actual, expected)\n\n    def test_write_simple_xhtml_as_html(self):\n        root = self._parse(_SIMPLE_XHTML)\n        actual = self._write_html(root)\n        expected = \"\"\"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n<html>\n   <body>Hello!</body>\n</html>\"\"\"\n        self.assertNormalizedHTMLEqual(actual, expected)\n\n    def test_write_complex_xhtml_as_html(self):\n        root = self._parse(_COMPLEX_XHTML)\n        actual = self._write_html(root)\n        expected = \"\"\"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n<html>\n  <head>\n    <meta content=\"text/html; charset=ISO-8859-1\" http-equiv=\"content-type\">\n    <title>This will be escaped in html output: &amp;</title>\n    <script>this won't be escaped in html output: &</script>\n    <script type=\"text/javascript\">\n            //\n              // this won't be escaped in html output\n              function match(a,b) {\n                 if (a < b && a > 0) then { return 1 }\n                }\n             //\n    </script>\n    <style>this won't be escaped in html output: &</style>\n  </head>\n  <!-- a comment -->\n  <body>\n    <div></div>\n    <div>\n      <form action=\".\" method=\"POST\">\n      <img src=\"foo.gif\">\n      <table border=\"0\">\n        <tbody>\n          <tr class=\"foo\">\n            <td>Name</td>\n            <td>Description</td>\n          </tr>\n        </tbody>\n      </table>\n      <input name=\"submit\" type=\"submit\" value=\" Next \">\n      </form>\n    </div>\n  </body>\n</html>\"\"\"\n\n        self.assertNormalizedHTMLEqual(actual, expected)\n\n    def test_write_complex_xhtml_as_xhtml(self):\n        # I'm not entirely sure if the cdata \"script\" quoting in this\n        # test is entirely correct for XHTML.  Ryan Tomayko suggests\n        # that escaped entities are handled properly in script tags by\n        # XML-aware browsers at\n        # http://sourceforge.net/mailarchive/message.php?msg_id=10835582\n        # but I haven't tested it at all.  ZPT does not seem to do\n        # this; it outputs unescaped data.\n        root = self._parse(_COMPLEX_XHTML)\n        actual = self._write_xhtml(root)\n        expected = \"\"\"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html>\n  <head>\n    <meta content=\"text/html; charset=ISO-8859-1\" http-equiv=\"content-type\" />\n    <title>This will be escaped in html output: &amp;</title>\n    <script>this won't be escaped in html output: &amp;</script>\n    <script type=\"text/javascript\">\n            //\n              // this won't be escaped in html output\n              function match(a,b) {\n                 if (a &lt; b &amp;&amp; a > 0) then { return 1 }\n                }\n             //\n    </script>\n    <style>this won't be escaped in html output: &amp;</style>\n  </head>\n  <!--  a comment  -->\n  <body>\n    <div ns0:baz=\"slab\" xmlns:ns0=\"http://foo/bar\" />\n    <div>\n      <form action=\".\" method=\"POST\">\n      <img src=\"foo.gif\" />\n      <table border=\"0\">\n        <tbody>\n          <tr class=\"foo\">\n            <td>Name</td>\n            <td>Description</td>\n          </tr>\n        </tbody>\n      </table>\n      <input name=\"submit\" type=\"submit\" value=\" Next \" />\n      </form>\n    </div>\n  </body>\n</html>\"\"\"\n        self.assertNormalizedXMLEqual(actual, expected)\n\n    def test_write_emptytags_html(self):\n        from supervisor.compat import as_string\n        root = self._parse(_EMPTYTAGS_HTML)\n        actual = self._write_html(root)\n        expected = \"\"\"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n<html>\n  <body>\n    <area><base><basefont><br><col><frame><hr><img><input><isindex>\n    <link><meta><param>\n  </body>\n</html>\"\"\"\n        self.assertEqual(as_string(actual, encoding='latin1'), expected)\n\n    def test_write_booleanattrs_xhtml_as_html(self):\n        root = self._parse(_BOOLEANATTRS_XHTML)\n        actual = self._write_html(root)\n        expected = \"\"\"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n<html>\n  <body>\n  <tag selected></tag>\n  <tag checked></tag>\n  <tag compact></tag>\n  <tag declare></tag>\n  <tag defer></tag>\n  <tag disabled></tag>\n  <tag ismap></tag>\n  <tag multiple></tag>\n  <tag nohref></tag>\n  <tag noresize></tag>\n  <tag noshade></tag>\n  <tag nowrap></tag>\n  </body>\n</html>\"\"\"\n        self.assertNormalizedHTMLEqual(actual, expected)\n\n    def test_write_simple_xhtml_pipeline(self):\n        root = self._parse(_SIMPLE_XHTML)\n        actual = self._write_xhtml(root, pipeline=True)\n        expected = \"\"\"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><html><body ns0:id=\"body\" xmlns:ns0=\"https://github.com/Supervisor/supervisor\">Hello!</body></html>\"\"\"\n        self.assertNormalizedXMLEqual(actual, expected)\n\n    def test_write_simple_xml_pipeline(self):\n        root = self._parse(_SIMPLE_XML)\n        actual = self._write_xml(root, pipeline=True)\n        expected = \"\"\"<?xml version=\"1.0\"?><root>\n  <list ns0:id=\"list\" xmlns:ns0=\"https://github.com/Supervisor/supervisor\">\n    <item ns0:id=\"item\">\n       <name ns0:id=\"name\">Name</name>\n       <description ns0:id=\"description\">Description</description>\n    </item>\n  </list>\n</root>\"\"\"\n        self.assertNormalizedXMLEqual(actual, expected)\n\n    def test_write_simple_xml_override_encoding(self):\n        root = self._parse(_SIMPLE_XML)\n        actual = self._write_xml(root, encoding=\"latin-1\")\n        expected = \"\"\"<?xml version=\"1.0\" encoding=\"latin-1\"?><root>\n  <list>\n    <item>\n       <name>Name</name>\n       <description>Description</description>\n    </item>\n  </list>\n</root>\"\"\"\n        self.assertNormalizedXMLEqual(actual, expected)\n\n\n    def test_write_simple_xml_as_fragment(self):\n        root = self._parse(_SIMPLE_XML)\n        actual = self._write_xml(root, fragment=True)\n        expected = \"\"\"<root>\n  <list>\n    <item>\n       <name>Name</name>\n       <description>Description</description>\n    </item>\n  </list>\n</root>\"\"\"\n        self.assertNormalizedXMLEqual(actual, expected)\n\n    def test_write_simple_xml_with_doctype(self):\n        root = self._parse(_SIMPLE_XML)\n        from supervisor.templating import doctype\n        actual = self._write_xml(root, doctype=doctype.xhtml)\n        expected = \"\"\"<?xml version=\"1.0\"?>\n        <!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><root>\n  <list>\n    <item>\n       <name>Name</name>\n       <description>Description</description>\n    </item>\n  </list>\n</root>\"\"\"\n        self.assertNormalizedXMLEqual(actual, expected)\n\n    def test_write_simple_xml_doctype_nodeclaration(self):\n        root = self._parse(_SIMPLE_XML)\n        from supervisor.templating import doctype\n        actual = self._write_xml(root, declaration=False,\n                                 doctype=doctype.xhtml)\n        expected = \"\"\"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><root>\n  <list>\n    <item>\n       <name>Name</name>\n       <description>Description</description>\n    </item>\n  </list>\n</root>\"\"\"\n        self.assertNormalizedXMLEqual(actual, expected)\n\n    def test_write_simple_xml_fragment_kills_doctype_and_declaration(self):\n        root = self._parse(_SIMPLE_XML)\n        from supervisor.templating import doctype\n        actual = self._write_xml(root, declaration=True,\n                                 doctype=doctype.xhtml, fragment=True)\n        expected = \"\"\"<root>\n  <list>\n    <item>\n       <name>Name</name>\n       <description>Description</description>\n    </item>\n  </list>\n</root>\"\"\"\n        self.assertNormalizedXMLEqual(actual, expected)\n\n    def test_write_simple_xhtml_override_encoding(self):\n        root = self._parse(_SIMPLE_XHTML)\n        actual = self._write_xhtml(root, encoding=\"latin-1\", declaration=True)\n        expected = \"\"\"<?xml version=\"1.0\" encoding=\"latin-1\"?><!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><html><body>Hello!</body></html>\"\"\"\n        self.assertNormalizedXMLEqual(actual, expected)\n\n    def test_write_simple_xhtml_as_fragment(self):\n        root = self._parse(_SIMPLE_XHTML)\n        actual = self._write_xhtml(root, fragment=True)\n        expected = \"\"\"<html><body>Hello!</body></html>\"\"\"\n        self.assertNormalizedXMLEqual(actual, expected)\n\n    def test_write_simple_xhtml_with_doctype(self):\n        root = self._parse(_SIMPLE_XHTML)\n        from supervisor.templating import doctype\n        actual = self._write_xhtml(root, doctype=doctype.xhtml)\n        expected = \"\"\"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><html><body>Hello!</body></html>\"\"\"\n        self.assertNormalizedXMLEqual(actual, expected)\n\n    def test_write_simple_xhtml_doctype_nodeclaration(self):\n        root = self._parse(_SIMPLE_XHTML)\n        from supervisor.templating import doctype\n        actual = self._write_xhtml(root, declaration=False,\n                                 doctype=doctype.xhtml)\n        expected = \"\"\"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><html><body>Hello!</body></html>\"\"\"\n        self.assertNormalizedXMLEqual(actual, expected)\n\n    def test_write_simple_xhtml_fragment_kills_doctype_and_declaration(self):\n        root = self._parse(_SIMPLE_XHTML)\n        from supervisor.templating import doctype\n        actual = self._write_xhtml(root, declaration=True,\n                                 doctype=doctype.xhtml, fragment=True)\n        expected = \"\"\"<html><body>Hello!</body></html>\"\"\"\n        self.assertNormalizedXMLEqual(actual, expected)\n\n    def test_write_simple_xhtml_as_html_fragment(self):\n        root = self._parse(_SIMPLE_XHTML)\n        actual = self._write_html(root, fragment=True)\n        expected = \"\"\"<html><body>Hello!</body></html>\"\"\"\n        self.assertNormalizedXMLEqual(actual, expected)\n\n    def test_write_simple_xhtml_with_doctype_as_html(self):\n        root = self._parse(_SIMPLE_XHTML)\n        actual = self._write_html(root)\n        expected = \"\"\"\n<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n<html><body>Hello!</body></html>\"\"\"\n        self.assertNormalizedXMLEqual(actual, expected)\n\n    def test_write_simple_xhtml_as_html_new_doctype(self):\n        root = self._parse(_SIMPLE_XHTML)\n        from supervisor.templating import doctype\n        actual = self._write_html(root, doctype=doctype.html_strict)\n        expected = \"\"\"\n<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n<html><body>Hello!</body></html>\"\"\"\n        self.assertNormalizedXMLEqual(actual, expected)\n\n    def test_unknown_entity(self):\n        # exception thrown may vary by python or expat version\n        from xml.parsers import expat\n        self.assertRaises((expat.error, SyntaxError), self._parse,\n                          '<html><head></head><body>&fleeb;</body></html>')\n\n    def test_content_nostructure(self):\n        root = self._parse(_SIMPLE_XML)\n        D = root.findmeld('description')\n        D.content('description &<foo>&amp;<bar>', structure=False)\n        actual = self._write_xml(root)\n        expected = \"\"\"<?xml version=\"1.0\"?>\n        <root>\n        <list>\n        <item>\n        <name>Name</name>\n          <description>description &amp;&lt;foo>&amp;&lt;bar></description>\n        </item>\n        </list>\n        </root>\"\"\"\n        self.assertNormalizedXMLEqual(actual, expected)\n\n    def test_content_structure(self):\n        root = self._parse(_SIMPLE_XML)\n        D = root.findmeld('description')\n        D.content('description &<foo> <bar>', structure=True)\n        actual = self._write_xml(root)\n        expected = \"\"\"<?xml version=\"1.0\"?>\n        <root>\n        <list>\n        <item>\n        <name>Name</name>\n          <description>description &<foo> <bar></description>\n        </item>\n        </list>\n        </root>\"\"\"\n        self.assertNormalizedXMLEqual(actual, expected)\n\n    def test_replace_nostructure(self):\n        root = self._parse(_SIMPLE_XML)\n        D = root.findmeld('description')\n        D.replace('description &<foo>&amp;<bar>', structure=False)\n        actual = self._write_xml(root)\n        expected = \"\"\"<?xml version=\"1.0\"?>\n        <root>\n        <list>\n        <item>\n        <name>Name</name>\n          description &amp;&lt;foo>&amp;&lt;bar>\n        </item>\n        </list>\n        </root>\"\"\"\n        self.assertNormalizedXMLEqual(actual, expected)\n\n    def test_replace_structure(self):\n        root = self._parse(_SIMPLE_XML)\n        D = root.findmeld('description')\n        D.replace('description &<foo> <bar>', structure=True)\n        actual = self._write_xml(root)\n        expected = \"\"\"<?xml version=\"1.0\"?>\n        <root>\n        <list>\n        <item>\n        <name>Name</name>\n          description &<foo> <bar>\n        </item>\n        </list>\n        </root>\"\"\"\n        self.assertNormalizedXMLEqual(actual, expected)\n\n    def test_escape_cdata(self):\n        from supervisor.compat import as_bytes\n        from supervisor.templating import _escape_cdata\n        a = ('< > &lt;&amp; &&apos; && &foo \"\" '\n             'http://www.example.com?foo=bar&bang=baz &#123;')\n        self.assertEqual(\n            as_bytes('&lt; > &lt;&amp; &amp;&apos; &amp;&amp; &amp;foo \"\" '\n                     'http://www.example.com?foo=bar&amp;bang=baz &#123;',\n                     encoding='latin1'),\n            _escape_cdata(a))\n\n    def test_escape_cdata_unicodeerror(self):\n        from supervisor.templating import _escape_cdata\n        from supervisor.compat import as_bytes\n        from supervisor.compat import as_string\n        a = as_string(as_bytes('\\x80', encoding='latin1'), encoding='latin1')\n        self.assertEqual(as_bytes('&#128;', encoding='latin1'),\n                         _escape_cdata(a, 'ascii'))\n\n    def test_escape_attrib(self):\n        from supervisor.templating import _escape_attrib\n        from supervisor.compat import as_bytes\n        a = ('< > &lt;&amp; &&apos; && &foo \"\" '\n             'http://www.example.com?foo=bar&bang=baz &#123;')\n        self.assertEqual(\n            as_bytes('&lt; > &lt;&amp; &amp;&apos; '\n                     '&amp;&amp; &amp;foo &quot;&quot; '\n                     'http://www.example.com?foo=bar&amp;bang=baz &#123;',\n                     encoding='latin1'),\n            _escape_attrib(a, None))\n\n    def test_escape_attrib_unicodeerror(self):\n        from supervisor.templating import _escape_attrib\n        from supervisor.compat import as_bytes\n        from supervisor.compat import as_string\n        a = as_string(as_bytes('\\x80', encoding='latin1'), encoding='latin1')\n        self.assertEqual(as_bytes('&#128;', encoding='latin1'),\n                         _escape_attrib(a, 'ascii'))\n\ndef normalize_html(s):\n    s = re.sub(r\"[ \\t]+\", \" \", s)\n    s = re.sub(r\"/>\", \">\", s)\n    return s\n\ndef normalize_xml(s):\n    s = re.sub(r\"\\s+\", \" \", s)\n    s = re.sub(r\"(?s)\\s+<\", \"<\", s)\n    s = re.sub(r\"(?s)>\\s+\", \">\", s)\n    return s\n"
  },
  {
    "path": "supervisor/tests/test_web.py",
    "content": "import unittest\n\nfrom supervisor.tests.base import DummySupervisor\nfrom supervisor.tests.base import DummyRequest\n\nclass DeferredWebProducerTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.web import DeferredWebProducer\n        return DeferredWebProducer\n\n    def _makeOne(self, request, callback):\n        producer = self._getTargetClass()(request, callback)\n        return producer\n\n    def test_ctor(self):\n        request = DummyRequest('/index.html', [], '', '')\n        callback = lambda *x: None\n        callback.delay = 1\n        producer = self._makeOne(request, callback)\n        self.assertEqual(producer.callback, callback)\n        self.assertEqual(producer.request, request)\n        self.assertEqual(producer.finished, False)\n        self.assertEqual(producer.delay, 1)\n\n    def test_more_not_done_yet(self):\n        request = DummyRequest('/index.html', [], '', '')\n        from supervisor.http import NOT_DONE_YET\n        callback = lambda *x: NOT_DONE_YET\n        callback.delay = 1\n        producer = self._makeOne(request, callback)\n        self.assertEqual(producer.more(), NOT_DONE_YET)\n\n    def test_more_finished(self):\n        request = DummyRequest('/index.html', [], '', '')\n        callback = lambda *x: 'done'\n        callback.delay = 1\n        producer = self._makeOne(request, callback)\n        self.assertEqual(producer.more(), None)\n        self.assertTrue(producer.finished)\n        self.assertEqual(producer.more(), '')\n\n    def test_more_exception_caught(self):\n        request = DummyRequest('/index.html', [], '', '')\n        def callback(*arg):\n            raise ValueError('foo')\n        callback.delay = 1\n        producer = self._makeOne(request, callback)\n        self.assertEqual(producer.more(), None)\n        logdata = request.channel.server.logger.logged\n        self.assertEqual(len(logdata), 1)\n        logged = logdata[0]\n        self.assertEqual(logged[0], 'Web interface error')\n        self.assertTrue(logged[1].startswith('Traceback'), logged[1])\n        self.assertEqual(producer.finished, True)\n        self.assertEqual(request._error, 500)\n\n    def test_sendresponse_redirect(self):\n        request = DummyRequest('/index.html', [], '', '')\n        callback = lambda *arg: None\n        callback.delay = 1\n        producer = self._makeOne(request, callback)\n        response = {'headers': {'Location':'abc'}}\n        result = producer.sendresponse(response)\n        self.assertEqual(result, None)\n        self.assertEqual(request._error, 301)\n        self.assertEqual(request.headers['Content-Type'], 'text/plain')\n        self.assertEqual(request.headers['Content-Length'], 0)\n\n    def test_sendresponse_withbody_and_content_type(self):\n        request = DummyRequest('/index.html', [], '', '')\n        callback = lambda *arg: None\n        callback.delay = 1\n        producer = self._makeOne(request, callback)\n        response = {'body': 'abc', 'headers':{'Content-Type':'text/html'}}\n        result = producer.sendresponse(response)\n        self.assertEqual(result, None)\n        self.assertEqual(request.headers['Content-Type'], 'text/html')\n        self.assertEqual(request.headers['Content-Length'], 3)\n        self.assertEqual(request.producers[0], 'abc')\n\nclass UIHandlerTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.web import supervisor_ui_handler\n        return supervisor_ui_handler\n\n    def _makeOne(self):\n        supervisord = DummySupervisor()\n        handler = self._getTargetClass()(supervisord)\n        return handler\n\n    def test_handle_request_no_view_method(self):\n        request = DummyRequest('/foo.css', [], '', '', {'PATH_INFO':'/foo.css'})\n        handler = self._makeOne()\n        data = handler.handle_request(request)\n        self.assertEqual(data, None)\n\n    def test_handle_request_default(self):\n        request = DummyRequest('/index.html', [], '', '',\n                               {'PATH_INFO':'/index.html'})\n        handler = self._makeOne()\n        data = handler.handle_request(request)\n        self.assertEqual(data, None)\n        self.assertEqual(request.channel.producer.request, request)\n        from supervisor.web import StatusView\n        self.assertEqual(request.channel.producer.callback.__class__,StatusView)\n\n    def test_handle_request_index_html(self):\n        request = DummyRequest('/index.html', [], '', '',\n                               {'PATH_INFO':'/index.html'})\n        handler = self._makeOne()\n        handler.handle_request(request)\n        from supervisor.web import StatusView\n        view = request.channel.producer.callback\n        self.assertEqual(view.__class__, StatusView)\n        self.assertEqual(view.context.template, 'ui/status.html')\n\n    def test_handle_request_tail_html(self):\n        request = DummyRequest('/tail.html', [], '', '',\n                               {'PATH_INFO':'/tail.html'})\n        handler = self._makeOne()\n        handler.handle_request(request)\n        from supervisor.web import TailView\n        view = request.channel.producer.callback\n        self.assertEqual(view.__class__, TailView)\n        self.assertEqual(view.context.template, 'ui/tail.html')\n\n    def test_handle_request_ok_html(self):\n        request = DummyRequest('/tail.html', [], '', '',\n                               {'PATH_INFO':'/ok.html'})\n        handler = self._makeOne()\n        handler.handle_request(request)\n        from supervisor.web import OKView\n        view = request.channel.producer.callback\n        self.assertEqual(view.__class__, OKView)\n        self.assertEqual(view.context.template, None)\n\n\nclass StatusViewTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.web import StatusView\n        return StatusView\n\n    def _makeOne(self, context):\n        klass = self._getTargetClass()\n        return klass(context)\n\n    def test_make_callback_noaction(self):\n        context = DummyContext()\n        context.supervisord = DummySupervisor()\n        context.template = 'ui/status.html'\n        context.form = {}\n        view = self._makeOne(context)\n        self.assertRaises(ValueError, view.make_callback, 'process', None)\n\n    def test_render_noaction(self):\n        context = DummyContext()\n        context.supervisord = DummySupervisor()\n        context.template = 'ui/status.html'\n        context.request = DummyRequest('/foo', [], '', '')\n        context.form = {}\n        context.response = {}\n        view = self._makeOne(context)\n        data = view.render()\n        self.assertTrue(data.startswith('<!DOCTYPE html PUBLIC'), data)\n\n    def test_render_refresh(self):\n        context = DummyContext()\n        context.supervisord = DummySupervisor()\n        context.template = 'ui/status.html'\n        context.response = {}\n        context.form = {'action':'refresh'}\n        view = self._makeOne(context)\n        data = view.render()\n        from supervisor.http import NOT_DONE_YET\n        self.assertTrue(data is NOT_DONE_YET, data)\n\nclass DummyContext:\n    pass\n"
  },
  {
    "path": "supervisor/tests/test_xmlrpc.py",
    "content": "import unittest\n\nfrom supervisor.tests.base import DummySupervisor\nfrom supervisor.tests.base import DummyRequest\nfrom supervisor.tests.base import DummySupervisorRPCNamespace\n\nfrom supervisor.compat import xmlrpclib\nfrom supervisor.compat import httplib\n\nclass GetFaultDescriptionTests(unittest.TestCase):\n    def test_returns_description_for_known_fault(self):\n        from supervisor import xmlrpc\n        desc = xmlrpc.getFaultDescription(xmlrpc.Faults.SHUTDOWN_STATE)\n        self.assertEqual(desc, 'SHUTDOWN_STATE')\n\n    def test_returns_unknown_for_unknown_fault(self):\n        from supervisor import xmlrpc\n        desc = xmlrpc.getFaultDescription(999999)\n        self.assertEqual(desc, 'UNKNOWN')\n\nclass RPCErrorTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.xmlrpc import RPCError\n        return RPCError\n\n    def _makeOne(self, code, extra=None):\n        return self._getTargetClass()(code, extra)\n\n    def test_sets_text_with_fault_name_only(self):\n        from supervisor import xmlrpc\n        e = self._makeOne(xmlrpc.Faults.FAILED)\n        self.assertEqual(e.text, 'FAILED')\n\n    def test_sets_text_with_fault_name_and_extra(self):\n        from supervisor import xmlrpc\n        e = self._makeOne(xmlrpc.Faults.FAILED, 'oops')\n        self.assertEqual(e.text, 'FAILED: oops')\n\n    def test___str___shows_code_and_text(self):\n        from supervisor import xmlrpc\n        e = self._makeOne(xmlrpc.Faults.NO_FILE, '/nonexistent')\n        self.assertEqual(str(e),\n            \"code=%r, text='NO_FILE: /nonexistent'\" % xmlrpc.Faults.NO_FILE\n            )\n\nclass XMLRPCMarshallingTests(unittest.TestCase):\n    def test_xmlrpc_marshal(self):\n        from supervisor import xmlrpc\n        data = xmlrpc.xmlrpc_marshal(1)\n        self.assertEqual(data, xmlrpclib.dumps((1,), methodresponse=True))\n        fault = xmlrpclib.Fault(1, 'foo')\n        data = xmlrpc.xmlrpc_marshal(fault)\n        self.assertEqual(data, xmlrpclib.dumps(fault))\n\nclass XMLRPCHandlerTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.xmlrpc import supervisor_xmlrpc_handler\n        return supervisor_xmlrpc_handler\n\n    def _makeOne(self, supervisord, subinterfaces):\n        return self._getTargetClass()(supervisord, subinterfaces)\n\n    def test_ctor(self):\n        supervisor = DummySupervisor()\n        subinterfaces = [('supervisor', DummySupervisorRPCNamespace())]\n        handler = self._makeOne(supervisor, subinterfaces)\n        self.assertEqual(handler.supervisord, supervisor)\n        from supervisor.xmlrpc import RootRPCInterface\n        self.assertEqual(handler.rpcinterface.__class__, RootRPCInterface)\n\n    def test_match(self):\n        class DummyRequest2:\n            def __init__(self, uri):\n                self.uri = uri\n        supervisor = DummySupervisor()\n        subinterfaces = [('supervisor', DummySupervisorRPCNamespace())]\n        handler = self._makeOne(supervisor, subinterfaces)\n        self.assertEqual(handler.match(DummyRequest2('/RPC2')), True)\n        self.assertEqual(handler.match(DummyRequest2('/nope')), False)\n\n    def test_continue_request_nosuchmethod(self):\n        supervisor = DummySupervisor()\n        subinterfaces = [('supervisor', DummySupervisorRPCNamespace())]\n        handler = self._makeOne(supervisor, subinterfaces)\n        data = xmlrpclib.dumps(('a', 'b'), 'supervisor.noSuchMethod')\n        request = DummyRequest('/what/ever', None, None, None)\n        handler.continue_request(data, request)\n        logdata = supervisor.options.logger.data\n        self.assertEqual(len(logdata), 2)\n        self.assertEqual(logdata[-2],\n                         'XML-RPC method called: supervisor.noSuchMethod()')\n        self.assertEqual(logdata[-1],\n           ('XML-RPC method supervisor.noSuchMethod() returned fault: '\n            '[1] UNKNOWN_METHOD'))\n        self.assertEqual(len(request.producers), 1)\n        xml_response = request.producers[0]\n        self.assertRaises(xmlrpclib.Fault, xmlrpclib.loads, xml_response)\n\n    def test_continue_request_methodsuccess(self):\n        supervisor = DummySupervisor()\n        subinterfaces = [('supervisor', DummySupervisorRPCNamespace())]\n        handler = self._makeOne(supervisor, subinterfaces)\n        data = xmlrpclib.dumps((), 'supervisor.getAPIVersion')\n        request = DummyRequest('/what/ever', None, None, None)\n        handler.continue_request(data, request)\n        logdata = supervisor.options.logger.data\n        self.assertEqual(len(logdata), 2)\n        self.assertEqual(logdata[-2],\n               'XML-RPC method called: supervisor.getAPIVersion()')\n        self.assertEqual(logdata[-1],\n            'XML-RPC method supervisor.getAPIVersion() returned successfully')\n        self.assertEqual(len(request.producers), 1)\n        xml_response = request.producers[0]\n        response = xmlrpclib.loads(xml_response)\n        from supervisor.rpcinterface import API_VERSION\n        self.assertEqual(response[0][0], API_VERSION)\n        self.assertEqual(request._done, True)\n        self.assertEqual(request.headers['Content-Type'], 'text/xml')\n        self.assertEqual(request.headers['Content-Length'], len(xml_response))\n\n    def test_continue_request_no_params_in_request(self):\n        supervisor = DummySupervisor()\n        subinterfaces = [('supervisor', DummySupervisorRPCNamespace())]\n        handler = self._makeOne(supervisor, subinterfaces)\n        data = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>' \\\n               '<methodCall>' \\\n               '<methodName>supervisor.getAPIVersion</methodName>' \\\n               '</methodCall>'\n        request = DummyRequest('/what/ever', None, None, None)\n        handler.continue_request(data, request)\n        logdata = supervisor.options.logger.data\n        self.assertEqual(len(logdata), 2)\n        self.assertEqual(logdata[-2],\n               'XML-RPC method called: supervisor.getAPIVersion()')\n        self.assertEqual(logdata[-1],\n            'XML-RPC method supervisor.getAPIVersion() returned successfully')\n        self.assertEqual(len(request.producers), 1)\n        xml_response = request.producers[0]\n        response = xmlrpclib.loads(xml_response)\n        from supervisor.rpcinterface import API_VERSION\n        self.assertEqual(response[0][0], API_VERSION)\n        self.assertEqual(request._done, True)\n        self.assertEqual(request.headers['Content-Type'], 'text/xml')\n        self.assertEqual(request.headers['Content-Length'], len(xml_response))\n\n    def test_continue_request_400_if_method_name_is_empty(self):\n        supervisor = DummySupervisor()\n        subinterfaces = [('supervisor', DummySupervisorRPCNamespace())]\n        handler = self._makeOne(supervisor, subinterfaces)\n        data = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>' \\\n               '<methodCall><methodName></methodName></methodCall>'\n        request = DummyRequest('/what/ever', None, None, None)\n        handler.continue_request(data, request)\n        logdata = supervisor.options.logger.data\n        self.assertEqual(len(logdata), 1)\n        self.assertTrue(logdata[0].startswith('XML-RPC request data'))\n        self.assertTrue(repr(data) in logdata[0])\n        self.assertTrue(logdata[0].endswith('is invalid: no method name'))\n        self.assertEqual(request._error, 400)\n\n    def test_continue_request_400_if_loads_raises_not_xml(self):\n        supervisor = DummySupervisor()\n        subinterfaces = [('supervisor', DummySupervisorRPCNamespace())]\n        handler = self._makeOne(supervisor, subinterfaces)\n        data = 'this is not an xml-rpc request body'\n        request = DummyRequest('/what/ever', None, None, None)\n        handler.continue_request(data, request)\n        logdata = supervisor.options.logger.data\n        self.assertEqual(len(logdata), 1)\n        self.assertTrue(logdata[0].startswith('XML-RPC request data'))\n        self.assertTrue(repr(data) in logdata[0])\n        self.assertTrue(logdata[0].endswith('is invalid: unmarshallable'))\n        self.assertEqual(request._error, 400)\n\n    def test_continue_request_400_if_loads_raises_weird_xml(self):\n        supervisor = DummySupervisor()\n        subinterfaces = [('supervisor', DummySupervisorRPCNamespace())]\n        handler = self._makeOne(supervisor, subinterfaces)\n        data = '<methodName></methodName><junk></junk>'\n        request = DummyRequest('/what/ever', None, None, None)\n        handler.continue_request(data, request)\n        logdata = supervisor.options.logger.data\n        self.assertEqual(len(logdata), 1)\n        self.assertTrue(logdata[0].startswith('XML-RPC request data'))\n        self.assertTrue(repr(data) in logdata[0])\n        self.assertTrue(logdata[0].endswith('is invalid: unmarshallable'))\n        self.assertEqual(request._error, 400)\n\n    def test_continue_request_500_if_rpcinterface_method_call_raises(self):\n        supervisor = DummySupervisor()\n        subinterfaces = [('supervisor', DummySupervisorRPCNamespace())]\n        handler = self._makeOne(supervisor, subinterfaces)\n        data = xmlrpclib.dumps((), 'supervisor.raiseError')\n        request = DummyRequest('/what/ever', None, None, None)\n        handler.continue_request(data, request)\n        logdata = supervisor.options.logger.data\n        self.assertEqual(len(logdata), 2)\n        self.assertEqual(logdata[0],\n               'XML-RPC method called: supervisor.raiseError()')\n        self.assertTrue(\"unexpected exception\" in logdata[1])\n        self.assertTrue(repr(data) in logdata[1])\n        self.assertTrue(\"Traceback\" in logdata[1])\n        self.assertTrue(\"ValueError: error\" in logdata[1])\n        self.assertEqual(request._error, 500)\n\n    def test_continue_request_500_if_xmlrpc_dumps_raises(self):\n        supervisor = DummySupervisor()\n        subinterfaces = [('supervisor', DummySupervisorRPCNamespace())]\n        handler = self._makeOne(supervisor, subinterfaces)\n        data = xmlrpclib.dumps((), 'supervisor.getXmlRpcUnmarshallable')\n        request = DummyRequest('/what/ever', None, None, None)\n        handler.continue_request(data, request)\n        logdata = supervisor.options.logger.data\n        self.assertEqual(len(logdata), 3)\n        self.assertEqual(logdata[0],\n               'XML-RPC method called: supervisor.getXmlRpcUnmarshallable()')\n        self.assertEqual(logdata[1],\n               'XML-RPC method supervisor.getXmlRpcUnmarshallable() '\n               'returned successfully')\n        self.assertTrue(\"unexpected exception\" in logdata[2])\n        self.assertTrue(repr(data) in logdata[2])\n        self.assertTrue(\"Traceback\" in logdata[2])\n        self.assertTrue(\"TypeError: cannot marshal\" in logdata[2])\n        self.assertEqual(request._error, 500)\n\n    def test_continue_request_value_is_function(self):\n        class DummyRPCNamespace(object):\n            def foo(self):\n                def inner(self):\n                    return 1\n                inner.delay = .05\n                return inner\n        supervisor = DummySupervisor()\n        subinterfaces = [('supervisor', DummySupervisorRPCNamespace()),\n                          ('ns1', DummyRPCNamespace())]\n        handler = self._makeOne(supervisor, subinterfaces)\n        data = xmlrpclib.dumps((), 'ns1.foo')\n        request = DummyRequest('/what/ever', None, None, None)\n        handler.continue_request(data, request)\n        logdata = supervisor.options.logger.data\n        self.assertEqual(len(logdata), 2)\n        self.assertEqual(logdata[-2],\n               'XML-RPC method called: ns1.foo()')\n        self.assertEqual(logdata[-1],\n            'XML-RPC method ns1.foo() returned successfully')\n        self.assertEqual(len(request.producers), 0)\n        self.assertEqual(request._done, False)\n\n    def test_iterparse_loads_methodcall(self):\n        s = \"\"\"<?xml version=\"1.0\"?>\n        <methodCall>\n        <methodName>examples.getStateName</methodName>\n        <params>\n        <param>\n        <value><i4>41</i4></value>\n        </param>\n        <param>\n        <value><string>foo</string></value>\n        </param>\n        <param>\n        <value><string></string></value>\n        </param>\n        <param>\n        <!-- xml-rpc spec allows strings without <string> tag -->\n        <value>bar</value>\n        </param>\n        <param>\n        <value></value>\n        </param>\n        <param>\n        <value><boolean>1</boolean></value>\n        </param>\n        <param>\n        <value><double>-12.214</double></value>\n        </param>\n        <param>\n        <value>\n        <dateTime.iso8601>19980717T14:08:55</dateTime.iso8601>\n        </value>\n        </param>\n        <param>\n        <value><base64>eW91IGNhbid0IHJlYWQgdGhpcyE=</base64></value>\n        </param>\n        <param>\n        <struct>\n        <member><name>j</name><value><i4>5</i4></value></member>\n        <member><name>k</name><value>abc</value></member>\n        </struct>\n        </param>\n        <param>\n        <array>\n          <data>\n            <value><i4>12</i4></value>\n            <value><string>abc</string></value>\n            <value>def</value>\n            <value><i4>34</i4></value>\n          </data>\n        </array>\n        </param>\n        <param>\n        <struct>\n          <member>\n            <name>k</name>\n            <value><array><data>\n              <value><i4>1</i4></value>\n              <struct></struct>\n            </data></array></value>\n          </member>\n        </struct>\n        </param>\n        </params>\n        </methodCall>\n        \"\"\"\n        supervisor = DummySupervisor()\n        subinterfaces = [('supervisor', DummySupervisorRPCNamespace())]\n        handler = self._makeOne(supervisor, subinterfaces)\n        result = handler.loads(s)\n        params, method = result\n        import datetime\n        self.assertEqual(method, 'examples.getStateName')\n        self.assertEqual(params[0], 41)\n        self.assertEqual(params[1], 'foo')\n        self.assertEqual(params[2], '')\n        self.assertEqual(params[3], 'bar')\n        self.assertEqual(params[4], '')\n        self.assertEqual(params[5], True)\n        self.assertEqual(params[6], -12.214)\n        self.assertEqual(params[7], datetime.datetime(1998, 7, 17, 14, 8, 55))\n        self.assertEqual(params[8], \"you can't read this!\")\n        self.assertEqual(params[9], {'j': 5, 'k': 'abc'})\n        self.assertEqual(params[10], [12, 'abc', 'def', 34])\n        self.assertEqual(params[11], {'k': [1, {}]})\n\nclass TraverseTests(unittest.TestCase):\n    def test_security_disallows_underscore_methods(self):\n        from supervisor import xmlrpc\n        class Root:\n            pass\n        class A:\n            def _danger(self):\n                return True\n        root = Root()\n        root.a = A()\n        self.assertRaises(xmlrpc.RPCError, xmlrpc.traverse,\n            root, 'a._danger', [])\n\n    def test_security_disallows_object_traversal(self):\n        from supervisor import xmlrpc\n        class Root:\n            pass\n        class A:\n            pass\n        class B:\n            def danger(self):\n                return True\n        root = Root()\n        root.a = A()\n        root.a.b = B()\n        self.assertRaises(xmlrpc.RPCError, xmlrpc.traverse,\n            root, 'a.b.danger', [])\n\n    def test_namespace_name_not_found(self):\n        from supervisor import xmlrpc\n        class Root:\n            pass\n        root = Root()\n        self.assertRaises(xmlrpc.RPCError, xmlrpc.traverse,\n            root, 'notfound.hello', None)\n\n    def test_method_name_not_found(self):\n        from supervisor import xmlrpc\n        class Root:\n            pass\n        class A:\n            pass\n        root = Root()\n        root.a = A()\n        self.assertRaises(xmlrpc.RPCError, xmlrpc.traverse,\n            root, 'a.notfound', [])\n\n    def test_method_name_exists_but_is_not_a_method(self):\n        from supervisor import xmlrpc\n        class Root:\n            pass\n        class A:\n            pass\n        class B:\n            pass\n        root = Root()\n        root.a = A()\n        root.a.b = B()\n        self.assertRaises(xmlrpc.RPCError, xmlrpc.traverse,\n            root, 'a.b', [])  # b is not a method\n\n    def test_bad_params(self):\n        from supervisor import xmlrpc\n        class Root:\n            pass\n        class A:\n            def hello(self, name):\n                return \"Hello %s\" % name\n        root = Root()\n        root.a = A()\n        self.assertRaises(xmlrpc.RPCError, xmlrpc.traverse,\n            root, 'a.hello', [\"there\", \"extra\"])  # too many params\n\n    def test_success(self):\n        from supervisor import xmlrpc\n        class Root:\n            pass\n        class A:\n            def hello(self, name):\n                return \"Hello %s\" % name\n        root = Root()\n        root.a = A()\n        result = xmlrpc.traverse(root, 'a.hello', [\"there\"])\n        self.assertEqual(result, \"Hello there\")\n\nclass SupervisorTransportTests(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.xmlrpc import SupervisorTransport\n        return SupervisorTransport\n\n    def _makeOne(self, *arg, **kw):\n        return self._getTargetClass()(*arg, **kw)\n\n    def test_ctor_unix(self):\n        from supervisor import xmlrpc\n        transport = self._makeOne('user', 'pass', 'unix:///foo/bar')\n        conn = transport._get_connection()\n        self.assertTrue(isinstance(conn, xmlrpc.UnixStreamHTTPConnection))\n        self.assertEqual(conn.host, 'localhost')\n        self.assertEqual(conn.socketfile, '/foo/bar')\n\n    def test_ctor_unknown(self):\n        self.assertRaises(ValueError,\n            self._makeOne, 'user', 'pass', 'unknown:///foo/bar'\n            )\n\n    def test__get_connection_http_9001(self):\n        transport = self._makeOne('user', 'pass', 'http://127.0.0.1:9001/')\n        conn = transport._get_connection()\n        self.assertTrue(isinstance(conn, httplib.HTTPConnection))\n        self.assertEqual(conn.host, '127.0.0.1')\n        self.assertEqual(conn.port, 9001)\n\n    def test__get_connection_http_80(self):\n        transport = self._makeOne('user', 'pass', 'http://127.0.0.1/')\n        conn = transport._get_connection()\n        self.assertTrue(isinstance(conn, httplib.HTTPConnection))\n        self.assertEqual(conn.host, '127.0.0.1')\n        self.assertEqual(conn.port, 80)\n\n    def test_request_non_200_response(self):\n        transport = self._makeOne('user', 'pass', 'http://127.0.0.1/')\n        dummy_conn = DummyConnection(400, '')\n        def getconn():\n            return dummy_conn\n        transport._get_connection = getconn\n        self.assertRaises(xmlrpclib.ProtocolError,\n                          transport.request, 'localhost', '/', '')\n        self.assertEqual(transport.connection, None)\n        self.assertEqual(dummy_conn.closed, True)\n\n    def test_request_400_response(self):\n        transport = self._makeOne('user', 'pass', 'http://127.0.0.1/')\n        dummy_conn = DummyConnection(400, '')\n        def getconn():\n            return dummy_conn\n        transport._get_connection = getconn\n        self.assertRaises(xmlrpclib.ProtocolError,\n                          transport.request, 'localhost', '/', '')\n        self.assertEqual(transport.connection, None)\n        self.assertEqual(dummy_conn.closed, True)\n        self.assertEqual(dummy_conn.requestargs[0], 'POST')\n        self.assertEqual(dummy_conn.requestargs[1], '/')\n        self.assertEqual(dummy_conn.requestargs[2], b'')\n        self.assertEqual(dummy_conn.requestargs[3]['Content-Length'], '0')\n        self.assertEqual(dummy_conn.requestargs[3]['Content-Type'], 'text/xml')\n        self.assertEqual(dummy_conn.requestargs[3]['Authorization'],\n                         'Basic dXNlcjpwYXNz')\n        self.assertEqual(dummy_conn.requestargs[3]['Accept'], 'text/xml')\n\n    def test_request_200_response(self):\n        transport = self._makeOne('user', 'pass', 'http://127.0.0.1/')\n        response = \"\"\"<?xml version=\"1.0\"?>\n        <methodResponse>\n        <params>\n        <param>\n        <value><string>South Dakota</string></value>\n        </param>\n        </params>\n        </methodResponse>\"\"\"\n        dummy_conn = DummyConnection(200, response)\n        def getconn():\n            return dummy_conn\n        transport._get_connection = getconn\n        result = transport.request('localhost', '/', '')\n        self.assertEqual(transport.connection, dummy_conn)\n        self.assertEqual(dummy_conn.closed, False)\n        self.assertEqual(dummy_conn.requestargs[0], 'POST')\n        self.assertEqual(dummy_conn.requestargs[1], '/')\n        self.assertEqual(dummy_conn.requestargs[2], b'')\n        self.assertEqual(dummy_conn.requestargs[3]['Content-Length'], '0')\n        self.assertEqual(dummy_conn.requestargs[3]['Content-Type'], 'text/xml')\n        self.assertEqual(dummy_conn.requestargs[3]['Authorization'],\n                         'Basic dXNlcjpwYXNz')\n        self.assertEqual(dummy_conn.requestargs[3]['Accept'], 'text/xml')\n        self.assertEqual(result, ('South Dakota',))\n\n    def test_close(self):\n        transport = self._makeOne('user', 'pass', 'http://127.0.0.1/')\n        dummy_conn = DummyConnection(200, '''<?xml version=\"1.0\"?>\n        <methodResponse><params/></methodResponse>''')\n        def getconn():\n            return dummy_conn\n        transport._get_connection = getconn\n        transport.request('localhost', '/', '')\n        transport.close()\n        self.assertTrue(dummy_conn.closed)\n\nclass TestDeferredXMLRPCResponse(unittest.TestCase):\n    def _getTargetClass(self):\n        from supervisor.xmlrpc import DeferredXMLRPCResponse\n        return DeferredXMLRPCResponse\n\n    def _makeOne(self, request=None, callback=None):\n        if request is None:\n            request = DummyRequest(None, None, None, None, None)\n        if callback is None:\n            callback = Dummy()\n            callback.delay = 1\n        return self._getTargetClass()(request, callback)\n\n    def test_ctor(self):\n        callback = Dummy()\n        callback.delay = 1\n        inst = self._makeOne(request='request', callback=callback)\n        self.assertEqual(inst.callback, callback)\n        self.assertEqual(inst.delay, 1.0)\n        self.assertEqual(inst.request, 'request')\n        self.assertEqual(inst.finished, False)\n\n    def test_more_finished(self):\n        inst = self._makeOne()\n        inst.finished = True\n        result = inst.more()\n        self.assertEqual(result,  '')\n\n    def test_more_callback_returns_not_done_yet(self):\n        from supervisor.http import NOT_DONE_YET\n        def callback():\n            return NOT_DONE_YET\n        callback.delay = 1\n        inst = self._makeOne(callback=callback)\n        self.assertEqual(inst.more(), NOT_DONE_YET)\n\n    def test_more_callback_raises_RPCError(self):\n        from supervisor.xmlrpc import RPCError, Faults\n        def callback():\n            raise RPCError(Faults.UNKNOWN_METHOD)\n        callback.delay = 1\n        inst = self._makeOne(callback=callback)\n        self.assertEqual(inst.more(), None)\n        self.assertEqual(len(inst.request.producers), 1)\n        self.assertTrue('UNKNOWN_METHOD' in inst.request.producers[0])\n        self.assertTrue(inst.finished)\n\n    def test_more_callback_returns_value(self):\n        def callback():\n            return 'abc'\n        callback.delay = 1\n        inst = self._makeOne(callback=callback)\n        self.assertEqual(inst.more(), None)\n        self.assertEqual(len(inst.request.producers), 1)\n        self.assertTrue('abc' in inst.request.producers[0])\n        self.assertTrue(inst.finished)\n\n    def test_more_callback_raises_unexpected_exception(self):\n        def callback():\n            raise ValueError('foo')\n        callback.delay = 1\n        inst = self._makeOne(callback=callback)\n        self.assertEqual(inst.more(), None)\n        self.assertEqual(inst.request._error, 500)\n        self.assertTrue(inst.finished)\n        logged = inst.request.channel.server.logger.logged\n        self.assertEqual(len(logged), 1)\n        src, msg = logged[0]\n        self.assertEqual(src, 'XML-RPC response callback error')\n        self.assertTrue(\"Traceback\" in msg)\n\n    def test_getresponse_http_10_with_keepalive(self):\n        inst = self._makeOne()\n        inst.request.version = '1.0'\n        inst.request.header.append('Connection: keep-alive')\n        inst.getresponse('abc')\n        self.assertEqual(len(inst.request.producers), 1)\n        self.assertEqual(inst.request.headers['Connection'], 'Keep-Alive')\n\n    def test_getresponse_http_10_no_keepalive(self):\n        inst = self._makeOne()\n        inst.request.version = '1.0'\n        inst.getresponse('abc')\n        self.assertEqual(len(inst.request.producers), 1)\n        self.assertEqual(inst.request.headers['Connection'], 'close')\n\n    def test_getresponse_http_11_without_close(self):\n        inst = self._makeOne()\n        inst.request.version = '1.1'\n        inst.getresponse('abc')\n        self.assertEqual(len(inst.request.producers), 1)\n        self.assertTrue('Connection' not in inst.request.headers)\n\n    def test_getresponse_http_11_with_close(self):\n        inst = self._makeOne()\n        inst.request.header.append('Connection: close')\n        inst.request.version = '1.1'\n        inst.getresponse('abc')\n        self.assertEqual(len(inst.request.producers), 1)\n        self.assertEqual(inst.request.headers['Connection'], 'close')\n\n    def test_getresponse_http_unknown(self):\n        inst = self._makeOne()\n        inst.request.version = None\n        inst.getresponse('abc')\n        self.assertEqual(len(inst.request.producers), 1)\n        self.assertEqual(inst.request.headers['Connection'], 'close')\n\nclass TestSystemNamespaceRPCInterface(unittest.TestCase):\n    def _makeOne(self, namespaces=()):\n        from supervisor.xmlrpc import SystemNamespaceRPCInterface\n        return SystemNamespaceRPCInterface(namespaces)\n\n    def test_listMethods_gardenpath(self):\n        inst = self._makeOne()\n        result = inst.listMethods()\n        self.assertEqual(\n            result,\n            ['system.listMethods',\n             'system.methodHelp',\n             'system.methodSignature',\n             'system.multicall',\n             ]\n            )\n\n    def test_listMethods_omits_underscore_attrs(self):\n        class DummyNamespace(object):\n            def foo(self): pass\n            def _bar(self): pass\n        ns1 = DummyNamespace()\n        inst = self._makeOne([('ns1', ns1)])\n        result = inst.listMethods()\n        self.assertEqual(\n            result,\n            ['ns1.foo',\n             'system.listMethods',\n             'system.methodHelp',\n             'system.methodSignature',\n             'system.multicall'\n             ]\n            )\n\n    def test_methodHelp_known_method(self):\n        inst = self._makeOne()\n        result = inst.methodHelp('system.listMethods')\n        self.assertTrue('array' in result)\n\n    def test_methodHelp_unknown_method(self):\n        from supervisor.xmlrpc import RPCError\n        inst = self._makeOne()\n        self.assertRaises(RPCError, inst.methodHelp, 'wont.be.found')\n\n    def test_methodSignature_known_method(self):\n        inst = self._makeOne()\n        result = inst.methodSignature('system.methodSignature')\n        self.assertEqual(result, ['array', 'string'])\n\n    def test_methodSignature_unknown_method(self):\n        from supervisor.xmlrpc import RPCError\n        inst = self._makeOne()\n        self.assertRaises(RPCError, inst.methodSignature, 'wont.be.found')\n\n    def test_methodSignature_with_bad_sig(self):\n        from supervisor.xmlrpc import RPCError\n        class DummyNamespace(object):\n            def foo(self):\n                \"\"\" @param string name The thing\"\"\"\n        ns1 = DummyNamespace()\n        inst = self._makeOne([('ns1', ns1)])\n        self.assertRaises(RPCError, inst.methodSignature, 'ns1.foo')\n\n    def test_multicall_faults_for_recursion(self):\n        from supervisor.xmlrpc import Faults\n        inst = self._makeOne()\n        calls = [{'methodName':'system.multicall'}]\n        results = inst.multicall(calls)\n        self.assertEqual(\n            results,\n            [{'faultCode': Faults.INCORRECT_PARAMETERS,\n              'faultString': ('INCORRECT_PARAMETERS: Recursive '\n                              'system.multicall forbidden')}]\n            )\n\n    def test_multicall_faults_for_missing_methodName(self):\n        from supervisor.xmlrpc import Faults\n        inst = self._makeOne()\n        calls = [{}]\n        results = inst.multicall(calls)\n        self.assertEqual(\n            results,\n            [{'faultCode': Faults.INCORRECT_PARAMETERS,\n              'faultString': 'INCORRECT_PARAMETERS: No methodName'}]\n            )\n\n    def test_multicall_faults_for_methodName_bad_namespace(self):\n        from supervisor.xmlrpc import Faults\n        inst = self._makeOne()\n        calls = [{'methodName': 'bad.stopProcess'}]\n        results = inst.multicall(calls)\n        self.assertEqual(\n            results,\n            [{'faultCode': Faults.UNKNOWN_METHOD,\n              'faultString': 'UNKNOWN_METHOD'}]\n            )\n\n    def test_multicall_faults_for_methodName_good_ns_bad_method(self):\n        from supervisor.xmlrpc import Faults\n        class DummyNamespace(object):\n            pass\n        ns1 = DummyNamespace()\n        inst = self._makeOne([('ns1', ns1)])\n        calls = [{'methodName': 'ns1.bad'}]\n        results = inst.multicall(calls)\n        self.assertEqual(\n            results,\n            [{'faultCode': Faults.UNKNOWN_METHOD,\n              'faultString': 'UNKNOWN_METHOD'}]\n            )\n\n    def test_multicall_returns_empty_results_for_empty_calls(self):\n        inst = self._makeOne()\n        calls = []\n        results = inst.multicall(calls)\n        self.assertEqual(results, [])\n\n    def test_multicall_performs_noncallback_functions_serially(self):\n        class DummyNamespace(object):\n            def say(self, name):\n                \"\"\" @param string name Process name\"\"\"\n                return name\n        ns1 = DummyNamespace()\n        inst = self._makeOne([('ns1', ns1)])\n        calls = [\n            {'methodName': 'ns1.say', 'params': ['Alvin']},\n            {'methodName': 'ns1.say', 'params': ['Simon']},\n            {'methodName': 'ns1.say', 'params': ['Theodore']}\n        ]\n        results = inst.multicall(calls)\n        self.assertEqual(results, ['Alvin', 'Simon', 'Theodore'])\n\n    def test_multicall_catches_noncallback_exceptions(self):\n        import errno\n        from supervisor.xmlrpc import RPCError, Faults\n        class DummyNamespace(object):\n            def bad_name(self):\n                raise RPCError(Faults.BAD_NAME, 'foo')\n            def os_error(self):\n                raise OSError(errno.ENOENT)\n        ns1 = DummyNamespace()\n        inst = self._makeOne([('ns1', ns1)])\n        calls = [{'methodName': 'ns1.bad_name'}, {'methodName': 'ns1.os_error'}]\n        results = inst.multicall(calls)\n\n        bad_name = {'faultCode': Faults.BAD_NAME,\n                    'faultString': 'BAD_NAME: foo'}\n        os_error = {'faultCode': Faults.FAILED,\n                    'faultString': \"FAILED: %s:2\" % OSError}\n        self.assertEqual(results, [bad_name, os_error])\n\n    def test_multicall_catches_callback_exceptions(self):\n        import errno\n        from supervisor.xmlrpc import RPCError, Faults\n        from supervisor.http import NOT_DONE_YET\n        class DummyNamespace(object):\n            def bad_name(self):\n                def inner():\n                    raise RPCError(Faults.BAD_NAME, 'foo')\n                return inner\n            def os_error(self):\n                def inner():\n                    raise OSError(errno.ENOENT)\n                return inner\n        ns1 = DummyNamespace()\n        inst = self._makeOne([('ns1', ns1)])\n        calls = [{'methodName': 'ns1.bad_name'}, {'methodName': 'ns1.os_error'}]\n        callback = inst.multicall(calls)\n        results = NOT_DONE_YET\n        while results is NOT_DONE_YET:\n            results = callback()\n\n        bad_name = {'faultCode': Faults.BAD_NAME,\n                    'faultString': 'BAD_NAME: foo'}\n        os_error = {'faultCode': Faults.FAILED,\n                    'faultString': \"FAILED: %s:2\" % OSError}\n        self.assertEqual(results, [bad_name, os_error])\n\n    def test_multicall_performs_callback_functions_serially(self):\n        from supervisor.http import NOT_DONE_YET\n        class DummyNamespace(object):\n            def __init__(self):\n                self.stop_results = [NOT_DONE_YET, NOT_DONE_YET,\n                    NOT_DONE_YET, 'stop result']\n                self.start_results = ['start result']\n            def stopProcess(self, name):\n                def inner():\n                    result = self.stop_results.pop(0)\n                    if result is not NOT_DONE_YET:\n                        self.stopped = True\n                    return result\n                return inner\n            def startProcess(self, name):\n                def inner():\n                    if not self.stopped:\n                        raise Exception(\"This should not raise\")\n                    return self.start_results.pop(0)\n                return inner\n        ns1 = DummyNamespace()\n        inst = self._makeOne([('ns1', ns1)])\n        calls = [{'methodName': 'ns1.stopProcess',\n                  'params': {'name': 'foo'}},\n                 {'methodName': 'ns1.startProcess',\n                  'params': {'name': 'foo'}}]\n        callback = inst.multicall(calls)\n        results = NOT_DONE_YET\n        while results is NOT_DONE_YET:\n            results = callback()\n        self.assertEqual(results, ['stop result', 'start result'])\n\nclass Test_gettags(unittest.TestCase):\n    def _callFUT(self, comment):\n        from supervisor.xmlrpc import gettags\n        return gettags(comment)\n\n    def test_one_atpart(self):\n        lines = '@foo'\n        result = self._callFUT(lines)\n        self.assertEqual(\n            result,\n            [(0, None, None, None, ''), (0, 'foo', '', '', '')]\n            )\n\n    def test_two_atparts(self):\n        lines = '@foo array'\n        result = self._callFUT(lines)\n        self.assertEqual(\n            result,\n            [(0, None, None, None, ''), (0, 'foo', 'array', '', '')]\n            )\n\n    def test_three_atparts(self):\n        lines = '@foo array name'\n        result = self._callFUT(lines)\n        self.assertEqual(\n            result,\n            [(0, None, None, None, ''), (0, 'foo', 'array', 'name', '')]\n            )\n\n    def test_four_atparts(self):\n        lines = '@foo array name text'\n        result = self._callFUT(lines)\n        self.assertEqual(\n            result,\n            [(0, None, None, None, ''), (0, 'foo', 'array', 'name', 'text')]\n            )\n\nclass Test_capped_int(unittest.TestCase):\n    def _callFUT(self, value):\n        from supervisor.xmlrpc import capped_int\n        return capped_int(value)\n\n    def test_converts_value_to_integer(self):\n        self.assertEqual(self._callFUT('42'), 42)\n\n    def test_caps_value_below_minint(self):\n        from supervisor.compat import xmlrpclib\n        self.assertEqual(self._callFUT(xmlrpclib.MININT - 1), xmlrpclib.MININT)\n\n    def test_caps_value_above_maxint(self):\n        from supervisor.compat import xmlrpclib\n        self.assertEqual(self._callFUT(xmlrpclib.MAXINT + 1), xmlrpclib.MAXINT)\n\n\nclass DummyResponse:\n    def __init__(self, status=200, body='', reason='reason'):\n        self.status = status\n        self.body = body\n        self.reason = reason\n\n    def read(self):\n        return self.body\n\nclass Dummy(object):\n    pass\n\nclass DummyConnection:\n    closed = False\n    def __init__(self, status=200, body='', reason='reason'):\n        self.response = DummyResponse(status, body, reason)\n\n    def getresponse(self):\n        return self.response\n\n    def request(self, *arg, **kw):\n        self.requestargs = arg\n        self.requestkw = kw\n\n    def close(self):\n        self.closed = True\n"
  },
  {
    "path": "supervisor/ui/status.html",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n   \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\"\n      xmlns:meld=\"https://github.com/Supervisor/supervisor\">\n<head>\n  <title>Supervisor Status</title>\n  <link href=\"stylesheets/supervisor.css\" rel=\"stylesheet\" type=\"text/css\" />\n  <link href=\"images/icon.png\" rel=\"icon\" type=\"image/png\" />\n</head>\n<body>\n<div id=\"wrapper\">\n\n  <div id=\"header\">\n    <img src=\"images/supervisor.gif\" alt=\"Supervisor status\" />\n  </div>\n\n  <div meld:id=\"content\">\n    <div class=\"hidden\" meld:id=\"statusmessage\">#</div>\n\n    <form meld:id=\"form\" action=\"index.html\" method=\"post\">\n      <ul id=\"buttons\" class=\"clr\">\n        <li class=\"action-button\"><a href=\"index.html?action=refresh\">Refresh</a></li>\n        <li class=\"action-button\"><a href=\"index.html?action=restartall\">Restart All</a></li>\n        <li class=\"action-button\"><a href=\"index.html?action=stopall\">Stop All</a></li>\n      </ul>\n\n      <table cellspacing=\"0\" meld:id=\"statustable\">\n        <thead>\n        <tr>\n          <th class=\"state\">State</th>\n          <th class=\"desc\">Description</th>\n          <th class=\"name\">Name</th>\n          <th class=\"action\">Action</th>\n        </tr>\n        </thead>\n\n        <tbody meld:id=\"tbody\">\n          <tr meld:id=\"tr\" class=\"\">\n            <td meld:id=\"status_td\" class=\"status\"><span meld:id=\"status_text\" class=\"statusnominal\">nominal</span></td>\n            <td meld:id=\"info_td\"><span meld:id=\"info_text\">Info</span></td>\n            <td meld:id=\"name_td\"><a href=\"#\" meld:id=\"name_anchor\" target=\"_blank\">Name</a></td>\n            <td meld:id=\"action_td\" class=\"action\">\n              <ul>\n                <li meld:id=\"actionitem_td\">\n                  <a href=\"#\" name=\"#\" meld:id=\"actionitem_anchor\">Action</a>\n                </li>\n              </ul>\n            </td>\n          </tr>\n        </tbody>\n      </table>\n    </form>\n\n  </div>\n\n  <div class=\"push\">\n  </div>\n</div>\n\n<div id=\"footer\" class=\"clr\">\n  <div class=\"left\">\n    <a href=\"http://supervisord.org\">Supervisor</a> <span meld:id=\"supervisor_version\">#</span>\n  </div>\n  <div class=\"right\">\n    &amp;copy; 2006-<span meld:id=\"copyright_date\">#</span> <strong><a href=\"http://agendaless.com/\">Agendaless Consulting and Contributors</a></strong>\n  </div>\n</div>\n</body>\n</html>\n"
  },
  {
    "path": "supervisor/ui/stylesheets/supervisor.css",
    "content": "/* =ORDER\n 1. display\n 2. float and position\n 3. width and height\n 4. Specific element properties\n 5. margin\n 6. border\n 7. padding\n 8. background\n 9. color\n10. font related properties\n----------------------------------------------- */\n\n/* =MAIN\n----------------------------------------------- */\nbody, td, input, select, textarea, a {\n  font: 12px/1.5em arial, helvetica, verdana, sans-serif;\n  color: #333;\n}\nhtml, body, form, fieldset, h1, h2, h3, h4, h5, h6,\np, pre, blockquote, ul, ol, dl, address {\n  margin: 0;\n  padding: 0;\n}\nform label {\n  cursor: pointer;\n}\nfieldset {\n  border: none;\n}\nimg, table {\n  border-width: 0;\n}\n\n/* =COLORS\n----------------------------------------------- */\nbody {\n  background-color: #FFFFF3;\n  color: #333;\n}\na:link,\na:visited {\n  color: #333;\n}\na:hover {\n  color: #000;\n}\n\n/* =FLOATS\n----------------------------------------------- */\n.left {\n  float: left;\n}\n.right {\n\ttext-align: right;\n\tfloat: right;\n}\n/* clear float */\n.clr:after {\n  content: \".\";\n  display: block;\n  height: 0;\n  clear: both;\n  visibility: hidden;\n}\n.clr {display: inline-block;}\n/* Hides from IE-mac \\*/\n* html .clr {height: 1%;}\n.clr {display: block;}\n/* End hide from IE-mac */\n\n/* =LAYOUT\n----------------------------------------------- */\nhtml, body {\n  height: 100%;\n}\n#wrapper {\n  min-height: 100%;\n  height: auto !important;\n  height: 100%;\n  width: 850px;\n  margin: 0 auto -31px;\n}\n#footer,\n.push {\n  height: 30px;\n}\n\n.hidden {\n  display: none;\n}\n\n/* =STATUS\n----------------------------------------------- */\n#header {\n  margin-bottom: 13px;\n  padding: 10px 0 13px 0;\n  background: url(\"../images/rule.gif\") left bottom repeat-x;\n}\n.status_msg {\n  padding: 5px 10px;\n  border: 1px solid #919191;\n  background-color: #FBFBFB;\n  color: #000000;\n}\n\n#buttons {\n  margin: 13px 0;\n}\n#buttons li {\n  float: left;\n  display: block;\n  margin: 0 7px 0 0;\n}\n#buttons a {\n  float: left;\n  display: block;\n  padding: 1px 0 0 0;\n}\n#buttons a, #buttons a:link {\n  text-decoration: none;\n}\n\n.action-button {\n  border: 1px solid #919191;\n  text-transform: uppercase;\n  padding: 0 5px;\n  border-radius: 4px;\n  color: #50504d;\n  font-size: 12px;\n  background: #fbfbfb;\n  font-weight: 600;\n}\n\n.action-button:hover {\n  border: 1px solid #88b0f2;\n  background: #ffffff;\n}\n\ntable {\n  width: 100%;\n  border: 1px solid #919191;\n}\nth {\n  background-color: #919191;\n  color: #fff;\n  text-align: left;\n}\nth.state {\n  text-align: center;\n  width: 44px;\n}\nth.desc {\n  width: 200px;\n}\nth.name {\n  width: 200px;\n}\nth.action {\n}\ntd, th {\n  padding: 4px 8px;\n  border-bottom: 1px solid #fff;\n}\ntr td {\n  background-color: #FBFBFB;\n}\ntr.shade td {\n  background-color: #F0F0F0;\n}\n.action ul {\n  list-style: none;\n  display: inline;\n}\n.action li {\n  margin-right: 10px;\n  display: inline;\n}\n\n/* status message */\n.status span {\n  display: block;\n  width: 60px;\n  height: 16px;\n  border: 1px solid #fff;\n  text-align: center;\n  font-size: 95%;\n  line-height: 1.4em;\n}\n.statusnominal {\n  background-image: url(\"../images/state0.gif\");\n}\n.statusrunning {\n  background-image: url(\"../images/state2.gif\");\n}\n.statuserror {\n  background-image: url(\"../images/state3.gif\");\n}\n\n#footer {\n  width: 760px;\n  margin: 0 auto;\n  padding: 0 10px;\n  line-height: 30px;\n  border: 1px solid #C8C8C2;\n  border-bottom-width: 0;\n  background-color: #FBFBFB;\n  overflow: hidden;\n  opacity: 0.7;\n  color: #000;\n  font-size: 95%;\n}\n#footer a {\n  font-size: inherit;\n}\n"
  },
  {
    "path": "supervisor/ui/tail.html",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n   \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\"\n      xmlns:meld=\"https://github.com/Supervisor/supervisor\">\n<head>\n  <title meld:id=\"title\">Supervisor Status</title>\n</head>\n<body>\n\n<div meld:id=\"content\">\n\n<pre meld:id=\"tailbody\"></pre>\n\n<form meld:id=\"form\" action=\"index.html\" method=\"POST\">\n\n<div style=\"margin-top: 10px;\">\n  <a href=\"tail.html?processname=thisprocess\" meld:id=\"refresh_anchor\">\n    <input type=\"button\" value=\" Refresh \" name=\"refresh\" />\n  </a>\n</div>\n\n</form>\n\n</div>\n\n</body>\n</html>\n"
  },
  {
    "path": "supervisor/version.txt",
    "content": "4.4.0.dev0\n"
  },
  {
    "path": "supervisor/web.py",
    "content": "import os\nimport re\nimport time\nimport traceback\nimport datetime\n\nfrom supervisor import templating\n\nfrom supervisor.compat import urllib\nfrom supervisor.compat import urlparse\nfrom supervisor.compat import as_bytes\nfrom supervisor.compat import as_string\nfrom supervisor.compat import PY2\nfrom supervisor.compat import unicode\n\nfrom supervisor.medusa import producers\nfrom supervisor.medusa.http_server import http_date\nfrom supervisor.medusa.http_server import get_header\nfrom supervisor.medusa.xmlrpc_handler import collector\n\nfrom supervisor.process import ProcessStates\nfrom supervisor.http import NOT_DONE_YET\n\nfrom supervisor.options import VERSION\nfrom supervisor.options import make_namespec\nfrom supervisor.options import split_namespec\n\nfrom supervisor.xmlrpc import SystemNamespaceRPCInterface\nfrom supervisor.xmlrpc import RootRPCInterface\nfrom supervisor.xmlrpc import Faults\nfrom supervisor.xmlrpc import RPCError\n\nfrom supervisor.rpcinterface import SupervisorNamespaceRPCInterface\n\nclass DeferredWebProducer:\n    \"\"\" A medusa producer that implements a deferred callback; requires\n    a subclass of asynchat.async_chat that handles NOT_DONE_YET sentinel \"\"\"\n    CONNECTION = re.compile ('Connection: (.*)', re.IGNORECASE)\n\n    def __init__(self, request, callback):\n        self.callback = callback\n        self.request = request\n        self.finished = False\n        self.delay = float(callback.delay)\n\n    def more(self):\n        if self.finished:\n            return ''\n        try:\n            response = self.callback()\n            if response is NOT_DONE_YET:\n                return NOT_DONE_YET\n\n            self.finished = True\n            return self.sendresponse(response)\n\n        except:\n            tb = traceback.format_exc()\n            # this should go to the main supervisor log file\n            self.request.channel.server.logger.log('Web interface error', tb)\n            self.finished = True\n            self.request.error(500)\n\n    def sendresponse(self, response):\n\n        headers = response.get('headers', {})\n        for header in headers:\n            self.request[header] = headers[header]\n\n        if 'Content-Type' not in self.request:\n            self.request['Content-Type'] = 'text/plain'\n\n        if headers.get('Location'):\n            self.request['Content-Length'] = 0\n            self.request.error(301)\n            return\n\n        body = response.get('body', '')\n        self.request['Content-Length'] = len(body)\n\n        self.request.push(body)\n\n        connection = get_header(self.CONNECTION, self.request.header)\n\n        close_it = 0\n        wrap_in_chunking = 0\n\n        if self.request.version == '1.0':\n            if connection == 'keep-alive':\n                if not self.request.has_key('Content-Length'):\n                    close_it = 1\n                else:\n                    self.request['Connection'] = 'Keep-Alive'\n            else:\n                close_it = 1\n        elif self.request.version == '1.1':\n            if connection == 'close':\n                close_it = 1\n            elif 'Content-Length' not in self.request:\n                if 'Transfer-Encoding' in self.request:\n                    if not self.request['Transfer-Encoding'] == 'chunked':\n                        close_it = 1\n                elif self.request.use_chunked:\n                    self.request['Transfer-Encoding'] = 'chunked'\n                    wrap_in_chunking = 1\n                else:\n                    close_it = 1\n        elif self.request.version is None:\n            close_it = 1\n\n        outgoing_header = producers.simple_producer (\n            self.request.build_reply_header())\n\n        if close_it:\n            self.request['Connection'] = 'close'\n\n        if wrap_in_chunking:\n            outgoing_producer = producers.chunked_producer (\n                    producers.composite_producer (self.request.outgoing)\n                    )\n            # prepend the header\n            outgoing_producer = producers.composite_producer(\n                [outgoing_header, outgoing_producer]\n                )\n        else:\n            # fix AttributeError: 'unicode' object has no attribute 'more'\n            if PY2 and (len(self.request.outgoing) > 0):\n                body = self.request.outgoing[0]\n                if isinstance(body, unicode):\n                    self.request.outgoing[0] = producers.simple_producer (body)\n\n            # prepend the header\n            self.request.outgoing.insert(0, outgoing_header)\n            outgoing_producer = producers.composite_producer (\n                self.request.outgoing)\n\n        # apply a few final transformations to the output\n        self.request.channel.push_with_producer (\n                # globbing gives us large packets\n                producers.globbing_producer (\n                        # hooking lets us log the number of bytes sent\n                        producers.hooked_producer (\n                                outgoing_producer,\n                                self.request.log\n                                )\n                        )\n                )\n\n        self.request.channel.current_request = None\n\n        if close_it:\n            self.request.channel.close_when_done()\n\nclass ViewContext:\n    def __init__(self, **kw):\n        self.__dict__.update(kw)\n\nclass MeldView:\n\n    content_type = 'text/html;charset=utf-8'\n    delay = .5\n\n    def __init__(self, context):\n        self.context = context\n        template = self.context.template\n        if not os.path.isabs(template):\n            here = os.path.abspath(os.path.dirname(__file__))\n            template = os.path.join(here, template)\n        self.root = templating.parse_xml(template)\n        self.callback = None\n\n    def __call__(self):\n        body = self.render()\n        if body is NOT_DONE_YET:\n            return NOT_DONE_YET\n\n        response = self.context.response\n        headers = response['headers']\n        headers['Content-Type'] = self.content_type\n        headers['Pragma'] = 'no-cache'\n        headers['Cache-Control'] = 'no-cache'\n        headers['Expires'] = http_date.build_http_date(0)\n        response['body'] = as_bytes(body)\n        return response\n\n    def render(self):\n        pass\n\n    def clone(self):\n        return self.root.clone()\n\nclass TailView(MeldView):\n    def render(self):\n        supervisord = self.context.supervisord\n        form = self.context.form\n\n        if not 'processname' in form:\n            tail = 'No process name found'\n            processname = None\n        else:\n            processname = form['processname']\n            offset = 0\n            limit = form.get('limit', '1024')\n            limit = min(-1024, int(limit)*-1 if limit.isdigit() else -1024)\n            if not processname:\n                tail = 'No process name found'\n            else:\n                rpcinterface = SupervisorNamespaceRPCInterface(supervisord)\n                try:\n                    tail = rpcinterface.readProcessStdoutLog(processname,\n                                                             limit, offset)\n                except RPCError as e:\n                    if e.code == Faults.NO_FILE:\n                        tail = 'No file for %s' % processname\n                    else:\n                        tail = 'ERROR: unexpected rpc fault [%d] %s' % (\n                            e.code, e.text)\n\n        root = self.clone()\n\n        title = root.findmeld('title')\n        title.content('Supervisor tail of process %s' % processname)\n        tailbody = root.findmeld('tailbody')\n        tailbody.content(tail)\n\n        refresh_anchor = root.findmeld('refresh_anchor')\n        if processname is not None:\n            refresh_anchor.attributes(\n                href='tail.html?processname=%s&limit=%s' % (\n                    urllib.quote(processname), urllib.quote(str(abs(limit)))\n                    )\n            )\n        else:\n            refresh_anchor.deparent()\n\n        return as_string(root.write_xhtmlstring())\n\nclass StatusView(MeldView):\n    def actions_for_process(self, process):\n        state = process.get_state()\n        processname = urllib.quote(make_namespec(process.group.config.name,\n                                                 process.config.name))\n        start = {\n            'name': 'Start',\n            'href': 'index.html?processname=%s&amp;action=start' % processname,\n            'target': None,\n        }\n        restart = {\n            'name': 'Restart',\n            'href': 'index.html?processname=%s&amp;action=restart' % processname,\n            'target': None,\n        }\n        stop = {\n            'name': 'Stop',\n            'href': 'index.html?processname=%s&amp;action=stop' % processname,\n            'target': None,\n        }\n        clearlog = {\n            'name': 'Clear Log',\n            'href': 'index.html?processname=%s&amp;action=clearlog' % processname,\n            'target': None,\n        }\n        tailf_stdout = {\n            'name': 'Tail -f Stdout',\n            'href': 'logtail/%s' % processname,\n            'target': '_blank'\n        }\n        tailf_stderr = {\n            'name': 'Tail -f Stderr',\n            'href': 'logtail/%s/stderr' % processname,\n            'target': '_blank'\n        }\n        if state == ProcessStates.RUNNING:\n            actions = [restart, stop, clearlog, tailf_stdout, tailf_stderr]\n        elif state in (ProcessStates.STOPPED, ProcessStates.EXITED,\n                       ProcessStates.FATAL):\n            actions = [start, None, clearlog, tailf_stdout, tailf_stderr]\n        else:\n            actions = [None, None, clearlog, tailf_stdout, tailf_stderr]\n        return actions\n\n    def css_class_for_state(self, state):\n        if state == ProcessStates.RUNNING:\n            return 'statusrunning'\n        elif state in (ProcessStates.FATAL, ProcessStates.BACKOFF):\n            return 'statuserror'\n        else:\n            return 'statusnominal'\n\n    def make_callback(self, namespec, action):\n        supervisord = self.context.supervisord\n\n        # the rpc interface code is already written to deal properly in a\n        # deferred world, so just use it\n        main =   ('supervisor', SupervisorNamespaceRPCInterface(supervisord))\n        system = ('system', SystemNamespaceRPCInterface([main]))\n\n        rpcinterface = RootRPCInterface([main, system])\n\n        if action:\n\n            if action == 'refresh':\n                def donothing():\n                    message = 'Page refreshed at %s' % time.ctime()\n                    return message\n                donothing.delay = 0.05\n                return donothing\n\n            elif action == 'stopall':\n                callback = rpcinterface.supervisor.stopAllProcesses()\n                def stopall():\n                    if callback() is NOT_DONE_YET:\n                        return NOT_DONE_YET\n                    else:\n                        return 'All stopped at %s' % time.ctime()\n                stopall.delay = 0.05\n                return stopall\n\n            elif action == 'restartall':\n                callback = rpcinterface.system.multicall(\n                    [ {'methodName':'supervisor.stopAllProcesses'},\n                      {'methodName':'supervisor.startAllProcesses'} ] )\n                def restartall():\n                    result = callback()\n                    if result is NOT_DONE_YET:\n                        return NOT_DONE_YET\n                    return 'All restarted at %s' % time.ctime()\n                restartall.delay = 0.05\n                return restartall\n\n            elif namespec:\n                def wrong():\n                    return 'No such process named %s' % namespec\n                wrong.delay = 0.05\n                group_name, process_name = split_namespec(namespec)\n                group = supervisord.process_groups.get(group_name)\n                if group is None:\n                    return wrong\n                process = group.processes.get(process_name)\n                if process is None:\n                    return wrong\n\n                if action == 'start':\n                    try:\n                        bool_or_callback = (\n                            rpcinterface.supervisor.startProcess(namespec)\n                            )\n                    except RPCError as e:\n                        if e.code == Faults.NO_FILE:\n                            msg = 'no such file'\n                        elif e.code == Faults.NOT_EXECUTABLE:\n                            msg = 'file not executable'\n                        elif e.code == Faults.ALREADY_STARTED:\n                            msg = 'already started'\n                        elif e.code == Faults.SPAWN_ERROR:\n                            msg = 'spawn error'\n                        elif e.code == Faults.ABNORMAL_TERMINATION:\n                            msg = 'abnormal termination'\n                        else:\n                            msg = 'unexpected rpc fault [%d] %s' % (\n                                e.code, e.text)\n                        def starterr():\n                            return 'ERROR: Process %s: %s' % (namespec, msg)\n                        starterr.delay = 0.05\n                        return starterr\n\n                    if callable(bool_or_callback):\n                        def startprocess():\n                            try:\n                                result = bool_or_callback()\n                            except RPCError as e:\n                                if e.code == Faults.SPAWN_ERROR:\n                                    msg = 'spawn error'\n                                elif e.code == Faults.ABNORMAL_TERMINATION:\n                                    msg = 'abnormal termination'\n                                else:\n                                    msg = 'unexpected rpc fault [%d] %s' % (\n                                        e.code, e.text)\n                                return 'ERROR: Process %s: %s' % (namespec, msg)\n\n                            if result is NOT_DONE_YET:\n                                return NOT_DONE_YET\n                            return 'Process %s started' % namespec\n                        startprocess.delay = 0.05\n                        return startprocess\n                    else:\n                        def startdone():\n                            return 'Process %s started' % namespec\n                        startdone.delay = 0.05\n                        return startdone\n\n                elif action == 'stop':\n                    try:\n                        bool_or_callback = (\n                            rpcinterface.supervisor.stopProcess(namespec)\n                            )\n                    except RPCError as e:\n                        msg = 'unexpected rpc fault [%d] %s' % (e.code, e.text)\n                        def stoperr():\n                            return msg\n                        stoperr.delay = 0.05\n                        return stoperr\n\n                    if callable(bool_or_callback):\n                        def stopprocess():\n                            try:\n                                result = bool_or_callback()\n                            except RPCError as e:\n                                return 'unexpected rpc fault [%d] %s' % (\n                                    e.code, e.text)\n                            if result is NOT_DONE_YET:\n                                return NOT_DONE_YET\n                            return 'Process %s stopped' % namespec\n                        stopprocess.delay = 0.05\n                        return stopprocess\n                    else:\n                        def stopdone():\n                            return 'Process %s stopped' % namespec\n                        stopdone.delay = 0.05\n                        return stopdone\n\n                elif action == 'restart':\n                    results_or_callback = rpcinterface.system.multicall(\n                        [ {'methodName':'supervisor.stopProcess',\n                           'params': [namespec]},\n                          {'methodName':'supervisor.startProcess',\n                           'params': [namespec]},\n                          ]\n                        )\n                    if callable(results_or_callback):\n                        callback = results_or_callback\n                        def restartprocess():\n                            results = callback()\n                            if results is NOT_DONE_YET:\n                                return NOT_DONE_YET\n                            return 'Process %s restarted' % namespec\n                        restartprocess.delay = 0.05\n                        return restartprocess\n                    else:\n                        def restartdone():\n                            return 'Process %s restarted' % namespec\n                        restartdone.delay = 0.05\n                        return restartdone\n\n                elif action == 'clearlog':\n                    try:\n                        callback = rpcinterface.supervisor.clearProcessLogs(\n                            namespec)\n                    except RPCError as e:\n                        msg = 'unexpected rpc fault [%d] %s' % (e.code, e.text)\n                        def clearerr():\n                            return msg\n                        clearerr.delay = 0.05\n                        return clearerr\n\n                    def clearlog():\n                        return 'Log for %s cleared' % namespec\n                    clearlog.delay = 0.05\n                    return clearlog\n\n        raise ValueError(action)\n\n    def render(self):\n        form = self.context.form\n        response = self.context.response\n        processname = form.get('processname')\n        action = form.get('action')\n        message = form.get('message')\n\n        if action:\n            if not self.callback:\n                self.callback = self.make_callback(processname, action)\n                return NOT_DONE_YET\n\n            else:\n                message =  self.callback()\n                if message is NOT_DONE_YET:\n                    return NOT_DONE_YET\n                if message is not None:\n                    server_url = form['SERVER_URL']\n                    location = server_url + \"/\" + '?message=%s' % urllib.quote(\n                        message)\n                    response['headers']['Location'] = location\n\n        supervisord = self.context.supervisord\n        rpcinterface = RootRPCInterface(\n            [('supervisor',\n              SupervisorNamespaceRPCInterface(supervisord))]\n            )\n\n        processnames = []\n        for group in supervisord.process_groups.values():\n            for gprocname in group.processes.keys():\n                processnames.append((group.config.name, gprocname))\n\n        processnames.sort()\n\n        data = []\n        for groupname, processname in processnames:\n            actions = self.actions_for_process(\n                supervisord.process_groups[groupname].processes[processname])\n            sent_name = make_namespec(groupname, processname)\n            info = rpcinterface.supervisor.getProcessInfo(sent_name)\n            data.append({\n                'status':info['statename'],\n                'name':processname,\n                'group':groupname,\n                'actions':actions,\n                'state':info['state'],\n                'description':info['description'],\n                })\n\n        root = self.clone()\n\n        if message is not None:\n            statusarea = root.findmeld('statusmessage')\n            statusarea.attrib['class'] = 'status_msg'\n            statusarea.content(message)\n\n        if data:\n            iterator = root.findmeld('tr').repeat(data)\n            shaded_tr = False\n\n            for tr_element, item in iterator:\n                status_text = tr_element.findmeld('status_text')\n                status_text.content(item['status'].lower())\n                status_text.attrib['class'] = self.css_class_for_state(\n                    item['state'])\n\n                info_text = tr_element.findmeld('info_text')\n                info_text.content(item['description'])\n\n                anchor = tr_element.findmeld('name_anchor')\n                processname = make_namespec(item['group'], item['name'])\n                anchor.attributes(href='tail.html?processname=%s' %\n                                  urllib.quote(processname))\n                anchor.content(processname)\n\n                actions = item['actions']\n                actionitem_td = tr_element.findmeld('actionitem_td')\n\n                for li_element, actionitem in actionitem_td.repeat(actions):\n                    anchor = li_element.findmeld('actionitem_anchor')\n                    if actionitem is None:\n                        anchor.attrib['class'] = 'hidden'\n                    else:\n                        anchor.attributes(href=actionitem['href'],\n                                          name=actionitem['name'])\n                        anchor.content(actionitem['name'])\n                        if actionitem['target']:\n                            anchor.attributes(target=actionitem['target'])\n                if shaded_tr:\n                    tr_element.attrib['class'] = 'shade'\n                shaded_tr = not shaded_tr\n        else:\n            table = root.findmeld('statustable')\n            table.replace('No programs to manage')\n\n        root.findmeld('supervisor_version').content(VERSION)\n        copyright_year = str(datetime.date.today().year)\n        root.findmeld('copyright_date').content(copyright_year)\n\n        return as_string(root.write_xhtmlstring())\n\nclass OKView:\n    delay = 0\n    def __init__(self, context):\n        self.context = context\n\n    def __call__(self):\n        return {'body':'OK'}\n\nVIEWS = {\n    'index.html': {\n          'template':'ui/status.html',\n          'view':StatusView\n          },\n    'tail.html': {\n           'template':'ui/tail.html',\n           'view':TailView,\n           },\n    'ok.html': {\n           'template':None,\n           'view':OKView,\n           },\n    }\n\n\nclass supervisor_ui_handler:\n    IDENT = 'Supervisor Web UI HTTP Request Handler'\n\n    def __init__(self, supervisord):\n        self.supervisord = supervisord\n\n    def match(self, request):\n        if request.command not in ('POST', 'GET'):\n            return False\n\n        path, params, query, fragment = request.split_uri()\n\n        while path.startswith('/'):\n            path = path[1:]\n\n        if not path:\n            path = 'index.html'\n\n        for viewname in VIEWS.keys():\n            if viewname == path:\n                return True\n\n    def handle_request(self, request):\n        if request.command == 'POST':\n            request.collector = collector(self, request)\n        else:\n            self.continue_request('', request)\n\n    def continue_request (self, data, request):\n        form = {}\n        cgi_env = request.cgi_environment()\n        form.update(cgi_env)\n        if 'QUERY_STRING' not in form:\n            form['QUERY_STRING'] = ''\n\n        query = form['QUERY_STRING']\n\n        # we only handle x-www-form-urlencoded values from POSTs\n        form_urlencoded = urlparse.parse_qsl(data)\n        query_data = urlparse.parse_qs(query)\n\n        for k, v in query_data.items():\n            # ignore dupes\n            form[k] = v[0]\n\n        for k, v in form_urlencoded:\n            # ignore dupes\n            form[k] = v\n\n        form['SERVER_URL'] = request.get_server_url()\n\n        path = form['PATH_INFO']\n        # strip off all leading slashes\n        while path and path[0] == '/':\n            path = path[1:]\n        if not path:\n            path = 'index.html'\n\n        viewinfo = VIEWS.get(path)\n        if viewinfo is None:\n            # this should never happen if our match method works\n            return\n\n        response = {'headers': {}}\n\n        viewclass = viewinfo['view']\n        viewtemplate = viewinfo['template']\n        context = ViewContext(template=viewtemplate,\n                              request = request,\n                              form = form,\n                              response = response,\n                              supervisord=self.supervisord)\n        view = viewclass(context)\n        pushproducer = request.channel.push_with_producer\n        pushproducer(DeferredWebProducer(request, view))\n\n"
  },
  {
    "path": "supervisor/xmlrpc.py",
    "content": "import datetime\nimport re\nimport socket\nimport sys\nimport time\nimport traceback\nimport types\nfrom xml.etree.ElementTree import iterparse\n\nfrom supervisor.compat import xmlrpclib\nfrom supervisor.compat import StringIO\nfrom supervisor.compat import urlparse\nfrom supervisor.compat import as_bytes\nfrom supervisor.compat import as_string\nfrom supervisor.compat import encodestring\nfrom supervisor.compat import decodestring\nfrom supervisor.compat import httplib\nfrom supervisor.compat import PY2\n\nfrom supervisor.medusa.http_server import get_header\nfrom supervisor.medusa.xmlrpc_handler import xmlrpc_handler\nfrom supervisor.medusa import producers\n\nfrom supervisor.http import NOT_DONE_YET\n\nclass Faults:\n    UNKNOWN_METHOD = 1\n    INCORRECT_PARAMETERS = 2\n    BAD_ARGUMENTS = 3\n    SIGNATURE_UNSUPPORTED = 4\n    SHUTDOWN_STATE = 6\n    BAD_NAME = 10\n    BAD_SIGNAL = 11\n    NO_FILE = 20\n    NOT_EXECUTABLE = 21\n    FAILED = 30\n    ABNORMAL_TERMINATION = 40\n    SPAWN_ERROR = 50\n    ALREADY_STARTED = 60\n    NOT_RUNNING = 70\n    SUCCESS = 80\n    ALREADY_ADDED = 90\n    STILL_RUNNING = 91\n    CANT_REREAD = 92\n\ndef getFaultDescription(code):\n    for faultname in Faults.__dict__:\n        if getattr(Faults, faultname) == code:\n            return faultname\n    return 'UNKNOWN'\n\nclass RPCError(Exception):\n    def __init__(self, code, extra=None):\n        self.code = code\n        self.text = getFaultDescription(code)\n        if extra is not None:\n            self.text = '%s: %s' % (self.text, extra)\n\n    def __str__(self):\n        return 'code=%r, text=%r' % (self.code, self.text)\n\nclass DeferredXMLRPCResponse:\n    \"\"\" A medusa producer that implements a deferred callback; requires\n    a subclass of asynchat.async_chat that handles NOT_DONE_YET sentinel \"\"\"\n    CONNECTION = re.compile ('Connection: (.*)', re.IGNORECASE)\n\n    def __init__(self, request, callback):\n        self.callback = callback\n        self.request = request\n        self.finished = False\n        self.delay = float(callback.delay)\n\n    def more(self):\n        if self.finished:\n            return ''\n        try:\n            try:\n                value = self.callback()\n                if value is NOT_DONE_YET:\n                    return NOT_DONE_YET\n            except RPCError as err:\n                value = xmlrpclib.Fault(err.code, err.text)\n\n            body = xmlrpc_marshal(value)\n\n            self.finished = True\n\n            return self.getresponse(body)\n\n        except:\n            tb = traceback.format_exc()\n            self.request.channel.server.logger.log(\n                \"XML-RPC response callback error\", tb\n                )\n            self.finished = True\n            self.request.error(500)\n\n    def getresponse(self, body):\n        self.request['Content-Type'] = 'text/xml'\n        self.request['Content-Length'] = len(body)\n        self.request.push(body)\n        connection = get_header(self.CONNECTION, self.request.header)\n\n        close_it = 0\n\n        if self.request.version == '1.0':\n            if connection == 'keep-alive':\n                self.request['Connection'] = 'Keep-Alive'\n            else:\n                close_it = 1\n        elif self.request.version == '1.1':\n            if connection == 'close':\n                close_it = 1\n        elif self.request.version is None:\n            close_it = 1\n\n        outgoing_header = producers.simple_producer (\n            self.request.build_reply_header())\n\n        if close_it:\n            self.request['Connection'] = 'close'\n\n        # prepend the header\n        self.request.outgoing.insert(0, outgoing_header)\n        outgoing_producer = producers.composite_producer(self.request.outgoing)\n\n        # apply a few final transformations to the output\n        self.request.channel.push_with_producer (\n                # globbing gives us large packets\n                producers.globbing_producer (\n                        # hooking lets us log the number of bytes sent\n                        producers.hooked_producer (\n                                outgoing_producer,\n                                self.request.log\n                                )\n                        )\n                )\n\n        self.request.channel.current_request = None\n\n        if close_it:\n            self.request.channel.close_when_done()\n\ndef xmlrpc_marshal(value):\n    ismethodresponse = not isinstance(value, xmlrpclib.Fault)\n    if ismethodresponse:\n        if not isinstance(value, tuple):\n            value = (value,)\n        body = xmlrpclib.dumps(value, methodresponse=ismethodresponse)\n    else:\n        body = xmlrpclib.dumps(value)\n    return body\n\nclass SystemNamespaceRPCInterface:\n    def __init__(self, namespaces):\n        self.namespaces = {}\n        for name, inst in namespaces:\n            self.namespaces[name] = inst\n        self.namespaces['system'] = self\n\n    def _listMethods(self):\n        methods = {}\n        for ns_name in self.namespaces:\n            namespace = self.namespaces[ns_name]\n            for method_name in namespace.__class__.__dict__:\n                # introspect; any methods that don't start with underscore\n                # are published\n                func = getattr(namespace, method_name)\n                if callable(func):\n                    if not method_name.startswith('_'):\n                        sig = '%s.%s' % (ns_name, method_name)\n                        methods[sig] = str(func.__doc__)\n        return methods\n\n    def listMethods(self):\n        \"\"\" Return an array listing the available method names\n\n        @return array result  An array of method names available (strings).\n        \"\"\"\n        methods = self._listMethods()\n        keys = list(methods.keys())\n        keys.sort()\n        return keys\n\n    def methodHelp(self, name):\n        \"\"\" Return a string showing the method's documentation\n\n        @param string name   The name of the method.\n        @return string result The documentation for the method name.\n        \"\"\"\n        methods = self._listMethods()\n        for methodname in methods.keys():\n            if methodname == name:\n                return methods[methodname]\n        raise RPCError(Faults.SIGNATURE_UNSUPPORTED)\n\n    def methodSignature(self, name):\n        \"\"\" Return an array describing the method signature in the\n        form [rtype, ptype, ptype...] where rtype is the return data type\n        of the method, and ptypes are the parameter data types that the\n        method accepts in method argument order.\n\n        @param string name  The name of the method.\n        @return array result  The result.\n        \"\"\"\n        methods = self._listMethods()\n        for method in methods:\n            if method == name:\n                rtype = None\n                ptypes = []\n                parsed = gettags(methods[method])\n                for thing in parsed:\n                    if thing[1] == 'return': # tag name\n                        rtype = thing[2] # datatype\n                    elif thing[1] == 'param': # tag name\n                        ptypes.append(thing[2]) # datatype\n                if rtype is None:\n                    raise RPCError(Faults.SIGNATURE_UNSUPPORTED)\n                return [rtype] + ptypes\n        raise RPCError(Faults.SIGNATURE_UNSUPPORTED)\n\n    def multicall(self, calls):\n        \"\"\"Process an array of calls, and return an array of\n        results. Calls should be structs of the form {'methodName':\n        string, 'params': array}. Each result will either be a\n        single-item array containing the result value, or a struct of\n        the form {'faultCode': int, 'faultString': string}. This is\n        useful when you need to make lots of small calls without lots\n        of round trips.\n\n        @param array calls  An array of call requests\n        @return array result  An array of results\n        \"\"\"\n        remaining_calls = calls[:] # [{'methodName':x, 'params':x}, ...]\n        callbacks = [] # always empty or 1 callback function only\n        results = [] # results of completed calls\n\n        # args are only to fool scoping and are never passed by caller\n        def multi(remaining_calls=remaining_calls,\n                  callbacks=callbacks,\n                  results=results):\n\n            # if waiting on a callback, call it, then remove it if it's done\n            if callbacks:\n                try:\n                    value = callbacks[0]()\n                except RPCError as exc:\n                    value = {'faultCode': exc.code,\n                             'faultString': exc.text}\n                except:\n                    info = sys.exc_info()\n                    errmsg = \"%s:%s\" % (info[0], info[1])\n                    value = {'faultCode': Faults.FAILED,\n                             'faultString': 'FAILED: ' + errmsg}\n                if value is not NOT_DONE_YET:\n                    callbacks.pop(0)\n                    results.append(value)\n\n            # if we don't have a callback now, pop calls and call them in\n            # order until one returns a callback.\n            while (not callbacks) and remaining_calls:\n                call = remaining_calls.pop(0)\n                name = call.get('methodName', None)\n                params = call.get('params', [])\n\n                try:\n                    if name is None:\n                        raise RPCError(Faults.INCORRECT_PARAMETERS,\n                            'No methodName')\n                    if name == 'system.multicall':\n                        raise RPCError(Faults.INCORRECT_PARAMETERS,\n                            'Recursive system.multicall forbidden')\n                    # make the call, may return a callback or not\n                    root = AttrDict(self.namespaces)\n                    value = traverse(root, name, params)\n                except RPCError as exc:\n                    value = {'faultCode': exc.code,\n                             'faultString': exc.text}\n                except:\n                    info = sys.exc_info()\n                    errmsg = \"%s:%s\" % (info[0], info[1])\n                    value = {'faultCode': Faults.FAILED,\n                             'faultString': 'FAILED: ' + errmsg}\n\n                if isinstance(value, types.FunctionType):\n                    callbacks.append(value)\n                else:\n                    results.append(value)\n\n            # we are done when there's no callback and no more calls queued\n            if callbacks or remaining_calls:\n                return NOT_DONE_YET\n            else:\n                return results\n        multi.delay = 0.05\n\n        # optimization: multi() is called here instead of just returning\n        # multi in case all calls complete and we can return with no delay.\n        value = multi()\n        if value is NOT_DONE_YET:\n            return multi\n        else:\n            return value\n\nclass AttrDict(dict):\n    # hack to make a dict's getattr equivalent to its getitem\n    def __getattr__(self, name):\n        return self.get(name)\n\nclass RootRPCInterface:\n    def __init__(self, subinterfaces):\n        for name, rpcinterface in subinterfaces:\n            setattr(self, name, rpcinterface)\n\ndef capped_int(value):\n    i = int(value)\n    if i < xmlrpclib.MININT:\n        i = xmlrpclib.MININT\n    elif i > xmlrpclib.MAXINT:\n        i = xmlrpclib.MAXINT\n    return i\n\ndef make_datetime(text):\n    return datetime.datetime(\n        *time.strptime(text, \"%Y%m%dT%H:%M:%S\")[:6]\n    )\n\nclass supervisor_xmlrpc_handler(xmlrpc_handler):\n    path = '/RPC2'\n    IDENT = 'Supervisor XML-RPC Handler'\n\n    unmarshallers = {\n        \"int\": lambda x: int(x.text),\n        \"i4\": lambda x: int(x.text),\n        \"boolean\": lambda x: x.text == \"1\",\n        \"string\": lambda x: x.text or \"\",\n        \"double\": lambda x: float(x.text),\n        \"dateTime.iso8601\": lambda x: make_datetime(x.text),\n        \"array\": lambda x: x[0].text,\n        \"data\": lambda x: [v.text for v in x],\n        \"struct\": lambda x: dict([(k.text or \"\", v.text) for k, v in x]),\n        \"base64\": lambda x: as_string(decodestring(as_bytes(x.text or \"\"))),\n        \"param\": lambda x: x[0].text,\n        }\n\n    def __init__(self, supervisord, subinterfaces):\n        self.rpcinterface = RootRPCInterface(subinterfaces)\n        self.supervisord = supervisord\n\n    def loads(self, data):\n        params = method = None\n        for action, elem in iterparse(StringIO(data)):\n            unmarshall = self.unmarshallers.get(elem.tag)\n            if unmarshall:\n                data = unmarshall(elem)\n                elem.clear()\n                elem.text = data\n            elif elem.tag == \"value\":\n                try:\n                    data = elem[0].text\n                except IndexError:\n                    data = elem.text or \"\"\n                elem.clear()\n                elem.text = data\n            elif elem.tag == \"methodName\":\n                method = elem.text\n            elif elem.tag == \"params\":\n                params = tuple([v.text for v in elem])\n        return params, method\n\n    def match(self, request):\n        return request.uri.startswith(self.path)\n\n    def continue_request(self, data, request):\n        logger = self.supervisord.options.logger\n\n        try:\n            try:\n                # on 2.x, the Expat parser doesn't like Unicode which actually\n                # contains non-ASCII characters. It's a bit of a kludge to\n                # do it conditionally here, but it's down to how underlying\n                # libs behave\n                if PY2:\n                    data = data.encode('ascii', 'xmlcharrefreplace')\n                params, method = self.loads(data)\n            except:\n                logger.error(\n                    'XML-RPC request data %r is invalid: unmarshallable' %\n                    (data,)\n                )\n                request.error(400)\n                return\n\n            # no <methodName> in the request or name is an empty string\n            if not method:\n                logger.error(\n                    'XML-RPC request data %r is invalid: no method name' %\n                    (data,)\n                    )\n                request.error(400)\n                return\n\n            # we allow xml-rpc clients that do not send empty <params>\n            # when there are no parameters for the method call\n            if params is None:\n                params = ()\n\n            try:\n                logger.trace('XML-RPC method called: %s()' % method)\n                value = self.call(method, params)\n                logger.trace('XML-RPC method %s() returned successfully' %\n                             method)\n            except RPCError as err:\n                # turn RPCError reported by method into a Fault instance\n                value = xmlrpclib.Fault(err.code, err.text)\n                logger.trace('XML-RPC method %s() returned fault: [%d] %s' % (\n                    method,\n                    err.code, err.text))\n\n            if isinstance(value, types.FunctionType):\n                # returning a function from an RPC method implies that\n                # this needs to be a deferred response (it needs to block).\n                pushproducer = request.channel.push_with_producer\n                pushproducer(DeferredXMLRPCResponse(request, value))\n\n            else:\n                # if we get anything but a function, it implies that this\n                # response doesn't need to be deferred, we can service it\n                # right away.\n                body = as_bytes(xmlrpc_marshal(value))\n                request['Content-Type'] = 'text/xml'\n                request['Content-Length'] = len(body)\n                request.push(body)\n                request.done()\n\n        except:\n            tb = traceback.format_exc()\n            logger.critical(\n                \"Handling XML-RPC request with data %r raised an unexpected \"\n                \"exception: %s\" % (data, tb)\n                )\n            # internal error, report as HTTP server error\n            request.error(500)\n\n    def call(self, method, params):\n        return traverse(self.rpcinterface, method, params)\n\ndef traverse(ob, method, params):\n    dotted_parts = method.split('.')\n    # security (CVE-2017-11610, don't allow object traversal)\n    if len(dotted_parts) != 2:\n        raise RPCError(Faults.UNKNOWN_METHOD)\n    namespace, method = dotted_parts\n\n    # security (don't allow methods that start with an underscore to\n    # be called remotely)\n    if method.startswith('_'):\n        raise RPCError(Faults.UNKNOWN_METHOD)\n\n    rpcinterface = getattr(ob, namespace, None)\n    if rpcinterface is None:\n        raise RPCError(Faults.UNKNOWN_METHOD)\n\n    func = getattr(rpcinterface, method, None)\n    if not isinstance(func, types.MethodType):\n        raise RPCError(Faults.UNKNOWN_METHOD)\n\n    try:\n        return func(*params)\n    except TypeError:\n        raise RPCError(Faults.INCORRECT_PARAMETERS)\n\nclass SupervisorTransport(xmlrpclib.Transport):\n    \"\"\"\n    Provides a Transport for xmlrpclib that uses\n    httplib.HTTPConnection in order to support persistent\n    connections.  Also support basic auth and UNIX domain socket\n    servers.\n    \"\"\"\n    connection = None\n\n    def __init__(self, username=None, password=None, serverurl=None):\n        xmlrpclib.Transport.__init__(self)\n        self.username = username\n        self.password = password\n        self.verbose = False\n        self.serverurl = serverurl\n        if serverurl.startswith('http://'):\n            parsed = urlparse.urlparse(serverurl)\n            host, port = parsed.hostname, parsed.port\n            if port is None:\n                port = 80\n            def get_connection(host=host, port=port):\n                return httplib.HTTPConnection(host, port)\n            self._get_connection = get_connection\n        elif serverurl.startswith('unix://'):\n            def get_connection(serverurl=serverurl):\n                # we use 'localhost' here because domain names must be\n                # < 64 chars (or we'd use the serverurl filename)\n                conn = UnixStreamHTTPConnection('localhost')\n                conn.socketfile = serverurl[7:]\n                return conn\n            self._get_connection = get_connection\n        else:\n            raise ValueError('Unknown protocol for serverurl %s' % serverurl)\n\n    def close(self):\n        if self.connection:\n            self.connection.close()\n            self.connection = None\n\n    def request(self, host, handler, request_body, verbose=0):\n        request_body = as_bytes(request_body)\n        if not self.connection:\n            self.connection = self._get_connection()\n            self.headers = {\n                \"User-Agent\" : self.user_agent,\n                \"Content-Type\" : \"text/xml\",\n                \"Accept\": \"text/xml\"\n                }\n\n            # basic auth\n            if self.username is not None and self.password is not None:\n                unencoded = \"%s:%s\" % (self.username, self.password)\n                encoded = as_string(encodestring(as_bytes(unencoded)))\n                encoded = encoded.replace('\\n', '')\n                encoded = encoded.replace('\\012', '')\n                self.headers[\"Authorization\"] = \"Basic %s\" % encoded\n\n        self.headers[\"Content-Length\"] = str(len(request_body))\n\n        self.connection.request('POST', handler, request_body, self.headers)\n\n        r = self.connection.getresponse()\n\n        if r.status != 200:\n            self.connection.close()\n            self.connection = None\n            raise xmlrpclib.ProtocolError(host + handler,\n                                          r.status,\n                                          r.reason,\n                                          '' )\n        data = r.read()\n        data = as_string(data)\n        # on 2.x, the Expat parser doesn't like Unicode which actually\n        # contains non-ASCII characters\n        data = data.encode('ascii', 'xmlcharrefreplace')\n        p, u = self.getparser()\n        p.feed(data)\n        p.close()\n        return u.close()\n\nclass UnixStreamHTTPConnection(httplib.HTTPConnection):\n    def connect(self): # pragma: no cover\n        self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\n        # we abuse the host parameter as the socketname\n        self.sock.connect(self.socketfile)\n\ndef gettags(comment):\n    \"\"\" Parse documentation strings into JavaDoc-like tokens \"\"\"\n\n    tags = []\n\n    tag = None\n    datatype = None\n    name = None\n    tag_lineno = lineno = 0\n    tag_text = []\n\n    for line in comment.split('\\n'):\n        line = line.strip()\n        if line.startswith(\"@\"):\n            tags.append((tag_lineno, tag, datatype, name, '\\n'.join(tag_text)))\n            parts = line.split(None, 3)\n            if len(parts) == 1:\n                datatype = ''\n                name = ''\n                tag_text = []\n            elif len(parts) == 2:\n                datatype = parts[1]\n                name = ''\n                tag_text = []\n            elif len(parts) == 3:\n                datatype = parts[1]\n                name = parts[2]\n                tag_text = []\n            elif len(parts) == 4:\n                datatype = parts[1]\n                name = parts[2]\n                tag_text = [parts[3].lstrip()]\n            tag = parts[0][1:]\n            tag_lineno = lineno\n        else:\n            if line:\n                tag_text.append(line)\n        lineno += 1\n\n    tags.append((tag_lineno, tag, datatype, name, '\\n'.join(tag_text)))\n\n    return tags\n"
  },
  {
    "path": "tox.ini",
    "content": "[tox]\nenvlist =\n    cover,cover3,docs,py27,py34,py35,py36,py37,py38,py39,py310,py311,py312,py313,py314\n\n[testenv]\ndeps =\n    attrs < 21.1.0    # see https://github.com/python-attrs/attrs/pull/608\n    pexpect == 4.7.0  # see https://github.com/Supervisor/supervisor/issues/1327\n    pytest\npassenv = END_TO_END\ncommands =\n    pytest --capture=no {posargs}\n\n[testenv:py27]\nbasepython = python2.7\ndeps =\n    {[testenv]deps}\n    mock >= 0.5.0\npassenv = {[testenv]passenv}\ncommands = {[testenv]commands}\n\n[testenv:py27-configparser]\n;see https://github.com/Supervisor/supervisor/issues/1230\nbasepython = python2.7\ndeps =\n    {[testenv:py27]deps}\n    configparser\npassenv = {[testenv:py27]passenv}\ncommands = {[testenv:py27]commands}\n\n[testenv:cover]\nbasepython = python2.7\ndeps =\n    {[testenv:py27]deps}\n    pytest-cov\ncommands =\n    pytest --capture=no --cov=supervisor --cov-report=term-missing --cov-report=xml {posargs}\n\n[testenv:cover3]\nbasepython = python3.8\ncommands =\n    pytest --capture=no --cov=supervisor --cov-report=term-missing --cov-report=xml {posargs}\ndeps =\n    {[testenv:cover]deps}\n\n[testenv:docs]\ndeps =\n    pygments >= 2.19.1  # Sphinx build fails on 2.19.0 when highlighting ini block\n    Sphinx\n    readme\n    setuptools >= 18.5\nallowlist_externals = make\ncommands =\n    make -C docs html BUILDDIR={envtmpdir} \"SPHINXOPTS=-W -E\"\n    python setup.py check -m -r -s\n"
  }
]