Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added filters by cache, SMT and Performance in the Affinity Dialog. #16

Merged
merged 1 commit into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/PPM.Application.Tests/PPM.Application.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
<PackageReference Include="Verify.NUnit" Version="28.9.0" />

</ItemGroup>
<ItemGroup>
Expand Down
25 changes: 17 additions & 8 deletions src/PPM.Application.Tests/ViewModels/MainPageViewModelTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using FluentAssertions;
using FluentAssertions.Events;
using NUnit.Framework;
using PPM.Unsafe;

namespace PPM.Application.Tests.ViewModels
{
Expand Down Expand Up @@ -40,7 +41,7 @@ public void Add_ShouldAddNewProcessConfigurationView_WhenProcessNameIsValid()
// Arrange
_viewModel.NewProcessName = "TestProcess.exe";
ProcessConfiguration processConfiguration = new("TestProcess");
ProcessConfigurationView processConfigurationView = new(processConfiguration, A.Fake<IOptionsProvider>(), _configurationApplier);
ProcessConfigurationView processConfigurationView = new(processConfiguration, CreateFakeOptionsProvider(), _configurationApplier);
A.CallTo(() => _viewFactory.Create(A<ProcessConfiguration>.Ignored)).Returns(processConfigurationView);

// Act
Expand Down Expand Up @@ -75,7 +76,7 @@ public void Add_ShouldNotAddNewProcessConfigurationView_WhenProcessNameIsInvalid
// Arrange
_viewModel.NewProcessName = "testexe";

using var monitor = _viewModel.Monitor();
using IMonitor<MainPageViewModel> monitor = _viewModel.Monitor();

// Act
_viewModel.Add();
Expand All @@ -91,7 +92,7 @@ public void Add_ShouldNotAddNewProcessConfigurationView_WhenProcessNameIsInvalid
public async Task SaveChangesAsync_ShouldSaveDirtyProcessConfigurations()
{
// Arrange
IOptionsProvider optionsProviderMock = A.Fake<IOptionsProvider>();
IOptionsProvider optionsProviderMock = CreateFakeOptionsProvider();

ProcessConfiguration processConfiguration = new("TestProcess");
ProcessConfigurationView processConfigurationView = new(processConfiguration, optionsProviderMock, _configurationApplier);
Expand All @@ -105,7 +106,7 @@ public async Task SaveChangesAsync_ShouldSaveDirtyProcessConfigurations()
_viewModel = new MainPageViewModel(_repository, _viewFactory, _autocompleteProvider, _configurationApplier);
await _viewModel.ReloadAsync();

using FluentAssertions.Events.IMonitor<MainPageViewModel> monitor = _viewModel.Monitor();
using IMonitor<MainPageViewModel> monitor = _viewModel.Monitor();

// Act
await _viewModel.SaveChangesAsync();
Expand All @@ -122,7 +123,7 @@ public async Task SaveChangesAsync_ShouldRefreshTheListAfterSave()
{
// Arrange
ProcessConfiguration processConfiguration = new("TestProcess");
ProcessConfigurationView processConfigurationView = new(processConfiguration, A.Fake<IOptionsProvider>(), _configurationApplier);
ProcessConfigurationView processConfigurationView = new(processConfiguration, CreateFakeOptionsProvider(), _configurationApplier);

// These are expectations that are returned before Save.
BindingCollectionWithUniqunessCheck<ProcessConfigurationView> processViews = new() { processConfigurationView };
Expand Down Expand Up @@ -159,7 +160,7 @@ public async Task ReloadAsync_ShouldReloadProcessConfigurations()
{
// Arrange
ProcessConfiguration processConfiguration = new("TestProcess");
ProcessConfigurationView processConfigurationView = new(processConfiguration, A.Fake<IOptionsProvider>(), _configurationApplier);
ProcessConfigurationView processConfigurationView = new(processConfiguration, CreateFakeOptionsProvider(), _configurationApplier);
BindingCollectionWithUniqunessCheck<ProcessConfigurationView> processConfigurations = new() { processConfigurationView };
A.CallTo(() => _viewFactory.CreateCollection(A<IEnumerable<ProcessConfiguration>>.Ignored)).Returns(processConfigurations);

Expand Down Expand Up @@ -233,8 +234,7 @@ public void SaveChangesAsync_ShouldSetSaveInProgressToFalse_WhenSaveIsCompleted(
public async Task SaveChangesAsync_ShouldApplyConfigurationsAsync([Values(true, false)] bool applyOnRunningProcess)
{
// Arrange
var optionsProvider = A.Fake<IOptionsProvider>();
A.CallTo(() => optionsProvider.NumberOfLogicalCpus).Returns(5u);
IOptionsProvider optionsProvider = CreateFakeOptionsProvider();

ProcessConfiguration processConfiguration = new("TestProcess");
A.CallTo(() => _repository.Get()).Returns(new List<ProcessConfiguration> { processConfiguration });
Expand Down Expand Up @@ -279,5 +279,14 @@ public void GetAutoCompleteList_ShouldReturnAutocompleteListBasedOnNewProcessNam
Assert.That(result, Is.SameAs(expectedList));
A.CallTo(() => _autocompleteProvider.GetAutocompleteList(processName)).MustHaveHappened();
}


private static IOptionsProvider CreateFakeOptionsProvider()
{
IOptionsProvider optionsProvider = A.Fake<IOptionsProvider>();
A.CallTo(() => optionsProvider.NumberOfLogicalCpus).Returns(5u);
A.CallTo(() => optionsProvider.ProcessorCoresInfo).Returns((IReadOnlyList<CoreInfo>)A.CollectionOfFake<CoreInfo>(5));
return optionsProvider;
}
}
}
279 changes: 279 additions & 0 deletions src/PPM.Application.Tests/ViewWrappers/Affinity/AffinityViewTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
using System.Collections.Generic;
using System.Linq;
using Affinity_manager.ViewWrappers;
using Affinity_manager.ViewWrappers.Affinity;
using FluentAssertions;
using FluentAssertions.Events;
using NUnit.Framework;
using PPM.Unsafe;

namespace PPM.Application.Tests.ViewWrappers.Affinity
{
[TestFixture]
public class AffinityViewTests
{
private AffinityView _affinityView;

[SetUp]
public void SetUp()
{
List<CoreInfo> coreInfos = Enumerable.Range(0, 4).Select(i =>
{
CoreInfo coreInfo = new() { Id = (uint)i };
PhysicalCoreGroup physicalCoreGroup = new() { Id = (uint)i / 2 };
coreInfo.AddAssociatedGroup(physicalCoreGroup);
return coreInfo;
}).ToList();
_affinityView = new AffinityView(0b1010, coreInfos);
}

[Test]
public void Constructor_ShouldInitializeProperties()
{
Assert.That(_affinityView.AffinityMask, Is.EqualTo(0b1010));
Assert.That(_affinityView.LogicalCpus.Count, Is.EqualTo(4));
}

private static IEnumerable<TestCaseData> FriendlyViewTestData()
{
yield return new TestCaseData(0b1010ul, $"{string.Format(Affinity_manager.Strings.PPM.CpuFormat, 1)}, {string.Format(Affinity_manager.Strings.PPM.CpuFormat, 3)}");
yield return new TestCaseData(0b1100ul, $"{string.Format(Affinity_manager.Strings.PPM.CpuFormat, 2)}, {string.Format(Affinity_manager.Strings.PPM.CpuFormat, 3)}");
yield return new TestCaseData(0b1111ul, Affinity_manager.Strings.PPM.AllCpus);
}

[TestCaseSource(nameof(FriendlyViewTestData))]
public void FriendlyView_ShouldReturnCorrectString(ulong affinity, string expectedFriendlyView)
{
_affinityView.UpdateAffinityMask(affinity);
Assert.That(_affinityView.FriendlyView, Is.EqualTo(expectedFriendlyView));
}

[TestCase(0b1010ul, false)]
[TestCase(0b1011ul, false)]
[TestCase(0b1111ul, true)]
public void AllCpus_ShoudReturnIfAllCoresAreSelected(ulong affinity, bool expected)
{
_affinityView.UpdateAffinityMask(affinity);
Assert.That(_affinityView.AllCpus, Is.EqualTo(expected));
}

[Test]
public void AllCpus_ShouldReturnFalse_WhenNotAllCoresAreEnabled()
{
Assert.That(_affinityView.AllCpus, Is.False);
}

[Test]
public void CanAccept_ShouldReturnTrue_WhenAtLeastOneCoreIsEnabled()
{
Assert.That(_affinityView.CanAccept, Is.True);
}

[Test]
public void CanAccept_ShouldReturnFalse_WhenNoCoresAreEnabled()
{
_affinityView.AllCpus = false;
Assert.That(_affinityView.CanAccept, Is.False);
}

[Test]
public void UpdateAffinityMask_ShouldUpdateAffinityMask()
{
_affinityView.UpdateAffinityMask(0b1100);
Assert.That(_affinityView.AffinityMask, Is.EqualTo(0b1100));
}

[Test]
public void ApplyChanges_ShouldUpdateAffinityMask()
{
_affinityView.LogicalCpus[0].Selected = true;
_affinityView.ApplyChanges();
Assert.That(_affinityView.AffinityMask - (ulong.MaxValue << 4), Is.EqualTo(0b1011));
}

[Test]
public void CancelChanges_ShouldRevertAffinityMask()
{
_affinityView.LogicalCpus[0].Selected = true;
_affinityView.CancelChanges();
Assert.That(_affinityView.AffinityMask, Is.EqualTo(0b1010));
}

[Test]
public void PropertyChanged_ShouldBeRaised_WhenAffinityMaskChanges()
{
using IMonitor<AffinityView> monitor = _affinityView.Monitor();
_affinityView.UpdateAffinityMask(0b1100);

monitor.Should().RaisePropertyChangeFor((view) => view.FriendlyView);
monitor.Should().RaisePropertyChangeFor((view) => view.AllCpus);
monitor.Should().RaisePropertyChangeFor((view) => view.CanAccept);
}

[Test]
public void PropertyChanged_ShouldBeRaised_WhenCoreViewIsChanged()
{
using IMonitor<AffinityView> monitor = _affinityView.Monitor();
_affinityView.LogicalCpus[0].Selected = true;
monitor.Should().RaisePropertyChangeFor((view) => view.AllCpus);
monitor.Should().RaisePropertyChangeFor((view) => view.CanAccept);
}

private static IEnumerable<TestCaseData> SelectSmtCoresTestData()
{
yield return new TestCaseData(0b0111ul, true, true);
yield return new TestCaseData(0b0010ul, false, false);
}

[TestCaseSource(nameof(SelectSmtCoresTestData))]
public void SelectSmtCores_ShouldSetAndGetCorrectValue(ulong expectedAffinity, bool value, bool expected)
{
_affinityView.UpdateAffinityMask(0b0010ul);
_affinityView.SelectSmtCores = value;
Assert.That(_affinityView.SelectSmtCores, Is.EqualTo(expected));
_affinityView.ApplyChanges();
Assert.That(_affinityView.AffinityMask - (ulong.MaxValue << 4), Is.EqualTo(expectedAffinity));
Assert.That(_affinityView.ShowSmtCores, Is.True);
}

[Test]
public void SelectSmtCores_ShouldReturnNull_WhenMixedCoresAreSelected()
{
_affinityView.UpdateAffinityMask(0b1011ul);
Assert.That(_affinityView.SelectSmtCores, Is.Null);
}
[Test]
public void ShowSmtCores_ShouldReturnCorrectValue()
{
// In DEBUG mode, ShowSmtCores should always return true
#if !DEBUG
// In non-DEBUG mode, ShowSmtCores should return true if there are SMT related cores

List<CoreInfo> coreInfos = Enumerable.Range(0, 4).Select(i =>
{
CoreInfo coreInfo = new() { Id = (uint)i };
PhysicalCoreGroup physicalCoreGroup = new() { Id = (uint)i };
coreInfo.AddAssociatedGroup(physicalCoreGroup);
return coreInfo;
}).ToList();

_affinityView = new AffinityView(uint.MaxValue, coreInfos);
Assert.That(_affinityView.ShowSmtCores, Is.False);
#else
Assert.Ignore("Only run in release mode.");
#endif
}

[Test]
public void CacheGroupView_ShouldInitializeCorrectly_BasedOnConstructorArguments()
{
CoreInfo[] coreInfos = CreateCoreInfosForGroupViewTest();

AffinityView affinityView = new(0b1010, coreInfos);
Affinity_manager.ViewWrappers.Affinity.GroupsView<CacheCoreGroup> cacheGroupView = affinityView.CacheGroupView;

Assert.That(cacheGroupView, Is.Not.Null);
Assert.That(cacheGroupView.Groups.Select(view => view.CoreGroup), Is.EqualTo(new[] { new CacheCoreGroup { Id = 0, Level = 3 }, new CacheCoreGroup { Id = 1, Level = 3 } }).AsCollection);
}

[Test]
public void PerformanceGroupView_ShouldInitializeCorrectly_BasedOnConstructorArguments()
{
CoreInfo[] coreInfos = CreateCoreInfosForGroupViewTest();

AffinityView affinityView = new(0b1010, coreInfos);
Affinity_manager.ViewWrappers.Affinity.GroupsView<PerformanceCoreGroup> performanceGroupView = affinityView.PerformanceGroupView;

Assert.That(performanceGroupView, Is.Not.Null);
Assert.That(performanceGroupView.Groups.Select(view => view.CoreGroup), Is.EqualTo(new[] { new PerformanceCoreGroup { Id = 3 }, new PerformanceCoreGroup { Id = 1 }, new PerformanceCoreGroup { Id = 0 } }).AsCollection);
}

[Test]
public void CacheGroupView_ShouldBeInSyncWithCoreViews()
{
CoreInfo[] coreInfos = CreateCoreInfosForGroupViewTest();
AffinityView affinityView = new(0b1010, coreInfos);
Affinity_manager.ViewWrappers.Affinity.GroupsView<CacheCoreGroup> cacheGroupView = affinityView.CacheGroupView;

// Change the selection state of a cache group
Assert.That(cacheGroupView.Groups[0].Selected, Is.Not.True);
cacheGroupView.Groups[0].Selected = true;

// Verify that the corresponding CoreViews are updated
foreach (CoreView? coreView in affinityView.LogicalCpus.Where(cpu => cpu.CoreInfo.AssociatedGroups.Contains(cacheGroupView.Groups[0].CoreGroup)))
{
Assert.That(coreView.Selected, Is.True);
}
}

[Test]
public void PerformanceGroupView_ShouldBeInSyncWithCoreViews()
{
CoreInfo[] coreInfos = CreateCoreInfosForGroupViewTest();
AffinityView affinityView = new(0b1010, coreInfos);
Affinity_manager.ViewWrappers.Affinity.GroupsView<PerformanceCoreGroup> performanceGroupView = affinityView.PerformanceGroupView;

// Change the selection state of a performance group
Assert.That(performanceGroupView.Groups[2].Selected, Is.Not.True);
performanceGroupView.Groups[2].Selected = true;

// Verify that the corresponding CoreViews are updated
foreach (CoreView? coreView in affinityView.LogicalCpus.Where(cpu => cpu.CoreInfo.AssociatedGroups.Contains(performanceGroupView.Groups[2].CoreGroup)))
{
Assert.That(coreView.Selected, Is.True);
}
}

[Test]
public void CoreViews_ShouldBeInSyncWithCacheGroupView()
{
CoreInfo[] coreInfos = CreateCoreInfosForGroupViewTest();
AffinityView affinityView = new(0b1010, coreInfos);
Affinity_manager.ViewWrappers.Affinity.GroupsView<CacheCoreGroup> cacheGroupView = affinityView.CacheGroupView;

// Change the selection state of a CoreView
Assert.That(cacheGroupView.Groups[0].Selected, Is.False);
CoreView coreViewToChange = affinityView.LogicalCpus.First(cpu => cpu.CoreInfo.AssociatedGroups.Contains(cacheGroupView.Groups[0].CoreGroup));
coreViewToChange.Selected = true;

// Verify that the corresponding cache group is updated
Assert.That(cacheGroupView.Groups[0].Selected, Is.True);
}

[Test]
public void CoreViews_ShouldBeInSyncWithPerformanceGroupView()
{
CoreInfo[] coreInfos = CreateCoreInfosForGroupViewTest();
AffinityView affinityView = new(0b1010, coreInfos);
Affinity_manager.ViewWrappers.Affinity.GroupsView<PerformanceCoreGroup> performanceGroupView = affinityView.PerformanceGroupView;

// Change the selection state of a CoreView
Assert.That(performanceGroupView.Groups[2].Selected, Is.False);
IEnumerable<CoreView> coreViewsToChange = affinityView.LogicalCpus.Where(cpu => cpu.CoreInfo.AssociatedGroups.Contains(performanceGroupView.Groups[2].CoreGroup));
foreach (CoreView? item in coreViewsToChange)
{
item.Selected = true;
}

// Verify that the corresponding performance group is updated
Assert.That(performanceGroupView.Groups[2].Selected, Is.True);
}


private static CoreInfo[] CreateCoreInfosForGroupViewTest()
{
CoreInfo[] coreInfos = Enumerable.Range(0, 4).Select(id => new CoreInfo { Id = (uint)id }).ToArray();
coreInfos[0].AddAssociatedGroup(new CacheCoreGroup { Id = 0, Level = 3 });
coreInfos[1].AddAssociatedGroup(new CacheCoreGroup { Id = 1, Level = 3 });
coreInfos[2].AddAssociatedGroup(new CacheCoreGroup { Id = 2, Level = 2 });
coreInfos[3].AddAssociatedGroup(new CacheCoreGroup { Id = 1, Level = 3 });

coreInfos[0].AddAssociatedGroup(new PerformanceCoreGroup { Id = 0 });
coreInfos[1].AddAssociatedGroup(new PerformanceCoreGroup { Id = 1 });
coreInfos[2].AddAssociatedGroup(new PerformanceCoreGroup { Id = 0 });
coreInfos[3].AddAssociatedGroup(new PerformanceCoreGroup { Id = 3 });

return coreInfos;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Фізичне ядро: 1
Клас швидкодії: 1 (більше — швидше)
Кеш 1 рівня: 1 КіБ + 2 КіБ
Кеш 3 рівня: 5 Б + 17 Б
Loading
Loading