Skip to content

Commit

Permalink
Adds checkes for critical DLLs
Browse files Browse the repository at this point in the history
Now does a rather paranoid check everytime AP.exe starts to ensure we can load System.ComponentModel.DataAnnotations. Because this DLL was in the GAC and because our dependencies depend on it, it was a rather insidious bug to fix. The only way to check for it was to do so before other things depend on it.

Closes #225
  • Loading branch information
atruskie committed Mar 12, 2019
1 parent e3317bd commit 64e0f98
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 99 deletions.
36 changes: 25 additions & 11 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.IO;
using System.Linq;
using System.Reflection;
using System.Text;
Expand All @@ -14,6 +15,7 @@ namespace AnalysisPrograms
using Acoustics.Shared;
using Acoustics.Tools.Audio;
using AnalysisPrograms.AnalyseLongRecordings;
using AnalysisPrograms.Production;
using AnalysisPrograms.Production.Arguments;
using log4net;
using McMaster.Extensions.CommandLineUtils;
Expand All @@ -24,9 +26,9 @@ public class CheckEnvironment

private static readonly ILog Log = LogManager.GetLogger(nameof(CheckEnvironment));

private void Execute(Arguments arguments)
private int Execute(Arguments arguments)
{
List<Exception> errors = new List<Exception>();
var errors = new List<string>();
Log.Info("Checking required executables can be found");

// master audio utility checks for available executables
Expand All @@ -36,7 +38,12 @@ private void Execute(Arguments arguments)
}
catch (Exception ex)
{
errors.Add(ex);
errors.Add(ex.Message);
}

if (MainEntry.CheckForDataAnnotations() is string message)
{
errors.Add(message);
}

if (AppConfigHelper.IsMono)
Expand All @@ -45,23 +52,23 @@ private void Execute(Arguments arguments)
if (type != null)
{
MethodInfo displayName = type.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static);
if (displayName != null)

if (displayName?.Invoke(null, null) is string name)
{
var name = displayName.Invoke(null, null);
var version = Regex.Match(name as string, @".*(\d+\.\d+\.\d+\.\d+).*").Groups[1].Value;
var version = Regex.Match(name, @".*(\d+\.\d+\.\d+\.\d+).*").Groups[1].Value;
Console.WriteLine(version);
if (new Version(version) > new Version(5, 5))
{
Log.Success($"Your mono version {name} is greater than our required Mono version 5.5");
}
else
{
errors.Add(new Exception($"Mono version is {name}, we require at least Mono 5.5"));
errors.Add($"Mono version is {name}, we require at least Mono 5.5");
}
}
else
{
errors.Add(new Exception("Could not check Mono version"));
errors.Add("Could not get Mono display name");
}
}
}
Expand All @@ -70,10 +77,18 @@ private void Execute(Arguments arguments)
if (errors.Count == 0)
{
Log.Success("Valid environment");

return ExceptionLookup.Ok;
}
else
{
throw new AggregateException(errors.ToArray());
foreach (var error in errors)
{
Log.Error(error);
}

// not using exception lookup on purpose - it's static constructor loads more types
return ExceptionLookup.UnhandledExceptionErrorCode;
}
}

