From: stronk7 Date: Tue, 23 Jun 2009 09:23:06 +0000 (+0000) Subject: MDL-19579 code coverage - add Spike PHPCoverage lib (LGPL) X-Git-Url: http://git.mjollnir.org/gw?a=commitdiff_plain;h=b5ba94e5a56bfe8eb66e708d412e94d1aec9504f;p=moodle.git MDL-19579 code coverage - add Spike PHPCoverage lib (LGPL) --- diff --git a/lib/spikephpcoverage/BUILD b/lib/spikephpcoverage/BUILD new file mode 100644 index 0000000000..0b87450e44 --- /dev/null +++ b/lib/spikephpcoverage/BUILD @@ -0,0 +1,17 @@ +################################################################################ +# $Id$ +# +# Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved. +# Licensed under the Open Software License version 2.1 +# (See http://www.spikesource.com/license.html) +################################################################################ +To build PHPCoverage Distribution archives from source... + + $ cd trunk/corestackbase/spike/util/coverage/php + $ svn update + $ source /opt/oss/env.sh + $ sudo pear install phpDocumentor ## Plus any other dependencies like + ## * Archive_Tar + ## * XML_Beautifier + $ ant distribute -Dcomp.version=x.y.z + diff --git a/lib/spikephpcoverage/CHANGES b/lib/spikephpcoverage/CHANGES new file mode 100644 index 0000000000..e552fac0b0 --- /dev/null +++ b/lib/spikephpcoverage/CHANGES @@ -0,0 +1,125 @@ +################################################################################ +# $Id$ +# +# Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved. +# Licensed under the Open Software License version 2.1 +# (See http://www.spikesource.com/license.html) +################################################################################ + +=============================== +Spike PHPCoverage Change Logs +=============================== + +Version 0.8.2 +=================== + * Fixed PHP Parser code to be much more smarter. Patch provided + by Marc_a + * Fixed bug 34 that included files with unspecified extensions in the + coverage report. + +Version 0.8.0 +=================== + * Added ability to combine several remote recordings into a single coverage report. + This feature will make it easier for anyone to run a multiple set of tests on a + PHP web site and combine the recordings into a single report. The remote code coverage + sample has been updated with a sample use case. + * Shipping a stripped down version of XML_Parser PEAR module along with the source. + * Shipping xdebug binaries along with PHPCoverage distro. + * PHPCoverage installation is now much simpler. + * Several bugs fixed - especially in the report generation area. + +Version 0.6.6 +============= + +This is a special release that only involves a change of licensing policy + for Spike PHPCoverage. Spike PHPCoverage has been dual-licensed under + origial Open Software License (OSL) and an additional GNU Lesser General + Public License (LGPL). Users are free to choose any license they see fit + for their use between these two. There are no additional bug fixes or + enhancement in this release. + +Version 0.6.5 +============= + This is mainly an bug fix and enhancement release. The most important + new feature in this release is the substancial performance improvement + in the code. While some more improvements are possible, this release should + reduce the memory consumption and increase the speed of PHPCoverage by a + significant amount. + + * Corrected location of __PHPCOVERAGE_HOME + * Changed ":" to PATH_SEPARATOR. + * Fixed problems with incorrect spellings in parser. + * Added options to pass in include paths and exclude paths. + * [Bug: 3737] Corrected the options passing. + * [Bug: 3738] Not all options are required for all actions. + * Added options to print report summary to console + * [Bug: 3803] Fixed paths so that Windows drive letter case does not make a + difference. + * Added parsing of local xml file. This means that the driver.php can be + used for generating a report from a local code coverage reading. This should + make the report generation faster by obviating the need of downloading the + data file from web server. + * [Bug: 4582] Code coverage not recorded if the application exits abnormally + by calling die() or exit(). + * Added error logging with multiple log levels. + * Changes to the phpcoverage bottom half ensure that the coverage object + is retrieved even if the code is executed repeatedly. + * Extension inc is also treated as a php extension + * Removed all echo and error_log statements to be replaced with logging + function calls. + * Added a config file for common properties + * Temporary directory is now OS-aware - this makes passing a tmp directory during 'init' optional. + * PHPCoverage home is deduced wherever possible from the location of that file. + * Removed most of the errors that shown when error level is set to E_ALL + * Several performance enhancements + * Changed getCoverageXml() implementation to dump the coverage xml data + chunk-by-chunk to the response stream. + * Changed XMLParser invocation to pass a URL (or file name) instead of a + huge XML string. + * Updated sample accordingly. + * Changed several foreach statements to use references instead of copying + * Added unset in some places to release memory as fast as possible. + + +Version 0.6.4 +============= + + 1. This is mainly a bug fix release. Fixed followings bugs: + * Incorrect line type if the line is a continuation (2478) + For lines that are continuation of a previous line, PHPCoverage gives incorrect + line type. This includes lines starting without a . in the beginning. + * Variable declarations with private, protected, public, and var are marked as executable but not covered. (3280) + Variable declarations starting with private, protected, public, and var are + marked as executable when an assignment is involved. But they are never marked + as executed. These should be discounted from the code coverage recording. + * For completeness sake, we need to document the installation instructions for XML_Parser. (3299) + * It would be helpful to have command line interface to the various parts of codeCoverageMain.php (init, report, cleanup) (3300) + Added the script contributed by Ed Espino as cli/driver.php. Some more parameters added. + * The report title should be displayed in the reports header (3302) + In addition to updating the report html (HtmlCoverageReporter), it would + be helpful to display it in the generated html report as well. + * It would be helpful to display the total number of instrumented files in the report header. (3303) + * It is possible to instrument a file twice. (3304) + If this is performed, PHPCoverage will be broken as the PHPcoverage php files + are instrumented. + * There should be a way to exclude a file list from being instrumented. (3305) + Added exclude files options -e <file1,file2,...> to cli/instrument.php + * RemoteCoverageRecorder.php does not define xmlBody before use (line 112) (3306) + The error is displayed when "error_reporting" is set to 'E_ALL'. + * Not able to rename instrumented tmp file. (3330) + PHPCoverage on Windows XP isn't able to overwrite the original web app source + file with the instrumented version. + +Version 0.6.2 +============= + + 1. Added support on Windows (Thanks to Rowan Hick for testing and verification.) + 2. Restriction on which files to instrument has been removed. Now, all files in an application can (and should) be instrumented. + 3. Added simple local and remote samples for set-up verification after installation. + +Version 0.6 +=========== + 1. Introduced remote coverage measurement. + 2. Improved report look and feel. + 3. Made reporting mechanism extensible. + 4. User can specify their own style sheets to change report look and feel. diff --git a/lib/spikephpcoverage/LICENSE.lgpl b/lib/spikephpcoverage/LICENSE.lgpl new file mode 100644 index 0000000000..8add30ad59 --- /dev/null +++ b/lib/spikephpcoverage/LICENSE.lgpl @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin St, 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. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +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 and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, 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 library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete 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 distribute a copy of this License along with the +Library. + + 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 Library or any portion +of it, thus forming a work based on the Library, 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) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +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 Library, 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 Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you 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. + + If distribution of 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 satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be 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. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library 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. + + 9. 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 Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +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 with +this License. + + 11. 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 Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library 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 Library. + +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. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library 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. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser 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 Library +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 Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +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 + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "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 +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. 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 LIBRARY 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 +LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), 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 Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. 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 library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/lib/spikephpcoverage/LICENSE.osl b/lib/spikephpcoverage/LICENSE.osl new file mode 100644 index 0000000000..800d67451c --- /dev/null +++ b/lib/spikephpcoverage/LICENSE.osl @@ -0,0 +1,156 @@ +Open Software License version 2.1 + + This Open Software License (the "License") applies to any original work of + authorship (the "Original Work") whose owner (the "Licensor") has placed the + following notice immediately following the copyright notice for the Original + Work: + +Licensed under the Open Software License version 2.1 + + 1. Grant of Copyright License. Licensor hereby grants You a world-wide, + royalty-free, non-exclusive, perpetual, sublicenseable license to do the + following: + 1. to reproduce the Original Work in copies; + 2. to prepare derivative works ("Derivative Works") based upon the + Original Work; + 3. to distribute copies of the Original Work and Derivative Works to + the public, with the proviso that copies of Original Work or + Derivative Works that You distribute shall be licensed under the + Open Software License; + 4. to perform the Original Work publicly; and + 5. to display the Original Work publicly + 2. Grant of Patent License. Licensor hereby grants You a world-wide, + royalty-free, non-exclusive, perpetual, sublicenseable license, under + patent claims owned or controlled by the Licensor that are embodied in + the Original Work as furnished by the Licensor, to make, use, sell and + offer for sale the Original Work and Derivative Works. + 3. Grant of Source Code License . The term "Source Code" means the + preferred form of the Original Work for making modifications to it and + all available documentation describing how to modify the Original Work. + Licensor hereby agrees to provide a machine-readable copy of the Source + Code of the Original Work along with each copy of the Original Work that + Licensor distributes. Licensor reserves the right to satisfy this + obligation by placing a machine-readable copy of the Source Code in an + information repository reasonably calculated to permit inexpensive and + convenient access by You for as long as Licensor continues to distribute + the Original Work, and by publishing the address of that information + repository in a notice immediately following the copyright notice that + applies to the Original Work. + 4. Exclusions From License Grant . Neither the names of Licensor, nor the + names of any contributors to the Original Work, nor any of their + trademarks or service marks, may be used to endorse or promote products + derived from this Original Work without express prior written permission + of the Licensor. Nothing in this License shall be deemed to grant any + rights to trademarks, copyrights, patents, trade secrets or any other + intellectual property of Licensor except as expressly stated herein. No + patent license is granted to make, use, sell or offer to sell + embodiments of any patent claims other than the licensed claims defined + in Section 2. No right is granted to the trademarks of Licensor even if + such marks are included in the Original Work. Nothing in this License + shall be interpreted to prohibit Licensor from licensing under different + terms from this License any Original Work that Licensor otherwise would + have a right to license. + 5. External Deployment . The term "External Deployment" means the use or + distribution of the Original Work or Derivative Works in any way such + that the Original Work or Derivative Works may be used by anyone other + than You, whether the Original Work or Derivative Works are distributed + to those persons or made available as an application intended for use + over a computer network. As an express condition for the grants of + license hereunder, You agree that any External Deployment by You of a + Derivative Work shall be deemed a distribution and shall be licensed to + all under the terms of this License, as prescribed in section 1(c) + herein. + 6. Attribution Rights . You must retain, in the Source Code of any + Derivative Works that You create, all copyright, patent or trademark + notices from the Source Code of the Original Work, as well as any + notices of licensing and any descriptive text identified therein as an + "Attribution Notice." You must cause the Source Code for any Derivative + Works that You create to carry a prominent Attribution Notice reasonably + calculated to inform recipients that You have modified the Original + Work. + 7. Warranty of Provenance and Disclaimer of Warranty . Licensor warrants + that the copyright in and to the Original Work and the patent rights + granted herein by Licensor are owned by the Licensor or are sublicensed + to You under the terms of this License with the permission of the + contributor(s) of those copyrights and patent rights. Except as + expressly stated in the immediately proceeding sentence, the Original + Work is provided under this License on an "AS IS" BASIS and WITHOUT + WARRANTY, either express or implied, including, without limitation, the + warranties of NON-INFRINGEMENT, MERCHANTABILITY or FITNESS FOR A + PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL + WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential + part of this License. No license to Original Work is granted hereunder + except under this disclaimer. + 8. Limitation of Liability . Under no circumstances and under no legal + theory, whether in tort (including negligence), contract, or otherwise, + shall the Licensor be liable to any person for any direct, indirect, + special, incidental, or consequential damages of any character arising + as a result of this License or the use of the Original Work including, + without limitation, damages for loss of goodwill, work stoppage, + computer failure or malfunction, or any and all other commercial damages + or losses. This limitation of liability shall not apply to liability for + death or personal injury resulting from Licensor's negligence to the + extent applicable law prohibits such limitation. Some jurisdictions do + not allow the exclusion or limitation of incidental or consequential + damages, so this exclusion and limitation may not apply to You. + 9. Acceptance and Termination . If You distribute copies of the Original + Work or a Derivative Work, You must make a reasonable effort under the + circumstances to obtain the express assent of recipients to the terms of + this License. Nothing else but this License (or another written + agreement between Licensor and You) grants You permission to create + Derivative Works based upon the Original Work or to exercise any of the + rights granted in Section 1 herein, and any attempt to do so except + under the terms of this License (or another written agreement between + Licensor and You) is expressly prohibited by U.S. copyright law, the + equivalent laws of other countries, and by international treaty. + Therefore, by exercising any of the rights granted to You in Section 1 + herein, You indicate Your acceptance of this License and all of its + terms and conditions. This License shall terminate immediately and you + may no longer exercise any of the rights granted to You by this License + upon Your failure to honor the proviso in Section 1(c) herein. + 10. Termination for Patent Action . This License shall terminate + automatically and You may no longer exercise any of the rights granted + to You by this License as of the date You commence an action, including + a cross-claim or counterclaim, against Licensor or any licensee alleging + that the Original Work infringes a patent. This termination provision + shall not apply for an action alleging patent infringement by + combinations of the Original Work with other software or hardware. + 11. Jurisdiction, Venue and Governing Law . Any action or suit relating to + this License may be brought only in the courts of a jurisdiction wherein + the Licensor resides or in which Licensor conducts its primary business, + and under the laws of that jurisdiction excluding its conflict-of-law + provisions. The application of the United Nations Convention on + Contracts for the International Sale of Goods is expressly excluded. Any + use of the Original Work outside the scope of this License or after its + termination shall be subject to the requirements and penalties of the + U.S. Copyright Act, 17 U.S.C. § 101 et seq., the equivalent laws of + other countries, and international treaty. This section shall survive + the termination of this License. + 12. Attorneys Fees . In any action to enforce the terms of this License or + seeking damages relating thereto, the prevailing party shall be entitled + to recover its costs and expenses, including, without limitation, + reasonable attorneys' fees and costs incurred in connection with such + action, including any appeal of such action. This section shall survive + the termination of this License. + 13. Miscellaneous . This License represents the complete agreement + concerning the subject matter hereof. If any provision of this License + is held to be unenforceable, such provision shall be reformed only to + the extent necessary to make it enforceable. + 14. Definition of "You" in This License . "You" throughout this License, + whether in upper or lower case, means an individual or a legal entity + exercising rights under, and complying with all of the terms of, this + License. For legal entities, "You" includes any entity that controls, is + controlled by, or is under common control with you. For 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. + 15. Right to Use. You may use the Original Work in all ways not otherwise + restricted or conditioned by this License or by law, and Licensor + promises not to interfere with or be responsible for such uses by You. + + This license is Copyright (C) 2003-2004 Lawrence E. Rosen. All rights + reserved. Permission is hereby granted to copy and distribute this license + without modification. This license may not be modified without the express + written permission of its copyright owner. + diff --git a/lib/spikephpcoverage/LICENSE.pear.3_01 b/lib/spikephpcoverage/LICENSE.pear.3_01 new file mode 100644 index 0000000000..8d3fa076f6 --- /dev/null +++ b/lib/spikephpcoverage/LICENSE.pear.3_01 @@ -0,0 +1,68 @@ +-------------------------------------------------------------------- + The PHP License, version 3.01 +Copyright (c) 1999 - 2006 The PHP Group. All rights reserved. +-------------------------------------------------------------------- + +Redistribution and use in source and binary forms, with or without +modification, is permitted provided that the following conditions +are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + 3. The name "PHP" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact group@php.net. + + 4. Products derived from this software may not be called "PHP", nor + may "PHP" appear in their name, without prior written permission + from group@php.net. You may indicate that your software works in + conjunction with PHP by saying "Foo for PHP" instead of calling + it "PHP Foo" or "phpfoo" + + 5. The PHP Group may publish revised and/or new versions of the + license from time to time. Each version will be given a + distinguishing version number. + Once covered code has been published under a particular version + of the license, you may always continue to use it under the terms + of that version. You may also choose to use such covered code + under the terms of any subsequent version of the license + published by the PHP Group. No one other than the PHP Group has + the right to modify the terms applicable to covered code created + under this License. + + 6. Redistributions of any form whatsoever must retain the following + acknowledgment: + "This product includes PHP software, freely available from + <http://www.php.net/software/>". + +THIS SOFTWARE IS PROVIDED BY THE PHP DEVELOPMENT TEAM ``AS IS'' AND +ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PHP +DEVELOPMENT TEAM OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------- + +This software consists of voluntary contributions made by many +individuals on behalf of the PHP Group. + +The PHP Group can be contacted via Email at group@php.net. + +For more information on the PHP Group and the PHP project, +please see <http://www.php.net>. + +PHP includes the Zend Engine, freely available at +<http://www.zend.com>. diff --git a/lib/spikephpcoverage/README b/lib/spikephpcoverage/README new file mode 100644 index 0000000000..d8afd4b85b --- /dev/null +++ b/lib/spikephpcoverage/README @@ -0,0 +1,53 @@ +################################################################################ +# $Id$ +# +# Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved. +# Licensed under the Open Software License version 2.1 +# (See http://www.spikesource.com/license.html) +################################################################################ + +=========================== + Spike PHPCoverage 0.8.2 +=========================== + +1. Introduction +=============== + + Spike PHPCoverage is an open-source tool for measuring and reporting code + coverage provided by the test suite of a PHP application. + Spike PHPCoverage can instrument and record the line coverage information + for any PHP script at runtime. + + Spike PHPCoverage also provides an extensible reporting mechanism with a + standard HTML report implemented out of the box. The default report + displays the summary information about the code coverage for an application + and also shows the detailed information about a file including which + lines were actually executed and with what frequency. It is possible + to specify the directories and files that should be included and/or + excluded from a coverage measurement. Screen shots of the summary and + detailed line coverage reports are available at + http://www.spikesource.com/projects/phpcoverage/screenshots.php + + Spike PHPCoverage works on PHP 5.0 and newer and uses XDebug Zend Extension + for gathering the coverage data. + + +2. Requirements +=============== + + * PHP 5.0 or newer + * Xdebug Zend Extension configured with PHP (Binaries for 32-bit Linux and Windows included.) + * XML_Parser PEAR module version 1.2.5 or newer (Included) + * Web browser to view the coverage report + +3. Frequently Asked Questions +============================= + See http://developer.spikesource.com/wiki/index.php/Projects:phpcoverageFAQ for latest FAQs. + +4. API Documentation +==================== + + Spike PHPCoverage API Documentation is available with this distribution under the 'doc' + directory. A latest version is also available online at + http://www.spikesource.com/projects/phpcoverage/doc/index.html + diff --git a/lib/spikephpcoverage/RELEASE_NOTES b/lib/spikephpcoverage/RELEASE_NOTES new file mode 100644 index 0000000000..78de9fda15 --- /dev/null +++ b/lib/spikephpcoverage/RELEASE_NOTES @@ -0,0 +1,29 @@ +################################################################################ +# $Id$ +# +# Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved. +# Licensed under the Open Software License version 2.1 +# (See http://www.spikesource.com/license.html) +################################################################################ + +=============================== +Spike PHPCoverage Release Notes +=============================== + +Version 0.8.2 +=================== + + * Known Issues + -------------- + 1. Incorrect execution frequency + The code coverage execution frequency for some of the PHP lines may + not be correct. Xdebug has changed its implementation to only return 1 + for executed lines. Currently, there is no time frame for fixing this issue in + Spike PHPCoverage. + + 2. It takes a long time to generate code coverage report for large sized + applications. + + 3. The "--cov-data-file" option in cli/driver.php has been changed to + "--cov-data-files" and accepts a comma-separated list of coverage recording + xml files. It will then generate a report after merging the data. diff --git a/lib/spikephpcoverage/index.html b/lib/spikephpcoverage/index.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/spikephpcoverage/readme_moodle.txt b/lib/spikephpcoverage/readme_moodle.txt new file mode 100644 index 0000000000..e56eb8a5b3 --- /dev/null +++ b/lib/spikephpcoverage/readme_moodle.txt @@ -0,0 +1,18 @@ +Description of Spike PHPCoverage 0.8.2 library import into Moodle + +Removed: + * build.xml + * samples + * screenshots + * xdebug_bin + * src/PEAR.php | => Already in lib/pear + * src/XML | + +Added: + * index.html - prevent directory browsing on misconfigured servers + * readme_moodle.txt - this file ;-) + +Our changes: /// Look for "moodle" in code + * src/parser/PHPParser.php - comment some debug lines causing some notices in moodle + +20090621 - Eloy Lafuente (stronk7): Original import of 0.8.2 release diff --git a/lib/spikephpcoverage/src/CoverageRecorder.php b/lib/spikephpcoverage/src/CoverageRecorder.php new file mode 100644 index 0000000000..6d1683655c --- /dev/null +++ b/lib/spikephpcoverage/src/CoverageRecorder.php @@ -0,0 +1,456 @@ +<?php + /* + * $Id$ + * + * Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved. + * Licensed under the Open Software License version 2.1 + * (See http://www.spikesource.com/license.html) + */ +?> +<?php + + if(!defined("__PHPCOVERAGE_HOME")) { + define("__PHPCOVERAGE_HOME", dirname(__FILE__)); + } + require_once __PHPCOVERAGE_HOME . "/conf/phpcoverage.conf.php"; + require_once __PHPCOVERAGE_HOME . "/util/Utility.php"; + require_once __PHPCOVERAGE_HOME . "/reporter/CoverageReporter.php"; + + /** + * + * The Coverage Recorder utility + * + * This is the main class for the CoverageRecorder. User should + * instantiate this class and set various parameters of it. + * The startInstrumentation and stopInstrumentation methods will + * switch code coverage recording on and off respectively. + * + * The code coverage is recorded using XDebug Zend Extension. Therefore, + * it is required to install that extension on the system where + * code coverage measurement is going to take place. See + * {@link http://www.xdebug.org www.xdebug.org} for more + * information. + * + * @author Nimish Pachapurkar <npac@spikesource.com> + * @version $Revision$ + */ + class CoverageRecorder { + + // {{{ Members + + protected $includePaths; + protected $excludePaths; + protected $reporter; + protected $coverageData; + protected $isRemote = false; + protected $stripped = false; + protected $phpCoverageFiles = array("phpcoverage.inc.php"); + protected $version; + protected $logger; + + /** + * What extensions are treated as php files. + * + * @param "php" Array of extension strings + */ + protected $phpExtensions; + + // }}} + // {{{ Constructor + + /** + * Constructor (PHP5 only) + * + * @param $includePaths Directories to be included in code coverage report + * @param $excludePaths Directories to be excluded from code coverage report + * @param $reporter Instance of a Reporter subclass + * @access public + */ + public function __construct( + $includePaths=array("."), + $excludePaths=array(), + $reporter="new HtmlCoverageReporter()" + ) { + + $this->includePaths = $includePaths; + $this->excludePaths = $excludePaths; + $this->reporter = $reporter; + // Set back reference + $this->reporter->setCoverageRecorder($this); + $this->excludeCoverageDir(); + $this->version = "0.8.2"; + + // Configuration + global $spc_config; + $this->phpExtensions = $spc_config['extensions']; + global $util; + $this->logger = $util->getLogger(); + } + + // }}} + // {{{ public function startInstrumentation() + + /** + * Starts the code coverage recording + * + * @access public + */ + public function startInstrumentation() { + if(extension_loaded("xdebug")) { + xdebug_start_code_coverage(); + return true; + } + $this->logger->critical("[CoverageRecorder::startInstrumentation()] " + . "ERROR: Xdebug not loaded.", __FILE__, __LINE__); + return false; + } + + // }}} + // {{{ public function stopInstrumentation() + + /** + * Stops code coverage recording + * + * @access public + */ + public function stopInstrumentation() { + if(extension_loaded("xdebug")) { + $this->coverageData = xdebug_get_code_coverage(); + xdebug_stop_code_coverage(); + $this->logger->debug("[CoverageRecorder::stopInstrumentation()] Code coverage: " . print_r($this->coverageData, true), + __FILE__, __LINE__); + return true; + } + else { + $this->logger->critical("[CoverageRecorder::stopInstrumentation()] Xdebug not loaded.", __FILE__, __LINE__); + } + return false; + } + + // }}} + // {{{ public function generateReport() + + /** + * Generate the code coverage report + * + * @access public + */ + public function generateReport() { + if($this->isRemote) { + $this->logger->info("[CoverageRecorder::generateReport()] " + ."Writing report.", __FILE__, __LINE__); + } + else { + $this->logger->info("[CoverageRecorder::generateReport()] " + . "Writing report:\t\t", __FILE__, __LINE__); + } + $this->logger->debug("[CoverageRecoder::generateReport()] " . print_r($this->coverageData, true), + __FILE__, __LINE__); + $this->unixifyCoverageData(); + $this->coverageData = $this->stripCoverageData(); + $this->reporter->generateReport($this->coverageData); + if($this->isRemote) { + $this->logger->info("[CoverageRecorder::generateReport()] [done]", __FILE__, __LINE__); + } + else { + $this->logger->info("[done]", __FILE__, __LINE__); + } + } + + // }}} + /*{{{ protected function removeAbsentPaths() */ + + /** + * Remove the directories that do not exist from the input array + * + * @param &$dirs Array of directory names + * @access protected + */ + protected function removeAbsentPaths(&$dirs) { + for($i = 0; $i < count($dirs); $i++) { + if(! file_exists($dirs[$i])) { + // echo "Not found: " . $dirs[$i] . "\n"; + $this->errors[] = "Not found: " . $dirs[$i] + . ". Removing ..."; + array_splice($dirs, $i, 1); + $i--; + } + else { + $dirs[$i] = realpath($dirs[$i]); + } + } + } + + /*}}}*/ + // {{{ protected function processSourcePaths() + + /** + * Processes and validates the source directories + * + * @access protected + */ + protected function processSourcePaths() { + $this->removeAbsentPaths($this->includePaths); + $this->removeAbsentPaths($this->excludePaths); + + sort($this->includePaths, SORT_STRING); + } + + // }}} + /*{{{ protected function getFilesAndDirs() */ + + /** + * Get the list of files that match the extensions in $this->phpExtensions + * + * @param $dir Root directory + * @param &$files Array of filenames to append to + * @access protected + */ + protected function getFilesAndDirs($dir, &$files) { + global $util; + $dirs[] = $dir; + while(count($dirs) > 0) { + $currDir = realpath(array_pop($dirs)); + if(!is_readable($currDir)) { + continue; + } + //echo "Current Dir: $currDir \n"; + $currFiles = scandir($currDir); + //print_r($currFiles); + for($j = 0; $j < count($currFiles); $j++) { + if($currFiles[$j] == "." || $currFiles[$j] == "..") { + continue; + } + $currFiles[$j] = $currDir . "/" . $currFiles[$j]; + //echo "Current File: " . $currFiles[$j] . "\n"; + if(is_file($currFiles[$j])) { + $pathParts = pathinfo($currFiles[$j]); + if(isset($pathParts['extension']) && in_array($pathParts['extension'], $this->phpExtensions)) { + $files[] = $util->replaceBackslashes($currFiles[$j]); + } + } + if(is_dir($currFiles[$j])) { + $dirs[] = $currFiles[$j]; + } + } + } + } + + /*}}}*/ + /*{{{ protected function addFiles() */ + + /** + * Add all source files to the list of files that need to be parsed. + * + * @access protected + */ + protected function addFiles() { + global $util; + $files = array(); + for($i = 0; $i < count($this->includePaths); $i++) { + $this->includePaths[$i] = $util->replaceBackslashes($this->includePaths[$i]); + if(is_dir($this->includePaths[$i])) { + //echo "Calling getFilesAndDirs with " . $this->includePaths[$i] . "\n"; + $this->getFilesAndDirs($this->includePaths[$i], $files); + } + else if(is_file($this->includePaths[$i])) { + $files[] = $this->includePaths[$i]; + } + } + + $this->logger->debug("Found files:" . print_r($files, true), + __FILE__, __LINE__); + for($i = 0; $i < count($this->excludePaths); $i++) { + $this->excludePaths[$i] = $util->replaceBackslashes($this->excludePaths[$i]); + } + + for($i = 0; $i < count($files); $i++) { + for($j = 0; $j < count($this->excludePaths); $j++) { + $this->logger->debug($files[$i] . "\t" . $this->excludePaths[$j] . "\n", __FILE__, __LINE__); + if(strpos($files[$i], $this->excludePaths[$j]) === 0) { + continue; + } + } + if(!array_key_exists($files[$i], $this->coverageData)) { + $this->coverageData[$files[$i]] = array(); + } + } + } + + /*}}}*/ + // {{{ protected function stripCoverageData() + + /** + * Removes the unwanted coverage data from the recordings + * + * @return Processed coverage data + * @access protected + */ + protected function stripCoverageData() { + if($this->stripped) { + $this->logger->debug("[CoverageRecorder::stripCoverageData()] Already stripped!", __FILE__, __LINE__); + return $this->coverageData; + } + $this->stripped = true; + if(empty($this->coverageData)) { + $this->logger->warn("[CoverageRecorder::stripCoverageData()] No coverage data found.", __FILE__, __LINE__); + return $this->coverageData; + } + $this->processSourcePaths(); + $this->logger->debug("!!!!!!!!!!!!! Source Paths !!!!!!!!!!!!!!", + __FILE__, __LINE__); + $this->logger->debug(print_r($this->includePaths, true), + __FILE__, __LINE__); + $this->logger->debug(print_r($this->excludePaths, true), + __FILE__, __LINE__); + $this->logger->debug("!!!!!!!!!!!!! Source Paths !!!!!!!!!!!!!!", + __FILE__, __LINE__); + $this->addFiles(); + $altCoverageData = array(); + foreach ($this->coverageData as $filename => &$lines) { + $preserve = false; + $realFile = $filename; + for($i = 0; $i < count($this->includePaths); $i++) { + if(strpos($realFile, $this->includePaths[$i]) === 0) { + $preserve = true; + } + else { + $this->logger->debug("File: " . $realFile + . "\nDoes not match: " . $this->includePaths[$i], + __FILE__, __LINE__); + } + } + // Exclude dirs have a precedence over includes. + for($i = 0; $i < count($this->excludePaths); $i++) { + if(strpos($realFile, $this->excludePaths[$i]) === 0) { + $preserve = false; + } + else if(in_array(basename($realFile), $this->phpCoverageFiles)) { + $preserve = false; + } + } + if($preserve) { + // Should be preserved + $altCoverageData[$filename] = $lines; + } + + // Fix for bug #34 <http://developer.spikesource.com/tracker/index.php?func=detail&aid=34&group_id=9&atid=117> + // Finally get rid of data for extensions we don't care about + if (is_file($filename)) { + $parts = pathinfo($filename); + if (!empty($parts['extension']) && !in_array($parts['extension'], $this->phpExtensions)) { + // Remove this key and its contents + if (isset($altCoverageData[$filename])) { + $this->logger->debug("!!! Extension mismatch; Removing file: " . $filename, __FILE__, __LINE__); + unset($altCoverageData[$filename]); + } + } + } + } + + array_multisort($altCoverageData, SORT_STRING); + return $altCoverageData; + } + + // }}} + /*{{{ protected function unixifyCoverageData() */ + + /** + * Convert filepaths in coverage data to forward slash separated + * paths. + * + * @access protected + */ + protected function unixifyCoverageData() { + global $util; + $tmpCoverageData = array(); + foreach($this->coverageData as $file => &$lines) { + $tmpCoverageData[$util->replaceBackslashes(realpath($file))] = $lines; + } + $this->coverageData = $tmpCoverageData; + } + + /*}}}*/ + // {{{ public function getErrors() + + /** + * Returns the errors array containing all error encountered so far. + * + * @return Array of error messages + * @access public + */ + public function getErrors() { + return $this->errors; + } + + // }}} + // {{{ public function logErrors() + + /** + * Writes all error messages to error log + * + * @access public + */ + public function logErrors() { + $this->logger->error(print_r($this->errors, true), + __FILE__, __LINE__); + } + + // }}} + /*{{{ Getters and Setters */ + + public function getIncludePaths() { + return $this->includePaths; + } + + public function setIncludePaths($includePaths) { + $this->includePaths = $includePaths; + } + + public function getExcludePaths() { + return $this->excludePaths; + } + + public function setExcludePaths($excludePaths) { + $this->excludePaths = $excludePaths; + $this->excludeCoverageDir(); + } + + public function getReporter() { + return $this->reporter; + } + + public function setReporter(&$reporter) { + $this->reporter = $reporter; + } + + public function getPhpExtensions() { + return $this->phpExtensions; + } + + public function setPhpExtensions(&$extensions) { + $this->phpExtensions = $extensions; + } + + public function getVersion() { + return $this->version; + } + + /*}}}*/ + /*{{{ public function excludeCoverageDir() */ + + /** + * Exclude the directory containing the coverage measurement code. + * + * @access public + */ + public function excludeCoverageDir() { + $f = __FILE__; + if(is_link($f)) { + $f = readlink($f); + } + $this->excludePaths[] = realpath(dirname($f)); + } + /*}}}*/ + } +?> diff --git a/lib/spikephpcoverage/src/cli/driver.php b/lib/spikephpcoverage/src/cli/driver.php new file mode 100644 index 0000000000..57677c3693 --- /dev/null +++ b/lib/spikephpcoverage/src/cli/driver.php @@ -0,0 +1,327 @@ +<?php +/* + * $Id$ + * + * Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved. + * Licensed under the Open Software License version 2.1 + * (See http://www.spikesource.com/license.html) +*/ +?> +<?php + /** + * This driver file can be used to initialize and generate PHPCoverage + * report when PHPCoverage is used with a non-PHP test tool - like HttpUnit + * It can, of course, be used for PHP test tools like SimpleTest and PHPUnit + * + * Notes: + * * Option parsing courtesy of "daevid at daevid dot com" (http://php.planetmirror.com/manual/en/function.getopt.php) + * * Originally contributed by Ed Espino <eespino@spikesource.com> + */ + + if(!defined("__PHPCOVERAGE_HOME")) { + define("__PHPCOVERAGE_HOME", dirname(dirname(__FILE__))); + } + require_once __PHPCOVERAGE_HOME . "/conf/phpcoverage.conf.php"; + require_once __PHPCOVERAGE_HOME . "/util/Utility.php"; + + // ###################################################################### + // ###################################################################### + + function usage() { + global $util; + echo "Usage: " . $_SERVER['argv'][0] . " <options>\n"; + echo "\n"; + echo " Options: \n"; + echo " --phpcoverage-home <path> OR -p <path> Path to PHPCoverage home (defaults to PHPCOVERAGE_HOME environment property)\n"; + echo " --init Initialize PHPCoverage Reporting\n"; + echo " --report Generate PHPCoverage Report\n"; + echo " --local Run and generate PHPCoverage report for local test run (Implies '--report')\n"; + echo " --cleanup Remove existing PHPCoverage data\n"; + echo " init options \n"; + echo " --cov-url <url> Specify application default url\n"; + echo " --tmp-dir <path> Specify tmp directory location (Defaults to '" . $util->getTmpDir() . "')\n"; + echo " --cov-file-name <name> Specify coverage data file name (Defaults to 'phpcoverage.data.xml')\n"; + echo " remote report options \n"; + echo " --cov-data-files <path1,path2,...> Coverage data file path [use this instead of --cov-url for a local file path]\n"; + echo " --report-name <name> Report name\n"; + echo " --report-dir <path> Report directory path (Defaults to 'report')\n"; + echo " --appbase-path <path> Application base path (Defaults to PHPCOVERAGE_APPBASE_PATH if specified on the command line)\n"; + echo " --include-paths <path1,path2,...> Comma-separated paths to include in code coverage report. (Includes appbase-path by default)\n"; + echo " --exclude-paths <path1,path2,...> Comma-separated paths to exclude from code coverage report.\n"; + echo " --print-summary Print coverage report summary to console.\n"; + echo " local report options \n"; + echo " --report-name <name> Report name\n"; + echo " --report-dir <path> Report directory path (Defaults to 'report')\n"; + echo " --appbase-path <path> Application base path (Defaults to PHPCOVERAGE_APPBASE_PATH if specified on the command line)\n"; + echo " --include-paths <path1,path2,...> Comma-separated paths to include in code coverage report.\n"; + echo " --exclude-paths <path1,path2,...> Comma-separated paths to exclude from code coverage report.\n"; + echo " --print-summary Print coverage report summary to console.\n"; + echo " --test-driver <path> Path to the test driver file.\n"; + echo " other options \n"; + echo " --verbose OR -v Print verbose information\n"; + echo " --help OR -h Display this usage information\n"; + echo ""; + echo " Sample Usage (local execution):\n"; + echo " php driver.php --local --report \\ \n"; + echo " -p /opt/phpcoverage/src \\ \n"; + echo " --report-name 'Test Report' --report-dir /tmp/report \\ \n"; + echo " --appbase-path /opt/phpcoverage/samples/local \\ \n"; + echo " --test-driver /opt/phpcoverage/samples/local/test_driver.php \\ \n"; + echo " --print-summary \n"; + exit; + } + + // + // Setup command line argument processing + // + + $OPTION["p"] = false; + $OPTION['verbose'] = false; + $OPTION['init'] = false; + $OPTION['report'] = false; + $OPTION['cleanup'] = false; + $OPTION['cov-url'] = false; + $OPTION['report-name'] = false; + $OPTION['report-dir'] = false; + $OPTION['tmp-dir'] = false; + $OPTION['cov-file-name'] = false; + $OPTION['cov-data-files'] = false; + $OPTION['appbase-path'] = false; + $OPTION['local'] = false; + $OPTION['test-driver'] = false; + + // + // loop through our arguments and see what the user selected + // + + for ($i = 1; $i < $_SERVER["argc"]; $i++) { + switch($_SERVER["argv"][$i]) { + case "--phpcoverage-home": + case "-p": + $OPTION['p'] = $_SERVER['argv'][++$i]; + break; + case "-v": + case "--verbose": + $OPTION['verbose'] = true; + break; + case "--init": + $OPTION['init'] = true; + break; + case "--report": + $OPTION['report'] = true; + break; + case "--local": + $OPTION['local'] = true; + $OPTION['report'] = true; + break; + case "--cleanup": + $OPTION['cleanup'] = true; + break; + case "--cov-url": + $OPTION['cov-url'] = $_SERVER['argv'][++$i] . "/" . "phpcoverage.remote.top.inc.php"; + break; + case "--tmp-dir": + $OPTION['tmp-dir'] = $_SERVER['argv'][++$i]; + break; + case "--cov-file-name": + $OPTION['cov-file-name'] = $_SERVER['argv'][++$i]; + break; + case "--cov-data-files": + $OPTION['cov-data-files'] = $_SERVER['argv'][++$i]; + break; + case "--report-name": + $OPTION['report-name'] = $_SERVER['argv'][++$i]; + break; + case "--report-dir": + $OPTION['report-dir'] = $_SERVER['argv'][++$i]; + break; + case "--appbase-path": + $OPTION['appbase-path'] = $_SERVER['argv'][++$i]; + break; + case "--include-paths": + $OPTION['include-paths'] = $_SERVER['argv'][++$i]; + break; + case "--exclude-paths": + $OPTION['exclude-paths'] = $_SERVER['argv'][++$i]; + break; + case "--print-summary": + $OPTION['print-summary'] = true; + break; + case "--test-driver": + $OPTION['test-driver'] = $_SERVER['argv'][++$i]; + break; + case "--help": + case "-h": + usage(); + break; + } + } + + if($OPTION['p'] == false) { + $OPTION['p'] = __PHPCOVERAGE_HOME; + if(empty($OPTION['p']) || !is_dir($OPTION['p'])) { + die("PHPCOVERAGE_HOME does not exist. [" . $OPTION['p'] . "]"); + } + } + + putenv("PHPCOVERAGE_HOME=" . $OPTION['p']); + + require_once $OPTION['p'] . "/phpcoverage.inc.php"; + require_once PHPCOVERAGE_HOME . "/remote/RemoteCoverageRecorder.php"; + require_once PHPCOVERAGE_HOME . "/reporter/HtmlCoverageReporter.php"; + + // Initializations + $includePaths = array(); + $excludePaths = array(); + + if (!$OPTION['cov-url']){ + if(!$OPTION['report'] && !$OPTION['cov-data-files']) { + echo "ERROR: No --cov-url option specified.\n"; + exit(1); + } + } + + if($OPTION['init']) { + if(!$OPTION['tmp-dir']) { + $OPTION['tmp-dir'] = $util->getTmpDir(); + } + if(!$OPTION['cov-file-name']) { + $OPTION['cov-file-name'] = "phpcoverage.data.xml"; + } + } + + if($OPTION['report']) { + if (!$OPTION['report-name']){ + echo "ERROR: No --report-name option specified.\n"; + exit(1); + } + + if(!$OPTION['report-dir']) { + if(!empty($PHPCOVERAGE_REPORT_DIR)) { + $OPTION["report-dir"] = $PHPCOVERAGE_REPORT_DIR; + } + else { + $OPTION["report-dir"] = "report"; + } + } + + if(empty($OPTION['appbase-path']) && !empty($PHPCOVERAGE_APPBASE_PATH)) { + $OPTION['appbase-path'] = realpath($PHPCOVERAGE_APPBASE_PATH); + } + + if(isset($OPTION['include-paths'])) { + $includePaths = explode(",", $OPTION['include-paths']); + } + if(isset($OPTION['appbase-path']) && !empty($OPTION["appbase-path"])) { + $includePaths[] = $OPTION['appbase-path']; + } + + if(isset($OPTION['exclude-paths'])) { + $excludePaths = explode(",", $OPTION['exclude-paths']); + } + + if($OPTION['local']) { + //local coverage run + if(empty($OPTION['test-driver'])) { + echo "Error: No test driver file specified.\n"; + exit(1); + } + if(!is_file($OPTION['test-driver'])) { + echo "Error: No such file: " . $OPTION['test-driver'] . "\n"; + exit(1); + } + $excludePaths[] = $OPTION['test-driver']; + } + } + + if ($OPTION['verbose']){ + echo "Options: " . print_r($OPTION, true) . "\n"; + echo "include-paths: " . print_r($includePaths, true) . "\n"; + echo "exclude-paths: " . print_r($excludePaths, true) . "\n"; + } + + // + // + // + + if ($OPTION['init']){ + echo "PHPCoverage: init " . $OPTION['cov-url'] . "?phpcoverage-action=init&cov-file-name=". urlencode($OPTION["cov-file-name"]) . "&tmp-dir=" . urlencode($OPTION['tmp-dir']) . "\n"; + + // + // Initialize the PHPCoverage reporting framework + // + + file_get_contents($OPTION['cov-url'] . "?phpcoverage-action=init&cov-file-name=". urlencode($OPTION["cov-file-name"]) . "&tmp-dir=" . urlencode($OPTION['tmp-dir'])); + + } + else if ($OPTION['report']){ + + if(empty($OPTION['local'])) { + + // + // Retrieve coverage data (xml) from the PHPCoverage reporting framework + // + + if($OPTION['cov-data-files']) { + $OPTION['cov-data-fileset'] = explode(",", $OPTION['cov-data-files']); + foreach($OPTION['cov-data-fileset'] as $covDataFile) { + if(!is_readable($covDataFile)) { + echo "Error: Cannot read cov-data-file: " . $covDataFile . "\n"; + exit(1); + } + $xmlUrl[] = $covDataFile; + } + } + else { + echo "PHPCoverage: report " . $OPTION['cov-url'] . "?phpcoverage-action=get-coverage-xml" . "\n"; + $xmlUrl = $OPTION['cov-url'] . "?phpcoverage-action=get-coverage-xml"; + } + + // + // Configure reporter, and generate the PHPCoverage report + // + + $covReporter = new HtmlCoverageReporter($OPTION['report-name'], "", $OPTION["report-dir"]); + + // + // Notice the coverage recorder is of type RemoteCoverageRecorder + // + + $cov = new RemoteCoverageRecorder($includePaths, $excludePaths, $covReporter); + $cov->generateReport($xmlUrl, true); + $covReporter->printTextSummary($OPTION["report-dir"] . "/report.txt"); + // Should the summary be printed to console ? + if(isset($OPTION['print-summary']) && $OPTION['print-summary']) { + $covReporter->printTextSummary(); + } + } + else { + // + // Configure reporter, and generate the PHPCoverage report + // + + $covReporter = new HtmlCoverageReporter($OPTION['report-name'], "", $OPTION["report-dir"]); + + $cov = new CoverageRecorder($includePaths, $excludePaths, $covReporter); + $cov->startInstrumentation(); + echo "\n>>>>>>>>>>>>>>>>>>> Output from the test driver begins\n\n"; + include $OPTION['test-driver']; + echo "\n\nOutput from the test driver ends <<<<<<<<<<<<<<<<<<<<<\n"; + $cov->stopInstrumentation(); + $cov->generateReport(); + $covReporter->printTextSummary($OPTION["report-dir"] . "/report.txt"); + // Should the summary be printed to console ? + if(isset($OPTION['print-summary']) && $OPTION['print-summary']) { + $covReporter->printTextSummary(); + } + } + } + else if ($OPTION['cleanup']){ + + echo "PHPCoverage: cleanup " . $OPTION['cov-url'] . "?phpcoverage-action=cleanup"; + file_get_contents($OPTION['cov-url'] . "?phpcoverage-action=cleanup"); + + } + +?> + diff --git a/lib/spikephpcoverage/src/cli/instrument.php b/lib/spikephpcoverage/src/cli/instrument.php new file mode 100644 index 0000000000..e6e78310fc --- /dev/null +++ b/lib/spikephpcoverage/src/cli/instrument.php @@ -0,0 +1,305 @@ +<?php + /* + * $Id$ + * + * Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved. + * Licensed under the Open Software License version 2.1 + * (See http://www.spikesource.com/license.html) + */ +?> +<?php + #!/bin/php + + if(!defined("__PHPCOVERAGE_HOME")) { + define("__PHPCOVERAGE_HOME", dirname(dirname(__FILE__))); + } + require_once __PHPCOVERAGE_HOME . "/conf/phpcoverage.conf.php"; + require_once __PHPCOVERAGE_HOME . "/util/Utility.php"; + + ## Instruments the PHP Source files + + /** + * Print help message and exit + * + * @access public + */ + function help() { + echo "Usage: " . basename(__FILE__) . " -b <application-base-path> [-p <phpcoverage-home>] [-r] [-u] [-e <exclude-file-list>]" + . "[-v] [-h] [path1 [path2 [...]]]\n"; + echo "\n"; + echo " Options: \n"; + echo " -b <application-base-path> Application directory accessible via HTTP " + . "where PHPCoverage files should be copied.\n"; + echo " -p <phpcoverage-home> Path to PHPCoverage Home.\n"; + echo " -r Recursively instrument PHP files.\n"; + echo " -u Undo instrumentation.\n"; + echo " -e <file1,file2,...> Execlude files in the file list.\n"; + echo " -v Be verbose.\n"; + echo " -h Print this help and exit.\n"; + echo "\n"; + exit(0); + } + + /** + * Print error message and exit + * + * @param $msg Message to write to console. + * @access public + */ + function error($msg) { + echo basename(__FILE__) . ": [ERROR] " . $msg . "\n"; + exit(1); + } + + /** + * Write a information message + * + * @param $msg Message to write to console. + * @access public + */ + function writeMsg($msg) { + global $VERBOSE; + if($VERBOSE) { + echo basename(__FILE__) . ": [INFO] " . $msg . "\n"; + } + } + + /** + * Instrument the PHP file. + * + * @param $file File path + * @access public + */ + function instrument($file) { + global $LOCAL_PHPCOVERAGE_LOCATION, $top, $bottom; + $tmpfile = "$file.tmp"; + $contents = file_get_contents($file); + $len = strlen($contents); + if(strpos($contents, $top) === 0 && strrpos($contents, $bottom) === ($len - strlen($bottom))) { + writeMsg("Skipping $file."); + return; + } + + $fp = fopen($tmpfile, "w"); + if(!$fp) { + error("Cannot write to file: $tmpfile"); + } + fputs($fp, $top); + fwrite($fp, $contents); + fputs($fp, $bottom); + fclose($fp); + // Delete if already exists - 'rename()' on Windows will return false otherwise + if(file_exists($file)) { + unlink($file); + } + $ret = rename($tmpfile, $file); + if(!$ret) { + error("Cannot save file: $file"); + } + writeMsg("Instrumented: $file."); + } + + /** + * Uninstrument the PHP file + * + * @param $file File path + * @access public + */ + function uninstrument($file) { + global $LOCAL_PHPCOVERAGE_LOCATION, $top, $bottom; + $tmpfile = "$file.tmp"; + + $contents = file_get_contents($file); + $len = strlen($contents); + if(strpos($contents, $top) !== 0 && strrpos($contents, $bottom) !== ($len - strlen($bottom))) { + writeMsg("Skipping $file."); + return; + } + + $fr = fopen($file, "r"); + $fw = fopen($tmpfile, "w"); + if(!$fr) { + error("Cannot read file: $file"); + } + if(!$fr) { + error("Cannot write to file: $tmpfile"); + } + while(!feof($fr)) { + $line = fgets($fr); + if(strpos($line, $top) === false && strpos($line, $bottom) === false) { + fputs($fw, $line); + } + } + fclose($fr); + fclose($fw); + + // Delete if already exists - 'rename()' on Windows will return false otherwise + if(file_exists($file)) { + unlink($file); + } + $ret = rename($tmpfile, $file); + if(!$ret) { + error("Cannot save file: $file"); + } + writeMsg("Uninstrumented: $file"); + } + + /** + * Retrive a list of all PHP files in the given directory + * + * @param $dir Directory to scan + * @param $recursive True is directory is scanned recursively + * @return Array List of PHP files + * @access public + */ + function get_all_php_files($dir, &$excludeFiles, $recursive) { + global $spc_config; + $phpExtensions = $spc_config["extensions"]; + $dirs[] = $dir; + while(count($dirs) > 0) { + $currDir = realpath(array_pop($dirs)); + if(!is_readable($currDir)) { + continue; + } + $currFiles = scandir($currDir); + for($j = 0; $j < count($currFiles); $j++) { + if($currFiles[$j] == "." || $currFiles[$j] == "..") { + continue; + } + $currFiles[$j] = $currDir . "/" . $currFiles[$j]; + if(is_file($currFiles[$j])) { + $pathParts = pathinfo($currFiles[$j]); + // Ignore phpcoverage bottom and top stubs + if(strpos($pathParts['basename'], "phpcoverage.remote.") !== false) { + continue; + } + // Ignore files specified in the exclude list + if(in_array(realpath($currFiles[$j]), $excludeFiles) !== false) { + continue; + } + if(isset($pathParts['extension']) + && in_array($pathParts['extension'], $phpExtensions)) { + $files[] = $currFiles[$j]; + } + } + else if(is_dir($currFiles[$j]) && $recursive) { + $dirs[] = $currFiles[$j]; + } + } + } + return $files; + } + + // Initialize + + $RECURSIVE = false; + $UNDO = false; + + $top_file = "/phpcoverage.remote.top.inc.php"; + $bottom_file = "/phpcoverage.remote.bottom.inc.php"; + + //print_r($argv); + for($i = 1; $i < $argc; $i++) { + switch($argv[$i]) { + case "-r": + $RECURSIVE = true; + break; + + case "-p": + $PHPCOVERAGE_HOME = $argv[++$i]; + break; + + case "-b": + $LOCAL_PHPCOVERAGE_LOCATION = $argv[++$i]; + break; + + case "-u": + $UNDO = true; + break; + + case "-e": + $EXCLUDE_FILES = explode(",", $argv[++$i]); + break; + + case "-v": + $VERBOSE = true; + break; + + case "-h": + help(); + break; + + default: + $paths[] = $argv[$i]; + break; + } + } + + + if(!is_dir($LOCAL_PHPCOVERAGE_LOCATION)) { + error("LOCAL_PHPCOVERAGE_LOCATION [$LOCAL_PHPCOVERAGE_LOCATION] not found."); + } + if(empty($PHPCOVERAGE_HOME) || !is_dir($PHPCOVERAGE_HOME)) { + $PHPCOVERAGE_HOME = __PHPCOVERAGE_HOME; + if(empty($PHPCOVERAGE_HOME) || !is_dir($PHPCOVERAGE_HOME)) { + error("PHPCOVERAGE_HOME does not exist. [" . $PHPCOVERAGE_HOME . "]"); + } + } + + $LOCAL_PHPCOVERAGE_LOCATION = realpath($LOCAL_PHPCOVERAGE_LOCATION); + if(file_exists($LOCAL_PHPCOVERAGE_LOCATION . $top_file)) { + unlink($LOCAL_PHPCOVERAGE_LOCATION . $top_file); + } + $ret = copy($PHPCOVERAGE_HOME . $top_file, $LOCAL_PHPCOVERAGE_LOCATION . $top_file); + if(!$ret) { + error("Cannot copy to $LOCAL_PHPCOVERAGE_LOCATION"); + } + if(file_exists($LOCAL_PHPCOVERAGE_LOCATION . $bottom_file)) { + unlink($LOCAL_PHPCOVERAGE_LOCATION . $bottom_file); + } + $ret = copy($PHPCOVERAGE_HOME . $bottom_file, $LOCAL_PHPCOVERAGE_LOCATION . $bottom_file); + if(!$ret) { + error("Cannot copy to $LOCAL_PHPCOVERAGE_LOCATION"); + } + $top="<?php require_once \"" . $LOCAL_PHPCOVERAGE_LOCATION . $top_file ."\"; ?>\n"; + $bottom="<?php require \"" . $LOCAL_PHPCOVERAGE_LOCATION . $bottom_file . "\"; ?>\n"; + + if(empty($paths)) { + $paths[] = getcwd(); + } + if(!isset($EXCLUDE_FILES) || empty($EXCLUDE_FILES)) { + $EXCLUDE_FILES = array(); + } + for($i = 0; $i < count($EXCLUDE_FILES); $i++) { + // Remove a file from the array if it does not exist + if(!file_exists($EXCLUDE_FILES[$i])) { + array_splice($EXCLUDE_FILES, $i, 1); + $i --; + continue; + } + $EXCLUDE_FILES[$i] = realpath($EXCLUDE_FILES[$i]); + } + + //print_r($paths); + foreach($paths as $path) { + unset($files); + if(is_dir($path)) { + $files = get_all_php_files($path, $EXCLUDE_FILES, $RECURSIVE); + } + else if(is_file($path)) { + $files[] = $path; + } + else { + error("Unknown entity: $path"); + } + //print_r($files); + foreach($files as $file) { + if($UNDO) { + uninstrument($file); + } + else { + instrument($file); + } + } + } +?> diff --git a/lib/spikephpcoverage/src/conf/phpcoverage.conf.php b/lib/spikephpcoverage/src/conf/phpcoverage.conf.php new file mode 100644 index 0000000000..bd7cea8ff8 --- /dev/null +++ b/lib/spikephpcoverage/src/conf/phpcoverage.conf.php @@ -0,0 +1,26 @@ +<?php +/* + * $Id$ + * + * Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved. + * Licensed under the Open Software License version 2.1 + * (See http://www.spikesource.com/license.html) + */ +?> +<?php + // Set to 'LOG_DEBUG' for maximum log output + // Note that the log file size will grow rapidly + // with LOG_DEBUG + $spc_config['log_level'] = 'LOG_NOTICE'; + //$spc_config['log_level'] = 'LOG_DEBUG'; + + // file extension to be treated as php files + // comma-separated list, no space + $spc_config['extensions'] = array('php', 'tpl', 'inc'); + + // temporary directory to save transient files + $spc_config['tmpdir'] = '/tmp'; + + // temporary directory on Windows machines + $spc_config['windows_tmpdir'] = 'C:/TMP'; +?> diff --git a/lib/spikephpcoverage/src/parser/BasicXmlParser.php b/lib/spikephpcoverage/src/parser/BasicXmlParser.php new file mode 100644 index 0000000000..a1106642e9 --- /dev/null +++ b/lib/spikephpcoverage/src/parser/BasicXmlParser.php @@ -0,0 +1,171 @@ +<?php +/* + * $Id$ + * + * Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved. + * Licensed under the Open Software License version 2.1 + * (See http://www.spikesource.com/license.html) + */ +?> +<?php + + require_once("XML/Parser.php"); + + if(!defined("ATTRIBUTES")) { + define("ATTRIBUTES", "__ATTRIBUTES__"); + } + + /** + * An XML parser that extends the functionality of PEAR XML_Parser + * module. + * + * @author Nimish Pachapurkar <npac@spikesource.com> + * @version $Revision$ + * @package SpikePHPCoverage_Parser + */ + class BasicXmlParser extends XML_Parser { + /*{{{ Members */ + + protected $openTags; + protected $docroot; + + /*}}}*/ + /*{{{ Constructor*/ + + /** + * Constructor + * @access public + */ + public function BasicXmlParser() { + parent::XML_Parser(); + } + + /*}}}*/ + /*{{{ public function handleAttrTag() */ + + /** + * Function that handles an element with attributes. + * + * @param $name Name of the element + * @param $attrs Attributes array (name, value pairs) + * @return Array An element + * @access public + */ + public function handleAttrTag($name, $attrs) { + $tag = array(); + foreach($attrs as $attr_name => $value) { + $tag[$attr_name] = $value; + } + return $tag; + } + + /*}}}*/ + /*{{{ public function startHandler() */ + + /** + * Function to handle start of an element + * + * @param $xp XMLParser handle + * @param $name Element name + * @param $attributes Attribute array + * @access public + */ + function startHandler($xp, $name, $attributes) { + $this->openTags[] = $name; + } + + /*}}}*/ + /*{{{ public function endHandler()*/ + + /** + * Function to handle end of an element + * + * @param $xp XML_Parser handle + * @param $name Name of the element + * @access public + */ + public function endHandler($xp, $name) { + // Handle error tags + $lastTag = $this->getLastOpenTag($name); + switch($name) { + case "MESSAGE": + if($lastTag == "ERROR") { + $this->docroot["ERROR"]["MESSAGE"] = $this->getCData(); + } + break; + } + // Empty CData + $this->lastCData = ""; + + // Close tag + if($this->openTags[count($this->openTags)-1] == $name) { + array_pop($this->openTags); + } + } + + /*}}}*/ + /*{{{ public function cdataHandler() */ + + /** + * Function to handle character data + * + * @param $xp XMLParser handle + * @param $cdata Character data + * @access public + */ + public function cdataHandler($xp, $cdata) { + $this->lastCData .= $cdata; + } + + /*}}}*/ + /*{{{ public function getCData() */ + + /** + * Returns the CData collected so far. + * + * @return String Character data collected. + * @access public + */ + public function getCData() { + return $this->lastCData; + } + + /*}}}*/ + /*{{{ public function getLastOpenTag() */ + + /** + * Returns the name of parent tag of give tag + * + * @param $tag Name of a child tag + * @return String Name of the parent tag of $tag + * @access public + */ + public function getLastOpenTag($tag) { + $lastTagIndex = count($this->openTags)-1; + if($this->openTags[$lastTagIndex] == $tag) { + if($lastTagIndex > 0) { + return $this->openTags[$lastTagIndex-1]; + } + } + return false; + } + + /*}}}*/ + /*{{{ public function getDocumentArray() */ + + /** + * Return the document array gathered during parsing. + * Document array is a data structure that mimics the XML + * contents. + * + * @return Array Document array + * @access public + */ + public function getDocumentArray() { + return $this->docroot; + } + + /*}}}*/ + } + +?> diff --git a/lib/spikephpcoverage/src/parser/CoverageXmlParser.php b/lib/spikephpcoverage/src/parser/CoverageXmlParser.php new file mode 100644 index 0000000000..c0cd0aabc9 --- /dev/null +++ b/lib/spikephpcoverage/src/parser/CoverageXmlParser.php @@ -0,0 +1,92 @@ +<?php + /* + * $Id$ + * + * Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved. + * Licensed under the Open Software License version 2.1 + * (See http://www.spikesource.com/license.html) + */ +?> +<?php + + require_once dirname(__FILE__) . "/BasicXmlParser.php"; + + /** + * Special parser for SpikePHPCoverage data parsing + * Expect input in following format: + * <spike-phpcoverage> + * <file path="/complete/file/path"> + * <line line-number="10" frequency="1"/> + * <line line-number="12" frequency="2"/> + * </file> + * <file path="/another/file/path"> + * ... + * </file> + * </spike-phpcoverage> + * + * @author Nimish Pachapurkar <npac@spikesource.com> + * @version $Revision$ + * @package SpikePHPCoverage_Parser + */ + class CoverageXmlParser extends BasicXmlParser { + /*{{{ Members */ + + protected $_data = array(); + protected $_lastFilePath; + + /*}}}*/ + /*{{{ public function startHandler() */ + + public function startHandler($xp, $name, $attrs) { + switch($name) { + case "FILE": + $fileAttributes = $this->handleAttrTag($name, $attrs); + $this->_lastFilePath = $fileAttributes["PATH"]; + if(!isset($this->_data[$this->_lastFilePath])) { + $this->_data[$this->_lastFilePath] = array(); + } + break; + + case "LINE": + $lineAttributes = $this->handleAttrTag($name, $attrs); + $lineNumber = (int)$lineAttributes["LINE-NUMBER"]; + if(!isset($this->_data[$this->_lastFilePath][$lineNumber])) { + $this->_data[$this->_lastFilePath][$lineNumber] = (int)$lineAttributes["FREQUENCY"]; + } + else { + $this->_data[$this->_lastFilePath][$lineNumber] += (int)$lineAttributes["FREQUENCY"]; + } + break; + } + } + + /*}}}*/ + /*{{{ public function getCoverageData() */ + + /** + * Returns coverage data array from the XML + * Format: + * Array + * ( + * [/php/src/remote/RemoteCoverageRecorder.php] => Array + * ( + * [220] => 1 + * [221] => 1 + * ) + * + * [/opt/oss/share/apache2/htdocs/web/sample.php] => Array + * ( + * [16] => 1 + * [18] => 1 + * ) + * ) + * + * @access public + */ + public function getCoverageData() { + return $this->_data; + } + + /*}}}*/ + } +?> diff --git a/lib/spikephpcoverage/src/parser/PHPParser.php b/lib/spikephpcoverage/src/parser/PHPParser.php new file mode 100644 index 0000000000..f5ce173b97 --- /dev/null +++ b/lib/spikephpcoverage/src/parser/PHPParser.php @@ -0,0 +1,476 @@ +<?php + /* + * $Id$ + * + * Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved. + * Licensed under the Open Software License version 2.1 + * (See http://www.spikesource.com/license.html) + */ +?> +<?php + + if(!defined("__PHPCOVERAGE_HOME")) { + define("__PHPCOVERAGE_HOME", dirname(dirname(__FILE__))); + } + require_once __PHPCOVERAGE_HOME . "/parser/Parser.php"; + + /** + * Parser for PHP files + * + * @author Nimish Pachapurkar (npac@spikesource.com) + * @version $Revision$ + * @package SpikePHPCoverage_Parser + */ + class PHPParser extends Parser { + /*{{{ Members */ + + private $inHereDoc = false; + private $inFunction = false; + private $phpStarters = array('<?php', '<?', '<?='); + private $phpFinisher = '?>'; + private $inComment = false; + private $lastLineEndTokenType = ""; + private $fileToken = null; + private $currFileToken = 0; + private $lineNb = 0; + private $multiLineTok = null; + // If one of these tokens occur as the last token of a line + // then the next line can be treated as a continuation line + // depending on how it starts. + public static $contTypes = array( + "(", + ",", + ".", + "=", + T_LOGICAL_XOR, + T_LOGICAL_AND, + T_LOGICAL_OR, + T_PLUS_EQUAL, + T_MINUS_EQUAL, + T_MUL_EQUAL, + T_DIV_EQUAL, + T_CONCAT_EQUAL, + T_MOD_EQUAL, + T_AND_EQUAL, + T_OR_EQUAL, + T_XOR_EQUAL, + T_BOOLEAN_AND, + T_BOOLEAN_OR, + T_OBJECT_OPERATOR, + T_DOUBLE_ARROW, + "[", + "]", + T_LOGICAL_OR, + T_LOGICAL_XOR, + T_LOGICAL_AND, + T_STRING + ); + /*}}}*/ + + /*{{{ protected function openFileReadOnly() */ + + /** + * Opens the file to be parsed in Read-only mode. + * Overriden to tokenize the whole file. + * + * @return FALSE on failure. + * @access protected + */ + protected function openFileReadOnly() { + $this->fileToken = @token_get_all(file_get_contents($this->filename)); + return parent::openFileReadOnly(); + } + + /*}}}*/ + /*{{{ protected function getNextToken() */ + + /** + * Gets the next token. + * + * The same token can be returned several times for multi-line + * tokens. + * + * @param $line The current line read from the file. + * @param &$pos The position of the next token string in the line. + * @return the next token or null at the end of the line. + * @access protected + */ + protected function getNextToken($line, &$pos) { + $lineLen = strlen($line); + if($pos >= $lineLen) { + return null; + } + if($this->multiLineTok != null) { + list($tok, $lnb, $posnl) = $this->multiLineTok; + if(is_string($tok)) { + $str = $tok; + } else { + $str = $tok[1]; + } + if($posnl >= strlen($str)) { + $this->multiLineTok = null; + } else { + if(substr($str, $posnl + 1, $lineLen) == $line) { + $pos += $lineLen; + $newPosnl = $posnl + $lineLen; + } else { + $newPosnl = strpos($str, "\n", $posnl + 1); + if($newPosnl === false) { + $newPosnl = strlen($str); + } + $len = $newPosnl - $posnl - 1; + //if(substr($str, $posnl + 1, $len) != substr($line, $pos, $len)) { + //} + $pos += $len; + } + $this->multiLineTok[1]++; + $this->multiLineTok[2] = $newPosnl; + return $tok; + } + } + if(!isset($this->fileToken[$this->currFileToken])) { + return null; + } + $tok = &$this->fileToken[$this->currFileToken]; + if(is_string($tok)) { + $str = $tok; + } else { + $str = $tok[1]; + } + $nbnl = substr_count($str, "\n"); + if($nbnl > 0 && ($posnl = strpos($str, "\n")) != strlen($str) - 1) { + $this->multiLineTok = array($tok, 1, $posnl); + $str = substr($str, 0, $posnl + 1); + } + if(substr($line, $pos, strlen($str)) == $str) { + $this->currFileToken++; + $pos += strlen($str); + return $tok; + } else { + return null; + } + } + + /*}}}*/ + /*{{{ protected function isMultiLineCont() */ + + /** + * Determines if the beginning of current line is a + * continuation of an executable multi-line token. + * + * Called at the first token of a line. + * + * @return Boolean true if it is a continuation line + * @access protected + */ + protected function isMultiLineCont() { + if($this->multiLineTok == null + || $this->multiLineTok[1] <= 1) { + return false; + } + switch ($this->getTokenType($this->multiLineTok[0])) { + case T_COMMENT: + case T_INLINE_HTML: // <br/><b>jhsk</b> + return false; + } + return true; + } + + /*}}}*/ + /*{{{ protected function processLine() */ + + /** + * Process a line read from the file and determine if it is an + * executable line or not. + * + * This is the work horse function that does most of the parsing. + * To parse PHP, get_all_tokens() tokenizer function is used. + * + * @param $line Line to be parsed. + * @access protected + */ + protected function processLine($line) { + + // Default values + $prevLineType = $this->lineType; + $this->lineType = LINE_TYPE_NOEXEC; + $tokenCnt = 0; + $this->lineNb++; + $pos = 0; + $seeMore = false; + $seenEnough = false; + $lastToken = null; + + while (($token = $this->getNextToken($line, $pos))) { + if (!is_string($token)) { + $stoken = token_name($token[0]) . ' "' + . str_replace(array("\\", "\"", "\n", "\r") + , array("\\\\", "\\\"", "\\n", "\\r") + , $token[1]) . '"'; + if (isset($token[2])) { + $stoken .= '[' . $token[2] . ']'; + if ($token[2] != $this->lineNb) { + $stoken .= ' != [' . $this->lineNb . ']'; + } + } + } else { + $stoken = $token; + } + $this->logger->debug("Token $stoken", __FILE__, __LINE__); + if (!is_string($token) && $token[0] == T_WHITESPACE) { + continue; + } + $lastToken = $token; + $tokenCnt ++; + if ($this->inHereDoc) { + $this->lineType = LINE_TYPE_CONT; + $this->logger->debug("Here doc Continuation! Token: $token", + __FILE__, __LINE__); + + if ($this->getTokenType($token) == T_END_HEREDOC) { + $this->inHereDoc = false; + } + continue; + } + if ($this->inFunction) { + $this->lineType = LINE_TYPE_NOEXEC; + $this->logger->debug("Function! Token: $token", + __FILE__, __LINE__); + if ($this->getTokenType($token) == '{') { + $this->inFunction = false; + } + continue; + } + + if($tokenCnt == 1 && $prevLineType != LINE_TYPE_NOEXEC + && ($this->isMultiLineCont() + || $this->isContinuation($token))) { + $this->lineType = LINE_TYPE_CONT; + $this->logger->debug("Continuation! Token: ".print_r($token, true), + __FILE__, __LINE__); + $seenEnough = true; + continue; + } + if ($seenEnough) { + continue; + } + if(is_string($token)) { + // FIXME: Add more cases, if needed + switch($token) { + // Any of these things, are non-executable. + // And so do not change the status of the line + case '{': + case '}': + case '(': + case ')': + case ';': + break; + + // Everything else by default is executable. + default: + $this->logger->debug("Other string: $token", + __FILE__, __LINE__); + if($this->lineType == LINE_TYPE_NOEXEC) { + $this->lineType = LINE_TYPE_EXEC; + } + break; + } + $this->logger->debug("Status: " . $this->getLineTypeStr($this->lineType) . "\t\tToken: $token", + __FILE__, __LINE__); + } + else { + // The token is an array + list($tokenType, $text) = $token; + switch($tokenType) { + case T_COMMENT: + case T_DOC_COMMENT: + case T_WHITESPACE: // white space + case T_OPEN_TAG: // < ? + case T_OPEN_TAG_WITH_ECHO: // < ? = + case T_CURLY_OPEN: // + case T_INLINE_HTML: // <br/><b>jhsk</b> + //case T_STRING: // + case T_EXTENDS: // extends + case T_STATIC: // static + case T_STRING_VARNAME: // string varname? + case T_CHARACTER: // character + case T_ELSE: // else + case T_CONSTANT_ENCAPSED_STRING: // "some str" + case T_ARRAY: // array + // Any of these things, are non-executable. + // And so do not change the status of the line + // as the line starts as non-executable. + break; + case T_START_HEREDOC: + $this->inHereDoc = true; + break; + case T_PRIVATE: // private + case T_PUBLIC: // public + case T_PROTECTED: // protected + case T_VAR: // var + case T_GLOBAL: // global + case T_CLASS: // class + case T_INTERFACE: // interface + case T_REQUIRE: // require + case T_REQUIRE_ONCE: // require_once + case T_INCLUDE: // include + case T_INCLUDE_ONCE: // include_once + case T_SWITCH: // switch + case T_CONST: // const + case T_TRY: // try + $this->lineType = LINE_TYPE_NOEXEC; + // No need to see any further + $seenEnough = true; + break; + case T_FUNCTION: // function + $this->lineType = LINE_TYPE_NOEXEC; + $this->inFunction = true; + // No need to see any further + $seenEnough = true; + break; + case T_VARIABLE: // $foo + $seeMore = true; + if($this->lineType == LINE_TYPE_NOEXEC) { + $this->lineType = LINE_TYPE_EXEC; + } + break; + case T_CLOSE_TAG: + if($this->lineType != LINE_TYPE_EXEC) { + $this->lineType = LINE_TYPE_NOEXEC; + } + break; + default: + $this->logger->debug("Other token: " . token_name($tokenType), + __FILE__, __LINE__); + $seeMore = false; + if($this->lineType == LINE_TYPE_NOEXEC) { + $this->lineType = LINE_TYPE_EXEC; + } + break; + } + $this->logger->debug("Status: " . $this->getLineTypeStr($this->lineType) . "\t\tToken type: $tokenType \tText: $text", + __FILE__, __LINE__); + } + if(($this->lineType == LINE_TYPE_EXEC && !$seeMore) + || $seenEnough) { + // start moodle modification: comment debug line causing notices + //$this->logger->debug("Made a decision! Exiting. Token Type: $tokenType & Text: $text", + // __FILE__, __LINE__); + // end moodle modification + if($seenEnough) { + $this->logger->debug("Seen enough at Token Type: $tokenType & Text: $text", + __FILE__, __LINE__); + } else { + $seenEnough = true; + } + } + } // end while + $this->logger->debug("Line Type: " . $this->getLineTypeStr($this->lineType), + __FILE__, __LINE__); + $this->lastLineEndTokenType = $this->getTokenType($lastToken); + $this->logger->debug("Last End Token: " . $this->lastLineEndTokenType, + __FILE__, __LINE__); + } + + /*}}}*/ + /*{{{ public function getLineType() */ + + /** + * Returns the type of line just read + * + * @return Line type + * @access public + */ + public function getLineType() { + return $this->lineType; + } + /*}}}*/ + /*{{{ protected function isContinuation() */ + + /** + * Check if a line is a continuation of the previous line + * + * @param &$token Second token in a line (after PHP start) + * @return Boolean True if the line is a continuation; false otherwise + * @access protected + */ + protected function isContinuation(&$token) { + if(is_string($token)) { + switch($token) { + case ".": + case ","; + case "]": + case "[": + case "(": + case ")": + case "=": + return true; + } + } + else { + list($tokenType, $text) = $token; + switch($tokenType) { + case T_CONSTANT_ENCAPSED_STRING: + case T_ARRAY: + case T_DOUBLE_ARROW: + case T_OBJECT_OPERATOR: + case T_LOGICAL_XOR: + case T_LOGICAL_AND: + case T_LOGICAL_OR: + case T_PLUS_EQUAL: + case T_MINUS_EQUAL: + case T_MUL_EQUAL: + case T_DIV_EQUAL: + case T_CONCAT_EQUAL: + case T_MOD_EQUAL: + case T_AND_EQUAL: + case T_OR_EQUAL: + case T_XOR_EQUAL: + case T_BOOLEAN_AND: + case T_BOOLEAN_OR: + case T_LNUMBER: + case T_DNUMBER: + return true; + + case T_STRING: + case T_VARIABLE: + return in_array($this->lastLineEndTokenType, PHPParser::$contTypes); + } + } + + return false; + } + /*}}}*/ + /*{{{ protected function getTokenType() */ + + /** + * Get the token type of a token (if exists) or + * the token itself. + * + * @param $token Token + * @return Token type or token itself + * @access protected + */ + protected function getTokenType($token) { + if(is_string($token)) { + return $token; + } + else { + list($tokenType, $text) = $token; + return $tokenType; + } + } + /*}}}*/ + /* + // Main + $obj = new PHPParser(); + $obj->parse("test.php"); + while(($line = $obj->getLine()) !== false) { + echo "#########################\n"; + echo "[" . $line . "] Type: [" . $obj->getLineTypeStr($obj->getLineType()) . "]\n"; + echo "#########################\n"; + } + */ + + } +?> diff --git a/lib/spikephpcoverage/src/parser/Parser.php b/lib/spikephpcoverage/src/parser/Parser.php new file mode 100644 index 0000000000..2456132e00 --- /dev/null +++ b/lib/spikephpcoverage/src/parser/Parser.php @@ -0,0 +1,217 @@ +<?php + /* + * $Id$ + * + * Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved. + * Licensed under the Open Software License version 2.1 + * (See http://www.spikesource.com/license.html) + */ +?> +<?php + + if(!defined("__PHPCOVERAGE_HOME")) { + define("__PHPCOVERAGE_HOME", dirname(dirname(__FILE__))); + } + require_once __PHPCOVERAGE_HOME . "/conf/phpcoverage.conf.php"; + require_once __PHPCOVERAGE_HOME . "/util/Utility.php"; + + + /** + * Base class for Parsers. + * + * @author Nimish Pachapurkar (npac@spikesource.com) + * @version $Revision$ + * @package SpikePHPCoverage_Parser + */ + + define("LINE_TYPE_UNKNOWN", "0"); + define("LINE_TYPE_EXEC", "1"); + define("LINE_TYPE_NOEXEC", "2"); + define("LINE_TYPE_CONT", "3"); + + abstract class Parser { + /*{{{ Members */ + + protected $totalLines; + protected $coveredLines; + protected $uncoveredLines; + protected $fileRef; + protected $filename; + + protected $line; + protected $logger; + + /*}}}*/ + /*{{{ public function __construct() */ + /** + * Constructor + * @access public + */ + public function __construct() { + global $util; + $this->totalLines = 0; + $this->coveredLines = 0; + $this->uncoveredLines = 0; + + $this->fileRef = false; + $this->line = false; + $this->lineType = false; + + $this->logger = $util->getLogger(); + } + + /*}}}*/ + /*{{{ public abstract function parse() */ + + /** + * Parse a given file + * + * @param $filename Full path of the file + * @return FALSE on error. + * @access public + */ + public function parse($filename) { + $this->filename = $filename; + $ret = $this->openFileReadOnly(); + if(!$ret) { + die("Error: Cannot open file: $this->filename \n"); + } + } + + /*}}}*/ + /*{{{ protected abstract function processLine() */ + + /** + * Process the line and classify it into either + * covered and uncovered. + * + * @param $line + * @return + * @access protected + */ + protected abstract function processLine($line); + + /*}}}*/ + /*{{{ public function getLine() */ + + /** + * Returns the next line from file. + * + * @return Next line from file + * @access public + */ + public function getLine() { + if(!feof($this->fileRef)) { + $this->line = fgets($this->fileRef); + $this->processLine($this->line); + } + else { + fclose($this->fileRef); + $this->line = false; + } + return $this->line; + } + + /*}}}*/ + /*{{{ public abstract function getLineType() */ + + /** + * Returns the type of last line read. + * + * The type can be either + * * LINE_TYPE_EXEC Line that can be executed. + * * LINE_TYPE_NOEXEC Line that cannot be executed. + * This includes the variable and function definitions + * (without initialization), blank lines, non-PHP lines, + * etc. + * + * @return Type of last line + * @access public + */ + public abstract function getLineType(); + + /*}}}*/ + /*{{{ public function getLineTypeStr() */ + + /** + * Returns the string representation of LINE_TYPE + * + * @param $lineType + * @return Type of line + * @access public + */ + public function getLineTypeStr($lineType) { + if($lineType == LINE_TYPE_EXEC) { + return "LINE_TYPE_EXEC"; + } + else if($lineType == LINE_TYPE_NOEXEC) { + return "LINE_TYPE_NOEXEC"; + } + else if($lineType == LINE_TYPE_CONT) { + return "LINE_TYPE_CONT"; + } + else { + return "LINE_TYPE_UNKNOWN"; + } + } + + /*}}}*/ + /*{{{ protected function openFileReadOnly() */ + + /** + * Opens the file to be parsed in Read-only mode + * + * @return FALSE on failure. + * @access protected + */ + protected function openFileReadOnly() { + $this->fileRef = fopen($this->filename, "r"); + return $this->fileRef !== false; + } + + /*}}}*/ + /*{{{ public function getTotalLines() */ + + /** + * Returns the total lines (PHP, non-PHP) from a file + * + * @return Number of lines + * @access public + */ + public function getTotalLines() { + return $this->totalLines; + } + + /*}}}*/ + /*{{{ public function getCoveredLines() */ + + /** + * Returns the number of covered PHP lines + * + * @return Number of covered lines + * @access public + */ + public function getCoveredLines() { + return $this->coveredLines; + } + + /*}}}*/ + /*{{{ public function getUncoveredLines() */ + + /** + * Returns the number of uncovered PHP lines + * + * Note that the sum of covered and uncovered + * lines may not be equal to total lines. + * + * @return Number of uncovered lines + * @access public + */ + public function getUncoveredLines() { + return $this->uncoveredLines; + } + + /*}}}*/ + } + +?> diff --git a/lib/spikephpcoverage/src/phpcoverage.inc.php b/lib/spikephpcoverage/src/phpcoverage.inc.php new file mode 100644 index 0000000000..367877c1a6 --- /dev/null +++ b/lib/spikephpcoverage/src/phpcoverage.inc.php @@ -0,0 +1,56 @@ +<?php + /* + * $Id$ + * + * Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved. + * Licensed under the Open Software License version 2.1 + * (See http://www.spikesource.com/license.html) + */ +?> +<?php + global $PHPCOVERAGE_REPORT_DIR; + global $PHPCOVERAGE_HOME; + global $PHPCOVERAGE_APPBASE_PATH; + + $basedir = dirname(__FILE__); + for($ii=1; $ii < $argc; $ii++) { + if(strpos($argv[$ii], "PHPCOVERAGE_REPORT_DIR=") !== false) { + parse_str($argv[$ii]); + } + else if(strpos($argv[$ii], "PHPCOVERAGE_HOME=") !== false) { + parse_str($argv[$ii]); + } + else if(strpos($argv[$ii], "PHPCOVERAGE_APPBASE_PATH=") !== false) { + parse_str($argv[$ii]); + } + } + + if(empty($PHPCOVERAGE_HOME)) { + $envvar = getenv("PHPCOVERAGE_HOME"); + if(empty($envvar)) { + $share_home = getenv("LOCAL_CACHE"); + $PHPCOVERAGE_HOME = $share_home . "/common/spikephpcoverage/src/"; + } + else { + $PHPCOVERAGE_HOME = $envvar; + } + } + + if(empty($PHPCOVERAGE_HOME) || !is_dir($PHPCOVERAGE_HOME)) { + $msg = "ERROR: Could not locate PHPCOVERAGE_HOME [$PHPCOVERAGE_HOME]. "; + $msg .= "Use 'php <filename> PHPCOVERAGE_HOME=/path/to/coverage/home'\n"; + die($msg); + } + + // Fallback + if(!defined("PHPCOVERAGE_HOME")) { + $include_path = get_include_path(); + set_include_path($PHPCOVERAGE_HOME. PATH_SEPARATOR . $include_path); + define('PHPCOVERAGE_HOME', $PHPCOVERAGE_HOME); + } + + error_log("[phpcoverage.inc.php] PHPCOVERAGE_HOME=" . $PHPCOVERAGE_HOME); + error_log("[phpcoverage.inc.php] PHPCOVERAGE_REPORT_DIR=" . $PHPCOVERAGE_REPORT_DIR); + error_log("[phpcoverage.inc.php] PHPCOVERAGE_APPBASE_PATH=" . $PHPCOVERAGE_APPBASE_PATH); + +?> diff --git a/lib/spikephpcoverage/src/phpcoverage.remote.bottom.inc.php b/lib/spikephpcoverage/src/phpcoverage.remote.bottom.inc.php new file mode 100644 index 0000000000..6b45037c34 --- /dev/null +++ b/lib/spikephpcoverage/src/phpcoverage.remote.bottom.inc.php @@ -0,0 +1,51 @@ +<?php +/* + * $Id$ + * + * Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved. + * Licensed under the Open Software License version 2.1 + * (See http://www.spikesource.com/license.html) +*/ +?> +<?php + + if(isset($_REQUEST)) { + global $spc_config, $util; + $logger = $util->getLogger(); + + // Create a distinct hash (may or may not be unique) + $session_id = md5($_SERVER["REMOTE_ADDR"] . $_SERVER["SERVER_NAME"]); + $tmpFile = $util->getTmpDir() . "/phpcoverage.session." . $session_id; + $logger->info("[phpcoverage.remote.bottom.inc.php] Session id: " . $session_id, + __FILE__, __LINE__); + + if(!isset($cov)) { + if(file_exists($tmpFile)) { + $object = file_get_contents($tmpFile); + $cov = unserialize($object); + $logger->info("[phpcoverage.remote.bottom.inc.php] Coverage object found: " . $cov, __FILE__, __LINE__); + } + } + + if(isset($cov)) { + // PHPCoverage bottom half + if(!isset($called_script)) { + $called_script = ""; + } + $logger->info("[phpcoverage.remote.bottom.inc.php] END: " . $called_script, + __FILE__, __LINE__); + // Save the code coverage + $cov->saveCoverageXml(); + $logger->info("[phpcoverage.remote.bottom.inc.php] Saved coverage xml", + __FILE__, __LINE__); + $cov->startInstrumentation(); + $logger->info("[phpcoverage.remote.bottom.inc.php] Instrumentation turned on.", + __FILE__, __LINE__); + $object = serialize($cov); + file_put_contents($tmpFile, $object); + $logger->info("[phpcoverage.remote.bottom.inc.php] ################## END ###################", + __FILE__, __LINE__); + } + } + +?> diff --git a/lib/spikephpcoverage/src/phpcoverage.remote.top.inc.php b/lib/spikephpcoverage/src/phpcoverage.remote.top.inc.php new file mode 100644 index 0000000000..4a2617647f --- /dev/null +++ b/lib/spikephpcoverage/src/phpcoverage.remote.top.inc.php @@ -0,0 +1,151 @@ +<?php +/* + * $Id$ + * + * Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved. + * Licensed under the Open Software License version 2.1 + * (See http://www.spikesource.com/license.html) + */ +?> +<?php + if(isset($_REQUEST)){ + $debug = false; + // Uncomment the line below to permanently turn on debugging. + // Alternatively, export a variable called phpcoverage-debug before + // starting the web server. + $debug = true; + if(isset($_REQUEST["phpcoverage-debug"]) || + isset($_SERVER["phpcoverage-debug"]) || + isset($_ENV["phpcoverage-debug"])) { + $debug = true; + } + if($debug) error_log("[phpcoverage.remote.top.inc.php] ################## START ###################"); + + $PHPCOVERAGE_HOME = false; + global $PHPCOVERAGE_HOME; + + $basedir = dirname(__FILE__); + $this_script = basename(__FILE__); + $called_script = basename($_SERVER["SCRIPT_FILENAME"]); + + if(!empty($_REQUEST["PHPCOVERAGE_HOME"])) { + $PHPCOVERAGE_HOME = $_REQUEST["PHPCOVERAGE_HOME"]; + } + if(empty($PHPCOVERAGE_HOME)) { + $env_var = getenv("PHPCOVERAGE_HOME"); + if(empty($env_var)) { + $msg = "Could not find PHPCOVERAGE_HOME. Please either export it in your environment before starting the web server. Or include PHPCOVERAGE_HOME=<path> in your HTTP request."; + error_log("[phpcoverage.remote.top.inc.php] FATAL: " . $msg); + die($msg); + } + else { + $PHPCOVERAGE_HOME = $env_var; + } + } + + if(empty($PHPCOVERAGE_HOME) || !is_dir($PHPCOVERAGE_HOME)) { + $msg = "ERROR: Could not locate PHPCOVERAGE_HOME [$PHPCOVERAGE_HOME]. "; + $msg .= "Use 'php <filename> PHPCOVERAGE_HOME=/path/to/coverage/home'\n"; + die($msg); + } + + + // Fallback + if(!defined("PHPCOVERAGE_HOME")) { + $include_path = get_include_path(); + set_include_path($PHPCOVERAGE_HOME. ":" . $include_path); + define('PHPCOVERAGE_HOME', $PHPCOVERAGE_HOME); + } + + if($debug) error_log("[phpcoverage.remote.top.inc.php] PHPCOVERAGE_HOME=" . $PHPCOVERAGE_HOME); + + // Register the shutdown function to get code coverage results before + // script exits abnormally. + register_shutdown_function('spikephpcoverage_before_shutdown'); + require_once PHPCOVERAGE_HOME . "/conf/phpcoverage.conf.php"; + require_once PHPCOVERAGE_HOME . "/util/Utility.php"; + require_once PHPCOVERAGE_HOME . "/remote/RemoteCoverageRecorder.php"; + require_once PHPCOVERAGE_HOME . "/reporter/HtmlCoverageReporter.php"; + + global $util; + $logger = $util->getLogger(); + + // Create a distinct hash (may or may not be unique) + $session_id = md5($_SERVER["REMOTE_ADDR"] . $_SERVER["SERVER_NAME"]); + $tmpFile = $util->getTmpDir() . "/phpcoverage.session." . $session_id; + $logger->info("[phpcoverage.remote.top.inc.php] Session id: " . $session_id . " Saved in: " . $tmpFile, + __FILE__, __LINE__); + if(file_exists($tmpFile)) { + $object = file_get_contents($tmpFile); + $cov = unserialize($object); + $logger->info("[phpcoverage.remote.top.inc.php] Coverage object found." , + __FILE__, __LINE__); + } + else { + $covReporter = new HtmlCoverageReporter( + "PHPCoverage report", + "", + $util->getTmpDir() . "/php-coverage-report" + ); + $cov = new RemoteCoverageRecorder(array(), array(), $covReporter); + $object = serialize($cov); + file_put_contents($tmpFile, $object); + $logger->info("[phpcoverage.remote.top.inc.php] Stored coverage object found", + __FILE__, __LINE__); + } + + if(!empty($_REQUEST["phpcoverage-action"])) { + $logger->info("[phpcoverage.remote.top.inc.php] phpcoverage-action=" . strtolower($_REQUEST["phpcoverage-action"]), + __FILE__, __LINE__); + switch(strtolower($_REQUEST["phpcoverage-action"])) { + case "init": + if(!empty($_REQUEST["tmp-dir"])) { + $cov->setTmpDir($_REQUEST["tmp-dir"]); + } + $cov->setCoverageFileName($_REQUEST["cov-file-name"]); + if(!$cov->cleanCoverageFile()) { + die("Cannot delete existing coverage data."); + } + break; + + case "instrument": + break; + + case "get-coverage-xml": + $cov->getCoverageXml(); + break; + + case "cleanup": + if(file_exists($tmpFile) && is_writable($tmpFile)) { + unlink($tmpFile); + unset($cov); + $logger->info("[phpcoverage.remote.top.inc.php] Cleaned up!", + __FILE__, __LINE__); + return; + } + else { + $logger->error("[phpcoverage.remote.top.inc.php] Error deleting file: " . $tmpFile, + __FILE__, __LINE__); + } + break; + } + } + + $cov->startInstrumentation(); + $logger->info("[phpcoverage.remote.top.inc.php] Instrumentation turned on.", + __FILE__, __LINE__); + $object = serialize($cov); + file_put_contents($tmpFile, $object); + $logger->info("[phpcoverage.remote.top.inc.php] BEGIN: " . $called_script, + __FILE__, __LINE__); + } + + function spikephpcoverage_before_shutdown() { + global $cov, $logger; + $logger->debug("[phpcoverage.remote.top.inc.php::before_shutdown()] Getting code coverage before shutdown: START", + __FILE__, __LINE__); + require dirname(__FILE__) . "/phpcoverage.remote.bottom.inc.php"; + $logger->debug("[phpcoverage.remote.top.inc.php::before_shutdown()] Getting code coverage before shutdown: FINISH", + __FILE__, __LINE__); + } +?> diff --git a/lib/spikephpcoverage/src/remote/RemoteCoverageRecorder.php b/lib/spikephpcoverage/src/remote/RemoteCoverageRecorder.php new file mode 100644 index 0000000000..023976ab9f --- /dev/null +++ b/lib/spikephpcoverage/src/remote/RemoteCoverageRecorder.php @@ -0,0 +1,309 @@ +<?php + /* + * $Id$ + * + * Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved. + * Licensed under the Open Software License version 2.1 + * (See http://www.spikesource.com/license.html) + */ +?> +<?php + + if(!defined("__PHPCOVERAGE_HOME")) { + define("__PHPCOVERAGE_HOME", dirname(dirname(__FILE__))); + } + require_once __PHPCOVERAGE_HOME . "/util/Utility.php"; + require_once __PHPCOVERAGE_HOME . "/CoverageRecorder.php"; + require_once __PHPCOVERAGE_HOME . "/remote/XdebugTraceReader.php"; + require_once __PHPCOVERAGE_HOME . "/parser/CoverageXmlParser.php"; + + /** + * A Coverage recorder extension for remote Coverage measurement. + * + * @author Nimish Pachapurkar <npac@spikesource.com> + * @version $Revision$ + * @package SpikePHPCoverage_Remote + */ + class RemoteCoverageRecorder extends CoverageRecorder { + /*{{{ Members */ + + protected $traceFilePath; + protected $xdebugTraceReader; + protected $tmpDir; + protected $tmpTraceFilename = "phpcoverage.xdebug.trace"; + protected $coverageFileName = "phpcoverage.coverage.xml"; + + protected $xmlStart = "<?xml version=\"1.0\" encoding=\"utf-8\" ?><spike-phpcoverage>"; + protected $xmlEnd = "</spike-phpcoverage>"; + + /*}}}*/ + /*{{{ public function __construct() */ + + /** + * Constructor + * + * @access public + */ + public function __construct( + $includePaths=array("."), + $excludePaths=array(), + $reporter="new HtmlCoverageReporter()" + ) { + global $util; + parent::__construct($includePaths, $excludePaths, $reporter); + $this->isRemote = true; + $this->phpCoverageFiles[] = "phpcoverage.remote.inc.php"; + $this->phpCoverageFiles[] = "phpcoverage.remote.top.inc.php"; + $this->phpCoverageFiles[] = "phpcoverage.remote.bottom.inc.php"; + + // configuration + $this->tmpDir = $util->getTmpDir(); + } + + /*}}}*/ + /*{{{ Getters and Setters */ + + public function getTraceFilePath() { + return $this->traceFilePath; + } + + public function setTraceFilePath($traceFilePath) { + $this->traceFilePath = $traceFilePath; + } + + public function getTmpDir() { + return $this->tmpDir; + } + + public function setTmpDir($tmpTraceDir) { + $this->tmpDir = $tmpTraceDir; + } + + public function getCoverageFileName() { + return $this->coverageFileName; + } + + public function setCoverageFileName($covFileName) { + $this->coverageFileName = $covFileName; + } + + /*}}}*/ + /*{{{ public function cleanCoverageFile() */ + + /** + * Deletes a coverage data file if one exists. + * + * @return Boolean True on success, False on failure. + * @access public + */ + public function cleanCoverageFile() { + $filepath = $this->tmpDir . "/" . $this->coverageFileName; + if(file_exists($filepath)) { + if(is_writable($filepath)) { + unlink($filepath); + } + else { + $this->logger->error("[RemoteCoverageRecorder::cleanCoverageFile()] " + . "ERROR: Cannot delete $filepath.", __FILE__, __LINE__); + return false; + } + } + return true; + } + + /*}}}*/ + /*{{{ protected function prepareCoverageXml() */ + + /** + * Convert the Coverage data into an XML. + * + * @return String XML generated from Coverage data + * @access protected + */ + protected function prepareCoverageXml() { + global $util; + $xmlString = ""; + $xmlBody = ""; + if(!empty($this->coverageData)) { + foreach($this->coverageData as $file => &$lines) { + $xmlBody .= "<file path=\"". $util->replaceBackslashes($file) . "\">"; + foreach($lines as $linenum => &$frequency) { + $xmlBody .= "<line line-number=\"" . $linenum . "\""; + $xmlBody .= " frequency=\"" . $frequency . "\"/>"; + } + $xmlBody .= "</file>\n"; + } + unset($this->coverageData); + } + else { + $this->logger->info("[RemoteCoverageRecorder::prepareCoverageXml()] Coverage data is empty.", + __FILE__, __LINE__); + } + $xmlString .= $xmlBody; + $this->logger->debug("[RemoteCoverageRecorder::prepareCoverageXml()] Xml: " . $xmlString, __FILE__, __LINE__); + return $xmlString; + } + + /*}}}*/ + /*{{{ protected function parseCoverageXml() */ + + /** + * Parse coverage XML to regenerate the Coverage data array. + * + * @param $xml XML String or URL of the coverage data + * @param $stream=false Is the input a stream? + * @return + * @access protected + */ + protected function parseCoverageXml(&$xml, $stream=false) { + // Need to handle multiple xml files. + if(!is_array($xml)) { + $xml = array($xml); + } + for($i = 0; $i < count($xml); $i++) { + $xmlParser = new CoverageXmlParser(); + if($stream) { + $xmlParser->setInput($xml[$i]); + } + else { + $xmlParser->setInputString($xml[$i]); + } + $xmlParser->parse(); + $data =& $xmlParser->getCoverageData(); + if(empty($this->coverageData)) { + $this->coverageData = $data; + } + else { + $data2 = array_merge_recursive($this->coverageData, $data); + $this->coverageData = $data2; + } + $this->logger->debug("[RemoteCoverageRecorder::prepareCoverageXml()] " . "Coverage data intermediate: " . print_r($this->coverageData, true)); + } + } + + /*}}}*/ + /*{{{ public function getCoverageXml() */ + + /** + * Dumps the coverage data in XML format + * + * @access public + */ + public function getCoverageXml() { + $filepath = $this->tmpDir . "/" . $this->coverageFileName; + if(file_exists($filepath) && is_readable($filepath)) { + $fp = fopen($filepath, "r"); + if($fp) { + while(!feof($fp)) { + $xml = fread($fp, 4096); + echo $xml; + } + fclose($fp); + return true; + } + else { + $this->logger->error("Could not read coverage data file.", + __FILE__, __LINE__); + } + } + else { + $this->logger->error("[RemoteCoverageRecorder::getCoverageXml()] " + . "ERROR: Cannot read file " . $filepath, __FILE__, __LINE__); + } + return false; + } + + /*}}} */ + /*{{{ protected function appendDataToFile() */ + + /** + * Append coverage data to xml file + * + * @param $newXml New xml recorded + * @return True on success; false otherwise + * @access protected + */ + protected function appendDataToFile($newXml) { + $filepath = $this->tmpDir . "/" . $this->coverageFileName; + if(!file_exists($filepath)) { + // If new file, write the xml start and end tags + $bytes = file_put_contents($filepath, $this->xmlStart . "\n" . $this->xmlEnd); + if(!$bytes) { + $this->logger->critical("[RemoteCoverageRecorder::appendDataToFile()] Could not create file: " . $filepath, __FILE__, __LINE__); + return false; + } + } + if(file_exists($filepath) && is_readable($filepath)) { + $res = fopen($filepath, "r+"); + if($res) { + fseek($res, -1 * strlen($this->xmlEnd), SEEK_END); + $ret = fwrite($res, $newXml); + if(!$ret) { + $this->logger->error("[RemoteCoverageRecorder::appendDataToFile()] Could not append data to file.", + __FILE__, __LINE__); + fclose($res); + return false; + } + fwrite($res, $this->xmlEnd); + fclose($res); + } + else { + $this->logger->error("[RemoteCoverageRecorder::appendDataToFile()] Error opening file for writing: " . $filepath, + __FILE__, __LINE__); + return false; + } + } + return true; + } + + /*}}}*/ + /*{{{ public function saveCoverageXml() */ + + /** + * Append coverage xml to a xml data file. + * + * @return Boolean True on success, False on error + * @access public + */ + public function saveCoverageXml() { + $filepath = $this->tmpDir . "/" . $this->coverageFileName; + if($this->stopInstrumentation()) { + $xml = $this->prepareCoverageXml(); + $ret = $this->appendDataToFile($xml); + if(!$ret) { + $this->logger->warn("[RemoteCoverageRecorder::saveCoverageXml()] " + . "ERROR: Nothing was written to " . $filepath, + __FILE__, __LINE__); + return false; + } + $this->logger->info("[RemoteCoverageRecorder::saveCoverageXml()] " + . "Saved XML to $filepath; size: [" . filesize($filepath) + . "]", __FILE__, __LINE__); + return true; + } + return false; + } + + /*}}}*/ + /*{{{ public function generateReport() */ + + /** + * Generate report from the xml coverage data + * The preferred method for usage of this function is + * passing a stream of the XML data in. This is much more + * efficient and consumes less memory. + * + * @param $xmlUrl Url where XML data is available or string + * @param $stream=false Is the xml available as stream? + * @access public + */ + public function generateReport($xmlUrl, $stream=false) { + $this->logger->debug("XML Url: " . $xmlUrl, __FILE__, __LINE__); + $this->parseCoverageXml($xmlUrl, true); + $this->logger->debug("Coverage Data final: " . print_r($this->coverageData, true)); + parent::generateReport(); + } + + /*}}}*/ + } +?> diff --git a/lib/spikephpcoverage/src/remote/XdebugTraceReader.php b/lib/spikephpcoverage/src/remote/XdebugTraceReader.php new file mode 100644 index 0000000000..b80d9bfa6c --- /dev/null +++ b/lib/spikephpcoverage/src/remote/XdebugTraceReader.php @@ -0,0 +1,125 @@ +<?php + /* + * $Id$ + * + * Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved. + * Licensed under the Open Software License version 2.1 + * (See http://www.spikesource.com/license.html) + */ +?> +<?php + + /** + * Reader that parses Xdebug Trace data. + * + * @author Nimish Pachapurkar <npac@spikesource.com> + * @version $Revision$ + * @package SpikePHPCoverage_Parser + */ + class XdebugTraceReader { + /*{{{ Members */ + + protected $traceFilePath; + protected $handle; + protected $coverage = array(); + + /*}}}*/ + /*{{{ Constructor */ + + /** + * Constructor + * + * @param $traceFilePath Path of the Xdebug trace file + * @access public + */ + public function __construct($traceFilePath) { + $this->traceFilePath = $traceFilePath; + } + + /*}}}*/ + /*{{{ protected function openTraceFile() */ + + /** + * Opens the trace file + * + * @return Boolean True on success, false on failure. + * @access protected + */ + protected function openTraceFile() { + $this->handle = fopen($this->traceFilePath, "r"); + return !empty($this->handle); + } + + /*}}}*/ + /*{{{ public function parseTraceFile() */ + + /** + * Parses the trace file + * + * @return Boolean True on success, false on failure. + * @access public + */ + public function parseTraceFile() { + if(!$this->openTraceFile()) { + error_log("[XdebugTraceReader::parseTraceFile()] Unable to read trace file."); + return false; + } + while(!feof($this->handle)) { + $line = fgets($this->handle); + // echo "Line: " . $line . "\n"; + $this->processTraceLine($line); + } + fclose($this->handle); + return true; + } + + /*}}}*/ + /*{{{ protected function processTraceLine() */ + + /** + * Process a give trace line + * + * @param $line Line from a trace file + * @return Boolean True on success, false on failure + * @access protected + */ + protected function processTraceLine($line) { + $dataparts = explode("\t", $line); + // print_r($dataparts); + $cnt = count($dataparts); + if($cnt < 2) { + return false; + } + if(!file_exists($dataparts[$cnt-2])) { + // echo "No file: " . $dataparts[$cnt-2] . "\n"; + return false; + } + // Trim the entries + $dataparts[$cnt-2] = trim($dataparts[$cnt-2]); + $dataparts[$cnt-1] = trim($dataparts[$cnt-1]); + + if(!isset($this->coverage[$dataparts[$cnt-2]][$dataparts[$cnt-1]])) { + $this->coverage[$dataparts[$cnt-2]][$dataparts[$cnt-1]] = 1; + } + else { + $this->coverage[$dataparts[$cnt-2]][$dataparts[$cnt-1]] ++; + } + return true; + } + + /*}}}*/ + /*{{{ public function getCoverageData() */ + + /** + * Returns the coverage array + * + * @return Array Array of coverage data from parsing. + * @access public + */ + public function getCoverageData() { + return $this->coverage; + } + + /*}}}*/ + } +?> diff --git a/lib/spikephpcoverage/src/reporter/CoverageReporter.php b/lib/spikephpcoverage/src/reporter/CoverageReporter.php new file mode 100644 index 0000000000..316f122896 --- /dev/null +++ b/lib/spikephpcoverage/src/reporter/CoverageReporter.php @@ -0,0 +1,302 @@ +<?php + /* + * $Id$ + * + * Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved. + * Licensed under the Open Software License version 2.1 + * (See http://www.spikesource.com/license.html) + */ +?> +<?php + + if(!defined("__PHPCOVERAGE_HOME")) { + define("__PHPCOVERAGE_HOME", dirname(dirname(__FILE__))); + } + require_once __PHPCOVERAGE_HOME . "/conf/phpcoverage.conf.php"; + require_once __PHPCOVERAGE_HOME . "/util/Utility.php"; + + /*{{{ Defines */ + + define("TOTAL_FILES_EXPLAIN", "count of included source code files"); + define("TOTAL_LINES_EXPLAIN", "includes comments and whitespaces"); + define("TOTAL_COVERED_LINES_EXPLAIN", "lines of code that were executed"); + define("TOTAL_UNCOVERED_LINES_EXPLAIN", "lines of executable code that were not executed"); + define ("TOTAL_LINES_OF_CODE_EXPLAIN", "lines of executable code"); + + /*}}}*/ + + /** + * The base class for reporting coverage. This is an abstract as it does not + * implement the generateReport() function. Every concrete subclass must + * implement this method to generate a report. + * + * @author Nimish Pachapurkar <npac@spikesource.com> + * @version $Revision$ + * @package SpikePHPCoverage_Reporter + */ + abstract class CoverageReporter { + // {{{ Members + + protected $logger; + + // Report heading - will be displayed as the title of the main page. + protected $heading; + // CSS file path to be used. + protected $style; + // Directory where the report file(s) are written. + protected $outputDir; + + // Total number of lines in all the source files. + protected $grandTotalLines; + // Total number of lines covered in code coverage measurement. + protected $grandTotalCoveredLines; + // Total number of executable code lines that were left untouched. + protected $grandTotalUncoveredLines; + // Total number of files included + protected $grandTotalFiles; + protected $fileCoverage = array(); + protected $recorder = false; + + // }}} + /*{{{ public function __construct()*/ + + /** + * The constructor (PHP5 compatible) + * + * @param $heading + * @param $style + * @param $dir + * @access public + */ + public function __construct( + $heading="Coverage Report", + $style="", + $dir="report" + ) { + + global $util; + $this->heading = $heading; + $this->style = $style; + $this->outputDir = $util->replaceBackslashes($dir); + // Create the directory if not there + $this->createReportDir(); + $this->grandTotalFiles = 0; + $this->grandTotalLines = 0; + $this->grandTotalCoveredLines = 0; + $this->grandTotalUncoveredLines = 0; + + // Configure + $this->logger = $util->getLogger(); + } + + /*}}}*/ + /*{{{ protected function createReportDir() */ + + /** + * Create the report directory if it does not exists + * + * @access protected + */ + protected function createReportDir() { + global $util; + if(!file_exists($this->outputDir)) { + $util->makeDirRecursive($this->outputDir, 0755); + } + if(file_exists($this->outputDir)) { + $this->outputDir = $util->replaceBackslashes(realpath($this->outputDir)); + } + } + + /*}}}*/ + /*{{{ protected function updateGrandTotals() */ + + /** + * Update the grand totals + * + * @param &$coverageCounts Coverage counts for a file + * @access protected + */ + protected function updateGrandTotals(&$coverageCounts) { + $this->grandTotalLines += $coverageCounts['total']; + $this->grandTotalCoveredLines += $coverageCounts['covered']; + $this->grandTotalUncoveredLines += $coverageCounts['uncovered']; + + $this->recordFileCoverageInfo($coverageCounts); + } + + /*}}}*/ + /*{{{ public function getGrandCodeCoveragePercentage()*/ + + /** + * Returns Overall Code Coverage percentage + * + * @return double Code Coverage percentage rounded to two decimals + * @access public + */ + public function getGrandCodeCoveragePercentage() { + if($this->grandTotalCoveredLines+$this->grandTotalUncoveredLines == 0) { + return round(0, 2); + } + return round(((double)$this->grandTotalCoveredLines/((double)$this->grandTotalCoveredLines + (double)$this->grandTotalUncoveredLines)) * 100.0, 2); + } + + /*}}}*/ + /*{{{ public function getFileCoverageInfo() */ + + /** + * Return the array containing file coverage information. + * + * The array returned contains following fields + * * filename: Name of the file + * * total: Total number of lines in that file + * * covered: Total number of executed lines in that file + * * uncovered: Total number of executable lines that were not executed. + * + * @return array Array of file coverage information + * @access public + */ + public function getFileCoverageInfo() { + return $this->fileCoverage; + } + + /*}}}*/ + /*{{{ public function recordFileCoverageInfo() */ + + /** + * Record the file coverage information for a file. + * + * @param &$fileCoverage Coverage information for a file + * @access protected + */ + protected function recordFileCoverageInfo(&$fileCoverage) { + $this->fileCoverage[] = $fileCoverage; + } + + /*}}}*/ + /*{{{ public function printTextSummary() */ + + /** + * Print the coverage summary to filename (if specified) or stderr + * + * @param $filename=false Filename to write the log to + * @access public + */ + public function printTextSummary($filename=false) { + global $util; + $str = "\n"; + $str .= "##############################################\n"; + $str .= " Code Coverage Summary: " . $this->heading . "\n"; + $str .= " Total Files: " . $this->grandTotalFiles . "\n"; + $str .= " Total Lines: " . $this->grandTotalLines . "\n"; + $str .= " Total Covered Lines of Code: " . $this->grandTotalCoveredLines . "\n"; + $str .= " Total Missed Lines of Code: " . $this->grandTotalUncoveredLines . "\n"; + $str .= " Total Lines of Code: " . ($this->grandTotalCoveredLines + $this->grandTotalUncoveredLines) . "\n"; + $str .= " Code Coverage: " . $this->getGrandCodeCoveragePercentage() . "%\n"; + $str .= "##############################################\n"; + + if(empty($filename)) { + echo $str; + //file_put_contents("php://stdout", $str); + } + else { + $filename = $util->replaceBackslashes($filename); + if(!file_exists(dirname($filename))) { + $ret = $util->makeDirRecursive(dirname($filename), 0755); + if(!$ret) { + die ("Cannot create directory " . dirname($filename) . "\n"); + } + } + file_put_contents($filename, $str); + } + } + + /*}}}*/ +/*{{{ protected function makeRelative() */ + + /** + * Convert the absolute path to PHP file markup to a path relative + * to the report dir. + * + * @param $filepath PHP markup file path + * @return Relative file path + * @access protected + */ + protected function makeRelative($filepath) { + $dirPath = realpath($this->outputDir); + $absFilePath = realpath($filepath); + + if(strpos($absFilePath, $dirPath) === 0) { + $relPath = substr($absFilePath, strlen($dirPath)+1); + return $relPath; + } + return $absFilePath; + } + +/*}}}*/ +/*{{{ protected function getRelativeOutputDirPath() */ + + + /** + * Get the relative path of report directory with respect to the given + * filepath + * + * @param $filepath Path of the file (relative to the report dir) + * @return String Relative path of report directory w.r.t. filepath + * @access protected + */ + protected function getRelativeOutputDirPath($filepath) { + $relPath = ""; + $filepath = dirname($filepath); + while($filepath !== false && $filepath != ".") { + $relPath = "../" . $relPath; + $filepath = dirname($filepath); + } + return $relPath; + } + +/*}}}*/ + /*{{{ public abstract function generateReport() */ + + /** + * + * This function generates report using one of the concrete subclasses. + * + * @param &$data Coverage Data recorded by coverage recorder. + * @access public + */ + public abstract function generateReport(&$data); + + /*}}}*/ + /*{{{ Getters and Setters */ + + public function setHeading($heading) { + $this->heading = $heading; + } + + public function getHeading() { + return $this->heading; + } + + public function setStyle($style) { + $this->style = $style; + } + + public function getStyle() { + return $this->style; + } + + public function setOutputDir($dir) { + $this->outputDir = $dir; + } + + public function getOutputDir() { + return $this->outputDir; + } + + public function setCoverageRecorder(&$recorder) { + $this->recorder = $recorder; + } + + /*}}}*/ + } +?> diff --git a/lib/spikephpcoverage/src/reporter/HtmlCoverageReporter.php b/lib/spikephpcoverage/src/reporter/HtmlCoverageReporter.php new file mode 100644 index 0000000000..c5d7f34684 --- /dev/null +++ b/lib/spikephpcoverage/src/reporter/HtmlCoverageReporter.php @@ -0,0 +1,642 @@ +<?php + /* + * $Id$ + * + * Copyright(c) 2004-2006, SpikeSource Inc. All Rights Reserved. + * Licensed under the Open Software License version 2.1 + * (See http://www.spikesource.com/license.html) + */ +?> +<?php + + if(!defined("__PHPCOVERAGE_HOME")) { + define("__PHPCOVERAGE_HOME", dirname(dirname(__FILE__))); + } + require_once __PHPCOVERAGE_HOME . "/reporter/CoverageReporter.php"; + require_once __PHPCOVERAGE_HOME . "/parser/PHPParser.php"; + require_once __PHPCOVERAGE_HOME . "/util/Utility.php"; + + /** + * Class that implements HTML Coverage Reporter. + * + * @author Nimish Pachapurkar <npac@spikesource.com> + * @version $Revision$ + * @package SpikePHPCoverage_Reporter + */ + class HtmlCoverageReporter extends CoverageReporter { + + /*{{{ Members */ + + private $coverageData; + private $htmlFile; + private $body; + private $header = "html/header.html"; + private $footer = "html/footer.html"; + private $indexHeader = "html/indexheader.html"; + private $indexFooter = "html/indexfooter.html"; + + /*}}}*/ + /*{{{ public function __construct() */ + + /** + * Constructor method (PHP5 only) + * + * @param $heading Heading of the report (shown as title) + * @param $style Name of the stylesheet file + * @param $dir Directory where the report files should be dumped + * @access public + */ + public function __construct( + $heading="Coverage Report", + $style="", + $dir="report" + ) { + parent::__construct($heading, $style, $dir); + } + + /*}}}*/ + /*{{{ public function generateReport() */ + + /** + * Implementaion of generateReport abstract function. + * This is the only function that will be called + * by the instrumentor. + * + * @param &$data Reference to Coverage Data + * @access public + */ + public function generateReport(&$data) { + if(!file_exists($this->outputDir)) { + mkdir($this->outputDir); + } + $this->coverageData =& $data; + $this->grandTotalFiles = count($this->coverageData); + $ret = $this->writeIndexFile(); + if($ret === FALSE) { + $this->logger->error("Error occured!!!", __FILE__, __LINE__); + } + $this->logger->debug(print_r($data, true), __FILE__, __LINE__); + } + + /*}}}*/ + /*{{{ private function writeIndexFileHeader() */ + + /** + * Write the index file header to a string + * + * @return string String containing HTML code for the index file header + * @access private + */ + private function writeIndexFileHeader() { + $str = false; + $dir = realpath(dirname(__FILE__)); + if($dir !== false) { + $str = file_get_contents($dir . "/" . $this->indexHeader); + if($str == false) { + return $str; + } + $str = str_replace("%%heading%%", $this->heading, $str); + $str = str_replace("%%style%%", $this->style, $str); + } + return $str; + } + + /*}}}*/ + /*{{{ private function writeIndexFileFooter() */ + + /** + * Write the index file footer to a string + * + * @return string String containing HTML code for the index file footer. + * @access private + */ + private function writeIndexFileFooter() { + $str = false; + $dir = realpath(dirname(__FILE__)); + if($dir !== false) { + $str = file_get_contents($dir . "/" . $this->indexFooter); + if($str == false) { + return $str; + } + } + return $str; + } + + /*}}}*/ + /*{{{ private function createJSDir() */ + + /** + * Create a directory for storing Javascript for the report + * + * @access private + */ + private function createJSDir() { + $jsDir = $this->outputDir . "/js"; + if(file_exists($this->outputDir) && !file_exists($jsDir)) { + mkdir($jsDir); + } + $jsSortFile = realpath(dirname(__FILE__)) . "/js/sort_spikesource.js"; + copy($jsSortFile, $jsDir . "/" . "sort_spikesource.js"); + return true; + } + + /*}}}*/ + /*{{{ private function createImagesDir() */ + + /** + * Create a directory for storing images for the report + * + * @access private + */ + private function createImagesDir() { + $imagesDir = $this->outputDir . "/images"; + if(file_exists($this->outputDir) && !file_exists($imagesDir)) { + mkdir($imagesDir); + } + $imagesSpikeDir = $imagesDir . "/spikesource"; + if(!file_exists($imagesSpikeDir)) { + mkdir($imagesSpikeDir); + } + $imagesArrowUpFile = realpath(dirname(__FILE__)) . "/images/arrow_up.gif"; + $imagesArrowDownFile = realpath(dirname(__FILE__)) . "/images/arrow_down.gif"; + $imagesPHPCoverageLogoFile = realpath(dirname(__FILE__)) . "/images/spikesource/phpcoverage.gif"; + $imagesSpacerFile = realpath(dirname(__FILE__)) . "/images/spacer.gif"; + copy($imagesArrowUpFile, $imagesDir . "/" . "arrow_up.gif"); + copy($imagesArrowDownFile, $imagesDir . "/" . "arrow_down.gif"); + copy($imagesSpacerFile, $imagesDir . "/" . "spacer.gif"); + copy($imagesPHPCoverageLogoFile, $imagesSpikeDir . "/" . "phpcoverage.gif"); + return true; + } + + /*}}}*/ + /*{{{ private function createStyleDir() */ + + private function createStyleDir() { + if(isset($this->style)) { + $this->style = trim($this->style); + } + if(empty($this->style)) { + $this->style = "spikesource.css"; + } + $styleDir = $this->outputDir . "/css"; + if(file_exists($this->outputDir) && !file_exists($styleDir)) { + mkdir($styleDir); + } + $styleFile = realpath(dirname(__FILE__)) . "/css/" . $this->style; + copy($styleFile, $styleDir . "/" . $this->style); + return true; + } + + /*}}}*/ + /*{{{ protected function writeIndexFileTableHead() */ + + /** + * Writes the table heading for index.html + * + * @return string Table heading row code + * @access protected + */ + protected function writeIndexFileTableHead() { + $str = ""; + $str .= '<h1>Details</h1> <table class="spikeDataTable" cellpadding="4" cellspacing="0" border="0" id="table2sort" width="800">'; + $str .= '<thead>'; + $str .= '<tr><td class="spikeDataTableHeadLeft" id="sortCell0" rowspan="2" style="white-space:nowrap" width="52%"><a id="sortCellLink0" class="headerlink" href="javascript:sort(0)" title="Sort Ascending">File Name </a></td>'; + $str .= '<td colspan="4" class="spikeDataTableHeadCenter">Lines</td>'; + $str .= '<td class="spikeDataTableHeadCenterLast" id="sortCell5" rowspan="2" width="16%" style="white-space:nowrap"><a id="sortCellLink5" class="headerlink" href="javascript:sort(5, \'percentage\')" title="Sort Ascending">Code Coverage </a></td>'; + $str .= '</tr>'; + + // Second row - subheadings + $str .= '<tr>'; + $str .= '<td class="spikeDataTableSubHeadCenter" id="sortCell1" style="white-space:nowrap" width="8%"><a id="sortCellLink1" title="Sort Ascending" class="headerlink" href="javascript:sort(1, \'number\')">Total </a></td>'; + $str .= '<td class="spikeDataTableSubHeadCenter" id="sortCell2" style="white-space:nowrap" width="9%"><a id="sortCellLink2" title="Sort Ascending" class="headerlink" href="javascript:sort(2, \'number\')">Covered </a></td>'; + $str .= '<td class="spikeDataTableSubHeadCenter" id="sortCell3" style="white-space:nowrap" width="8%"><a id="sortCellLink3" title="Sort Ascending" class="headerlink" href="javascript:sort(3, \'number\')">Missed </a></td>'; + $str .= '<td class="spikeDataTableSubHeadCenter" id="sortCell4" style="white-space:nowrap" width="10%"><a id="sortCellLink4" title="Sort Ascending" class="headerlink" href="javascript:sort(4, \'number\')">Executable </a></td>'; + $str .= '</tr>'; + $str .= '</thead>'; + return $str; + } + + /*}}}*/ + /*{{{ protected function writeIndexFileTableRow() */ + + /** + * Writes one row in the index.html table to display filename + * and coverage recording. + * + * @param $fileLink link to html details file. + * @param $realFile path to real PHP file. + * @param $fileCoverage Coverage recording for that file. + * @return string HTML code for a single row. + * @access protected + */ + protected function writeIndexFileTableRow($fileLink, $realFile, $fileCoverage) { + + global $util; + $fileLink = $this->makeRelative($fileLink); + $realFileShort = $util->shortenFilename($realFile); + $str = ""; + + $str .= '<tr><td class="spikeDataTableCellLeft">'; + $str .= '<a class="contentlink" href="' . $util->unixifyPath($fileLink) . '" title="' + . $realFile .'">' . $realFileShort. '</a>' . '</td>'; + $str .= '<td class="spikeDataTableCellCenter">' . $fileCoverage['total'] . "</td>"; + $str .= '<td class="spikeDataTableCellCenter">' . $fileCoverage['covered'] . "</td>"; + $str .= '<td class="spikeDataTableCellCenter">' . $fileCoverage['uncovered'] . "</td>"; + $str .= '<td class="spikeDataTableCellCenter">' . ($fileCoverage['covered']+$fileCoverage['uncovered']) . "</td>"; + if($fileCoverage['uncovered'] + $fileCoverage['covered'] == 0) { + // If there are no executable lines, assume coverage to be 100% + $str .= '<td class="spikeDataTableCellCenter">100%</td></tr>'; + } + else { + $str .= '<td class="spikeDataTableCellCenter">' + . round(($fileCoverage['covered']/($fileCoverage['uncovered'] + + $fileCoverage['covered']))*100.0, 2) + . '%</td></tr>'; + } + return $str; + } + + /*}}}*/ + /*{{{ protected function writeIndexFileGrandTotalPercentage() */ + + /** + * Writes the grand total for coverage recordings on the index.html + * + * @return string HTML code for grand total row + * @access protected + */ + protected function writeIndexFileGrandTotalPercentage() { + $str = ""; + + $str .= "<br/><h1>" . $this->heading . "</h1><br/>"; + + $str .= '<table border="0" cellpadding="0" cellspacing="0" id="contentBox" width="800"> <tr>'; + $str .= '<td align="left" valign="top"><h1>Summary</h1>'; + $str .= '<table class="spikeVerticalTable" cellpadding="4" cellspacing="0" width="800" style="margin-bottom:10px" border="0">'; + $str .= '<td width="380" class="spikeVerticalTableHead" style="font-size:14px">Overall Code Coverage </td>'; + $str .= '<td class="spikeVerticalTableCell" style="font-size:14px" colspan="2"><strong>' . $this->getGrandCodeCoveragePercentage() . '%</td>'; + + $str .= '</tr><tr>'; + + $str .= '<td class="spikeVerticalTableHead">Total Covered Lines of Code </td>'; + $str .= '<td width="30" class="spikeVerticalTableCell"><span class="emphasis">' . $this->grandTotalCoveredLines.'</span></td>'; + $str .= '<td class="spikeVerticalTableCell"><span class="note">(' . TOTAL_COVERED_LINES_EXPLAIN . ')</span></td>'; + + $str .= '</tr><tr>'; + + $str .= '<td class="spikeVerticalTableHead">Total Missed Lines of Code </td>'; + $str .= '<td class="spikeVerticalTableCell"><span class="emphasis">' . $this->grandTotalUncoveredLines.'</span></td>'; + $str .= '<td class="spikeVerticalTableCell"><span class="note">(' . TOTAL_UNCOVERED_LINES_EXPLAIN . ')</span></td>'; + + $str .= '</tr><tr>'; + + $str .= '<td class="spikeVerticalTableHead">Total Lines of Code </td>'; + $str .= '<td class="spikeVerticalTableCell"><span class="emphasis">' . ($this->grandTotalCoveredLines + $this->grandTotalUncoveredLines) .'</span></td>'; + $str .= '<td class="spikeVerticalTableCell"><span class="note">(' . + TOTAL_LINES_OF_CODE_EXPLAIN . ')</span></td>'; + + $str .= '</tr><tr>'; + + $str .= '<td class="spikeVerticalTableHead" >Total Lines </td>'; + $str .= '<td class="spikeVerticalTableCell"><span class="emphasis">' . $this->grandTotalLines.'</span></td>'; + $str .= '<td class="spikeVerticalTableCell"><span class="note">(' . TOTAL_LINES_EXPLAIN . ')</span></td>'; + + $str .= '</tr><tr>'; + + $str .= '<td class="spikeVerticalTableHeadLast" >Total Files </td>'; + $str .= '<td class="spikeVerticalTableCellLast"><span class="emphasis">' . $this->grandTotalFiles.'</span></td>'; + $str .= '<td class="spikeVerticalTableCellLast"><span class="note">(' . TOTAL_FILES_EXPLAIN . ')</span></td>'; + + $str .= '</tr></table>'; + + return $str; + } + + /*}}}*/ + /*{{{ protected function writeIndexFile() */ + + /** + * Writes index.html file from all coverage recordings. + * + * @return boolean FALSE on failure + * @access protected + */ + protected function writeIndexFile() { + global $util; + $str = ""; + $this->createJSDir(); + $this->createImagesDir(); + $this->createStyleDir(); + $this->htmlFile = $this->outputDir . "/index.html"; + $indexFile = fopen($this->htmlFile, "w"); + if(empty($indexFile)) { + $this->logger->error("Cannot open file for writing: $this->htmlFile", + __FILE__, __LINE__); + return false; + } + + $strHead = $this->writeIndexFileHeader(); + if($strHead == false) { + return false; + } + $str .= $this->writeIndexFileTableHead(); + $str .= '<tbody>'; + if(!empty($this->coverageData)) { + foreach($this->coverageData as $filename => &$lines) { + $realFile = realpath($filename); + $fileLink = $this->outputDir . $util->unixifyPath($realFile). ".html"; + $fileCoverage = $this->markFile($realFile, $fileLink, $lines); + if(empty($fileCoverage)) { + return false; + } + $this->recordFileCoverageInfo($fileCoverage); + $this->updateGrandTotals($fileCoverage); + + $str .= $this->writeIndexFileTableRow($fileLink, $realFile, $fileCoverage); + unset($this->coverageData[$filename]); + } + } + $str .= '</tbody>'; + $str .= "</table></td></tr>"; + + $str .= "<tr><td><p align=\"right\" class=\"content\">Report Generated On: " . $util->getTimeStamp() . "<br/>"; + $str .= "Generated using Spike PHPCoverage " . $this->recorder->getVersion() . "</p></td></tr></table>"; + + // Get the summary + $strSummary = $this->writeIndexFileGrandTotalPercentage(); + + // Merge them - with summary on top + $str = $strHead . $strSummary . $str; + + $str .= $this->writeIndexFileFooter(); + fwrite($indexFile, $str); + fclose($indexFile); + return TRUE; + } + + /*}}}*/ + /*{{{ private function writePhpFileHeader() */ + + /** + * Write the header for the source file with mark-up + * + * @param $filename Name of the php file + * @return string String containing the HTML for PHP file header + * @access private + */ + private function writePhpFileHeader($filename, $fileLink) { + $fileLink = $this->makeRelative($fileLink); + $str = false; + $dir = realpath(dirname(__FILE__)); + if($dir !== false) { + $str = file_get_contents($dir . "/" . $this->header); + if($str == false) { + return $str; + } + $str = str_replace("%%filename%%", $filename, $str); + // Get the path to parent CSS directory + $relativeCssPath = $this->getRelativeOutputDirPath($fileLink); + $relativeCssPath .= "/css/" . $this->style; + $str = str_replace("%%style%%", $relativeCssPath, $str); + } + return $str; + } + + /*}}}*/ + /*{{{ private function writePhpFileFooter() */ + + /** + * Write the footer for the source file with mark-up + * + * @return string String containing the HTML for PHP file footer + * @access private + */ + private function writePhpFileFooter() { + $str = false; + $dir = realpath(dirname(__FILE__)); + if($dir !== false) { + $str = file_get_contents($dir . "/" . $this->footer); + if($str == false) { + return $str; + } + } + return $str; + } + + /*}}}*/ + /*{{{ protected function markFile() */ + + /** + * Mark a source code file based on the coverage data gathered + * + * @param $phpFile Name of the actual source file + * @param $fileLink Link to the html mark-up file for the $phpFile + * @param &$coverageLines Coverage recording for $phpFile + * @return boolean FALSE on failure + * @access protected + */ + protected function markFile($phpFile, $fileLink, &$coverageLines) { + global $util; + $fileLink = $util->replaceBackslashes($fileLink); + $parentDir = $util->replaceBackslashes(dirname($fileLink)); + if(!file_exists($parentDir)) { + //echo "\nCreating dir: $parentDir\n"; + $util->makeDirRecursive($parentDir, 0755); + } + $writer = fopen($fileLink, "w"); + + if(empty($writer)) { + $this->logger->error("Could not open file for writing: $fileLink", + __FILE__, __LINE__); + return false; + } + + // Get the header for file + $filestr = $this->writePhpFileHeader(basename($phpFile), $fileLink); + + // Add header for table + $filestr .= '<table width="100%" border="0" cellpadding="2" cellspacing="0">'; + $filestr .= $this->writeFileTableHead(); + + $lineCnt = $coveredCnt = $uncoveredCnt = 0; + $parser = new PHPParser(); + $parser->parse($phpFile); + $lastLineType = "non-exec"; + $fileLines = array(); + while(($line = $parser->getLine()) !== false) { + if (substr($line, -1) == "\n") { + $line = substr($line, 0, -1); + } + $lineCnt++; + $coverageLineNumbers = array_keys($coverageLines); + if(in_array($lineCnt, $coverageLineNumbers)) { + $lineType = $parser->getLineType(); + if($lineType == LINE_TYPE_EXEC) { + $coveredCnt ++; + $type = "covered"; + } + else if($lineType == LINE_TYPE_CONT) { + // XDebug might return this as covered - when it is + // actually merely a continuation of previous line + if($lastLineType == "covered" || $lastLineType == "covered_cont") { + unset($coverageLines[$lineCnt]); + $type = "covered_cont"; + $coveredCnt ++; + } + else { + $ft = "uncovered_cont"; + for($il = $lineCnt-1 + ; $il >=0 + && isset($fileLines[$lineCnt-1]["type"]) + && $ft == "uncovered_cont" + ; $il--) { + $ft = $fileLines[$il]["type"]; + $uncoveredCnt --; + $coveredCnt ++; + if($ft == "uncovered") { + $fileLines[$il]["type"] = "covered"; + } else { + $fileLines[$il]["type"] = "covered_cont"; + } + } + $coveredCnt ++; + $type = "covered_cont"; + } + } + else { + $type = "non-exec"; + $coverageLines[$lineCnt] = 0; + } + } + else if($parser->getLineType() == LINE_TYPE_EXEC) { + $uncoveredCnt ++; + $type = "uncovered"; + } + else if($parser->getLineType() == LINE_TYPE_CONT) { + if($lastLineType == "uncovered" || $lastLineType == "uncovered_cont") { + $uncoveredCnt ++; + $type = "uncovered_cont"; + } else if($lastLineType == "covered" || $lastLineType == "covered_cont") { + $coveredCnt ++; + $type = "covered_cont"; + } else { + $type = $lastLineType; + $this->logger->debug("LINE_TYPE_CONT with lastLineType=$lastLineType", + __FILE__, __LINE__); + } + } + else { + $type = "non-exec"; + } + // Save line type + $lastLineType = $type; + //echo $line . "\t[" . $type . "]\n"; + + if(!isset($coverageLines[$lineCnt])) { + $coverageLines[$lineCnt] = 0; + } + $fileLines[$lineCnt] = array("type" => $type, "lineCnt" => $lineCnt, "line" => $line, "coverageLines" => $coverageLines[$lineCnt]); + } + $this->logger->debug("File lines: ". print_r($fileLines, true), + __FILE__, __LINE__); + for($i = 1; $i <= count($fileLines); $i++) { + $filestr .= $this->writeFileTableRow($fileLines[$i]["type"], + $fileLines[$i]["lineCnt"], + $fileLines[$i]["line"], + $fileLines[$i]["coverageLines"]); + } + $filestr .= "</table>"; + $filestr .= $this->writePhpFileFooter(); + fwrite($writer, $filestr); + fclose($writer); + return array( + 'filename' => $phpFile, + 'covered' => $coveredCnt, + 'uncovered' => $uncoveredCnt, + 'total' => $lineCnt + ); + } + + /*}}}*/ + /*{{{ protected function writeFileTableHead() */ + + /** + * Writes table heading for file details table. + * + * @return string HTML string representing one table row. + * @access protected + */ + protected function writeFileTableHead() { + $filestr = ""; + + $filestr .= '<td width="10%"class="coverageDetailsHead" >Line #</td>'; + $filestr .= '<td width="10%" class="coverageDetailsHead">Frequency</td>'; + $filestr .= '<td width="80%" class="coverageDetailsHead">Source Line</td>'; + return $filestr; + } + + /*}}}*/ + /*{{{ protected function writeFileTableRow() */ + + /** + * Write a line for file details table. + * + * @param $color Text color + * @param $bgcolor Row bgcolor + * @param $lineCnt Line number + * @param $line The source code line + * @param $coverageLineCnt Number of time the line was executed. + * @return string HTML code for a table row. + * @access protected + */ + protected function writeFileTableRow($type, $lineCnt, $line, $coverageLineCnt) { + $spanstr = ""; + if($type == "covered" || $type == "covered_cont") { + $spanstr .= '<span class="codeExecuted">'; + } + else if($type == "uncovered" || $type == "uncovered_cont") { + $spanstr .= '<span class="codeMissed">'; + } + else { + $spanstr .= '<span>'; + } + + if(empty($coverageLineCnt)) { + $coverageLineCnt = ""; + } + + $filestr = '<tr>'; + $filestr .= '<td class="coverageDetails">' . $spanstr; + if($type == "covered_cont" || $type == "uncovered_cont") { + $filestr .= '+'; + } + $filestr .= $lineCnt . '</span></td>'; + if(empty($coverageLineCnt)) { + $coverageLineCnt = " "; + } + $filestr .= '<td class="coverageDetails">' . $spanstr . $coverageLineCnt . '</span></td>'; + $filestr .= '<td class="coverageDetailsCode"><code>' . $spanstr . $this->preserveSpacing($line) . '</span></code></td>'; + $filestr .= "</tr>"; + return $filestr; + } + + /*}}}*/ + /*{{{ protected function preserveSpacing() */ + + /** + * Changes all tabs and spaces with HTML non-breakable spaces. + * + * @param $string String containing spaces and tabs. + * @return string HTML string with replacements. + * @access protected + */ + protected function preserveSpacing($string) { + $string = htmlspecialchars($string); + $string = str_replace(" ", " ", $string); + $string = str_replace("\t", "    ", $string); + return $string; + } + + /*}}}*/ + } +?> diff --git a/lib/spikephpcoverage/src/reporter/css/spikesource.css b/lib/spikephpcoverage/src/reporter/css/spikesource.css new file mode 100644 index 0000000000..e299698dc5 --- /dev/null +++ b/lib/spikephpcoverage/src/reporter/css/spikesource.css @@ -0,0 +1,1035 @@ +/* www.spikesource.com style */ + +/* colors */ + +.logoBlue{ + background-color:#0066cc +} +.darkBlue{ + background-color:#003399 +} + +.lightBlue { + + background-color:#D2E7FC; +} +.faintBlue{ + background-color:#efefef; +} +/* Overload html tags */ +body{ + background-color:#ffffff; + font-size:11px; + font-family:Arial,Helvetica,sans-serif; + margin:0px; + color:#222222; +} + + +/* major page sections; containers are have "Box" suffix */ + +#pageBox{ + width:800px; + margin-left:20px; +} +#navBox{ + width:800px +} +#contentBox{ + width:800px; + margin-top:10px; +} + +/* spikesource.com elements */ + +#leftBox{ + width:600px; + padding-left:2px; + font-size:11px; + color:#222222; + line-height:14px +} +#rightBox{ + width:190px; + margin-bottom:5px; + margin-left:15px +} +#footerBox{ + border-top:2px solid #0066cc; + margin-top:15px; + width:800px; + height:22px; +} + +#leftNav { + width:165px; + margin-right:10px; +} +#leftRule { + width:175px; + border-right:1px solid #000000; +} + +#rightContent { + margin-left:15px; + width:600px; +} +#userInfo { + height:20px; + width:800px; +} + + +.navCell{ + height:31px; + border-left:1px solid #000000 +} + +.copyright{ + font-family:arial,sans-serif; + color:#666666; + font-size:10px; + font-weight:bold +} + +/* Convention:Do not overload html default styles - use spike[sometag] */ +h1,.spikeh1{ + font-size:17px; + font-family:Arial,Helvetica,sans-serif; + font-weight:bold; + color:#000000; + margin-bottom:4px; + margin-top:0px; +} +h2,.spikeh2{ + font-size:14px; + font-family:Arial,Helvetica,sans-serif; + font-weight:bold; + color:#003399; + margin-bottom:8px +} +h3,.spikeh3{ + font-size:12px; + font-family:Arial,Helvetica,sans-serif; + font-weight:bold; + color:#444444; + margin-bottom:0px +} +.spikep{ + margin-bottom:10px; + margin-top:5px; + padding-left:2px; + font-size:11px; + line-height:15px; + width:90%; +} +p{ + margin-bottom:10px; + margin-top:5px; + padding-left:2px; + font-size:11px; + line-height:15px; +} +.emph{ + font-style:oblique +} +ul,.spikeul{ + margin-left:22px; + padding:0px; + margin-top:5px; +} +li,.spikeli{ + list-style-position:outside; + line-height:14px; + font-size:11px; + margin-top:4px; + margin-left:2px; +} + + +.emphasis{ + font-size:13px; + font-family:Arial,Helvetica,sans-serif; + font-weight:bold; + color:#666666 +} + + +.rightHeader{ + font-size:14px; + font-family:Arial,Helvetica,sans-serif; + color:#222222; + font-weight:bold; + padding-bottom:2px; +} + +.blueRuleBox{ + width:175px; + border-bottom:2px solid #0066cc; + margin-left:15px; +} + +.rightRule{ + border-left:1px solid #000000; +} +.rightIndent{ + width:178px; + margin-left:15px; + font-size:11px; + color:#222222; + line-height:1.3em; + margin-top:7px; + text-align:left; + margin-bottom:5px +} + +.newsHeader{ + width:178px; + margin-left:15px; + font-size:12px; + color:#222222; + margin-bottom:2px; + margin-top:6px; + +} +ul.newsBox{ + margin-left:30px; + padding-bottom:5px +} + + +.content_small{ + font-family:Arial,Helvetica,sans-serif; + font-size:10px; + color:#444444 +} +.content{ + font-family:Arial,Helvetica,sans-serif; + font-size:11px; + color:#444444 +} +.content_gray{ + font-family:Arial,Helvetica,sans-serif; + font-size:11px; + color:#000000; + background-color:#e9e9e9 +} +.content_error{ + font-family:Arial,Helvetica,sans-serif; + font-size:11px; + color:#0066cc; + font-weight:bold +} +.contentError{ + font-family:Arial,Helvetica,sans-serif; + font-size:11px; + color:#990000; + font-weight:bold; +} +.content_required{ + font-family:Arial,Helvetica,sans-serif; + font-size:11px; + color:#0066cc +} +.content_bold { + font-size:11px; + font-weight:bold; +} +/* Tables */ +.spikeDataTable { + border-top:1px solid #5d6c7b; + border-right:1px solid #5d6c7b; + border-left:1px solid #5d6c7b; +} + +/* DataTable headings and cell styles */ +.spikeDataTableHeadRight { + font-size:12px; + font-weight:bold; + background-color:#d2e7fc; + border-right:2px solid #ffffff; + text-align:right; + padding-right:5px; +} +.spikeDataTableHeadLeft { + font-size:12px; + font-weight:bold; + background-color:#d2e7fc; + border-right:2px solid #ffffff; + text-align:left; + padding-left:5px; +} +.spikeDataTableHeadCenter { + font-size:12px; + font-weight:bold; + background-color:#d2e7fc; + border-right:2px solid #ffffff; + text-align:center; + margin-left:auto; + margin-right:auto; +} + +.spikeDataTableSubHeadRight { + font-size:11px; + font-weight:bold; + background-color:#d2e7fc; + border-right:2px solid #ffffff; + border-top:2px solid #ffffff; + text-align:right; + padding-right:5px; +} +.spikeDataTableSubHeadLeft { + font-size:11px; + font-weight:bold; + background-color:#d2e7fc; + border-right:2px solid #ffffff; + border-top:2px solid #ffffff; + text-align:left; + padding-left:5px; +} + +.spikeDataTableSubHeadCenter { + font-size:11px; + font-weight:bold; + background-color:#d2e7fc; + border-right:2px solid #ffffff; + border-top:2px solid #ffffff; + text-align:center; + margin-left:auto; + margin-right:auto; +} + +.spikeDataTableSubHeadLeftLast{ + font-size:11px; + font-weight:bold; + background-color:#d2e7fc; + border-top:2px solid #ffffff; + text-align:left; + padding-left:5px; +} +.spikeDataTableSubHeadCenterLast { + font-size:11px; + font-weight:bold; + background-color:#d2e7fc; + border-top:2px solid #ffffff; + text-align:center; + margin-left:auto; + margin-right:auto; +} +.spikeDataTableHeadRightLast { + font-size:12px; + font-weight:bold; + background-color:#d2e7fc; + text-align:right; + padding-right:5px; +} + +.spikeDataTableHeadLeftLast { + font-size:12px; + font-weight:bold; + background-color:#d2e7fc; + text-align:left; + padding-left:5px; +} +.spikeDataTableHeadCenterLast { + background-color:#d2e7fc; + font-size:12px; + font-weight:bold; + text-align:center; + margin-left:auto; + margin-right:auto; +} + + + +.spikeDataTableCellRight { + font-size:11px; + text-align:right; + border-bottom:1px solid #5d6c7b; + padding-right:5px; +} +.spikeDataTableCellRightBorder { + font-size:11px; + text-align:right; + border-bottom:1px solid #5d6c7b; + border-right:1px solid #cccccc; + padding-right:5px; +} +.spikeDataTableCellLeft { + font-size:11px; + text-align:left; + border-bottom:1px solid #5d6c7b; + padding-left:5px; +} +.spikeDataTableCellLeftBorder { + font-size:11px; + text-align:left; + border-bottom:1px solid #5d6c7b; + border-right:1px solid #cccccc; + padding-left:5px; +} +.spikeDataTableCellCenter { + font-size:11px; + text-align:center; + margin-left:auto; + margin-right:auto; + border-bottom:1px solid #5d6c7b; +} +.spikeDataTableCellCenterBorder { + font-size:11px; + text-align:center; + margin-left:auto; + margin-right:auto; + border-bottom:1px solid #5d6c7b; + border-right:1px solid #cccccc; +} +/* vertical table */ +.spikeVerticalTable { + border:1px solid #5d6c7b; +} +.spikeVerticalTableHead { + + font-size:12px; + font-weight:bold; + background-color:#d2e7fc; + border-bottom:2px solid #ffffff; + text-align:right; + padding-right:5px; + vertical-align:top; +} + +.spikeVerticalTableHeadLast { + + font-size:12px; + font-weight:bold; + background-color:#d2e7fc; + text-align:right; + padding-left:5px; + vertical-align:top; +} + +.spikeVerticalTableCell { + font-size:11px; + text-align:left; + border-bottom:1px solid #5d6c7b; + padding-left:5px; + vertical-align:top; +} +.spikeVerticalTableCellBold { + font-weight:bold; + font-size:11px; + text-align:left; + border-bottom:1px solid #5d6c7b; + padding-left:5px; + vertical-align:top; +} +.spikeVerticalTableCellLast { + font-size:11px; + text-align:left; + padding-left:5px; + vertical-align:top; +} + + +/* Top navigation */ +#navbar { + height:80px; + border-bottom:2px solid #0066cc; + width:642px; +} +.navText{ + padding-left:5px; + font-size:13px; + color:#000000; + font-weight:bold; + letter-spacing:1px; + text-decoration:none +} + +.navText:link,.navText:visited{ + color:#0066cc +} +.navText:hover{ + color:#000000 +} +.navTextOn { + color:#000000; + padding-left:5px; + font-size:13px; + font-weight:bold; + letter-spacing:1px; + text-decoration:none +} +/* left side navigation */ +.parent { + margin-left:15px; + width:160px; + font-weight: bold; + font-size: 11px; + font-family: Arial, Helvetica, sans-serif; + text-decoration:none; + padding-bottom:5px; + color:#0066cc; + vertical-align:top; +} + +.parentChild { + margin-left:0px; + width:175px; + font-weight: bold; + font-size: 11px; + font-family: Arial, Helvetica, sans-serif; + text-decoration:none; + padding-bottom:5px; + color:#0066cc; + vertical-align:top; +} +.submenu { + color:#000000; + width:145px; + text-decoration: none; + padding-top:1px; + padding-bottom:1px; + padding-left:25px; + +} + + +.submenu a { + white-space:normal; +} + + +.child { + padding-left:0px; + width:175px; + font-weight: bold; + font-size: 11px; + text-decoration:none; + display:none; + padding-bottom:2px; +} + + +.menuImage { + margin-right:6px; +} +.submenu a:visited {color: #0066cc; text-decoration:none; border:0px;} +.submenu a:link {color: #0066cc; text-decoration: none; border:0px;} +.submenu a:hover { color: #000000; text-decoration: none ; border:0px;} + +.parentChild a:visited { + color: #0066cc; + text-decoration:none; +} +.parentChild a:link { + color: #0066cc; + text-decoration:none; +} +.parentChild a:hover { + color: #000000; + text-decoration:none +} +.parent a:visited { + color: #0066cc; + text-decoration:none; +} +.parent a:link { + color: #0066cc; + text-decoration:none; +} +.parent a:hover { + color: #000000; + text-decoration:none +} + + +/* links */ +.footerlink:link, .footerlink:visited{ + color:#003399; + font-size:10px; + text-decoration:none +} + +.footerlink:hover{ + color:#000000; + text-decoration:underline +} +.contentlink{ + font-family:arial,sans-serif; + font-size:11px; +} +.contentlink:visited, .contentlink:link { + color:#0055bb; + text-decoration:none; +} +.contentlink:hover{ + color:#000000; + text-decoration:underline +} +.contentlinkBold{ + font-family:arial,sans-serif; + font-size:11px; + font-weight:bold; +} +.contentlinkBold:visited, .contentlinkBold:link { + color:#0055bb; + text-decoration:none; +} +.contentlinkBold:hover{ + color:#000000; + text-decoration:underline +} +a.headerlink{ + color:#000000; + text-decoration:underline; + +} +a.headerlink:hover { + text-decoration:underline; +} +a.externalLink, a.tablelink { + color:#000000; + text-decoration:none; +} +a.externalLink:link,a.tablelink:link { + color:#0055bb; +} +a.externalLink:visited,a.tablelink:visited { + color:#0055bb; +} +a.externalLink:hover,a.tablelink:hover { + color:#000000; + text-decoration:underline; +} + +a.tablelinkBold:link { + font-weight:bold; + text-decoration:none; + color:#0055bb; +} +a.tablelinkBold:visited { + font-weight:bold; + text-decoration:none; + color:#0066cc; +} +a.tablelinkBold:hover { + font-weight:bold; + color:#000000; + text-decoration:underline; +} +.blueHeader,.greyHeader{ + font-size:12px; + font-family:Arial,Helvetica,sans-serif; + color:#0066cc; + font-weight:bold +} + +/* form layout */ +.spikeForm{ + vertical-align:top; + margin:0px; + padding:0px; + border:1px solid #5d6c7b; +} +.spikeButton { + padding-right:10px; +} +.required { + margin-bottom:0px; + text-align:right; +} +.formRule{ + border-left:1px solid #000000; + padding-top:0px; + margin-left:0px; +} + + +.formHeader{ + padding-left:20px; + height: 22px; + background-color:#D2E7FC; + color: #000000; + font-size:12px; + font-weight:bold; +} +.formLabel{ + height:14px; + font-family:Arial,Helvetica,sans-serif; + font-size:11px; + color:#000000; + text-align:right; + font-weight:bold; + white-space:nowrap; +} +.formLabelL { + font-size:11px; + font-weight:bold; + color:#000000; + white-space:nowrap; +} +.formDirection { + padding-left:20px; + height: 20px; + background-color:#ededed; + color: #000000; + font-size:11px; +} +.formNote { + color:#333333; + font-size:10px; + padding-left:2px; +} +.label,.element{ + font-family:Arial,Helvetica,sans-serif; + color:#000000; + font-size:11px; +} +.checkLabel{ + font-family:Arial,Helvetica,sans-serif; + font-size:10px; + color:#000000; + font-weight:bold; + margin-right:2px; + vertical-align:middle; +} +.button{ + font-family:Arial,Helvetica,sans-serif; + font-size:10px; + color:#000000; + background-color:#e9e9e9; + border:1px solid #666666 +} + +.asterickR{ + margin-right:2px +} +.asterickL{ + margin-left:2px +} + +.blueButton{ + font-family:arial,tahoma,sans-serif; + font-size:11px; + font-weight:bold; + color:#ffffff; + background-color:#0066cc; + border-style:none; + border:none; + margin:0px; + padding:1px; +} + +/* Defines input boxes,textareas,and input checkboxes */ +.textfield{ + margin-left:2px; + border:1px solid #3d86ce; + background-color:#E7F1FA; + font-size:11px; + color:#333333 +} +input{ + margin:0px; + padding:0px +} +.checkbox { + margin-right:2px; + margin-left:0px; +} +.selectMenu{ + height:16px; + margin-left:4px; + background-color:#ffffff; + font-size:11px; + color:#333333 +} +.optionElem{ + font-size:11px; + color:#333333; + padding-bottom:2px +} +.multiBox{ + height:50px; + font-size:11px +} + +/* Required by HTML_QuickForm module */ +.errors{ + font-family:Arial,Helvetica,sans-serif; + color:#990000; + font-weight:bold +} +.note{ + font-family:Arial,Helvetica,sans-serif; + font-size:9px; + color:#000000 +} +/* Job Page styles */ + +.jobTitle { + font-family: Arial, Helvetica, sans-serif; + font-size:11px; + text-decoration:none; + line-height:12px; + font-weight:bold; + color:#0066cc; +} + +a.jobTitle:link { + color:#0066cc; + +} + +a.jobTitle:visited { + color:#0066cc; + +} + +a.jobTitle:hover { + color:#000000; + text-decoration:underline; + +} + +.jobTable { + margin-bottom:20px; +} + +.jobTable td { + padding-left:4px; +} + +.jobTable .jobTitle { + font-family:Verdana, Arial, Helvetica, sans-serif; + font-weight:bold; + color:#ffffff; + background-color:#0066cc; + /*background-color:#D2E7FC; */ + font-size:12px; + height:20px; + +} +p.jobDescription { + margin-bottom:5px; + margin-top:2px; +} + + + +/* old stuff */ + +.loginTable{ + width:250px; + padding-bottom:2px; + padding-top:10px +} +.category_sublink:hover,.boxtitlelink:hover,.category_mainlink:hover{ + color:#990000; + text-decoration:underline +} +.spacer,#betaTable{ + background-color:#ffffff +} +.borderedbg{ + background-color:#ffffff; + font-family:arial; + font-size:10px; + color:#333333 +} +table.bordered{ + background-color:#999999 +} +td.boxoff{ + background-color:#ffffff; + color:#000000; + font-size:8pt; + font-family:Arial,Helvetica,sans-serif +} +td.boxon{ + background-color:#eeeeee; + color:#000000; + font-size:8pt; + font-family:Arial,Helvetica,sans-serif +} +.pagetitle{ + font-family:arial; + font-weight:bold; + font-size:18pt; + color:#333333; + border-bottom:1px solid #999999 +} +.boxtitlelink{ + color:#336699; + font-size:9pt; + font-family:tahoma,verdana,arial; + text-decoration:none +} +.sectiontitle{ + font-family:Arial,Helvetica,sans-serif; + font-size:10pt; + color:#990000; + font-weight:bold +} +.subtitle{ + font-family:Arial,Helvetica,sans-serif; + font-size:11pt; + font-weight:bold; + color:#333333; + border-bottom:1px dashed #000000 +} +.subcategory{ + background-color:#eeeeee; + color:#000000; + font-size:8pt; + font-family:Arial,Helvetica,sans-serif; + border-left:5px solid #cccccc +} +.subcategory2{ + background-color:#d9d6c5; + color:#000000; + font-size:8pt; + font-family:Arial,Helvetica,sans-serif; + border-left:5px solid #cccccc +} +.row_title{ + background-color:#999999; + color:#ffffff; + font-family:Arial,Helvetica,sans-serif; + font-size:8pt; + font-weight:bold +} +.row_category{ + background-color:#1D97C3; + font-family:Arial,Helvetica,sans-serif; + font-size:10pt; + font-weight:bold +} +.row1{ + color:#000000; + background-color:#dadada; + font-family:Arial,Helvetica,sans-serif; + font-size:8pt +} +.row2{ + color:#000000; + background-color:#e9e9e9; + font-family:Arial,Helvetica,sans-serif; + font-size:8pt +} +.row3{ + color:#990000; + background-color:#e9e9e9; + font-family:Arial,Helvetica,sans-serif; + font-size:8pt +} +.row4{ + color:#990000; + background-color:#ffffff; + font-family:Arial,Helvetica,sans-serif; + font-size:8pt +} +.rowsection{ + color:#000000; + background-color:#f9f9f9; + font-family:Arial,Helvetica,sans-serif; + font-size:11px; +} +.category{ + background-color:#ffffff; + font-family:Arial,Helvetica,sans-serif; + font-size:9pt +} +.category_mainlink{ + font-family:Arial,Helvetica,sans-serif; + font-size:9pt; + color:#336699; + font-family:Arial,Helvetica,sans-serif; + text-decoration:underline; + font-weight:bold +} +.category_sublink{ + font-family:Arial,Helvetica,sans-serif; + font-size:8pt; + color:#336699; + font-family:Arial,Helvetica,sans-serif; + text-decoration:underline +} +.boxed_sectionheader{ + font-family:trebuchet MS,verdana,arial; + font-size:11pt; + font-weight:bold; + color:#333366; + background-color:#e9e9e9 +} +.dotted_sectionheader{ + font-family:trebuchet MS,verdana,arial; + font-size:11pt; + font-weight:bold; + color:#333366; + background-color:#ffffff +} +.tab_on{ + background-color:#ffffff; + font-size:8pt; + font-family:Arial,Helvetica,sans-serif; + border-left:1px solid #666666; + border-right:1px solid #666666; + border-top:1px solid #666666 +} +.tab_off{ + background-color:#e9e9e9; + font-size:8pt; + font-family:Arial,Helvetica,sans-serif; + border-left:1px solid #666666; + border-right:1px solid #666666; + border-bottom:1px solid #666666; + border-top:1px solid #666666 +} +.tab_spacer{ + background-color:#ffffff; + border-bottom:1px solid #666666 +} +.tab_content{ + background-color:#ffffff; + border-left:1px solid #666666; + border-right:1px solid #666666; + border-bottom:1px solid #666666 +} + +/* PHPCoverage Specific */ + +.emphasis { + font-size:12px; + font-weight:bold; + color:#222222; +} +.note { + vertical-align:text-bottom +} +.coverageDetailsHead { + border-right:2px solid #eeeeee; + background-color:#C0CEDC; + color:#000000; + font-size:12px; + font-weight:bold; + white-space:nowrap; +} +.coverageDetails { + color:#666666; + font-size:13px; + border-right:1px solid #A2AFBC +} +.coverageDetailsCode { + font-weight:normal; + color:#666666; + font-size:13px; +} +.codeExecuted { + color:#003399; + font-weight:bold; +} +.codeMissed { + color:#990000; + font-weight:bold; +} diff --git a/lib/spikephpcoverage/src/reporter/html/footer.html b/lib/spikephpcoverage/src/reporter/html/footer.html new file mode 100644 index 0000000000..9b66de5e11 --- /dev/null +++ b/lib/spikephpcoverage/src/reporter/html/footer.html @@ -0,0 +1,6 @@ +</td></tr> + </table> + + </div> + </body> +</html> diff --git a/lib/spikephpcoverage/src/reporter/html/header.html b/lib/spikephpcoverage/src/reporter/html/header.html new file mode 100644 index 0000000000..41cdf4e17b --- /dev/null +++ b/lib/spikephpcoverage/src/reporter/html/header.html @@ -0,0 +1,17 @@ +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> + <title>Spike PHPCoverage Details: %%filename%% + + + +
+ + + + + + +

