From 56831daceaa1e4249c94f1125a04b03df056e0f8 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 1 Aug 2026 11:04:20 -0500 Subject: [PATCH 1/3] Add deterministic shared-output index ownership --- README.md | 6 +++ Xml2Doc/src/Xml2Doc.Cli/Config.cs | 5 ++- Xml2Doc/src/Xml2Doc.Cli/xml2doc.cs | 8 +++- Xml2Doc/src/Xml2Doc.Core/MarkdownRenderer.cs | 11 +++-- Xml2Doc/src/Xml2Doc.Core/README.md | 1 + Xml2Doc/src/Xml2Doc.Core/RendererOptions.cs | 7 +++- .../GenerateMarkdownFromXmlDoc.cs | 20 ++++++---- Xml2Doc/src/Xml2Doc.MSBuild/README.md | 12 ++++++ .../build/Xml2Doc.MSBuild.props | 1 + .../build/Xml2Doc.MSBuild.targets | 5 ++- .../tests/Xml2Doc.Tests/RenderSnapshots.cs | 19 ++++++++- .../adr/ADR-011-generated-output-ownership.md | 40 +++++++++++++++++++ docs/adr/README.md | 1 + docs/roadmap.md | 2 + 14 files changed, 121 insertions(+), 17 deletions(-) create mode 100644 docs/adr/ADR-011-generated-output-ownership.md diff --git a/README.md b/README.md index 6542357..48bfe35 100644 --- a/README.md +++ b/README.md @@ -214,6 +214,7 @@ Add this to your project’s `.csproj`: | `Xml2Doc_SingleFile` | Combine all output into a single Markdown file | | `Xml2Doc_OutputFile` | Path for the merged Markdown file | | `Xml2Doc_OutputDir` | Directory for per-type docs | +| `Xml2Doc_GenerateIndex` | Generate `index.md` in per-type mode (default: true) | | `Xml2Doc_FileNameMode` | `verbatim` or `clean` | | `Xml2Doc_RootNamespaceToTrim` | Namespace prefix to trim for cleaner names | | `Xml2Doc_CodeBlockLanguage` | Code block language (`csharp` by default) | @@ -229,6 +230,11 @@ Example configuration: ``` +When multiple projects intentionally write per-type pages to the same output directory, only one +invocation may own `index.md`. Set `false` on each +independent project and generate the repository-level index in a separate aggregation step. Xml2Doc +does not currently merge indexes produced by concurrent project builds. + --- ## 🧪 Testing diff --git a/Xml2Doc/src/Xml2Doc.Cli/Config.cs b/Xml2Doc/src/Xml2Doc.Cli/Config.cs index 543bfa6..a7d2df1 100644 --- a/Xml2Doc/src/Xml2Doc.Cli/Config.cs +++ b/Xml2Doc/src/Xml2Doc.Cli/Config.cs @@ -15,7 +15,7 @@ namespace Xml2Doc.Cli /// --xml, --out, --single, --file-names, --rootns, --lang, /// --trim-rootns-filenames, --report, --dry-run, --diff, /// --anchor-algorithm, --template, --front-matter, --auto-link, - /// --alias-map, --external-docs, --toc, --namespace-index, --parallel. + /// --alias-map, --external-docs, --toc, --namespace-index, --no-index, --parallel. /// public sealed class CliConfig { @@ -73,6 +73,9 @@ public sealed class CliConfig /// Emit namespace index when true. Maps to --namespace-index. public bool? NamespaceIndex { get; set; } + /// Emit the per-type index.md. Defaults to true. Maps inversely to --no-index. + public bool? GenerateIndex { get; set; } + /// Max parallelism (less than or equal to 0 or null uses default heuristic). Maps to --parallel option. public int? Parallel { get; set; } diff --git a/Xml2Doc/src/Xml2Doc.Cli/xml2doc.cs b/Xml2Doc/src/Xml2Doc.Cli/xml2doc.cs index f23a544..abb38c8 100644 --- a/Xml2Doc/src/Xml2Doc.Cli/xml2doc.cs +++ b/Xml2Doc/src/Xml2Doc.Cli/xml2doc.cs @@ -84,6 +84,7 @@ public static int Main(string[] args) string? externalDocs = null; bool toc = false; bool namespaceIndex = false; + bool generateIndex = true; int? parallel = null; bool? basenameOnly = false; string? configPath = null; @@ -114,6 +115,7 @@ public static int Main(string[] args) case "--external-docs" when i + 1 < args.Length: externalDocs = args[++i]; break; case "--toc": toc = true; break; case "--namespace-index": namespaceIndex = true; break; + case "--no-index": generateIndex = false; break; case "--basename-only": basenameOnly = true; break; case "--parallel" when i + 1 < args.Length: if (int.TryParse(args[++i], out var p)) parallel = p; @@ -154,6 +156,7 @@ public static int Main(string[] args) if (!string.IsNullOrWhiteSpace(cfg?.ExternalDocs)) externalDocs = externalDocs ?? cfg.ExternalDocs!; if (cfg?.Toc is bool tc) toc = tc || toc; if (cfg?.NamespaceIndex is bool ni) namespaceIndex = ni || namespaceIndex; + if (cfg?.GenerateIndex is bool gi && generateIndex) generateIndex = gi; if (cfg?.BasenameOnly is bool bo) basenameOnly = basenameOnly ?? bo; if (cfg?.Parallel is int pi && parallel is null) parallel = pi; if (cfg?.Diff is bool df) diff = df || diff; @@ -193,7 +196,8 @@ public static int Main(string[] args) EmitToc: toc, EmitNamespaceIndex: namespaceIndex, BasenameOnly: basenameOnly ?? false, - ParallelDegree: parallel + ParallelDegree: parallel, + GenerateIndex: generateIndex ); var renderer = new MarkdownRenderer(model, options); @@ -258,6 +262,7 @@ public static int Main(string[] args) externalDocs, toc, namespaceIndex, + generateIndex, basenameOnly = options.BasenameOnly, parallel }, @@ -342,6 +347,7 @@ private static void PrintHelp() Console.WriteLine(" [--external-docs ]"); Console.WriteLine(" [--toc]"); Console.WriteLine(" [--namespace-index]"); + Console.WriteLine(" [--no-index]"); Console.WriteLine(" [--basename-only]"); Console.WriteLine(" [--parallel ]"); Console.WriteLine(" [--config ]"); diff --git a/Xml2Doc/src/Xml2Doc.Core/MarkdownRenderer.cs b/Xml2Doc/src/Xml2Doc.Core/MarkdownRenderer.cs index a8ac75c..203282c 100644 --- a/Xml2Doc/src/Xml2Doc.Core/MarkdownRenderer.cs +++ b/Xml2Doc/src/Xml2Doc.Core/MarkdownRenderer.cs @@ -96,7 +96,7 @@ public MarkdownRenderer(Models.Xml2Doc model, RendererOptions? options = null) // === Public APIs === /// - /// Emits one Markdown file per documented type plus an index.md. Optionally emits namespace index pages. + /// Emits one Markdown file per documented type and, by default, an index.md. Optionally emits namespace index pages. /// /// Destination directory (created if absent). /// @@ -125,7 +125,8 @@ public void RenderToDirectory(string outDir) var file = Path.Combine(outDir, FileNameForPerType(t.Id)); File.WriteAllText(file, RenderType(t, includeHeader: true)); } - File.WriteAllText(Path.Combine(outDir, "index.md"), RenderIndex(types, useAnchors: false)); + if (_opt.GenerateIndex) + File.WriteAllText(Path.Combine(outDir, "index.md"), RenderIndex(types, useAnchors: false)); if (_opt.EmitNamespaceIndex) { @@ -384,7 +385,8 @@ static string GroupKey(XMember mm) /// If non-null, plans single-file output; otherwise multi‑file. /// Absolute paths of files that would be produced. /// - /// Multi‑file mode always includes index.md. Namespace index emission adds namespaces.md and one page per namespace. + /// Multi‑file mode includes index.md when is true. + /// Namespace index emission adds namespaces.md and one page per namespace. /// public IReadOnlyList PlanOutputs(string outDir, string? singleFilePath = null) { @@ -404,7 +406,8 @@ public IReadOnlyList PlanOutputs(string outDir, string? singleFilePath = list.Add(Path.Combine(root, name)); } - list.Add(Path.Combine(root, "index.md")); + if (_opt.GenerateIndex) + list.Add(Path.Combine(root, "index.md")); if (_opt.EmitNamespaceIndex) { diff --git a/Xml2Doc/src/Xml2Doc.Core/README.md b/Xml2Doc/src/Xml2Doc.Core/README.md index a9b5095..e5645f9 100644 --- a/Xml2Doc/src/Xml2Doc.Core/README.md +++ b/Xml2Doc/src/Xml2Doc.Core/README.md @@ -24,6 +24,7 @@ Now multi-targeted and verified for consistent output across modern .NET TFMs. - Filename mode: `Verbatim` or `CleanGenerics` - `RootNamespaceToTrim` (display-only trimming) - Code block language (default `csharp`) + - Per-type `index.md` generation (`GenerateIndex`, default `true`) - Output mode (single vs. multi-file) ## Supported Target Frameworks diff --git a/Xml2Doc/src/Xml2Doc.Core/RendererOptions.cs b/Xml2Doc/src/Xml2Doc.Core/RendererOptions.cs index 1f29f8c..7d4061e 100644 --- a/Xml2Doc/src/Xml2Doc.Core/RendererOptions.cs +++ b/Xml2Doc/src/Xml2Doc.Core/RendererOptions.cs @@ -94,6 +94,10 @@ public enum AnchorAlgorithm /// /// Max parallelism for rendering; or <= 0 selects a heuristic (typically Environment.ProcessorCount). /// + /// + /// When true, per-type output includes index.md. Disable this when multiple independent + /// invocations intentionally share one output directory and index ownership is handled separately. + /// /// /// Example: /// public bool EmitNamespaceIndex { get; set; } + /// + /// When true, per-type output includes index.md. Set false for projects that share an + /// output directory and delegate index ownership to a separate aggregation step. Defaults to true. + /// + public bool GenerateIndex { get; set; } = true; + /// /// When true, uses only the basename for file references (omits directory paths). /// @@ -231,7 +237,8 @@ public override bool Execute() EmitToc: EmitToc, EmitNamespaceIndex: EmitNamespaceIndex, BasenameOnly: BasenameOnly, - AnchorAlgorithm: anchorAlgEnum + AnchorAlgorithm: anchorAlgEnum, + GenerateIndex: GenerateIndex ); var renderer = new MarkdownRenderer(model, options); @@ -279,12 +286,11 @@ public override bool Execute() DidWork = true; } - if (Directory.Exists(outDir)) - { - GeneratedFiles = Directory.GetFiles(outDir, "*.md", SearchOption.TopDirectoryOnly) - .Select(p => (ITaskItem)new TaskItem(p)) - .ToArray(); - } + GeneratedFiles = DryRun + ? Array.Empty() + : renderer.PlanOutputs(outDir) + .Select(p => (ITaskItem)new TaskItem(p)) + .ToArray(); Log.LogMessage(MessageImportance.High, $"Xml2Doc {(DryRun ? "[dry-run] would write" : "wrote")} Markdown files to {outDir}"); } diff --git a/Xml2Doc/src/Xml2Doc.MSBuild/README.md b/Xml2Doc/src/Xml2Doc.MSBuild/README.md index 57bfdcd..0c30a45 100644 --- a/Xml2Doc/src/Xml2Doc.MSBuild/README.md +++ b/Xml2Doc/src/Xml2Doc.MSBuild/README.md @@ -45,6 +45,7 @@ That’s it—on successful build, docs are generated according to the propertie | `Xml2Doc_SingleFile` | `true` = generate one combined Markdown file; `false` = per-type files. | | `Xml2Doc_OutputFile` | Output file path when `SingleFile=true` (e.g. `$(ProjectDir)docs\api.md`). | | `Xml2Doc_OutputDir` | Output directory when `SingleFile=false` (e.g. `$(ProjectDir)docs`). | +| `Xml2Doc_GenerateIndex` | Generate `index.md` in per-type mode. Default: `true`. | | `Xml2Doc_FileNameMode` | `verbatim` (keep generic arity) or `clean` (friendly generic names). | | `Xml2Doc_RootNamespaceToTrim` | Optional namespace prefix trimmed from display names. | | `Xml2Doc_CodeBlockLanguage` | Code block language for fenced blocks (default `csharp`). | @@ -62,6 +63,17 @@ That’s it—on successful build, docs are generated according to the propertie ``` +**Shared output directories:** independent project builds cannot safely merge the same `index.md`. +When projects share `Xml2Doc_OutputDir`, set `Xml2Doc_GenerateIndex` to `false` for those projects +and create the repository-level index in a separate aggregation step: + +```xml + + $(SolutionDir)docs + false + +``` + **Per-type files (good for large APIs)** ```xml diff --git a/Xml2Doc/src/Xml2Doc.MSBuild/build/Xml2Doc.MSBuild.props b/Xml2Doc/src/Xml2Doc.MSBuild/build/Xml2Doc.MSBuild.props index 618452b..8735ed6 100644 --- a/Xml2Doc/src/Xml2Doc.MSBuild/build/Xml2Doc.MSBuild.props +++ b/Xml2Doc/src/Xml2Doc.MSBuild/build/Xml2Doc.MSBuild.props @@ -41,6 +41,7 @@ false false false + true false diff --git a/Xml2Doc/src/Xml2Doc.MSBuild/build/Xml2Doc.MSBuild.targets b/Xml2Doc/src/Xml2Doc.MSBuild/build/Xml2Doc.MSBuild.targets index a22b614..3ce11cd 100644 --- a/Xml2Doc/src/Xml2Doc.MSBuild/build/Xml2Doc.MSBuild.targets +++ b/Xml2Doc/src/Xml2Doc.MSBuild/build/Xml2Doc.MSBuild.targets @@ -82,7 +82,7 @@ - <_Xml2Doc_Options>$(Xml2Doc_SingleFile)|$(Xml2Doc_OutputFile)|$(Xml2Doc_OutputDir)|$(Xml2Doc_FileNameMode)|$(Xml2Doc_RootNamespaceToTrim)|$(Xml2Doc_CodeBlockLanguage)|$(Xml2Doc_Toc)|$(Xml2Doc_NamespaceIndex)|$(Xml2Doc_BasenameOnly)|$(Xml2Doc_TrimRootNamespaceInFileNames)|$(Xml2Doc_AnchorAlgorithm) + <_Xml2Doc_Options>$(Xml2Doc_SingleFile)|$(Xml2Doc_OutputFile)|$(Xml2Doc_OutputDir)|$(Xml2Doc_FileNameMode)|$(Xml2Doc_RootNamespaceToTrim)|$(Xml2Doc_CodeBlockLanguage)|$(Xml2Doc_Toc)|$(Xml2Doc_NamespaceIndex)|$(Xml2Doc_BasenameOnly)|$(Xml2Doc_TrimRootNamespaceInFileNames)|$(Xml2Doc_AnchorAlgorithm)|$(Xml2Doc_GenerateIndex) @@ -142,6 +142,7 @@ Diff="$(Xml2Doc_Diff)" EmitToc="$(Xml2Doc_Toc)" EmitNamespaceIndex="$(Xml2Doc_NamespaceIndex)" + GenerateIndex="$(Xml2Doc_GenerateIndex)" BasenameOnly="$(Xml2Doc_BasenameOnly)" AnchorAlgorithm="$(Xml2Doc_AnchorAlgorithm)" Fingerprint="$(_Xml2Doc_Fingerprint)" @@ -162,4 +163,4 @@ Condition="'$(Xml2Doc_DidWork)'=='true' or !Exists('$(Xml2Doc_OutputStamp)')" /> - \ No newline at end of file + diff --git a/Xml2Doc/tests/Xml2Doc.Tests/RenderSnapshots.cs b/Xml2Doc/tests/Xml2Doc.Tests/RenderSnapshots.cs index 3c24053..7bc789a 100644 --- a/Xml2Doc/tests/Xml2Doc.Tests/RenderSnapshots.cs +++ b/Xml2Doc/tests/Xml2Doc.Tests/RenderSnapshots.cs @@ -160,6 +160,23 @@ public async Task PerType_CleanNames_Basic() } } + [Fact] + public void PerType_GenerateIndexFalse_OmitsIndexFromPlanAndOutput() + { + var model = LoadFixtureModel(); + var options = DefaultOptions() with { GenerateIndex = false }; + var renderer = new MarkdownRenderer(model, options); + var outDir = Path.Combine(Path.GetTempPath(), "Xml2Doc.Tests", Path.GetRandomFileName()); + + var planned = renderer.PlanOutputs(outDir); + planned.ShouldNotContain(Path.Combine(Path.GetFullPath(outDir), "index.md")); + + renderer.RenderToDirectory(outDir); + + File.Exists(Path.Combine(outDir, "index.md")).ShouldBeFalse(); + Directory.GetFiles(outDir, "*.md", SearchOption.TopDirectoryOnly).ShouldNotBeEmpty(); + } + [Fact] public async Task Generic_BraceHandling_IsClean() { @@ -201,4 +218,4 @@ public async Task Generic_BraceHandling_IsClean() private static string Normalize(string s) => s.Replace("\r\n", "\n").Trim(); -} \ No newline at end of file +} diff --git a/docs/adr/ADR-011-generated-output-ownership.md b/docs/adr/ADR-011-generated-output-ownership.md new file mode 100644 index 0000000..4165e36 --- /dev/null +++ b/docs/adr/ADR-011-generated-output-ownership.md @@ -0,0 +1,40 @@ +# ADR‑011 — Generated Output Ownership and Lifecycle + +## Status + +Proposed + +## Context + +Per-type rendering writes type pages and `index.md` into one output directory. When independent +CLI or MSBuild invocations share that directory, each invocation has only its own XML model and +cannot deterministically aggregate the other projects' types. Concurrent builds therefore race to +replace `index.md`, and project-scoped cleanup cannot safely infer ownership of neighboring files. + +Generated Markdown is a public output contract. Index ownership and deletion boundaries must be +explicit before shared-directory aggregation or stale-output pruning can be considered safe. + +## Decision + +1. Core owns whether per-type rendering generates `index.md` through + `RendererOptions.GenerateIndex`. The default remains `true` for backward compatibility. +2. CLI and MSBuild expose the same option. Independent invocations sharing an output directory must + disable index generation and delegate the repository-level index to a separate aggregation step. +3. A project-scoped invocation owns only the paths in its deterministic output plan. Existing files + discovered in the output directory are not implicitly owned by that invocation. +4. Future stale-output pruning must use an explicit, invocation-scoped manifest. It may delete only + paths recorded by the same manifest identity, and only after successful generation. +5. Manifest and output replacement must be atomic. Reports and dry runs must list planned writes and + deletions in ordinal order. +6. Full multi-input aggregation is a separate capability. It must consume all inputs in one logical + operation and produce one canonically ordered index; concurrent last-writer-wins merging is not a + supported aggregation strategy. + +## Consequences + +- Existing single-project behavior is unchanged. +- Shared-directory consumers gain a deterministic configuration that prevents index corruption, but + must provide their own aggregation step when a combined index is required. +- Host configuration remains a projection of Core behavior rather than host-specific rendering. +- Safe stale-file cleanup can be implemented without treating hand-authored or other-project files as + Xml2Doc-owned content. diff --git a/docs/adr/README.md b/docs/adr/README.md index 2f9c5a0..684f9d1 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -18,3 +18,4 @@ These records explain **why architectural decisions were made**. | ADR‑008 | Multi‑target compatibility | Accepted | | ADR‑009 | Structured diagnostics | Proposed | | ADR‑010 | Pluggable anchor algorithms | Proposed | +| ADR‑011 | Generated output ownership | Proposed | diff --git a/docs/roadmap.md b/docs/roadmap.md index 2e90afb..8d6c10e 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -127,6 +127,7 @@ Focus areas: • report output • multi‑target compatibility • improved test infrastructure +• explicit ownership controls for shared output directories Architectural themes: @@ -137,6 +138,7 @@ Related ADRs: • ADR‑007 MSBuild incremental generation • ADR‑008 Multi‑target compatibility +• ADR‑011 Generated output ownership and lifecycle --- From 766397785594614cd95a0b731d37441f6fb0a2f7 Mon Sep 17 00:00:00 2001 From: Jeff Patton Date: Sat, 1 Aug 2026 11:36:16 -0500 Subject: [PATCH 2/3] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- Xml2Doc/src/Xml2Doc.MSBuild/GenerateMarkdownFromXmlDoc.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Xml2Doc/src/Xml2Doc.MSBuild/GenerateMarkdownFromXmlDoc.cs b/Xml2Doc/src/Xml2Doc.MSBuild/GenerateMarkdownFromXmlDoc.cs index 999b7e8..09779b0 100644 --- a/Xml2Doc/src/Xml2Doc.MSBuild/GenerateMarkdownFromXmlDoc.cs +++ b/Xml2Doc/src/Xml2Doc.MSBuild/GenerateMarkdownFromXmlDoc.cs @@ -286,11 +286,9 @@ public override bool Execute() DidWork = true; } - GeneratedFiles = DryRun - ? Array.Empty() - : renderer.PlanOutputs(outDir) - .Select(p => (ITaskItem)new TaskItem(p)) - .ToArray(); +GeneratedFiles = renderer.PlanOutputs(outDir) + .Select(p => (ITaskItem)new TaskItem(p)) + .ToArray(); Log.LogMessage(MessageImportance.High, $"Xml2Doc {(DryRun ? "[dry-run] would write" : "wrote")} Markdown files to {outDir}"); } From ecbc6393a7a66589c3650bbf2dc95b32255be788 Mon Sep 17 00:00:00 2001 From: Jeffrey Patton Date: Sat, 1 Aug 2026 12:05:44 -0500 Subject: [PATCH 3/3] Add tests for GenerateMarkdownFromXmlDoc MSBuild task Added package and project references to enable testing. Introduced GenerateMarkdownFromXmlDocTests to verify dry-run behavior, ensuring correct reporting of planned Markdown outputs and index file generation based on the GenerateIndex flag. --- .../GenerateMarkdownFromXmlDocTests.cs | 101 ++++++++++++++++++ .../tests/Xml2Doc.Tests/Xml2Doc.Tests.csproj | 3 + 2 files changed, 104 insertions(+) create mode 100644 Xml2Doc/tests/Xml2Doc.Tests/GenerateMarkdownFromXmlDocTests.cs diff --git a/Xml2Doc/tests/Xml2Doc.Tests/GenerateMarkdownFromXmlDocTests.cs b/Xml2Doc/tests/Xml2Doc.Tests/GenerateMarkdownFromXmlDocTests.cs new file mode 100644 index 0000000..5a11d3f --- /dev/null +++ b/Xml2Doc/tests/Xml2Doc.Tests/GenerateMarkdownFromXmlDocTests.cs @@ -0,0 +1,101 @@ +using Microsoft.Build.Framework; +using Shouldly; +using System; +using System.Collections; +using System.IO; +using System.Linq; +using Xml2Doc.Core; +using Xml2Doc.MSBuild; +using Xunit; + +namespace Xml2Doc.Tests +{ + public class GenerateMarkdownFromXmlDocTests + { + private sealed class TestBuildEngine : IBuildEngine + { + public bool ContinueOnError => false; + public int LineNumberOfTaskNode => 0; + public int ColumnNumberOfTaskNode => 0; + public string ProjectFileOfTaskNode => string.Empty; + + public void LogErrorEvent(BuildErrorEventArgs e) { } + public void LogWarningEvent(BuildWarningEventArgs e) { } + public void LogMessageEvent(BuildMessageEventArgs e) { } + public void LogCustomEvent(CustomBuildEventArgs e) { } + + public bool BuildProjectFile( + string projectFileName, + string[] targetNames, + IDictionary globalProperties, + IDictionary targetOutputs) => true; + } + + private static readonly string SampleXml = + Path.Combine( + AppContext.BaseDirectory, + "Xml2Doc.Sample.xml"); + + private static RendererOptions DefaultOptions() => new( + FileNameMode: FileNameMode.CleanGenerics, + RootNamespaceToTrim: "Xml2Doc.Sample", + CodeBlockLanguage: "csharp", + TrimRootNamespaceInFileNames: true + ); + + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void PerType_DryRun_ReportsPlannedFilesWithoutWriting(bool generateIndex) + { + var outDir = Path.Combine( + Path.GetTempPath(), + "Xml2Doc.Tests", + Path.GetRandomFileName()); + + var task = new GenerateMarkdownFromXmlDoc + { + BuildEngine = new TestBuildEngine(), + XmlPath = SampleXml, + OutputDirectory = outDir, + SingleFile = false, + DryRun = true, + GenerateIndex = generateIndex, + FileNameMode = "clean", + RootNamespaceToTrim = "Xml2Doc.Sample", + TrimRootNamespaceInFileNames = true + }; + + task.Execute().ShouldBeTrue(); + task.DidWork.ShouldBeFalse(); + + var model = Xml2Doc.Core.Models.Xml2Doc.Load(SampleXml); + var renderer = new MarkdownRenderer( + model, + DefaultOptions() with { GenerateIndex = generateIndex }); + + var expected = renderer.PlanOutputs(outDir) + .OrderBy(path => path, StringComparer.Ordinal) + .ToArray(); + + var actual = task.GeneratedFiles + .Select(item => item.ItemSpec) + .OrderBy(path => path, StringComparer.Ordinal) + .ToArray(); + + actual.ShouldBe(expected); + + actual.Any(path => + string.Equals( + Path.GetFileName(path), + "index.md", + StringComparison.OrdinalIgnoreCase)) + .ShouldBe(generateIndex); + + Directory.Exists(outDir).ShouldBeFalse( + "Dry-run must not create the output directory or Markdown files."); + } + + } +} diff --git a/Xml2Doc/tests/Xml2Doc.Tests/Xml2Doc.Tests.csproj b/Xml2Doc/tests/Xml2Doc.Tests/Xml2Doc.Tests.csproj index 48fe1ca..20ea571 100644 --- a/Xml2Doc/tests/Xml2Doc.Tests/Xml2Doc.Tests.csproj +++ b/Xml2Doc/tests/Xml2Doc.Tests/Xml2Doc.Tests.csproj @@ -14,9 +14,12 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + + +