Skip to content

Commit c04cbb8

Browse files
Replace Csc task with Roslyn API in FileTrackerTests (dotnet#13094)
1 parent ada8229 commit c04cbb8

2 files changed

Lines changed: 112 additions & 54 deletions

File tree

src/Utilities.UnitTests/Microsoft.Build.Utilities.UnitTests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<Reference Include="System.Net.Http" Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'" />
1414

1515
<PackageReference Include="Shouldly" />
16+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'" />
1617

1718
<ProjectReference Include="..\Utilities\Microsoft.Build.Utilities.csproj" />
1819
<ProjectReference Include="..\Build\Microsoft.Build.csproj" />

src/Utilities.UnitTests/TrackedDependencies/FileTrackerTests.cs

Lines changed: 111 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@
1515
using Microsoft.Build.Shared;
1616
using Microsoft.Build.Utilities;
1717

18-
#if ENABLE_TRACKER_TESTS // https://github.com/dotnet/msbuild/issues/12063
19-
using Microsoft.CodeAnalysis.BuildTasks;
20-
#endif
18+
using Microsoft.CodeAnalysis;
19+
using Microsoft.CodeAnalysis.CSharp;
20+
using Microsoft.CodeAnalysis.Emit;
2121

22+
using Shouldly;
2223
using Xunit;
2324
using Windows.Win32;
2425
using Windows.Win32.Foundation;
@@ -250,6 +251,33 @@ public void Dispose()
250251
FileTrackerTestHelper.CleanTlogs();
251252
}
252253

254+
[WindowsOnlyFact]
255+
public void CompileCSharpExecutable_CompilesSimpleProgram()
256+
{
257+
string outputFile = Path.Combine(Path.GetTempPath(), $"TestCompile_{Guid.NewGuid()}.exe");
258+
try
259+
{
260+
string codeContent = @"
261+
using System;
262+
class Program
263+
{
264+
static void Main()
265+
{
266+
Console.WriteLine(""Hello from compiled code!"");
267+
}
268+
}";
269+
FileTrackerTestHelper.CompileCSharpExecutable(codeContent, outputFile);
270+
File.Exists(outputFile).ShouldBeTrue("Output executable should exist");
271+
}
272+
finally
273+
{
274+
if (File.Exists(outputFile))
275+
{
276+
File.Delete(outputFile);
277+
}
278+
}
279+
}
280+
253281
[Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
254282
public void FileTrackerHelp()
255283
{
@@ -444,8 +472,6 @@ public void FileTrackerFindStrInOperationsExtended_AttributesOnly()
444472
Assert.True(foundCreateFileW || foundCreateFileA);
445473
}
446474

447-
448-
#if ENABLE_TRACKER_TESTS // https://github.com/dotnet/msbuild/issues/12063
449475
[Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
450476
public void FileTrackerExtendedDirectoryTracking()
451477
{
@@ -454,7 +480,6 @@ public void FileTrackerExtendedDirectoryTracking()
454480
File.Delete("directoryattributes.read.1.tlog");
455481
File.Delete("directoryattributes.write.1.tlog");
456482

457-
string codeFile = null;
458483
string outputFile = Path.Combine(Path.GetTempPath(), "directoryattributes.exe");
459484
string codeContent = @"
460485
using System.IO;
@@ -479,13 +504,7 @@ static void Main(string[] args)
479504

480505
try
481506
{
482-
codeFile = FileUtilities.GetTemporaryFileName();
483-
File.WriteAllText(codeFile, codeContent);
484-
Csc csc = new Csc();
485-
csc.BuildEngine = new MockEngine3();
486-
csc.Sources = new ITaskItem[] { new TaskItem(codeFile) };
487-
csc.OutputAssembly = new TaskItem(outputFile);
488-
csc.Execute();
507+
FileTrackerTestHelper.CompileCSharpExecutable(codeContent, outputFile);
489508

490509
string trackerPath = FileTracker.GetTrackerPath(ExecutableType.ManagedIL);
491510
string fileTrackerPath = FileTracker.GetFileTrackerPath(ExecutableType.ManagedIL);
@@ -496,8 +515,8 @@ static void Main(string[] args)
496515
Assert.Equal(0, exit);
497516

498517
// Should track directories when '/e' is passed
499-
FileTrackerTestHelper.AssertFoundStringInTLog("GetFileAttributesExW:" + FrameworkFileUtilities.EnsureTrailingSlash(Directory.GetCurrentDirectory()).ToUpperInvariant(), "directoryattributes.read.1.tlog");
500-
FileTrackerTestHelper.AssertFoundStringInTLog("GetFileAttributesW:" + FrameworkFileUtilities.EnsureTrailingSlash(Directory.GetCurrentDirectory()).ToUpperInvariant(), "directoryattributes.read.1.tlog");
518+
FileTrackerTestHelper.AssertFoundStringInTLog("GetFileAttributesExW:" + FileUtilities.EnsureTrailingSlash(Directory.GetCurrentDirectory()).ToUpperInvariant(), "directoryattributes.read.1.tlog");
519+
FileTrackerTestHelper.AssertFoundStringInTLog("GetFileAttributesW:" + FileUtilities.EnsureTrailingSlash(Directory.GetCurrentDirectory()).ToUpperInvariant(), "directoryattributes.read.1.tlog");
501520

502521
File.Delete("directoryattributes.read.1.tlog");
503522
File.Delete("directoryattributes.write.1.tlog");
@@ -509,8 +528,8 @@ static void Main(string[] args)
509528
Assert.Equal(0, exit);
510529

511530
// With '/a', should *not* track GetFileAttributes on directories, even though we do so on files.
512-
FileTrackerTestHelper.AssertDidntFindStringInTLog("GetFileAttributesExW:" + FrameworkFileUtilities.EnsureTrailingSlash(Directory.GetCurrentDirectory()).ToUpperInvariant(), "directoryattributes.read.1.tlog");
513-
FileTrackerTestHelper.AssertDidntFindStringInTLog("GetFileAttributesW:" + FrameworkFileUtilities.EnsureTrailingSlash(Directory.GetCurrentDirectory()).ToUpperInvariant(), "directoryattributes.read.1.tlog");
531+
FileTrackerTestHelper.AssertDidntFindStringInTLog("GetFileAttributesExW:" + FileUtilities.EnsureTrailingSlash(Directory.GetCurrentDirectory()).ToUpperInvariant(), "directoryattributes.read.1.tlog");
532+
FileTrackerTestHelper.AssertDidntFindStringInTLog("GetFileAttributesW:" + FileUtilities.EnsureTrailingSlash(Directory.GetCurrentDirectory()).ToUpperInvariant(), "directoryattributes.read.1.tlog");
514533

515534
File.Delete("directoryattributes.read.1.tlog");
516535
File.Delete("directoryattributes.write.1.tlog");
@@ -522,8 +541,8 @@ static void Main(string[] args)
522541
Assert.Equal(0, exit);
523542

524543
// With neither '/a' nor '/e', should not do any directory tracking whatsoever
525-
FileTrackerTestHelper.AssertDidntFindStringInTLog("GetFileAttributesExW:" + FrameworkFileUtilities.EnsureTrailingSlash(Directory.GetCurrentDirectory()).ToUpperInvariant(), "directoryattributes.read.1.tlog");
526-
FileTrackerTestHelper.AssertDidntFindStringInTLog("GetFileAttributesW:" + FrameworkFileUtilities.EnsureTrailingSlash(Directory.GetCurrentDirectory()).ToUpperInvariant(), "directoryattributes.read.1.tlog");
544+
FileTrackerTestHelper.AssertDidntFindStringInTLog("GetFileAttributesExW:" + FileUtilities.EnsureTrailingSlash(Directory.GetCurrentDirectory()).ToUpperInvariant(), "directoryattributes.read.1.tlog");
545+
FileTrackerTestHelper.AssertDidntFindStringInTLog("GetFileAttributesW:" + FileUtilities.EnsureTrailingSlash(Directory.GetCurrentDirectory()).ToUpperInvariant(), "directoryattributes.read.1.tlog");
527546

528547
File.Delete("directoryattributes.read.1.tlog");
529548
File.Delete("directoryattributes.write.1.tlog");
@@ -535,7 +554,7 @@ static void Main(string[] args)
535554
Assert.Equal(0, exit);
536555

537556
// Should track directories when '/e' is passed
538-
FileTrackerTestHelper.AssertFoundStringInTLog(FrameworkFileUtilities.EnsureTrailingSlash(Directory.GetCurrentDirectory()).ToUpperInvariant(), "directoryattributes.read.1.tlog");
557+
FileTrackerTestHelper.AssertFoundStringInTLog(FileUtilities.EnsureTrailingSlash(Directory.GetCurrentDirectory()).ToUpperInvariant(), "directoryattributes.read.1.tlog");
539558

540559
File.Delete("directoryattributes.read.1.tlog");
541560
File.Delete("directoryattributes.write.1.tlog");
@@ -547,7 +566,7 @@ static void Main(string[] args)
547566
Assert.Equal(0, exit);
548567

549568
// With '/a', should *not* track GetFileAttributes on directories, even though we do so on files.
550-
FileTrackerTestHelper.AssertDidntFindStringInTLog(FrameworkFileUtilities.EnsureTrailingSlash(Directory.GetCurrentDirectory()).ToUpperInvariant(), "directoryattributes.read.1.tlog");
569+
FileTrackerTestHelper.AssertDidntFindStringInTLog(FileUtilities.EnsureTrailingSlash(Directory.GetCurrentDirectory()).ToUpperInvariant(), "directoryattributes.read.1.tlog");
551570

552571
File.Delete("directoryattributes.read.1.tlog");
553572
File.Delete("directoryattributes.write.1.tlog");
@@ -559,11 +578,10 @@ static void Main(string[] args)
559578
Assert.Equal(0, exit);
560579

561580
// With neither '/a' nor '/e', should not do any directory tracking whatsoever
562-
FileTrackerTestHelper.AssertDidntFindStringInTLog(FrameworkFileUtilities.EnsureTrailingSlash(Directory.GetCurrentDirectory()).ToUpperInvariant(), "directoryattributes.read.1.tlog");
581+
FileTrackerTestHelper.AssertDidntFindStringInTLog(FileUtilities.EnsureTrailingSlash(Directory.GetCurrentDirectory()).ToUpperInvariant(), "directoryattributes.read.1.tlog");
563582
}
564583
finally
565584
{
566-
File.Delete(codeFile);
567585
File.Delete(outputFile);
568586
}
569587
}
@@ -576,21 +594,14 @@ public void FileTrackerFindStrInIncludeDuplicates()
576594
File.Delete("findstr.read.1.tlog");
577595
FileTrackerTestHelper.WriteAll("test.in", "foo");
578596

579-
string codeFile = null;
580597
string outputFile = Path.Combine(Path.GetTempPath(), "readtwice.exe");
581598
File.Delete(outputFile);
582599

583600
try
584601
{
585602
string inputPath = Path.GetFullPath("test.in");
586-
codeFile = FileUtilities.GetTemporaryFileName();
587603
string codeContent = @"using System.IO; class X { static void Main() { File.ReadAllText(@""" + inputPath + @"""); File.ReadAllText(@""" + inputPath + @"""); }}";
588-
File.WriteAllText(codeFile, codeContent);
589-
Csc csc = new Csc();
590-
csc.BuildEngine = new MockEngine3();
591-
csc.Sources = new[] { new TaskItem(codeFile) };
592-
csc.OutputAssembly = new TaskItem(outputFile);
593-
csc.Execute();
604+
FileTrackerTestHelper.CompileCSharpExecutable(codeContent, outputFile);
594605

595606
string trackerPath = FileTracker.GetTrackerPath(ExecutableType.ManagedIL);
596607
string fileTrackerPath = FileTracker.GetFileTrackerPath(ExecutableType.ManagedIL);
@@ -602,7 +613,6 @@ public void FileTrackerFindStrInIncludeDuplicates()
602613
}
603614
finally
604615
{
605-
File.Delete(codeFile);
606616
File.Delete(outputFile);
607617
}
608618

@@ -632,7 +642,6 @@ public void FileTrackerDoNotRecordWriteAsRead()
632642
try
633643
{
634644
writeFile = Path.Combine(testDirectory, "test.out");
635-
string codeFile = Path.Combine(testDirectory, "code.cs");
636645
string codeContent = @"
637646
using System.IO;
638647
using System.Runtime.InteropServices;
@@ -646,14 +655,7 @@ static void Main()
646655
}
647656
}";
648657

649-
File.WriteAllText(codeFile, codeContent);
650-
Csc csc = new Csc();
651-
csc.BuildEngine = new MockEngine3();
652-
csc.Sources = new[] { new TaskItem(codeFile) };
653-
csc.OutputAssembly = new TaskItem(outputFile);
654-
bool success = csc.Execute();
655-
656-
Assert.True(success);
658+
FileTrackerTestHelper.CompileCSharpExecutable(codeContent, outputFile);
657659

658660
string trackerPath = FileTracker.GetTrackerPath(ExecutableType.ManagedIL);
659661
string fileTrackerPath = FileTracker.GetFileTrackerPath(ExecutableType.ManagedIL);
@@ -674,7 +676,6 @@ static void Main()
674676
FileTrackerTestHelper.AssertDidntFindStringInTLog("CreateFileW, Desired Access=0xc0000000, Creation Disposition=0x1:" + writeFile.ToUpperInvariant(), "writenoread.read.1.tlog");
675677
FileTrackerTestHelper.AssertFoundStringInTLog("CreateFileW, Desired Access=0xc0000000, Creation Disposition=0x1:" + writeFile.ToUpperInvariant(), "writenoread.write.1.tlog");
676678
}
677-
#endif // ENABLE_TRACKER_TESTS
678679

679680
[Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
680681
public void FileTrackerFindStrInCommandLine()
@@ -2390,7 +2391,6 @@ public void LaunchMultipleOfSameTool_DifferentContexts()
23902391
}
23912392
}
23922393

2393-
#if ENABLE_TRACKER_TESTS // https://github.com/dotnet/msbuild/issues/12063
23942394
[Fact(Skip = "Needs investigation")]
23952395
public void LaunchMultipleOfSameTool_ToolLaunchesOthers()
23962396
{
@@ -2435,16 +2435,7 @@ static void Main(string[] args)
24352435

24362436
File.Delete(outputFile);
24372437

2438-
string codeFile = Path.Combine(testDir, "Program.cs");
2439-
File.WriteAllText(codeFile, codeContent);
2440-
Csc csc = new Csc();
2441-
csc.BuildEngine = new MockEngine3();
2442-
csc.Sources = new ITaskItem[] { new TaskItem(codeFile) };
2443-
csc.OutputAssembly = new TaskItem(outputFile);
2444-
csc.Platform = "x86";
2445-
bool compileSucceeded = csc.Execute();
2446-
2447-
Assert.True(compileSucceeded);
2438+
FileTrackerTestHelper.CompileCSharpExecutable(codeContent, outputFile, "x86");
24482439

24492440
// Item1: appname
24502441
// Item2: command line
@@ -2476,7 +2467,6 @@ static void Main(string[] args)
24762467
}
24772468
}
24782469
}
2479-
#endif // ENABLE_TRACKER_TESTS
24802470

24812471
private static void InProcTrackingSpawnsToolWithTracker(bool useTrackerResponseFile)
24822472
{
@@ -2721,6 +2711,73 @@ public static void AssertFoundStringInTLog(string file, string tlog, int timesFo
27212711
}
27222712

27232713
public static void AssertFoundStringInTLog(string file, string tlog) => AssertFoundStringInTLog(file, tlog, 1);
2714+
2715+
/// <summary>
2716+
/// Compiles C# source code into an executable using Roslyn.
2717+
/// </summary>
2718+
/// <param name="sourceCode">The C# source code to compile.</param>
2719+
/// <param name="outputPath">The path where the executable will be written.</param>
2720+
/// <param name="platform">Optional platform target (e.g., "x86", "x64", "AnyCpu"). Defaults to AnyCpu.</param>
2721+
/// <exception cref="InvalidOperationException">Thrown when compilation fails; the message contains the compiler diagnostics.</exception>
2722+
public static void CompileCSharpExecutable(string sourceCode, string outputPath, string platform = null)
2723+
{
2724+
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(sourceCode);
2725+
2726+
string assemblyName = Path.GetFileNameWithoutExtension(outputPath);
2727+
2728+
// Add references to required assemblies
2729+
var references = new List<MetadataReference>
2730+
{
2731+
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
2732+
MetadataReference.CreateFromFile(typeof(Console).Assembly.Location),
2733+
MetadataReference.CreateFromFile(typeof(System.IO.File).Assembly.Location),
2734+
MetadataReference.CreateFromFile(typeof(System.Diagnostics.Process).Assembly.Location),
2735+
MetadataReference.CreateFromFile(typeof(System.Runtime.InteropServices.DllImportAttribute).Assembly.Location),
2736+
};
2737+
2738+
// Add reference to System.Runtime for core types
2739+
string runtimePath = Path.GetDirectoryName(typeof(object).Assembly.Location);
2740+
string systemRuntimePath = Path.Combine(runtimePath, "System.Runtime.dll");
2741+
if (File.Exists(systemRuntimePath))
2742+
{
2743+
references.Add(MetadataReference.CreateFromFile(systemRuntimePath));
2744+
}
2745+
2746+
// Determine platform
2747+
Platform targetPlatform = string.IsNullOrEmpty(platform)
2748+
? Platform.AnyCpu
2749+
: platform.ToLowerInvariant() switch
2750+
{
2751+
"x86" => Platform.X86,
2752+
"x64" => Platform.X64,
2753+
_ => Platform.AnyCpu,
2754+
};
2755+
2756+
CSharpCompilationOptions options = new CSharpCompilationOptions(
2757+
OutputKind.ConsoleApplication,
2758+
optimizationLevel: OptimizationLevel.Release,
2759+
platform: targetPlatform);
2760+
2761+
CSharpCompilation compilation = CSharpCompilation.Create(
2762+
assemblyName,
2763+
syntaxTrees: [syntaxTree],
2764+
references: references,
2765+
options: options);
2766+
2767+
EmitResult result = compilation.Emit(outputPath);
2768+
2769+
if (!result.Success)
2770+
{
2771+
string diagnostics = string.Join(
2772+
Environment.NewLine,
2773+
result.Diagnostics
2774+
.Where(d => d.Severity == DiagnosticSeverity.Error)
2775+
.Select(d => d.ToString()));
2776+
2777+
throw new InvalidOperationException(
2778+
$"Compilation failed for '{outputPath}'.{Environment.NewLine}{diagnostics}");
2779+
}
2780+
}
27242781
}
27252782
}
27262783
#endif

0 commit comments

Comments
 (0)