Add tests for Core; fix found issues

This commit is contained in:
Matt Nadareski
2025-01-04 19:47:39 -05:00
parent acfd6b1d11
commit f5e2d8a11c
24 changed files with 3531 additions and 293 deletions

View File

@@ -74,9 +74,14 @@ namespace SabreTools.Core
if (disk == null)
return null;
// Append a suffix to the name
string? name = disk.ReadString(Disk.NameKey);
if (name != null)
name += ".chd";
return new Rom
{
[Rom.NameKey] = disk.ReadString(Disk.NameKey) + ".chd",
[Rom.NameKey] = name,
[Rom.MergeKey] = disk.ReadString(Disk.MergeKey),
[Rom.RegionKey] = disk.ReadString(Disk.RegionKey),
[Rom.StatusKey] = disk.ReadString(Disk.StatusKey),
@@ -95,9 +100,14 @@ namespace SabreTools.Core
if (media == null)
return null;
// Append a suffix to the name
string? name = media.ReadString(Media.NameKey);
if (name != null)
name += ".aaruf";
return new Rom
{
[Rom.NameKey] = media.ReadString(Media.NameKey) + ".aaruf",
[Rom.NameKey] = name,
[Rom.MD5Key] = media.ReadString(Media.MD5Key),
[Rom.SHA1Key] = media.ReadString(Media.SHA1Key),
[Rom.SHA256Key] = media.ReadString(Media.SHA256Key),
@@ -179,8 +189,18 @@ namespace SabreTools.Core
break;
default:
if (kvp.Value != other[kvp.Key])
// Handle cases where a null is involved
if (kvp.Value == null && other[kvp.Key] == null)
return true;
else if (kvp.Value == null && other[kvp.Key] != null)
return false;
else if (kvp.Value != null && other[kvp.Key] == null)
return false;
// Try to rely on type hashes
else if (kvp.Value!.GetHashCode() != other[kvp.Key]!.GetHashCode())
return false;
break;
}
}
@@ -257,6 +277,8 @@ namespace SabreTools.Core
// If we have a file that has no known size, rely on the hashes only
if (selfSize == null && self.HashMatch(other))
return true;
else if (otherSize == null && self.HashMatch(other))
return true;
// If we get a partial match
if (selfSize == otherSize && self.HashMatch(other))
@@ -714,11 +736,8 @@ namespace SabreTools.Core
/// <summary>
/// Get unique duplicate suffix on name collision
/// </summary>
private static string GetDuplicateSuffix(this Disk? self)
private static string GetDuplicateSuffix(this Disk self)
{
if (self == null)
return string.Empty;
string? md5 = self.ReadString(Disk.MD5Key);
if (!string.IsNullOrEmpty(md5))
return $"_{md5}";
@@ -733,11 +752,8 @@ namespace SabreTools.Core
/// <summary>
/// Get unique duplicate suffix on name collision
/// </summary>
private static string GetDuplicateSuffix(this Media? self)
private static string GetDuplicateSuffix(this Media self)
{
if (self == null)
return string.Empty;
string? md5 = self.ReadString(Media.MD5Key);
if (!string.IsNullOrEmpty(md5))
return $"_{md5}";
@@ -760,11 +776,8 @@ namespace SabreTools.Core
/// <summary>
/// Get unique duplicate suffix on name collision
/// </summary>
private static string GetDuplicateSuffix(this Rom? self)
private static string GetDuplicateSuffix(this Rom self)
{
if (self == null)
return string.Empty;
string? crc = self.ReadString(Rom.CRCKey);
if (!string.IsNullOrEmpty(crc))
return $"_{crc}";

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using SabreTools.IO.Logging;
using SabreTools.IO.Readers;
@@ -12,7 +13,6 @@ namespace SabreTools.Core.Filter
/// <summary>
/// Item type and field to update with INI information
/// </summary>
/// <remarks>Formatted like "ItemName.FieldName"</remarks>
public readonly FilterKey Key;
/// <summary>
@@ -24,17 +24,17 @@ namespace SabreTools.Core.Filter
#region Constructors
public ExtraIniItem(string key, string ini)
public ExtraIniItem(string key, string iniPath)
{
Key = new FilterKey(key);
if (!PopulateFromFile(ini))
if (!PopulateFromFile(iniPath))
Mappings.Clear();
}
public ExtraIniItem(string itemName, string fieldName, string ini)
public ExtraIniItem(string itemName, string fieldName, string iniPath)
{
Key = new FilterKey(itemName, fieldName);
if (!PopulateFromFile(ini))
if (!PopulateFromFile(iniPath))
Mappings.Clear();
}
@@ -45,7 +45,7 @@ namespace SabreTools.Core.Filter
/// <summary>
/// Populate the dictionary from an INI file
/// </summary>
/// <param name="ini">Path to INI file to populate from</param>
/// <param name="iniPath">Path to INI file to populate from</param>
/// <remarks>
/// The INI file format that is supported here is not exactly the same
/// as a traditional one. This expects a MAME extras format, which usually
@@ -54,10 +54,16 @@ namespace SabreTools.Core.Filter
/// the value is boolean. If there's another section name, then that is set
/// as the value instead.
/// </remarks>
private bool PopulateFromFile(string ini)
private bool PopulateFromFile(string iniPath)
{
// Prepare all intenral variables
IniReader ir = new(ini) { ValidateRows = false };
// Validate the path
if (iniPath.Length == 0)
return false;
else if (!File.Exists(iniPath))
return false;
// Prepare all internal variables
var ir = new IniReader(iniPath) { ValidateRows = false };
bool foundRootFolder = false;
// If we got a null reader, just return
@@ -107,7 +113,7 @@ namespace SabreTools.Core.Filter
}
catch (Exception ex)
{
LoggerImpl.Warning(ex, $"Exception found while parsing '{ini}'");
LoggerImpl.Warning(ex, $"Exception found while parsing '{iniPath}'");
return false;
}

View File

@@ -55,11 +55,11 @@ namespace SabreTools.Core.Filter
itemName = string.Empty; fieldName = string.Empty;
// If we don't have a filter ID, we can't do anything
if (itemFieldString == null)
if (string.IsNullOrEmpty(itemFieldString))
return false;
// If we only have one part, we can't do anything
string[] splitFilter = itemFieldString.Split('.');
string[] splitFilter = itemFieldString!.Split('.');
if (splitFilter.Length != 2)
return false;

View File

@@ -26,10 +26,10 @@ namespace SabreTools.Core.Filter
/// </summary>
public readonly Operation Operation;
public FilterObject(string filterString)
public FilterObject(string? filterString)
{
if (!SplitFilterString(filterString, out var keyItem, out Operation operation, out var value))
throw new ArgumentOutOfRangeException(nameof(filterString));
throw new ArgumentException(nameof(filterString));
Key = new FilterKey(keyItem);
Value = value;
@@ -57,7 +57,6 @@ namespace SabreTools.Core.Filter
/// </summary>
public bool Matches(DictionaryBase dictionaryBase)
{
// TODO: Add validation of dictionary base type from the key values
return Operation switch
{
Operation.Equals => MatchesEqual(dictionaryBase),
@@ -77,16 +76,16 @@ namespace SabreTools.Core.Filter
{
// If the key doesn't exist, we count it as null
if (!dictionaryBase.ContainsKey(Key.FieldName))
return Value == null;
return string.IsNullOrEmpty(Value);
// If the value in the dictionary is null
string? checkValue = dictionaryBase.ReadString(Key.FieldName);
if (checkValue == null)
return Value == null;
return string.IsNullOrEmpty(Value);
// If we have both a potentally boolean check and value
bool? checkValueBool = ConvertToBoolean(checkValue);
bool? matchValueBool = ConvertToBoolean(Value);
bool? checkValueBool = checkValue.AsYesNo();
bool? matchValueBool = Value.AsYesNo();
if (checkValueBool != null && matchValueBool != null)
return checkValueBool == matchValueBool;
@@ -120,16 +119,16 @@ namespace SabreTools.Core.Filter
{
// If the key doesn't exist, we count it as null
if (!dictionaryBase.ContainsKey(Key.FieldName))
return Value != null;
return !string.IsNullOrEmpty(Value);
// If the value in the dictionary is null
string? checkValue = dictionaryBase.ReadString(Key.FieldName);
if (checkValue == null)
return Value == null;
return !string.IsNullOrEmpty(Value);
// If we have both a potentally boolean check and value
bool? checkValueBool = ConvertToBoolean(checkValue);
bool? matchValueBool = ConvertToBoolean(Value);
bool? checkValueBool = checkValue.AsYesNo();
bool? matchValueBool = Value.AsYesNo();
if (checkValueBool != null && matchValueBool != null)
return checkValueBool != matchValueBool;
@@ -309,19 +308,11 @@ namespace SabreTools.Core.Filter
return false;
// If we find a special character, try parsing as regex
#if NETFRAMEWORK
if (value.Contains("^")
|| value.Contains("$")
|| value.Contains("*")
|| value.Contains("?")
|| value.Contains("+"))
#else
if (value.Contains('^')
|| value.Contains('$')
|| value.Contains('*')
|| value.Contains('?')
|| value.Contains('+'))
#endif
{
try
{
@@ -337,23 +328,6 @@ namespace SabreTools.Core.Filter
return false;
}
/// <summary>
/// Convert a string to a Boolean
/// </summary>
private bool? ConvertToBoolean(string? value)
{
// If we don't have a valid string, we can't do anything
if (string.IsNullOrEmpty(value))
return null;
return value!.ToLowerInvariant() switch
{
"true" or "yes" => true,
"false" or "no" => false,
_ => null,
};
}
/// <summary>
/// Derive an operation from the input string, if possible
/// </summary>
@@ -388,11 +362,11 @@ namespace SabreTools.Core.Filter
// Set default values
key = null; operation = Operation.NONE; value = null;
if (filterString == null)
if (string.IsNullOrEmpty(filterString))
return false;
// Trim quotations, if necessary
if (filterString.StartsWith("\""))
if (filterString!.StartsWith("\""))
filterString = filterString.Substring(1, filterString.Length - 2);
// Split the string using regex
@@ -402,7 +376,10 @@ namespace SabreTools.Core.Filter
key = match.Groups["itemField"].Value;
operation = GetOperation(match.Groups["operation"].Value);
value = match.Groups["value"].Value;
// Only non-zero length values are counted as non-null
if (value?.Length > 0)
value = match.Groups["value"].Value;
return true;
}

View File

@@ -22,7 +22,7 @@
</PropertyGroup>
<ItemGroup>
<InternalsVisibleTo Include="SabreTools.Test" />
<InternalsVisibleTo Include="SabreTools.Core.Test" />
</ItemGroup>
<!-- Support for old .NET versions -->

View File

@@ -14,9 +14,11 @@ namespace SabreTools.Core.Tools
private readonly static long GigaByte = (long)Math.Pow(KiloByte, 3);
private readonly static long TeraByte = (long)Math.Pow(KiloByte, 4);
private readonly static long PetaByte = (long)Math.Pow(KiloByte, 5);
private readonly static long ExaByte = (long)Math.Pow(KiloByte, 6);
private readonly static long ZettaByte = (long)Math.Pow(KiloByte, 7);
private readonly static long YottaByte = (long)Math.Pow(KiloByte, 8);
// The following are too big to be represented in Int64
// private readonly static long ExaByte = (long)Math.Pow(KiloByte, 6);
// private readonly static long ZettaByte = (long)Math.Pow(KiloByte, 7);
// private readonly static long YottaByte = (long)Math.Pow(KiloByte, 8);
#endregion
@@ -27,9 +29,11 @@ namespace SabreTools.Core.Tools
private readonly static long GibiByte = (long)Math.Pow(KibiByte, 3);
private readonly static long TibiByte = (long)Math.Pow(KibiByte, 4);
private readonly static long PibiByte = (long)Math.Pow(KibiByte, 5);
private readonly static long ExiByte = (long)Math.Pow(KibiByte, 6);
private readonly static long ZittiByte = (long)Math.Pow(KibiByte, 7);
private readonly static long YittiByte = (long)Math.Pow(KibiByte, 8);
// The following are too big to be represented in Int64
// private readonly static long ExiByte = (long)Math.Pow(KibiByte, 6);
// private readonly static long ZittiByte = (long)Math.Pow(KibiByte, 7);
// private readonly static long YittiByte = (long)Math.Pow(KibiByte, 8);
#endregion
@@ -108,18 +112,20 @@ namespace SabreTools.Core.Tools
multiplier = PetaByte;
else if (numeric.EndsWith("pi") || numeric.EndsWith("pib"))
multiplier = PibiByte;
else if (numeric.EndsWith("e") || numeric.EndsWith("eb"))
multiplier = ExaByte;
else if (numeric.EndsWith("ei") || numeric.EndsWith("eib"))
multiplier = ExiByte;
else if (numeric.EndsWith("z") || numeric.EndsWith("zb"))
multiplier = ZettaByte;
else if (numeric.EndsWith("zi") || numeric.EndsWith("zib"))
multiplier = ZittiByte;
else if (numeric.EndsWith("y") || numeric.EndsWith("yb"))
multiplier = YottaByte;
else if (numeric.EndsWith("yi") || numeric.EndsWith("yib"))
multiplier = YittiByte;
// The following are too big to be represented in Int64
// else if (numeric.EndsWith("e") || numeric.EndsWith("eb"))
// multiplier = ExaByte;
// else if (numeric.EndsWith("ei") || numeric.EndsWith("eib"))
// multiplier = ExiByte;
// else if (numeric.EndsWith("z") || numeric.EndsWith("zb"))
// multiplier = ZettaByte;
// else if (numeric.EndsWith("zi") || numeric.EndsWith("zib"))
// multiplier = ZittiByte;
// else if (numeric.EndsWith("y") || numeric.EndsWith("yb"))
// multiplier = YottaByte;
// else if (numeric.EndsWith("yi") || numeric.EndsWith("yib"))
// multiplier = YittiByte;
return multiplier;
}
@@ -138,9 +144,18 @@ namespace SabreTools.Core.Tools
if (value.StartsWith("0x"))
value = value.Substring(2);
// If we have a negative value
if (value.StartsWith("-"))
value = value.Substring(1);
// If the value has a multiplier
if (DetermineMultiplier(value) > 1)
value = value.TrimEnd(['k', 'm', 'g', 't', 'p', 'e', 'z', 'y', 'i', 'b', ' ']);
// If the value is empty after trimming
if (value.Length == 0)
return false;
#if NET7_0_OR_GREATER
return value.All(c => char.IsAsciiHexDigit(c) || c == '.' || c == ',');
#else

View File

@@ -80,8 +80,14 @@ namespace SabreTools.Core.Tools
}
/// <summary>
/// Remove all unicode-specific chars from a string
/// Remove all Unicode-specific chars from a string
/// </summary>
/// <remarks>
/// "Unicode characters" here means any characters outside of the
/// Extended ASCII (0x00 to 0xFF) set. This is just a simple
/// way of filtering out characters that won't work on all
/// supported platforms.
/// </remarks>
public static string RemoveUnicodeCharacters(string? input)
{
if (string.IsNullOrEmpty(input))
@@ -151,9 +157,9 @@ namespace SabreTools.Core.Tools
private static string? NormalizeHashData(string? hash, int expectedLength)
{
// If we have a known blank hash, return blank
if (string.IsNullOrEmpty(hash))
if (hash == null)
return null;
else if (hash == "-" || hash == "_")
else if (hash == string.Empty || hash == "-" || hash == "_")
return string.Empty;
// Check to see if it's a "hex" hash