Skip to content

Commit 0a5f143

Browse files
Add backup settings to Hosts File Editor
1 parent fc94cd7 commit 0a5f143

File tree

15 files changed

+518
-76
lines changed

15 files changed

+518
-76
lines changed

src/modules/Hosts/Hosts.FuzzTests/FuzzTests.cs

+3-5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
// Copyright (c) Microsoft Corporation
22
// The Microsoft Corporation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
4-
using System;
5-
using System.IO;
6-
using System.IO.Abstractions.TestingHelpers;
74
using System.Text.RegularExpressions;
8-
using System.Threading.Tasks;
95

106
using Hosts.Tests.Mocks;
117
using HostsUILib.Helpers;
@@ -19,6 +15,7 @@ public class FuzzTests
1915
{
2016
private static Mock<IUserSettings> _userSettings;
2117
private static Mock<IElevationHelper> _elevationHelper;
18+
private static Mock<IBackupManager> _backupManager;
2219

2320
// Case1: Fuzzing method for ValidIPv4
2421
public static void FuzzValidIPv4(ReadOnlySpan<byte> input)
@@ -73,9 +70,10 @@ public static void FuzzWriteAsync(ReadOnlySpan<byte> data)
7370
_userSettings = new Mock<IUserSettings>();
7471
_elevationHelper = new Mock<IElevationHelper>();
7572
_elevationHelper.Setup(m => m.IsElevated).Returns(true);
73+
_backupManager = new Mock<IBackupManager>();
7674

7775
var fileSystem = new CustomMockFileSystem();
78-
var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object);
76+
var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object, _backupManager.Object);
7977

8078
string input = System.Text.Encoding.UTF8.GetString(data);
8179

src/modules/Hosts/Hosts.FuzzTests/Hosts.FuzzTests.csproj

+2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
<Compile Include="..\HostsUILib\Settings\HostsAdditionalLinesPosition.cs" Link="HostsAdditionalLinesPosition.cs" />
2929
<Compile Include="..\HostsUILib\Settings\HostsEncoding.cs" Link="HostsEncoding.cs" />
3030
<Compile Include="..\HostsUILib\Settings\IUserSettings.cs" Link="IUserSettings.cs" />
31+
<Compile Include="..\HostsUILib\Helpers\IBackupManager.cs" Link="IBackupManager.cs" />
32+
<Compile Include="..\HostsUILib\Helpers\BackupManager.cs" Link="BackupManager.cs" />
3133
</ItemGroup>
3234

3335
<ItemGroup>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// Copyright (c) Microsoft Corporation
2+
// The Microsoft Corporation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.IO.Abstractions.TestingHelpers;
6+
using HostsUILib.Helpers;
7+
using HostsUILib.Settings;
8+
using Microsoft.VisualStudio.TestTools.UnitTesting;
9+
using Moq;
10+
11+
namespace Hosts.Tests
12+
{
13+
[TestClass]
14+
public class BackupManagerTest
15+
{
16+
private const string HostsPath = @"C:\Windows\System32\Drivers\etc\hosts";
17+
private const string BackupPath = @"C:\Backup\hosts";
18+
private const string BackupSearchPattern = $"*_PowerToysBackup_*";
19+
20+
[TestMethod]
21+
public void Hosts_Backup_Not_Done()
22+
{
23+
var fileSystem = new MockFileSystem();
24+
SetupFiles(fileSystem, false);
25+
var userSettings = new Mock<IUserSettings>();
26+
userSettings.Setup(m => m.BackupHosts).Returns(false);
27+
userSettings.Setup(m => m.BackupPath).Returns(BackupPath);
28+
var backupManager = new BackupManager(fileSystem, userSettings.Object);
29+
backupManager.CreateBackup(HostsPath);
30+
31+
Assert.AreEqual(0, fileSystem.Directory.GetFiles(BackupPath, BackupSearchPattern).Length);
32+
}
33+
34+
[TestMethod]
35+
public void Hosts_Backup_Done_Once()
36+
{
37+
var fileSystem = new MockFileSystem();
38+
SetupFiles(fileSystem, false);
39+
var userSettings = new Mock<IUserSettings>();
40+
userSettings.Setup(m => m.BackupHosts).Returns(true);
41+
userSettings.Setup(m => m.BackupPath).Returns(BackupPath);
42+
var backupManager = new BackupManager(fileSystem, userSettings.Object);
43+
backupManager.CreateBackup(HostsPath);
44+
backupManager.CreateBackup(HostsPath);
45+
46+
Assert.AreEqual(1, fileSystem.Directory.GetFiles(BackupPath, BackupSearchPattern).Length);
47+
var hostsContent = fileSystem.File.ReadAllText(HostsPath);
48+
var backupContent = fileSystem.File.ReadAllText(fileSystem.Directory.GetFiles(BackupPath, BackupSearchPattern)[0]);
49+
Assert.AreEqual(hostsContent, backupContent);
50+
}
51+
52+
[DataTestMethod]
53+
[DataRow(false, 1, 1)]
54+
[DataRow(true, 0, 0)]
55+
[DataRow(true, -1, -1)]
56+
public void Hosts_Backup_Not_Deleted(bool deleteBackup, int daysToKeep, int copiesToKeep)
57+
{
58+
var fileSystem = new MockFileSystem();
59+
SetupFiles(fileSystem, true);
60+
var userSettings = new Mock<IUserSettings>();
61+
userSettings.Setup(m => m.BackupPath).Returns(BackupPath);
62+
userSettings.Setup(m => m.DeleteBackups).Returns(deleteBackup);
63+
userSettings.Setup(m => m.DaysToKeep).Returns(daysToKeep);
64+
userSettings.Setup(m => m.CopiesToKeep).Returns(copiesToKeep);
65+
var backupManager = new BackupManager(fileSystem, userSettings.Object);
66+
backupManager.DeleteBackups();
67+
68+
Assert.AreEqual(10, fileSystem.Directory.GetFiles(BackupPath, BackupSearchPattern).Length);
69+
}
70+
71+
// MockFileSystem doesn't support CreationTime, so we can't test the DaysToKeep logic
72+
[DataTestMethod]
73+
[DataRow(0, 4, 4)]
74+
[DataRow(2, 4, 4)]
75+
public void Hosts_Backup_Deleted(int daysToKeep, int copiesToKeep, int expectedBackups)
76+
{
77+
var fileSystem = new MockFileSystem();
78+
SetupFiles(fileSystem, true);
79+
var userSettings = new Mock<IUserSettings>();
80+
userSettings.Setup(m => m.BackupPath).Returns(BackupPath);
81+
userSettings.Setup(m => m.DeleteBackups).Returns(true);
82+
userSettings.Setup(m => m.DaysToKeep).Returns(daysToKeep);
83+
userSettings.Setup(m => m.CopiesToKeep).Returns(copiesToKeep);
84+
var backupManager = new BackupManager(fileSystem, userSettings.Object);
85+
backupManager.DeleteBackups();
86+
87+
Assert.AreEqual(expectedBackups + 1, fileSystem.Directory.GetFiles(BackupPath).Length);
88+
Assert.AreEqual(expectedBackups, fileSystem.Directory.GetFiles(BackupPath, BackupSearchPattern).Length);
89+
}
90+
91+
private void SetupFiles(MockFileSystem fileSystem, bool addBackups)
92+
{
93+
fileSystem.AddFile(HostsPath, new MockFileData("HOSTS FILE CONTENT"));
94+
fileSystem.AddFile(fileSystem.Path.Combine(BackupPath, "unrelated_file"), new MockFileData(string.Empty));
95+
96+
if (addBackups)
97+
{
98+
for (var i = 0; i < 10; i++)
99+
{
100+
fileSystem.AddEmptyFile(fileSystem.Path.Combine(BackupPath, $"hosts_PowerToysBackup_{i}"));
101+
}
102+
}
103+
}
104+
}
105+
}

src/modules/Hosts/Hosts.Tests/HostsServiceTest.cs

+65-32
Original file line numberDiff line numberDiff line change
@@ -20,36 +20,18 @@ namespace Hosts.Tests
2020
[TestClass]
2121
public class HostsServiceTest
2222
{
23+
private const string BackupPath = @"C:\Backup\hosts";
2324
private static Mock<IUserSettings> _userSettings;
2425
private static Mock<IElevationHelper> _elevationHelper;
26+
private static Mock<IBackupManager> _backupManager;
2527

2628
[ClassInitialize]
2729
public static void ClassInitialize(TestContext context)
2830
{
2931
_userSettings = new Mock<IUserSettings>();
3032
_elevationHelper = new Mock<IElevationHelper>();
3133
_elevationHelper.Setup(m => m.IsElevated).Returns(true);
32-
}
33-
34-
[TestMethod]
35-
public void Hosts_Exists()
36-
{
37-
var fileSystem = new CustomMockFileSystem();
38-
var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object);
39-
fileSystem.AddFile(service.HostsFilePath, new MockFileData(string.Empty));
40-
var result = service.Exists();
41-
42-
Assert.IsTrue(result);
43-
}
44-
45-
[TestMethod]
46-
public void Hosts_Not_Exists()
47-
{
48-
var fileSystem = new CustomMockFileSystem();
49-
var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object);
50-
var result = service.Exists();
51-
52-
Assert.IsFalse(result);
34+
_backupManager = new Mock<IBackupManager>();
5335
}
5436

5537
[TestMethod]
@@ -67,7 +49,7 @@ 10.1.1.2 host2 host2.local # another comment
6749
";
6850

6951
var fileSystem = new CustomMockFileSystem();
70-
var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object);
52+
var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object, _backupManager.Object);
7153
fileSystem.AddFile(service.HostsFilePath, new MockFileData(content));
7254

7355
var data = await service.ReadAsync();
@@ -92,7 +74,7 @@ 10.1.1.2 host2 host2.local # another comment
9274
";
9375

9476
var fileSystem = new CustomMockFileSystem();
95-
var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object);
77+
var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object, _backupManager.Object);
9678
fileSystem.AddFile(service.HostsFilePath, new MockFileData(content));
9779

9880
var data = await service.ReadAsync();
@@ -118,7 +100,7 @@ 10.1.1.2 host2 host2.local # another comment
118100
";
119101

120102
var fileSystem = new CustomMockFileSystem();
121-
var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object);
103+
var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object, _backupManager.Object);
122104
fileSystem.AddFile(service.HostsFilePath, new MockFileData(content));
123105

124106
var data = await service.ReadAsync();
@@ -137,7 +119,7 @@ 10.1.1.2 host2 host2.local # another comment
137119
public async Task Empty_Hosts()
138120
{
139121
var fileSystem = new CustomMockFileSystem();
140-
var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object);
122+
var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object, _backupManager.Object);
141123
fileSystem.AddFile(service.HostsFilePath, new MockFileData(string.Empty));
142124

143125
await service.WriteAsync(string.Empty, Enumerable.Empty<Entry>());
@@ -168,7 +150,7 @@ 10.1.1.2 host2 host2.local # another comment
168150
var fileSystem = new CustomMockFileSystem();
169151
var userSettings = new Mock<IUserSettings>();
170152
userSettings.Setup(m => m.AdditionalLinesPosition).Returns(HostsAdditionalLinesPosition.Top);
171-
var service = new HostsService(fileSystem, userSettings.Object, _elevationHelper.Object);
153+
var service = new HostsService(fileSystem, userSettings.Object, _elevationHelper.Object, _backupManager.Object);
172154
fileSystem.AddFile(service.HostsFilePath, new MockFileData(content));
173155

174156
var data = await service.ReadAsync();
@@ -200,7 +182,7 @@ 10.1.1.2 host2 host2.local # another comment
200182
var fileSystem = new CustomMockFileSystem();
201183
var userSettings = new Mock<IUserSettings>();
202184
userSettings.Setup(m => m.AdditionalLinesPosition).Returns(HostsAdditionalLinesPosition.Bottom);
203-
var service = new HostsService(fileSystem, userSettings.Object, _elevationHelper.Object);
185+
var service = new HostsService(fileSystem, userSettings.Object, _elevationHelper.Object, _backupManager.Object);
204186
fileSystem.AddFile(service.HostsFilePath, new MockFileData(content));
205187

206188
var data = await service.ReadAsync();
@@ -224,7 +206,7 @@ 10.1.1.1 host19 # commen
224206
";
225207

226208
var fileSystem = new CustomMockFileSystem();
227-
var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object);
209+
var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object, _backupManager.Object);
228210
fileSystem.AddFile(service.HostsFilePath, new MockFileData(content));
229211

230212
var data = await service.ReadAsync();
@@ -241,15 +223,15 @@ public async Task Save_NotRunningElevatedException()
241223
var elevationHelper = new Mock<IElevationHelper>();
242224
elevationHelper.Setup(m => m.IsElevated).Returns(false);
243225

244-
var service = new HostsService(fileSystem, _userSettings.Object, elevationHelper.Object);
226+
var service = new HostsService(fileSystem, _userSettings.Object, elevationHelper.Object, _backupManager.Object);
245227
await Assert.ThrowsExceptionAsync<NotRunningElevatedException>(async () => await service.WriteAsync("# Empty hosts file", Enumerable.Empty<Entry>()));
246228
}
247229

248230
[TestMethod]
249231
public async Task Save_ReadOnlyHostsException()
250232
{
251233
var fileSystem = new CustomMockFileSystem();
252-
var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object);
234+
var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object, _backupManager.Object);
253235

254236
var hostsFile = new MockFileData(string.Empty)
255237
{
@@ -265,7 +247,7 @@ public async Task Save_ReadOnlyHostsException()
265247
public void Remove_ReadOnly_Attribute()
266248
{
267249
var fileSystem = new CustomMockFileSystem();
268-
var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object);
250+
var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object, _backupManager.Object);
269251