Expand All @@ -85,8 +100,7 @@ public class Arguments : SubCommandBase
public override Task<int> Execute(CommandLineApplication app)
{
var instance = new CheckEnvironment();
instance.Execute(this);
return this.Ok();
return Task.FromResult(instance.Execute(this));
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/AnalysisPrograms/MainEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public static async Task<int> Main(string[] args)

Copyright();

AttachExceptionHandler();
PrepareForErrors();

NoConsole.Log.Info($"Executable called with these arguments: {NewLine}{CommandLine}{NewLine}");

Expand Down
162 changes: 78 additions & 84 deletions src/AnalysisPrograms/Production/Exceptions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// --------------------------------------------------------------------------------------------------------------------
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Exceptions.cs" company="QutEcoacoustics">
// All code in this file and all associated files are the copyright and property of the QUT Ecoacoustics Research Group (formerly MQUTeR, and formerly QUT Bioacoustics Research Group).
// </copyright>
Expand All @@ -19,102 +19,96 @@ namespace AnalysisPrograms.Production
public static class ExceptionLookup
{
/// <summary>
/// The default exit code to use for exceptions not recognised. Must not be greater than 255.
/// The default exit code to use for exceptions not recognized. Must not be greater than 255.
/// </summary>
public const int UnhandledExceptionErrorCode = 200;

internal static readonly Dictionary<Type, ExceptionStyle> ErrorLevels;

static ExceptionLookup()
{
// WARNING: EXIT CODES CANNOT BE > 255 (for linux compatibility)
ErrorLevels = new Dictionary<Type, ExceptionStyle>
{
{
typeof(ValidationException),
new ExceptionStyle { ErrorCode = ValiationError, PrintUsage = false }
},
{
typeof(CommandLineArgumentException),
new ExceptionStyle() { ErrorCode = 3 }
},
{
typeof(CommandParsingException),
new ExceptionStyle() { ErrorCode = 4 }
},
{
typeof(DirectoryNotFoundException),

// disabled print usage because these exceptions happen at all levels of the stack
new ExceptionStyle { ErrorCode = 51, PrintUsage = false }
},
{
typeof(FileNotFoundException),

// disabled print usage because these exceptions happen at all levels of the stack
new ExceptionStyle { ErrorCode = 52, PrintUsage = false }
},
{
typeof(InvalidDurationException),
new ExceptionStyle { ErrorCode = 100 }
},
{
typeof(InvalidStartOrEndException),
new ExceptionStyle { ErrorCode = 101 }
},
{
typeof(InvalidFileDateException),
new ExceptionStyle() { ErrorCode = 102, PrintUsage = false }
},
{
typeof(ConfigFileException),
new ExceptionStyle() { ErrorCode = 103, PrintUsage = false }
},
{
typeof(AudioRecordingTooShortException),
new ExceptionStyle() { ErrorCode = 104, PrintUsage = false }
},
{
typeof(InvalidAudioChannelException),
new ExceptionStyle() { ErrorCode = 105, PrintUsage = false }
},
{
typeof(AnalysisOptionDevilException),
new ExceptionStyle
{
ErrorCode = 66,
Handle = false,
}
},
{
typeof(NoDeveloperMethodException),
new ExceptionStyle { ErrorCode = 199 }
},
{
typeof(Exception),
new ExceptionStyle
{
ErrorCode =
UnhandledExceptionErrorCode,
}
},
};
}
private static Dictionary<Type, ExceptionStyle> levels;

public static int Ok => 0;

public static int ValiationError => 2;
public static int ValidationError => 2;

public static int ActionRequired => 2;

public static int NoData => 10;

public static int SpecialExceptionErrorLevel
internal static Dictionary<Type, ExceptionStyle> ErrorLevels => levels ?? (levels = CreateExceptionMap());

private static Dictionary<Type, ExceptionStyle> CreateExceptionMap()
{
get
// WARNING: EXIT CODES CANNOT BE > 255 (for linux compatibility)
return new Dictionary<Type, ExceptionStyle>
{
return ErrorLevels[typeof(Exception)].ErrorCode;
}
{
typeof(ValidationException),
new ExceptionStyle { ErrorCode = ValidationError, PrintUsage = false }
},
{
typeof(CommandLineArgumentException),
new ExceptionStyle() { ErrorCode = 3 }
},
{
typeof(CommandParsingException),
new ExceptionStyle() { ErrorCode = 4 }
},
{
typeof(DirectoryNotFoundException),

// disabled print usage because these exceptions happen at all levels of the stack
new ExceptionStyle { ErrorCode = 51, PrintUsage = false }
},
{
typeof(FileNotFoundException),

// disabled print usage because these exceptions happen at all levels of the stack
new ExceptionStyle { ErrorCode = 52, PrintUsage = false }
},
{
typeof(InvalidDurationException),
new ExceptionStyle { ErrorCode = 100 }
},
{
typeof(InvalidStartOrEndException),
new ExceptionStyle { ErrorCode = 101 }
},
{
typeof(InvalidFileDateException),
new ExceptionStyle() { ErrorCode = 102, PrintUsage = false }
},
{
typeof(ConfigFileException),
new ExceptionStyle() { ErrorCode = 103, PrintUsage = false }
},
{
typeof(AudioRecordingTooShortException),
new ExceptionStyle() { ErrorCode = 104, PrintUsage = false }
},
{
typeof(InvalidAudioChannelException),
new ExceptionStyle() { ErrorCode = 105, PrintUsage = false }
},
{
typeof(AnalysisOptionDevilException),
new ExceptionStyle
{
ErrorCode = 66,
Handle = false,
}
},
{
typeof(NoDeveloperMethodException),
new ExceptionStyle { ErrorCode = 199 }
},
{
typeof(Exception),
new ExceptionStyle
{
ErrorCode =
UnhandledExceptionErrorCode,
}
},
};
}

public class ExceptionStyle
Expand Down
46 changes: 43 additions & 3 deletions src/AnalysisPrograms/Production/MainEntryUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,36 @@ internal static void BeforeExecute(MainArgs main, CommandLineApplication applica
LoadNativeCode();
}

/// <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
/// diagnose errors (and also because our CLI parser depends on it).
/// </summary>
/// <returns>A message if there is a problem.</returns>
internal static string CheckForDataAnnotations()
{
// https://github.com/QutEcoacoustics/audio-analysis/issues/225
try
{
Assembly.Load(
"System.ComponentModel.DataAnnotations, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
return null;
}
catch (FileNotFoundException fnfex)
{
return $@"{fnfex.ToString()}
!
!
!
In cases where System.ComponentModel.DataAnnotations is not found we have
previously found that the mono install is corrupt. Try installing mono again
and make sure you install the `mono-complete` package.
!
!
!";
}
}

internal static void Copyright()
{
LoggedConsole.WriteLine(
Expand Down Expand Up @@ -338,9 +368,19 @@ internal static CommandLineApplication CreateCommandLineApplication()
return app;
}

private static void AttachExceptionHandler()
/// <summary>
/// Attaches an exception handler and also does a check
/// to see if necessary DLLs exist. WARNING: can quit process!.
/// </summary>
private static void PrepareForErrors()
{
Environment.ExitCode = ExceptionLookup.SpecialExceptionErrorLevel;
ExitCode = ExceptionLookup.UnhandledExceptionErrorCode;

if (CheckForDataAnnotations() is string message)
{
Console.WriteLine(message);
Exit(ExceptionLookup.UnhandledExceptionErrorCode);
}

AppDomain.CurrentDomain.UnhandledException += CurrentDomainOnUnhandledException;
}
Expand Down Expand Up @@ -403,7 +443,7 @@ private static void CurrentDomainOnUnhandledException(object sender, UnhandledEx
PrintAggregateException(ex);
}

int returnCode = style?.ErrorCode ?? ExceptionLookup.SpecialExceptionErrorLevel;
int returnCode = style?.ErrorCode ?? ExceptionLookup.UnhandledExceptionErrorCode;

// finally return error level
NoConsole.Log.Info("ERRORLEVEL: " + returnCode);
Expand Down

0 comments on commit 64e0f98

Please sign in to comment.