Skip to content

Commit faf5321

Browse files
committed
[tests] feat: cover closure graph and verb output
1 parent e34e959 commit faf5321

2 files changed

Lines changed: 219 additions & 0 deletions

File tree

tests/e2e/ClosureTests.cs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
using System.Text.Json;
2+
3+
namespace PeFix.Tests;
4+
5+
[Trait("Category", "E2E")]
6+
public sealed class ClosureE2E : IDisposable
7+
{
8+
private readonly TempDir _temp = new();
9+
10+
[Fact]
11+
public void TextOutput()
12+
{
13+
_temp.CopyAll(
14+
"F20_closure_entry.dll",
15+
"F21_closure_mid.dll",
16+
"F22_closure_deep.dll",
17+
"F23_closure_cycle_a.dll",
18+
"F24_closure_cycle_b.dll");
19+
20+
CliResult result = CliRunner.Run("closure", _temp.DirPath);
21+
22+
Assert.Equal(0, result.ExitCode);
23+
Assert.Contains("UNRESOLVED", result.Stdout);
24+
Assert.Contains("ClosureMissing", result.Stdout);
25+
Assert.Contains("MISSING", result.Stdout);
26+
Assert.Contains("Cycle chains:", result.Stdout);
27+
Assert.Contains("ClosureCycleA", result.Stdout);
28+
Assert.Contains("ClosureCycleB", result.Stdout);
29+
Assert.Contains("CYCLE", result.Stdout);
30+
Assert.Contains("entry assemblies", result.Stdout);
31+
Assert.Contains("transitive references", result.Stdout);
32+
Assert.Contains("→ ClosureMid.dll v1.0.0.0", result.Stdout);
33+
Assert.Contains("→ ClosureDeep.dll v1.0.0.0", result.Stdout);
34+
Assert.Contains("→ ClosureMissing.dll v1.0.0.0 [MISSING]", result.Stdout);
35+
Assert.Contains("[resolved]", result.Stdout);
36+
}
37+
38+
[Fact]
39+
public void JsonContract()
40+
{
41+
_temp.CopyAll(
42+
"F20_closure_entry.dll",
43+
"F21_closure_mid.dll",
44+
"F22_closure_deep.dll");
45+
46+
CliResult result = CliRunner.Run("closure", _temp.DirPath, "--json");
47+
48+
Assert.Equal(0, result.ExitCode);
49+
Assert.DoesNotContain("\r", result.Stdout);
50+
Assert.EndsWith("\n", result.Stdout);
51+
52+
using JsonDocument doc = JsonDocument.Parse(result.Stdout);
53+
JsonElement root = doc.RootElement;
54+
55+
Assert.Equal(1, root.GetProperty("schema_version").GetInt32());
56+
Assert.True(root.TryGetProperty("directory", out _));
57+
Assert.True(root.TryGetProperty("entry_assemblies", out _));
58+
Assert.True(root.TryGetProperty("unresolved_chains", out _));
59+
Assert.True(root.TryGetProperty("cycle_chains", out _));
60+
Assert.True(root.TryGetProperty("total_refs_walked", out _));
61+
Assert.True(root.TryGetProperty("framework_leaves", out _));
62+
63+
JsonElement unresolved = root.GetProperty("unresolved_chains");
64+
JsonElement chain = Assert.Single(
65+
unresolved.EnumerateArray(),
66+
item => item.GetProperty("entry").GetString() == "ClosureEntry");
67+
JsonElement segments = chain.GetProperty("segments");
68+
Assert.Equal(3, segments.GetArrayLength());
69+
AssertSeg(segments[0], "ClosureMid", "resolved");
70+
AssertSeg(segments[1], "ClosureDeep", "resolved");
71+
AssertSeg(segments[2], "ClosureMissing", "unresolved");
72+
}
73+
74+
[Fact]
75+
public void FailOnFlag()
76+
{
77+
_temp.CopyAll(
78+
"F20_closure_entry.dll",
79+
"F21_closure_mid.dll",
80+
"F22_closure_deep.dll");
81+
82+
CliResult result = CliRunner.Run("closure", _temp.DirPath, "--fail-on-unresolved");
83+
84+
Assert.Equal(1, result.ExitCode);
85+
}
86+
87+
[Fact]
88+
public void FailOnFlag_AllResolved()
89+
{
90+
_temp.CopyAll("F01_compatible_anycpu.dll");
91+
92+
CliResult result = CliRunner.Run("closure", _temp.DirPath, "--fail-on-unresolved");
93+
94+
Assert.Equal(0, result.ExitCode);
95+
}
96+
97+
private static void AssertSeg(JsonElement segment, string name, string kind)
98+
{
99+
Assert.Equal(name, segment.GetProperty("name").GetString());
100+
Assert.Equal("1.0.0.0", segment.GetProperty("version").GetString());
101+
Assert.Equal(kind, segment.GetProperty("kind").GetString());
102+
}
103+
104+
public void Dispose() => _temp.Dispose();
105+
}