270252
var hostsFile = new MockFileData(string.Empty)
271253
{
@@ -284,7 +266,7 @@ public void Remove_ReadOnly_Attribute()
284266
public async Task Save_Hidden_Hosts()
285267
{
286268
var fileSystem = new CustomMockFileSystem();
287-
var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object);
269+
var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object, _backupManager.Object);
288270

289271
var hostsFile = new MockFileData(string.Empty)
290272
{
@@ -298,5 +280,56 @@ public async Task Save_Hidden_Hosts()
298280
var hidden = fileSystem.FileInfo.New(service.HostsFilePath).Attributes.HasFlag(FileAttributes.Hidden);
299281
Assert.IsTrue(hidden);
300282
}
283+
284+
[TestMethod]
285+
public async Task Hosts_Backup_Not_Done()
286+
{
287+
var content =
288+
@"10.1.1.1 host host.local # comment
289+
10.1.1.2 host2 host2.local # another comment
290+
";
291+
292+
var fileSystem = new CustomMockFileSystem();
293+
fileSystem.AddDirectory(BackupPath);
294+
_userSettings.Setup(m => m.BackupHosts).Returns(false);
295+
_userSettings.Setup(m => m.BackupPath).Returns(BackupPath);
296+
var backupManager = new BackupManager(fileSystem, _userSettings.Object);
297+
var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object, backupManager);
298+
299+
fileSystem.AddFile(service.HostsFilePath, new MockFileData(content));
300+
301+
var data = await service.ReadAsync();
302+
var entries = data.Entries.ToList();
303+
entries.Add(new Entry(0, "10.1.1.30", "host30 host30.local", "new entry", false));
304+
await service.WriteAsync(data.AdditionalLines, data.Entries);
305+
306+
Assert.AreEqual(0, fileSystem.Directory.GetFiles(BackupPath).Length);
307+
}
308+
309+
[TestMethod]
310+
public async Task Hosts_Backup_Done()
311+
{
312+
var content =
313+
@"10.1.1.1 host host.local # comment
314+
10.1.1.2 host2 host2.local # another comment
315+
";
316+
317+
var fileSystem = new CustomMockFileSystem();
318+
_userSettings.Setup(m => m.BackupHosts).Returns(true);
319+
_userSettings.Setup(m => m.BackupPath).Returns(BackupPath);
320+
var backupManager = new BackupManager(fileSystem, _userSettings.Object);
321+
var service = new HostsService(fileSystem, _userSettings.Object, _elevationHelper.Object, backupManager);
322+
323+
fileSystem.AddFile(service.HostsFilePath, new MockFileData(content));
324+
325+
var data = await service.ReadAsync();
326+
var entries = data.Entries.ToList();
327+
entries.Add(new Entry(0, "10.1.1.30", "host30 host30.local", "new entry", false));
328+
await service.WriteAsync(data.AdditionalLines, data.Entries);
329+
330+
Assert.AreEqual(1, fileSystem.Directory.GetFiles(BackupPath).Length);
331+
var backupContent = fileSystem.File.ReadAllText(fileSystem.Directory.GetFiles(BackupPath)[0]);
332+
Assert.AreEqual(content, backupContent);
333+
}
301334
}
302335
}

src/modules/Hosts/Hosts/HostsXAML/App.xaml.cs

+5-4
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ public App()
5353
{
5454
// Core Services
5555
services.AddSingleton<IFileSystem, FileSystem>();
56+
services.AddSingleton<IBackupManager, BackupManager>();
5657
services.AddSingleton<IHostsService, HostsService>();
5758
services.AddSingleton<IUserSettings, Hosts.Settings.UserSettings>();
5859
services.AddSingleton<IElevationHelper, ElevationHelper>();
@@ -71,7 +72,7 @@ public App()
7172
}).
7273
Build();
7374

74-
var cleanupBackupThread = new Thread(() =>
75+
var deleteBackupThread = new Thread(() =>
7576
{
7677
// Delete old backups only if running elevated
7778
if (!Host.GetService<IElevationHelper>().IsElevated)
@@ -81,16 +82,16 @@ public App()
8182

8283
try
8384
{
84-
Host.GetService<IHostsService>().CleanupBackup();
85+
Host.GetService<IBackupManager>().DeleteBackups();
8586
}
8687
catch (Exception ex)
8788
{
8889
Logger.LogError("Failed to delete backup", ex);
8990
}
9091
});
9192

92-
cleanupBackupThread.IsBackground = true;
93-
cleanupBackupThread.Start();
93+
deleteBackupThread.IsBackground = true;
94+
deleteBackupThread.Start();
9495

9596
UnhandledException += App_UnhandledException;
9697

0 commit comments

Comments
 (0)