Skip to content

Commit a4c5e1e

Browse files
committed
test: Program統合テスト追加と低カバレッジ領域のテスト強化
1 parent e768edb commit a4c5e1e

9 files changed

Lines changed: 668 additions & 29 deletions
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
using System;
2+
using System.IO;
3+
using System.Reflection;
4+
using System.Threading.Tasks;
5+
using FolderDiffIL4DotNet.Services;
6+
using Xunit;
7+
8+
namespace FolderDiffIL4DotNet.Tests
9+
{
10+
public sealed class ProgramTests
11+
{
12+
private static readonly string ConfigFilePath = Path.Combine(AppContext.BaseDirectory, "config.json");
13+
14+
[Fact]
15+
public async Task Main_WithInsufficientArguments_ReturnsErrorCode()
16+
{
17+
var exitCode = await InvokeProgramMainAsync(new[] { "--no-pause" });
18+
Assert.Equal(1, exitCode);
19+
}
20+
21+
[Fact]
22+
public async Task Main_WithValidArguments_ReturnsSuccessAndGeneratesReport()
23+
{
24+
var tempRoot = Path.Combine(Path.GetTempPath(), "fd-program-tests-" + Guid.NewGuid().ToString("N"));
25+
var oldDir = Path.Combine(tempRoot, "old");
26+
var newDir = Path.Combine(tempRoot, "new");
27+
Directory.CreateDirectory(oldDir);
28+
Directory.CreateDirectory(newDir);
29+
await File.WriteAllTextAsync(Path.Combine(oldDir, "sample.txt"), "same");
30+
await File.WriteAllTextAsync(Path.Combine(newDir, "sample.txt"), "same");
31+
32+
var reportLabel = "report_" + Guid.NewGuid().ToString("N");
33+
var reportDir = Path.Combine(AppContext.BaseDirectory, "Reports", reportLabel);
34+
var configJson = """
35+
{
36+
"IgnoredExtensions": [],
37+
"TextFileExtensions": [".txt"],
38+
"MaxLogGenerations": 3,
39+
"ShouldIncludeUnchangedFiles": true,
40+
"ShouldIncludeIgnoredFiles": false,
41+
"ShouldOutputILText": false,
42+
"ShouldIgnoreILLinesContainingConfiguredStrings": false,
43+
"ILIgnoreLineContainingStrings": [],
44+
"ShouldOutputFileTimestamps": false,
45+
"MaxParallelism": 1,
46+
"EnableILCache": false,
47+
"OptimizeForNetworkShares": false,
48+
"AutoDetectNetworkShares": false
49+
}
50+
""";
51+
52+
try
53+
{
54+
await WithConfigFileAsync(configJson, async () =>
55+
{
56+
var exitCode = await InvokeProgramMainAsync(new[] { oldDir, newDir, reportLabel, "--no-pause" });
57+
Assert.Equal(0, exitCode);
58+
Assert.True(File.Exists(Path.Combine(reportDir, "diff_report.md")));
59+
});
60+
}
61+
finally
62+
{
63+
try
64+
{
65+
if (Directory.Exists(tempRoot))
66+
{
67+
Directory.Delete(tempRoot, recursive: true);
68+
}
69+
}
70+
catch
71+
{
72+
// ignore cleanup errors in tests
73+
}
74+
try
75+
{
76+
if (Directory.Exists(reportDir))
77+
{
78+
Directory.Delete(reportDir, recursive: true);
79+
}
80+
}
81+
catch
82+
{
83+
// ignore cleanup errors in tests
84+
}
85+
}
86+
}
87+
88+
private static async Task<int> InvokeProgramMainAsync(string[] args)
89+
{
90+
var programType = typeof(ConfigService).Assembly.GetType("FolderDiffIL4DotNet.Program");
91+
Assert.NotNull(programType);
92+
93+
var mainMethod = programType.GetMethod("Main", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
94+
Assert.NotNull(mainMethod);
95+
96+
var taskObject = mainMethod.Invoke(null, new object[] { args });
97+
var task = Assert.IsAssignableFrom<Task<int>>(taskObject);
98+
return await task;
99+
}
100+
101+
private static async Task WithConfigFileAsync(string content, Func<Task> assertion)
102+
{
103+
var backupExists = File.Exists(ConfigFilePath);
104+
var backupContent = backupExists ? await File.ReadAllTextAsync(ConfigFilePath) : null;
105+
106+
try
107+
{
108+
await File.WriteAllTextAsync(ConfigFilePath, content);
109+
await assertion();
110+
}
111+
finally
112+
{
113+
if (backupExists)
114+
{
115+
await File.WriteAllTextAsync(ConfigFilePath, backupContent ?? string.Empty);
116+
}
117+
else if (File.Exists(ConfigFilePath))
118+
{
119+
File.Delete(ConfigFilePath);
120+
}
121+
}
122+
}
123+
}
124+
}

‎FolderDiffIL4DotNet.Tests/Services/DotNetDisassembleServiceTests.cs‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.IO;
44
using System.Reflection;
55
using System.Threading.Tasks;
6+
using FolderDiffIL4DotNet.Common;
67
using FolderDiffIL4DotNet.Models;
78
using FolderDiffIL4DotNet.Services;
89
using FolderDiffIL4DotNet.Services.Caching;
@@ -359,6 +360,44 @@ exit 1
359360
}
360361
}
361362

