private async Task <List <TestResult> > RunTestOnHardwareAsync(List <TestCase> tests) { _logger.LogMessage( "Setting up test runner in *** CONNECTED DEVICE ***", Settings.LoggingLevel.Detailed); List <TestResult> results = PrepareListResult(tests); List <byte[]> assemblies = new List <byte[]>(); int retryCount = 0; var serialDebugClient = PortBase.CreateInstanceForSerial(true, 2000); retryConnection: if (string.IsNullOrEmpty(_settings.RealHardwarePort)) { _logger.LogMessage($"Waiting for device enumeration to complete.", Settings.LoggingLevel.Verbose); } else { _logger.LogMessage($"Checking device on port {_settings.RealHardwarePort}.", Settings.LoggingLevel.Verbose); } while (!serialDebugClient.IsDevicesEnumerationComplete) { Thread.Sleep(1); } _logger.LogMessage($"Found: {serialDebugClient.NanoFrameworkDevices.Count} devices", Settings.LoggingLevel.Verbose); if (serialDebugClient.NanoFrameworkDevices.Count == 0) { if (retryCount > _numberOfRetries) { results.First().Outcome = TestOutcome.Failed; results.First().ErrorMessage = $"Couldn't find any device, please try to disable the device scanning in the Visual Studio Extension! If the situation persists reboot the device as well."; return(results); } else { retryCount++; serialDebugClient.ReScanDevices(); goto retryConnection; } } retryCount = 0; NanoDeviceBase device; if (serialDebugClient.NanoFrameworkDevices.Count > 1 && !string.IsNullOrEmpty(_settings.RealHardwarePort)) { // get the device at the requested COM port (if there is one) device = serialDebugClient.NanoFrameworkDevices.FirstOrDefault(m => m.SerialNumber == _settings.RealHardwarePort); // sanity check if (device is null) { // no device, done here _logger.LogMessage($"No device available at {_settings.RealHardwarePort}.", Settings.LoggingLevel.Verbose); return(results); } } else { // no COM port requested, just grab the 1st one device = serialDebugClient.NanoFrameworkDevices[0]; } _logger.LogMessage( $"Getting things ready with {device.Description}", Settings.LoggingLevel.Detailed); // check if debugger engine exists if (device.DebugEngine == null) { device.CreateDebugEngine(); _logger.LogMessage($"Debug engine created.", Settings.LoggingLevel.Verbose); } bool deviceIsInInitializeState = false; retryDebug: bool connectResult = device.DebugEngine.Connect(5000, true, true); _logger.LogMessage($"Device connect result is {connectResult}. Attempt {retryCount}/{_numberOfRetries}", Settings.LoggingLevel.Verbose); if (!connectResult) { if (retryCount < _numberOfRetries) { // Give it a bit of time await Task.Delay(100); retryCount++; goto retryDebug; } else { results.First().Outcome = TestOutcome.Failed; results.First().ErrorMessage = $"Couldn't connect to the device, please try to disable the device scanning in the Visual Studio Extension! If the situation persists reboot the device as well."; return(results); } } retryCount = 0; retryErase: // erase the device _logger.LogMessage($"Erase deployment block storage. Attempt {retryCount}/{_numberOfRetries}.", Settings.LoggingLevel.Verbose); var eraseResult = device.Erase( EraseOptions.Deployment, null, null); _logger.LogMessage($"Erase result is {eraseResult}.", Settings.LoggingLevel.Verbose); if (!eraseResult) { if (retryCount < _numberOfRetries) { // Give it a bit of time await Task.Delay(400); retryCount++; goto retryErase; } else { results.First().Outcome = TestOutcome.Failed; results.First().ErrorMessage = $"Couldn't erase the device, please try to disable the device scanning in the Visual Studio Extension! If the situation persists reboot the device as well."; return(results); } } retryCount = 0; // initial check if (device.DebugEngine.IsDeviceInInitializeState()) { _logger.LogMessage($"Device status verified as being in initialized state. Requesting to resume execution. Attempt {retryCount}/{_numberOfRetries}.", Settings.LoggingLevel.Error); // set flag deviceIsInInitializeState = true; // device is still in initialization state, try resume execution device.DebugEngine.ResumeExecution(); } // handle the workflow required to try resuming the execution on the device // only required if device is not already there // retry 5 times with a 500ms interval between retries while (retryCount++ < _numberOfRetries && deviceIsInInitializeState) { if (!device.DebugEngine.IsDeviceInInitializeState()) { _logger.LogMessage($"Device has completed initialization.", Settings.LoggingLevel.Verbose); // done here deviceIsInInitializeState = false; break; } _logger.LogMessage($"Waiting for device to report initialization completed ({retryCount}/{_numberOfRetries}).", Settings.LoggingLevel.Verbose); // provide feedback to user on the 1st pass if (retryCount == 0) { _logger.LogMessage($"Waiting for device to initialize.", Settings.LoggingLevel.Verbose); } if (device.DebugEngine.IsConnectedTonanoBooter) { _logger.LogMessage($"Device reported running nanoBooter. Requesting to load nanoCLR.", Settings.LoggingLevel.Verbose); // request nanoBooter to load CLR device.DebugEngine.ExecuteMemory(0); } else if (device.DebugEngine.IsConnectedTonanoCLR) { _logger.LogMessage($"Device reported running nanoCLR. Requesting to reboot nanoCLR.", Settings.LoggingLevel.Error); await Task.Run(delegate { // already running nanoCLR try rebooting the CLR device.DebugEngine.RebootDevice(RebootOptions.ClrOnly); }); } // wait before next pass // use a back-off strategy of increasing the wait time to accommodate slower or less responsive targets (such as networked ones) await Task.Delay(TimeSpan.FromMilliseconds(_timeoutMiliseconds * (retryCount + 1))); await Task.Yield(); } // check if device is still in initialized state if (!deviceIsInInitializeState) { // device has left initialization state _logger.LogMessage($"Device is initialized and ready!", Settings.LoggingLevel.Verbose); await Task.Yield(); ////////////////////////////////////////////////////////// // sanity check for devices without native assemblies ?!?! if (device.DeviceInfo.NativeAssemblies.Count == 0) { _logger.LogMessage($"Device reporting no assemblies loaded. This can not happen. Sanity check failed.", Settings.LoggingLevel.Error); // there are no assemblies deployed?! results.First().Outcome = TestOutcome.Failed; results.First().ErrorMessage = $"Couldn't find any native assemblies deployed in {device.Description}, {device.TargetName} on {device.SerialNumber}! If the situation persists reboot the device."; return(results); } _logger.LogMessage($"Computing deployment blob.", Settings.LoggingLevel.Verbose); // build a list with the full path for each DLL, referenced DLL and EXE List <DeploymentAssembly> assemblyList = new List <DeploymentAssembly>(); var source = tests.First().Source; var workingDirectory = Path.GetDirectoryName(source); var allPeFiles = Directory.GetFiles(workingDirectory, "*.pe"); var decompilerSettings = new DecompilerSettings { LoadInMemory = false, ThrowOnAssemblyResolveErrors = false }; foreach (string assemblyPath in allPeFiles) { // load assembly in order to get the versions var file = Path.Combine(workingDirectory, assemblyPath.Replace(".pe", ".dll")); if (!File.Exists(file)) { // Check with an exe file = Path.Combine(workingDirectory, assemblyPath.Replace(".pe", ".exe")); } var decompiler = new CSharpDecompiler(file, decompilerSettings);; var assemblyProperties = decompiler.DecompileModuleAndAssemblyAttributesToString(); // AssemblyVersion string pattern = @"(?<=AssemblyVersion\("")(.*)(?=\""\)])"; var match = Regex.Matches(assemblyProperties, pattern, RegexOptions.IgnoreCase); string assemblyVersion = match[0].Value; // AssemblyNativeVersion pattern = @"(?<=AssemblyNativeVersion\("")(.*)(?=\""\)])"; match = Regex.Matches(assemblyProperties, pattern, RegexOptions.IgnoreCase); // only class libs have this attribute, therefore sanity check is required string nativeVersion = ""; if (match.Count == 1) { nativeVersion = match[0].Value; } assemblyList.Add(new DeploymentAssembly(Path.Combine(workingDirectory, assemblyPath), assemblyVersion, nativeVersion)); } _logger.LogMessage($"Added {assemblyList.Count} assemblies to deploy.", Settings.LoggingLevel.Verbose); await Task.Yield(); // Keep track of total assembly size long totalSizeOfAssemblies = 0; // now we will re-deploy all system assemblies foreach (DeploymentAssembly peItem in assemblyList) { // append to the deploy blob the assembly using (FileStream fs = File.Open(peItem.Path, FileMode.Open, FileAccess.Read)) { long length = (fs.Length + 3) / 4 * 4; _logger.LogMessage($"Adding {Path.GetFileNameWithoutExtension(peItem.Path)} v{peItem.Version} ({length} bytes) to deployment bundle", Settings.LoggingLevel.Verbose); byte[] buffer = new byte[length]; await Task.Yield(); await fs.ReadAsync(buffer, 0, (int)fs.Length); assemblies.Add(buffer); // Increment totalizer totalSizeOfAssemblies += length; } } _logger.LogMessage($"Deploying {assemblyList.Count:N0} assemblies to device... Total size in bytes is {totalSizeOfAssemblies}.", Settings.LoggingLevel.Verbose); // need to keep a copy of the deployment blob for the second attempt (if needed) var assemblyCopy = new List <byte[]>(assemblies); await Task.Yield(); var deploymentLogger = new Progress <string>((m) => _logger.LogMessage(m, Settings.LoggingLevel.Detailed)); await Task.Run(async delegate { // OK to skip erase as we just did that // no need to reboot device if (!device.DebugEngine.DeploymentExecute( assemblyCopy, false, false, null, deploymentLogger)) { // if the first attempt fails, give it another try // wait before next pass await Task.Delay(TimeSpan.FromSeconds(1)); await Task.Yield(); _logger.LogMessage("Deploying assemblies. Second attempt.", Settings.LoggingLevel.Verbose); // !! need to use the deployment blob copy assemblyCopy = new List <byte[]>(assemblies); // can't skip erase as we just did that // no need to reboot device if (!device.DebugEngine.DeploymentExecute( assemblyCopy, false, false, null, deploymentLogger)) { _logger.LogMessage("Deployment failed.", Settings.LoggingLevel.Error); // throw exception to signal deployment failure results.First().Outcome = TestOutcome.Failed; results.First().ErrorMessage = $"Deployment failed in {device.Description}, {device.TargetName} on {device.SerialNumber}! If the situation persists reboot the device."; } } }); await Task.Yield(); // If there has been an issue before, the first test is marked as failed if (results.First().Outcome == TestOutcome.Failed) { return(results); } StringBuilder output = new StringBuilder(); bool isFinished = false; // attach listener for messages device.DebugEngine.OnMessage += (message, text) => { _logger.LogMessage(text, Settings.LoggingLevel.Verbose); output.Append(text); if (text.Contains(Done)) { isFinished = true; } }; device.DebugEngine.RebootDevice(RebootOptions.ClrOnly); while (!isFinished) { Thread.Sleep(1); } _logger.LogMessage($"Tests finished.", Settings.LoggingLevel.Verbose); CheckAllTests(output.ToString(), results); } else { _logger.LogMessage("Failed to initialize device.", Settings.LoggingLevel.Error); } return(results); }
private string GetAssemblyCode(string assemblyPath, CSharpDecompiler decompiler) { using (var output = new StringWriter()) { WriteCommentLine(output, assemblyPath); var module = decompiler.TypeSystem.MainModule.PEFile; var metadata = module.Metadata; if (metadata.IsAssembly) { var name = metadata.GetAssemblyDefinition(); if ((name.Flags & System.Reflection.AssemblyFlags.WindowsRuntime) != 0) { WriteCommentLine(output, metadata.GetString(name.Name) + " [WinRT]"); } else { WriteCommentLine(output, metadata.GetFullAssemblyName()); } } else { WriteCommentLine(output, module.Name); } var mainModule = decompiler.TypeSystem.MainModule; var globalType = mainModule.TypeDefinitions.FirstOrDefault(); if (globalType != null) { output.Write("// Global type: "); output.Write(globalType.FullName); output.WriteLine(); } var corHeader = module.Reader.PEHeaders.CorHeader; var entrypointHandle = MetadataTokenHelpers.EntityHandleOrNil(corHeader.EntryPointTokenOrRelativeVirtualAddress); if (!entrypointHandle.IsNil && entrypointHandle.Kind == HandleKind.MethodDefinition) { var entrypoint = mainModule.ResolveMethod(entrypointHandle, new ICSharpCode.Decompiler.TypeSystem.GenericContext()); if (entrypoint != null) { output.Write("// Entry point: "); output.Write(entrypoint.DeclaringType.FullName + "." + entrypoint.Name); output.WriteLine(); } } output.WriteLine("// Architecture: " + module.GetPlatformDisplayName()); if ((corHeader.Flags & System.Reflection.PortableExecutable.CorFlags.ILOnly) == 0) { output.WriteLine("// This assembly contains unmanaged code."); } string runtimeName = module.GetRuntimeDisplayName(); if (runtimeName != null) { output.WriteLine("// Runtime: " + runtimeName); } output.WriteLine(); output.Write(decompiler.DecompileModuleAndAssemblyAttributesToString()); output.WriteLine(); return(output.ToString()); } }