Spike PHPCoverage Details: %%filename%%

+
+ diff --git a/lib/spikephpcoverage/src/reporter/html/indexfooter.html b/lib/spikephpcoverage/src/reporter/html/indexfooter.html new file mode 100644 index 0000000000..6d247c293e --- /dev/null +++ b/lib/spikephpcoverage/src/reporter/html/indexfooter.html @@ -0,0 +1,18 @@ +
+ + + + + +
+ spacer
+ + + + diff --git a/lib/spikephpcoverage/src/reporter/html/indexheader.html b/lib/spikephpcoverage/src/reporter/html/indexheader.html new file mode 100644 index 0000000000..cb0702083b --- /dev/null +++ b/lib/spikephpcoverage/src/reporter/html/indexheader.html @@ -0,0 +1,41 @@ + + + + %%heading%% + + + + + +
+ + + + + + + diff --git a/lib/spikephpcoverage/src/reporter/images/arrow_down.gif b/lib/spikephpcoverage/src/reporter/images/arrow_down.gif new file mode 100644 index 0000000000..582d6f5786 Binary files /dev/null and b/lib/spikephpcoverage/src/reporter/images/arrow_down.gif differ diff --git a/lib/spikephpcoverage/src/reporter/images/arrow_up.gif b/lib/spikephpcoverage/src/reporter/images/arrow_up.gif new file mode 100644 index 0000000000..fb39804590 Binary files /dev/null and b/lib/spikephpcoverage/src/reporter/images/arrow_up.gif differ diff --git a/lib/spikephpcoverage/src/reporter/images/spacer.gif b/lib/spikephpcoverage/src/reporter/images/spacer.gif new file mode 100644 index 0000000000..fc2560981e Binary files /dev/null and b/lib/spikephpcoverage/src/reporter/images/spacer.gif differ diff --git a/lib/spikephpcoverage/src/reporter/images/spikesource/phpcoverage.gif b/lib/spikephpcoverage/src/reporter/images/spikesource/phpcoverage.gif new file mode 100644 index 0000000000..bd580a9317 Binary files /dev/null and b/lib/spikephpcoverage/src/reporter/images/spikesource/phpcoverage.gif differ diff --git a/lib/spikephpcoverage/src/reporter/js/sort_spikesource.js b/lib/spikephpcoverage/src/reporter/js/sort_spikesource.js new file mode 100644 index 0000000000..45222db7a8 --- /dev/null +++ b/lib/spikephpcoverage/src/reporter/js/sort_spikesource.js @@ -0,0 +1,310 @@ +/*---------------------------------------------------\ +| Table Sorter | +|----------------------------------------------------| +| Author: Vinay Srinivasaiah (vsrini@spikesource.com)| +| SpikeSource (http://www.spikesource.com) | +| - DOM 1 based script that makes the table sortable.| +| - Copyright (c) 2004 SpikeSource Inc. | +|---------------------------------------------------*/ +//http://www.w3.org/TR/REC-DOM-Level-1/java-language-binding.html + +var tableBody; +var table2sort; +var imgUp; +var imgDown; + +function TableSorter(table) { + this.table2sort = table; + this.tableBody = this.table2sort.getElementsByTagName("tbody")[0]; + + this.imgUp = document.createElement("img"); + this.imgUp.src = "images/arrow_up.gif"; + this.imgDown = document.createElement("img"); + this.imgDown.src = "images/arrow_down.gif"; +} + +var lastSortCol = -1; +var lastSortOrderAsc = true; +var origChildRows; + +function createImgLink(row, imageSrc) { + var cell = row.cells[0]; + var id = _getInnerText(cell) + "_" + imageSrc; + + imgExpand = document.createElement("img"); + imgExpand.src = "images" + imageSrc + ".gif"; + imgExpand.border="0"; + + imgBlank = document.createElement("img"); + imgBlank.src = "results/images/transdot.gif"; + imgBlank.border="0"; + imgBlank2 = imgBlank.cloneNode(false); + imgBlank3 = imgBlank.cloneNode(false); + + anchorTag = document.createElement("a"); + anchorTag.href="javascript:toggleShowChildren('" + id + "');" + anchorTag.appendChild(imgExpand); + anchorTag.appendChild(imgBlank); + anchorTag.appendChild(imgBlank2); + anchorTag.appendChild(imgBlank3); + anchorTag.id = id; + + cell.id = id + "_cell"; + row.id = id + "_row"; + + cell.insertBefore(anchorTag, cell.firstChild); +} + +TableSorter.prototype.initTable = function () { + this.populateChildRowsMap(); + for (i = 0; i < origChildRows.length; i++) { + if (origChildRows[i].id != "indented_row") { + createImgLink(origChildRows[i], "minus"); + } + } +} + +TableSorter.prototype.collapseAllChildren = function () { + for (i = 0; i < origChildRows.length; i++) { + if (origChildRows[i].id != "indented_row") { + id = _getInnerText(origChildRows[i].cells[0]) + "_" + "minus"; + var anchorTag = document.getElementById(id); + if (anchorTag != null) { + this.togglechildren(id); + } + } + } +} + +TableSorter.prototype.expandAllChildren = function () { + for (i = 0; i < origChildRows.length; i++) { + if (origChildRows[i].id != "indented_row") { + id = _getInnerText(origChildRows[i].cells[0]) + "_" + "plus"; + var anchorTag = document.getElementById(id); + if (anchorTag != null) { + this.togglechildren(id); + } + } + } +} + +TableSorter.prototype.togglechildren = function (id) { + anchorTag = document.getElementById(id); + anchorParent = document.getElementById((id + "_cell")); + anchorParent.removeChild(anchorTag); + row = document.getElementById((id + "_row")); + nextRow = row.nextSibling; + + var addChildren = false; + if (anchorTag.firstChild.src.indexOf("plus") != -1) { + addChildren = true; + createImgLink(row, "minus"); + } else if (anchorTag.firstChild.src.indexOf("minus") != -1) { + addChildren = false; + createImgLink(row, "plus"); + } + for (i = 0; i < origChildRows.length; i++) { + //alert("comparing " + _getInnerText(origChildRows[i].cells[0]) + // + " and " + _getInnerText(row.cells[0])); + if (_getInnerText(origChildRows[i].cells[0]) == _getInnerText(row.cells[0])) { + for (j = i + 1; j < origChildRows.length; j++) { + if (origChildRows[j].id == "indented_row") { + if (addChildren) { + this.tableBody.insertBefore(origChildRows[j], nextRow); + } else { + this.tableBody.removeChild(origChildRows[j]); + } + } else { + // done; + break; + } + } + break; + } + } +} + +TableSorter.prototype.populateChildRowsMap = function () { + var rows = this.tableBody.rows; + origChildRows = new Array(); + var count = 0; + var newRowsCount = 0; + for (i = 0; i < rows.length; i ++) { + if (rows[i].id == "indented_row") { + if (parentRow != null) { + origChildRows[count++] = parentRow; + parentRow = null; + } + origChildRows[count++] = rows[i]; + } else { + parentRow = rows[i]; + } + } +} + +TableSorter.prototype.sort = function (col, type) { + if (lastSortCol != -1) { + sortCell = document.getElementById("sortCell" + lastSortCol); + if (sortCell != null) { + if (lastSortOrderAsc == true) { + sortCell.removeChild(this.imgUp); + } else { + sortCell.removeChild(this.imgDown); + } + } + sortLink = document.getElementById("sortCellLink" + lastSortCol); + if(sortLink != null) { + sortLink.title = "Sort Ascending"; + } + } + + if (lastSortCol == col) { + lastSortOrderAsc = !lastSortOrderAsc; + } else { + lastSortCol = col; + lastSortOrderAsc = true; + } + + var rows = this.tableBody.rows; + var newRows = new Array(); + var parentRow; + + var childRows = new Array(); + var count = 0; + var newRowsCount = 0; + for (i = 0; i < rows.length; i ++) { + if (rows[i].id == "indented_row") { + if (parentRow != null) { + childRows[count++] = parentRow; + parentRow = null; + } + childRows[count++] = rows[i]; + } else { + newRows[newRowsCount++] = rows[i]; + parentRow = rows[i]; + } + } + + // default + sortFunction = sort_caseInsensitive; + if (type == "string") sortFunction = sort_caseSensitive; + if (type == "percentage") sortFunction = sort_numericPercentage; + if (type == "number") sortFunction = sort_numeric; + + newRows.sort(sortFunction); + + if (lastSortOrderAsc == false) { + newRows.reverse(); + } + + for (i = 0; i < newRows.length; i ++) { + this.table2sort.tBodies[0].appendChild(newRows[i]); + var parentRowText = _getInnerText(newRows[i].cells[0]); + var match = -1; + for (j = 0; j < childRows.length; j++) { + var childRowText = _getInnerText(childRows[j].cells[0]); + if (childRowText == parentRowText) { + match = j; + break; + } + } + if (match != -1) { + for (j = match + 1; j < childRows.length; j++) { + if (childRows[j].id == "indented_row") { + this.table2sort.tBodies[0].appendChild(childRows[j]); + } else { + break; + } + } + } + } + + sortCell = document.getElementById("sortCell" + col); + if (sortCell == null) { + } else { + if (lastSortOrderAsc == true) { + sortCell.appendChild(this.imgUp); + } else { + sortCell.appendChild(this.imgDown); + } + } + + sortLink = document.getElementById("sortCellLink" + col); + if (sortLink == null) { + } else { + if (lastSortOrderAsc == true) { + sortLink.title = "Sort Descending"; + } else { + sortLink.title = "Sort Ascending"; + } + } +} + +function sort_caseSensitive(a, b) { + aa = _getInnerText(a.cells[lastSortCol]); + bb = _getInnerText(b.cells[lastSortCol]); + return compareString(aa, bb); +} + +function sort_caseInsensitive(a,b) { + aa = _getInnerText(a.cells[lastSortCol]).toLowerCase(); + bb = _getInnerText(b.cells[lastSortCol]).toLowerCase(); + return compareString(aa, bb); +} + +function sort_numeric(a,b) { + aa = _getInnerText(a.cells[lastSortCol]); + bb = _getInnerText(b.cells[lastSortCol]); + return compareNumber(aa, bb); +} + +function sort_numericPercentage(a,b) { + aa = _getInnerText(a.cells[lastSortCol]); + bb = _getInnerText(b.cells[lastSortCol]); + + var aaindex = aa.indexOf("%"); + var bbindex = bb.indexOf("%"); + + if (aaindex != -1 && bbindex != -1) { + aa = aa.substring(0, aaindex); + bb = bb.substring(0, bbindex); + return compareNumber(aa, bb); + } + + return compareString(aa, bb); +} + +function compareString(a, b) { + if (a == b) return 0; + if (a < b) return -1; + return 1; +} + +function compareNumber(a, b) { + aa = parseFloat(a); + if (isNaN(aa)) aa = 0; + bb = parseFloat(b); + if (isNaN(bb)) bb = 0; + return aa-bb; +} + +function _getInnerText(el) { + if (typeof el == "string") return el; + if (typeof el == "undefined") { return el }; + if (el.innerText) return el.innerText; + var str = ""; + + var cs = el.childNodes; + var l = cs.length; + for (var i = 0; i < l; i++) { + switch (cs[i].nodeType) { + case 1: //ELEMENT_NODE + str += _getInnerText(cs[i]); + break; + case 3: //TEXT_NODE + str += cs[i].nodeValue; + break; + } + } + return str; +} diff --git a/lib/spikephpcoverage/src/util/CoverageLogger.php b/lib/spikephpcoverage/src/util/CoverageLogger.php new file mode 100644 index 0000000000..1e81771e07 --- /dev/null +++ b/lib/spikephpcoverage/src/util/CoverageLogger.php @@ -0,0 +1,103 @@ + +logLevels); $i++) { + if(strcasecmp($this->logLevels[$i], $level) === 0) { + $level = $i; + break; + } + } + } + $this->level = $level; + } + + public function critical($str, $file="", $line="") { + if($this->level >= 0) { + error_log("[CRITICAL] [" . $file . ":" . $line . "] " . $str); + } + } + + public function error($str, $file="", $line="") { + if($this->level >= 1) { + error_log("[ERROR] [" . $file . ":" . $line . "] " . $str); + } + } + + public function warn($str, $file="", $line="") { + if($this->level >= 2) { + error_log("[WARNING] [" . $file . ":" . $line . "] " . $str); + } + } + + public function notice($str, $file="", $line="") { + if($this->level >= 3) { + error_log("[NOTICE] [" . $file . ":" . $line . "] " . $str); + } + } + + public function info($str, $file="", $line="") { + if($this->level >= 4) { + error_log("[INFO] [" . $file . ":" . $line . "] " . $str); + } + } + + public function debug($str, $file="", $line="") { + if($this->level >= 5) { + error_log("[DEBUG] [" . $file . ":" . $line . "] " . $str); + } + } + + public function getLevelName($level) { + return $this->logLevels[$level]; + } + } + + // testing + if(isset($_SERVER["argv"][1]) && $_SERVER["argv"][1] == "__main__") { + $logger = new CoverageLogger(); + for($i = 0; $i < 6; $i++) { + $logger->setLevel($i); + error_log("############## Level now: " . $i); + $logger->debug(""); + $logger->info(""); + $logger->notice(""); + $logger->warn(""); + $logger->error(""); + $logger->critical(""); + } + + error_log("############# With Level Names"); + for($i = 0; $i < 6; $i++) { + $logger->setLevel($logger->getLevelName($i)); + error_log("############## Level now: " . $logger->getLevelName($i)); + $logger->debug(""); + $logger->info("", __FILE__, __LINE__); + $logger->notice(""); + $logger->warn(""); + $logger->error(""); + $logger->critical(""); + } + } +?> diff --git a/lib/spikephpcoverage/src/util/Utility.php b/lib/spikephpcoverage/src/util/Utility.php new file mode 100644 index 0000000000..8a74dc096e --- /dev/null +++ b/lib/spikephpcoverage/src/util/Utility.php @@ -0,0 +1,225 @@ + + + * @version $Revision$ + * @package SpikePHPCoverage_Util + */ + class Utility { + + public static $logger; + + /*{{{ public function getTimeStamp() */ + + /** + * Return the current timestamp in human readable format. + * Thursday March 17, 2005 19:10:47 + * + * @return Readable timestamp + * @access public + */ + public function getTimeStamp() { + $ts = getdate(); + return $ts["weekday"] . " " . $ts["month"] . " " . $ts["mday"] + . ", " . $ts["year"] . " " . sprintf("%02d:%02d:%02d", $ts["hours"], $ts["minutes"], $ts["seconds"]); + } + + /*}}}*/ + /*{{{ public function shortenFilename() */ + + /** + * Shorten the filename to some maximum characters + * + * @param $filename Complete file path + * @param $maxlength=150 Maximum allowable length of the shortened + * filepath + * @return Shortened file path + * @access public + */ + public function shortenFilename($filename, $maxlength=80) { + $length = strlen($filename); + if($length < $maxlength) { + return $filename; + } + + // trim the first few characters + $filename = substr($filename, $length-$maxlength); + // If there is a path separator slash in first n characters, + // trim upto that point. + $n = 20; + $firstSlash = strpos($filename, "/"); + if($firstSlash === false || $firstSlash > $n) { + $firstSlash = strpos($filename, "\\"); + if($firstSlash === false || $firstSlash > $n) { + return "..." . $filename; + } + return "..." . substr($filename, $firstSlash); + } + return "..." . substr($filename, $firstSlash); + } + + /*}}}*/ + /*{{{ public function writeError() */ + + /** + * Write error log if debug is on + * + * @param $str Error string + * @access public + */ + public function writeError($str) { + if(__PHPCOVERAGE_DEBUG) { + error_log($str); + } + } + /*}}}*/ + /*{{{ public function unixifyPath() */ + + /** + * Convert Windows paths to Unix paths + * + * @param $path File path + * @return String Unixified file path + * @access public + */ + public function unixifyPath($path) { + // Remove the drive-letter: + if(strpos($path, ":") == 1) { + $path = substr($path, 2); + } + $path = $this->replaceBackslashes($path); + return $path; + } + + /*}}}*/ + /*{{{ public function replaceBackslashes() */ + + /** + * Convert the back slash path separators with forward slashes. + * + * @param $path Windows path with backslash path separators + * @return String Path with back slashes replaced with forward slashes. + * @access public + */ + public function replaceBackslashes($path) { + $path = str_replace("\\", "/", $path); + return $this->capitalizeDriveLetter($path); + } + /*}}}*/ + /*{{{ public function capitalizeDriveLetter() */ + + /** + * Convert the drive letter to upper case + * + * @param $path Windows path with "c:" + * @return String Path with driver letter capitalized. + * @access public + */ + public function capitalizeDriveLetter($path) { + if(strpos($path, ":") === 1) { + $path = strtoupper(substr($path, 0, 1)) . substr($path, 1); + } + return $path; + } + + /*}}}*/ + /*{{{ public function makeDirRecursive() */ + /** + * Make directory recursively. + * (Taken from: http://aidan.dotgeek.org/lib/?file=function.mkdirr.php) + * + * @param $dir Directory path to create + * @param $mode=0755 + * @return True on success, False on failure + * @access public + */ + public function makeDirRecursive($dir, $mode=0755) { + // Check if directory already exists + if (is_dir($dir) || empty($dir)) { + return true; + } + + // Ensure a file does not already exist with the same name + if (is_file($dir)) { + $this->getLogger()->debug("File already exists: " . $dir, + __FILE__, __LINE__); + return false; + } + + $dir = $this->replaceBackslashes($dir); + + // Crawl up the directory tree + $next_pathname = substr($dir, 0, strrpos($dir, "/")); + if ($this->makeDirRecursive($next_pathname, $mode)) { + if (!file_exists($dir)) { + return mkdir($dir, $mode); + } + } + + return false; + } + /*}}}*/ + /*{{{ public function getOS() */ + /** + * Returns the current OS code + * WIN - Windows, LIN -Linux, etc. + * + * @return String 3 letter code for current OS + * @access public + * @since 0.6.6 + */ + public function getOS() { + return strtoupper(substr(PHP_OS, 0, 3)); + } + /*}}}*/ + /*{{{ public function getTmpDir() */ + + public function getTmpDir() { + global $spc_config; + $OS = $this->getOS(); + switch($OS) { + case "WIN": + return $spc_config['windows_tmpdir']; + default: + return $spc_config['tmpdir']; + } + } + + /*}}}*/ + /*{{{ public function getLogger() */ + + public function getLogger($package=false) { + global $spc_config; + if(!isset($this->logger) || $this->logger == NULL) { + $this->logger =& new CoverageLogger(); + $this->logger->setLevel($spc_config["log_level"]); + } + return $this->logger; + } + + /*}}}*/ + } + + $util = new Utility(); + global $util; +?>