363+
[Fact]
364+
public async Task PrefetchIlCacheAsync_NullInput_ReturnsWithoutThrowing()
365+
{
366+
var service = CreateService(CreateConfig(enableIlCache: true), new ILCache(Path.Combine(_rootDir, "prefetch-null"), _logger));
367+
await service.PrefetchIlCacheAsync(null, maxParallel: 1);
368+
Assert.Equal(0, service.IlCacheHits);
369+
}
370+
371+
[Fact]
372+
public async Task PrefetchIlCacheAsync_InvalidMaxParallel_Throws()
373+
{
374+
var service = CreateService(CreateConfig(enableIlCache: true), new ILCache(Path.Combine(_rootDir, "prefetch-invalid"), _logger));
375+
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(() => service.PrefetchIlCacheAsync(new[] { "dummy.dll" }, maxParallel: 0));
376+
}
377+
378+
[Fact]
379+
public async Task PrefetchIlCacheAsync_WhenSeededCacheExists_IncrementsHitCounter()
380+
{
381+
var cacheDir = Path.Combine(_rootDir, "prefetch-hit-cache");
382+
var ilCache = new ILCache(cacheDir, _logger);
383+
var service = CreateService(CreateConfig(enableIlCache: true), ilCache);
384+
385+
var assemblyPath = Path.Combine(_rootDir, "prefetch-target.dll");
386+
await File.WriteAllTextAsync(assemblyPath, "dummy");
387+
388+
const string version = "1.2.3";
389+
SeedDisassemblerVersionCache(Constants.DOTNET_ILDASM, version);
390+
SeedDisassemblerVersionCache($"{Constants.DOTNET_MUXER} {Constants.ILDASM_LABEL}", version);
391+
SeedDisassemblerVersionCache(Constants.ILSPY_CMD, version);
392+
393+
var label = $"{Constants.DOTNET_ILDASM} {Path.GetFileName(assemblyPath)} (version: {version})";
394+
await ilCache.SetILAsync(assemblyPath, label, "CACHED_IL");
395+
396+
await service.PrefetchIlCacheAsync(new[] { assemblyPath }, maxParallel: 1);
397+
398+
Assert.True(service.IlCacheHits >= 1);
399+
}
400+
362401
private static ConfigSettings CreateConfig(bool enableIlCache) => new()
363402
{
364403
EnableILCache = enableIlCache,
@@ -416,6 +455,15 @@ private void ResetDisassemblerVersionCacheState()
416455
dictionary.Clear();
417456
}
418457

458+
private void SeedDisassemblerVersionCache(string key, string version)
459+
{
460+
var field = typeof(DotNetDisassemblerCache).GetField("_disassemblerVersionCache", BindingFlags.Instance | BindingFlags.NonPublic);
461+
Assert.NotNull(field);
462+
var dictionary = field.GetValue(_dotNetDisassemblerCache) as ConcurrentDictionary<string, string>;
463+
Assert.NotNull(dictionary);
464+
dictionary[key] = version;
465+
}
466+
419467
private DotNetDisassembleService CreateService(ConfigSettings config, ILCache ilCache)
420468
=> new(config, ilCache, _resultLists, _logger, _dotNetDisassemblerCache);
421469
}

‎FolderDiffIL4DotNet.Tests/Services/ILOutputServiceTests.cs‎

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
using System;
12
using System.Collections.Generic;
3+
using System.IO;
24
using System.Reflection;
5+
using System.Threading.Tasks;
36
using FolderDiffIL4DotNet.Models;
47
using FolderDiffIL4DotNet.Services;
8+
using FolderDiffIL4DotNet.Services.Caching;
9+
using Microsoft.Extensions.DependencyInjection;
510
using Xunit;
611

712
namespace FolderDiffIL4DotNet.Tests.Services
@@ -38,6 +43,103 @@ public void GetNormalizedIlIgnoreContainingStrings_RemovesEmptyTrimAndDuplicates
3843
Assert.Equal(new[] { "buildserver", "buildpath" }, result);
3944
}
4045

