2024-03-15 14:18:56 +01:00
// Copyright (C) 2024, The Duplicati Team
2024-02-28 15:45:30 +01:00
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
2015-09-17 20:04:49 +02:00
using System ;
using NUnit.Framework ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
2024-04-18 15:34:34 +02:00
using System.Threading.Tasks ;
using System.Net.Http ;
using Duplicati.Library.Utility ;
2015-04-10 22:29:52 +02:00
namespace Duplicati.UnitTest
{
2015-09-17 20:04:49 +02:00
public class CommandLineOperationsTests : BasicSetupHelper
{
2020-10-19 00:44:10 +02:00
private static readonly string S3_URL = $"https://testfiles.duplicati.com/" ;
2019-08-25 13:34:30 -04:00
2018-03-12 14:07:11 +01:00
/// <summary>
/// The log tag
/// </summary>
private static readonly string LOGTAG = Library . Logging . Log . LogTagFromType < CommandLineOperationsTests >();
2019-08-25 10:59:14 -04:00
2015-09-17 20:04:49 +02:00
/// <summary>
/// The folder that contains all the source data which the test is based on
/// </summary>
protected readonly string SOURCEFOLDER = Path . Combine ( BASEFOLDER , "data" );
2019-08-18 14:54:53 -07:00
private readonly string zipFilename = "data.zip" ;
2019-08-18 17:43:05 -07:00
private string zipFilepath => Path . Combine ( BASEFOLDER , this . zipFilename );
2024-04-18 15:34:34 +02:00
2019-08-25 10:59:14 -04:00
private readonly string zipAlternativeFilename = "data-alternative.zip" ;
private string zipAlternativeFilepath => Path . Combine ( BASEFOLDER , this . zipAlternativeFilename );
2015-09-17 20:04:49 +02:00
protected virtual IEnumerable < string > SourceDataFolders
{
get
{
2019-08-25 10:59:14 -04:00
return
2020-07-27 18:35:39 -07:00
from x in systemIO . EnumerateDirectories ( SOURCEFOLDER )
2015-09-17 20:04:49 +02:00
orderby x
select x ;
}
}
2022-05-05 22:30:15 -07:00
[SetUp]
public void SetUp ()
2019-08-18 13:39:46 -07:00
{
2020-07-27 18:35:39 -07:00
if (! systemIO . FileExists ( zipAlternativeFilepath ))
2019-08-25 10:59:14 -04:00
{
2019-08-25 13:34:30 -04:00
var url = $"{S3_URL}{this.zipFilename}" ;
DownloadS3FileIfNewer ( zipFilepath , url );
2020-07-27 18:35:39 -07:00
ZipFileExtractToDirectory ( this . zipFilepath , BASEFOLDER );
2019-08-25 10:59:14 -04:00
}
else
2019-08-18 13:39:46 -07:00
{
2020-07-27 18:35:39 -07:00
ZipFileExtractToDirectory ( this . zipAlternativeFilepath , BASEFOLDER );
2019-08-18 13:39:46 -07:00
}
}
2024-04-18 15:34:34 +02:00
private void DownloadS3FileIfNewer ( string destinationFilePath , string url , int retries = 5 )
=> DownloadS3FileIfNewerAsync ( destinationFilePath , url , retries ). Wait ();
2019-08-25 13:34:30 -04:00
2024-04-18 15:34:34 +02:00
private async Task DownloadS3FileIfNewerAsync ( string destinationFilePath , string url , int retries = 5 )
{
do
2019-08-25 13:34:30 -04:00
{
2024-04-18 15:34:34 +02:00
try
{
using var httpClient = new HttpClient ();
using var request = new HttpRequestMessage ( HttpMethod . Get , url );
2019-08-25 13:34:30 -04:00
2024-04-18 15:34:34 +02:00
if ( systemIO . FileExists ( destinationFilePath ))
request . Headers . IfModifiedSince = systemIO . FileGetLastWriteTimeUtc ( destinationFilePath );
using var response = await httpClient . SendAsync ( request );
using var tmpFile = new TempFile ();
if ( response . StatusCode == System . Net . HttpStatusCode . NotModified )
{
Console . WriteLine ( "File has not been modified since last download." );
2024-04-18 18:21:10 +02:00
return ;
2023-05-07 00:09:23 +02:00
}
2024-04-18 15:34:34 +02:00
else
{
Console . WriteLine ( $"Downloading file from {url} to: {tmpFile}" );
var contentStream = await response . Content . ReadAsStreamAsync ();
var fileInfo = new FileInfo ( tmpFile );
using ( var fileStream = fileInfo . OpenWrite ())
await contentStream . CopyToAsync ( fileStream );
// After download, check if the file length matches the response length
long responseLength = response . Content . Headers . ContentLength ?? 0 ;
long fileLength = new FileInfo ( tmpFile ). Length ;
if ( responseLength != fileLength )
throw new Exception ( $"Downloaded file length {fileLength} does not match response length {responseLength}" );
Console . WriteLine ( $"Download completed, moving to {destinationFilePath}" );
File . Move ( tmpFile , destinationFilePath , true );
return ;
2023-05-07 00:09:23 +02:00
}
2019-08-25 13:34:30 -04:00
}
2024-04-18 15:34:34 +02:00
catch ( Exception ex )
2019-08-25 13:34:30 -04:00
{
2024-04-18 15:34:34 +02:00
retries --;
Console . WriteLine ( $"Download failed: {ex.Message}" );
if ( retries <= 0 )
throw ;
await Task . Delay ( TimeSpan . FromSeconds ( 1 )). ConfigureAwait ( false );
Console . WriteLine ( $"Retrying download, {retries} retries left." );
2019-08-25 13:34:30 -04:00
}
2024-04-18 15:34:34 +02:00
} while ( retries > 0 );
2019-08-25 13:34:30 -04:00
}
2022-05-05 22:30:15 -07:00
[OneTimeTearDown]
public void OneTimeTearDown ()
2019-08-18 14:54:53 -07:00
{
2020-07-27 18:35:39 -07:00
if ( systemIO . DirectoryExists ( this . SOURCEFOLDER ))
2019-08-18 14:54:53 -07:00
{
2020-07-27 18:35:39 -07:00
systemIO . DirectoryDelete ( this . SOURCEFOLDER , true );
2019-08-18 14:54:53 -07:00
}
}
2015-09-17 20:04:49 +02:00
[Test]
2016-09-15 11:39:27 +02:00
[Category("BulkData")]
2016-09-29 13:45:55 +02:00
[Category("BulkNormal")]
2016-09-15 11:39:27 +02:00
public void RunCommands ()
2015-09-17 20:04:49 +02:00
{
DoRunCommands ( TARGETFOLDER );
}
[Test]
2016-09-15 11:39:27 +02:00
[Category("BulkData")]
2016-09-29 13:45:55 +02:00
[Category("BulkNoSize")]
2016-09-15 11:39:27 +02:00
public void RunCommandsWithoutSize ()
2015-09-17 20:04:49 +02:00
{
DoRunCommands ( new SizeOmittingBackend (). ProtocolKey + "://" + TARGETFOLDER );
}
private void DoRunCommands ( string target )
{
var opts = from n in TestOptions select string . Format ( "--{0}=\"{1}\"" , n . Key , n . Value );
var backupargs = ( new string [] { "backup" , target , DATAFOLDER }. Union ( opts )). ToArray ();
2019-07-16 13:58:07 -04:00
if ( SourceDataFolders == null || SourceDataFolders . Count () < 3 )
{
ProgressWriteLine ( $"ERROR: A minimum of 3 data folders are required in {SOURCEFOLDER}." );
throw new Exception ( "Failed during initial minimum data folder check" );
}
foreach ( var n in SourceDataFolders )
2015-09-17 20:04:49 +02:00
{
var foldername = Path . GetFileName ( n );
var targetfolder = Path . Combine ( DATAFOLDER , foldername );
2016-09-29 13:45:55 +02:00
ProgressWriteLine ( "Adding folder {0} to source" , foldername );
2015-09-17 20:04:49 +02:00
2020-07-27 18:35:39 -07:00
systemIO . DirectoryMove ( n , targetfolder );
2019-08-25 10:59:14 -04:00
2020-07-27 18:35:39 -07:00
var size = systemIO . EnumerateFiles ( targetfolder , "*" , SearchOption . AllDirectories ). Select ( systemIO . FileLength ). Sum ();
2015-09-17 20:04:49 +02:00
2016-09-29 13:45:55 +02:00
ProgressWriteLine ( "Running backup with {0} data added ..." , Duplicati . Library . Utility . Utility . FormatSizeString ( size ));
2019-08-25 10:59:14 -04:00
using ( new Library . Logging . Timer ( LOGTAG , "BackupWithDataAdded" , string . Format ( "Backup with {0} data added" , Duplicati . Library . Utility . Utility . FormatSizeString ( size ))))
2024-03-15 14:18:56 +01:00
Duplicati . CommandLine . Program . Main ( backupargs );
2018-06-13 21:42:34 +02:00
ProgressWriteLine ( "Testing data ..." );
using ( new Library . Logging . Timer ( LOGTAG , "TestRemoteData" , "Test remote data" ))
2024-03-15 14:18:56 +01:00
if ( Duplicati . CommandLine . Program . Main (( new string [] { "test" , target , "all" }. Union ( opts )). ToArray ()) != 0 )
2018-06-13 21:42:34 +02:00
throw new Exception ( "Failed during remote verification" );
2015-09-17 20:04:49 +02:00
}
2016-09-29 13:45:55 +02:00
ProgressWriteLine ( "Running unchanged backup ..." );
2019-08-25 10:59:14 -04:00
using ( new Library . Logging . Timer ( LOGTAG , "UnchangedBackup" , "Unchanged backup" ))
2024-03-15 14:18:56 +01:00
Duplicati . CommandLine . Program . Main ( backupargs );
2015-09-17 20:04:49 +02:00
2020-07-27 18:35:39 -07:00
var datafolders = systemIO . EnumerateDirectories ( DATAFOLDER );
2019-08-25 10:59:14 -04:00
2015-09-17 20:04:49 +02:00
var f = datafolders . Skip ( datafolders . Count () / 2 ). First ();
2016-09-29 13:45:55 +02:00
ProgressWriteLine ( "Renaming folder {0}" , Path . GetFileName ( f ));
2020-07-27 18:35:39 -07:00
systemIO . DirectoryMove ( f , Path . Combine ( Path . GetDirectoryName ( f ), Path . GetFileName ( f ) + "-renamed" ));
2015-09-17 20:04:49 +02:00
2016-09-29 13:45:55 +02:00
ProgressWriteLine ( "Running backup with renamed folder..." );
2019-08-25 10:59:14 -04:00
using ( new Library . Logging . Timer ( LOGTAG , "BackupWithRenamedFolder" , "Backup with renamed folder" ))
2024-03-15 14:18:56 +01:00
Duplicati . CommandLine . Program . Main ( backupargs );
2015-09-17 20:04:49 +02:00
2020-07-27 18:35:39 -07:00
datafolders = systemIO . EnumerateDirectories ( DATAFOLDER );
2015-09-17 20:04:49 +02:00
2016-09-29 13:45:55 +02:00
ProgressWriteLine ( "Deleting data" );
2015-09-17 20:04:49 +02:00
var rm1 = datafolders . First ();
var rm2 = datafolders . Skip ( 1 ). First ();
var rm3 = datafolders . Skip ( 2 ). First ();
2020-07-27 18:35:39 -07:00
systemIO . DirectoryDelete ( rm1 , true );
systemIO . DirectoryDelete ( rm2 , true );
var rmfiles = systemIO . EnumerateFiles ( rm3 , "*" , SearchOption . AllDirectories );
2019-07-16 13:58:07 -04:00
foreach ( var n in rmfiles . Take ( rmfiles . Count () / 2 ))
2020-07-27 18:35:39 -07:00
systemIO . FileDelete ( n );
2015-09-17 20:04:49 +02:00
2016-09-29 13:45:55 +02:00
ProgressWriteLine ( "Running backup with deleted data..." );
2019-08-25 10:59:14 -04:00
using ( new Library . Logging . Timer ( LOGTAG , "BackupWithDeletedData" , "Backup with deleted data" ))
2024-03-15 14:18:56 +01:00
Duplicati . CommandLine . Program . Main ( backupargs );
2015-09-17 20:04:49 +02:00
2016-09-29 13:45:55 +02:00
ProgressWriteLine ( "Testing the compare method ..." );
2019-08-25 10:59:14 -04:00
using ( new Library . Logging . Timer ( LOGTAG , "CompareMethod" , "Compare method" ))
2024-03-15 14:18:56 +01:00
Duplicati . CommandLine . Program . Main (( new string [] { "compare" , target , "0" , "1" }. Union ( opts )). ToArray ());
2015-09-17 20:04:49 +02:00
2019-08-25 10:59:14 -04:00
for ( var i = 0 ; i < 5 ; i ++)
2015-09-17 20:04:49 +02:00
{
2016-09-29 13:45:55 +02:00
ProgressWriteLine ( "Running backup with changed logfile {0} of {1} ..." , i + 1 , 5 );
2020-07-27 18:35:39 -07:00
systemIO . FileCopy ( LOGFILE , Path . Combine ( SOURCEFOLDER , Path . GetFileName ( LOGFILE )), true );
2015-09-17 20:04:49 +02:00
2019-08-25 10:59:14 -04:00
using ( new Library . Logging . Timer ( LOGTAG , "BackupWithLogfileChange" , string . Format ( "Backup with logfilechange {0}" , i + 1 )))
2024-03-15 14:18:56 +01:00
Duplicati . CommandLine . Program . Main ( backupargs );
2015-09-17 20:04:49 +02:00
}
2018-06-12 08:52:57 +02:00
ProgressWriteLine ( "Compacting data ..." );
2019-08-25 10:59:14 -04:00
using ( new Library . Logging . Timer ( LOGTAG , "Compacting" , "Compacting" ))
2024-03-15 14:18:56 +01:00
Duplicati . CommandLine . Program . Main (( new string [] { "compact" , target , "--small-file-max-count=2" }. Union ( opts )). ToArray ());
2015-09-17 20:04:49 +02:00
2020-07-27 18:35:39 -07:00
datafolders = systemIO . EnumerateDirectories ( DATAFOLDER );
2015-09-17 20:04:49 +02:00
var rf = datafolders . Skip ( datafolders . Count () - 2 ). First ();
2016-09-29 13:45:55 +02:00
ProgressWriteLine ( "Partial restore of {0} ..." , Path . GetFileName ( rf ));
2019-08-25 10:59:14 -04:00
using ( new Library . Logging . Timer ( LOGTAG , "PartialRestore" , "Partial restore" ))
2024-03-15 14:18:56 +01:00
Duplicati . CommandLine . Program . Main (( new string [] { "restore" , target , rf + "*" , "--restore-path=\"" + RESTOREFOLDER + "\"" }. Union ( opts )). ToArray ());
2015-09-17 20:04:49 +02:00
2016-09-29 13:45:55 +02:00
ProgressWriteLine ( "Verifying partial restore ..." );
2020-09-12 10:52:01 -07:00
using ( new Library . Logging . Timer ( LOGTAG , "VerificationOfPartialRestore" , "Verification of partial restored files" ))
TestUtils . AssertDirectoryTreesAreEquivalent ( rf , RESTOREFOLDER , true , "VerificationOfPartialRestore" );
2015-09-17 20:04:49 +02:00
2020-07-27 18:35:39 -07:00
systemIO . DirectoryDelete ( RESTOREFOLDER , true );
2015-09-17 20:04:49 +02:00
2016-09-29 13:45:55 +02:00
ProgressWriteLine ( "Partial restore of {0} without local db..." , Path . GetFileName ( rf ));
2019-08-25 10:59:14 -04:00
using ( new Library . Logging . Timer ( LOGTAG , "PartialRestoreWithoutLocalDb" , "Partial restore without local db" ))
2024-03-15 14:18:56 +01:00
Duplicati . CommandLine . Program . Main (( new string [] { "restore" , target , rf + "*" , "--restore-path=\"" + RESTOREFOLDER + "\"" , "--no-local-db" }. Union ( opts )). ToArray ());
2015-09-17 20:04:49 +02:00
2016-09-29 13:45:55 +02:00
ProgressWriteLine ( "Verifying partial restore ..." );
2018-03-12 14:07:11 +01:00
using ( new Library . Logging . Timer ( LOGTAG , "VerificationOfPartialRestore" , "Verification of partial restored files" ))
2020-09-10 18:41:55 -07:00
TestUtils . AssertDirectoryTreesAreEquivalent ( rf , RESTOREFOLDER , true , "VerificationOfPartialRestore" );
2019-08-25 10:59:14 -04:00
2020-07-27 18:35:39 -07:00
systemIO . DirectoryDelete ( RESTOREFOLDER , true );
2015-09-17 20:04:49 +02:00
2016-09-29 13:45:55 +02:00
ProgressWriteLine ( "Full restore ..." );
2019-08-25 10:59:14 -04:00
using ( new Library . Logging . Timer ( LOGTAG , "FullRestore" , "Full restore" ))
2024-03-15 14:18:56 +01:00
Duplicati . CommandLine . Program . Main (( new string [] { "restore" , target , "*" , "--restore-path=\"" + RESTOREFOLDER + "\"" }. Union ( opts )). ToArray ());
2015-09-17 20:04:49 +02:00
2016-09-29 13:45:55 +02:00
ProgressWriteLine ( "Verifying full restore ..." );
2018-03-12 14:07:11 +01:00
using ( new Library . Logging . Timer ( LOGTAG , "VerificationOfFullRestore" , "Verification of restored files" ))
2020-07-27 18:35:39 -07:00
foreach ( var s in systemIO . EnumerateDirectories ( DATAFOLDER ))
2020-09-10 18:41:55 -07:00
TestUtils . AssertDirectoryTreesAreEquivalent ( s , Path . Combine ( RESTOREFOLDER , Path . GetFileName ( s )), true , "VerificationOfFullRestore" );
2015-09-17 20:04:49 +02:00
2020-07-27 18:35:39 -07:00
systemIO . DirectoryDelete ( RESTOREFOLDER , true );
2015-09-17 20:04:49 +02:00
2016-09-29 13:45:55 +02:00
ProgressWriteLine ( "Full restore without local db..." );
2019-08-25 10:59:14 -04:00
using ( new Library . Logging . Timer ( LOGTAG , "FullRestoreWithoutDb" , "Full restore without local db" ))
2024-03-15 14:18:56 +01:00
Duplicati . CommandLine . Program . Main (( new string [] { "restore" , target , "*" , "--restore-path=\"" + RESTOREFOLDER + "\"" , "--no-local-db" }. Union ( opts )). ToArray ());
2015-09-17 20:04:49 +02:00
2016-09-29 13:45:55 +02:00
ProgressWriteLine ( "Verifying full restore ..." );
2018-03-12 14:07:11 +01:00
using ( new Library . Logging . Timer ( LOGTAG , "VerificationOfFullRestoreWithoutDb" , "Verification of restored files" ))
2020-07-27 18:35:39 -07:00
foreach ( var s in systemIO . EnumerateDirectories ( DATAFOLDER ))
2020-09-10 18:41:55 -07:00
TestUtils . AssertDirectoryTreesAreEquivalent ( s , Path . Combine ( RESTOREFOLDER , Path . GetFileName ( s )), true , "VerificationOfFullRestoreWithoutDb" );
2019-08-25 10:59:14 -04:00
2016-09-29 13:45:55 +02:00
ProgressWriteLine ( "Testing data ..." );
2018-03-12 14:07:11 +01:00
using ( new Library . Logging . Timer ( LOGTAG , "TestRemoteData" , "Test remote data" ))
2024-03-15 14:18:56 +01:00
if ( Duplicati . CommandLine . Program . Main (( new string [] { "test" , target , "all" }. Union ( opts )). ToArray ()) != 0 )
2017-03-07 23:09:04 +01:00
throw new Exception ( "Failed during final remote verification" );
2015-09-17 20:04:49 +02:00
}
2015-04-10 22:29:52 +02:00
}
}