mirror of
https://github.com/cyanfish/naps2.git
synced 2024-09-11 15:26:55 +03:00
Make the console return a non-zero exit code on error, add console app tests, and remove static variables in console deps
This commit is contained in:
parent
dafc26c6b3
commit
425b896cb4
@ -8,9 +8,9 @@ static class Program
|
||||
/// The NAPS2.Console.exe main method.
|
||||
/// </summary>
|
||||
[STAThread]
|
||||
static void Main(string[] args)
|
||||
static int Main(string[] args)
|
||||
{
|
||||
// Use reflection to avoid antivirus false positives (yes, really)
|
||||
typeof(ConsoleEntryPoint).GetMethod("Run").Invoke(null, new object[] { args });
|
||||
return (int) typeof(ConsoleEntryPoint).GetMethod("Run")!.Invoke(null, new object[] { args })!;
|
||||
}
|
||||
}
|
@ -72,4 +72,10 @@ public static class AppTestHelper
|
||||
var path = Path.Combine(appData, "NAPS2", "errorlog.txt");
|
||||
Assert.False(File.Exists(path));
|
||||
}
|
||||
|
||||
public static void AssertErrorLog(string appData)
|
||||
{
|
||||
var path = Path.Combine(appData, "NAPS2", "errorlog.txt");
|
||||
Assert.True(File.Exists(path));
|
||||
}
|
||||
}
|
53
NAPS2.App.Tests/ConsoleAppTests.cs
Normal file
53
NAPS2.App.Tests/ConsoleAppTests.cs
Normal file
@ -0,0 +1,53 @@
|
||||
using NAPS2.Sdk.Tests;
|
||||
using Xunit;
|
||||
|
||||
namespace NAPS2.App.Tests;
|
||||
|
||||
public class ConsoleAppTests : ContextualTexts
|
||||
{
|
||||
[Fact]
|
||||
public void ConvertsImportedFile()
|
||||
{
|
||||
var importPath = Path.Combine(FolderPath, "in.png");
|
||||
SharedData.color_image.Save(importPath);
|
||||
var outputPath = Path.Combine(FolderPath, "out.jpg");
|
||||
var args = $"-n 0 -i \"{importPath}\" -o \"{outputPath}\"";
|
||||
|
||||
var process = AppTestHelper.StartProcess("NAPS2.Console.exe", FolderPath, args);
|
||||
try
|
||||
{
|
||||
Assert.True(process.WaitForExit(2000));
|
||||
Assert.Equal(0, process.ExitCode);
|
||||
Assert.Empty(process.StandardOutput.ReadToEnd());
|
||||
AppTestHelper.AssertNoErrorLog(FolderPath);
|
||||
Assert.True(File.Exists(outputPath));
|
||||
}
|
||||
finally
|
||||
{
|
||||
AppTestHelper.Cleanup(process);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NonZeroExitCodeForError()
|
||||
{
|
||||
var importPath = Path.Combine(FolderPath, "doesnotexist.png");
|
||||
var outputPath = Path.Combine(FolderPath, "out.jpg");
|
||||
var args = $"-n 0 -i \"{importPath}\" -o \"{outputPath}\"";
|
||||
|
||||
var process = AppTestHelper.StartProcess("NAPS2.Console.exe", FolderPath, args);
|
||||
try
|
||||
{
|
||||
Assert.True(process.WaitForExit(2000));
|
||||
var stdout = process.StandardOutput.ReadToEnd();
|
||||
Assert.NotEqual(0, process.ExitCode);
|
||||
Assert.NotEmpty(stdout);
|
||||
AppTestHelper.AssertErrorLog(FolderPath);
|
||||
Assert.False(File.Exists(outputPath));
|
||||
}
|
||||
finally
|
||||
{
|
||||
AppTestHelper.Cleanup(process);
|
||||
}
|
||||
}
|
||||
}
|
@ -22,5 +22,4 @@ public class WinFormsAppTests : ContextualTexts
|
||||
AppTestHelper.Cleanup(process);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -86,7 +86,6 @@ public class AutomatedScanning
|
||||
}
|
||||
|
||||
_placeholders = Placeholders.All.WithDate(DateTime.Now);
|
||||
ConsoleOverwritePrompt.ForceOverwrite = _options.ForceOverwrite;
|
||||
|
||||
if (_options.Install != null)
|
||||
{
|
||||
@ -266,8 +265,6 @@ public class AutomatedScanning
|
||||
{
|
||||
OutputVerbose(ConsoleResources.Importing);
|
||||
|
||||
ConsolePdfPasswordProvider.PasswordToProvide = _options.ImportPassword;
|
||||
|
||||
var filePaths = _options.ImportPath.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
int i = 0;
|
||||
foreach (var filePath in filePaths)
|
||||
@ -292,6 +289,7 @@ public class AutomatedScanning
|
||||
{
|
||||
Log.ErrorException(string.Format(ConsoleResources.ErrorImporting, filePath), ex);
|
||||
_errorOutput.DisplayError(string.Format(ConsoleResources.ErrorImporting, filePath));
|
||||
// TODO: Should we really continue?
|
||||
continue;
|
||||
}
|
||||
OutputVerbose(ConsoleResources.ImportedFile, i, filePaths.Length);
|
||||
|
@ -9,27 +9,27 @@ public class AutomatedScanningOptions
|
||||
[Option('o', "output", HelpText = "The name and path of the file to save." +
|
||||
" The extension determines the output type (e.g. .pdf for a PDF file, .jpg for a JPEG)." +
|
||||
" Placeholders can be used (e.g. $(YYYY)-$(MM)-$(DD) for the date, $(hh)_$(mm)_$(ss) for the time, $(nnnn) for an auto-incrementing number).")]
|
||||
public string OutputPath { get; set; }
|
||||
public string? OutputPath { get; set; }
|
||||
|
||||
[Option('a', "autosave", HelpText = "Use the Auto Save settings from the selected profile." +
|
||||
" Only works if the profile has Auto Save enabled.")]
|
||||
public bool AutoSave { get; set; }
|
||||
|
||||
[Option("install", HelpText = "Use this option to download and install optional components (e.g. \"ocr-eng\", \"generic-import\").")]
|
||||
public string Install { get; set; }
|
||||
public string? Install { get; set; }
|
||||
|
||||
[Option('p', "profile", HelpText = "The name of the profile to use for scanning." +
|
||||
" If not specified, the most-recently-used profile from the GUI is selected.")]
|
||||
public string ProfileName { get; set; }
|
||||
public string? ProfileName { get; set; }
|
||||
|
||||
[Option('i', "import", HelpText = "The name and path of one or more pdf/image files to import." +
|
||||
" Imported files are prepended to the output in the order they are specified." +
|
||||
" Multiple files are separated by a semicolon (\";\")." +
|
||||
" Slice notation can be used to only import some pages (e.g. \"[0]\" for the first page or \"[:2]\" for the first two pages).")]
|
||||
public string ImportPath { get; set; }
|
||||
public string? ImportPath { get; set; }
|
||||
|
||||
[Option("importpassword", HelpText = "The password to use to import one or more encrypted PDF files.")]
|
||||
public string ImportPassword { get; set; }
|
||||
public string? ImportPassword { get; set; }
|
||||
|
||||
[Option("progress", HelpText = "Display a graphical window for scanning progress.")]
|
||||
public bool Progress { get; set; }
|
||||
@ -91,16 +91,16 @@ public class AutomatedScanningOptions
|
||||
#region PDF Options
|
||||
|
||||
[Option("pdftitle", HelpText = "The title for generated PDF metadata.")]
|
||||
public string PdfTitle { get; set; }
|
||||
public string? PdfTitle { get; set; }
|
||||
|
||||
[Option("pdfauthor", HelpText = "The author for generated PDF metadata.")]
|
||||
public string PdfAuthor { get; set; }
|
||||
public string? PdfAuthor { get; set; }
|
||||
|
||||
[Option("pdfsubject", HelpText = "The subject for generated PDF metadata.")]
|
||||
public string PdfSubject { get; set; }
|
||||
public string? PdfSubject { get; set; }
|
||||
|
||||
[Option("pdfkeywords", HelpText = "The keywords for generated PDF metadata.")]
|
||||
public string PdfKeywords { get; set; }
|
||||
public string? PdfKeywords { get; set; }
|
||||
|
||||
[Option("usesavedmetadata", HelpText = "Use the metadata (title, author, subject, keywords) configured in the GUI, if any, for the generated PDF.")]
|
||||
public bool UseSavedMetadata { get; set; }
|
||||
@ -134,29 +134,29 @@ public class AutomatedScanningOptions
|
||||
[Option('e', "email", HelpText = "The name of the file to attach to an email." +
|
||||
" The extension determines the output type (e.g. .pdf for a PDF file, .jpg for a JPEG).")]
|
||||
//" You can use \"<date>\" and/or \"<time>\" to insert the date/time of the scan.")]
|
||||
public string EmailFileName { get; set; }
|
||||
public string? EmailFileName { get; set; }
|
||||
|
||||
[Option("subject", HelpText = "The email message's subject." +
|
||||
//" You can use \"<date>\" and/or \"<time>\" to insert the date/time of the scan." +
|
||||
" Requires -e/--email.")]
|
||||
public string EmailSubject { get; set; }
|
||||
public string? EmailSubject { get; set; }
|
||||
|
||||
[Option("body", HelpText = "The email message's body text." +
|
||||
//" You can use \"<date>\" and/or \"<time>\" to insert the date/time of the scan." +
|
||||
" Requires -e/--email.")]
|
||||
public string EmailBody { get; set; }
|
||||
public string? EmailBody { get; set; }
|
||||
|
||||
[Option("to", HelpText = "A comma-separated list of email addresses of the recipients." +
|
||||
" Requires -e/--email.")]
|
||||
public string EmailTo { get; set; }
|
||||
public string? EmailTo { get; set; }
|
||||
|
||||
[Option("cc", HelpText = "A comma-separated list of email addresses of the recipients." +
|
||||
" Requires -e/--email.")]
|
||||
public string EmailCc { get; set; }
|
||||
public string? EmailCc { get; set; }
|
||||
|
||||
[Option("bcc", HelpText = "A comma-separated list of email addresses of the recipients." +
|
||||
" Requires -e/--email.")]
|
||||
public string EmailBcc { get; set; }
|
||||
public string? EmailBcc { get; set; }
|
||||
|
||||
[Option("autosend", HelpText = "Actually send the email immediately after scanning completes without prompting the user for changes." +
|
||||
" However, this may prompt the user to login. To avoid that, use --silentsend." +
|
||||
@ -178,7 +178,7 @@ public class AutomatedScanningOptions
|
||||
public int JpegQuality { get; set; }
|
||||
|
||||
[Option("tiffcomp", HelpText = "The compression to use for TIFF files. Possible values: auto, lzw, ccitt4, none")]
|
||||
public string TiffComp { get; set; }
|
||||
public string? TiffComp { get; set; }
|
||||
|
||||
#endregion
|
||||
}
|
@ -9,8 +9,11 @@ public class ConsoleErrorOutput : ErrorOutput
|
||||
_output = output;
|
||||
}
|
||||
|
||||
public bool HasError { get; private set; }
|
||||
|
||||
public override void DisplayError(string errorMessage)
|
||||
{
|
||||
HasError = true;
|
||||
_output.Writer.WriteLine(errorMessage);
|
||||
}
|
||||
|
||||
|
@ -5,18 +5,18 @@ namespace NAPS2.Automation;
|
||||
|
||||
public class ConsoleOverwritePrompt : IOverwritePrompt
|
||||
{
|
||||
public static bool ForceOverwrite { get; set; }
|
||||
|
||||
private readonly AutomatedScanningOptions _options;
|
||||
private readonly ErrorOutput _errorOutput;
|
||||
|
||||
public ConsoleOverwritePrompt(ErrorOutput errorOutput)
|
||||
public ConsoleOverwritePrompt(AutomatedScanningOptions options, ErrorOutput errorOutput)
|
||||
{
|
||||
_options = options;
|
||||
_errorOutput = errorOutput;
|
||||
}
|
||||
|
||||
public OverwriteResponse ConfirmOverwrite(string path)
|
||||
{
|
||||
if (ForceOverwrite)
|
||||
if (_options.ForceOverwrite)
|
||||
{
|
||||
return OverwriteResponse.Yes;
|
||||
}
|
||||
|
@ -5,24 +5,24 @@ namespace NAPS2.Automation;
|
||||
|
||||
public class ConsolePdfPasswordProvider : IPdfPasswordProvider
|
||||
{
|
||||
private readonly AutomatedScanningOptions _options;
|
||||
private readonly ErrorOutput _errorOutput;
|
||||
|
||||
public ConsolePdfPasswordProvider(ErrorOutput errorOutput)
|
||||
public ConsolePdfPasswordProvider(AutomatedScanningOptions options, ErrorOutput errorOutput)
|
||||
{
|
||||
_options = options;
|
||||
_errorOutput = errorOutput;
|
||||
}
|
||||
|
||||
public bool ProvidePassword(string fileName, int attemptCount, out string password)
|
||||
{
|
||||
password = PasswordToProvide ?? "";
|
||||
password = _options.ImportPassword ?? "";
|
||||
if (attemptCount > 0)
|
||||
{
|
||||
_errorOutput.DisplayError(PasswordToProvide == null
|
||||
_errorOutput.DisplayError(_options.ImportPassword == null
|
||||
? ConsoleResources.ImportErrorNoPassword : ConsoleResources.ImportErrorWrongPassword);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static string PasswordToProvide { get; set; }
|
||||
}
|
@ -3,7 +3,6 @@ using NAPS2.Automation;
|
||||
using NAPS2.Modules;
|
||||
using NAPS2.Remoting.Worker;
|
||||
using Ninject;
|
||||
using Ninject.Parameters;
|
||||
|
||||
namespace NAPS2.EntryPoints;
|
||||
|
||||
@ -12,25 +11,28 @@ namespace NAPS2.EntryPoints;
|
||||
/// </summary>
|
||||
public static class ConsoleEntryPoint
|
||||
{
|
||||
public static void Run(string[] args)
|
||||
public static int Run(string[] args)
|
||||
{
|
||||
// Initialize Ninject (the DI framework)
|
||||
var kernel = new StandardKernel(new CommonModule(), new ConsoleModule(), new RecoveryModule(), new ContextModule());
|
||||
|
||||
Paths.ClearTemp();
|
||||
|
||||
// Parse the command-line arguments (and display help text if appropriate)
|
||||
var options = Parser.Default.ParseArguments<AutomatedScanningOptions>(args).Value;
|
||||
if (options == null)
|
||||
{
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Initialize Ninject (the DI framework)
|
||||
var kernel = new StandardKernel(new CommonModule(), new ConsoleModule(options), new RecoveryModule(),
|
||||
new ContextModule());
|
||||
|
||||
Paths.ClearTemp();
|
||||
|
||||
// Start a pending worker process
|
||||
kernel.Get<IWorkerFactory>().Init();
|
||||
|
||||
// Run the scan automation logic
|
||||
var scanning = kernel.Get<AutomatedScanning>(new ConstructorArgument("options", options));
|
||||
var scanning = kernel.Get<AutomatedScanning>();
|
||||
scanning.Execute().Wait();
|
||||
|
||||
return ((ConsoleErrorOutput) kernel.Get<ErrorOutput>()).HasError ? 1 : 0;
|
||||
}
|
||||
}
|
@ -9,10 +9,19 @@ namespace NAPS2.Modules;
|
||||
|
||||
public class ConsoleModule : NinjectModule
|
||||
{
|
||||
private readonly AutomatedScanningOptions _options;
|
||||
|
||||
public ConsoleModule(AutomatedScanningOptions options)
|
||||
{
|
||||
_options = options;
|
||||
}
|
||||
|
||||
public override void Load()
|
||||
{
|
||||
Bind<AutomatedScanningOptions>().ToConstant(_options);
|
||||
|
||||
Bind<IPdfPasswordProvider>().To<ConsolePdfPasswordProvider>();
|
||||
Bind<ErrorOutput>().To<ConsoleErrorOutput>();
|
||||
Bind<ErrorOutput>().To<ConsoleErrorOutput>().InSingletonScope();
|
||||
Bind<IOverwritePrompt>().To<ConsoleOverwritePrompt>();
|
||||
Bind<OperationProgress>().To<ConsoleOperationProgress>();
|
||||
Bind<IComponentInstallPrompt>().To<ConsoleComponentInstallPrompt>();
|
||||
|
@ -29,9 +29,9 @@ public class CommandLineIntegrationTests : ContextualTexts
|
||||
private async Task RunCommand(AutomatedScanningOptions options, params Bitmap[] imagesToScan)
|
||||
{
|
||||
var scanDriverFactory = new ScanDriverFactoryBuilder().WithScannedImages(imagesToScan).Build();
|
||||
var kernel = new StandardKernel(new CommonModule(), new ConsoleModule(),
|
||||
var kernel = new StandardKernel(new CommonModule(), new ConsoleModule(options),
|
||||
new TestModule(ScanningContext, ImageContext, scanDriverFactory, _testOutputHelper, FolderPath));
|
||||
var automatedScanning = kernel.Get<AutomatedScanning>(new ConstructorArgument("options", options));
|
||||
var automatedScanning = kernel.Get<AutomatedScanning>();
|
||||
await automatedScanning.Execute();
|
||||
}
|
||||
|
||||
@ -162,7 +162,8 @@ public class CommandLineIntegrationTests : ContextualTexts
|
||||
_folderPath)).InSingletonScope();
|
||||
|
||||
string recoveryFolderPath = Path.Combine(_folderPath, "recovery");
|
||||
var recoveryStorageManager = RecoveryStorageManager.CreateFolder(recoveryFolderPath);
|
||||
var recoveryStorageManager =
|
||||
RecoveryStorageManager.CreateFolder(recoveryFolderPath, Kernel.Get<UiImageList>());
|
||||
var fileStorageManager = new FileStorageManager(recoveryFolderPath);
|
||||
Kernel.Bind<RecoveryStorageManager>().ToConstant(recoveryStorageManager);
|
||||
Kernel.Bind<FileStorageManager>().ToConstant(fileStorageManager);
|
||||
|
Loading…
Reference in New Issue
Block a user