summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@baserock.org>2014-08-04 05:45:35 +0000
committer <>2014-12-10 05:33:45 +0000
commitafcc4ea312255a2545f9c67d7c34ffefb00c80c0 (patch)
tree5ca5269e5d4fa6263242a7a96b713616e5f389e0
parent02378192d5bb4b16498d87ace57da425166426bf (diff)
downloadpython-daemon-master.tar.gz
Imported from /home/lorry/working-area/delta_python-packages_python-daemon/python-daemon-1.6.1.tar.gz.HEADpython-daemon-1.6.1master
-rw-r--r--ChangeLog61
-rw-r--r--LICENSE.ASF-2202
-rw-r--r--LICENSE.GPL-2339
-rw-r--r--LICENSE.GPL-3674
-rw-r--r--LICENSE.PSF-248
-rw-r--r--MANIFEST.in2
-rw-r--r--PKG-INFO21
-rw-r--r--daemon/__init__.py25
-rw-r--r--daemon/daemon.py144
-rw-r--r--daemon/pidfile.py49
-rw-r--r--daemon/pidlockfile.py194
-rw-r--r--daemon/runner.py54
-rw-r--r--daemon/version/__init__.py46
-rw-r--r--daemon/version/version_info.py11
-rw-r--r--doc/FAQ143
-rw-r--r--doc/TODO89
-rw-r--r--python_daemon.egg-info/PKG-INFO21
-rw-r--r--python_daemon.egg-info/SOURCES.txt12
-rw-r--r--python_daemon.egg-info/requires.txt2
-rw-r--r--setup.py153
-rw-r--r--test/__init__.py19
-rw-r--r--test/scaffold.py402
-rw-r--r--test/test_daemon.py912
-rw-r--r--test/test_pidfile.py407
-rw-r--r--test/test_pidlockfile.py791
-rw-r--r--test/test_runner.py302
26 files changed, 2548 insertions, 2575 deletions
diff --git a/ChangeLog b/ChangeLog
index d96fad7..b9203cc 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,58 @@
+2014-08-04 Ben Finney <ben+python@benfinney.id.au>
+
+ Version 1.6.1 released.
+
+2014-08-01 Ben Finney <ben+python@benfinney.id.au>
+
+ * Use unambiguous “except FooType as foo” syntax.
+ This is to ease the port to Python 3, where the ambiguous comma
+ usage is an error.
+ * Ensure a ‘basestring’ name bound to the base type for strings.
+ This is to allow checks to work on Python 2 and 3.
+ * Specify versions of Python supported, as trove classifiers.
+
+2014-05-20 Ben Finney <ben+python@benfinney.id.au>
+
+ * Update copyright notices.
+ * Add editor hints for most files.
+ * Distinguish continuation-line indentation versus block indentation.
+
+2012-05-30 Ben Finney <ben+python@benfinney.id.au>
+
+ * Use unicode literals by default, specifying bytes where necessary.
+ This is to ease the port to Python 3, where the string type is
+ unicode.
+ * Update copyright notices.
+ * Update the GPL license file to version 3, as declared in our
+ copyright notices.
+
+2011-07-02 Ben Finney <ben+python@benfinney.id.au>
+
+ * Change license of library code to Apache License 2.0. Rationale at
+ <URL:http://wiki.python.org/moin/PythonSoftwareFoundationLicenseFaq#Contributing_Code_to_Python>.
+
+2010-05-10 Ben Finney <ben+python@benfinney.id.au>
+
+ Version 1.6 released.
+
+ * Use absolute imports to disambiguate provenance of names.
+ * setup.py: Require ‘lockfile >=0.9’.
+ * daemon/pidfile.py: Renamed from ‘daemon/pidlockfile.py’. Change
+ references elsewhere to use this new name.
+ * test/test_pidfile.py: Renamed from ‘test/test_pidlockfile.py’.
+ Change references elsewhere to use this new name.
+ * daemon/pidfile.py: Remove functionality now migrated to ‘lockfile’
+ library.
+
+2010-03-09 Ben Finney <ben+python@benfinney.id.au>
+
+ * Use ‘unicode’ data type for all text values.
+ * Prepare for Python 3 upgrade by tweaking some names and imports.
+
+2010-03-03 Ben Finney <ben+python@benfinney.id.au>
+
+ * MANIFEST.in: Include the documentation in the distribution.
+
2010-03-02 Ben Finney <ben+python@benfinney.id.au>
Version 1.5.5 released.
@@ -179,9 +234,15 @@
* Begin unit test suite.
+This is free software: you may copy, modify, and/or distribute this work
+under the terms of the Apache License version 2.0 as published by the
+Apache Software Foundation.
+No warranty expressed or implied. See the file LICENSE.ASF-2 for details.
+
Local variables:
mode: change-log
coding: utf-8
left-margin: 4
indent-tabs-mode: nil
End:
+vim: fileencoding=utf-8 filetype=changelog :
diff --git a/LICENSE.ASF-2 b/LICENSE.ASF-2
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE.ASF-2
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/LICENSE.GPL-2 b/LICENSE.GPL-2
deleted file mode 100644
index d511905..0000000
--- a/LICENSE.GPL-2
+++ /dev/null
@@ -1,339 +0,0 @@
- GNU GENERAL PUBLIC LICENSE
- Version 2, June 1991
-
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
- Preamble
-
- The licenses for most software are designed to take away your
-freedom to share and change it. By contrast, the GNU General Public
-License is intended to guarantee your freedom to share and change free
-software--to make sure the software is free for all its users. This
-General Public License applies to most of the Free Software
-Foundation's software and to any other program whose authors commit to
-using it. (Some other Free Software Foundation software is covered by
-the GNU Lesser General Public License instead.) You can apply it to
-your programs, too.
-
- When we speak of free software, we are referring to freedom, not
-price. Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-this service if you wish), that you receive source code or can get it
-if you want it, that you can change the software or use pieces of it
-in new free programs; and that you know you can do these things.
-
- To protect your rights, we need to make restrictions that forbid
-anyone to deny you these rights or to ask you to surrender the rights.
-These restrictions translate to certain responsibilities for you if you
-distribute copies of the software, or if you modify it.
-
- For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must give the recipients all the rights that
-you have. You must make sure that they, too, receive or can get the
-source code. And you must show them these terms so they know their
-rights.
-
- We protect your rights with two steps: (1) copyright the software, and
-(2) offer you this license which gives you legal permission to copy,
-distribute and/or modify the software.
-
- Also, for each author's protection and ours, we want to make certain
-that everyone understands that there is no warranty for this free
-software. If the software is modified by someone else and passed on, we
-want its recipients to know that what they have is not the original, so
-that any problems introduced by others will not reflect on the original
-authors' reputations.
-
- Finally, any free program is threatened constantly by software
-patents. We wish to avoid the danger that redistributors of a free
-program will individually obtain patent licenses, in effect making the
-program proprietary. To prevent this, we have made it clear that any
-patent must be licensed for everyone's free use or not licensed at all.
-
- The precise terms and conditions for copying, distribution and
-modification follow.
-
- GNU GENERAL PUBLIC LICENSE
- TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
- 0. This License applies to any program or other work which contains
-a notice placed by the copyright holder saying it may be distributed
-under the terms of this General Public License. The "Program", below,
-refers to any such program or work, and a "work based on the Program"
-means either the Program or any derivative work under copyright law:
-that is to say, a work containing the Program or a portion of it,
-either verbatim or with modifications and/or translated into another
-language. (Hereinafter, translation is included without limitation in
-the term "modification".) Each licensee is addressed as "you".
-
-Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope. The act of
-running the Program is not restricted, and the output from the Program
-is covered only if its contents constitute a work based on the
-Program (independent of having been made by running the Program).
-Whether that is true depends on what the Program does.
-
- 1. You may copy and distribute verbatim copies of the Program's
-source code as you receive it, in any medium, provided that you
-conspicuously and appropriately publish on each copy an appropriate
-copyright notice and disclaimer of warranty; keep intact all the
-notices that refer to this License and to the absence of any warranty;
-and give any other recipients of the Program a copy of this License
-along with the Program.
-
-You may charge a fee for the physical act of transferring a copy, and
-you may at your option offer warranty protection in exchange for a fee.
-
- 2. You may modify your copy or copies of the Program or any portion
-of it, thus forming a work based on the Program, and copy and
-distribute such modifications or work under the terms of Section 1
-above, provided that you also meet all of these conditions:
-
- a) You must cause the modified files to carry prominent notices
- stating that you changed the files and the date of any change.
-
- b) You must cause any work that you distribute or publish, that in
- whole or in part contains or is derived from the Program or any
- part thereof, to be licensed as a whole at no charge to all third
- parties under the terms of this License.
-
- c) If the modified program normally reads commands interactively
- when run, you must cause it, when started running for such
- interactive use in the most ordinary way, to print or display an
- announcement including an appropriate copyright notice and a
- notice that there is no warranty (or else, saying that you provide
- a warranty) and that users may redistribute the program under
- these conditions, and telling the user how to view a copy of this
- License. (Exception: if the Program itself is interactive but
- does not normally print such an announcement, your work based on
- the Program is not required to print an announcement.)
-
-These requirements apply to the modified work as a whole. If
-identifiable sections of that work are not derived from the Program,
-and can be reasonably considered independent and separate works in
-themselves, then this License, and its terms, do not apply to those
-sections when you distribute them as separate works. But when you
-distribute the same sections as part of a whole which is a work based
-on the Program, the distribution of the whole must be on the terms of
-this License, whose permissions for other licensees extend to the
-entire whole, and thus to each and every part regardless of who wrote it.
-
-Thus, it is not the intent of this section to claim rights or contest
-your rights to work written entirely by you; rather, the intent is to
-exercise the right to control the distribution of derivative or
-collective works based on the Program.
-
-In addition, mere aggregation of another work not based on the Program
-with the Program (or with a work based on the Program) on a volume of
-a storage or distribution medium does not bring the other work under
-the scope of this License.
-
- 3. You may copy and distribute the Program (or a work based on it,
-under Section 2) in object code or executable form under the terms of
-Sections 1 and 2 above provided that you also do one of the following:
-
- a) Accompany it with the complete corresponding machine-readable
- source code, which must be distributed under the terms of Sections
- 1 and 2 above on a medium customarily used for software interchange; or,
-
- b) Accompany it with a written offer, valid for at least three
- years, to give any third party, for a charge no more than your
- cost of physically performing source distribution, a complete
- machine-readable copy of the corresponding source code, to be
- distributed under the terms of Sections 1 and 2 above on a medium
- customarily used for software interchange; or,
-
- c) Accompany it with the information you received as to the offer
- to distribute corresponding source code. (This alternative is
- allowed only for noncommercial distribution and only if you
- received the program in object code or executable form with such
- an offer, in accord with Subsection b above.)
-
-The source code for a work means the preferred form of the work for
-making modifications to it. For an executable work, complete source
-code means all the source code for all modules it contains, plus any
-associated interface definition files, plus the scripts used to
-control compilation and installation of the executable. However, as a
-special exception, the source code distributed need not include
-anything that is normally distributed (in either source or binary
-form) with the major components (compiler, kernel, and so on) of the
-operating system on which the executable runs, unless that component
-itself accompanies the executable.
-
-If distribution of executable or object code is made by offering
-access to copy from a designated place, then offering equivalent
-access to copy the source code from the same place counts as
-distribution of the source code, even though third parties are not
-compelled to copy the source along with the object code.
-
- 4. You may not copy, modify, sublicense, or distribute the Program
-except as expressly provided under this License. Any attempt
-otherwise to copy, modify, sublicense or distribute the Program is
-void, and will automatically terminate your rights under this License.
-However, parties who have received copies, or rights, from you under
-this License will not have their licenses terminated so long as such
-parties remain in full compliance.
-
- 5. You are not required to accept this License, since you have not
-signed it. However, nothing else grants you permission to modify or
-distribute the Program or its derivative works. These actions are
-prohibited by law if you do not accept this License. Therefore, by
-modifying or distributing the Program (or any work based on the
-Program), you indicate your acceptance of this License to do so, and
-all its terms and conditions for copying, distributing or modifying
-the Program or works based on it.
-
- 6. Each time you redistribute the Program (or any work based on the
-Program), the recipient automatically receives a license from the
-original licensor to copy, distribute or modify the Program subject to
-these terms and conditions. You may not impose any further
-restrictions on the recipients' exercise of the rights granted herein.
-You are not responsible for enforcing compliance by third parties to
-this License.
-
- 7. If, as a consequence of a court judgment or allegation of patent
-infringement or for any other reason (not limited to patent issues),
-conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License. If you cannot
-distribute so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you
-may not distribute the Program at all. For example, if a patent
-license would not permit royalty-free redistribution of the Program by
-all those who receive copies directly or indirectly through you, then
-the only way you could satisfy both it and this License would be to
-refrain entirely from distribution of the Program.
-
-If any portion of this section is held invalid or unenforceable under
-any particular circumstance, the balance of the section is intended to
-apply and the section as a whole is intended to apply in other
-circumstances.
-
-It is not the purpose of this section to induce you to infringe any
-patents or other property right claims or to contest validity of any
-such claims; this section has the sole purpose of protecting the
-integrity of the free software distribution system, which is
-implemented by public license practices. Many people have made
-generous contributions to the wide range of software distributed
-through that system in reliance on consistent application of that
-system; it is up to the author/donor to decide if he or she is willing
-to distribute software through any other system and a licensee cannot
-impose that choice.
-
-This section is intended to make thoroughly clear what is believed to
-be a consequence of the rest of this License.
-
- 8. If the distribution and/or use of the Program is restricted in
-certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Program under this License
-may add an explicit geographical distribution limitation excluding
-those countries, so that distribution is permitted only in or among
-countries not thus excluded. In such case, this License incorporates
-the limitation as if written in the body of this License.
-
- 9. The Free Software Foundation may publish revised and/or new versions
-of the General Public License from time to time. Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
-Each version is given a distinguishing version number. If the Program
-specifies a version number of this License which applies to it and "any
-later version", you have the option of following the terms and conditions
-either of that version or of any later version published by the Free
-Software Foundation. If the Program does not specify a version number of
-this License, you may choose any version ever published by the Free Software
-Foundation.
-
- 10. If you wish to incorporate parts of the Program into other free
-programs whose distribution conditions are different, write to the author
-to ask for permission. For software which is copyrighted by the Free
-Software Foundation, write to the Free Software Foundation; we sometimes
-make exceptions for this. Our decision will be guided by the two goals
-of preserving the free status of all derivatives of our free software and
-of promoting the sharing and reuse of software generally.
-
- NO WARRANTY
-
- 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
-FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
-OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
-PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
-OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
-TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
-PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
-REPAIR OR CORRECTION.
-
- 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
-REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
-INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
-OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
-TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
-YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
-PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGES.
-
- END OF TERMS AND CONDITIONS
-
- How to Apply These Terms to Your New Programs
-
- If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
- To do so, attach the following notices to the program. It is safest
-to attach them to the start of each source file to most effectively
-convey the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
- <one line to give the program's name and a brief idea of what it does.>
- Copyright (C) <year> <name of author>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along
- with this program; if not, write to the Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-
-Also add information on how to contact you by electronic and paper mail.
-
-If the program is interactive, make it output a short notice like this
-when it starts in an interactive mode:
-
- Gnomovision version 69, Copyright (C) year name of author
- Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
- This is free software, and you are welcome to redistribute it
- under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License. Of course, the commands you use may
-be called something other than `show w' and `show c'; they could even be
-mouse-clicks or menu items--whatever suits your program.
-
-You should also get your employer (if you work as a programmer) or your
-school, if any, to sign a "copyright disclaimer" for the program, if
-necessary. Here is a sample; alter the names:
-
- Yoyodyne, Inc., hereby disclaims all copyright interest in the program
- `Gnomovision' (which makes passes at compilers) written by James Hacker.
-
- <signature of Ty Coon>, 1 April 1989
- Ty Coon, President of Vice
-
-This General Public License does not permit incorporating your program into
-proprietary programs. If your program is a subroutine library, you may
-consider it more useful to permit linking proprietary applications with the
-library. If this is what you want to do, use the GNU Lesser General
-Public License instead of this License.
diff --git a/LICENSE.GPL-3 b/LICENSE.GPL-3
new file mode 100644
index 0000000..94a9ed0
--- /dev/null
+++ b/LICENSE.GPL-3
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/LICENSE.PSF-2 b/LICENSE.PSF-2
deleted file mode 100644
index 28533b6..0000000
--- a/LICENSE.PSF-2
+++ /dev/null
@@ -1,48 +0,0 @@
-PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
---------------------------------------------
-
-1. This LICENSE AGREEMENT is between the Python Software Foundation
-("PSF"), and the Individual or Organization ("Licensee") accessing and
-otherwise using this software ("Python") in source or binary form and
-its associated documentation.
-
-2. Subject to the terms and conditions of this License Agreement, PSF
-hereby grants Licensee a nonexclusive, royalty-free, world-wide
-license to reproduce, analyze, test, perform and/or display publicly,
-prepare derivative works, distribute, and otherwise use Python
-alone or in any derivative version, provided, however, that PSF's
-License Agreement and PSF's notice of copyright, i.e., "Copyright (c)
-2001, 2002, 2003, 2004, 2005, 2006, 2007 Python Software Foundation;
-All Rights Reserved" are retained in Python alone or in any derivative
-version prepared by Licensee.
-
-3. In the event Licensee prepares a derivative work that is based on
-or incorporates Python or any part thereof, and wants to make
-the derivative work available to others as provided herein, then
-Licensee hereby agrees to include in any such work a brief summary of
-the changes made to Python.
-
-4. PSF is making Python available to Licensee on an "AS IS"
-basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
-IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
-DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
-FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
-INFRINGE ANY THIRD PARTY RIGHTS.
-
-5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
-FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
-A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
-OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
-
-6. This License Agreement will automatically terminate upon a material
-breach of its terms and conditions.
-
-7. Nothing in this License Agreement shall be deemed to create any
-relationship of agency, partnership, or joint venture between PSF and
-Licensee. This License Agreement does not grant permission to use PSF
-trademarks or trade name in a trademark sense to endorse or promote
-products or services of Licensee, or any third party.
-
-8. By copying, installing or otherwise using Python, Licensee
-agrees to be bound by the terms and conditions of this License
-Agreement.
diff --git a/MANIFEST.in b/MANIFEST.in
index ef71641..5cc7ba8 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,4 +1,4 @@
include MANIFEST.in
include LICENSE.*
include ChangeLog
-include TODO
+recursive-include doc *
diff --git a/PKG-INFO b/PKG-INFO
index df8f553..352c1ba 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,13 +1,13 @@
-Metadata-Version: 1.0
+Metadata-Version: 1.1
Name: python-daemon
-Version: 1.5.5
+Version: 1.6.1
Summary: Library to implement a well-behaved Unix daemon process.
Home-page: http://pypi.python.org/pypi/python-daemon/
Author: Ben Finney
Author-email: ben+python@benfinney.id.au
-License: PSF-2+
+License: Apache-2
Description: This library implements the well-behaved daemon specification of
- :pep:`3143`, "Standard daemon process library".
+ :pep:`3143`, “Standard daemon process library”.
A well-behaved Unix daemon process is tricky to get right, but the
required steps are much the same for every daemon program. A
@@ -17,12 +17,12 @@ Description: This library implements the well-behaved daemon specification of
Simple example of usage::
- import daemon
+ import daemon
- from spam import do_main_program
+ from spam import do_main_program
- with daemon.DaemonContext():
- do_main_program()
+ with daemon.DaemonContext():
+ do_main_program()
Customisation of the steps to become a daemon is available by
setting options on the `DaemonContext` instance; see the
@@ -30,8 +30,9 @@ Description: This library implements the well-behaved daemon specification of
Keywords: daemon,fork,unix
Platform: UNKNOWN
Classifier: Development Status :: 4 - Beta
-Classifier: License :: OSI Approved :: Python Software Foundation License
+Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: POSIX
-Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Libraries :: Python Modules
diff --git a/daemon/__init__.py b/daemon/__init__.py
index d8dc171..2bd0281 100644
--- a/daemon/__init__.py
+++ b/daemon/__init__.py
@@ -1,20 +1,20 @@
# -*- coding: utf-8 -*-
# daemon/__init__.py
-# Part of python-daemon, an implementation of PEP 3143.
+# Part of ‘python-daemon’, an implementation of PEP 3143.
#
-# Copyright © 2009–2010 Ben Finney <ben+python@benfinney.id.au>
+# Copyright © 2009–2014 Ben Finney <ben+python@benfinney.id.au>
# Copyright © 2006 Robert Niederreiter
#
# This is free software: you may copy, modify, and/or distribute this work
-# under the terms of the Python Software Foundation License, version 2 or
-# later as published by the Python Software Foundation.
-# No warranty expressed or implied. See the file LICENSE.PSF-2 for details.
+# under the terms of the Apache License, version 2.0 as published by the
+# Apache Software Foundation.
+# No warranty expressed or implied. See the file LICENSE.ASF-2 for details.
""" Library to implement a well-behaved Unix daemon process.
This library implements the well-behaved daemon specification of
- :pep:`3143`, "Standard daemon process library".
+ :pep:`3143`, “Standard daemon process library”.
A well-behaved Unix daemon process is tricky to get right, but the
required steps are much the same for every daemon program. A
@@ -37,11 +37,20 @@
"""
-import version
-from daemon import DaemonContext
+from __future__ import (absolute_import, unicode_literals)
+
+from . import version
+from .daemon import DaemonContext
_version = version.version
_copyright = version.copyright
_license = version.license
_url = "http://pypi.python.org/pypi/python-daemon/"
+
+
+# Local variables:
+# coding: utf-8
+# mode: python
+# End:
+# vim: fileencoding=utf-8 filetype=python :
diff --git a/daemon/daemon.py b/daemon/daemon.py
index 28db695..b998966 100644
--- a/daemon/daemon.py
+++ b/daemon/daemon.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
# daemon/daemon.py
-# Part of python-daemon, an implementation of PEP 3143.
+# Part of ‘python-daemon’, an implementation of PEP 3143.
#
-# Copyright © 2008–2010 Ben Finney <ben+python@benfinney.id.au>
+# Copyright © 2008–2014 Ben Finney <ben+python@benfinney.id.au>
# Copyright © 2007–2008 Robert Niederreiter, Jens Klein
# Copyright © 2004–2005 Chad J. Schroeder
# Copyright © 2003 Clark Evans
@@ -11,13 +11,15 @@
# Copyright © 2001 Jürgen Hermann
#
# This is free software: you may copy, modify, and/or distribute this work
-# under the terms of the Python Software Foundation License, version 2 or
-# later as published by the Python Software Foundation.
-# No warranty expressed or implied. See the file LICENSE.PSF-2 for details.
+# under the terms of the Apache License, version 2.0 as published by the
+# Apache Software Foundation.
+# No warranty expressed or implied. See the file LICENSE.ASF-2 for details.
""" Daemon process behaviour.
"""
+from __future__ import (absolute_import, unicode_literals)
+
import os
import sys
import resource
@@ -26,6 +28,14 @@ import signal
import socket
import atexit
+
+try:
+ # Base type of strings in Python 2.
+ basestring
+except NameError:
+ # Python 3 only has one string type.
+ basestring = str
+
class DaemonError(Exception):
""" Base exception class for errors from this module. """
@@ -204,21 +214,21 @@ class DaemonContext(object):
"""
def __init__(
- self,
- chroot_directory=None,
- working_directory='/',
- umask=0,
- uid=None,
- gid=None,
- prevent_core=True,
- detach_process=None,
- files_preserve=None,
- pidfile=None,
- stdin=None,
- stdout=None,
- stderr=None,
- signal_map=None,
- ):
+ self,
+ chroot_directory=None,
+ working_directory='/',
+ umask=0,
+ uid=None,
+ gid=None,
+ prevent_core=True,
+ detach_process=None,
+ files_preserve=None,
+ pidfile=None,
+ stdin=None,
+ stdout=None,
+ stderr=None,
+ signal_map=None,
+ ):
""" Set up a new instance. """
self.chroot_directory = chroot_directory
self.working_directory = working_directory
@@ -394,8 +404,8 @@ class DaemonContext(object):
"""
exception = SystemExit(
- "Terminating on signal %(signal_number)r"
- % vars())
+ "Terminating on signal %(signal_number)r"
+ % vars())
raise exception
def _get_exclude_file_descriptors(self):
@@ -418,8 +428,8 @@ class DaemonContext(object):
if files_preserve is None:
files_preserve = []
files_preserve.extend(
- item for item in [self.stdin, self.stdout, self.stderr]
- if hasattr(item, 'fileno'))
+ item for item in [self.stdin, self.stdout, self.stderr]
+ if hasattr(item, 'fileno'))
exclude_descriptors = set()
for item in files_preserve:
if item is None:
@@ -458,8 +468,8 @@ class DaemonContext(object):
"""
signal_handler_map = dict(
- (signal_number, self._make_signal_handler(target))
- for (signal_number, target) in self.signal_map.items())
+ (signal_number, self._make_signal_handler(target))
+ for (signal_number, target) in self.signal_map.items())
return signal_handler_map
@@ -468,10 +478,10 @@ def change_working_directory(directory):
"""
try:
os.chdir(directory)
- except Exception, exc:
+ except Exception as exc:
error = DaemonOSEnvironmentError(
- "Unable to change working directory (%(exc)s)"
- % vars())
+ "Unable to change working directory (%(exc)s)"
+ % vars())
raise error
@@ -486,10 +496,10 @@ def change_root_directory(directory):
try:
os.chdir(directory)
os.chroot(directory)
- except Exception, exc:
+ except Exception as exc:
error = DaemonOSEnvironmentError(
- "Unable to change root directory (%(exc)s)"
- % vars())
+ "Unable to change root directory (%(exc)s)"
+ % vars())
raise error
@@ -498,10 +508,10 @@ def change_file_creation_mask(mask):
"""
try:
os.umask(mask)
- except Exception, exc:
+ except Exception as exc:
error = DaemonOSEnvironmentError(
- "Unable to change file creation mask (%(exc)s)"
- % vars())
+ "Unable to change file creation mask (%(exc)s)"
+ % vars())
raise error
@@ -516,10 +526,10 @@ def change_process_owner(uid, gid):
try:
os.setgid(gid)
os.setuid(uid)
- except Exception, exc:
+ except Exception as exc:
error = DaemonOSEnvironmentError(
- "Unable to change file creation mask (%(exc)s)"
- % vars())
+ "Unable to change process owner (%(exc)s)"
+ % vars())
raise error
@@ -535,15 +545,15 @@ def prevent_core_dump():
try:
# Ensure the resource limit exists on this platform, by requesting
- # its current value
+ # its current value.
core_limit_prev = resource.getrlimit(core_resource)
- except ValueError, exc:
+ except ValueError as exc:
error = DaemonOSEnvironmentError(
- "System does not support RLIMIT_CORE resource limit (%(exc)s)"
- % vars())
+ "System does not support RLIMIT_CORE resource limit (%(exc)s)"
+ % vars())
raise error
- # Set hard and soft limits to zero, i.e. no core dump at all
+ # Set hard and soft limits to zero, i.e. no core dump at all.
core_limit = (0, 0)
resource.setrlimit(core_resource, core_limit)
@@ -571,11 +581,12 @@ def detach_process_context():
pid = os.fork()
if pid > 0:
os._exit(0)
- except OSError, exc:
+ except OSError as exc:
exc_errno = exc.errno
exc_strerror = exc.strerror
error = DaemonProcessDetachError(
- "%(error_message)s: [%(exc_errno)d] %(exc_strerror)s" % vars())
+ "%(error_message)s: [%(exc_errno)d] %(exc_strerror)s"
+ % vars())
raise error
fork_then_exit_parent(error_message="Failed first fork")
@@ -612,17 +623,17 @@ def is_socket(fd):
try:
socket_type = file_socket.getsockopt(
- socket.SOL_SOCKET, socket.SO_TYPE)
- except socket.error, exc:
+ socket.SOL_SOCKET, socket.SO_TYPE)
+ except socket.error as exc:
exc_errno = exc.args[0]
if exc_errno == errno.ENOTSOCK:
- # Socket operation on non-socket
+ # Socket operation on non-socket.
pass
else:
- # Some other socket error
+ # Some other socket error.
result = True
else:
- # No error getting socket type
+ # No error getting socket type.
result = True
return result
@@ -673,15 +684,15 @@ def close_file_descriptor_if_open(fd):
"""
try:
os.close(fd)
- except OSError, exc:
+ except OSError as exc:
if exc.errno == errno.EBADF:
- # File descriptor was not open
+ # File descriptor was not open.
pass
else:
error = DaemonOSEnvironmentError(
- "Failed to close file descriptor %(fd)d"
- " (%(exc)s)"
- % vars())
+ "Failed to close file descriptor %(fd)d"
+ " (%(exc)s)"
+ % vars())
raise error
@@ -742,15 +753,15 @@ def make_default_signal_map():
"""
name_map = {
- 'SIGTSTP': None,
- 'SIGTTIN': None,
- 'SIGTTOU': None,
- 'SIGTERM': 'terminate',
- }
+ 'SIGTSTP': None,
+ 'SIGTTIN': None,
+ 'SIGTTOU': None,
+ 'SIGTERM': 'terminate',
+ }
signal_map = dict(
- (getattr(signal, name), target)
- for (name, target) in name_map.items()
- if hasattr(signal, name))
+ (getattr(signal, name), target)
+ for (name, target) in name_map.items()
+ if hasattr(signal, name))
return signal_map
@@ -774,3 +785,10 @@ def register_atexit_function(func):
"""
atexit.register(func)
+
+
+# Local variables:
+# coding: utf-8
+# mode: python
+# End:
+# vim: fileencoding=utf-8 filetype=python :
diff --git a/daemon/pidfile.py b/daemon/pidfile.py
new file mode 100644
index 0000000..3248aca
--- /dev/null
+++ b/daemon/pidfile.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+
+# daemon/pidfile.py
+# Part of ‘python-daemon’, an implementation of PEP 3143.
+#
+# Copyright © 2008–2014 Ben Finney <ben+python@benfinney.id.au>
+#
+# This is free software: you may copy, modify, and/or distribute this work
+# under the terms of the Apache License, version 2.0 as published by the
+# Apache Software Foundation.
+# No warranty expressed or implied. See the file LICENSE.ASF-2 for details.
+
+""" Lockfile behaviour implemented via Unix PID files.
+ """
+
+from __future__ import (absolute_import, unicode_literals)
+
+from lockfile.pidlockfile import PIDLockFile
+
+
+class TimeoutPIDLockFile(PIDLockFile, object):
+ """ Lockfile with default timeout, implemented as a Unix PID file.
+
+ This uses the ``PIDLockFile`` implementation, with the
+ following changes:
+
+ * The `acquire_timeout` parameter to the initialiser will be
+ used as the default `timeout` parameter for the `acquire`
+ method.
+
+ """
+
+ def __init__(self, path, acquire_timeout=None, *args, **kwargs):
+ """ Set up the parameters of a TimeoutPIDLockFile. """
+ self.acquire_timeout = acquire_timeout
+ super(TimeoutPIDLockFile, self).__init__(path, *args, **kwargs)
+
+ def acquire(self, timeout=None, *args, **kwargs):
+ """ Acquire the lock. """
+ if timeout is None:
+ timeout = self.acquire_timeout
+ super(TimeoutPIDLockFile, self).acquire(timeout, *args, **kwargs)
+
+
+# Local variables:
+# coding: utf-8
+# mode: python
+# End:
+# vim: fileencoding=utf-8 filetype=python :
diff --git a/daemon/pidlockfile.py b/daemon/pidlockfile.py
deleted file mode 100644
index c38beae..0000000
--- a/daemon/pidlockfile.py
+++ /dev/null
@@ -1,194 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# daemon/pidlockfile.py
-# Part of python-daemon, an implementation of PEP 3143.
-#
-# Copyright © 2008–2010 Ben Finney <ben+python@benfinney.id.au>
-#
-# This is free software: you may copy, modify, and/or distribute this work
-# under the terms of the Python Software Foundation License, version 2 or
-# later as published by the Python Software Foundation.
-# No warranty expressed or implied. See the file LICENSE.PSF-2 for details.
-
-""" Lockfile behaviour implemented via Unix PID files.
- """
-
-import os
-import errno
-
-from lockfile import (
- LinkFileLock,
- AlreadyLocked, LockFailed,
- NotLocked, NotMyLock,
- )
-
-
-class PIDFileError(Exception):
- """ Abstract base class for errors specific to PID files. """
-
-class PIDFileParseError(ValueError, PIDFileError):
- """ Raised when parsing contents of PID file fails. """
-
-
-class PIDLockFile(LinkFileLock, object):
- """ Lockfile implemented as a Unix PID file.
-
- The PID file is named by the attribute `path`. When locked,
- the file will be created with a single line of text,
- containing the process ID (PID) of the process that acquired
- the lock.
-
- The lock is acquired and maintained as per `LinkFileLock`.
-
- """
-
- def read_pid(self):
- """ Get the PID from the lock file.
- """
- result = read_pid_from_pidfile(self.path)
- return result
-
- def acquire(self, *args, **kwargs):
- """ Acquire the lock.
-
- Locks the PID file then creates the PID file for this
- lock. The `timeout` parameter is used as for the
- `LinkFileLock` class.
-
- """
- super(PIDLockFile, self).acquire(*args, **kwargs)
- try:
- write_pid_to_pidfile(self.path)
- except OSError, exc:
- error = LockFailed("%(exc)s" % vars())
- raise error
-
- def release(self):
- """ Release the lock.
-
- Removes the PID file then releases the lock, or raises an
- error if the current process does not hold the lock.
-
- """
- if self.i_am_locking():
- remove_existing_pidfile(self.path)
- super(PIDLockFile, self).release()
-
- def break_lock(self):
- """ Break an existing lock.
-
- If the lock is held, breaks the lock and removes the PID
- file.
-
- """
- super(PIDLockFile, self).break_lock()
- remove_existing_pidfile(self.path)
-
-
-class TimeoutPIDLockFile(PIDLockFile):
- """ Lockfile with default timeout, implemented as a Unix PID file.
-
- This uses the ``PIDLockFile`` implementation, with the
- following changes:
-
- * The `acquire_timeout` parameter to the initialiser will be
- used as the default `timeout` parameter for the `acquire`
- method.
-
- """
-
- def __init__(self, path, acquire_timeout=None, *args, **kwargs):
- """ Set up the parameters of a DaemonRunnerLock. """
- self.acquire_timeout = acquire_timeout
- super(TimeoutPIDLockFile, self).__init__(path, *args, **kwargs)
-
- def acquire(self, timeout=None, *args, **kwargs):
- """ Acquire the lock. """
- if timeout is None:
- timeout = self.acquire_timeout
- super(TimeoutPIDLockFile, self).acquire(timeout, *args, **kwargs)
-
-
-def read_pid_from_pidfile(pidfile_path):
- """ Read the PID recorded in the named PID file.
-
- Read and return the numeric PID recorded as text in the named
- PID file. If the PID file does not exist, return ``None``. If
- the content is not a valid PID, raise ``PIDFileParseError``.
-
- """
- pid = None
- pidfile = None
- try:
- pidfile = open(pidfile_path, 'r')
- except IOError, exc:
- if exc.errno == errno.ENOENT:
- pass
- else:
- raise
-
- if pidfile:
- # According to the FHS 2.3 section on PID files in ‘/var/run’:
- #
- # The file must consist of the process identifier in
- # ASCII-encoded decimal, followed by a newline character. …
- #
- # Programs that read PID files should be somewhat flexible
- # in what they accept; i.e., they should ignore extra
- # whitespace, leading zeroes, absence of the trailing
- # newline, or additional lines in the PID file.
-
- line = pidfile.readline().strip()
- try:
- pid = int(line)
- except ValueError:
- raise PIDFileParseError(
- "PID file %(pidfile_path)r contents invalid" % vars())
- pidfile.close()
-
- return pid
-
-
-def write_pid_to_pidfile(pidfile_path):
- """ Write the PID in the named PID file.
-
- Get the numeric process ID (“PID”) of the current process
- and write it to the named file as a line of text.
-
- """
- open_flags = (os.O_CREAT | os.O_EXCL | os.O_WRONLY)
- open_mode = (
- ((os.R_OK | os.W_OK) << 6) |
- ((os.R_OK) << 3) |
- ((os.R_OK)))
- pidfile_fd = os.open(pidfile_path, open_flags, open_mode)
- pidfile = os.fdopen(pidfile_fd, 'w')
-
- # According to the FHS 2.3 section on PID files in ‘/var/run’:
- #
- # The file must consist of the process identifier in
- # ASCII-encoded decimal, followed by a newline character. For
- # example, if crond was process number 25, /var/run/crond.pid
- # would contain three characters: two, five, and newline.
-
- pid = os.getpid()
- line = "%(pid)d\n" % vars()
- pidfile.write(line)
- pidfile.close()
-
-
-def remove_existing_pidfile(pidfile_path):
- """ Remove the named PID file if it exists.
-
- Remove the named PID file. Ignore the condition if the file
- does not exist, since that only means we are already in the
- desired state.
-
- """
- try:
- os.remove(pidfile_path)
- except OSError, exc:
- if exc.errno == errno.ENOENT:
- pass
- else:
- raise
diff --git a/daemon/runner.py b/daemon/runner.py
index 0642695..748daeb 100644
--- a/daemon/runner.py
+++ b/daemon/runner.py
@@ -1,30 +1,33 @@
# -*- coding: utf-8 -*-
# daemon/runner.py
-# Part of python-daemon, an implementation of PEP 3143.
+# Part of ‘python-daemon’, an implementation of PEP 3143.
#
-# Copyright © 2009–2010 Ben Finney <ben+python@benfinney.id.au>
+# Copyright © 2009–2014 Ben Finney <ben+python@benfinney.id.au>
# Copyright © 2007–2008 Robert Niederreiter, Jens Klein
# Copyright © 2003 Clark Evans
# Copyright © 2002 Noah Spurrier
# Copyright © 2001 Jürgen Hermann
#
# This is free software: you may copy, modify, and/or distribute this work
-# under the terms of the Python Software Foundation License, version 2 or
-# later as published by the Python Software Foundation.
-# No warranty expressed or implied. See the file LICENSE.PSF-2 for details.
+# under the terms of the Apache License, version 2.0 as published by the
+# Apache Software Foundation.
+# No warranty expressed or implied. See the file LICENSE.ASF-2 for details.
""" Daemon runner library.
"""
+from __future__ import (absolute_import, unicode_literals)
+
import sys
import os
import signal
import errno
-import pidlockfile
+import lockfile
-from daemon import DaemonContext
+from . import pidfile
+from .daemon import DaemonContext
class DaemonRunnerError(Exception):
@@ -79,12 +82,12 @@ class DaemonRunner(object):
self.daemon_context.stdin = open(app.stdin_path, 'r')
self.daemon_context.stdout = open(app.stdout_path, 'w+')
self.daemon_context.stderr = open(
- app.stderr_path, 'w+', buffering=0)
+ app.stderr_path, 'w+', buffering=0)
self.pidfile = None
if app.pidfile_path is not None:
self.pidfile = make_pidlockfile(
- app.pidfile_path, app.pidfile_timeout)
+ app.pidfile_path, app.pidfile_timeout)
self.daemon_context.pidfile = self.pidfile
def _usage_exit(self, argv):
@@ -107,7 +110,7 @@ class DaemonRunner(object):
if len(argv) < min_args:
self._usage_exit(argv)
- self.action = argv[1]
+ self.action = unicode(argv[1])
if self.action not in self.action_funcs:
self._usage_exit(argv)
@@ -119,10 +122,10 @@ class DaemonRunner(object):
try:
self.daemon_context.open()
- except pidlockfile.AlreadyLocked:
+ except lockfile.AlreadyLocked:
pidfile_path = self.pidfile.path
raise DaemonRunnerStartFailureError(
- "PID file %(pidfile_path)r already locked" % vars())
+ "PID file %(pidfile_path)r already locked" % vars())
pid = os.getpid()
message = self.start_message % vars()
@@ -136,9 +139,9 @@ class DaemonRunner(object):
pid = self.pidfile.read_pid()
try:
os.kill(pid, signal.SIGTERM)
- except OSError, exc:
+ except OSError as exc:
raise DaemonRunnerStopFailureError(
- "Failed to terminate %(pid)d: %(exc)s" % vars())
+ "Failed to terminate %(pid)d: %(exc)s" % vars())
def _stop(self):
""" Exit the daemon process specified in the current PID file.
@@ -146,7 +149,7 @@ class DaemonRunner(object):
if not self.pidfile.is_locked():
pidfile_path = self.pidfile.path
raise DaemonRunnerStopFailureError(
- "PID file %(pidfile_path)r not locked" % vars())
+ "PID file %(pidfile_path)r not locked" % vars())
if is_pidfile_stale(self.pidfile):
self.pidfile.break_lock()
@@ -160,10 +163,10 @@ class DaemonRunner(object):
self._start()
action_funcs = {
- 'start': _start,
- 'stop': _stop,
- 'restart': _restart,
- }
+ 'start': _start,
+ 'stop': _stop,
+ 'restart': _restart,
+ }
def _get_action_func(self):
""" Return the function for the specified action.
@@ -176,7 +179,7 @@ class DaemonRunner(object):
func = self.action_funcs[self.action]
except KeyError:
raise DaemonRunnerInvalidActionError(
- "Unknown action: %(action)r" % vars(self))
+ "Unknown action: %(action)r" % vars(self))
return func
def do_action(self):
@@ -202,7 +205,7 @@ def make_pidlockfile(path, acquire_timeout):
if not os.path.isabs(path):
error = ValueError("Not an absolute path: %(path)r" % vars())
raise error
- lockfile = pidlockfile.TimeoutPIDLockFile(path, acquire_timeout)
+ lockfile = pidfile.TimeoutPIDLockFile(path, acquire_timeout)
return lockfile
@@ -221,9 +224,16 @@ def is_pidfile_stale(pidfile):
if pidfile_pid is not None:
try:
os.kill(pidfile_pid, signal.SIG_DFL)
- except OSError, exc:
+ except OSError as exc:
if exc.errno == errno.ESRCH:
# The specified PID does not exist
result = True
return result
+
+
+# Local variables:
+# coding: utf-8
+# mode: python
+# End:
+# vim: fileencoding=utf-8 filetype=python :
diff --git a/daemon/version/__init__.py b/daemon/version/__init__.py
index d2eafa6..2efb9d1 100644
--- a/daemon/version/__init__.py
+++ b/daemon/version/__init__.py
@@ -1,36 +1,46 @@
# -*- coding: utf-8 -*-
# daemon/version/__init__.py
-# Part of python-daemon, an implementation of PEP 3143.
+# Part of ‘python-daemon’, an implementation of PEP 3143.
+#
+# Copyright © 2008–2014 Ben Finney <ben+python@benfinney.id.au>
#
-# Copyright © 2008–2010 Ben Finney <ben+python@benfinney.id.au>
# This is free software: you may copy, modify, and/or distribute this work
-# under the terms of the Python Software Foundation License, version 2 or
-# later as published by the Python Software Foundation.
-# No warranty expressed or implied. See the file LICENSE.PSF-2 for details.
+# under the terms of the Apache License, version 2.0 as published by the
+# Apache Software Foundation.
+# No warranty expressed or implied. See the file LICENSE.ASF-2 for details.
+
+""" Version information for the ‘python-daemon’ distribution. """
-""" Version information for the python-daemon distribution. """
+from __future__ import (absolute_import, unicode_literals)
-from version_info import version_info
+from .version_info import version_info
-version_info['version_string'] = u"1.5.5"
+version_info['version_string'] = "1.6.1"
-version_short = u"%(version_string)s" % version_info
-version_full = u"%(version_string)s.r%(revno)s" % version_info
+version_short = "%(version_string)s" % version_info
+version_full = "%(version_string)s.r%(revno)s" % version_info
version = version_short
-author_name = u"Ben Finney"
-author_email = u"ben+python@benfinney.id.au"
-author = u"%(author_name)s <%(author_email)s>" % vars()
+author_name = "Ben Finney"
+author_email = "ben+python@benfinney.id.au"
+author = "%(author_name)s <%(author_email)s>" % vars()
-copyright_year_begin = u"2001"
+copyright_year_begin = "2001"
date = version_info['date'].split(' ', 1)[0]
copyright_year = date.split('-')[0]
copyright_year_range = copyright_year_begin
if copyright_year > copyright_year_begin:
- copyright_year_range += u"–%(copyright_year)s" % vars()
+ copyright_year_range += "–%(copyright_year)s" % vars()
copyright = (
- u"Copyright © %(copyright_year_range)s %(author)s and others"
- ) % vars()
-license = u"PSF-2+"
+ "Copyright © %(copyright_year_range)s %(author)s and others"
+ ) % vars()
+license = "Apache-2"
+
+
+# Local variables:
+# coding: utf-8
+# mode: python
+# End:
+# vim: fileencoding=utf-8 filetype=python :
diff --git a/daemon/version/version_info.py b/daemon/version/version_info.py
index cdbf280..3a768cf 100644
--- a/daemon/version/version_info.py
+++ b/daemon/version/version_info.py
@@ -5,19 +5,18 @@ So don't edit it. :)
"""
version_info = {'branch_nick': u'python-daemon.devel',
- 'build_date': '2009-05-22 19:50:06 +1000',
+ 'build_date': '2014-08-04 15:17:39 +1000',
'clean': None,
- 'date': '2009-05-22 19:47:30 +1000',
- 'revision_id': 'ben+python@benfinney.id.au-20090522094730-p4vsa0reh7ktt4e1',
- 'revno': 145}
+ 'date': '2014-08-04 15:15:33 +1000',
+ 'revision_id': 'ben+python@benfinney.id.au-20140804051533-cg428nfiuigo0kax',
+ 'revno': '241'}
revisions = {}
file_revisions = {}
-
if __name__ == '__main__':
- print 'revision: %(revno)d' % version_info
+ print 'revision: %(revno)s' % version_info
print 'nick: %(branch_nick)s' % version_info
print 'revision id: %(revision_id)s' % version_info
diff --git a/doc/FAQ b/doc/FAQ
new file mode 100644
index 0000000..c8148c4
--- /dev/null
+++ b/doc/FAQ
@@ -0,0 +1,143 @@
+‘python-daemon’ Frequently Asked Questions
+##########################################
+
+:Author: Ben Finney <ben+python@benfinney.id.au>
+:Updated: 2014-05-21
+
+..
+ This is free software: you may copy, modify, and/or distribute this work
+ under the terms of the Apache License version 2.0 as published by the
+ Apache Software Foundation.
+ No warranty expressed or implied. See the file LICENSE.ASF-2 for details.
+
+.. contents::
+..
+ 1 General
+ 1.1 What is the purpose of the ‘python-daemon’ library?
+ 1.2 How can I run a service communicating with a separate daemon process?
+ 2 File descriptors
+ 2.1 Why does the output stop after opening the daemon context?
+ 2.2 How can I preserve a ‘logging’ handler's file descriptor?
+
+General
+=======
+
+What is the purpose of the ‘python-daemon’ library?
+---------------------------------------------------
+
+The ‘python-daemon’ library has a deliberately narrow focus: that of
+being a reference implementation for `PEP 3143`_, “Standard daemon
+process library”.
+
+.. _`PEP 3143`: http://www.python.org/dev/peps/pep-3143
+
+How can I run a service communicating with a separate daemon process?
+---------------------------------------------------------------------
+
+As specified in `PEP 3143`_, the ‘python-daemon’ library is
+specifically focussed on the goal of having the *current running
+program* become a well-behaved Unix daemon process. This leaves open
+the question of how this program is started, or about multiple
+programs interacting. As detailed in PEP 3143:
+
+ A daemon is not a service
+
+ There is a related concept in many systems, called a “service”. A
+ service differs from the model in this PEP, in that rather than
+ having the *current* program continue to run as a daemon process,
+ a service starts an *additional* process to run in the background,
+ and the current process communicates with that additional process
+ via some defined channels.
+
+ The Unix-style daemon model in this PEP can be used, among other
+ things, to implement the background-process part of a service; but
+ this PEP does not address the other aspects of setting up and
+ managing a service.
+
+A possible starting point for such a “service” model of execution is
+in a `message from 2009-01-30`_ to the ``python-ideas`` forum.
+
+.. _`message from 2009-01-30`: http://mail.python.org/pipermail/python-ideas/2009-January/002606.html
+
+
+File descriptors
+================
+
+Why does the output stop after opening the daemon context?
+----------------------------------------------------------
+
+The specified behaviour in `PEP 3143`_ includes the requirement to
+detach the process from the controlling terminal (to allow the process
+to continue to run as a daemon), and to close all file descriptors not
+known to be safe once detached (to ensure any files that continue to
+be used are under the control of the daemon process).
+
+If you want the process to generate output via the system streams
+‘sys.stdout’ and ‘sys.stderr’, set the ‘DaemonContext’'s ‘stdout’
+and/or ‘stderr’ options to a file-like object (e.g. the ‘stream’
+attribute of a ‘logging.Handler’ instance). If these objects have file
+descriptors, they will be preserved when the daemon context opens.
+
+How can I preserve a ‘logging’ handler's file descriptor?
+---------------------------------------------------------
+
+The ‘DaemonContext.open’ method conforms to `PEP 3143`_ by closing all
+open file descriptors, but excluding those files specified in the
+‘files_preserve’ option. This option is a list of files or file
+descriptors.
+
+The Python standard library ‘logging’ module provides log handlers
+that write to streams, including to files via the ‘StreamHandler’
+class and its sub-classes. The documentation (both the online `logging
+module documentation`_ and the docstrings for the code) makes no
+mention of a way to get at the stream associated with a handler
+object.
+
+However, looking at the source code for ‘StreamHandler’, in Python 2.5
+as ``/usr/lib/python2.5/logging/__init__.py``, shows a ‘stream’
+attribute that is bound to the stream object. The attribute is not
+marked private (i.e. it is not named with a leading underscore), so we
+can presume it is part of the public API.
+
+That attribute can then be used to specify that a logging handler's
+file descriptor should, when the ‘DaemonContext’ opens, be excluded
+from closure::
+
+ import logging
+ import daemon
+
+ # any subclass of StreamHandler should provide the ‘stream’ attribute.
+ lh = logging.handlers.TimedRotatingFileHandler(
+ "/var/log/foo.log",
+ # …
+ )
+
+ # … do some logging and other activity …
+
+ daemon_context = daemon.DaemonContext()
+ daemon_context.files_preserve = [lh.stream]
+
+ daemon_context.open()
+
+ # … continue as a daemon process …
+
+.. _`logging module documentation`: http://docs.python.org/library/logging
+
+
+..
+ This is free software: you may copy, modify, and/or distribute this work
+ under the terms of the Apache License version 2.0 as published by the
+ Apache Software Foundation.
+ No warranty expressed or implied. See the file LICENSE.ASF-2 for details.
+
+..
+ Local variables:
+ coding: utf-8
+ mode: text
+ mode: rst
+ time-stamp-format: "%:y-%02m-%02d"
+ time-stamp-start: "^:Updated:[ ]+"
+ time-stamp-end: "$"
+ time-stamp-line-limit: 20
+ End:
+ vim: fileencoding=utf-8 filetype=rst :
diff --git a/doc/TODO b/doc/TODO
new file mode 100644
index 0000000..94a1e72
--- /dev/null
+++ b/doc/TODO
@@ -0,0 +1,89 @@
+TODO for ‘python-daemon’ library
+################################
+
+=======
+PENDING
+=======
+
+Tests
+=====
+
+* Write full unit tests for every new or changed behaviour at time of
+ commit.
+
+Libraries
+=========
+
+* Work correctly with current ‘lockfile’ library (0.9 or later).
+
+* Evaluate switching to ‘flufl.lock’ library for PID lockfile behaviour
+ <http://pypi.python.org/pypi/flufl.lock>_.
+
+Features
+========
+
+Important
+---------
+
+Wishlist
+--------
+
+* Allow specification of a syslog service name to log as (default:
+ output to stdout and stderr, not syslog).
+
+Documentation
+=============
+
+Standard library inclusion
+==========================
+
+* Convert to Python 3.
+
+
+====
+DONE
+====
+
+* Detect whether started by another process that handles
+ daemonisation, such as ‘inetd’, and behave appropriately.
+
+* Detach to new process and session group.
+
+* Allow specification of working directory (default: '/').
+
+* Allow specification of umask (default: 0000).
+
+* Drop ‘suid’ and ‘sgid’ privileges if set.
+
+* Close all open file handles.
+
+* Re-open stdin, stdout, stderr to user-specified files.
+
+* Default re-open stdin, stdout, stderr to ‘/dev/null’.
+
+* Allow specification of a non-root user and group to drop to, if
+ started as ‘root’ (default: no change of user or group).
+
+* Implement context manager protocol for daemon context.
+
+* Allow specification of PID file with its own context manager
+ (default: no PID file).
+
+* Full docstrings for functions, classes, and modules.
+
+* PEP 3143 for adding this library to the Python standard library.
+
+
+..
+ This is free software: you may copy, modify, and/or distribute this work
+ under the terms of the Apache License version 2.0 as published by the
+ Apache Software Foundation.
+ No warranty expressed or implied. See the file LICENSE.ASF-2 for details.
+
+..
+ Local variables:
+ coding: utf-8
+ mode: text
+ mode: rst
+ End:
+ vim: fileencoding=utf-8 filetype=rst :
diff --git a/python_daemon.egg-info/PKG-INFO b/python_daemon.egg-info/PKG-INFO
index df8f553..352c1ba 100644
--- a/python_daemon.egg-info/PKG-INFO
+++ b/python_daemon.egg-info/PKG-INFO
@@ -1,13 +1,13 @@
-Metadata-Version: 1.0
+Metadata-Version: 1.1
Name: python-daemon
-Version: 1.5.5
+Version: 1.6.1
Summary: Library to implement a well-behaved Unix daemon process.
Home-page: http://pypi.python.org/pypi/python-daemon/
Author: Ben Finney
Author-email: ben+python@benfinney.id.au
-License: PSF-2+
+License: Apache-2
Description: This library implements the well-behaved daemon specification of
- :pep:`3143`, "Standard daemon process library".
+ :pep:`3143`, “Standard daemon process library”.
A well-behaved Unix daemon process is tricky to get right, but the
required steps are much the same for every daemon program. A
@@ -17,12 +17,12 @@ Description: This library implements the well-behaved daemon specification of
Simple example of usage::
- import daemon
+ import daemon
- from spam import do_main_program
+ from spam import do_main_program
- with daemon.DaemonContext():
- do_main_program()
+ with daemon.DaemonContext():
+ do_main_program()
Customisation of the steps to become a daemon is available by
setting options on the `DaemonContext` instance; see the
@@ -30,8 +30,9 @@ Description: This library implements the well-behaved daemon specification of
Keywords: daemon,fork,unix
Platform: UNKNOWN
Classifier: Development Status :: 4 - Beta
-Classifier: License :: OSI Approved :: Python Software Foundation License
+Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: POSIX
-Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Libraries :: Python Modules
diff --git a/python_daemon.egg-info/SOURCES.txt b/python_daemon.egg-info/SOURCES.txt
index ab2b523..7b3281a 100644
--- a/python_daemon.egg-info/SOURCES.txt
+++ b/python_daemon.egg-info/SOURCES.txt
@@ -1,22 +1,22 @@
ChangeLog
-LICENSE.GPL-2
-LICENSE.PSF-2
+LICENSE.ASF-2
+LICENSE.GPL-3
MANIFEST.in
setup.py
daemon/__init__.py
daemon/daemon.py
-daemon/pidlockfile.py
+daemon/pidfile.py
daemon/runner.py
daemon/version/__init__.py
daemon/version/version_info.py
+doc/FAQ
+doc/TODO
python_daemon.egg-info/PKG-INFO
python_daemon.egg-info/SOURCES.txt
python_daemon.egg-info/dependency_links.txt
python_daemon.egg-info/not-zip-safe
python_daemon.egg-info/requires.txt
python_daemon.egg-info/top_level.txt
-test/__init__.py
-test/scaffold.py
test/test_daemon.py
-test/test_pidlockfile.py
+test/test_pidfile.py
test/test_runner.py \ No newline at end of file
diff --git a/python_daemon.egg-info/requires.txt b/python_daemon.egg-info/requires.txt
index 1c7ae21..29777cf 100644
--- a/python_daemon.egg-info/requires.txt
+++ b/python_daemon.egg-info/requires.txt
@@ -1,2 +1,2 @@
setuptools
-lockfile >=0.7 \ No newline at end of file
+lockfile >=0.9
diff --git a/setup.py b/setup.py
index 8570c8a..802151f 100644
--- a/setup.py
+++ b/setup.py
@@ -1,64 +1,129 @@
# -*- coding: utf-8 -*-
# setup.py
-# Part of python-daemon, an implementation of PEP 3143.
+# Part of ‘python-daemon’, an implementation of PEP 3143.
#
-# Copyright © 2008–2010 Ben Finney <ben+python@benfinney.id.au>
+# Copyright © 2008–2014 Ben Finney <ben+python@benfinney.id.au>
# Copyright © 2008 Robert Niederreiter, Jens Klein
#
# This is free software: you may copy, modify, and/or distribute this work
-# under the terms of the Python Software Foundation License, version 2 or
-# later as published by the Python Software Foundation.
-# No warranty expressed or implied. See the file LICENSE.PSF-2 for details.
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation; version 3 of that license or any later version.
+# No warranty expressed or implied. See the file LICENSE.GPL-3 for details.
-""" Distribution setup for python-daemon library.
+""" Distribution setup for ‘python-daemon’ library.
"""
+from __future__ import unicode_literals
+
import textwrap
from setuptools import setup, find_packages
distribution_name = "python-daemon"
main_module_name = 'daemon'
-main_module = __import__(main_module_name, fromlist=['version'])
+main_module = __import__(main_module_name, fromlist=[b'version'])
version = main_module.version
-short_description, long_description = (
- textwrap.dedent(d).strip()
- for d in main_module.__doc__.split(u'\n\n', 1)
- )
+
+def get_descriptions_from_docstring(docstring):
+ """ Get package description text from a docstring.
+
+ :param docstring: A docstring formatted conformant with PEP 257.
+ :return: A two-item tuple of (`synopsis`, `long_description`). If
+ the docstring contains only a single line, `long_description`
+ will be ``None``.
+
+ Important implications of PEP 257 convention:
+
+ * The docstring either has only a synopsis (a single line of text),
+ or a synopsis and a long description.
+
+ * The synopsis is the first line (only) of the docstring. It may be
+ preceded by a blank line if the docstring has a synopsis and long
+ description.
+
+ * Leading and trailing whitespace is not part of the synopsis nor
+ long description.
+
+ * If the docstring has a long description:
+
+ * The second line of the docstring is blank, separating the
+ synopsis from the long description.
+
+ * The long description starts after the blank separator line, and
+ extends to the end of the docstring.
+
+ * Common leading whitespace on all the long description lines is
+ removed.
+
+ """
+ synopsis = None
+ long_description = None
+
+ lines = docstring.expandtabs().strip().splitlines()
+ if len(lines) < 2:
+ synopsis = lines[0].strip()
+ else:
+ if lines[1].strip():
+ raise ValueError(
+ "PEP 257 multi-line docstrings must have second line blank")
+ synopsis = lines[0].strip()
+ long_description = textwrap.dedent("\n".join(lines[2:]))
+
+ return (synopsis, long_description)
+
+
+description_translate_map = {
+ "‘": "'", "’": "'",
+ "“": '"', "”": '"',
+ }
+
+synopsis, long_description = get_descriptions_from_docstring(
+ main_module.__doc__)
+short_description, long_description = ((
+ synopsis.translate(description_translate_map),
+ long_description.translate(description_translate_map)))
setup(
- name=distribution_name,
- version=version.version,
- packages=find_packages(exclude=["test"]),
-
- # setuptools metadata
- zip_safe=False,
- test_suite="test.suite",
- tests_require=[
- "MiniMock >=1.2.2",
- ],
- install_requires=[
- "setuptools",
- "lockfile >=0.7",
- ],
-
- # PyPI metadata
- author=version.author_name,
- author_email=version.author_email,
- description=short_description,
- license=version.license,
- keywords=u"daemon fork unix".split(),
- url=main_module._url,
- long_description=long_description,
- classifiers=[
- # Reference: http://pypi.python.org/pypi?%3Aaction=list_classifiers
- "Development Status :: 4 - Beta",
- "License :: OSI Approved :: Python Software Foundation License",
- "Operating System :: POSIX",
- "Programming Language :: Python",
- "Intended Audience :: Developers",
- "Topic :: Software Development :: Libraries :: Python Modules",
- ],
- )
+ name=distribution_name,
+ version=version.version,
+ packages=find_packages(exclude=["test"]),
+
+ # Setuptools metadata.
+ zip_safe=False,
+ test_suite="test.suite",
+ tests_require=[
+ "MiniMock >=1.2.2",
+ ],
+ install_requires=[
+ "setuptools",
+ "lockfile >=0.9",
+ ],
+
+ # PyPI metadata.
+ author=version.author_name,
+ author_email=version.author_email,
+ description=short_description,
+ license=version.license,
+ keywords="daemon fork unix".split(),
+ url=main_module._url,
+ long_description=long_description,
+ classifiers=[
+ # Reference: http://pypi.python.org/pypi?%3Aaction=list_classifiers
+ "Development Status :: 4 - Beta",
+ "License :: OSI Approved :: Apache Software License",
+ "Operating System :: POSIX",
+ "Programming Language :: Python :: 2.6",
+ "Programming Language :: Python :: 2.7",
+ "Intended Audience :: Developers",
+ "Topic :: Software Development :: Libraries :: Python Modules",
+ ],
+ )
+
+
+# Local variables:
+# coding: utf-8
+# mode: python
+# End:
+# vim: fileencoding=utf-8 filetype=python :
diff --git a/test/__init__.py b/test/__init__.py
deleted file mode 100644
index b3efac7..0000000
--- a/test/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# test/__init__.py
-# Part of python-daemon, an implementation of PEP 3143.
-#
-# Copyright © 2008–2010 Ben Finney <ben+python@benfinney.id.au>
-#
-# This is free software: you may copy, modify, and/or distribute this work
-# under the terms of the Python Software Foundation License, version 2 or
-# later as published by the Python Software Foundation.
-# No warranty expressed or implied. See the file LICENSE.PSF-2 for details.
-
-""" Unit test suite for daemon package.
- """
-
-import scaffold
-
-
-suite = scaffold.make_suite()
diff --git a/test/scaffold.py b/test/scaffold.py
deleted file mode 100644
index 566cfb9..0000000
--- a/test/scaffold.py
+++ /dev/null
@@ -1,402 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# test/scaffold.py
-# Part of python-daemon, an implementation of PEP 3143.
-#
-# Copyright © 2007–2010 Ben Finney <ben+python@benfinney.id.au>
-# This is free software; you may copy, modify and/or distribute this work
-# under the terms of the GNU General Public License, version 2 or later.
-# No warranty expressed or implied. See the file LICENSE.GPL-2 for details.
-
-""" Scaffolding for unit test modules.
- """
-
-import unittest
-import doctest
-import logging
-import os
-import sys
-import operator
-import textwrap
-from minimock import (
- Mock,
- TraceTracker as MockTracker,
- mock,
- restore as mock_restore,
- )
-
-test_dir = os.path.dirname(os.path.abspath(__file__))
-parent_dir = os.path.dirname(test_dir)
-if not test_dir in sys.path:
- sys.path.insert(1, test_dir)
-if not parent_dir in sys.path:
- sys.path.insert(1, parent_dir)
-
-# Disable all but the most critical logging messages
-logging.disable(logging.CRITICAL)
-
-
-def get_python_module_names(file_list, file_suffix='.py'):
- """ Return a list of module names from a filename list. """
- module_names = [m[:m.rfind(file_suffix)] for m in file_list
- if m.endswith(file_suffix)]
- return module_names
-
-
-def get_test_module_names(module_list, module_prefix='test_'):
- """ Return the list of module names that qualify as test modules. """
- module_names = [m for m in module_list
- if m.startswith(module_prefix)]
- return module_names
-
-
-def make_suite(path=test_dir):
- """ Create the test suite for the given path. """
- loader = unittest.TestLoader()
- python_module_names = get_python_module_names(os.listdir(path))
- test_module_names = get_test_module_names(python_module_names)
- suite = loader.loadTestsFromNames(test_module_names)
-
- return suite
-
-
-def get_function_signature(func):
- """ Get the function signature as a mapping of attributes. """
- arg_count = func.func_code.co_argcount
- arg_names = func.func_code.co_varnames[:arg_count]
-
- arg_defaults = {}
- func_defaults = ()
- if func.func_defaults is not None:
- func_defaults = func.func_defaults
- for (name, value) in zip(arg_names[::-1], func_defaults[::-1]):
- arg_defaults[name] = value
-
- signature = {
- 'name': func.__name__,
- 'arg_count': arg_count,
- 'arg_names': arg_names,
- 'arg_defaults': arg_defaults,
- }
-
- non_pos_names = list(func.func_code.co_varnames[arg_count:])
- COLLECTS_ARBITRARY_POSITIONAL_ARGS = 0x04
- if func.func_code.co_flags & COLLECTS_ARBITRARY_POSITIONAL_ARGS:
- signature['var_args'] = non_pos_names.pop(0)
- COLLECTS_ARBITRARY_KEYWORD_ARGS = 0x08
- if func.func_code.co_flags & COLLECTS_ARBITRARY_KEYWORD_ARGS:
- signature['var_kw_args'] = non_pos_names.pop(0)
-
- return signature
-
-
-def format_function_signature(func):
- """ Format the function signature as printable text. """
- signature = get_function_signature(func)
-
- args_text = []
- for arg_name in signature['arg_names']:
- if arg_name in signature['arg_defaults']:
- arg_default = signature['arg_defaults'][arg_name]
- arg_text_template = "%(arg_name)s=%(arg_default)r"
- else:
- arg_text_template = "%(arg_name)s"
- args_text.append(arg_text_template % vars())
- if 'var_args' in signature:
- args_text.append("*%(var_args)s" % signature)
- if 'var_kw_args' in signature:
- args_text.append("**%(var_kw_args)s" % signature)
- signature_args_text = ", ".join(args_text)
-
- func_name = signature['name']
- signature_text = (
- "%(func_name)s(%(signature_args_text)s)" % vars())
-
- return signature_text
-
-
-class TestCase(unittest.TestCase):
- """ Test case behaviour. """
-
- def failUnlessRaises(self, exc_class, func, *args, **kwargs):
- """ Fail unless the function call raises the expected exception.
-
- Fail the test if an instance of the exception class
- ``exc_class`` is not raised when calling ``func`` with the
- arguments ``*args`` and ``**kwargs``.
-
- """
- try:
- super(TestCase, self).failUnlessRaises(
- exc_class, func, *args, **kwargs)
- except self.failureException:
- exc_class_name = exc_class.__name__
- msg = (
- "Exception %(exc_class_name)s not raised"
- " for function call:"
- " func=%(func)r args=%(args)r kwargs=%(kwargs)r"
- ) % vars()
- raise self.failureException(msg)
-
- def failIfIs(self, first, second, msg=None):
- """ Fail if the two objects are identical.
-
- Fail the test if ``first`` and ``second`` are identical,
- as determined by the ``is`` operator.
-
- """
- if first is second:
- if msg is None:
- msg = "%(first)r is %(second)r" % vars()
- raise self.failureException(msg)
-
- def failUnlessIs(self, first, second, msg=None):
- """ Fail unless the two objects are identical.
-
- Fail the test unless ``first`` and ``second`` are
- identical, as determined by the ``is`` operator.
-
- """
- if first is not second:
- if msg is None:
- msg = "%(first)r is not %(second)r" % vars()
- raise self.failureException(msg)
-
- assertIs = failUnlessIs
- assertNotIs = failIfIs
-
- def failIfIn(self, first, second, msg=None):
- """ Fail if the second object is in the first.
-
- Fail the test if ``first`` contains ``second``, as
- determined by the ``in`` operator.
-
- """
- if second in first:
- if msg is None:
- msg = "%(second)r is in %(first)r" % vars()
- raise self.failureException(msg)
-
- def failUnlessIn(self, first, second, msg=None):
- """ Fail unless the second object is in the first.
-
- Fail the test unless ``first`` contains ``second``, as
- determined by the ``in`` operator.
-
- """
- if second not in first:
- if msg is None:
- msg = "%(second)r is not in %(first)r" % vars()
- raise self.failureException(msg)
-
- assertIn = failUnlessIn
- assertNotIn = failIfIn
-
- def failUnlessOutputCheckerMatch(self, want, got, msg=None):
- """ Fail unless the specified string matches the expected.
-
- Fail the test unless ``want`` matches ``got``, as
- determined by a ``doctest.OutputChecker`` instance. This
- is not an equality check, but a pattern match according to
- the ``OutputChecker`` rules.
-
- """
- checker = doctest.OutputChecker()
- want = textwrap.dedent(want)
- source = ""
- example = doctest.Example(source, want)
- got = textwrap.dedent(got)
- checker_optionflags = reduce(operator.or_, [
- doctest.ELLIPSIS,
- ])
- if not checker.check_output(want, got, checker_optionflags):
- if msg is None:
- diff = checker.output_difference(
- example, got, checker_optionflags)
- msg = "\n".join([
- "Output received did not match expected output",
- "%(diff)s",
- ]) % vars()
- raise self.failureException(msg)
-
- assertOutputCheckerMatch = failUnlessOutputCheckerMatch
-
- def failUnlessMockCheckerMatch(self, want, tracker=None, msg=None):
- """ Fail unless the mock tracker matches the wanted output.
-
- Fail the test unless `want` matches the output tracked by
- `tracker` (defaults to ``self.mock_tracker``. This is not
- an equality check, but a pattern match according to the
- ``minimock.MinimockOutputChecker`` rules.
-
- """
- if tracker is None:
- tracker = self.mock_tracker
- if not tracker.check(want):
- if msg is None:
- diff = tracker.diff(want)
- msg = "\n".join([
- "Output received did not match expected output",
- "%(diff)s",
- ]) % vars()
- raise self.failureException(msg)
-
- def failIfMockCheckerMatch(self, want, tracker=None, msg=None):
- """ Fail if the mock tracker matches the specified output.
-
- Fail the test if `want` matches the output tracked by
- `tracker` (defaults to ``self.mock_tracker``. This is not
- an equality check, but a pattern match according to the
- ``minimock.MinimockOutputChecker`` rules.
-
- """
- if tracker is None:
- tracker = self.mock_tracker
- if tracker.check(want):
- if msg is None:
- diff = tracker.diff(want)
- msg = "\n".join([
- "Output received matched specified undesired output",
- "%(diff)s",
- ]) % vars()
- raise self.failureException(msg)
-
- assertMockCheckerMatch = failUnlessMockCheckerMatch
- assertNotMockCheckerMatch = failIfMockCheckerMatch
-
- def failIfIsInstance(self, obj, classes, msg=None):
- """ Fail if the object is an instance of the specified classes.
-
- Fail the test if the object ``obj`` is an instance of any
- of ``classes``.
-
- """
- if isinstance(obj, classes):
- if msg is None:
- msg = (
- "%(obj)r is an instance of one of %(classes)r"
- ) % vars()
- raise self.failureException(msg)
-
- def failUnlessIsInstance(self, obj, classes, msg=None):
- """ Fail unless the object is an instance of the specified classes.
-
- Fail the test unless the object ``obj`` is an instance of
- any of ``classes``.
-
- """
- if not isinstance(obj, classes):
- if msg is None:
- msg = (
- "%(obj)r is not an instance of any of %(classes)r"
- ) % vars()
- raise self.failureException(msg)
-
- assertIsInstance = failUnlessIsInstance
- assertNotIsInstance = failIfIsInstance
-
- def failUnlessFunctionInTraceback(self, traceback, function, msg=None):
- """ Fail if the function is not in the traceback.
-
- Fail the test if the function ``function`` is not at any
- of the levels in the traceback object ``traceback``.
-
- """
- func_in_traceback = False
- expect_code = function.func_code
- current_traceback = traceback
- while current_traceback is not None:
- if expect_code is current_traceback.tb_frame.f_code:
- func_in_traceback = True
- break
- current_traceback = current_traceback.tb_next
-
- if not func_in_traceback:
- if msg is None:
- msg = (
- "Traceback did not lead to original function"
- " %(function)s"
- ) % vars()
- raise self.failureException(msg)
-
- assertFunctionInTraceback = failUnlessFunctionInTraceback
-
- def failUnlessFunctionSignatureMatch(self, first, second, msg=None):
- """ Fail if the function signatures do not match.
-
- Fail the test if the function signature does not match
- between the ``first`` function and the ``second``
- function.
-
- The function signature includes:
-
- * function name,
-
- * count of named parameters,
-
- * sequence of named parameters,
-
- * default values of named parameters,
-
- * collector for arbitrary positional arguments,
-
- * collector for arbitrary keyword arguments.
-
- """
- first_signature = get_function_signature(first)
- second_signature = get_function_signature(second)
-
- if first_signature != second_signature:
- if msg is None:
- first_signature_text = format_function_signature(first)
- second_signature_text = format_function_signature(second)
- msg = (textwrap.dedent("""\
- Function signatures do not match:
- %(first_signature)r != %(second_signature)r
- Expected:
- %(first_signature_text)s
- Got:
- %(second_signature_text)s""")
- ) % vars()
- raise self.failureException(msg)
-
- assertFunctionSignatureMatch = failUnlessFunctionSignatureMatch
-
-
-class Exception_TestCase(TestCase):
- """ Test cases for exception classes. """
-
- def __init__(self, *args, **kwargs):
- """ Set up a new instance """
- self.valid_exceptions = NotImplemented
- super(Exception_TestCase, self).__init__(*args, **kwargs)
-
- def setUp(self):
- """ Set up test fixtures. """
- for exc_type, params in self.valid_exceptions.items():
- args = (None, ) * params['min_args']
- params['args'] = args
- instance = exc_type(*args)
- params['instance'] = instance
-
- super(Exception_TestCase, self).setUp()
-
- def test_exception_instance(self):
- """ Exception instance should be created. """
- for params in self.valid_exceptions.values():
- instance = params['instance']
- self.failIfIs(None, instance)
-
- def test_exception_types(self):
- """ Exception instances should match expected types. """
- for params in self.valid_exceptions.values():
- instance = params['instance']
- for match_type in params['types']:
- match_type_name = match_type.__name__
- fail_msg = (
- "%(instance)r is not an instance of"
- " %(match_type_name)s"
- ) % vars()
- self.failUnless(
- isinstance(instance, match_type),
- msg=fail_msg)
diff --git a/test/test_daemon.py b/test/test_daemon.py
index c3f46e3..6ed6ccf 100644
--- a/test/test_daemon.py
+++ b/test/test_daemon.py
@@ -1,18 +1,20 @@
# -*- coding: utf-8 -*-
#
# test/test_daemon.py
-# Part of python-daemon, an implementation of PEP 3143.
+# Part of ‘python-daemon’, an implementation of PEP 3143.
#
-# Copyright © 2008–2010 Ben Finney <ben+python@benfinney.id.au>
+# Copyright © 2008–2014 Ben Finney <ben+python@benfinney.id.au>
#
# This is free software: you may copy, modify, and/or distribute this work
-# under the terms of the Python Software Foundation License, version 2 or
-# later as published by the Python Software Foundation.
-# No warranty expressed or implied. See the file LICENSE.PSF-2 for details.
+# under the terms of the Apache License, version 2.0 as published by the
+# Apache Software Foundation.
+# No warranty expressed or implied. See the file LICENSE.ASF-2 for details.
-""" Unit test for daemon module.
+""" Unit test for ‘daemon’ module.
"""
+from __future__ import unicode_literals
+
import os
import sys
import tempfile
@@ -25,12 +27,12 @@ import atexit
from StringIO import StringIO
import scaffold
-from test_pidlockfile import (
- FakeFileDescriptorStringIO,
- setup_pidfile_fixtures,
- )
+from test_pidfile import (
+ FakeFileDescriptorStringIO,
+ setup_pidfile_fixtures,
+ )
-from daemon import pidlockfile
+from lockfile import pidlockfile
import daemon
@@ -42,19 +44,19 @@ class Exception_TestCase(scaffold.Exception_TestCase):
super(Exception_TestCase, self).__init__(*args, **kwargs)
self.valid_exceptions = {
- daemon.daemon.DaemonError: dict(
- min_args = 1,
- types = (Exception,),
- ),
- daemon.daemon.DaemonOSEnvironmentError: dict(
- min_args = 1,
- types = (daemon.daemon.DaemonError, OSError),
- ),
- daemon.daemon.DaemonProcessDetachError: dict(
- min_args = 1,
- types = (daemon.daemon.DaemonError, OSError),
- ),
- }
+ daemon.daemon.DaemonError: dict(
+ min_args=1,
+ types=(Exception,),
+ ),
+ daemon.daemon.DaemonOSEnvironmentError: dict(
+ min_args=1,
+ types=(daemon.daemon.DaemonError, OSError),
+ ),
+ daemon.daemon.DaemonProcessDetachError: dict(
+ min_args=1,
+ types=(daemon.daemon.DaemonError, OSError),
+ ),
+ }
def setup_daemon_context_fixtures(testcase):
@@ -67,35 +69,35 @@ def setup_daemon_context_fixtures(testcase):
testcase.mock_pidfile_path = tempfile.mktemp()
testcase.mock_pidlockfile = scaffold.Mock(
- "pidlockfile.PIDLockFile",
- tracker=testcase.mock_tracker)
+ "pidlockfile.PIDLockFile",
+ tracker=testcase.mock_tracker)
testcase.mock_pidlockfile.path = testcase.mock_pidfile_path
scaffold.mock(
- "daemon.daemon.is_detach_process_context_required",
- returns=True,
- tracker=testcase.mock_tracker)
+ "daemon.daemon.is_detach_process_context_required",
+ returns=True,
+ tracker=testcase.mock_tracker)
scaffold.mock(
- "daemon.daemon.make_default_signal_map",
- returns=object(),
- tracker=testcase.mock_tracker)
+ "daemon.daemon.make_default_signal_map",
+ returns=object(),
+ tracker=testcase.mock_tracker)
scaffold.mock(
- "os.getuid",
- returns=object(),
- tracker=testcase.mock_tracker)
+ "os.getuid",
+ returns=object(),
+ tracker=testcase.mock_tracker)
scaffold.mock(
- "os.getgid",
- returns=object(),
- tracker=testcase.mock_tracker)
+ "os.getgid",
+ returns=object(),
+ tracker=testcase.mock_tracker)
testcase.daemon_context_args = dict(
- stdin = testcase.stream_files_by_name['stdin'],
- stdout = testcase.stream_files_by_name['stdout'],
- stderr = testcase.stream_files_by_name['stderr'],
- )
+ stdin=testcase.stream_files_by_name['stdin'],
+ stdout=testcase.stream_files_by_name['stdout'],
+ stderr=testcase.stream_files_by_name['stderr'],
+ )
testcase.test_instance = daemon.DaemonContext(
- **testcase.daemon_context_args)
+ **testcase.daemon_context_args)
class DaemonContext_TestCase(scaffold.TestCase):
@@ -112,7 +114,7 @@ class DaemonContext_TestCase(scaffold.TestCase):
def test_instantiate(self):
""" New instance of DaemonContext should be created. """
self.failUnlessIsInstance(
- self.test_instance, daemon.daemon.DaemonContext)
+ self.test_instance, daemon.daemon.DaemonContext)
def test_minimum_zero_arguments(self):
""" Initialiser should not require any arguments. """
@@ -122,8 +124,8 @@ class DaemonContext_TestCase(scaffold.TestCase):
def test_has_specified_chroot_directory(self):
""" Should have specified chroot_directory option. """
args = dict(
- chroot_directory = object(),
- )
+ chroot_directory=object(),
+ )
expect_directory = args['chroot_directory']
instance = daemon.daemon.DaemonContext(**args)
self.failUnlessEqual(expect_directory, instance.chroot_directory)
@@ -131,8 +133,8 @@ class DaemonContext_TestCase(scaffold.TestCase):
def test_has_specified_working_directory(self):
""" Should have specified working_directory option. """
args = dict(
- working_directory = object(),
- )
+ working_directory=object(),
+ )
expect_directory = args['working_directory']
instance = daemon.daemon.DaemonContext(**args)
self.failUnlessEqual(expect_directory, instance.working_directory)
@@ -147,8 +149,8 @@ class DaemonContext_TestCase(scaffold.TestCase):
def test_has_specified_creation_mask(self):
""" Should have specified umask option. """
args = dict(
- umask = object(),
- )
+ umask=object(),
+ )
expect_mask = args['umask']
instance = daemon.daemon.DaemonContext(**args)
self.failUnlessEqual(expect_mask, instance.umask)
@@ -163,8 +165,8 @@ class DaemonContext_TestCase(scaffold.TestCase):
def test_has_specified_uid(self):
""" Should have specified uid option. """
args = dict(
- uid = object(),
- )
+ uid=object(),
+ )
expect_id = args['uid']
instance = daemon.daemon.DaemonContext(**args)
self.failUnlessEqual(expect_id, instance.uid)
@@ -179,8 +181,8 @@ class DaemonContext_TestCase(scaffold.TestCase):
def test_has_specified_gid(self):
""" Should have specified gid option. """
args = dict(
- gid = object(),
- )
+ gid=object(),
+ )
expect_id = args['gid']
instance = daemon.daemon.DaemonContext(**args)
self.failUnlessEqual(expect_id, instance.gid)
@@ -195,8 +197,8 @@ class DaemonContext_TestCase(scaffold.TestCase):
def test_has_specified_detach_process(self):
""" Should have specified detach_process option. """
args = dict(
- detach_process = object(),
- )
+ detach_process=object(),
+ )
expect_value = args['detach_process']
instance = daemon.daemon.DaemonContext(**args)
self.failUnlessEqual(expect_value, instance.detach_process)
@@ -212,8 +214,8 @@ class DaemonContext_TestCase(scaffold.TestCase):
def test_has_specified_files_preserve(self):
""" Should have specified files_preserve option. """
args = dict(
- files_preserve = object(),
- )
+ files_preserve=object(),
+ )
expect_files_preserve = args['files_preserve']
instance = daemon.daemon.DaemonContext(**args)
self.failUnlessEqual(expect_files_preserve, instance.files_preserve)
@@ -221,8 +223,8 @@ class DaemonContext_TestCase(scaffold.TestCase):
def test_has_specified_pidfile(self):
""" Should have the specified pidfile. """
args = dict(
- pidfile = object(),
- )
+ pidfile=object(),
+ )
expect_pidfile = args['pidfile']
instance = daemon.daemon.DaemonContext(**args)
self.failUnlessEqual(expect_pidfile, instance.pidfile)
@@ -230,8 +232,8 @@ class DaemonContext_TestCase(scaffold.TestCase):
def test_has_specified_stdin(self):
""" Should have specified stdin option. """
args = dict(
- stdin = object(),
- )
+ stdin=object(),
+ )
expect_file = args['stdin']
instance = daemon.daemon.DaemonContext(**args)
self.failUnlessEqual(expect_file, instance.stdin)
@@ -239,8 +241,8 @@ class DaemonContext_TestCase(scaffold.TestCase):
def test_has_specified_stdout(self):
""" Should have specified stdout option. """
args = dict(
- stdout = object(),
- )
+ stdout=object(),
+ )
expect_file = args['stdout']
instance = daemon.daemon.DaemonContext(**args)
self.failUnlessEqual(expect_file, instance.stdout)
@@ -248,8 +250,8 @@ class DaemonContext_TestCase(scaffold.TestCase):
def test_has_specified_stderr(self):
""" Should have specified stderr option. """
args = dict(
- stderr = object(),
- )
+ stderr=object(),
+ )
expect_file = args['stderr']
instance = daemon.daemon.DaemonContext(**args)
self.failUnlessEqual(expect_file, instance.stderr)
@@ -257,8 +259,8 @@ class DaemonContext_TestCase(scaffold.TestCase):
def test_has_specified_signal_map(self):
""" Should have specified signal_map option. """
args = dict(
- signal_map = object(),
- )
+ signal_map=object(),
+ )
expect_signal_map = args['signal_map']
instance = daemon.daemon.DaemonContext(**args)
self.failUnlessEqual(expect_signal_map, instance.signal_map)
@@ -291,8 +293,8 @@ class DaemonContext_is_open_TestCase(scaffold.TestCase):
""" Writing to is_open should fail. """
instance = self.test_instance
self.failUnlessRaises(
- AttributeError,
- setattr, instance, 'is_open', object())
+ AttributeError,
+ setattr, instance, 'is_open', object())
class DaemonContext_open_TestCase(scaffold.TestCase):
@@ -306,57 +308,57 @@ class DaemonContext_open_TestCase(scaffold.TestCase):
self.test_instance._is_open = False
scaffold.mock(
- "daemon.daemon.detach_process_context",
- tracker=self.mock_tracker)
+ "daemon.daemon.detach_process_context",
+ tracker=self.mock_tracker)
scaffold.mock(
- "daemon.daemon.change_working_directory",
- tracker=self.mock_tracker)
+ "daemon.daemon.change_working_directory",
+ tracker=self.mock_tracker)
scaffold.mock(
- "daemon.daemon.change_root_directory",
- tracker=self.mock_tracker)
+ "daemon.daemon.change_root_directory",
+ tracker=self.mock_tracker)
scaffold.mock(
- "daemon.daemon.change_file_creation_mask",
- tracker=self.mock_tracker)
+ "daemon.daemon.change_file_creation_mask",
+ tracker=self.mock_tracker)
scaffold.mock(
- "daemon.daemon.change_process_owner",
- tracker=self.mock_tracker)
+ "daemon.daemon.change_process_owner",
+ tracker=self.mock_tracker)
scaffold.mock(
- "daemon.daemon.prevent_core_dump",
- tracker=self.mock_tracker)
+ "daemon.daemon.prevent_core_dump",
+ tracker=self.mock_tracker)
scaffold.mock(
- "daemon.daemon.close_all_open_files",
- tracker=self.mock_tracker)
+ "daemon.daemon.close_all_open_files",
+ tracker=self.mock_tracker)
scaffold.mock(
- "daemon.daemon.redirect_stream",
- tracker=self.mock_tracker)
+ "daemon.daemon.redirect_stream",
+ tracker=self.mock_tracker)
scaffold.mock(
- "daemon.daemon.set_signal_handlers",
- tracker=self.mock_tracker)
+ "daemon.daemon.set_signal_handlers",
+ tracker=self.mock_tracker)
scaffold.mock(
- "daemon.daemon.register_atexit_function",
- tracker=self.mock_tracker)
+ "daemon.daemon.register_atexit_function",
+ tracker=self.mock_tracker)
self.test_files_preserve_fds = object()
scaffold.mock(
- "daemon.daemon.DaemonContext._get_exclude_file_descriptors",
- returns=self.test_files_preserve_fds,
- tracker=self.mock_tracker)
+ "daemon.daemon.DaemonContext._get_exclude_file_descriptors",
+ returns=self.test_files_preserve_fds,
+ tracker=self.mock_tracker)
self.test_signal_handler_map = object()
scaffold.mock(
- "daemon.daemon.DaemonContext._make_signal_handler_map",
- returns=self.test_signal_handler_map,
- tracker=self.mock_tracker)
+ "daemon.daemon.DaemonContext._make_signal_handler_map",
+ returns=self.test_signal_handler_map,
+ tracker=self.mock_tracker)
scaffold.mock(
- "sys.stdin",
- tracker=self.mock_tracker)
+ "sys.stdin",
+ tracker=self.mock_tracker)
scaffold.mock(
- "sys.stdout",
- tracker=self.mock_tracker)
+ "sys.stdout",
+ tracker=self.mock_tracker)
scaffold.mock(
- "sys.stderr",
- tracker=self.mock_tracker)
+ "sys.stderr",
+ tracker=self.mock_tracker)
def tearDown(self):
""" Tear down test fixtures. """
@@ -369,22 +371,22 @@ class DaemonContext_open_TestCase(scaffold.TestCase):
instance.detach_process = True
instance.pidfile = self.mock_pidlockfile
expect_mock_output = """\
- Called daemon.daemon.change_root_directory(...)
- Called daemon.daemon.prevent_core_dump()
- Called daemon.daemon.change_file_creation_mask(...)
- Called daemon.daemon.change_working_directory(...)
- Called daemon.daemon.change_process_owner(...)
- Called daemon.daemon.detach_process_context()
- Called daemon.daemon.DaemonContext._make_signal_handler_map()
- Called daemon.daemon.set_signal_handlers(...)
- Called daemon.daemon.DaemonContext._get_exclude_file_descriptors()
- Called daemon.daemon.close_all_open_files(...)
- Called daemon.daemon.redirect_stream(...)
- Called daemon.daemon.redirect_stream(...)
- Called daemon.daemon.redirect_stream(...)
- Called pidlockfile.PIDLockFile.__enter__()
- Called daemon.daemon.register_atexit_function(...)
- """ % vars()
+ Called daemon.daemon.change_root_directory(...)
+ Called daemon.daemon.prevent_core_dump()
+ Called daemon.daemon.change_file_creation_mask(...)
+ Called daemon.daemon.change_working_directory(...)
+ Called daemon.daemon.change_process_owner(...)
+ Called daemon.daemon.detach_process_context()
+ Called daemon.daemon.DaemonContext._make_signal_handler_map()
+ Called daemon.daemon.set_signal_handlers(...)
+ Called daemon.daemon.DaemonContext._get_exclude_file_descriptors()
+ Called daemon.daemon.close_all_open_files(...)
+ Called daemon.daemon.redirect_stream(...)
+ Called daemon.daemon.redirect_stream(...)
+ Called daemon.daemon.redirect_stream(...)
+ Called pidlockfile.PIDLockFile.__enter__()
+ Called daemon.daemon.register_atexit_function(...)
+ """ % vars()
self.mock_tracker.clear()
instance.open()
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -394,7 +396,7 @@ class DaemonContext_open_TestCase(scaffold.TestCase):
instance = self.test_instance
instance._is_open = True
expect_mock_output = """\
- """
+ """
self.mock_tracker.clear()
instance.open()
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -405,10 +407,10 @@ class DaemonContext_open_TestCase(scaffold.TestCase):
chroot_directory = object()
instance.chroot_directory = chroot_directory
expect_mock_output = """\
- Called daemon.daemon.change_root_directory(
- %(chroot_directory)r)
- ...
- """ % vars()
+ Called daemon.daemon.change_root_directory(
+ %(chroot_directory)r)
+ ...
+ """ % vars()
instance.open()
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -417,7 +419,7 @@ class DaemonContext_open_TestCase(scaffold.TestCase):
instance = self.test_instance
instance.chroot_directory = None
unwanted_output = """\
- ...Called daemon.daemon.change_root_directory(...)..."""
+ ...Called daemon.daemon.change_root_directory(...)..."""
instance.open()
self.failIfMockCheckerMatch(unwanted_output)
@@ -425,9 +427,9 @@ class DaemonContext_open_TestCase(scaffold.TestCase):
""" Should request prevention of core dumps. """
instance = self.test_instance
expect_mock_output = """\
- Called daemon.daemon.prevent_core_dump()
- ...
- """ % vars()
+ Called daemon.daemon.prevent_core_dump()
+ ...
+ """ % vars()
instance.open()
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -436,7 +438,7 @@ class DaemonContext_open_TestCase(scaffold.TestCase):
instance = self.test_instance
instance.prevent_core = False
unwanted_output = """\
- ...Called daemon.daemon.prevent_core_dump()..."""
+ ...Called daemon.daemon.prevent_core_dump()..."""
instance.open()
self.failIfMockCheckerMatch(unwanted_output)
@@ -445,11 +447,11 @@ class DaemonContext_open_TestCase(scaffold.TestCase):
instance = self.test_instance
expect_exclude = self.test_files_preserve_fds
expect_mock_output = """\
- ...
- Called daemon.daemon.close_all_open_files(
- exclude=%(expect_exclude)r)
- ...
- """ % vars()
+ ...
+ Called daemon.daemon.close_all_open_files(
+ exclude=%(expect_exclude)r)
+ ...
+ """ % vars()
instance.open()
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -459,11 +461,11 @@ class DaemonContext_open_TestCase(scaffold.TestCase):
working_directory = object()
instance.working_directory = working_directory
expect_mock_output = """\
- ...
- Called daemon.daemon.change_working_directory(
- %(working_directory)r)
- ...
- """ % vars()
+ ...
+ Called daemon.daemon.change_working_directory(
+ %(working_directory)r)
+ ...
+ """ % vars()
instance.open()
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -473,10 +475,10 @@ class DaemonContext_open_TestCase(scaffold.TestCase):
umask = object()
instance.umask = umask
expect_mock_output = """\
- ...
- Called daemon.daemon.change_file_creation_mask(%(umask)r)
- ...
- """ % vars()
+ ...
+ Called daemon.daemon.change_file_creation_mask(%(umask)r)
+ ...
+ """ % vars()
instance.open()
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -488,10 +490,10 @@ class DaemonContext_open_TestCase(scaffold.TestCase):
instance.uid = uid
instance.gid = gid
expect_mock_output = """\
- ...
- Called daemon.daemon.change_process_owner(%(uid)r, %(gid)r)
- ...
- """ % vars()
+ ...
+ Called daemon.daemon.change_process_owner(%(uid)r, %(gid)r)
+ ...
+ """ % vars()
instance.open()
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -499,10 +501,10 @@ class DaemonContext_open_TestCase(scaffold.TestCase):
""" Should request detach of process context. """
instance = self.test_instance
expect_mock_output = """\
- ...
- Called daemon.daemon.detach_process_context()
- ...
- """ % vars()
+ ...
+ Called daemon.daemon.detach_process_context()
+ ...
+ """ % vars()
instance.open()
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -511,7 +513,7 @@ class DaemonContext_open_TestCase(scaffold.TestCase):
instance = self.test_instance
instance.detach_process = False
unwanted_output = """\
- ...Called daemon.daemon.detach_process_context(...)..."""
+ ...Called daemon.daemon.detach_process_context(...)..."""
instance.open()
self.failIfMockCheckerMatch(unwanted_output)
@@ -521,11 +523,11 @@ class DaemonContext_open_TestCase(scaffold.TestCase):
instance.signal_map = object()
expect_signal_handler_map = self.test_signal_handler_map
expect_mock_output = """\
- ...
- Called daemon.daemon.set_signal_handlers(
- %(expect_signal_handler_map)r)
- ...
- """ % vars()
+ ...
+ Called daemon.daemon.set_signal_handlers(
+ %(expect_signal_handler_map)r)
+ ...
+ """ % vars()
instance.open()
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -533,20 +535,20 @@ class DaemonContext_open_TestCase(scaffold.TestCase):
""" Should request redirection of standard stream files. """
instance = self.test_instance
(system_stdin, system_stdout, system_stderr) = (
- sys.stdin, sys.stdout, sys.stderr)
+ sys.stdin, sys.stdout, sys.stderr)
(target_stdin, target_stdout, target_stderr) = (
- self.stream_files_by_name[name]
- for name in ['stdin', 'stdout', 'stderr'])
+ self.stream_files_by_name[name]
+ for name in ['stdin', 'stdout', 'stderr'])
expect_mock_output = """\
- ...
- Called daemon.daemon.redirect_stream(
- %(system_stdin)r, %(target_stdin)r)
- Called daemon.daemon.redirect_stream(
- %(system_stdout)r, %(target_stdout)r)
- Called daemon.daemon.redirect_stream(
- %(system_stderr)r, %(target_stderr)r)
- ...
- """ % vars()
+ ...
+ Called daemon.daemon.redirect_stream(
+ %(system_stdin)r, %(target_stdin)r)
+ Called daemon.daemon.redirect_stream(
+ %(system_stdout)r, %(target_stdout)r)
+ Called daemon.daemon.redirect_stream(
+ %(system_stderr)r, %(target_stderr)r)
+ ...
+ """ % vars()
instance.open()
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -555,10 +557,10 @@ class DaemonContext_open_TestCase(scaffold.TestCase):
instance = self.test_instance
instance.pidfile = self.mock_pidlockfile
expect_mock_output = """\
- ...
- Called pidlockfile.PIDLockFile.__enter__()
- ...
- """
+ ...
+ Called pidlockfile.PIDLockFile.__enter__()
+ ...
+ """
instance.open()
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -573,9 +575,9 @@ class DaemonContext_open_TestCase(scaffold.TestCase):
instance = self.test_instance
close_method = instance.close
expect_mock_output = """\
- ...
- Called daemon.daemon.register_atexit_function(%(close_method)r)
- """ % vars()
+ ...
+ Called daemon.daemon.register_atexit_function(%(close_method)r)
+ """ % vars()
instance.open()
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -600,7 +602,7 @@ class DaemonContext_close_TestCase(scaffold.TestCase):
instance._is_open = False
instance.pidfile = object()
expect_mock_output = """\
- """
+ """
self.mock_tracker.clear()
instance.close()
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -610,8 +612,8 @@ class DaemonContext_close_TestCase(scaffold.TestCase):
instance = self.test_instance
instance.pidfile = self.mock_pidlockfile
expect_mock_output = """\
- Called pidlockfile.PIDLockFile.__exit__(None, None, None)
- """
+ Called pidlockfile.PIDLockFile.__exit__(None, None, None)
+ """
instance.close()
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -638,8 +640,8 @@ class DaemonContext_context_manager_enter_TestCase(scaffold.TestCase):
self.mock_tracker.clear()
scaffold.mock(
- "daemon.daemon.DaemonContext.open",
- tracker=self.mock_tracker)
+ "daemon.daemon.DaemonContext.open",
+ tracker=self.mock_tracker)
def tearDown(self):
""" Tear down test fixtures. """
@@ -649,8 +651,8 @@ class DaemonContext_context_manager_enter_TestCase(scaffold.TestCase):
""" Should open the DaemonContext. """
instance = self.test_instance
expect_mock_output = """\
- Called daemon.daemon.DaemonContext.open()
- """
+ Called daemon.daemon.DaemonContext.open()
+ """
instance.__enter__()
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -671,14 +673,14 @@ class DaemonContext_context_manager_exit_TestCase(scaffold.TestCase):
self.mock_tracker.clear()
self.test_args = dict(
- exc_type = object(),
- exc_value = object(),
- traceback = object(),
- )
+ exc_type=object(),
+ exc_value=object(),
+ traceback=object(),
+ )
scaffold.mock(
- "daemon.daemon.DaemonContext.close",
- tracker=self.mock_tracker)
+ "daemon.daemon.DaemonContext.close",
+ tracker=self.mock_tracker)
def tearDown(self):
""" Tear down test fixtures. """
@@ -689,8 +691,8 @@ class DaemonContext_context_manager_exit_TestCase(scaffold.TestCase):
instance = self.test_instance
args = self.test_args
expect_mock_output = """\
- Called daemon.daemon.DaemonContext.close()
- """
+ Called daemon.daemon.DaemonContext.close()
+ """
instance.__exit__(**args)
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -724,8 +726,8 @@ class DaemonContext_terminate_TestCase(scaffold.TestCase):
args = self.test_args
expect_exception = SystemExit
self.failUnlessRaises(
- expect_exception,
- instance.terminate, *args)
+ expect_exception,
+ instance.terminate, *args)
def test_exception_message_contains_signal_number(self):
""" Should raise exception with a message containing signal number. """
@@ -735,7 +737,7 @@ class DaemonContext_terminate_TestCase(scaffold.TestCase):
expect_exception = SystemExit
try:
instance.terminate(*args)
- except expect_exception, exc:
+ except expect_exception as exc:
pass
self.failUnlessIn(str(exc), str(signal_number))
@@ -755,17 +757,17 @@ class DaemonContext_get_exclude_file_descriptors_TestCase(scaffold.TestCase):
23: FakeFileDescriptorStringIO(),
37: 37,
42: FakeFileDescriptorStringIO(),
- }
+ }
for (fileno, item) in self.test_files.items():
if hasattr(item, '_fileno'):
item._fileno = fileno
self.test_file_descriptors = set(
- fd for (fd, item) in self.test_files.items()
- if item is not None)
+ fd for (fd, item) in self.test_files.items()
+ if item is not None)
self.test_file_descriptors.update(
- self.stream_files_by_name[name].fileno()
- for name in ['stdin', 'stdout', 'stderr']
- )
+ self.stream_files_by_name[name].fileno()
+ for name in ['stdin', 'stdout', 'stderr']
+ )
def tearDown(self):
""" Tear down test fixtures. """
@@ -784,8 +786,8 @@ class DaemonContext_get_exclude_file_descriptors_TestCase(scaffold.TestCase):
instance = self.test_instance
instance.files_preserve = None
expect_result = set(
- stream.fileno()
- for stream in self.stream_files_by_name.values())
+ stream.fileno()
+ for stream in self.stream_files_by_name.values())
result = instance._get_exclude_file_descriptors()
self.failUnlessEqual(expect_result, result)
@@ -846,8 +848,8 @@ class DaemonContext_make_signal_handler_TestCase(scaffold.TestCase):
target = 'b0gUs'
expect_error = AttributeError
self.failUnlessRaises(
- expect_error,
- instance._make_signal_handler, target)
+ expect_error,
+ instance._make_signal_handler, target)
def test_returns_object_for_object(self):
""" Should return same object for any other object. """
@@ -866,24 +868,24 @@ class DaemonContext_make_signal_handler_map_TestCase(scaffold.TestCase):
setup_daemon_context_fixtures(self)
self.test_instance.signal_map = {
- object(): object(),
- object(): object(),
- object(): object(),
- }
+ object(): object(),
+ object(): object(),
+ object(): object(),
+ }
self.test_signal_handlers = dict(
- (key, object())
- for key in self.test_instance.signal_map.values())
+ (key, object())
+ for key in self.test_instance.signal_map.values())
self.test_signal_handler_map = dict(
- (key, self.test_signal_handlers[target])
- for (key, target) in self.test_instance.signal_map.items())
+ (key, self.test_signal_handlers[target])
+ for (key, target) in self.test_instance.signal_map.items())
def mock_make_signal_handler(target):
return self.test_signal_handlers[target]
scaffold.mock(
- "daemon.daemon.DaemonContext._make_signal_handler",
- returns_func=mock_make_signal_handler,
- tracker=self.mock_tracker)
+ "daemon.daemon.DaemonContext._make_signal_handler",
+ returns_func=mock_make_signal_handler,
+ tracker=self.mock_tracker)
def tearDown(self):
""" Tear down test fixtures. """
@@ -905,13 +907,13 @@ class change_working_directory_TestCase(scaffold.TestCase):
self.mock_tracker = scaffold.MockTracker()
scaffold.mock(
- "os.chdir",
- tracker=self.mock_tracker)
+ "os.chdir",
+ tracker=self.mock_tracker)
self.test_directory = object()
self.test_args = dict(
- directory=self.test_directory,
- )
+ directory=self.test_directory,
+ )
def tearDown(self):
""" Tear down test fixtures. """
@@ -922,8 +924,8 @@ class change_working_directory_TestCase(scaffold.TestCase):
args = self.test_args
directory = self.test_directory
expect_mock_output = """\
- Called os.chdir(%(directory)r)
- """ % vars()
+ Called os.chdir(%(directory)r)
+ """ % vars()
daemon.daemon.change_working_directory(**args)
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -934,8 +936,8 @@ class change_working_directory_TestCase(scaffold.TestCase):
os.chdir.mock_raises = test_error
expect_error = daemon.daemon.DaemonOSEnvironmentError
self.failUnlessRaises(
- expect_error,
- daemon.daemon.change_working_directory, **args)
+ expect_error,
+ daemon.daemon.change_working_directory, **args)
def test_error_message_contains_original_error_message(self):
""" Should raise a DaemonError with original message. """
@@ -945,7 +947,7 @@ class change_working_directory_TestCase(scaffold.TestCase):
expect_error = daemon.daemon.DaemonOSEnvironmentError
try:
daemon.daemon.change_working_directory(**args)
- except expect_error, exc:
+ except expect_error as exc:
pass
self.failUnlessIn(str(exc), str(test_error))
@@ -958,16 +960,16 @@ class change_root_directory_TestCase(scaffold.TestCase):
self.mock_tracker = scaffold.MockTracker()
scaffold.mock(
- "os.chdir",
- tracker=self.mock_tracker)
+ "os.chdir",
+ tracker=self.mock_tracker)
scaffold.mock(
- "os.chroot",
- tracker=self.mock_tracker)
+ "os.chroot",
+ tracker=self.mock_tracker)
self.test_directory = object()
self.test_args = dict(
- directory=self.test_directory,
- )
+ directory=self.test_directory,
+ )
def tearDown(self):
""" Tear down test fixtures. """
@@ -978,9 +980,9 @@ class change_root_directory_TestCase(scaffold.TestCase):
args = self.test_args
directory = self.test_directory
expect_mock_output = """\
- Called os.chdir(%(directory)r)
- ...
- """ % vars()
+ Called os.chdir(%(directory)r)
+ ...
+ """ % vars()
daemon.daemon.change_root_directory(**args)
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -989,9 +991,9 @@ class change_root_directory_TestCase(scaffold.TestCase):
args = self.test_args
directory = self.test_directory
expect_mock_output = """\
- ...
- Called os.chroot(%(directory)r)
- """ % vars()
+ ...
+ Called os.chroot(%(directory)r)
+ """ % vars()
daemon.daemon.change_root_directory(**args)
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -1002,8 +1004,8 @@ class change_root_directory_TestCase(scaffold.TestCase):
os.chdir.mock_raises = test_error
expect_error = daemon.daemon.DaemonOSEnvironmentError
self.failUnlessRaises(
- expect_error,
- daemon.daemon.change_root_directory, **args)
+ expect_error,
+ daemon.daemon.change_root_directory, **args)
def test_raises_daemon_error_on_os_error_from_chroot(self):
""" Should raise a DaemonError on receiving an OSError from chroot. """
@@ -1012,8 +1014,8 @@ class change_root_directory_TestCase(scaffold.TestCase):
os.chroot.mock_raises = test_error
expect_error = daemon.daemon.DaemonOSEnvironmentError
self.failUnlessRaises(
- expect_error,
- daemon.daemon.change_root_directory, **args)
+ expect_error,
+ daemon.daemon.change_root_directory, **args)
def test_error_message_contains_original_error_message(self):
""" Should raise a DaemonError with original message. """
@@ -1023,7 +1025,7 @@ class change_root_directory_TestCase(scaffold.TestCase):
expect_error = daemon.daemon.DaemonOSEnvironmentError
try:
daemon.daemon.change_root_directory(**args)
- except expect_error, exc:
+ except expect_error as exc:
pass
self.failUnlessIn(str(exc), str(test_error))
@@ -1036,13 +1038,13 @@ class change_file_creation_mask_TestCase(scaffold.TestCase):
self.mock_tracker = scaffold.MockTracker()
scaffold.mock(
- "os.umask",
- tracker=self.mock_tracker)
+ "os.umask",
+ tracker=self.mock_tracker)
self.test_mask = object()
self.test_args = dict(
- mask=self.test_mask,
- )
+ mask=self.test_mask,
+ )
def tearDown(self):
""" Tear down test fixtures. """
@@ -1053,8 +1055,8 @@ class change_file_creation_mask_TestCase(scaffold.TestCase):
args = self.test_args
mask = self.test_mask
expect_mock_output = """\
- Called os.umask(%(mask)r)
- """ % vars()
+ Called os.umask(%(mask)r)
+ """ % vars()
daemon.daemon.change_file_creation_mask(**args)
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -1065,8 +1067,8 @@ class change_file_creation_mask_TestCase(scaffold.TestCase):
os.umask.mock_raises = test_error
expect_error = daemon.daemon.DaemonOSEnvironmentError
self.failUnlessRaises(
- expect_error,
- daemon.daemon.change_file_creation_mask, **args)
+ expect_error,
+ daemon.daemon.change_file_creation_mask, **args)
def test_error_message_contains_original_error_message(self):
""" Should raise a DaemonError with original message. """
@@ -1076,7 +1078,7 @@ class change_file_creation_mask_TestCase(scaffold.TestCase):
expect_error = daemon.daemon.DaemonOSEnvironmentError
try:
daemon.daemon.change_file_creation_mask(**args)
- except expect_error, exc:
+ except expect_error as exc:
pass
self.failUnlessIn(str(exc), str(test_error))
@@ -1089,18 +1091,18 @@ class change_process_owner_TestCase(scaffold.TestCase):
self.mock_tracker = scaffold.MockTracker()
scaffold.mock(
- "os.setuid",
- tracker=self.mock_tracker)
+ "os.setuid",
+ tracker=self.mock_tracker)
scaffold.mock(
- "os.setgid",
- tracker=self.mock_tracker)
+ "os.setgid",
+ tracker=self.mock_tracker)
self.test_uid = object()
self.test_gid = object()
self.test_args = dict(
- uid=self.test_uid,
- gid=self.test_gid,
- )
+ uid=self.test_uid,
+ gid=self.test_gid,
+ )
def tearDown(self):
""" Tear down test fixtures. """
@@ -1116,9 +1118,9 @@ class change_process_owner_TestCase(scaffold.TestCase):
"""
args = self.test_args
expect_mock_output = """\
- Called os.setgid(...)
- Called os.setuid(...)
- """ % vars()
+ Called os.setgid(...)
+ Called os.setuid(...)
+ """ % vars()
daemon.daemon.change_process_owner(**args)
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -1127,9 +1129,9 @@ class change_process_owner_TestCase(scaffold.TestCase):
args = self.test_args
gid = self.test_gid
expect_mock_output = """\
- Called os.setgid(%(gid)r)
- ...
- """ % vars()
+ Called os.setgid(%(gid)r)
+ ...
+ """ % vars()
daemon.daemon.change_process_owner(**args)
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -1138,9 +1140,9 @@ class change_process_owner_TestCase(scaffold.TestCase):
args = self.test_args
uid = self.test_uid
expect_mock_output = """\
- ...
- Called os.setuid(%(uid)r)
- """ % vars()
+ ...
+ Called os.setuid(%(uid)r)
+ """ % vars()
daemon.daemon.change_process_owner(**args)
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -1151,8 +1153,8 @@ class change_process_owner_TestCase(scaffold.TestCase):
os.setgid.mock_raises = test_error
expect_error = daemon.daemon.DaemonOSEnvironmentError
self.failUnlessRaises(
- expect_error,
- daemon.daemon.change_process_owner, **args)
+ expect_error,
+ daemon.daemon.change_process_owner, **args)
def test_raises_daemon_error_on_os_error_from_setuid(self):
""" Should raise a DaemonError on receiving an OSError from setuid. """
@@ -1161,8 +1163,8 @@ class change_process_owner_TestCase(scaffold.TestCase):
os.setuid.mock_raises = test_error
expect_error = daemon.daemon.DaemonOSEnvironmentError
self.failUnlessRaises(
- expect_error,
- daemon.daemon.change_process_owner, **args)
+ expect_error,
+ daemon.daemon.change_process_owner, **args)
def test_error_message_contains_original_error_message(self):
""" Should raise a DaemonError with original message. """
@@ -1172,7 +1174,7 @@ class change_process_owner_TestCase(scaffold.TestCase):
expect_error = daemon.daemon.DaemonOSEnvironmentError
try:
daemon.daemon.change_process_owner(**args)
- except expect_error, exc:
+ except expect_error as exc:
pass
self.failUnlessIn(str(exc), str(test_error))
@@ -1186,14 +1188,14 @@ class prevent_core_dump_TestCase(scaffold.TestCase):
self.RLIMIT_CORE = object()
scaffold.mock(
- "resource.RLIMIT_CORE", mock_obj=self.RLIMIT_CORE,
- tracker=self.mock_tracker)
+ "resource.RLIMIT_CORE", mock_obj=self.RLIMIT_CORE,
+ tracker=self.mock_tracker)
scaffold.mock(
- "resource.getrlimit", returns=None,
- tracker=self.mock_tracker)
+ "resource.getrlimit", returns=None,
+ tracker=self.mock_tracker)
scaffold.mock(
- "resource.setrlimit", returns=None,
- tracker=self.mock_tracker)
+ "resource.setrlimit", returns=None,
+ tracker=self.mock_tracker)
def tearDown(self):
""" Tear down test fixtures. """
@@ -1204,12 +1206,12 @@ class prevent_core_dump_TestCase(scaffold.TestCase):
expect_resource = self.RLIMIT_CORE
expect_limit = (0, 0)
expect_mock_output = """\
- Called resource.getrlimit(
- %(expect_resource)r)
- Called resource.setrlimit(
- %(expect_resource)r,
- %(expect_limit)r)
- """ % vars()
+ Called resource.getrlimit(
+ %(expect_resource)r)
+ Called resource.setrlimit(
+ %(expect_resource)r,
+ %(expect_limit)r)
+ """ % vars()
daemon.daemon.prevent_core_dump()
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -1223,8 +1225,8 @@ class prevent_core_dump_TestCase(scaffold.TestCase):
resource.getrlimit.mock_returns_func = mock_getrlimit
expect_error = daemon.daemon.DaemonOSEnvironmentError
self.failUnlessRaises(
- expect_error,
- daemon.daemon.prevent_core_dump)
+ expect_error,
+ daemon.daemon.prevent_core_dump)
class close_file_descriptor_if_open_TestCase(scaffold.TestCase):
@@ -1237,8 +1239,8 @@ class close_file_descriptor_if_open_TestCase(scaffold.TestCase):
self.test_fd = 274
scaffold.mock(
- "os.close",
- tracker=self.mock_tracker)
+ "os.close",
+ tracker=self.mock_tracker)
def tearDown(self):
""" Tear down test fixtures. """
@@ -1248,8 +1250,8 @@ class close_file_descriptor_if_open_TestCase(scaffold.TestCase):
""" Should request close of file descriptor. """
fd = self.test_fd
expect_mock_output = """\
- Called os.close(%(fd)r)
- """ % vars()
+ Called os.close(%(fd)r)
+ """ % vars()
daemon.daemon.close_file_descriptor_if_open(fd)
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -1261,8 +1263,8 @@ class close_file_descriptor_if_open_TestCase(scaffold.TestCase):
raise test_error
os.close.mock_returns_func = os_close
expect_mock_output = """\
- Called os.close(%(fd)r)
- """ % vars()
+ Called os.close(%(fd)r)
+ """ % vars()
daemon.daemon.close_file_descriptor_if_open(fd)
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -1275,8 +1277,8 @@ class close_file_descriptor_if_open_TestCase(scaffold.TestCase):
os.close.mock_returns_func = os_close
expect_error = daemon.daemon.DaemonOSEnvironmentError
self.failUnlessRaises(
- expect_error,
- daemon.daemon.close_file_descriptor_if_open, fd)
+ expect_error,
+ daemon.daemon.close_file_descriptor_if_open, fd)
class maxfd_TestCase(scaffold.TestCase):
@@ -1304,9 +1306,9 @@ class maxfd_TestCase(scaffold.TestCase):
expect_minimum = 2048
maxfd = daemon.daemon.MAXFD
self.failUnless(
- expect_minimum <= maxfd,
- msg="MAXFD should be at least %(expect_minimum)r (got %(maxfd)r)"
- % vars())
+ expect_minimum <= maxfd,
+ msg="MAXFD should be at least %(expect_minimum)r (got %(maxfd)r)"
+ % vars())
class get_maximum_file_descriptors_TestCase(scaffold.TestCase):
@@ -1328,18 +1330,18 @@ class get_maximum_file_descriptors_TestCase(scaffold.TestCase):
self.test_maxfd = object()
scaffold.mock(
- "daemon.daemon.MAXFD", mock_obj=self.test_maxfd,
- tracker=self.mock_tracker)
+ "daemon.daemon.MAXFD", mock_obj=self.test_maxfd,
+ tracker=self.mock_tracker)
scaffold.mock(
- "resource.RLIMIT_NOFILE", mock_obj=self.RLIMIT_NOFILE,
- tracker=self.mock_tracker)
+ "resource.RLIMIT_NOFILE", mock_obj=self.RLIMIT_NOFILE,
+ tracker=self.mock_tracker)
scaffold.mock(
- "resource.RLIM_INFINITY", mock_obj=self.RLIM_INFINITY,
- tracker=self.mock_tracker)
+ "resource.RLIM_INFINITY", mock_obj=self.RLIM_INFINITY,
+ tracker=self.mock_tracker)
scaffold.mock(
- "resource.getrlimit", returns_func=mock_getrlimit,
- tracker=self.mock_tracker)
+ "resource.getrlimit", returns_func=mock_getrlimit,
+ tracker=self.mock_tracker)
def tearDown(self):
""" Tear down test fixtures. """
@@ -1378,23 +1380,23 @@ class close_all_open_files_TestCase(scaffold.TestCase):
self.test_maxfd = 8
scaffold.mock(
- "daemon.daemon.get_maximum_file_descriptors",
- returns=self.test_maxfd,
- tracker=self.mock_tracker)
+ "daemon.daemon.get_maximum_file_descriptors",
+ returns=self.test_maxfd,
+ tracker=self.mock_tracker)
scaffold.mock(
- "resource.RLIMIT_NOFILE", mock_obj=self.RLIMIT_NOFILE,
- tracker=self.mock_tracker)
+ "resource.RLIMIT_NOFILE", mock_obj=self.RLIMIT_NOFILE,
+ tracker=self.mock_tracker)
scaffold.mock(
- "resource.RLIM_INFINITY", mock_obj=self.RLIM_INFINITY,
- tracker=self.mock_tracker)
+ "resource.RLIM_INFINITY", mock_obj=self.RLIM_INFINITY,
+ tracker=self.mock_tracker)
scaffold.mock(
- "resource.getrlimit", returns_func=mock_getrlimit,
- tracker=self.mock_tracker)
+ "resource.getrlimit", returns_func=mock_getrlimit,
+ tracker=self.mock_tracker)
scaffold.mock(
- "daemon.daemon.close_file_descriptor_if_open",
- tracker=self.mock_tracker)
+ "daemon.daemon.close_file_descriptor_if_open",
+ tracker=self.mock_tracker)
def tearDown(self):
""" Tear down test fixtures. """
@@ -1404,9 +1406,9 @@ class close_all_open_files_TestCase(scaffold.TestCase):
""" Should request close of all open files. """
expect_file_descriptors = reversed(range(self.test_maxfd))
expect_mock_output = "...\n" + "".join(
- "Called daemon.daemon.close_file_descriptor_if_open(%(fd)r)\n"
- % vars()
- for fd in expect_file_descriptors)
+ "Called daemon.daemon.close_file_descriptor_if_open(%(fd)r)\n"
+ % vars()
+ for fd in expect_file_descriptors)
daemon.daemon.close_all_open_files()
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -1414,15 +1416,15 @@ class close_all_open_files_TestCase(scaffold.TestCase):
""" Should request close of all open files but those excluded. """
test_exclude = set([3, 7])
args = dict(
- exclude = test_exclude,
- )
+ exclude=test_exclude,
+ )
expect_file_descriptors = (
fd for fd in reversed(range(self.test_maxfd))
if fd not in test_exclude)
expect_mock_output = "...\n" + "".join(
- "Called daemon.daemon.close_file_descriptor_if_open(%(fd)r)\n"
- % vars()
- for fd in expect_file_descriptors)
+ "Called daemon.daemon.close_file_descriptor_if_open(%(fd)r)\n"
+ % vars()
+ for fd in expect_file_descriptors)
daemon.daemon.close_all_open_files(**args)
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -1439,18 +1441,18 @@ class detach_process_context_TestCase(scaffold.TestCase):
test_pids = [0, 0]
scaffold.mock(
- "os.fork", returns_iter=test_pids,
- tracker=self.mock_tracker)
+ "os.fork", returns_iter=test_pids,
+ tracker=self.mock_tracker)
scaffold.mock(
- "os.setsid",
- tracker=self.mock_tracker)
+ "os.setsid",
+ tracker=self.mock_tracker)
def raise_os_exit(status=None):
raise self.FakeOSExit(status)
scaffold.mock(
- "os._exit", returns_func=raise_os_exit,
- tracker=self.mock_tracker)
+ "os._exit", returns_func=raise_os_exit,
+ tracker=self.mock_tracker)
def tearDown(self):
""" Tear down test fixtures. """
@@ -1459,15 +1461,16 @@ class detach_process_context_TestCase(scaffold.TestCase):
def test_parent_exits(self):
""" Parent process should exit. """
parent_pid = 23
- scaffold.mock("os.fork", returns_iter=[parent_pid],
- tracker=self.mock_tracker)
+ scaffold.mock(
+ "os.fork", returns_iter=[parent_pid],
+ tracker=self.mock_tracker)
expect_mock_output = """\
- Called os.fork()
- Called os._exit(0)
- """
+ Called os.fork()
+ Called os._exit(0)
+ """
self.failUnlessRaises(
- self.FakeOSExit,
- daemon.daemon.detach_process_context)
+ self.FakeOSExit,
+ daemon.daemon.detach_process_context)
self.failUnlessMockCheckerMatch(expect_mock_output)
def test_first_fork_error_raises_error(self):
@@ -1478,46 +1481,50 @@ class detach_process_context_TestCase(scaffold.TestCase):
test_pids_iter = iter([fork_error])
def mock_fork():
- next = test_pids_iter.next()
- if isinstance(next, Exception):
- raise next
+ next_item = test_pids_iter.next()
+ if isinstance(next_item, Exception):
+ raise next_item
else:
- return next
+ return next_item
- scaffold.mock("os.fork", returns_func=mock_fork,
- tracker=self.mock_tracker)
+ scaffold.mock(
+ "os.fork",
+ returns_func=mock_fork,
+ tracker=self.mock_tracker)
expect_mock_output = """\
- Called os.fork()
- """
+ Called os.fork()
+ """
self.failUnlessRaises(
- daemon.daemon.DaemonProcessDetachError,
- daemon.daemon.detach_process_context)
+ daemon.daemon.DaemonProcessDetachError,
+ daemon.daemon.detach_process_context)
self.failUnlessMockCheckerMatch(expect_mock_output)
def test_child_starts_new_process_group(self):
""" Child should start new process group. """
expect_mock_output = """\
- Called os.fork()
- Called os.setsid()
- ...
- """
+ Called os.fork()
+ Called os.setsid()
+ ...
+ """
daemon.daemon.detach_process_context()
self.failUnlessMockCheckerMatch(expect_mock_output)
def test_child_forks_next_parent_exits(self):
""" Child should fork, then exit if parent. """
test_pids = [0, 42]
- scaffold.mock("os.fork", returns_iter=test_pids,
- tracker=self.mock_tracker)
+ scaffold.mock(
+ "os.fork",
+ returns_iter=test_pids,
+ tracker=self.mock_tracker)
expect_mock_output = """\
- Called os.fork()
- Called os.setsid()
- Called os.fork()
- Called os._exit(0)
- """
+ Called os.fork()
+ Called os.setsid()
+ Called os.fork()
+ Called os._exit(0)
+ """
self.failUnlessRaises(
- self.FakeOSExit,
- daemon.daemon.detach_process_context)
+ self.FakeOSExit,
+ daemon.daemon.detach_process_context)
self.failUnlessMockCheckerMatch(expect_mock_output)
def test_second_fork_error_reports_to_stderr(self):
@@ -1528,31 +1535,33 @@ class detach_process_context_TestCase(scaffold.TestCase):
test_pids_iter = iter([0, fork_error])
def mock_fork():
- next = test_pids_iter.next()
- if isinstance(next, Exception):
- raise next
+ next_item = test_pids_iter.next()
+ if isinstance(next_item, Exception):
+ raise next_item
else:
- return next
+ return next_item
- scaffold.mock("os.fork", returns_func=mock_fork,
- tracker=self.mock_tracker)
+ scaffold.mock(
+ "os.fork",
+ returns_func=mock_fork,
+ tracker=self.mock_tracker)
expect_mock_output = """\
- Called os.fork()
- Called os.setsid()
- Called os.fork()
- """
+ Called os.fork()
+ Called os.setsid()
+ Called os.fork()
+ """
self.failUnlessRaises(
- daemon.daemon.DaemonProcessDetachError,
- daemon.daemon.detach_process_context)
+ daemon.daemon.DaemonProcessDetachError,
+ daemon.daemon.detach_process_context)
self.failUnlessMockCheckerMatch(expect_mock_output)
def test_child_forks_next_child_continues(self):
""" Child should fork, then continue if child. """
expect_mock_output = """\
- Called os.fork()
- Called os.setsid()
- Called os.fork()
- """ % vars()
+ Called os.fork()
+ Called os.setsid()
+ Called os.fork()
+ """ % vars()
daemon.daemon.detach_process_context()
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -1567,9 +1576,9 @@ class is_process_started_by_init_TestCase(scaffold.TestCase):
self.test_ppid = 765
scaffold.mock(
- "os.getppid",
- returns=self.test_ppid,
- tracker=self.mock_tracker)
+ "os.getppid",
+ returns=self.test_ppid,
+ tracker=self.mock_tracker)
def tearDown(self):
""" Tear down test fixtures. """
@@ -1606,21 +1615,21 @@ class is_socket_TestCase(scaffold.TestCase):
self.mock_socket_getsockopt_func = mock_getsockopt
self.mock_socket_error = socket.error(
- errno.ENOTSOCK,
- "Socket operation on non-socket")
+ errno.ENOTSOCK,
+ "Socket operation on non-socket")
self.mock_socket = scaffold.Mock(
- "socket.socket",
- tracker=self.mock_tracker)
+ "socket.socket",
+ tracker=self.mock_tracker)
self.mock_socket.getsockopt.mock_raises = self.mock_socket_error
def mock_socket_fromfd(fd, family, type, proto=None):
return self.mock_socket
scaffold.mock(
- "socket.fromfd",
- returns_func=mock_socket_fromfd,
- tracker=self.mock_tracker)
+ "socket.fromfd",
+ returns_func=mock_socket_fromfd,
+ tracker=self.mock_tracker)
def tearDown(self):
""" Tear down test fixtures. """
@@ -1648,7 +1657,7 @@ class is_socket_TestCase(scaffold.TestCase):
test_fd = 23
getsockopt = self.mock_socket.getsockopt
getsockopt.mock_raises = socket.error(
- object(), "Weird socket stuff")
+ object(), "Weird socket stuff")
expect_result = True
result = daemon.daemon.is_socket(test_fd)
self.failUnlessIs(expect_result, result)
@@ -1671,9 +1680,9 @@ class is_process_started_by_superserver_TestCase(scaffold.TestCase):
self.mock_stdin_is_socket_func = (lambda: False)
scaffold.mock(
- "daemon.daemon.is_socket",
- returns_func=mock_is_socket,
- tracker=self.mock_tracker)
+ "daemon.daemon.is_socket",
+ returns_func=mock_is_socket,
+ tracker=self.mock_tracker)
def tearDown(self):
""" Tear down test fixtures. """
@@ -1701,11 +1710,11 @@ class is_detach_process_context_required_TestCase(scaffold.TestCase):
self.mock_tracker = scaffold.MockTracker()
scaffold.mock(
- "daemon.daemon.is_process_started_by_init",
- tracker=self.mock_tracker)
+ "daemon.daemon.is_process_started_by_init",
+ tracker=self.mock_tracker)
scaffold.mock(
- "daemon.daemon.is_process_started_by_superserver",
- tracker=self.mock_tracker)
+ "daemon.daemon.is_process_started_by_superserver",
+ tracker=self.mock_tracker)
def tearDown(self):
""" Tear down test fixtures. """
@@ -1737,25 +1746,25 @@ def setup_streams_fixtures(testcase):
testcase.mock_tracker = scaffold.MockTracker()
testcase.stream_file_paths = dict(
- stdin = tempfile.mktemp(),
- stdout = tempfile.mktemp(),
- stderr = tempfile.mktemp(),
- )
+ stdin=tempfile.mktemp(),
+ stdout=tempfile.mktemp(),
+ stderr=tempfile.mktemp(),
+ )
testcase.stream_files_by_name = dict(
- (name, FakeFileDescriptorStringIO())
- for name in ['stdin', 'stdout', 'stderr']
- )
+ (name, FakeFileDescriptorStringIO())
+ for name in ['stdin', 'stdout', 'stderr']
+ )
testcase.stream_files_by_path = dict(
- (testcase.stream_file_paths[name],
- testcase.stream_files_by_name[name])
- for name in ['stdin', 'stdout', 'stderr']
- )
+ (testcase.stream_file_paths[name],
+ testcase.stream_files_by_name[name])
+ for name in ['stdin', 'stdout', 'stderr']
+ )
scaffold.mock(
- "os.dup2",
- tracker=testcase.mock_tracker)
+ "os.dup2",
+ tracker=testcase.mock_tracker)
class redirect_stream_TestCase(scaffold.TestCase):
@@ -1777,9 +1786,9 @@ class redirect_stream_TestCase(scaffold.TestCase):
return result
scaffold.mock(
- "os.open",
- returns_func=mock_open,
- tracker=self.mock_tracker)
+ "os.open",
+ returns_func=mock_open,
+ tracker=self.mock_tracker)
def tearDown(self):
""" Tear down test fixtures. """
@@ -1792,8 +1801,8 @@ class redirect_stream_TestCase(scaffold.TestCase):
target_stream = self.test_target_stream
target_fileno = target_stream.fileno()
expect_mock_output = """\
- Called os.dup2(%(target_fileno)r, %(system_fileno)r)
- """ % vars()
+ Called os.dup2(%(target_fileno)r, %(system_fileno)r)
+ """ % vars()
daemon.daemon.redirect_stream(system_stream, target_stream)
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -1807,9 +1816,9 @@ class redirect_stream_TestCase(scaffold.TestCase):
null_file = self.test_null_file
null_fileno = null_file.fileno()
expect_mock_output = """\
- Called os.open(%(null_path)r, %(null_flag)r)
- Called os.dup2(%(null_fileno)r, %(system_fileno)r)
- """ % vars()
+ Called os.open(%(null_path)r, %(null_flag)r)
+ Called os.dup2(%(null_fileno)r, %(system_fileno)r)
+ """ % vars()
daemon.daemon.redirect_stream(system_stream, target_stream)
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -1821,38 +1830,38 @@ class make_default_signal_map_TestCase(scaffold.TestCase):
""" Set up test fixtures. """
self.mock_tracker = scaffold.MockTracker()
- mock_signal_module = ModuleType('signal')
+ mock_signal_module = ModuleType(b'signal')
mock_signal_names = [
- 'SIGHUP',
- 'SIGCLD',
- 'SIGSEGV',
- 'SIGTSTP',
- 'SIGTTIN',
- 'SIGTTOU',
- 'SIGTERM',
- ]
+ 'SIGHUP',
+ 'SIGCLD',
+ 'SIGSEGV',
+ 'SIGTSTP',
+ 'SIGTTIN',
+ 'SIGTTOU',
+ 'SIGTERM',
+ ]
for name in mock_signal_names:
setattr(mock_signal_module, name, object())
scaffold.mock(
- "signal",
- mock_obj=mock_signal_module,
- tracker=self.mock_tracker)
+ "signal",
+ mock_obj=mock_signal_module,
+ tracker=self.mock_tracker)
scaffold.mock(
- "daemon.daemon.signal",
- mock_obj=mock_signal_module,
- tracker=self.mock_tracker)
+ "daemon.daemon.signal",
+ mock_obj=mock_signal_module,
+ tracker=self.mock_tracker)
default_signal_map_by_name = {
- 'SIGTSTP': None,
- 'SIGTTIN': None,
- 'SIGTTOU': None,
- 'SIGTERM': 'terminate',
- }
+ 'SIGTSTP': None,
+ 'SIGTTIN': None,
+ 'SIGTTOU': None,
+ 'SIGTERM': 'terminate',
+ }
self.default_signal_map = dict(
- (getattr(signal, name), target)
- for (name, target) in default_signal_map_by_name.items())
+ (getattr(signal, name), target)
+ for (name, target) in default_signal_map_by_name.items())
def tearDown(self):
""" Tear down test fixtures. """
@@ -1888,14 +1897,14 @@ class set_signal_handlers_TestCase(scaffold.TestCase):
self.mock_tracker = scaffold.MockTracker()
scaffold.mock(
- "signal.signal",
- tracker=self.mock_tracker)
+ "signal.signal",
+ tracker=self.mock_tracker)
self.signal_handler_map = {
- signal.SIGQUIT: object(),
- signal.SIGSEGV: object(),
- signal.SIGINT: object(),
- }
+ signal.SIGQUIT: object(),
+ signal.SIGSEGV: object(),
+ signal.SIGINT: object(),
+ }
def tearDown(self):
""" Tear down test fixtures. """
@@ -1905,9 +1914,9 @@ class set_signal_handlers_TestCase(scaffold.TestCase):
""" Should set signal handler for each item in map. """
signal_handler_map = self.signal_handler_map
expect_mock_output = "".join(
- "Called signal.signal(%(signal_number)r, %(handler)r)\n"
- % vars()
- for (signal_number, handler) in signal_handler_map.items())
+ "Called signal.signal(%(signal_number)r, %(handler)r)\n"
+ % vars()
+ for (signal_number, handler) in signal_handler_map.items())
daemon.daemon.set_signal_handlers(signal_handler_map)
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -1920,8 +1929,8 @@ class register_atexit_function_TestCase(scaffold.TestCase):
self.mock_tracker = scaffold.MockTracker()
scaffold.mock(
- "atexit.register",
- tracker=self.mock_tracker)
+ "atexit.register",
+ tracker=self.mock_tracker)
def tearDown(self):
""" Tear down test fixtures. """
@@ -1931,7 +1940,14 @@ class register_atexit_function_TestCase(scaffold.TestCase):
""" Should register specified function for atexit processing. """
func = object()
expect_mock_output = """\
- Called atexit.register(%(func)r)
- """ % vars()
+ Called atexit.register(%(func)r)
+ """ % vars()
daemon.daemon.register_atexit_function(func)
self.failUnlessMockCheckerMatch(expect_mock_output)
+
+
+# Local variables:
+# coding: utf-8
+# mode: python
+# End:
+# vim: fileencoding=utf-8 filetype=python :
diff --git a/test/test_pidfile.py b/test/test_pidfile.py
new file mode 100644
index 0000000..6c31a0e
--- /dev/null
+++ b/test/test_pidfile.py
@@ -0,0 +1,407 @@
+# -*- coding: utf-8 -*-
+#
+# test/test_pidfile.py
+# Part of ‘python-daemon’, an implementation of PEP 3143.
+#
+# Copyright © 2008–2014 Ben Finney <ben+python@benfinney.id.au>
+#
+# This is free software: you may copy, modify, and/or distribute this work
+# under the terms of the Apache License, version 2.0 as published by the
+# Apache Software Foundation.
+# No warranty expressed or implied. See the file LICENSE.ASF-2 for details.
+
+""" Unit test for ‘pidfile’ module.
+ """
+
+from __future__ import unicode_literals
+
+import __builtin__ as builtins
+import os
+from StringIO import StringIO
+import itertools
+import tempfile
+import errno
+
+import lockfile
+from lockfile import pidlockfile
+
+import scaffold
+
+import daemon.pidfile
+
+
+class FakeFileDescriptorStringIO(StringIO, object):
+ """ A StringIO class that fakes a file descriptor. """
+
+ _fileno_generator = itertools.count()
+
+ def __init__(self, *args, **kwargs):
+ self._fileno = self._fileno_generator.next()
+ super_instance = super(FakeFileDescriptorStringIO, self)
+ super_instance.__init__(*args, **kwargs)
+
+ def fileno(self):
+ return self._fileno
+
+
+def make_pidlockfile_scenarios():
+ """ Make a collection of scenarios for testing PIDLockFile instances. """
+
+ mock_current_pid = 235
+ mock_other_pid = 8642
+ mock_pidfile_path = tempfile.mktemp()
+
+ mock_pidfile_empty = FakeFileDescriptorStringIO()
+ mock_pidfile_current_pid = FakeFileDescriptorStringIO(
+ "%(mock_current_pid)d\n" % vars())
+ mock_pidfile_other_pid = FakeFileDescriptorStringIO(
+ "%(mock_other_pid)d\n" % vars())
+ mock_pidfile_bogus = FakeFileDescriptorStringIO(
+ "b0gUs")
+
+ scenarios = {
+ 'simple': {},
+ 'not-exist': {
+ 'open_func_name': 'mock_open_nonexist',
+ 'os_open_func_name': 'mock_os_open_nonexist',
+ },
+ 'not-exist-write-denied': {
+ 'open_func_name': 'mock_open_nonexist',
+ 'os_open_func_name': 'mock_os_open_nonexist',
+ },
+ 'not-exist-write-busy': {
+ 'open_func_name': 'mock_open_nonexist',
+ 'os_open_func_name': 'mock_os_open_nonexist',
+ },
+ 'exist-read-denied': {
+ 'open_func_name': 'mock_open_read_denied',
+ 'os_open_func_name': 'mock_os_open_read_denied',
+ },
+ 'exist-locked-read-denied': {
+ 'locking_pid': mock_other_pid,
+ 'open_func_name': 'mock_open_read_denied',
+ 'os_open_func_name': 'mock_os_open_read_denied',
+ },
+ 'exist-empty': {},
+ 'exist-invalid': {
+ 'pidfile': mock_pidfile_bogus,
+ },
+ 'exist-current-pid': {
+ 'pidfile': mock_pidfile_current_pid,
+ 'pidfile_pid': mock_current_pid,
+ },
+ 'exist-current-pid-locked': {
+ 'pidfile': mock_pidfile_current_pid,
+ 'pidfile_pid': mock_current_pid,
+ 'locking_pid': mock_current_pid,
+ },
+ 'exist-other-pid': {
+ 'pidfile': mock_pidfile_other_pid,
+ 'pidfile_pid': mock_other_pid,
+ },
+ 'exist-other-pid-locked': {
+ 'pidfile': mock_pidfile_other_pid,
+ 'pidfile_pid': mock_other_pid,
+ 'locking_pid': mock_other_pid,
+ },
+ }
+
+ for scenario in scenarios.values():
+ scenario['pid'] = mock_current_pid
+ scenario['path'] = mock_pidfile_path
+ if 'pidfile' not in scenario:
+ scenario['pidfile'] = mock_pidfile_empty
+ if 'pidfile_pid' not in scenario:
+ scenario['pidfile_pid'] = None
+ if 'locking_pid' not in scenario:
+ scenario['locking_pid'] = None
+ if 'open_func_name' not in scenario:
+ scenario['open_func_name'] = 'mock_open_okay'
+ if 'os_open_func_name' not in scenario:
+ scenario['os_open_func_name'] = 'mock_os_open_okay'
+
+ return scenarios
+
+
+def setup_pidfile_fixtures(testcase):
+ """ Set up common fixtures for PID file test cases. """
+ testcase.mock_tracker = scaffold.MockTracker()
+
+ scenarios = make_pidlockfile_scenarios()
+ testcase.pidlockfile_scenarios = scenarios
+
+ def get_scenario_option(testcase, key, default=None):
+ value = default
+ try:
+ value = testcase.scenario[key]
+ except (NameError, TypeError, AttributeError, KeyError):
+ pass
+ return value
+
+ scaffold.mock(
+ "os.getpid",
+ returns=scenarios['simple']['pid'],
+ tracker=testcase.mock_tracker)
+
+ def make_mock_open_funcs(testcase):
+
+ def mock_open_nonexist(filename, mode, buffering):
+ if 'r' in mode:
+ raise IOError(
+ errno.ENOENT, "No such file %(filename)r" % vars())
+ else:
+ result = testcase.scenario['pidfile']
+ return result
+
+ def mock_open_read_denied(filename, mode, buffering):
+ if 'r' in mode:
+ raise IOError(
+ errno.EPERM, "Read denied on %(filename)r" % vars())
+ else:
+ result = testcase.scenario['pidfile']
+ return result
+
+ def mock_open_okay(filename, mode, buffering):
+ result = testcase.scenario['pidfile']
+ return result
+
+ def mock_os_open_nonexist(filename, flags, mode):
+ if (flags & os.O_CREAT):
+ result = testcase.scenario['pidfile'].fileno()
+ else:
+ raise OSError(
+ errno.ENOENT, "No such file %(filename)r" % vars())
+ return result
+
+ def mock_os_open_read_denied(filename, flags, mode):
+ if (flags & os.O_CREAT):
+ result = testcase.scenario['pidfile'].fileno()
+ else:
+ raise OSError(
+ errno.EPERM, "Read denied on %(filename)r" % vars())
+ return result
+
+ def mock_os_open_okay(filename, flags, mode):
+ result = testcase.scenario['pidfile'].fileno()
+ return result
+
+ funcs = dict(
+ (name, obj) for (name, obj) in vars().items()
+ if hasattr(obj, '__call__'))
+
+ return funcs
+
+ testcase.mock_pidfile_open_funcs = make_mock_open_funcs(testcase)
+
+ def mock_open(filename, mode='r', buffering=None):
+ scenario_path = get_scenario_option(testcase, 'path')
+ if filename == scenario_path:
+ func_name = testcase.scenario['open_func_name']
+ mock_open_func = testcase.mock_pidfile_open_funcs[func_name]
+ result = mock_open_func(filename, mode, buffering)
+ else:
+ result = FakeFileDescriptorStringIO()
+ return result
+
+ scaffold.mock(
+ "builtins.open",
+ returns_func=mock_open,
+ tracker=testcase.mock_tracker)
+
+ def mock_os_open(filename, flags, mode=None):
+ scenario_path = get_scenario_option(testcase, 'path')
+ if filename == scenario_path:
+ func_name = testcase.scenario['os_open_func_name']
+ mock_os_open_func = testcase.mock_pidfile_open_funcs[func_name]
+ result = mock_os_open_func(filename, flags, mode)
+ else:
+ result = FakeFileDescriptorStringIO().fileno()
+ return result
+
+ scaffold.mock(
+ "os.open",
+ returns_func=mock_os_open,
+ tracker=testcase.mock_tracker)
+
+ def mock_os_fdopen(fd, mode='r', buffering=None):
+ scenario_pidfile = get_scenario_option(
+ testcase, 'pidfile', FakeFileDescriptorStringIO())
+ if fd == testcase.scenario['pidfile'].fileno():
+ result = testcase.scenario['pidfile']
+ else:
+ raise OSError(errno.EBADF, "Bad file descriptor")
+ return result
+
+ scaffold.mock(
+ "os.fdopen",
+ returns_func=mock_os_fdopen,
+ tracker=testcase.mock_tracker)
+
+ testcase.scenario = NotImplemented
+
+
+def setup_lockfile_method_mocks(testcase, scenario, class_name):
+ """ Set up common mock methods for lockfile class. """
+
+ def mock_read_pid():
+ return scenario['pidfile_pid']
+ def mock_is_locked():
+ return (scenario['locking_pid'] is not None)
+ def mock_i_am_locking():
+ return (
+ scenario['locking_pid'] == scenario['pid'])
+ def mock_acquire(timeout=None):
+ if scenario['locking_pid'] is not None:
+ raise lockfile.AlreadyLocked()
+ scenario['locking_pid'] = scenario['pid']
+ def mock_release():
+ if scenario['locking_pid'] is None:
+ raise lockfile.NotLocked()
+ if scenario['locking_pid'] != scenario['pid']:
+ raise lockfile.NotMyLock()
+ scenario['locking_pid'] = None
+ def mock_break_lock():
+ scenario['locking_pid'] = None
+
+ for func_name in [
+ 'read_pid',
+ 'is_locked', 'i_am_locking',
+ 'acquire', 'release', 'break_lock',
+ ]:
+ mock_func = vars()["mock_%(func_name)s" % vars()]
+ lockfile_func_name = "%(class_name)s.%(func_name)s" % vars()
+ mock_lockfile_func = scaffold.Mock(
+ lockfile_func_name,
+ returns_func=mock_func,
+ tracker=testcase.mock_tracker)
+ try:
+ scaffold.mock(
+ lockfile_func_name,
+ mock_obj=mock_lockfile_func,
+ tracker=testcase.mock_tracker)
+ except NameError:
+ pass
+
+
+def setup_pidlockfile_fixtures(testcase, scenario_name=None):
+ """ Set up common fixtures for PIDLockFile test cases. """
+
+ setup_pidfile_fixtures(testcase)
+
+ scaffold.mock(
+ "pidlockfile.write_pid_to_pidfile",
+ tracker=testcase.mock_tracker)
+ scaffold.mock(
+ "pidlockfile.remove_existing_pidfile",
+ tracker=testcase.mock_tracker)
+
+ if scenario_name is not None:
+ set_pidlockfile_scenario(testcase, scenario_name, clear_tracker=False)
+
+
+def set_pidlockfile_scenario(testcase, scenario_name, clear_tracker=True):
+ """ Set up the test case to the specified scenario. """
+ testcase.scenario = testcase.pidlockfile_scenarios[scenario_name]
+ setup_lockfile_method_mocks(
+ testcase, testcase.scenario, "lockfile.LinkLockFile")
+ testcase.pidlockfile_args = dict(
+ path=testcase.scenario['path'],
+ )
+ testcase.test_instance = pidlockfile.PIDLockFile(
+ **testcase.pidlockfile_args)
+ if clear_tracker:
+ testcase.mock_tracker.clear()
+
+
+class TimeoutPIDLockFile_TestCase(scaffold.TestCase):
+ """ Test cases for ‘TimeoutPIDLockFile’ class. """
+
+ def setUp(self):
+ """ Set up test fixtures. """
+ self.mock_tracker = scaffold.MockTracker()
+
+ pidlockfile_scenarios = make_pidlockfile_scenarios()
+ self.pidlockfile_scenario = pidlockfile_scenarios['simple']
+ pidfile_path = self.pidlockfile_scenario['path']
+
+ scaffold.mock(
+ "pidlockfile.PIDLockFile.__init__",
+ tracker=self.mock_tracker)
+ scaffold.mock(
+ "pidlockfile.PIDLockFile.acquire",
+ tracker=self.mock_tracker)
+
+ self.scenario = {
+ 'pidfile_path': self.pidlockfile_scenario['path'],
+ 'acquire_timeout': object(),
+ }
+
+ self.test_kwargs = dict(
+ path=self.scenario['pidfile_path'],
+ acquire_timeout=self.scenario['acquire_timeout'],
+ )
+ self.test_instance = daemon.pidfile.TimeoutPIDLockFile(
+ **self.test_kwargs)
+
+ def tearDown(self):
+ """ Tear down test fixtures. """
+ scaffold.mock_restore()
+
+ def test_inherits_from_pidlockfile(self):
+ """ Should inherit from PIDLockFile. """
+ instance = self.test_instance
+ self.failUnlessIsInstance(instance, pidlockfile.PIDLockFile)
+
+ def test_init_has_expected_signature(self):
+ """ Should have expected signature for ‘__init__’. """
+ def test_func(self, path, acquire_timeout=None, *args, **kwargs): pass
+ test_func.__name__ = b'__init__'
+ self.failUnlessFunctionSignatureMatch(
+ test_func,
+ daemon.pidfile.TimeoutPIDLockFile.__init__)
+
+ def test_has_specified_acquire_timeout(self):
+ """ Should have specified ‘acquire_timeout’ value. """
+ instance = self.test_instance
+ expect_timeout = self.test_kwargs['acquire_timeout']
+ self.failUnlessEqual(expect_timeout, instance.acquire_timeout)
+
+ def test_calls_superclass_init(self):
+ """ Should call the superclass ‘__init__’. """
+ expect_path = self.test_kwargs['path']
+ expect_mock_output = """\
+ Called pidlockfile.PIDLockFile.__init__(
+ %(expect_path)r)
+ """ % vars()
+ self.failUnlessMockCheckerMatch(expect_mock_output)
+
+ def test_acquire_uses_specified_timeout(self):
+ """ Should call the superclass ‘acquire’ with specified timeout. """
+ instance = self.test_instance
+ test_timeout = object()
+ expect_timeout = test_timeout
+ self.mock_tracker.clear()
+ expect_mock_output = """\
+ Called pidlockfile.PIDLockFile.acquire(%(expect_timeout)r)
+ """ % vars()
+ instance.acquire(test_timeout)
+ self.failUnlessMockCheckerMatch(expect_mock_output)
+
+ def test_acquire_uses_stored_timeout_by_default(self):
+ """ Should call superclass ‘acquire’ with stored timeout by default. """
+ instance = self.test_instance
+ test_timeout = self.test_kwargs['acquire_timeout']
+ expect_timeout = test_timeout
+ self.mock_tracker.clear()
+ expect_mock_output = """\
+ Called pidlockfile.PIDLockFile.acquire(%(expect_timeout)r)
+ """ % vars()
+ instance.acquire()
+ self.failUnlessMockCheckerMatch(expect_mock_output)
+
+
+# Local variables:
+# coding: utf-8
+# mode: python
+# End:
+# vim: fileencoding=utf-8 filetype=python :
diff --git a/test/test_pidlockfile.py b/test/test_pidlockfile.py
deleted file mode 100644
index c8f952e..0000000
--- a/test/test_pidlockfile.py
+++ /dev/null
@@ -1,791 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# test/test_pidlockfile.py
-# Part of python-daemon, an implementation of PEP 3143.
-#
-# Copyright © 2008–2010 Ben Finney <ben+python@benfinney.id.au>
-#
-# This is free software: you may copy, modify, and/or distribute this work
-# under the terms of the Python Software Foundation License, version 2 or
-# later as published by the Python Software Foundation.
-# No warranty expressed or implied. See the file LICENSE.PSF-2 for details.
-
-""" Unit test for pidlockfile module.
- """
-
-import __builtin__
-import os
-from StringIO import StringIO
-import itertools
-import tempfile
-import errno
-
-import lockfile
-
-import scaffold
-from daemon import pidlockfile
-
-
-class FakeFileDescriptorStringIO(StringIO, object):
- """ A StringIO class that fakes a file descriptor. """
-
- _fileno_generator = itertools.count()
-
- def __init__(self, *args, **kwargs):
- self._fileno = self._fileno_generator.next()
- super_instance = super(FakeFileDescriptorStringIO, self)
- super_instance.__init__(*args, **kwargs)
-
- def fileno(self):
- return self._fileno
-
-
-class Exception_TestCase(scaffold.Exception_TestCase):
- """ Test cases for module exception classes. """
-
- def __init__(self, *args, **kwargs):
- """ Set up a new instance. """
- super(Exception_TestCase, self).__init__(*args, **kwargs)
-
- self.valid_exceptions = {
- pidlockfile.PIDFileError: dict(
- min_args = 1,
- types = (Exception,),
- ),
- pidlockfile.PIDFileParseError: dict(
- min_args = 2,
- types = (pidlockfile.PIDFileError, ValueError),
- ),
- }
-
-
-def make_pidlockfile_scenarios():
- """ Make a collection of scenarios for testing PIDLockFile instances. """
-
- mock_current_pid = 235
- mock_other_pid = 8642
- mock_pidfile_path = tempfile.mktemp()
-
- mock_pidfile_empty = FakeFileDescriptorStringIO()
- mock_pidfile_current_pid = FakeFileDescriptorStringIO(
- "%(mock_current_pid)d\n" % vars())
- mock_pidfile_other_pid = FakeFileDescriptorStringIO(
- "%(mock_other_pid)d\n" % vars())
- mock_pidfile_bogus = FakeFileDescriptorStringIO(
- "b0gUs")
-
- scenarios = {
- 'simple': {},
- 'not-exist': {
- 'open_func_name': 'mock_open_nonexist',
- 'os_open_func_name': 'mock_os_open_nonexist',
- },
- 'not-exist-write-denied': {
- 'open_func_name': 'mock_open_nonexist',
- 'os_open_func_name': 'mock_os_open_nonexist',
- },
- 'not-exist-write-busy': {
- 'open_func_name': 'mock_open_nonexist',
- 'os_open_func_name': 'mock_os_open_nonexist',
- },
- 'exist-read-denied': {
- 'open_func_name': 'mock_open_read_denied',
- 'os_open_func_name': 'mock_os_open_read_denied',
- },
- 'exist-locked-read-denied': {
- 'locking_pid': mock_other_pid,
- 'open_func_name': 'mock_open_read_denied',
- 'os_open_func_name': 'mock_os_open_read_denied',
- },
- 'exist-empty': {},
- 'exist-invalid': {
- 'pidfile': mock_pidfile_bogus,
- },
- 'exist-current-pid': {
- 'pidfile': mock_pidfile_current_pid,
- 'pidfile_pid': mock_current_pid,
- },
- 'exist-current-pid-locked': {
- 'pidfile': mock_pidfile_current_pid,
- 'pidfile_pid': mock_current_pid,
- 'locking_pid': mock_current_pid,
- },
- 'exist-other-pid': {
- 'pidfile': mock_pidfile_other_pid,
- 'pidfile_pid': mock_other_pid,
- },
- 'exist-other-pid-locked': {
- 'pidfile': mock_pidfile_other_pid,
- 'pidfile_pid': mock_other_pid,
- 'locking_pid': mock_other_pid,
- },
- }
-
- for scenario in scenarios.values():
- scenario['pid'] = mock_current_pid
- scenario['path'] = mock_pidfile_path
- if 'pidfile' not in scenario:
- scenario['pidfile'] = mock_pidfile_empty
- if 'pidfile_pid' not in scenario:
- scenario['pidfile_pid'] = None
- if 'locking_pid' not in scenario:
- scenario['locking_pid'] = None
- if 'open_func_name' not in scenario:
- scenario['open_func_name'] = 'mock_open_okay'
- if 'os_open_func_name' not in scenario:
- scenario['os_open_func_name'] = 'mock_os_open_okay'
-
- return scenarios
-
-
-def setup_pidfile_fixtures(testcase):
- """ Set up common fixtures for PID file test cases. """
- testcase.mock_tracker = scaffold.MockTracker()
-
- scenarios = make_pidlockfile_scenarios()
- testcase.pidlockfile_scenarios = scenarios
-
- def get_scenario_option(testcase, key, default=None):
- value = default
- try:
- value = testcase.scenario[key]
- except (NameError, TypeError, AttributeError, KeyError):
- pass
- return value
-
- scaffold.mock(
- "os.getpid",
- returns=scenarios['simple']['pid'],
- tracker=testcase.mock_tracker)
-
- def make_mock_open_funcs(testcase):
-
- def mock_open_nonexist(filename, mode, buffering):
- if 'r' in mode:
- raise IOError(
- errno.ENOENT, "No such file %(filename)r" % vars())
- else:
- result = testcase.scenario['pidfile']
- return result
-
- def mock_open_read_denied(filename, mode, buffering):
- if 'r' in mode:
- raise IOError(
- errno.EPERM, "Read denied on %(filename)r" % vars())
- else:
- result = testcase.scenario['pidfile']
- return result
-
- def mock_open_okay(filename, mode, buffering):
- result = testcase.scenario['pidfile']
- return result
-
- def mock_os_open_nonexist(filename, flags, mode):
- if (flags & os.O_CREAT):
- result = testcase.scenario['pidfile'].fileno()
- else:
- raise OSError(
- errno.ENOENT, "No such file %(filename)r" % vars())
- return result
-
- def mock_os_open_read_denied(filename, flags, mode):
- if (flags & os.O_CREAT):
- result = testcase.scenario['pidfile'].fileno()
- else:
- raise OSError(
- errno.EPERM, "Read denied on %(filename)r" % vars())
- return result
-
- def mock_os_open_okay(filename, flags, mode):
- result = testcase.scenario['pidfile'].fileno()
- return result
-
- funcs = dict(
- (name, obj) for (name, obj) in vars().items()
- if hasattr(obj, '__call__'))
-
- return funcs
-
- testcase.mock_pidfile_open_funcs = make_mock_open_funcs(testcase)
-
- def mock_open(filename, mode='r', buffering=None):
- scenario_path = get_scenario_option(testcase, 'path')
- if filename == scenario_path:
- func_name = testcase.scenario['open_func_name']
- mock_open_func = testcase.mock_pidfile_open_funcs[func_name]
- result = mock_open_func(filename, mode, buffering)
- else:
- result = FakeFileDescriptorStringIO()
- return result
-
- scaffold.mock(
- "__builtin__.open",
- returns_func=mock_open,
- tracker=testcase.mock_tracker)
-
- def mock_os_open(filename, flags, mode=None):
- scenario_path = get_scenario_option(testcase, 'path')
- if filename == scenario_path:
- func_name = testcase.scenario['os_open_func_name']
- mock_os_open_func = testcase.mock_pidfile_open_funcs[func_name]
- result = mock_os_open_func(filename, flags, mode)
- else:
- result = FakeFileDescriptorStringIO().fileno()
- return result
-
- scaffold.mock(
- "os.open",
- returns_func=mock_os_open,
- tracker=testcase.mock_tracker)
-
- def mock_os_fdopen(fd, mode='r', buffering=None):
- scenario_pidfile = get_scenario_option(
- testcase, 'pidfile', FakeFileDescriptorStringIO())
- if fd == testcase.scenario['pidfile'].fileno():
- result = testcase.scenario['pidfile']
- else:
- raise OSError(errno.EBADF, "Bad file descriptor")
- return result
-
- scaffold.mock(
- "os.fdopen",
- returns_func=mock_os_fdopen,
- tracker=testcase.mock_tracker)
-
- testcase.scenario = NotImplemented
-
-
-def setup_lockfile_method_mocks(testcase, scenario, class_name):
- """ Set up common mock methods for lockfile class. """
-
- def mock_read_pid():
- return scenario['pidfile_pid']
- def mock_is_locked():
- return (scenario['locking_pid'] is not None)
- def mock_i_am_locking():
- return (
- scenario['locking_pid'] == scenario['pid'])
- def mock_acquire(timeout=None):
- if scenario['locking_pid'] is not None:
- raise lockfile.AlreadyLocked()
- scenario['locking_pid'] = scenario['pid']
- def mock_release():
- if scenario['locking_pid'] is None:
- raise lockfile.NotLocked()
- if scenario['locking_pid'] != scenario['pid']:
- raise lockfile.NotMyLock()
- scenario['locking_pid'] = None
- def mock_break_lock():
- scenario['locking_pid'] = None
-
- for func_name in [
- 'read_pid',
- 'is_locked', 'i_am_locking',
- 'acquire', 'release', 'break_lock',
- ]:
- mock_func = vars()["mock_%(func_name)s" % vars()]
- lockfile_func_name = "%(class_name)s.%(func_name)s" % vars()
- mock_lockfile_func = scaffold.Mock(
- lockfile_func_name,
- returns_func=mock_func,
- tracker=testcase.mock_tracker)
- try:
- scaffold.mock(
- lockfile_func_name,
- mock_obj=mock_lockfile_func,
- tracker=testcase.mock_tracker)
- except NameError:
- pass
-
-
-def setup_pidlockfile_fixtures(testcase, scenario_name=None):
- """ Set up common fixtures for PIDLockFile test cases. """
-
- setup_pidfile_fixtures(testcase)
-
- scaffold.mock(
- "pidlockfile.write_pid_to_pidfile",
- tracker=testcase.mock_tracker)
- scaffold.mock(
- "pidlockfile.remove_existing_pidfile",
- tracker=testcase.mock_tracker)
-
- if scenario_name is not None:
- set_pidlockfile_scenario(testcase, scenario_name, clear_tracker=False)
-
-
-def set_pidlockfile_scenario(testcase, scenario_name, clear_tracker=True):
- """ Set up the test case to the specified scenario. """
- testcase.scenario = testcase.pidlockfile_scenarios[scenario_name]
- setup_lockfile_method_mocks(
- testcase, testcase.scenario, "lockfile.LinkFileLock")
- testcase.pidlockfile_args = dict(
- path=testcase.scenario['path'],
- )
- testcase.test_instance = pidlockfile.PIDLockFile(
- **testcase.pidlockfile_args)
- if clear_tracker:
- testcase.mock_tracker.clear()
-
-
-class PIDLockFile_TestCase(scaffold.TestCase):
- """ Test cases for PIDLockFile class. """
-
- def setUp(self):
- """ Set up test fixtures. """
- setup_pidlockfile_fixtures(self, 'exist-other-pid')
-
- def tearDown(self):
- """ Tear down test fixtures. """
- scaffold.mock_restore()
-
- def test_instantiate(self):
- """ New instance of PIDLockFile should be created. """
- instance = self.test_instance
- self.failUnlessIsInstance(instance, pidlockfile.PIDLockFile)
-
- def test_inherits_from_linkfilelock(self):
- """ Should inherit from LinkFileLock. """
- instance = self.test_instance
- self.failUnlessIsInstance(instance, lockfile.LinkFileLock)
-
- def test_has_specified_path(self):
- """ Should have specified path. """
- instance = self.test_instance
- expect_path = self.scenario['path']
- self.failUnlessEqual(expect_path, instance.path)
-
-
-class PIDLockFile_read_pid_TestCase(scaffold.TestCase):
- """ Test cases for PIDLockFile.read_pid method. """
-
- def setUp(self):
- """ Set up test fixtures. """
- setup_pidlockfile_fixtures(self, 'exist-other-pid')
-
- def tearDown(self):
- """ Tear down test fixtures. """
- scaffold.mock_restore()
-
- def test_gets_pid_via_read_pid_from_pidfile(self):
- """ Should get PID via read_pid_from_pidfile. """
- instance = self.test_instance
- test_pid = self.scenario['pidfile_pid']
- expect_pid = test_pid
- result = instance.read_pid()
- self.failUnlessEqual(expect_pid, result)
-
-
-class PIDLockFile_acquire_TestCase(scaffold.TestCase):
- """ Test cases for PIDLockFile.acquire function. """
-
- def setUp(self):
- """ Set up test fixtures. """
- setup_pidlockfile_fixtures(self)
- set_pidlockfile_scenario(self, 'not-exist')
-
- def tearDown(self):
- """ Tear down test fixtures. """
- scaffold.mock_restore()
-
- def test_calls_linkfilelock_acquire(self):
- """ Should first call LinkFileLock.acquire method. """
- instance = self.test_instance
- expect_mock_output = """\
- Called lockfile.LinkFileLock.acquire()
- ...
- """
- instance.acquire()
- self.failUnlessMockCheckerMatch(expect_mock_output)
-
- def test_calls_linkfilelock_acquire_with_timeout(self):
- """ Should call LinkFileLock.acquire method with specified timeout. """
- instance = self.test_instance
- test_timeout = object()
- expect_mock_output = """\
- Called lockfile.LinkFileLock.acquire(timeout=%(test_timeout)r)
- ...
- """ % vars()
- instance.acquire(timeout=test_timeout)
- self.failUnlessMockCheckerMatch(expect_mock_output)
-
- def test_writes_pid_to_specified_file(self):
- """ Should request writing current PID to specified file. """
- instance = self.test_instance
- pidfile_path = self.scenario['path']
- expect_mock_output = """\
- ...
- Called pidlockfile.write_pid_to_pidfile(%(pidfile_path)r)
- """ % vars()
- instance.acquire()
- scaffold.mock_restore()
- self.failUnlessMockCheckerMatch(expect_mock_output)
-
- def test_raises_lock_failed_on_write_error(self):
- """ Should raise LockFailed error if write fails. """
- set_pidlockfile_scenario(self, 'not-exist-write-busy')
- instance = self.test_instance
- pidfile_path = self.scenario['path']
- mock_error = OSError(errno.EBUSY, "Bad stuff", pidfile_path)
- pidlockfile.write_pid_to_pidfile.mock_raises = mock_error
- expect_error = pidlockfile.LockFailed
- self.failUnlessRaises(
- expect_error,
- instance.acquire)
-
-
-class PIDLockFile_release_TestCase(scaffold.TestCase):
- """ Test cases for PIDLockFile.release function. """
-
- def setUp(self):
- """ Set up test fixtures. """
- setup_pidlockfile_fixtures(self)
-
- def tearDown(self):
- """ Tear down test fixtures. """
- scaffold.mock_restore()
-
- def test_does_not_remove_existing_pidfile_if_not_locking(self):
- """ Should not request removal of PID file if not locking. """
- set_pidlockfile_scenario(self, 'exist-empty')
- instance = self.test_instance
- expect_error = lockfile.NotLocked
- unwanted_mock_output = (
- "..."
- "Called pidlockfile.remove_existing_pidfile"
- "...")
- self.failUnlessRaises(
- expect_error,
- instance.release)
- self.failIfMockCheckerMatch(unwanted_mock_output)
-
- def test_does_not_remove_existing_pidfile_if_not_my_lock(self):
- """ Should not request removal of PID file if we are not locking. """
- set_pidlockfile_scenario(self, 'exist-other-pid-locked')
- instance = self.test_instance
- expect_error = lockfile.NotMyLock
- unwanted_mock_output = (
- "..."
- "Called pidlockfile.remove_existing_pidfile"
- "...")
- self.failUnlessRaises(
- expect_error,
- instance.release)
- self.failIfMockCheckerMatch(unwanted_mock_output)
-
- def test_removes_existing_pidfile_if_i_am_locking(self):
- """ Should request removal of specified PID file if lock is ours. """
- set_pidlockfile_scenario(self, 'exist-current-pid-locked')
- instance = self.test_instance
- pidfile_path = self.scenario['path']
- expect_mock_output = """\
- ...
- Called pidlockfile.remove_existing_pidfile(%(pidfile_path)r)
- ...
- """ % vars()
- instance.release()
- self.failUnlessMockCheckerMatch(expect_mock_output)
-
- def test_calls_linkfilelock_release(self):
- """ Should finally call LinkFileLock.release method. """
- set_pidlockfile_scenario(self, 'exist-current-pid-locked')
- instance = self.test_instance
- expect_mock_output = """\
- ...
- Called lockfile.LinkFileLock.release()
- """
- instance.release()
- self.failUnlessMockCheckerMatch(expect_mock_output)
-
-
-class PIDLockFile_break_lock_TestCase(scaffold.TestCase):
- """ Test cases for PIDLockFile.break_lock function. """
-
- def setUp(self):
- """ Set up test fixtures. """
- setup_pidlockfile_fixtures(self)
- set_pidlockfile_scenario(self, 'exist-other-pid-locked')
-
- def tearDown(self):
- """ Tear down test fixtures. """
- scaffold.mock_restore()
-
- def test_calls_linkfilelock_break_lock(self):
- """ Should first call LinkFileLock.break_lock method. """
- instance = self.test_instance
- expect_mock_output = """\
- Called lockfile.LinkFileLock.break_lock()
- ...
- """
- instance.break_lock()
- self.failUnlessMockCheckerMatch(expect_mock_output)
-
- def test_removes_existing_pidfile(self):
- """ Should request removal of specified PID file. """
- instance = self.test_instance
- pidfile_path = self.scenario['path']
- expect_mock_output = """\
- ...
- Called pidlockfile.remove_existing_pidfile(%(pidfile_path)r)
- """ % vars()
- instance.break_lock()
- self.failUnlessMockCheckerMatch(expect_mock_output)
-
-
-class read_pid_from_pidfile_TestCase(scaffold.TestCase):
- """ Test cases for read_pid_from_pidfile function. """
-
- def setUp(self):
- """ Set up test fixtures. """
- setup_pidfile_fixtures(self)
-
- def tearDown(self):
- """ Tear down test fixtures. """
- scaffold.mock_restore()
-
- def test_opens_specified_filename(self):
- """ Should attempt to open specified pidfile filename. """
- set_pidlockfile_scenario(self, 'exist-other-pid')
- pidfile_path = self.scenario['path']
- expect_mock_output = """\
- Called __builtin__.open(%(pidfile_path)r, 'r')
- """ % vars()
- dummy = pidlockfile.read_pid_from_pidfile(pidfile_path)
- scaffold.mock_restore()
- self.failUnlessMockCheckerMatch(expect_mock_output)
-
- def test_reads_pid_from_file(self):
- """ Should read the PID from the specified file. """
- set_pidlockfile_scenario(self, 'exist-other-pid')
- pidfile_path = self.scenario['path']
- expect_pid = self.scenario['pidfile_pid']
- pid = pidlockfile.read_pid_from_pidfile(pidfile_path)
- scaffold.mock_restore()
- self.failUnlessEqual(expect_pid, pid)
-
- def test_returns_none_when_file_nonexist(self):
- """ Should return None when the PID file does not exist. """
- set_pidlockfile_scenario(self, 'not-exist')
- pidfile_path = self.scenario['path']
- pid = pidlockfile.read_pid_from_pidfile(pidfile_path)
- scaffold.mock_restore()
- self.failUnlessIs(None, pid)
-
- def test_raises_error_when_file_read_fails(self):
- """ Should raise error when the PID file read fails. """
- set_pidlockfile_scenario(self, 'exist-read-denied')
- pidfile_path = self.scenario['path']
- expect_error = EnvironmentError
- self.failUnlessRaises(
- expect_error,
- pidlockfile.read_pid_from_pidfile, pidfile_path)
-
- def test_raises_error_when_file_empty(self):
- """ Should raise error when the PID file is empty. """
- set_pidlockfile_scenario(self, 'exist-empty')
- pidfile_path = self.scenario['path']
- expect_error = pidlockfile.PIDFileParseError
- self.failUnlessRaises(
- expect_error,
- pidlockfile.read_pid_from_pidfile, pidfile_path)
-
- def test_raises_error_when_file_contents_invalid(self):
- """ Should raise error when the PID file contents are invalid. """
- set_pidlockfile_scenario(self, 'exist-invalid')
- pidfile_path = self.scenario['path']
- expect_error = pidlockfile.PIDFileParseError
- self.failUnlessRaises(
- expect_error,
- pidlockfile.read_pid_from_pidfile, pidfile_path)
-
-
-class remove_existing_pidfile_TestCase(scaffold.TestCase):
- """ Test cases for remove_existing_pidfile function. """
-
- def setUp(self):
- """ Set up test fixtures. """
- setup_pidfile_fixtures(self)
-
- scaffold.mock(
- "os.remove",
- tracker=self.mock_tracker)
-
- def tearDown(self):
- """ Tear down test fixtures. """
- scaffold.mock_restore()
-
- def test_removes_specified_filename(self):
- """ Should attempt to remove specified PID file filename. """
- set_pidlockfile_scenario(self, 'exist-current-pid')
- pidfile_path = self.scenario['path']
- expect_mock_output = """\
- Called os.remove(%(pidfile_path)r)
- """ % vars()
- pidlockfile.remove_existing_pidfile(pidfile_path)
- scaffold.mock_restore()
- self.failUnlessMockCheckerMatch(expect_mock_output)
-
- def test_ignores_file_not_exist_error(self):
- """ Should ignore error if file does not exist. """
- set_pidlockfile_scenario(self, 'not-exist')
- pidfile_path = self.scenario['path']
- mock_error = OSError(errno.ENOENT, "Not there", pidfile_path)
- os.remove.mock_raises = mock_error
- expect_mock_output = """\
- Called os.remove(%(pidfile_path)r)
- """ % vars()
- pidlockfile.remove_existing_pidfile(pidfile_path)
- scaffold.mock_restore()
- self.failUnlessMockCheckerMatch(expect_mock_output)
-
- def test_propagates_arbitrary_oserror(self):
- """ Should propagate any OSError other than ENOENT. """
- set_pidlockfile_scenario(self, 'exist-current-pid')
- pidfile_path = self.scenario['path']
- mock_error = OSError(errno.EACCES, "Denied", pidfile_path)
- os.remove.mock_raises = mock_error
- self.failUnlessRaises(
- type(mock_error),
- pidlockfile.remove_existing_pidfile,
- pidfile_path)
-
-
-class write_pid_to_pidfile_TestCase(scaffold.TestCase):
- """ Test cases for write_pid_to_pidfile function. """
-
- def setUp(self):
- """ Set up test fixtures. """
- setup_pidfile_fixtures(self)
- set_pidlockfile_scenario(self, 'not-exist')
-
- def tearDown(self):
- """ Tear down test fixtures. """
- scaffold.mock_restore()
-
- def test_opens_specified_filename(self):
- """ Should attempt to open specified PID file filename. """
- pidfile_path = self.scenario['path']
- expect_flags = (os.O_CREAT | os.O_EXCL | os.O_WRONLY)
- expect_mode = 0644
- expect_mock_output = """\
- Called os.open(%(pidfile_path)r, %(expect_flags)r, %(expect_mode)r)
- ...
- """ % vars()
- pidlockfile.write_pid_to_pidfile(pidfile_path)
- scaffold.mock_restore()
- self.failUnlessMockCheckerMatch(expect_mock_output)
-
- def test_writes_pid_to_file(self):
- """ Should write the current PID to the specified file. """
- pidfile_path = self.scenario['path']
- self.scenario['pidfile'].close = scaffold.Mock(
- "PIDLockFile.close",
- tracker=self.mock_tracker)
- expect_line = "%(pid)d\n" % self.scenario
- pidlockfile.write_pid_to_pidfile(pidfile_path)
- scaffold.mock_restore()
- self.failUnlessEqual(expect_line, self.scenario['pidfile'].getvalue())
-
- def test_closes_file_after_write(self):
- """ Should close the specified file after writing. """
- pidfile_path = self.scenario['path']
- self.scenario['pidfile'].write = scaffold.Mock(
- "PIDLockFile.write",
- tracker=self.mock_tracker)
- self.scenario['pidfile'].close = scaffold.Mock(
- "PIDLockFile.close",
- tracker=self.mock_tracker)
- expect_mock_output = """\
- ...
- Called PIDLockFile.write(...)
- Called PIDLockFile.close()
- """ % vars()
- pidlockfile.write_pid_to_pidfile(pidfile_path)
- scaffold.mock_restore()
- self.failUnlessMockCheckerMatch(expect_mock_output)
-
-
-class TimeoutPIDLockFile_TestCase(scaffold.TestCase):
- """ Test cases for ‘TimeoutPIDLockFile’ class. """
-
- def setUp(self):
- """ Set up test fixtures. """
- self.mock_tracker = scaffold.MockTracker()
-
- pidlockfile_scenarios = make_pidlockfile_scenarios()
- self.pidlockfile_scenario = pidlockfile_scenarios['simple']
- pidfile_path = self.pidlockfile_scenario['path']
-
- scaffold.mock(
- "pidlockfile.PIDLockFile.__init__",
- tracker=self.mock_tracker)
- scaffold.mock(
- "pidlockfile.PIDLockFile.acquire",
- tracker=self.mock_tracker)
-
- self.scenario = {
- 'pidfile_path': self.pidlockfile_scenario['path'],
- 'acquire_timeout': object(),
- }
-
- self.test_kwargs = dict(
- path=self.scenario['pidfile_path'],
- acquire_timeout=self.scenario['acquire_timeout'],
- )
- self.test_instance = pidlockfile.TimeoutPIDLockFile(**self.test_kwargs)
-
- def tearDown(self):
- """ Tear down test fixtures. """
- scaffold.mock_restore()
-
- def test_inherits_from_pidlockfile(self):
- """ Should inherit from PIDLockFile. """
- instance = self.test_instance
- self.failUnlessIsInstance(instance, pidlockfile.PIDLockFile)
-
- def test_init_has_expected_signature(self):
- """ Should have expected signature for ‘__init__’. """
- def test_func(self, path, acquire_timeout=None, *args, **kwargs): pass
- test_func.__name__ = '__init__'
- self.failUnlessFunctionSignatureMatch(
- test_func,
- pidlockfile.TimeoutPIDLockFile.__init__)
-
- def test_has_specified_acquire_timeout(self):
- """ Should have specified ‘acquire_timeout’ value. """
- instance = self.test_instance
- expect_timeout = self.test_kwargs['acquire_timeout']
- self.failUnlessEqual(expect_timeout, instance.acquire_timeout)
-
- def test_calls_superclass_init(self):
- """ Should call the superclass ‘__init__’. """
- expect_path = self.test_kwargs['path']
- expect_mock_output = """\
- Called pidlockfile.PIDLockFile.__init__(
- %(expect_path)r)
- """ % vars()
- self.failUnlessMockCheckerMatch(expect_mock_output)
-
- def test_acquire_uses_specified_timeout(self):
- """ Should call the superclass ‘acquire’ with specified timeout. """
- instance = self.test_instance
- test_timeout = object()
- expect_timeout = test_timeout
- self.mock_tracker.clear()
- expect_mock_output = """\
- Called pidlockfile.PIDLockFile.acquire(%(expect_timeout)r)
- """ % vars()
- instance.acquire(test_timeout)
- self.failUnlessMockCheckerMatch(expect_mock_output)
-
- def test_acquire_uses_stored_timeout_by_default(self):
- """ Should call superclass ‘acquire’ with stored timeout by default. """
- instance = self.test_instance
- test_timeout = self.test_kwargs['acquire_timeout']
- expect_timeout = test_timeout
- self.mock_tracker.clear()
- expect_mock_output = """\
- Called pidlockfile.PIDLockFile.acquire(%(expect_timeout)r)
- """ % vars()
- instance.acquire()
- self.failUnlessMockCheckerMatch(expect_mock_output)
diff --git a/test/test_runner.py b/test/test_runner.py
index 11551ab..f384c1e 100644
--- a/test/test_runner.py
+++ b/test/test_runner.py
@@ -1,38 +1,42 @@
# -*- coding: utf-8 -*-
#
# test/test_runner.py
-# Part of python-daemon, an implementation of PEP 3143.
+# Part of ‘python-daemon’, an implementation of PEP 3143.
#
-# Copyright © 2009–2010 Ben Finney <ben+python@benfinney.id.au>
+# Copyright © 2009–2014 Ben Finney <ben+python@benfinney.id.au>
#
# This is free software: you may copy, modify, and/or distribute this work
-# under the terms of the Python Software Foundation License, version 2 or
-# later as published by the Python Software Foundation.
-# No warranty expressed or implied. See the file LICENSE.PSF-2 for details.
+# under the terms of the Apache License, version 2.0 as published by the
+# Apache Software Foundation.
+# No warranty expressed or implied. See the file LICENSE.ASF-2 for details.
-""" Unit test for runner module.
+""" Unit test for ‘runner’ module.
"""
-import __builtin__
+from __future__ import unicode_literals
+
+import __builtin__ as builtins
import os
import sys
import tempfile
import errno
import signal
+import lockfile
+
import scaffold
-from test_pidlockfile import (
- FakeFileDescriptorStringIO,
- setup_pidfile_fixtures,
- make_pidlockfile_scenarios,
- setup_lockfile_method_mocks,
- )
+from test_pidfile import (
+ FakeFileDescriptorStringIO,
+ setup_pidfile_fixtures,
+ make_pidlockfile_scenarios,
+ setup_lockfile_method_mocks,
+ )
from test_daemon import (
- setup_streams_fixtures,
- )
+ setup_streams_fixtures,
+ )
import daemon.daemon
-from daemon import pidlockfile
+from daemon import pidfile
from daemon import runner
@@ -44,23 +48,23 @@ class Exception_TestCase(scaffold.Exception_TestCase):
super(Exception_TestCase, self).__init__(*args, **kwargs)
self.valid_exceptions = {
- runner.DaemonRunnerError: dict(
- min_args = 1,
- types = (Exception,),
- ),
- runner.DaemonRunnerInvalidActionError: dict(
- min_args = 1,
- types = (runner.DaemonRunnerError, ValueError),
- ),
- runner.DaemonRunnerStartFailureError: dict(
- min_args = 1,
- types = (runner.DaemonRunnerError, RuntimeError),
- ),
- runner.DaemonRunnerStopFailureError: dict(
- min_args = 1,
- types = (runner.DaemonRunnerError, RuntimeError),
- ),
- }
+ runner.DaemonRunnerError: dict(
+ min_args = 1,
+ types = (Exception,),
+ ),
+ runner.DaemonRunnerInvalidActionError: dict(
+ min_args = 1,
+ types = (runner.DaemonRunnerError, ValueError),
+ ),
+ runner.DaemonRunnerStartFailureError: dict(
+ min_args = 1,
+ types = (runner.DaemonRunnerError, RuntimeError),
+ ),
+ runner.DaemonRunnerStopFailureError: dict(
+ min_args = 1,
+ types = (runner.DaemonRunnerError, RuntimeError),
+ ),
+ }
def make_runner_scenarios():
@@ -69,18 +73,18 @@ def make_runner_scenarios():
pidlockfile_scenarios = make_pidlockfile_scenarios()
scenarios = {
- 'simple': {
- 'pidlockfile_scenario_name': 'simple',
- },
- 'pidfile-locked': {
- 'pidlockfile_scenario_name': 'exist-other-pid-locked',
- },
- }
+ 'simple': {
+ 'pidlockfile_scenario_name': 'simple',
+ },
+ 'pidfile-locked': {
+ 'pidlockfile_scenario_name': 'exist-other-pid-locked',
+ },
+ }
for scenario in scenarios.values():
if 'pidlockfile_scenario_name' in scenario:
pidlockfile_scenario = pidlockfile_scenarios.pop(
- scenario['pidlockfile_scenario_name'])
+ scenario['pidlockfile_scenario_name'])
scenario['pid'] = pidlockfile_scenario['pid']
scenario['pidfile_path'] = pidlockfile_scenario['path']
scenario['pidfile_timeout'] = 23
@@ -94,7 +98,7 @@ def set_runner_scenario(testcase, scenario_name, clear_tracker=True):
scenarios = testcase.runner_scenarios
testcase.scenario = scenarios[scenario_name]
set_pidlockfile_scenario(
- testcase, testcase.scenario['pidlockfile_scenario_name'])
+ testcase, testcase.scenario['pidlockfile_scenario_name'])
if clear_tracker:
testcase.mock_tracker.clear()
@@ -104,8 +108,8 @@ def set_pidlockfile_scenario(testcase, scenario_name):
scenarios = testcase.pidlockfile_scenarios
testcase.pidlockfile_scenario = scenarios[scenario_name]
setup_lockfile_method_mocks(
- testcase, testcase.pidlockfile_scenario,
- testcase.lockfile_class_name)
+ testcase, testcase.pidlockfile_scenario,
+ testcase.lockfile_class_name)
def setup_runner_fixtures(testcase):
@@ -119,23 +123,23 @@ def setup_runner_fixtures(testcase):
testcase.mock_stderr = FakeFileDescriptorStringIO()
scaffold.mock(
- "sys.stderr",
- mock_obj=testcase.mock_stderr,
- tracker=testcase.mock_tracker)
+ "sys.stderr",
+ mock_obj=testcase.mock_stderr,
+ tracker=testcase.mock_tracker)
simple_scenario = testcase.runner_scenarios['simple']
- testcase.lockfile_class_name = "pidlockfile.TimeoutPIDLockFile"
+ testcase.lockfile_class_name = "pidfile.TimeoutPIDLockFile"
testcase.mock_runner_lock = scaffold.Mock(
- testcase.lockfile_class_name,
- tracker=testcase.mock_tracker)
+ testcase.lockfile_class_name,
+ tracker=testcase.mock_tracker)
testcase.mock_runner_lock.path = simple_scenario['pidfile_path']
scaffold.mock(
- testcase.lockfile_class_name,
- returns=testcase.mock_runner_lock,
- tracker=testcase.mock_tracker)
+ testcase.lockfile_class_name,
+ returns=testcase.mock_runner_lock,
+ tracker=testcase.mock_tracker)
class TestApp(object):
@@ -147,28 +151,28 @@ def setup_runner_fixtures(testcase):
self.pidfile_timeout = simple_scenario['pidfile_timeout']
run = scaffold.Mock(
- "TestApp.run",
- tracker=testcase.mock_tracker)
+ "TestApp.run",
+ tracker=testcase.mock_tracker)
testcase.TestApp = TestApp
scaffold.mock(
- "daemon.runner.DaemonContext",
- returns=scaffold.Mock(
- "DaemonContext",
- tracker=testcase.mock_tracker),
- tracker=testcase.mock_tracker)
+ "daemon.runner.DaemonContext",
+ returns=scaffold.Mock(
+ "DaemonContext",
+ tracker=testcase.mock_tracker),
+ tracker=testcase.mock_tracker)
testcase.test_app = testcase.TestApp()
testcase.test_program_name = "bazprog"
testcase.test_program_path = (
- "/foo/bar/%(test_program_name)s" % vars(testcase))
+ "/foo/bar/%(test_program_name)s" % vars(testcase))
testcase.valid_argv_params = {
- 'start': [testcase.test_program_path, 'start'],
- 'stop': [testcase.test_program_path, 'stop'],
- 'restart': [testcase.test_program_path, 'restart'],
- }
+ 'start': [testcase.test_program_path, 'start'],
+ 'stop': [testcase.test_program_path, 'stop'],
+ 'restart': [testcase.test_program_path, 'restart'],
+ }
def mock_open(filename, mode=None, buffering=None):
if filename in testcase.stream_files_by_path:
@@ -180,18 +184,18 @@ def setup_runner_fixtures(testcase):
return result
scaffold.mock(
- "__builtin__.open",
- returns_func=mock_open,
- tracker=testcase.mock_tracker)
+ "builtins.open",
+ returns_func=mock_open,
+ tracker=testcase.mock_tracker)
scaffold.mock(
- "os.kill",
- tracker=testcase.mock_tracker)
+ "os.kill",
+ tracker=testcase.mock_tracker)
scaffold.mock(
- "sys.argv",
- mock_obj=testcase.valid_argv_params['start'],
- tracker=testcase.mock_tracker)
+ "sys.argv",
+ mock_obj=testcase.valid_argv_params['start'],
+ tracker=testcase.mock_tracker)
testcase.test_instance = runner.DaemonRunner(testcase.test_app)
@@ -207,8 +211,8 @@ class DaemonRunner_TestCase(scaffold.TestCase):
set_runner_scenario(self, 'simple')
scaffold.mock(
- "runner.DaemonRunner.parse_args",
- tracker=self.mock_tracker)
+ "runner.DaemonRunner.parse_args",
+ tracker=self.mock_tracker)
self.test_instance = runner.DaemonRunner(self.test_app)
@@ -223,9 +227,9 @@ class DaemonRunner_TestCase(scaffold.TestCase):
def test_parses_commandline_args(self):
""" Should parse commandline arguments. """
expect_mock_output = """\
- Called runner.DaemonRunner.parse_args()
- ...
- """
+ Called runner.DaemonRunner.parse_args()
+ ...
+ """
self.failUnlessMockCheckerMatch(expect_mock_output)
def test_has_specified_app(self):
@@ -246,8 +250,8 @@ class DaemonRunner_TestCase(scaffold.TestCase):
self.test_app.pidfile_path = pidfile_path
expect_error = ValueError
self.failUnlessRaises(
- expect_error,
- runner.DaemonRunner, self.test_app)
+ expect_error,
+ runner.DaemonRunner, self.test_app)
def test_error_when_pidfile_path_not_absolute(self):
""" Should raise ValueError when PID file path not absolute. """
@@ -255,8 +259,8 @@ class DaemonRunner_TestCase(scaffold.TestCase):
self.test_app.pidfile_path = pidfile_path
expect_error = ValueError
self.failUnlessRaises(
- expect_error,
- runner.DaemonRunner, self.test_app)
+ expect_error,
+ runner.DaemonRunner, self.test_app)
def test_creates_lock_with_specified_parameters(self):
""" Should create a TimeoutPIDLockFile with specified params. """
@@ -264,11 +268,11 @@ class DaemonRunner_TestCase(scaffold.TestCase):
pidfile_timeout = self.scenario['pidfile_timeout']
lockfile_class_name = self.lockfile_class_name
expect_mock_output = """\
- ...
- Called %(lockfile_class_name)s(
- %(pidfile_path)r,
- %(pidfile_timeout)r)
- """ % vars()
+ ...
+ Called %(lockfile_class_name)s(
+ %(pidfile_path)r,
+ %(pidfile_timeout)r)
+ """ % vars()
scaffold.mock_restore()
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -277,14 +281,14 @@ class DaemonRunner_TestCase(scaffold.TestCase):
expect_pidfile = self.mock_runner_lock
instance = self.test_instance
self.failUnlessIs(
- expect_pidfile, instance.pidfile)
+ expect_pidfile, instance.pidfile)
def test_daemon_context_has_created_pidfile(self):
""" DaemonContext component should have new PID lock file. """
expect_pidfile = self.mock_runner_lock
daemon_context = self.test_instance.daemon_context
self.failUnlessIs(
- expect_pidfile, daemon_context.pidfile)
+ expect_pidfile, daemon_context.pidfile)
def test_daemon_context_has_specified_stdin_stream(self):
""" DaemonContext component should have specified stdin file. """
@@ -330,7 +334,7 @@ class DaemonRunner_TestCase(scaffold.TestCase):
expect_buffering = 0
daemon_context = self.test_instance.daemon_context
self.failUnlessEqual(
- expect_buffering, daemon_context.stderr.buffering)
+ expect_buffering, daemon_context.stderr.buffering)
class DaemonRunner_usage_exit_TestCase(scaffold.TestCase):
@@ -350,8 +354,8 @@ class DaemonRunner_usage_exit_TestCase(scaffold.TestCase):
instance = self.test_instance
argv = [self.test_program_path]
self.failUnlessRaises(
- SystemExit,
- instance._usage_exit, argv)
+ SystemExit,
+ instance._usage_exit, argv)
def test_message_follows_conventional_format(self):
""" Should emit a conventional usage message. """
@@ -359,13 +363,13 @@ class DaemonRunner_usage_exit_TestCase(scaffold.TestCase):
progname = self.test_program_name
argv = [self.test_program_path]
expect_stderr_output = """\
- usage: %(progname)s ...
- """ % vars()
+ usage: %(progname)s ...
+ """ % vars()
self.failUnlessRaises(
- SystemExit,
- instance._usage_exit, argv)
+ SystemExit,
+ instance._usage_exit, argv)
self.failUnlessOutputCheckerMatch(
- expect_stderr_output, self.mock_stderr.getvalue())
+ expect_stderr_output, self.mock_stderr.getvalue())
class DaemonRunner_parse_args_TestCase(scaffold.TestCase):
@@ -377,9 +381,9 @@ class DaemonRunner_parse_args_TestCase(scaffold.TestCase):
set_runner_scenario(self, 'simple')
scaffold.mock(
- "daemon.runner.DaemonRunner._usage_exit",
- raises=NotImplementedError,
- tracker=self.mock_tracker)
+ "daemon.runner.DaemonRunner._usage_exit",
+ raises=NotImplementedError,
+ tracker=self.mock_tracker)
def tearDown(self):
""" Tear down test fixtures. """
@@ -390,8 +394,8 @@ class DaemonRunner_parse_args_TestCase(scaffold.TestCase):
instance = self.test_instance
argv = [self.test_program_path]
expect_mock_output = """\
- Called daemon.runner.DaemonRunner._usage_exit(%(argv)r)
- """ % vars()
+ Called daemon.runner.DaemonRunner._usage_exit(%(argv)r)
+ """ % vars()
try:
instance.parse_args(argv)
except NotImplementedError:
@@ -404,8 +408,8 @@ class DaemonRunner_parse_args_TestCase(scaffold.TestCase):
progname = self.test_program_name
argv = [self.test_program_path, 'bogus']
expect_mock_output = """\
- Called daemon.runner.DaemonRunner._usage_exit(%(argv)r)
- """ % vars()
+ Called daemon.runner.DaemonRunner._usage_exit(%(argv)r)
+ """ % vars()
try:
instance.parse_args(argv)
except NotImplementedError:
@@ -418,9 +422,9 @@ class DaemonRunner_parse_args_TestCase(scaffold.TestCase):
expect_action = 'start'
argv = self.valid_argv_params['start']
scaffold.mock(
- "sys.argv",
- mock_obj=argv,
- tracker=self.mock_tracker)
+ "sys.argv",
+ mock_obj=argv,
+ tracker=self.mock_tracker)
instance.parse_args()
self.failUnlessEqual(expect_action, instance.action)
@@ -474,18 +478,18 @@ class DaemonRunner_do_action_start_TestCase(scaffold.TestCase):
set_pidlockfile_scenario(self, 'exist-other-pid-locked')
instance = self.test_instance
instance.daemon_context.open.mock_raises = (
- pidlockfile.AlreadyLocked)
+ lockfile.AlreadyLocked)
pidfile_path = self.scenario['pidfile_path']
expect_error = runner.DaemonRunnerStartFailureError
expect_message_content = pidfile_path
try:
instance.do_action()
- except expect_error, exc:
+ except expect_error as exc:
pass
else:
raise self.failureException(
- "Failed to raise " + expect_error.__name__)
- self.failUnlessIn(str(exc), expect_message_content)
+ "Failed to raise " + expect_error.__name__)
+ self.failUnlessIn(unicode(exc.message), expect_message_content)
def test_breaks_lock_if_no_such_process(self):
""" Should request breaking lock if PID file process is not running. """
@@ -500,11 +504,11 @@ class DaemonRunner_do_action_start_TestCase(scaffold.TestCase):
os.kill.mock_raises = error
lockfile_class_name = self.lockfile_class_name
expect_mock_output = """\
- ...
- Called os.kill(%(test_pid)r, %(expect_signal)r)
- Called %(lockfile_class_name)s.break_lock()
- ...
- """ % vars()
+ ...
+ Called os.kill(%(test_pid)r, %(expect_signal)r)
+ Called %(lockfile_class_name)s.break_lock()
+ ...
+ """ % vars()
instance.do_action()
scaffold.mock_restore()
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -513,10 +517,10 @@ class DaemonRunner_do_action_start_TestCase(scaffold.TestCase):
""" Should request the daemon context to open. """
instance = self.test_instance
expect_mock_output = """\
- ...
- Called DaemonContext.open()
- ...
- """
+ ...
+ Called DaemonContext.open()
+ ...
+ """
instance.do_action()
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -525,8 +529,8 @@ class DaemonRunner_do_action_start_TestCase(scaffold.TestCase):
instance = self.test_instance
current_pid = self.scenario['pid']
expect_stderr = """\
- started with pid %(current_pid)d
- """ % vars()
+ started with pid %(current_pid)d
+ """ % vars()
instance.do_action()
self.failUnlessOutputCheckerMatch(
expect_stderr, self.mock_stderr.getvalue())
@@ -535,9 +539,9 @@ class DaemonRunner_do_action_start_TestCase(scaffold.TestCase):
""" Should request the application to run. """
instance = self.test_instance
expect_mock_output = """\
- ...
- Called TestApp.run()
- """
+ ...
+ Called TestApp.run()
+ """
instance.do_action()
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -555,7 +559,7 @@ class DaemonRunner_do_action_stop_TestCase(scaffold.TestCase):
self.mock_runner_lock.is_locked.mock_returns = True
self.mock_runner_lock.i_am_locking.mock_returns = False
self.mock_runner_lock.read_pid.mock_returns = (
- self.scenario['pidlockfile_scenario']['pidfile_pid'])
+ self.scenario['pidlockfile_scenario']['pidfile_pid'])
def tearDown(self):
""" Tear down test fixtures. """
@@ -568,17 +572,17 @@ class DaemonRunner_do_action_stop_TestCase(scaffold.TestCase):
self.mock_runner_lock.is_locked.mock_returns = False
self.mock_runner_lock.i_am_locking.mock_returns = False
self.mock_runner_lock.read_pid.mock_returns = (
- self.scenario['pidlockfile_scenario']['pidfile_pid'])
+ self.scenario['pidlockfile_scenario']['pidfile_pid'])
pidfile_path = self.scenario['pidfile_path']
expect_error = runner.DaemonRunnerStopFailureError
expect_message_content = pidfile_path
try:
instance.do_action()
- except expect_error, exc:
+ except expect_error as exc:
pass
else:
raise self.failureException(
- "Failed to raise " + expect_error.__name__)
+ "Failed to raise " + expect_error.__name__)
scaffold.mock_restore()
self.failUnlessIn(str(exc), expect_message_content)
@@ -592,9 +596,9 @@ class DaemonRunner_do_action_stop_TestCase(scaffold.TestCase):
os.kill.mock_raises = error
lockfile_class_name = self.lockfile_class_name
expect_mock_output = """\
- ...
- Called %(lockfile_class_name)s.break_lock()
- """ % vars()
+ ...
+ Called %(lockfile_class_name)s.break_lock()
+ """ % vars()
instance.do_action()
scaffold.mock_restore()
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -605,9 +609,9 @@ class DaemonRunner_do_action_stop_TestCase(scaffold.TestCase):
test_pid = self.scenario['pidlockfile_scenario']['pidfile_pid']
expect_signal = signal.SIGTERM
expect_mock_output = """\
- ...
- Called os.kill(%(test_pid)r, %(expect_signal)r)
- """ % vars()
+ ...
+ Called os.kill(%(test_pid)r, %(expect_signal)r)
+ """ % vars()
instance.do_action()
scaffold.mock_restore()
self.failUnlessMockCheckerMatch(expect_mock_output)
@@ -623,12 +627,13 @@ class DaemonRunner_do_action_stop_TestCase(scaffold.TestCase):
expect_message_content = str(test_pid)
try:
instance.do_action()
- except expect_error, exc:
+ except expect_error as exc:
pass
else:
raise self.failureException(
- "Failed to raise " + expect_error.__name__)
- self.failUnlessIn(str(exc), expect_message_content)
+ "Failed to raise " + expect_error.__name__)
+ scaffold.mock_restore()
+ self.failUnlessIn(unicode(exc), expect_message_content)
class DaemonRunner_do_action_restart_TestCase(scaffold.TestCase):
@@ -649,14 +654,21 @@ class DaemonRunner_do_action_restart_TestCase(scaffold.TestCase):
""" Should request stop, then start. """
instance = self.test_instance
scaffold.mock(
- "daemon.runner.DaemonRunner._start",
- tracker=self.mock_tracker)
+ "daemon.runner.DaemonRunner._start",
+ tracker=self.mock_tracker)
scaffold.mock(
- "daemon.runner.DaemonRunner._stop",
- tracker=self.mock_tracker)
+ "daemon.runner.DaemonRunner._stop",
+ tracker=self.mock_tracker)
expect_mock_output = """\
- Called daemon.runner.DaemonRunner._stop()
- Called daemon.runner.DaemonRunner._start()
- """
+ Called daemon.runner.DaemonRunner._stop()
+ Called daemon.runner.DaemonRunner._start()
+ """
instance.do_action()
self.failUnlessMockCheckerMatch(expect_mock_output)
+
+
+# Local variables:
+# coding: utf-8
+# mode: python
+# End:
+# vim: fileencoding=utf-8 filetype=python :