Skip to content

Commit

Permalink
Test and patch for short-name-app-config bug
Browse files Browse the repository at this point in the history
See #241
  • Loading branch information
atruskie committed Sep 10, 2019
1 parent 7542295 commit 16881a3
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 42 deletions.
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ csharp_style_var_when_type_is_apparent = true:none
csharp_style_var_elsewhere = true:none
# Expression-bodied members
# https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference#expression_bodied_members
csharp_style_expression_bodied_methods = when_on_single_line:suggestion
csharp_style_expression_bodied_methods = when_on_single_line:hint
csharp_style_expression_bodied_constructors = when_on_single_line:suggestion
csharp_style_expression_bodied_operators = true:warning
csharp_style_expression_bodied_properties = true:warning
Expand Down
2 changes: 1 addition & 1 deletion docs/developing.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
See [Contributing](https://github.com/QutEcoacoustics/audio-analysis/blob/master/CONTRIBUTING.md)
See [Contributing](https://github.com/QutEcoacoustics/audio-analysis/blob/master/CONTRIBUTING.md)
2 changes: 2 additions & 0 deletions src/AnalysisPrograms/CheckEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace AnalysisPrograms
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
Expand All @@ -30,6 +31,7 @@ public class CheckEnvironment
private int Execute(Arguments arguments)
{
var errors = new List<string>();

Log.Info("Checking required executables and libraries can be found and loaded");

// this is an important call used in analyze long recordings.
Expand Down
5 changes: 4 additions & 1 deletion src/AnalysisPrograms/MainEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ public static async Task<int> Main(string[] args)
// Uses an env var to attach debugger before argument parsing
AttachDebugger(ApAutoAttach ? DebugOptions.YesSilent : DebugOptions.No);

Logging = new Logging(colorConsole: !ApPlainLogging, Level.Info, quietConsole: false);
Logging = new Logging(
colorConsole: !ApPlainLogging,
VerbosityToLevel(ApDefaultLogVerbosity ?? LogVerbosity.Info),
quietConsole: false);

Copyright();

Expand Down
161 changes: 122 additions & 39 deletions src/AnalysisPrograms/Production/MainEntryUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ namespace AnalysisPrograms
#endif
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Linq;
Expand All @@ -28,11 +29,11 @@ namespace AnalysisPrograms
#if DEBUG
using Acoustics.Shared.Debugging;
#endif
using AnalysisPrograms.Production;
using AnalysisPrograms.Production.Arguments;
using AnalysisPrograms.Production.Parsers;
using log4net;
using McMaster.Extensions.CommandLineUtils;
using Production;
using Production.Arguments;
using Production.Parsers;
using static System.Environment;

public static partial class MainEntry
Expand All @@ -44,6 +45,11 @@ public static partial class MainEntry
public const bool InDEBUG = false;
#endif

public const string ApDefaultLogVerbosityKey = "AP_DEFAULT_LOG_VERBOSITY";
public const string ApPlainLoggingKey = "AP_PLAIN_LOGGING";
public const string ApMetricsKey = "AP_METRICS";
public const string ApAutoAttachKey = "AP_AUTO_ATTACH";

public static readonly Dictionary<string, string> EnvironmentOptions =
new Dictionary<string, string>
{
Expand All @@ -63,9 +69,8 @@ public static partial class MainEntry
#endif
};

private const string ApPlainLoggingKey = "AP_PLAIN_LOGGING";
private const string ApMetricsKey = "AP_METRICS";
private const string ApAutoAttachKey = "AP_AUTO_ATTACH";
private static bool AppConfigOverridden;


internal enum Usages
{
Expand All @@ -75,6 +80,11 @@ internal enum Usages
NoAction,
}

/// <summary>
/// Gets the default log level set by an environment variable.
/// </summary>
public static LogVerbosity? ApDefaultLogVerbosity { get; private set; } = null;

/// <summary>
/// Gets a value indicating whether or not we should use simpler logging semantics. Usually means no color.
/// </summary>
Expand All @@ -98,37 +108,7 @@ internal enum Usages

public static void SetLogVerbosity(LogVerbosity logVerbosity, bool quietConsole = false)
{
Level modifiedLevel;
switch (logVerbosity)
{
case LogVerbosity.None:
// we never turn the logger completely off - sometimes the logger just really needs to log something.
modifiedLevel = LogExtensions.PromptLevel;
break;
case LogVerbosity.Error:
modifiedLevel = Level.Error;
break;
case LogVerbosity.Warn:
modifiedLevel = Level.Warn;
break;
case LogVerbosity.Info:
modifiedLevel = Level.Info;
break;
case LogVerbosity.Debug:
modifiedLevel = Level.Debug;
break;
case LogVerbosity.Trace:
modifiedLevel = Level.Trace;
break;
case LogVerbosity.Verbose:
modifiedLevel = Level.Verbose;
break;
case LogVerbosity.All:
modifiedLevel = Level.All;
break;
default:
throw new ArgumentOutOfRangeException();
}
var modifiedLevel = VerbosityToLevel(logVerbosity);

Logging.ModifyVerbosity(modifiedLevel, quietConsole);
Log.Debug("Log level changed to: " + logVerbosity);
Expand Down Expand Up @@ -199,6 +179,63 @@ internal static void BeforeExecute(MainArgs main, CommandLineApplication applica
LoadNativeCode();
}

/// <summary>
/// If AnalysisPrograms.exe is launched with a 8.3 short name then it
/// fails to find it's application config. .NET looks for a config named
/// the same name as the executable. Normally AnalysisPrograms.exe looks
/// for and finds AnalysisPrograms.exe.config. But if the program is launched
/// as ANALYS~1.EXE it tries to search for ANALYS~2.EXE.config which does not
/// exist.
/// <para>
/// This has shown to be an issue with R's system and system2 functions. They
/// translate the executable function using `Sys.which` which automatically
/// converts executable names to 8.3 short format on Windows.
/// </para>
/// <para>
/// This method checks if this is the case and overrides the setting for
/// checking where to find a config file.
/// </para>
/// </summary>
/// <returns><value>True</value> if modifications were made.</returns>
internal static bool CheckAndUpdateApplicationConfig()
{
var executableName = Process.GetCurrentProcess().MainModule.ModuleName;
var expectedName = Assembly.GetAssembly(typeof(MainEntry)).ManifestModule.ScopeName;

Log.Verbose($"Executable name is {executableName} and expected name is {expectedName}");

if (expectedName != executableName)
{
var correctConfig = expectedName + ".config";
Log.Verbose($"Updating AppDomain APP_CONFIG_FILE to point to `{correctConfig}`");
AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", correctConfig);
ResetConfigMechanism();
return true;
}

return false;

void ResetConfigMechanism()
{
// https://stackoverflow.com/a/6151688/224512
var bindingFlags = BindingFlags.NonPublic | BindingFlags.Static;
typeof(ConfigurationManager)
.GetField("s_initState", bindingFlags)
.SetValue(null, 0);

typeof(ConfigurationManager)
.GetField("s_configSystem", bindingFlags)
.SetValue(null, null);

typeof(ConfigurationManager)
.Assembly
.GetTypes()
.First(x => x.FullName == "System.Configuration.ClientConfigPaths")
.GetField("s_current", bindingFlags)
.SetValue(null, null);
}
}

/// <summary>
/// Checks to see whether we can load a DLL that we depend on from the GAC.
/// We have to check this early on or else we get some pretty hard to
Expand Down Expand Up @@ -377,6 +414,8 @@ private static void PrepareForErrors()
{
ExitCode = ExceptionLookup.UnhandledExceptionErrorCode;

AppConfigOverridden = CheckAndUpdateApplicationConfig();

if (CheckForDataAnnotations() is string message)
{
Console.WriteLine(message);
Expand Down Expand Up @@ -472,9 +511,9 @@ private static void CurrentDomainOnUnhandledException(object sender, UnhandledEx
/// system. Thus instead we:
/// - copy runtimes manually as a build step
/// (due to a mono bug, the folder to copy in is named `libruntimes`. See https://github.com/libgit2/libgit2sharp/issues/1170)
/// - map Dlls to their appropriate native DLLs in the dllmap entried in the App.config (which is used by the
/// - map Dlls to their appropriate native DLLs in the dllmap entry in the App.config (which is used by the
/// mono runtime
/// - and finally, call any intialization code that is needed here in this method.
/// - and finally, call any initialization code that is needed here in this method.
/// </remarks>
private static void LoadNativeCode()
{
Expand Down Expand Up @@ -508,6 +547,12 @@ private static void ModifyVerbosity(MainArgs arguments)

private static void ParseEnvironment()
{
Enum.TryParse(
GetEnvironmentVariable(ApDefaultLogVerbosityKey),
true,
out LogVerbosity verbosity);
ApDefaultLogVerbosity = verbosity;

ApPlainLogging = bool.TryParse(GetEnvironmentVariable(ApPlainLoggingKey), out var plainLogging) && plainLogging;

// default value is true
Expand Down Expand Up @@ -538,5 +583,43 @@ private static void PrintAggregateException(Exception ex, int depth = 0)
}
}
}

private static Level VerbosityToLevel(LogVerbosity logVerbosity)
{
Level modifiedLevel;
switch (logVerbosity)
{
case LogVerbosity.None:

// we never turn the logger completely off - sometimes the logger just really needs to log something.
modifiedLevel = LogExtensions.PromptLevel;
break;
case LogVerbosity.Error:
modifiedLevel = Level.Error;
break;
case LogVerbosity.Warn:
modifiedLevel = Level.Warn;
break;
case LogVerbosity.Info:
modifiedLevel = Level.Info;
break;
case LogVerbosity.Debug:
modifiedLevel = Level.Debug;
break;
case LogVerbosity.Trace:
modifiedLevel = Level.Trace;
break;
case LogVerbosity.Verbose:
modifiedLevel = Level.Verbose;
break;
case LogVerbosity.All:
modifiedLevel = Level.All;
break;
default:
throw new ArgumentOutOfRangeException();
}

return modifiedLevel;
}
}
}
29 changes: 29 additions & 0 deletions tests/Acoustics.Test/AnalysisPrograms/MainEntryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ namespace Acoustics.Test.AnalysisPrograms
{
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Accord.Math.Optimization;
using Acoustics.Shared;
using Acoustics.Test.TestHelpers;
using global::AnalysisPrograms;
using global::AnalysisPrograms.Production.Arguments;
using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestClass]
Expand Down Expand Up @@ -118,6 +120,33 @@ public void HelpPagingIsDisabled()
Assert.IsFalse(app.UsePagerForHelpText);
}

[TestMethod]
public void TestConfigCanBeLoadedWithShortName()
{
const string shortName = "ANALYS~1.EXE";

Process process = new Process();
process.StartInfo = new ProcessStartInfo(shortName, $"{CheckEnvironment.CommandName} -n")
{
EnvironmentVariables =
{
{ MainEntry.ApDefaultLogVerbosityKey, LogVerbosity.All.ToString() },
},
WorkingDirectory = PathHelper.AnalysisProgramsBuild,
UseShellExecute = false,
RedirectStandardOutput = true,
};
process.Start();
process.WaitForExit(milliseconds: 30_000);

var output = process.StandardOutput.ReadToEnd();

StringAssert.Contains(output, "Updating AppDomain APP_CONFIG_FILE to point to `AnalysisPrograms.exe.config`");
Assert.IsFalse(output.Contains("ReflectionTypeLoadException"),$"Output should not contain `ReflectionTypeLoadException`. Output is:\n{output}");

Assert.AreEqual(0, process.ExitCode);
}

private void AssertContainsCopyright(ReadOnlyCollection<string> lines)
{
// copyright always on third line
Expand Down

0 comments on commit 16881a3

Please sign in to comment.