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 GetData(MethodInfo testMethod) + public override bool SupportsDiscoveryEnumeration() => true; + + public override ValueTask> GetData(MethodInfo testMethod, DisposalTracker disposalTracker) { var key = GetTestDataKey(); var paths = TestData.GetTestDataFilePaths(key); @@ -28,7 +31,9 @@ public override IEnumerable GetData(MethodInfo testMethod) throw new NotSupportedException($"{className} is not supported."); } - return new TheoryData(paths); + var results = paths.Select(x => new TheoryDataRow(x)).ToArray(); + + return ValueTask.FromResult>(results); } private static string GetTestDataKey() diff --git a/test/docfx.Tests/docfx.Tests.csproj b/test/docfx.Tests/docfx.Tests.csproj index 13bd2fdfaf3..cd6bb204eab 100644 --- a/test/docfx.Tests/docfx.Tests.csproj +++ b/test/docfx.Tests/docfx.Tests.csproj @@ -1,11 +1,15 @@ - + + Exe + true + + - + diff --git a/test/xunit.runner.json b/test/xunit.runner.json new file mode 100644 index 00000000000..d22a744e068 --- /dev/null +++ b/test/xunit.runner.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", + "diagnosticMessages": true, + "showLiveOutput": true +}