Skip to content

Commit 7e4af16

Browse files
authored
Initial checking in AzPredictor (#12847)
- It contains an implementation of ICommandPredictor that provides predictions to PSReadLine. - It also talks to the service endpoints to get the predictions. - It reads from the profile settings about the service endpoint. - There are some tests cases.
1 parent e2f6357 commit 7e4af16

37 files changed

+1985
-0
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net5.0</TargetFramework>
5+
<AssemblyName>Microsoft.Azure.PowerShell.AzPredictor.Test</AssemblyName>
6+
<RootNamespace>Microsoft.Azure.PowerShell.AzPredictor.Test</RootNamespace>
7+
<IsPackable>false</IsPackable>
8+
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
9+
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
10+
<DocumentationFile>$(OutputPath)\Microsoft.Azure.PowerShell.AzPredictor.Test.xml</DocumentationFile>
11+
<GenerateSerializationAssemblies>Auto</GenerateSerializationAssemblies>
12+
</PropertyGroup>
13+
14+
<ItemGroup>
15+
<PackageReference Include="System.Management.Automation" Version="7.1.0-preview.9" />
16+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
17+
<PackageReference Include="xunit" Version="2.4.1" />
18+
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
19+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
20+
<PrivateAssets>all</PrivateAssets>
21+
</PackageReference>
22+
<PackageReference Include="coverlet.collector" Version="1.3.0">
23+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
24+
<PrivateAssets>all</PrivateAssets>
25+
</PackageReference>
26+
<ProjectReference Include="..\AzPredictor\AzPredictor.csproj" />
27+
</ItemGroup>
28+
29+
<ItemGroup>
30+
<None Include="Data\CommandsModel.zip" CopyToOutputDirectory="PreserveNewest" />
31+
<None Include="Data\PredictionsModel.zip" CopyToOutputDirectory="PreserveNewest" />
32+
</ItemGroup>
33+
34+
</Project>
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// ----------------------------------------------------------------------------------
2+
//
3+
// Copyright Microsoft Corporation
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
// ----------------------------------------------------------------------------------
14+
15+
using Microsoft.Azure.PowerShell.AzPredictor.Test.Mocks;
16+
using System.Management.Automation.Subsystem;
17+
using System.Threading;
18+
using Xunit;
19+
20+
namespace Microsoft.Azure.PowerShell.AzPredictor.Test
21+
{
22+
/// <summary>
23+
/// Tests for <see cref="AzPredictorService"/>
24+
/// </summary>
25+
[Collection("Model collection")]
26+
public class AzPredictorServiceTests
27+
{
28+
private readonly ModelFixture _fixture;
29+
private readonly AzPredictorService _service;
30+
private readonly Predictor _suggestionsPredictor;
31+
private readonly Predictor _commandsPredictor;
32+
33+
/// <summary>
34+
/// Constructs a new instance of <see cref="AzPredictorServiceTests"/>
35+
/// </summary>
36+
/// <param name="fixture"></param>
37+
public AzPredictorServiceTests(ModelFixture fixture)
38+
{
39+
this._fixture = fixture;
40+
var startHistory = $"{AzPredictorConstants.CommandHistoryPlaceholder}{AzPredictorConstants.CommandConcatenator}{AzPredictorConstants.CommandHistoryPlaceholder}";
41+
this._suggestionsPredictor = new Predictor(this._fixture.PredictionCollection[startHistory]);
42+
this._commandsPredictor = new Predictor(this._fixture.CommandCollection);
43+
44+
this._service = new MockAzPredictorService(this._fixture.PredictionCollection[startHistory], this._fixture.CommandCollection);
45+
}
46+
47+
48+
/// <summary>
49+
/// Verifies that the prediction comes from the suggestions list, not the command list.
50+
/// </summary>
51+
[Theory]
52+
[InlineData("CONNECT-AZACCOUNT")]
53+
[InlineData("set-azstorageaccount ")]
54+
[InlineData("Get-AzResourceG")]
55+
[InlineData("Get-AzStorageAcco")]
56+
[InlineData("Get-AzKeyVault -VaultName")]
57+
[InlineData("GET-AZSTORAGEACCOUNTKEY -NAME ")]
58+
[InlineData("new-azresourcegroup -name hello")]
59+
[InlineData("Get-AzContext -Name")]
60+
[InlineData("Get-AzContext -ErrorAction")]
61+
public void VerifyUsingSuggestion(string userInput)
62+
{
63+
var predictionContext = PredictionContext.Create(userInput);
64+
var expected = this._suggestionsPredictor.Query(predictionContext.InputAst, CancellationToken.None);
65+
var actual = this._service.GetSuggestion(predictionContext.InputAst, CancellationToken.None);
66+
Assert.Equal(expected, actual);
67+
Assert.NotNull(actual);
68+
}
69+
70+
71+
/// <summary>
72+
/// Verifies that when no prediction is in the suggestion list, we'll use the command list.
73+
/// </summary>
74+
[Theory]
75+
[InlineData("Get-AzResource -Name hello -Pre")]
76+
[InlineData("Get-AzADServicePrincipal -ApplicationObject")]
77+
public void VerifyUsingCommand(string userInput)
78+
{
79+
var predictionContext = PredictionContext.Create(userInput);
80+
var expected = this._commandsPredictor.Query(predictionContext.InputAst, CancellationToken.None);
81+
var actual = this._service.GetSuggestion(predictionContext.InputAst, CancellationToken.None);
82+
Assert.Equal(expected, actual);
83+
Assert.NotNull(actual);
84+
}
85+
86+
/// <summary>
87+
/// Verify that no prediction for the user input, meaning it's not in the prediction list or the command list.
88+
/// </summary>
89+
[Theory]
90+
[InlineData(AzPredictorConstants.CommandHistoryPlaceholder)]
91+
[InlineData("git status")]
92+
[InlineData("Get-ChildItem")]
93+
[InlineData("new-azresourcegroup -NoExistingParam")]
94+
[InlineData("get-azaccount ")]
95+
[InlineData("Get-AzContext Name")]
96+
[InlineData("NEW-AZCONTEXT")]
97+
public void VerifyNoPrediction(string userInput)
98+
{
99+
var predictionContext = PredictionContext.Create(userInput);
100+
var actual = this._service.GetSuggestion(predictionContext.InputAst, CancellationToken.None);
101+
Assert.Null(actual);
102+
}
103+
}
104+
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// ----------------------------------------------------------------------------------
2+
//
3+
// Copyright Microsoft Corporation
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
// ----------------------------------------------------------------------------------
14+
15+
using Microsoft.Azure.PowerShell.AzPredictor.Test.Mocks;
16+
using System.Collections.Generic;
17+
using System.Linq;
18+
using System.Management.Automation.Subsystem;
19+
using System.Threading;
20+
using Xunit;
21+
22+
namespace Microsoft.Azure.PowerShell.AzPredictor.Test
23+
{
24+
/// <summary>
25+
/// Tests for <see cref="AzPredictor"/>
26+
/// </summary>
27+
[Collection("Model collection")]
28+
public sealed class AzPredictorTests
29+
{
30+
private readonly ModelFixture _fixture;
31+
private readonly MockAzPredictorTelemetryClient _telemetryClient;
32+
private readonly MockAzPredictorService _service;
33+
private readonly AzPredictor _azPredictor;
34+
35+
/// <summary>
36+
/// Constructs a new instance of <see cref="AzPredictorTests"/>
37+
/// </summary>
38+
public AzPredictorTests(ModelFixture modelFixture)
39+
{
40+
this._fixture = modelFixture;
41+
var startHistory = $"{AzPredictorConstants.CommandHistoryPlaceholder}{AzPredictorConstants.CommandConcatenator}{AzPredictorConstants.CommandHistoryPlaceholder}";
42+
43+
this._service = new MockAzPredictorService(this._fixture.PredictionCollection[startHistory], this._fixture.CommandCollection);
44+
this._telemetryClient = new MockAzPredictorTelemetryClient();
45+
this._azPredictor = new AzPredictor(this._service, this._telemetryClient);
46+
}
47+
48+
/// <summary>
49+
/// Verifies when the last command in history are not supported.
50+
/// We don't collect the telemetry and only request prediction while StartEarlyProcess is called.
51+
/// </summary>
52+
[Theory]
53+
[InlineData("start_of_snippet\nstart_of_snippet\nstart_of_snippet")]
54+
[InlineData("start_of_snippet")]
55+
[InlineData("")]
56+
[InlineData("git status")]
57+
[InlineData("git status\nGet-ChildItem")]
58+
[InlineData("^29a9l2")]
59+
[InlineData("'Get-AzResource'")]
60+
[InlineData("Get-AzResource\ngit log")]
61+
[InlineData("Get-ChildItem")]
62+
public void VerifyWithNonSupportedCommand(string historyLine)
63+
{
64+
IReadOnlyList<string> history = historyLine.Split('\n');
65+
66+
this._telemetryClient.RecordedSuggestion = null;
67+
this._service.IsPredictionRequested = false;
68+
69+
this._azPredictor.StartEarlyProcessing(history);
70+
71+
Assert.True(this._service.IsPredictionRequested);
72+
Assert.Null(this._telemetryClient.RecordedSuggestion);
73+
}
74+
75+
/// <summary>
76+
/// Verifies when the last command in history are not supported.
77+
/// We don't collect the telemetry and only request prediction while StartEarlyProcess is called.
78+
/// </summary>
79+
[Theory]
80+
[InlineData("start_of_snippet\nConnect-AzAccount")]
81+
[InlineData("Get-AzResource")]
82+
[InlineData("git status\nGet-AzContext")]
83+
[InlineData("Get-AzContext\nGet-AzLog")]
84+
public void VerifyWithOneSupportedCommand(string historyLine)
85+
{
86+
IReadOnlyList<string> history = historyLine.Split('\n');
87+
88+
this._telemetryClient.RecordedSuggestion = null;
89+
this._service.IsPredictionRequested = false;
90+
91+
this._azPredictor.StartEarlyProcessing(history);
92+
93+
Assert.True(this._service.IsPredictionRequested);
94+
Assert.NotNull(this._telemetryClient.RecordedSuggestion);
95+
}
96+
97+
/// <summary>
98+
/// Verify that the supported commands parameter values are masked.
99+
/// </summary>
100+
[Fact]
101+
public void VerifySupportedCommandMasked()
102+
{
103+
var input = "Get-AzVMExtension -ResourceGroupName 'ResourceGroup11' -VMName 'VirtualMachine22'";
104+
var expected = "Get-AzVMExtension -ResourceGroupName *** -VMName ***";
105+
106+
this._telemetryClient.RecordedSuggestion = null;
107+
this._service.IsPredictionRequested = false;
108+
109+
this._azPredictor.StartEarlyProcessing(new List<string> { input } );
110+
111+
Assert.True(this._service.IsPredictionRequested);
112+
Assert.NotNull(this._telemetryClient.RecordedSuggestion);
113+
Assert.Equal(expected, this._telemetryClient.RecordedSuggestion.HistoryLine);
114+
115+
input = "Get-AzStorageAccountKey -Name:'ContosoStorage' -ResourceGroupName:'ContosoGroup02'";
116+
expected = "Get-AzStorageAccountKey -Name:*** -ResourceGroupName:***";
117+
118+
119+
this._telemetryClient.RecordedSuggestion = null;
120+
this._service.IsPredictionRequested = false;
121+
122+
this._azPredictor.StartEarlyProcessing(new List<string> { input } );
123+
124+
Assert.True(this._service.IsPredictionRequested);
125+
Assert.NotNull(this._telemetryClient.RecordedSuggestion);
126+
Assert.Equal(expected, this._telemetryClient.RecordedSuggestion.HistoryLine);
127+
}
128+
129+
/// <summary>
130+
/// Verifies AzPredictor returns the same value as AzPredictorService for the prediction.
131+
/// </summary>
132+
[Theory]
133+
[InlineData("git status")]
134+
[InlineData("new-azresourcegroup -name hello")]
135+
[InlineData("Get-AzContext -Name")]
136+
[InlineData("Get-AzContext -ErrorAction")]
137+
[InlineData("Get-AzADServicePrincipal -ApplicationObject")]
138+
public void VerifySuggestion(string userInput)
139+
{
140+
var predictionContext = PredictionContext.Create(userInput);
141+
var expected = this._service.GetSuggestion(predictionContext.InputAst, CancellationToken.None);
142+
var actual = this._azPredictor.GetSuggestion(predictionContext, CancellationToken.None);
143+
if (actual == null)
144+
{
145+
Assert.Null(expected);
146+
}
147+
else
148+
{
149+
Assert.Single(actual);
150+
Assert.Equal(expected, actual.First().SuggestionText);
151+
}
152+
153+
}
154+
}
155+
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// ----------------------------------------------------------------------------------
2+
//
3+
// Copyright Microsoft Corporation
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
// ----------------------------------------------------------------------------------
14+
15+
using System.Collections.Generic;
16+
17+
namespace Microsoft.Azure.PowerShell.AzPredictor.Test.Mocks
18+
{
19+
/// <summary>
20+
/// Mock <see cref="AzPredictorService"/> so that it doesn't do httpd request to get the commands and predictions.
21+
/// </summary>
22+
public sealed class MockAzPredictorService : AzPredictorService
23+
{
24+
/// <summary>
25+
/// Gets or sets if a predictions is requested.
26+
/// </summary>
27+
public bool IsPredictionRequested { get; set; }
28+
29+
/// <summary>
30+
/// Constructs a new instance of <see cref="MockAzPredictorService"/>
31+
/// </summary>
32+
/// <param name="suggestions">The suggestions collection</param>
33+
/// <param name="commands">The commands collection</param>
34+
public MockAzPredictorService(IList<string> suggestions, IList<string> commands)
35+
{
36+
SetCommandsPredictor(commands);
37+
SetSuggestionPredictor(suggestions);
38+
}
39+
40+
/// <inheritdoc/>
41+
public override void RequestPredictions(string history)
42+
{
43+
this.IsPredictionRequested = true;
44+
}
45+
46+
/// <inheritdoc/>
47+
protected override void RequestCommands()
48+
{
49+
// Do nothing since we've set the command and suggestion predictors.
50+
}
51+
}
52+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// ----------------------------------------------------------------------------------
2+
//
3+
// Copyright Microsoft Corporation
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
// ----------------------------------------------------------------------------------
14+
15+
using System.Collections.Generic;
16+
17+
namespace Microsoft.Azure.PowerShell.AzPredictor.Test.Mocks
18+
{
19+
sealed class MockAzPredictorTelemetryClient : ITelemetryClient
20+
{
21+
public class RecordedSuggestionForHistory
22+
{
23+
public string HistoryLine { get; set; }
24+
public int? SuggestionIndex { get; set; }
25+
public int? FallbackIndex { get; set; }
26+
public IEnumerable<string> TopSuggestions { get; set; }
27+
}
28+
29+
public RecordedSuggestionForHistory RecordedSuggestion { get; set; }
30+
public int SuggestionAccepted { get; set; }
31+
32+
/// <inheritdoc/>
33+
public void OnSuggestionForHistory(string historyLine, int? suggestionIndex, int? fallbackIndex, IEnumerable<string> topSuggestions)
34+
{
35+
this.RecordedSuggestion = new RecordedSuggestionForHistory()
36+
{
37+
HistoryLine = historyLine,
38+
SuggestionIndex = suggestionIndex,
39+
FallbackIndex = fallbackIndex,
40+
TopSuggestions = topSuggestions
41+
};
42+
}
43+
44+
/// <inheritdoc/>
45+
public void OnSuggestionAccepted(string acceptedSuggestion)
46+
{
47+
++this.SuggestionAccepted;
48+
}
49+
}
50+
}

0 commit comments

Comments
 (0)