diff --git a/Domain/ArrayType.cs b/Domain/ArrayType.cs new file mode 100644 index 0000000000000000000000000000000000000000..a3067d0b78cdff9a84bc7cfcc0a6b219cad8c6aa --- /dev/null +++ b/Domain/ArrayType.cs @@ -0,0 +1,15 @@ +namespace Domain; + +public enum ArrayType +{ + Int, + Char, + Varchar +} + +public static class ArrayTypeTranscript +{ + public const string Int = "int"; + public const string Char = "char"; + public const string Varchar = "varchar"; +} \ No newline at end of file diff --git a/Domain/Domain.csproj b/Domain/Domain.csproj new file mode 100644 index 0000000000000000000000000000000000000000..f53c6e7ae89f09f9f46e425b802c1e542796f516 --- /dev/null +++ b/Domain/Domain.csproj @@ -0,0 +1,18 @@ +п»ї<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net9.0</TargetFramework> + <ImplicitUsings>enable</ImplicitUsings> + <Nullable>enable</Nullable> + <RootNamespace>Domain</RootNamespace> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.2" /> + </ItemGroup> + + <ItemGroup> + <Folder Include="Service\" /> + </ItemGroup> + +</Project> diff --git a/Domain/Exception/VirtualMemoryAlreadyInitializedException.cs b/Domain/Exception/VirtualMemoryAlreadyInitializedException.cs new file mode 100644 index 0000000000000000000000000000000000000000..97b0bb885b6a3c38e9308f8183430832346384a8 --- /dev/null +++ b/Domain/Exception/VirtualMemoryAlreadyInitializedException.cs @@ -0,0 +1,3 @@ +namespace Domain.Exception; + +public class VirtualMemoryAlreadyInitializedException: InvalidOperationException; \ No newline at end of file diff --git a/Domain/Exception/VirtualMemoryNotInitializedException.cs b/Domain/Exception/VirtualMemoryNotInitializedException.cs new file mode 100644 index 0000000000000000000000000000000000000000..ede7fa3632d73b6d619135da5c3ca7712b03c8aa --- /dev/null +++ b/Domain/Exception/VirtualMemoryNotInitializedException.cs @@ -0,0 +1,3 @@ +namespace Domain.Exception; + +public class VirtualMemoryNotInitializedException: InvalidOperationException; \ No newline at end of file diff --git a/Domain/UseCase/ICreateCharVirtualMemoryUseCase.cs b/Domain/UseCase/ICreateCharVirtualMemoryUseCase.cs new file mode 100644 index 0000000000000000000000000000000000000000..46d39a3368f16a133a77500298ca17e3e837a0de --- /dev/null +++ b/Domain/UseCase/ICreateCharVirtualMemoryUseCase.cs @@ -0,0 +1,6 @@ +namespace Domain.UseCase; + +public interface ICreateCharVirtualMemoryUseCase +{ + void Invoke(string fileName, int size, int fixedStringLength); +} \ No newline at end of file diff --git a/Domain/UseCase/ICreateIntVirtualMemoryUseCase.cs b/Domain/UseCase/ICreateIntVirtualMemoryUseCase.cs new file mode 100644 index 0000000000000000000000000000000000000000..02f92f6ac01a577076340fcaae9373e39ccdbc59 --- /dev/null +++ b/Domain/UseCase/ICreateIntVirtualMemoryUseCase.cs @@ -0,0 +1,6 @@ +namespace Domain.UseCase; + +public interface ICreateIntVirtualMemoryUseCase +{ + void Invoke(string fileName, int size); +} \ No newline at end of file diff --git a/Domain/UseCase/ICreateVarcharVirtualMemoryUseCase.cs b/Domain/UseCase/ICreateVarcharVirtualMemoryUseCase.cs new file mode 100644 index 0000000000000000000000000000000000000000..96502ff9c0106796e2bbd6b3e2db571042d2a442 --- /dev/null +++ b/Domain/UseCase/ICreateVarcharVirtualMemoryUseCase.cs @@ -0,0 +1,6 @@ +namespace Domain.UseCase; + +public interface ICreateVarcharVirtualMemoryUseCase +{ + void Invoke(string fileName, int size, int maxStringLength); +} \ No newline at end of file diff --git a/Domain/UseCase/IExitUseCase.cs b/Domain/UseCase/IExitUseCase.cs new file mode 100644 index 0000000000000000000000000000000000000000..d5eb5ee63d3585a5c3bf9946c431750f0cb627ab --- /dev/null +++ b/Domain/UseCase/IExitUseCase.cs @@ -0,0 +1,5 @@ +namespace Domain.UseCase; +public interface IExitUseCase +{ + void Invoke(); +} \ No newline at end of file diff --git a/Domain/UseCase/IGetActiveArrayTypeUseCase.cs b/Domain/UseCase/IGetActiveArrayTypeUseCase.cs new file mode 100644 index 0000000000000000000000000000000000000000..18b037dcd4bf3018ba202d2148e910d44b0a60f6 --- /dev/null +++ b/Domain/UseCase/IGetActiveArrayTypeUseCase.cs @@ -0,0 +1,6 @@ +namespace Domain.UseCase; + +public interface IGetActiveArrayTypeUseCase +{ + ArrayType? Invoke(); +} \ No newline at end of file diff --git a/Domain/UseCase/IGetItemByIndexUseCase.cs b/Domain/UseCase/IGetItemByIndexUseCase.cs new file mode 100644 index 0000000000000000000000000000000000000000..6fc0f9a3af3886b50ac52b38122865ee333a2f70 --- /dev/null +++ b/Domain/UseCase/IGetItemByIndexUseCase.cs @@ -0,0 +1,6 @@ +namespace Domain.UseCase; + +public interface IGetItemByIndexUseCase +{ + string Invoke(int index); +} \ No newline at end of file diff --git a/Domain/UseCase/IInputIntUseCase.cs b/Domain/UseCase/IInputIntUseCase.cs new file mode 100644 index 0000000000000000000000000000000000000000..a8988bf9c84022968123f74dd8d7059b6b1f6b71 --- /dev/null +++ b/Domain/UseCase/IInputIntUseCase.cs @@ -0,0 +1,6 @@ +namespace Domain.UseCase; + +public interface IInputIntUseCase +{ + void Invoke(int index, int value); +} \ No newline at end of file diff --git a/Domain/UseCase/IInputUseCase.cs b/Domain/UseCase/IInputUseCase.cs new file mode 100644 index 0000000000000000000000000000000000000000..e2de582f6c514ecf98d5e01e15d7a5400c274169 --- /dev/null +++ b/Domain/UseCase/IInputUseCase.cs @@ -0,0 +1,6 @@ +namespace Domain.UseCase; + +public interface IInputStringUseCase +{ + void Invoke(int index, string value); +} \ No newline at end of file diff --git a/Lab1.Tests/Data/Service/InMemoryVirtualMemoryServiceTest.cs b/Lab1.Tests/Data/Service/InMemoryVirtualMemoryServiceTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..1c725e874ce8f9f1349bf891642a80a4b9d15006 --- /dev/null +++ b/Lab1.Tests/Data/Service/InMemoryVirtualMemoryServiceTest.cs @@ -0,0 +1,109 @@ +using Domain; +using Domain.Exception; +using Lab1.Data.Service; +using Lab1.Domain.Service; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using VM.Domain; + +namespace Lab1.Tests.Data.Service +{ + [TestClass] + public class InMemoryVirtualMemoryServiceTests + { + private Mock<IIntVirtualMemoryFactory> _intVirtualMemoryFactoryMock = null!; + private Mock<ICharVirtualMemoryFactory> _charVirtualMemoryFactoryMock = null!; + private Mock<IVarcharVirtualMemoryFactory> _varcharVirtualMemoryFactoryMock = null!; + private InMemoryVirtualMemoryService _service = null!; + + private Mock<IVirtualMemory<int>> _intVirtualMemoryMock = null!; + private Mock<IVirtualMemory<string>> _charVirtualMemoryMock = null!; + private Mock<IVirtualMemory<string>> _varcharVirtualMemoryMock = null!; + + [TestInitialize] + public void Setup() + { + _intVirtualMemoryFactoryMock = new Mock<IIntVirtualMemoryFactory>(); + _charVirtualMemoryFactoryMock = new Mock<ICharVirtualMemoryFactory>(); + _varcharVirtualMemoryFactoryMock = new Mock<IVarcharVirtualMemoryFactory>(); + + _service = new InMemoryVirtualMemoryService( + _intVirtualMemoryFactoryMock.Object, + _charVirtualMemoryFactoryMock.Object, + _varcharVirtualMemoryFactoryMock.Object + ); + + _intVirtualMemoryMock = new Mock<IVirtualMemory<int>>(); + _charVirtualMemoryMock = new Mock<IVirtualMemory<string>>(); + _varcharVirtualMemoryMock = new Mock<IVirtualMemory<string>>(); + } + + [TestMethod] + [ExpectedException(typeof(VirtualMemoryAlreadyInitializedException))] + public void CreateCharVirtualMemory_ThrowsException_WhenAlreadyInitialized() + { + // Arrange + string fileName = "testfile"; + int size = 1000; + int fixedStringLength = 20; + + _service.CreateCharVirtualMemory(fileName, size, fixedStringLength); + + // Act + _service.CreateCharVirtualMemory(fileName, size, fixedStringLength); // Should throw exception + } + + [TestMethod] + public void CreateVarcharVirtualMemory_CreatesVarcharVirtualMemory_WhenNotInitialized() + { + // Arrange + string fileName = "testfile"; + int size = 1000; + int maxStringLength = 50; + + _varcharVirtualMemoryFactoryMock + .Setup(x => x.Create(fileName, size, maxStringLength)) + .Returns(_varcharVirtualMemoryMock.Object); + + // Act + _service.CreateVarcharVirtualMemory(fileName, size, maxStringLength); + + // Assert + Assert.IsTrue(_service.IsActive); + Assert.AreEqual(ArrayType.Varchar, _service.ActiveArrayType); + _varcharVirtualMemoryFactoryMock.Verify(x => x.Create(fileName, size, maxStringLength), Times.Once); + + _service.CloseVirtualMemory(); + + // Assert + Assert.IsFalse(_service.IsActive); + _varcharVirtualMemoryMock.Verify(x => x.Dispose(), Times.Once); + } + + [TestMethod] + public void CreateIntVirtualMemory_CreatesIntVirtualMemory_WhenNotInitialized() + { + // Arrange + string fileName = "testfile"; + int size = 1000; + + _intVirtualMemoryFactoryMock + .Setup(x => x.Create(fileName, size)) + .Returns(_intVirtualMemoryMock.Object); + + // Act + _service.CreateIntVirtualMemory(fileName, size); + + // Assert + Assert.IsTrue(_service.IsActive); + Assert.AreEqual(ArrayType.Int, _service.ActiveArrayType); + _intVirtualMemoryFactoryMock.Verify(x => x.Create(fileName, size), Times.Once); + _service.CloseVirtualMemory(); + + // Assert + Assert.IsFalse(_service.IsActive); + _intVirtualMemoryMock.Verify(x => x.Dispose(), Times.Once); + } + + } +} \ No newline at end of file diff --git a/Lab1.Tests/Lab1.Tests.csproj b/Lab1.Tests/Lab1.Tests.csproj new file mode 100644 index 0000000000000000000000000000000000000000..f72176eaa1de92f9bd0b0f6409a9ff36e2e2031b --- /dev/null +++ b/Lab1.Tests/Lab1.Tests.csproj @@ -0,0 +1,24 @@ +п»ї<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net9.0</TargetFramework> + <LangVersion>latest</LangVersion> + <ImplicitUsings>enable</ImplicitUsings> + <Nullable>enable</Nullable> + <IsPackable>false</IsPackable> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="JetBrains.Annotations" Version="2025.1.0-eap1"/> + <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.2"/> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0"/> + <PackageReference Include="Moq" Version="4.20.72"/> + <PackageReference Include="MSTest.TestAdapter" Version="3.8.2"/> + <PackageReference Include="MSTest.TestFramework" Version="3.8.2"/> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\Lab1\Lab1.csproj" /> + </ItemGroup> + +</Project> diff --git a/Lab1.sln b/Lab1.sln index f707e5c2d313331679ed5370ce5db7ca28b6059b..1a69ca188cab0b1c8b812eb9fa5fb48ae6d15d4e 100644 --- a/Lab1.sln +++ b/Lab1.sln @@ -2,6 +2,18 @@ Microsoft Visual Studio Solution File, Format Version 12.00 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab1", "Lab1\Lab1.csproj", "{2851C4FA-47FB-4B7B-A7F2-229EC08CA36C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VM", "VM\VM.csproj", "{32D7C5DD-9647-43AC-A7C6-8323513073D0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VM.Tests", "VM.Tests\VM.Tests.csproj", "{0753A72A-6A76-4D02-81AF-B6548D8AEA41}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UI", "UI\UI.csproj", "{B0D69506-EF6C-4C37-B3E6-6D02EF1C8E14}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UI.Tests", "UI.Tests\UI.Tests.csproj", "{CFFBE5D7-BC91-4994-9BE4-0795E02E3EE7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Domain", "Domain\Domain.csproj", "{0062B734-4F10-4577-97BA-C1369C99754C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lab1.Tests", "Lab1.Tests\Lab1.Tests.csproj", "{75E2BDD0-6C46-4028-8CB2-4EAEA2B9FD27}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -12,5 +24,29 @@ Global {2851C4FA-47FB-4B7B-A7F2-229EC08CA36C}.Debug|Any CPU.Build.0 = Debug|Any CPU {2851C4FA-47FB-4B7B-A7F2-229EC08CA36C}.Release|Any CPU.ActiveCfg = Release|Any CPU {2851C4FA-47FB-4B7B-A7F2-229EC08CA36C}.Release|Any CPU.Build.0 = Release|Any CPU + {32D7C5DD-9647-43AC-A7C6-8323513073D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {32D7C5DD-9647-43AC-A7C6-8323513073D0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {32D7C5DD-9647-43AC-A7C6-8323513073D0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {32D7C5DD-9647-43AC-A7C6-8323513073D0}.Release|Any CPU.Build.0 = Release|Any CPU + {0753A72A-6A76-4D02-81AF-B6548D8AEA41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0753A72A-6A76-4D02-81AF-B6548D8AEA41}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0753A72A-6A76-4D02-81AF-B6548D8AEA41}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0753A72A-6A76-4D02-81AF-B6548D8AEA41}.Release|Any CPU.Build.0 = Release|Any CPU + {B0D69506-EF6C-4C37-B3E6-6D02EF1C8E14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B0D69506-EF6C-4C37-B3E6-6D02EF1C8E14}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B0D69506-EF6C-4C37-B3E6-6D02EF1C8E14}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B0D69506-EF6C-4C37-B3E6-6D02EF1C8E14}.Release|Any CPU.Build.0 = Release|Any CPU + {CFFBE5D7-BC91-4994-9BE4-0795E02E3EE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CFFBE5D7-BC91-4994-9BE4-0795E02E3EE7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CFFBE5D7-BC91-4994-9BE4-0795E02E3EE7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CFFBE5D7-BC91-4994-9BE4-0795E02E3EE7}.Release|Any CPU.Build.0 = Release|Any CPU + {0062B734-4F10-4577-97BA-C1369C99754C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0062B734-4F10-4577-97BA-C1369C99754C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0062B734-4F10-4577-97BA-C1369C99754C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0062B734-4F10-4577-97BA-C1369C99754C}.Release|Any CPU.Build.0 = Release|Any CPU + {75E2BDD0-6C46-4028-8CB2-4EAEA2B9FD27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {75E2BDD0-6C46-4028-8CB2-4EAEA2B9FD27}.Debug|Any CPU.Build.0 = Debug|Any CPU + {75E2BDD0-6C46-4028-8CB2-4EAEA2B9FD27}.Release|Any CPU.ActiveCfg = Release|Any CPU + {75E2BDD0-6C46-4028-8CB2-4EAEA2B9FD27}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/Lab1/Data/Service/CharVirtualMemoryFactory.cs b/Lab1/Data/Service/CharVirtualMemoryFactory.cs new file mode 100644 index 0000000000000000000000000000000000000000..2d0411cb5807f57c0b30c0784dc0650f94ca70b3 --- /dev/null +++ b/Lab1/Data/Service/CharVirtualMemoryFactory.cs @@ -0,0 +1,19 @@ +using Lab1.Domain.Service; +using VM.Domain; + +namespace Lab1.Data.Service; + +public class CharVirtualMemoryFactory(IDateTimeService dateTimeService) : ICharVirtualMemoryFactory +{ + public IVirtualMemory<string> Create(string fileName, int size, int fixedStringLength) + { + var charPageBuffer = CreatePageBuffer(fileName, size, fixedStringLength); + return new CharVirtualMemory(charPageBuffer, fixedStringLength, size); + } + + private IPageBuffer CreatePageBuffer(string fileName, int size, int fixedStringLength) + { + var swapfile = new SwapFile(fileName, fixedStringLength * size / sizeof(int)); + return new PageBuffer(swapfile, dateTimeService); + } +} \ No newline at end of file diff --git a/Lab1/Data/Service/InMemoryVirtualMemoryService.cs b/Lab1/Data/Service/InMemoryVirtualMemoryService.cs new file mode 100644 index 0000000000000000000000000000000000000000..3177fb2807ed80484e3d6dcbd7f77011fe092e86 --- /dev/null +++ b/Lab1/Data/Service/InMemoryVirtualMemoryService.cs @@ -0,0 +1,70 @@ +using Domain; +using Domain.Exception; +using Lab1.Domain.Service; +using VM.Domain; + +namespace Lab1.Data.Service; + +public class InMemoryVirtualMemoryService( + IIntVirtualMemoryFactory intVirtualMemoryFactory, + ICharVirtualMemoryFactory charVirtualMemoryFactory, + IVarcharVirtualMemoryFactory varcharVirtualMemoryFactory +) : IVirtualMemoryService +{ + private IVirtualMemory? _virtualMemory; + private ArrayType? _arrayType; + + public ArrayType? ActiveArrayType => _arrayType; + public bool IsActive => _virtualMemory != null; + + public void CreateCharVirtualMemory(string fileName, int size, int fixedStringLength) + { + if (_arrayType != null) throw new VirtualMemoryAlreadyInitializedException(); + _arrayType = ArrayType.Char; + _virtualMemory = charVirtualMemoryFactory.Create(fileName, size, fixedStringLength); + } + + public void CreateVarcharVirtualMemory(string fileName, int size, int maxStringLength) + { + if (_arrayType != null) throw new VirtualMemoryAlreadyInitializedException(); + _arrayType = ArrayType.Varchar; + _virtualMemory = varcharVirtualMemoryFactory.Create(fileName, size, maxStringLength); + } + + public void CreateIntVirtualMemory(string fileName, int size) + { + if (_arrayType != null) throw new VirtualMemoryAlreadyInitializedException(); + _arrayType = ArrayType.Int; + _virtualMemory = intVirtualMemoryFactory.Create(fileName, size); + } + + public string GetItemByIndex(int index) + { + var memory = RequireVirtualMemory(); + var value = memory.Get(index); + return value.ToString(); + } + + public void CloseVirtualMemory() + { + RequireVirtualMemory().Dispose(); + _virtualMemory = null; + _arrayType = null; + } + + public void InsertValueToActiveVirtualMemory(int index, int value) + { + RequireVirtualMemory().Set(index, value); + } + + public void InsertValueToActiveVirtualMemory(int index, string value) + { + RequireVirtualMemory().Set(index, value); + } + + private IVirtualMemory RequireVirtualMemory() + { + if (_virtualMemory == null) throw new VirtualMemoryNotInitializedException(); + return _virtualMemory; + } +} \ No newline at end of file diff --git a/Lab1/Data/Service/IntVirtualMemoryFactory.cs b/Lab1/Data/Service/IntVirtualMemoryFactory.cs new file mode 100644 index 0000000000000000000000000000000000000000..9c564d61a4642be775a5bf2fe3fc4233597e38df --- /dev/null +++ b/Lab1/Data/Service/IntVirtualMemoryFactory.cs @@ -0,0 +1,20 @@ +using Lab1.Domain.Service; +using VM.Domain; + +namespace Lab1.Data.Service; + +public class IntVirtualMemoryFactory(IDateTimeService dateTimeService) : IIntVirtualMemoryFactory +{ + public IVirtualMemory<int> Create(string fileName, int size) + { + var pageBuffer = CreatePageBuffer(fileName, size); + return new IntVirtualMemory(pageBuffer, size); + } + + + private IPageBuffer CreatePageBuffer(string fileName, int size) + { + var swapfile = new SwapFile(fileName, size); + return new PageBuffer(swapfile, dateTimeService); + } +} \ No newline at end of file diff --git a/Lab1/Data/Service/VarcharVirtualMemoryFactory.cs b/Lab1/Data/Service/VarcharVirtualMemoryFactory.cs new file mode 100644 index 0000000000000000000000000000000000000000..499c09d0ce63522779100c7c3c2d60872b16fd84 --- /dev/null +++ b/Lab1/Data/Service/VarcharVirtualMemoryFactory.cs @@ -0,0 +1,13 @@ +using Lab1.Domain.Service; +using VM.Domain; + +namespace Lab1.Data.Service; + +public class VarcharVirtualMemoryFactory : IVarcharVirtualMemoryFactory +{ + public IVirtualMemory<string> Create(string fileName, int size, int maxStringLength) + { + var swapFile = new SwapFile(fileName, size); + return new VarcharVirtualMemory(swapFile, $"{fileName}.data", maxStringLength, size); + } +} \ No newline at end of file diff --git a/Lab1/Data/UseCase/VmCreateCharVirtualMemoryUseCase.cs b/Lab1/Data/UseCase/VmCreateCharVirtualMemoryUseCase.cs new file mode 100644 index 0000000000000000000000000000000000000000..c026f4d434cedbc463c07bddc7504aa3f623a3fd --- /dev/null +++ b/Lab1/Data/UseCase/VmCreateCharVirtualMemoryUseCase.cs @@ -0,0 +1,13 @@ +using Domain.UseCase; +using Lab1.Domain.Service; + +namespace Lab1.Data.UseCase; + +public class VmCreateCharVirtualMemoryUseCase(IVirtualMemoryService virtualMemoryService) + : ICreateCharVirtualMemoryUseCase +{ + public void Invoke(string fileName, int size, int fixedStringLength) + { + virtualMemoryService.CreateCharVirtualMemory(fileName, size, fixedStringLength); + } +} \ No newline at end of file diff --git a/Lab1/Data/UseCase/VmCreateIntVirtualMemoryUseCase.cs b/Lab1/Data/UseCase/VmCreateIntVirtualMemoryUseCase.cs new file mode 100644 index 0000000000000000000000000000000000000000..2e72fbf7aa1de747eec64b155f408b6b7ad60faa --- /dev/null +++ b/Lab1/Data/UseCase/VmCreateIntVirtualMemoryUseCase.cs @@ -0,0 +1,13 @@ +using Domain.UseCase; +using Lab1.Domain.Service; + +namespace Lab1.Data.UseCase; + +public class VmCreateIntVirtualMemoryUseCase(IVirtualMemoryService virtualMemoryService) + : ICreateIntVirtualMemoryUseCase +{ + public void Invoke(string fileName, int size) + { + virtualMemoryService.CreateIntVirtualMemory(fileName, size); + } +} \ No newline at end of file diff --git a/Lab1/Data/UseCase/VmCreateVarcharVirtualMemoryUseCase.cs b/Lab1/Data/UseCase/VmCreateVarcharVirtualMemoryUseCase.cs new file mode 100644 index 0000000000000000000000000000000000000000..af494510b7a31e32fd6bce05c2ce40e37860e3fc --- /dev/null +++ b/Lab1/Data/UseCase/VmCreateVarcharVirtualMemoryUseCase.cs @@ -0,0 +1,13 @@ +using Domain.UseCase; +using Lab1.Domain.Service; + +namespace Lab1.Data.UseCase; + +public class VmCreateVarcharVirtualMemoryUseCase(IVirtualMemoryService virtualMemoryService) + : ICreateVarcharVirtualMemoryUseCase +{ + public void Invoke(string fileName, int size, int maxStringLength) + { + virtualMemoryService.CreateVarcharVirtualMemory(fileName, size, maxStringLength); + } +} \ No newline at end of file diff --git a/Lab1/Data/UseCase/VmExitUseCase.cs b/Lab1/Data/UseCase/VmExitUseCase.cs new file mode 100644 index 0000000000000000000000000000000000000000..7beee7839764b5d5dff59cc5911a18851fb8cda6 --- /dev/null +++ b/Lab1/Data/UseCase/VmExitUseCase.cs @@ -0,0 +1,12 @@ +using Domain.UseCase; +using Lab1.Domain.Service; + +namespace Lab1.Data.UseCase; + +public class VmExitUseCase(IVirtualMemoryService virtualMemoryService) : IExitUseCase +{ + public void Invoke() + { + virtualMemoryService.CloseVirtualMemory(); + } +} \ No newline at end of file diff --git a/Lab1/Data/UseCase/VmGetActiveArrayTypeUseCase.cs b/Lab1/Data/UseCase/VmGetActiveArrayTypeUseCase.cs new file mode 100644 index 0000000000000000000000000000000000000000..2cf73c910d89f99cecbba121c45c9521779dbb2d --- /dev/null +++ b/Lab1/Data/UseCase/VmGetActiveArrayTypeUseCase.cs @@ -0,0 +1,13 @@ +using Domain; +using Domain.UseCase; +using Lab1.Domain.Service; + +namespace Lab1.Data.UseCase; + +public class VmGetActiveArrayTypeUseCase(IVirtualMemoryService virtualMemoryService) : IGetActiveArrayTypeUseCase +{ + public ArrayType? Invoke() + { + return virtualMemoryService.ActiveArrayType; + } +} \ No newline at end of file diff --git a/Lab1/Data/UseCase/VmGetItemByIndexUseCase.cs b/Lab1/Data/UseCase/VmGetItemByIndexUseCase.cs new file mode 100644 index 0000000000000000000000000000000000000000..e50a445b3c196b862c436e8311d8beb1066c8553 --- /dev/null +++ b/Lab1/Data/UseCase/VmGetItemByIndexUseCase.cs @@ -0,0 +1,12 @@ +using Domain.UseCase; +using Lab1.Domain.Service; + +namespace Lab1.Data.UseCase; + +public class VmGetItemByIndexUseCase(IVirtualMemoryService virtualMemoryService) : IGetItemByIndexUseCase +{ + public string Invoke(int index) + { + return virtualMemoryService.GetItemByIndex(index); + } +} \ No newline at end of file diff --git a/Lab1/Data/UseCase/VmInputUseCase.cs b/Lab1/Data/UseCase/VmInputUseCase.cs new file mode 100644 index 0000000000000000000000000000000000000000..550b41f94f6a3f266d72145232ec2a03acb4d7aa --- /dev/null +++ b/Lab1/Data/UseCase/VmInputUseCase.cs @@ -0,0 +1,12 @@ +using Domain.UseCase; +using Lab1.Domain.Service; + +namespace Lab1.Data.UseCase; + +public class VmInputStringUseCase(IVirtualMemoryService virtualMemoryService) : IInputStringUseCase +{ + public void Invoke(int index, string value) + { + virtualMemoryService.InsertValueToActiveVirtualMemory(index, value); + } +} \ No newline at end of file diff --git a/Lab1/Data/UseCase/VmIntInputUseCase.cs b/Lab1/Data/UseCase/VmIntInputUseCase.cs new file mode 100644 index 0000000000000000000000000000000000000000..f4e26a591b00ce489386a1b8e54f2101880a0247 --- /dev/null +++ b/Lab1/Data/UseCase/VmIntInputUseCase.cs @@ -0,0 +1,12 @@ +using Domain.UseCase; +using Lab1.Domain.Service; + +namespace Lab1.Data.UseCase; + +public class VmInputIntUseCase(IVirtualMemoryService virtualMemoryService) : IInputIntUseCase +{ + public void Invoke(int index, int value) + { + virtualMemoryService.InsertValueToActiveVirtualMemory(index, value); + } +} \ No newline at end of file diff --git a/Lab1/Domain/Service/ICharVirtualMemoryFactory.cs b/Lab1/Domain/Service/ICharVirtualMemoryFactory.cs new file mode 100644 index 0000000000000000000000000000000000000000..bcdbbf8f399dea2a46f3c77408b438d174d51d3b --- /dev/null +++ b/Lab1/Domain/Service/ICharVirtualMemoryFactory.cs @@ -0,0 +1,8 @@ +using VM.Domain; + +namespace Lab1.Domain.Service; + +public interface ICharVirtualMemoryFactory +{ + IVirtualMemory<string> Create(string fileName, int size, int fixedStringLength); +} \ No newline at end of file diff --git a/Lab1/Domain/Service/IIntVirtualMemoryFactory.cs b/Lab1/Domain/Service/IIntVirtualMemoryFactory.cs new file mode 100644 index 0000000000000000000000000000000000000000..5dfd6c321173bdf23145ad90dc833cd0120919d3 --- /dev/null +++ b/Lab1/Domain/Service/IIntVirtualMemoryFactory.cs @@ -0,0 +1,8 @@ +using VM.Domain; + +namespace Lab1.Domain.Service; + +public interface IIntVirtualMemoryFactory +{ + IVirtualMemory<int> Create(string fileName, int size); +} \ No newline at end of file diff --git a/Lab1/Domain/Service/IVarcharVirtualMemoryFactory.cs b/Lab1/Domain/Service/IVarcharVirtualMemoryFactory.cs new file mode 100644 index 0000000000000000000000000000000000000000..c1ec72f959bf17e41f465618b04228bfc5594b5f --- /dev/null +++ b/Lab1/Domain/Service/IVarcharVirtualMemoryFactory.cs @@ -0,0 +1,8 @@ +using VM.Domain; + +namespace Lab1.Domain.Service; + +public interface IVarcharVirtualMemoryFactory +{ + IVirtualMemory<string> Create(string fileName, int size, int maxStringLength); +} \ No newline at end of file diff --git a/Lab1/Domain/Service/IVirtualMemoryService.cs b/Lab1/Domain/Service/IVirtualMemoryService.cs new file mode 100644 index 0000000000000000000000000000000000000000..0e9f7d7431e37fd0176f09b8ddfee1cfbd451b85 --- /dev/null +++ b/Lab1/Domain/Service/IVirtualMemoryService.cs @@ -0,0 +1,24 @@ +using Domain; + +namespace Lab1.Domain.Service; + +public interface IVirtualMemoryService +{ + public ArrayType? ActiveArrayType { get; } + + public bool IsActive { get; } + + void CreateCharVirtualMemory(string fileName, int size, int fixedStringLength); + + void CreateVarcharVirtualMemory(string fileName, int size, int maxStringLength); + + void CreateIntVirtualMemory(string fileName, int size); + + string GetItemByIndex(int index); + + void CloseVirtualMemory(); + + void InsertValueToActiveVirtualMemory(int index, int value); + + void InsertValueToActiveVirtualMemory(int index, string value); +} \ No newline at end of file diff --git a/Lab1/Lab1.csproj b/Lab1/Lab1.csproj index 85b49591f61b9368cbdc71fc54757d476c41f1d5..c4711cf809bfef49a473c20a0c5431be432e2ace 100644 --- a/Lab1/Lab1.csproj +++ b/Lab1/Lab1.csproj @@ -7,4 +7,13 @@ <Nullable>enable</Nullable> </PropertyGroup> + <ItemGroup> + <ProjectReference Include="..\VM\VM.csproj"/> + <ProjectReference Include="..\UI\UI.csproj" /> + </ItemGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.2" /> + </ItemGroup> + </Project> diff --git a/Lab1/Program.cs b/Lab1/Program.cs index e5dff12bc410be0bac59bc70865a8e21827ec7f3..76d77234e41c370794f626d58b2836c261b583f0 100644 --- a/Lab1/Program.cs +++ b/Lab1/Program.cs @@ -1,3 +1,48 @@ -п»ї// See https://aka.ms/new-console-template for more information +п»їusing Domain.UseCase; +using Lab1.Data.Service; +using Lab1.Data.UseCase; +using Lab1.Domain.Service; +using Microsoft.Extensions.DependencyInjection; +using UI.Impl; +using UI.Model; +using UI.Presentation; +using UI.Presentation.Command; +using VM.Domain; -Console.WriteLine("Hello, World!"); \ No newline at end of file +var services = new ServiceCollection(); + +services.AddSingleton<IDateTimeService, SystemDateTimeService>(); +services.AddSingleton<IConsole, SystemConsole>(); + +services.AddSingleton<IApplicationLifecycle, ApplicationLifecycle>(); +services.AddSingleton<IApplication, App>(); +services.AddSingleton<ICommandProcessor, CommandProcessor>(); + +services.AddSingleton<CreateCommandExecutor>(); +services.AddSingleton<ExitCommandExecutor>(); +services.AddSingleton<InputCommandExecutor>(); +services.AddSingleton<PrintCommandExecutor>(); + +services.AddTransient<ICommand, CreateCommand>(); +services.AddTransient<ICommand, ExitCommand>(); +services.AddTransient<ICommand, InputCommand>(); +services.AddTransient<ICommand, PrintCommand>(); + +services.AddSingleton<IVirtualMemoryService, InMemoryVirtualMemoryService>(); +services.AddSingleton<ICharVirtualMemoryFactory, CharVirtualMemoryFactory>(); +services.AddSingleton<IIntVirtualMemoryFactory, IntVirtualMemoryFactory>(); +services.AddSingleton<IVarcharVirtualMemoryFactory, VarcharVirtualMemoryFactory>(); + +services.AddSingleton<IExitUseCase, VmExitUseCase>(); +services.AddSingleton<IGetItemByIndexUseCase, VmGetItemByIndexUseCase>(); +services.AddSingleton<IInputIntUseCase, VmInputIntUseCase>(); +services.AddSingleton<IInputStringUseCase, VmInputStringUseCase>(); +services.AddSingleton<ICreateCharVirtualMemoryUseCase, VmCreateCharVirtualMemoryUseCase>(); +services.AddSingleton<ICreateIntVirtualMemoryUseCase, VmCreateIntVirtualMemoryUseCase>(); +services.AddSingleton<ICreateVarcharVirtualMemoryUseCase, VmCreateVarcharVirtualMemoryUseCase>(); +services.AddSingleton<IGetActiveArrayTypeUseCase, VmGetActiveArrayTypeUseCase>(); + +var serviceProvider = services.BuildServiceProvider(); + +var application = serviceProvider.GetRequiredService<IApplication>(); +application.Run(); \ No newline at end of file diff --git a/UI.Tests/Impl/ExitCommandExecutorTest.cs b/UI.Tests/Impl/ExitCommandExecutorTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..bc45c0599b1cd9e7b79d9d73027b3a88b9a81dcf --- /dev/null +++ b/UI.Tests/Impl/ExitCommandExecutorTest.cs @@ -0,0 +1,55 @@ +using System; +using Domain.UseCase; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using UI.Impl; +using UI.Presentation; + +namespace UI.Tests.Impl +{ + [TestClass] + public class ExitCommandExecutorTests + { + [TestMethod] + public void OnCommand_ValidCommand_CallsUseCaseInvoke() + { + var mockApplication = new Mock<IApplicationLifecycle>(); + var mockConsole = new Mock<IConsole>(); + var mockUseCase = new Mock<IExitUseCase>(); + var executor = new ExitCommandExecutor(mockUseCase.Object, mockApplication.Object, mockConsole.Object); + var args = new[] { "exit" }; + + executor.OnCommand("exit", args); + + mockUseCase.Verify(u => u.Invoke(), Times.Once); + } + + [TestMethod] + public void OnCommand_EmptyArguments_CallsUseCaseInvoke() + { + var mockApplication = new Mock<IApplicationLifecycle>(); + var mockUseCase = new Mock<IExitUseCase>(); + var mockConsole = new Mock<IConsole>(); + var executor = new ExitCommandExecutor(mockUseCase.Object, mockApplication.Object, mockConsole.Object); + var args = Array.Empty<string>(); + + executor.OnCommand("exit", args); + + mockUseCase.Verify(u => u.Invoke(), Times.Once); + } + + [TestMethod] + public void OnCommand_NullArguments_CallsUseCaseInvoke() + { + var mockApplication = new Mock<IApplicationLifecycle>(); + var mockUseCase = new Mock<IExitUseCase>(); + var mockConsole = new Mock<IConsole>(); + var executor = new ExitCommandExecutor(mockUseCase.Object, mockApplication.Object, mockConsole.Object); + string[] args = null; + + executor.OnCommand("exit", args); + + mockUseCase.Verify(u => u.Invoke(), Times.Once); + } + } +} \ No newline at end of file diff --git a/UI.Tests/Model/CommandTest.cs b/UI.Tests/Model/CommandTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..5b4290ad6df835d11c10ee53c1b282ed2205269f --- /dev/null +++ b/UI.Tests/Model/CommandTest.cs @@ -0,0 +1,98 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using UI.Interface; +using UI.Model; + +namespace UI.Tests.Model +{ + [TestClass] + public class CommandTests + { + [TestMethod] + public void Constructor_ThrowsException_WhenNameContainsWhitespace() + { + var mockExecutor = new Mock<ICommandExecutor>(); + Assert.ThrowsException<ArgumentException>(() => new Command("invalid name", mockExecutor.Object)); + } + + [TestMethod] + public void Constructor_ThrowsException_WhenNameIsEmpty() + { + var mockExecutor = new Mock<ICommandExecutor>(); + Assert.ThrowsException<ArgumentException>(() => new Command("", mockExecutor.Object)); + } + + [TestMethod] + public void Execute_CallsOnCommand_WithCorrectArguments() + { + var mockExecutor = new Mock<ICommandExecutor>(); + var command = new Command("test", mockExecutor.Object); + var args = new[] { "test", "arg1", "arg2" }; + + command.Execute(args); + + mockExecutor.Verify(e => e.OnCommand("test", new[] { "arg1", "arg2" }), Times.Once); + } + + [TestMethod] + public void Execute_DoesNotCallOnCommand_WhenArgsIsNull() + { + var mockExecutor = new Mock<ICommandExecutor>(); + var command = new Command("test", mockExecutor.Object); + string[] args = null; + + command.Execute(args); + + mockExecutor.Verify(e => e.OnCommand(It.IsAny<string>(), It.IsAny<string[]>()), Times.Never); + } + + [TestMethod] + public void Execute_DoesNotCallOnCommand_WhenArgsLengthIsZero() + { + var mockExecutor = new Mock<ICommandExecutor>(); + var command = new Command("test", mockExecutor.Object); + var args = Array.Empty<string>(); + + command.Execute(args); + + mockExecutor.Verify(e => e.OnCommand(It.IsAny<string>(), It.IsAny<string[]>()), Times.Never); + } + + [TestMethod] + public void Execute_DoesNotCallOnCommand_WhenFirstArgDoesNotMatchCommandName() + { + var mockExecutor = new Mock<ICommandExecutor>(); + var command = new Command("test", mockExecutor.Object); + var args = new[] { "other", "arg1" }; + + command.Execute(args); + + mockExecutor.Verify(e => e.OnCommand(It.IsAny<string>(), It.IsAny<string[]>()), Times.Never); + } + + [TestMethod] + public void Execute_CallsOnCommand_WithEmptyStringArguments() + { + var mockExecutor = new Mock<ICommandExecutor>(); + var command = new Command("test", mockExecutor.Object); + var args = new[] { "test", "" }; + + command.Execute(args); + + mockExecutor.Verify(e => e.OnCommand("test", new[] { "" }), Times.Once); + } + + [TestMethod] + public void Execute_CallsOnCommand_WithMultipleArguments() + { + var mockExecutor = new Mock<ICommandExecutor>(); + var command = new Command("test", mockExecutor.Object); + var args = new[] { "test", "arg1", "arg2", "arg3" }; + + command.Execute(args); + + mockExecutor.Verify(e => e.OnCommand("test", new[] { "arg1", "arg2", "arg3" }), Times.Once); + } + } +} diff --git a/UI.Tests/Presentation/AppTest.cs b/UI.Tests/Presentation/AppTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..f34e937a3c4a45419e89bae1bbb934338cb34db8 --- /dev/null +++ b/UI.Tests/Presentation/AppTest.cs @@ -0,0 +1,54 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using UI.Presentation; + +namespace UI.Tests.Presentation +{ + [TestClass] + public class AppTests + { + private Mock<IApplicationLifecycle> _mockLifecycle; + private Mock<ICommandProcessor> _mockCommandProcessor; + private Mock<IConsole> _mockConsole; + + private App _app; + + [TestInitialize] + public void Setup() + { + _mockLifecycle = new Mock<IApplicationLifecycle>(); + _mockCommandProcessor = new Mock<ICommandProcessor>(); + _mockConsole = new Mock<IConsole>(); + _app = new App(_mockLifecycle.Object, _mockCommandProcessor.Object, _mockConsole.Object); + } + + [TestMethod] + public void LifecycleProperty_ReturnsInjectedLifecycle() + { + Assert.AreEqual(_mockLifecycle.Object, _app.Lifecycle); + } + + [TestMethod] + public void Run_CallsProcessCommands_WhileIsRunningIsTrue() + { + _mockLifecycle.SetupSequence(l => l.IsRunning) + .Returns(true) + .Returns(true) + .Returns(false); // Остановка после РґРІСѓС… вызовов + + _app.Run(); + + _mockCommandProcessor.Verify(p => p.ProcessCommands(), Times.Exactly(2)); + } + + [TestMethod] + public void Run_DoesNotCallProcessCommands_WhenIsRunningIsFalse() + { + _mockLifecycle.Setup(l => l.IsRunning).Returns(false); + + _app.Run(); + + _mockCommandProcessor.Verify(p => p.ProcessCommands(), Times.Never); + } + } +} \ No newline at end of file diff --git a/UI.Tests/Presentation/ApplicationLifecycleTest.cs b/UI.Tests/Presentation/ApplicationLifecycleTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..f9507cc5cca4b502372f5ccaafaba867ebc4f978 --- /dev/null +++ b/UI.Tests/Presentation/ApplicationLifecycleTest.cs @@ -0,0 +1,31 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using UI.Presentation; + +namespace UI.Tests.Presentation +{ + [TestClass] + public class ApplicationLifecycleTests + { + private ApplicationLifecycle _lifecycle; + + [TestInitialize] + public void Setup() + { + _lifecycle = new ApplicationLifecycle(); + } + + [TestMethod] + public void IsRunning_ShouldBeTrue_ByDefault() + { + Assert.IsTrue(_lifecycle.IsRunning); + } + + [TestMethod] + public void Finish_ShouldSetIsRunning_ToFalse() + { + _lifecycle.Finish(); + + Assert.IsFalse(_lifecycle.IsRunning); + } + } +} \ No newline at end of file diff --git a/UI.Tests/Presentation/CommandProcessorTest.cs b/UI.Tests/Presentation/CommandProcessorTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..8671d9b71bc3153bf3b5f09a480659d910da57e8 --- /dev/null +++ b/UI.Tests/Presentation/CommandProcessorTest.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using UI.Model; +using UI.Presentation; + +namespace UI.Tests.Presentation +{ + [TestClass] + public class CommandProcessorTests + { + private Mock<ICommand> _mockCommand1; + private Mock<ICommand> _mockCommand2; + private Mock<IConsole> _mockConsole; + + [TestInitialize] + public void Setup() + { + _mockCommand1 = new Mock<ICommand>(); + _mockCommand2 = new Mock<ICommand>(); + _mockConsole = new Mock<IConsole>(); + } + + [TestMethod] + public void ProcessCommands_ShouldExecuteCommands_WhenInputIsValid() + { + // Arrange + var commands = new List<ICommand> { _mockCommand1.Object, _mockCommand2.Object }; + var processor = new CommandProcessor(commands, _mockConsole.Object); + var input = "command1 arg1 arg2"; + + // Set up the mock to return the input string + _mockConsole.Setup(c => c.ReadLine()).Returns(input); + + // Act + processor.ProcessCommands(); + + // Assert + // command1 should be executed with ["command1", "arg1", "arg2"] + _mockCommand1.Verify(c => c.Execute(new[] { "command1", "arg1", "arg2" }), Times.Once); + _mockCommand2.Verify(c => c.Execute(new[] { "command1", "arg1", "arg2" }), Times.Once); + } + } +} diff --git a/UI.Tests/Presentation/SystemConsoleTest.cs b/UI.Tests/Presentation/SystemConsoleTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..ffb5f209a1049d989e5719ceed3e9c44a461018d --- /dev/null +++ b/UI.Tests/Presentation/SystemConsoleTest.cs @@ -0,0 +1,102 @@ +using System; +using System.IO; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using UI.Presentation; + +namespace UI.Tests.Presentation +{ + [TestClass] + public class SystemConsoleTests + { + private StringWriter _stringWriter; + private StringReader _stringReader; + private TextWriter _originalOutput; + private TextReader _originalInput; + + [TestInitialize] + public void Setup() + { + // Backup original streams + _originalOutput = Console.Out; + _originalInput = Console.In; + + // Set up StringWriter to capture Console output + _stringWriter = new StringWriter(); + Console.SetOut(_stringWriter); + + // Set up StringReader for simulating Console input + _stringReader = new StringReader("Test Input"); + Console.SetIn(_stringReader); + } + + [TestCleanup] + public void Cleanup() + { + // Restore original streams + Console.SetOut(_originalOutput); + Console.SetIn(_originalInput); + + _stringWriter?.Dispose(); + _stringReader?.Dispose(); + } + + [TestMethod] + public void WriteLine_ShouldCallConsoleWriteLine_WithCorrectMessage() + { + // Arrange + var console = new SystemConsole(); + var message = "Hello, World!"; + + // Act + console.WriteLine(message); + + // Assert + var output = _stringWriter.ToString(); + Assert.AreEqual(message + Environment.NewLine, output); + } + + [TestMethod] + public void ReadLine_ShouldReturnInput_WhenNotNull() + { + // Arrange + var console = new SystemConsole(); + var expectedInput = "Test Input"; + + // Act + var result = console.ReadLine(); + + // Assert + Assert.AreEqual(expectedInput, result); + } + + [TestMethod] + public void ReadLine_ShouldReturnEmptyString_WhenInputIsNull() + { + // Arrange + var console = new SystemConsole(); + Console.SetIn(new StringReader("")); // Simulating null input + + Console.In.ReadLine(); + // Act + var result = console.ReadLine(); + + // Assert + Assert.AreEqual(string.Empty, result); + } + + [TestMethod] + public void Write_ShouldCallConsoleWrite_WithCorrectMessage() + { + // Arrange + var console = new SystemConsole(); + var message = "Test message"; + + // Act + console.Write(message); + + // Assert + var output = _stringWriter.ToString(); + Assert.AreEqual(message, output); + } + } +} diff --git a/UI.Tests/UI.Tests.csproj b/UI.Tests/UI.Tests.csproj new file mode 100644 index 0000000000000000000000000000000000000000..d7bbbe1fb26da4135707cffd0f74cae42e7de5ae --- /dev/null +++ b/UI.Tests/UI.Tests.csproj @@ -0,0 +1,22 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFrameworks>net9.0</TargetFrameworks> + + <IsPackable>false</IsPackable> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="JetBrains.Annotations" Version="2025.1.0-eap1" /> + <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.2" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" /> + <PackageReference Include="Moq" Version="4.20.72" /> + <PackageReference Include="MSTest.TestAdapter" Version="3.8.2" /> + <PackageReference Include="MSTest.TestFramework" Version="3.8.2" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\UI\UI.csproj" /> + </ItemGroup> + +</Project> \ No newline at end of file diff --git a/UI/Impl/CreateCommandExecutor.cs b/UI/Impl/CreateCommandExecutor.cs new file mode 100644 index 0000000000000000000000000000000000000000..92f39db9df169065d43609ee56ece9a704d80aac --- /dev/null +++ b/UI/Impl/CreateCommandExecutor.cs @@ -0,0 +1,130 @@ +using Domain; +using Domain.Exception; +using Domain.UseCase; +using UI.Interface; +using UI.Presentation; + +namespace UI.Impl; + +public class CreateCommandExecutor( + ICreateIntVirtualMemoryUseCase createIntVirtualMemoryUseCase, + ICreateCharVirtualMemoryUseCase createCharVirtualMemoryUseCase, + ICreateVarcharVirtualMemoryUseCase createVarcharVirtualMemoryUseCase, + IConsole console +) : ICommandExecutor +{ + public void OnCommand(string command, string[] args) + { + if (args.Length < 3) + { + PrintUsage(); + return; + } + var fileName = args[0]; + var resultArrayType = ParseArrayType(args[1], out var arrayType); + if (!resultArrayType) + { + PrintUsage(StringResource.InvalidArrayType); + return; + } + + var resultSize = int.TryParse(args[2], out var size); + if (!resultSize) + { + PrintUsage(StringResource.InvalidSize); + return; + } + switch (arrayType) + { + case ArrayType.Int: + try + { + createIntVirtualMemoryUseCase.Invoke(fileName, size); + } + catch (VirtualMemoryAlreadyInitializedException) + { + PrintUsage(StringResource.VirtualMemoryInitialized); + return; + } + break; + case ArrayType.Char: + if (args.Length < 4) + { + PrintUsage(StringResource.CharTypeRequiresStringLength); + return; + } + + if (!int.TryParse(args[3], out var fixedStringLength)) + { + PrintUsage(StringResource.InvalidStringLength); + return; + } + + try + { + createCharVirtualMemoryUseCase.Invoke(fileName, size, fixedStringLength); + } + catch (VirtualMemoryAlreadyInitializedException) + { + PrintUsage(StringResource.VirtualMemoryInitialized); + return; + } + + break; + case ArrayType.Varchar: + if (args.Length < 4) + { + PrintUsage(StringResource.VarcharTypeRequiresMaxStringLength); + return; + } + if (!int.TryParse(args[3], out var maxStringLength)) + { + PrintUsage(StringResource.InvalidStringLength); + return; + } + try + { + createVarcharVirtualMemoryUseCase.Invoke(fileName, size, maxStringLength); + } + catch (VirtualMemoryAlreadyInitializedException) + { + PrintUsage(StringResource.VirtualMemoryInitialized); + return; + } + + break; + default: + throw new ArgumentOutOfRangeException(); + } + + console.WriteLine(StringResource.ArrayCreated); + } + + private void PrintUsage(string prefix = "") + { + if (prefix.Length != 0) + { + console.Write(prefix + " "); + } + console.WriteLine($"{StringResource.CommandsUsage.Create}"); + } + + private bool ParseArrayType(string input, out ArrayType result) + { + switch (input.ToLower()) + { + case ArrayTypeTranscript.Int: + result = ArrayType.Int; + return true; + case ArrayTypeTranscript.Char: + result = ArrayType.Char; + return true; + case ArrayTypeTranscript.Varchar: + result = ArrayType.Varchar; + return true; + default: + result = ArrayType.Int; + return false; + } + } +} \ No newline at end of file diff --git a/UI/Impl/ExitCommandExecutor.cs b/UI/Impl/ExitCommandExecutor.cs new file mode 100644 index 0000000000000000000000000000000000000000..fe19f9344d0835950084ee07fb6852ad29c2bdbe --- /dev/null +++ b/UI/Impl/ExitCommandExecutor.cs @@ -0,0 +1,31 @@ +using Domain.Exception; +using Domain.UseCase; +using UI.Interface; +using UI.Presentation; + +namespace UI.Impl; + +public class ExitCommandExecutor(IExitUseCase useCase, IApplicationLifecycle applicationLifecycle, IConsole console): ICommandExecutor +{ + public void OnCommand(string command, string[] args) + { + applicationLifecycle.Finish(); + try + { + useCase.Invoke(); + } + catch (VirtualMemoryNotInitializedException) + { + console.WriteLine(StringResource.Exit); + } + } + + private void PrintUsage(string prefix = "") + { + if (prefix.Length != 0) + { + console.Write(prefix + " "); + } + console.WriteLine(StringResource.CommandsUsage.Exit); + } +} \ No newline at end of file diff --git a/UI/Impl/InputCommandExecutor.cs b/UI/Impl/InputCommandExecutor.cs new file mode 100644 index 0000000000000000000000000000000000000000..64da3d46995516330bf03f5d4f1faa11458b4247 --- /dev/null +++ b/UI/Impl/InputCommandExecutor.cs @@ -0,0 +1,92 @@ +using Domain; +using Domain.Exception; +using Domain.UseCase; +using UI.Interface; +using UI.Presentation; + +namespace UI.Impl; + +public class InputCommandExecutor( + IInputIntUseCase inputIntUseCase, + IInputStringUseCase inputStringUseCase, + IGetActiveArrayTypeUseCase getActiveArrayTypeUseCase, + IConsole console +) : ICommandExecutor +{ + public void OnCommand(string command, string[] args) + { + if (args.Length < 2) + { + PrintUsage(); + return; + } + if (!int.TryParse(args[0], out var index)) + { + PrintUsage(StringResource.InvalidIndex); + return; + } + var value = args[1]; + var activeArrayType = getActiveArrayTypeUseCase.Invoke(); + switch (activeArrayType) + { + case ArrayType.Int: + try + { + if (!int.TryParse(value, out var intValue)) + { + PrintUsage(StringResource.InvalidValue); + return; + } + inputIntUseCase.Invoke(index, intValue); + break; + } + catch (VirtualMemoryNotInitializedException) + { + PrintUsage(StringResource.VirtualMemoryNotInitialized); + return; + } + catch (IndexOutOfRangeException) + { + PrintUsage(StringResource.IndexOutOfRange); + return; + } + case ArrayType.Char: + case ArrayType.Varchar: + try + { + inputStringUseCase.Invoke(index, value); + break; + } + catch (VirtualMemoryNotInitializedException) + { + PrintUsage(StringResource.VirtualMemoryNotInitialized); + return; + } + catch (IndexOutOfRangeException) + { + PrintUsage(StringResource.IndexOutOfRange); + return; + } + catch (ArgumentException) + { + PrintUsage(StringResource.StringLengthOutOfLimit); + return; + } + case null: + PrintUsage(StringResource.VirtualMemoryNotInitialized); + break; + default: + throw new ArgumentOutOfRangeException(); + } + console.WriteLine(StringResource.InputSuccess); + } + + private void PrintUsage(string prefix = "") + { + if (prefix.Length != 0) + { + console.Write(prefix + " "); + } + console.WriteLine(StringResource.CommandsUsage.Input); + } +} \ No newline at end of file diff --git a/UI/Impl/PrintCommandExecutor.cs b/UI/Impl/PrintCommandExecutor.cs new file mode 100644 index 0000000000000000000000000000000000000000..34726a4254ed0398219bfddf1250ca5643a86565 --- /dev/null +++ b/UI/Impl/PrintCommandExecutor.cs @@ -0,0 +1,46 @@ +using Domain.Exception; +using Domain.UseCase; +using UI.Interface; +using UI.Presentation; + +namespace UI.Impl; + +public class PrintCommandExecutor(IGetItemByIndexUseCase useCase, IConsole console) : ICommandExecutor +{ + public void OnCommand(string command, string[] args) + { + if (args.Length < 1) + { + PrintUsage(); + return; + } + if (!int.TryParse(args[0], out var index)) + { + PrintUsage(StringResource.InvalidIndex); + return; + } + + try + { + var value = useCase.Invoke(index); + console.WriteLine(value); + } + catch (VirtualMemoryNotInitializedException) + { + PrintUsage(StringResource.VirtualMemoryNotInitialized); + } + catch (InvalidOperationException) + { + PrintUsage(StringResource.ElementNotInitialized); + } + } + + private void PrintUsage(string prefix = "") + { + if (prefix.Length != 0) + { + console.Write(prefix + " "); + } + console.WriteLine(StringResource.CommandsUsage.Print); + } +} \ No newline at end of file diff --git a/UI/Interface/ICommandExecutor.cs b/UI/Interface/ICommandExecutor.cs new file mode 100644 index 0000000000000000000000000000000000000000..d1f9063ed093d2416ac42197c25be186a2515144 --- /dev/null +++ b/UI/Interface/ICommandExecutor.cs @@ -0,0 +1,6 @@ +namespace UI.Interface; + +public interface ICommandExecutor +{ + void OnCommand(string command, string[] args); +} \ No newline at end of file diff --git a/UI/Model/Command.cs b/UI/Model/Command.cs new file mode 100644 index 0000000000000000000000000000000000000000..20167bd59bae03d471045aed2d61742ef0a8f489 --- /dev/null +++ b/UI/Model/Command.cs @@ -0,0 +1,39 @@ +using UI.Interface; + +namespace UI.Model; + +public class Command : ICommand +{ + public Command(string name, ICommandExecutor commandExecutor) + { + _commandExecutor = commandExecutor; + Name = name; + } + + private readonly ICommandExecutor _commandExecutor; + + private readonly string _name; + + public string Name + { + get => _name; + private init + { + if (value.Any(char.IsWhiteSpace) || value.Length == 0) + { + throw new ArgumentException("Command name cannot contain whitespace or be empty"); + } + + _name = value; + } + } + + public bool Execute(string[]? args) + { + if (args == null || args.Length == 0) return false; + if (!args[0].Equals(Name)) return false; + var argsWithoutCommand = args.Skip(1).ToArray(); + _commandExecutor.OnCommand(Name, argsWithoutCommand); + return true; + } +} \ No newline at end of file diff --git a/UI/Model/ICommand.cs b/UI/Model/ICommand.cs new file mode 100644 index 0000000000000000000000000000000000000000..fc1cd82e459e1b09f35a17918a561b616019c192 --- /dev/null +++ b/UI/Model/ICommand.cs @@ -0,0 +1,7 @@ +namespace UI.Model; + +public interface ICommand +{ + public string Name { get; } + public bool Execute(string[]? args); +} \ No newline at end of file diff --git a/UI/Presentation/App.cs b/UI/Presentation/App.cs new file mode 100644 index 0000000000000000000000000000000000000000..47f172d99dd76caaafa7ead3f41b9b65b4f6cec9 --- /dev/null +++ b/UI/Presentation/App.cs @@ -0,0 +1,19 @@ +namespace UI.Presentation; + +public class App( + IApplicationLifecycle lifecycle, + ICommandProcessor commandProcessor, + IConsole console +) : IApplication +{ + public IApplicationLifecycle Lifecycle => lifecycle; + + public void Run() + { + console.WriteLine(StringResource.CommandsUsage.Help); + while (Lifecycle.IsRunning) + { + commandProcessor.ProcessCommands(); + } + } +} \ No newline at end of file diff --git a/UI/Presentation/ApplicationLifecycle.cs b/UI/Presentation/ApplicationLifecycle.cs new file mode 100644 index 0000000000000000000000000000000000000000..549da9cfeeaa59dc101408b9fb9ed7c559ec81fe --- /dev/null +++ b/UI/Presentation/ApplicationLifecycle.cs @@ -0,0 +1,11 @@ +namespace UI.Presentation; + +public class ApplicationLifecycle : IApplicationLifecycle +{ + public void Finish() + { + IsRunning = false; + } + + public bool IsRunning { get; private set; } = true; +} \ No newline at end of file diff --git a/UI/Presentation/Command/CreateCommand.cs b/UI/Presentation/Command/CreateCommand.cs new file mode 100644 index 0000000000000000000000000000000000000000..8ee03e7a945f196039aff1b3bdf838ac39edd3bb --- /dev/null +++ b/UI/Presentation/Command/CreateCommand.cs @@ -0,0 +1,5 @@ +using UI.Impl; + +namespace UI.Presentation.Command; + +public class CreateCommand(CreateCommandExecutor executor) : Model.Command("create", executor); \ No newline at end of file diff --git a/UI/Presentation/Command/ExitCommand.cs b/UI/Presentation/Command/ExitCommand.cs new file mode 100644 index 0000000000000000000000000000000000000000..f6b15810cd33062f4074b3936e23252b4c4dea3c --- /dev/null +++ b/UI/Presentation/Command/ExitCommand.cs @@ -0,0 +1,5 @@ +using UI.Impl; + +namespace UI.Presentation.Command; + +public class ExitCommand(ExitCommandExecutor executor) : Model.Command("exit", executor); \ No newline at end of file diff --git a/UI/Presentation/Command/InputCommand.cs b/UI/Presentation/Command/InputCommand.cs new file mode 100644 index 0000000000000000000000000000000000000000..83ef0dfc041763fc054af765588895b32dc06f34 --- /dev/null +++ b/UI/Presentation/Command/InputCommand.cs @@ -0,0 +1,5 @@ +using UI.Impl; + +namespace UI.Presentation.Command; + +public class InputCommand(InputCommandExecutor executor) : Model.Command("input", executor); \ No newline at end of file diff --git a/UI/Presentation/Command/PrintCommand.cs b/UI/Presentation/Command/PrintCommand.cs new file mode 100644 index 0000000000000000000000000000000000000000..d6b009959a0182679f52a5e88ddbdbfee59ce1df --- /dev/null +++ b/UI/Presentation/Command/PrintCommand.cs @@ -0,0 +1,5 @@ +using UI.Impl; + +namespace UI.Presentation.Command; + +public class PrintCommand(PrintCommandExecutor executor) : Model.Command("print", executor); \ No newline at end of file diff --git a/UI/Presentation/CommandProcessor.cs b/UI/Presentation/CommandProcessor.cs new file mode 100644 index 0000000000000000000000000000000000000000..11cd5018ea1b3899fb2770fda63a0b5dc329e34e --- /dev/null +++ b/UI/Presentation/CommandProcessor.cs @@ -0,0 +1,40 @@ +using Domain.Exception; +using UI.Model; + +namespace UI.Presentation; + +public class CommandProcessor( + IEnumerable<ICommand> commands, + IConsole console +) : ICommandProcessor +{ + public void ProcessCommands() + { + var input = console.ReadLine(); + var args = input.Split(" "); + var status = false; + foreach (var command in commands) + { + try + { + status = command.Execute(args); + if (status) + { + break; + } + } + catch (Exception e) + { + console.WriteLine(e.Message); + } + } + + if (!status) + PrintMenu(); + } + + private void PrintMenu() + { + console.WriteLine(StringResource.CommandsUsage.Help); + } +} \ No newline at end of file diff --git a/UI/Presentation/IApplication.cs b/UI/Presentation/IApplication.cs new file mode 100644 index 0000000000000000000000000000000000000000..ad6f82144faca372a589e0890bce51a3a45d9054 --- /dev/null +++ b/UI/Presentation/IApplication.cs @@ -0,0 +1,7 @@ +namespace UI.Presentation; + +public interface IApplication +{ + public void Run(); + public IApplicationLifecycle Lifecycle { get; } +} \ No newline at end of file diff --git a/UI/Presentation/IApplicationLifecycle.cs b/UI/Presentation/IApplicationLifecycle.cs new file mode 100644 index 0000000000000000000000000000000000000000..61594b1eae463ace9993c02ff0ba98149206b645 --- /dev/null +++ b/UI/Presentation/IApplicationLifecycle.cs @@ -0,0 +1,8 @@ +namespace UI.Presentation; + +public interface IApplicationLifecycle +{ + public void Finish(); + + public bool IsRunning { get; } +} \ No newline at end of file diff --git a/UI/Presentation/ICommandProcessor.cs b/UI/Presentation/ICommandProcessor.cs new file mode 100644 index 0000000000000000000000000000000000000000..55f35e003df69b94d99e9a016c5c0aa4068103d0 --- /dev/null +++ b/UI/Presentation/ICommandProcessor.cs @@ -0,0 +1,6 @@ +namespace UI.Presentation; + +public interface ICommandProcessor +{ + void ProcessCommands(); +} \ No newline at end of file diff --git a/UI/Presentation/IConsole.cs b/UI/Presentation/IConsole.cs new file mode 100644 index 0000000000000000000000000000000000000000..5bf477fd92bfe5b9a7736436d1d94049abf171f6 --- /dev/null +++ b/UI/Presentation/IConsole.cs @@ -0,0 +1,8 @@ +namespace UI.Presentation; + +public interface IConsole +{ + void WriteLine(object message); + string ReadLine(); + void Write(object message); +} \ No newline at end of file diff --git a/UI/Presentation/SystemConsole.cs b/UI/Presentation/SystemConsole.cs new file mode 100644 index 0000000000000000000000000000000000000000..bcd7896c0a78d2565d998a62ad1d06c7cc008463 --- /dev/null +++ b/UI/Presentation/SystemConsole.cs @@ -0,0 +1,10 @@ +namespace UI.Presentation; + +public class SystemConsole : IConsole +{ + public void WriteLine(object message) => Console.WriteLine(message); + + public string ReadLine() => Console.ReadLine() ?? ""; + + public void Write(object message) => Console.Write(message); +} \ No newline at end of file diff --git a/UI/StringResource.cs b/UI/StringResource.cs new file mode 100644 index 0000000000000000000000000000000000000000..2b1fea6290c1a663d2501e31ebed91604cf2599d --- /dev/null +++ b/UI/StringResource.cs @@ -0,0 +1,47 @@ +namespace UI; + +public static class StringResource +{ + public static class CommandsUsage + { + private const string UseLetter = "Рспользуйте: "; + + public const string Create = UseLetter + + "create <fileName> <arrayType - int/char/varchar> <size> <stringLength - опционально для char Рё varchar>"; + + public const string Input = UseLetter + "input <index> <value>"; + public const string Print = UseLetter + "print <index>"; + public const string Exit = UseLetter + "exit"; + + public const string Help = + "Доступные команды:\n" + + "1. " + Create + "\n" + + "2. " + Input + "\n" + + "3. " + Print + "\n" + + "4. " + Exit; + } + + public const string InvalidArrayType = "Неправильный тип массива."; + public const string ElementNotInitialized = "Рлемент РЅРµ инициализирован."; + public const string InvalidSize = "Неправильный размер массива."; + public const string CharTypeRequiresStringLength = "Для типа char требуется указать фиксированную длину строки."; + public const string InvalidStringLength = "Неверный размер строки."; + + public const string VarcharTypeRequiresMaxStringLength = + "Для типа varchar требуется указать максимальную длину строки."; + + public const string InvalidIndex = "Неверный индекс."; + public const string StringLengthOutOfLimit = "Строка превышает максимальный размер"; + public const string IndexOutOfRange = "Рндекс находится Р·Р° границами массива."; + public const string VirtualMemoryNotInitialized = "Виртуальная память РЅРµ инициализирована."; + public const string Exit = "Выключение."; + public const string VirtualMemoryInitialized = "Виртуальная память СѓР¶Рµ инициализирована."; + public const string InvalidValue = "Неверное значение."; + public const string CreateArrayFirst = "Сначала создайте массив."; + public const string ErrorArrayCreating = "Ошибка РїСЂРё создании массива:"; + public const string ArrayCreated = "Массив создан."; + public const string ErrorWhilePerformingCommand = "Ошибка РїСЂРё выполнении команды:"; + public const string InputSuccess = "Значение успешно записано."; + public const string ErrorWriteInt = "Ошибка РїСЂРё записи числа:"; + public const string ErrorWriteChar = "Ошибка РїСЂРё записи строки:"; +} \ No newline at end of file diff --git a/UI/UI.csproj b/UI/UI.csproj new file mode 100644 index 0000000000000000000000000000000000000000..ecb5d40fae89f6aed1223422805c6f9482cb30a8 --- /dev/null +++ b/UI/UI.csproj @@ -0,0 +1,18 @@ +п»ї<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net9.0</TargetFramework> + <ImplicitUsings>enable</ImplicitUsings> + <Nullable>enable</Nullable> + <RootNamespace>UI</RootNamespace> + </PropertyGroup> + + <ItemGroup> + <ProjectReference Include="..\Domain\Domain.csproj" /> + </ItemGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.2" /> + </ItemGroup> + +</Project> diff --git a/VM.Tests/Domain/CharVirtualMemoryTest.cs b/VM.Tests/Domain/CharVirtualMemoryTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..a11b31b3e6a88bbbe67b94e96237008521255df1 --- /dev/null +++ b/VM.Tests/Domain/CharVirtualMemoryTest.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections; +using System.Linq; +using System.Text; +using JetBrains.Annotations; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using VM.Domain; + +namespace VM.Tests.Domain; + +[TestClass] +[TestSubject(typeof(CharVirtualMemory))] +public class CharVirtualMemoryTests +{ + private Mock<IPageBuffer> _mockPageBuffer; + private CharVirtualMemory _memory; + private const int FixedStringLength = 16; // 16 байтов = 4 int-Р° + private int _size; + + [TestInitialize] + public void Setup() + { + _mockPageBuffer = new Mock<IPageBuffer>(); + _size = 12; + _memory = new CharVirtualMemory(_mockPageBuffer.Object, FixedStringLength, _size); + } + + [TestMethod] + public void Set_And_Get_SinglePageString_ReturnsCorrectValue() + { + var page = new Page(0, new BitArray(128), new int[128]); + _mockPageBuffer.Setup(m => m.GetPage(0)).Returns(page); + + _memory.Set(0, "Test"); + var result = _memory.Get(0); + + Assert.AreEqual("Test", result); + for (int i = 0; i < FixedStringLength / 4; i++) + { + Assert.IsTrue(page.BitMap[i]); + } + } + + [TestMethod] + public void Get_UninitializedString_ThrowsException() + { + var page = new Page(0, new BitArray(128), new int[128]); + _mockPageBuffer.Setup(m => m.GetPage(0)).Returns(page); + + Assert.ThrowsException<InvalidOperationException>(() => _memory.Get(0)); + } + + [TestMethod] + public void Set_StringAcrossMultiplePages_CorrectlySplitsData() + { + var page1 = new Page(0, new BitArray(128), new int[128]); + var page2 = new Page(1, new BitArray(128), new int[128]); + + _mockPageBuffer.Setup(m => m.GetPage(0)).Returns(page1); + _mockPageBuffer.Setup(m => m.GetPage(1)).Returns(page2); + + string longString = "VeryLongString"; // 14 байтов (разобьется РЅР° РґРІРµ страницы) + + _memory.Set(0, longString); + var result = _memory.Get(0); + + Assert.AreEqual(longString, result); + } + + [TestMethod] + public void Set_HugeString_CorrectlySplitsDataAcrossManyPages() + { + int stringSize = 512 * 4; // 512 байтов = 128 int-РѕРІ в†’ займет 4 страницы + string hugeString = new string('A', stringSize); + + var pages = new Page[4]; + for (int i = 0; i < 4; i++) + { + pages[i] = new Page(i, new BitArray(128), new int[128]); + _mockPageBuffer.Setup(m => m.GetPage(i)).Returns(pages[i]); + } + + var memory = new CharVirtualMemory(_mockPageBuffer.Object, 512 * 4, _size); + + memory.Set(0, hugeString); + var result = memory.Get(0); + + Assert.AreEqual(hugeString, result); + for (int i = 0; i < 4; i++) + { + Assert.IsTrue(pages[i].BitMap.Cast<bool>().Take(32).All(b => b)); + } + } + + [TestMethod] + public void Set_StringExactlyFillsPage_WorksCorrectly() + { + int stringSize = 128 * 4; // Заполняет СЂРѕРІРЅРѕ 1 страницу + string exactFit = new string('B', stringSize); + + var page = new Page(0, new BitArray(128), new int[128]); + _mockPageBuffer.Setup(m => m.GetPage(0)).Returns(page); + var memory = new CharVirtualMemory(_mockPageBuffer.Object, 128 * 4, _size); + + memory.Set(0, exactFit); + var result = memory.Get(0); + + Assert.AreEqual(exactFit, result); + Assert.IsTrue(page.BitMap.Cast<bool>().Take(32).All(b => b)); + } + + [TestMethod] + public void Set_StringTooLong_ThrowsException() + { + string tooLong = new string('X', FixedStringLength + 1); + Assert.ThrowsException<ArgumentException>(() => _memory.Set(0, tooLong)); + } + + [TestMethod] + public void Set_NegativeIndex_ThrowsException() + { + Assert.ThrowsException<IndexOutOfRangeException>(() => _memory.Set(-1, "Test")); + } + + [TestMethod] + public void Get_NegativeIndex_ThrowsException() + { + Assert.ThrowsException<IndexOutOfRangeException>(() => _memory.Get(-1)); + } + + [TestMethod] + public void Dispose_CallsDisposeOnPageBuffer() + { + // Arrange + var pageBufferMock = new Mock<IPageBuffer>(); + var virtualMemory = new CharVirtualMemory(pageBufferMock.Object, 10, _size); + + // Act + virtualMemory.Dispose(); + + // Assert + pageBufferMock.Verify(pb => pb.Dispose(), Times.Once); + } +} \ No newline at end of file diff --git a/VM.Tests/Domain/IVirtualMemoryTest.cs b/VM.Tests/Domain/IVirtualMemoryTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..b72612ed5a2fa97b7b2da53c455d604bd0d73c52 --- /dev/null +++ b/VM.Tests/Domain/IVirtualMemoryTest.cs @@ -0,0 +1,85 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using VM.Domain; + +namespace VM.Tests.Domain; + +[TestClass] +public class VirtualMemoryTests +{ + private Mock<IVirtualMemory<string>> _mockStringMemory; + + private class StringVirtualMemory(IVirtualMemory<string> virtualMemory) : IVirtualMemory<string> + { + public void Dispose() + { + virtualMemory.Dispose(); + } + + public void Set(int index, string value) + { + virtualMemory.Set(index, value); + } + + public string Get(int index) + { + return virtualMemory.Get(index); + } + } + + [TestInitialize] + public void Setup() + { + _mockStringMemory = new Mock<IVirtualMemory<string>>(); + } + + [TestMethod] + public void IVirtualMemory_Set_ShouldCallGenericSet_WhenValueIsCorrectType() + { + // Arrange + var index = 1; + var value = "Test String"; + + _mockStringMemory.Setup(m => m.Set(index, value)); + + IVirtualMemory virtualMemory = new StringVirtualMemory(_mockStringMemory.Object); + // Act + virtualMemory.Set(index, value); + + // Assert + _mockStringMemory.Verify(m => m.Set(index, value), Times.Once); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void IVirtualMemory_Set_ShouldThrowException_WhenValueIsIncorrectType() + { + // Arrange + var index = 2; + var invalidValue = 42; // Целое число вместо строки + + // Act + IVirtualMemory virtualMemory = new StringVirtualMemory(_mockStringMemory.Object); + virtualMemory.Set(index, invalidValue); + } + + [TestMethod] + public void IVirtualMemory_Get_ShouldCallGenericGet_AndReturnCorrectValue() + { + // Arrange + var index = 3; + var expectedValue = "Another Test String"; + + _mockStringMemory.Setup(m => m.Get(index)).Returns(expectedValue); + + IVirtualMemory virtualMemory = new StringVirtualMemory(_mockStringMemory.Object); + + // Act + var result = virtualMemory.Get(index); + + // Assert + Assert.AreEqual(expectedValue, result); + _mockStringMemory.Verify(m => m.Get(index), Times.Once); + } +} diff --git a/VM.Tests/Domain/IntVirtualMemoryTest.cs b/VM.Tests/Domain/IntVirtualMemoryTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..20453d6c5c545c6e42a3e462ee2e5b42dba17369 --- /dev/null +++ b/VM.Tests/Domain/IntVirtualMemoryTest.cs @@ -0,0 +1,134 @@ +using VM.Domain; + +namespace VM.Tests.Domain; + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using System; +using System.Collections; + +[TestClass] +public class IntVirtualMemoryTests +{ + private const int ElementsPerPage = 128; + private Mock<IPageBuffer> _pageBufferMock = null!; + private IntVirtualMemory _virtualMemory = null!; + private int _size; + + [TestInitialize] + public void Setup() + { + _pageBufferMock = new Mock<IPageBuffer>(); + _size = 12; + _virtualMemory = new IntVirtualMemory(_pageBufferMock.Object, _size); + } + + [TestMethod] + public void Set_SetsValueCorrectly() + { + var page = CreateMockPage(0); + + _pageBufferMock + .Setup(pb => pb.GetPage(0)) + .Returns(page); + + _virtualMemory.Set(5, 42); + + Assert.AreEqual(42, page.Data[5]); + Assert.IsTrue(page.BitMap[5]); + + _pageBufferMock.Verify(pb => pb.MarkPageModified(0), Times.Once); + } + + [TestMethod] + public void Get_GetsValueCorrectly() + { + var page = CreateMockPage(0); + page.Data[5] = 42; + page.BitMap[5] = true; + + _pageBufferMock + .Setup(pb => pb.GetPage(0)) + .Returns(page); + + int value = _virtualMemory.Get(5); + + Assert.AreEqual(42, value); + } + + [TestMethod] + [ExpectedException(typeof(InvalidOperationException))] + public void Get_UninitializedValue_ThrowsException() + { + var page = CreateMockPage(0); + + _pageBufferMock + .Setup(pb => pb.GetPage(0)) + .Returns(page); + + _virtualMemory.Get(5); // Ожидаем исключение + } + + [TestMethod] + [ExpectedException(typeof(IndexOutOfRangeException))] + public void Set_NegativeIndex_ThrowsException() + { + _virtualMemory.Set(-1, 42); + } + + [TestMethod] + [ExpectedException(typeof(IndexOutOfRangeException))] + public void Get_NegativeIndex_ThrowsException() + { + _virtualMemory.Get(-1); + } + + [TestMethod] + [ExpectedException(typeof(IndexOutOfRangeException))] + public void Set_IndexBeyondBitMap_ThrowsException() + { + var page = CreateMockPage(0, bitMapSize: 5); // Меньший размер битовой карты + + _pageBufferMock + .Setup(pb => pb.GetPage(0)) + .Returns(page); + + _virtualMemory.Set(10, 42); + } + + [TestMethod] + [ExpectedException(typeof(IndexOutOfRangeException))] + public void Get_IndexBeyondBitMap_ThrowsException() + { + var page = CreateMockPage(0, bitMapSize: 5); // Меньший размер битовой карты + + _pageBufferMock + .Setup(pb => pb.GetPage(0)) + .Returns(page); + + _virtualMemory.Get(10); + } + + private IPage CreateMockPage(int pageIndex, int bitMapSize = ElementsPerPage) + { + var mock = new Mock<IPage>(); + mock.Setup(p => p.PageIndex).Returns(pageIndex); + mock.Setup(p => p.BitMap).Returns(new BitArray(bitMapSize)); + mock.Setup(p => p.Data).Returns(new int[ElementsPerPage]); + return mock.Object; + } + + [TestMethod] + public void Dispose_CallsDisposeOnPageBuffer() + { + // Arrange + var pageBufferMock = new Mock<IPageBuffer>(); + var virtualMemory = new IntVirtualMemory(pageBufferMock.Object, _size); + + // Act + virtualMemory.Dispose(); + + // Assert + pageBufferMock.Verify(pb => pb.Dispose(), Times.Once); + } +} \ No newline at end of file diff --git a/VM.Tests/Domain/PageBufferTest.cs b/VM.Tests/Domain/PageBufferTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..1e7227f310b64fd5ae706cb76f507ffd61efbf20 --- /dev/null +++ b/VM.Tests/Domain/PageBufferTest.cs @@ -0,0 +1,159 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using JetBrains.Annotations; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using VM.Domain; + +namespace VM.Tests.Domain; + +[TestClass] +[TestSubject(typeof(PageBuffer))] +public class PageBufferTest +{ + private Mock<ISwapFile> _swapFileMock; + private Mock<IDateTimeService> _dateTimeServiceMock; + private PageBuffer _buffer; + private readonly DateTime _dateTime = DateTime.Now; + + [TestInitialize] + public void Setup() + { + _swapFileMock = new Mock<ISwapFile>(); + _dateTimeServiceMock = new Mock<IDateTimeService>(); + _dateTimeServiceMock.Setup(d => d.CurrentDateTime()).Returns(_dateTime); + _buffer = new PageBuffer(_swapFileMock.Object, _dateTimeServiceMock.Object, 2); + } + + [TestMethod] + public void GetPage_ShouldLoadPage_IfNotInBuffer() + { + var page = new Page(0, new BitArray(128, true), new int[128]); + _swapFileMock.Setup(s => s.LoadPage(0)).Returns(page); + + var loadedPage = _buffer.GetPage(0); + + Assert.AreEqual(page, loadedPage); + _swapFileMock.Verify(s => s.LoadPage(0), Times.Once); + } + + [TestMethod] + public void GetPage_ShouldNotReloadPage_IfAlreadyInBuffer() + { + var page = new Page(0, new BitArray(128, true), new int[128]); + _swapFileMock.Setup(s => s.LoadPage(0)).Returns(page); + + _buffer.GetPage(0); + _swapFileMock.Invocations.Clear(); + + var loadedPage = _buffer.GetPage(0); + + Assert.AreEqual(page, loadedPage); + _swapFileMock.Verify(s => s.LoadPage(It.IsAny<int>()), Times.Never); + } + + [TestMethod] + public void MarkPageModified_ShouldSetIsModified_IfPageIsInBuffer() + { + var page = new Page(0, new BitArray(128, true), new int[128]); + _swapFileMock.Setup(s => s.LoadPage(0)).Returns(page); + + _buffer.GetPage(0); + + _buffer.MarkPageModified(0); + + Assert.IsTrue(page.IsModified); + } + + [TestMethod] + public void ReplaceOldestPage_ShouldSaveAndRemoveOldestPage_IfModified() + { + // Arrange + var page1 = new Page(0, new BitArray(128, true), new int[128]) + { + AccessTime = DateTime.Now.AddMinutes(-10), + IsModified = true + }; + var page2 = new Page(1, new BitArray(128, true), new int[128]) + { + AccessTime = DateTime.Now.AddMinutes(-5), + IsModified = false + }; + var page3 = new Page(2, new BitArray(128, true), new int[128]); // Новая страница + + _swapFileMock.Setup(s => s.LoadPage(2)).Returns(page3); + _swapFileMock.Setup(s => s.SavePage(page1)).Verifiable(); // Должен быть вызван `SavePage` + + _buffer.GetType() + .GetField("_pages", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance) + ?.SetValue(_buffer, new List<IPage> { page1, page2 }); + + // Act + _buffer.GetPage(2); // Должен вызвать ReplaceOldestPage Рё удалить `page1` + + // Assert + _swapFileMock.Verify(s => s.SavePage(page1), Times.Once); // Проверяем, что измененная страница сохранена + var pages = (List<IPage>)_buffer.GetType() + .GetField("_pages", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance) + ?.GetValue(_buffer); + Assert.IsFalse(pages.Contains(page1)); // Проверяем, что `page1` удалена + }[TestMethod] + public void GetPage_ShouldUpdateAccessTime() + { + var page = new Page(0, new BitArray(128, true), new int[128]); + _swapFileMock.Setup(s => s.LoadPage(0)).Returns(page); + + _buffer.GetPage(0); + + Assert.AreEqual(_dateTime, page.AccessTime); + } + + [TestMethod] + public void ReplaceOldestPage_ShouldRemoveOldestPage_IfNotModified() + { + var page1 = new Page(0, new BitArray(128, true), new int[128]) + { + AccessTime = DateTime.Now.AddMinutes(-10), + IsModified = false + }; + var page2 = new Page(1, new BitArray(128, true), new int[128]) + { + AccessTime = DateTime.Now.AddMinutes(-5) + }; + var page3 = new Page(2, new BitArray(128, true), new int[128]); + + _swapFileMock.Setup(s => s.LoadPage(2)).Returns(page3); + + _buffer.GetType() + .GetField("_pages", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance) + ?.SetValue(_buffer, new List<IPage> { page1, page2 }); + + _buffer.GetPage(2); + + var pages = (List<IPage>)_buffer.GetType() + .GetField("_pages", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance) + ?.GetValue(_buffer); + + Assert.IsFalse(pages.Contains(page1)); + _swapFileMock.Verify(s => s.SavePage(It.IsAny<IPage>()), Times.Never); + } + + [TestMethod] + public void Dispose_ShouldSaveAllPagesAndDisposeSwapFile() + { + var page1 = new Mock<IPage>(); + var page2 = new Mock<IPage>(); + + _buffer.GetType() + .GetField("_pages", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance) + ?.SetValue(_buffer, new List<IPage> { page1.Object, page2.Object }); + + _buffer.Dispose(); + + _swapFileMock.Verify(s => s.SavePage(page1.Object), Times.Once); + _swapFileMock.Verify(s => s.SavePage(page2.Object), Times.Once); + _swapFileMock.Verify(s => s.Dispose(), Times.Once); + } + +} \ No newline at end of file diff --git a/VM.Tests/Domain/PageTest.cs b/VM.Tests/Domain/PageTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..51c0eea9821d750cf702c84735897fbd552a54af --- /dev/null +++ b/VM.Tests/Domain/PageTest.cs @@ -0,0 +1,88 @@ +using VM.Domain; + +namespace VM.Tests.Domain; + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections; + +[TestClass] +public class PageTests +{ + [TestMethod] + public void Constructor_ValidParameters_ShouldInitializeCorrectly() + { + var bitMap = new BitArray(128); + var data = new int[128]; + + var page = new Page(1, bitMap, data); + + Assert.AreEqual(1, page.PageIndex); + Assert.IsNotNull(page.BitMap); + Assert.IsNotNull(page.Data); + Assert.IsFalse(page.IsModified); + Assert.AreEqual(128, page.BitMap.Length); + Assert.AreEqual(128, page.Data.Length); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void Constructor_NullBitMap_ShouldThrowException() + { + var data = new int[128]; + var page = new Page(1, null, data); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void Constructor_InvalidBitMapSize_ShouldThrowException() + { + var bitMap = new BitArray(127); + var data = new int[128]; + var page = new Page(1, bitMap, data); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void Constructor_NullData_ShouldThrowException() + { + var bitMap = new BitArray(128); + var page = new Page(1, bitMap, null); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void Constructor_InvalidDataSize_ShouldThrowException() + { + var bitMap = new BitArray(128); + var data = new int[127]; + var page = new Page(1, bitMap, data); + } + + [TestMethod] + public void AccessTime_ShouldUpdateCorrectly() + { + var bitMap = new BitArray(128); + var data = new int[128]; + + var page = new Page(1, bitMap, data); + var initialTime = page.AccessTime; + + System.Threading.Thread.Sleep(10); + page.AccessTime = DateTime.Now; + + Assert.IsTrue(page.AccessTime > initialTime); + } + + [TestMethod] + public void IsModified_ShouldBeSettable() + { + var bitMap = new BitArray(128); + var data = new int[128]; + + var page = new Page(1, bitMap, data); + page.IsModified = true; + + Assert.IsTrue(page.IsModified); + } +} diff --git a/VM.Tests/Domain/SwapFileTest.cs b/VM.Tests/Domain/SwapFileTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..43c35ed4aa59dcf1c7e4fa179380b0d863345a18 --- /dev/null +++ b/VM.Tests/Domain/SwapFileTest.cs @@ -0,0 +1,65 @@ +using VM.Domain; + +namespace VM.Tests.Domain; + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.IO; +using System.Collections; + +[TestClass] +public class SwapFileTests +{ + private const string TestFileName = "test.vm"; + + [TestInitialize] + public void Setup() + { + if (File.Exists(TestFileName)) + File.Delete(TestFileName); + } + + [TestCleanup] + public void Cleanup() + { + if (File.Exists(TestFileName)) + File.Delete(TestFileName); + } + + [TestMethod] + public void SwapFile_ShouldCreateFileWithCorrectSignature() + { + using var swapFile = new SwapFile(TestFileName, 10000); + + using var fs = new FileStream(TestFileName, FileMode.Open, FileAccess.Read); + using var reader = new BinaryReader(fs); + + var signature = reader.ReadBytes(2); + CollectionAssert.AreEqual("VM"u8.ToArray(), signature, "Файл должен начинаться СЃ сигнатуры 'VM'."); + } + + [TestMethod] + public void SwapFile_ShouldLoadAndSavePageCorrectly() + { + using var swapFile = new SwapFile(TestFileName, 10000); + var page = new Page(0, new BitArray(128, true), new int[128]); + for (var i = 0; i < 128; i++) page.Data[i] = i; + + swapFile.SavePage(page); + var loadedPage = swapFile.LoadPage(0); + + for (var i = 0; i < 128; i++) + { + Assert.AreEqual(i, loadedPage.Data[i], $"Данные РІ ячейке {i} РЅРµ совпадают."); + } + } + + [TestMethod] + public void SwapFile_LoadPage_ShouldThrowException_WhenOutOfBounds() + { + using var swapFile = new SwapFile(TestFileName, 10); + + Assert.Throws<InvalidOperationException>(() => swapFile.LoadPage(1000)); + } +} + diff --git a/VM.Tests/Domain/SystemDateTimeServiceTest.cs b/VM.Tests/Domain/SystemDateTimeServiceTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..c9d64c1fa61faf463df1d9892bfd6d7d75c37ea5 --- /dev/null +++ b/VM.Tests/Domain/SystemDateTimeServiceTest.cs @@ -0,0 +1,28 @@ +using System; +using JetBrains.Annotations; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using VM.Domain; + +namespace VM.Tests.Domain; + +[TestClass] +[TestSubject(typeof(SystemDateTimeService))] +public class SystemDateTimeServiceTest +{ + [TestMethod] + public void CurrentDateTime_ShouldReturnCurrentSystemTime() + { + // Arrange + var service = new SystemDateTimeService(); + var beforeCall = DateTime.Now; + + // Act + var result = service.CurrentDateTime(); + + var afterCall = DateTime.Now; + + // Assert + Assert.IsTrue(result >= beforeCall && result <= afterCall, + $"Returned time {result} is not within expected range {beforeCall} - {afterCall}"); + } +} diff --git a/VM.Tests/Domain/VarcharVirtualMemoryTest.cs b/VM.Tests/Domain/VarcharVirtualMemoryTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..f1af0963465c5a32779db22d2221d8e4b49343a8 --- /dev/null +++ b/VM.Tests/Domain/VarcharVirtualMemoryTest.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections; +using System.IO; +using System.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using VM.Domain; + +namespace VM.Tests.Domain; + +[TestClass] +public class VarcharVirtualMemoryTest +{ + private Mock<ISwapFile> _swapFileMock; + private string _testFileName; + private VarcharVirtualMemory _memory; + private Page _testPage; + private int _size; + [TestInitialize] + public void Setup() + { + _swapFileMock = new Mock<ISwapFile>(); + _testFileName = "test_data.dat"; + File.Delete(_testFileName); + + _testPage = new Page(0, new BitArray(128, false), new int[128]); + _swapFileMock.Setup(s => s.LoadPage(It.IsAny<int>())).Returns(_testPage); + _size = 12; + _memory = new VarcharVirtualMemory(_swapFileMock.Object, _testFileName, 50, _size); + } + + [TestCleanup] + public void Cleanup() + { + _memory.Dispose(); + File.Delete(_testFileName); + } + + [TestMethod] + public void Set_ShouldStoreStringValue() + { + // Arrange + var value = "Hello, World!"; + var index = 5; + + // Act + _memory.Set(index, value); + + // Assert + Assert.IsTrue(_testPage.BitMap[index]); + _swapFileMock.Verify(s => s.SavePage(_testPage), Times.Once); + + // Проверяем, что данные записаны РІ файл + using var reader = new BinaryReader(File.OpenRead(_testFileName), Encoding.UTF8); + reader.BaseStream.Seek(_testPage.Data[index], SeekOrigin.Begin); + var length = reader.ReadInt32(); + var storedBytes = reader.ReadBytes(length); + var storedValue = Encoding.UTF8.GetString(storedBytes); + + Assert.AreEqual(value, storedValue); + } + + [TestMethod] + public void Get_ShouldRetrieveStoredStringValue() + { + // Arrange + var value = "Test String"; + var index = 10; + _memory.Set(index, value); + + // Act + var retrievedValue = _memory.Get(index); + + // Assert + Assert.AreEqual(value, retrievedValue); + } + + [TestMethod] + [ExpectedException(typeof(IndexOutOfRangeException))] + public void Set_ShouldThrowException_WhenIndexIsNegative() + { + _memory.Set(-1, "Invalid"); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void Set_ShouldThrowException_WhenStringExceedsMaxSize() + { + _memory.Set(0, new string('a', 51)); // Превышает лимит РІ 50 символов + } + + [TestMethod] + [ExpectedException(typeof(IndexOutOfRangeException))] + public void Get_ShouldThrowException_WhenIndexIsNegative() + { + _memory.Get(-1); + } + + [TestMethod] + [ExpectedException(typeof(InvalidOperationException))] + public void Get_ShouldThrowException_WhenElementIsNotInitialized() + { + _memory.Get(20); // Нет вызова Set -> элемент неинициализирован + } + + [TestMethod] + public void Dispose_ShouldCloseResources() + { + _memory.Dispose(); + + Assert.ThrowsException<ArgumentException>(() => + { + using var reader = new BinaryReader(_memory.GetType() + .GetField("_dataFile", + System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance) + ?.GetValue(_memory) as FileStream); + reader.ReadByte(); + }); + + _swapFileMock.Verify(s => s.Dispose(), Times.Once); + } +} \ No newline at end of file diff --git a/VM.Tests/VM.Tests.csproj b/VM.Tests/VM.Tests.csproj new file mode 100644 index 0000000000000000000000000000000000000000..e136fd2861790ea924491d52b9eb4c3734b6c99f --- /dev/null +++ b/VM.Tests/VM.Tests.csproj @@ -0,0 +1,28 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net9.0</TargetFramework> + <LangVersion>latest</LangVersion> + <ImplicitUsings>enable</ImplicitUsings> + <Nullable>enable</Nullable> + <IsPackable>false</IsPackable> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="JetBrains.Annotations" Version="2025.1.0-eap1"/> + <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.2"/> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0"/> + <PackageReference Include="Moq" Version="4.20.72"/> + <PackageReference Include="MSTest.TestAdapter" Version="3.8.2"/> + <PackageReference Include="MSTest.TestFramework" Version="3.8.2"/> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\VM\VM.csproj"/> + </ItemGroup> + + <ItemGroup> + <Folder Include="Data\Service\"/> + </ItemGroup> + +</Project> \ No newline at end of file diff --git a/VM/Domain/CharVirtualMemory.cs b/VM/Domain/CharVirtualMemory.cs new file mode 100644 index 0000000000000000000000000000000000000000..876eef4516e779ce9a64fd1f81d4846f8d339b9c --- /dev/null +++ b/VM/Domain/CharVirtualMemory.cs @@ -0,0 +1,130 @@ +namespace VM.Domain; + +using System; +using System.Text; + +/// <summary> +/// Represents a virtual memory for storing fixed-length strings. +/// </summary> +public class CharVirtualMemory : IVirtualMemory<string> +{ + private const int ElementsPerPage = 128; + private readonly IPageBuffer _pageBuffer; + private readonly int _alignedFixedStringLength; + private readonly int _realFixedStringLength; + private readonly int _intPerString; + private readonly int _size; + + /// <summary> + /// Initializes a new instance of the <see cref="CharVirtualMemory"/> class. + /// </summary> + /// <param name="pageBuffer">The page buffer used to manage pages of memory.</param> + /// <param name="fixedStringLength">The fixed length of the strings to be stored.</param> + /// <param name="size">The length of the array</param> + /// <exception cref="ArgumentOutOfRangeException">Thrown when the fixed string length is negative or zero.</exception> + public CharVirtualMemory(IPageBuffer pageBuffer, int fixedStringLength, int size) + { + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(fixedStringLength, "Длина строки"); + + _pageBuffer = pageBuffer; + _alignedFixedStringLength = (fixedStringLength + 3) / 4 * 4; + _realFixedStringLength = fixedStringLength; + _intPerString = _alignedFixedStringLength / 4; + _size = size; + } + + /// <summary> + /// Sets the string value at the specified index in the virtual memory. + /// </summary> + /// <param name="index">The index at which to set the value.</param> + /// <param name="value">The string value to set.</param> + /// <exception cref="IndexOutOfRangeException">Thrown when the index is out of range.</exception> + /// <exception cref="ArgumentException">Thrown when the string is too long.</exception> + public void Set(int index, string value) + { + if (index < 0 || index >= _size) + throw new IndexOutOfRangeException("Рндекс РІРЅРµ границ массива"); + + if (Encoding.UTF8.GetByteCount(value) > _realFixedStringLength) + throw new ArgumentException("Строка слишком длинная"); + + byte[] bytes = new byte[_alignedFixedStringLength]; + Encoding.UTF8.GetBytes(value, 0, value.Length, bytes, 0); + + int startElementIndex = index * _intPerString; + int remainingInts = _intPerString; + + while (remainingInts > 0) + { + int pageIndex = startElementIndex / ElementsPerPage; + int localIndex = startElementIndex % ElementsPerPage; + + var page = _pageBuffer.GetPage(pageIndex); + + int spaceInPage = ElementsPerPage - localIndex; + int toWrite = Math.Min(spaceInPage, remainingInts); + + for (int i = 0; i < toWrite; i++) + { + page.Data[localIndex + i] = BitConverter.ToInt32(bytes, (i + (_intPerString - remainingInts)) * 4); + page.BitMap[localIndex + i] = true; + } + + _pageBuffer.MarkPageModified(pageIndex); + + remainingInts -= toWrite; + startElementIndex += toWrite; + } + } + + /// <summary> + /// Gets the string value at the specified index in the virtual memory. + /// </summary> + /// <param name="index">The index from which to get the value.</param> + /// <returns>The string value at the specified index.</returns> + /// <exception cref="IndexOutOfRangeException">Thrown when the index is out of range.</exception> + /// <exception cref="InvalidOperationException">Thrown when the element is not initialized.</exception> + public string Get(int index) + { + if (index < 0) + throw new IndexOutOfRangeException("Рндекс РІРЅРµ границ массива"); + + byte[] bytes = new byte[_alignedFixedStringLength]; + int startElementIndex = index * _intPerString; + int remainingInts = _intPerString; + + while (remainingInts > 0) + { + int pageIndex = startElementIndex / ElementsPerPage; + int localIndex = startElementIndex % ElementsPerPage; + + var page = _pageBuffer.GetPage(pageIndex); + + int spaceInPage = ElementsPerPage - localIndex; + int toRead = Math.Min(spaceInPage, remainingInts); + + for (int i = 0; i < toRead; i++) + { + if (!page.BitMap[localIndex + i]) + throw new InvalidOperationException("Рлемент РЅРµ инициализирован"); + + Array.Copy(BitConverter.GetBytes(page.Data[localIndex + i]), 0, bytes, + (i + (_intPerString - remainingInts)) * 4, 4); + } + + remainingInts -= toRead; + startElementIndex += toRead; + } + + return Encoding.UTF8.GetString(bytes).TrimEnd('\0'); + } + + /// <summary> + /// Disposes the resources used by the virtual memory. + /// </summary> + public void Dispose() + { + _pageBuffer.Dispose(); + GC.SuppressFinalize(this); + } +} \ No newline at end of file diff --git a/VM/Domain/IDateTimeService.cs b/VM/Domain/IDateTimeService.cs new file mode 100644 index 0000000000000000000000000000000000000000..b7be7f7dfd4bc05bf021188e3287736d028a32c3 --- /dev/null +++ b/VM/Domain/IDateTimeService.cs @@ -0,0 +1,13 @@ +namespace VM.Domain; + +/// <summary> +/// Provides an interface for obtaining the current date and time. +/// </summary> +public interface IDateTimeService +{ + /// <summary> + /// Gets the current date and time. + /// </summary> + /// <returns>The current date and time.</returns> + DateTime CurrentDateTime(); +} \ No newline at end of file diff --git a/VM/Domain/IPage.cs b/VM/Domain/IPage.cs new file mode 100644 index 0000000000000000000000000000000000000000..e57425c93f082c53a62914336bf719ae57635487 --- /dev/null +++ b/VM/Domain/IPage.cs @@ -0,0 +1,34 @@ +using System.Collections; + +namespace VM.Domain; + +/// <summary> +/// Represents a page in the virtual memory domain. +/// </summary> +public interface IPage +{ + /// <summary> + /// Gets the index of the page. + /// </summary> + int PageIndex { get; } + + /// <summary> + /// Gets or sets the last access time of the page. + /// </summary> + DateTime AccessTime { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the page has been modified. + /// </summary> + bool IsModified { get; set; } + + /// <summary> + /// Gets the bitmap representing the page. + /// </summary> + BitArray BitMap { get; } + + /// <summary> + /// Gets the data contained in the page. + /// </summary> + int[] Data { get; } +} \ No newline at end of file diff --git a/VM/Domain/IPageBuffer.cs b/VM/Domain/IPageBuffer.cs new file mode 100644 index 0000000000000000000000000000000000000000..4c7cec9a72bf8fb8ab9967cecc05389196621ec9 --- /dev/null +++ b/VM/Domain/IPageBuffer.cs @@ -0,0 +1,20 @@ +namespace VM.Domain; + +/// <summary> +/// Provides an interface for managing pages in a buffer. +/// </summary> +public interface IPageBuffer : IDisposable +{ + /// <summary> + /// Gets the page at the specified index. + /// </summary> + /// <param name="pageIndex">The index of the page to retrieve.</param> + /// <returns>The page at the specified index.</returns> + public IPage GetPage(int pageIndex); + + /// <summary> + /// Marks the page at the specified index as modified. + /// </summary> + /// <param name="pageIndex">The index of the page to mark as modified.</param> + public void MarkPageModified(int pageIndex); +} \ No newline at end of file diff --git a/VM/Domain/ISwapFile.cs b/VM/Domain/ISwapFile.cs new file mode 100644 index 0000000000000000000000000000000000000000..f448844d71ecacb2689cf16f7147c22d33b8f247 --- /dev/null +++ b/VM/Domain/ISwapFile.cs @@ -0,0 +1,20 @@ +namespace VM.Domain; + +/// <summary> +/// Represents an interface for a swap file used in virtual memory management. +/// </summary> +public interface ISwapFile : IDisposable +{ + /// <summary> + /// Loads the page at the specified index. + /// </summary> + /// <param name="pageIndex">The index of the page to load.</param> + /// <returns>The loaded page.</returns> + public IPage LoadPage(int pageIndex); + + /// <summary> + /// Saves the specified page to the swap file. + /// </summary> + /// <param name="page">The page to save.</param> + public void SavePage(IPage page); +} \ No newline at end of file diff --git a/VM/Domain/IVirtualMemory.cs b/VM/Domain/IVirtualMemory.cs new file mode 100644 index 0000000000000000000000000000000000000000..078aaf50084eacbeedf6dc2e497e6bf2a33e3cde --- /dev/null +++ b/VM/Domain/IVirtualMemory.cs @@ -0,0 +1,70 @@ +namespace VM.Domain; + +/// <summary> +/// Interface representing a virtual memory that supports setting and getting values by index. +/// </summary> +public interface IVirtualMemory : IDisposable +{ + /// <summary> + /// Sets the value at the specified index. + /// </summary> + /// <param name="index">The index at which to set the value.</param> + /// <param name="value">The value to set.</param> + void Set(int index, object value); + + /// <summary> + /// Gets the value at the specified index. + /// </summary> + /// <param name="index">The index from which to get the value.</param> + /// <returns>The value at the specified index.</returns> + object Get(int index); +} + +/// <summary> +/// Generic interface representing a virtual memory that supports setting and getting values of a specific type by index. +/// </summary> +/// <typeparam name="T">The type of values stored in the virtual memory.</typeparam> +public interface IVirtualMemory<T> : IVirtualMemory +{ + /// <summary> + /// Sets the value of type <typeparamref name="T"/> at the specified index. + /// </summary> + /// <param name="index">The index at which to set the value.</param> + /// <param name="value">The value of type <typeparamref name="T"/> to set.</param> + void Set(int index, T value); + + /// <summary> + /// Gets the value of type <typeparamref name="T"/> at the specified index. + /// </summary> + /// <param name="index">The index from which to get the value.</param> + /// <returns>The value of type <typeparamref name="T"/> at the specified index.</returns> + new T Get(int index); + + /// <summary> + /// Gets the value at the specified index as an object. + /// </summary> + /// <param name="index">The index from which to get the value.</param> + /// <returns>The value at the specified index as an object.</returns> + object IVirtualMemory.Get(int index) + { + return Get(index); + } + + /// <summary> + /// Sets the value at the specified index as an object. + /// </summary> + /// <param name="index">The index at which to set the value.</param> + /// <param name="value">The value to set as an object.</param> + /// <exception cref="ArgumentException">Thrown when the value is not of type <typeparamref name="T"/>.</exception> + void IVirtualMemory.Set(int index, object value) + { + if (value is T tValue) + { + Set(index, tValue); + } + else + { + throw new ArgumentException("Invalid value type"); + } + } +} \ No newline at end of file diff --git a/VM/Domain/IntVirtualMemory.cs b/VM/Domain/IntVirtualMemory.cs new file mode 100644 index 0000000000000000000000000000000000000000..42d1ad37d8ed4f7233dc87179e90c51664678274 --- /dev/null +++ b/VM/Domain/IntVirtualMemory.cs @@ -0,0 +1,77 @@ +namespace VM.Domain; + +/// <summary> +/// Represents a virtual memory implementation for integers. +/// </summary> +/// <param name="pageBuffer">The page buffer used to manage memory pages.</param> +public class IntVirtualMemory(IPageBuffer pageBuffer, int size) : IVirtualMemory<int> +{ + private const int ElementsPerPage = 128; + + /// <summary> + /// Sets the value at the specified index in the virtual memory. + /// </summary> + /// <param name="index">The index at which to set the value.</param> + /// <param name="value">The value to set.</param> + /// <exception cref="IndexOutOfRangeException">Thrown when the index is out of range.</exception> + public void Set(int index, int value) + { + if (index < 0 || index >= size) + { + throw new IndexOutOfRangeException("Рндекс РІРЅРµ границ массива"); + } + + var pageIndex = index / ElementsPerPage; + var localIndex = index % ElementsPerPage; + var page = pageBuffer.GetPage(pageIndex); + + if (localIndex >= page.BitMap.Length) + { + throw new IndexOutOfRangeException("Локальный индекс выходит Р·Р° границы битовой карты страницы"); + } + + page.Data[localIndex] = value; + page.BitMap[localIndex] = true; + pageBuffer.MarkPageModified(pageIndex); + } + + /// <summary> + /// Gets the value at the specified index in the virtual memory. + /// </summary> + /// <param name="index">The index from which to get the value.</param> + /// <returns>The value at the specified index.</returns> + /// <exception cref="IndexOutOfRangeException">Thrown when the index is out of range.</exception> + /// <exception cref="InvalidOperationException">Thrown when the element is not initialized.</exception> + public int Get(int index) + { + if (index < 0) + { + throw new IndexOutOfRangeException("Рндекс РІРЅРµ границ массива"); + } + + var pageIndex = index / ElementsPerPage; + var localIndex = index % ElementsPerPage; + var page = pageBuffer.GetPage(pageIndex); + + if (localIndex >= page.BitMap.Length) + { + throw new IndexOutOfRangeException("Локальный индекс выходит Р·Р° границы битовой карты страницы"); + } + + if (!page.BitMap[localIndex]) + { + throw new InvalidOperationException("Рлемент РЅРµ инициализирован"); + } + + return page.Data[localIndex]; + } + + /// <summary> + /// Disposes the virtual memory and releases all resources. + /// </summary> + public void Dispose() + { + pageBuffer.Dispose(); + GC.SuppressFinalize(this); + } +} \ No newline at end of file diff --git a/VM/Domain/Page.cs b/VM/Domain/Page.cs new file mode 100644 index 0000000000000000000000000000000000000000..9deef0e51212e1fbc180a0748e6b53048556223e --- /dev/null +++ b/VM/Domain/Page.cs @@ -0,0 +1,28 @@ + +namespace VM.Domain; + +using System; +using System.Collections; + +public class Page: IPage +{ + public int PageIndex { get; } + public DateTime AccessTime { get; set; } + public bool IsModified { get; set; } + public BitArray BitMap { get; } + public int[] Data { get; } + + public Page(int pageIndex, BitArray bitMap, int[] data) + { + if (bitMap is not { Length: 128 }) + throw new ArgumentException("BitMap должен содержать 128 Р±РёС‚", nameof(bitMap)); + if (data is not { Length: 128 }) + throw new ArgumentException("Data должен содержать 128 элементов", nameof(data)); + + PageIndex = pageIndex; + AccessTime = DateTime.Now; + IsModified = false; + BitMap = bitMap; + Data = data; + } +} \ No newline at end of file diff --git a/VM/Domain/PageBuffer.cs b/VM/Domain/PageBuffer.cs new file mode 100644 index 0000000000000000000000000000000000000000..f8d46c44c6da97835634c9473fa7cc1ec8cee4bf --- /dev/null +++ b/VM/Domain/PageBuffer.cs @@ -0,0 +1,66 @@ +namespace VM.Domain; + +public class PageBuffer( + ISwapFile swapFile, + IDateTimeService dateTimeService, + int maxPages = PageBuffer.DefaultMaxPagesInMemory) + : IPageBuffer +{ + private readonly List<IPage> _pages = []; + + public IPage GetPage(int pageIndex) + { + var page = _pages.FirstOrDefault(p => p.PageIndex == pageIndex); + if (page != null) + { + page.AccessTime = dateTimeService.CurrentDateTime(); + return page; + } + + if (_pages.Count >= maxPages) + ReplaceOldestPage(); + + page = swapFile.LoadPage(pageIndex); + page.AccessTime = dateTimeService.CurrentDateTime(); + _pages.Add(page); + return page; + } + + public void MarkPageModified(int pageIndex) + { + var page = _pages.FirstOrDefault(p => p.PageIndex == pageIndex) ?? GetPage(pageIndex); + page.IsModified = true; + page.AccessTime = dateTimeService.CurrentDateTime(); + } + + private void ReplaceOldestPage() + { + var oldestPage = _pages.MinBy(p => p.AccessTime); + if (oldestPage == null) return; + + if (oldestPage.IsModified) + { + swapFile.SavePage(oldestPage); + } + + _pages.Remove(oldestPage); + } + + private void SaveAllPages() + { + foreach (var page in _pages) + { + swapFile.SavePage(page); + } + _pages.Clear(); + } + + private const int DefaultMaxPagesInMemory = 3; + + public void Dispose() + { + SaveAllPages(); + swapFile.Dispose(); + GC.SuppressFinalize(this); + } +} \ No newline at end of file diff --git a/VM/Domain/SwapFile.cs b/VM/Domain/SwapFile.cs new file mode 100644 index 0000000000000000000000000000000000000000..f5857ef23dd15f68033856263243772190de09b6 --- /dev/null +++ b/VM/Domain/SwapFile.cs @@ -0,0 +1,104 @@ +using System.Collections; + +namespace VM.Domain; + +/// <summary> +/// Represents a swap file used for managing virtual memory pages. +/// </summary> +public class SwapFile : ISwapFile +{ + private readonly FileStream _fs; + private readonly BinaryWriter _writer; + private readonly BinaryReader _reader; + + private const int PageSize = 512; + private const int ElementsPerPage = 128; + private const int BitMapSize = ElementsPerPage / 8; + private const int StartOffset = 2; + + /// <summary> + /// Initializes a new instance of the <see cref="SwapFile"/> class. + /// </summary> + /// <param name="fileName">The name of the swap file.</param> + /// <param name="size">The size of the swap file.</param> + public SwapFile(string fileName, long size) + { + _fs = new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite); + _writer = new BinaryWriter(_fs); + _reader = new BinaryReader(_fs); + + InitializeFile(size); + } + + /// <summary> + /// Initializes the swap file with the specified size. + /// </summary> + /// <param name="size">The size of the swap file.</param> + private void InitializeFile(long size) + { + _writer.Write("VM"u8.ToArray()); + + var pageCount = (size * sizeof(int) + PageSize - 1) / PageSize; + for (int i = 0; i < pageCount; i++) + { + _writer.Write(new byte[BitMapSize]); + _writer.Write(new byte[PageSize]); + } + + _writer.Flush(); + } + + /// <summary> + /// Loads the page at the specified index. + /// </summary> + /// <param name="pageIndex">The index of the page to load.</param> + /// <returns>The loaded page.</returns> + /// <exception cref="InvalidOperationException">Thrown when the requested page is out of file bounds.</exception> + public IPage LoadPage(int pageIndex) + { + if (_fs.Length < StartOffset + (pageIndex + 1) * PageSize) + throw new InvalidOperationException("Запрашиваемая страница выходит Р·Р° границы файла."); + + _fs.Seek(StartOffset + pageIndex * PageSize, SeekOrigin.Begin); + var bitMapBytes = _reader.ReadBytes(BitMapSize); + var bitmap = new BitArray(bitMapBytes); + var data = new int[ElementsPerPage]; + + for (int i = 0; i < ElementsPerPage; i++) + { + data[i] = _reader.ReadInt32(); + } + + return new Page(pageIndex, bitmap, data); + } + + /// <summary> + /// Saves the specified page to the swap file. + /// </summary> + /// <param name="page">The page to save.</param> + public void SavePage(IPage page) + { + _fs.Seek(StartOffset + page.PageIndex * PageSize, SeekOrigin.Begin); + var bitMapBytes = new byte[BitMapSize]; + page.BitMap.CopyTo(bitMapBytes, 0); + _writer.Write(bitMapBytes); + + for (var i = 0; i < ElementsPerPage; i++) + { + _writer.Write(page.Data[i]); + } + + _writer.Flush(); + } + + /// <summary> + /// Disposes the resources used by the swap file. + /// </summary> + public void Dispose() + { + _writer.Dispose(); + _reader.Dispose(); + _fs.Dispose(); + GC.SuppressFinalize(this); + } +} \ No newline at end of file diff --git a/VM/Domain/SystemDateTimeService.cs b/VM/Domain/SystemDateTimeService.cs new file mode 100644 index 0000000000000000000000000000000000000000..641876c3ab120099a18fb02da848f95a1b3d59f4 --- /dev/null +++ b/VM/Domain/SystemDateTimeService.cs @@ -0,0 +1,9 @@ +namespace VM.Domain; + +public class SystemDateTimeService : IDateTimeService +{ + public DateTime CurrentDateTime() + { + return DateTime.Now; + } +} \ No newline at end of file diff --git a/VM/Domain/VarcharVirtualMemory.cs b/VM/Domain/VarcharVirtualMemory.cs new file mode 100644 index 0000000000000000000000000000000000000000..704ae3f64c4c6a847a44bd7f1c05f2ef63914c5d --- /dev/null +++ b/VM/Domain/VarcharVirtualMemory.cs @@ -0,0 +1,104 @@ +using System.Text; + +namespace VM.Domain; + +/// <summary> +/// Represents a virtual memory for storing variable-length strings. +/// </summary> +public class VarcharVirtualMemory : IVirtualMemory<string> +{ + private const int ElementsPerPage = 128; + + private readonly int _maxStringSize; + private readonly ISwapFile _swapFile; + private readonly FileStream _dataFile; + private readonly int _size; + + /// <summary> + /// Initializes a new instance of the <see cref="VarcharVirtualMemory"/> class. + /// </summary> + /// <param name="swapFile">The swap file used to manage pages of memory.</param> + /// <param name="dataFileName">The name of the data file used to store string values.</param> + /// <param name="maxStringSize">The maximum size of the strings to be stored.</param> + public VarcharVirtualMemory(ISwapFile swapFile, string dataFileName, int maxStringSize, int size) + { + _maxStringSize = maxStringSize; + _swapFile = swapFile; + _dataFile = new FileStream(dataFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite); + _size = size; + } + + /// <summary> + /// Sets the string value at the specified index in the virtual memory. + /// </summary> + /// <param name="index">The index at which to set the value.</param> + /// <param name="value">The string value to set.</param> + /// <exception cref="IndexOutOfRangeException">Thrown when the index is out of range.</exception> + /// <exception cref="ArgumentException">Thrown when the string exceeds the maximum size.</exception> + public void Set(int index, string value) + { + if (index < 0 || index >= _size) + throw new IndexOutOfRangeException("Рндекс РІРЅРµ границ массива"); + + if (value.Length > _maxStringSize) + throw new ArgumentException($"Строка превышает максимальный размер {_maxStringSize} символов"); + + var pageIndex = index / ElementsPerPage; + var localIndex = index % ElementsPerPage; + + var page = _swapFile.LoadPage(pageIndex); + long position; + + using (var writer = new BinaryWriter(_dataFile, Encoding.UTF8, true)) + { + _dataFile.Seek(0, SeekOrigin.End); + position = _dataFile.Position; + + writer.Write(value.Length); + writer.Write(Encoding.UTF8.GetBytes(value)); + } + + page.Data[localIndex] = (int)position; + page.BitMap[localIndex] = true; + + _swapFile.SavePage(page); + } + + /// <summary> + /// Gets the string value at the specified index in the virtual memory. + /// </summary> + /// <param name="index">The index from which to get the value.</param> + /// <returns>The string value at the specified index.</returns> + /// <exception cref="IndexOutOfRangeException">Thrown when the index is out of range.</exception> + /// <exception cref="InvalidOperationException">Thrown when the element is not initialized.</exception> + public string Get(int index) + { + if (index < 0) + throw new IndexOutOfRangeException("Рндекс РІРЅРµ границ массива"); + + var pageIndex = index / ElementsPerPage; + var localIndex = index % ElementsPerPage; + var page = _swapFile.LoadPage(pageIndex); + + if (!page.BitMap[localIndex]) + throw new InvalidOperationException("Рлемент РЅРµ инициализирован"); + + long position = page.Data[localIndex]; + + using var reader = new BinaryReader(_dataFile, Encoding.UTF8, true); + _dataFile.Seek(position, SeekOrigin.Begin); + var length = reader.ReadInt32(); + var bytes = reader.ReadBytes(length); + return Encoding.UTF8.GetString(bytes); + } + + /// <summary> + /// Disposes the resources used by the virtual memory. + /// </summary> + public void Dispose() + { + _swapFile.Dispose(); + _dataFile.Dispose(); + GC.SuppressFinalize(this); + } +} \ No newline at end of file diff --git a/VM/VM.csproj b/VM/VM.csproj new file mode 100644 index 0000000000000000000000000000000000000000..31d2e9283f7c42ff4b732cd409ebc68a5eee5b89 --- /dev/null +++ b/VM/VM.csproj @@ -0,0 +1,13 @@ +п»ї<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net9.0</TargetFramework> + <ImplicitUsings>enable</ImplicitUsings> + <Nullable>enable</Nullable> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.2" /> + </ItemGroup> + +</Project>