This commit is contained in:
KevinMarquette
2017-02-05 00:01:44 -08:00
8 changed files with 170 additions and 56 deletions

View File

@@ -0,0 +1,48 @@
class Chronometer
{
[hashtable]$FileMap = @{}
$Breakpoint = @()
[void]AddBreakpoint([string[]]$Path)
{
foreach($file in (Resolve-Path $Path -ea 0))
{
$script = [MonitoredScript]@{Path=$file.Path}
$lines = $script.SetScript($file)
$this.fileMap[$file.Path] = $script
$breakpointParam = @{
Script = $file
Line = (1..$lines)
Action = {[ScriptProfiler]::RecordExecution( $_) }
}
$this.breakPoint += Set-PSBreakpoint @breakpointParam
}
}
[void]ClearBreakpoint()
{
if($this.Breakpoint -ne $null -and $this.Breakpoint.count -gt 0)
{
Remove-PSBreakpoint -Breakpoint $this.Breakpoint
}
}
[void] AddExecution([hashtable]$Execution)
{
$script = $Execution.Breakpoint.Script
if($this.FileMap.ContainsKey($script))
{
# Each script tracks it's own execution times
$this.FileMap[$script].AddExecution($Execution)
}
}
[MonitoredScript[]] GetResults()
{
return $this.FileMap.Values
}
}

View File

@@ -0,0 +1,49 @@
class MonitoredScript
{
[string]$Path
[System.Collections.Generic.List[ScriptLine]]$Line
hidden $lastNode = $null
hidden $lastRecord = $null
[float]$ExecutionTime = 0
[int]$LinesOfCode = 0
MonitoredScript()
{
$this.Line =New-Object 'System.Collections.Generic.List[ScriptLine]'
}
[int] SetScript([string]$Path)
{
Get-Content -Path $Path | %{ $this.Line.Add( [ScriptLine]@{text=$_;path=$path})}
$this.LinesOfCode = $this.Line.Count
return $this.LinesOfCode
}
[void] AddExecution([hashtable]$node)
{
# Line numbers start at 1 but the array starts at 0
$lineNumber = $node.Breakpoint.Line - 1
$record = $this.Line[$lineNumber]
$record.LineNumber = $lineNumber
if($this.lastNode)
{
$duration = $node.ElapsedMilliseconds - $this.lastNode.ElapsedMilliseconds
}
else
{
$duration = $node.ElapsedMilliseconds
}
if($this.lastRecord)
{
$this.lastRecord.AddExecutionTime($duration)
$this.ExecutionTime += $duration
}
$this.lastRecord = $record
$this.lastNode = $node
}
}

View File

@@ -21,54 +21,30 @@ function Get-Chronometer
$ScriptBlock $ScriptBlock
) )
$breakPoint = @() $Chronometer = [Chronometer]::New()
$fileMap = @{}
foreach($file in (Resolve-Path $Path -ea 0)) Write-Verbose "Setting breapoints"
$Chronometer.AddBreakpoint($Path)
if($Chronometer.breakPoint -ne $null)
{ {
$fileMap[$file.Path] = @( Get-Content -Path $file | %{[ScriptLine]@{text=$_;path=$file.path}}) Write-Verbose "Executing Script"
[ScriptProfiler]::Start()
[void] $ScriptBlock.Invoke()
$lines = $fileMap[$file.Path].count Write-Verbose "Clearing Breapoints"
$breakPoint += Set-PSBreakpoint -Script $file -Line (1..$lines) -Action {[ScriptProfiler]::RecordExecution( $_) } $Chronometer.ClearBreakpoint()
Write-Verbose "Processing data"
foreach($node in [ScriptProfiler]::Queue.GetEnumerator())
{
$Chronometer.AddExecution($node)
}
Write-Output $Chronometer.GetResults()
} }
else
[ScriptProfiler]::Start()
[void] $ScriptBlock.Invoke()
Remove-PSBreakpoint $breakpoint
#$fileMap | ConvertTo-Json
foreach($node in [ScriptProfiler]::Queue.GetEnumerator())
{ {
$record = $fileMap[$node.Breakpoint.Script][$node.Breakpoint.Line-1] Write-Warning "Parsing files did not result in any breakpoints"
$record.LineNumber = $node.Breakpoint.Line - 1
if($lastNode)
{
$duration = $node.ElapsedMilliseconds - $lastNode.ElapsedMilliseconds
}
else
{
$duration = $node.ElapsedMilliseconds
}
if($lastRecord)
{
$lastRecord.AddExecutionTime($duration)
}
$lastRecord = $record
$lastNode = $node
}
foreach($script in $fileMap.Keys)
{
foreach($line in $fileMap[$script])
{
Write-Output $line
}
} }
} }