tests/fixtures/ClosureTests.cs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
using PeFix.Meta;
2+
3+
namespace PeFix.Tests;
4+
5+
[Trait("Category", "Unit")]
6+
public sealed class ClosureTests
7+
{
8+
[Fact]
9+
public void All_Resolved()
10+
{
11+
Inspection a = Make("A", "1.0", [Asm("B", "1.0")]);
12+
Inspection b = Make("B", "1.0", [Asm("C", "1.0")]);
13+
Inspection c = Make("C", "1.0", []);
14+
15+
ClosureReport report = ClosureGraph.Build([a, b, c], "/test");
16+
17+
Assert.Equal(3, report.Entries.Length);
18+
Assert.Empty(report.Unresolved);
19+
Assert.Empty(report.CycleChains);
20+
Assert.Equal(0, report.HostLeaves);
21+
}
22+
23+
[Fact]
24+
public void Direct_Missing()
25+
{
26+
Inspection a = Make("A", "1.0", [Asm("Missing", "2.0")]);
27+
28+
ClosureReport report = ClosureGraph.Build([a], "/test");
29+
30+
Assert.Single(report.Unresolved);
31+
ClosureChain chain = report.Unresolved[0];
32+
Assert.Equal("A", chain.Entry.AssemblyName);
33+
Assert.Single(chain.Segments);
34+
Assert.Equal("Missing", chain.Segments[0].AssemblyName);
35+
Assert.Equal(ChainKind.Unresolved, chain.Segments[0].Kind);
36+
Assert.Equal(1, report.RefsWalked);
37+
}
38+
39+
[Fact]
40+
public void Transitive_Missing()
41+
{
42+
Inspection a = Make("A", "1.0", [Asm("B", "1.0")]);
43+
Inspection b = Make("B", "1.0", [Asm("C", "1.0")]);
44+
Inspection c = Make("C", "1.0", [Asm("Missing", "1.0")]);
45+
46+
ClosureReport report = ClosureGraph.Build([a, b, c], "/test");
47+
48+
Assert.Equal(3, report.Entries.Length);
49+
Assert.Single(report.Unresolved, ch => ch.Entry.AssemblyName == "A");
50+
51+
ClosureChain chain = report.Unresolved.First(ch => ch.Entry.AssemblyName == "A");
52+
Assert.Equal(3, chain.Segments.Length);
53+
Assert.Equal("B", chain.Segments[0].AssemblyName);
54+
Assert.Equal(ChainKind.Resolved, chain.Segments[0].Kind);
55+
Assert.Equal("C", chain.Segments[1].AssemblyName);
56+
Assert.Equal("Missing", chain.Segments[2].AssemblyName);
57+
Assert.Equal(ChainKind.Unresolved, chain.Segments[2].Kind);
58+
}
59+
60+
[Fact]
61+
public void Cycle_Handled()
62+
{
63+
Inspection a = Make("A", "1.0", [Asm("B", "1.0")]);
64+
Inspection b = Make("B", "1.0", [Asm("A", "1.0")]);
65+
66+
ClosureReport report = ClosureGraph.Build([a, b], "/test");
67+
68+
Assert.NotEmpty(report.CycleChains);
69+
Assert.Empty(report.Unresolved);
70+
Assert.Equal(2, report.CycleChains.Length);
71+
Assert.Contains(report.CycleChains, ch => ch.Entry.AssemblyName == "A");
72+
Assert.Contains(report.CycleChains, ch => ch.Entry.AssemblyName == "B");
73+
}
74+
75+
[Fact]
76+
public void Framework_NotReported()
77+
{
78+
Inspection a = Make("A", "1.0", [Asm("System", "4.0")]);
79+
80+
ClosureReport report = ClosureGraph.Build([a], "/test");
81+
82+
Assert.Empty(report.Unresolved);
83+
Assert.Equal(1, report.RefsWalked);
84+
Assert.Equal(1, report.HostLeaves);
85+
}
86+
87+
private static Inspection Make(string name, string ver, AsmRef[] refs)
88+
{
89+
return new Inspection(
90+
$"{name}.dll",
91+
true,
92+
true,
93+
null,
94+
null,
95+
default,
96+
default,
97+
null,
98+
Status.Compatible,
99+
"test",
100+
"cause",
101+
[],
102+
[],
103+
[],
104+
null,
105+
null,
106+
null,
107+
null,
108+
null,
109+
refs,
110+
Asm(name, ver));
111+
}
112+
113+
private static AsmRef Asm(string name, string version) => new(name, version);
114+
}

0 commit comments

Comments
 (0)