diff --git a/Magma.sln b/Magma.sln index de00e04..886c879 100644 --- a/Magma.sln +++ b/Magma.sln @@ -57,6 +57,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Magma.WinTun.TcpHost", "sam EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Magma.AF_XDP", "src\Magma.AF_XDP\Magma.AF_XDP.csproj", "{5922914B-D7E6-4A23-9A26-5589FD72727B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Magma.WinTun.Facts", "test\Magma.WinTun.Facts\Magma.WinTun.Facts.csproj", "{8B93DD2C-0C6B-4440-9126-2AF232478660}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -307,6 +309,18 @@ Global {5922914B-D7E6-4A23-9A26-5589FD72727B}.Release|x64.Build.0 = Release|Any CPU {5922914B-D7E6-4A23-9A26-5589FD72727B}.Release|x86.ActiveCfg = Release|Any CPU {5922914B-D7E6-4A23-9A26-5589FD72727B}.Release|x86.Build.0 = Release|Any CPU + {8B93DD2C-0C6B-4440-9126-2AF232478660}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8B93DD2C-0C6B-4440-9126-2AF232478660}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8B93DD2C-0C6B-4440-9126-2AF232478660}.Debug|x64.ActiveCfg = Debug|Any CPU + {8B93DD2C-0C6B-4440-9126-2AF232478660}.Debug|x64.Build.0 = Debug|Any CPU + {8B93DD2C-0C6B-4440-9126-2AF232478660}.Debug|x86.ActiveCfg = Debug|Any CPU + {8B93DD2C-0C6B-4440-9126-2AF232478660}.Debug|x86.Build.0 = Debug|Any CPU + {8B93DD2C-0C6B-4440-9126-2AF232478660}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8B93DD2C-0C6B-4440-9126-2AF232478660}.Release|Any CPU.Build.0 = Release|Any CPU + {8B93DD2C-0C6B-4440-9126-2AF232478660}.Release|x64.ActiveCfg = Release|Any CPU + {8B93DD2C-0C6B-4440-9126-2AF232478660}.Release|x64.Build.0 = Release|Any CPU + {8B93DD2C-0C6B-4440-9126-2AF232478660}.Release|x86.ActiveCfg = Release|Any CPU + {8B93DD2C-0C6B-4440-9126-2AF232478660}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -332,6 +346,7 @@ Global {503140F9-24F6-41B5-89F2-BB0FB24CCB2E} = {34A1DC50-486C-4BB9-9929-1805CA0B0DD0} {845A5A0B-E59B-46AF-BF95-4FC50D660967} = {23E375E0-8A4A-4D6A-8C96-9F2046CE9EB0} {5922914B-D7E6-4A23-9A26-5589FD72727B} = {34A1DC50-486C-4BB9-9929-1805CA0B0DD0} + {8B93DD2C-0C6B-4440-9126-2AF232478660} = {B1BA53C8-CCCF-46D5-BA9F-6031811F2E19} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {99D656D2-FC86-462A-BB4C-610D644ADC62} diff --git a/test/Magma.WinTun.Facts/Magma.WinTun.Facts.csproj b/test/Magma.WinTun.Facts/Magma.WinTun.Facts.csproj new file mode 100644 index 0000000..bd78be3 --- /dev/null +++ b/test/Magma.WinTun.Facts/Magma.WinTun.Facts.csproj @@ -0,0 +1,19 @@ + + + + net10.0 + true + false + + + + + + + + + + + + + diff --git a/test/Magma.WinTun.Facts/README.md b/test/Magma.WinTun.Facts/README.md new file mode 100644 index 0000000..969b419 --- /dev/null +++ b/test/Magma.WinTun.Facts/README.md @@ -0,0 +1,57 @@ +# Magma.WinTun.Facts + +Test suite for the WinTun module. + +## Running Tests + +### Unit Tests + +Unit tests run on all platforms (Windows and Linux) and do not require the WinTun driver: + +```bash +dotnet test test/Magma.WinTun.Facts +``` + +### Integration Tests + +Integration tests require: +- Windows operating system +- WinTun driver installed +- A configured TAP adapter + +Integration tests are **skipped by default**. To enable them: + +1. Set the environment variable: + ```powershell + $env:WINTUN_INTEGRATION_TESTS="1" + ``` + +2. (Optional) Specify a custom adapter name: + ```powershell + $env:WINTUN_ADAPTER_NAME="YourAdapterName" + ``` + +3. Run the tests: + ```bash + dotnet test test/Magma.WinTun.Facts + ``` + +Or run only integration tests: +```bash +dotnet test test/Magma.WinTun.Facts --filter Category=Integration +``` + +## Test Categories + +- **Unit Tests**: Test public APIs without requiring the WinTun driver + - `WinIOFacts`: Constants and P/Invoke declarations + - `WinTunTransportReceiverFacts`: Transport receiver interface + - `WinTunMemoryPoolFacts`: Memory pool lifecycle and management + +- **Integration Tests** (Category="Integration"): Require WinTun driver + - `WinTunPortIntegrationFacts`: Adapter create/destroy lifecycle + +## Platform Support + +- **Linux**: Unit tests run and pass. Integration tests are skipped. +- **Windows**: All tests can run if WinTun driver is installed and integration tests are enabled. diff --git a/test/Magma.WinTun.Facts/WinTunMemoryPoolFacts.cs b/test/Magma.WinTun.Facts/WinTunMemoryPoolFacts.cs new file mode 100644 index 0000000..56aaeff --- /dev/null +++ b/test/Magma.WinTun.Facts/WinTunMemoryPoolFacts.cs @@ -0,0 +1,115 @@ +using System; +using System.Buffers; +using System.Threading.Tasks; +using Magma.WinTun.Internal; +using Xunit; + +namespace Magma.WinTun.Facts +{ + public class WinTunMemoryPoolFacts + { + [Fact] + public void PoolCreatesBuffersOfCorrectSize() + { + var poolSize = 10; + var bufferSize = 2000; + var pool = new WinTunMemoryPool(poolSize, bufferSize); + + var success = pool.TryGetMemory(out var memory); + + Assert.True(success); + Assert.NotNull(memory); + Assert.Equal(bufferSize, memory.Memory.Length); + + memory.Return(); + } + + [Fact] + public void PoolSupportsMultipleBuffers() + { + var poolSize = 5; + var bufferSize = 1500; + var pool = new WinTunMemoryPool(poolSize, bufferSize); + + var buffers = new WinTunMemoryPool.WinTunOwnedMemory[poolSize]; + + for (var i = 0; i < poolSize; i++) + { + var success = pool.TryGetMemory(out buffers[i]); + Assert.True(success); + Assert.Equal(bufferSize, buffers[i].Memory.Length); + } + + var exhausted = pool.TryGetMemory(out _); + Assert.False(exhausted); + + for (var i = 0; i < poolSize; i++) + { + buffers[i].Return(); + } + } + + [Fact] + public void ReturnedBufferCanBeReused() + { + var pool = new WinTunMemoryPool(2, 1000); + + var success1 = pool.TryGetMemory(out var memory1); + Assert.True(success1); + + memory1.Return(); + + var success2 = pool.TryGetMemory(out var memory2); + Assert.True(success2); + + memory2.Return(); + } + + [Fact] + public void TryGetMemoryReturnsFalseWhenExhausted() + { + var pool = new WinTunMemoryPool(1, 1000); + + var success1 = pool.TryGetMemory(out var memory1); + Assert.True(success1); + + var success2 = pool.TryGetMemory(out var memory2); + Assert.False(success2); + Assert.Null(memory2); + + memory1.Return(); + } + + [Fact] + public async Task GetMemoryAsyncWaitsWhenPoolIsEmpty() + { + var pool = new WinTunMemoryPool(1, 1000); + + var memory1 = await pool.GetMemoryAsync(); + + var getTask = pool.GetMemoryAsync(); + await Task.Delay(50); + Assert.False(getTask.IsCompleted); + + memory1.Return(); + + var memory2 = await getTask; + Assert.NotNull(memory2); + + memory2.Return(); + } + + [Fact] + public void MemoryOwnerGetSpanReturnsCorrectSpan() + { + var pool = new WinTunMemoryPool(1, 1500); + var success = pool.TryGetMemory(out var memory); + + Assert.True(success); + var span = memory.GetSpan(); + Assert.Equal(1500, span.Length); + + memory.Return(); + } + } +} diff --git a/test/Magma.WinTun.Facts/WinTunPortIntegrationFacts.cs b/test/Magma.WinTun.Facts/WinTunPortIntegrationFacts.cs new file mode 100644 index 0000000..685c272 --- /dev/null +++ b/test/Magma.WinTun.Facts/WinTunPortIntegrationFacts.cs @@ -0,0 +1,65 @@ +using System; +using System.Runtime.InteropServices; +using Magma.Network.Abstractions; +using Magma.WinTun.Internal; +using Xunit; + +namespace Magma.WinTun.Facts +{ + [Trait("Category", "Integration")] + public class WinTunPortIntegrationFacts + { + private static bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + private static bool IntegrationTestsEnabled => + !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("WINTUN_INTEGRATION_TESTS")); + + [Fact(Skip = "Requires WinTun driver and adapter - set WINTUN_INTEGRATION_TESTS=1 to enable")] + public void CanCreateWinTunPort() + { + if (!IsWindows || !IntegrationTestsEnabled) + { + return; + } + + var adapterName = Environment.GetEnvironmentVariable("WINTUN_ADAPTER_NAME") ?? "WinTun"; + + using (var port = new WinTunPort( + adapterName, + transmitter => new TestPacketReceiver())) + { + Assert.NotNull(port); + } + } + + [Fact(Skip = "Requires WinTun driver and adapter - set WINTUN_INTEGRATION_TESTS=1 to enable")] + public void WinTunPortDisposesCleanly() + { + if (!IsWindows || !IntegrationTestsEnabled) + { + return; + } + + var adapterName = Environment.GetEnvironmentVariable("WINTUN_ADAPTER_NAME") ?? "WinTun"; + + var port = new WinTunPort( + adapterName, + transmitter => new TestPacketReceiver()); + + port.Dispose(); + + port.Dispose(); + } + + private class TestPacketReceiver : IPacketReceiver + { + public void FlushPendingAcks() + { + } + + public T TryConsume(T input) where T : System.Buffers.IMemoryOwner + { + return default; + } + } + } +} diff --git a/test/Magma.WinTun.Facts/WinTunTransportReceiverFacts.cs b/test/Magma.WinTun.Facts/WinTunTransportReceiverFacts.cs new file mode 100644 index 0000000..e60c711 --- /dev/null +++ b/test/Magma.WinTun.Facts/WinTunTransportReceiverFacts.cs @@ -0,0 +1,33 @@ +using System; +using System.Buffers; +using Magma.WinTun; +using Xunit; + +namespace Magma.WinTun.Facts +{ + public class WinTunTransportReceiverFacts + { + [Fact] + public void CanInstantiateWinTunTransportReceiver() + { + var receiver = new WinTunTransportReceiver(); + Assert.NotNull(receiver); + } + + [Fact] + public void FlushPendingAcksThrowsNotImplementedException() + { + var receiver = new WinTunTransportReceiver(); + Assert.Throws(() => receiver.FlushPendingAcks()); + } + + [Fact] + public void TryConsumeThrowsNotImplementedException() + { + var receiver = new WinTunTransportReceiver(); + using var memory = MemoryPool.Shared.Rent(100); + + Assert.Throws(() => receiver.TryConsume(memory)); + } + } +}