View File

@@ -1,9 +1,24 @@
#Requires -Version 5.0 #Requires -Version 5.0
[cmdletbinding()]
param()
Write-Verbose "Importing Functions" Write-Verbose $PSScriptRoot
Write-Verbose 'Import Classes in order because of dependencies'
$classList = @(
'ScriptLine',
'ScriptProfiler',
'MonitoredScript',
'Chronometer'
)
# Import everything in sub folders folder foreach($class in $classList)
foreach($folder in @('classes', 'private', 'public','includes')) {
Write-Verbose " Class: $class"
. "$psscriptroot\classes\$class.ps1"
}
Write-Verbose 'Import everything in sub folders folder'
foreach($folder in @('private', 'public','includes'))
{ {
$root = Join-Path -Path $PSScriptRoot -ChildPath $folder $root = Join-Path -Path $PSScriptRoot -ChildPath $folder
if(Test-Path -Path $root) if(Test-Path -Path $root)
@@ -13,12 +28,9 @@ foreach($folder in @('classes', 'private', 'public','includes'))
# dot source each file # dot source each file
$files | where-Object{ $_.name -NotLike '*.Tests.ps1'} | $files | where-Object{ $_.name -NotLike '*.Tests.ps1'} |
ForEach-Object{Write-Verbose $_.name; . $_.FullName} ForEach-Object{Write-Verbose $_.basename; . $_.FullName}
} }
} }
Export-ModuleMember -function (Get-ChildItem -Path "$PSScriptRoot\public\*.ps1").basename Export-ModuleMember -function (Get-ChildItem -Path "$PSScriptRoot\public\*.ps1").basename
# Hack for my build system that had a conflit with the keyword node
New-Alias -Name 'DiGraph' -Value 'Graph' -ErrorAction SilentlyContinue
Export-ModuleMember -Alias 'DiGraph'

View File

@@ -16,7 +16,8 @@ Provide a script file and a command to execute.
$path = myscript.ps1 $path = myscript.ps1
Get-Chronometer -Path $path -Script {. .\myscript.ps1} -OutVariable report Get-Chronometer -Path $path -Script {. .\myscript.ps1} -OutVariable report
$report.ToString() $report.line | % tostring
The user experience is important to me but I am working on the core logic right now. I will loop back to make it more intuitive and simple to use. The user experience is important to me but I am working on the core logic right now. I will loop back to make it more intuitive and simple to use.
## Things to know ## Things to know

View File

