From 4b66e3e3098fe0e15e618b4a3e04c3cd4e56f627 Mon Sep 17 00:00:00 2001 From: Frank Robijn Date: Thu, 10 Oct 2024 10:00:49 +0200 Subject: [PATCH 1/3] GlobalExclusiveDeviceAccess added to (current) testframework --- .editorconfig | 213 +++++++++++ source/TestAdapter/Executor.cs | 624 +++++++++++++++++---------------- spelling_exclusion.dic | 19 + 3 files changed, 561 insertions(+), 295 deletions(-) create mode 100644 .editorconfig create mode 100644 spelling_exclusion.dic diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..a5a9d5fe --- /dev/null +++ b/.editorconfig @@ -0,0 +1,213 @@ +# EditorConfig for Visual Studio 2022: https://learn.microsoft.com/en-us/visualstudio/ide/create-portable-custom-editor-options?view=vs-2022 + +# This is a top-most .editorconfig file +root = true + +#===================================================== +# +# nanoFramework specific settings +# +# +#===================================================== +[*] +# Generic EditorConfig settings +end_of_line = crlf +charset = utf-8-bom + +# Visual Studio spell checker +spelling_languages = en-us +spelling_checkable_types = strings,identifiers,comments +spelling_error_severity = information +spelling_exclusion_path = spelling_exclusion.dic + +#===================================================== +# +# Settings copied from the .NET runtime +# +# https://github.com/dotnet/runtime +# +#===================================================== +# Default settings: +# A newline ending every file +# Use 4 spaces as indentation +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +# Generated code +[*{_AssemblyInfo.cs,.notsupported.cs,AsmOffsets.cs}] +generated_code = true + +# C# files +[*.cs] +# New line preferences +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = false +csharp_indent_switch_labels = true +csharp_indent_labels = one_less_than_current + +# Modifier preferences +csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async:suggestion + +# avoid this. unless absolutely necessary +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_event = false:suggestion + +# Types: use keywords instead of BCL types, and permit var only when the type is clear +csharp_style_var_for_built_in_types = false:suggestion +csharp_style_var_when_type_is_apparent = false:none +csharp_style_var_elsewhere = false:suggestion +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion + +# name all constant fields using PascalCase +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.required_modifiers = const +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +# static fields should have s_ prefix +dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion +dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields +dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style +dotnet_naming_symbols.static_fields.applicable_kinds = field +dotnet_naming_symbols.static_fields.required_modifiers = static +dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected +dotnet_naming_style.static_prefix_style.required_prefix = s_ +dotnet_naming_style.static_prefix_style.capitalization = camel_case + +# internal and private fields should be _camelCase +dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion +dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields +dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style +dotnet_naming_symbols.private_internal_fields.applicable_kinds = field +dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal +dotnet_naming_style.camel_case_underscore_style.required_prefix = _ +dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case + +# Code style defaults +csharp_using_directive_placement = outside_namespace:suggestion +dotnet_sort_system_directives_first = true +csharp_prefer_braces = true:silent +csharp_preserve_single_line_blocks = true:none +csharp_preserve_single_line_statements = false:none +csharp_prefer_static_local_function = true:suggestion +csharp_prefer_simple_using_statement = false:none +csharp_style_prefer_switch_expression = true:suggestion +dotnet_style_readonly_field = true:suggestion + +# Expression-level preferences +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_collection_expression = when_types_exactly_match +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +csharp_prefer_simple_default_expression = true:suggestion + +# Expression-bodied members +csharp_style_expression_bodied_methods = true:silent +csharp_style_expression_bodied_constructors = true:silent +csharp_style_expression_bodied_operators = true:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = true:silent + +# Pattern matching +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion + +# Null checking preferences +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion + +# Other features +csharp_style_prefer_index_operator = false:none +csharp_style_prefer_range_operator = false:none +csharp_style_pattern_local_over_anonymous_function = false:none + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = do_not_ignore +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# License header +file_header_template = Licensed to the .NET Foundation under one or more agreements.\nThe .NET Foundation licenses this file to you under the MIT license. + +# C++ Files +[*.{cpp,h,in}] +curly_bracket_next_line = true +indent_brace_style = Allman + +# Xml project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}] +indent_size = 2 + +[*.{csproj,vbproj,proj,nativeproj,locproj}] +charset = utf-8 + +# Xml build files +[*.builds] +indent_size = 2 + +# Xml files +[*.{xml,stylecop,resx,ruleset}] +indent_size = 2 + +# Xml config files +[*.{props,targets,config,nuspec}] +indent_size = 2 + +# YAML config files +[*.{yml,yaml}] +indent_size = 2 + +# Shell scripts +[*.sh] +end_of_line = lf +[*.{cmd,bat}] +end_of_line = crlf \ No newline at end of file diff --git a/source/TestAdapter/Executor.cs b/source/TestAdapter/Executor.cs index 601d650f..2f9a14ad 100644 --- a/source/TestAdapter/Executor.cs +++ b/source/TestAdapter/Executor.cs @@ -1,18 +1,6 @@ -// -// Copyright (c) .NET Foundation and Contributors -// Portions Copyright (c) Microsoft Corporation. All rights reserved. -// See LICENSE file in the project root for full license information. -// +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -using CliWrap; -using CliWrap.Buffered; -using ICSharpCode.Decompiler; -using ICSharpCode.Decompiler.CSharp; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; -using nanoFramework.TestAdapter; -using nanoFramework.Tools.Debugger; -using nanoFramework.Tools.Debugger.Extensions; using System; using System.Collections.Generic; using System.Diagnostics; @@ -23,6 +11,16 @@ using System.Threading; using System.Threading.Tasks; using System.Xml; +using CliWrap; +using CliWrap.Buffered; +using ICSharpCode.Decompiler; +using ICSharpCode.Decompiler.CSharp; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; +using nanoFramework.TestAdapter; +using nanoFramework.Tools.Debugger; +using nanoFramework.Tools.Debugger.Extensions; +using nanoFramework.Tools.Debugger.NFDevice; namespace nanoFramework.TestPlatform.TestAdapter { @@ -50,6 +48,9 @@ class Executor : ITestExecutor /// test session timeout (from the runsettings file) private int _testSessionTimeout = 30_0000; + // timeout to get exclusive access to a device + private const int _timeoutExclusiveAccess = 5000; + private IFrameworkHandle _frameworkHandle = null; /// @@ -195,335 +196,342 @@ private async Task> RunTestOnHardwareAsync(List tests List assemblies = new List(); int retryCount = 0; NanoDeviceBase device = null; - - bool realHardwarePortSet = !string.IsNullOrEmpty(_settings.RealHardwarePort); - - PortBase serialDebugClient; - - if (realHardwarePortSet) + GlobalExclusiveDeviceAccess exclusiveAccess = null; + try { - serialDebugClient = PortBase.CreateInstanceForSerial(false); - _logger.LogMessage($"Checking device on port {_settings.RealHardwarePort}.", Settings.LoggingLevel.Verbose); + bool realHardwarePortSet = !string.IsNullOrEmpty(_settings.RealHardwarePort); - try - { - serialDebugClient.AddDevice(_settings.RealHardwarePort); + PortBase serialDebugClient; - device = serialDebugClient.NanoFrameworkDevices[0]; - - // all good here, proceed to execute tests - goto executeTests; - } -#if DEBUG - catch (Exception ex) -#else - catch -#endif + if (realHardwarePortSet) { - results.First().Outcome = TestOutcome.Failed; - results.First().ErrorMessage = $"Couldn't find any valid nanoDevice @ {_settings.RealHardwarePort}. Maybe try to disable the device watchers in Visual Studio Extension! If the situation persists reboot the device and/or disconnect and connect it again."; + serialDebugClient = PortBase.CreateInstanceForSerial(false); - _logger.LogMessage($"Couldn't find any valid nanoDevice @ {_settings.RealHardwarePort}.", Settings.LoggingLevel.Verbose); + _logger.LogMessage($"Checking device on port {_settings.RealHardwarePort}.", Settings.LoggingLevel.Verbose); - return results; - } - } - else - { - serialDebugClient = PortBase.CreateInstanceForSerial(true, - 2000); - } + exclusiveAccess = GlobalExclusiveDeviceAccess.TryGet(_settings.RealHardwarePort, _timeoutExclusiveAccess); + if (exclusiveAccess is null) + { + results.First().Outcome = TestOutcome.Skipped; + results.First().ErrorMessage = $"Couldn't access the device @ {_settings.RealHardwarePort}. Another application is using the device. If the situation persists reboot the device and/or disconnect and connect it again."; - retryConnection: + _logger.LogMessage($"Couldn't get exclusive access to the nanoDevice @ {_settings.RealHardwarePort}.", Settings.LoggingLevel.Verbose); - if (string.IsNullOrEmpty(_settings.RealHardwarePort)) - { - _logger.LogMessage($"Waiting for device enumeration to complete.", Settings.LoggingLevel.Verbose); - } + return results; + } - while (!serialDebugClient.IsDevicesEnumerationComplete) - { - Thread.Sleep(1); - } + try + { + serialDebugClient.AddDevice(_settings.RealHardwarePort); - _logger.LogMessage($"Found: {serialDebugClient.NanoFrameworkDevices.Count} devices", Settings.LoggingLevel.Verbose); + device = serialDebugClient.NanoFrameworkDevices[0]; - if (serialDebugClient.NanoFrameworkDevices.Count == 0) - { - if (retryCount > _numberOfRetries) - { - results.First().Outcome = TestOutcome.Failed; - results.First().ErrorMessage = "Couldn't find any valid nanoDevice. Maybe try to disable the device watchers in Visual Studio Extension! If the situation persists reboot the device and/or disconnect and connect it again."; + // all good here, proceed to execute tests + goto executeTests; + } +#if DEBUG + catch (Exception ex) +#else + catch +#endif + { + results.First().Outcome = TestOutcome.Failed; + results.First().ErrorMessage = $"Couldn't find any valid nanoDevice @ {_settings.RealHardwarePort}. Maybe try to disable the device watchers in Visual Studio Extension! If the situation persists reboot the device and/or disconnect and connect it again."; - _logger.LogMessage("Couldn't find any valid nanoDevice.", Settings.LoggingLevel.Verbose); + _logger.LogMessage($"Couldn't find any valid nanoDevice @ {_settings.RealHardwarePort}.", Settings.LoggingLevel.Verbose); - return results; + return results; + } } else { - // add retry counter before trying again - retryCount++; - - // re-scan devices - serialDebugClient.ReScanDevices(); - - goto retryConnection; + serialDebugClient = PortBase.CreateInstanceForSerial(true, + 2000); } - } - retryCount = 0; + retryConnection: - // grab the 1st device available - device = serialDebugClient.NanoFrameworkDevices[0]; + if (string.IsNullOrEmpty(_settings.RealHardwarePort)) + { + _logger.LogMessage($"Waiting for device enumeration to complete.", Settings.LoggingLevel.Verbose); + } - executeTests: + while (!serialDebugClient.IsDevicesEnumerationComplete) + { + Thread.Sleep(1); + } - _logger.LogMessage( - $"Getting things ready with {device.Description}", - Settings.LoggingLevel.Detailed); + _logger.LogMessage($"Found: {serialDebugClient.NanoFrameworkDevices.Count} devices", Settings.LoggingLevel.Verbose); - // check if debugger engine exists - if (device.DebugEngine == null) - { - device.CreateDebugEngine(); - _logger.LogMessage($"Debug engine created.", Settings.LoggingLevel.Verbose); - } + if (serialDebugClient.NanoFrameworkDevices.Count == 0) + { + if (retryCount > _numberOfRetries) + { + results.First().Outcome = TestOutcome.Failed; + results.First().ErrorMessage = "Couldn't find any valid nanoDevice. Maybe try to disable the device watchers in Visual Studio Extension! If the situation persists reboot the device and/or disconnect and connect it again."; - bool deviceIsInInitializeState = false; + _logger.LogMessage("Couldn't find any valid nanoDevice.", Settings.LoggingLevel.Verbose); - retryDebug: - bool connectResult = device.DebugEngine.Connect(5000, true, true); - _logger.LogMessage($"Device connect result is {connectResult}. Attempt {retryCount}/{_numberOfRetries}", Settings.LoggingLevel.Verbose); + return results; + } + else + { + // add retry counter before trying again + retryCount++; - if (!connectResult) - { - if (retryCount < _numberOfRetries) - { - // Give it a bit of time - await Task.Delay(100); - retryCount++; + // re-scan devices + serialDebugClient.ReScanDevices(); - 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; + goto retryConnection; + } } - } - retryCount = 0; + retryCount = 0; - retryErase: - // erase the device - _logger.LogMessage($"Erase deployment block storage. Attempt {retryCount}/{_numberOfRetries}.", Settings.LoggingLevel.Verbose); + // grab the 1st device available + device = serialDebugClient.NanoFrameworkDevices[0]; - var eraseResult = device.Erase( - EraseOptions.Deployment, - null, - null); + if (exclusiveAccess is null) + { + exclusiveAccess = GlobalExclusiveDeviceAccess.TryGet(device, _timeoutExclusiveAccess); + if (exclusiveAccess is null) + { + results.First().Outcome = TestOutcome.Skipped; + results.First().ErrorMessage = $"Couldn't access the device {device.Description}. Another application is using the device. If the situation persists reboot the device and/or disconnect and connect it again."; - _logger.LogMessage($"Erase result is {eraseResult}.", Settings.LoggingLevel.Verbose); + _logger.LogMessage($"Couldn't get exclusive access to the nanoDevice @ {device.Description}.", 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; + 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; + executeTests: - // device is still in initialization state, try resume execution - device.DebugEngine.ResumeExecution(); - } + _logger.LogMessage( + $"Getting things ready with {device.Description}", + Settings.LoggingLevel.Detailed); - // 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()) + // check if debugger engine exists + if (device.DebugEngine == null) { - _logger.LogMessage($"Device has completed initialization.", Settings.LoggingLevel.Verbose); - // done here - deviceIsInInitializeState = false; - break; + device.CreateDebugEngine(); + _logger.LogMessage($"Debug engine created.", Settings.LoggingLevel.Verbose); } - _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); - } + bool deviceIsInInitializeState = false; - 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) + retryDebug: + bool connectResult = device.DebugEngine.Connect(5000, true, true); + _logger.LogMessage($"Device connect result is {connectResult}. Attempt {retryCount}/{_numberOfRetries}", Settings.LoggingLevel.Verbose); + + if (!connectResult) { - _logger.LogMessage($"Device reported running nanoCLR. Requesting to reboot nanoCLR.", Settings.LoggingLevel.Error); - await Task.Run(delegate + if (retryCount < _numberOfRetries) { - // already running nanoCLR try rebooting the CLR - device.DebugEngine.RebootDevice(RebootOptions.ClrOnly); - }); + // 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; + } } - // 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))); + retryCount = 0; - await Task.Yield(); - } + retryErase: + // erase the device + _logger.LogMessage($"Erase deployment block storage. Attempt {retryCount}/{_numberOfRetries}.", Settings.LoggingLevel.Verbose); - // 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(); + var eraseResult = device.Erase( + EraseOptions.Deployment, + null, + null); + _logger.LogMessage($"Erase result is {eraseResult}.", Settings.LoggingLevel.Verbose); - ////////////////////////////////////////////////////////// - // sanity check for devices without native assemblies ?!?! - if (device.DeviceInfo.NativeAssemblies.Count == 0) + if (!eraseResult) { - _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; + 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; + } } - _logger.LogMessage($"Computing deployment blob.", Settings.LoggingLevel.Verbose); + retryCount = 0; - // build a list with the full path for each DLL, referenced DLL and EXE - List assemblyList = new List(); + // 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; - var source = tests.First().Source; - var workingDirectory = Path.GetDirectoryName(source); - var allPeFiles = Directory.GetFiles(workingDirectory, "*.pe"); + // device is still in initialization state, try resume execution + device.DebugEngine.ResumeExecution(); + } - var decompilerSettings = new DecompilerSettings + // 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) { - LoadInMemory = false, - ThrowOnAssemblyResolveErrors = false - }; + if (!device.DebugEngine.IsDeviceInInitializeState()) + { + _logger.LogMessage($"Device has completed initialization.", Settings.LoggingLevel.Verbose); + // done here + deviceIsInInitializeState = false; + break; + } - 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)) + _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) { - // Check with an exe - file = Path.Combine(workingDirectory, assemblyPath.Replace(".pe", ".exe")); + _logger.LogMessage($"Waiting for device to initialize.", Settings.LoggingLevel.Verbose); } - var decompiler = new CSharpDecompiler(file, decompilerSettings); ; - var assemblyProperties = decompiler.DecompileModuleAndAssemblyAttributesToString(); + 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); + }); + } - // AssemblyVersion - string pattern = @"(?<=AssemblyVersion\("")(.*)(?=\""\)])"; - var match = Regex.Matches(assemblyProperties, pattern, RegexOptions.IgnoreCase); - string assemblyVersion = match[0].Value; + // 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))); - // AssemblyNativeVersion - pattern = @"(?<=AssemblyNativeVersion\("")(.*)(?=\""\)])"; - match = Regex.Matches(assemblyProperties, pattern, RegexOptions.IgnoreCase); + await Task.Yield(); + } - // only class libs have this attribute, therefore sanity check is required - string nativeVersion = ""; - if (match.Count == 1) + // 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) { - nativeVersion = match[0].Value; + _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; } - assemblyList.Add(new DeploymentAssembly(Path.Combine(workingDirectory, assemblyPath), assemblyVersion, nativeVersion)); - } + _logger.LogMessage($"Computing deployment blob.", Settings.LoggingLevel.Verbose); - _logger.LogMessage($"Added {assemblyList.Count} assemblies to deploy.", Settings.LoggingLevel.Verbose); - await Task.Yield(); + // build a list with the full path for each DLL, referenced DLL and EXE + List assemblyList = new List(); - // Keep track of total assembly size - long totalSizeOfAssemblies = 0; + var source = tests.First().Source; + var workingDirectory = Path.GetDirectoryName(source); + var allPeFiles = Directory.GetFiles(workingDirectory, "*.pe"); - // 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)) + var decompilerSettings = new DecompilerSettings { - 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]; + LoadInMemory = false, + ThrowOnAssemblyResolveErrors = false + }; - await Task.Yield(); + 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")); + } - await fs.ReadAsync(buffer, 0, (int)fs.Length); - assemblies.Add(buffer); + var decompiler = new CSharpDecompiler(file, decompilerSettings); ; + var assemblyProperties = decompiler.DecompileModuleAndAssemblyAttributesToString(); - // Increment totalizer - totalSizeOfAssemblies += length; - } - } + // 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); - _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(assemblies); + // 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)); + } - await Task.Yield(); + _logger.LogMessage($"Added {assemblyList.Count} assemblies to deploy.", Settings.LoggingLevel.Verbose); + await Task.Yield(); - var deploymentLogger = new Progress((m) => _logger.LogMessage(m, Settings.LoggingLevel.Detailed)); + // Keep track of total assembly size + long totalSizeOfAssemblies = 0; - 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)) + // now we will re-deploy all system assemblies + foreach (DeploymentAssembly peItem in assemblyList) { - // if the first attempt fails, give it another try + // 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); - // wait before next pass - await Task.Delay(TimeSpan.FromSeconds(1)); + // Increment totalizer + totalSizeOfAssemblies += length; + } + } - await Task.Yield(); + _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(assemblies); - _logger.LogMessage("Deploying assemblies. Second attempt.", Settings.LoggingLevel.Verbose); + await Task.Yield(); - // !! need to use the deployment blob copy - assemblyCopy = new List(assemblies); + var deploymentLogger = new Progress((m) => _logger.LogMessage(m, Settings.LoggingLevel.Detailed)); - // can't skip erase as we just did that + await Task.Run(async delegate + { + // OK to skip erase as we just did that // no need to reboot device if (!device.DebugEngine.DeploymentExecute( assemblyCopy, @@ -532,57 +540,83 @@ await Task.Run(async delegate null, deploymentLogger)) { - _logger.LogMessage("Deployment failed.", Settings.LoggingLevel.Error); + // if the first attempt fails, give it another try - // 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."; - } - } - }); + // wait before next pass + await Task.Delay(TimeSpan.FromSeconds(1)); - 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; - } + await Task.Yield(); - StringBuilder output = new StringBuilder(); - ManualResetEvent testExecutionCompleted = new ManualResetEvent(false); + _logger.LogMessage("Deploying assemblies. Second attempt.", Settings.LoggingLevel.Verbose); - // attach listener for messages - device.DebugEngine.OnMessage += (message, text) => - { - _logger.LogMessage(text, Settings.LoggingLevel.Verbose); - output.Append(text); - if (text.Contains(Done)) + // !! need to use the deployment blob copy + assemblyCopy = new List(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) { - // signal test execution completed - testExecutionCompleted.Set(); + return results; } - }; - device.DebugEngine.RebootDevice(RebootOptions.ClrOnly); + StringBuilder output = new StringBuilder(); + ManualResetEvent testExecutionCompleted = new ManualResetEvent(false); - DateTime timeoutForExecution = DateTime.UtcNow.AddMilliseconds(_testSessionTimeout); + // attach listener for messages + device.DebugEngine.OnMessage += (message, text) => + { + _logger.LogMessage(text, Settings.LoggingLevel.Verbose); + output.Append(text); + if (text.Contains(Done)) + { + // signal test execution completed + testExecutionCompleted.Set(); + } + }; - if (testExecutionCompleted.WaitOne(_testSessionTimeout)) - { - _logger.LogMessage($"Tests finished.", Settings.LoggingLevel.Verbose); + device.DebugEngine.RebootDevice(RebootOptions.ClrOnly); + + DateTime timeoutForExecution = DateTime.UtcNow.AddMilliseconds(_testSessionTimeout); + + if (testExecutionCompleted.WaitOne(_testSessionTimeout)) + { + _logger.LogMessage($"Tests finished.", Settings.LoggingLevel.Verbose); - ParseTestResults(output.ToString(), results); + ParseTestResults(output.ToString(), results); + } + else + { + _logger.LogMessage($"Tests timed out.", Settings.LoggingLevel.Error); + results.First().Outcome = TestOutcome.Failed; + results.First().ErrorMessage = $"Tests timed out in {device.Description}"; + } } else { - _logger.LogMessage($"Tests timed out.", Settings.LoggingLevel.Error); - results.First().Outcome = TestOutcome.Failed; - results.First().ErrorMessage = $"Tests timed out in {device.Description}"; + _logger.LogMessage("Failed to initialize device.", Settings.LoggingLevel.Error); } } - else + finally { - _logger.LogMessage("Failed to initialize device.", Settings.LoggingLevel.Error); + exclusiveAccess?.Dispose(); } return results; diff --git a/spelling_exclusion.dic b/spelling_exclusion.dic new file mode 100644 index 00000000..cb8feadd --- /dev/null +++ b/spelling_exclusion.dic @@ -0,0 +1,19 @@ +nano +analyze +cleanup +nfproj +nuget +analyzer +Cleanup' +runsettings +nanoclr +dotnet +nanoclr' +clrversion +csproj +vstest +mscorlib +decompiler +int' +dll' +intellisense From b2e6e2c8b603f45f9a89501d771a8958baf8567b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Sim=C3=B5es?= Date: Mon, 18 Nov 2024 11:13:12 +0000 Subject: [PATCH 2/3] Update source/TestAdapter/Executor.cs Co-authored-by: Laurent Ellerbach --- source/TestAdapter/Executor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/TestAdapter/Executor.cs b/source/TestAdapter/Executor.cs index 2f9a14ad..5c89b1a7 100644 --- a/source/TestAdapter/Executor.cs +++ b/source/TestAdapter/Executor.cs @@ -486,7 +486,7 @@ await Task.Run(delegate match = Regex.Matches(assemblyProperties, pattern, RegexOptions.IgnoreCase); // only class libs have this attribute, therefore sanity check is required - string nativeVersion = ""; + string nativeVersion = string.Empty; if (match.Count == 1) { nativeVersion = match[0].Value; From d1ca1ce1d5517ff31926b549431d54cc3d4fce4c Mon Sep 17 00:00:00 2001 From: josesimoes Date: Mon, 18 Nov 2024 11:19:08 +0000 Subject: [PATCH 3/3] Fix code style issues --- source/TestAdapter/Executor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/TestAdapter/Executor.cs b/source/TestAdapter/Executor.cs index 5c89b1a7..7db2c4cf 100644 --- a/source/TestAdapter/Executor.cs +++ b/source/TestAdapter/Executor.cs @@ -233,7 +233,7 @@ private async Task> RunTestOnHardwareAsync(List tests #if DEBUG catch (Exception ex) #else - catch + catch #endif { results.First().Outcome = TestOutcome.Failed; @@ -434,8 +434,8 @@ await Task.Run(delegate { // device has left initialization state _logger.LogMessage($"Device is initialized and ready!", Settings.LoggingLevel.Verbose); - await Task.Yield(); + await Task.Yield(); ////////////////////////////////////////////////////////// // sanity check for devices without native assemblies ?!?!