Merge pull request #1 from KevinMarquette/develop !deploy
This is a major refactoring of the project
This commit is contained in:
48
Chronometer/Classes/Chronometer.ps1
Normal file
48
Chronometer/Classes/Chronometer.ps1
Normal 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
|
||||
}
|
||||
}
|
||||
|
||||
49
Chronometer/Classes/MonitoredScript.ps1
Normal file
49
Chronometer/Classes/MonitoredScript.ps1
Normal 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
|
||||
}
|
||||
}
|
||||
29
Chronometer/Private/Write-ScriptLine.ps1
Normal file
29
Chronometer/Private/Write-ScriptLine.ps1
Normal file
@@ -0,0 +1,29 @@
|
||||
|
||||
function Write-ScriptLine
|
||||
{
|
||||
param(
|
||||
[scriptline]
|
||||
$line,
|
||||
$WarningAt = [int]::MaxValue,
|
||||
$ErrorAt = [int]::MaxValue
|
||||
)
|
||||
|
||||
if($line)
|
||||
{
|
||||
$Color = 'Green'
|
||||
if($line.HitCount -eq 0)
|
||||
{
|
||||
$Color = 'Gray'
|
||||
}
|
||||
elseif($line.Average -ge $ErrorAt)
|
||||
{
|
||||
$Color = 'Red'
|
||||
}
|
||||
elseif($line.Average -ge $WarningAt)
|
||||
{
|
||||
$Color = 'Yellow'
|
||||
}
|
||||
|
||||
Write-Host $line.toString() -ForegroundColor $Color
|
||||
}
|
||||
}
|
||||
50
Chronometer/Public/Format-Chronometer.ps1
Normal file
50
Chronometer/Public/Format-Chronometer.ps1
Normal file
@@ -0,0 +1,50 @@
|
||||
function Format-Chronometer
|
||||
{
|
||||
<#
|
||||
.Description
|
||||
Generates a report from a Chronometer
|
||||
|
||||
.Example
|
||||
$script = ls C:\workspace\PSGraph\PSGraph -Recurse -Filter *.ps1
|
||||
$resultes = Get-Chronometer -Path $script.fullname -ScriptBlock {Invoke-Pester C:\workspace\PSGraph}
|
||||
$results | Format-Chronometer -WarnAt 20 -ErrorAt 200
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
param(
|
||||
# This is a MonitoredScript object from Get-Chronometer
|
||||
[Parameter(
|
||||
ValueFromPipeline=$true
|
||||
)]
|
||||
[MonitoredScript[]]
|
||||
$InputObject,
|
||||
|
||||
# If the average time of a command is more than this, the output is yellow
|
||||
[int]
|
||||
$WarningAt = 20,
|
||||
|
||||
#If the average time of a comamand is more than this, the output is red
|
||||
[int]
|
||||
$ErrorAt = 200
|
||||
)
|
||||
|
||||
begin {
|
||||
$green = @{ForgroundColor='green'}
|
||||
$grey = @{ForgroundColor='grey'}
|
||||
$yellow = @{ForgroundColor='grey'}
|
||||
$yellow = @{ForgroundColor='grey'}
|
||||
$yellow = @{ForgroundColor='grey'}
|
||||
}
|
||||
process
|
||||
{
|
||||
foreach($script in $InputObject)
|
||||
{
|
||||
Write-Host ''
|
||||
Write-Host "Script: $($script.Path)" -ForegroundColor Green
|
||||
Write-Host "Execution Time: $($script.ExecutionTime)" -ForegroundColor Green
|
||||
foreach($line in $script.line)
|
||||
{
|
||||
Write-ScriptLine $line -WarningAt $WarningAt -ErrorAt $ErrorAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,54 +21,30 @@ function Get-Chronometer
|
||||
$ScriptBlock
|
||||
)
|
||||
|
||||
$breakPoint = @()
|
||||
$fileMap = @{}
|
||||
$Chronometer = [Chronometer]::New()
|
||||
|
||||
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}})
|
||||
|
||||
$lines = $fileMap[$file.Path].count
|
||||
$breakPoint += Set-PSBreakpoint -Script $file -Line (1..$lines) -Action {[ScriptProfiler]::RecordExecution( $_) }
|
||||
}
|
||||
|
||||
Write-Verbose "Executing Script"
|
||||
[ScriptProfiler]::Start()
|
||||
[void] $ScriptBlock.Invoke()
|
||||
|
||||
Remove-PSBreakpoint $breakpoint
|
||||
|
||||
#$fileMap | ConvertTo-Json
|
||||
|
||||
Write-Verbose "Clearing Breapoints"
|
||||
$Chronometer.ClearBreakpoint()
|
||||
|
||||
Write-Verbose "Processing data"
|
||||
foreach($node in [ScriptProfiler]::Queue.GetEnumerator())
|
||||
{
|
||||
$record = $fileMap[$node.Breakpoint.Script][$node.Breakpoint.Line-1]
|
||||
$record.LineNumber = $node.Breakpoint.Line - 1
|
||||
$Chronometer.AddExecution($node)
|
||||
}
|
||||
|
||||
if($lastNode)
|
||||
{
|
||||
$duration = $node.ElapsedMilliseconds - $lastNode.ElapsedMilliseconds
|
||||
Write-Output $Chronometer.GetResults()
|
||||
}
|
||||
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
|
||||
}
|
||||
Write-Warning "Parsing files did not result in any breakpoints"
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -1,9 +1,24 @@
|
||||
#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($folder in @('classes', 'private', 'public','includes'))
|
||||
foreach($class in $classList)
|
||||
{
|
||||
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
|
||||
if(Test-Path -Path $root)
|
||||
@@ -13,12 +28,9 @@ foreach($folder in @('classes', 'private', 'public','includes'))
|
||||
|
||||
# dot source each file
|
||||
$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
|
||||
|
||||
# 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'
|
||||
|
||||
@@ -15,8 +15,9 @@ Place the Chronometer folder into your `$PSModulePath`. I will publish to the Po
|
||||
Provide a script file and a command to execute.
|
||||
|
||||
$path = myscript.ps1
|
||||
Get-Chronometer -Path $path -Script {. .\myscript.ps1} -OutVariable report
|
||||
$report.ToString()
|
||||
$Chronometer = Get-Chronometer -Path $path -Script {. .\myscript.ps1}
|
||||
$Chronometer | % tostring | Format-Chronometer
|
||||
|
||||
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
|
||||
|
||||
@@ -10,7 +10,8 @@ foreach($n in 1..10)
|
||||
"test string"
|
||||
sleep -Milliseconds 3
|
||||
}
|
||||
|
||||
sleep -Milliseconds 12
|
||||
sleep -Milliseconds 5
|
||||
sleep -Milliseconds 120
|
||||
$test = 1+1
|
||||
$test = 1+1
|
||||
@@ -2,10 +2,11 @@ $projectRoot = Resolve-Path "$PSScriptRoot\.."
|
||||
$moduleRoot = Split-Path (Resolve-Path "$projectRoot\*\*.psm1")
|
||||
$moduleName = Split-Path $moduleRoot -Leaf
|
||||
|
||||
Import-Module (Join-Path $moduleRoot "$moduleName.psm1") -force
|
||||
|
||||
Describe "Help tests for $moduleName" -Tags Build {
|
||||
|
||||
Import-Module (Join-Path $moduleRoot "$moduleName.psm1") -force
|
||||
|
||||
$functions = Get-Command -Module $moduleName
|
||||
$help = $functions | %{Get-Help $_.name}
|
||||
foreach($node in $help)
|
||||
|
||||
@@ -4,7 +4,7 @@ $moduleName = Split-Path $moduleRoot -Leaf
|
||||
|
||||
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
|
||||
$testCase = $scripts | Foreach-Object{@{file=$_}}
|
||||
@@ -19,6 +19,18 @@ Describe "General project validation: $moduleName" -Tags Build {
|
||||
$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" {
|
||||
{Import-Module (Join-Path $moduleRoot "$moduleName.psm1") -force } | Should Not Throw
|
||||
}
|
||||
|
||||
@@ -2,14 +2,33 @@ $projectRoot = Resolve-Path "$PSScriptRoot\.."
|
||||
$moduleRoot = Split-Path (Resolve-Path "$projectRoot\*\*.psd1")
|
||||
$moduleName = Split-Path $moduleRoot -Leaf
|
||||
|
||||
Import-Module (Join-Path $moduleRoot "$moduleName.psm1") -force
|
||||
|
||||
Describe "Basic unit tests" -Tags Build {
|
||||
|
||||
Import-Module (Join-Path $moduleRoot "$moduleName.psm1") -force
|
||||
|
||||
Context "Function: Get-Chronometer" {
|
||||
it "Does not throw" {
|
||||
# Get-Chronometer -Path ScratchFiles\example.ps1 -Script {"Test"}
|
||||
{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
|
||||
}
|
||||
}
|
||||
|
||||
Context "Function: Format-Chronometer" {
|
||||
|
||||
it "Does not throw" {
|
||||
{$null | Format-Chronometer } | Should Not Throw
|
||||
}
|
||||
|
||||
it "Can process a result object without throwing" {
|
||||
$results = Get-Chronometer -Path $PSScriptRoot\..\ScratchFiles\example.ps1 -Script {. "$PSScriptRoot\..\ScratchFiles\example.ps1"}
|
||||
$results | Format-Chronometer *>&1 | Should Not BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
InModuleScope $moduleName {
|
||||
@@ -18,6 +37,12 @@ Describe "Basic unit tests" -Tags Build {
|
||||
it "Creates an Object" {
|
||||
{[ScriptLine]::New()} | Should Not Throw
|
||||
}
|
||||
it "ToString()" {
|
||||
{[ScriptLine]::New().toString()} | Should Not Throw
|
||||
}
|
||||
it "Creates an Object" {
|
||||
{[ScriptLine]::New().AddExecutionTime(1)} | Should Not Throw
|
||||
}
|
||||
}
|
||||
|
||||
Context "Class: ScriptProfiler" {
|
||||
@@ -25,7 +50,23 @@ Describe "Basic unit tests" -Tags Build {
|
||||
it "Creates an Object" {
|
||||
{[ScriptProfiler]::New()} | Should Not Throw
|
||||
}
|
||||
it "Start()" {
|
||||
{[ScriptProfiler]::Start()} | Should Not Throw
|
||||
}
|
||||
}
|
||||
|
||||
Context "Class: MonitoredScript" {
|
||||
it "Creates an object" {
|
||||
{[MonitoredScript]::New()} | Should Not Throw
|
||||
}
|
||||
|
||||
it "SetScript()" {
|
||||
pushd $projectRoot
|
||||
$monitor = [MonitoredScript]::New()
|
||||
{$monitor.SetScript(".\scratchfiles\example.ps1")} | Should Not Throw
|
||||
popd
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ Task Init {
|
||||
Task UnitTests -Depends Init {
|
||||
$lines
|
||||
'Running quick unit tests to fail early if there is an error'
|
||||
$TestResults = Invoke-Pester -Path $ProjectRoot\Tests\*unit* -PassThru -Tag Build -Show Failed
|
||||
$TestResults = Invoke-Pester -Path $ProjectRoot\Tests\*unit* -PassThru -Tag Build
|
||||
|
||||
if($TestResults.FailedCount -gt 0)
|
||||
{
|
||||
@@ -47,7 +47,7 @@ Task Test -Depends UnitTests {
|
||||
"`n`tSTATUS: Testing with PowerShell $PSVersion"
|
||||
|
||||
# Gather test results. Store them in a variable and file
|
||||
$TestResults = Invoke-Pester -Path $ProjectRoot\Tests -PassThru -OutputFormat NUnitXml -OutputFile "$ProjectRoot\$TestFile" -Tag Build -Show Failed
|
||||
$TestResults = Invoke-Pester -Path $ProjectRoot\Tests -PassThru -OutputFormat NUnitXml -OutputFile "$ProjectRoot\$TestFile" -Tag Build
|
||||
|
||||
# In Appveyor? Upload our tests! #Abstract this into a function?
|
||||
If($ENV:BHBuildSystem -eq 'AppVeyor')
|
||||
|
||||
Reference in New Issue
Block a user