Skip to content

Commit 6f5df13

Browse files
Merge pull request #1522 from PowerShell/andschwa/tests
Run new PSReadLine test on Windows
2 parents 2b70a25 + f911ef1 commit 6f5df13

File tree

14 files changed

+113
-62
lines changed

14 files changed

+113
-62
lines changed

PowerShellEditorServices.Common.props

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,5 @@
1111
<RepositoryType>git</RepositoryType>
1212
<RepositoryUrl>https://github.com/PowerShell/PowerShellEditorServices</RepositoryUrl>
1313
<DebugType>portable</DebugType>
14-
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
1514
</PropertyGroup>
1615
</Project>

PowerShellEditorServices.build.ps1

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -259,20 +259,20 @@ task TestServer TestServerWinPS,TestServerPS7,TestServerPS72
259259

260260
task TestServerWinPS -If (-not $script:IsNix) {
261261
Set-Location .\test\PowerShellEditorServices.Test\
262-
exec { & $script:dotnetExe test -p:ExtraDefineConstants=TEST --logger trx -f $script:NetRuntime.Desktop (DotNetTestFilter) }
262+
exec { & $script:dotnetExe test --logger trx -f $script:NetRuntime.Desktop (DotNetTestFilter) }
263263
}
264264

265265
task TestServerPS7 -If (-not $script:IsRosetta) {
266266
Set-Location .\test\PowerShellEditorServices.Test\
267267
Invoke-WithCreateDefaultHook -NewModulePath $script:PSCoreModulePath {
268-
exec { & $script:dotnetExe test -p:ExtraDefineConstants=TEST --logger trx -f $script:NetRuntime.PS7 (DotNetTestFilter) }
268+
exec { & $script:dotnetExe test --logger trx -f $script:NetRuntime.PS7 (DotNetTestFilter) }
269269
}
270270
}
271271

272272
task TestServerPS72 {
273273
Set-Location .\test\PowerShellEditorServices.Test\
274274
Invoke-WithCreateDefaultHook -NewModulePath $script:PSCoreModulePath {
275-
exec { & $script:dotnetExe test -p:ExtraDefineConstants=TEST --logger trx -f $script:NetRuntime.PS72 (DotNetTestFilter) }
275+
exec { & $script:dotnetExe test --logger trx -f $script:NetRuntime.PS72 (DotNetTestFilter) }
276276
}
277277
}
278278

@@ -281,13 +281,13 @@ task TestE2E {
281281

282282
$env:PWSH_EXE_NAME = if ($IsCoreCLR) { "pwsh" } else { "powershell" }
283283
$NetRuntime = if ($IsRosetta) { $script:NetRuntime.PS72 } else { $script:NetRuntime.PS7 }
284-
exec { & $script:dotnetExe test -p:ExtraDefineConstants=TEST --logger trx -f $NetRuntime (DotNetTestFilter) }
284+
exec { & $script:dotnetExe test --logger trx -f $NetRuntime (DotNetTestFilter) }
285285

286286
# Run E2E tests in ConstrainedLanguage mode.
287287
if (!$script:IsNix) {
288288
try {
289289
[System.Environment]::SetEnvironmentVariable("__PSLockdownPolicy", "0x80000007", [System.EnvironmentVariableTarget]::Machine);
290-
exec { & $script:dotnetExe test -p:ExtraDefineConstants=TEST --logger trx -f $script:NetRuntime.PS7 (DotNetTestFilter) }
290+
exec { & $script:dotnetExe test --logger trx -f $script:NetRuntime.PS7 (DotNetTestFilter) }
291291
} finally {
292292
[System.Environment]::SetEnvironmentVariable("__PSLockdownPolicy", $null, [System.EnvironmentVariableTarget]::Machine);
293293
}

src/PowerShellEditorServices.Hosting/Internal/EditorServicesRunner.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,8 @@ private HostStartupInfo CreateHostStartupInfo()
292292
_config.LogPath,
293293
(int)_config.LogLevel,
294294
consoleReplEnabled: _config.ConsoleRepl != ConsoleReplKind.None,
295-
usesLegacyReadLine: _config.ConsoleRepl == ConsoleReplKind.LegacyReadLine);
295+
usesLegacyReadLine: _config.ConsoleRepl == ConsoleReplKind.LegacyReadLine,
296+
bundledModulePath: _config.BundledModulePath);
296297
}
297298

298299
private void WriteStartupBanner()

src/PowerShellEditorServices/Hosting/HostStartupInfo.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,11 @@ public sealed class HostStartupInfo
107107
/// </remarks>
108108
public int LogLevel { get; }
109109

110+
/// <summary>
111+
/// The path to find the bundled modules. User configurable for advanced usage.
112+
/// </summary>
113+
public string BundledModulePath { get; }
114+
110115
#endregion
111116

112117
#region Constructors
@@ -135,6 +140,7 @@ public sealed class HostStartupInfo
135140
/// <param name="logLevel">The minimum log event level.</param>
136141
/// <param name="consoleReplEnabled">Enable console if true.</param>
137142
/// <param name="usesLegacyReadLine">Use PSReadLine if false, otherwise use the legacy readline implementation.</param>
143+
/// <param name="bundledModulePath">A custom path to the expected bundled modules.</param>
138144
public HostStartupInfo(
139145
string name,
140146
string profileId,
@@ -147,7 +153,8 @@ public HostStartupInfo(
147153
string logPath,
148154
int logLevel,
149155
bool consoleReplEnabled,
150-
bool usesLegacyReadLine)
156+
bool usesLegacyReadLine,
157+
string bundledModulePath)
151158
{
152159
Name = name ?? DefaultHostName;
153160
ProfileId = profileId ?? DefaultHostProfileId;
@@ -161,6 +168,7 @@ public HostStartupInfo(
161168
LogLevel = logLevel;
162169
ConsoleReplEnabled = consoleReplEnabled;
163170
UsesLegacyReadLine = usesLegacyReadLine;
171+
BundledModulePath = bundledModulePath;
164172
}
165173

166174
#endregion

src/PowerShellEditorServices/Services/PowerShellContext/PowerShellContextService.cs

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using System.Management.Automation.Remoting;
1212
using System.Management.Automation.Runspaces;
1313
using System.Reflection;
14+
using System.Runtime.InteropServices;
1415
using System.Text;
1516
using System.Threading;
1617
using System.Threading.Tasks;
@@ -32,10 +33,18 @@ namespace Microsoft.PowerShell.EditorServices.Services
3233
/// </summary>
3334
internal class PowerShellContextService : IHostSupportsInteractiveSession
3435
{
35-
private static readonly string s_commandsModulePath = Path.GetFullPath(
36-
Path.Combine(
37-
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location),
38-
"../../Commands/PowerShellEditorServices.Commands.psd1"));
36+
// This is a default that can be overriden at runtime by the user or tests.
37+
private static string s_bundledModulePath = Path.GetFullPath(Path.Combine(
38+
Path.GetDirectoryName(typeof(PowerShellContextService).Assembly.Location),
39+
"..",
40+
"..",
41+
".."));
42+
43+
private static string s_commandsModulePath => Path.GetFullPath(Path.Combine(
44+
s_bundledModulePath,
45+
"PowerShellEditorServices",
46+
"Commands",
47+
"PowerShellEditorServices.Commands.psd1"));
3948

4049
private static readonly Action<Runspace, ApartmentState> s_runspaceApartmentStateSetter;
4150
private static readonly PropertyInfo s_writeStreamProperty;
@@ -189,9 +198,16 @@ public static PowerShellContextService Create(
189198
OmniSharp.Extensions.LanguageServer.Protocol.Server.ILanguageServerFacade languageServer,
190199
HostStartupInfo hostStartupInfo)
191200
{
201+
var logger = factory.CreateLogger<PowerShellContextService>();
202+
192203
Validate.IsNotNull(nameof(hostStartupInfo), hostStartupInfo);
193204

194-
var logger = factory.CreateLogger<PowerShellContextService>();
205+
// Respect a user provided bundled module path.
206+
if (Directory.Exists(hostStartupInfo.BundledModulePath))
207+
{
208+
logger.LogTrace($"Using new bundled module path: {hostStartupInfo.BundledModulePath}");
209+
s_bundledModulePath = hostStartupInfo.BundledModulePath;
210+
}
195211

196212
bool shouldUsePSReadLine = hostStartupInfo.ConsoleReplEnabled
197213
&& !hostStartupInfo.UsesLegacyReadLine;
@@ -281,6 +297,15 @@ public static Runspace CreateRunspace(PSHost psHost, PSLanguageMode languageMode
281297
// should have the same LanguageMode of whatever is set by the system.
282298
initialSessionState.LanguageMode = languageMode;
283299

300+
// We set the process scope's execution policy (which is really the runspace's scope) to
301+
// Bypass so we can import our bundled modules. This is equivalent in scope to the CLI
302+
// argument `-Bypass`, which (for instance) the extension passes. Thus we emulate this
303+
// behavior for consistency such that unit tests can pass in a similar environment.
304+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
305+
{
306+
initialSessionState.ExecutionPolicy = ExecutionPolicy.Bypass;
307+
}
308+
284309
Runspace runspace = RunspaceFactory.CreateRunspace(psHost, initialSessionState);
285310

286311
// Windows PowerShell must be hosted in STA mode
@@ -396,7 +421,7 @@ public void Initialize(
396421

397422
if (powerShellVersion.Major >= 5 &&
398423
this.isPSReadLineEnabled &&
399-
PSReadLinePromptContext.TryGetPSReadLineProxy(logger, initialRunspace, out PSReadLineProxy proxy))
424+
PSReadLinePromptContext.TryGetPSReadLineProxy(logger, initialRunspace, s_bundledModulePath, out PSReadLineProxy proxy))
400425
{
401426
this.PromptContext = new PSReadLinePromptContext(
402427
this,
@@ -420,15 +445,13 @@ public void Initialize(
420445
/// the runspace. This method will be moved somewhere else soon.
421446
/// </summary>
422447
/// <returns></returns>
423-
public Task ImportCommandsModuleAsync() => ImportCommandsModuleAsync(s_commandsModulePath);
424-
425-
public Task ImportCommandsModuleAsync(string path)
448+
public Task ImportCommandsModuleAsync()
426449
{
427-
this.logger.LogTrace($"Importing PowershellEditorServices commands from {path}");
450+
this.logger.LogTrace($"Importing PowershellEditorServices commands from {s_commandsModulePath}");
428451

429452
PSCommand importCommand = new PSCommand()
430453
.AddCommand("Import-Module")
431-
.AddArgument(path);
454+
.AddArgument(s_commandsModulePath);
432455

433456
return this.ExecuteCommandAsync<PSObject>(importCommand, sendOutputToHost: false, sendErrorToHost: false);
434457
}

src/PowerShellEditorServices/Services/PowerShellContext/Session/PSReadLinePromptContext.cs

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,6 @@ namespace Microsoft.PowerShell.EditorServices.Services.PowerShellContext
1717

1818
internal class PSReadLinePromptContext : IPromptContext
1919
{
20-
private static readonly string _psReadLineModulePath = Path.Combine(
21-
Path.GetDirectoryName(typeof(PSReadLinePromptContext).Assembly.Location),
22-
"..",
23-
"..",
24-
"..",
25-
#if TEST
26-
// When using xUnit (dotnet test) the assemblies are deployed to the
27-
// test project folder, invalidating our relative path assumption.
28-
"..",
29-
"..",
30-
"module",
31-
#endif
32-
"PSReadLine");
33-
3420
private static readonly Lazy<CmdletInfo> s_lazyInvokeReadLineForEditorServicesCmdletInfo = new Lazy<CmdletInfo>(() =>
3521
{
3622
var type = Type.GetType("Microsoft.PowerShell.EditorServices.Commands.InvokeReadLineForEditorServicesCommand, Microsoft.PowerShell.EditorServices.Hosting");
@@ -79,6 +65,7 @@ internal PSReadLinePromptContext(
7965
internal static bool TryGetPSReadLineProxy(
8066
ILogger logger,
8167
Runspace runspace,
68+
string bundledModulePath,
8269
out PSReadLineProxy readLineProxy)
8370
{
8471
readLineProxy = null;
@@ -87,15 +74,33 @@ internal static bool TryGetPSReadLineProxy(
8774
{
8875
pwsh.Runspace = runspace;
8976
pwsh.AddCommand("Microsoft.PowerShell.Core\\Import-Module")
90-
.AddParameter("Name", _psReadLineModulePath)
77+
.AddParameter("Name", Path.Combine(bundledModulePath, "PSReadLine"))
9178
.Invoke();
9279

80+
if (pwsh.HadErrors)
81+
{
82+
logger.LogWarning("PSConsoleReadline type not found: {Reason}", pwsh.Streams.Error[0].ToString());
83+
return false;
84+
}
85+
9386
var psReadLineType = Type.GetType("Microsoft.PowerShell.PSConsoleReadLine, Microsoft.PowerShell.PSReadLine2");
9487

9588
if (psReadLineType == null)
9689
{
97-
logger.LogWarning("PSConsoleReadline type not found: {Reason}", pwsh.HadErrors ? pwsh.Streams.Error[0].ToString() : "<Unknown reason>");
98-
return false;
90+
// NOTE: For some reason `Type.GetType(...)` can fail to find the type,
91+
// and in that case, this search through the `AppDomain` for some reason will succeed.
92+
// It's slower, but only happens when needed.
93+
logger.LogTrace("PSConsoleReadline type not found using Type.GetType(), searching all loaded assemblies...");
94+
psReadLineType = AppDomain.CurrentDomain
95+
.GetAssemblies()
96+
.FirstOrDefault(asm => asm.GetName().Name.Equals("Microsoft.PowerShell.PSReadLine2"))
97+
?.ExportedTypes
98+
?.FirstOrDefault(type => type.FullName.Equals("Microsoft.PowerShell.PSConsoleReadLine"));
99+
if (psReadLineType == null)
100+
{
101+
logger.LogWarning("PSConsoleReadLine type not found anywhere!");
102+
return false;
103+
}
99104
}
100105

101106
try

test/PowerShellEditorServices.Test.E2E/PowerShellEditorServices.Test.E2E.csproj

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@
2222
</ItemGroup>
2323

2424
<ItemGroup>
25-
<None Update="xunit.runner.json">
26-
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
27-
</None>
25+
<Content Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
2826
</ItemGroup>
2927
</Project>

test/PowerShellEditorServices.Test.E2E/xunit.runner.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
{
2-
"parallelizeTestCollections": false
2+
"$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
3+
"appDomain": "denied",
4+
"parallelizeTestCollections": false,
5+
"methodDisplay": "method"
36
}
4-

test/PowerShellEditorServices.Test/App.config

Lines changed: 0 additions & 6 deletions
This file was deleted.

test/PowerShellEditorServices.Test/Language/SemanticTokenTest.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ namespace Microsoft.PowerShell.EditorServices.Test.Language
1717
public class SemanticTokenTest
1818
{
1919
[Fact]
20-
public async Task TokenizesFunctionElements()
20+
public void TokenizesFunctionElements()
2121
{
2222
string text = @"
2323
function Get-Sum {
@@ -59,7 +59,7 @@ function Get-Sum {
5959
}
6060

6161
[Fact]
62-
public async Task TokenizesStringExpansion()
62+
public void TokenizesStringExpansion()
6363
{
6464
string text = "Write-Host \"$(Test-Property Get-Whatever) $(Get-Whatever)\"";
6565
ScriptFile scriptFile = new ScriptFile(
@@ -82,7 +82,7 @@ public async Task TokenizesStringExpansion()
8282
}
8383

8484
[Fact]
85-
public async Task RecognizesTokensWithAsterisk()
85+
public void RecognizesTokensWithAsterisk()
8686
{
8787
string text = @"
8888
function Get-A*A {
@@ -111,7 +111,7 @@ function Get-A*A {
111111
}
112112

113113
[Fact]
114-
public async Task RecognizesArrayPropertyInExpandableString()
114+
public void RecognizesArrayPropertyInExpandableString()
115115
{
116116
string text = "\"$(@($Array).Count) OtherText\"";
117117
ScriptFile scriptFile = new ScriptFile(
@@ -136,7 +136,7 @@ public async Task RecognizesArrayPropertyInExpandableString()
136136
}
137137

138138
[Fact]
139-
public async Task RecognizesCurlyQuotedString()
139+
public void RecognizesCurlyQuotedString()
140140
{
141141
string text = "“^[-'a-z]*”";
142142
ScriptFile scriptFile = new ScriptFile(
@@ -150,7 +150,7 @@ public async Task RecognizesCurlyQuotedString()
150150
}
151151

152152
[Fact]
153-
public async Task RecognizeEnum()
153+
public void RecognizeEnum()
154154
{
155155
string text = @"
156156
enum MyEnum{

test/PowerShellEditorServices.Test/PowerShellContextFactory.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ internal static class PowerShellContextFactory
3232
Path.GetFullPath(
3333
TestUtilities.NormalizePath("../../../../PowerShellEditorServices.Test.Shared/ProfileTest.ps1")));
3434

35-
public static System.Management.Automation.Runspaces.Runspace initialRunspace;
35+
public static readonly string BundledModulePath = Path.GetFullPath(
36+
TestUtilities.NormalizePath("../../../../../module"));
37+
38+
public static System.Management.Automation.Runspaces.Runspace InitialRunspace;
3639

3740
public static PowerShellContextService Create(ILogger logger)
3841
{
@@ -46,21 +49,24 @@ public static PowerShellContextService Create(ILogger logger)
4649
TestProfilePaths,
4750
new List<string>(),
4851
new List<string>(),
52+
// TODO: We want to replace this property with an entire initial session state,
53+
// which would then also control the process-scoped execution policy.
4954
PSLanguageMode.FullLanguage,
5055
null,
5156
0,
5257
consoleReplEnabled: false,
53-
usesLegacyReadLine: false);
58+
usesLegacyReadLine: false,
59+
bundledModulePath: BundledModulePath);
5460

55-
initialRunspace = PowerShellContextService.CreateRunspace(
61+
InitialRunspace = PowerShellContextService.CreateRunspace(
5662
testHostDetails,
5763
powerShellContext,
5864
new TestPSHostUserInterface(powerShellContext, logger),
5965
logger);
6066

6167
powerShellContext.Initialize(
6268
TestProfilePaths,
63-
initialRunspace,
69+
InitialRunspace,
6470
ownsInitialRunspace: true,
6571
consoleHost: null);
6672

0 commit comments

Comments
 (0)