initial commit

This commit is contained in:
Jo 2025-01-07 02:06:59 +01:00
parent 6715289efe
commit 788c3389af
37645 changed files with 2526849 additions and 80 deletions

View file

@ -0,0 +1,91 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Security.Cryptography;
namespace UnityEditor.CacheServerTests
{
internal class ByteArrayStream : Stream
{
private int m_pos;
public override bool CanRead
{
get { return true; }
}
public override bool CanSeek
{
get { return true; }
}
public override bool CanWrite
{
get { return true; }
}
public override long Length
{
get { return BackingBuffer.Length; }
}
public override long Position
{
get { return m_pos; }
set
{
m_pos = Math.Min((int)value, BackingBuffer.Length - 1);
Debug.Assert(m_pos >= 0);
}
}
public byte[] BackingBuffer { get; private set; }
public ByteArrayStream(long size)
{
BackingBuffer = new byte[size];
RandomNumberGenerator.Create().GetBytes(BackingBuffer);
}
public override void SetLength(long value) {}
public override void Flush() {}
public override void Write(byte[] buffer, int offset, int count)
{
Debug.Assert(count <= BackingBuffer.Length - m_pos); // can't write past out buffer length
count = Math.Min(count, buffer.Length - offset); // Don't read past the input buffer length
Buffer.BlockCopy(buffer, offset, BackingBuffer, m_pos, count);
m_pos += count;
}
public override int Read(byte[] buffer, int offset, int count)
{
count = Math.Min(count, BackingBuffer.Length - m_pos); // Don't copy more bytes than we have
count = Math.Min(count, buffer.Length - offset); // Don't overrun the destination buffer
Buffer.BlockCopy(BackingBuffer, m_pos, buffer, offset, count);
m_pos += count;
return count;
}
public override long Seek(long offset, SeekOrigin origin)
{
switch (origin)
{
case SeekOrigin.Begin:
Position = (int)offset;
break;
case SeekOrigin.Current:
Position += (int)offset;
break;
case SeekOrigin.End:
Position = BackingBuffer.Length - (int)offset - 1;
break;
default:
throw new ArgumentOutOfRangeException("origin", origin, null);
}
return Position;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8cce051c0b60040d68cd27a80e58f787
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,133 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using UnityEditor;
using UnityEngine;
namespace UnityEditor.CacheServerTests
{
internal class LocalCacheServer : ScriptableSingleton<LocalCacheServer>
{
[SerializeField] public string m_path;
[SerializeField] public int m_port;
[SerializeField] public int m_pid = -1;
private void Create(int port, ulong size, string cachePath)
{
var nodeExecutable = Utils.Paths.Combine(EditorApplication.applicationContentsPath, "Tools", "nodejs");
nodeExecutable = Application.platform == RuntimePlatform.WindowsEditor
? Utils.Paths.Combine(nodeExecutable, "node.exe")
: Utils.Paths.Combine(nodeExecutable, "bin", "node");
if (!Directory.Exists(cachePath))
Directory.CreateDirectory(cachePath);
m_path = cachePath;
var cacheServerJs = Utils.Paths.Combine(EditorApplication.applicationContentsPath, "Tools", "CacheServer", "main.js");
var processStartInfo = new ProcessStartInfo(nodeExecutable)
{
Arguments = "\"" + cacheServerJs + "\""
+ " --port " + port
+ " --path \"" + m_path
+ "\" --nolegacy"
+ " --monitor-parent-process " + Process.GetCurrentProcess().Id
// node.js has issues running on windows with stdout not redirected.
// so we silence logging to avoid that. And also to avoid CacheServer
// spamming the editor logs on OS X.
+ " --silent"
+ " --size " + size,
UseShellExecute = false,
CreateNoWindow = true
};
var p = new Process { StartInfo = processStartInfo };
p.Start();
m_port = port;
m_pid = p.Id;
Save(true);
}
public static string CachePath
{
get { return instance.m_path; }
}
public static int Port
{
get { return instance.m_port; }
}
public static void Setup(ulong size, string cachePath)
{
Kill();
instance.Create(GetRandomUnusedPort(), size, cachePath);
WaitForServerToComeAlive(instance.m_port);
}
public static void Kill()
{
if (instance.m_pid == -1)
return;
try
{
var p = Process.GetProcessById(instance.m_pid);
p.Kill();
instance.m_pid = -1;
}
catch
{
// if we could not get a process, there is non alive. continue.
}
}
public static void Clear()
{
Kill();
if (Directory.Exists(instance.m_path))
Directory.Delete(instance.m_path, true);
}
private static void WaitForServerToComeAlive(int port)
{
var start = DateTime.Now;
var maximum = start.AddSeconds(5);
while (DateTime.Now < maximum)
{
if (!PingHost("localhost", port, 10)) continue;
Console.WriteLine("Server Came alive after {0} ms", (DateTime.Now - start).TotalMilliseconds);
break;
}
}
private static int GetRandomUnusedPort()
{
var listener = new TcpListener(IPAddress.Any, 0);
listener.Start();
var port = ((IPEndPoint)listener.LocalEndpoint).Port;
listener.Stop();
return port;
}
private static bool PingHost(string host, int port, int timeout)
{
try
{
using (var client = new TcpClient())
{
var result = client.BeginConnect(host, port, null, null);
result.AsyncWaitHandle.WaitOne(TimeSpan.FromMilliseconds(timeout));
return client.Connected;
}
}
catch
{
return false;
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1b623084ee8eb4b9199e34858add3cac
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,30 @@
using System.IO;
using UnityEditor.Build.CacheServer;
namespace UnityEditor.CacheServerTests
{
internal class TestDownloadItem : IDownloadItem
{
private ByteArrayStream m_writeStream;
public FileId Id { get; private set; }
public FileType Type { get; private set; }
public void Finish() {}
public byte[] Bytes
{
get { return m_writeStream.BackingBuffer; }
}
public Stream GetWriteStream(long size)
{
return m_writeStream ?? (m_writeStream = new ByteArrayStream(size));
}
public TestDownloadItem(FileId fileId, FileType fileType)
{
Id = fileId;
Type = fileType;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b734b613791eb4b0381013f84745a995
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,460 @@
using System;
using System.IO;
using System.Threading;
using NUnit.Framework;
using UnityEditor.Build.CacheServer;
using UnityEngine;
using Random = System.Random;
namespace UnityEditor.CacheServerTests
{
[TestFixture]
public class Tests
{
private const string KTestHost = "127.0.0.1";
private const string KInvalidTestHost = "192.0.2.1";
private Random rand;
private static int TestPort
{
get { return LocalCacheServer.Port; }
}
private FileId GenerateFileId()
{
if (rand == null)
rand = new Random();
var guid = new byte[16];
var hash = new byte[16];
rand.NextBytes(guid);
rand.NextBytes(hash);
return FileId.From(guid, hash);
}
[OneTimeSetUp]
public void BeforeAll()
{
var cachePath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
LocalCacheServer.Setup(1024 * 1024, cachePath);
rand = new Random();
}
[OneTimeTearDown]
public void AfterAll()
{
LocalCacheServer.Clear();
}
[Test]
public void FileDownloadItem()
{
var fileId = GenerateFileId();
var readStream = new ByteArrayStream(128 * 1024);
var client = new Client(KTestHost, TestPort);
client.Connect();
client.BeginTransaction(fileId);
client.Upload(FileType.Asset, readStream);
client.EndTransaction();
Thread.Sleep(50); // give the server a little time to finish the transaction
var targetFile = Path.GetTempFileName();
var downloadItem = new FileDownloadItem(fileId, FileType.Asset, targetFile);
var mre = new ManualResetEvent(false);
Exception err = null;
client.DownloadFinished += (sender, args) =>
{
try
{
Assert.AreEqual(DownloadResult.Success, args.Result);
Assert.AreEqual(args.DownloadItem.Id, fileId);
Assert.IsTrue(File.Exists(targetFile));
var fileBytes = File.ReadAllBytes(targetFile);
Assert.IsTrue(Util.ByteArraysAreEqual(readStream.BackingBuffer, fileBytes));
}
catch (Exception e)
{
err = e;
}
finally
{
if (File.Exists(targetFile))
File.Delete(targetFile);
mre.Set();
}
};
client.QueueDownload(downloadItem);
Assert.IsTrue(mre.WaitOne(2000));
if (err != null)
throw err;
}
[Test]
public void QueueDownloadFromDownloadFinishedCallback()
{
var fileId = GenerateFileId();
var readStream = new ByteArrayStream(128 * 1024);
var client = new Client(KTestHost, TestPort);
client.Connect();
client.BeginTransaction(fileId);
client.Upload(FileType.Asset, readStream);
client.EndTransaction();
Thread.Sleep(50); // give the server a little time to finish the transaction
var targetFile1 = Path.GetTempFileName();
var downloadItem1 = new FileDownloadItem(fileId, FileType.Asset, targetFile1);
var targetFile2 = Path.GetTempFileName();
var downloadItem2 = new FileDownloadItem(fileId, FileType.Asset, targetFile2);
var mre = new ManualResetEvent(false);
Exception err = null;
client.DownloadFinished += (sender1, args1) =>
{
try
{
Assert.AreEqual(DownloadResult.Success, args1.Result);
Assert.AreEqual(args1.DownloadItem.Id, fileId);
Assert.IsTrue(File.Exists(targetFile1));
var fileBytes = File.ReadAllBytes(targetFile1);
Assert.IsTrue(Util.ByteArraysAreEqual(readStream.BackingBuffer, fileBytes));
}
catch (Exception e)
{
err = e;
}
finally
{
if (File.Exists(targetFile1))
File.Delete(targetFile1);
client.ResetDownloadFinishedEventHandler();
client.DownloadFinished += (sender2, args2) =>
{
try
{
Assert.AreEqual(DownloadResult.Success, args2.Result);
Assert.AreEqual(args2.DownloadItem.Id, fileId);
Assert.IsTrue(File.Exists(targetFile2));
var fileBytes = File.ReadAllBytes(targetFile2);
Assert.IsTrue(Util.ByteArraysAreEqual(readStream.BackingBuffer, fileBytes));
}
catch (Exception e)
{
err = e;
}
finally
{
if (File.Exists(targetFile2))
File.Delete(targetFile2);
mre.Set();
}
};
client.QueueDownload(downloadItem2);
}
};
client.QueueDownload(downloadItem1);
Assert.IsTrue(mre.WaitOne(2000));
if (err != null)
throw err;
}
[Test]
public void Connect()
{
var client = new Client(KTestHost, TestPort);
client.Connect();
client.Close();
}
[Test]
public void ConnectTimeout()
{
var client = new Client(KInvalidTestHost, TestPort);
TimeoutException err = null;
try
{
client.Connect(0);
}
catch (TimeoutException e)
{
err = e;
}
finally
{
client.Close();
Debug.Assert(err != null);
}
}
[Test]
public void TransactionIsolation()
{
var fileId = GenerateFileId();
var readStream = new ByteArrayStream(16 * 1024);
var client = new Client(KTestHost, TestPort);
client.Connect();
Assert.Throws<TransactionIsolationException>(() => client.Upload(FileType.Asset, readStream));
Assert.Throws<TransactionIsolationException>(() => client.EndTransaction());
// Back-to-back begin transactions are allowed
client.BeginTransaction(fileId);
Assert.DoesNotThrow(() => client.BeginTransaction(fileId));
}
[Test]
public void UploadDownloadOne()
{
var fileId = GenerateFileId();
var readStream = new ByteArrayStream(16 * 1024);
var client = new Client(KTestHost, TestPort);
client.Connect();
client.BeginTransaction(fileId);
client.Upload(FileType.Asset, readStream);
client.EndTransaction();
Thread.Sleep(50); // give the server a little time to finish the transaction
var downloadItem = new TestDownloadItem(fileId, FileType.Asset);
client.QueueDownload(downloadItem);
Exception err = null;
var mre = new ManualResetEvent(false);
client.DownloadFinished += (sender, args) =>
{
try
{
Assert.AreEqual(0, args.DownloadQueueLength);
Assert.AreEqual(DownloadResult.Success, args.Result);
Assert.AreEqual(fileId, args.DownloadItem.Id);
}
catch (Exception e)
{
err = e;
}
finally
{
mre.Set();
}
};
Assert.IsTrue(mre.WaitOne(2000));
if (err != null)
throw err;
Assert.IsTrue(Util.ByteArraysAreEqual(readStream.BackingBuffer, downloadItem.Bytes));
}
[Test]
public void DownloadMany()
{
const int fileCount = 5;
var fileIds = new FileId[fileCount];
var fileStreams = new ByteArrayStream[fileCount];
var client = new Client(KTestHost, TestPort);
client.Connect();
// Upload files
var rand = new Random();
for (var i = 0; i < fileCount; i++)
{
fileIds[i] = GenerateFileId();
fileStreams[i] = new ByteArrayStream(rand.Next(64 * 1024, 128 * 1024));
client.BeginTransaction(fileIds[i]);
client.Upload(FileType.Asset, fileStreams[i]);
client.EndTransaction();
}
Thread.Sleep(50);
// Download
var receivedCount = 0;
Exception err = null;
var mre = new ManualResetEvent(false);
client.DownloadFinished += (sender, args) =>
{
try
{
Assert.AreEqual(args.Result, DownloadResult.Success);
Assert.AreEqual(args.DownloadItem.Id, fileIds[receivedCount]);
var downloadItem = (TestDownloadItem)args.DownloadItem;
Assert.IsTrue(Util.ByteArraysAreEqual(fileStreams[receivedCount].BackingBuffer, downloadItem.Bytes));
receivedCount++;
Assert.AreEqual(fileCount - receivedCount, args.DownloadQueueLength);
}
catch (Exception e)
{
err = e;
}
finally
{
if (err != null || receivedCount == fileCount)
mre.Set();
}
};
for (var i = 0; i < fileCount; i++)
client.QueueDownload(new TestDownloadItem(fileIds[i], FileType.Asset));
Assert.AreEqual(fileCount, client.DownloadQueueLength);
Assert.IsTrue(mre.WaitOne(2000));
if (err != null)
throw err;
Assert.AreEqual(fileCount, receivedCount);
}
[Test]
public void DonwloadFileNotFound()
{
var client = new Client(KTestHost, TestPort);
client.Connect();
var fileId = FileId.From(new byte[16], new byte[16]);
var mre = new ManualResetEvent(false);
var downloadItem = new TestDownloadItem(fileId, FileType.Asset);
client.QueueDownload(downloadItem);
Exception err = null;
client.DownloadFinished += (sender, args) =>
{
try
{
Assert.AreEqual(args.Result, DownloadResult.FileNotFound);
Assert.AreEqual(args.DownloadItem.Id, fileId);
}
catch (Exception e)
{
err = e;
}
finally
{
mre.Set();
}
};
mre.WaitOne(500);
if (err != null)
throw err;
}
[Test]
public void ResetDownloadFinishedEventHandler()
{
var fileId = GenerateFileId();
var readStream = new ByteArrayStream(16 * 1024);
var client = new Client(KTestHost, TestPort);
client.Connect();
client.BeginTransaction(fileId);
client.Upload(FileType.Asset, readStream);
client.EndTransaction();
Thread.Sleep(50);
var downloadItem = new TestDownloadItem(fileId, FileType.Asset);
// Add two listeners that will assert if called
client.DownloadFinished += (sender, args) => { Debug.Assert(false); };
client.DownloadFinished += (sender, args) => { Debug.Assert(false); };
// Clear the listeners so they will not be called
client.ResetDownloadFinishedEventHandler();
client.QueueDownload(downloadItem);
var mre = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem(state =>
{
while (client.DownloadQueueLength > 0)
Thread.Sleep(0);
mre.Set();
});
Assert.IsTrue(mre.WaitOne(2000));
}
static void WriteBytesToStream(Stream stream, byte[] buffer, int offset, int count)
{
// Write to stream, and reset position for read
stream.Write(buffer, offset, count);
stream.Position -= count;
}
[Test]
public void ReadHeader_AppendsDataToBuffer()
{
using (var stream = new MemoryStream())
{
var client = new Client(KTestHost, TestPort);
client.m_stream = stream;
Exception err = null;
var resetEvent = new ManualResetEvent(false);
var testBuffer = new byte[] { (byte)'-', (byte)'0' };
WriteBytesToStream(stream, testBuffer, (int)stream.Length, 1);
// Setup checks / callback
client.OnReadHeader += (bytesRead, readBuffer) =>
{
try
{
// Validate written and read bytes match
Assert.AreEqual((int)stream.Length, bytesRead);
// Validate buffer contents are correct up to the current written bytes
for (int i = 0; i < (int)stream.Length; i++)
Assert.AreEqual(testBuffer[i], readBuffer[i]);
if ((int)stream.Length < testBuffer.Length)
WriteBytesToStream(stream, testBuffer, (int)stream.Length, 1);
else
resetEvent.Set();
}
catch (Exception e)
{
err = e;
resetEvent.Set();
}
};
// Run test
client.ReadNextDownloadResult();
Assert.IsTrue(resetEvent.WaitOne(2000));
if (err != null)
throw err;
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 012e8bc53e96d4c638db9f5b04ef84de
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,17 @@
{
"name": "UnityEditor.CacheServerTests",
"references": [
"UnityEditor.CacheServer"
],
"includePlatforms": [
"Editor"
],
"optionalUnityReferences": [
"TestAssemblies"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"defineConstraints": [
"UNITY_INCLUDE_TESTS"
]
}

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: de0adf19d0a2a46a19acaa82fcb5a988
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,24 @@
using System;
using System.IO;
namespace UnityEditor.CacheServerTests
{
public static class Utils
{
public static class Paths
{
public static string Combine(params string[] components)
{
if (components.Length < 1)
throw new ArgumentException("At least one component must be provided!");
var path1 = components[0];
for (var index = 1; index < components.Length; ++index)
path1 = Path.Combine(path1, components[index]);
return path1;
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 493e67a6e86284a8681aab6f10d05c34
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: