-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
Also changed SHA256 Hash creation, it now takes 92% less memory! Added IDisposable to TuupUpdatePackage Added "External Test", making it so it's test source can be found from the class that is actually holding the tests and not the abstracted classes
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,58 +1,81 @@ | ||
using System.Text.RegularExpressions; | ||
using Microsoft.Extensions.Logging; | ||
using Microsoft.Extensions.Logging.Abstractions; | ||
using TinyUpdate.Core.Abstract; | ||
|
||
namespace TinyUpdate.Core; | ||
|
||
/// <summary> | ||
/// Easy access to processing Streams into a SHA256 hash and comparing SHA256 hashes | ||
/// </summary> | ||
public partial class SHA256(ILogger logger) : IHasher | ||
public partial class SHA256 : IHasher | ||
{ | ||
public static readonly SHA256 Instance = new SHA256(NullLogger.Instance); | ||
|
||
private const string EmptyHash = "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855"; | ||
private static readonly Regex Sha256Regex = MyRegex(); | ||
|
||
public bool CheckHash(Stream stream, string expectedHash) | ||
{ | ||
stream.Seek(0, SeekOrigin.Begin); | ||
var hash = CreateHash(stream); | ||
return hash == expectedHash; | ||
} | ||
public static readonly SHA256 Instance = new SHA256(); | ||
|
||
public bool CheckHash(byte[] byteArray, string expectedHash) | ||
public bool CompareHash(Stream stream, string expectedHash) | ||
{ | ||
if (IsValidHash(expectedHash)) | ||
{ | ||
var sameHash = CreateHash(byteArray) == expectedHash; | ||
logger.LogInformation("Do we have the expected SHA256 hash?: {SameHash}", sameHash); | ||
return sameHash; | ||
var hash = HashData(stream); | ||
return hash == expectedHash; | ||
} | ||
|
||
logger.LogWarning("We been given an invalid hash, can't check"); | ||
return false; | ||
} | ||
|
||
public string CreateHash(Stream stream) | ||
public bool CompareHash(byte[] byteArray, string expectedHash) | ||
{ | ||
stream.Seek(0, SeekOrigin.Begin); | ||
using var sha256 = System.Security.Cryptography.SHA256.Create(); | ||
return CreateHash(sha256.ComputeHash(stream)); | ||
if (IsValidHash(expectedHash)) | ||
{ | ||
var hash = HashData(byteArray, true); | ||
return hash == expectedHash; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
public string CreateHash(byte[] bytes) | ||
public string HashData(Stream stream) | ||
{ | ||
string result = string.Empty; | ||
foreach (var b in bytes) | ||
//If we got nothing then return this, this will always be calculated by below | ||
if (stream is { CanSeek: true, Length: 0 }) | ||
{ | ||
result += b.ToString("X2"); | ||
return EmptyHash; | ||
} | ||
return result; | ||
|
||
var dataHashed = System.Security.Cryptography.SHA256.HashData(stream); | ||
return HashData(dataHashed, false); | ||
Check warning on line 47 in src/TinyUpdate.Core/SHA256.cs GitHub Actions / build (ubuntu-latest)
Check warning on line 47 in src/TinyUpdate.Core/SHA256.cs GitHub Actions / build (ubuntu-latest)
Check warning on line 47 in src/TinyUpdate.Core/SHA256.cs GitHub Actions / build (windows-latest)
Check warning on line 47 in src/TinyUpdate.Core/SHA256.cs GitHub Actions / build (windows-latest)
Check warning on line 47 in src/TinyUpdate.Core/SHA256.cs GitHub Actions / build (macos-latest)
|
||
} | ||
|
||
public string HashData(byte[] bytes) => HashData(bytes, true); | ||
Check warning on line 50 in src/TinyUpdate.Core/SHA256.cs GitHub Actions / build (ubuntu-latest)
Check warning on line 50 in src/TinyUpdate.Core/SHA256.cs GitHub Actions / build (ubuntu-latest)
Check warning on line 50 in src/TinyUpdate.Core/SHA256.cs GitHub Actions / build (windows-latest)
Check warning on line 50 in src/TinyUpdate.Core/SHA256.cs GitHub Actions / build (windows-latest)
Check warning on line 50 in src/TinyUpdate.Core/SHA256.cs GitHub Actions / build (macos-latest)
|
||
|
||
public bool IsValidHash(string hash) => !string.IsNullOrWhiteSpace(hash) && Sha256Regex.IsMatch(hash); | ||
|
||
[GeneratedRegex("^[a-fA-F0-9]{64}$", RegexOptions.Compiled)] | ||
private static partial Regex MyRegex(); | ||
|
||
private static string? HashData(byte[] bytes, bool processBytes) | ||
{ | ||
if (processBytes) | ||
{ | ||
//If we got nothing then return this, this will always be calculated by below | ||
if (bytes.Length == 0) | ||
{ | ||
return EmptyHash; | ||
} | ||
|
||
using var memStream = new MemoryStream(bytes); | ||
bytes = System.Security.Cryptography.SHA256.HashData(memStream); | ||
} | ||
|
||
var resultArray = new Span<char>(new char[64]); | ||
var charsWritten = 0; | ||
|
||
foreach (var @byte in bytes) | ||
{ | ||
@byte.TryFormat(resultArray[charsWritten..], out var written, "X2"); | ||
charsWritten += written; | ||
} | ||
return resultArray.ToString(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
| ||
using TinyUpdate.Core.Abstract; | ||
using TinyUpdate.Core.Tests.Attributes; | ||
|
||
namespace TinyUpdate.Core.Tests.Abstract; | ||
|
||
public abstract class HasherCan(IHasher hasher) | ||
{ | ||
private IHasher Hasher { get; } = hasher; | ||
|
||
[ExternalTest] | ||
public void CompareCorrectly_Stream(bool expectedStatus, string expectedHash, Stream streamToHash) | ||
{ | ||
var hash = Hasher.CompareHash(streamToHash, expectedHash); | ||
Assert.That(hash, Is.EqualTo(expectedStatus)); | ||
} | ||
|
||
[ExternalTest] | ||
public void CompareCorrectly_Array(bool expectedStatus, string expectedHash, byte[] arrayToHash) | ||
{ | ||
var hash = Hasher.CompareHash(arrayToHash, expectedHash); | ||
Assert.That(hash, Is.EqualTo(expectedStatus)); | ||
} | ||
|
||
[ExternalTest] | ||
public void ReturnCorrectHash_Stream(string expectedHash, Stream streamToHash) | ||
{ | ||
var hash = Hasher.HashData(streamToHash); | ||
Assert.That(hash, Is.EqualTo(expectedHash)); | ||
} | ||
|
||
[ExternalTest] | ||
public void ReturnCorrectHash_Array(string expectedHash, byte[] arrayToHash) | ||
{ | ||
var hash = Hasher.HashData(arrayToHash); | ||
Assert.That(hash, Is.EqualTo(expectedHash)); | ||
} | ||
|
||
[ExternalTest] | ||
public void ValidateHashCorrectly(string hash, bool expectedValidation) | ||
{ | ||
Assert.That(Hasher.IsValidHash(hash), Is.EqualTo(expectedValidation)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
using System.Collections; | ||
using System.Globalization; | ||
using System.Reflection; | ||
using NUnit.Framework.Interfaces; | ||
using NUnit.Framework.Internal; | ||
using NUnit.Framework.Internal.Builders; | ||
|
||
namespace TinyUpdate.Core.Tests.Attributes; | ||
|
||
//Readopted TestCaseSourceAttribute to make this | ||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] | ||
public class DynamicTestCaseSourceAttribute : Attribute, ITestBuilder | ||
{ | ||
private readonly NUnitTestCaseBuilder _builder = new(); | ||
|
||
public DynamicTestCaseSourceAttribute(string testName, Type sourceType, string methodName, object?[]? parameters = null) | ||
{ | ||
TestName = testName; | ||
SourceType = sourceType; | ||
MethodName = methodName; | ||
Parameters = parameters; | ||
} | ||
|
||
public string TestName { get; } | ||
|
||
public Type SourceType { get; } | ||
|
||
public string MethodName { get; } | ||
|
||
public object?[]? Parameters { get; } | ||
|
||
public IEnumerable<TestMethod> BuildFrom(IMethodInfo method, Test? suite) | ||
{ | ||
if (method.Name != TestName) yield break; | ||
|
||
var count = 0; | ||
foreach (var parms in GetTestCasesFor(method)) | ||
{ | ||
count++; | ||
yield return _builder.BuildTestMethod(method, suite, parms); | ||
} | ||
|
||
// If count > 0, error messages will be shown for each case | ||
// but if it's 0, we need to add an extra "test" to show the message. | ||
if (count == 0 && method.GetParameters().Length == 0) | ||
{ | ||
var parms = new TestCaseParameters { RunState = RunState.NotRunnable }; | ||
parms.Properties.Set(PropertyNames.SkipReason, "DynamicTestCaseSource may not be used on a method without parameters"); | ||
|
||
yield return _builder.BuildTestMethod(method, suite, parms); | ||
} | ||
} | ||
|
||
private IEnumerable<TestCaseParameters> GetTestCasesFor(IMethodInfo method) | ||
{ | ||
var methodEnumerable = SourceType.GetMethod(MethodName)?.Invoke(null, BindingFlags.Public | BindingFlags.Static, | ||
null, Parameters, CultureInfo.CurrentCulture) as IEnumerable; | ||
if (methodEnumerable == null) | ||
{ | ||
var parms = new TestCaseParameters { RunState = RunState.NotRunnable }; | ||
parms.Properties.Set(PropertyNames.SkipReason, "DynamicTestCaseSource can't find the method to invoke"); | ||
|
||
yield return parms; | ||
yield break; | ||
} | ||
|
||
foreach (var item in methodEnumerable) | ||
{ | ||
// First handle two easy cases: | ||
// 1. Source is null. This is really an error but if we | ||
// throw an exception we simply get an invalid fixture | ||
// without good info as to what caused it. Passing a | ||
// single null argument will cause an error to be | ||
// reported at the test level, in most cases. | ||
// 2. User provided an TestCaseParameters and we just use it. | ||
var parms = item is null | ||
? new TestCaseParameters(new object?[] { null }) | ||
: item as TestCaseParameters; | ||
|
||
if (parms is not null) | ||
{ | ||
yield return parms; | ||
continue; | ||
} | ||
|
||
object?[]? args = null; | ||
|
||
// 3. An array was passed, it may be an object[] | ||
// or possibly some other kind of array, which | ||
// TestCaseSource can accept. | ||
if (item is Array array) | ||
{ | ||
// If array has the same number of elements as parameters | ||
// and it does not fit exactly into single existing parameter | ||
// we believe that this array contains arguments, not is a bare | ||
// argument itself. | ||
var parameters = method.GetParameters(); | ||
var argsNeeded = parameters.Length; | ||
if (argsNeeded > 0 && argsNeeded == array.Length && parameters[0].ParameterType != array.GetType()) | ||
{ | ||
args = new object?[array.Length]; | ||
for (var i = 0; i < array.Length; i++) | ||
args[i] = array.GetValue(i); | ||
} | ||
} | ||
|
||
args ??= new[] { item }; | ||
yield return new TestCaseParameters(args); | ||
} | ||
} | ||
} |