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);
        }
Beispiel #2
0
        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());
            }
        }