/
githubmirror
/
PowerShell
Обзор
Документация
Войти
/
githubmirror
/
PowerShell
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
test/xUnit/csharp/test_ProgressNode.cs
331 строка
14 KB
Yoshifumi
Fix progress bar rendering with double-width unicode characters (#26185)
26 июн 2026, 19:15
Не верифицирован
26 июн 2026, 19:15
7d596de
Код
Авторство
О чём код?
// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System.Collections; using System.Management.Automation; using Microsoft.PowerShell; using Xunit; namespace PSTests.Parallel { /// <summary> /// Tests for ProgressNode rendering with double-width unicode characters. /// These tests verify the fix for Issue #21293 where progress bars with /// double-width characters (Japanese, Chinese, Korean, emoji) exceeded maxWidth. /// </summary> public static class ProgressNodeTests { /// <summary> /// Verify Issue #21293 scenario - the original bug report. /// This test reproduces the exact scenario from the issue. /// Tests with edge cases around standard terminal width (80 columns). /// </summary> /// <param name="maxWidth">The maximum width in buffer cells for the progress bar rendering.</param> [Theory] [InlineData(79)] [InlineData(80)] [InlineData(81)] public static void ProgressBar_OriginalBugScenario_MustNotExceedMaxWidth(int maxWidth) { var rawUI = new TestProgressRawUI(); var record = new ProgressRecord(0, "My Status", "1/6 次の段階"); var node = new ProgressNode(1, record); node.PercentComplete = 50; node.SecondsRemaining = 120; node.Style = ProgressNode.RenderStyle.Ansi; var strCollection = new ArrayList(); node.Render(strCollection, 0, maxWidth, rawUI); var output = strCollection[0] as string; int actualWidth = ConsoleControl.LengthInBufferCells(output, 0, checkEscapeSequences: true); Assert.True( actualWidth <= maxWidth, $"Issue #21293 (width {maxWidth}): Progress bar ({actualWidth} cells) exceeds maxWidth ({maxWidth}). Output: {output}"); } /// <summary> /// Verify that progress bar respects maxWidth for Japanese text. /// </summary> /// <param name="maxWidth">The maximum width in buffer cells for the progress bar rendering.</param> [Theory] [InlineData(79)] [InlineData(80)] [InlineData(81)] public static void ProgressBar_WithDoubleWidthChars_MustNotExceedMaxWidth(int maxWidth) { var rawUI = new TestProgressRawUI(); var record = new ProgressRecord(0, "Activity", "日本語テキスト"); var node = new ProgressNode(1, record); node.PercentComplete = 50; node.Style = ProgressNode.RenderStyle.Ansi; var strCollection = new ArrayList(); node.Render(strCollection, 0, maxWidth, rawUI); var output = strCollection[0] as string; int actualWidth = ConsoleControl.LengthInBufferCells(output, 0, checkEscapeSequences: true); Assert.True( actualWidth <= maxWidth, $"Progress bar width {maxWidth}: ({actualWidth}) exceeds maxWidth ({maxWidth}). Output: {output}"); } /// <summary> /// Verify emoji handling with sufficient text length. /// </summary> /// <param name="maxWidth">The maximum width in buffer cells for the progress bar rendering.</param> [Theory] [InlineData(79)] [InlineData(80)] [InlineData(81)] public static void ProgressBar_WithEmoji_MustNotExceedMaxWidth(int maxWidth) { var rawUI = new TestProgressRawUI(); var record = new ProgressRecord(0, "Upload", "📁ファイル転送中🔄処理中📊"); var node = new ProgressNode(1, record); node.PercentComplete = 50; node.Style = ProgressNode.RenderStyle.Ansi; var strCollection = new ArrayList(); node.Render(strCollection, 0, maxWidth, rawUI); var output = strCollection[0] as string; int actualWidth = ConsoleControl.LengthInBufferCells(output, 0, checkEscapeSequences: true); Assert.True( actualWidth <= maxWidth, $"Emoji progress bar width {maxWidth}: ({actualWidth} cells) exceeds maxWidth ({maxWidth}). Output: {output}"); } /// <summary> /// Test various lengths of double-width text. /// All test cases have sufficient length to detect the bug. /// </summary> /// <param name="statusText">The status message displayed in the progress bar.</param> /// <param name="maxWidth">The maximum width in buffer cells for the progress bar rendering.</param> [Theory] [InlineData("あいうえお", 79)] [InlineData("あいうえお", 80)] [InlineData("あいうえお", 81)] [InlineData("データ処理中", 79)] [InlineData("データ処理中", 80)] [InlineData("データ処理中", 81)] [InlineData("ファイルをアップロード中", 79)] [InlineData("ファイルをアップロード中", 80)] [InlineData("ファイルをアップロード中", 81)] public static void ProgressBar_VariousDoubleWidthLengths_MustNotExceedMaxWidth(string statusText, int maxWidth) { var rawUI = new TestProgressRawUI(); var record = new ProgressRecord(0, "Test", statusText); var node = new ProgressNode(1, record); node.PercentComplete = 50; node.Style = ProgressNode.RenderStyle.Ansi; var strCollection = new ArrayList(); node.Render(strCollection, 0, maxWidth, rawUI); var output = strCollection[0] as string; int actualWidth = ConsoleControl.LengthInBufferCells(output, 0, checkEscapeSequences: true); Assert.True( actualWidth <= maxWidth, $"Status '{statusText}' width {maxWidth}: resulted in width {actualWidth} exceeding maxWidth {maxWidth}. Output: {output}"); } /// <summary> /// Verify mixed ASCII and double-width characters. /// </summary> /// <param name="maxWidth">The maximum width in buffer cells for the progress bar rendering.</param> [Theory] [InlineData(79)] [InlineData(80)] [InlineData(81)] public static void ProgressBar_MixedWidthText_MustNotExceedMaxWidth(int maxWidth) { var rawUI = new TestProgressRawUI(); var record = new ProgressRecord(0, "Process", "File_日本語_document.txt"); var node = new ProgressNode(1, record); node.PercentComplete = 50; node.Style = ProgressNode.RenderStyle.Ansi; var strCollection = new ArrayList(); node.Render(strCollection, 0, maxWidth, rawUI); var output = strCollection[0] as string; int actualWidth = ConsoleControl.LengthInBufferCells(output, 0, checkEscapeSequences: true); Assert.True( actualWidth <= maxWidth, $"Mixed-width text width {maxWidth}: resulted in width {actualWidth} exceeding maxWidth {maxWidth}. Output: {output}"); } /// <summary> /// Verify long double-width string handling and truncation. /// </summary> /// <param name="maxWidth">The maximum width in buffer cells for the progress bar rendering.</param> [Theory] [InlineData(79)] [InlineData(80)] [InlineData(81)] public static void ProgressBar_LongDoubleWidthString_MustNotExceedMaxWidth(int maxWidth) { var rawUI = new TestProgressRawUI(); var record = new ProgressRecord(0, "処理", "ファイルを処理中です今しばらくお待ちください"); var node = new ProgressNode(1, record); node.PercentComplete = 50; node.Style = ProgressNode.RenderStyle.Ansi; var strCollection = new ArrayList(); node.Render(strCollection, 0, maxWidth, rawUI); var output = strCollection[0] as string; int actualWidth = ConsoleControl.LengthInBufferCells(output, 0, checkEscapeSequences: true); Assert.True( actualWidth <= maxWidth, $"Long double-width text width {maxWidth}: resulted in width {actualWidth} exceeding maxWidth {maxWidth}. Output: {output}"); } /// <summary> /// Verify that truncation respects character boundaries. /// </summary> /// <param name="maxWidth">The maximum width in buffer cells for the progress bar rendering.</param> [Theory] [InlineData(79)] [InlineData(80)] [InlineData(81)] public static void ProgressBar_Truncation_MustRespectCharacterBoundaries(int maxWidth) { var rawUI = new TestProgressRawUI(); var record = new ProgressRecord(0, "Downloading", "ファイル処理中123456789012345678901234567890"); var node = new ProgressNode(1, record); node.PercentComplete = 50; node.Style = ProgressNode.RenderStyle.Ansi; var strCollection = new ArrayList(); node.Render(strCollection, 0, maxWidth, rawUI); var output = strCollection[0] as string; int actualWidth = ConsoleControl.LengthInBufferCells(output, 0, checkEscapeSequences: true); Assert.True( actualWidth <= maxWidth, $"Truncated progress bar width {maxWidth}: ({actualWidth} cells) exceeds maxWidth ({maxWidth}). Output: {output}"); } /// <summary> /// Test for Issue: Double closing bracket when truncating with double-width characters. /// Verifies that statusPartDisplayWidth is calculated from actual statusPart length, /// not from estimated value, to prevent progress bar from exceeding maxWidth. /// </summary> /// <param name="maxWidth">The maximum width in buffer cells for the progress bar rendering.</param> [Theory] [InlineData(79)] [InlineData(80)] [InlineData(81)] public static void ProgressBar_LongStatusWithDoubleWidth_MustNotExceedMaxWidth(int maxWidth) { var rawUI = new TestProgressRawUI(); // Very long status with emojis and mixed width characters that will be truncated var record = new ProgressRecord(0, "Long Text Test", "🚀🎯⚡💾✅ 絵文字も含めた超長文テストケース with emojis and very long text to test the truncation feature properly"); var node = new ProgressNode(1, record); node.PercentComplete = 100; node.Style = ProgressNode.RenderStyle.Ansi; var strCollection = new ArrayList(); node.Render(strCollection, 0, maxWidth, rawUI); var output = strCollection[0] as string; int actualWidth = ConsoleControl.LengthInBufferCells(output, 0, checkEscapeSequences: true); // The progress bar must not exceed maxWidth Assert.True( actualWidth <= maxWidth, $"Progress bar with long truncated status width {maxWidth}: ({actualWidth} cells) exceeds maxWidth ({maxWidth}). Output: {output}"); // Verify no double closing brackets - count ']' occurrences at end int closingBracketCount = 0; for (int i = output.Length - 1; i >= 0 && output[i] == ']'; i--) { closingBracketCount++; } Assert.True( closingBracketCount <= 1, $"Found {closingBracketCount} consecutive closing brackets at end (width {maxWidth}), expected 1. Output: {output}"); } /// <summary> /// Test activityDisplayWidth bug with small maxWidth to amplify error. /// Bug: After truncation, activityDisplayWidth is set to maxWidth/2 but actual width differs. /// </summary> /// <param name="maxWidth">The maximum width in buffer cells for the progress bar rendering.</param> [Theory] [InlineData(79)] [InlineData(80)] [InlineData(81)] public static void ExtremeTest_ActivityWidthBug_SmallMaxWidth(int maxWidth) { var rawUI = new TestProgressRawUI(); string longActivity = "日本語日本語日本語日本語日本語"; // 20 cells var record = new ProgressRecord(0, longActivity, "St"); var node = new ProgressNode(1, record); node.PercentComplete = 50; node.SecondsRemaining = 30; node.Style = ProgressNode.RenderStyle.Ansi; var strCollection = new ArrayList(); node.Render(strCollection, 0, maxWidth, rawUI); var output = strCollection[0] as string; int actualWidth = ConsoleControl.LengthInBufferCells(output, 0, checkEscapeSequences: true); Assert.True( actualWidth <= maxWidth, $"maxWidth {maxWidth} test: width {actualWidth} exceeds {maxWidth}. Output: [{output}]"); } /// <summary> /// Test surrogate pair handling with strategically positioned emojis. /// Bug: Substring(i,1) splits surrogate pairs causing width calculation errors. /// </summary> /// <param name="maxWidth">The maximum width in buffer cells for the progress bar rendering.</param> [Theory] [InlineData(79)] [InlineData(80)] [InlineData(81)] public static void ExtremeTest_SurrogatePairAtVTBoundary(int maxWidth) { var rawUI = new TestProgressRawUI(); var record = new ProgressRecord(0, "A", "X🚀Y🚀Z🚀"); var node = new ProgressNode(1, record); node.PercentComplete = 33; node.Style = ProgressNode.RenderStyle.Ansi; var strCollection = new ArrayList(); node.Render(strCollection, 0, maxWidth, rawUI); var output = strCollection[0] as string; int actualWidth = ConsoleControl.LengthInBufferCells(output, 0, checkEscapeSequences: true); Assert.True( actualWidth <= maxWidth, $"Surrogate pair test width {maxWidth}: width {actualWidth} exceeds {maxWidth}. Output: [{output}]"); } } }