Skip to content

Commit dc1dc95

Browse files
authored
Replace WIN32 with .NET tool for nanoCLR in test executor (#202)
1 parent 6626e88 commit dc1dc95

File tree

8 files changed

+197
-173
lines changed

8 files changed

+197
-173
lines changed

poc/TestOfTestFramework/NFUnitTest.nfproj

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,4 @@
5252
<ProjectConfigurationsDeclaredAsItems />
5353
</ProjectCapabilities>
5454
</ProjectExtensions>
55-
<Import Project="..\packages\nanoFramework.TestFramework.2.1.60\build\nanoFramework.TestFramework.targets" Condition="Exists('..\packages\nanoFramework.TestFramework.2.1.60\build\nanoFramework.TestFramework.targets')" />
56-
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
57-
<PropertyGroup>
58-
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105.The missing file is {0}.</ErrorText>
59-
</PropertyGroup>
60-
<Error Condition="!Exists('..\packages\nanoFramework.TestFramework.2.1.60\build\nanoFramework.TestFramework.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\nanoFramework.TestFramework.2.1.60\build\nanoFramework.TestFramework.targets'))" />
61-
</Target>
6255
</Project>

source/TestAdapter/Executor.cs

Lines changed: 87 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
// See LICENSE file in the project root for full license information.
55
//
66

7+
using CliWrap;
8+
using CliWrap.Buffered;
79
using ICSharpCode.Decompiler;
810
using ICSharpCode.Decompiler.CSharp;
911
using 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,
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
//
2+
// Copyright (c) .NET Foundation and Contributors
3+
// See LICENSE file in the project root for full license information.
4+
//
5+
6+
using CliWrap;
7+
using CliWrap.Buffered;
8+
using nanoFramework.TestPlatform.TestAdapter;
9+
using System;
10+
using System.Text.RegularExpressions;
11+
using System.Threading;
12+
13+
namespace nanoFramework.TestAdapter
14+
{
15+
internal class NanoCLRHelper
16+
{
17+
/// <summary>
18+
/// Flag to report if nanoCLR CLI .NET tool is installed.
19+
/// </summary>
20+
public static bool NanoClrIsInstalled { get; private set; } = false;
21+
22+
public static bool InstallNanoClr(LogMessenger logger)
23+
{
24+
logger.LogMessage(
25+
"Install/upate nanoclr tool",
26+
Settings.LoggingLevel.Verbose);
27+
28+
var cmd = Cli.Wrap("dotnet")
29+
.WithArguments("tool update -g nanoclr")
30+
.WithValidation(CommandResultValidation.None);
31+
32+
// setup cancellation token with a timeout of 1 minute
33+
using (var cts = new CancellationTokenSource(TimeSpan.FromMinutes(1)))
34+
{
35+
var cliResult = cmd.ExecuteBufferedAsync(cts.Token).Task.Result;
36+
37+
if (cliResult.ExitCode == 0)
38+
{
39+
// this will be either (on update):
40+
// Tool 'nanoclr' was successfully updated from version '1.0.205' to version '1.0.208'.
41+
// or (update becoming reinstall with same version, if there is no new version):
42+
// Tool 'nanoclr' was reinstalled with the latest stable version (version '1.0.208').
43+
var regexResult = Regex.Match(cliResult.StandardOutput, @"((?>version ')(?'version'\d+\.\d+\.\d+)(?>'))");
44+
45+
if (regexResult.Success)
46+
{
47+
logger.LogMessage($"Install/update successful. Running v{regexResult.Groups["version"].Value}",
48+
Settings.LoggingLevel.Verbose);
49+
50+
NanoClrIsInstalled = true;
51+
}
52+
else
53+
{
54+
logger.LogPanicMessage($"*** Failed to install/update nanoclr. {cliResult.StandardOutput}.");
55+
56+
NanoClrIsInstalled = false;
57+
}
58+
}
59+
else
60+
{
61+
logger.LogPanicMessage(
62+
$"Failed to install/update nanoclr. Exit code {cliResult.ExitCode}."
63+
+ Environment.NewLine
64+
+ Environment.NewLine
65+
+ "****************************************"
66+
+ Environment.NewLine
67+
+ "*** WON'T BE ABLE TO RUN UNITS TESTS ***"
68+
+ Environment.NewLine
69+
+ "****************************************");
70+
71+
NanoClrIsInstalled = false;
72+
}
73+
}
74+
75+
// report outcome
76+
return NanoClrIsInstalled;
77+
}
78+
}
79+
}

source/TestAdapter/Settings.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
namespace nanoFramework.TestPlatform.TestAdapter
1111
{
1212
/// <summary>
13-
/// Settings for the nanoFramework tests
13+
/// Settings for .NET nanoFramework Test Adapter.
1414
/// </summary>
1515
public class Settings
1616
{
@@ -24,6 +24,11 @@ public class Settings
2424
/// </summary>
2525
public string RealHardwarePort { get; set; } = string.Empty;
2626

27+
/// <summary>
28+
/// Path to a local nanoCLR instance to use to run Unit Tests.
29+
/// </summary>
30+
public string PathToLocalCLRInstance { get; set; } = string.Empty;
31+
2732
/// <summary>
2833
/// Level of logging for test execution.
2934
/// </summary>

0 commit comments

Comments
 (0)