Skip to content

Commit 3b6b7dd

Browse files
committed
prefactoring + fixing tests for RADownloader
1 parent 9d4a629 commit 3b6b7dd

8 files changed

Lines changed: 149 additions & 82 deletions

File tree

src/RustAnalyzer.TestAdapter.UnitTests/Common/EnvironmentExtensionsTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public void GetDictionaryFromEnvironmentBlockTests(string envBlock, string dict)
2525
[InlineData(null)]
2626
[InlineData("\0")]
2727
[InlineData("abc=x\0\0")]
28+
[InlineData("A=x\u0001\0\0")]
2829
[InlineData("a=x\0abc=x\0\0")]
2930
[InlineData("A B=this\" is a\"b\0XX=this is xx\0A=a\0\0")]
3031
public void EnvBlockToEnvDictRoundTripTests(string envBlock)

src/RustAnalyzer.TestAdapter/Common/EnvironmentExtensions.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ namespace KS.RustAnalyzer.TestAdapter.Common;
77

88
public static class EnvironmentExtensions
99
{
10+
private const char EqEscape = '\u0001';
11+
private static readonly char[] NullSep = new[] { '\0' };
12+
private static readonly char[] EqSep = new[] { '=' };
13+
1014
public static IDictionary<string, string> OverrideProcessEnvironment(this string @this)
1115
{
1216
return @this.ToNullSeparatedDictionary()
@@ -34,8 +38,21 @@ public static IDictionary<string, string> GetEnvironmentVariables()
3438
public static string ToEnvironmentBlock(this IDictionary<string, string> @this)
3539
{
3640
return @this
37-
.Aggregate(new StringBuilder(), (acc, e) => acc.Append($"{e.Key}={e.Value}\0"))
41+
.Aggregate(new StringBuilder(), (acc, e) => acc.Append($"{e.Key}={e.Value.Replace('=', EqEscape)}\0"))
3842
.Append('\0')
3943
.ToString();
4044
}
45+
46+
public static IDictionary<string, string> ToNullSeparatedDictionary(this string @this)
47+
{
48+
return @this
49+
.FromNullSeparatedArray()
50+
.Select(x => x.Split(EqSep, StringSplitOptions.None))
51+
.ToDictionary(x => x[0], x => x.Length == 2 ? x[1].Replace(EqEscape, '=') : string.Empty);
52+
}
53+
54+
public static string[] FromNullSeparatedArray(this string @this)
55+
{
56+
return (@this ?? string.Empty).Split(NullSep, StringSplitOptions.RemoveEmptyEntries);
57+
}
4158
}

src/RustAnalyzer.TestAdapter/Common/StringExtensions.cs

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
11
using System;
2-
using System.Collections.Generic;
32
using System.IO;
4-
using System.Linq;
53
using System.Text;
64
using System.Text.RegularExpressions;
75

86
namespace KS.RustAnalyzer.TestAdapter.Common;
97

108
public static class StringExtensions
119
{
12-
private static readonly char[] NullSep = new[] { '\0' };
13-
private static readonly char[] EqSep = new[] { '=' };
14-
1510
public static Stream ToStreamUTF8(this string s)
1611
{
1712
return new MemoryStream(Encoding.UTF8.GetBytes(s));
@@ -42,19 +37,6 @@ public static string ReplaceNullWithBar(this string s)
4237
return s?.Replace("\0", "|");
4338
}
4439

45-
public static IDictionary<string, string> ToNullSeparatedDictionary(this string @this)
46-
{
47-
return @this
48-
.FromNullSeparatedArray()
49-
.Select(x => x.Split(EqSep, StringSplitOptions.None))
50-
.ToDictionary(x => x[0], x => x.Length == 2 ? x[1] : string.Empty);
51-
}
52-
53-
public static string[] FromNullSeparatedArray(this string @this)
54-
{
55-
return (@this ?? string.Empty).Split(NullSep, StringSplitOptions.RemoveEmptyEntries);
56-
}
57-
5840
public static string RegexReplace(this string @this, string pattern, string replacement)
5941
{
6042
return Regex.Replace(@this, pattern, replacement);
Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
using System;
22
using System.Globalization;
3-
using System.Linq;
4-
using System.Net.Http;
5-
using System.Threading;
63
using System.Threading.Tasks;
74
using FluentAssertions;
8-
using KS.RustAnalyzer.TestAdapter.Common;
5+
using KS.RustAnalyzer.Infrastructure;
96
using Xunit;
107

118
namespace KS.RustAnalyzer.UnitTests;
@@ -17,19 +14,10 @@ public sealed class RAExeRelease
1714
[Fact]
1815
public async Task LastUpdateShouldNotBeOlderThan30DaysAsync()
1916
{
20-
var latestRelUri = await GetRedirectedUrlAsync("https://github.com/rust-lang/rust-analyzer/releases/latest".ToUri());
17+
var ret = await RADownloader.GetLatestRAReleaseRedirectUriAsync();
2118

22-
string latestRelVersion = latestRelUri.Segments[latestRelUri.Segments.Length - 1];
23-
var latestRelDate = DateTime.ParseExact(latestRelVersion, "yyyy-MM-dd", CultureInfo.InvariantCulture);
24-
var lastUpdateDate = DateTime.ParseExact(LastUpdatedRAExeVersion, "yyyy-MM-dd", CultureInfo.InvariantCulture);
25-
lastUpdateDate.Should().NotBeBefore(latestRelDate.AddDays(-30), $"new rust-analyzer.exe is available https://github.com/rust-lang/rust-analyzer/releases/download/{latestRelVersion}/rust-analyzer-x86_64-pc-windows-msvc.zip");
26-
}
27-
28-
private static async Task<Uri> GetRedirectedUrlAsync(Uri uri, CancellationToken cancellationToken = default)
29-
{
30-
using var client = new HttpClient(new HttpClientHandler { AllowAutoRedirect = false, }, true);
31-
using var response = await client.GetAsync(uri, cancellationToken);
32-
33-
return new Uri(response.Headers.GetValues("Location").First());
19+
var latestRelDate = DateTime.ParseExact(ret?.Version, RADownloader.RAVersionFormat, CultureInfo.InvariantCulture);
20+
var lastUpdateDate = DateTime.ParseExact(LastUpdatedRAExeVersion, RADownloader.RAVersionFormat, CultureInfo.InvariantCulture);
21+
lastUpdateDate.Should().NotBeBefore(latestRelDate.AddDays(-30), $"new rust-analyzer.exe is available https://github.com/rust-lang/rust-analyzer/releases/download/{ret?.Version}/rust-analyzer-x86_64-pc-windows-msvc.zip");
3422
}
3523
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using System;
2+
using System.Globalization;
3+
using System.Linq;
4+
using System.Net.Http;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
using KS.RustAnalyzer.TestAdapter.Common;
8+
9+
namespace KS.RustAnalyzer.Infrastructure;
10+
11+
public static class RADownloader
12+
{
13+
public const string RAVersionFormat = "yyyy-MM-dd";
14+
15+
public static async Task<(Uri Uri, string Version)?> GetLatestRAReleaseRedirectUriAsync()
16+
{
17+
try
18+
{
19+
var latestRelUri = await GetRedirectedUrlAsync("https://github.com/rust-lang/rust-analyzer/releases/latest".ToUri());
20+
21+
var latestRelVersion = latestRelUri.Segments[latestRelUri.Segments.Length - 1];
22+
var latestRelDate = DateTime.ParseExact(latestRelVersion, RAVersionFormat, CultureInfo.InvariantCulture);
23+
24+
return (Uri: latestRelUri, Version: latestRelDate.ToString(RAVersionFormat, CultureInfo.InvariantCulture));
25+
}
26+
catch
27+
{
28+
return null;
29+
}
30+
}
31+
32+
private static async Task<Uri> GetRedirectedUrlAsync(Uri uri, CancellationToken cancellationToken = default)
33+
{
34+
using var client = new HttpClient(new HttpClientHandler { AllowAutoRedirect = false, }, true);
35+
using var response = await client.GetAsync(uri, cancellationToken);
36+
37+
return new Uri(response.Headers.GetValues("Location").First());
38+
}
39+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
using System;
2+
using System.ComponentModel.Composition;
3+
using System.IO;
4+
using KS.RustAnalyzer.TestAdapter.Common;
5+
using Microsoft.VisualStudio;
6+
using Microsoft.VisualStudio.Shell;
7+
using Microsoft.VisualStudio.Shell.Interop;
8+
using Microsoft.Win32;
9+
10+
namespace KS.RustAnalyzer.Infrastructure;
11+
12+
public interface IRegistrySettingsService
13+
{
14+
public bool InfoBarDismissedByUser { get; set; }
15+
}
16+
17+
[Export(typeof(IRegistrySettingsService))]
18+
[PartCreationPolicy(CreationPolicy.Shared)]
19+
public class RegistrySettingsService : IRegistrySettingsService
20+
{
21+
private const string DismissedRegKeyName = "release_notes_dismissed";
22+
23+
private readonly TL _tl;
24+
25+
private readonly IServiceProvider _serviceProvider;
26+
27+
[ImportingConstructor]
28+
public RegistrySettingsService([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider, [Import] ITelemetryService t, [Import] ILogger l)
29+
{
30+
_serviceProvider = serviceProvider;
31+
_tl = new TL
32+
{
33+
T = t,
34+
L = l,
35+
};
36+
}
37+
38+
public bool InfoBarDismissedByUser
39+
{
40+
get
41+
{
42+
ThreadHelper.ThrowIfNotOnUIThread();
43+
44+
if (GetPackageRegistryRoot(_serviceProvider, out string regRoot))
45+
{
46+
return Registry.GetValue(regRoot, DismissedRegKeyName, null)?.ToString() == Vsix.Version;
47+
}
48+
49+
return false;
50+
}
51+
52+
set
53+
{
54+
ThreadHelper.ThrowIfNotOnUIThread();
55+
56+
if (value && GetPackageRegistryRoot(_serviceProvider, out string regRoot))
57+
{
58+
Registry.SetValue(regRoot, DismissedRegKeyName, Vsix.Version);
59+
}
60+
}
61+
}
62+
63+
private static bool GetPackageRegistryRoot(IServiceProvider sp, out string packageRegistryRoot)
64+
{
65+
ThreadHelper.ThrowIfNotOnUIThread();
66+
67+
packageRegistryRoot = null;
68+
if (sp.GetService(typeof(SLocalRegistry)) is ILocalRegistry2 localReg && ErrorHandler.Succeeded(localReg.GetLocalRegistryRoot(out var localRegRoot)))
69+
{
70+
packageRegistryRoot = Path.Combine("HKEY_CURRENT_USER", localRegRoot, Vsix.Name);
71+
return true;
72+
}
73+
74+
return false;
75+
}
76+
}

src/RustAnalyzer/RustAnalyzer.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
<WarningLevel>4</WarningLevel>
4646
</PropertyGroup>
4747
<ItemGroup>
48+
<Compile Include="Infrastructure\RADownloader.cs" />
49+
<Compile Include="Infrastructure\RegistrySettingsService.cs" />
4850
<Compile Include="Infrastructure\SettingsInfo.cs" />
4951
<Compile Include="LanguageService\CommentHelper.cs" />
5052
<Compile Include="LanguageService\CommentSelectionCommandHandler.cs" />

src/RustAnalyzer/RustAnalyzerPackage.cs

Lines changed: 8 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.IO;
43
using System.Linq;
54
using System.Reflection;
65
using System.Runtime.InteropServices;
@@ -13,8 +12,6 @@
1312
using Microsoft.VisualStudio.ComponentModelHost;
1413
using Microsoft.VisualStudio.Imaging;
1514
using Microsoft.VisualStudio.Shell;
16-
using Microsoft.VisualStudio.Shell.Interop;
17-
using Microsoft.Win32;
1815
using CommunityVS = Community.VisualStudio.Toolkit.VS;
1916
using Constants = KS.RustAnalyzer.TestAdapter.Constants;
2017

@@ -37,6 +34,7 @@ namespace KS.RustAnalyzer;
3734
public sealed class RustAnalyzerPackage : ToolkitPackage
3835
{
3936
private TL _tl;
37+
private IRegistrySettingsService _regSettings;
4038
private IPreReqsCheckService _preReqs;
4139

4240
protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
@@ -51,6 +49,7 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke
5149
L = cmServiceProvider?.GetService<ILogger>(),
5250
T = cmServiceProvider?.GetService<ITelemetryService>(),
5351
};
52+
_regSettings = cmServiceProvider?.GetService<IRegistrySettingsService>();
5453
_preReqs = cmServiceProvider?.GetService<IPreReqsCheckService>();
5554
}
5655

@@ -60,7 +59,7 @@ protected override async Task OnAfterPackageLoadedAsync(CancellationToken cancel
6059

6160
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
6261

63-
await ReleaseSummaryNotification.ShowAsync(this, _tl);
62+
await ReleaseSummaryNotification.ShowAsync(_regSettings, _tl);
6463
await SearchAndDisableIncompatibleExtensionsAsync();
6564
await _preReqs.SatisfyAsync();
6665
}
@@ -148,14 +147,13 @@ public static class ReleaseSummaryNotification
148147
private const string ActionContextGetHelp = "get_help";
149148
private const string ActionContextRateExtension = "rate_extension";
150149
private const string ActionContextTestExperienceDemo = "test_experience_demo";
151-
private const string DismissedRegKeyName = "release_notes_dismissed";
152150

153-
public static async Task ShowAsync(IServiceProvider sp, TL tl)
151+
public static async Task ShowAsync(IRegistrySettingsService regSettings, TL tl)
154152
{
155153
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
156154

157155
tl.L.WriteLine("Attempting to show release notes...");
158-
if (HasBeenDismissedByUser(sp))
156+
if (regSettings.InfoBarDismissedByUser)
159157
{
160158
tl.L.WriteLine("... Not showing release notes as it has already been dismissed by the user.");
161159
return;
@@ -173,11 +171,11 @@ public static async Task ShowAsync(IServiceProvider sp, TL tl)
173171
image: KnownMonikers.StatusInformation,
174172
isCloseButtonVisible: true);
175173
var infoBar = await CommunityVS.InfoBar.CreateAsync(model);
176-
infoBar.ActionItemClicked += (s, ea) => InfoBar_ActionItemClicked(s, ea, sp, tl);
174+
infoBar.ActionItemClicked += (s, ea) => InfoBar_ActionItemClicked(s, ea, regSettings, tl);
177175
await infoBar.TryShowInfoBarUIAsync();
178176
}
179177

180-
private static void InfoBar_ActionItemClicked(object sender, InfoBarActionItemEventArgs e, IServiceProvider sp, TL tl)
178+
private static void InfoBar_ActionItemClicked(object sender, InfoBarActionItemEventArgs e, IRegistrySettingsService regSettings, TL tl)
181179
{
182180
ThreadHelper.ThrowIfNotOnUIThread();
183181

@@ -197,7 +195,7 @@ private static void InfoBar_ActionItemClicked(object sender, InfoBarActionItemEv
197195
break;
198196

199197
case ActionContextDismiss:
200-
MarkDismissedByUser(sp);
198+
regSettings.InfoBarDismissedByUser = true;
201199
(sender as InfoBar)?.Close();
202200
break;
203201

@@ -207,42 +205,6 @@ private static void InfoBar_ActionItemClicked(object sender, InfoBarActionItemEv
207205

208206
tl.T.TrackEvent("InfoBarAction", ("Context", actionContext));
209207
}
210-
211-
private static void MarkDismissedByUser(IServiceProvider sp)
212-
{
213-
ThreadHelper.ThrowIfNotOnUIThread();
214-
215-
if (GetPackageRegistryRoot(sp, out string regRoot))
216-
{
217-
Registry.SetValue(regRoot, DismissedRegKeyName, Vsix.Version);
218-
}
219-
}
220-
221-
private static bool HasBeenDismissedByUser(IServiceProvider sp)
222-
{
223-
ThreadHelper.ThrowIfNotOnUIThread();
224-
225-
if (GetPackageRegistryRoot(sp, out string regRoot))
226-
{
227-
return Registry.GetValue(regRoot, DismissedRegKeyName, null)?.ToString() == Vsix.Version;
228-
}
229-
230-
return false;
231-
}
232-
233-
private static bool GetPackageRegistryRoot(IServiceProvider sp, out string packageRegistryRoot)
234-
{
235-
ThreadHelper.ThrowIfNotOnUIThread();
236-
237-
packageRegistryRoot = null;
238-
if (sp.GetService(typeof(SLocalRegistry)) is ILocalRegistry2 localReg && ErrorHandler.Succeeded(localReg.GetLocalRegistryRoot(out var localRegRoot)))
239-
{
240-
packageRegistryRoot = Path.Combine("HKEY_CURRENT_USER", localRegRoot, Vsix.Name);
241-
return true;
242-
}
243-
244-
return false;
245-
}
246208
}
247209

248210
#endregion

0 commit comments

Comments
 (0)