diff options
author | Lorry Tar Creator <lorry-tar-importer@baserock.org> | 2014-08-04 05:45:35 +0000 |
---|---|---|
committer | <> | 2014-12-10 05:33:45 +0000 |
commit | afcc4ea312255a2545f9c67d7c34ffefb00c80c0 (patch) | |
tree | 5ca5269e5d4fa6263242a7a96b713616e5f389e0 | |
parent | 02378192d5bb4b16498d87ace57da425166426bf (diff) | |
download | python-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-- | ChangeLog | 61 | ||||
-rw-r--r-- | LICENSE.ASF-2 | 202 | ||||
-rw-r--r-- | LICENSE.GPL-2 | 339 | ||||
-rw-r--r-- | LICENSE.GPL-3 | 674 | ||||
-rw-r--r-- | LICENSE.PSF-2 | 48 | ||||
-rw-r--r-- | MANIFEST.in | 2 | ||||
-rw-r--r-- | PKG-INFO | 21 | ||||
-rw-r--r-- | daemon/__init__.py | 25 | ||||
-rw-r--r-- | daemon/daemon.py | 144 | ||||
-rw-r--r-- | daemon/pidfile.py | 49 | ||||
-rw-r--r-- | daemon/pidlockfile.py | 194 | ||||
-rw-r--r-- | daemon/runner.py | 54 | ||||
-rw-r--r-- | daemon/version/__init__.py | 46 | ||||
-rw-r--r-- | daemon/version/version_info.py | 11 | ||||
-rw-r--r-- | doc/FAQ | 143 | ||||
-rw-r--r-- | doc/TODO | 89 | ||||
-rw-r--r-- | python_daemon.egg-info/PKG-INFO | 21 | ||||
-rw-r--r-- | python_daemon.egg-info/SOURCES.txt | 12 | ||||
-rw-r--r-- | python_daemon.egg-info/requires.txt | 2 | ||||
-rw-r--r-- | setup.py | 153 | ||||
-rw-r--r-- | test/__init__.py | 19 | ||||
-rw-r--r-- | test/scaffold.py | 402 | ||||
-rw-r--r-- | test/test_daemon.py | 912 | ||||
-rw-r--r-- | test/test_pidfile.py | 407 | ||||
-rw-r--r-- | test/test_pidlockfile.py | 791 | ||||
-rw-r--r-- | test/test_runner.py | 302 |
26 files changed, 2548 insertions, 2575 deletions
@@ -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 * @@ -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 @@ -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 @@ -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 : |