[ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ] [ 7 ] [ 8 ]

VFP project coverage

VFP coverage analysis is not, by nature, a project-centric activity. A single log may include information from multiple apps and multiple projects. However, sometimes you are interested primarily in the analysis of one project and its source files. For this reason, the Coverage Profiler Statistics dialog gives you an opportunity to pick a project to analyze. The engine evaluates all the files listed in that project with appropriate source code types and lets you know which files you've covered, and to what degree you've covered them.

To do this job properly, you will notice that the Profiler quickly goes through all the target workfile records to make sure they have already been marked and that all relevant statistics are available, before going on to report on Project coverage.

Yes, I said " report" , and I meant " report" <g>. The default Profiler display of project statistics uses an FRX. This report form is quite rudimentary, and it's designed for you to edit to your liking. You can either edit COV_PJX.FRX directly or change the COV_TUNE.H #DEFINE that indicates the appropriate FRX, and design a completely new report.

Project statistics are gathered in a separate workfile from other Coverage results, in this format:

CREATE CURSOR (cProject) ;
      (Hostfile c(COV_LEN_HOSTFILE), ;
      FileType c(4), ;
      Coverable n(8), ;
      Covered n(8), ;
      ObjTotal n(8), ;
      ObjHits n(8))

You'll find this line in the engine's GetProjectStatistics() method. Here is the sequence of events that takes place in the engine's ShowProjectStatistics() method:

  1. GetProjectStatistics() is called to gather the statistics.
  2. If the above is successful, DisplayProjectStatistics() is called to show the statistics to the user.

In the engine class, DisplayProjectStatistics() is abstract (empty). In the standard User Interface engine subclass, DisplayProjectStatistics() issues the REPORT FORM statement with appropriate WINDOW clauses and other behavior for this report to appear within the standard Coverage Profiler interface.

This is a perfect example of the relationship between the Coverage engine's role and the responsibilities of the display or interface subclasses. It also gives you an idea of your opportunities to create behavior that you might prefer. As you can see, along with simply modifying the report form, your Coverage subclass might:

  • add columns and calculations to the Project workfile, or perhaps eliminate some files that are not of interest, by augmenting GetProjectStatistics()
  • change the display to another report, a BROWSE, save the Project results to disk instead of displaying them, or do anything else you like, by creating an entirely different DisplayProjectStatistics() method.

Coverage in VFP 5

Can you do all this good stuff in VFP 5? The short answer is " yes" !

The standard COVERAGE.APP will run in VFP 5. Although I haven't tested extensively, I would expect most or all of the sample subclasses of both the engine and the shipping interface class, plus all the AddIns, to work in VFP 5 as well as VFP 6. (In fact, GR_STATS.SCX originally used an ActiveX control, but I re-wrote its graphical interface in pure VFP code, simply to avoid versioning problems <g>.)

To use the Coverage Profiler in VFP 5, you will need to follow these steps, first:

  1. Unpack the Coverage source into the location of your choice. In that location:
  2. COMPILE REPORT COV_PJX. This step is necessary because the default project-reporting FRX contains code and an APP re-build in VFP 5 won't recompile this code.
  3. BUILD APP COVERAGE FROM COVERAGE RECOMPILE. That's it!

