From d2a360221046e609ba38cd58d2724682f9b32b81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexey=20ALERT=20Rubash=D1=91ff?= Date: Wed, 26 Aug 2026 19:10:07 +0300 Subject: [PATCH] fix: make the plan say what the run will actually do Two mismatches, both seen in a live run that declined deduplication. The plan promised an initial optimization job whatever the answer, while an optimization job is a deduplication job: the work sits inside the block that declining deduplication skips. Somebody who chose "Neither" was told about a step that could not happen. The line moves inside that branch. The plan also listed marking the drive trusted second to last, after deduplication, while the run applies it straight after formatting and before BitLocker. Read before deciding whether to proceed, that put the trusted designation after the longest and most failure-prone step instead of before it. The line moves to where the work is. The test that covered both asserted their presence and nothing else, and its title called the optimization job one of "the two steps that always run" - which was the falsehood. It is replaced by assertions about position: within the plan, and against the order the body carries the steps out in, since those two lists drifting apart is the defect. Closes #101 Co-Authored-By: Claude Opus 5 --- dev_drive.Tests.ps1 | 67 ++++++++++++++++++++++++++++++++++++++++++--- dev_drive.ps1 | 6 ++-- 2 files changed, 67 insertions(+), 6 deletions(-) diff --git a/dev_drive.Tests.ps1 b/dev_drive.Tests.ps1 index 4776a86..7e2b7f7 100644 --- a/dev_drive.Tests.ps1 +++ b/dev_drive.Tests.ps1 @@ -1315,14 +1315,73 @@ Describe 'Format-CreationPlan' { (Format-CreationPlan -Answers $answers).Text -join "`n" | Should -Not -Match 'claims all of its space up front' } - It 'ends every mode with the two steps that always run' { + It 'names the trusted designation in every mode, where the run actually applies it' { + # It used to be listed second to last, after deduplication, while the run applies it right + # after formatting - so the plan told the reader it came after the longest step, not before. foreach ($mode in 'FreeSpace', 'ShrinkDrive', 'Vhdx') { - $plan = (Format-CreationPlan -Answers (New-PlanAnswerSet -Mode $mode)).Text -join "`n" - $plan | Should -Match '\* Mark Dev Drive as trusted for Windows Defender performance' - $plan | Should -Match '\* Run initial optimization job to prepare the drive' + $plan = @((Format-CreationPlan -Answers (New-PlanAnswerSet -Mode $mode)).Text) + $trustAt = [array]::FindIndex($plan, [Predicate[string]] { $args[0] -match '^\* Mark Dev Drive as trusted' }) + $bitLockerAt = [array]::FindIndex($plan, [Predicate[string]] { $args[0] -match '^\* (Enable|Skip) BitLocker' }) + $nameAt = [array]::FindIndex($plan, [Predicate[string]] { $args[0] -match '^\* Name the Dev Drive' }) + $trustAt | Should -BeGreaterThan $nameAt + $trustAt | Should -BeLessThan $bitLockerAt } } + It 'lists the trusted designation in the order the body carries it out' { + # The plan and the body are two lists that have to agree; comparing the plan against a + # literal order is what let them drift apart in the first place. + $ast = [System.Management.Automation.Language.Parser]::ParseFile($script:ScriptPath, [ref]$null, [ref]$null) + # The call, not the phrase: two pieces of retry advice quote the same fsutil line earlier in + # the file, and matching text would have found one of those instead. + $trust = @($ast.FindAll({ + param($node) + $node -is [System.Management.Automation.Language.CommandAst] -and + $node.GetCommandName() -eq 'fsutil' -and + $node.Extent.Text -match 'devdrv trust' + }, $true)) + $trust.Count | Should -Be 1 + $format = @($ast.FindAll({ + param($node) + $node -is [System.Management.Automation.Language.CommandAst] -and + $node.GetCommandName() -eq 'Format-Volume' + }, $true)) + $format.Count | Should -Be 1 + $bitLockerAt = (Get-Content -Path $script:ScriptPath -Raw).IndexOf('BitLocker setup for $devLetterColon') + $trust[0].Extent.StartOffset | Should -BeGreaterThan $format[0].Extent.StartOffset + $bitLockerAt | Should -BeGreaterThan $trust[0].Extent.StartOffset + } + + It 'promises the initial optimization job only where deduplication is set up' { + # An optimization job is a deduplication job: the work sits inside the block that declining + # deduplication skips, so promising it to somebody who declined describes an impossible run. + $answers = New-PlanAnswerSet -Mode 'Vhdx' + $answers.SkipDeduplication = $false + (Format-CreationPlan -Answers $answers).Text -join "`n" | + Should -Match '\* Run initial optimization job to prepare the drive' + $answers.SkipDeduplication = $true + (Format-CreationPlan -Answers $answers).Text -join "`n" | + Should -Not -Match 'optimization job' + } + + It 'keeps the optimization promise inside the branch, not merely near it' { + # A proximity check is not a branch check: ask the tree which branch body the line sits in. + $ast = [System.Management.Automation.Language.Parser]::ParseFile($script:ScriptPath, [ref]$null, [ref]$null) + $guards = @($ast.FindAll({ + param($node) + $node -is [System.Management.Automation.Language.IfStatementAst] -and + $node.Clauses[0].Item1.Extent.Text -eq '$Answers.SkipDeduplication' + }, $true)) + $guards.Count | Should -Be 1 + $content = Get-Content -Path $script:ScriptPath -Raw + $at = $content.IndexOf("'* Run initial optimization job to prepare the drive'") + $at | Should -BeGreaterThan 0 + # The else body, because the guard asks whether deduplication is being skipped. + $elseBody = $guards[0].ElseClause.Extent + $at | Should -BeGreaterThan $elseBody.StartOffset + $at | Should -BeLessThan $elseBody.EndOffset + } + It 'gives each kind of line the colour that kind is printed in' { # Not merely "a colour": the warning that turns yellow into white stops being a warning. $answers = New-PlanAnswerSet -Mode 'ShrinkDrive' diff --git a/dev_drive.ps1 b/dev_drive.ps1 index fd6e7b9..fa7feaf 100644 --- a/dev_drive.ps1 +++ b/dev_drive.ps1 @@ -1658,6 +1658,8 @@ function Format-CreationPlan { } $lines += New-PlanLine -Text "* Name the Dev Drive $($Answers.DevDriveLabel)" + # Where the run does it: straight after formatting, and before the longest step it takes. + $lines += New-PlanLine -Text '* Mark Dev Drive as trusted for Windows Defender performance' if ($Answers.SkipBitLocker) { $lines += New-PlanLine -Text '* Skip BitLocker encryption' @@ -1683,10 +1685,10 @@ function Format-CreationPlan { -WeeklyJob:$Answers.DedupWeeklyJob)) { $lines += New-PlanLine -Text "* $($summary.Trim())" } + # An optimization job is a deduplication job: skip one, skip both. + $lines += New-PlanLine -Text '* Run initial optimization job to prepare the drive' } - $lines += New-PlanLine -Text '* Mark Dev Drive as trusted for Windows Defender performance' - $lines += New-PlanLine -Text '* Run initial optimization job to prepare the drive' $lines += New-PlanLine -Text '' -Colour Cyan $lines += New-PlanLine -Text $rule -Colour Cyan return $lines