MDL-19579 code coverage - add Spike PHPCoverage lib (LGPL)

This commit is contained in:
stronk7
2009-06-23 09:23:06 +00:00
parent 86cc669c1b
commit b5ba94e5a5
36 changed files with 6431 additions and 0 deletions
+17
View File
@@ -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
+125
View File
@@ -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 <http://developer.spikesource.com/forums/viewtopic.php?t=761>
* 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 <[email protected]> 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 <title> (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.
+504
View File
@@ -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!
+156
View File
@@ -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.
+68
View File
@@ -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 [email protected].
4. Products derived from this software may not be called "PHP", nor
may "PHP" appear in their name, without prior written permission
from [email protected]. 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 [email protected].
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>.
+53
View File
@@ -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
+29
View File
@@ -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.
View File
+18
View File
@@ -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
@@ -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 <[email protected]>
* @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));
}
/*}}}*/
}
?>
+327
View File
@@ -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 <[email protected]>
*/
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");
}
?>
+305
View File
@@ -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);
}
}
}
?>
@@ -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';
?>
@@ -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 <[email protected]>
* @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;
}
/*}}}*/
}
?>
@@ -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 <[email protected]>
* @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;
}
/*}}}*/
}
?>
@@ -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 ([email protected])
* @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";
}
*/
}
?>
+217
View File
@@ -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 ([email protected])
* @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;
}
/*}}}*/
}
?>
@@ -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);
?>
@@ -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__);
}
}
?>
@@ -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__);
}
?>
@@ -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 <[email protected]>
* @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();
}
/*}}}*/
}
?>
@@ -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 <[email protected]>
* @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;
}
/*}}}*/
}
?>
@@ -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 <[email protected]>
* @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;
}
/*}}}*/
}
?>
@@ -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 <[email protected]>
* @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&nbsp;</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&nbsp;</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&nbsp;</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&nbsp;</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&nbsp;</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&nbsp;</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 = "&nbsp;";
}
$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(" ", "&nbsp;", $string);
$string = str_replace("\t", "&nbsp;&nbsp;&nbsp;&nbsp;", $string);
return $string;
}
/*}}}*/
}
?>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,6 @@
</td></tr>
</table>
</div>
</body>
</html>
@@ -0,0 +1,17 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>Spike PHPCoverage Details: %%filename%%</title>
<link href="%%style%%" rel="stylesheet" type="text/css"/>
</head>
<body>
<div id="pageBox" >
<table border="0" cellpadding="2" cellspacing="0" id="contentBox" width="100%">
<tr>
<td align="left" valign="top"><h1>Spike PHPCoverage Details: %%filename%%</h1>
</td>
</tr>
<tr>
<td>
@@ -0,0 +1,18 @@
<div id="footerBox">
<table width="800" border="0" cellspacing="0" cellpadding="0" >
<tr>
<td class="copyright" ><a class="footerlink" href="http://www.spikesource.com/projects/phpcoverage/" >Spike PHPCoverage Home</a></td>
<td class="copyright" align="right">&copy; 2004<script language="JavaScript" type="text/javascript">
var d=new Date();
yr=d.getFullYear();
if (yr!=2004) {
document.write("-"+yr);
}
</script>, SpikeSource, Inc.</td>
</tr>
</table>
<a href="mailto:[email protected]"><img src="images/spacer.gif" width="1" height="1" border="0" alt="spacer"/></a> </div>
</div>
</body>
</html>
@@ -0,0 +1,41 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>%%heading%%</title>
<link href="css/%%style%%" rel="stylesheet" type="text/css"/>
<script language="JavaScript" type="text/javascript" src="js/sort_spikesource.js"> </script>
<script language="JavaScript" type="text/javascript">
var tableSorter = null;
function initSorting() {
if (document.getElementById("table2sort")) {
tableSorter = new TableSorter(document.getElementById("table2sort"));
// initialize table
tableSorter.initTable();
// collapse all
collapseAll();
}
}
function sort(col, type) {
if (tableSorter != null) {
tableSorter.sort(col, type);
}
}
function toggleShowChildren(id) { tableSorter.togglechildren(id); }
function collapseAll() { tableSorter.collapseAllChildren(); }
function expandAll() { tableSorter.expandAllChildren(); }
</script>
</head>
<body onload="initSorting(); sort(5, 'percentage'); sort(5,'percentage');">
<div id="pageBox" >
<table id="navBox" border="0" cellpadding="0" cellspacing="0">
<tr>
<td style=""><img align="bottom" name="logo" src="images/spikesource/phpcoverage.gif" alt="home" border="0" height="82" width="800"/></td>
</td>
</tr>
</table>
Binary file not shown.

After

Width:  |  Height:  |  Size: 169 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

@@ -0,0 +1,310 @@
/*---------------------------------------------------\
| Table Sorter |
|----------------------------------------------------|
| Author: Vinay Srinivasaiah ([email protected])|
| 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;
}
@@ -0,0 +1,103 @@
<?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
class CoverageLogger {
private $level;
private $logLevels = array(
"LOG_CRITICAL",
"LOG_ERROR",
"LOG_WARNING",
"LOG_NOTICE",
"LOG_INFO",
"LOG_DEBUG"
);
public function setLevel($level) {
if(!is_numeric($level)) {
for($i = 0; $i < count($this->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("");
}
}
?>
+225
View File
@@ -0,0 +1,225 @@
<?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";
// include our dummy implementation
require_once 'CoverageLogger.php';
/**
* Utility functions
*
* @author Nimish Pachapurkar <[email protected]>
* @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:<blah>"
* @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;
?>