46+
[Theory]
47+
[InlineData("dotnet ildasm sample.dll (version: 9.0.0)", "dotnet-ildasm (version: 9.0.0)")]
48+
[InlineData("ilspycmd -il sample.dll (version: 8.2.1)", "ilspycmd (version: 8.2.1)")]
49+
[InlineData("dotnet-ildasm sample.dll", "dotnet-ildasm")]
50+
public void BuildToolAndVersionLabel_ReturnsExpectedLabel(string command, string expected)
51+
{
52+
var method = typeof(ILOutputService).GetMethod("BuildToolAndVersionLabel", BindingFlags.Static | BindingFlags.NonPublic);
53+
Assert.NotNull(method);
54+
55+
var result = method.Invoke(null, new object[] { command });
56+
57+
Assert.Equal(expected, Assert.IsType<string>(result));
58+
}
59+
60+
[Fact]
61+
public void BuildComparisonDisassemblerLabel_WhenLabelsMismatch_Throws()
62+
{
63+
var method = typeof(ILOutputService).GetMethod("BuildComparisonDisassemblerLabel", BindingFlags.Static | BindingFlags.NonPublic);
64+
Assert.NotNull(method);
65+
66+
var ex = Assert.Throws<TargetInvocationException>(() =>
67+
method.Invoke(null, new object[]
68+
{
69+
"dotnet ildasm sample.dll (version: 1.0.0)",
70+
"ilspycmd -il sample.dll (version: 2.0.0)"
71+
}));
72+
Assert.IsType<InvalidOperationException>(ex.InnerException);
73+
}
74+
75+
[Fact]
76+
public void BuildComparisonDisassemblerLabel_WhenOnlyOneSideHasLabel_ReturnsAvailableOne()
77+
{
78+
var method = typeof(ILOutputService).GetMethod("BuildComparisonDisassemblerLabel", BindingFlags.Static | BindingFlags.NonPublic);
79+
Assert.NotNull(method);
80+
81+
var result = method.Invoke(null, new object[] { null, "ilspycmd -il sample.dll (version: 8.2.1)" });
82+
Assert.Equal("ilspycmd (version: 8.2.1)", Assert.IsType<string>(result));
83+
}
84+
85+
[Fact]
86+
public void BuildComparisonDisassemblerLabel_WhenBothMatch_IgnoresCaseAndReturnsLabel()
87+
{
88+
var method = typeof(ILOutputService).GetMethod("BuildComparisonDisassemblerLabel", BindingFlags.Static | BindingFlags.NonPublic);
89+
Assert.NotNull(method);
90+
91+
var result = method.Invoke(null, new object[]
92+
{
93+
"dotnet ildasm sample.dll (version: 1.0.0)",
94+
"DOTNET ILDASM sample.dll (version: 1.0.0)"
95+
});
96+
Assert.Equal("dotnet-ildasm (version: 1.0.0)", Assert.IsType<string>(result));
97+
}
98+
99+
[Fact]
100+
public async Task PrecomputeAsync_WhenOptimizeForNetworkShares_ExitsWithoutThrowing()
101+
{
102+
var config = new ConfigSettings
103+
{
104+
OptimizeForNetworkShares = true,
105+
EnableILCache = true,
106+
IgnoredExtensions = new(),
107+
TextFileExtensions = new()
108+
};
109+
110+
var service = CreateILOutputService(config);
111+
await service.PrecomputeAsync(new[] { "/tmp/non-existent.dll" }, maxParallel: 0);
112+
}
113+
114+
[Fact]
115+
public async Task PrecomputeAsync_WithInvalidMaxParallel_ThrowsWhenNotNetworkOptimized()
116+
{
117+
var config = new ConfigSettings
118+
{
119+
OptimizeForNetworkShares = false,
120+
EnableILCache = false,
121+
IgnoredExtensions = new(),
122+
TextFileExtensions = new()
123+
};
124+
125+
var service = CreateILOutputService(config);
126+
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(() => service.PrecomputeAsync(Array.Empty<string>(), maxParallel: 0));
127+
}
128+
129+
[Fact]
130+
public async Task PrecomputeAsync_WithCacheDisabled_ReturnsWithoutThrowing()
131+
{
132+
var config = new ConfigSettings
133+
{
134+
OptimizeForNetworkShares = false,
135+
EnableILCache = false,
136+
IgnoredExtensions = new(),
137+
TextFileExtensions = new()
138+
};
139+
var service = CreateILOutputService(config);
140+
await service.PrecomputeAsync(new[] { "/tmp/non-existent.dll" }, maxParallel: 1);
141+
}
142+
41143
private static bool InvokeShouldExcludeIlLine(string line, bool shouldIgnoreContainingStrings, IReadOnlyCollection<string> ilIgnoreContainingStrings)
42144
{
43145
var method = typeof(ILOutputService).GetMethod("ShouldExcludeIlLine", BindingFlags.Static | BindingFlags.NonPublic);
@@ -53,5 +155,21 @@ private static List<string> InvokeGetNormalizedIlIgnoreContainingStrings(ConfigS
53155
var result = method.Invoke(null, new object[] { config });
54156
return Assert.IsType<List<string>>(result);
55157
}
158+
159+
private static ILOutputService CreateILOutputService(ConfigSettings config, string ilOldFolder = null, string ilNewFolder = null)
160+
{
161+
var logger = new LoggerService();
162+
var services = new ServiceCollection();
163+
services.AddSingleton<ILoggerService>(logger);
164+
services.AddSingleton(new FileDiffResultLists());
165+
services.AddSingleton(new DotNetDisassemblerCache(logger));
166+
var provider = services.BuildServiceProvider();
167+
168+
var oldDir = ilOldFolder ?? Path.Combine(Path.GetTempPath(), "fd-iloutput-old-" + Guid.NewGuid().ToString("N"));
169+
var newDir = ilNewFolder ?? Path.Combine(Path.GetTempPath(), "fd-iloutput-new-" + Guid.NewGuid().ToString("N"));
170+
Directory.CreateDirectory(oldDir);
171+
Directory.CreateDirectory(newDir);
172+
return new ILOutputService(config, oldDir, newDir, provider, logger);
173+
}
56174
}
57175
}

0 commit comments

Comments
 (0)