| 1 | function Invoke-Stress($app, $section, $command, $output, $shared) { |
| 2 | $hierarchy = '' |
| 3 | $app.GetHierarchy($section, 4, [ref]$hierarchy, 1) |
| 4 | [IO.File]::WriteAllText("$output\hierarchy.xml", $hierarchy, [Text.Encoding]::UTF8) |
| 5 | [xml]$tree = $hierarchy |
| 6 | $pages = @($tree.SelectNodes('//*[local-name()="Page"]')) |
| 7 | if ($pages.Count -ne 1) { throw 'Expected one stress-test page.' } |
| 8 | $pageId = $pages[0].GetAttribute('ID') |
| 9 | if ($command.action -eq 'prepare-stress') { |
| 10 | for ($i = 0; $i -lt $command.clients; $i++) { |
| 11 | $xml = '<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="' + $pageId + '"><one:Outline><one:Position x="48" y="' + (150 + 75 * $i) + '" z="' + ($i + 1) + '"/><one:OEChildren><one:OE><one:T><![CDATA[Native ' + $i + ':]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>' |
| 12 | $app.UpdatePageContent($xml, [DateTime]::MinValue, 1, $false) |
| 13 | } |
| 14 | return |
| 15 | } |
| 16 | $random = New-Object Random([int]$command.seed) |
| 17 | $events = New-Object IO.StreamWriter("$output\events.jsonl", $false, [Text.Encoding]::UTF8) |
| 18 | $events.AutoFlush = $true |
| 19 | try { |
| 20 | [IO.File]::WriteAllText("$output\ready", 'ready') |
| 21 | $deadline = [DateTime]::UtcNow.AddMinutes(10) |
| 22 | while (-not (Test-Path "$shared\stress-start")) { |
| 23 | if ([DateTime]::UtcNow -gt $deadline) { throw 'Stress start timed out.' } |
| 24 | Start-Sleep -Milliseconds 20 |
| 25 | } |
| 26 | for ($i = 0; $i -lt $command.operations; $i++) { |
| 27 | if (($command.PSObject.Properties.Name -contains 'maintenance') -and $command.maintenance -and $i -eq [Math]::Floor($command.operations / 2)) { |
| 28 | [IO.File]::WriteAllText("$shared\maintenance-paused-n$($command.actor)", 'paused') |
| 29 | while (-not (Test-Path "$shared\maintenance-resume")) { |
| 30 | if ([DateTime]::UtcNow -gt $deadline) { throw 'Maintenance pause timed out.' } |
| 31 | Start-Sleep -Milliseconds 100 |
| 32 | } |
| 33 | } |
| 34 | Start-Sleep -Milliseconds $random.Next(10, 100) |
| 35 | $started = [DateTime]::UtcNow.Ticks |
| 36 | $content = '' |
| 37 | $app.GetPageContent($pageId, [ref]$content, 1, 1) |
| 38 | [xml]$page = $content |
| 39 | $matches = @($page.SelectNodes('//*[local-name()="T"]') | Where-Object { |
| 40 | (Get-ParagraphText $_.InnerText).StartsWith($command.prefix) |
| 41 | }) |
| 42 | if ($matches.Count -ne 1) { throw 'Expected one stress-test paragraph.' } |
| 43 | $before = Get-ParagraphText $matches[0].InnerText |
| 44 | $token = ' [n' + $command.actor + ':' + $i + ']' |
| 45 | $start = $end = $before.Length |
| 46 | $replacement = $token |
| 47 | $bold = $italic = $false |
| 48 | if ($command.edit) { |
| 49 | $boundaries = New-Object 'Collections.Generic.List[int]' |
| 50 | for ($at = $command.prefix.Length; $at -le $before.Length; $at++) { |
| 51 | $boundaries.Add($at) |
| 52 | if ($at -lt $before.Length -and [char]::IsHighSurrogate($before[$at])) { $at++ } |
| 53 | } |
| 54 | $first = $boundaries[$random.Next($boundaries.Count)] |
| 55 | $second = $boundaries[$random.Next($boundaries.Count)] |
| 56 | $start = [Math]::Min($first, $second) |
| 57 | $end = [Math]::Max($first, $second) |
| 58 | $replacement = ' caf' + [char]0xe9 + ' ' + [char]::ConvertFromUtf32(0x1f980) + $token |
| 59 | $bold = $random.Next(2) -eq 1 |
| 60 | $italic = $random.Next(2) -eq 1 |
| 61 | } |
| 62 | $after = $before.Substring(0, $start) + $replacement + $before.Substring($end) |
| 63 | $html = [Security.SecurityElement]::Escape($after) |
| 64 | if ($command.edit) { |
| 65 | $weight = if ($bold) { 'bold' } else { 'normal' } |
| 66 | $style = if ($italic) { 'italic' } else { 'normal' } |
| 67 | $html = '<span style="font-weight:' + $weight + ';font-style:' + $style + '">' + $html + '</span>' |
| 68 | } |
| 69 | $matches[0].InnerText = $html |
| 70 | Keep-Container $page $matches[0] |
| 71 | $updateStarted = [DateTime]::UtcNow.Ticks |
| 72 | $app.UpdatePageContent($page.OuterXml, [DateTime]::MinValue, 1, $false) |
| 73 | $updated = [DateTime]::UtcNow.Ticks |
| 74 | $events.WriteLine((@{ operation=$i; before=$before; token=$token; started_ticks=$started; |
| 75 | range=@($start, $end); replacement=$replacement; bold=$bold; italic=$italic; |
| 76 | update_started_ticks=$updateStarted; updated_ticks=$updated } | ConvertTo-Json -Compress)) |
| 77 | if ($i -eq 0) { [IO.File]::WriteAllText("$output\editing", 'editing') } |
| 78 | if ($command.sync_every -gt 0 -and (($i + 1) % $command.sync_every) -eq 0) { |
| 79 | $app.SyncHierarchy($section) |
| 80 | } |
| 81 | } |
| 82 | } finally { $events.Dispose() } |
| 83 | } |