initial
This commit is contained in:
commit
baa0056244
352 changed files with 47928 additions and 0 deletions
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using Xunit;
|
||||
using Microsoft.PowerShell.EditorServices.Utility;
|
||||
|
||||
namespace PowerShellEditorServices.Test.Session
|
||||
{
|
||||
public class PathEscapingTests
|
||||
{
|
||||
[Trait("Category", "PathEscaping")]
|
||||
[Theory]
|
||||
[InlineData("DebugTest.ps1", "DebugTest.ps1")]
|
||||
[InlineData("../../DebugTest.ps1", "../../DebugTest.ps1")]
|
||||
[InlineData(@"C:\Users\me\Documents\DebugTest.ps1", @"C:\Users\me\Documents\DebugTest.ps1")]
|
||||
[InlineData("/home/me/Documents/weird&folder/script.ps1", "/home/me/Documents/weird&folder/script.ps1")]
|
||||
[InlineData("./path/with some/spaces", "./path/with some/spaces")]
|
||||
[InlineData(@"C:\path\with[some]brackets\file.ps1", @"C:\path\with`[some`]brackets\file.ps1")]
|
||||
[InlineData(@"C:\look\an*\here.ps1", @"C:\look\an`*\here.ps1")]
|
||||
[InlineData("/Users/me/Documents/?here.ps1", "/Users/me/Documents/`?here.ps1")]
|
||||
[InlineData("/Brackets [and s]paces/path.ps1", "/Brackets `[and s`]paces/path.ps1")]
|
||||
[InlineData("/CJK.chars/脚本/hello.ps1", "/CJK.chars/脚本/hello.ps1")]
|
||||
[InlineData("/CJK.chars/脚本/[hello].ps1", "/CJK.chars/脚本/`[hello`].ps1")]
|
||||
[InlineData(@"C:\Animals\утка\quack.ps1", @"C:\Animals\утка\quack.ps1")]
|
||||
[InlineData(@"C:\&nimals\утка\qu*ck?.ps1", @"C:\&nimals\утка\qu`*ck`?.ps1")]
|
||||
public void CorrectlyWildcardEscapesPathsNoSpaces(string unescapedPath, string escapedPath)
|
||||
{
|
||||
string extensionEscapedPath = PathUtils.WildcardEscapePath(unescapedPath);
|
||||
Assert.Equal(escapedPath, extensionEscapedPath);
|
||||
}
|
||||
|
||||
[Trait("Category", "PathEscaping")]
|
||||
[Theory]
|
||||
[InlineData("DebugTest.ps1", "DebugTest.ps1")]
|
||||
[InlineData("../../DebugTest.ps1", "../../DebugTest.ps1")]
|
||||
[InlineData(@"C:\Users\me\Documents\DebugTest.ps1", @"C:\Users\me\Documents\DebugTest.ps1")]
|
||||
[InlineData("/home/me/Documents/weird&folder/script.ps1", "/home/me/Documents/weird&folder/script.ps1")]
|
||||
[InlineData("./path/with some/spaces", "./path/with` some/spaces")]
|
||||
[InlineData(@"C:\path\with[some]brackets\file.ps1", @"C:\path\with`[some`]brackets\file.ps1")]
|
||||
[InlineData(@"C:\look\an*\here.ps1", @"C:\look\an`*\here.ps1")]
|
||||
[InlineData("/Users/me/Documents/?here.ps1", "/Users/me/Documents/`?here.ps1")]
|
||||
[InlineData("/Brackets [and s]paces/path.ps1", "/Brackets` `[and` s`]paces/path.ps1")]
|
||||
[InlineData("/CJK chars/脚本/hello.ps1", "/CJK` chars/脚本/hello.ps1")]
|
||||
[InlineData("/CJK chars/脚本/[hello].ps1", "/CJK` chars/脚本/`[hello`].ps1")]
|
||||
[InlineData(@"C:\Animal s\утка\quack.ps1", @"C:\Animal` s\утка\quack.ps1")]
|
||||
[InlineData(@"C:\&nimals\утка\qu*ck?.ps1", @"C:\&nimals\утка\qu`*ck`?.ps1")]
|
||||
public void CorrectlyWildcardEscapesPathsSpaces(string unescapedPath, string escapedPath)
|
||||
{
|
||||
string extensionEscapedPath = PathUtils.WildcardEscapePath(unescapedPath, escapeSpaces: true);
|
||||
Assert.Equal(escapedPath, extensionEscapedPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,294 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.PowerShell.EditorServices.Hosting;
|
||||
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Console;
|
||||
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Execution;
|
||||
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Host;
|
||||
using Microsoft.PowerShell.EditorServices.Services.PowerShell.Utility;
|
||||
using Microsoft.PowerShell.EditorServices.Test;
|
||||
using Xunit;
|
||||
|
||||
namespace PowerShellEditorServices.Test.Session
|
||||
{
|
||||
using System.Management.Automation;
|
||||
using System.Management.Automation.Runspaces;
|
||||
|
||||
[Trait("Category", "PsesInternalHost")]
|
||||
public class PsesInternalHostTests : IAsyncLifetime
|
||||
{
|
||||
private PsesInternalHost psesHost;
|
||||
|
||||
public async Task InitializeAsync() => psesHost = await PsesHostFactory.Create(NullLoggerFactory.Instance);
|
||||
|
||||
public async Task DisposeAsync() => await psesHost.StopAsync();
|
||||
|
||||
[Fact]
|
||||
public async Task CanExecutePSCommand()
|
||||
{
|
||||
Assert.True(psesHost.IsRunning);
|
||||
PSCommand command = new PSCommand().AddScript("$a = \"foo\"; $a");
|
||||
Task<IReadOnlyList<string>> task = psesHost.ExecutePSCommandAsync<string>(command, CancellationToken.None);
|
||||
IReadOnlyList<string> result = await task;
|
||||
Assert.Equal("foo", result[0]);
|
||||
}
|
||||
|
||||
[Fact] // https://github.com/PowerShell/vscode-powershell/issues/3677
|
||||
public async Task CanHandleThrow()
|
||||
{
|
||||
await psesHost.ExecutePSCommandAsync(
|
||||
new PSCommand().AddScript("throw"),
|
||||
CancellationToken.None,
|
||||
new PowerShellExecutionOptions { ThrowOnError = false });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanQueueParallelPSCommands()
|
||||
{
|
||||
// Concurrently initiate 4 requests in the session.
|
||||
Task taskOne = psesHost.ExecutePSCommandAsync(
|
||||
new PSCommand().AddScript("$x = 100"),
|
||||
CancellationToken.None);
|
||||
|
||||
Task taskTwo = psesHost.ExecutePSCommandAsync(
|
||||
new PSCommand().AddScript("$x += 200"),
|
||||
CancellationToken.None);
|
||||
|
||||
Task taskThree = psesHost.ExecutePSCommandAsync(
|
||||
new PSCommand().AddScript("$x = $x / 100"),
|
||||
CancellationToken.None);
|
||||
|
||||
Task<IReadOnlyList<int>> resultTask = psesHost.ExecutePSCommandAsync<int>(
|
||||
new PSCommand().AddScript("$x"),
|
||||
CancellationToken.None);
|
||||
|
||||
// Wait for all of the executes to complete.
|
||||
await Task.WhenAll(taskOne, taskTwo, taskThree, resultTask);
|
||||
|
||||
// Sanity checks
|
||||
Assert.Equal(RunspaceState.Opened, psesHost.Runspace.RunspaceStateInfo.State);
|
||||
|
||||
// 100 + 200 = 300, then divided by 100 is 3. We are ensuring that
|
||||
// the commands were executed in the sequence they were called.
|
||||
Assert.Equal(3, (await resultTask)[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanCancelExecutionWithToken()
|
||||
{
|
||||
using CancellationTokenSource cancellationSource = new(millisecondsDelay: 1000);
|
||||
await Assert.ThrowsAsync<TaskCanceledException>(() =>
|
||||
{
|
||||
return psesHost.ExecutePSCommandAsync(
|
||||
new PSCommand().AddScript("Start-Sleep 10"),
|
||||
cancellationSource.Token);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD003:Avoid awaiting foreign Tasks", Justification = "Explicitly checking task cancellation status.")]
|
||||
public async Task CanCancelExecutionWithMethod()
|
||||
{
|
||||
Task executeTask = psesHost.ExecutePSCommandAsync(
|
||||
new PSCommand().AddScript("Start-Sleep 10"),
|
||||
CancellationToken.None);
|
||||
|
||||
// Cancel the task after 1 second in another thread.
|
||||
Task.Run(() => { Thread.Sleep(1000); psesHost.CancelCurrentTask(); });
|
||||
await Assert.ThrowsAsync<TaskCanceledException>(() => executeTask);
|
||||
Assert.True(executeTask.IsCanceled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanHandleNoProfiles()
|
||||
{
|
||||
// Call LoadProfiles with profile paths that won't exist, and assert that it does not
|
||||
// throw PSInvalidOperationException (which it previously did when it tried to invoke an
|
||||
// empty command).
|
||||
ProfilePathInfo emptyProfilePaths = new("", "", "", "");
|
||||
await psesHost.ExecuteDelegateAsync(
|
||||
"LoadProfiles",
|
||||
executionOptions: null,
|
||||
(pwsh, _) =>
|
||||
{
|
||||
pwsh.LoadProfiles(emptyProfilePaths);
|
||||
Assert.Empty(pwsh.Commands.Commands);
|
||||
},
|
||||
CancellationToken.None);
|
||||
}
|
||||
|
||||
// NOTE: Tests where we call functions that use PowerShell runspaces are slightly more
|
||||
// complicated than one would expect because we explicitly need the methods to run on the
|
||||
// pipeline thread, otherwise Windows complains about the the thread's apartment state not
|
||||
// matching. Hence we use a delegate where it looks like we could just call the method.
|
||||
|
||||
[Fact]
|
||||
public async Task CanHandleBrokenPrompt()
|
||||
{
|
||||
_ = await Assert.ThrowsAsync<RuntimeException>(() =>
|
||||
{
|
||||
return psesHost.ExecutePSCommandAsync(
|
||||
new PSCommand().AddScript("function prompt { throw }; prompt"),
|
||||
CancellationToken.None);
|
||||
});
|
||||
|
||||
string prompt = await psesHost.ExecuteDelegateAsync(
|
||||
nameof(psesHost.GetPrompt),
|
||||
executionOptions: null,
|
||||
(_, _) => psesHost.GetPrompt(CancellationToken.None),
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.Equal(PsesInternalHost.DefaultPrompt, prompt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanHandleUndefinedPrompt()
|
||||
{
|
||||
Assert.Empty(await psesHost.ExecutePSCommandAsync<PSObject>(
|
||||
new PSCommand().AddScript("Remove-Item function:prompt; Get-Item function:prompt -ErrorAction Ignore"),
|
||||
CancellationToken.None));
|
||||
|
||||
string prompt = await psesHost.ExecuteDelegateAsync(
|
||||
nameof(psesHost.GetPrompt),
|
||||
executionOptions: null,
|
||||
(_, _) => psesHost.GetPrompt(CancellationToken.None),
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.Equal(PsesInternalHost.DefaultPrompt, prompt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanRunOnIdleTask()
|
||||
{
|
||||
IReadOnlyList<PSObject> task = await psesHost.ExecutePSCommandAsync<PSObject>(
|
||||
new PSCommand().AddScript("$handled = $false; Register-EngineEvent -SourceIdentifier PowerShell.OnIdle -MaxTriggerCount 1 -Action { $global:handled = $true }"),
|
||||
CancellationToken.None);
|
||||
|
||||
IReadOnlyList<bool> handled = await psesHost.ExecutePSCommandAsync<bool>(
|
||||
new PSCommand().AddScript("$handled"),
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.Collection(handled, Assert.False);
|
||||
|
||||
await psesHost.ExecuteDelegateAsync(
|
||||
nameof(psesHost.OnPowerShellIdle),
|
||||
executionOptions: null,
|
||||
(_, _) => psesHost.OnPowerShellIdle(CancellationToken.None),
|
||||
CancellationToken.None);
|
||||
|
||||
// TODO: Why is this racy?
|
||||
Thread.Sleep(2000);
|
||||
|
||||
handled = await psesHost.ExecutePSCommandAsync<bool>(
|
||||
new PSCommand().AddScript("$handled"),
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.Collection(handled, Assert.True);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanLoadPSReadLine()
|
||||
{
|
||||
Assert.True(await psesHost.ExecuteDelegateAsync(
|
||||
nameof(psesHost.TryLoadPSReadLine),
|
||||
executionOptions: null,
|
||||
(pwsh, _) => psesHost.TryLoadPSReadLine(
|
||||
pwsh,
|
||||
(EngineIntrinsics)pwsh.Runspace.SessionStateProxy.GetVariable("ExecutionContext"),
|
||||
out IReadLine readLine),
|
||||
CancellationToken.None));
|
||||
}
|
||||
|
||||
// This test asserts that we do not mess up the console encoding, which leads to native
|
||||
// commands receiving piped input failing.
|
||||
[Fact]
|
||||
public async Task ExecutesNativeCommandsCorrectly()
|
||||
{
|
||||
await psesHost.ExecutePSCommandAsync(
|
||||
new PSCommand().AddScript("\"protocol=https`nhost=myhost.com`nusername=john`npassword=doe`n`n\" | git.exe credential approve; if ($LastExitCode) { throw }"),
|
||||
CancellationToken.None);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")] // Regression test for "unset" path.
|
||||
[InlineData(@"C:\Some\Bad\Directory")] // Non-existent directory.
|
||||
[InlineData("testhost.dll")] // Existent file.
|
||||
public async Task CanHandleBadInitialWorkingDirectory(string path)
|
||||
{
|
||||
string cwd = Environment.CurrentDirectory;
|
||||
await psesHost.SetInitialWorkingDirectoryAsync(path, CancellationToken.None);
|
||||
|
||||
IReadOnlyList<string> getLocation = await psesHost.ExecutePSCommandAsync<string>(
|
||||
new PSCommand().AddCommand("Get-Location"),
|
||||
CancellationToken.None);
|
||||
Assert.Collection(getLocation, (d) => Assert.Equal(cwd, d, ignoreCase: true));
|
||||
}
|
||||
}
|
||||
|
||||
[Trait("Category", "PsesInternalHost")]
|
||||
public class PsesInternalHostWithProfileTests : IAsyncLifetime
|
||||
{
|
||||
private PsesInternalHost psesHost;
|
||||
|
||||
public async Task InitializeAsync() => psesHost = await PsesHostFactory.Create(NullLoggerFactory.Instance, loadProfiles: true);
|
||||
|
||||
public async Task DisposeAsync() => await psesHost.StopAsync();
|
||||
|
||||
[Fact]
|
||||
public async Task CanResolveAndLoadProfilesForHostId()
|
||||
{
|
||||
// Ensure that the $PROFILE variable is a string with the value of CurrentUserCurrentHost.
|
||||
IReadOnlyList<string> profileVariable = await psesHost.ExecutePSCommandAsync<string>(
|
||||
new PSCommand().AddScript("$PROFILE"),
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.Collection(profileVariable,
|
||||
(p) => Assert.Equal(PsesHostFactory.TestProfilePaths.CurrentUserCurrentHost, p));
|
||||
|
||||
// Ensure that all the profile paths are set in the correct note properties.
|
||||
IReadOnlyList<string> profileProperties = await psesHost.ExecutePSCommandAsync<string>(
|
||||
new PSCommand().AddScript("$PROFILE | Get-Member -Type NoteProperty"),
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.Collection(profileProperties,
|
||||
(p) => Assert.Equal($"string AllUsersAllHosts={PsesHostFactory.TestProfilePaths.AllUsersAllHosts}", p, ignoreCase: true),
|
||||
(p) => Assert.Equal($"string AllUsersCurrentHost={PsesHostFactory.TestProfilePaths.AllUsersCurrentHost}", p, ignoreCase: true),
|
||||
(p) => Assert.Equal($"string CurrentUserAllHosts={PsesHostFactory.TestProfilePaths.CurrentUserAllHosts}", p, ignoreCase: true),
|
||||
(p) => Assert.Equal($"string CurrentUserCurrentHost={PsesHostFactory.TestProfilePaths.CurrentUserCurrentHost}", p, ignoreCase: true));
|
||||
|
||||
// Ensure that the profile was loaded. The profile also checks that $PROFILE was defined.
|
||||
IReadOnlyList<bool> profileLoaded = await psesHost.ExecutePSCommandAsync<bool>(
|
||||
new PSCommand().AddScript("Assert-ProfileLoaded"),
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.Collection(profileLoaded, Assert.True);
|
||||
}
|
||||
|
||||
// This test specifically relies on a handler registered in the test profile, and on the
|
||||
// test host loading the profiles during startup, that way the pipeline timing is
|
||||
// consistent.
|
||||
[Fact]
|
||||
public async Task CanRunOnIdleInProfileTask()
|
||||
{
|
||||
await psesHost.ExecuteDelegateAsync(
|
||||
nameof(psesHost.OnPowerShellIdle),
|
||||
executionOptions: null,
|
||||
(_, _) => psesHost.OnPowerShellIdle(CancellationToken.None),
|
||||
CancellationToken.None);
|
||||
|
||||
// TODO: Why is this racy?
|
||||
Thread.Sleep(2000);
|
||||
|
||||
IReadOnlyList<bool> handled = await psesHost.ExecutePSCommandAsync<bool>(
|
||||
new PSCommand().AddScript("$handledInProfile"),
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.Collection(handled, Assert.True);
|
||||
}
|
||||
}
|
||||
}
|
||||
674
test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs
Normal file
674
test/PowerShellEditorServices.Test/Session/ScriptFileTests.cs
Normal file
|
|
@ -0,0 +1,674 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.PowerShell.EditorServices.Services.TextDocument;
|
||||
using Microsoft.PowerShell.EditorServices.Test.Shared;
|
||||
using Microsoft.PowerShell.EditorServices.Utility;
|
||||
using OmniSharp.Extensions.LanguageServer.Protocol;
|
||||
using Xunit;
|
||||
|
||||
namespace PowerShellEditorServices.Test.Session
|
||||
{
|
||||
public class ScriptFileChangeTests
|
||||
{
|
||||
#if CoreCLR
|
||||
private static readonly Version PowerShellVersion = new(7, 2);
|
||||
#else
|
||||
private static readonly Version PowerShellVersion = new(5, 1);
|
||||
#endif
|
||||
|
||||
[Trait("Category", "ScriptFile")]
|
||||
[Fact]
|
||||
public void CanApplySingleLineInsert()
|
||||
{
|
||||
AssertFileChange(
|
||||
"This is a test.",
|
||||
"This is a working test.",
|
||||
new FileChange
|
||||
{
|
||||
Line = 1,
|
||||
EndLine = 1,
|
||||
Offset = 10,
|
||||
EndOffset = 10,
|
||||
InsertString = " working"
|
||||
});
|
||||
}
|
||||
|
||||
[Trait("Category", "ScriptFile")]
|
||||
[Fact]
|
||||
public void CanApplySingleLineReplace()
|
||||
{
|
||||
AssertFileChange(
|
||||
"This is a potentially broken test.",
|
||||
"This is a working test.",
|
||||
new FileChange
|
||||
{
|
||||
Line = 1,
|
||||
EndLine = 1,
|
||||
Offset = 11,
|
||||
EndOffset = 29,
|
||||
InsertString = "working"
|
||||
});
|
||||
}
|
||||
|
||||
[Trait("Category", "ScriptFile")]
|
||||
[Fact]
|
||||
public void CanApplySingleLineDelete()
|
||||
{
|
||||
AssertFileChange(
|
||||
"This is a test of the emergency broadcasting system.",
|
||||
"This is a test.",
|
||||
new FileChange
|
||||
{
|
||||
Line = 1,
|
||||
EndLine = 1,
|
||||
Offset = 15,
|
||||
EndOffset = 52,
|
||||
InsertString = ""
|
||||
});
|
||||
}
|
||||
|
||||
[Trait("Category", "ScriptFile")]
|
||||
[Fact]
|
||||
public void CanApplyMultiLineInsert()
|
||||
{
|
||||
AssertFileChange(
|
||||
TestUtilities.NormalizeNewlines("first\nsecond\nfifth"),
|
||||
TestUtilities.NormalizeNewlines("first\nsecond\nthird\nfourth\nfifth"),
|
||||
new FileChange
|
||||
{
|
||||
Line = 3,
|
||||
EndLine = 3,
|
||||
Offset = 1,
|
||||
EndOffset = 1,
|
||||
InsertString = TestUtilities.NormalizeNewlines("third\nfourth\n")
|
||||
});
|
||||
}
|
||||
|
||||
[Trait("Category", "ScriptFile")]
|
||||
[Fact]
|
||||
public void CanApplyMultiLineReplace()
|
||||
{
|
||||
AssertFileChange(
|
||||
TestUtilities.NormalizeNewlines("first\nsecoXX\nXXfth"),
|
||||
TestUtilities.NormalizeNewlines("first\nsecond\nthird\nfourth\nfifth"),
|
||||
new FileChange
|
||||
{
|
||||
Line = 2,
|
||||
EndLine = 3,
|
||||
Offset = 5,
|
||||
EndOffset = 3,
|
||||
InsertString = TestUtilities.NormalizeNewlines("nd\nthird\nfourth\nfi")
|
||||
});
|
||||
}
|
||||
|
||||
[Trait("Category", "ScriptFile")]
|
||||
[Fact]
|
||||
public void CanApplyMultiLineReplaceWithRemovedLines()
|
||||
{
|
||||
AssertFileChange(
|
||||
TestUtilities.NormalizeNewlines("first\nsecoXX\nREMOVE\nTHESE\nLINES\nXXfth"),
|
||||
TestUtilities.NormalizeNewlines("first\nsecond\nthird\nfourth\nfifth"),
|
||||
new FileChange
|
||||
{
|
||||
Line = 2,
|
||||
EndLine = 6,
|
||||
Offset = 5,
|
||||
EndOffset = 3,
|
||||
InsertString = TestUtilities.NormalizeNewlines("nd\nthird\nfourth\nfi")
|
||||
});
|
||||
}
|
||||
|
||||
[Trait("Category", "ScriptFile")]
|
||||
[Fact]
|
||||
public void CanApplyMultiLineDelete()
|
||||
{
|
||||
AssertFileChange(
|
||||
TestUtilities.NormalizeNewlines("first\nsecond\nREMOVE\nTHESE\nLINES\nthird"),
|
||||
TestUtilities.NormalizeNewlines("first\nsecond\nthird"),
|
||||
new FileChange
|
||||
{
|
||||
Line = 3,
|
||||
EndLine = 6,
|
||||
Offset = 1,
|
||||
EndOffset = 1,
|
||||
InsertString = ""
|
||||
});
|
||||
}
|
||||
|
||||
[Trait("Category", "ScriptFile")]
|
||||
[Fact]
|
||||
public void CanApplyEditsToEndOfFile()
|
||||
{
|
||||
AssertFileChange(
|
||||
TestUtilities.NormalizeNewlines("line1\nline2\nline3\n\n"),
|
||||
TestUtilities.NormalizeNewlines("line1\nline2\nline3\n\n\n\n"),
|
||||
new FileChange
|
||||
{
|
||||
Line = 5,
|
||||
EndLine = 5,
|
||||
Offset = 1,
|
||||
EndOffset = 1,
|
||||
InsertString = Environment.NewLine + Environment.NewLine
|
||||
});
|
||||
}
|
||||
|
||||
[Trait("Category", "ScriptFile")]
|
||||
[Fact]
|
||||
public void CanAppendToEndOfFile()
|
||||
{
|
||||
AssertFileChange(
|
||||
TestUtilities.NormalizeNewlines("line1\nline2\nline3"),
|
||||
TestUtilities.NormalizeNewlines("line1\nline2\nline3\nline4\nline5"),
|
||||
new FileChange
|
||||
{
|
||||
Line = 4,
|
||||
EndLine = 5,
|
||||
Offset = 1,
|
||||
EndOffset = 1,
|
||||
InsertString = $"line4{Environment.NewLine}line5"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
[Trait("Category", "ScriptFile")]
|
||||
[Fact]
|
||||
public void ThrowsExceptionWithEditOutsideOfRange()
|
||||
{
|
||||
Assert.Throws<ArgumentOutOfRangeException>(
|
||||
() =>
|
||||
{
|
||||
AssertFileChange(
|
||||
TestUtilities.NormalizeNewlines("first\nsecond\nREMOVE\nTHESE\nLINES\nthird"),
|
||||
TestUtilities.NormalizeNewlines("first\nsecond\nthird"),
|
||||
new FileChange
|
||||
{
|
||||
Line = 3,
|
||||
EndLine = 8,
|
||||
Offset = 1,
|
||||
EndOffset = 1,
|
||||
InsertString = ""
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Trait("Category", "ScriptFile")]
|
||||
[Fact]
|
||||
public void CanDeleteFromEndOfFile()
|
||||
{
|
||||
AssertFileChange(
|
||||
TestUtilities.NormalizeNewlines("line1\nline2\nline3\nline4"),
|
||||
TestUtilities.NormalizeNewlines("line1\nline2"),
|
||||
new FileChange
|
||||
{
|
||||
Line = 3,
|
||||
EndLine = 5,
|
||||
Offset = 1,
|
||||
EndOffset = 1,
|
||||
InsertString = ""
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
[Trait("Category", "ScriptFile")]
|
||||
[Fact]
|
||||
public void UpdatesParseErrorDiagnosticMarkers()
|
||||
{
|
||||
ScriptFile myScript = CreateScriptFile(TestUtilities.NormalizeNewlines("{\n{"));
|
||||
|
||||
// Verify parse errors were detected on file open
|
||||
Assert.Collection(myScript.DiagnosticMarkers.OrderBy(dm => dm.ScriptRegion.StartLineNumber),
|
||||
(actual) =>
|
||||
{
|
||||
Assert.Equal(1, actual.ScriptRegion.StartLineNumber);
|
||||
Assert.Equal("Missing closing '}' in statement block or type definition.", actual.Message);
|
||||
Assert.Equal("PowerShell", actual.Source);
|
||||
},
|
||||
(actual) =>
|
||||
{
|
||||
Assert.Equal(2, actual.ScriptRegion.StartLineNumber);
|
||||
Assert.Equal("Missing closing '}' in statement block or type definition.", actual.Message);
|
||||
Assert.Equal("PowerShell", actual.Source);
|
||||
});
|
||||
|
||||
// Remove second {
|
||||
myScript.ApplyChange(
|
||||
new FileChange
|
||||
{
|
||||
Line = 2,
|
||||
EndLine = 2,
|
||||
Offset = 1,
|
||||
EndOffset = 2,
|
||||
InsertString = ""
|
||||
});
|
||||
|
||||
// Verify parse errors were updated on file change
|
||||
Assert.Collection(myScript.DiagnosticMarkers,
|
||||
(actual) =>
|
||||
{
|
||||
Assert.Equal(1, actual.ScriptRegion.StartLineNumber);
|
||||
Assert.Equal("Missing closing '}' in statement block or type definition.", actual.Message);
|
||||
Assert.Equal("PowerShell", actual.Source);
|
||||
});
|
||||
}
|
||||
|
||||
internal static ScriptFile CreateScriptFile(string initialString)
|
||||
{
|
||||
using StringReader stringReader = new(initialString);
|
||||
// Create an in-memory file from the StringReader
|
||||
ScriptFile fileToChange =
|
||||
new(
|
||||
// Use any absolute path. Even if it doesn't exist.
|
||||
DocumentUri.FromFileSystemPath(Path.Combine(Path.GetTempPath(), "TestFile.ps1")),
|
||||
stringReader,
|
||||
PowerShellVersion);
|
||||
|
||||
return fileToChange;
|
||||
}
|
||||
|
||||
private static void AssertFileChange(
|
||||
string initialString,
|
||||
string expectedString,
|
||||
FileChange fileChange)
|
||||
{
|
||||
// Create an in-memory file from the StringReader
|
||||
ScriptFile fileToChange = CreateScriptFile(initialString);
|
||||
|
||||
// Apply the FileChange and assert the resulting contents
|
||||
fileToChange.ApplyChange(fileChange);
|
||||
Assert.Equal(expectedString, fileToChange.Contents);
|
||||
}
|
||||
}
|
||||
|
||||
public class ScriptFileGetLinesTests
|
||||
{
|
||||
private static readonly string TestString_NoTrailingNewline = TestUtilities.NormalizeNewlines(
|
||||
"Line One\nLine Two\nLine Three\nLine Four\nLine Five");
|
||||
|
||||
private static readonly string TestString_TrailingNewline = TestUtilities.NormalizeNewlines(
|
||||
TestString_NoTrailingNewline + "\n");
|
||||
|
||||
private static readonly string[] s_newLines = new string[] { Environment.NewLine };
|
||||
|
||||
private static readonly string[] s_testStringLines_noTrailingNewline = TestString_NoTrailingNewline.Split(s_newLines, StringSplitOptions.None);
|
||||
|
||||
private static readonly string[] s_testStringLines_trailingNewline = TestString_TrailingNewline.Split(s_newLines, StringSplitOptions.None);
|
||||
|
||||
private readonly ScriptFile _scriptFile_trailingNewline;
|
||||
|
||||
private readonly ScriptFile _scriptFile_noTrailingNewline;
|
||||
|
||||
public ScriptFileGetLinesTests()
|
||||
{
|
||||
_scriptFile_noTrailingNewline = ScriptFileChangeTests.CreateScriptFile(
|
||||
TestString_NoTrailingNewline);
|
||||
|
||||
_scriptFile_trailingNewline = ScriptFileChangeTests.CreateScriptFile(
|
||||
TestString_TrailingNewline);
|
||||
}
|
||||
|
||||
[Trait("Category", "ScriptFile")]
|
||||
[Fact]
|
||||
public void CanGetWholeLine()
|
||||
{
|
||||
string[] lines =
|
||||
_scriptFile_noTrailingNewline.GetLinesInRange(
|
||||
new BufferRange(5, 1, 5, 10));
|
||||
|
||||
Assert.Single(lines);
|
||||
Assert.Equal("Line Five", lines[0]);
|
||||
}
|
||||
|
||||
[Trait("Category", "ScriptFile")]
|
||||
[Fact]
|
||||
public void CanGetMultipleWholeLines()
|
||||
{
|
||||
string[] lines =
|
||||
_scriptFile_noTrailingNewline.GetLinesInRange(
|
||||
new BufferRange(2, 1, 4, 10));
|
||||
|
||||
Assert.Equal(s_testStringLines_noTrailingNewline.Skip(1).Take(3), lines);
|
||||
}
|
||||
|
||||
[Trait("Category", "ScriptFile")]
|
||||
[Fact]
|
||||
public void CanGetSubstringInSingleLine()
|
||||
{
|
||||
string[] lines =
|
||||
_scriptFile_noTrailingNewline.GetLinesInRange(
|
||||
new BufferRange(4, 3, 4, 8));
|
||||
|
||||
Assert.Single(lines);
|
||||
Assert.Equal("ne Fo", lines[0]);
|
||||
}
|
||||
|
||||
[Trait("Category", "ScriptFile")]
|
||||
[Fact]
|
||||
public void CanGetEmptySubstringRange()
|
||||
{
|
||||
string[] lines =
|
||||
_scriptFile_noTrailingNewline.GetLinesInRange(
|
||||
new BufferRange(4, 3, 4, 3));
|
||||
|
||||
Assert.Single(lines);
|
||||
Assert.Equal("", lines[0]);
|
||||
}
|
||||
|
||||
[Trait("Category", "ScriptFile")]
|
||||
[Fact]
|
||||
public void CanGetSubstringInMultipleLines()
|
||||
{
|
||||
string[] expectedLines = new string[]
|
||||
{
|
||||
"Two",
|
||||
"Line Three",
|
||||
"Line Fou"
|
||||
};
|
||||
|
||||
string[] lines =
|
||||
_scriptFile_noTrailingNewline.GetLinesInRange(
|
||||
new BufferRange(2, 6, 4, 9));
|
||||
|
||||
Assert.Equal(expectedLines, lines);
|
||||
}
|
||||
|
||||
[Trait("Category", "ScriptFile")]
|
||||
[Fact]
|
||||
public void CanGetRangeAtLineBoundaries()
|
||||
{
|
||||
string[] expectedLines = new string[]
|
||||
{
|
||||
"",
|
||||
"Line Three",
|
||||
""
|
||||
};
|
||||
|
||||
string[] lines =
|
||||
_scriptFile_noTrailingNewline.GetLinesInRange(
|
||||
new BufferRange(2, 9, 4, 1));
|
||||
|
||||
Assert.Equal(expectedLines, lines);
|
||||
}
|
||||
|
||||
[Trait("Category", "ScriptFile")]
|
||||
[Fact]
|
||||
public void CanSplitLinesNoTrailingNewline() => Assert.Equal(s_testStringLines_noTrailingNewline, _scriptFile_noTrailingNewline.FileLines);
|
||||
|
||||
[Trait("Category", "ScriptFile")]
|
||||
[Fact]
|
||||
public void CanSplitLinesTrailingNewline() => Assert.Equal(s_testStringLines_trailingNewline, _scriptFile_trailingNewline.FileLines);
|
||||
|
||||
[Trait("Category", "ScriptFile")]
|
||||
[Fact]
|
||||
public void CanGetSameLinesWithUnixLineBreaks()
|
||||
{
|
||||
ScriptFile unixFile = ScriptFileChangeTests.CreateScriptFile(TestString_NoTrailingNewline.Replace("\r\n", "\n"));
|
||||
Assert.Equal(_scriptFile_noTrailingNewline.FileLines, unixFile.FileLines);
|
||||
}
|
||||
|
||||
[Trait("Category", "ScriptFile")]
|
||||
[Fact]
|
||||
public void CanGetLineForEmptyString()
|
||||
{
|
||||
ScriptFile emptyFile = ScriptFileChangeTests.CreateScriptFile(string.Empty);
|
||||
Assert.Single(emptyFile.FileLines);
|
||||
Assert.Equal(string.Empty, emptyFile.FileLines[0]);
|
||||
}
|
||||
|
||||
[Trait("Category", "ScriptFile")]
|
||||
[Fact]
|
||||
public void CanGetLineForSpace()
|
||||
{
|
||||
ScriptFile spaceFile = ScriptFileChangeTests.CreateScriptFile(" ");
|
||||
Assert.Single(spaceFile.FileLines);
|
||||
Assert.Equal(" ", spaceFile.FileLines[0]);
|
||||
}
|
||||
}
|
||||
|
||||
public class ScriptFilePositionTests
|
||||
{
|
||||
private readonly ScriptFile scriptFile;
|
||||
|
||||
public ScriptFilePositionTests()
|
||||
{
|
||||
scriptFile =
|
||||
ScriptFileChangeTests.CreateScriptFile(@"
|
||||
First line
|
||||
Second line is longer
|
||||
Third line
|
||||
");
|
||||
}
|
||||
|
||||
[Trait("Category", "ScriptFile")]
|
||||
[Fact]
|
||||
public void CanOffsetByLine()
|
||||
{
|
||||
AssertNewPosition(
|
||||
1, 1,
|
||||
2, 0,
|
||||
3, 1);
|
||||
|
||||
AssertNewPosition(
|
||||
3, 1,
|
||||
-2, 0,
|
||||
1, 1);
|
||||
}
|
||||
|
||||
[Trait("Category", "ScriptFile")]
|
||||
[Fact]
|
||||
public void CanOffsetByColumn()
|
||||
{
|
||||
AssertNewPosition(
|
||||
2, 1,
|
||||
0, 2,
|
||||
2, 3);
|
||||
|
||||
AssertNewPosition(
|
||||
2, 5,
|
||||
0, -3,
|
||||
2, 2);
|
||||
}
|
||||
|
||||
[Trait("Category", "ScriptFile")]
|
||||
[Fact]
|
||||
public void ThrowsWhenPositionOutOfRange()
|
||||
{
|
||||
// Less than line range
|
||||
Assert.Throws<ArgumentOutOfRangeException>(
|
||||
() =>
|
||||
{
|
||||
scriptFile.CalculatePosition(
|
||||
new BufferPosition(1, 1),
|
||||
-10, 0);
|
||||
});
|
||||
|
||||
// Greater than line range
|
||||
Assert.Throws<ArgumentOutOfRangeException>(
|
||||
() =>
|
||||
{
|
||||
scriptFile.CalculatePosition(
|
||||
new BufferPosition(1, 1),
|
||||
10, 0);
|
||||
});
|
||||
|
||||
// Less than column range
|
||||
Assert.Throws<ArgumentOutOfRangeException>(
|
||||
() =>
|
||||
{
|
||||
scriptFile.CalculatePosition(
|
||||
new BufferPosition(1, 1),
|
||||
0, -10);
|
||||
});
|
||||
|
||||
// Greater than column range
|
||||
Assert.Throws<ArgumentOutOfRangeException>(
|
||||
() =>
|
||||
{
|
||||
scriptFile.CalculatePosition(
|
||||
new BufferPosition(1, 1),
|
||||
0, 10);
|
||||
});
|
||||
}
|
||||
|
||||
[Trait("Category", "ScriptFile")]
|
||||
[Fact]
|
||||
public void CanFindBeginningOfLine()
|
||||
{
|
||||
AssertNewPosition(
|
||||
4, 12,
|
||||
pos => pos.GetLineStart(),
|
||||
4, 5);
|
||||
}
|
||||
|
||||
[Trait("Category", "ScriptFile")]
|
||||
[Fact]
|
||||
public void CanFindEndOfLine()
|
||||
{
|
||||
AssertNewPosition(
|
||||
4, 12,
|
||||
pos => pos.GetLineEnd(),
|
||||
4, 15);
|
||||
}
|
||||
|
||||
[Trait("Category", "ScriptFile")]
|
||||
[Fact]
|
||||
public void CanComposePositionOperations()
|
||||
{
|
||||
AssertNewPosition(
|
||||
4, 12,
|
||||
pos => pos.AddOffset(-1, 1).GetLineStart(),
|
||||
3, 3);
|
||||
}
|
||||
|
||||
private void AssertNewPosition(
|
||||
int originalLine, int originalColumn,
|
||||
int lineOffset, int columnOffset,
|
||||
int expectedLine, int expectedColumn)
|
||||
{
|
||||
AssertNewPosition(
|
||||
originalLine, originalColumn,
|
||||
pos => pos.AddOffset(lineOffset, columnOffset),
|
||||
expectedLine, expectedColumn);
|
||||
}
|
||||
|
||||
private void AssertNewPosition(
|
||||
int originalLine, int originalColumn,
|
||||
Func<FilePosition, FilePosition> positionOperation,
|
||||
int expectedLine, int expectedColumn)
|
||||
{
|
||||
FilePosition newPosition =
|
||||
positionOperation(
|
||||
new FilePosition(
|
||||
scriptFile,
|
||||
originalLine,
|
||||
originalColumn));
|
||||
|
||||
Assert.Equal(expectedLine, newPosition.Line);
|
||||
Assert.Equal(expectedColumn, newPosition.Column);
|
||||
}
|
||||
}
|
||||
|
||||
public class ScriptFileConstructorTests
|
||||
{
|
||||
private static readonly Version PowerShellVersion = new("5.0");
|
||||
|
||||
[Trait("Category", "ScriptFile")]
|
||||
[Fact]
|
||||
public void PropertiesInitializedCorrectlyForFile()
|
||||
{
|
||||
// Use any absolute path. Even if it doesn't exist.
|
||||
string path = Path.Combine(Path.GetTempPath(), "TestFile.ps1");
|
||||
ScriptFile scriptFile = ScriptFileChangeTests.CreateScriptFile("");
|
||||
|
||||
Assert.Equal(path, scriptFile.FilePath, ignoreCase: !VersionUtils.IsLinux);
|
||||
Assert.True(scriptFile.IsAnalysisEnabled);
|
||||
Assert.False(scriptFile.IsInMemory);
|
||||
Assert.Empty(scriptFile.DiagnosticMarkers);
|
||||
Assert.Single(scriptFile.ScriptTokens);
|
||||
Assert.Single(scriptFile.FileLines);
|
||||
}
|
||||
|
||||
[Trait("Category", "ScriptFile")]
|
||||
[Fact]
|
||||
public void PropertiesInitializedCorrectlyForUntitled()
|
||||
{
|
||||
const string path = "untitled:untitled-1";
|
||||
|
||||
// 3 lines and 10 tokens in this script.
|
||||
const string script = @"function foo() {
|
||||
'foo'
|
||||
}";
|
||||
|
||||
using StringReader stringReader = new(script);
|
||||
// Create an in-memory file from the StringReader
|
||||
ScriptFile scriptFile = new(DocumentUri.From(path), stringReader, PowerShellVersion);
|
||||
|
||||
Assert.Equal(path, scriptFile.FilePath);
|
||||
Assert.Equal(path, scriptFile.DocumentUri);
|
||||
Assert.True(scriptFile.IsAnalysisEnabled);
|
||||
Assert.True(scriptFile.IsInMemory);
|
||||
Assert.Empty(scriptFile.DiagnosticMarkers);
|
||||
Assert.Equal(10, scriptFile.ScriptTokens.Length);
|
||||
Assert.Equal(3, scriptFile.FileLines.Count);
|
||||
}
|
||||
|
||||
[Trait("Category", "ScriptFile")]
|
||||
[Fact]
|
||||
public void DocumentUriReturnsCorrectStringForAbsolutePath()
|
||||
{
|
||||
string path;
|
||||
ScriptFile scriptFile;
|
||||
StringReader emptyStringReader = new("");
|
||||
|
||||
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
|
||||
{
|
||||
path = @"C:\Users\AmosBurton\projects\Rocinate\ProtoMolecule.ps1";
|
||||
scriptFile = new ScriptFile(DocumentUri.FromFileSystemPath(path), emptyStringReader, PowerShellVersion);
|
||||
Assert.Equal("file:///c:/Users/AmosBurton/projects/Rocinate/ProtoMolecule.ps1", scriptFile.DocumentUri);
|
||||
|
||||
path = @"c:\Users\BobbieDraper\projects\Rocinate\foo's_~#-[@] +,;=%.ps1";
|
||||
scriptFile = new ScriptFile(DocumentUri.FromFileSystemPath(path), emptyStringReader, PowerShellVersion);
|
||||
Assert.Equal("file:///c:/Users/BobbieDraper/projects/Rocinate/foo%27s_~%23-%5B%40%5D%20%2B%2C%3B%3D%25.ps1", scriptFile.DocumentUri);
|
||||
|
||||
// Test UNC path
|
||||
path = @"\\ClarissaMao\projects\Rocinate\foo's_~#-[@] +,;=%.ps1";
|
||||
scriptFile = new ScriptFile(DocumentUri.FromFileSystemPath(path), emptyStringReader, PowerShellVersion);
|
||||
// UNC authorities are lowercased. This is what VS Code does as well.
|
||||
Assert.Equal("file://clarissamao/projects/Rocinate/foo%27s_~%23-%5B%40%5D%20%2B%2C%3B%3D%25.ps1", scriptFile.DocumentUri);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Test the following only on Linux and macOS.
|
||||
path = "/home/AlexKamal/projects/Rocinate/ProtoMolecule.ps1";
|
||||
scriptFile = new ScriptFile(DocumentUri.FromFileSystemPath(path), emptyStringReader, PowerShellVersion);
|
||||
Assert.Equal("file:///home/AlexKamal/projects/Rocinate/ProtoMolecule.ps1", scriptFile.DocumentUri);
|
||||
|
||||
path = "/home/BobbieDraper/projects/Rocinate/foo's_~#-[@] +,;=%.ps1";
|
||||
scriptFile = new ScriptFile(DocumentUri.FromFileSystemPath(path), emptyStringReader, PowerShellVersion);
|
||||
Assert.Equal("file:///home/BobbieDraper/projects/Rocinate/foo%27s_~%23-%5B%40%5D%20%2B%2C%3B%3D%25.ps1", scriptFile.DocumentUri);
|
||||
|
||||
path = "/home/NaomiNagata/projects/Rocinate/Proto:Mole:cule.ps1";
|
||||
scriptFile = new ScriptFile(DocumentUri.FromFileSystemPath(path), emptyStringReader, PowerShellVersion);
|
||||
Assert.Equal("file:///home/NaomiNagata/projects/Rocinate/Proto%3AMole%3Acule.ps1", scriptFile.DocumentUri);
|
||||
|
||||
path = @"/home/JamesHolden/projects/Rocinate/Proto:Mole\cule.ps1";
|
||||
scriptFile = new ScriptFile(DocumentUri.FromFileSystemPath(path), emptyStringReader, PowerShellVersion);
|
||||
Assert.Equal("file:///home/JamesHolden/projects/Rocinate/Proto%3AMole%5Ccule.ps1", scriptFile.DocumentUri);
|
||||
}
|
||||
}
|
||||
|
||||
[Trait("Category", "ScriptFile")]
|
||||
[Theory]
|
||||
[InlineData(@"C:\Users\me\Documents\test.ps1", false)]
|
||||
[InlineData("/Users/me/Documents/test.ps1", false)]
|
||||
[InlineData("vscode-notebook-cell:/Users/me/Documents/test.ps1#0001", true)]
|
||||
[InlineData("https://microsoft.com", true)]
|
||||
[InlineData("Untitled:Untitled-1", true)]
|
||||
[InlineData(@"'a log statement' > 'c:\Users\me\Documents\test.txt'
|
||||
", false)]
|
||||
public void IsUntitledFileIsCorrect(string path, bool expected) => Assert.Equal(expected, ScriptFile.IsUntitledPath(path));
|
||||
}
|
||||
}
|
||||
202
test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs
Normal file
202
test/PowerShellEditorServices.Test/Session/WorkspaceTests.cs
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.PowerShell.EditorServices.Services;
|
||||
using Microsoft.PowerShell.EditorServices.Services.TextDocument;
|
||||
using Xunit;
|
||||
using Microsoft.PowerShell.EditorServices.Utility;
|
||||
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
|
||||
using OmniSharp.Extensions.LanguageServer.Protocol;
|
||||
|
||||
namespace PowerShellEditorServices.Test.Session
|
||||
{
|
||||
[Trait("Category", "Workspace")]
|
||||
public class WorkspaceTests
|
||||
{
|
||||
private static readonly Lazy<string> s_lazyDriveLetter = new(() => Path.GetFullPath("\\").Substring(0, 1));
|
||||
|
||||
public static string CurrentDriveLetter => RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
||||
? s_lazyDriveLetter.Value
|
||||
: string.Empty;
|
||||
|
||||
internal static ScriptFile CreateScriptFile(string path) => ScriptFile.Create(path, "", VersionUtils.PSVersion);
|
||||
|
||||
// Remember that LSP does weird stuff to the drive letter, so we have to convert it to a URI
|
||||
// and back to ensure that drive letter gets lower cased and everything matches up.
|
||||
private static string s_workspacePath =>
|
||||
DocumentUri.FromFileSystemPath(Path.GetFullPath("Fixtures/Workspace")).GetFileSystemPath();
|
||||
|
||||
[Fact]
|
||||
public void CanResolveWorkspaceRelativePath()
|
||||
{
|
||||
string workspacePath = "c:/Test/Workspace/";
|
||||
ScriptFile testPathInside = CreateScriptFile("c:/Test/Workspace/SubFolder/FilePath.ps1");
|
||||
ScriptFile testPathOutside = CreateScriptFile("c:/Test/PeerPath/FilePath.ps1");
|
||||
ScriptFile testPathAnotherDrive = CreateScriptFile("z:/TryAndFindMe/FilePath.ps1");
|
||||
|
||||
WorkspaceService workspace = new(NullLoggerFactory.Instance);
|
||||
|
||||
// Test with zero workspace folders
|
||||
Assert.Equal(
|
||||
testPathOutside.DocumentUri.ToUri().AbsolutePath,
|
||||
workspace.GetRelativePath(testPathOutside));
|
||||
|
||||
string expectedInsidePath = "SubFolder/FilePath.ps1";
|
||||
string expectedOutsidePath = "../PeerPath/FilePath.ps1";
|
||||
|
||||
// Test with a single workspace folder
|
||||
workspace.WorkspaceFolders.Add(new WorkspaceFolder
|
||||
{
|
||||
Uri = DocumentUri.FromFileSystemPath(workspacePath)
|
||||
});
|
||||
|
||||
Assert.Equal(expectedInsidePath, workspace.GetRelativePath(testPathInside));
|
||||
Assert.Equal(expectedOutsidePath, workspace.GetRelativePath(testPathOutside));
|
||||
Assert.Equal(
|
||||
testPathAnotherDrive.DocumentUri.ToUri().AbsolutePath,
|
||||
workspace.GetRelativePath(testPathAnotherDrive));
|
||||
|
||||
// Test with two workspace folders
|
||||
string anotherWorkspacePath = "c:/Test/AnotherWorkspace/";
|
||||
ScriptFile anotherTestPathInside = CreateScriptFile("c:/Test/AnotherWorkspace/DifferentFolder/FilePath.ps1");
|
||||
string anotherExpectedInsidePath = "DifferentFolder/FilePath.ps1";
|
||||
|
||||
workspace.WorkspaceFolders.Add(new WorkspaceFolder
|
||||
{
|
||||
Uri = DocumentUri.FromFileSystemPath(anotherWorkspacePath)
|
||||
});
|
||||
|
||||
Assert.Equal(expectedInsidePath, workspace.GetRelativePath(testPathInside));
|
||||
Assert.Equal(anotherExpectedInsidePath, workspace.GetRelativePath(anotherTestPathInside));
|
||||
}
|
||||
|
||||
internal static WorkspaceService FixturesWorkspace()
|
||||
{
|
||||
return new WorkspaceService(NullLoggerFactory.Instance)
|
||||
{
|
||||
WorkspaceFolders =
|
||||
{
|
||||
new WorkspaceFolder { Uri = DocumentUri.FromFileSystemPath(s_workspacePath) }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HasDefaultForWorkspacePaths()
|
||||
{
|
||||
WorkspaceService workspace = FixturesWorkspace();
|
||||
string workspacePath = Assert.Single(workspace.WorkspacePaths);
|
||||
Assert.Equal(s_workspacePath, workspacePath);
|
||||
// We shouldn't assume an initial working directory since none was given.
|
||||
Assert.Null(workspace.InitialWorkingDirectory);
|
||||
}
|
||||
|
||||
// These are the default values for the EnumeratePSFiles() method
|
||||
// in Microsoft.PowerShell.EditorServices.Workspace class
|
||||
private static readonly string[] s_defaultExcludeGlobs = Array.Empty<string>();
|
||||
private static readonly string[] s_defaultIncludeGlobs = new[] { "**/*" };
|
||||
private const int s_defaultMaxDepth = 64;
|
||||
private const bool s_defaultIgnoreReparsePoints = false;
|
||||
|
||||
internal static List<string> ExecuteEnumeratePSFiles(
|
||||
WorkspaceService workspace,
|
||||
string[] excludeGlobs,
|
||||
string[] includeGlobs,
|
||||
int maxDepth,
|
||||
bool ignoreReparsePoints)
|
||||
{
|
||||
List<string> fileList = new(workspace.EnumeratePSFiles(
|
||||
excludeGlobs: excludeGlobs,
|
||||
includeGlobs: includeGlobs,
|
||||
maxDepth: maxDepth,
|
||||
ignoreReparsePoints: ignoreReparsePoints
|
||||
));
|
||||
|
||||
// Assume order is not important from EnumeratePSFiles and sort the array so we can use
|
||||
// deterministic asserts
|
||||
fileList.Sort();
|
||||
return fileList;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanRecurseDirectoryTree()
|
||||
{
|
||||
WorkspaceService workspace = FixturesWorkspace();
|
||||
List<string> actual = ExecuteEnumeratePSFiles(
|
||||
workspace: workspace,
|
||||
excludeGlobs: s_defaultExcludeGlobs,
|
||||
includeGlobs: s_defaultIncludeGlobs,
|
||||
maxDepth: s_defaultMaxDepth,
|
||||
ignoreReparsePoints: s_defaultIgnoreReparsePoints
|
||||
);
|
||||
|
||||
List<string> expected = new()
|
||||
{
|
||||
Path.Combine(s_workspacePath, "nested", "donotfind.ps1"),
|
||||
Path.Combine(s_workspacePath, "nested", "nestedmodule.psd1"),
|
||||
Path.Combine(s_workspacePath, "nested", "nestedmodule.psm1"),
|
||||
Path.Combine(s_workspacePath, "rootfile.ps1")
|
||||
};
|
||||
|
||||
// .NET Core doesn't appear to use the same three letter pattern matching rule although the docs
|
||||
// suggest it should be find the '.ps1xml' files because we search for the pattern '*.ps1'
|
||||
// ref https://docs.microsoft.com/en-us/dotnet/api/system.io.directory.getfiles?view=netcore-2.1#System_IO_Directory_GetFiles_System_String_System_String_System_IO_EnumerationOptions_
|
||||
if (RuntimeInformation.FrameworkDescription.StartsWith(".NET Framework"))
|
||||
{
|
||||
expected.Insert(3, Path.Combine(s_workspacePath, "other", "other.ps1xml"));
|
||||
}
|
||||
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanRecurseDirectoryTreeWithLimit()
|
||||
{
|
||||
WorkspaceService workspace = FixturesWorkspace();
|
||||
List<string> actual = ExecuteEnumeratePSFiles(
|
||||
workspace: workspace,
|
||||
excludeGlobs: s_defaultExcludeGlobs,
|
||||
includeGlobs: s_defaultIncludeGlobs,
|
||||
maxDepth: 1,
|
||||
ignoreReparsePoints: s_defaultIgnoreReparsePoints
|
||||
);
|
||||
Assert.Equal(new[] { Path.Combine(s_workspacePath, "rootfile.ps1") }, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanRecurseDirectoryTreeWithGlobs()
|
||||
{
|
||||
WorkspaceService workspace = FixturesWorkspace();
|
||||
List<string> actual = ExecuteEnumeratePSFiles(
|
||||
workspace: workspace,
|
||||
excludeGlobs: new[] { "**/donotfind*" }, // Exclude any files starting with donotfind
|
||||
includeGlobs: new[] { "**/*.ps1", "**/*.psd1" }, // Only include PS1 and PSD1 files
|
||||
maxDepth: s_defaultMaxDepth,
|
||||
ignoreReparsePoints: s_defaultIgnoreReparsePoints
|
||||
);
|
||||
|
||||
Assert.Equal(new[] {
|
||||
Path.Combine(s_workspacePath, "nested", "nestedmodule.psd1"),
|
||||
Path.Combine(s_workspacePath, "rootfile.ps1")
|
||||
}, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanOpenAndCloseFile()
|
||||
{
|
||||
WorkspaceService workspace = FixturesWorkspace();
|
||||
string filePath = Path.GetFullPath(Path.Combine(s_workspacePath, "rootfile.ps1"));
|
||||
|
||||
ScriptFile file = workspace.GetFile(filePath);
|
||||
Assert.Equal(workspace.GetOpenedFiles(), new[] { file });
|
||||
|
||||
workspace.CloseFile(file);
|
||||
Assert.Equal(workspace.GetOpenedFiles(), Array.Empty<ScriptFile>());
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue