1515using Microsoft . Build . Shared ;
1616using 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 ;
2223using Xunit ;
2324using Windows . Win32 ;
2425using 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 = @"
460485using 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 = @"
637646using System.IO;
638647using 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