Microsoft & .NET .NET Happy Breakpoints for Testing!

Happy Breakpoints for Testing!

by John Robbins of Wintellect

The other day I was testing a bunch of code for my upcoming book Debugging Microsoft .NET and Windows Applications (Microsoft Press) and found myself wishing there was a way to set breakpoints on all the functions in a particular source file. In order to initially verify that my unit test was actually doing anything worthwhile, I wanted to at least ensure I was executing each method in a particular file.

Since I was working with a new set of code, I was doing the initial testing and scrolling like mad so I could click in the margin next to each function to get that breakpoint set. After I’d scrolled half way through the file, I was cursing at myself thinking that there had to be a better way. After I got done with the initial set of testing, I simply had to take a look at finding a way to automate setting breakpoints for a file no matter what language I was using. At the rate I was going, I was going to wear the bottom off my poor optical mouse scrolling all over the place. Fortunately, the very cool extensibility model with Visual Studio .NET actually made it relatively easy to achieve my goal. Even better was instead of grinding through and doing an Add-In, it was something that was easily accomplished with a macro. The icing on the cake is that with the code at the end of this article, you can set, and more importantly, remove, those function breakpoints without messing up any of your carefully set existing breakpoints!

There’s two pieces of work necessary to achieve the goal. The first is to figure out how to find each function/method in a file. The second part is setting and removing the breakpoints. Finding the particular functions in a source file can be a daunting task involving parsers and all sorts of weird technologies such as FLEX and YACC. If you’ve ever worked on real parsers you know they are extremely hard to do. In fact, since the premise behind .NET is the language independence, there’s no telling how many languages you might have to write parsers for simply to find the function locations. While it would be nice to spend the next five years working on parsers, I just wanted a simple utility!

The good news is that Visual Studio .NET does all the heavy lifting for you and essentially hands you the parse tree for the current document through a very clean interface. If you search the MSDN for “Automation Object Model Chart” you’ll find the chart that shows you a set of very cool objects such as called CodeElement, CodeFunction, CodeClass, and CodeEnum. By enumerating and recursing these elements you’ll get the complete layout of what’s in a particular source file. Keep in mind that the source file enumeration is only available on files that are part of the open project. Given the fact that this works for all languages is such a huge feature I’m sure people will be doing all sorts of very cool tools that we always wished for in the past but didn’t have the time to write a complete parser.

If something as complicated as the parsing is already done for you, having complete access to the Debugger object to set or clear breakpoints is almost anticlimactic. The algorithm for setting breakpoints on all function entry is the following:

Get the code elements for the active document in the project
for each code element
{
    Is the element a Namespace, Class or Struct?
    {
        Recurse the child elements
    }
    Is the element a function or property?
    {
        Get the line where this element starts
        Set a file and line breakpoint at that location
    }
}

One thing I want to point out about SetBreakpointsOnAllCurrentDocFunctions is that after you run it, you’ll see the breakpoints set, but they might look like they are in the wrong place. For example, in a .NET program the breakpoint can be sitting on an attribute before the function. That’s perfectly fine as once you start debugging the debugger does the exact right thing and moves the breakpoint down to the first executing line of the function.

After I whipped up the first version of the SetBreakpointsOnAllCurrentDocFunctions macro, it ran just like I expected. However, further testing showed up some problems, not in my code, but in Visual Studio .NET that I need to make you aware of. The worst problem is with C++ header files. For some reason, nearly everything in the file is marked as a function and the starting and ending points for the elements don’t relate too reality in the file. I played around with it quite a bit and considered not processing header files, but since many people do put inline functions, I decided against it. What you’ll see are a bunch of breakpoints on empty lines and in comments but the good news is that you can forget about them because the debugger will ignore them as they can’t be set.

The second issue I found was that some C++ source files are not properly parsed by the environment and might be missing a function or two in the code model. In those cases, there’s nothing you can do to get the actual function unless you want to grind through the file yourself. The good news is that it’s not something you’ll run into very much. For those of you doing primarily .NET development everything lines up perfectly with Visual Basic .NET and C#.

The last issue I ran into was what got me thinking that I needed a way to easily remove any breakpoints put in by SetBreakpointsOnAllCurrentDocFunctions. If you click on the red dot breakpoint marker in the source file, you’ll find that it will never toggle off. You can clear the breakpoint by either right clicking on it and selecting Remove from the context menu or clearing it from the Breakpoint window.

If I was going to be setting all these breakpoints automatically, I simply had to have a way to clear them out. While I could have grabbed the breakpoints collection from the Debugger object and wiped it clear, having a macro remove your carefully placed breakpoints isn’t that useful. In reading about the Breakpoint object, I saw that Microsoft was really thinking ahead and gave us a Tag property where we could squirrel away a user defined string! All I had to do was uniquely identify any breakpoints I set and they’d be a piece of cake to remove. The one worry I had was that the Tag field wouldn’t have been saved between sessions, but a little experimentation proved it was. For the tag, I use the filename as part of it so when you run the RemoveBreakpointsOnAllCurrentDocFunctions macro it only removes the breakpoints put in by SetBreakpointsOnAllCurrentDocFunctions for the active file and leaves any others you set in that file alone.

Armed with SetBreakpointsOnAllCurrentDocFunctions and RemoveBreakpointsOnAllCurrentDocFunctions I’ve found that my testing is going easier because fewer than 200 lines of macro code quickly automate something I was doing manually all the time. Now you can easily find out if all the functions in a file are being called. As you hit each function, clear the breakpoint. At the end of the run, you’ll see exactly which functions haven’t been called. Good luck and crank that code coverage.

About the Author

