Skip to content

Commit

Permalink
Merge pull request #110 from Archomeda/feature/custom-mumble-link-reader
Browse files Browse the repository at this point in the history
Custom Mumble Link reader
  • Loading branch information
Archomeda authored Dec 30, 2021
2 parents 65ba4a4 + 6dac371 commit e9d89da
Show file tree
Hide file tree
Showing 15 changed files with 578 additions and 157 deletions.
14 changes: 11 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
# Gw2Sharp History

## 1.6.0 (15 November 2021)
This release includes a feature to change the API hostname(s) in case you want to run a proxy API server. See [the documentation](https://archomeda.github.io/Gw2Sharp/master/guides/proxy.html) for more information.
## 1.6.0 (30 December 2021)
This release includes support for a couple of additional customizations:
- Ability to change the API hostname(s) in case you want to run a proxy API server (see [the documentation](https://archomeda.github.io/Gw2Sharp/master/guides/proxy.html) for more information)
- Ability to change the Mumble Link reader for mocking, or to use a different method and/or source for Mumble Link

### HTTP
- Add `ApiBaseUrl` and `RenderBaseUrl` properties to `Gw2Sharp.IConnection` ([#108](https://github.com/Archomeda/Gw2Sharp/issues/108), [#107](https://github.com/Archomeda/Gw2Sharp/pull/109))
- Add `ApiBaseUrl` and `RenderBaseUrl` properties to `Gw2Sharp.IConnection` ([#108](https://github.com/Archomeda/Gw2Sharp/issues/108), [#109](https://github.com/Archomeda/Gw2Sharp/pull/109))

### Mumble Link
- Add support for custom Mumble Link readers ([#110](https://github.com/Archomeda/Gw2Sharp/pull/110))
- Add `MumbleClientReaderFactory` property to `Gw2Sharp.IConnection` that constructs a new Mumble Link reader that implements `Gw2Sharp.Mumble.IGw2MumbleClientReader`
- .NET 5 and up: `SupportedOSPlatformAttribute`s have been moved around to support these changes
- The following structs that are used in the reader have been made public: `Gw2Sharp.Mumble.Gw2LinkedMem`, `Gw2Sharp.Mumble.Gw2Context` and `Gw2Sharp.Mumble.Models.UiState`

---

Expand Down
19 changes: 19 additions & 0 deletions Gw2Sharp.Tests/ConnectionTests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System;
using System.Diagnostics;
using AutoFixture.Xunit2;
using FluentAssertions;
using FluentAssertions.Execution;
using Gw2Sharp.Mumble;
using Gw2Sharp.WebApi;
using Gw2Sharp.WebApi.Caching;
using Gw2Sharp.WebApi.Http;
Expand Down Expand Up @@ -108,5 +110,22 @@ public void LocaleStringTest(string expected, Locale locale)
var connection = new Connection(locale);
Assert.Equal(expected, connection.LocaleString);
}

[Theory]
[AutoData]
public void UsesCorrectMumbleClientReaderFactoryTest(string mumbleLinkName)
{
var connection = new Connection();
using var actual = connection.MumbleClientReaderFactory(mumbleLinkName);

#if NET5_0_OR_GREATER
if (OperatingSystem.IsWindows())
#else
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
#endif
actual.Should().BeOfType<Gw2MumbleClientReader>();
else
actual.Should().BeOfType<UnsupportedMumblePlatformClientReader>();
}
}
}
1 change: 1 addition & 0 deletions Gw2Sharp.Tests/Gw2Sharp.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<PropertyGroup>
<TargetFrameworks>net5.0;netcoreapp3.1;netcoreapp2.1;net461</TargetFrameworks>
<LangVersion>preview</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Nullable>annotations</Nullable>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<IsTestProject>true</IsTestProject>
Expand Down
110 changes: 110 additions & 0 deletions Gw2Sharp.Tests/Mumble/Gw2MumbleClientReaderTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
using System;
using System.IO.MemoryMappedFiles;
using System.Net.Sockets;
using System.Reflection;
using AutoFixture.Xunit2;
using FluentAssertions;
using FluentAssertions.Execution;
using Gw2Sharp.Models;
using Gw2Sharp.Mumble;
using Gw2Sharp.Mumble.Models;
using Xunit;

namespace Gw2Sharp.Tests.Mumble
{
public class Gw2MumbleClientReaderTests
{
#if NET5_0_OR_GREATER
private bool isWindows = OperatingSystem.IsWindows();
#else
private bool isWindows = Environment.OSVersion.Platform == PlatformID.Win32NT;
#endif

[Theory]
[AutoData]
public void ThrowsPlatformNotSupportedExceptionIfNotWindowsTest(string mumbleLinkName)
{
Action createClient = () =>
{
using var reader = new Gw2MumbleClientReader(mumbleLinkName);
};

if (this.isWindows)
createClient.Should().NotThrow<PlatformNotSupportedException>();
else
createClient.Should().Throw<PlatformNotSupportedException>();
}

[SkippableFact]
public unsafe void ReadStructCorrectlyTest()
{
// Named memory mapped files aren't supported on Unix based systems.
// So we need to skip this test.
Skip.IfNot(this.isWindows, "Mumble Link is only supported on Windows");

using var memorySource = Assembly.GetExecutingAssembly().GetManifestResourceStream($"Gw2Sharp.Tests.TestFiles.Mumble.MemoryMappedFile.bin");
using var memoryMappedFile = MemoryMappedFile.CreateOrOpen(Gw2MumbleClient.DEFAULT_MUMBLE_LINK_MAP_NAME, memorySource.Length);
using var stream = memoryMappedFile.CreateViewStream();
memorySource.CopyTo(stream);

using var client = new Gw2MumbleClientReader(Gw2MumbleClient.DEFAULT_MUMBLE_LINK_MAP_NAME);
client.Open();
var mem = client.Read();

using (new AssertionScope())
{
mem.uiVersion.Should().Be(2);
mem.uiTick.Should().Be(7244);
mem.fAvatarPosition[0].Should().BeApproximately(-68.27287f, 5);
mem.fAvatarPosition[1].Should().BeApproximately(119.209f, 3);
mem.fAvatarPosition[2].Should().BeApproximately(-6.154798f, 6);
mem.fAvatarFront[0].Should().BeApproximately(0.8834972f, 7);
mem.fAvatarFront[1].Should().BeApproximately(0f, 5);
mem.fAvatarFront[2].Should().BeApproximately(-0.4684365f, 7);
new string(mem.name).Should().BeEquivalentTo(Gw2MumbleClient.MUMBLE_LINK_GAME_NAME_GUILD_WARS_2);
mem.fCameraPosition[0].Should().BeApproximately(-78.08988f, 5);
mem.fCameraPosition[1].Should().BeApproximately(126.4892f, 4);
mem.fCameraPosition[2].Should().BeApproximately(-0.2525153f, 7);
mem.fCameraFront[0].Should().BeApproximately(0.8136908f, 7);
mem.fCameraFront[1].Should().BeApproximately(-0.3139677f, 7);
mem.fCameraFront[2].Should().BeApproximately(-0.4892152f, 7);
new string(mem.identity).Should().BeEquivalentTo(@"{""name"":""Reiga Fiercecrusher"",""profession"":2,""spec"":18,""race"":1,""map_id"":1206,""world_id"":268435460,""team_color_id"":0,""commander"":false,""map"":1206,""fov"":0.960,""uisz"":1}");
mem.context.socketAddressFamily.Should().Be(AddressFamily.InterNetwork);
mem.context.socketAddress4[0].Should().Be(18);
mem.context.socketAddress4[1].Should().Be(197);
mem.context.socketAddress4[2].Should().Be(217);
mem.context.socketAddress4[3].Should().Be(165);
mem.context.socketPort.Should().Be(0);
mem.context.mapId.Should().Be(1206);
mem.context.mapType.Should().Be((uint)MapType.PublicMini);
mem.context.shardId.Should().Be(268435460u);
mem.context.instance.Should().Be(0);
mem.context.buildId.Should().Be(99552);
mem.context.uiState.Should().Be(UiState.IsCompassRotationEnabled | UiState.DoesGameHaveFocus | UiState.IsCompetitiveMode | UiState.DoesAnyInputHaveFocus);
mem.context.compassWidth.Should().Be(362);
mem.context.compassHeight.Should().Be(229);
mem.context.compassRotation.Should().BeApproximately(-2.11212f, 5);
mem.context.playerMapX.Should().BeApproximately(14400.01f, 2);
mem.context.playerMapY.Should().BeApproximately(18180.19f, 2);
mem.context.mapCenterX.Should().BeApproximately(14400.01f, 2);
mem.context.mapCenterY.Should().BeApproximately(18180.19f, 2);
mem.context.mapScale.Should().Be(1);
mem.context.processId.Should().Be(15101);
mem.context.mount.Should().Be(MountType.Griffon);
}
}

[SkippableTheory]
[AutoData]
public void DisposeCorrectlyTest(string mumbleLinkName)
{
// Named memory mapped files aren't supported on Unix based systems.
// So we need to skip this test.
Skip.IfNot(this.isWindows, "Mumble Link is only supported in Windows");

var reader = new Gw2MumbleClientReader(mumbleLinkName);
reader.Dispose();
Assert.ThrowsAny<ObjectDisposedException>(() => reader.Read());
}
}
}
183 changes: 83 additions & 100 deletions Gw2Sharp.Tests/Mumble/Gw2MumbleClientTests.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
using System;
using System.IO.MemoryMappedFiles;
using System.Reflection;
using System.Runtime.InteropServices;
using AutoFixture.Xunit2;
using FluentAssertions;
using Gw2Sharp.Models;
using FluentAssertions.Execution;
using Gw2Sharp.Mumble;
using Gw2Sharp.Mumble.Models;
using NSubstitute;
using Xunit;

namespace Gw2Sharp.Tests.Mumble
Expand All @@ -13,128 +14,110 @@ public class Gw2MumbleClientTests
{
private bool isWindows = Environment.OSVersion.Platform == PlatformID.Win32NT;

private Gw2LinkedMem GetLinkedMem()
{
using var resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"Gw2Sharp.Tests.TestFiles.Mumble.MemoryMappedFile.bin");
var buffer = new byte[resourceStream.Length];
resourceStream.Read(buffer, 0, buffer.Length);

var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
try
{
return Marshal.PtrToStructure<Gw2LinkedMem>(handle.AddrOfPinnedObject());
}
finally
{
handle.Free();
}
}

[Fact]
public void ThrowsPlatformNotSupportedExceptionIfNotWindowsTest()
public void ReadsCorrectlyTest()
{
Action createClient = () =>
using var reader = Substitute.For<IGw2MumbleClientReader>();
reader.IsOpen.Returns(false);
reader.Read().Returns(GetLinkedMem());

using (var client = new Gw2MumbleClient(_ => reader))
{
using var client = new Gw2MumbleClient();
client.Update();
};

if (this.isWindows)
createClient.Should().NotThrow<PlatformNotSupportedException>();
else
createClient.Should().Throw<PlatformNotSupportedException>();
}
reader.Received(1).Open();
reader.Received(1).Read();
}

[SkippableFact]
public void ReadStructCorrectlyTest()
{
// Named memory mapped files aren't supported on Unix based systems.
// So we need to skip this test.
Skip.IfNot(this.isWindows, "Mumble Link is only supported in Windows");

using var memorySource = Assembly.GetExecutingAssembly().GetManifestResourceStream($"Gw2Sharp.Tests.TestFiles.Mumble.MemoryMappedFile.bin");
using var memoryMappedFile = MemoryMappedFile.CreateOrOpen(Gw2MumbleClient.DEFAULT_MUMBLE_LINK_MAP_NAME, memorySource.Length);
using var stream = memoryMappedFile.CreateViewStream();
memorySource.CopyTo(stream);

using var client = new Gw2MumbleClient();
client.Update();
Assert.True(client.IsAvailable);
Assert.Equal(2, client.Version);
Assert.Equal(7244, client.Tick);
Assert.Equal(-68.27287, client.AvatarPosition.X, 5);
Assert.Equal(119.209, client.AvatarPosition.Y, 3);
Assert.Equal(-6.154798, client.AvatarPosition.Z, 6);
Assert.Equal(0.8834972, client.AvatarFront.X, 7);
Assert.Equal(0, client.AvatarFront.Y, 5);
Assert.Equal(-0.4684365, client.AvatarFront.Z, 7);
Assert.Equal(Gw2MumbleClient.MUMBLE_LINK_GAME_NAME_GUILD_WARS_2, client.Name);
Assert.Equal(-78.08988, client.CameraPosition.X, 5);
Assert.Equal(126.4892, client.CameraPosition.Y, 4);
Assert.Equal(-0.2525153, client.CameraPosition.Z, 7);
Assert.Equal(0.8136908, client.CameraFront.X, 7);
Assert.Equal(-0.3139677, client.CameraFront.Y, 7);
Assert.Equal(-0.4892152, client.CameraFront.Z, 7);
Assert.Equal(@"{""name"":""Reiga Fiercecrusher"",""profession"":2,""spec"":18,""race"":1,""map_id"":1206,""world_id"":268435460,""team_color_id"":0,""commander"":false,""map"":1206,""fov"":0.960,""uisz"":1}", client.RawIdentity);
Assert.Equal("Reiga Fiercecrusher", client.CharacterName);
Assert.Equal(ProfessionType.Warrior, client.Profession);
Assert.Equal(RaceType.Charr, client.Race);
Assert.Equal(18, client.Specialization);
Assert.Equal(0, client.TeamColorId);
Assert.False(client.IsCommander);
Assert.Equal(0.960, client.FieldOfView);
Assert.Equal(UiSize.Normal, client.UiSize);
Assert.Equal("18.197.217.165", client.ServerAddress);
Assert.Equal(0, client.ServerPort);
Assert.Equal(1206, client.MapId);
Assert.Equal(MapType.PublicMini, client.MapType);
Assert.Equal(268435460u, client.ShardId);
Assert.Equal(0u, client.Instance);
Assert.Equal(99552, client.BuildId);
Assert.False(client.IsMapOpen);
Assert.False(client.IsCompassTopRight);
Assert.True(client.IsCompassRotationEnabled);
Assert.True(client.DoesGameHaveFocus);
Assert.True(client.IsCompetitiveMode);
Assert.True(client.DoesAnyInputHaveFocus);
Assert.False(client.IsInCombat);
Assert.Equal(362, client.Compass.Width);
Assert.Equal(229, client.Compass.Height);
Assert.Equal(-2.11212, client.CompassRotation, 5);
Assert.Equal(14400.01, client.PlayerLocationMap.X, 2);
Assert.Equal(18180.19, client.PlayerLocationMap.Y, 2);
Assert.Equal(14400.01, client.MapCenter.X, 2);
Assert.Equal(18180.19, client.MapCenter.Y, 2);
Assert.Equal(1, client.MapScale);
Assert.Equal(15101u, client.ProcessId);
Assert.Equal(MountType.Griffon, client.Mount);
reader.Received(1).Dispose();
}

[SkippableFact]
[Fact]
public void DisposeCorrectlyTest()
{
// Named memory mapped files aren't supported on Unix based systems.
// So we need to skip this test.
Skip.IfNot(this.isWindows, "Mumble Link is only supported in Windows");

var client = new Gw2MumbleClient();
using var reader = Substitute.For<IGw2MumbleClientReader>();
var client = new Gw2MumbleClient(_ => reader);
client.Dispose();
Assert.ThrowsAny<ObjectDisposedException>(() => client.Update());

Action act = () => client.Update();
act.Should().Throw<ObjectDisposedException>();
}

[SkippableFact]
[Fact]
public void DisposeChildOnlyCorrectlyTest()
{
// Named memory mapped files aren't supported on Unix based systems.
// So we need to skip this test.
Skip.IfNot(this.isWindows, "Mumble Link is only supported in Windows");

using var rootClient = new Gw2MumbleClient();
using var reader = Substitute.For<IGw2MumbleClientReader>();
using var rootClient = new Gw2MumbleClient(_ => reader);
var childClientA = rootClient["CinderSteeltemper"];
var childClientB = rootClient["VishenSteelshot"];
childClientA.Dispose();
Assert.ThrowsAny<ObjectDisposedException>(() => childClientA.Update());
childClientB.Update(); // Sibling should not be disposed
rootClient.Update(); // Root should not be disposed

using (new AssertionScope())
{
Action actRoot = () => rootClient.Update();
Action actChildA = () => childClientA.Update();
Action actChildB = () => childClientB.Update();

// Only child A should be disposed
actRoot.Should().NotThrow();
actChildA.Should().Throw<ObjectDisposedException>();
actChildB.Should().NotThrow();
}
}

[SkippableFact]
[Fact]
public void DisposeAllFromRootCorrectlyTest()
{
// Named memory mapped files aren't supported on Unix based systems.
// So we need to skip this test.
Skip.IfNot(this.isWindows, "Mumble Link is only supported in Windows");

var rootClient = new Gw2MumbleClient();
using var reader = Substitute.For<IGw2MumbleClientReader>();
var rootClient = new Gw2MumbleClient(_ => reader);
var childClientA = rootClient["CinderSteeltemper"];
var childClientB = rootClient["VishenSteelshot"];
rootClient.Dispose();
Assert.ThrowsAny<ObjectDisposedException>(() => rootClient.Update());
Assert.ThrowsAny<ObjectDisposedException>(() => childClientA.Update());
Assert.ThrowsAny<ObjectDisposedException>(() => childClientB.Update());

using (new AssertionScope())
{
Action actRoot = () => rootClient.Update();
Action actChildA = () => childClientA.Update();
Action actChildB = () => childClientB.Update();

// Everything should be disposed
actRoot.Should().Throw<ObjectDisposedException>();
actChildA.Should().Throw<ObjectDisposedException>();
actChildB.Should().Throw<ObjectDisposedException>();
}
}

[Theory]
[AutoData]
public void UsesSameInstanceForSameNamesTest(string mumbleLinkName)
{
using var client = new Gw2MumbleClient(x => Substitute.For<IGw2MumbleClientReader>());
var childClientA = client[mumbleLinkName];
var childClientB = client[mumbleLinkName];
var childClientAA = childClientA[mumbleLinkName];

using (new AssertionScope())
{
childClientA.Should().BeSameAs(childClientB);
childClientA.Should().BeSameAs(childClientAA);
}
}
}
}
Loading

0 comments on commit e9d89da

Please sign in to comment.