COVERAGE.APP will now run in VFP 5. The VFP 5 log has a few limitations. Accordingly, the app will not show you information about FRX or LBX files in VFP 5, because code for reporting objects is mis-attributed in the VFP 5 log. You may find that a few other statistics are mis-represented. (Primarily, VFP 5 logs did not handle DODEFAULT() items very well, and will mis-attribute the execution of parentclass lines invoked by a DODEFAULT() to the child class that contained the DODEFAULT() statement. You may also notice some problems with leaf objects -- option buttons, pages, command buttons in command groups -- for which you've written code.)

However, in my experience, the inaccuracies and omissions will be minor. They do not detract from the general usefulness of the analyzing tool (or even the generated log) within VFP 5.

Although VFP 6 can run VFP 5-compiled code, you should probably BUILD … RECOMPILE separate versions of the Coverage app for VFP 5 and VFP 6 on general principles.

Also, you will only be able to analyze VFP 5-generated logs in VFP 5 and VF 6-generated logs in VFP 6. This distinction is made at runtime, not compiled time, and is intended to make sure that you know exactly what you are testing. (If you executed the code and generated the log under VFP 6, in other words, you should analyze its coverage under VFP 6 -- whether that code was compiled in VFP 5 or VFP 6 is irrelevant.)

One problem people might have with the Coverage classes under VFP 5 involves the use of the common file dialog ActiveX control for file- and font- picking. You may have different versions of this control in your system.

In most cases, whether under VFP 5 or any other product version, the Coverage classes will detect any OCX error and switch smoothly over to use of GETFILE(), PUTFILE(), and GETFONT() after the first such error. However, you can permanently dispose of the problem (or you can take care of it in any case where the OCX error is not trapped and the switch isn't automatic), by changing a #DEFINE in the COV_TUNE.H file, discussed below.

VFP Coverage and Coverage Profiler Internals

But wait -- there's more. This section will tell you quite a bit more about how the Coverage Profiler works, and also about how Coverage works within VFP. Skip this entire section, if you've had enough of these details for now, or read on to see more ways you can manipulate the Coverage Profiler features and more deeply understand the internally-produced Coverage log.

If you do skip the rest of this section, I recommend you come back and read the material on the Coverage workfiles at a later date. Understanding these cursors, and especially understanding the engine's ability to manage multiple sets of these cursors, is critical to getting maximum value from the Coverage engine.

Understand and use the COVERAGE.VCX source

The COV*.H files and the COVERAGE.VCX/VCT contain all the source you need. The wrapper program that instantiates the shipping class in COVERAGE.APP is not necessary.

You can instantiate cov_Engine (the engine class), cov_Standard (the shipping UI subclass), or additional subclasses, from the Command window, with a simple call:

* SET PATH TO the location of COVERAGE.VCX
NEWOBJECT(" cov_standard" ," coverage.vcx" )

Notice I said that you should SET PATH to COVERAGE.VCX's location. This is because the coverage classes will need to instantiate other items from this VCX later on.

If you build the above NEWOBJECT( ) line into an .APP of your own, the SET PATH will not be necessary. Not only will COVERAGE.VCX be pulled into the project, and therefore be available to the APP, but EXTERNAL statements within COVERAGE.VCX will make sure that additional pieces (such as ICO files) will come into the APP as well.

 

NB: The engine is careful to store information about the APP that instantiated it, and to use appropriate syntax to find its VCX IN a running application as necessary, since Coverge is a modaless app, it will be instantiating items when this APP is not actually running. Without the protection of an APP, however, the SET PATH statement will be necessary. Coverage subclasses in other locations will demonstrate the same behavior (they should either be built into an APP or have access to their own VCX and COVERAGE.VCX via a SET PATH statement).

Beyond cov_Engine and cov_Standard, the COVERAGE.VCX classes can be roughly divided into these groups:

  • sCov… classes, subclassed from the VFP baseclasses, that provide superclasses with standard error handling to all the Coverage members
  • the forms required in the cov_Standard interface
  • additional UI pieces (mostly subclassed from cov_ModalDialog) used in cov_Standard
  • subclasses of the common file dialog ActiveX control, used by default for file and font picking. Please see the note on ActiveX use in the section on VFP 5 Coverage for more information.

Tune the Coverage header files

The Coverage header file set has a master header file (COVERAGE.H) used by all the Coverage classes. This COVERAGE.H is simply a series of #INCLUDE statements that breaks the necessary #DEFINEs into categories.

If you are interested in localizing Coverage for use in different languages, you will want to look at the strings in COV_LOCS.H. If you are interested in the internals of the Coverage engine, you may want to take a brief look at COV_SPECS.H. However, most developers will be more interested in COV_TUNE.H, where all the constants you can reasonably and safely alter are located.

In this file, you can alter the Coverage Profiler's debugging behavior, its default code marks, the classes it instantiates, the report form used to report on projects, and a whole lot more. If you are planning to subclass the engine, I'd like to draw your attention, especially, to the use of the COV_TOPSPEED #DEFINE, which switches code marking in the engine's MarkTargetCoverage method between a method call to the MarkCodeLine() method and an in-line expression.

Additionally, you'll see the _oCoverage default public reference stored here. The Coverage engine maintains this reference itself (you don't need a wrapper program to do it). A future version of the Profiler, or a subclass that you write, might prefer to place the string that becomes the public reference to the Profiler in a new Coverage registry key.

Take a look at the Cov_SaveState class in COV_SUBS.VCX for an example of a Coverage Profiler subclass that creates new registry keys. Cov_SaveState, and the little custom object it attaches to all the forms in the Coverage formset, creates these keys to save its window positions (for the Coverage frame and each window in the frame). You may want to drop the custom object cus_SaveSate to forms in a completely different type of Coverage interface.

What's really in the log

Have you looked at what happens when you SET COVERAGE to create a log file? The result is a comma-delimited text file, looking something like this:

 

0.000,,main,8,tastrade.app,1
0.000,,dataenvironment.init,5,orders.frt,5
0.000,,frmorderentry.cbocustomer_id.click,3,ordentry.sct,7
0.000,orderentry,orderentry.refreshcustomerinfo,2,orders.vct,8
0.000,,remainingcredit,5,tastrade.dct,8
0.000,,formisobject,65,utility.fxp,3

The lines above are not consecutive lines from any one coverage log. They are just representative lines from different types of source code, in a VFP 6 log. In VFP 5 they'd look similar but the last column would be missing. In both versions, they ordinarily have some pathing information in the column containing the filename.

Here's what you get:

  1. The first column gives you the profiling (timing) information for the executed line of code.
  2. The second column supplies the object record information for VCTs, which holds code in multiple records about multiple objects, each of which can be instantiated separately. (Note: this column and the third column are a little different in VFP 5, but the effect is the same.)
  3. The third column shows you the procedure or method executing the line of code. For an APP or EXE this column shows you the name of the main program. For an FXP or other text file, or a DCT, this column shows the procedure within the file. It's the same as the filename, without extension, if the code is in the main program section of a text source file.

For object-oriented code, things get a little more complicated. Since an SCX, LBX, or FRX is considered to have one object instantiated (the DataEnvironment, in a report or label), you'll find the entire hierarchy here, even though the code-in-question may be in one of several records of the SCT. For example, in the frmorderentry.cbocustomer_id.click SCT record above, if cboCustomer_ID was a member of a frmOrderEntry class in a VCX, before this form class was added to the SCX, you may have typed code into this combobox's Click() method in the Form Designer. In this case, its method code is in the frmOrderEntry record of the SCX (identified there in a PROCEDURE cbocustomer_id.click section). On the other hand, if this combobox was dropped onto the form directly in the Form Designer, then the code is in the combobox's own record in the SCX (its code would be identified as PROCEDURE Click in that record.)

  1. The fourth column shows the line number of the executing line. For code stored in memo fields (such as DCTs or SCTs), you'll find these line numbers relative to the method or stored procedure. However, if the code comes from a text-type file (a QPX , SPX, MPX, or an FXP), the line number is relative to the entire text file, even if the code is in a procedure.
  2. The fifth column tells you the filename for this source code. The filename you see in the next-to-last column is the file storing the object (compiled) version of the source code. In some cases, such as the UTILITY.FXP line above, this information may actually be a record in the PJX rather than a separate file on disk, but the name still represents what this file would be called if it existed! (If it didn't, we'd have no chance to match it up with its uncompiled version for parsing.)
  3. The sixth column, new to VFP 6, gives you call stack information. It isn't used in the current COVERAGE.APP but the engine makes this information available to you. You can use it to examine coverage dependency, as defined earlier. Later in this paper you'll see that you can easily add to the coverage interface, or design your own -- once the engine has organized the information for you.

What's not in the log (uncoverable lines) or unattributable

There are some lines of code that VFP deliberately excludes from the coverage log. This is why the number of lines you'll see in Coverage Profiler statistics rarely matches the total number of lines in the PRG file or method. These lines must be distinguished from " coverable" lines, so they are not counted against your line totals for statistical analysis or show up with erroneous 0 times in profiling figures.

Lines that never appear in the log include:

  • comments
  • #DEFINEs
  • lines between a TEXT/ENDTEXT
  • all the segments except for the last line in a concatenated line of code
  • DEFINE CLASS/ENDDEFINE and initial property assignment statements in a programmatically-defined class, as well as PROC/ENDPROC lines
  • additional keywords such as ELSE whose execution is apparently " assumed" from context rather than actually occurring

NB: If you want to see a complete list of these line types, you can look at the Coverage engine's MarkTargetCoverage method code to see which lines are skipped over for statistical purposes. I don't suggest you actually edit this method, but you can change the display of " uncoverable" lines in the Coverage Profiler quite easily, if you like, by augmenting MarkCodeLine(). This is the engine method that sets up the actual text after the extensive dirty work is done.

Additionally there are other lines the log includes, without attributing to a particular source code file. These are primarily " interrupts" such as ON SELECTION and ON ERROR that can trigger at any point in your code. When you DEFINE a POPUP and issue ON SELECTION statements, these lines of code execute within the context of your PRG, method, or MPR, and they are coverable. The Profiler will show these lines as executing once.

Later, however, you may select that popup bar many times, executing its attached command. Each time, the log will note the execution of an " on selection" interrupt, but it will not attach that information to any source code (because, at that point, VFP has no idea in what source file the ON SELECTION statement exists).

The log and the Profiler cannot show you how many times this code executed. They will, of course, show you the code that executed in response to the trigger. For example, if you say ON SELECTION Bar 1 OF MyPop DO MyFunc, all the lines in MyFunc will be properly logged, and the Profiler will properly account for them in its marks and statistics.

But this issue of " unattributable lines" points to a second problem with the concept of " 100% dependency coverage" statistics. Although in this case you can analyze your code to see in what ways your code might be called, to include such triggers in your " total path" figure, you cannot properly attribute any single execution to these particular paths, so you can never know if these paths were covered. .

[ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ] [ 7 ] [ 8 ]