@@ -2,9 +2,10 @@ $projectRoot = Resolve-Path "$PSScriptRoot\.."
$moduleRoot = Split-Path (Resolve-Path "$projectRoot\*\*.psm1") $moduleRoot = Split-Path (Resolve-Path "$projectRoot\*\*.psm1")
$moduleName = Split-Path $moduleRoot -Leaf $moduleName = Split-Path $moduleRoot -Leaf
Import-Module (Join-Path $moduleRoot "$moduleName.psm1") -force
Describe "Help tests for $moduleName" -Tags Build { Describe "Help tests for $moduleName" -Tags Build {
Import-Module (Join-Path $moduleRoot "$moduleName.psm1") -force
$functions = Get-Command -Module $moduleName $functions = Get-Command -Module $moduleName
$help = $functions | %{Get-Help $_.name} $help = $functions | %{Get-Help $_.name}

View File

@@ -4,7 +4,7 @@ $moduleName = Split-Path $moduleRoot -Leaf
Describe "General project validation: $moduleName" -Tags Build { Describe "General project validation: $moduleName" -Tags Build {
$scripts = Get-ChildItem $projectRoot -Include *.ps1,*.psm1,*.psd1 -Recurse $scripts = Get-ChildItem $projectRoot -Include *.ps1,*.psm1,*.psd1 -Recurse | where fullname -notmatch 'classes'
# TestCases are splatted to the script so we need hashtables # TestCases are splatted to the script so we need hashtables
$testCase = $scripts | Foreach-Object{@{file=$_}} $testCase = $scripts | Foreach-Object{@{file=$_}}
@@ -19,6 +19,18 @@ Describe "General project validation: $moduleName" -Tags Build {
$errors.Count | Should Be 0 $errors.Count | Should Be 0
} }
It "Classes are valid" {
$classes = Get-ChildItem $projectRoot -Include *.ps1,*.psm1,*.psd1 -Recurse | where fullname -match 'classes'
# Must be imported togehter incase they depend on each other
$contents = Get-Content -Path $classes.FullName | Out-String
$errors = $null
$null = [System.Management.Automation.PSParser]::Tokenize($contents, [ref]$errors)
$errors.Count | Should Be 0
}
It "Module '$moduleName' can import cleanly" { It "Module '$moduleName' can import cleanly" {
{Import-Module (Join-Path $moduleRoot "$moduleName.psm1") -force } | Should Not Throw {Import-Module (Join-Path $moduleRoot "$moduleName.psm1") -force } | Should Not Throw
} }

View File

@@ -2,14 +2,21 @@ $projectRoot = Resolve-Path "$PSScriptRoot\.."
$moduleRoot = Split-Path (Resolve-Path "$projectRoot\*\*.psd1") $moduleRoot = Split-Path (Resolve-Path "$projectRoot\*\*.psd1")
$moduleName = Split-Path $moduleRoot -Leaf $moduleName = Split-Path $moduleRoot -Leaf
Import-Module (Join-Path $moduleRoot "$moduleName.psm1") -force
Describe "Basic unit tests" -Tags Build { Describe "Basic unit tests" -Tags Build {
Import-Module (Join-Path $moduleRoot "$moduleName.psm1") -force
Context "Function: Get-Chronometer" { Context "Function: Get-Chronometer" {
it "Does not throw" { it "Does not throw" {
# Get-Chronometer -Path ScratchFiles\example.ps1 -Script {"Test"}
{Get-Chronometer -Path $PSScriptRoot\..\ScratchFiles\example.ps1 -Script {"Test"} } | Should Not Throw {Get-Chronometer -Path $PSScriptRoot\..\ScratchFiles\example.ps1 -Script {"Test"} } | Should Not Throw
} }
it "Executes a script and gives results" {
# Get-Chronometer -Path ScratchFiles\example.ps1 -Script {"Test"}
$results = Get-Chronometer -Path $PSScriptRoot\..\ScratchFiles\example.ps1 -Script {. "$PSScriptRoot\..\ScratchFiles\example.ps1"}
$results | Should Not BeNullOrEmpty
}
} }
InModuleScope $moduleName { InModuleScope $moduleName {
@@ -35,5 +42,13 @@ Describe "Basic unit tests" -Tags Build {
{[ScriptProfiler]::Start()} | Should Not Throw {[ScriptProfiler]::Start()} | Should Not Throw
} }
} }
Context "Class: MonitoredScript" {
{[MonitoredScript]::New()} | Should Not Throw
}
Context "Class: MonitoredScript" {
{[MonitoredScript]::SetScript("$projectRoot\scratchfiles\example.ps1")} | Should Not Throw
}
} }
} }