-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Split RunFileTests into multiple files to match main branch structure #54037
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
dsplaisted
merged 3 commits into
dotnet:release/10.0.4xx
from
jjonescz:sprint-split-tests
Apr 22, 2026
+7,626
−7,522
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,250 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using Microsoft.Build.Framework; | ||
| using Microsoft.Build.Logging.StructuredLogger; | ||
| using Microsoft.DotNet.Cli.Commands; | ||
| using Microsoft.DotNet.Cli.Commands.Run; | ||
| using Microsoft.DotNet.FileBasedPrograms; | ||
| using Microsoft.DotNet.ProjectTools; | ||
|
|
||
| namespace Microsoft.DotNet.Cli.Run.Tests; | ||
|
|
||
| public sealed class RunFileTestFixture(IMessageSink sink) : IAsyncLifetime | ||
| { | ||
| public System.Threading.Tasks.Task InitializeAsync() | ||
| { | ||
| RunFileTestBase.CopyNuGetConfigToRunfileDirectory(); | ||
|
|
||
| // Ensure a simple app runs fully with MSBuild before running other csc-only tests | ||
| // so we have packages like ILLink.Tasks restored and csc-only optimization can kick in. | ||
| new DotnetCommand(new SharedTestOutputHelper(sink), "run", "-") | ||
| .WithStandardInput(""" | ||
| Console.WriteLine("Hello"); | ||
| """) | ||
| .Execute() | ||
| .Should().Pass() | ||
| .And.HaveStdOut("Hello"); | ||
|
|
||
| return System.Threading.Tasks.Task.CompletedTask; | ||
| } | ||
|
|
||
| public System.Threading.Tasks.Task DisposeAsync() => System.Threading.Tasks.Task.CompletedTask; | ||
| } | ||
|
|
||
| public abstract class RunFileTestBase(ITestOutputHelper log) : SdkTest(log), IClassFixture<RunFileTestFixture> | ||
| { | ||
| internal static string s_includeExcludeDefaultKnownExtensions | ||
| => field ??= string.Join(", ", CSharpDirective.IncludeOrExclude.DefaultMapping.Select(static e => e.Extension)); | ||
|
|
||
| internal static readonly string s_program = /* lang=C#-Test */ """ | ||
| if (args.Length > 0) | ||
| { | ||
| Console.WriteLine("echo args:" + string.Join(";", args)); | ||
| } | ||
| Console.WriteLine("Hello from " + System.Reflection.Assembly.GetExecutingAssembly().GetName().Name); | ||
| #if !DEBUG | ||
| Console.WriteLine("Release config"); | ||
| #endif | ||
| #if CUSTOM_DEFINE | ||
| Console.WriteLine("Custom define"); | ||
| #endif | ||
| """; | ||
|
|
||
| internal static readonly string s_programDependingOnUtil = /* lang=C#-Test */ """ | ||
| if (args.Length > 0) | ||
| { | ||
| Console.WriteLine("echo args:" + string.Join(";", args)); | ||
| } | ||
| Console.WriteLine("Hello, " + Util.GetMessage()); | ||
| """; | ||
|
|
||
| internal static readonly string s_util = /* lang=C#-Test */ """ | ||
| static class Util | ||
| { | ||
| public static string GetMessage() | ||
| { | ||
| return "String from Util"; | ||
| } | ||
| } | ||
| """; | ||
|
|
||
| internal static readonly string s_programReadingEmbeddedResource = /* lang=C#-Test */ """ | ||
| var assembly = System.Reflection.Assembly.GetExecutingAssembly(); | ||
| var resourceName = assembly.GetManifestResourceNames().SingleOrDefault(); | ||
|
|
||
| if (resourceName is null) | ||
| { | ||
| Console.WriteLine("Resource not found"); | ||
| return; | ||
| } | ||
|
|
||
| using var stream = assembly.GetManifestResourceStream(resourceName)!; | ||
| using var reader = new System.Resources.ResourceReader(stream); | ||
| Console.WriteLine(reader.Cast<System.Collections.DictionaryEntry>().Single()); | ||
| """; | ||
|
|
||
| internal static readonly string s_resx = """ | ||
| <root> | ||
| <data name="MyString"> | ||
| <value>TestValue</value> | ||
| </data> | ||
| </root> | ||
| """; | ||
|
|
||
| internal static readonly string s_consoleProject = $""" | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
| <PropertyGroup> | ||
| <OutputType>Exe</OutputType> | ||
| <TargetFramework>{ToolsetInfo.CurrentTargetFramework}</TargetFramework> | ||
| <ImplicitUsings>enable</ImplicitUsings> | ||
| </PropertyGroup> | ||
| </Project> | ||
| """; | ||
|
|
||
| internal static readonly string s_launchSettings = """ | ||
| { | ||
| "profiles": { | ||
| "TestProfile1": { | ||
| "commandName": "Project", | ||
| "environmentVariables": { | ||
| "Message": "TestProfileMessage1" | ||
| } | ||
| }, | ||
| "TestProfile2": { | ||
| "commandName": "Project", | ||
| "environmentVariables": { | ||
| "Message": "TestProfileMessage2" | ||
| } | ||
| } | ||
| } | ||
| } | ||
| """; | ||
|
|
||
| /// <summary> | ||
| /// Used when we need an out-of-tree base test directory to avoid having implicit build files | ||
| /// like Directory.Build.props in scope and negating the optimizations we want to test. | ||
| /// </summary> | ||
| internal static string OutOfTreeBaseDirectory => field ??= PrepareOutOfTreeBaseDirectory(); | ||
|
|
||
| internal static bool HasCaseInsensitiveFileSystem | ||
| { | ||
| get | ||
| { | ||
| return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) | ||
| || RuntimeInformation.IsOSPlatform(OSPlatform.OSX); | ||
| } | ||
| } | ||
|
|
||
| /// <inheritdoc cref="OutOfTreeBaseDirectory"/> | ||
| private static string PrepareOutOfTreeBaseDirectory() | ||
| { | ||
| string outOfTreeBaseDirectory = TestPathUtility.ResolveTempPrefixLink(Path.Join(Path.GetTempPath(), "dotnetSdkTests")); | ||
| Directory.CreateDirectory(outOfTreeBaseDirectory); | ||
|
|
||
| // Create NuGet.config in our out-of-tree base directory. | ||
| var sourceNuGetConfig = Path.Join(SdkTestContext.Current.TestExecutionDirectory, "NuGet.config"); | ||
| var targetNuGetConfig = Path.Join(outOfTreeBaseDirectory, "NuGet.config"); | ||
| File.Copy(sourceNuGetConfig, targetNuGetConfig, overwrite: true); | ||
|
|
||
| // Check there are no implicit build files that would prevent testing optimizations. | ||
| VirtualProjectBuildingCommand.CollectImplicitBuildFiles(new DirectoryInfo(outOfTreeBaseDirectory), [], out var exampleMSBuildFile); | ||
| exampleMSBuildFile.Should().BeNull(because: "there should not be any implicit build files in the temp directory or its parents " + | ||
| "so we can test optimizations that would be disabled with implicit build files present"); | ||
|
|
||
| return outOfTreeBaseDirectory; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Copies NuGet.config to the runfile base directory so virtual projects created by | ||
| /// <c>dotnet run -</c> (stdin) can resolve packages from test feeds. The virtual project | ||
| /// is created under this directory, and NuGet walks up from the project location to | ||
| /// find config files. | ||
| /// </summary> | ||
| internal static void CopyNuGetConfigToRunfileDirectory() | ||
| { | ||
| var sourceNuGetConfig = Path.Join(SdkTestContext.Current.TestExecutionDirectory, "NuGet.config"); | ||
| var runfileDir = VirtualProjectBuilder.GetTempSubdirectory(); | ||
| Directory.CreateDirectory(runfileDir); | ||
| File.Copy(sourceNuGetConfig, Path.Join(runfileDir, "NuGet.config"), overwrite: true); | ||
| } | ||
|
|
||
| internal static string DirectiveError(string path, int line, string messageFormat, params ReadOnlySpan<object> args) | ||
| { | ||
| return $"{path}({line}): {FileBasedProgramsResources.DirectiveError}: {string.Format(messageFormat, args)}"; | ||
| } | ||
|
|
||
| internal static void EnableRefDirective(TestDirectory testInstance) | ||
| { | ||
| var propsPath = Path.Join(testInstance.Path, "Directory.Build.props"); | ||
| var propsContent = File.Exists(propsPath) ? File.ReadAllText(propsPath) : null; | ||
| if (propsContent is not null && propsContent.Contains(CSharpDirective.Ref.ExperimentalFileBasedProgramEnableRefDirective)) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| File.WriteAllText(propsPath, $""" | ||
| <Project> | ||
| <PropertyGroup> | ||
| <{CSharpDirective.Ref.ExperimentalFileBasedProgramEnableRefDirective}>true</{CSharpDirective.Ref.ExperimentalFileBasedProgramEnableRefDirective}> | ||
| </PropertyGroup> | ||
| </Project> | ||
| """); | ||
| } | ||
|
|
||
|
|
||
| internal static void VerifyBinLogEvaluationDataCount(string binaryLogPath, int expectedCount) | ||
| { | ||
| var records = BinaryLog.ReadRecords(binaryLogPath).ToList(); | ||
| records.Count(static r => r.Args is ProjectEvaluationStartedEventArgs).Should().Be(expectedCount); | ||
| records.Count(static r => r.Args is ProjectEvaluationFinishedEventArgs).Should().Be(expectedCount); | ||
| } | ||
|
|
||
| private protected void Build( | ||
| TestDirectory testInstance, | ||
| BuildLevel expectedLevel, | ||
| ReadOnlySpan<string> args = default, | ||
| string expectedOutput = "Hello from Program", | ||
| string programFileName = "Program.cs", | ||
| string? workDir = null, | ||
| Func<TestCommand, TestCommand>? customizeCommand = null) | ||
| { | ||
| string prefix = expectedLevel switch | ||
| { | ||
| BuildLevel.None => CliCommandStrings.NoBinaryLogBecauseUpToDate + Environment.NewLine, | ||
| BuildLevel.Csc => CliCommandStrings.NoBinaryLogBecauseRunningJustCsc + Environment.NewLine, | ||
| BuildLevel.All => string.Empty, | ||
| _ => throw new ArgumentOutOfRangeException(paramName: nameof(expectedLevel)), | ||
| }; | ||
|
|
||
| var command = new DotnetCommand(Log, ["run", programFileName, "-bl", .. args]) | ||
| .WithWorkingDirectory(workDir ?? testInstance.Path); | ||
|
|
||
| if (customizeCommand != null) | ||
| { | ||
| command = customizeCommand(command); | ||
| } | ||
|
|
||
| command.Execute() | ||
| .Should().Pass() | ||
| .And.HaveStdOut(prefix + expectedOutput); | ||
|
|
||
| var binlogs = new DirectoryInfo(workDir ?? testInstance.Path) | ||
| .EnumerateFiles("*.binlog", SearchOption.TopDirectoryOnly); | ||
|
|
||
| binlogs.Select(f => f.Name) | ||
| .Should().BeEquivalentTo( | ||
| expectedLevel switch | ||
| { | ||
| BuildLevel.None or BuildLevel.Csc => [], | ||
| BuildLevel.All => ["msbuild.binlog"], | ||
| _ => throw new ArgumentOutOfRangeException(paramName: nameof(expectedLevel), message: expectedLevel.ToString()), | ||
| }); | ||
|
|
||
| foreach (var binlog in binlogs) | ||
| { | ||
| binlog.Delete(); | ||
| } | ||
| } | ||
|
|
||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RunFileTestBaseimplementsIClassFixture<RunFileTestFixture>, which meansRunFileTestFixture.InitializeAsync()runs once per derived test class (now several files), repeating a relatively expensivedotnet run -restore/warmup multiple times. Consider switching this to a shared collection fixture (ICollectionFixture + [Collection]) or adding a static one-time guard inside the fixture to ensure the warmup runs only once per test assembly run.