diff --git a/NAPS2.Sdk.ScannerTests/.gitignore b/NAPS2.Sdk.ScannerTests/.gitignore new file mode 100644 index 000000000..30794521b --- /dev/null +++ b/NAPS2.Sdk.ScannerTests/.gitignore @@ -0,0 +1,33 @@ +Thumbs.db +*.obj +*.exe +*.pdb +*.user +*.aps +*.pch +*.vspscc +*_i.c +*_p.c +*.ncb +*.suo +*.sln.docstates +*.tlb +*.tlh +*.bak +*.cache +*.ilk +*.log +[Bb]in +[Dd]ebug*/ +*.lib +*.sbr +obj/ +[Rr]elease*/ +_ReSharper*/ +[Tt]est[Rr]esult* +*.vssscc +$tf*/ +publish/ +bin/ +temp/ +*.credentials.json \ No newline at end of file diff --git a/NAPS2.Sdk.ScannerTests/HowToRunScannerTests.cs b/NAPS2.Sdk.ScannerTests/HowToRunScannerTests.cs new file mode 100644 index 000000000..e09dce366 --- /dev/null +++ b/NAPS2.Sdk.ScannerTests/HowToRunScannerTests.cs @@ -0,0 +1,11 @@ +namespace NAPS2.Sdk.ScannerTests; + +public class HowToRunScannerTests +{ + // 1. Print out the NAPS2 test page (naps2_test_page.pdf) and put it in your scanner. + + // 2. Set to "" to use the first scanner device, otherwise a substring of the scanner name (e.g. "Canon"). + public const string SCANNER_NAME = ""; + + // 3. Start debugging the test you want to run. +} \ No newline at end of file diff --git a/NAPS2.Sdk.ScannerTests/NAPS2.Sdk.ScannerTests.csproj b/NAPS2.Sdk.ScannerTests/NAPS2.Sdk.ScannerTests.csproj new file mode 100644 index 000000000..8be1abd8a --- /dev/null +++ b/NAPS2.Sdk.ScannerTests/NAPS2.Sdk.ScannerTests.csproj @@ -0,0 +1,21 @@ + + + + net462 + NAPS2.Sdk.ScannerTests + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/NAPS2.Sdk.ScannerTests/ScannerFactAttribute.cs b/NAPS2.Sdk.ScannerTests/ScannerFactAttribute.cs new file mode 100644 index 000000000..8e840c357 --- /dev/null +++ b/NAPS2.Sdk.ScannerTests/ScannerFactAttribute.cs @@ -0,0 +1,14 @@ +using Xunit; + +namespace NAPS2.Sdk.ScannerTests; + +public sealed class ScannerFactAttribute : FactAttribute +{ + public ScannerFactAttribute() + { + if (!Debugger.IsAttached) + { + Skip = "Scanner tests can only run when debugging as they require user interaction."; + } + } +} \ No newline at end of file diff --git a/NAPS2.Sdk.ScannerTests/ScannerTests.cs b/NAPS2.Sdk.ScannerTests/ScannerTests.cs new file mode 100644 index 000000000..f41de029e --- /dev/null +++ b/NAPS2.Sdk.ScannerTests/ScannerTests.cs @@ -0,0 +1,58 @@ +using NAPS2.Images.Gdi; +using NAPS2.Scan; +using Xunit; + +namespace NAPS2.Sdk.ScannerTests; + +public class ScannerTests +{ + // TODO: Make real tests for WIA/TWAIN, Flatbed/Feeder, Color/Gray/BW, DPIs, etc. + + [ScannerFact] + public async Task Test1() + { + var imageContext = new GdiImageContext(); + using var scanningContext = new ScanningContext(imageContext); + + var scanController = new ScanController(scanningContext); + var devices = await scanController.GetDeviceList(); + var device = GetUserDevice(devices); + + var options = new ScanOptions + { + Device = device, + Driver = Driver.Wia, + PaperSource = PaperSource.Flatbed, + Dpi = 100 + }; + + var source = scanController.Scan(options); + var image = await source.Next(); + + Assert.NotNull(image); + + using var rendered = imageContext.Render(image); + + // TODO: Aside from generating the relevant files/resources, we also need to consider how to compare images when ImageAsserts assumes perfect pixel alignment. + // TODO: One possibility is having a section of the test page with gradual gradients and only compare that subsection of the images. + // ImageAsserts.Similar(ScannerTestResources.naps2_test_page, rendered); + } + + // TODO: Generalize the common infrastructure into helper classes (ScannerTests as a base class, FlatbedTests, FeederTests, etc.?) + private static ScanDevice GetUserDevice(List devices) + { + if (devices.Count == 0) + { + throw new InvalidOperationException("No scanner available"); + } + foreach (var device in devices) + { + if (device.Name!.IndexOf(HowToRunScannerTests.SCANNER_NAME, StringComparison.OrdinalIgnoreCase) != -1) + { + return device; + } + } + throw new InvalidOperationException("Set SCANNER_NAME to one of: " + + string.Join(",", devices.Select(x => x.Name))); + } +} \ No newline at end of file diff --git a/NAPS2.Sdk.ScannerTests/xunit.runner.json b/NAPS2.Sdk.ScannerTests/xunit.runner.json new file mode 100644 index 000000000..369786b65 --- /dev/null +++ b/NAPS2.Sdk.ScannerTests/xunit.runner.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", + "parallelizeTestCollections": false +} \ No newline at end of file diff --git a/NAPS2.Sdk/Scan/ScanDevice.cs b/NAPS2.Sdk/Scan/ScanDevice.cs index f20b08ab6..0ada1a916 100644 --- a/NAPS2.Sdk/Scan/ScanDevice.cs +++ b/NAPS2.Sdk/Scan/ScanDevice.cs @@ -1,5 +1,6 @@ namespace NAPS2.Scan; +// TODO: Can we make this a record and/or make properties non-nullable? /// /// The representation of a scanning device identified by a driver. /// diff --git a/NAPS2.sln b/NAPS2.sln index e84f486a1..b838318f9 100644 --- a/NAPS2.sln +++ b/NAPS2.sln @@ -55,6 +55,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NAPS2.Images.Gdi", "NAPS2.I EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NAPS2.App.Tests", "NAPS2.App.Tests\NAPS2.App.Tests.csproj", "{3CA45B6B-F055-4FFB-B9B6-0FF381A752F9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NAPS2.Sdk.ScannerTests", "NAPS2.Sdk.ScannerTests\NAPS2.Sdk.ScannerTests.csproj", "{D291C9E9-42D2-4601-9EE3-1CBCA200B897}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -253,6 +255,16 @@ Global {3CA45B6B-F055-4FFB-B9B6-0FF381A752F9}.InstallerMSI|Any CPU.Build.0 = Debug|Any CPU {3CA45B6B-F055-4FFB-B9B6-0FF381A752F9}.Standalone|Any CPU.ActiveCfg = Debug|Any CPU {3CA45B6B-F055-4FFB-B9B6-0FF381A752F9}.Standalone|Any CPU.Build.0 = Debug|Any CPU + {D291C9E9-42D2-4601-9EE3-1CBCA200B897}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D291C9E9-42D2-4601-9EE3-1CBCA200B897}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D291C9E9-42D2-4601-9EE3-1CBCA200B897}.DebugLang|Any CPU.ActiveCfg = Debug|Any CPU + {D291C9E9-42D2-4601-9EE3-1CBCA200B897}.DebugLang|Any CPU.Build.0 = Debug|Any CPU + {D291C9E9-42D2-4601-9EE3-1CBCA200B897}.InstallerEXE|Any CPU.ActiveCfg = Debug|Any CPU + {D291C9E9-42D2-4601-9EE3-1CBCA200B897}.InstallerEXE|Any CPU.Build.0 = Debug|Any CPU + {D291C9E9-42D2-4601-9EE3-1CBCA200B897}.InstallerMSI|Any CPU.ActiveCfg = Debug|Any CPU + {D291C9E9-42D2-4601-9EE3-1CBCA200B897}.InstallerMSI|Any CPU.Build.0 = Debug|Any CPU + {D291C9E9-42D2-4601-9EE3-1CBCA200B897}.Standalone|Any CPU.ActiveCfg = Debug|Any CPU + {D291C9E9-42D2-4601-9EE3-1CBCA200B897}.Standalone|Any CPU.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE