Skip to content
This repository was archived by the owner on Jan 18, 2022. It is now read-only.

Commit b969e8b

Browse files
author
Jamie Brynes
authored
Add SpatialDeploymentManager to test utils package (#1088)
1 parent 294204b commit b969e8b

10 files changed

+357
-0
lines changed

workers/unity/Packages/io.improbable.gdk.testutils/Editor.meta

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "Improbable.Gdk.TestUtils.Editor",
3+
"references": [
4+
"Improbable.Gdk.Tools"
5+
],
6+
"optionalUnityReferences": [],
7+
"includePlatforms": [
8+
"Editor"
9+
],
10+
"excludePlatforms": [],
11+
"allowUnsafeCode": false,
12+
"overrideReferences": false,
13+
"precompiledReferences": [],
14+
"autoReferenced": true,
15+
"defineConstraints": [],
16+
"versionDefines": []
17+
}

workers/unity/Packages/io.improbable.gdk.testutils/Editor/Improbable.Gdk.TestUtils.Editor.asmdef.meta

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.IO;
5+
using System.Runtime.InteropServices;
6+
using System.Text;
7+
using System.Threading.Tasks;
8+
using Improbable.Gdk.Tools;
9+
using Debug = UnityEngine.Debug;
10+
11+
namespace Improbable.Gdk.TestUtils.Editor
12+
{
13+
/// <summary>
14+
/// Manages a single `spatial local launch` instance.
15+
/// </summary>
16+
public class SpatialDeploymentManager : IDisposable
17+
{
18+
private Process spatialProcess;
19+
20+
public static Task<SpatialDeploymentManager> Start(string deploymentJsonPath, string snapshotPath)
21+
{
22+
if (!File.Exists(Path.Combine(Common.SpatialProjectRootDir, deploymentJsonPath)))
23+
{
24+
return Task.FromException<SpatialDeploymentManager>(
25+
new ArgumentException($"Could not find deployment config at {deploymentJsonPath}"));
26+
}
27+
28+
if (!File.Exists(Path.Combine(Common.SpatialProjectRootDir, snapshotPath)))
29+
{
30+
return Task.FromException<SpatialDeploymentManager>(
31+
new ArgumentException($"Could not find snapshot at {snapshotPath}"));
32+
}
33+
34+
var tcs = new TaskCompletionSource<SpatialDeploymentManager>();
35+
36+
var processInfo =
37+
new ProcessStartInfo("spatial", $"local launch {deploymentJsonPath} --snapshot {snapshotPath} --enable_pre_run_check=false")
38+
{
39+
CreateNoWindow = true,
40+
RedirectStandardOutput = true,
41+
RedirectStandardError = true,
42+
UseShellExecute = false,
43+
WorkingDirectory = Common.SpatialProjectRootDir
44+
};
45+
46+
var process = new Process
47+
{
48+
StartInfo = processInfo,
49+
EnableRaisingEvents = true
50+
};
51+
52+
var output = new StringBuilder();
53+
54+
process.Exited += (sender, args) =>
55+
{
56+
tcs.TrySetException(
57+
new Exception($"Spatial process failed to start. Raw output:\n{output.ToString()}"));
58+
};
59+
60+
process.OutputDataReceived += (sender, args) =>
61+
{
62+
if (string.IsNullOrEmpty(args.Data))
63+
{
64+
return;
65+
}
66+
67+
if (args.Data.Contains("localhost:21000/inspector-v2"))
68+
{
69+
tcs.TrySetResult(new SpatialDeploymentManager
70+
{
71+
spatialProcess = process
72+
});
73+
}
74+
75+
output.AppendLine(args.Data);
76+
};
77+
78+
process.Start();
79+
process.BeginOutputReadLine();
80+
81+
return tcs.Task;
82+
}
83+
84+
public void Dispose()
85+
{
86+
var success = spatialProcess.KillTree();
87+
88+
if (success != 0)
89+
{
90+
Debug.LogWarning("Failed to stop spatial process tree.");
91+
}
92+
}
93+
}
94+
95+
// Copied from: https://raw.githubusercontent.com/dotnet/cli/master/test/Microsoft.DotNet.Tools.Tests.Utilities/Extensions/ProcessExtensions.cs
96+
// Licensed under MIT.
97+
public static class ProcessExtensions
98+
{
99+
private static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
100+
private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(value: 30);
101+
102+
public static int KillTree(this Process process)
103+
{
104+
return process.KillTree(DefaultTimeout);
105+
}
106+
107+
public static int KillTree(this Process process, TimeSpan timeout)
108+
{
109+
if (IsWindows)
110+
{
111+
return RunProcessAndWaitForExit(
112+
"taskkill",
113+
$"/T /F /PID {process.Id}",
114+
timeout,
115+
out _,
116+
out _);
117+
}
118+
119+
var children = new HashSet<int>();
120+
if (GetAllChildIdsUnix(process.Id, children, timeout) != 0)
121+
{
122+
return 1;
123+
}
124+
125+
foreach (var childId in children)
126+
{
127+
if (KillProcessUnix(childId, timeout) != 0)
128+
{
129+
return 1;
130+
}
131+
}
132+
133+
return KillProcessUnix(process.Id, timeout);
134+
}
135+
136+
private static int GetAllChildIdsUnix(int parentId, ISet<int> children, TimeSpan timeout)
137+
{
138+
var exitCode = RunProcessAndWaitForExit(
139+
"pgrep",
140+
$"-P {parentId}",
141+
timeout,
142+
out var stdout,
143+
out _);
144+
145+
if (exitCode == 0 && !string.IsNullOrEmpty(stdout))
146+
{
147+
using (var reader = new StringReader(stdout))
148+
{
149+
while (true)
150+
{
151+
var text = reader.ReadLine();
152+
153+
if (string.IsNullOrEmpty(text))
154+
{
155+
break;
156+
}
157+
158+
if (int.TryParse(text, out var id))
159+
{
160+
children.Add(id);
161+
// Recursively get the children
162+
GetAllChildIdsUnix(id, children, timeout);
163+
}
164+
}
165+
}
166+
}
167+
168+
return exitCode;
169+
}
170+
171+
private static int KillProcessUnix(int processId, TimeSpan timeout)
172+
{
173+
return RunProcessAndWaitForExit(
174+
"kill",
175+
$"-TERM {processId}",
176+
timeout,
177+
out _,
178+
out _);
179+
}
180+
181+
private static int RunProcessAndWaitForExit(string fileName, string arguments, TimeSpan timeout,
182+
out string stdout, out string stderr)
183+
{
184+
var startInfo = new ProcessStartInfo
185+
{
186+
FileName = fileName,
187+
Arguments = arguments,
188+
RedirectStandardOutput = true,
189+
RedirectStandardError = true,
190+
UseShellExecute = false
191+
};
192+
193+
var process = Process.Start(startInfo);
194+
195+
stdout = null;
196+
stderr = null;
197+
198+
if (process.WaitForExit((int) timeout.TotalMilliseconds))
199+
{
200+
stdout = process.StandardOutput.ReadToEnd();
201+
stderr = process.StandardError.ReadToEnd();
202+
}
203+
else
204+
{
205+
process.Kill();
206+
}
207+
208+
return process.ExitCode;
209+
}
210+
}
211+
}

