44// See LICENSE file in the project root for full license information.
55//
66
7+ using CliWrap ;
8+ using CliWrap . Buffered ;
79using ICSharpCode . Decompiler ;
810using ICSharpCode . Decompiler . CSharp ;
911using Microsoft . VisualStudio . TestPlatform . ObjectModel ;
@@ -78,6 +80,7 @@ public void RunTests(IEnumerable<string> sources, IRunContext runContext, IFrame
7880 try
7981 {
8082 InitializeLogger ( runContext , frameworkHandle ) ;
83+
8184 foreach ( var source in sources )
8285 {
8386 var testsCases = TestDiscoverer . ComposeTestCases ( source ) ;
@@ -120,8 +123,10 @@ public void RunTests(IEnumerable<TestCase> tests, IRunContext runContext, IFrame
120123 }
121124 else
122125 {
123- // we are connecting to WIN32 nanoCLR
124- results = RunTestOnEmulator ( groups . ToList ( ) ) ;
126+ // we are connecting to nanoCLR CLI
127+ results = RunTestOnEmulatorAsync (
128+ groups . ToList ( ) ,
129+ _logger ) . GetAwaiter ( ) . GetResult ( ) ;
125130 }
126131
127132 foreach ( var result in results )
@@ -536,7 +541,7 @@ await Task.Run(async delegate
536541 }
537542
538543 _logger . LogMessage ( $ "Tests finished.", Settings . LoggingLevel . Verbose ) ;
539- CheckAllTests ( output . ToString ( ) , results ) ;
544+ ParseTestResults ( output . ToString ( ) , results ) ;
540545 }
541546 else
542547 {
@@ -566,17 +571,29 @@ private List<TestResult> PrepareListResult(List<TestCase> tests)
566571 return results ;
567572 }
568573
569- private List < TestResult > RunTestOnEmulator ( List < TestCase > tests )
574+ private async Task < List < TestResult > > RunTestOnEmulatorAsync (
575+ List < TestCase > tests ,
576+ LogMessenger _logger )
570577 {
578+ List < TestResult > results = PrepareListResult ( tests ) ;
579+
571580 _logger . LogMessage (
572- "Setting up test runner in *** nanoCLR WIN32 ***" ,
581+ "Setting up test runner in *** nanoCLR CLI ***" ,
573582 Settings . LoggingLevel . Detailed ) ;
574583
575584 _logger . LogMessage (
576585 $ "Timeout set to { _testSessionTimeout } ms",
577586 Settings . LoggingLevel . Verbose ) ;
578587
579- List < TestResult > results = PrepareListResult ( tests ) ;
588+ // check if nanoCLR needs to be installed/updated
589+ if ( ! NanoCLRHelper . NanoClrIsInstalled
590+ && ! NanoCLRHelper . InstallNanoClr ( _logger ) )
591+ {
592+ results . First ( ) . Outcome = TestOutcome . Failed ;
593+ results . First ( ) . ErrorMessage = "Failed to install/update nanoCLR CLI. Check log for details." ;
594+
595+ return results ;
596+ }
580597
581598 _logger . LogMessage (
582599 "Processing assemblies to load into test runner..." ,
@@ -586,143 +603,96 @@ private List<TestResult> RunTestOnEmulator(List<TestCase> tests)
586603 var workingDirectory = Path . GetDirectoryName ( source ) ;
587604 var allPeFiles = Directory . GetFiles ( workingDirectory , "*.pe" ) ;
588605
589- // prepare the process start of the WIN32 nanoCLR
590- _nanoClr = new Process ( ) ;
606+ // prepare launch of nanoCLR CLI
607+ StringBuilder arguments = new StringBuilder ( ) ;
591608
592- AutoResetEvent outputWaitHandle = new AutoResetEvent ( false ) ;
593- AutoResetEvent errorWaitHandle = new AutoResetEvent ( false ) ;
594- StringBuilder output = new StringBuilder ( ) ;
595- StringBuilder error = new StringBuilder ( ) ;
609+ // assemblies to load
610+ arguments . Append ( "run --assemblies " ) ;
596611
597- try
612+ foreach ( var pe in allPeFiles )
598613 {
599- // prepare parameters to load nanoCLR, include:
600- // 1. unit test launcher
601- // 2. mscorlib
602- // 3. test framework
603- // 4. test application
604- StringBuilder str = new StringBuilder ( ) ;
605- foreach ( var pe in allPeFiles )
606- {
607- str . Append ( $ " -load \" { Path . Combine ( workingDirectory , pe ) } \" ") ;
608- }
609-
610- string parameter = str . ToString ( ) ;
611-
612- _logger . LogMessage (
613- $ "Parameters to pass to nanoCLR: <{ parameter } >",
614- Settings . LoggingLevel . Verbose ) ;
615-
616- var nanoClrLocation = TestObjectHelper . GetNanoClrLocation ( ) ;
617- if ( string . IsNullOrEmpty ( nanoClrLocation ) )
618- {
619- _logger . LogPanicMessage ( "Can't find nanoCLR Win32 in any of the directories!" ) ;
620- results . First ( ) . Outcome = TestOutcome . Failed ;
621- results . First ( ) . ErrorMessage = "Can't find nanoCLR Win32 in any of the directories!" ;
622- return results ;
623- }
614+ arguments . Append ( $ " \" { Path . Combine ( workingDirectory , pe ) } \" ") ;
615+ }
624616
625- _logger . LogMessage ( $ "Found nanoCLR Win32: { nanoClrLocation } ", Settings . LoggingLevel . Verbose ) ;
626- _nanoClr . StartInfo = new ProcessStartInfo ( nanoClrLocation , parameter )
627- {
628- WorkingDirectory = workingDirectory ,
629- UseShellExecute = false ,
630- RedirectStandardError = true ,
631- RedirectStandardOutput = true
632- } ;
617+ // should we use a local nanoCLR instance?
618+ if ( ! string . IsNullOrEmpty ( _settings . PathToLocalCLRInstance ) )
619+ {
620+ arguments . Append ( $ " --localinstance \" { _settings . PathToLocalCLRInstance } \" ") ;
621+ }
633622
634- _logger . LogMessage (
635- $ "Launching process with nanoCLR (from { Path . GetFullPath ( TestObjectHelper . GetNanoClrLocation ( ) ) } )",
636- Settings . LoggingLevel . Verbose ) ;
623+ // if requested, set diagnostic output
624+ if ( _settings . Logging > Settings . LoggingLevel . None )
625+ {
626+ arguments . Append ( " -v diag" ) ;
627+ }
637628
638- // launch nanoCLR
639- if ( ! _nanoClr . Start ( ) )
640- {
641- results . First ( ) . Outcome = TestOutcome . Failed ;
642- results . First ( ) . ErrorMessage = "Failed to start nanoCLR" ;
629+ _logger . LogMessage (
630+ $ "Launching nanoCLR with these arguments: '{ arguments } '",
631+ Settings . LoggingLevel . Verbose ) ;
643632
644- _logger . LogPanicMessage (
645- "Failed to start nanoCLR!" ) ;
646- }
633+ // launch nanoCLR
634+ var cmd = Cli . Wrap ( "nanoclr" )
635+ . WithArguments ( arguments . ToString ( ) )
636+ . WithValidation ( CommandResultValidation . None ) ;
647637
648- _nanoClr . OutputDataReceived += ( sender , e ) =>
649- {
650- if ( e . Data == null )
651- {
652- outputWaitHandle . Set ( ) ;
653- }
654- else
655- {
656- output . AppendLine ( e . Data ) ;
657- }
658- } ;
638+ // setup cancellation token with a timeout of 5 seconds
639+ using ( var cts = new CancellationTokenSource ( TimeSpan . FromSeconds ( 5 ) ) )
640+ {
641+ var cliResult = await cmd . ExecuteBufferedAsync ( cts . Token ) ;
642+ var exitCode = cliResult . ExitCode ;
643+
644+ // read standard output
645+ var output = cliResult . StandardOutput ;
659646
660- _nanoClr . ErrorDataReceived += ( sender , e ) =>
647+ if ( exitCode == 0 )
661648 {
662- if ( e . Data == null )
663- {
664- errorWaitHandle . Set ( ) ;
665- }
666- else
649+ try
667650 {
668- error . AppendLine ( e . Data ) ;
669- }
670- } ;
671-
672- _nanoClr . Start ( ) ;
651+ // process output to gather tests results
652+ ParseTestResults ( output , results ) ;
673653
674- _nanoClr . BeginOutputReadLine ( ) ;
675- _nanoClr . BeginErrorReadLine ( ) ;
654+ _logger . LogMessage ( output , Settings . LoggingLevel . Verbose ) ;
676655
677- _logger . LogMessage (
678- $ "nanoCLR started @ process ID: { _nanoClr . Id } ",
679- Settings . LoggingLevel . Detailed ) ;
656+ if ( ! output . Contains ( Done ) )
657+ {
658+ results . First ( ) . Outcome = TestOutcome . Failed ;
659+ results . First ( ) . ErrorMessage = output ;
660+ }
680661
662+ var notPassedOrFailed = results . Where ( m => m . Outcome != TestOutcome . Failed
663+ && m . Outcome != TestOutcome . Passed
664+ && m . Outcome != TestOutcome . Skipped ) ;
681665
682- // wait for exit, no worries about the outcome
683- _nanoClr . WaitForExit ( _testSessionTimeout ) ;
666+ if ( notPassedOrFailed . Any ( ) )
667+ {
668+ notPassedOrFailed . First ( ) . ErrorMessage = output ;
669+ }
684670
685- CheckAllTests ( output . ToString ( ) , results ) ;
686- _logger . LogMessage ( output . ToString ( ) , Settings . LoggingLevel . Verbose ) ;
687- if ( ! output . ToString ( ) . Contains ( Done ) )
688- {
689- results . First ( ) . Outcome = TestOutcome . Failed ;
690- results . First ( ) . ErrorMessage = output . ToString ( ) ;
691- }
671+ }
672+ catch ( Exception ex )
673+ {
674+ _logger . LogMessage (
675+ $ "Fatal exception when processing test results: >>>{ ex . Message } \r \n { output } ",
676+ Settings . LoggingLevel . Detailed ) ;
692677
693- var notPassedOrFailed = results . Where ( m => m . Outcome != TestOutcome . Failed && m . Outcome != TestOutcome . Passed && m . Outcome != TestOutcome . Skipped ) ;
694- if ( notPassedOrFailed . Any ( ) )
695- {
696- notPassedOrFailed . First ( ) . ErrorMessage = output . ToString ( ) ;
678+ results . First ( ) . Outcome = TestOutcome . Failed ;
679+ }
697680 }
698-
699- }
700- catch ( Exception ex )
701- {
702- _logger . LogMessage (
703- $ "Fatal exception when processing test results: >>>{ ex . Message } \r \n { output } \r \n { error } ",
704- Settings . LoggingLevel . Detailed ) ;
705-
706- results . First ( ) . Outcome = TestOutcome . Failed ;
707- results . First ( ) . ErrorMessage = $ "Fatal exception when processing test results. Set logging to 'Detailed' for details.";
708- }
709- finally
710- {
711- if ( ! _nanoClr . HasExited )
681+ else
712682 {
713- _logger . LogMessage (
714- "Attempting to kill nanoCLR process..." ,
715- Settings . LoggingLevel . Verbose ) ;
683+ _logger . LogPanicMessage ( $ "nanoCLR ended with '{ exitCode } ' exit code.\r \n >>>>>>>>>>>>>\r \n { output } \r \n >>>>>>>>>>>>>") ;
716684
717- _nanoClr . Kill ( ) ;
718- _nanoClr . WaitForExit ( 2000 ) ;
685+ results . First ( ) . Outcome = TestOutcome . Failed ;
686+ results . First ( ) . ErrorMessage = $ "nanoCLR execution ended with exit code: { exitCode } . Check log for details.";
687+
688+ return results ;
719689 }
720690 }
721691
722692 return results ;
723693 }
724694
725- private void CheckAllTests ( string rawOutput , List < TestResult > results )
695+ private void ParseTestResults ( string rawOutput , List < TestResult > results )
726696 {
727697 var outputStrings = Regex . Replace (
728698 rawOutput ,
0 commit comments