2009-08-20 20:54:43 +00:00
#region Disclaimer / License
2011-05-09 19:52:28 +00:00
// Copyright (C) 2011, Kenneth Skovhede
2009-08-20 20:54:43 +00:00
// http://www.hexad.dk, opensource@hexad.dk
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
#endregion
using System ;
2009-07-19 12:08:59 +00:00
using System.Collections.Generic ;
using System.Text ;
2010-06-19 21:08:21 +00:00
using Duplicati.Library.Interface ;
2009-07-19 12:08:59 +00:00
namespace Duplicati.CommandLine.BackendTester
{
class Program
{
class TempFile
{
2011-10-25 19:01:55 +00:00
public readonly string remotefilename ;
public readonly string localfilename ;
public readonly byte [] hash ;
public readonly long length ;
2009-07-19 12:08:59 +00:00
public bool found = false ;
2011-10-25 19:01:55 +00:00
public TempFile ( string remotefilename , string localfilename , byte [] hash , long length )
2009-07-19 12:08:59 +00:00
{
2011-10-25 19:01:55 +00:00
this . remotefilename = remotefilename ;
this . localfilename = localfilename ;
2009-07-19 12:08:59 +00:00
this . hash = hash ;
this . length = length ;
}
}
private const string ValidFilenameChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789" ;
2010-03-30 20:11:38 +00:00
private const string ExtendedChars = "-_',=)(&%$#@! +" ;
2009-07-19 12:08:59 +00:00
static void Main ( string [] _args )
{
2011-10-25 19:01:55 +00:00
try
{
List < string > args = new List < string >( _args );
2011-11-28 11:05:42 +00:00
Dictionary < string , string > options = Library . Utility . CommandLineParser . ExtractOptions ( args );
2009-07-19 12:08:59 +00:00
2011-10-25 19:01:55 +00:00
if (! options . ContainsKey ( "ftp_password" ) && ! string . IsNullOrEmpty ( System . Environment . GetEnvironmentVariable ( "FTP_PASSWORD" )))
options [ "ftp_password" ] = System . Environment . GetEnvironmentVariable ( "FTP_PASSWORD" );
2009-07-19 12:08:59 +00:00
2011-10-25 19:01:55 +00:00
if (! options . ContainsKey ( "ftp_username" ) && ! string . IsNullOrEmpty ( System . Environment . GetEnvironmentVariable ( "FTP_USERNAME" )))
options [ "ftp_username" ] = System . Environment . GetEnvironmentVariable ( "FTP_USERNAME" );
2009-07-19 12:08:59 +00:00
2011-10-25 19:01:55 +00:00
if ( options . ContainsKey ( "tempdir" ) && ! string . IsNullOrEmpty ( options [ "tempdir" ]))
Library . Utility . TempFolder . SystemTempPath = options [ "tempdir" ];
2009-08-10 20:54:13 +00:00
2011-10-25 19:01:55 +00:00
if ( args . Count != 1 || args [ 0 ]. ToLower () == "help" || args [ 0 ] == "?" )
{
Console . WriteLine ( "Usage: <protocol>://<username>:<password>@<path>" );
Console . WriteLine ( "Example: ftp://user:pass@server/folder" );
Console . WriteLine ();
Console . WriteLine ( "Supported backends: " + string . Join ( "," , Duplicati . Library . DynamicLoader . BackendLoader . Keys ));
2009-07-19 12:08:59 +00:00
2011-10-25 19:01:55 +00:00
Console . WriteLine ();
List < string > lines = new List < string >();
foreach ( Library . Interface . ICommandLineArgument arg in SupportedCommands )
2012-05-08 18:32:31 +00:00
Library . Interface . CommandLineArgument . PrintArgument ( lines , arg );
2009-07-19 12:08:59 +00:00
2011-10-25 19:01:55 +00:00
foreach ( string s in lines )
Console . WriteLine ( s );
2009-07-19 12:08:59 +00:00
2011-10-25 19:01:55 +00:00
return ;
}
2009-07-19 12:08:59 +00:00
2011-10-25 19:01:55 +00:00
int reruns = 5 ;
if ( options . ContainsKey ( "reruns" ))
reruns = int . Parse ( options [ "reruns" ]);
2009-07-19 12:08:59 +00:00
2011-10-25 19:01:55 +00:00
for ( int i = 0 ; i < reruns ; i ++)
{
Console . WriteLine ( "Starting run no {0}" , i );
if (! Run ( args , options , i == 0 ))
return ;
}
Console . WriteLine ( "Unittest complete!" );
}
catch ( Exception ex )
2009-07-19 12:08:59 +00:00
{
2011-10-25 19:01:55 +00:00
Console . WriteLine ( "Unittest failed: " + ex . Message );
2009-07-19 12:08:59 +00:00
}
}
2009-08-01 12:26:28 +00:00
static bool Run ( List < string > args , Dictionary < string , string > options , bool first )
2009-07-19 12:08:59 +00:00
{
string allowedChars = ValidFilenameChars ;
if ( options . ContainsKey ( "extended-chars" ))
allowedChars += options [ "extended-chars" ];
else
allowedChars += ExtendedChars ;
2011-09-27 05:35:49 +00:00
bool autoCreateFolders = Library . Utility . Utility . ParseBoolOption ( options , "auto-create-folder" );
2010-12-07 21:28:05 +00:00
2010-06-19 21:08:21 +00:00
Library . Interface . IBackend backend = Library . DynamicLoader . BackendLoader . GetBackend ( args [ 0 ], options );
2009-07-19 12:08:59 +00:00
if ( backend == null )
{
Console . WriteLine ( "Unsupported backend" );
Console . WriteLine ();
2010-06-19 21:08:21 +00:00
Console . WriteLine ( "Supported backends: " + string . Join ( "," , Duplicati . Library . DynamicLoader . BackendLoader . Keys ));
2009-07-19 12:08:59 +00:00
return false ;
}
2012-05-05 18:14:02 +00:00
string disabledModulesValue ;
string enabledModulesValue ;
options . TryGetValue ( "enable-module" , out enabledModulesValue );
options . TryGetValue ( "disable-module" , out disabledModulesValue );
string [] enabledModules = enabledModulesValue == null ? new string [ 0 ] : enabledModulesValue . Trim (). ToLower (). Split ( ',' );
string [] disabledModules = disabledModulesValue == null ? new string [ 0 ] : disabledModulesValue . Trim (). ToLower (). Split ( ',' );
List < Library . Interface . IGenericModule > loadedModules = new List < IGenericModule >();
foreach ( Library . Interface . IGenericModule m in Library . DynamicLoader . GenericLoader . Modules )
if ( Array . IndexOf < string >( disabledModules , m . Key . ToLower ()) < 0 && ( m . LoadAsDefault || Array . IndexOf < string >( enabledModules , m . Key . ToLower ()) >= 0 ))
{
m . Configure ( options );
loadedModules . Add ( m );
}
2010-12-07 21:28:05 +00:00
try
{
2012-05-05 18:14:02 +00:00
List < Library . Interface . IFileEntry > curlist = null ;
try
2010-12-07 21:28:05 +00:00
{
2012-05-05 18:14:02 +00:00
curlist = backend . List ();
2010-12-07 21:28:05 +00:00
}
2012-05-05 18:14:02 +00:00
catch ( FolderMissingException fex )
2009-07-19 12:08:59 +00:00
{
2012-05-05 18:14:02 +00:00
if ( autoCreateFolders )
{
try
2009-08-01 12:26:28 +00:00
{
2012-05-05 18:14:02 +00:00
if ( backend is IBackend_v2 )
(( IBackend_v2 ) backend ). CreateFolder ();
curlist = backend . List ();
2009-08-01 12:26:28 +00:00
}
2012-05-05 18:14:02 +00:00
catch ( Exception ex )
{
Console . WriteLine ( "Autocreate folder failed with message: " + ex . Message );
}
}
2009-08-01 12:26:28 +00:00
2012-05-05 18:14:02 +00:00
if ( curlist == null )
throw fex ;
2009-07-19 12:08:59 +00:00
}
2012-05-05 18:14:02 +00:00
foreach ( Library . Interface . IFileEntry fe in curlist )
if (! fe . IsFolder )
{
if ( Library . Utility . Utility . ParseBoolOption ( options , "auto-clean" ) && first )
if ( Library . Utility . Utility . ParseBoolOption ( options , "force" ))
{
Console . WriteLine ( "Auto clean, removing file: {0}" , fe . Name );
backend . Delete ( fe . Name );
continue ;
}
else
Console . WriteLine ( "Specify the --force flag to actually delete files" );
2009-07-19 12:08:59 +00:00
2012-05-05 18:14:02 +00:00
Console . WriteLine ( "*** Remote folder is not empty, aborting" );
return false ;
}
2009-07-19 12:08:59 +00:00
2012-05-05 18:14:02 +00:00
int number_of_files = 10 ;
int min_file_size = 1024 ;
int max_file_size = 1024 * 1024 * 50 ;
int min_filename_size = 5 ;
int max_filename_size = 80 ;
bool disableStreaming = Library . Utility . Utility . ParseBoolOption ( options , "disable-streaming-transfers" );
bool skipOverwriteTest = Library . Utility . Utility . ParseBoolOption ( options , "skip-overwrite-test" );
2009-07-19 12:08:59 +00:00
2012-05-05 18:14:02 +00:00
if ( options . ContainsKey ( "number-of-files" ))
number_of_files = int . Parse ( options [ "number-of-files" ]);
if ( options . ContainsKey ( "min-file-size" ))
min_file_size = ( int ) Duplicati . Library . Utility . Sizeparser . ParseSize ( options [ "min-file-size" ], "mb" );
if ( options . ContainsKey ( "max-file-size" ))
max_file_size = ( int ) Duplicati . Library . Utility . Sizeparser . ParseSize ( options [ "max-file-size" ]);
2009-07-19 12:08:59 +00:00
2012-05-05 18:14:02 +00:00
if ( options . ContainsKey ( "min-filename-length" ))
min_filename_size = int . Parse ( options [ "min-filename-length" ]);
if ( options . ContainsKey ( "max-filename-length" ))
max_filename_size = int . Parse ( options [ "max-filename-length" ]);
2009-07-19 12:08:59 +00:00
2012-05-05 18:14:02 +00:00
Random rnd = new Random ();
System . Security . Cryptography . SHA256 sha = System . Security . Cryptography . SHA256 . Create ();
2009-09-14 20:15:04 +00:00
2012-05-05 18:14:02 +00:00
//Create random files
using ( Library . Utility . TempFolder tf = new Duplicati . Library . Utility . TempFolder ())
2011-10-25 19:01:55 +00:00
{
2012-05-05 18:14:02 +00:00
List < TempFile > files = new List < TempFile >();
for ( int i = 0 ; i < number_of_files ; i ++)
2011-10-25 19:01:55 +00:00
{
2009-07-19 12:08:59 +00:00
2012-05-05 18:14:02 +00:00
StringBuilder filename = new StringBuilder ();
int filenamelen = rnd . Next ( min_filename_size , max_filename_size );
for ( int j = 0 ; j < filenamelen ; j ++)
filename . Append ( allowedChars [ rnd . Next ( 0 , allowedChars . Length )]);
2009-07-19 12:08:59 +00:00
2012-05-05 18:14:02 +00:00
string localfilename = CreateRandomFile ( tf , i , min_file_size , max_file_size , rnd );
2009-07-19 12:08:59 +00:00
2012-05-05 18:14:02 +00:00
//Calculate local hash and length
using ( System . IO . FileStream fs = new System . IO . FileStream ( localfilename , System . IO . FileMode . Open , System . IO . FileAccess . Read ))
files . Add ( new TempFile ( filename . ToString (), localfilename , sha . ComputeHash ( fs ), fs . Length ));
}
2009-07-19 12:08:59 +00:00
2012-05-05 18:14:02 +00:00
byte [] dummyFileHash = null ;
if (! skipOverwriteTest )
2009-07-19 12:08:59 +00:00
{
2012-05-05 18:14:02 +00:00
Console . WriteLine ( "Uploading wrong files ..." );
using ( Library . Utility . TempFile dummy = new Library . Utility . TempFile ( CreateRandomFile ( tf , files . Count , 1024 , 2048 , rnd )))
{
using ( System . IO . FileStream fs = new System . IO . FileStream ( dummy , System . IO . FileMode . Open , System . IO . FileAccess . Read ))
dummyFileHash = sha . ComputeHash ( fs );
//Upload a dummy file for entry 0 and the last one, they will be replaced by the real files afterwards
//We upload entry 0 twice just to try to freak any internal cache list
Uploadfile ( dummy , 0 , files [ 0 ]. remotefilename , backend , disableStreaming );
Uploadfile ( dummy , 0 , files [ 0 ]. remotefilename , backend , disableStreaming );
Uploadfile ( dummy , files . Count - 1 , files [ files . Count - 1 ]. remotefilename , backend , disableStreaming );
}
2009-07-19 12:08:59 +00:00
}
2012-05-05 18:14:02 +00:00
Console . WriteLine ( "Uploading files ..." );
2009-07-19 12:08:59 +00:00
2012-05-05 18:14:02 +00:00
for ( int i = 0 ; i < files . Count ; i ++)
Uploadfile ( files [ i ]. localfilename , i , files [ i ]. remotefilename , backend , disableStreaming );
2009-07-19 12:08:59 +00:00
2012-05-05 18:14:02 +00:00
Console . WriteLine ( "Verifying file list ..." );
2011-10-25 19:01:55 +00:00
2012-05-05 18:14:02 +00:00
curlist = backend . List ();
foreach ( Library . Interface . IFileEntry fe in curlist )
if (! fe . IsFolder )
2009-08-01 12:26:28 +00:00
{
2012-05-05 18:14:02 +00:00
bool found = false ;
foreach ( TempFile tx in files )
if ( tx . remotefilename == fe . Name )
{
if ( tx . found )
Console . WriteLine ( "*** File with name {0} was found more than once" , tx . remotefilename );
found = true ;
tx . found = true ;
if ( fe . Size > 0 && tx . length != fe . Size )
Console . WriteLine ( "*** File with name {0} has size {1} but the size was reported as {2}" , tx . remotefilename , tx . length , fe . Size );
break ;
}
if (! found )
Console . WriteLine ( "*** File with name {0} was found on server but not uploaded!" , fe . Name );
2009-08-01 12:26:28 +00:00
}
2009-07-19 12:08:59 +00:00
2012-05-05 18:14:02 +00:00
foreach ( TempFile tx in files )
if (! tx . found )
Console . WriteLine ( "*** File with name {0} was uploaded but not found afterwards" , tx . remotefilename );
2009-07-19 12:08:59 +00:00
2012-05-05 18:14:02 +00:00
Console . WriteLine ( "Downloading files" );
2009-07-19 12:08:59 +00:00
2012-05-05 18:14:02 +00:00
for ( int i = 0 ; i < files . Count ; i ++)
{
using ( Duplicati . Library . Utility . TempFile cf = new Duplicati . Library . Utility . TempFile ())
{
Exception e = null ;
Console . Write ( "Downloading file {0} ... " , i );
try
2011-10-25 19:01:55 +00:00
{
2012-05-05 18:14:02 +00:00
if ( backend is Library . Interface . IStreamingBackend && ! disableStreaming )
{
using ( System . IO . FileStream fs = new System . IO . FileStream ( cf , System . IO . FileMode . Create , System . IO . FileAccess . Write , System . IO . FileShare . None ))
using ( NonSeekableStream nss = new NonSeekableStream ( fs ))
( backend as Library . Interface . IStreamingBackend ). Get ( files [ i ]. remotefilename , nss );
}
2011-10-25 19:01:55 +00:00
else
2012-05-05 18:14:02 +00:00
backend . Get ( files [ i ]. remotefilename , cf );
e = null ;
}
catch ( Exception ex )
{
e = ex ;
2011-10-25 19:01:55 +00:00
}
2012-05-05 18:14:02 +00:00
if ( e != null )
Console . WriteLine ( "failed\n*** Error: {0}" , e . ToString ());
2009-07-19 12:08:59 +00:00
else
Console . WriteLine ( "done" );
2012-05-05 18:14:02 +00:00
Console . Write ( "Checking hash ... " );
2009-07-19 12:08:59 +00:00
2012-05-05 18:14:02 +00:00
using ( System . IO . FileStream fs = new System . IO . FileStream ( cf , System . IO . FileMode . Open , System . IO . FileAccess . Read ))
if ( Convert . ToBase64String ( sha . ComputeHash ( fs )) != Convert . ToBase64String ( files [ i ]. hash ))
{
if ( dummyFileHash != null && Convert . ToBase64String ( sha . ComputeHash ( fs )) == Convert . ToBase64String ( dummyFileHash ))
Console . WriteLine ( "failed\n*** Downloaded file was the dummy file" );
else
Console . WriteLine ( "failed\n*** Downloaded file was corrupt" );
}
else
Console . WriteLine ( "done" );
}
2009-07-19 12:08:59 +00:00
}
2012-05-05 18:14:02 +00:00
Console . WriteLine ( "Deleting files..." );
2009-07-19 12:08:59 +00:00
2012-05-05 18:14:02 +00:00
foreach ( TempFile tx in files )
try { backend . Delete ( tx . remotefilename ); }
catch ( Exception ex )
{
Console . WriteLine ( "*** Failed to delete file {0}, message: {1}" , tx . remotefilename , ex . ToString ());
}
curlist = backend . List ();
foreach ( Library . Interface . IFileEntry fe in curlist )
if (! fe . IsFolder )
{
Console . WriteLine ( "*** Remote folder contains {0} after cleanup" , fe . Name );
}
}
}
finally
{
foreach ( Library . Interface . IGenericModule m in loadedModules )
if ( m is IDisposable )
(( IDisposable ) m ). Dispose ();
2009-07-19 12:08:59 +00:00
}
return true ;
}
2011-10-25 19:01:55 +00:00
private static void Uploadfile ( string localfilename , int i , string remotefilename , IBackend backend , bool disableStreaming )
{
Console . Write ( "Uploading file {0}, {1} ... " , i , Duplicati . Library . Utility . Utility . FormatSizeString ( new System . IO . FileInfo ( localfilename ). Length ));
Exception e = null ;
try
{
if ( backend is Library . Interface . IStreamingBackend && ! disableStreaming )
{
using ( System . IO . FileStream fs = new System . IO . FileStream ( localfilename , System . IO . FileMode . Open , System . IO . FileAccess . Read , System . IO . FileShare . Read ))
using ( NonSeekableStream nss = new NonSeekableStream ( fs ))
( backend as Library . Interface . IStreamingBackend ). Put ( remotefilename , nss );
}
else
backend . Put ( remotefilename , localfilename );
e = null ;
}
catch ( Exception ex )
{
e = ex ;
}
if ( e != null )
{
Console . WriteLine ( "Failed to upload file {0}, error message: {1}, remote name: {2}" , i , e . ToString (), remotefilename );
while ( e . InnerException != null )
{
e = e . InnerException ;
Console . WriteLine ( string . Format ( " Inner exception: {0}" , e . ToString ()));
}
}
else
{
Console . WriteLine ( " done!" );
}
}
private static string CreateRandomFile ( Library . Utility . TempFolder tf , int i , int min_file_size , int max_file_size , Random rnd )
{
Console . Write ( "Generating file {0}" , i );
string filename = System . IO . Path . Combine ( tf , i . ToString ());
using ( System . IO . FileStream fs = new System . IO . FileStream ( filename , System . IO . FileMode . CreateNew , System . IO . FileAccess . Write ))
{
//Random size
byte [] buf = new byte [ 1024 ];
int size = rnd . Next ( min_file_size , max_file_size );
Console . WriteLine ( " ({0})" , Duplicati . Library . Utility . Utility . FormatSizeString ( size ));
while ( size > 0 )
{
rnd . NextBytes ( buf );
fs . Write ( buf , 0 , Math . Min ( buf . Length , size ));
size -= buf . Length ;
}
}
return filename ;
}
2009-07-19 12:08:59 +00:00
public static IList < ICommandLineArgument > SupportedCommands
{
get
{
return new List < ICommandLineArgument >( new ICommandLineArgument [] {
new CommandLineArgument ( "reruns" , CommandLineArgument . ArgumentType . Integer , "The number of test runs to perform" , "A number that describes how many times the test is performed" , "5" ),
2009-08-10 20:54:13 +00:00
new CommandLineArgument ( "tempdir" , CommandLineArgument . ArgumentType . Path , "The path used to store temporary files" , "The backend tester will use the system default temp path. You can set this option to choose another path." ),
2009-07-19 12:08:59 +00:00
new CommandLineArgument ( "extended-chars" , CommandLineArgument . ArgumentType . String , "A list of allowed extended filename chars" , "A list of characters besides {a-z, A-Z, 0-9} to use when generating filenames" , ExtendedChars ),
new CommandLineArgument ( "number-of-files" , CommandLineArgument . ArgumentType . Integer , "The number of files to test with" , "An integer describing how many files to upload during a test run" , "10" ),
new CommandLineArgument ( "min-file-size" , CommandLineArgument . ArgumentType . Size , "The minimum allowed file size" , "File sizes are chosen at random, this valus is the lower bound" , "1kb" ),
new CommandLineArgument ( "max-file-size" , CommandLineArgument . ArgumentType . Size , "The maximum allowed file size" , "File sizes are chosen at random, this valus is the upper bound" , "50mb" ),
new CommandLineArgument ( "min-filename-length" , CommandLineArgument . ArgumentType . Integer , "The minimum allowed filename length" , "File name lengths are chosen at random, this valus is the lower bound" , "5" ),
2010-12-07 21:28:05 +00:00
new CommandLineArgument ( "max-filename-length" , CommandLineArgument . ArgumentType . Integer , "The minimum allowed filename length" , "File name lengths are chosen at random, this valus is the upper bound" , "80" ),
2011-10-25 19:01:55 +00:00
new CommandLineArgument ( "auto-create-folder" , CommandLineArgument . ArgumentType . Boolean , "Allows automatic folder creation" , "A value that indicates if missing folders are created automatically" , "false" ),
new CommandLineArgument ( "skip-overwrite-test" , CommandLineArgument . ArgumentType . Boolean , "Bypasses the overwrite test" , "A value that indicates if dummy files should be uploaded prior to uploading the real files" , "false" ),
new CommandLineArgument ( "auto-clean" , CommandLineArgument . ArgumentType . Boolean , "Removes any files found in target folder" , "A value that indicates if all files in the target folder should be deleted before starting the first test" , "false" ),
new CommandLineArgument ( "force" , CommandLineArgument . ArgumentType . Boolean , "Activates file deletion" , "A value that indicates if existing files should really be deleted when using auto-clean" , "false" )
2009-07-19 12:08:59 +00:00
});
}
}
}
}