diff --git a/.gitignore b/.gitignore index 3820028..e0b8f92 100644 --- a/.gitignore +++ b/.gitignore @@ -14,10 +14,14 @@ [Rr]eleases/ [Bb]in/ [Oo]bj/ +[Bb]enchmark[Dd]ot[Nn]et.Artifacts/ -# Visual Studio 2015 cache/options directory +# Visual Studio cache/options directory .vs/ +# IntelliJ directory +.idea/ + # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* @@ -25,4 +29,4 @@ # NuGet Packages *.nupkg **/packages -!**/packages/build/ \ No newline at end of file +!**/packages/build/ diff --git a/benchmark.sh b/benchmark.sh new file mode 100644 index 0000000..6788e9d --- /dev/null +++ b/benchmark.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +dotnet run --project src/FileTypeInterrogator.Benchmark --configuration Release diff --git a/src/FileTypeInterrogator.Benchmark/BenchmarkConfig.cs b/src/FileTypeInterrogator.Benchmark/BenchmarkConfig.cs new file mode 100644 index 0000000..e5d53dc --- /dev/null +++ b/src/FileTypeInterrogator.Benchmark/BenchmarkConfig.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Environments; +using BenchmarkDotNet.Exporters; +using BenchmarkDotNet.Exporters.Csv; +using BenchmarkDotNet.Exporters.Json; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Loggers; + +namespace FileTypeInterrogator.Benchmark; + +public class BenchmarkConfig : ManualConfig +{ + public BenchmarkConfig(IEnumerable latestPackageVersionNumbers) + { + var framework = CoreRuntime.Core10_0; + + AddExporter(MarkdownExporter.GitHub); + AddExporter(CsvExporter.Default); + AddExporter(JsonExporter.Default); + + AddLogger(ConsoleLogger.Default); + + AddColumnProvider(DefaultColumnProviders.Instance); + + AddDiagnoser(MemoryDiagnoser.Default); + + // run benchmarks with the latest nuget package version + foreach (var versionNumber in latestPackageVersionNumbers) + { + var nugetPackageJob = Job.ShortRun + .WithRuntime(framework) + .WithMsBuildArguments($"/p:NugetPackageVersion={versionNumber}") + .WithId($"v{versionNumber}"); + + AddJob(nugetPackageJob); + } + + // job for locally referenced project (current build) + var currentProjectJob = Job.ShortRun + .WithRuntime(framework) + .WithId("current"); + + AddJob(currentProjectJob); + } +} diff --git a/src/FileTypeInterrogator.Benchmark/FileTypeInterrogator.Benchmark.csproj b/src/FileTypeInterrogator.Benchmark/FileTypeInterrogator.Benchmark.csproj new file mode 100644 index 0000000..58b2748 --- /dev/null +++ b/src/FileTypeInterrogator.Benchmark/FileTypeInterrogator.Benchmark.csproj @@ -0,0 +1,30 @@ + + + + Exe + net10.0 + enable + + + + + + + + + + + + + + + + <_TestFiles Include="../TestFiles/**/*.*" /> + + TestFiles\%(RecursiveDir)%(Filename)%(Extension) + PreserveNewest + false + + + + diff --git a/src/FileTypeInterrogator.Benchmark/FileTypeInterrogatorBenchmark.cs b/src/FileTypeInterrogator.Benchmark/FileTypeInterrogatorBenchmark.cs new file mode 100644 index 0000000..1198834 --- /dev/null +++ b/src/FileTypeInterrogator.Benchmark/FileTypeInterrogatorBenchmark.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Order; + +namespace FileTypeInterrogator.Benchmark; + +[Orderer(SummaryOrderPolicy.Method)] +public class FileTypeInterrogatorBenchmark +{ + private readonly global::FileTypeInterrogator.FileTypeInterrogator _fileTypeInterrogator = new(); + + [Benchmark] + public void DetectTypes_WithBytes() + { + DetectAll(false); + } + + [Benchmark] + public void DetectTypes_WithStream() + { + DetectAll(true); + } + + private void DetectAll(bool useStream) + { + DetectType("bmp", useStream); + DetectType("doc", useStream); + DetectType("mkv", useStream); + DetectType("pdf", useStream); + DetectType("png", useStream); + DetectType("txt", useStream); + DetectType("wav", useStream); + DetectType("wmv", useStream); + DetectType("xls", useStream); + DetectType("xml", useStream); + DetectType("zip", useStream); + } + + private void DetectType(string extension, bool useStream = false) + { + DetectType(extension, result => + { + ArgumentNullException.ThrowIfNull(result); + + if (!(result.FileType.Equals(extension, StringComparison.OrdinalIgnoreCase) || + result.Alias?.Any(a => a.Equals(extension, StringComparison.OrdinalIgnoreCase)) == true)) + { + throw new InvalidOperationException(string.Format("{0} and/or {1} do not equal {2}", + result.FileType, result.Alias?.FirstOrDefault(), extension)); + } + }, useStream); + } + + private void DetectType(string extension, Action assertionValidator, bool useStream = false) + { + var files = GetFilesByExtension(extension); + foreach (var file in files) + { + if (!useStream) + { + var fileContents = File.ReadAllBytes(file); + + var result = _fileTypeInterrogator.DetectType(fileContents); + + assertionValidator(result); + } + else + { + using var stream = File.OpenRead(file); + + var result = _fileTypeInterrogator.DetectType(stream); + + assertionValidator(result); + } + } + } + + private string GetFileByType(string type) + { + return Path.Combine(GetTestFileDirectory(), $"{type}.{type}"); + } + + private IEnumerable GetFilesByExtension(string type) + { + // GetFiles with searchPattern returns 4 character extensions when + // filtering for 3 so we'll filter ourselves + return Directory.GetFiles(GetTestFileDirectory(), $"*.{type}") + .Where(path => path.EndsWith(type, StringComparison.OrdinalIgnoreCase)); + } + + private string GetTestFileDirectory() + { + return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "TestFiles"); + } +} diff --git a/src/FileTypeInterrogator.Benchmark/Nuget/NugetPackage.cs b/src/FileTypeInterrogator.Benchmark/Nuget/NugetPackage.cs new file mode 100644 index 0000000..c1b3468 --- /dev/null +++ b/src/FileTypeInterrogator.Benchmark/Nuget/NugetPackage.cs @@ -0,0 +1,12 @@ +namespace FileTypeInterrogator.Benchmark.Nuget; + +using System.Text.Json.Serialization; + +public class NugetPackage +{ + [JsonPropertyName("id")] + public required string Id { get; set; } + + [JsonPropertyName("version")] + public required string Version { get; set; } +} diff --git a/src/FileTypeInterrogator.Benchmark/Nuget/NugetSearchResultRoot.cs b/src/FileTypeInterrogator.Benchmark/Nuget/NugetSearchResultRoot.cs new file mode 100644 index 0000000..5e01132 --- /dev/null +++ b/src/FileTypeInterrogator.Benchmark/Nuget/NugetSearchResultRoot.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace FileTypeInterrogator.Benchmark.Nuget; + +public class NugetSearchResultRoot +{ + [JsonPropertyName("version")] + public int Version { get; set; } + + [JsonPropertyName("problems")] + public List Problems { get; set; } = new List(); + + [JsonPropertyName("searchResult")] + public List SearchResult { get; set; } = new List(); +} diff --git a/src/FileTypeInterrogator.Benchmark/Nuget/NugetSourceResult.cs b/src/FileTypeInterrogator.Benchmark/Nuget/NugetSourceResult.cs new file mode 100644 index 0000000..077eef6 --- /dev/null +++ b/src/FileTypeInterrogator.Benchmark/Nuget/NugetSourceResult.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace FileTypeInterrogator.Benchmark.Nuget; + +public class NugetSourceResult +{ + [JsonPropertyName("sourceName")] + public required string SourceName { get; set; } + + [JsonPropertyName("packages")] + public List Packages { get; set; } = new List(); +} diff --git a/src/FileTypeInterrogator.Benchmark/Program.cs b/src/FileTypeInterrogator.Benchmark/Program.cs new file mode 100644 index 0000000..57c06bd --- /dev/null +++ b/src/FileTypeInterrogator.Benchmark/Program.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using BenchmarkDotNet.Running; +using FileTypeInterrogator.Benchmark.Nuget; + +namespace FileTypeInterrogator.Benchmark; + +public static class Program +{ + public static async Task Main(string[] args) + { + var latestVersions = await GetLatestPackageVersionNumbers("FileTypeInterrogator", topN: 1); + + _ = BenchmarkRunner.Run(new BenchmarkConfig(latestVersions)); + } + + private static async Task> GetLatestPackageVersionNumbers(string packageName, int topN = 1, + CancellationToken cancellationToken = default) + { + var startInfo = new ProcessStartInfo + { + FileName = "dotnet", + Arguments = $"package search {packageName} --format json --exact-match", + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true, + }; + + using Process? process = Process.Start(startInfo); + + if (process == null) throw new InvalidOperationException("Failed to start dotnet process"); + + await process.WaitForExitAsync(cancellationToken); + + string output = await process.StandardOutput.ReadToEndAsync(cancellationToken); + + var packages = JsonSerializer.Deserialize(output); + + var latestPackages = packages?.SearchResult.SelectMany(sr => sr.Packages) + .OrderByDescending(p => Version.Parse(p.Version)) + .Take(topN); + + return latestPackages?.Select(p => p.Version).ToList() ?? new List(); + } +} diff --git a/src/FileTypeInterrogator.Tests/FileTypeInterrogator.Tests.csproj b/src/FileTypeInterrogator.Tests/FileTypeInterrogator.Tests.csproj index 669c3ef..fd1931c 100644 --- a/src/FileTypeInterrogator.Tests/FileTypeInterrogator.Tests.csproj +++ b/src/FileTypeInterrogator.Tests/FileTypeInterrogator.Tests.csproj @@ -10,8 +10,8 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -19,12 +19,15 @@ - + - - Always + <_TestFiles Include="../TestFiles/**/*.*"/> + + TestFiles\%(RecursiveDir)%(Filename)%(Extension) + PreserveNewest + false - \ No newline at end of file + diff --git a/src/FileTypeInterrogator.sln b/src/FileTypeInterrogator.sln index c22af02..05a3c27 100644 --- a/src/FileTypeInterrogator.sln +++ b/src/FileTypeInterrogator.sln @@ -16,6 +16,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\README.md = ..\README.md EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTypeInterrogator.Benchmark", "FileTypeInterrogator.Benchmark\FileTypeInterrogator.Benchmark.csproj", "{C6E6F23D-AE65-4DBD-A975-FD71291CD1BE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -30,6 +32,10 @@ Global {8181D724-8886-4541-8819-9FF208A28074}.Debug|Any CPU.Build.0 = Debug|Any CPU {8181D724-8886-4541-8819-9FF208A28074}.Release|Any CPU.ActiveCfg = Release|Any CPU {8181D724-8886-4541-8819-9FF208A28074}.Release|Any CPU.Build.0 = Release|Any CPU + {C6E6F23D-AE65-4DBD-A975-FD71291CD1BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C6E6F23D-AE65-4DBD-A975-FD71291CD1BE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C6E6F23D-AE65-4DBD-A975-FD71291CD1BE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C6E6F23D-AE65-4DBD-A975-FD71291CD1BE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/FileTypeInterrogator/BaseFileTypeInterrogator.cs b/src/FileTypeInterrogator/BaseFileTypeInterrogator.cs index d5b21eb..6162724 100644 --- a/src/FileTypeInterrogator/BaseFileTypeInterrogator.cs +++ b/src/FileTypeInterrogator/BaseFileTypeInterrogator.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using System.Collections.Generic; using System.IO; using System.Linq; @@ -11,10 +12,16 @@ namespace FileTypeInterrogator /// public abstract class BaseFileTypeInterrogator : IFileTypeInterrogator { + private static readonly UTF8Encoding Utf8WithBomEncoding = new UTF8Encoding(true, true); + private static readonly UTF8Encoding Utf8WithoutBomEncoding = new UTF8Encoding(false, true); + private static readonly byte[] Utf8Bom = Utf8WithBomEncoding.GetPreamble(); + private readonly Lazy> lazyFileTypes; private readonly FileTypeInfo asciiFileType = new FileTypeInfo("ASCII Text", "txt", "text/plain", null); private readonly FileTypeInfo utf8FileType = new FileTypeInfo("UTF-8 Text", "txt", "text/plain", null); - private readonly FileTypeInfo utf8FileTypeWithBOM = new FileTypeInfo("UTF-8 Text with BOM", "txt", "text/plain", null); + + private readonly FileTypeInfo utf8FileTypeWithBOM = + new FileTypeInfo("UTF-8 Text with BOM", "txt", "text/plain", null); /// /// Initializes a with the provided json definition. @@ -43,13 +50,33 @@ public FileTypeInfo DetectType(Stream inputStream) if (inputStream.CanSeek) inputStream.Position = 0; - byte[] byteBuffer = new byte[inputStream.Length]; - _ = inputStream.Read(byteBuffer, 0, byteBuffer.Length); + long streamLength = inputStream.Length; + if (streamLength > int.MaxValue) + throw new NotSupportedException("Streams larger than 2 GB are not supported."); - if (inputStream.CanSeek) - inputStream.Position = 0; + int bufferSize = (int)streamLength; + byte[] byteBuffer = ArrayPool.Shared.Rent(bufferSize); + try + { + int bytesRead = 0; + while (bytesRead < bufferSize) + { + int read = inputStream.Read(byteBuffer, bytesRead, bufferSize - bytesRead); + if (read == 0) + break; - return DetectType(byteBuffer); + bytesRead += read; + } + + return DetectType(byteBuffer, bytesRead); + } + finally + { + if (inputStream.CanSeek) + inputStream.Position = 0; + + ArrayPool.Shared.Return(byteBuffer); + } } /// @@ -62,21 +89,31 @@ public FileTypeInfo DetectType(byte[] fileContent) if (fileContent == null) throw new ArgumentNullException(nameof(fileContent)); - if (fileContent.Length == 0) + return DetectType(fileContent, fileContent.Length); + } + + private FileTypeInfo DetectType(byte[] fileContent, int length) + { + if (fileContent == null) + throw new ArgumentNullException(nameof(fileContent)); + + if (length == 0) throw new ArgumentException("input must not be empty"); + ReadOnlySpan input = fileContent.AsSpan(0, length); + // iterate over each type and determine if we have a match based on file signature. foreach (var fileTypeInfo in AvailableTypes) { // if we found a match return the matching filetypeinfo - if (IsMatchingType(fileContent, fileTypeInfo)) + if (IsMatchingType(input, fileTypeInfo)) return fileTypeInfo; } - if (IsAscii(fileContent)) + if (IsAscii(input)) return asciiFileType; - if (IsUTF8(fileContent, out bool hasBOM)) + if (IsUTF8(fileContent, length, out bool hasBOM)) return hasBOM ? utf8FileTypeWithBOM : utf8FileType; return null; @@ -108,13 +145,24 @@ public IEnumerable GetAvailableMimeTypes() /// public bool IsType(byte[] fileContent, string extensionAliasOrMimeType) { - foreach (var fileTypeInfo in AvailableTypes.Where(t => - t.FileType.Equals(extensionAliasOrMimeType, StringComparison.OrdinalIgnoreCase) || - t.MimeType.Equals(extensionAliasOrMimeType, StringComparison.OrdinalIgnoreCase) || - (t.Alias != null && t.Alias.Contains(extensionAliasOrMimeType, StringComparer.OrdinalIgnoreCase)))) + if (fileContent == null) + throw new ArgumentNullException(nameof(fileContent)); + + return IsType(fileContent, fileContent.Length, extensionAliasOrMimeType); + } + + private bool IsType(byte[] fileContent, int length, string extensionAliasOrMimeType) + { + foreach (var fileTypeInfo in AvailableTypes) { - if (IsMatchingType(fileContent, fileTypeInfo)) - return true; + if (fileTypeInfo.FileType.Equals(extensionAliasOrMimeType, StringComparison.OrdinalIgnoreCase) || + fileTypeInfo.MimeType.Equals(extensionAliasOrMimeType, StringComparison.OrdinalIgnoreCase) || + (fileTypeInfo.Alias != null && + fileTypeInfo.Alias.Contains(extensionAliasOrMimeType, StringComparer.OrdinalIgnoreCase))) + { + if (IsMatchingType(fileContent.AsSpan(0, length), fileTypeInfo)) + return true; + } } if (extensionAliasOrMimeType.Equals("txt", StringComparison.OrdinalIgnoreCase) || @@ -131,23 +179,18 @@ private static bool IsMatchingType(ReadOnlySpan input, FileTypeInfo type) // some file types have the same header // but different signature in another location, if its one of these determine what the true file type is - if (isMatch && type.SubHeader != null) + int subHeaderLength = type.SubHeader?.Length ?? 0; + if (isMatch && subHeaderLength > 0) { - // find all indices of matching the 1st byte of the additional sequence - var matchingIndices = new List(); - for (int i = 0; i < input.Length; i++) + isMatch = false; + for (int i = 0; i <= input.Length - subHeaderLength; i++) { if (input[i] == type.SubHeader[0]) - matchingIndices.Add(i); - } - - // investigate all of them for a match - foreach (int potentialMatchingIndex in matchingIndices) - { - isMatch = FindMatch(input, type.SubHeader, potentialMatchingIndex); - - if (isMatch) - break; + { + isMatch = FindMatch(input, type.SubHeader, i); + if (isMatch) + break; + } } } @@ -177,8 +220,10 @@ private static bool FindMatch(ReadOnlySpan input, ReadOnlySpan searc matchingCount = 0; break; } + matchingCount++; } + return matchingCount == searchArray.Length; } @@ -200,7 +245,8 @@ private static IEnumerable LoadFileTypes(string flatFileData) string alias = segments.Length == 8 ? segments[7] : null; byte[] sigBytes = HexStringToByteArray(signature); - byte[] additionalBytes = string.IsNullOrWhiteSpace(additional) ? null : HexStringToByteArray(additional); + byte[] additionalBytes = + string.IsNullOrWhiteSpace(additional) ? null : HexStringToByteArray(additional); string[] aliases = string.IsNullOrWhiteSpace(alias) ? null : alias.Split('|'); yield return new FileTypeInfo( @@ -231,7 +277,7 @@ private static bool IsText(byte[] input, out bool hasBOM) bool isAscii = IsAscii(input); - return isAscii || IsUTF8(input, out hasBOM); + return isAscii || IsUTF8(input, input.Length, out hasBOM); } private static bool IsAscii(ReadOnlySpan input) @@ -242,23 +288,23 @@ private static bool IsAscii(ReadOnlySpan input) if (b > maxAscii) return false; } + return true; } - private static bool IsUTF8(byte[] input, out bool hasBOM) + private static bool IsUTF8(byte[] input, int length, out bool hasBOM) { - UTF8Encoding utf8WithBOM = new UTF8Encoding(true, true); bool isUTF8 = true; - byte[] bom = utf8WithBOM.GetPreamble(); - int bomLength = bom.Length; + int bomLength = Utf8Bom.Length; hasBOM = false; - if (input.Length >= bomLength && bom.SequenceEqual(input.Take(bomLength))) + ReadOnlySpan inputSpan = input.AsSpan(0, length); + if (length >= bomLength && inputSpan.Slice(0, bomLength).SequenceEqual(Utf8Bom)) { try { - utf8WithBOM.GetString(input, bomLength, input.Length - bomLength); + Utf8WithBomEncoding.GetString(input, bomLength, length - bomLength); hasBOM = true; } catch (ArgumentException) @@ -270,10 +316,9 @@ private static bool IsUTF8(byte[] input, out bool hasBOM) if (isUTF8 && !hasBOM) { - UTF8Encoding utf8WithoutBOM = new UTF8Encoding(false, true); try { - utf8WithoutBOM.GetString(input, 0, input.Length); + Utf8WithoutBomEncoding.GetString(input, 0, length); isUTF8 = true; } catch (ArgumentException) diff --git a/src/FileTypeInterrogator.Tests/TestFiles/3gp.3gp b/src/TestFiles/3gp.3gp similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/3gp.3gp rename to src/TestFiles/3gp.3gp diff --git a/src/FileTypeInterrogator.Tests/TestFiles/7z.7z b/src/TestFiles/7z.7z similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/7z.7z rename to src/TestFiles/7z.7z diff --git a/src/FileTypeInterrogator.Tests/TestFiles/ac3.ac3 b/src/TestFiles/ac3.ac3 similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/ac3.ac3 rename to src/TestFiles/ac3.ac3 diff --git a/src/FileTypeInterrogator.Tests/TestFiles/ai.ai b/src/TestFiles/ai.ai similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/ai.ai rename to src/TestFiles/ai.ai diff --git a/src/FileTypeInterrogator.Tests/TestFiles/aiff.aiff b/src/TestFiles/aiff.aiff similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/aiff.aiff rename to src/TestFiles/aiff.aiff diff --git a/src/FileTypeInterrogator.Tests/TestFiles/ascii.ascii b/src/TestFiles/ascii.ascii similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/ascii.ascii rename to src/TestFiles/ascii.ascii diff --git a/src/FileTypeInterrogator.Tests/TestFiles/avi.avi b/src/TestFiles/avi.avi similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/avi.avi rename to src/TestFiles/avi.avi diff --git a/src/FileTypeInterrogator.Tests/TestFiles/bmp.bmp b/src/TestFiles/bmp.bmp similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/bmp.bmp rename to src/TestFiles/bmp.bmp diff --git a/src/FileTypeInterrogator.Tests/TestFiles/doc.doc b/src/TestFiles/doc.doc similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/doc.doc rename to src/TestFiles/doc.doc diff --git a/src/FileTypeInterrogator.Tests/TestFiles/doc2.doc b/src/TestFiles/doc2.doc similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/doc2.doc rename to src/TestFiles/doc2.doc diff --git a/src/FileTypeInterrogator.Tests/TestFiles/doc3.doc b/src/TestFiles/doc3.doc similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/doc3.doc rename to src/TestFiles/doc3.doc diff --git a/src/FileTypeInterrogator.Tests/TestFiles/doc4.doc b/src/TestFiles/doc4.doc similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/doc4.doc rename to src/TestFiles/doc4.doc diff --git a/src/FileTypeInterrogator.Tests/TestFiles/docx.docx b/src/TestFiles/docx.docx similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/docx.docx rename to src/TestFiles/docx.docx diff --git a/src/FileTypeInterrogator.Tests/TestFiles/eml.eml b/src/TestFiles/eml.eml similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/eml.eml rename to src/TestFiles/eml.eml diff --git a/src/FileTypeInterrogator.Tests/TestFiles/fdf.fdf b/src/TestFiles/fdf.fdf similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/fdf.fdf rename to src/TestFiles/fdf.fdf diff --git a/src/FileTypeInterrogator.Tests/TestFiles/flac.flac b/src/TestFiles/flac.flac similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/flac.flac rename to src/TestFiles/flac.flac diff --git a/src/FileTypeInterrogator.Tests/TestFiles/flv.flv b/src/TestFiles/flv.flv similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/flv.flv rename to src/TestFiles/flv.flv diff --git a/src/FileTypeInterrogator.Tests/TestFiles/gif.gif b/src/TestFiles/gif.gif similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/gif.gif rename to src/TestFiles/gif.gif diff --git a/src/FileTypeInterrogator.Tests/TestFiles/gz.gz b/src/TestFiles/gz.gz similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/gz.gz rename to src/TestFiles/gz.gz diff --git a/src/FileTypeInterrogator.Tests/TestFiles/ico.ico b/src/TestFiles/ico.ico similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/ico.ico rename to src/TestFiles/ico.ico diff --git a/src/FileTypeInterrogator.Tests/TestFiles/jp2.jp2 b/src/TestFiles/jp2.jp2 similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/jp2.jp2 rename to src/TestFiles/jp2.jp2 diff --git a/src/FileTypeInterrogator.Tests/TestFiles/jpg.jpg b/src/TestFiles/jpg.jpg similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/jpg.jpg rename to src/TestFiles/jpg.jpg diff --git a/src/FileTypeInterrogator.Tests/TestFiles/mid.mid b/src/TestFiles/mid.mid similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/mid.mid rename to src/TestFiles/mid.mid diff --git a/src/FileTypeInterrogator.Tests/TestFiles/mkv.mkv b/src/TestFiles/mkv.mkv similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/mkv.mkv rename to src/TestFiles/mkv.mkv diff --git a/src/FileTypeInterrogator.Tests/TestFiles/mp3.mp3 b/src/TestFiles/mp3.mp3 similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/mp3.mp3 rename to src/TestFiles/mp3.mp3 diff --git a/src/FileTypeInterrogator.Tests/TestFiles/mp4.mp4 b/src/TestFiles/mp4.mp4 similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/mp4.mp4 rename to src/TestFiles/mp4.mp4 diff --git a/src/FileTypeInterrogator.Tests/TestFiles/odp.odp b/src/TestFiles/odp.odp similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/odp.odp rename to src/TestFiles/odp.odp diff --git a/src/FileTypeInterrogator.Tests/TestFiles/odt.odt b/src/TestFiles/odt.odt similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/odt.odt rename to src/TestFiles/odt.odt diff --git a/src/FileTypeInterrogator.Tests/TestFiles/ogg.ogg b/src/TestFiles/ogg.ogg similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/ogg.ogg rename to src/TestFiles/ogg.ogg diff --git a/src/FileTypeInterrogator.Tests/TestFiles/pcx.pcx b/src/TestFiles/pcx.pcx similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/pcx.pcx rename to src/TestFiles/pcx.pcx diff --git a/src/FileTypeInterrogator.Tests/TestFiles/pdf.pdf b/src/TestFiles/pdf.pdf similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/pdf.pdf rename to src/TestFiles/pdf.pdf diff --git a/src/FileTypeInterrogator.Tests/TestFiles/png.png b/src/TestFiles/png.png similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/png.png rename to src/TestFiles/png.png diff --git a/src/FileTypeInterrogator.Tests/TestFiles/ppt.ppt b/src/TestFiles/ppt.ppt similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/ppt.ppt rename to src/TestFiles/ppt.ppt diff --git a/src/FileTypeInterrogator.Tests/TestFiles/pptx.pptx b/src/TestFiles/pptx.pptx similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/pptx.pptx rename to src/TestFiles/pptx.pptx diff --git a/src/FileTypeInterrogator.Tests/TestFiles/psd.psd b/src/TestFiles/psd.psd similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/psd.psd rename to src/TestFiles/psd.psd diff --git a/src/FileTypeInterrogator.Tests/TestFiles/ra.ra b/src/TestFiles/ra.ra similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/ra.ra rename to src/TestFiles/ra.ra diff --git a/src/FileTypeInterrogator.Tests/TestFiles/rar.rar b/src/TestFiles/rar.rar similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/rar.rar rename to src/TestFiles/rar.rar diff --git a/src/FileTypeInterrogator.Tests/TestFiles/rtf.rtf b/src/TestFiles/rtf.rtf similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/rtf.rtf rename to src/TestFiles/rtf.rtf diff --git a/src/FileTypeInterrogator.Tests/TestFiles/tif.tif b/src/TestFiles/tif.tif similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/tif.tif rename to src/TestFiles/tif.tif diff --git a/src/FileTypeInterrogator.Tests/TestFiles/ttf.ttf b/src/TestFiles/ttf.ttf similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/ttf.ttf rename to src/TestFiles/ttf.ttf diff --git a/src/FileTypeInterrogator.Tests/TestFiles/utf8.utf8 b/src/TestFiles/utf8.utf8 similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/utf8.utf8 rename to src/TestFiles/utf8.utf8 diff --git a/src/FileTypeInterrogator.Tests/TestFiles/utf8bom.utf8bom b/src/TestFiles/utf8bom.utf8bom similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/utf8bom.utf8bom rename to src/TestFiles/utf8bom.utf8bom diff --git a/src/FileTypeInterrogator.Tests/TestFiles/vcf.vcf b/src/TestFiles/vcf.vcf similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/vcf.vcf rename to src/TestFiles/vcf.vcf diff --git a/src/FileTypeInterrogator.Tests/TestFiles/wav.wav b/src/TestFiles/wav.wav similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/wav.wav rename to src/TestFiles/wav.wav diff --git a/src/FileTypeInterrogator.Tests/TestFiles/webm.webm b/src/TestFiles/webm.webm similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/webm.webm rename to src/TestFiles/webm.webm diff --git a/src/FileTypeInterrogator.Tests/TestFiles/webp.webp b/src/TestFiles/webp.webp similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/webp.webp rename to src/TestFiles/webp.webp diff --git a/src/FileTypeInterrogator.Tests/TestFiles/wmv.wmv b/src/TestFiles/wmv.wmv similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/wmv.wmv rename to src/TestFiles/wmv.wmv diff --git a/src/FileTypeInterrogator.Tests/TestFiles/woff.woff b/src/TestFiles/woff.woff similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/woff.woff rename to src/TestFiles/woff.woff diff --git a/src/FileTypeInterrogator.Tests/TestFiles/xls.xls b/src/TestFiles/xls.xls similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/xls.xls rename to src/TestFiles/xls.xls diff --git a/src/FileTypeInterrogator.Tests/TestFiles/xls2.xls b/src/TestFiles/xls2.xls similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/xls2.xls rename to src/TestFiles/xls2.xls diff --git a/src/FileTypeInterrogator.Tests/TestFiles/xlsx.xlsx b/src/TestFiles/xlsx.xlsx similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/xlsx.xlsx rename to src/TestFiles/xlsx.xlsx diff --git a/src/FileTypeInterrogator.Tests/TestFiles/zip.zip b/src/TestFiles/zip.zip similarity index 100% rename from src/FileTypeInterrogator.Tests/TestFiles/zip.zip rename to src/TestFiles/zip.zip