Skip to content

Commit 2fadb83

Browse files
committed
Convert LanguageServer tests project to C#
1 parent 37b7fd1 commit 2fadb83

File tree

8 files changed

+541
-20
lines changed

8 files changed

+541
-20
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ All notable changes to FScript are documented in this file.
55
## [Unreleased]
66

77
- Removed F# sources from `src/FScript.LanguageServer*` by moving LSP semantic modules into `FScript.CSharpInterop` and keeping `FScript.LanguageServer` as C# host.
8+
- Replaced `FScript.LanguageServer.Tests` project with a C# test project and C# LSP test harness to remove F# compile cost from LanguageServer test builds.
89
- Enabled F# preview parallel compilation globally, disabled deterministic builds, and removed global RuntimeIdentifiers to reduce CI build latency.
910
- Added `FScript.CSharpInterop` as a stable bridge for parse/infer/runtime-extern/stdlib-source services and wired LanguageServer through it.
1011
- Added `FScript.LanguageServer` host executable as the migration entrypoint for C#-owned LSP startup.

FScript.sln

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FScript.Runtime", "src\FScr
1717
EndProject
1818
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FScript.Runtime.Tests", "tests\FScript.Runtime.Tests\FScript.Runtime.Tests.fsproj", "{1E2C7B34-04B8-42C9-880D-CC47DEC156A7}"
1919
EndProject
20-
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FScript.LanguageServer.Tests", "tests\FScript.LanguageServer.Tests\FScript.LanguageServer.Tests.fsproj", "{B734E1E1-59C2-47E0-8D19-A9C5C95938F1}"
20+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FScript.LanguageServer.Tests", "tests\FScript.LanguageServer.Tests\FScript.LanguageServer.Tests.csproj", "{B734E1E1-59C2-47E0-8D19-A9C5C95938F1}"
2121
EndProject
2222
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FScript.CSharpInterop", "src\FScript.CSharpInterop\FScript.CSharpInterop.fsproj", "{8A28B784-F90B-469C-91BE-F96F63ACEA32}"
2323
EndProject
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
using System.Text.Json.Nodes;
2+
using NUnit.Framework;
3+
4+
namespace FScript.LanguageServer.Tests;
5+
6+
[TestFixture]
7+
public sealed class CSharpServerCoreTests
8+
{
9+
[Test]
10+
public void CSharp_server_initialize_returns_capabilities()
11+
{
12+
var client = LspClient.StartCSharp();
13+
try
14+
{
15+
LspTestFixture.Initialize(client);
16+
17+
var hoverReq = new JsonObject
18+
{
19+
["textDocument"] = new JsonObject { ["uri"] = "file:///tmp/test.fss" },
20+
["position"] = new JsonObject { ["line"] = 0, ["character"] = 0 }
21+
};
22+
23+
LspClient.SendRequest(client, 42, "textDocument/hover", hoverReq);
24+
var hoverResp = LspClient.ReadUntil(client, 10_000, msg => msg["id"] is JsonValue idv && idv.TryGetValue<int>(out var id) && id == 42);
25+
Assert.That(hoverResp["result"], Is.Null);
26+
}
27+
finally
28+
{
29+
try { LspTestFixture.Shutdown(client); } catch { }
30+
LspClient.Stop(client);
31+
}
32+
}
33+
34+
[Test]
35+
public void CSharp_server_returns_stdlib_source()
36+
{
37+
var client = LspClient.StartCSharp();
38+
try
39+
{
40+
LspTestFixture.Initialize(client);
41+
var requestParams = new JsonObject { ["uri"] = "fscript-stdlib:///Option.fss" };
42+
43+
LspClient.SendRequest(client, 43, "fscript/stdlibSource", requestParams);
44+
var resp = LspClient.ReadUntil(client, 10_000, msg => msg["id"] is JsonValue idv && idv.TryGetValue<int>(out var id) && id == 43);
45+
46+
var result = resp["result"] as JsonObject ?? throw new Exception("Expected result object");
47+
Assert.That(result["ok"]?.GetValue<bool>(), Is.True);
48+
var data = result["data"] as JsonObject ?? throw new Exception("Expected data object");
49+
var text = data["text"]?.GetValue<string>() ?? string.Empty;
50+
Assert.That(text.Contains("let", StringComparison.Ordinal), Is.True);
51+
}
52+
finally
53+
{
54+
try { LspTestFixture.Shutdown(client); } catch { }
55+
LspClient.Stop(client);
56+
}
57+
}
58+
59+
[Test]
60+
public void CSharp_server_returns_method_not_found_for_unknown_request()
61+
{
62+
var client = LspClient.StartCSharp();
63+
try
64+
{
65+
LspTestFixture.Initialize(client);
66+
LspClient.SendRequest(client, 44, "fscript/unknown", null);
67+
var resp = LspClient.ReadUntil(client, 10_000, msg => msg["id"] is JsonValue idv && idv.TryGetValue<int>(out var id) && id == 44);
68+
var err = resp["error"] as JsonObject ?? throw new Exception("Expected error object");
69+
Assert.That(err["code"]?.GetValue<int>(), Is.EqualTo(-32601));
70+
}
71+
finally
72+
{
73+
try { LspTestFixture.Shutdown(client); } catch { }
74+
LspClient.Stop(client);
75+
}
76+
}
77+
78+
[Test]
79+
public void CSharp_server_didOpen_publishes_parse_diagnostics()
80+
{
81+
var client = LspClient.StartCSharp();
82+
try
83+
{
84+
LspTestFixture.Initialize(client);
85+
86+
var uri = "file:///tmp/csharp-diagnostics-test.fss";
87+
var didOpenParams = new JsonObject
88+
{
89+
["textDocument"] = new JsonObject
90+
{
91+
["uri"] = uri,
92+
["languageId"] = "fscript",
93+
["version"] = 1,
94+
["text"] = "let x ="
95+
}
96+
};
97+
LspClient.SendNotification(client, "textDocument/didOpen", didOpenParams);
98+
99+
var diagMsg = LspClient.ReadUntil(client, 10_000, msg =>
100+
{
101+
if (msg["method"]?.GetValue<string>() != "textDocument/publishDiagnostics")
102+
{
103+
return false;
104+
}
105+
106+
var p = msg["params"] as JsonObject;
107+
var u = p?["uri"]?.GetValue<string>();
108+
var diagnostics = p?["diagnostics"] as JsonArray;
109+
return u == uri && diagnostics is { Count: > 0 };
110+
});
111+
112+
var hasParseCode = false;
113+
var paramsObj = diagMsg["params"] as JsonObject;
114+
var diagnosticsArray = paramsObj?["diagnostics"] as JsonArray;
115+
if (diagnosticsArray is not null)
116+
{
117+
foreach (var diag in diagnosticsArray)
118+
{
119+
if (diag is JsonObject d && d["code"]?.GetValue<string>() == "parse")
120+
{
121+
hasParseCode = true;
122+
break;
123+
}
124+
}
125+
}
126+
127+
Assert.That(hasParseCode, Is.True);
128+
}
129+
finally
130+
{
131+
try { LspTestFixture.Shutdown(client); } catch { }
132+
LspClient.Stop(client);
133+
}
134+
}
135+
136+
[Test]
137+
public void CSharp_server_viewAst_returns_program_json()
138+
{
139+
var client = LspClient.StartCSharp();
140+
try
141+
{
142+
LspTestFixture.Initialize(client);
143+
144+
var uri = "file:///tmp/csharp-view-ast-test.fss";
145+
var source = "let value = 42\nvalue\n";
146+
var didOpenParams = new JsonObject
147+
{
148+
["textDocument"] = new JsonObject
149+
{
150+
["uri"] = uri,
151+
["languageId"] = "fscript",
152+
["version"] = 1,
153+
["text"] = source
154+
}
155+
};
156+
LspClient.SendNotification(client, "textDocument/didOpen", didOpenParams);
157+
_ = LspClient.ReadUntil(client, 10_000, msg => msg["method"]?.GetValue<string>() == "textDocument/publishDiagnostics");
158+
159+
var requestParams = new JsonObject { ["textDocument"] = new JsonObject { ["uri"] = uri } };
160+
LspClient.SendRequest(client, 45, "fscript/viewAst", requestParams);
161+
var response = LspClient.ReadUntil(client, 10_000, msg => msg["id"] is JsonValue idv && idv.TryGetValue<int>(out var id) && id == 45);
162+
163+
var kindValue = ((response["result"] as JsonObject)?["data"] as JsonObject)?["kind"]?.GetValue<string>();
164+
Assert.That(kindValue, Is.EqualTo("program"));
165+
}
166+
finally
167+
{
168+
try { LspTestFixture.Shutdown(client); } catch { }
169+
LspClient.Stop(client);
170+
}
171+
}
172+
173+
[Test]
174+
public void CSharp_server_viewInferredAst_returns_typed_program_json()
175+
{
176+
var client = LspClient.StartCSharp();
177+
try
178+
{
179+
LspTestFixture.Initialize(client);
180+
181+
var uri = "file:///tmp/csharp-view-inferred-test.fss";
182+
var source = "let inc x = x + 1\ninc 1\n";
183+
var didOpenParams = new JsonObject
184+
{
185+
["textDocument"] = new JsonObject
186+
{
187+
["uri"] = uri,
188+
["languageId"] = "fscript",
189+
["version"] = 1,
190+
["text"] = source
191+
}
192+
};
193+
LspClient.SendNotification(client, "textDocument/didOpen", didOpenParams);
194+
_ = LspClient.ReadUntil(client, 10_000, msg => msg["method"]?.GetValue<string>() == "textDocument/publishDiagnostics");
195+
196+
var requestParams = new JsonObject { ["textDocument"] = new JsonObject { ["uri"] = uri } };
197+
LspClient.SendRequest(client, 46, "fscript/viewInferredAst", requestParams);
198+
var response = LspClient.ReadUntil(client, 10_000, msg => msg["id"] is JsonValue idv && idv.TryGetValue<int>(out var id) && id == 46);
199+
200+
var kindValue = ((response["result"] as JsonObject)?["data"] as JsonObject)?["kind"]?.GetValue<string>();
201+
Assert.That(kindValue, Is.EqualTo("typedProgram"));
202+
}
203+
finally
204+
{
205+
try { LspTestFixture.Shutdown(client); } catch { }
206+
LspClient.Stop(client);
207+
}
208+
}
209+
}

tests/FScript.LanguageServer.Tests/FScript.LanguageServer.Tests.fsproj renamed to tests/FScript.LanguageServer.Tests/FScript.LanguageServer.Tests.csproj

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,13 @@
11
<Project Sdk="Microsoft.NET.Sdk">
2-
32
<PropertyGroup>
43
<TargetFramework>net10.0</TargetFramework>
54
<IsPackable>false</IsPackable>
6-
<GenerateProgramFile>false</GenerateProgramFile>
75
<Nullable>enable</Nullable>
6+
<ImplicitUsings>enable</ImplicitUsings>
87
</PropertyGroup>
98

10-
<ItemGroup>
11-
<Compile Include="LspTestWire.fs" />
12-
<Compile Include="LspTestClient.fs" />
13-
<Compile Include="LspTestFixture.fs" />
14-
<Compile Include="LspCoreTests.fs" />
15-
<Compile Include="LspCompletionAndSignatureTests.fs" />
16-
<Compile Include="LspHoverAndInlayTests.fs" />
17-
<Compile Include="LspNavigationTests.fs" />
18-
<Compile Include="LspSymbolsAndActionsTests.fs" />
19-
<Compile Include="LspCustomRequestsTests.fs" />
20-
<Compile Include="InteropServicesTests.fs" />
21-
<Compile Include="CSharpServerCoreTests.fs" />
22-
<Compile Include="Program.fs" />
23-
</ItemGroup>
24-
259
<ItemGroup>
2610
<PackageReference Include="coverlet.collector" Version="6.0.4" />
27-
<PackageReference Include="FsUnit" Version="7.1.1" />
2811
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
2912
<PackageReference Include="NUnit" Version="4.4.0" />
3013
<PackageReference Include="NUnit.Analyzers" Version="4.11.2" />
@@ -35,5 +18,4 @@
3518
<ProjectReference Include="..\..\src\FScript.Language\FScript.Language.fsproj" />
3619
<ProjectReference Include="..\..\src\FScript.CSharpInterop\FScript.CSharpInterop.fsproj" />
3720
</ItemGroup>
38-
3921
</Project>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using FScript.CSharpInterop;
2+
using NUnit.Framework;
3+
4+
namespace FScript.LanguageServer.Tests;
5+
6+
[TestFixture]
7+
public sealed class InteropServicesTests
8+
{
9+
[Test]
10+
public void Interop_loads_stdlib_virtual_source()
11+
{
12+
var source = InteropServices.tryLoadStdlibSourceText("fscript-stdlib:///Option.fss");
13+
Assert.That(source is not null, Is.True);
14+
}
15+
16+
[Test]
17+
public void Interop_parses_and_infers_a_simple_script()
18+
{
19+
const string script = "let add x y = x + y";
20+
const string sourcePath = "/tmp/interop-test.fss";
21+
var externs = InteropServices.runtimeExternsForSourcePath(sourcePath);
22+
var program = InteropServices.parseProgramFromSourceWithIncludes(sourcePath, script);
23+
var inferred = InteropServices.inferProgramWithExternsAndLocalVariableTypes(externs, program);
24+
var typed = inferred.Item1;
25+
Assert.That(typed, Is.Not.Null);
26+
}
27+
}

0 commit comments

Comments
 (0)