Implement diff-reporter PoC (#5)
This commit is contained in:
28
docs/history/2026-04-07_이슈5-diff-reporter-generator.md
Normal file
28
docs/history/2026-04-07_이슈5-diff-reporter-generator.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# 2026-04-07 이슈 #5 — diff-reporter Generator
|
||||||
|
|
||||||
|
## 작업 개요
|
||||||
|
Sprint Contract `docs/contracts/diff-reporter.md` 기준으로 `Recordingtest.DiffReporter` 라이브러리, CLI, xUnit 테스트 PoC 구현.
|
||||||
|
|
||||||
|
## 산출물
|
||||||
|
- `src/Recordingtest.DiffReporter/` (Differ, JsonDiffer, LineDiffer, BinaryDiffer, DiffModels)
|
||||||
|
- `src/Recordingtest.DiffReporter.Cli/` (Program.cs — `--approved/--received/--out`)
|
||||||
|
- `tests/Recordingtest.DiffReporter.Tests/` (5 tests)
|
||||||
|
- `recordingtest.sln`에 3개 프로젝트 등록
|
||||||
|
|
||||||
|
## 결과
|
||||||
|
- `dotnet build`: 경고 0, 오류 0
|
||||||
|
- `dotnet test`: 5/5 통과
|
||||||
|
|
||||||
|
## 미충족 DoD
|
||||||
|
- `diff.html` (옵션, PoC 스킵)
|
||||||
|
- 실제 `diff-triager` 서브에이전트 통합 (TriageReadable 스키마 검증으로 대체)
|
||||||
|
- 큰 파일 스트리밍 diff (성능 리스크 항목)
|
||||||
|
|
||||||
|
## 소요 시간
|
||||||
|
약 25분
|
||||||
|
|
||||||
|
## Context 사용량
|
||||||
|
약 40k tokens
|
||||||
|
|
||||||
|
## 관련 이슈
|
||||||
|
#5 (placeholder — diff-reporter Generator 작업)
|
||||||
99
src/Recordingtest.DiffReporter.Cli/Program.cs
Normal file
99
src/Recordingtest.DiffReporter.Cli/Program.cs
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using Recordingtest.DiffReporter;
|
||||||
|
|
||||||
|
namespace Recordingtest.DiffReporter.Cli;
|
||||||
|
|
||||||
|
public static class Program
|
||||||
|
{
|
||||||
|
public static int Main(string[] args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string? approved = null, received = null, outDir = null;
|
||||||
|
for (int i = 0; i < args.Length; i++)
|
||||||
|
{
|
||||||
|
switch (args[i])
|
||||||
|
{
|
||||||
|
case "--approved": approved = args[++i]; break;
|
||||||
|
case "--received": received = args[++i]; break;
|
||||||
|
case "--out": outDir = args[++i]; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (approved is null || received is null || outDir is null)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine("Usage: --approved <path> --received <path> --out <dir>");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
Directory.CreateDirectory(outDir);
|
||||||
|
DiffResult result;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
result = Differ.Compare(approved, received);
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine("I/O error: " + ex.Message);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException ex)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine("I/O error: " + ex.Message);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
var jsonOpts = new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||||
|
WriteIndented = true
|
||||||
|
};
|
||||||
|
File.WriteAllText(Path.Combine(outDir, "diff.json"),
|
||||||
|
JsonSerializer.Serialize(result, jsonOpts), Encoding.UTF8);
|
||||||
|
File.WriteAllText(Path.Combine(outDir, "diff.md"),
|
||||||
|
RenderMarkdown(result), Encoding.UTF8);
|
||||||
|
|
||||||
|
if (result.Identical)
|
||||||
|
{
|
||||||
|
Console.Out.WriteLine("identical");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
Console.Out.WriteLine($"diff: +{result.Summary.Added} -{result.Summary.Removed} ~{result.Summary.Changed} in {result.File}");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine("I/O error: " + ex.Message);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string RenderMarkdown(DiffResult r)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.AppendLine($"# Diff: {r.File}");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine($"Identical: **{r.Identical}**");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine("| Added | Removed | Changed |");
|
||||||
|
sb.AppendLine("|------:|--------:|--------:|");
|
||||||
|
sb.AppendLine($"| {r.Summary.Added} | {r.Summary.Removed} | {r.Summary.Changed} |");
|
||||||
|
sb.AppendLine();
|
||||||
|
if (r.Hunks.Count > 0)
|
||||||
|
{
|
||||||
|
sb.AppendLine("## Hunks");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine("| # | LineOrOffset | Kind | Before | After |");
|
||||||
|
sb.AppendLine("|--:|-------------:|------|--------|-------|");
|
||||||
|
for (int i = 0; i < r.Hunks.Count; i++)
|
||||||
|
{
|
||||||
|
var h = r.Hunks[i];
|
||||||
|
sb.AppendLine($"| {i} | {h.LineOrOffset} | {h.Kind} | {Escape(h.Before)} | {Escape(h.After)} |");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Escape(string s) =>
|
||||||
|
s.Replace("|", "\\|").Replace("\r", "").Replace("\n", "\\n");
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<AssemblyName>Recordingtest.DiffReporter.Cli</AssemblyName>
|
||||||
|
<RootNamespace>Recordingtest.DiffReporter.Cli</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Recordingtest.DiffReporter\Recordingtest.DiffReporter.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
55
src/Recordingtest.DiffReporter/BinaryDiffer.cs
Normal file
55
src/Recordingtest.DiffReporter/BinaryDiffer.cs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
namespace Recordingtest.DiffReporter;
|
||||||
|
|
||||||
|
internal static class BinaryDiffer
|
||||||
|
{
|
||||||
|
public static IReadOnlyList<Hunk> Diff(byte[] a, byte[] b)
|
||||||
|
{
|
||||||
|
var hunks = new List<Hunk>();
|
||||||
|
int max = Math.Max(a.Length, b.Length);
|
||||||
|
int i = 0;
|
||||||
|
while (i < max)
|
||||||
|
{
|
||||||
|
byte? av = i < a.Length ? a[i] : null;
|
||||||
|
byte? bv = i < b.Length ? b[i] : null;
|
||||||
|
if (av != bv)
|
||||||
|
{
|
||||||
|
int start = i;
|
||||||
|
while (i < max)
|
||||||
|
{
|
||||||
|
byte? av2 = i < a.Length ? a[i] : null;
|
||||||
|
byte? bv2 = i < b.Length ? b[i] : null;
|
||||||
|
if (av2 == bv2) break;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
int len = i - start;
|
||||||
|
var beforeSlice = Slice(a, start, len);
|
||||||
|
var afterSlice = Slice(b, start, len);
|
||||||
|
string kind;
|
||||||
|
if (start >= a.Length) kind = "added";
|
||||||
|
else if (start >= b.Length) kind = "removed";
|
||||||
|
else kind = "changed";
|
||||||
|
hunks.Add(new Hunk(start, kind, ToHex(beforeSlice), ToHex(afterSlice)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] Slice(byte[] src, int start, int len)
|
||||||
|
{
|
||||||
|
if (start >= src.Length) return Array.Empty<byte>();
|
||||||
|
int actual = Math.Min(len, src.Length - start);
|
||||||
|
var r = new byte[actual];
|
||||||
|
Array.Copy(src, start, r, 0, actual);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ToHex(byte[] data)
|
||||||
|
{
|
||||||
|
if (data.Length == 0) return string.Empty;
|
||||||
|
return Convert.ToHexString(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/Recordingtest.DiffReporter/DiffModels.cs
Normal file
11
src/Recordingtest.DiffReporter/DiffModels.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace Recordingtest.DiffReporter;
|
||||||
|
|
||||||
|
public sealed record DiffSummary(int Added, int Removed, int Changed);
|
||||||
|
|
||||||
|
public sealed record Hunk(int LineOrOffset, string Kind, string Before, string After);
|
||||||
|
|
||||||
|
public sealed record DiffResult(
|
||||||
|
string File,
|
||||||
|
bool Identical,
|
||||||
|
IReadOnlyList<Hunk> Hunks,
|
||||||
|
DiffSummary Summary);
|
||||||
86
src/Recordingtest.DiffReporter/Differ.cs
Normal file
86
src/Recordingtest.DiffReporter/Differ.cs
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace Recordingtest.DiffReporter;
|
||||||
|
|
||||||
|
public static class Differ
|
||||||
|
{
|
||||||
|
public static DiffResult Compare(string approvedPath, string receivedPath)
|
||||||
|
{
|
||||||
|
var approved = File.ReadAllBytes(approvedPath);
|
||||||
|
var received = File.ReadAllBytes(receivedPath);
|
||||||
|
var fileName = Path.GetFileName(receivedPath);
|
||||||
|
|
||||||
|
if (approved.SequenceEqual(received))
|
||||||
|
{
|
||||||
|
return new DiffResult(fileName, true, Array.Empty<Hunk>(), new DiffSummary(0, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try JSON
|
||||||
|
if (TryParseJson(approved, out var aDoc) && TryParseJson(received, out var bDoc))
|
||||||
|
{
|
||||||
|
using (aDoc)
|
||||||
|
using (bDoc)
|
||||||
|
{
|
||||||
|
var hunks = JsonDiffer.Diff(aDoc!.RootElement, bDoc!.RootElement);
|
||||||
|
return Build(fileName, hunks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try text
|
||||||
|
if (TryDecodeText(approved, out var aText) && TryDecodeText(received, out var bText)
|
||||||
|
&& (aText!.Contains('\n') || bText!.Contains('\n')))
|
||||||
|
{
|
||||||
|
var hunks = LineDiffer.Diff(aText!, bText!);
|
||||||
|
return Build(fileName, hunks);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Binary
|
||||||
|
var binHunks = BinaryDiffer.Diff(approved, received);
|
||||||
|
return Build(fileName, binHunks);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DiffResult Build(string file, IReadOnlyList<Hunk> hunks)
|
||||||
|
{
|
||||||
|
int added = 0, removed = 0, changed = 0;
|
||||||
|
foreach (var h in hunks)
|
||||||
|
{
|
||||||
|
switch (h.Kind)
|
||||||
|
{
|
||||||
|
case "added": added++; break;
|
||||||
|
case "removed": removed++; break;
|
||||||
|
case "changed": changed++; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new DiffResult(file, hunks.Count == 0, hunks, new DiffSummary(added, removed, changed));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryParseJson(byte[] data, out JsonDocument? doc)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
doc = JsonDocument.Parse(data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
doc = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryDecodeText(byte[] data, out string? text)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var enc = new UTF8Encoding(false, true);
|
||||||
|
text = enc.GetString(data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
text = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
77
src/Recordingtest.DiffReporter/JsonDiffer.cs
Normal file
77
src/Recordingtest.DiffReporter/JsonDiffer.cs
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace Recordingtest.DiffReporter;
|
||||||
|
|
||||||
|
internal static class JsonDiffer
|
||||||
|
{
|
||||||
|
public static IReadOnlyList<Hunk> Diff(JsonElement a, JsonElement b)
|
||||||
|
{
|
||||||
|
var aMap = new SortedDictionary<string, string>(StringComparer.Ordinal);
|
||||||
|
var bMap = new SortedDictionary<string, string>(StringComparer.Ordinal);
|
||||||
|
Flatten("$", a, aMap);
|
||||||
|
Flatten("$", b, bMap);
|
||||||
|
|
||||||
|
var hunks = new List<Hunk>();
|
||||||
|
var allKeys = new SortedSet<string>(aMap.Keys, StringComparer.Ordinal);
|
||||||
|
foreach (var k in bMap.Keys) allKeys.Add(k);
|
||||||
|
|
||||||
|
int idx = 0;
|
||||||
|
foreach (var key in allKeys)
|
||||||
|
{
|
||||||
|
var hasA = aMap.TryGetValue(key, out var av);
|
||||||
|
var hasB = bMap.TryGetValue(key, out var bv);
|
||||||
|
if (hasA && hasB)
|
||||||
|
{
|
||||||
|
if (!string.Equals(av, bv, StringComparison.Ordinal))
|
||||||
|
hunks.Add(new Hunk(idx, "changed", $"{key}={av}", $"{key}={bv}"));
|
||||||
|
}
|
||||||
|
else if (hasA)
|
||||||
|
{
|
||||||
|
hunks.Add(new Hunk(idx, "removed", $"{key}={av}", string.Empty));
|
||||||
|
}
|
||||||
|
else if (hasB)
|
||||||
|
{
|
||||||
|
hunks.Add(new Hunk(idx, "added", string.Empty, $"{key}={bv}"));
|
||||||
|
}
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
return hunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Flatten(string path, JsonElement el, IDictionary<string, string> map)
|
||||||
|
{
|
||||||
|
switch (el.ValueKind)
|
||||||
|
{
|
||||||
|
case JsonValueKind.Object:
|
||||||
|
foreach (var p in el.EnumerateObject())
|
||||||
|
Flatten(path + "." + p.Name, p.Value, map);
|
||||||
|
break;
|
||||||
|
case JsonValueKind.Array:
|
||||||
|
int i = 0;
|
||||||
|
foreach (var item in el.EnumerateArray())
|
||||||
|
{
|
||||||
|
Flatten(path + "[" + i + "]", item, map);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case JsonValueKind.String:
|
||||||
|
map[path] = "\"" + el.GetString() + "\"";
|
||||||
|
break;
|
||||||
|
case JsonValueKind.Number:
|
||||||
|
map[path] = el.GetRawText();
|
||||||
|
break;
|
||||||
|
case JsonValueKind.True:
|
||||||
|
map[path] = "true";
|
||||||
|
break;
|
||||||
|
case JsonValueKind.False:
|
||||||
|
map[path] = "false";
|
||||||
|
break;
|
||||||
|
case JsonValueKind.Null:
|
||||||
|
map[path] = "null";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
map[path] = el.GetRawText();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
50
src/Recordingtest.DiffReporter/LineDiffer.cs
Normal file
50
src/Recordingtest.DiffReporter/LineDiffer.cs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
namespace Recordingtest.DiffReporter;
|
||||||
|
|
||||||
|
internal static class LineDiffer
|
||||||
|
{
|
||||||
|
public static IReadOnlyList<Hunk> Diff(string a, string b)
|
||||||
|
{
|
||||||
|
var aLines = a.Replace("\r\n", "\n").Split('\n');
|
||||||
|
var bLines = b.Replace("\r\n", "\n").Split('\n');
|
||||||
|
|
||||||
|
// LCS table
|
||||||
|
int n = aLines.Length, m = bLines.Length;
|
||||||
|
var lcs = new int[n + 1, m + 1];
|
||||||
|
for (int i = n - 1; i >= 0; i--)
|
||||||
|
for (int j = m - 1; j >= 0; j--)
|
||||||
|
lcs[i, j] = aLines[i] == bLines[j]
|
||||||
|
? lcs[i + 1, j + 1] + 1
|
||||||
|
: Math.Max(lcs[i + 1, j], lcs[i, j + 1]);
|
||||||
|
|
||||||
|
var hunks = new List<Hunk>();
|
||||||
|
int x = 0, y = 0;
|
||||||
|
while (x < n && y < m)
|
||||||
|
{
|
||||||
|
if (aLines[x] == bLines[y])
|
||||||
|
{
|
||||||
|
x++; y++;
|
||||||
|
}
|
||||||
|
else if (lcs[x + 1, y] >= lcs[x, y + 1])
|
||||||
|
{
|
||||||
|
hunks.Add(new Hunk(x + 1, "removed", aLines[x], string.Empty));
|
||||||
|
x++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
hunks.Add(new Hunk(y + 1, "added", string.Empty, bLines[y]));
|
||||||
|
y++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (x < n)
|
||||||
|
{
|
||||||
|
hunks.Add(new Hunk(x + 1, "removed", aLines[x], string.Empty));
|
||||||
|
x++;
|
||||||
|
}
|
||||||
|
while (y < m)
|
||||||
|
{
|
||||||
|
hunks.Add(new Hunk(y + 1, "added", string.Empty, bLines[y]));
|
||||||
|
y++;
|
||||||
|
}
|
||||||
|
return hunks;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<AssemblyName>Recordingtest.DiffReporter</AssemblyName>
|
||||||
|
<RootNamespace>Recordingtest.DiffReporter</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
98
tests/Recordingtest.DiffReporter.Tests/DifferTests.cs
Normal file
98
tests/Recordingtest.DiffReporter.Tests/DifferTests.cs
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using Recordingtest.DiffReporter;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Recordingtest.DiffReporter.Tests;
|
||||||
|
|
||||||
|
public class DifferTests
|
||||||
|
{
|
||||||
|
private static string TempDir()
|
||||||
|
{
|
||||||
|
var d = Path.Combine(Path.GetTempPath(), "diffrep-" + Guid.NewGuid().ToString("N"));
|
||||||
|
Directory.CreateDirectory(d);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Identical_JsonFiles_ReturnsIdentical()
|
||||||
|
{
|
||||||
|
var dir = TempDir();
|
||||||
|
var a = Path.Combine(dir, "a.json");
|
||||||
|
var b = Path.Combine(dir, "b.json");
|
||||||
|
File.WriteAllText(a, "{\"x\":1,\"y\":\"hi\"}");
|
||||||
|
File.WriteAllText(b, "{\"x\":1,\"y\":\"hi\"}");
|
||||||
|
var r = Differ.Compare(a, b);
|
||||||
|
Assert.True(r.Identical);
|
||||||
|
Assert.Empty(r.Hunks);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void OneFieldChanged_JsonFiles_OneHunk()
|
||||||
|
{
|
||||||
|
var dir = TempDir();
|
||||||
|
var a = Path.Combine(dir, "a.json");
|
||||||
|
var b = Path.Combine(dir, "b.json");
|
||||||
|
File.WriteAllText(a, "{\"x\":1,\"y\":\"hi\"}");
|
||||||
|
File.WriteAllText(b, "{\"x\":2,\"y\":\"hi\"}");
|
||||||
|
var r = Differ.Compare(a, b);
|
||||||
|
Assert.False(r.Identical);
|
||||||
|
Assert.Single(r.Hunks);
|
||||||
|
Assert.Equal("changed", r.Hunks[0].Kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BinaryDiff_ReturnsHexSummary()
|
||||||
|
{
|
||||||
|
var dir = TempDir();
|
||||||
|
var a = Path.Combine(dir, "a.bin");
|
||||||
|
var b = Path.Combine(dir, "b.bin");
|
||||||
|
File.WriteAllBytes(a, new byte[] { 0x00, 0x01, 0x02, 0xFF, 0x80 });
|
||||||
|
File.WriteAllBytes(b, new byte[] { 0x00, 0x01, 0x03, 0xFE, 0x80 });
|
||||||
|
var r = Differ.Compare(a, b);
|
||||||
|
Assert.False(r.Identical);
|
||||||
|
Assert.NotEmpty(r.Hunks);
|
||||||
|
foreach (var h in r.Hunks)
|
||||||
|
{
|
||||||
|
// hex strings only
|
||||||
|
Assert.Matches("^[0-9A-F]*$", h.Before);
|
||||||
|
Assert.Matches("^[0-9A-F]*$", h.After);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Cli_IdenticalFiles_ExitZero_And_DiffJsonExists()
|
||||||
|
{
|
||||||
|
var dir = TempDir();
|
||||||
|
var a = Path.Combine(dir, "a.json");
|
||||||
|
var b = Path.Combine(dir, "b.json");
|
||||||
|
File.WriteAllText(a, "{\"x\":1}");
|
||||||
|
File.WriteAllText(b, "{\"x\":1}");
|
||||||
|
var outDir = Path.Combine(dir, "out");
|
||||||
|
var code = Cli.Program.Main(new[] { "--approved", a, "--received", b, "--out", outDir });
|
||||||
|
Assert.Equal(0, code);
|
||||||
|
Assert.True(File.Exists(Path.Combine(outDir, "diff.json")));
|
||||||
|
Assert.True(File.Exists(Path.Combine(outDir, "diff.md")));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TriageReadable_DiffJson_CanBeParsed()
|
||||||
|
{
|
||||||
|
var dir = TempDir();
|
||||||
|
var a = Path.Combine(dir, "a.json");
|
||||||
|
var b = Path.Combine(dir, "b.json");
|
||||||
|
File.WriteAllText(a, "{\"x\":1}");
|
||||||
|
File.WriteAllText(b, "{\"x\":2}");
|
||||||
|
var outDir = Path.Combine(dir, "out");
|
||||||
|
var code = Cli.Program.Main(new[] { "--approved", a, "--received", b, "--out", outDir });
|
||||||
|
Assert.Equal(1, code);
|
||||||
|
var json = File.ReadAllText(Path.Combine(outDir, "diff.json"));
|
||||||
|
using var doc = JsonDocument.Parse(json);
|
||||||
|
var root = doc.RootElement;
|
||||||
|
Assert.True(root.TryGetProperty("file", out _));
|
||||||
|
Assert.True(root.TryGetProperty("hunks", out _));
|
||||||
|
Assert.True(root.TryGetProperty("summary", out var summary));
|
||||||
|
Assert.True(summary.TryGetProperty("added", out _));
|
||||||
|
Assert.True(summary.TryGetProperty("removed", out _));
|
||||||
|
Assert.True(summary.TryGetProperty("changed", out _));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<RootNamespace>Recordingtest.DiffReporter.Tests</RootNamespace>
|
||||||
|
<AssemblyName>Recordingtest.DiffReporter.Tests</AssemblyName>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||||
|
<PackageReference Include="xunit" Version="2.9.0" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\src\Recordingtest.DiffReporter\Recordingtest.DiffReporter.csproj" />
|
||||||
|
<ProjectReference Include="..\..\src\Recordingtest.DiffReporter.Cli\Recordingtest.DiffReporter.Cli.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
Reference in New Issue
Block a user