-
Notifications
You must be signed in to change notification settings - Fork 0
Kr2 #55
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Kr2 #55
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| global using NUnit.Framework; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <TargetFramework>net8.0</TargetFramework> | ||
| <ImplicitUsings>enable</ImplicitUsings> | ||
| <Nullable>enable</Nullable> | ||
|
|
||
| <IsPackable>false</IsPackable> | ||
| <IsTestProject>true</IsTestProject> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0"/> | ||
| <PackageReference Include="NUnit" Version="3.13.3"/> | ||
| <PackageReference Include="NUnit3TestAdapter" Version="4.2.1"/> | ||
| <PackageReference Include="NUnit.Analyzers" Version="3.6.1"/> | ||
| <PackageReference Include="coverlet.collector" Version="6.0.0"/> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <ProjectReference Include="..\Md5Calculator\Md5Calculator.csproj" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,160 @@ | ||
| namespace Md5Calculator.Tests; | ||
|
|
||
| public class Md5CalculatorTests | ||
| { | ||
| private static IEnumerable<Func<string, byte[]>> ComputeInstances() | ||
| { | ||
| yield return Md5Calculator.Compute; | ||
| yield return path => Md5Calculator.ComputeAsync(path).Result; | ||
| } | ||
|
|
||
| [Test] | ||
| public void AsyncEqualsSyncTest() | ||
| { | ||
| var dirNames = new [] | ||
| { | ||
| "./AsyncEqualsSyncTest", | ||
| "./AsyncEqualsSyncTest/Dir1", | ||
| }; | ||
| (string path, string data)[] files = new [] | ||
| { | ||
| ("./AsyncEqualsSyncTest/Dir1/file1.txt", "Asd"), | ||
| ("./AsyncEqualsSyncTest/file1.txt", "Asd"), | ||
| }; | ||
| dirNames.AsParallel().ForAll(path => Directory.CreateDirectory(path)); | ||
| files.AsParallel() | ||
| .ForAll(file => File.WriteAllBytes(file.path, System.Text.Encoding.ASCII.GetBytes(file.data))); | ||
|
|
||
| Assert.That(Md5Calculator.Compute(dirNames[0]), Is.EqualTo(Md5Calculator.ComputeAsync(dirNames[0]).Result)); | ||
|
|
||
| files.AsParallel() | ||
| .ForAll(file => File.Delete(file.path)); | ||
| Directory.Delete(dirNames[1]); | ||
| } | ||
|
|
||
| [Test, TestCaseSource(nameof(ComputeInstances))] | ||
| public void RecalculateEqualsTest(Func<string, byte[]> compute) | ||
| { | ||
| var dirNames = new [] | ||
| { | ||
| "./RecalculateEqualsTest", | ||
| "./RecalculateEqualsTest/Dir1", | ||
| }; | ||
| (string path, string data)[] files = new [] | ||
| { | ||
| ("./RecalculateEqualsTest/Dir1/file1.txt", "Asd"), | ||
| ("./RecalculateEqualsTest/file1.txt", "Asd"), | ||
| }; | ||
| dirNames.AsParallel().ForAll(path => Directory.CreateDirectory(path)); | ||
| files.AsParallel() | ||
| .ForAll(file => File.WriteAllBytes(file.path, System.Text.Encoding.ASCII.GetBytes(file.data))); | ||
|
|
||
| Assert.That(compute(dirNames[0]), Is.EqualTo(compute(dirNames[0]))); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Анализатор ругается, а не должен. Можно было бы переписать этот фрагмент кода, чтобы убрать предупреждение. |
||
|
|
||
| files.AsParallel() | ||
| .ForAll(file => File.Delete(file.path)); | ||
| Directory.Delete(dirNames[1]); | ||
| } | ||
|
|
||
| [Test, TestCaseSource(nameof(ComputeInstances))] | ||
| public void FileNameMattersTest(Func<string, byte[]> compute) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. По условию как раз имя файла в формуле для хеша не участвовало, но ладно, это скорее дефект условия |
||
| { | ||
| var dirNames = new [] | ||
| { | ||
| "./FileNameMattersTest", | ||
| }; | ||
| (string path, string data)[] files = new [] | ||
| { | ||
| ("./FileNameMattersTest/file2.txt", "Asd"), | ||
| ("./FileNameMattersTest/file1.txt", "Asd"), | ||
| }; | ||
| dirNames.AsParallel().ForAll(path => Directory.CreateDirectory(path)); | ||
| files.AsParallel() | ||
| .ForAll(file => File.WriteAllBytes(file.path, System.Text.Encoding.ASCII.GetBytes(file.data))); | ||
|
|
||
| Assert.That(compute(files[0].path), Is.Not.EqualTo(compute(files[1].path))); | ||
|
|
||
| files.AsParallel() | ||
| .ForAll(file => File.Delete(file.path)); | ||
| } | ||
|
|
||
| [Test, TestCaseSource(nameof(ComputeInstances))] | ||
| public void DirNameMattersTest(Func<string, byte[]> compute) | ||
| { | ||
| var dirNames = new [] | ||
| { | ||
| "./DirNameMattersTest/Dir1", | ||
| "./DirNameMattersTest/Dirs5", | ||
| }; | ||
| (string path, string data)[] files = new [] | ||
| { | ||
| ("./DirNameMattersTest/Dir1/file1.txt", "Asd"), | ||
| ("./DirNameMattersTest/Dirs5/file1.txt", "Asd"), | ||
| }; | ||
| dirNames.AsParallel().ForAll(path => Directory.CreateDirectory(path)); | ||
| files.AsParallel() | ||
| .ForAll(file => File.WriteAllBytes(file.path, System.Text.Encoding.ASCII.GetBytes(file.data))); | ||
|
|
||
| Assert.That(compute(dirNames[0]), Is.Not.EqualTo(compute(dirNames[1]))); | ||
|
|
||
| files.AsParallel() | ||
| .ForAll(file => File.Delete(file.path)); | ||
| dirNames.AsParallel().ForAll(path => Directory.Delete(path)); | ||
| } | ||
|
|
||
| [Test, TestCaseSource(nameof(ComputeInstances))] | ||
| public void DirFilesMatterTest(Func<string, byte[]> compute) | ||
| { | ||
| var dirNames = new [] | ||
| { | ||
| "./DirFilesMatterTest/Dir1", | ||
| "./DirFilesMatterTest/Dir2", | ||
| }; | ||
| (string path, string data)[] files = new [] | ||
| { | ||
| ("./DirFilesMatterTest/Dir1/file1.txt", "Asd"), | ||
| ("./DirFilesMatterTest/Dir2/file1.txt", "Asde"), | ||
| }; | ||
| dirNames.AsParallel().ForAll(path => Directory.CreateDirectory(path)); | ||
| files.AsParallel() | ||
| .ForAll(file => File.WriteAllBytes(file.path, System.Text.Encoding.ASCII.GetBytes(file.data))); | ||
|
|
||
| Assert.That(compute(dirNames[0]), Is.Not.EqualTo(compute(dirNames[1]))); | ||
|
|
||
| files.AsParallel() | ||
| .ForAll(file => File.Delete(file.path)); | ||
| dirNames.AsParallel().ForAll(path => Directory.Delete(path)); | ||
| } | ||
|
|
||
| [Test, TestCaseSource(nameof(ComputeInstances))] | ||
| public void SubDirsMatterTest(Func<string, byte[]> compute) | ||
| { | ||
| var dirNames = new [] | ||
| { | ||
| "./SubDirsMatterTest/Dir1", | ||
| "./SubDirsMatterTest/Dir2", | ||
| "./SubDirsMatterTest/Dir1/Dir11", | ||
| "./SubDirsMatterTest/Dir2/Dir21", | ||
| }; | ||
| (string path, string data)[] files = new [] | ||
| { | ||
| ("./SubDirsMatterTest/Dir1/Dir11/file1.txt", "Asd"), | ||
| ("./SubDirsMatterTest/Dir2/Dir21/file1.txt", "Asde"), | ||
| }; | ||
| dirNames.AsParallel().ForAll(path => Directory.CreateDirectory(path)); | ||
| files.AsParallel() | ||
| .ForAll(file => File.WriteAllBytes(file.path, System.Text.Encoding.ASCII.GetBytes(file.data))); | ||
|
|
||
| Assert.That(compute(dirNames[0]), Is.Not.EqualTo(compute(dirNames[1]))); | ||
|
|
||
| files.AsParallel() | ||
| .ForAll(file => File.Delete(file.path)); | ||
| Directory.Delete(dirNames[3]); | ||
| Directory.Delete(dirNames[2]); | ||
| Directory.Delete(dirNames[1]); | ||
| Directory.Delete(dirNames[0]); | ||
|
|
||
|
|
||
|
|
||
|
Comment on lines
+156
to
+158
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Лишние пустые строки |
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| namespace Md5Calculator; | ||
|
|
||
| /// <summary> | ||
| /// Represents exception thrown in Md5 calculator | ||
| /// </summary> | ||
| public class CalculatorException : ArgumentException | ||
| { | ||
| /// <summary> | ||
| /// Creates exception with message | ||
| /// </summary> | ||
| /// <param name="message">Exception message</param> | ||
| public CalculatorException(string message) : base(message) | ||
| { | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Creates empty exception | ||
| /// </summary> | ||
| public CalculatorException() | ||
| { | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| namespace Md5Calculator; | ||
|
|
||
| using System.Security.Cryptography; | ||
|
|
||
| /// <summary> | ||
| /// Represents md5 hash calculator | ||
| /// </summary> | ||
| public static class Md5Calculator | ||
| { | ||
| /// <summary> | ||
| /// Asynchronously computes md5 hash of given path | ||
| /// </summary> | ||
| /// <param name="path">Path to compute</param> | ||
| /// <returns>Hash as byte array</returns> | ||
| /// <exception cref="CalculatorException">If given does not exist</exception> | ||
| public static async Task<byte[]> ComputeAsync(string path) | ||
| { | ||
| if (!Directory.Exists(path) && !File.Exists(path)) | ||
| { | ||
| throw new CalculatorException("Path doesnt exist"); | ||
| } | ||
|
|
||
| return !Directory.Exists(path) ? await ComputeFileAsync(path) : await ComputeDirAsync(path); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Synchronously computes md5 hash of given path | ||
| /// </summary> | ||
| /// <param name="path">Path to compute</param> | ||
| /// <returns>Hash as byte array</returns> | ||
| /// <exception cref="CalculatorException">If given does not exist</exception> | ||
| public static byte[] Compute(string path) | ||
| { | ||
| if (!Directory.Exists(path) && !File.Exists(path)) | ||
| { | ||
| throw new CalculatorException("Path doesnt exist"); | ||
| } | ||
|
|
||
| return !Directory.Exists(path) ? ComputeFile(path) : ComputeDir(path); | ||
| } | ||
|
|
||
|
|
||
| private static async Task<byte[]> ComputeDirAsync(string path) | ||
| { | ||
| var fileTasks = Directory.GetFiles(path).Select(ComputeFileAsync); | ||
|
|
||
| var dirTasks = Directory.GetDirectories(path).Select(ComputeDirAsync); | ||
|
Comment on lines
+45
to
+47
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Directory.GetFiles не фиксирует порядок возвращаемых файлов, так что их сначала надо было отсортировать, иначе значение хеша будет зависеть от непредсказуемых внешних факторов (типа удалили и добавили тот же файл). |
||
|
|
||
| var tasks = fileTasks.Concat(dirTasks).ToArray(); | ||
|
|
||
| var overallBytes = (await Task.WhenAll(tasks)) | ||
| .SelectMany(byteArray => byteArray) | ||
| .Concat(ComputeString(new DirectoryInfo(path).Name)) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. По условию от имени директории хеш не считается, она добавляется как есть. Впрочем, это особо не важно, просто лишняя работа. |
||
| .ToArray(); | ||
| return MD5.HashData(overallBytes); | ||
| } | ||
|
|
||
| private static byte[] ComputeDir(string path) | ||
| { | ||
| var fileBytes = Directory.GetFiles(path).SelectMany(ComputeFile); | ||
|
|
||
| var dirBytes = Directory.GetDirectories(path).SelectMany(ComputeDir); | ||
|
|
||
| var overallBytes = fileBytes.Concat(dirBytes).Concat(ComputeString(new DirectoryInfo(path).Name)).ToArray(); | ||
|
|
||
| return MD5.HashData(overallBytes); | ||
| } | ||
|
|
||
| private static async Task<byte[]> ComputeFileAsync(string path) | ||
| { | ||
| var byteArray = (await MD5.HashDataAsync(new FileInfo(path).OpenRead())).Concat(ComputeString(new FileInfo(path).Name)).ToArray(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Чтобы всё было вообще идеально в плане асинхронности, надо было ещё CancellationToken принимать. |
||
| return MD5.HashData(byteArray); | ||
| } | ||
|
|
||
| private static byte[] ComputeFile(string path) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Однопоточная версия тоже могла бы (да и должна была, по идее) использовать ComputeFileAsync. Асинхронность и многопоточность — разные вещи. |
||
| { | ||
| var byteArray = MD5.HashData(new FileInfo(path).OpenRead()).Concat(ComputeString(new FileInfo(path).Name)).ToArray(); | ||
| return MD5.HashData(byteArray); | ||
| } | ||
|
|
||
| private static byte[] ComputeString(string @string) => | ||
| MD5.HashData(System.Text.Encoding.ASCII.GetBytes(@string)); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <OutputType>Exe</OutputType> | ||
| <TargetFramework>net8.0</TargetFramework> | ||
| <RootNamespace>Md5_calculator</RootNamespace> | ||
| <ImplicitUsings>enable</ImplicitUsings> | ||
| <Nullable>enable</Nullable> | ||
| </PropertyGroup> | ||
|
|
||
| </Project> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| using Md5Calculator; | ||
| using System.Diagnostics; | ||
|
|
||
| if (args.Length != 1 || string.IsNullOrEmpty(args[0])) | ||
| { | ||
| Console.WriteLine("Should be 1 argument with path"); | ||
| return; | ||
| } | ||
|
|
||
| try | ||
| { | ||
| var startTime = Stopwatch.GetTimestamp(); | ||
| var hash = Md5Calculator.Md5Calculator.Compute(args[0]); | ||
| var endTime = Stopwatch.GetTimestamp(); | ||
| Console.WriteLine($"Single thread: {Convert.ToBase64String(hash)}, {Stopwatch.GetElapsedTime(startTime, endTime)}"); | ||
|
|
||
| startTime = Stopwatch.GetTimestamp(); | ||
| hash = await Md5Calculator.Md5Calculator.ComputeAsync(args[0]); | ||
| endTime = Stopwatch.GetTimestamp(); | ||
| Console.WriteLine($"Multi thread: {Convert.ToBase64String(hash)}, {Stopwatch.GetElapsedTime(startTime, endTime)}"); | ||
| } | ||
| catch (CalculatorException e) | ||
| { | ||
| Console.WriteLine(e.Message); | ||
| } | ||
| catch (IOException e) | ||
| { | ||
| Console.WriteLine(e.Message); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Имело смысл это в try/finally обернуть, или вынести в SetUp/TearDown, иначе при исключении в тестируемом коде тест оставит за собой временные файлы.