diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index cd1decd5a7c..b778fecb521 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -5,6 +5,16 @@ on:
push:
branches: [ main, feature/*, hotfix/* ]
workflow_dispatch:
+ inputs:
+ seed:
+ description: 'Enter an integer value between 0 and 2147483647.'
+ type: number
+ required: true
+ default: 0
+
+env:
+ seed: ${{ inputs.seed || github.run_number }}
+ NO_COLOR: true
jobs:
test:
@@ -31,26 +41,43 @@ jobs:
shell: bash
working-directory: templates
- - run: dotnet test -c Release -f net8.0 --no-build --collect:"XPlat Code Coverage" --consoleLoggerParameters:"Summary;Verbosity=Minimal"
+ - name: Install Chromium headless shell
+ shell: pwsh
+ working-directory: src/docfx/bin/Release/net8.0
+ run: |
+ $env:PLAYWRIGHT_NODEJS_PATH = (Get-Command node).Path
+ ./playwright.ps1 install chromium --only-shell
+
+ - name: Install `dotnet-coverage` as .NET Global Tool
+ run: dotnet tool install -g dotnet-coverage
+
+ - name: Start dotnet-coverage with background server mode
+ run: dotnet coverage collect --session-id docfx_coverage --settings test/CodeCoverage.runsettings --server-mode --background
+
+ - run: dotnet coverage connect docfx_coverage "dotnet test -c Release -f net8.0 --no-build -- --seed ${{ env.seed }}"
id: test-net80
- - run: dotnet test -c Release -f net9.0 --no-build --collect:"XPlat Code Coverage" --consoleLoggerParameters:"Summary;Verbosity=Minimal"
+ - run: dotnet coverage connect docfx_coverage "dotnet test -c Release -f net9.0 --no-build -- --seed ${{ env.seed }}"
if: matrix.os == 'ubuntu-latest'
id: test-net90
- run: npm i -g @percy/cli
if: matrix.os == 'ubuntu-latest'
- - run: percy exec -- dotnet test -c Release -f net8.0 --filter Stage=Percy --no-build --collect:"XPlat Code Coverage"
+ - run: dotnet coverage connect docfx_coverage "percy exec -- dotnet test -c Release -f net8.0 --no-build -- --filter-trait "Stage=Percy" --seed ${{ env.seed }}"
if: matrix.os == 'ubuntu-latest'
env:
PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }}
+ - name: Shutdown dotnet-coverage server.
+ run: dotnet coverage shutdown docfx_coverage --timeout 60000
+
- uses: codecov/codecov-action@v5
if: matrix.os == 'ubuntu-latest'
with:
fail_ci_if_error: false
token: ${{ secrets.CODECOV_TOKEN }}
+ directory: test/TestResults/code-coverages
- run: echo "DOTNET_DbgEnableMiniDump=1" >> $GITHUB_ENV
if: matrix.os == 'ubuntu-latest'
@@ -70,8 +97,10 @@ jobs:
name: logs-${{ matrix.os }}
path: |
msbuild.binlog
+ test/**/TestResults/*.log
test/**/TestResults/*.trx
test/**/TestResults/*.html
+ test/**/TestResults/*.ctrf
- uses: actions/upload-artifact@v4
if: ${{ failure() && matrix.os == 'ubuntu-latest' }}
diff --git a/docfx.sln b/docfx.sln
index a5974afe119..758358c6503 100644
--- a/docfx.sln
+++ b/docfx.sln
@@ -15,7 +15,9 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{926A0726-B806-4215-82EF-AF8E22D0FACF}"
ProjectSection(SolutionItems) = preProject
test\Directory.Build.props = test\Directory.Build.props
+ test\CodeCoverage.runsettings = test\CodeCoverage.runsettings
test\Directory.Packages.props = test\Directory.Packages.props
+ test\xunit.runner.json = test\xunit.runner.json
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "docfx", "src\docfx\docfx.csproj", "{EF53214F-BA98-4026-BEED-CF771865C312}"
diff --git a/samples/Directory.Packages.props b/samples/Directory.Packages.props
new file mode 100644
index 00000000000..8c119d5413b
--- /dev/null
+++ b/samples/Directory.Packages.props
@@ -0,0 +1,2 @@
+
+
diff --git a/test/.editorconfig b/test/.editorconfig
index aa113483a11..e93fef60884 100644
--- a/test/.editorconfig
+++ b/test/.editorconfig
@@ -10,3 +10,5 @@ dotnet_diagnostic.CA1861.severity = none # CA1861: Avoid constant arrays as argu
dotnet_diagnostic.CA1869.severity = none # CA1869: Cache and reuse 'JsonSerializerOptions' instances
dotnet_diagnostic.SYSLIB1045.severity = silent # SYSLIB1045: Convert to 'GeneratedRegexAttribute'.
+
+indent_size = 2
diff --git a/test/CodeCoverage.runsettings b/test/CodeCoverage.runsettings
new file mode 100644
index 00000000000..2538b8bcbb3
--- /dev/null
+++ b/test/CodeCoverage.runsettings
@@ -0,0 +1,47 @@
+
+
+
+ coverage.cobertura.xml
+ cobertura
+ false
+ true
+ true
+
+
+
+ .*Docfx.*\.dll$
+
+
+
+ .*Docfx\.Tests\.Common\.dll$
+ .*\.Tests\.dll$
+
+
+ .*ICSharpCode\.Decompiler\.dll$
+ .*Spectre\.Console\.Cli\.dll$
+ .*Spectre\.Console\.dll$
+ .*DiffEngine\.dll$
+
+
+ .*Argon\.dll$
+ .*EmptyFiles\.dll$
+ .*Verify\.dll$
+ .*Verify\.DiffPlex\.dll$
+ .*Verify\.XunitV3\.dll$
+
+
+
+
+ ^System\.CodeDom\.Compiler\.GeneratedCodeAttribute$
+
+
+
+
+ .*\\[^\\]*\.g\.cs
+
+
+
+ False
+ False
+
+
diff --git a/test/Directory.Build.props b/test/Directory.Build.props
index a0b1fcf254b..5c598c1b34f 100644
--- a/test/Directory.Build.props
+++ b/test/Directory.Build.props
@@ -1,37 +1,82 @@
+
+
false
en
+
-
false
-
+
+
+
+ true
-
-
-
+
+ true
+
+ false
+
+
+ true
+
+
+ $(TestingPlatformCommandLineArguments) --xunit-info
+
+
+ $(TestingPlatformCommandLineArguments) --results-directory "$(MSBuildThisFileDirectory)TestResults"
+
+
+ $(TestingPlatformCommandLineArguments) --ignore-exit-code 8
+
+
+ $(TestingPlatformCommandLineArguments) --output Detailed
+
+
+
+
+
+ $(TestingPlatformCommandLineArguments) --no-progress
+
+
+ $(TestingPlatformCommandLineArguments) --report-xunit-trx --report-xunit-trx-filename TestResults-$(MSBuildProjectName)-$(TargetFramework)-$(RUNNER_OS).trx
+ $(TestingPlatformCommandLineArguments) --report-xunit-html --report-xunit-html-filename TestResults-$(MSBuildProjectName)-$(TargetFramework)-$(RUNNER_OS).html
+ $(TestingPlatformCommandLineArguments) --report-ctrf --report-ctrf-filename TestResults-$(MSBuildProjectName)-$(TargetFramework)-$(RUNNER_OS).ctrf
+
+
+
+
$(MSBuildThisFileDirectory)TestResults
$(VSTestLogger);trx%3BLogFileName=TestResults-$(MSBuildProjectName)-$(TargetFramework)-$(RUNNER_OS).trx
$(VSTestLogger);html%3BLogFileName=TestResults-$(MSBuildProjectName)-$(TargetFramework)-$(RUNNER_OS).html
+
+
+
+
+
+
+
+
-
-
+
+
+
-
-
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
diff --git a/test/Directory.Packages.props b/test/Directory.Packages.props
index a7cb66a9a3f..441713d7485 100644
--- a/test/Directory.Packages.props
+++ b/test/Directory.Packages.props
@@ -4,14 +4,16 @@
-
-
-
-
+
+
+
+
-
+
+
-
+
+
diff --git a/test/Docfx.Build.Common.Tests/Docfx.Build.Common.Tests.csproj b/test/Docfx.Build.Common.Tests/Docfx.Build.Common.Tests.csproj
index 474324b4f13..bb3b4778aff 100644
--- a/test/Docfx.Build.Common.Tests/Docfx.Build.Common.Tests.csproj
+++ b/test/Docfx.Build.Common.Tests/Docfx.Build.Common.Tests.csproj
@@ -1,4 +1,9 @@
+
+ Exe
+ true
+
+
diff --git a/test/Docfx.Build.ManagedReference.Tests/Docfx.Build.ManagedReference.Tests.csproj b/test/Docfx.Build.ManagedReference.Tests/Docfx.Build.ManagedReference.Tests.csproj
index d2189eb119f..30e7860249f 100644
--- a/test/Docfx.Build.ManagedReference.Tests/Docfx.Build.ManagedReference.Tests.csproj
+++ b/test/Docfx.Build.ManagedReference.Tests/Docfx.Build.ManagedReference.Tests.csproj
@@ -1,4 +1,9 @@
+
+ Exe
+ true
+
+
diff --git a/test/Docfx.Build.OverwriteDocuments.Tests/Docfx.Build.OverwriteDocuments.Tests.csproj b/test/Docfx.Build.OverwriteDocuments.Tests/Docfx.Build.OverwriteDocuments.Tests.csproj
index 8e3c524d874..32b32b030e8 100644
--- a/test/Docfx.Build.OverwriteDocuments.Tests/Docfx.Build.OverwriteDocuments.Tests.csproj
+++ b/test/Docfx.Build.OverwriteDocuments.Tests/Docfx.Build.OverwriteDocuments.Tests.csproj
@@ -1,7 +1,13 @@
+
+ Exe
+ true
+
+
+
diff --git a/test/Docfx.Build.RestApi.Tests/Docfx.Build.RestApi.Tests.csproj b/test/Docfx.Build.RestApi.Tests/Docfx.Build.RestApi.Tests.csproj
index e62f8103d22..6998b9a05bc 100644
--- a/test/Docfx.Build.RestApi.Tests/Docfx.Build.RestApi.Tests.csproj
+++ b/test/Docfx.Build.RestApi.Tests/Docfx.Build.RestApi.Tests.csproj
@@ -1,4 +1,9 @@
+
+ Exe
+ true
+
+
diff --git a/test/Docfx.Build.RestApi.WithPlugins.Tests/Docfx.Build.RestApi.WithPlugins.Tests.csproj b/test/Docfx.Build.RestApi.WithPlugins.Tests/Docfx.Build.RestApi.WithPlugins.Tests.csproj
index 610b6f5bc54..ab628918463 100644
--- a/test/Docfx.Build.RestApi.WithPlugins.Tests/Docfx.Build.RestApi.WithPlugins.Tests.csproj
+++ b/test/Docfx.Build.RestApi.WithPlugins.Tests/Docfx.Build.RestApi.WithPlugins.Tests.csproj
@@ -1,4 +1,9 @@
+
+ Exe
+ true
+
+
diff --git a/test/Docfx.Build.SchemaDriven.Tests/Docfx.Build.SchemaDriven.Tests.csproj b/test/Docfx.Build.SchemaDriven.Tests/Docfx.Build.SchemaDriven.Tests.csproj
index 474324b4f13..bb3b4778aff 100644
--- a/test/Docfx.Build.SchemaDriven.Tests/Docfx.Build.SchemaDriven.Tests.csproj
+++ b/test/Docfx.Build.SchemaDriven.Tests/Docfx.Build.SchemaDriven.Tests.csproj
@@ -1,4 +1,9 @@
+
+ Exe
+ true
+
+
diff --git a/test/Docfx.Build.Tests/Docfx.Build.Tests.csproj b/test/Docfx.Build.Tests/Docfx.Build.Tests.csproj
index 7a73d0fa005..950982929fa 100644
--- a/test/Docfx.Build.Tests/Docfx.Build.Tests.csproj
+++ b/test/Docfx.Build.Tests/Docfx.Build.Tests.csproj
@@ -1,4 +1,9 @@
+
+ Exe
+ true
+
+
diff --git a/test/Docfx.Build.Tests/ExtractSearchIndexFromHtmlTest.cs b/test/Docfx.Build.Tests/ExtractSearchIndexFromHtmlTest.cs
index 478ce955538..8a787418472 100644
--- a/test/Docfx.Build.Tests/ExtractSearchIndexFromHtmlTest.cs
+++ b/test/Docfx.Build.Tests/ExtractSearchIndexFromHtmlTest.cs
@@ -284,7 +284,7 @@ This is article title
manifest.Files.Add(manifestItem);
// process the fake manifest, using tempTestFolder as the output folder
- _extractor.Process(manifest, tempTestFolder);
+ _extractor.Process(manifest, tempTestFolder, TestContext.Current.CancellationToken);
var expectedIndexJSON = @"{
""index.html"": {
diff --git a/test/Docfx.Build.Tests/PostProcessors/SitemapGeneratorTests.cs b/test/Docfx.Build.Tests/PostProcessors/SitemapGeneratorTests.cs
index 68860158bfb..8178cab569e 100644
--- a/test/Docfx.Build.Tests/PostProcessors/SitemapGeneratorTests.cs
+++ b/test/Docfx.Build.Tests/PostProcessors/SitemapGeneratorTests.cs
@@ -50,7 +50,7 @@ public void TestSitemapGenerator()
var sitemapPath = Path.Combine(outputFolder, "sitemap.xml");
// Act
- manifest = sitemapGenerator.Process(manifest, outputFolder);
+ manifest = sitemapGenerator.Process(manifest, outputFolder, TestContext.Current.CancellationToken);
// Assert
Assert.Equal("https://example.com/", manifest.Sitemap.BaseUrl);
diff --git a/test/Docfx.Build.Tests/RemoveDebugInfoTest.cs b/test/Docfx.Build.Tests/RemoveDebugInfoTest.cs
index 588155d564b..e86739f3176 100644
--- a/test/Docfx.Build.Tests/RemoveDebugInfoTest.cs
+++ b/test/Docfx.Build.Tests/RemoveDebugInfoTest.cs
@@ -43,7 +43,7 @@ public void TestBasicFeature()
new HtmlPostProcessor
{
Handlers = { new RemoveDebugInfo() }
- }.Process(manifest, _outputFolder);
+ }.Process(manifest, _outputFolder, TestContext.Current.CancellationToken);
var actual = File.ReadAllText(Path.Combine(_outputFolder, "a.html"));
Assert.Equal("sectionMicrosoft Bing
", actual);
diff --git a/test/Docfx.Build.Tests/ValidateBookmarkTest.cs b/test/Docfx.Build.Tests/ValidateBookmarkTest.cs
index 24cc7edf5de..72d155b67df 100644
--- a/test/Docfx.Build.Tests/ValidateBookmarkTest.cs
+++ b/test/Docfx.Build.Tests/ValidateBookmarkTest.cs
@@ -63,7 +63,7 @@ public void TestBasicFeature()
new HtmlPostProcessor
{
Handlers = { new ValidateBookmark() }
- }.Process(manifest, _outputFolder);
+ }.Process(manifest, _outputFolder, TestContext.Current.CancellationToken);
}
finally
{
@@ -107,7 +107,7 @@ public void TestNoCheck()
new HtmlPostProcessor
{
Handlers = { new ValidateBookmark() }
- }.Process(manifest, _outputFolder);
+ }.Process(manifest, _outputFolder, TestContext.Current.CancellationToken);
}
finally
{
@@ -150,7 +150,7 @@ public void TestLinkThatContainsNonAsciiChars()
new HtmlPostProcessor
{
Handlers = { new ValidateBookmark() }
- }.Process(manifest, _outputFolder);
+ }.Process(manifest, _outputFolder, TestContext.Current.CancellationToken);
}
finally
{
diff --git a/test/Docfx.Build.Tests/XRefArchiveBuilderTest.cs b/test/Docfx.Build.Tests/XRefArchiveBuilderTest.cs
index 65314446a06..6a721e57e75 100644
--- a/test/Docfx.Build.Tests/XRefArchiveBuilderTest.cs
+++ b/test/Docfx.Build.Tests/XRefArchiveBuilderTest.cs
@@ -20,7 +20,7 @@ public async Task TestDownload()
// sorted: true
// references: []
// ```
- Assert.True(await builder.DownloadAsync(new Uri("http://dotnet.github.io/docfx/xrefmap.yml"), ZipFile));
+ Assert.True(await builder.DownloadAsync(new Uri("http://dotnet.github.io/docfx/xrefmap.yml"), ZipFile, TestContext.Current.CancellationToken));
using (var xar = XRefArchive.Open(ZipFile, XRefArchiveMode.Read))
{
diff --git a/test/Docfx.Build.Tests/XRefMapDownloaderTest.cs b/test/Docfx.Build.Tests/XRefMapDownloaderTest.cs
index 04f21141a85..977eb64fb13 100644
--- a/test/Docfx.Build.Tests/XRefMapDownloaderTest.cs
+++ b/test/Docfx.Build.Tests/XRefMapDownloaderTest.cs
@@ -12,7 +12,7 @@ public class XRefMapDownloadTest
public async Task BaseUrlIsSet()
{
var downloader = new XRefMapDownloader();
- var xrefs = await downloader.DownloadAsync(new Uri("https://dotnet.github.io/docfx/xrefmap.yml")) as XRefMap;
+ var xrefs = await downloader.DownloadAsync(new Uri("https://dotnet.github.io/docfx/xrefmap.yml"), TestContext.Current.CancellationToken) as XRefMap;
Assert.NotNull(xrefs);
Assert.Equal("https://dotnet.github.io/docfx/", xrefs.BaseUrl);
}
@@ -26,7 +26,7 @@ public async Task ReadLocalXRefMapWithFallback()
// Get fallback TestData/xrefmap.yml which contains uid: 'str'
var reader = await new XRefCollection(from u in xrefmaps
- select new Uri(u, UriKind.RelativeOrAbsolute)).GetReaderAsync(basePath, fallbackFolders);
+ select new Uri(u, UriKind.RelativeOrAbsolute)).GetReaderAsync(basePath, fallbackFolders, TestContext.Current.CancellationToken);
var xrefSpec = reader.Find("str");
Assert.NotNull(xrefSpec);
@@ -40,7 +40,7 @@ public async Task ReadLocalXRefMapJsonFileTest()
var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "xrefmap.json");
var downloader = new XRefMapDownloader();
- var xrefMap = await downloader.DownloadAsync(new Uri(path)) as XRefMap;
+ var xrefMap = await downloader.DownloadAsync(new Uri(path), TestContext.Current.CancellationToken) as XRefMap;
// Assert
xrefMap.Should().NotBeNull();
@@ -54,7 +54,7 @@ public async Task ReadLocalXRefMapGZippedJsonFileTest()
var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "xrefmap.json.gz");
var downloader = new XRefMapDownloader();
- var xrefMap = await downloader.DownloadAsync(new Uri(path)) as XRefMap;
+ var xrefMap = await downloader.DownloadAsync(new Uri(path), TestContext.Current.CancellationToken) as XRefMap;
// Assert
xrefMap.Should().NotBeNull();
@@ -68,7 +68,7 @@ public async Task ReadLocalXRefMapGZippedYamlFileTest()
var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "xrefmap.yml.gz");
var downloader = new XRefMapDownloader();
- var xrefMap = await downloader.DownloadAsync(new Uri(path)) as XRefMap;
+ var xrefMap = await downloader.DownloadAsync(new Uri(path), TestContext.Current.CancellationToken) as XRefMap;
// Assert
xrefMap.Should().NotBeNull();
@@ -85,7 +85,7 @@ public async Task ReadRemoteXRefMapYamlFileTest1()
var path = "https://horizongir.github.io/ZedGraph/xrefmap.yml";
var downloader = new XRefMapDownloader();
- var xrefMap = await downloader.DownloadAsync(new Uri(path)) as XRefMap;
+ var xrefMap = await downloader.DownloadAsync(new Uri(path), TestContext.Current.CancellationToken) as XRefMap;
// Assert
xrefMap.Sorted.Should().BeTrue();
@@ -114,7 +114,7 @@ public async Task ReadRemoteXRefMapJsonFileTest2()
var path = "https://normanderwan.github.io/UnityXrefMaps/xrefmap.yml";
var downloader = new XRefMapDownloader();
- var xrefMap = await downloader.DownloadAsync(new Uri(path)) as XRefMap;
+ var xrefMap = await downloader.DownloadAsync(new Uri(path), TestContext.Current.CancellationToken) as XRefMap;
// Assert
xrefMap.Sorted.Should().BeTrue();
diff --git a/test/Docfx.Build.UniversalReference.Tests/Docfx.Build.UniversalReference.Tests.csproj b/test/Docfx.Build.UniversalReference.Tests/Docfx.Build.UniversalReference.Tests.csproj
index 286e7730784..e08d7de69ac 100644
--- a/test/Docfx.Build.UniversalReference.Tests/Docfx.Build.UniversalReference.Tests.csproj
+++ b/test/Docfx.Build.UniversalReference.Tests/Docfx.Build.UniversalReference.Tests.csproj
@@ -1,7 +1,8 @@
-
-
-
+
+ Exe
+ true
+
diff --git a/test/Docfx.Common.Tests/Docfx.Common.Tests.csproj b/test/Docfx.Common.Tests/Docfx.Common.Tests.csproj
index ba18c40f876..b2fae75fa55 100644
--- a/test/Docfx.Common.Tests/Docfx.Common.Tests.csproj
+++ b/test/Docfx.Common.Tests/Docfx.Common.Tests.csproj
@@ -1,4 +1,9 @@
+
+ Exe
+ true
+
+
diff --git a/test/Docfx.Dotnet.Tests/Docfx.Dotnet.Tests.csproj b/test/Docfx.Dotnet.Tests/Docfx.Dotnet.Tests.csproj
index b5042bdb201..5a2b0a5e350 100644
--- a/test/Docfx.Dotnet.Tests/Docfx.Dotnet.Tests.csproj
+++ b/test/Docfx.Dotnet.Tests/Docfx.Dotnet.Tests.csproj
@@ -1,4 +1,9 @@
+
+ Exe
+ true
+
+
diff --git a/test/Docfx.Dotnet.Tests/GenerateMetadataFromAssemblyTest.cs b/test/Docfx.Dotnet.Tests/GenerateMetadataFromAssemblyTest.cs
index 9ef41b066c9..eceb01ef205 100644
--- a/test/Docfx.Dotnet.Tests/GenerateMetadataFromAssemblyTest.cs
+++ b/test/Docfx.Dotnet.Tests/GenerateMetadataFromAssemblyTest.cs
@@ -14,7 +14,7 @@ public void TestGenerateMetadataFromAssembly()
{
{
var (compilation, assembly) = CompilationHelper.CreateCompilationFromAssembly("TestData/CatLibrary.dll");
- Assert.Empty(compilation.GetDeclarationDiagnostics());
+ Assert.Empty(compilation.GetDeclarationDiagnostics(TestContext.Current.CancellationToken));
var output = assembly.GenerateMetadataItem(compilation);
var @class = output.Items[0].Items[2];
@@ -26,7 +26,7 @@ public void TestGenerateMetadataFromAssembly()
{
var (compilation, assembly) = CompilationHelper.CreateCompilationFromAssembly("TestData/CatLibrary2.dll");
- Assert.Empty(compilation.GetDeclarationDiagnostics());
+ Assert.Empty(compilation.GetDeclarationDiagnostics(TestContext.Current.CancellationToken));
var output = assembly.GenerateMetadataItem(compilation);
var @class = output.Items[0].Items[0];
@@ -40,7 +40,7 @@ public void TestGenerateMetadataFromAssembly()
public void TestGenerateMetadataFromAssemblyWithReferences()
{
var (compilation, assembly) = CompilationHelper.CreateCompilationFromAssembly("TestData/TupleLibrary.dll");
- Assert.Empty(compilation.GetDeclarationDiagnostics());
+ Assert.Empty(compilation.GetDeclarationDiagnostics(TestContext.Current.CancellationToken));
var output = assembly.GenerateMetadataItem(compilation);
var @class = output.Items[0].Items[0];
diff --git a/test/Docfx.Glob.Tests/Docfx.Glob.Tests.csproj b/test/Docfx.Glob.Tests/Docfx.Glob.Tests.csproj
index 659823ee298..2555c8035df 100644
--- a/test/Docfx.Glob.Tests/Docfx.Glob.Tests.csproj
+++ b/test/Docfx.Glob.Tests/Docfx.Glob.Tests.csproj
@@ -1,4 +1,9 @@
+
+ Exe
+ true
+
+
diff --git a/test/Docfx.MarkdigEngine.Extensions.Tests/Docfx.MarkdigEngine.Extensions.Tests.csproj b/test/Docfx.MarkdigEngine.Extensions.Tests/Docfx.MarkdigEngine.Extensions.Tests.csproj
index 6bb49f20177..cd1f24cfa95 100644
--- a/test/Docfx.MarkdigEngine.Extensions.Tests/Docfx.MarkdigEngine.Extensions.Tests.csproj
+++ b/test/Docfx.MarkdigEngine.Extensions.Tests/Docfx.MarkdigEngine.Extensions.Tests.csproj
@@ -1,4 +1,9 @@
+
+ Exe
+ true
+
+
diff --git a/test/Docfx.MarkdigEngine.Tests/Docfx.MarkdigEngine.Tests.csproj b/test/Docfx.MarkdigEngine.Tests/Docfx.MarkdigEngine.Tests.csproj
index a90ff20f42d..4ab69e66b58 100644
--- a/test/Docfx.MarkdigEngine.Tests/Docfx.MarkdigEngine.Tests.csproj
+++ b/test/Docfx.MarkdigEngine.Tests/Docfx.MarkdigEngine.Tests.csproj
@@ -1,4 +1,9 @@
+
+ Exe
+ true
+
+
diff --git a/test/Docfx.Tests.Common/Docfx.Tests.Common.csproj b/test/Docfx.Tests.Common/Docfx.Tests.Common.csproj
index 230ab4eeb47..6b3ab1a352a 100644
--- a/test/Docfx.Tests.Common/Docfx.Tests.Common.csproj
+++ b/test/Docfx.Tests.Common/Docfx.Tests.Common.csproj
@@ -1,5 +1,6 @@
+ Library
false
@@ -11,4 +12,10 @@
+
+
+
+
+
+
diff --git a/test/docfx.Snapshot.Tests/Attributes/SetBranchNameAttribute.cs b/test/docfx.Snapshot.Tests/Attributes/SetBranchNameAttribute.cs
new file mode 100644
index 00000000000..43aef3a6172
--- /dev/null
+++ b/test/docfx.Snapshot.Tests/Attributes/SetBranchNameAttribute.cs
@@ -0,0 +1,23 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Reflection;
+using Xunit.v3;
+
+namespace Docfx.Tests;
+
+internal class UseCustomBranchNameAttribute(string branchName) : BeforeAfterTestAttribute
+{
+ public override void Before(MethodInfo methodUnderTest, IXunitTest test)
+ {
+ if (test.TestCase.TestCollection.TestCollectionDisplayName != "docfx STA")
+ throw new InvalidOperationException(@"UseCustomBranchNameAttribute change global context. Use `[Collection(""docfx STA"")]` to avoid parallel test executions.");
+
+ Environment.SetEnvironmentVariable("DOCFX_SOURCE_BRANCH_NAME", branchName);
+ }
+
+ public override void After(MethodInfo methodUnderTest, IXunitTest test)
+ {
+ Environment.SetEnvironmentVariable("DOCFX_SOURCE_BRANCH_NAME", null);
+ }
+}
diff --git a/test/docfx.Snapshot.Tests/PercyTest.cs b/test/docfx.Snapshot.Tests/PercyTest.cs
index 9528ebe1ed6..ccdba5d9e78 100644
--- a/test/docfx.Snapshot.Tests/PercyTest.cs
+++ b/test/docfx.Snapshot.Tests/PercyTest.cs
@@ -1,7 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System.Diagnostics;
using System.Net.Http.Json;
using System.Net.NetworkInformation;
using System.Text.RegularExpressions;
@@ -43,13 +42,13 @@ static PercyTest()
}
[PercyFact]
+ [UseCustomBranchName("main")]
public async Task SeedHtml()
{
var samplePath = $"{s_samplesDir}/seed";
Clean(samplePath);
- using var process = Process.Start("dotnet", $"build \"{s_samplesDir}/seed/dotnet/assembly/BuildFromAssembly.csproj\"");
- await process.WaitForExitAsync();
+ Exec("dotnet", $"build \"{s_samplesDir}/seed/dotnet/assembly/BuildFromAssembly.csproj\"");
var docfxPath = Path.GetFullPath(OperatingSystem.IsWindows() ? "docfx.exe" : "docfx");
Assert.Equal(0, Exec(docfxPath, $"metadata {samplePath}/docfx.json"));
@@ -115,13 +114,14 @@ private static async Task PercySnapshot(IPage page, string name)
private static int Exec(string filename, string args, string workingDirectory = null)
{
- var psi = new ProcessStartInfo(filename, args);
- psi.EnvironmentVariables.Add("DOCFX_SOURCE_BRANCH_NAME", "main");
- if (workingDirectory != null)
- psi.WorkingDirectory = Path.GetFullPath(workingDirectory);
- using var process = Process.Start(psi);
- process.WaitForExit();
- return process.ExitCode;
+ var execTask = ProcessHelper.ExecAsync(
+ filename,
+ args,
+ workingDirectory,
+ environmentVariables: [],
+ TestContext.Current.CancellationToken);
+
+ return execTask.GetAwaiter().GetResult();
}
private static void Clean(string samplePath)
diff --git a/test/docfx.Snapshot.Tests/SamplesTest.cs b/test/docfx.Snapshot.Tests/SamplesTest.cs
index d5f8ae8212c..b6e8a9b02fc 100644
--- a/test/docfx.Snapshot.Tests/SamplesTest.cs
+++ b/test/docfx.Snapshot.Tests/SamplesTest.cs
@@ -49,6 +49,7 @@ public SamplesFactAttribute()
}
[SamplesFact]
+ [UseCustomBranchName("main")]
public async Task Seed()
{
var samplePath = $"{s_samplesDir}/seed";
@@ -60,7 +61,6 @@ public async Task Seed()
if (Debugger.IsAttached || IsWslRemoteTest())
{
- Environment.SetEnvironmentVariable("DOCFX_SOURCE_BRANCH_NAME", "main");
Assert.Equal(0, Program.Main([$"{samplePath}/docfx.json"]));
}
else
@@ -116,6 +116,7 @@ object ToBookmarks(IEnumerable nodes)
}
[SamplesFact]
+ [UseCustomBranchName("main")]
public async Task SeedMarkdown()
{
var samplePath = $"{s_samplesDir}/seed";
@@ -129,32 +130,28 @@ public async Task SeedMarkdown()
}
[SamplesFact]
+ [UseCustomBranchName("main")]
public async Task CSharp()
{
var samplePath = $"{s_samplesDir}/csharp";
Clean(samplePath);
- Environment.SetEnvironmentVariable("DOCFX_SOURCE_BRANCH_NAME", "main");
-
- try
- {
- await DotnetApiCatalog.GenerateManagedReferenceYamlFiles($"{samplePath}/docfx.json");
- await Docset.Build($"{samplePath}/docfx.json");
- }
- finally
- {
- Environment.SetEnvironmentVariable("DOCFX_SOURCE_BRANCH_NAME", null);
- }
+ await DotnetApiCatalog.GenerateManagedReferenceYamlFiles($"{samplePath}/docfx.json");
+ await Docset.Build($"{samplePath}/docfx.json");
await VerifyDirectory($"{samplePath}/_site", IncludeFile).AutoVerify(includeBuildServer: false);
}
[SamplesFact]
+ [UseCustomBranchName("main")]
public Task Extensions()
{
var samplePath = $"{s_samplesDir}/extensions";
Clean(samplePath);
+ // On some specific environment. `dotnet build` command takes about 15 minutes.
+ // So adding `-nodeReuse:false` parameter to resolve issue (https://github.com/dotnet/sdk/issues/9452)
+ // Additionaly use `--no-dependencies` parameter to suppress dependent projects build.
#if DEBUG
using var process = Process.Start("dotnet", $"build \"{samplePath}/build\"");
process.WaitForExit();
@@ -172,13 +169,14 @@ public Task Extensions()
private static int Exec(string filename, string args, string workingDirectory = null)
{
- var psi = new ProcessStartInfo(filename, args);
- psi.EnvironmentVariables.Add("DOCFX_SOURCE_BRANCH_NAME", "main");
- if (workingDirectory != null)
- psi.WorkingDirectory = Path.GetFullPath(workingDirectory);
- using var process = Process.Start(psi);
- process.WaitForExit();
- return process.ExitCode;
+ var execTask = ProcessHelper.ExecAsync(
+ filename,
+ args,
+ workingDirectory,
+ environmentVariables: [],
+ TestContext.Current.CancellationToken);
+
+ return execTask.GetAwaiter().GetResult();
}
private static void Clean(string samplePath)
diff --git a/test/docfx.Snapshot.Tests/Utilities/ProcessHelper.cs b/test/docfx.Snapshot.Tests/Utilities/ProcessHelper.cs
new file mode 100644
index 00000000000..8c468524067
--- /dev/null
+++ b/test/docfx.Snapshot.Tests/Utilities/ProcessHelper.cs
@@ -0,0 +1,115 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+
+namespace Docfx.Tests;
+
+internal static class ProcessHelper
+{
+ public static async ValueTask ExecAsync(
+ string filename,
+ string args,
+ string workingDirectory = null,
+ KeyValuePair[] environmentVariables = null,
+ CancellationToken cancellationToken = default)
+ {
+ var psi = new ProcessStartInfo(filename, args)
+ {
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ ErrorDialog = false,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ RedirectStandardInput = false,
+ };
+
+ if (workingDirectory != null)
+ psi.WorkingDirectory = Path.GetFullPath(workingDirectory);
+
+ if (environmentVariables != null)
+ {
+ foreach (var v in environmentVariables)
+ psi.EnvironmentVariables.Add(v.Key, v.Value);
+ }
+
+ using var process = new Process
+ {
+ StartInfo = psi,
+ EnableRaisingEvents = true,
+ };
+ process.OutputDataReceived += OnOutputDataReceived;
+ process.ErrorDataReceived += OnErrorDataReceived;
+
+ try
+ {
+ process.Start();
+ process.BeginOutputReadLine();
+ process.BeginErrorReadLine();
+
+ // On .NET 6 or later. Process.WaitForExitAsync wait for redirected output reads
+ await process.WaitForExitAsync(cancellationToken);
+ }
+ catch (OperationCanceledException)
+ {
+ TerminateProcess(process);
+ throw;
+ }
+ catch
+ {
+ TerminateProcess(process);
+ return -1;
+ }
+
+ CleanupProcess(process);
+ return process.ExitCode;
+ }
+
+ private static void CleanupProcess(Process process)
+ {
+ // Stop async event processing. (To avoid callback invoked after disposed)
+ process.CancelOutputRead();
+ process.CancelErrorRead();
+
+ // Remove event handler
+ process.OutputDataReceived -= OnOutputDataReceived;
+ process.ErrorDataReceived -= OnErrorDataReceived;
+ }
+
+ private static void TerminateProcess(Process process)
+ {
+ CleanupProcess(process);
+
+ if (process.HasExited)
+ return;
+
+ try
+ {
+ process.Kill(entireProcessTree: true);
+ }
+ catch
+ {
+ // Ignore exception
+ }
+ }
+
+ private static void OnOutputDataReceived(object sender, DataReceivedEventArgs e)
+ {
+ var message = e.Data;
+ if (string.IsNullOrEmpty(message))
+ return;
+
+ // Ignore output message. When need to output logs. uncomment following line.
+ // TestContext.Current.TestOutputHelper.WriteLine(message);
+ }
+
+ private static void OnErrorDataReceived(object sender, DataReceivedEventArgs e)
+ {
+ var message = e.Data;
+ if (string.IsNullOrEmpty(message))
+ return;
+
+ // Ignore output message. When need to output logs. uncomment following line.
+ // TestContext.Current.TestOutputHelper.WriteLine($"Error: {message}");
+ }
+}
diff --git a/test/docfx.Snapshot.Tests/docfx.Snapshot.Tests.csproj b/test/docfx.Snapshot.Tests/docfx.Snapshot.Tests.csproj
index 56c718ed26b..c9e8d462b24 100644
--- a/test/docfx.Snapshot.Tests/docfx.Snapshot.Tests.csproj
+++ b/test/docfx.Snapshot.Tests/docfx.Snapshot.Tests.csproj
@@ -1,12 +1,14 @@
+ Exe
net8.0
+ true
-
+
-
+
diff --git a/test/docfx.Tests/MetadataCommandTest.cs b/test/docfx.Tests/MetadataCommandTest.cs
index e01761fb414..7885b7fcc36 100644
--- a/test/docfx.Tests/MetadataCommandTest.cs
+++ b/test/docfx.Tests/MetadataCommandTest.cs
@@ -22,6 +22,10 @@ public MetadataCommandTest()
{
_outputFolder = GetRandomFolder();
_projectFolder = GetRandomFolder();
+
+ // Create empty `Directory.Build.props`.
+ var propsFilePath = Path.Combine(_projectFolder, "Directory.Build.props");
+ File.WriteAllText(propsFilePath, "");
}
[Fact]
diff --git a/test/docfx.Tests/SerializationTests/TestData/TestDataAttribute.cs b/test/docfx.Tests/SerializationTests/TestData/TestDataAttribute.cs
index 1e53e2bc5ec..f2f7e21345f 100644
--- a/test/docfx.Tests/SerializationTests/TestData/TestDataAttribute.cs
+++ b/test/docfx.Tests/SerializationTests/TestData/TestDataAttribute.cs
@@ -3,12 +3,15 @@
using System.Reflection;
using Xunit.Sdk;
+using Xunit.v3;
namespace docfx.Tests;
public class TestDataAttribute : DataAttribute
{
- public override IEnumerable