-
Notifications
You must be signed in to change notification settings - Fork 238
Add simple integration test, with an LSP client PowerShell module #944
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
47 commits
Select commit
Hold shift + click to select a range
c708a1b
Add first parts of ps pses client
e36001a
Make message reading work
9e67dea
Start pipe module
rjmholt b66a9ac
Add simple startup test
rjmholt 6711016
Add shutdown test
rjmholt be4587e
Add pester installation to build step
rjmholt 2158e3b
Response to Codacy bot's feedback
rjmholt 4952bad
Add classes and types to psm1
rjmholt 7ba21c2
Fix build logic, add return types
rjmholt 558abc0
Rename pester test file to be more specific
rjmholt 5c35561
Kill process properly
rjmholt 81631a1
Fix process kill
rjmholt adcb527
Add diagnostic line;
rjmholt b16429d
Attempt to exit
rjmholt 0435cb6
Add some documentation comments
rjmholt 7e9a76d
Ensure pipe is closed during disposal
rjmholt 9b66b61
Fix pipe disposal
rjmholt d949558
Change cancellation method
rjmholt b061b65
Try invoking pester in proc
rjmholt 6b13e22
Add doc comments
rjmholt 29ccd29
Use better exceptions
rjmholt 2896933
Comply with some of Codacy's draconian views
rjmholt 7b3ee70
Publish Pester results in CI
rjmholt 5a2b3d0
Remove .vscode folder
rjmholt 3bb6df4
Merge branch 'master' into integration-tools
rjmholt 228b5eb
Fix typo
rjmholt 516cbb0
Fix NRE in test
rjmholt beeef02
Improve client module building
rjmholt dae7511
Pass through dotnet location
rjmholt f8654d6
Ensure new enough Pester is installed
rjmholt 14b8884
Add -Force flag
rjmholt 4069a40
Update tools/PsesPsClient/PsesPsClient.psd1
TylerLeonhardt a6e2e99
Rework module, address @TylerLeonhardt's feedback
rjmholt e8529dd
Update PowerShellEditorServices.build.ps1
TylerLeonhardt b0d704c
Update tools/PsesPsClient/Client.cs
rjmholt 1b90683
Add server output to integration test
c0bcefb
Try stderr recording
d9b3a0b
Try procinfo
c48b586
Don't use stream when it doesn't exist
e2e3c9b
Try again with error file
a7e599c
Use log parser to find errors
47cdb90
Add test exception for pwsh pipe close error
d5a67c2
Improve error message
dc2506e
Test without skipping crash log exception
135d47d
Try ending server stream first
37ddedf
Improve testing
9f5666f
Add comments about test ordering
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Start pipe module
- Loading branch information
commit 9e67deab1cdc732cab41740b463c23400b2cb222
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,220 @@ | ||
using System; | ||
using System.IO.Pipes; | ||
using Newtonsoft.Json; | ||
using Newtonsoft.Json.Serialization; | ||
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol.Serializers; | ||
using System.Text; | ||
using System.IO; | ||
using Newtonsoft.Json.Linq; | ||
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol; | ||
using System.Collections.Generic; | ||
|
||
namespace PsesPsClient | ||
{ | ||
public class LspPipe : IDisposable | ||
{ | ||
private static readonly IReadOnlyDictionary<string, Type> s_messageBodyTypes = new Dictionary<string, Type>() | ||
{ | ||
|
||
}; | ||
|
||
private readonly NamedPipeClientStream _namedPipeClient; | ||
|
||
private readonly StringBuilder _headerBuffer; | ||
|
||
private readonly JsonSerializerSettings _jsonSettings; | ||
|
||
private readonly JsonSerializer _jsonSerializer; | ||
|
||
private readonly JsonRpcMessageSerializer _jsonRpcSerializer; | ||
|
||
private readonly Encoding _pipeEncoding; | ||
|
||
private int _msgId; | ||
|
||
private StreamReader _reader; | ||
|
||
private StreamWriter _writer; | ||
|
||
private char[] _readerBuffer; | ||
|
||
public LspPipe(NamedPipeClientStream namedPipeClient) | ||
{ | ||
_namedPipeClient = namedPipeClient; | ||
|
||
_readerBuffer = new char[1024]; | ||
|
||
_headerBuffer = new StringBuilder(128); | ||
|
||
_jsonSettings = new JsonSerializerSettings() | ||
{ | ||
ContractResolver = new CamelCasePropertyNamesContractResolver() | ||
}; | ||
|
||
_jsonSerializer = JsonSerializer.Create(_jsonSettings); | ||
|
||
_jsonRpcSerializer = new JsonRpcMessageSerializer(); | ||
|
||
_pipeEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); | ||
} | ||
|
||
public bool HasContent | ||
{ | ||
get | ||
{ | ||
return _reader.Peek() > 0; | ||
} | ||
} | ||
|
||
public void Connect() | ||
{ | ||
_namedPipeClient.Connect(timeout: 1000); | ||
_reader = new StreamReader(_namedPipeClient, _pipeEncoding); | ||
_writer = new StreamWriter(_namedPipeClient, _pipeEncoding) | ||
{ | ||
AutoFlush = true | ||
}; | ||
} | ||
|
||
public void Write( | ||
string method, | ||
object parameters) | ||
{ | ||
_msgId++; | ||
|
||
Message msg = Message.Request( | ||
_msgId.ToString(), | ||
method, | ||
JToken.FromObject(parameters, _jsonSerializer)); | ||
|
||
JObject msgJson = _jsonRpcSerializer.SerializeMessage(msg); | ||
string msgString = JsonConvert.SerializeObject(msgJson, _jsonSettings); | ||
byte[] msgBytes = _pipeEncoding.GetBytes(msgString); | ||
|
||
string header = "Content-Length: " + msgBytes.Length + "\r\n\r\n"; | ||
|
||
_writer.Write(header); | ||
_writer.Write(msgBytes); | ||
} | ||
|
||
public LspMessage Read() | ||
{ | ||
int contentLength = GetContentLength(); | ||
string msgString = ReadString(contentLength); | ||
JObject msgJson = JObject.Parse(msgString); | ||
|
||
if (!msgJson.TryGetValue("method", out JToken methodJsonToken) | ||
|| !(methodJsonToken is JValue methodJsonValue) | ||
|| !(methodJsonValue.Value is string method)) | ||
{ | ||
throw new Exception($"No method given on message: '{msgString}'"); | ||
} | ||
|
||
if (!s_messageBodyTypes.TryGetValue(method, out Type bodyType)) | ||
{ | ||
throw new Exception($"Unknown message method: '{method}'"); | ||
} | ||
|
||
int id = (int)msgJson["id"]; | ||
|
||
object body = msgJson["params"].ToObject(bodyType); | ||
|
||
return new LspMessage(id, method, body); | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
_namedPipeClient.Dispose(); | ||
} | ||
|
||
private string ReadString(int bytesToRead) | ||
{ | ||
if (bytesToRead > _readerBuffer.Length) | ||
{ | ||
Array.Resize(ref _readerBuffer, _readerBuffer.Length * 2); | ||
} | ||
|
||
_reader.Read(_readerBuffer, 0, bytesToRead); | ||
|
||
return new string(_readerBuffer); | ||
} | ||
|
||
private int GetContentLength() | ||
{ | ||
_headerBuffer.Clear(); | ||
int endHeaderState = 0; | ||
int currChar; | ||
while ((currChar = _reader.Read()) >= 0) | ||
{ | ||
char c = (char)currChar; | ||
_headerBuffer.Append(c); | ||
switch (c) | ||
{ | ||
case '\r': | ||
if (endHeaderState == 2) | ||
{ | ||
endHeaderState = 3; | ||
continue; | ||
} | ||
endHeaderState = 1; | ||
continue; | ||
|
||
case '\n': | ||
if (endHeaderState == 1) | ||
{ | ||
endHeaderState = 2; | ||
continue; | ||
} | ||
|
||
// This is the end, my only friend, the end | ||
if (endHeaderState == 3) | ||
{ | ||
return ParseContentLength(_headerBuffer.ToString()); | ||
} | ||
|
||
continue; | ||
} | ||
} | ||
|
||
throw new Exception("Buffer emptied before end of headers"); | ||
} | ||
|
||
private static int ParseContentLength(string headers) | ||
{ | ||
const string clHeaderPrefix = "Content-Length: "; | ||
|
||
int clIdx = headers.IndexOf(clHeaderPrefix); | ||
if (clIdx < 0) | ||
{ | ||
throw new Exception("No Content-Length header found"); | ||
} | ||
|
||
int endIdx = headers.IndexOf("\r\n", clIdx); | ||
if (endIdx < 0) | ||
{ | ||
throw new Exception("Header CRLF terminator not found"); | ||
} | ||
|
||
int numStartIdx = clIdx + clHeaderPrefix.Length; | ||
int numLength = endIdx - numStartIdx; | ||
|
||
return int.Parse(headers.Substring(numStartIdx, numLength)); | ||
} | ||
} | ||
|
||
public class LspMessage | ||
{ | ||
public LspMessage(int id, string method, object body) | ||
{ | ||
Id = id; | ||
Method = method; | ||
Body = body; | ||
} | ||
|
||
public int Id { get; } | ||
|
||
public string Method { get; } | ||
|
||
public object Body; | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.