mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 07:47:54 +01:00
Tweaks.
Refactor Dialog tests to use fake driver; cleanup config - Migrated Dialog alignment/layout tests to ViewsTests using FakeDriverBase and a fake driver for improved isolation and parallelization. - Removed old DialogTests from UnitTests; new tests use explicit disposal and resource management. - Refactored Dialog static default properties to use auto-properties with [ConfigurationProperty] attributes, removing old backing fields and simplifying code. - Made DriverAssert public for cross-project test use. - Removed obsolete test migration and performance analysis markdown docs. - Improved test infrastructure for future migrations.
This commit is contained in:
@@ -14,21 +14,49 @@ namespace Terminal.Gui.Views;
|
||||
/// </remarks>
|
||||
public class Dialog : Window
|
||||
{
|
||||
private static LineStyle _defaultBorderStyle = LineStyle.Heavy; // Resources/config.json overrides
|
||||
private static Alignment _defaultButtonAlignment = Alignment.End; // Resources/config.json overrides
|
||||
private static AlignmentModes _defaultButtonAlignmentModes = AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems; // Resources/config.json overrides
|
||||
private static int _defaultMinimumHeight = 80; // Resources/config.json overrides
|
||||
private static int _defaultMinimumWidth = 80; // Resources/config.json overrides
|
||||
private static ShadowStyle _defaultShadow = ShadowStyle.Transparent; // Resources/config.json overrides
|
||||
/// <summary>
|
||||
/// Defines the default border styling for <see cref="Dialog"/>. Can be configured via
|
||||
/// <see cref="ConfigurationManager"/>.
|
||||
/// </summary>
|
||||
[ConfigurationProperty (Scope = typeof (ThemeScope))]
|
||||
public new static LineStyle DefaultBorderStyle { get; set; } = LineStyle.Heavy;
|
||||
|
||||
/// <summary>The default <see cref="Alignment"/> for <see cref="Dialog"/>.</summary>
|
||||
/// <remarks>This property can be set in a Theme.</remarks>
|
||||
[ConfigurationProperty (Scope = typeof (ThemeScope))]
|
||||
public static Alignment DefaultButtonAlignment { get; set; } = Alignment.End;
|
||||
|
||||
/// <summary>The default <see cref="AlignmentModes"/> for <see cref="Dialog"/>.</summary>
|
||||
/// <remarks>This property can be set in a Theme.</remarks>
|
||||
[ConfigurationProperty (Scope = typeof (ThemeScope))]
|
||||
public static AlignmentModes DefaultButtonAlignmentModes { get; set; } = AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the default minimum Dialog height, as a percentage of the container width. Can be configured via
|
||||
/// <see cref="ConfigurationManager"/>.
|
||||
/// </summary>
|
||||
[ConfigurationProperty (Scope = typeof (ThemeScope))]
|
||||
public static int DefaultMinimumHeight { get; set; } = 80;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the default minimum Dialog width, as a percentage of the container width. Can be configured via
|
||||
/// <see cref="ConfigurationManager"/>.
|
||||
/// </summary>
|
||||
[ConfigurationProperty (Scope = typeof (ThemeScope))]
|
||||
public static int DefaultMinimumWidth { get; set; } = 80;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether all <see cref="Window"/>s are shown with a shadow effect by default.
|
||||
/// </summary>
|
||||
[ConfigurationProperty (Scope = typeof (ThemeScope))]
|
||||
public new static ShadowStyle DefaultShadow { get; set; } = ShadowStyle.Transparent;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Dialog"/> class with no <see cref="Button"/>s.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// By default, <see cref="View.X"/>, <see cref="View.Y"/>, <see cref="View.Width"/>, and <see cref="View.Height"/> are
|
||||
/// set
|
||||
/// such that the <see cref="Dialog"/> will be centered in, and no larger than 90% of <see cref="IApplication.TopRunnableView"/>, if
|
||||
/// there is one. Otherwise,
|
||||
/// By default, the <see cref="Dialog"/> will be centered in, and no larger than 90% of
|
||||
/// <see cref="IApplication.TopRunnableView"/>, if there is one. Otherwise,
|
||||
/// it will be bound by the screen dimensions.
|
||||
/// </remarks>
|
||||
public Dialog ()
|
||||
@@ -49,7 +77,6 @@ public class Dialog : Window
|
||||
}
|
||||
|
||||
private readonly List<Button> _buttons = [];
|
||||
private bool _endInitCalled;
|
||||
|
||||
private bool _canceled;
|
||||
|
||||
@@ -65,26 +92,18 @@ public class Dialog : Window
|
||||
button.Y = Pos.AnchorEnd ();
|
||||
|
||||
// Subscribe to FrameChanged to update padding dynamically
|
||||
button.FrameChanged += Button_FrameChanged;
|
||||
button.FrameChanged += ButtonFrameChanged;
|
||||
|
||||
_buttons.Add (button);
|
||||
|
||||
// Add to Padding if it exists and EndInit has been called, otherwise add to the Dialog
|
||||
if (Padding is { } && _endInitCalled)
|
||||
if (Padding is { })
|
||||
{
|
||||
Padding.Add (button);
|
||||
UpdatePaddingBottom ();
|
||||
}
|
||||
else
|
||||
{
|
||||
Add (button);
|
||||
}
|
||||
}
|
||||
|
||||
private void Button_FrameChanged (object? sender, EventArgs e)
|
||||
{
|
||||
UpdatePaddingBottom ();
|
||||
}
|
||||
private void ButtonFrameChanged (object? sender, EventArgs e) { UpdatePaddingBottom (); }
|
||||
|
||||
// TODO: Update button.X = Pos.Justify when alignment changes
|
||||
/// <summary>Determines how the <see cref="Dialog"/> <see cref="Button"/>s are aligned along the bottom of the dialog.</summary>
|
||||
@@ -125,133 +144,34 @@ public class Dialog : Window
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the default border styling for <see cref="Dialog"/>. Can be configured via
|
||||
/// <see cref="ConfigurationManager"/>.
|
||||
/// </summary>
|
||||
|
||||
[ConfigurationProperty (Scope = typeof (ThemeScope))]
|
||||
public new static LineStyle DefaultBorderStyle
|
||||
{
|
||||
get => _defaultBorderStyle;
|
||||
set => _defaultBorderStyle = value;
|
||||
}
|
||||
|
||||
/// <summary>The default <see cref="Alignment"/> for <see cref="Dialog"/>.</summary>
|
||||
/// <remarks>This property can be set in a Theme.</remarks>
|
||||
[ConfigurationProperty (Scope = typeof (ThemeScope))]
|
||||
public static Alignment DefaultButtonAlignment
|
||||
{
|
||||
get => _defaultButtonAlignment;
|
||||
set => _defaultButtonAlignment = value;
|
||||
}
|
||||
|
||||
/// <summary>The default <see cref="AlignmentModes"/> for <see cref="Dialog"/>.</summary>
|
||||
/// <remarks>This property can be set in a Theme.</remarks>
|
||||
[ConfigurationProperty (Scope = typeof (ThemeScope))]
|
||||
public static AlignmentModes DefaultButtonAlignmentModes
|
||||
{
|
||||
get => _defaultButtonAlignmentModes;
|
||||
set => _defaultButtonAlignmentModes = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the default minimum Dialog height, as a percentage of the container width. Can be configured via
|
||||
/// <see cref="ConfigurationManager"/>.
|
||||
/// </summary>
|
||||
[ConfigurationProperty (Scope = typeof (ThemeScope))]
|
||||
public static int DefaultMinimumHeight
|
||||
{
|
||||
get => _defaultMinimumHeight;
|
||||
set => _defaultMinimumHeight = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the default minimum Dialog width, as a percentage of the container width. Can be configured via
|
||||
/// <see cref="ConfigurationManager"/>.
|
||||
/// </summary>
|
||||
[ConfigurationProperty (Scope = typeof (ThemeScope))]
|
||||
public static int DefaultMinimumWidth
|
||||
{
|
||||
get => _defaultMinimumWidth;
|
||||
set => _defaultMinimumWidth = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether all <see cref="Window"/>s are shown with a shadow effect by default.
|
||||
/// </summary>
|
||||
[ConfigurationProperty (Scope = typeof (ThemeScope))]
|
||||
public new static ShadowStyle DefaultShadow
|
||||
{
|
||||
get => _defaultShadow;
|
||||
set => _defaultShadow = value;
|
||||
}
|
||||
|
||||
|
||||
// Dialogs are Modal and Focus is indicated by their Border. The following code ensures the
|
||||
// Text of the dialog (e.g. for a MessageBox) is always drawn using the Normal Attribute.
|
||||
// Text of the dialog (e.g. for a MessageBox) is always drawn using the Normal Attribute
|
||||
// instead of the Focus attribute.
|
||||
private bool _drawingText;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool OnDrawingText ()
|
||||
{
|
||||
_drawingText = true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void OnDrewText ()
|
||||
{
|
||||
_drawingText = false;
|
||||
}
|
||||
protected override void OnDrewText () { _drawingText = false; }
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <inheritdoc/>
|
||||
protected override bool OnGettingAttributeForRole (in VisualRole role, ref Attribute currentAttribute)
|
||||
{
|
||||
if (_drawingText && role is VisualRole.Focus && Border?.Thickness != Thickness.Empty)
|
||||
if (!_drawingText || role is not VisualRole.Focus || Border?.Thickness == Thickness.Empty)
|
||||
{
|
||||
currentAttribute = GetScheme ().Normal;
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
currentAttribute = GetScheme ().Normal;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void EndInit ()
|
||||
{
|
||||
// Move buttons from the Dialog to the Padding
|
||||
if (Padding is { })
|
||||
{
|
||||
foreach (Button button in _buttons)
|
||||
{
|
||||
// Remove from Dialog if it was added there
|
||||
Remove (button);
|
||||
return true;
|
||||
|
||||
// Add to Padding
|
||||
Padding.Add (button);
|
||||
}
|
||||
|
||||
UpdatePaddingBottom ();
|
||||
}
|
||||
|
||||
_endInitCalled = true;
|
||||
base.EndInit ();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Dispose (bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// Unsubscribe from button events to prevent memory leaks
|
||||
foreach (Button button in _buttons)
|
||||
{
|
||||
button.FrameChanged -= Button_FrameChanged;
|
||||
}
|
||||
}
|
||||
|
||||
base.Dispose (disposing);
|
||||
}
|
||||
|
||||
private void UpdatePaddingBottom ()
|
||||
@@ -264,12 +184,9 @@ public class Dialog : Window
|
||||
// Find the maximum button height
|
||||
var maxHeight = 1; // Default to minimum height of 1 for buttons
|
||||
|
||||
foreach (Button button in _buttons)
|
||||
foreach (Button button in _buttons.Where (b => b.Frame.Height > maxHeight))
|
||||
{
|
||||
if (button.Frame.Height > maxHeight)
|
||||
{
|
||||
maxHeight = button.Frame.Height;
|
||||
}
|
||||
maxHeight = button.Frame.Height;
|
||||
}
|
||||
|
||||
// Set the bottom padding to match button height
|
||||
@@ -279,4 +196,19 @@ public class Dialog : Window
|
||||
Padding.Thickness = Padding.Thickness with { Bottom = maxHeight };
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Dispose (bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// Unsubscribe from button events to prevent memory leaks
|
||||
foreach (Button button in _buttons)
|
||||
{
|
||||
button.FrameChanged -= ButtonFrameChanged;
|
||||
}
|
||||
}
|
||||
|
||||
base.Dispose (disposing);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
# Category A Migration Summary
|
||||
|
||||
## Overview
|
||||
|
||||
This document summarizes the Category A test migration effort to move parallelizable unit tests from `UnitTests` to `UnitTests.Parallelizable`.
|
||||
|
||||
## Tests Migrated: 35
|
||||
|
||||
### Drawing/LineCanvasTests.cs: 31 tests
|
||||
**Migrated pure unit tests that don't require Application.Driver:**
|
||||
- ToString_Empty (1 test)
|
||||
- Clear_Removes_All_Lines (1 test)
|
||||
- Lines_Property_Returns_ReadOnly_Collection (1 test)
|
||||
- AddLine_Adds_Line_To_Collection (1 test)
|
||||
- Constructor_With_Lines_Creates_Canvas_With_Lines (1 test)
|
||||
- Viewport_H_And_V_Lines_Both_Positive (7 test cases)
|
||||
- Viewport_H_Line (7 test cases)
|
||||
- Viewport_Specific (1 test)
|
||||
- Bounds_Empty_Canvas_Returns_Empty_Rectangle (1 test)
|
||||
- Bounds_Single_Point_Zero_Length (1 test)
|
||||
- Bounds_Horizontal_Line (1 test)
|
||||
- Bounds_Vertical_Line (1 test)
|
||||
- Bounds_Multiple_Lines_Returns_Union (1 test)
|
||||
- Bounds_Negative_Length_Line (1 test)
|
||||
- Bounds_Complex_Box (1 test)
|
||||
- ClearExclusions_Clears_Exclusion_Region (1 test)
|
||||
- Exclude_Removes_Points_From_Map (1 test)
|
||||
- Fill_Property_Can_Be_Set (1 test)
|
||||
- Fill_Property_Defaults_To_Null (1 test)
|
||||
|
||||
**Tests that remain in UnitTests as integration tests:**
|
||||
- All tests using GetCanvas() and View.Draw() (16 tests)
|
||||
- Tests that verify rendered output (ToString with specific glyphs) - these require Application.Driver for glyph resolution
|
||||
|
||||
### Drawing/RulerTests.cs: 4 tests
|
||||
**Migrated pure unit tests:**
|
||||
- Constructor_Defaults
|
||||
- Attribute_Set
|
||||
- Length_Set
|
||||
- Orientation_Set
|
||||
|
||||
**Tests that remain in UnitTests as integration tests:**
|
||||
- Draw_Default (requires Application.Init with [AutoInitShutdown])
|
||||
- Draw_Horizontal (uses [SetupFakeDriver] - could potentially be migrated)
|
||||
- Draw_Vertical (uses [SetupFakeDriver] - could potentially be migrated)
|
||||
|
||||
## Key Findings
|
||||
|
||||
### 1. LineCanvas and Rendering Dependencies
|
||||
**Issue:** LineCanvas.ToString() internally calls GetMap() which calls GetRuneForIntersects(Application.Driver). The glyph resolution depends on Application.Driver for:
|
||||
- Configuration-dependent glyphs (Glyphs class)
|
||||
- Line intersection character selection
|
||||
- Style-specific characters (Single, Double, Heavy, etc.)
|
||||
|
||||
**Solution:** Tests using [SetupFakeDriver] CAN be parallelized as long as they don't use Application statics. This includes rendering tests that verify visual output with DriverAssert.
|
||||
|
||||
### 2. Test Categories
|
||||
Tests fall into three categories:
|
||||
|
||||
**a) Pure Unit Tests (CAN be parallelized):**
|
||||
- Tests of properties (Bounds, Lines, Length, Orientation, Attribute, Fill)
|
||||
- Tests of basic operations (AddLine, Clear, Exclude, ClearExclusions)
|
||||
- Tests that don't require Application static context
|
||||
|
||||
**b) Rendering Tests with [SetupFakeDriver] (CAN be parallelized):**
|
||||
- Tests using [SetupFakeDriver] without Application statics
|
||||
- Tests using View.Draw() and LayoutAndDraw() without Application statics
|
||||
- Tests that verify visual output with DriverAssert (when using [SetupFakeDriver])
|
||||
- Tests using GetCanvas() helper as long as Application statics are not used
|
||||
|
||||
**c) Integration Tests (CANNOT be parallelized):**
|
||||
- Tests using [AutoInitShutdown]
|
||||
- Tests using Application.Begin, Application.RaiseKeyDownEvent, or other Application static methods
|
||||
- Tests that validate component behavior within full Application context
|
||||
- Tests that require ConfigurationManager or Application.Navigation
|
||||
|
||||
### 3. View/Adornment and View/Draw Tests
|
||||
**Finding:** After analyzing these tests, they all use [SetupFakeDriver] and test View.Draw() with visual verification. These are integration tests that validate how adornments render within the View system. They correctly belong in UnitTests.
|
||||
|
||||
**Recommendation:** Do NOT migrate these tests. They are integration tests by design and require the full Application/Driver context.
|
||||
|
||||
## Test Results
|
||||
|
||||
### UnitTests.Parallelizable
|
||||
- **Before:** 9,360 tests passing
|
||||
- **After:** 9,395 tests passing (+35)
|
||||
- **Result:** ✅ All tests pass
|
||||
|
||||
### UnitTests
|
||||
- **Status:** 3,488 tests passing (unchanged)
|
||||
- **Result:** ✅ No regressions
|
||||
|
||||
## Recommendations for Future Work
|
||||
|
||||
### 1. Continue Focused Migration
|
||||
|
||||
**Tests CAN be parallelized if they:**
|
||||
- ✅ Test properties, constructors, and basic operations
|
||||
- ✅ Use [SetupFakeDriver] without Application statics
|
||||
- ✅ Call View.Draw(), LayoutAndDraw() without Application statics
|
||||
- ✅ Verify visual output with DriverAssert (when using [SetupFakeDriver])
|
||||
- ✅ Create View hierarchies without Application.Top
|
||||
- ✅ Test events and behavior without global state
|
||||
|
||||
**Tests CANNOT be parallelized if they:**
|
||||
- ❌ Use [AutoInitShutdown] (requires Application.Init/Shutdown global state)
|
||||
- ❌ Set Application.Driver (global singleton)
|
||||
- ❌ Call Application.Init(), Application.Run/Run<T>(), or Application.Begin()
|
||||
- ❌ Modify ConfigurationManager global state (Enable/Load/Apply/Disable)
|
||||
- ❌ Modify static properties (Key.Separator, CultureInfo.CurrentCulture, etc.)
|
||||
- ❌ Use Application.Top, Application.Driver, Application.MainLoop, or Application.Navigation
|
||||
- ❌ Are true integration tests testing multiple components together
|
||||
|
||||
**Important Notes:**
|
||||
- Many tests blindly use the above when they don't need to and CAN be rewritten
|
||||
- Many tests APPEAR to be integration tests but are just poorly written and can be split
|
||||
- When in doubt, analyze if the test truly needs global state or can be refactored
|
||||
|
||||
### 2. Documentation
|
||||
Update test documentation to clarify:
|
||||
- **UnitTests** = Integration tests that validate components within Application context
|
||||
- **UnitTests.Parallelizable** = Pure unit tests with no global state dependencies
|
||||
- Provide examples of each type
|
||||
|
||||
### 3. New Test Development
|
||||
- Default to UnitTests.Parallelizable for new tests unless they require Application/Driver
|
||||
- When testing rendering, create both:
|
||||
- Pure unit test (properties, behavior) in Parallelizable
|
||||
- Rendering test with [SetupFakeDriver] can also go in Parallelizable (as long as Application statics are not used)
|
||||
- Integration test (Application context) in UnitTests
|
||||
|
||||
### 4. Remaining Category A Tests
|
||||
**Status:** Can be re-evaluated for migration
|
||||
|
||||
**Rationale:**
|
||||
- View/Adornment/* tests (19 tests): Use [SetupFakeDriver] and test View.Draw() - CAN be migrated if they don't use Application statics
|
||||
- View/Draw/* tests (32 tests): Use [SetupFakeDriver] and test rendering - CAN be migrated if they don't use Application statics
|
||||
- Need to analyze each test individually to check for Application static dependencies
|
||||
|
||||
## Conclusion
|
||||
|
||||
This migration successfully identified and moved 52 tests (35 Category A + 17 Views) to UnitTests.Parallelizable.
|
||||
|
||||
**Key Discovery:** Tests with [SetupFakeDriver] CAN run in parallel as long as they avoid Application statics. This significantly expands the scope of tests that can be parallelized beyond just property/constructor tests to include rendering tests.
|
||||
|
||||
The approach taken was to:
|
||||
1. Identify tests that don't use Application.Begin, Application.RaiseKeyDownEvent, Application.Navigation, or other Application static members
|
||||
2. Keep [SetupFakeDriver] tests that only use View.Draw() and DriverAssert
|
||||
3. Move [AutoInitShutdown] tests only if they can be rewritten to not use Application.Begin
|
||||
|
||||
**Migration Rate:** 52 tests migrated so far. Many more tests with [SetupFakeDriver] can potentially be migrated once they're analyzed for Application static usage. Estimated ~3,400 tests remaining to analyze.
|
||||
9
Tests/IntegrationTests/DialogTests.cs
Normal file
9
Tests/IntegrationTests/DialogTests.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
#nullable enable
|
||||
using UnitTests;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace IntegrationTests;
|
||||
|
||||
public class DialogTests (ITestOutputHelper output)
|
||||
{
|
||||
}
|
||||
@@ -1,363 +0,0 @@
|
||||
# UnitTests Performance Analysis Report
|
||||
|
||||
## Executive Summary
|
||||
|
||||
This report provides a comprehensive performance analysis of the `UnitTests` project, identifying the highest-impact opportunities for test migration to improve CI/CD performance.
|
||||
|
||||
**Key Findings:**
|
||||
- **Total tests analyzed:** 3,260 tests across 121 test files
|
||||
- **Top bottleneck:** Views folder (962 tests, 59.6s, 50% of total runtime)
|
||||
- **Highest average time per test:** Input/ folder (0.515s/test)
|
||||
- **Tests with AutoInitShutdown:** 449 tests (35.4%) - these are the slowest
|
||||
- **Tests with SetupFakeDriver:** 198 tests (15.6%)
|
||||
- **Tests with no attributes:** 622 tests (49.0%) - easiest to migrate
|
||||
|
||||
## Performance Analysis by Folder
|
||||
|
||||
### Folder-Level Timing Results (Ranked by Total Duration)
|
||||
|
||||
| Folder | Tests | Duration | Avg/Test | Impact Score |
|
||||
|--------|-------|----------|----------|--------------|
|
||||
| **Views/** | 962 | 59.64s | 0.062s | ⭐⭐⭐⭐⭐ CRITICAL |
|
||||
| **View/** | 739 | 27.14s | 0.036s | ⭐⭐⭐⭐ HIGH |
|
||||
| **Application/** | 187 | 14.82s | 0.079s | ⭐⭐⭐ MEDIUM |
|
||||
| **Dialogs/** | 116 | 13.42s | 0.115s | ⭐⭐⭐ MEDIUM |
|
||||
| **Text/** | 467 | 10.18s | 0.021s | ⭐⭐ LOW |
|
||||
| **ConsoleDrivers/** | 475 | 5.74s | 0.012s | ⭐ VERY LOW |
|
||||
| **FileServices/** | 35 | 5.56s | 0.158s | ⭐⭐ LOW |
|
||||
| **Drawing/** | 173 | 5.35s | 0.030s | ⭐ VERY LOW |
|
||||
| **Configuration/** | 98 | 5.05s | 0.051s | ⭐ VERY LOW |
|
||||
| **Input/** | 8 | 4.12s | 0.515s | ⭐⭐ LOW |
|
||||
|
||||
**Total:** 3,260 tests, ~150s total runtime
|
||||
|
||||
### Folder-Level Static Analysis
|
||||
|
||||
| Folder | Files | Tests | AutoInit | SetupDrv | App.Begin | App.Top |
|
||||
|--------|-------|-------|----------|----------|-----------|---------|
|
||||
| Views | 33 | 612 | 232 (37.9%) | 104 (17.0%) | 139 | 219 |
|
||||
| Application | 12 | 120 | 27 (22.5%) | 6 (5.0%) | 20 | 145 |
|
||||
| Configuration | 9 | 82 | 0 (0.0%) | 0 (0.0%) | 0 | 0 |
|
||||
| ConsoleDrivers | 17 | 75 | 15 (20.0%) | 3 (4.0%) | 8 | 34 |
|
||||
| Drawing | 4 | 58 | 21 (36.2%) | 32 (55.2%) | 1 | 0 |
|
||||
| Dialogs | 3 | 50 | 40 (80.0%) | 0 (0.0%) | 6 | 7 |
|
||||
| View/Draw | 7 | 37 | 15 (40.5%) | 17 (45.9%) | 15 | 0 |
|
||||
|
||||
## High-Impact Migration Targets
|
||||
|
||||
### 🎯 Priority 1: CRITICAL Impact (50-60s potential savings)
|
||||
|
||||
#### Views/ Folder - 59.6s (50% of total runtime)
|
||||
**Profile:**
|
||||
- 962 tests total
|
||||
- 232 with AutoInitShutdown (37.9%)
|
||||
- 104 with SetupFakeDriver (17.0%)
|
||||
- **~380 tests with no attributes** (potential quick wins)
|
||||
|
||||
**Top Individual Files:**
|
||||
1. **TextViewTests.cs** - 105 tests, 9.26s, 0.088s/test
|
||||
- 41 AutoInitShutdown (39%)
|
||||
- 64 tests are potentially migratable
|
||||
|
||||
2. **TableViewTests.cs** - 80 tests, 5.38s, 0.055s/test
|
||||
- 45 SetupFakeDriver (56%)
|
||||
- 8 AutoInitShutdown
|
||||
- Many rendering tests that may need refactoring
|
||||
|
||||
3. **TileViewTests.cs** - 45 tests, 9.25s, 0.197s/test ⚠️ SLOWEST AVG
|
||||
- 42 AutoInitShutdown (93%)
|
||||
- High overhead per test - prime candidate for optimization
|
||||
|
||||
4. **TextFieldTests.cs** - 43 tests
|
||||
- 8 AutoInitShutdown (19%)
|
||||
- 3 SetupFakeDriver
|
||||
- ~32 tests likely migratable
|
||||
|
||||
5. **GraphViewTests.cs** - 42 tests
|
||||
- 24 AutoInitShutdown (57%)
|
||||
- ~18 tests potentially migratable
|
||||
|
||||
**Recommendation:** Focus on Views/ folder first
|
||||
- Extract simple property/event tests from TextViewTests
|
||||
- Refactor TileViewTests to reduce AutoInitShutdown usage
|
||||
- Split TableViewTests into unit vs integration tests
|
||||
|
||||
### 🎯 Priority 2: HIGH Impact (20-30s potential savings)
|
||||
|
||||
#### View/ Folder - 27.14s
|
||||
**Profile:**
|
||||
- 739 tests total
|
||||
- Wide distribution across subdirectories
|
||||
- Mix of layout, drawing, and behavioral tests
|
||||
|
||||
**Key subdirectories:**
|
||||
- View/Layout - 35 tests (6 AutoInit, 1 SetupDriver)
|
||||
- View/Draw - 37 tests (15 AutoInit, 17 SetupDriver)
|
||||
- View/Adornment - 25 tests (9 AutoInit, 10 SetupDriver)
|
||||
|
||||
**Top Files:**
|
||||
1. **GetViewsUnderLocationTests.cs** - 21 tests, NO attributes ✅
|
||||
- Easy migration candidate
|
||||
|
||||
2. **DrawTests.cs** - 17 tests
|
||||
- 10 AutoInitShutdown
|
||||
- 6 SetupFakeDriver
|
||||
- Mix that needs analysis
|
||||
|
||||
**Recommendation:**
|
||||
- Migrate GetViewsUnderLocationTests.cs immediately
|
||||
- Analyze layout tests for unnecessary Application dependencies
|
||||
|
||||
### 🎯 Priority 3: MEDIUM Impact (10-15s potential savings)
|
||||
|
||||
#### Dialogs/ Folder - 13.42s
|
||||
**Profile:**
|
||||
- 116 tests, 0.115s/test average (SLOW)
|
||||
- 40 AutoInitShutdown (80% usage rate!)
|
||||
- Heavy Application.Begin usage
|
||||
|
||||
**Files:**
|
||||
1. **DialogTests.cs** - 23 tests, all with AutoInitShutdown
|
||||
2. **MessageBoxTests.cs** - 11 tests, all with AutoInitShutdown
|
||||
|
||||
**Recommendation:**
|
||||
- These are true integration tests that likely need Application
|
||||
- Some could be refactored to test dialog construction separately from display
|
||||
- Lower priority for migration
|
||||
|
||||
#### Application/ Folder - 14.82s
|
||||
**Profile:**
|
||||
- 187 tests
|
||||
- 27 AutoInitShutdown (22.5%)
|
||||
- Heavy Application.Top usage (145 occurrences)
|
||||
|
||||
**Easy wins:**
|
||||
1. **MainLoopTests.cs** - 23 tests, NO attributes ✅ (already migrated)
|
||||
2. **ApplicationImplTests.cs** - 13 tests, NO attributes ✅
|
||||
3. **ApplicationPopoverTests.cs** - 10 tests, NO attributes ✅
|
||||
|
||||
**Recommendation:**
|
||||
- Migrate the remaining files with no attributes
|
||||
- Many Application tests genuinely need Application static state
|
||||
|
||||
## Performance by Test Pattern
|
||||
|
||||
### AutoInitShutdown Tests (449 tests, ~35% of total)
|
||||
|
||||
**Characteristics:**
|
||||
- Average 0.115s per test (vs 0.051s for no-attribute tests)
|
||||
- **2.25x slower than tests without attributes**
|
||||
- Creates Application singleton, initializes driver, sets up MainLoop
|
||||
- Calls Application.Shutdown after each test
|
||||
|
||||
**Top Files Using AutoInitShutdown:**
|
||||
1. TileViewTests.cs - 42 tests (93% usage)
|
||||
2. TextViewTests.cs - 41 tests (39% usage)
|
||||
3. MenuBarv1Tests.cs - 40 tests (95% usage)
|
||||
4. GraphViewTests.cs - 24 tests (57% usage)
|
||||
5. DialogTests.cs - 23 tests (100% usage)
|
||||
6. MenuBarTests.cs - 20 tests (111% - multiple per test method)
|
||||
|
||||
**Estimated Impact:** If 50% of AutoInitShutdown tests can be refactored:
|
||||
- ~225 tests × 0.064s overhead = **~14.4s savings**
|
||||
|
||||
### SetupFakeDriver Tests (198 tests, ~16% of total)
|
||||
|
||||
**Characteristics:**
|
||||
- Average 0.055s per test
|
||||
- Sets up Application.Driver globally
|
||||
- Many test visual output with DriverAssert
|
||||
- Less overhead than AutoInitShutdown but still blocks parallelization
|
||||
|
||||
**Top Files Using SetupFakeDriver:**
|
||||
1. TableViewTests.cs - 45 tests (56% usage)
|
||||
2. LineCanvasTests.cs - 30 tests (86% usage)
|
||||
3. TabViewTests.cs - 18 tests (53% usage)
|
||||
4. TextFormatterTests.cs - 18 tests (78% usage)
|
||||
5. ColorPickerTests.cs - 16 tests (100% usage)
|
||||
|
||||
**Estimated Impact:** If 30% can be refactored to remove driver dependency:
|
||||
- ~60 tests × 0.025s overhead = **~1.5s savings**
|
||||
|
||||
### Tests with No Attributes (622 tests, ~49% of total)
|
||||
|
||||
**Characteristics:**
|
||||
- Average 0.051s per test (fastest)
|
||||
- Should be immediately migratable
|
||||
- Many already identified in previous migration
|
||||
|
||||
**Top Remaining Files:**
|
||||
1. ConfigurationMangerTests.cs - 27 tests ✅ (already migrated)
|
||||
2. MainLoopTests.cs - 23 tests ✅ (already migrated)
|
||||
3. GetViewsUnderLocationTests.cs - 21 tests ⭐ **HIGH PRIORITY**
|
||||
4. ConfigPropertyTests.cs - 18 tests (partial migration done)
|
||||
5. SchemeManagerTests.cs - 14 tests (partial migration done)
|
||||
|
||||
## Recommendations: Phased Approach
|
||||
|
||||
### Phase 1: Quick Wins (Estimated 15-20s savings, 1-2 days)
|
||||
|
||||
**Target:** 150-200 tests with no attributes
|
||||
|
||||
1. **Immediate migrations** (no refactoring needed):
|
||||
- GetViewsUnderLocationTests.cs (21 tests)
|
||||
- ApplicationImplTests.cs (13 tests)
|
||||
- ApplicationPopoverTests.cs (10 tests)
|
||||
- HexViewTests.cs (12 tests)
|
||||
- TimeFieldTests.cs (6 tests)
|
||||
- Various smaller files with no attributes
|
||||
|
||||
2. **Complete partial migrations**:
|
||||
- ConfigPropertyTests.cs (add 14 more tests)
|
||||
- SchemeManagerTests.cs (add 4 more tests)
|
||||
- SettingsScopeTests.cs (add 9 more tests)
|
||||
|
||||
**Expected Impact:** ~20s runtime reduction in UnitTests
|
||||
|
||||
### Phase 2: TextViewTests Refactoring (Estimated 4-5s savings, 2-3 days)
|
||||
|
||||
**Target:** Split 64 tests from TextViewTests.cs
|
||||
|
||||
1. Extract simple tests (no AutoInitShutdown needed):
|
||||
- Property tests (Text, Enabled, Visible, etc.)
|
||||
- Event tests (TextChanged, etc.)
|
||||
- Constructor tests
|
||||
|
||||
2. Extract tests that can use BeginInit/EndInit instead of Application.Begin:
|
||||
- Basic layout tests
|
||||
- Focus tests
|
||||
- Some selection tests
|
||||
|
||||
3. Leave integration tests in UnitTests:
|
||||
- Tests that verify rendering output
|
||||
- Tests that need actual driver interaction
|
||||
- Multi-component interaction tests
|
||||
|
||||
**Expected Impact:** ~4-5s runtime reduction
|
||||
|
||||
### Phase 3: TileViewTests Optimization (Estimated 4-5s savings, 2-3 days)
|
||||
|
||||
**Target:** Reduce TileViewTests from 9.25s to ~4s
|
||||
|
||||
TileViewTests has the highest average time per test (0.197s) - nearly 4x the normal rate!
|
||||
|
||||
**Analysis needed:**
|
||||
1. Why are these tests so slow?
|
||||
2. Are they testing multiple things per test?
|
||||
3. Can Application.Begin calls be replaced with BeginInit/EndInit?
|
||||
4. Are there setup/teardown inefficiencies?
|
||||
|
||||
**Approach:**
|
||||
1. Profile individual test methods
|
||||
2. Look for common patterns causing slowness
|
||||
3. Refactor to reduce overhead
|
||||
4. Consider splitting into multiple focused test classes
|
||||
|
||||
**Expected Impact:** ~5s runtime reduction
|
||||
|
||||
### Phase 4: TableViewTests Refactoring (Estimated 2-3s savings, 2-3 days)
|
||||
|
||||
**Target:** Extract ~35 tests from TableViewTests.cs
|
||||
|
||||
TableViewTests has 45 SetupFakeDriver usages for visual testing. However:
|
||||
- Some tests may only need basic View hierarchy (BeginInit/EndInit)
|
||||
- Some tests may be testing properties that don't need rendering
|
||||
- Some tests may be duplicating coverage
|
||||
|
||||
**Approach:**
|
||||
1. Categorize tests: pure unit vs rendering verification
|
||||
2. Extract pure unit tests to Parallelizable
|
||||
3. Keep rendering verification tests in UnitTests
|
||||
4. Look for duplicate coverage
|
||||
|
||||
**Expected Impact:** ~3s runtime reduction
|
||||
|
||||
### Phase 5: Additional View Tests (Estimated 10-15s savings, 1-2 weeks)
|
||||
|
||||
**Target:** 200-300 tests across multiple View test files
|
||||
|
||||
Focus on files with mix of attribute/no-attribute tests:
|
||||
- TextFieldTests.cs (43 tests, only 11 with attributes)
|
||||
- GraphViewTests.cs (42 tests, 24 AutoInit - can some be refactored?)
|
||||
- ListViewTests.cs (27 tests, 6 AutoInit)
|
||||
- LabelTests.cs (24 tests, 16 AutoInit + 3 SetupDriver)
|
||||
- TreeViewTests.cs (38 tests, 1 AutoInit + 9 SetupDriver)
|
||||
|
||||
**Expected Impact:** ~15s runtime reduction
|
||||
|
||||
## Summary of Potential Savings
|
||||
|
||||
| Phase | Tests Migrated | Estimated Savings | Effort | Priority |
|
||||
|-------|----------------|-------------------|--------|----------|
|
||||
| Phase 1: Quick Wins | 150-200 | 15-20s | 1-2 days | ⭐⭐⭐⭐⭐ |
|
||||
| Phase 2: TextViewTests | 64 | 4-5s | 2-3 days | ⭐⭐⭐⭐ |
|
||||
| Phase 3: TileViewTests | 20-30 | 4-5s | 2-3 days | ⭐⭐⭐⭐ |
|
||||
| Phase 4: TableViewTests | 35 | 2-3s | 2-3 days | ⭐⭐⭐ |
|
||||
| Phase 5: Additional Views | 200-300 | 10-15s | 1-2 weeks | ⭐⭐⭐ |
|
||||
| **TOTAL** | **469-623 tests** | **35-48s** | **3-4 weeks** | |
|
||||
|
||||
**Target Runtime:**
|
||||
- Current: ~90s (UnitTests)
|
||||
- After all phases: **~42-55s (38-47% reduction)**
|
||||
- Combined with Parallelizable: **~102-115s total (vs 150s current = 23-32% reduction)**
|
||||
|
||||
## Key Insights
|
||||
|
||||
### Why Some Tests Are Slow
|
||||
|
||||
1. **AutoInitShutdown overhead** (0.064s per test):
|
||||
- Creates Application singleton
|
||||
- Initializes FakeDriver
|
||||
- Sets up MainLoop
|
||||
- Teardown and cleanup
|
||||
|
||||
2. **Application.Begin overhead** (varies):
|
||||
- Initializes view hierarchy
|
||||
- Runs layout engine
|
||||
- Sets up focus/navigation
|
||||
- Creates event loops
|
||||
|
||||
3. **Integration test nature**:
|
||||
- Dialogs/ tests average 0.115s/test
|
||||
- FileServices/ tests average 0.158s/test
|
||||
- Input/ tests average 0.515s/test (!)
|
||||
- These test full workflows, not units
|
||||
|
||||
### Migration Difficulty Assessment
|
||||
|
||||
**Easy (No refactoring):**
|
||||
- Tests with no attributes: 622 tests
|
||||
- Simply copy to Parallelizable and add base class
|
||||
|
||||
**Medium (Minor refactoring):**
|
||||
- Tests using SetupFakeDriver but not Application statics: ~60 tests
|
||||
- Replace SetupFakeDriver with inline driver creation if needed
|
||||
- Or remove driver dependency entirely
|
||||
|
||||
**Hard (Significant refactoring):**
|
||||
- Tests using AutoInitShutdown: 449 tests
|
||||
- Must replace Application.Begin with BeginInit/EndInit
|
||||
- Or split into unit vs integration tests
|
||||
- Or redesign test approach
|
||||
|
||||
**Very Hard (May not be migratable):**
|
||||
- True integration tests: ~100-150 tests
|
||||
- Tests requiring actual rendering verification
|
||||
- Tests requiring Application singleton behavior
|
||||
- Keep these in UnitTests
|
||||
|
||||
## Conclusion
|
||||
|
||||
The analysis reveals clear opportunities for significant performance improvements:
|
||||
|
||||
1. **Immediate impact:** 150-200 tests with no attributes can be migrated in 1-2 days for ~20s savings
|
||||
2. **High value:** TextViewTests and TileViewTests contain ~100 tests that can yield ~10s savings with moderate effort
|
||||
3. **Long-term:** Systematic refactoring of 469-623 tests could reduce UnitTests runtime by 38-47%
|
||||
|
||||
The Views/ folder is the critical bottleneck, representing 50% of runtime. Focusing migration efforts here will yield the greatest impact on CI/CD performance.
|
||||
|
||||
---
|
||||
|
||||
**Report Generated:** 2025-10-20
|
||||
**Analysis Method:** Static analysis + runtime profiling
|
||||
**Total Tests Analyzed:** 3,260 tests across 121 files
|
||||
@@ -1,285 +0,0 @@
|
||||
# Test Migration Report - UnitTests Performance Improvement
|
||||
|
||||
## Executive Summary
|
||||
|
||||
This PR migrates 181 tests from the non-parallelizable `UnitTests` project to the parallelizable `UnitTests.Parallelizable` project, reducing the test execution burden on the slower project and establishing clear patterns for future migrations.
|
||||
|
||||
## Quantitative Results
|
||||
|
||||
### Test Count Changes
|
||||
| Project | Before | After | Change |
|
||||
|---------|--------|-------|--------|
|
||||
| **UnitTests** | 3,396 | 3,066 | **-330 (-9.7%)** |
|
||||
| **UnitTests.Parallelizable** | 9,478 | 9,625 | **+147 (+1.6%)** |
|
||||
| **Total** | 12,874 | 12,691 | -183 |
|
||||
|
||||
*Note: Net reduction due to consolidation of duplicate/refactored tests*
|
||||
|
||||
### Performance Metrics
|
||||
| Metric | Before | After (Estimated) | Improvement |
|
||||
|--------|--------|-------------------|-------------|
|
||||
| UnitTests Runtime | ~90s | ~85s | ~5s (5.5%) |
|
||||
| UnitTests.Parallelizable Runtime | ~60s | ~61s | -1s |
|
||||
| **Total CI/CD Time** | ~150s | ~146s | **~4s (2.7%)** |
|
||||
| **Across 3 Platforms** | ~450s | ~438s | **~12s saved per run** |
|
||||
|
||||
*Current improvement is modest because migrated tests were already fast. Larger gains possible with continued migration.*
|
||||
|
||||
## Files Migrated
|
||||
|
||||
### Complete File Migrations (8 files)
|
||||
1. **SliderTests.cs** (32 tests, 3 classes)
|
||||
- `SliderOptionTests`
|
||||
- `SliderEventArgsTests`
|
||||
- `SliderTests`
|
||||
|
||||
2. **TextValidateFieldTests.cs** (27 tests, 2 classes)
|
||||
- `TextValidateField_NET_Provider_Tests`
|
||||
- `TextValidateField_Regex_Provider_Tests`
|
||||
|
||||
3. **AnsiResponseParserTests.cs** (13 tests)
|
||||
- ANSI escape sequence parsing and detection
|
||||
|
||||
4. **ThemeManagerTests.cs** (13 tests)
|
||||
- Theme management and memory size estimation
|
||||
- Includes helper: `MemorySizeEstimator.cs`
|
||||
|
||||
5. **MainLoopDriverTests.cs** (11 tests)
|
||||
- Main loop driver functionality
|
||||
|
||||
6. **ResourceManagerTests.cs** (10 tests)
|
||||
- Resource management tests
|
||||
|
||||
7. **StackExtensionsTests.cs** (10 tests)
|
||||
- Stack extension method tests
|
||||
|
||||
8. **EscSeqRequestsTests.cs** (8 tests)
|
||||
- Escape sequence request tests
|
||||
|
||||
### Partial File Migrations (1 file)
|
||||
1. **ButtonTests.cs** (11 tests migrated, 8 methods)
|
||||
- Property and event tests
|
||||
- Keyboard interaction tests
|
||||
- Command invocation tests
|
||||
|
||||
## Migration Methodology
|
||||
|
||||
### Selection Criteria
|
||||
Tests were selected for migration if they:
|
||||
- ✅ Had no `[AutoInitShutdown]` attribute
|
||||
- ✅ Had no `[SetupFakeDriver]` attribute (or could be refactored to remove it)
|
||||
- ✅ Did not use `Application.Begin()`, `Application.Top`, `Application.Driver`, etc.
|
||||
- ✅ Did not modify `ConfigurationManager` global state
|
||||
- ✅ Tested discrete units of functionality
|
||||
|
||||
### Migration Process
|
||||
1. **Analysis**: Scan test files for dependencies
|
||||
2. **Copy**: Copy test file/methods to `UnitTests.Parallelizable`
|
||||
3. **Modify**: Add `: UnitTests.Parallelizable.ParallelizableBase` inheritance
|
||||
4. **Build**: Verify compilation
|
||||
5. **Test**: Run migrated tests to ensure they pass
|
||||
6. **Cleanup**: Remove original tests from `UnitTests`
|
||||
7. **Verify**: Confirm both projects build and pass tests
|
||||
|
||||
## Remaining Opportunities
|
||||
|
||||
### High-Impact Targets (300-500 tests)
|
||||
Based on analysis of 130 test files in `UnitTests`:
|
||||
|
||||
1. **Large test files with mixed dependencies**:
|
||||
- TextViewTests.cs (105 tests) - Many simple property tests can be extracted
|
||||
- TableViewTests.cs (80 tests) - Mix of unit and integration tests
|
||||
- TextFieldTests.cs (43 tests) - Several simple tests
|
||||
- TileViewTests.cs (45 tests)
|
||||
- GraphViewTests.cs (42 tests)
|
||||
- MenuBarv1Tests.cs (42 tests)
|
||||
|
||||
2. **Files with `[SetupFakeDriver]` but no Application statics** (85 tests):
|
||||
- LineCanvasTests.cs (35 tests, 17 missing from Parallelizable)
|
||||
- TextFormatterTests.cs (23 tests, some refactorable)
|
||||
- ClipTests.cs (6 tests)
|
||||
- CursorTests.cs (6 tests)
|
||||
- Others (15 tests across multiple files)
|
||||
|
||||
3. **Partial migrations to complete** (~27 tests):
|
||||
- ConfigPropertyTests.cs (14 additional tests)
|
||||
- SchemeManagerTests.cs (4 additional tests)
|
||||
- SettingsScopeTests.cs (9 additional tests)
|
||||
|
||||
4. **Simple attribute-free tests** (~400 tests):
|
||||
- Tests with only `[Fact]` or `[Theory]` attributes
|
||||
- Property tests, constructor tests, event tests
|
||||
- Tests that don't actually need Application infrastructure
|
||||
|
||||
### Blockers Analysis
|
||||
|
||||
**Tests that must remain in UnitTests:**
|
||||
- **452 tests** using `[AutoInitShutdown]` - require Application singleton
|
||||
- **79 files** using `Application.Begin()`, `Application.Top`, etc.
|
||||
- Tests requiring actual rendering verification with `DriverAssert`
|
||||
- True integration tests testing multiple components together
|
||||
|
||||
## Recommended Next Steps
|
||||
|
||||
### Phase 1: Quick Wins (1-2 days, 50-100 tests)
|
||||
**Goal**: Double the migration count with minimal effort
|
||||
|
||||
1. Extract simple tests from:
|
||||
- CheckBoxTests
|
||||
- LabelTests
|
||||
- RadioGroupTests
|
||||
- ComboBoxTests
|
||||
- ProgressBarTests
|
||||
|
||||
2. Complete partial migrations:
|
||||
- ConfigPropertyTests
|
||||
- SchemeManagerTests
|
||||
- SettingsScopeTests
|
||||
|
||||
**Estimated Impact**: Additional ~100 tests, ~3-5% more speedup
|
||||
|
||||
### Phase 2: Medium Refactoring (1-2 weeks, 200-300 tests)
|
||||
**Goal**: Refactor tests to remove unnecessary dependencies
|
||||
|
||||
1. **Pattern 1**: Replace `[SetupFakeDriver]` with inline driver creation where needed
|
||||
```csharp
|
||||
// Before (UnitTests)
|
||||
[Fact]
|
||||
[SetupFakeDriver]
|
||||
public void Test_Draw_Output() {
|
||||
var view = new Button();
|
||||
view.Draw();
|
||||
DriverAssert.AssertDriverContentsAre("...", output);
|
||||
}
|
||||
|
||||
// After (UnitTests.Parallelizable) - if rendering not critical
|
||||
[Fact]
|
||||
public void Test_Properties() {
|
||||
var view = new Button();
|
||||
Assert.Equal(...);
|
||||
}
|
||||
```
|
||||
|
||||
2. **Pattern 2**: Replace `Application.Begin()` with `BeginInit()/EndInit()`
|
||||
```csharp
|
||||
// Before (UnitTests)
|
||||
[Fact]
|
||||
[AutoInitShutdown]
|
||||
public void Test_Layout() {
|
||||
var top = new Toplevel();
|
||||
var view = new Button();
|
||||
top.Add(view);
|
||||
Application.Begin(top);
|
||||
Assert.Equal(...);
|
||||
}
|
||||
|
||||
// After (UnitTests.Parallelizable)
|
||||
[Fact]
|
||||
public void Test_Layout() {
|
||||
var container = new View();
|
||||
var view = new Button();
|
||||
container.Add(view);
|
||||
container.BeginInit();
|
||||
container.EndInit();
|
||||
Assert.Equal(...);
|
||||
}
|
||||
```
|
||||
|
||||
3. **Pattern 3**: Split "mega tests" into focused unit tests
|
||||
- Break tests that verify multiple things into separate tests
|
||||
- Each test should verify one behavior
|
||||
|
||||
**Estimated Impact**: Additional ~250 tests, ~10-15% speedup
|
||||
|
||||
### Phase 3: Major Refactoring (2-4 weeks, 500+ tests)
|
||||
**Goal**: Systematically refactor large test suites
|
||||
|
||||
1. **TextViewTests** deep dive:
|
||||
- Categorize all 105 tests
|
||||
- Extract ~50 simple property/event tests
|
||||
- Refactor ~30 tests to remove Application dependency
|
||||
- Keep ~25 true integration tests in UnitTests
|
||||
|
||||
2. **TableViewTests** deep dive:
|
||||
- Similar analysis and refactoring
|
||||
- Potential to extract 40-50 tests
|
||||
|
||||
3. **Create migration guide**:
|
||||
- Document patterns for test authors
|
||||
- Add examples to README
|
||||
- Update CONTRIBUTING.md
|
||||
|
||||
**Estimated Impact**: Additional ~500+ tests, **30-50% total speedup**
|
||||
|
||||
## Long-Term Vision
|
||||
|
||||
### Target State
|
||||
- **UnitTests**: ~1,500-2,000 tests (~45-50s runtime)
|
||||
- Only tests requiring Application/ConfigurationManager
|
||||
- True integration tests
|
||||
- Tests requiring actual rendering validation
|
||||
|
||||
- **UnitTests.Parallelizable**: ~11,000-12,000 tests (~70-75s runtime)
|
||||
- All property, constructor, event tests
|
||||
- Unit tests with isolated dependencies
|
||||
- Tests using `BeginInit()/EndInit()` instead of Application
|
||||
|
||||
- **Total CI/CD time**: ~120s (20% faster than current)
|
||||
- **Across 3 platforms**: ~360s (30s saved per run)
|
||||
|
||||
### Process Improvements
|
||||
1. **Update test templates** to default to parallelizable patterns
|
||||
2. **Add pre-commit checks** to warn when adding tests to UnitTests
|
||||
3. **Create migration dashboard** to track progress
|
||||
4. **Celebrate milestones** (every 100 tests migrated)
|
||||
|
||||
## Technical Notes
|
||||
|
||||
### Base Class Requirement
|
||||
All test classes in `UnitTests.Parallelizable` must inherit from `ParallelizableBase`:
|
||||
|
||||
```csharp
|
||||
public class MyTests : UnitTests.Parallelizable.ParallelizableBase
|
||||
{
|
||||
[Fact]
|
||||
public void My_Test() { ... }
|
||||
}
|
||||
```
|
||||
|
||||
This ensures proper test isolation and parallel execution.
|
||||
|
||||
### No Duplicate Test Names
|
||||
The CI/CD pipeline checks for duplicate test names across both projects. This ensures:
|
||||
- No conflicts during test execution
|
||||
- Clear test identification in reports
|
||||
- Proper test migration tracking
|
||||
|
||||
### Common Pitfalls
|
||||
|
||||
**Avoid:**
|
||||
- Using `Application.Driver` (sets global state)
|
||||
- Using `Application.Top` (requires Application.Begin)
|
||||
- Modifying `ConfigurationManager` (global state)
|
||||
- Using `[AutoInitShutdown]` or `[SetupFakeDriver]` attributes
|
||||
- Testing multiple behaviors in one test method
|
||||
|
||||
**Prefer:**
|
||||
- Using `View.BeginInit()/EndInit()` for layout
|
||||
- Creating View hierarchies without Application
|
||||
- Testing one behavior per test method
|
||||
- Using constructor/property assertions
|
||||
- Mocking dependencies when needed
|
||||
|
||||
## Conclusion
|
||||
|
||||
This PR successfully demonstrates the viability and value of migrating tests from `UnitTests` to `UnitTests.Parallelizable`. While the current performance improvement is modest (~3%), it establishes proven patterns and identifies clear opportunities for achieving the target 30-50% speedup through continued migration efforts.
|
||||
|
||||
The work can be continued incrementally, with each batch of 50-100 tests providing measurable improvements to CI/CD performance across all platforms.
|
||||
|
||||
---
|
||||
|
||||
**Files Changed**: 17 files (9 created, 8 deleted/modified)
|
||||
**Tests Migrated**: 181 tests (330 removed, 147 added after consolidation)
|
||||
**Performance Gain**: ~3% (with potential for 30-50% with full migration)
|
||||
**Effort**: ~4-6 hours (analysis + migration + validation)
|
||||
|
||||
@@ -1,255 +0,0 @@
|
||||
# Text Tests Deep Dive and Migration Analysis
|
||||
|
||||
## Overview
|
||||
|
||||
The `Text/` folder in UnitTests contains **27 tests** across 2 files that focus on text formatting and autocomplete functionality. This analysis examines each test to determine migration feasibility.
|
||||
|
||||
## Test Files Summary
|
||||
|
||||
| File | Total Tests | AutoInitShutdown | SetupFakeDriver | No Attributes | Migratable |
|
||||
|------|-------------|------------------|-----------------|---------------|------------|
|
||||
| TextFormatterTests.cs | 23 | 0 | 18 | 5 | 15-18 (refactor) |
|
||||
| AutocompleteTests.cs | 4 | 2 | 0 | 2 | 2 (migrated) |
|
||||
| **TOTAL** | **27** | **2** | **18** | **7** | **17-20 (63-74%)** |
|
||||
|
||||
## AutocompleteTests.cs - Detailed Analysis
|
||||
|
||||
### ✅ MIGRATED (2 tests)
|
||||
|
||||
#### 1. Test_GenerateSuggestions_Simple
|
||||
**Status:** ✅ Migrated to UnitTests.Parallelizable
|
||||
- **Type:** Pure unit test
|
||||
- **Tests:** Suggestion generation logic
|
||||
- **Dependencies:** None (no Application, no Driver)
|
||||
- **Why migratable:** Tests internal logic only
|
||||
|
||||
#### 2. TestSettingSchemeOnAutocomplete
|
||||
**Status:** ✅ Migrated to UnitTests.Parallelizable
|
||||
- **Type:** Pure unit test
|
||||
- **Tests:** Scheme/color configuration
|
||||
- **Dependencies:** None (no Application, no Driver)
|
||||
- **Why migratable:** Tests property setting only
|
||||
|
||||
### ❌ REMAIN IN UNITTESTS (2 tests)
|
||||
|
||||
#### 3. CursorLeft_CursorRight_Mouse_Button_Pressed_Does_Not_Show_Popup
|
||||
**Status:** ❌ Must remain in UnitTests
|
||||
- **Type:** Integration test
|
||||
- **Tests:** Popup display behavior with keyboard/mouse interaction
|
||||
- **Dependencies:** `[AutoInitShutdown]`, Application.Begin(), DriverAssert
|
||||
- **Why not migratable:**
|
||||
- Tests full UI interaction workflow
|
||||
- Verifies visual rendering of popup
|
||||
- Requires Application.Begin() to set up event loop
|
||||
- Uses DriverAssert to verify screen content
|
||||
|
||||
#### 4. KeyBindings_Command
|
||||
**Status:** ❌ Must remain in UnitTests
|
||||
- **Type:** Integration test
|
||||
- **Tests:** Keyboard navigation in autocomplete popup
|
||||
- **Dependencies:** `[AutoInitShutdown]`, Application.Begin()
|
||||
- **Why not migratable:**
|
||||
- Tests keyboard command handling in context
|
||||
- Requires Application event loop
|
||||
- Verifies state changes across multiple interactions
|
||||
|
||||
## TextFormatterTests.cs - Detailed Analysis
|
||||
|
||||
### Test Categorization
|
||||
|
||||
All 23 tests use `[SetupFakeDriver]` and test TextFormatter's Draw() method. However, many are testing **formatting logic** rather than actual **rendering**.
|
||||
|
||||
### 🟡 REFACTORABLE TESTS (15-18 tests can be converted)
|
||||
|
||||
These tests can be converted from testing Draw() output to testing Format() logic:
|
||||
|
||||
#### Horizontal Alignment Tests (10 tests) - HIGH PRIORITY
|
||||
1. **Draw_Horizontal_Centered** (Theory with 9 InlineData)
|
||||
- Tests horizontal centering logic
|
||||
- **Conversion:** Use Format() instead of Draw(), verify string output
|
||||
|
||||
2. **Draw_Horizontal_Justified** (Theory with 9 InlineData)
|
||||
- Tests text justification (Fill alignment)
|
||||
- **Conversion:** Use Format() instead of Draw()
|
||||
|
||||
3. **Draw_Horizontal_Left** (Theory with 8 InlineData)
|
||||
- Tests left alignment
|
||||
- **Conversion:** Use Format() instead of Draw()
|
||||
|
||||
4. **Draw_Horizontal_Right** (Theory with 8 InlineData)
|
||||
- Tests right alignment
|
||||
- **Conversion:** Use Format() instead of Draw()
|
||||
|
||||
#### Direction Tests (2 tests)
|
||||
5. **Draw_Horizontal_RightLeft_TopBottom** (Theory with 11 InlineData)
|
||||
- Tests right-to-left text direction
|
||||
- **Conversion:** Use Format() to test string manipulation logic
|
||||
|
||||
6. **Draw_Horizontal_RightLeft_BottomTop** (Theory with 9 InlineData)
|
||||
- Tests right-to-left, bottom-to-top direction
|
||||
- **Conversion:** Use Format() to test string manipulation
|
||||
|
||||
#### Size Calculation Tests (2 tests) - EASY WINS
|
||||
7. **FormatAndGetSize_Returns_Correct_Size**
|
||||
- Tests size calculation without actually rendering
|
||||
- **Conversion:** Already doesn't need Draw(), just remove SetupFakeDriver
|
||||
|
||||
8. **FormatAndGetSize_WordWrap_False_Returns_Correct_Size**
|
||||
- Tests size calculation with word wrap disabled
|
||||
- **Conversion:** Already doesn't need Draw(), just remove SetupFakeDriver
|
||||
|
||||
#### Tab Handling Tests (3 tests) - EASY WINS
|
||||
9. **TabWith_PreserveTrailingSpaces_False**
|
||||
- Tests tab expansion logic
|
||||
- **Conversion:** Use Format() to verify tab handling
|
||||
|
||||
10. **TabWith_PreserveTrailingSpaces_True**
|
||||
- Tests tab expansion with preserved spaces
|
||||
- **Conversion:** Use Format() to verify tab handling
|
||||
|
||||
11. **TabWith_WordWrap_True**
|
||||
- Tests tab handling with word wrap
|
||||
- **Conversion:** Use Format() to verify logic
|
||||
|
||||
### ❌ KEEP IN UNITTESTS (5-8 tests require actual rendering)
|
||||
|
||||
These tests verify actual console driver behavior and should remain:
|
||||
|
||||
#### Vertical Layout Tests (Variable - need individual assessment)
|
||||
12. **Draw_Vertical_BottomTop_LeftRight**
|
||||
- Complex vertical text layout
|
||||
- May need driver to verify correct glyph positioning
|
||||
|
||||
13. **Draw_Vertical_BottomTop_RightLeft**
|
||||
- Complex vertical text with RTL
|
||||
- May need driver behavior
|
||||
|
||||
14. **Draw_Vertical_Bottom_Horizontal_Right**
|
||||
- Mixed orientation layout
|
||||
- Driver-dependent positioning
|
||||
|
||||
15. **Draw_Vertical_TopBottom_LeftRight**
|
||||
16. **Draw_Vertical_TopBottom_LeftRight_Middle**
|
||||
17. **Draw_Vertical_TopBottom_LeftRight_Top**
|
||||
- Various vertical alignments
|
||||
- Some may be convertible, others may need driver
|
||||
|
||||
#### Unicode/Rendering Tests (MUST STAY)
|
||||
18. **Draw_With_Combining_Runes**
|
||||
- Tests Unicode combining character rendering
|
||||
- **Must stay:** Verifies actual glyph composition in driver
|
||||
|
||||
19. **Draw_Vertical_Throws_IndexOutOfRangeException_With_Negative_Bounds**
|
||||
- Tests error handling with invalid bounds
|
||||
- **Must stay:** Tests Draw() method directly
|
||||
|
||||
#### Complex Tests (NEED INDIVIDUAL REVIEW)
|
||||
20. **Draw_Text_Justification** (Theory with 44 InlineData)
|
||||
- Massive test with many scenarios
|
||||
- Some may be convertible, others may need driver
|
||||
|
||||
21. **Justify_Horizontal**
|
||||
- Tests justification logic
|
||||
- Possibly convertible
|
||||
|
||||
22. **UICatalog_AboutBox_Text**
|
||||
- Tests real-world complex text
|
||||
- May need driver for full verification
|
||||
|
||||
## Conversion Strategy
|
||||
|
||||
### Step 1: Easy Conversions (5 tests - 30 minutes)
|
||||
Convert tests that already mostly test logic:
|
||||
- FormatAndGetSize_Returns_Correct_Size
|
||||
- FormatAndGetSize_WordWrap_False_Returns_Correct_Size
|
||||
- TabWith_PreserveTrailingSpaces_False
|
||||
- TabWith_PreserveTrailingSpaces_True
|
||||
- TabWith_WordWrap_True
|
||||
|
||||
**Change required:**
|
||||
```csharp
|
||||
// Before
|
||||
[SetupFakeDriver]
|
||||
[Theory]
|
||||
[InlineData(...)]
|
||||
public void Test_Name(params)
|
||||
{
|
||||
tf.Draw(...);
|
||||
DriverAssert.AssertDriverContentsWithFrameAre(expected, _output);
|
||||
}
|
||||
|
||||
// After
|
||||
[Theory]
|
||||
[InlineData(...)]
|
||||
public void Test_Name(params)
|
||||
{
|
||||
var result = tf.Format();
|
||||
Assert.Equal(expected, result);
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: Alignment Test Conversions (10 tests - 1-2 hours)
|
||||
Convert horizontal alignment tests (Centered, Justified, Left, Right):
|
||||
- Replace Draw() with Format()
|
||||
- Remove DriverAssert, use Assert.Equal on string
|
||||
- Test output logic without driver
|
||||
|
||||
### Step 3: Direction Test Conversions (2 tests - 30 minutes)
|
||||
Convert RightLeft direction tests:
|
||||
- These manipulate strings, not render-specific
|
||||
- Use Format() to verify string reversal logic
|
||||
|
||||
### Step 4: Evaluate Vertical Tests (Variable - 1-2 hours)
|
||||
Individually assess each vertical test:
|
||||
- Some may be convertible to Format() logic tests
|
||||
- Others genuinely test driver glyph positioning
|
||||
- Keep those that need driver behavior
|
||||
|
||||
### Step 5: Complex Test Assessment (3 tests - 1-2 hours)
|
||||
Evaluate Draw_Text_Justification, Justify_Horizontal, UICatalog_AboutBox_Text:
|
||||
- May require splitting into logic + rendering tests
|
||||
- Logic parts can migrate, rendering parts stay
|
||||
|
||||
## Expected Results
|
||||
|
||||
### After Full Migration
|
||||
- **Migrated to Parallelizable:** 17-20 tests (63-74%)
|
||||
- **Remaining in UnitTests:** 7-10 tests (26-37%)
|
||||
- 2 Autocomplete integration tests
|
||||
- 5-8 TextFormatter rendering tests
|
||||
|
||||
### Performance Impact
|
||||
- **Current Text/ tests:** ~10.18s for 467 tests (from performance analysis)
|
||||
- **After migration:** Estimated 8-9s for remaining integration tests
|
||||
- **Savings:** ~1.2-2.2s (12-22% reduction in Text/ folder)
|
||||
|
||||
### Test Quality Improvements
|
||||
1. **Better test focus:** Separates logic testing from rendering testing
|
||||
2. **Faster feedback:** Logic tests run in parallel without driver overhead
|
||||
3. **Clearer intent:** Tests named Format_* clearly test logic, Draw_* test rendering
|
||||
4. **Easier maintenance:** Logic tests don't depend on driver implementation details
|
||||
|
||||
## Conclusion
|
||||
|
||||
The Text/ folder is an excellent candidate for migration because:
|
||||
|
||||
1. **2 tests already migrated** with zero refactoring (AutocompleteTests)
|
||||
2. **15-18 tests are testing logic** but using driver unnecessarily
|
||||
3. **Clear conversion pattern** exists (Draw → Format)
|
||||
4. **High success rate:** 63-74% of tests can be migrated
|
||||
|
||||
The remaining 26-37% are legitimate integration tests that verify actual rendering behavior and should appropriately remain in UnitTests.
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. ✅ **DONE:** Migrate 2 AutocompleteTests (Test_GenerateSuggestions_Simple, TestSettingSchemeOnAutocomplete)
|
||||
2. **TODO:** Convert 5 easy TextFormatterTests (FormatAndGetSize, TabWith tests)
|
||||
3. **TODO:** Convert 10 alignment tests (Horizontal Centered/Justified/Left/Right)
|
||||
4. **TODO:** Assess and convert 2-5 additional tests
|
||||
5. **TODO:** Document remaining tests as integration tests
|
||||
|
||||
---
|
||||
|
||||
**Report Created:** 2025-10-20
|
||||
**Tests Analyzed:** 27 tests across 2 files
|
||||
**Migration Status:** 2/27 migrated (7.4%), 15-18/27 planned (63-74% total potential)
|
||||
@@ -8,7 +8,7 @@ namespace UnitTests;
|
||||
/// <summary>
|
||||
/// Provides xUnit-style assertions for <see cref="IDriver"/> contents.
|
||||
/// </summary>
|
||||
internal partial class DriverAssert
|
||||
public partial class DriverAssert
|
||||
{
|
||||
private const char SPACE_CHAR = ' ';
|
||||
private static readonly Rune _spaceRune = (Rune)SPACE_CHAR;
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\UnitTests\DriverAssert.cs" Link="DriverAssert.cs" />
|
||||
<Compile Include="..\UnitTests\OutputAssert.cs" Link="OutputAssert.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user