diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..a5a9d5f
--- /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 601d650..7db2c4c 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;
+ executeTests:
- // 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();
- }
+ _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 = string.Empty;
+ 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 0000000..cb8fead
--- /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