John Robbins is the co-founder of Wintellect (http://www.wintellect.com), a consulting, debugging, and education firm that helps client’s ship better code faster. He is also the author of Debugging Microsoft .NET and Windows Applications (Microsoft Press) as well as the Bugslayer columnist for MSDN Magazine. Before founding Wintellect, John was an architect and product manager at NuMega Technologies for products such as BoundsChecker, TrueTime, and TrueCoverage. Prior to joining the software world, John was a Paratrooper and Green Beret in the U. S. Army.


”””””””””””””””””””””””””””””””””””
‘ BreakPoints
Module

‘ John Robbins
– Wintellect – http://www.wintellect.com

‘ A module
that will set and clear breakpoints at the entry point of
‘ all
functions in the current source file. 
What’s even cooler is
‘ that this
code will not screw up breakpoints you already have set!

Additionally, when removing breakpoints, it will only remove the

‘ breakpoints
put for the current source file.

‘ There are
some caveats:
‘ 0.  The breakpoints set by the
SetBreakpointsOnAllCurrentDocFunctions
     macro show up as you’d
expect in the source windows as a red dot
     next to the line where
they were set.  However, you can
click on
     that dot all day long
as it will not clear it.  Either
run
    
RemoveBreakpointsOnAllCurrentDocFunctions or clear them from
the
     Breakpoints
window.  This seems to be a bug in
the IDE.
‘ 1.  There’s a bug in the CodeModel for C++
header files.  Pretty
much
     anything in one gets
called a function.  There’s no clean
way to
     double check, short of
parsing the file yourself, if the
     TextPoint values are
real.  If you run this on a header,
you’ll
     get breakpoints all
over the place.  Fortunately, the
debugger
     is smart enough to
ignore them.
‘ 2.  The breakpoints are set at what the
CodeElement.StartPoint
     property says is the
first line.  This can be at the
start of an
     attribute or
something.  Don’t worry, the
debugger does the right
     thing and moves the
breakpoint to the first executable line ‘
     inside the
function.  (Go Microsoft!)  If a .NET method is
empty,
     the breakpoint is set
on the end of the function.
‘ 3.  There’s an odd bug you might run into
when debugging this code.
     After setting the
breakpoint, I access it to set the Tag field so
     I can identify which
breakpoints this macro set.  When
debugging,
     that access seems to
cause a Null Reference exception in some
     cases.  However, if you don’t set breakpoints,
it will run fine.
‘ 4.  In some C++ source files, the CodeModel
occasionally does not
     have a function or two
that’s shown in the code window. 
Since
     you can’t get them,
you can’t set breakpoints on them.
‘ 5.  The active document returned from
DTE.ActiveDocument is odd.
     It’s the last code
document that had focus.  This can
mean you’re
     looking at the Start
Page, but setting breakpoints on something
     hidden.  These macros force you to have the
cursor in a real code
     window before they
will run.


‘ Version 1.0
– August 28, 2002
””””””””””””””””””””””””””””””””””’

Imports EnvDTE
Imports
System.Diagnostics
Imports
System.Collections
 
Public Module BreakPoints
 
    Const k_ConstantTagVal As String =
“Wintellect Rocks “
 
    Public Sub
SetBreakpointsOnAllCurrentDocFunctions()
 
       
‘ Get the current source file name doing all
the checking.
       
Dim
CurrDoc As Document =
GetCurrentDocument()
       
If (CurrDoc Is Nothing) Then
           
Exit Sub
       
End If
 
       
‘ Get the source file name and build up the
tag value.
       
Dim SrcFile As String =
CurrDoc.FullName
       
Dim TagValue As String =
BuildTagValue(CurrDoc)



       
‘ While I might have a document, I still need
to check this
       
‘ is one I can get a code model
from.
       
Dim FileMod As FileCodeModel = 
                                
CurrDoc.ProjectItem.FileCodeModel
       
If (FileMod Is Nothing) Then
           
MsgBox(“Unable to get code model from document.”, _
                   
MsgBoxStyle.OKOnly, _
                   
k_ConstantTagVal)
           
Exit Sub
       
End If
 
       
‘ Everything’s lined up to
enumerate!
       
ProcessCodeElements(FileMod.CodeElements, SrcFile,
TagValue)
 
    End Sub
 
    Private Sub
ProcessCodeElements(ByVal Elems As CodeElements, _
                                   
ByVal SrcFile As String,
_
                                   
ByVal TagValue As String)
 
       
‘ Look at each item in this
collection.
       
Dim CurrElem As CodeElement
       
For Each CurrElem In
Elems
 
           
‘ If I’m looking at a class, struct or
namespace, I need 
           
‘ to recurse.
           
If (vsCMElement.vsCMElementNamespace =
CurrElem.Kind) Or _
               
(vsCMElement.vsCMElementClass = CurrElem.Kind) Or _
               
(vsCMElement.vsCMElementStruct = CurrElem.Kind) Then
 
               
‘ This is kinda odd.  Some CodeElements use a
Children
                ‘ property to
get sub elements while others use 
               
‘ Members.
               
Dim SubCodeElems As CodeElements = Nothing
 
               
Try
                   
SubCodeElems = CurrElem.Children
               
Catch
                   
Try
                       
SubCodeElems = CurrElem.Members
                   
Catch
                       
SubCodeElems = Nothing
                   
End Try
               
End Try
 
               
If (Not
(SubCodeElems Is Nothing)) Then
                    If
(SubCodeElems.Count > 0) Then
                       
ProcessCodeElements(SubCodeElems, _
                                           
SrcFile, _
                                           
TagValue)
                   
End If
               
End If
            ElseIf (CurrElem.Kind = _
                       
vsCMElement.vsCMElementFunction) Or
_
                  
(CurrElem.Kind = _
                       
vsCMElement.vsCMElementProperty) Then
               
‘ Interestingly, Attributed COM component
attributes 
               
‘ show up broken out into their
functions.  The
only
               
‘ thing is that their StartPoint property is
invalid
               
‘ and throws an exception when
accessed.
               
Dim TxtPt As TextPoint
                Try
                   
TxtPt = CurrElem.StartPoint
               
Catch
                   
TxtPt = Nothing
               
End Try
 
               
If (Not
(TxtPt Is Nothing)) Then
                   
Dim LineNum As Long =
TxtPt.Line
                    Dim Bps As
EnvDTE.Breakpoints
 
                   
‘ Plop in one of my
breakpoints.
                   
Bps = DTE.Debugger.Breakpoints.Add(File:=SrcFile, _
                                                     
Line:=LineNum)
                   
‘ Get the BP from the collection and set the
tag
                   
‘ property so I can find the ones I
set.
                   
Try
                       
‘ There’s some sort of bug here.  If you debug
                       
‘ through this with the VSA debugger, it
fails
                       
‘ (0x8004005’s) on accessing the
breakpoints 
                       
‘ collection occasionally.  However, if
you 
                       
‘ run it, life is good.
Whateva!
                       
Dim Bp As EnvDTE.Breakpoint
                       
For Each Bp In
Bps
                           
Bp.Tag = TagValue
                       
Next
                   
Catch
                   
End Try
               
End If
           
End If
       
Next
 
    End Sub
 
    Public Sub
RemoveBreakpointsOnAllCurrentDocFunctions()
       
‘ This is a much simpler function since I set
the tag value on
       
‘ the breakpoints, I can remove them simply by
screaming 
       
‘ through all BPs and removing
those.
       
Dim
CurrDoc As Document =
GetCurrentDocument()
       
If (CurrDoc Is Nothing) Then
           
Exit Sub
       
End If
 
       
Dim TagValue As String =
BuildTagValue(CurrDoc)
 
       
Dim CurrBP As EnvDTE.Breakpoint
       
For Each CurrBP In
DTE.Debugger.Breakpoints
           
If (CurrBP.Tag = TagValue) Then
               
CurrBP.Delete()
           
End If
       
Next
    End Sub
 
    Private Function
GetCurrentDocument() As
Document
       
‘ Check to see if a project or solution is
open.  If not,
you
       
‘ can’t get at the code model for the
file.
       
Dim Projs As System.Array =
DTE.ActiveSolutionProjects
       
If (Projs.Length = 0) Then
           
MsgBox(“You must have a project open.”, _
                   
MsgBoxStyle.OKOnly, _
                   
k_ConstantTagVal)
           
GetCurrentDocument = Nothing
           
Exit Function
       
End If
 
       
‘ Getting the active document is a little
odd.  
       
‘ DTE.ActiveDocument will return the active
code document, but
       
‘ it might not be the real ACTIVE window.  It’s quite 
       
‘ disconcerting to see macros working on a
document when you’re
       
‘ looking at the Start Page.  Anyway, I’ll ensure the
active 
       
‘ document is really the active
window.
       
Dim CurrWin As Window = DTE.ActiveWindow
       
Dim
CurrWinDoc As Document =
CurrWin.Document
       
Dim CurrDoc As Document = DTE.ActiveDocument
 
       
‘ Gotta play
the game to keep from null ref exceptions in the 
       
‘ real active doc check
below.
       
Dim WinDocName As String =
“”
       
If Not
(CurrWinDoc Is Nothing) Then
           
WinDocName = CurrWinDoc.Name
       
End If
       
Dim DocName As String =
“x”
       
If Not
(CurrDoc Is Nothing) Then
           
DocName = CurrDoc.Name
       
End If
 
       
If ((CurrWinDoc Is Nothing) And _
           
(WinDocName <> DocName)) Then
           
MsgBox(“The active cursor is not in a code document.”, _
                   
MsgBoxStyle.OKOnly, _
                   
k_ConstantTagVal)
           
GetCurrentDocument = Nothing
           
Exit Function
       
End If
 
       
‘ While I might have a document, I still need
to check this is
       
‘ one I can get a code model
from.
       
Dim FileMod As FileCodeModel = 
                                  
CurrDoc.ProjectItem.FileCodeModel
       
If (FileMod Is Nothing) Then
           
MsgBox(“Unable to get code model from document.”, _
                   
MsgBoxStyle.OKOnly, _
                   
k_ConstantTagVal)
           
GetCurrentDocument = Nothing
           
Exit Function
       
End If
 
       
GetCurrentDocument = CurrDoc
    End Function
 
    Private Function
BuildTagValue(ByVal Doc As Document) As
String
       
BuildTagValue = k_ConstantTagVal + Doc.FullName
    End Function
 
End
Module

# # #

Latest Posts

Related Stories