chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using NUnit.Framework;
|
||||
using MCPForUnity.Editor.Helpers;
|
||||
|
||||
namespace MCPForUnityTests.Editor.Services
|
||||
{
|
||||
[TestFixture]
|
||||
public class PortManagerTests
|
||||
{
|
||||
private string _savedPortFileContent;
|
||||
private string _savedLegacyFileContent;
|
||||
private string _portFilePath;
|
||||
private string _legacyFilePath;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
// Snapshot the on-disk port config so DiscoverNewPort tests don't
|
||||
// permanently alter the running bridge's persisted port.
|
||||
string dir = Path.Combine(
|
||||
System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile),
|
||||
".unity-mcp");
|
||||
_legacyFilePath = Path.Combine(dir, "unity-mcp-port.json");
|
||||
|
||||
// The hashed file uses a private helper; approximate the same hash.
|
||||
// We snapshot every json file in the directory to be safe.
|
||||
_portFilePath = null;
|
||||
_savedPortFileContent = null;
|
||||
_savedLegacyFileContent = null;
|
||||
|
||||
if (File.Exists(_legacyFilePath))
|
||||
_savedLegacyFileContent = File.ReadAllText(_legacyFilePath);
|
||||
|
||||
// Find the hashed port file for this project
|
||||
if (Directory.Exists(dir))
|
||||
{
|
||||
foreach (var f in Directory.GetFiles(dir, "unity-mcp-port-*.json"))
|
||||
{
|
||||
_portFilePath = f;
|
||||
_savedPortFileContent = File.ReadAllText(f);
|
||||
break; // one project at a time
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
// Restore the original port files
|
||||
if (_savedLegacyFileContent != null && _legacyFilePath != null)
|
||||
File.WriteAllText(_legacyFilePath, _savedLegacyFileContent);
|
||||
|
||||
if (_savedPortFileContent != null && _portFilePath != null)
|
||||
File.WriteAllText(_portFilePath, _savedPortFileContent);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsPortAvailable_ReturnsFalse_WhenPortIsOccupied()
|
||||
{
|
||||
var listener = new TcpListener(IPAddress.Loopback, 0);
|
||||
listener.Start();
|
||||
int port = ((IPEndPoint)listener.LocalEndpoint).Port;
|
||||
|
||||
try
|
||||
{
|
||||
Assert.IsFalse(PortManager.IsPortAvailable(port),
|
||||
"IsPortAvailable should return false for a port that is already bound");
|
||||
}
|
||||
finally
|
||||
{
|
||||
listener.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsPortAvailable_ReturnsTrue_WhenPortIsFree()
|
||||
{
|
||||
var listener = new TcpListener(IPAddress.Loopback, 0);
|
||||
listener.Start();
|
||||
int port = ((IPEndPoint)listener.LocalEndpoint).Port;
|
||||
listener.Stop();
|
||||
|
||||
Assert.IsTrue(PortManager.IsPortAvailable(port),
|
||||
"IsPortAvailable should return true for a port that is not bound");
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR_OSX
|
||||
[Test]
|
||||
public void IsPortAvailable_ReturnsFalse_WhenPortHeldWithReuseAddr()
|
||||
{
|
||||
// Simulate what AssetImportWorkers do: bind with SO_REUSEADDR.
|
||||
// IsPortAvailable must still detect this as occupied.
|
||||
var holder = new TcpListener(IPAddress.Loopback, 0);
|
||||
holder.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
|
||||
holder.Start();
|
||||
int port = ((IPEndPoint)holder.LocalEndpoint).Port;
|
||||
|
||||
try
|
||||
{
|
||||
Assert.IsFalse(PortManager.IsPortAvailable(port),
|
||||
"IsPortAvailable should detect ports held with SO_REUSEADDR on macOS");
|
||||
}
|
||||
finally
|
||||
{
|
||||
holder.Stop();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
[Test]
|
||||
public void ShouldAbandonBusyPort_KeepsSamePort_WithinReleaseWindow()
|
||||
{
|
||||
// A port busy for less than the fallback window is treated as our own
|
||||
// not-yet-released listener after a domain reload — keep retrying the same
|
||||
// port instead of silently switching and stranding the client (#1173).
|
||||
Assert.IsFalse(PortManager.ShouldAbandonBusyPort(0.0));
|
||||
Assert.IsFalse(PortManager.ShouldAbandonBusyPort(
|
||||
PortManager.BusyPortFallbackWindowSeconds - 0.5));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ShouldAbandonBusyPort_FallsBack_AfterReleaseWindow()
|
||||
{
|
||||
// A port that stays busy past the window is a foreign occupant — only then
|
||||
// does the bridge discover and switch to a new port.
|
||||
Assert.IsTrue(PortManager.ShouldAbandonBusyPort(
|
||||
PortManager.BusyPortFallbackWindowSeconds));
|
||||
Assert.IsTrue(PortManager.ShouldAbandonBusyPort(
|
||||
PortManager.BusyPortFallbackWindowSeconds + 5.0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DiscoverNewPort_ReturnsAvailablePort()
|
||||
{
|
||||
int port = PortManager.DiscoverNewPort();
|
||||
Assert.Greater(port, 0, "DiscoverNewPort should return a positive port number");
|
||||
Assert.IsTrue(PortManager.IsPortAvailable(port),
|
||||
"The port returned by DiscoverNewPort should be available");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DiscoverNewPort_SkipsOccupiedDefaultPort()
|
||||
{
|
||||
// Hold the default port (6400) so DiscoverNewPort must find an alternative
|
||||
TcpListener holder = null;
|
||||
try
|
||||
{
|
||||
holder = new TcpListener(IPAddress.Loopback, 6400);
|
||||
#if UNITY_EDITOR_OSX
|
||||
try { holder.Server.ExclusiveAddressUse = true; } catch { }
|
||||
#endif
|
||||
holder.Start();
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
// Port 6400 already occupied (e.g., by the running bridge) — that's fine,
|
||||
// the test still validates that DiscoverNewPort picks a different port.
|
||||
holder = null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
int port = PortManager.DiscoverNewPort();
|
||||
Assert.AreNotEqual(6400, port,
|
||||
"DiscoverNewPort should not return the default port when it is occupied");
|
||||
Assert.Greater(port, 0);
|
||||
}
|
||||
finally
|
||||
{
|
||||
holder?.Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user