workers/unity/Packages/io.improbable.gdk.testutils/Editor/SpatialDeploymentManager.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

workers/unity/Packages/io.improbable.gdk.testutils/Editor/Tests.meta

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "Improbable.Gdk.TestUtils.Editor.Tests",
3+
"references": [
4+
"Improbable.Gdk.TestUtils.Editor"
5+
],
6+
"optionalUnityReferences": [
7+
"TestAssemblies"
8+
],
9+
"includePlatforms": [
10+
"Editor"
11+
],
12+
"excludePlatforms": [],
13+
"allowUnsafeCode": false,
14+
"overrideReferences": false,
15+
"precompiledReferences": [],
16+
"autoReferenced": false,
17+
"defineConstraints": [],
18+
"versionDefines": []
19+
}

workers/unity/Packages/io.improbable.gdk.testutils/Editor/Tests/Improbable.Gdk.TestUtils.Editor.Tests.asmdef.meta

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using System;
2+
using System.Linq;
3+
using System.Net;
4+
using NUnit.Framework;
5+
6+
namespace Improbable.Gdk.TestUtils.Editor.Tests
7+
{
8+
[TestFixture]
9+
public class SpatialDeploymentManagerTests
10+
{
11+
[Test]
12+
public void Start_throws_an_exception_if_deployment_config_does_not_exist()
13+
{
14+
var task = SpatialDeploymentManager.Start("does_not_exist.json", "snapshots/default.snapshot");
15+
Assert.IsTrue(task.IsFaulted);
16+
Assert.IsInstanceOf<ArgumentException>(task.Exception.InnerExceptions.First());
17+
}
18+
19+
[Test]
20+
public void Start_throws_an_exception_if_snapshot_does_not_exist()
21+
{
22+
var task = SpatialDeploymentManager.Start("default_launch.json", "does_not_exist.snapshot");
23+
Assert.IsTrue(task.IsFaulted);
24+
Assert.IsInstanceOf<ArgumentException>(task.Exception.InnerExceptions.First());
25+
}
26+
27+
[Test]
28+
public void Start_correctly_starts_a_spatial_deployment()
29+
{
30+
using (SpatialDeploymentManager.Start("default_launch.json", "snapshots/default.snapshot").Result)
31+
{
32+
var request = WebRequest.Create("http://localhost:21000/inspector");
33+
Assert.DoesNotThrow(() => request.GetResponse());
34+
}
35+
}
36+
37+
[Test]
38+
public void Deployment_is_dead_after_Dispose()
39+
{
40+
using (SpatialDeploymentManager.Start("default_launch.json", "snapshots/default.snapshot").Result)
41+
{
42+
}
43+
44+
var request = WebRequest.Create("http://localhost:21000/inspector");
45+
Assert.Throws<WebException>(() => request.GetResponse());
46+
}
47+
48+
[Test]
49+
public void Start_throws_if_deployment_fails_to_start()
50+
{
51+
using (SpatialDeploymentManager.Start("default_launch.json", "snapshots/default.snapshot").Result)
52+
{
53+
var task = SpatialDeploymentManager.Start("default_launch.json", "snapshots/default.snapshot");
54+
Assert.Throws<AggregateException>(() => task.Wait());
55+
}
56+
}
57+
}
58+
}

workers/unity/Packages/io.improbable.gdk.testutils/Editor/Tests/SpatialDeploymentManagerTests.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)