# encoding: ascii
# api: powershell
# title: Get-ScriptCoverage.ps1
# description: From Windows PowerShell Cookbook (O’Reilly) by Lee Holmes
# version: 0.1
# type: script
# author: Lee Holmes
# license: CC0
# x-poshcode-id: 2164
# x-archived: 2016-03-19T00:35:03
# x-published: 2011-09-09T21:41:00
#
#
#############################################################################
##
## Get-ScriptCoverage
##
## From Windows PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
##############################################################################

<#

.SYNOPSIS

Uses conditional breakpoints to obtain information about what regions of
a script are executed when run.

.EXAMPLE

PS >Get-Content c:\temp\looper.ps1

param($userInput)

for($count = 0; $count -lt 10; $count++)
{
    "Count is: $count"
}

if($userInput -eq "One")
{
    "Got 'One'"
}

if($userInput -eq "Two")
{
    "Got 'Two'"
}

PS >$action = { c:\temp\looper.ps1 -UserInput 'One' }
PS >$coverage = Get-ScriptCoverage c:\temp\looper.ps1 -Action $action
PS >$coverage | Select Content,StartLine,StartColumn | Format-Table -Auto

Content   StartLine StartColumn
-------   --------- -----------
param             1           1
(                 1           6
userInput         1           7
)                 1          17
Got 'Two'        15           5
}                16           1

This example exercises a 'looper.ps1' script, and supplies it with some
user input. The output demonstrates that we didn't exercise the
"Got 'Two'" statement.

#>

param(
    ## The path of the script to monitor
    $Path,

    ## The command to exercise the script
    [ScriptBlock] $Action = { & $path }
)

Set-StrictMode -Version Latest

## Determine all of the tokens in the script
$scriptContent = Get-Content $path
$ignoreTokens = "Comment","NewLine"
$tokens = [System.Management.Automation.PsParser]::Tokenize(
    $scriptContent, [ref] $null) |
    Where-Object { $ignoreTokens -notcontains $_.Type }
$tokens = $tokens | Sort-Object StartLine,StartColumn

## Create a variable to hold the tokens that PowerShell actually hits
$visited = New-Object System.Collections.ArrayList

## Go through all of the tokens
$breakpoints = foreach($token in $tokens)
{
    ## Create a new action. This action logs the token that we
    ## hit. We call GetNewClosure() so that the $token variable
    ## gets the _current_ value of the $token variable, as opposed
    ## to the value it has when the breakpoints gets hit.
    $breakAction = { $null = $visited.Add($token) }.GetNewClosure()

    ## Set a breakpoint on the line and column of the current token.
    ## We use the action from above, which simply logs that we've hit
    ## that token.
    Set-PsBreakpoint $path -Line `
        $token.StartLine -Column $token.StartColumn -Action $breakAction
}

## Invoke the action that exercises the script
$null = . $action

## Remove the temporary breakpoints we set
$breakpoints | Remove-PsBreakpoint

## Sort the tokens that we hit, and compare them with all of the tokens
## in the script. Output the result of that comparison.
$visited = $visited | Sort-Object -Unique StartLine,StartColumn
Compare-Object $tokens $visited -Property StartLine,StartColumn -PassThru

## Clean up our temporary variable
Remove-Item variable:\visited
