92 lines
3.6 KiB
C#
92 lines
3.6 KiB
C#
using Microsoft.Extensions.Caching.Memory;
|
|
using NSubstitute;
|
|
using ScadaLink.CentralUI.ScriptAnalysis;
|
|
using ScadaLink.TemplateEngine;
|
|
|
|
namespace ScadaLink.CentralUI.Tests.ScriptAnalysis;
|
|
|
|
/// <summary>
|
|
/// Regression tests for CentralUI-013. <c>ResolveCalledShape</c> resolved shared
|
|
/// script shapes with <c>_sharedScripts.GetShapesAsync().GetAwaiter().GetResult()</c>
|
|
/// — a sync-over-async block on a request thread that risks thread-pool
|
|
/// starvation and deadlock. <c>Hover</c> and <c>SignatureHelp</c> were synchronous
|
|
/// purely to accommodate that block. The fix makes both methods async and
|
|
/// <c>await</c>s the catalog.
|
|
/// </summary>
|
|
public class ScriptAnalysisAsyncResolveTests
|
|
{
|
|
private readonly ISharedScriptCatalog _catalog = Substitute.For<ISharedScriptCatalog>();
|
|
private readonly IMemoryCache _cache = new MemoryCache(new MemoryCacheOptions { SizeLimit = 100 });
|
|
private readonly IServiceProvider _services = Substitute.For<IServiceProvider>();
|
|
private readonly ScriptAnalysisService _svc;
|
|
|
|
public ScriptAnalysisAsyncResolveTests()
|
|
{
|
|
_catalog.GetShapesAsync().Returns(Array.Empty<ScriptShape>());
|
|
_svc = new ScriptAnalysisService(_catalog, _cache, _services);
|
|
}
|
|
|
|
private static ScriptShape Shape(string name, params ParameterShape[] ps) => new(name, ps, null);
|
|
private static ParameterShape Param(string name, string type) => new(name, type, true);
|
|
|
|
[Fact]
|
|
public async Task HoverAsync_OnSharedCallName_AwaitsCatalog_AndResolvesShape()
|
|
{
|
|
// The catalog only completes after yielding — a truly asynchronous
|
|
// source. The fixed Hover awaits it instead of blocking.
|
|
_catalog.GetShapesAsync().Returns(async _ =>
|
|
{
|
|
await Task.Yield();
|
|
return (IReadOnlyList<ScriptShape>)new[]
|
|
{
|
|
Shape("Aggregate", Param("window", "Integer")),
|
|
};
|
|
});
|
|
|
|
var resp = await _svc.Hover(new HoverRequest(
|
|
CodeText: "var r = Scripts.CallShared(\"Aggregate\");",
|
|
Line: 1,
|
|
Column: 30));
|
|
|
|
Assert.NotNull(resp.Markdown);
|
|
Assert.Contains("shared script", resp.Markdown);
|
|
Assert.Contains("Aggregate", resp.Markdown);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SignatureHelpAsync_InsideSharedCall_AwaitsCatalog_AndResolvesParameters()
|
|
{
|
|
_catalog.GetShapesAsync().Returns(async _ =>
|
|
{
|
|
await Task.Yield();
|
|
return (IReadOnlyList<ScriptShape>)new[]
|
|
{
|
|
Shape("Aggregate", Param("window", "Integer"), Param("mode", "String")),
|
|
};
|
|
});
|
|
|
|
var resp = await _svc.SignatureHelp(new SignatureHelpRequest(
|
|
CodeText: "var r = Scripts.CallShared(\"Aggregate\", ",
|
|
Line: 1,
|
|
Column: 41));
|
|
|
|
Assert.NotNull(resp.Label);
|
|
Assert.Contains("Aggregate", resp.Label!);
|
|
Assert.Equal(2, resp.Parameters!.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public void HoverAndSignatureHelp_AreAsync_NotSyncOverAsync()
|
|
{
|
|
// Structural guard: the methods must return Task so the catalog can be
|
|
// awaited rather than blocked with .GetAwaiter().GetResult().
|
|
var hover = typeof(ScriptAnalysisService).GetMethod(nameof(ScriptAnalysisService.Hover));
|
|
var sigHelp = typeof(ScriptAnalysisService).GetMethod(nameof(ScriptAnalysisService.SignatureHelp));
|
|
|
|
Assert.NotNull(hover);
|
|
Assert.NotNull(sigHelp);
|
|
Assert.Equal(typeof(Task<HoverResponse>), hover!.ReturnType);
|
|
Assert.Equal(typeof(Task<SignatureHelpResponse>), sigHelp!.ReturnType);
|
|
}
|
|
}
|