Skip to content

Commit

Permalink
Add option for building a test exe as single file (#42972)
Browse files Browse the repository at this point in the history
* Add option for building a test exe as single file

* Remove left over test

* Add target to exclude references from single-file

* First attempt at adding a CI job

* Opt-in specific libraries for single-file testing support

Start with System.Collections as all tests pass.

* Config testing using single-file in build.cmd

* Change yml suffix name to SingleFile

* Windows_NT_x64 -> windows_x64

* Fix for helix queueing

* Respond to host rename

* Change TargetOS to check for windows

* chmod test exe on linux

* Direct singlefilehost to the locally built copy

* Adjust singlefilehost copy+call

* Add .exe suffix on Windows

* Move libraries after hosts build to allow for libs.test to depend on hosts build

* Split up host and libs packaging and tests

* Move host packaging

* Move pretest up

* Move packages up as well

* Reorder libs pretest and libs.packages

* Add isSingleFile build parameter to limit Linux Helix jobs

* Typo

* Change conditional check

* Fix yml

* Fix yml

* Fix neq

* Fix subsets

* Typo

* Fix

* Adjust assert

* Include code from Michal to skip failing test

* Remove empty ItemGroup

* Update src/libraries/Common/tests/SingleFileTestRunner/SingleFileTestRunner.cs

Co-authored-by: Michal Strehovský <MichalStrehovsky@users.noreply.github.com>

* Update eng/testing/tests.singlefile.targets

Co-authored-by: Michal Strehovský <MichalStrehovsky@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Michal Strehovský <MichalStrehovsky@users.noreply.github.com>

* Use ProjectExclusions

* Update eng/testing/tests.singlefile.targets

Co-authored-by: Michal Strehovský <MichalStrehovsky@users.noreply.github.com>

* Revert changes

* Remove host build from tests

Co-authored-by: Michal Strehovský <MichalStrehovsky@users.noreply.github.com>
  • Loading branch information
agocke and MichalStrehovsky authored Mar 31, 2021
1 parent 4581290 commit 7677f7d
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 16 deletions.
6 changes: 3 additions & 3 deletions eng/pipelines/libraries/helix-queues-setup.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ jobs:

# Linux x64
- ${{ if eq(parameters.platform, 'Linux_x64') }}:
- ${{ if eq(parameters.jobParameters.interpreter, '') }}:
- ${{ if and(eq(parameters.jobParameters.interpreter, ''), ne(parameters.jobParameters.isSingleFile, true)) }}:
- ${{ if and(eq(parameters.jobParameters.testScope, 'outerloop'), eq(parameters.jobParameters.runtimeFlavor, 'mono')) }}:
- (Centos.8.Amd64.Open)Ubuntu.1604.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:centos-8-helix-20201229003624-c1bf759
- RedHat.7.Amd64.Open
Expand Down Expand Up @@ -81,7 +81,7 @@ jobs:
- Ubuntu.1804.Amd64.Open
- SLES.15.Amd64.Open
- (Fedora.30.Amd64.Open)ubuntu.1604.amd64.open@mcr.microsoft.com/dotnet-buildtools/prereqs:fedora-30-helix-20200512010621-4f8cef7
- ${{ if eq(parameters.jobParameters.interpreter, 'true') }}:
- ${{ if or(eq(parameters.jobParameters.interpreter, 'true'), eq(parameters.jobParameters.isSingleFile, true)) }}:
# Limiting interp runs as we don't need as much coverage.
- Debian.9.Amd64.Open

Expand Down Expand Up @@ -135,7 +135,7 @@ jobs:
# .NETFramework
- ${{ if eq(parameters.jobParameters.framework, 'net48') }}:
- Windows.10.Amd64.Client19H1.Open

# AllConfigurations
- ${{ if eq(parameters.jobParameters.framework, 'allConfigurations') }}:
- Windows.10.Amd64.Server19H1.Open
Expand Down
22 changes: 22 additions & 0 deletions eng/pipelines/runtime.yml
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,28 @@ jobs:
eq(variables['monoContainsChange'], true),
eq(variables['isFullMatrix'], true))
# Build and test libraries under single-file publishing
- template: /eng/pipelines/common/platform-matrix.yml
parameters:
jobTemplate: /eng/pipelines/common/global-build-job.yml
helixQueuesTemplate: /eng/pipelines/libraries/helix-queues-setup.yml
buildConfig: Release
platforms:
- windows_x64
- Linux_x64
jobParameters:
testGroup: innerloop
isFullMatrix: ${{ variables.isFullMatrix }}
isSingleFile: true
nameSuffix: SingleFile
buildArgs: -s clr+libs+libs.tests -c $(_BuildConfig) /p:TestSingleFile=true /p:ArchiveTests=true
timeoutInMinutes: 120
# extra steps, run tests
extraStepsTemplate: /eng/pipelines/libraries/helix.yml
extraStepsParameters:
creator: dotnet-bot
testRunNamePrefixSuffix: SingleFile_$(_BuildConfig)

#
# Build the whole product using Mono and run runtime tests
#
Expand Down
62 changes: 62 additions & 0 deletions eng/testing/tests.singlefile.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<Project>
<PropertyGroup>
<OutputType>Exe</OutputType>

<BundleDir>$([MSBuild]::NormalizeDirectory('$(OutDir)', 'publish'))</BundleDir>
<RunScriptOutputPath>$([MSBuild]::NormalizePath('$(BundleDir)', '$(RunScriptOutputName)'))</RunScriptOutputPath>
<RuntimeIdentifier>$(PackageRID)</RuntimeIdentifier>

<RunScriptCommand Condition="'$(TargetOS)' == 'windows'">$(AssemblyName).exe</RunScriptCommand>
<RunScriptCommand Condition="'$(TargetOS)' != 'windows'">chmod +rwx $(AssemblyName) &amp;&amp; ./$(AssemblyName)</RunScriptCommand>
</PropertyGroup>

<PropertyGroup>
<PublishSingleFile>true</PublishSingleFile>
<UseAppHost>true</UseAppHost>
<SelfContained>true</SelfContained>
<SingleFileHostSourcePath>$([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'coreclr', '$(TargetOS).$(TargetArchitecture).$(Configuration)', 'corehost'))/singlefilehost</SingleFileHostSourcePath>
<SingleFileHostSourcePath Condition="'$(TargetOS)' == 'windows'">$(SingleFileHostSourcePath).exe</SingleFileHostSourcePath>
</PropertyGroup>

<ItemGroup>
<Compile Include="$(CommonTestPath)SingleFileTestRunner\SingleFileTestRunner.cs"
Link="Common\SingleFileTestRunner\SingleFileTestRunner.cs" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="xunit.runner.utility" Version="$(XUnitVersion)" />
</ItemGroup>

<Target Name="__ExcludeAssembliesFromSingleFile"
Inputs="%(ResolvedFileToPublish.Identity)"
Outputs="__NewResolvedFiles"
BeforeTargets="_ComputeFilesToBundle">
<PropertyGroup>
<__Identity>%(ResolvedFileToPublish.Identity)</__Identity>
<__FileName>%(ResolvedFileToPublish.Filename)%(ResolvedFileToPublish.Extension)</__FileName>
</PropertyGroup>

<ItemGroup>
<__NewResolvedFiles Include="@(ResolvedFileToPublish)">
<ExcludeFromSingleFile Condition="'%(__ExcludeFromBundle.Identity)' == '$(__FileName)'">true</ExcludeFromSingleFile>
</__NewResolvedFiles>
</ItemGroup>
</Target>

<Target Name="__UpdateExcludedAssembliesFromSingleFile"
Inputs="ExcludeFromSingleFile"
Outputs="ResolvedFileToPublish"
DependsOnTargets="ComputeResolvedFilesToPublishList"
BeforeTargets="_ComputeFilesToBundle">
<ItemGroup>
<ResolvedFileToPublish Remove="@(ResolvedFileToPublish)" />
<ResolvedFileToPublish Include="@(__NewResolvedFiles)" />
</ItemGroup>
</Target>

<Target Name="PublishTestAsSingleFile"
Condition="'$(IsCrossTargetingBuild)' != 'true'"
AfterTargets="Build"
DependsOnTargets="Publish;ArchiveTests" />

</Project>
7 changes: 4 additions & 3 deletions eng/testing/tests.targets
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<!-- For browser we need to hook up the target with DependsOnTargets in PublishTestAsSelfContained
because we do a Publish which runs after Build, if we run after PrepareForRun we would generated
an empty zip because we haven't published the selfcontained app. -->
<ArchiveTestsAfterTargets Condition="'$(TargetOS)' == 'Browser'" />
<ArchiveTestsAfterTargets Condition="'$(TargetOS)' == 'Browser' or '$(TestSingleFile)' == 'true'" />
</PropertyGroup>

<!-- Archive test binaries. -->
Expand All @@ -37,7 +37,7 @@

<PropertyGroup>
<_ZipSourceDirectory>$(OutDir)</_ZipSourceDirectory>
<_ZipSourceDirectory Condition="'$(TargetOS)' == 'Browser'">$(BundleDir)</_ZipSourceDirectory>
<_ZipSourceDirectory Condition="'$(TargetOS)' == 'Browser' or '$(TestSingleFile)' == 'true'">$(BundleDir)</_ZipSourceDirectory>
</PropertyGroup>

<MakeDir Directories="$(TestArchiveTestsDir)" />
Expand Down Expand Up @@ -118,10 +118,11 @@
</Target>

<Import Project="$(MSBuildThisFileDirectory)tests.mobile.targets" Condition="'$(TargetsMobile)' == 'true'" />
<Import Project="$(MSBuildThisFileDirectory)tests.singlefile.targets" Condition="'$(TestSingleFile)' == 'true'" />
<Import Project="$(MSBuildThisFileDirectory)xunit\xunit.targets" Condition="'$(TestFramework)' == 'xunit'" />

<!-- Main test targets -->
<Target Name="Test" DependsOnTargets="$(TestDependsOn)" />

<Import Project="$(MSBuildThisFileDirectory)outerBuild.targets" Condition="'$(IsCrossTargetingBuild)' == 'true'" />
</Project>
4 changes: 2 additions & 2 deletions eng/testing/xunit/xunit.console.targets
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<UseXunitExcludesTxtFile Condition="'$(TargetOS)' == 'Android' or '$(TargetOS)' == 'iOS' or '$(TargetOS)' == 'iOSSimulator' or '$(TargetOS)' == 'tvOS' or '$(TargetOS)' == 'tvOSSimulator'">true</UseXunitExcludesTxtFile>
</PropertyGroup>

<PropertyGroup Condition="'$(TargetsMobile)' != 'true'">
<PropertyGroup Condition="'$(TargetsMobile)' != 'true' and '$(TestSingleFile)' != 'true'">
<_depsFileArgument Condition="'$(GenerateDependencyFile)' == 'true'">--depsfile $(AssemblyName).deps.json</_depsFileArgument>
<RunScriptCommand Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp'">"$(RunScriptHost)" exec --runtimeconfig $(AssemblyName).runtimeconfig.json $(_depsFileArgument) xunit.console.dll</RunScriptCommand>
<RunScriptCommand Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'">xunit.console.exe</RunScriptCommand>
Expand Down Expand Up @@ -36,7 +36,7 @@
<XunitExcludesTxtFileContent>$(_withoutCategories.Replace(';', '%0dcategory='))</XunitExcludesTxtFileContent>
</PropertyGroup>

<ItemGroup>
<ItemGroup Condition="'$(TestSingleFile)' != 'true'">
<PackageReference Include="Microsoft.DotNet.XUnitConsoleRunner"
Version="$(MicrosoftDotNetXUnitConsoleRunnerVersion)"
Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp'" />
Expand Down
2 changes: 1 addition & 1 deletion eng/testing/xunit/xunit.props
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<PackageReference Include="Microsoft.DotNet.XUnitExtensions" Version="$(MicrosoftDotNetXUnitExtensionsVersion)" />
</ItemGroup>

<ItemGroup Condition="'$(ArchiveTests)' != 'true' AND '$(PublishingTestsRun)' != 'true'">
<ItemGroup Condition="'$(ArchiveTests)' != 'true' and '$(PublishingTestsRun)' != 'true' and '$(TestSingleFile)' != 'true'">
<!-- Microsoft.Net.Test.Sdk brings a lot of assemblies with it. To reduce helix payload submission size we disable it on CI. -->
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkVersion)" />
<PackageReference Include="xunit.runner.visualstudio" Version="$(XUnitRunnerVisualStudioVersion)" GeneratePathProperty="true" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable disable

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using System.Threading.Tasks;
using System.Xml.Linq;
using Xunit;
using Xunit.Abstractions;
using Xunit.Sdk;

public class SingleFileTestRunner : XunitTestFramework
{
private SingleFileTestRunner(IMessageSink messageSink)
: base(messageSink) { }

public static int Main(string[] args)
{
var asm = typeof(SingleFileTestRunner).Assembly;
Console.WriteLine("Running assembly:" + asm.FullName);

var diagnosticSink = new ConsoleDiagnosticMessageSink();
var testsFinished = new TaskCompletionSource();
var testSink = new TestMessageSink();
var summarySink = new DelegatingExecutionSummarySink(testSink,
() => false,
(completed, summary) => Console.WriteLine($"Tests run: {summary.Total}, Errors: {summary.Errors}, Failures: {summary.Failed}, Skipped: {summary.Skipped}. Time: {TimeSpan.FromSeconds((double)summary.Time).TotalSeconds}s"));
var resultsXmlAssembly = new XElement("assembly");
var resultsSink = new DelegatingXmlCreationSink(summarySink, resultsXmlAssembly);

testSink.Execution.TestSkippedEvent += args => { Console.WriteLine($"[SKIP] {args.Message.Test.DisplayName}"); };
testSink.Execution.TestFailedEvent += args => { Console.WriteLine($"[FAIL] {args.Message.Test.DisplayName}{Environment.NewLine}{Xunit.ExceptionUtility.CombineMessages(args.Message)}{Environment.NewLine}{Xunit.ExceptionUtility.CombineStackTraces(args.Message)}"); };

testSink.Execution.TestAssemblyFinishedEvent += args =>
{
Console.WriteLine($"Finished {args.Message.TestAssembly.Assembly}{Environment.NewLine}");
testsFinished.SetResult();
};

var xunitTestFx = new SingleFileTestRunner(diagnosticSink);
var asmInfo = Reflector.Wrap(asm);
var asmName = asm.GetName();

var discoverySink = new TestDiscoverySink();
var discoverer = xunitTestFx.CreateDiscoverer(asmInfo);
discoverer.Find(false, discoverySink, TestFrameworkOptions.ForDiscovery());
discoverySink.Finished.WaitOne();
XunitFilters filters = new XunitFilters();
filters.ExcludedTraits.Add("category", new List<string> { "failing" });
var filteredTestCases = discoverySink.TestCases.Where(filters.Filter).ToList();
var executor = xunitTestFx.CreateExecutor(asmName);
executor.RunTests(filteredTestCases, resultsSink, TestFrameworkOptions.ForExecution());

resultsSink.Finished.WaitOne();

var failed = resultsSink.ExecutionSummary.Failed > 0 || resultsSink.ExecutionSummary.Errors > 0;
return failed ? 1 : 0;
}
}

internal class ConsoleDiagnosticMessageSink : IMessageSink
{
public bool OnMessage(IMessageSinkMessage message)
{
if (message is IDiagnosticMessage diagnosticMessage)
{
return true;
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,8 @@
<WasmFilesToIncludeFromPublishDir Include="$(AssemblyName).dll" />
<WasmFilesToIncludeFromPublishDir Include="$(AssemblyName).pdb" />
</ItemGroup>
<ItemGroup>
<!-- Assemblies that should be excluded from the bundle -->
<__ExcludeFromBundle Include="TestAssembly.dll" />
</ItemGroup>
</Project>
20 changes: 13 additions & 7 deletions src/libraries/tests.proj
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<TestPackages Condition="'$(TestPackages)' == ''">false</TestPackages>
<TestTrimming Condition="'$(TestTrimming)' == ''">false</TestTrimming>
</PropertyGroup>

<!-- Projects that don't support code coverage measurement. -->
<ItemGroup Condition="'$(Coverage)' == 'true'">
<ProjectExclusions Include="$(CommonTestPath)Common.Tests.csproj" />
Expand Down Expand Up @@ -107,17 +107,17 @@
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Net.NameResolution\tests\UnitTests\System.Net.NameResolution.Unit.Tests.csproj" />
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Net.NetworkInformation\tests\FunctionalTests\System.Net.NetworkInformation.Functional.Tests.csproj" />
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Net.Ping\tests\FunctionalTests\System.Net.Ping.Functional.Tests.csproj" />

<!-- https://github.com/dotnet/runtime/issues/49191 -->
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Net.Quic\tests\FunctionalTests\System.Net.Quic.Functional.Tests.csproj" />

<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Net.Requests\tests\System.Net.Requests.Tests.csproj" />
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Net.Security\tests\FunctionalTests\System.Net.Security.Tests.csproj" />
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Net.Sockets\tests\FunctionalTests\System.Net.Sockets.Tests.csproj" />

<!-- https://github.com/dotnet/runtime/issues/49192-->
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Net.WebClient\tests\System.Net.WebClient.Tests.csproj" />

<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Net.WebSockets\tests\System.Net.WebSockets.Tests.csproj" />
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Net.WebSockets.Client\tests\System.Net.WebSockets.Client.Tests.csproj" />
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Numerics.Tensors\tests\System.Numerics.Tensors.Tests.csproj" />
Expand Down Expand Up @@ -320,6 +320,12 @@
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Threading.Timer/tests/System.Threading.Timer.Tests.csproj" />
</ItemGroup>

<ItemGroup Condition="'$(TestSingleFile)' == 'true'">
<!-- Run only a small randomly chosen set of passing test suites -->
<ProjectExclusions Include="$(MSBuildThisFileDirectory)*\tests\**\*.Tests.csproj" />
<ProjectExclusions Remove="$(MSBuildThisFileDirectory)System.Collections\tests\System.Collections.Tests.csproj" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="$(MSBuildThisFileDirectory)*\tests\**\*.Tests.csproj"
Exclude="@(ProjectExclusions)"
Expand Down Expand Up @@ -348,7 +354,7 @@
<ProjectReference Include="$(RepoRoot)\src\tests\FunctionalTests\tvOS\**\*.Test.csproj"
BuildInParallel="false" />
</ItemGroup>

<ItemGroup Condition="'$(ArchiveTests)' == 'true' and '$(TargetOS)' == 'Android'">
<ProjectReference Include="$(MonoProjectRoot)sample\Android\AndroidSampleApp.csproj"
BuildInParallel="false" />
Expand All @@ -357,7 +363,7 @@
Exclude="$(RepoRoot)\src\tests\FunctionalTests\Android\Device_Emulator\AOT\Android.Device_Emulator.Aot.Test.csproj"
BuildInParallel="false" />
</ItemGroup>

<ItemGroup Condition="'$(ArchiveTests)' == 'true' and '$(TargetOS)' == 'Browser'">
<ProjectReference Include="$(MonoProjectRoot)sample\wasm\**\*.Sample.csproj"
BuildInParallel="false" />
Expand Down

0 comments on commit 7677f7d

Please sign in to comment.