From 658612ac937f8dc4e38f9c803c8e438fbf3409ea Mon Sep 17 00:00:00 2001 From: AdityaNPL Date: Mon, 1 Mar 2021 12:28:42 +0000 Subject: [PATCH 01/16] fix(striped_backups): changed logic to get all striped backups --- src/BackupChain.cs | 72 ++++++++++++++++++++++++++++--------------- src/BackupMetadata.cs | 4 ++- 2 files changed, 50 insertions(+), 26 deletions(-) diff --git a/src/BackupChain.cs b/src/BackupChain.cs index 95096ea..60337b1 100755 --- a/src/BackupChain.cs +++ b/src/BackupChain.cs @@ -4,6 +4,7 @@ namespace AgDatabaseMove using System.Collections.Generic; using System.IO; using System.Linq; + using Exceptions; using SmoFacade; @@ -19,26 +20,33 @@ public class BackupChain : IBackupChain { private readonly IList _orderedBackups; + /// + /// Chain needs to be 1 full backup, 1 diff and then the logs in the order they were taken + /// Sometimes backups can be striped (i.e. split into multiple files) - so we need to handle these cases too + /// https://www.sqlservercentral.com/articles/getting-a-list-of-the-striped-backup-files + /// + /// private BackupChain(IList recentBackups) { var backups = recentBackups.Distinct(new BackupMetadataEqualityComparer()) - .Where(b => IsValidFilePath(b)) // A third party application caused invalid path strings to be inserted into backupmediafamily + .Where(IsValidFilePath) // A third party application caused invalid path strings to be inserted into backupmediafamily .ToList(); - var mostRecentFullBackup = MostRecentFullBackup(recentBackups); - _orderedBackups = new List { mostRecentFullBackup }; - - var differentialBackup = MostRecentDifferentialBackup(backups, mostRecentFullBackup); - if(differentialBackup != null) - _orderedBackups.Add(differentialBackup); - - var mostRecentBackup = _orderedBackups.Last(); - while(mostRecentBackup != null) { - mostRecentBackup = NextLogBackup(backups, mostRecentBackup); - - if(mostRecentBackup != null) - _orderedBackups.Add(mostRecentBackup); + var mostRecentFullBackups = MostRecentFullBackups(recentBackups).ToList(); + + var differentialBackups = MostRecentDifferentialBackups(backups, mostRecentFullBackups.First()); + // differentialBackups can be null + + var orderedBackups = mostRecentFullBackups.Concat(differentialBackups).ToList(); + + var prevBackup = orderedBackups.Last(); + IEnumerable nextLogBackups; + while((nextLogBackups = NextLogBackups(backups, prevBackup)).Any()) { + orderedBackups.AddRange(nextLogBackups); + prevBackup = orderedBackups.Last(); } + + _orderedBackups = orderedBackups; } /// @@ -56,26 +64,40 @@ public BackupChain(Database database) : this(database.RecentBackups()) { } /// public IEnumerable OrderedBackups => _orderedBackups; - private BackupMetadata MostRecentFullBackup(IList backups) + private static IEnumerable MostRecentFullBackups(IEnumerable backups) { - return backups.Where(b => b.BackupType == BackupFileTools.BackupType.Full).OrderByDescending(d => d.CheckpointLsn) - .First(); + var fullBackupsOrdered = backups + .Where(b => b.BackupType == BackupFileTools.BackupType.Full) + .OrderByDescending(d => d.CheckpointLsn).ToList(); + if(!fullBackupsOrdered.Any()) { + throw new BackupChainException("Could not find any full backups"); + } + + var targetCheckpointLsn = fullBackupsOrdered.First().CheckpointLsn; + // get all the stripes of the most recent full backup (i.e. has the same CheckpointLsn) + return fullBackupsOrdered.Where(fullBackup => fullBackup.CheckpointLsn == targetCheckpointLsn); } - private BackupMetadata MostRecentDifferentialBackup(IList backups, BackupMetadata lastFullBackup) + private static IEnumerable MostRecentDifferentialBackups(IEnumerable backups, BackupMetadata lastFullBackup) { - return backups.Where(b => b.BackupType == BackupFileTools.BackupType.Diff && - b.DatabaseBackupLsn == lastFullBackup.CheckpointLsn) - .OrderByDescending(b => b.LastLsn).FirstOrDefault(); + var diffBackupsOrdered = backups + .Where(b => b.BackupType == BackupFileTools.BackupType.Diff && + b.DatabaseBackupLsn == lastFullBackup.CheckpointLsn) + .OrderByDescending(b => b.LastLsn).ToList(); + + var targetLastLsn = diffBackupsOrdered.First().LastLsn; + // get all the stripes of the most recent diff backup (i.e. has the same LastLsn) + return diffBackupsOrdered.Where(diffBackup => diffBackup.LastLsn == targetLastLsn); } - private BackupMetadata NextLogBackup(IList backups, BackupMetadata prevBackup) + private static IEnumerable NextLogBackups(IEnumerable backups, BackupMetadata prevBackup) { - return backups.Where(b => b.BackupType == BackupFileTools.BackupType.Log) - .SingleOrDefault(d => prevBackup.LastLsn >= d.FirstLsn && prevBackup.LastLsn + 1 < d.LastLsn); + // get all the stripes of the next log backup + return backups.Where(b => b.BackupType == BackupFileTools.BackupType.Log && + prevBackup.LastLsn >= b.FirstLsn && prevBackup.LastLsn + 1 < b.LastLsn); } - private bool IsValidFilePath(BackupMetadata meta) + private static bool IsValidFilePath(BackupMetadata meta) { if(BackupFileTools.IsUrl(meta.PhysicalDeviceName)) return true; diff --git a/src/BackupMetadata.cs b/src/BackupMetadata.cs index 705c1cd..f50f9d4 100755 --- a/src/BackupMetadata.cs +++ b/src/BackupMetadata.cs @@ -17,13 +17,15 @@ public bool Equals(BackupMetadata x, BackupMetadata y) return x.LastLsn == y.LastLsn && x.FirstLsn == y.FirstLsn && x.BackupType == y.BackupType && - x.DatabaseName == y.DatabaseName; + x.DatabaseName == y.DatabaseName && + x.PhysicalDeviceName == y.PhysicalDeviceName; } public int GetHashCode(BackupMetadata obj) { var hashCode = -1277603921; hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(obj.DatabaseName); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(obj.PhysicalDeviceName); hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(obj.BackupType); hashCode = hashCode * -1521134295 + obj.FirstLsn.GetHashCode(); From 57e2b19e234dffcb06cf27d777468a17f5afe7e4 Mon Sep 17 00:00:00 2001 From: AdityaNPL Date: Mon, 1 Mar 2021 16:28:24 +0000 Subject: [PATCH 02/16] test(striped_backups): adding a unit test for backup chains with striped backups --- src/BackupChain.cs | 2 +- tests/AgDatabaseMove.Unit/BackupOrder.cs | 97 ++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 1 deletion(-) diff --git a/src/BackupChain.cs b/src/BackupChain.cs index 60337b1..c79aebb 100755 --- a/src/BackupChain.cs +++ b/src/BackupChain.cs @@ -32,7 +32,7 @@ private BackupChain(IList recentBackups) .Where(IsValidFilePath) // A third party application caused invalid path strings to be inserted into backupmediafamily .ToList(); - var mostRecentFullBackups = MostRecentFullBackups(recentBackups).ToList(); + var mostRecentFullBackups = MostRecentFullBackups(backups).ToList(); var differentialBackups = MostRecentDifferentialBackups(backups, mostRecentFullBackups.First()); // differentialBackups can be null diff --git a/tests/AgDatabaseMove.Unit/BackupOrder.cs b/tests/AgDatabaseMove.Unit/BackupOrder.cs index b97e321..4143be0 100755 --- a/tests/AgDatabaseMove.Unit/BackupOrder.cs +++ b/tests/AgDatabaseMove.Unit/BackupOrder.cs @@ -12,10 +12,12 @@ namespace AgDatabaseMove.Unit public class BackupOrder { private readonly List _listBackups; + private readonly List _listBackupsWithStripes; public BackupOrder() { _listBackups = ListBackups(); + _listBackupsWithStripes = ListBackupsWithStripes(); } private static List ListBackups() @@ -68,6 +70,89 @@ private static List ListBackups() }; } + private static List ListBackupsWithStripes() + { + return new List { + new BackupMetadata { + BackupType = BackupFileTools.BackupType.Log, + DatabaseBackupLsn = 126000000882000037, + CheckpointLsn = 126000000953600034, + FirstLsn = 126000000955200001, + LastLsn = 126000000955500001, + DatabaseName = "TestDb", + ServerName = "ServerA", + PhysicalDeviceName = @"\\DFS\BACKUP\ServerA\testDb\LogBackup_2_stripe_2.trn", + StartTime = DateTime.Parse("2018-10-29 02:00:07.000") + }, + new BackupMetadata { + BackupType = BackupFileTools.BackupType.Log, + DatabaseBackupLsn = 126000000943800037, + CheckpointLsn = 126000000953600034, + FirstLsn = 126000000955500001, + LastLsn = 126000000955800001, + DatabaseName = "TestDb", + ServerName = "ServerB", + PhysicalDeviceName = @"\\DFS\BACKUP\ServerA\testDb\LogBackup_1_noStripes.trn", + StartTime = DateTime.Parse("2018-10-29 03:00:06.000") + }, + new BackupMetadata { + BackupType = BackupFileTools.BackupType.Full, + DatabaseBackupLsn = 126000000882000037, + CheckpointLsn = 126000000943800037, + FirstLsn = 126000000936100001, + LastLsn = 126000000945500001, + DatabaseName = "TestDb", + ServerName = "ServerA", + PhysicalDeviceName = @"\\DFS\BACKUP\ServerA\testDb\FullBackup_stripe_1.full", + StartTime = DateTime.Parse("2018-10-28 00:02:28.000") + }, + new BackupMetadata { + BackupType = BackupFileTools.BackupType.Diff, + DatabaseBackupLsn = 126000000943800037, + CheckpointLsn = 126000000953600034, + FirstLsn = 126000000943800037, + LastLsn = 126000000955200001, + DatabaseName = "TestDb", + ServerName = "ServerA", + PhysicalDeviceName = @"\\DFS\BACKUP\ServerA\testDb\DiffBackup_stripe_1.diff", + StartTime = DateTime.Parse("2018-10-29 00:03:39.000") + }, + new BackupMetadata { + BackupType = BackupFileTools.BackupType.Log, + DatabaseBackupLsn = 126000000882000037, + CheckpointLsn = 126000000953600034, + FirstLsn = 126000000955200001, + LastLsn = 126000000955500001, + DatabaseName = "TestDb", + ServerName = "ServerA", + PhysicalDeviceName = @"\\DFS\BACKUP\ServerA\testDb\LogBackup_2_stripe_1.trn", + StartTime = DateTime.Parse("2018-10-29 02:00:07.000") + }, + new BackupMetadata { + BackupType = BackupFileTools.BackupType.Full, + DatabaseBackupLsn = 126000000882000037, + CheckpointLsn = 126000000943800037, + FirstLsn = 126000000936100001, + LastLsn = 126000000945500001, + DatabaseName = "TestDb", + ServerName = "ServerA", + PhysicalDeviceName = @"\\DFS\BACKUP\ServerA\testDb\FullBackup_stripe_2.full", + StartTime = DateTime.Parse("2018-10-28 00:02:28.000") + }, + new BackupMetadata { + BackupType = BackupFileTools.BackupType.Diff, + DatabaseBackupLsn = 126000000943800037, + CheckpointLsn = 126000000953600034, + FirstLsn = 126000000943800037, + LastLsn = 126000000955200001, + DatabaseName = "TestDb", + ServerName = "ServerA", + PhysicalDeviceName = @"\\DFS\BACKUP\ServerA\testDb\DiffBackup_stripe_2.diff", + StartTime = DateTime.Parse("2018-10-29 00:03:39.000") + } + }; + } + [Fact] public void BackupChainOrdered() { @@ -91,6 +176,18 @@ public void MissingLink() Assert.NotEqual(chain.Last().LastLsn, ListBackups().Max(b => b.LastLsn)); } + [Fact] + public void BackupChainWithStripesOrdered() + { + var agDatabase = new Mock(); + agDatabase.Setup(agd => agd.RecentBackups()).Returns(_listBackupsWithStripes); + var backupChain = new BackupChain(agDatabase.Object); + + var expected = _listBackupsWithStripes.OrderBy(bu => bu.FirstLsn); + + Assert.Equal(backupChain.OrderedBackups, expected); + } + // TODO: test skipping of logs if diff last LSN and log last LSN matches // TODO: test skipping of logs between diffs // TODO: test only keep last diff From 3af7fb004fc6836998a18b408e79726cb1a0ec26 Mon Sep 17 00:00:00 2001 From: AdityaNPL Date: Mon, 1 Mar 2021 17:04:08 +0000 Subject: [PATCH 03/16] refactor(striped_backups): renames, comments and list handling based on PR suggestions --- src/BackupChain.cs | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/src/BackupChain.cs b/src/BackupChain.cs index c79aebb..52e8d13 100755 --- a/src/BackupChain.cs +++ b/src/BackupChain.cs @@ -20,28 +20,19 @@ public class BackupChain : IBackupChain { private readonly IList _orderedBackups; - /// - /// Chain needs to be 1 full backup, 1 diff and then the logs in the order they were taken - /// Sometimes backups can be striped (i.e. split into multiple files) - so we need to handle these cases too - /// https://www.sqlservercentral.com/articles/getting-a-list-of-the-striped-backup-files - /// - /// + // This also handles any striped backups private BackupChain(IList recentBackups) { var backups = recentBackups.Distinct(new BackupMetadataEqualityComparer()) .Where(IsValidFilePath) // A third party application caused invalid path strings to be inserted into backupmediafamily .ToList(); - var mostRecentFullBackups = MostRecentFullBackups(backups).ToList(); - - var differentialBackups = MostRecentDifferentialBackups(backups, mostRecentFullBackups.First()); - // differentialBackups can be null - - var orderedBackups = mostRecentFullBackups.Concat(differentialBackups).ToList(); + var orderedBackups = MostRecentFullBackup(backups).ToList(); + orderedBackups.AddRange(MostRecentDiffBackup(backups, orderedBackups.First())); var prevBackup = orderedBackups.Last(); IEnumerable nextLogBackups; - while((nextLogBackups = NextLogBackups(backups, prevBackup)).Any()) { + while((nextLogBackups = NextLogBackup(backups, prevBackup)).Any()) { orderedBackups.AddRange(nextLogBackups); prevBackup = orderedBackups.Last(); } @@ -64,21 +55,22 @@ public BackupChain(Database database) : this(database.RecentBackups()) { } /// public IEnumerable OrderedBackups => _orderedBackups; - private static IEnumerable MostRecentFullBackups(IEnumerable backups) + private static IEnumerable MostRecentFullBackup(IEnumerable backups) { var fullBackupsOrdered = backups .Where(b => b.BackupType == BackupFileTools.BackupType.Full) .OrderByDescending(d => d.CheckpointLsn).ToList(); + if(!fullBackupsOrdered.Any()) { throw new BackupChainException("Could not find any full backups"); } var targetCheckpointLsn = fullBackupsOrdered.First().CheckpointLsn; - // get all the stripes of the most recent full backup (i.e. has the same CheckpointLsn) + // get all the stripes of this backup return fullBackupsOrdered.Where(fullBackup => fullBackup.CheckpointLsn == targetCheckpointLsn); } - private static IEnumerable MostRecentDifferentialBackups(IEnumerable backups, BackupMetadata lastFullBackup) + private static IEnumerable MostRecentDiffBackup(IEnumerable backups, BackupMetadata lastFullBackup) { var diffBackupsOrdered = backups .Where(b => b.BackupType == BackupFileTools.BackupType.Diff && @@ -86,13 +78,13 @@ private static IEnumerable MostRecentDifferentialBackups(IEnumer .OrderByDescending(b => b.LastLsn).ToList(); var targetLastLsn = diffBackupsOrdered.First().LastLsn; - // get all the stripes of the most recent diff backup (i.e. has the same LastLsn) + // get all the stripes of this backup return diffBackupsOrdered.Where(diffBackup => diffBackup.LastLsn == targetLastLsn); } - private static IEnumerable NextLogBackups(IEnumerable backups, BackupMetadata prevBackup) + private static IEnumerable NextLogBackup(IEnumerable backups, BackupMetadata prevBackup) { - // get all the stripes of the next log backup + // also gets all the stripes of the next backup return backups.Where(b => b.BackupType == BackupFileTools.BackupType.Log && prevBackup.LastLsn >= b.FirstLsn && prevBackup.LastLsn + 1 < b.LastLsn); } From 2de06d18f02cb0f88bc7a30182e9a2839b928425 Mon Sep 17 00:00:00 2001 From: AdityaNPL Date: Mon, 1 Mar 2021 18:33:54 +0000 Subject: [PATCH 04/16] fix(striped_backups): return empty list if no diff backups --- src/BackupChain.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/BackupChain.cs b/src/BackupChain.cs index 52e8d13..aff0c98 100755 --- a/src/BackupChain.cs +++ b/src/BackupChain.cs @@ -76,7 +76,9 @@ private static IEnumerable MostRecentDiffBackup(IEnumerable b.BackupType == BackupFileTools.BackupType.Diff && b.DatabaseBackupLsn == lastFullBackup.CheckpointLsn) .OrderByDescending(b => b.LastLsn).ToList(); - + if (!diffBackupsOrdered.Any()) { + return new List(); + } var targetLastLsn = diffBackupsOrdered.First().LastLsn; // get all the stripes of this backup return diffBackupsOrdered.Where(diffBackup => diffBackup.LastLsn == targetLastLsn); From 063cb2687b4cd3c04ad6ae91977416c42c2185db Mon Sep 17 00:00:00 2001 From: AdityaNPL Date: Mon, 1 Mar 2021 18:35:11 +0000 Subject: [PATCH 05/16] refactor(striped_backups): whitespace --- src/BackupChain.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/BackupChain.cs b/src/BackupChain.cs index aff0c98..7e58245 100755 --- a/src/BackupChain.cs +++ b/src/BackupChain.cs @@ -76,6 +76,7 @@ private static IEnumerable MostRecentDiffBackup(IEnumerable b.BackupType == BackupFileTools.BackupType.Diff && b.DatabaseBackupLsn == lastFullBackup.CheckpointLsn) .OrderByDescending(b => b.LastLsn).ToList(); + if (!diffBackupsOrdered.Any()) { return new List(); } From e792938604da67adadb04c369f09329952748380 Mon Sep 17 00:00:00 2001 From: AdityaNPL Date: Tue, 2 Mar 2021 10:20:10 +0000 Subject: [PATCH 06/16] fix(striped_backups): Exception on empty list of backups --- src/BackupChain.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/BackupChain.cs b/src/BackupChain.cs index 7e58245..3ed1c67 100755 --- a/src/BackupChain.cs +++ b/src/BackupChain.cs @@ -23,6 +23,10 @@ public class BackupChain : IBackupChain // This also handles any striped backups private BackupChain(IList recentBackups) { + if (!recentBackups.Any()) { + throw new BackupChainException("There are no recent backups to form a chain"); + } + var backups = recentBackups.Distinct(new BackupMetadataEqualityComparer()) .Where(IsValidFilePath) // A third party application caused invalid path strings to be inserted into backupmediafamily .ToList(); From 0d8e5d681dc28f7dde05a464e0a57db5327f524b Mon Sep 17 00:00:00 2001 From: AdityaNPL Date: Tue, 2 Mar 2021 10:27:53 +0000 Subject: [PATCH 07/16] fix(striped_backups): also a null check for the list of backups --- src/BackupChain.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BackupChain.cs b/src/BackupChain.cs index 3ed1c67..8b4158b 100755 --- a/src/BackupChain.cs +++ b/src/BackupChain.cs @@ -23,7 +23,7 @@ public class BackupChain : IBackupChain // This also handles any striped backups private BackupChain(IList recentBackups) { - if (!recentBackups.Any()) { + if (recentBackups == null || !recentBackups.Any()) { throw new BackupChainException("There are no recent backups to form a chain"); } From ff0b36d870b052bf1f9f08fe183acbbaafdae7e7 Mon Sep 17 00:00:00 2001 From: AdityaNPL Date: Tue, 2 Mar 2021 18:17:29 +0000 Subject: [PATCH 08/16] test(striped_backups): few more thorough positive tests with stripes and duplicates --- src/BackupMetadata.cs | 8 +- tests/AgDatabaseMove.Unit/BackupOrder.cs | 226 ++++++++++++----------- 2 files changed, 122 insertions(+), 112 deletions(-) diff --git a/src/BackupMetadata.cs b/src/BackupMetadata.cs index f50f9d4..5253f57 100755 --- a/src/BackupMetadata.cs +++ b/src/BackupMetadata.cs @@ -37,7 +37,7 @@ public int GetHashCode(BackupMetadata obj) /// /// Metadata about backups from msdb.dbo.backupset and msdb.dbo.backupmediafamily /// - public class BackupMetadata + public class BackupMetadata : ICloneable { public decimal CheckpointLsn { get; set; } public decimal DatabaseBackupLsn { get; set; } @@ -54,5 +54,11 @@ public class BackupMetadata /// https://docs.microsoft.com/en-us/sql/relational-databases/system-tables/backupset-transact-sql?view=sql-server-2017 /// public BackupFileTools.BackupType BackupType { get; set; } + + // used during testing + public object Clone() + { + return MemberwiseClone(); + } } } \ No newline at end of file diff --git a/tests/AgDatabaseMove.Unit/BackupOrder.cs b/tests/AgDatabaseMove.Unit/BackupOrder.cs index 4143be0..42ce15e 100755 --- a/tests/AgDatabaseMove.Unit/BackupOrder.cs +++ b/tests/AgDatabaseMove.Unit/BackupOrder.cs @@ -11,88 +11,40 @@ namespace AgDatabaseMove.Unit public class BackupOrder { - private readonly List _listBackups; - private readonly List _listBackupsWithStripes; - public BackupOrder() + private static IEnumerable CloneBackupMetaDataList(List baseList) { - _listBackups = ListBackups(); - _listBackupsWithStripes = ListBackupsWithStripes(); + var result = new List(); + baseList.ForEach(b => { + result.Add((BackupMetadata)b.Clone()); + }); + result.Reverse(); + return result; } - private static List ListBackups() + private static List GetBackupList() { return new List { new BackupMetadata { BackupType = BackupFileTools.BackupType.Log, DatabaseBackupLsn = 126000000943800037, CheckpointLsn = 126000000953600034, - FirstLsn = 126000000955500001, - LastLsn = 126000000955800001, - DatabaseName = "TestDb", - ServerName = "ServerB", - PhysicalDeviceName = @"\\DFS\BACKUP\ServerB\testDb\Testdb_backup_2018_10_29_030006_660.trn", - StartTime = DateTime.Parse("2018-10-29 03:00:06.000") - }, - new BackupMetadata { - BackupType = BackupFileTools.BackupType.Full, - DatabaseBackupLsn = 126000000882000037, - CheckpointLsn = 126000000943800037, - FirstLsn = 126000000936100001, - LastLsn = 126000000945500001, - DatabaseName = "TestDb", - ServerName = "ServerA", - PhysicalDeviceName = @"\\DFS\BACKUP\ServerA\testDb\Testdb_backup_2018_10_28_000227_200.full", - StartTime = DateTime.Parse("2018-10-28 00:02:28.000") - }, - new BackupMetadata { - BackupType = BackupFileTools.BackupType.Diff, - DatabaseBackupLsn = 126000000943800037, - CheckpointLsn = 126000000953600034, - FirstLsn = 126000000943800037, - LastLsn = 126000000955200001, - DatabaseName = "TestDb", - ServerName = "ServerA", - PhysicalDeviceName = @"\\DFS\BACKUP\ServerA\testDb\Testdb_backup_2018_10_29_000339_780.diff", - StartTime = DateTime.Parse("2018-10-29 00:03:39.000") - }, - new BackupMetadata { - BackupType = BackupFileTools.BackupType.Log, - DatabaseBackupLsn = 126000000882000037, - CheckpointLsn = 126000000953600034, FirstLsn = 126000000955200001, LastLsn = 126000000955500001, DatabaseName = "TestDb", ServerName = "ServerA", PhysicalDeviceName = @"\\DFS\BACKUP\ServerA\testDb\Testdb_backup_2018_10_29_020007_343.trn", StartTime = DateTime.Parse("2018-10-29 02:00:07.000") - } - }; - } - - private static List ListBackupsWithStripes() - { - return new List { - new BackupMetadata { - BackupType = BackupFileTools.BackupType.Log, - DatabaseBackupLsn = 126000000882000037, - CheckpointLsn = 126000000953600034, - FirstLsn = 126000000955200001, - LastLsn = 126000000955500001, - DatabaseName = "TestDb", - ServerName = "ServerA", - PhysicalDeviceName = @"\\DFS\BACKUP\ServerA\testDb\LogBackup_2_stripe_2.trn", - StartTime = DateTime.Parse("2018-10-29 02:00:07.000") }, new BackupMetadata { BackupType = BackupFileTools.BackupType.Log, DatabaseBackupLsn = 126000000943800037, CheckpointLsn = 126000000953600034, - FirstLsn = 126000000955500001, - LastLsn = 126000000955800001, + FirstLsn = 126000000955800001, + LastLsn = 126000000965800001, DatabaseName = "TestDb", ServerName = "ServerB", - PhysicalDeviceName = @"\\DFS\BACKUP\ServerA\testDb\LogBackup_1_noStripes.trn", + PhysicalDeviceName = @"\\DFS\BACKUP\ServerB\testDb\Testdb_backup_2018_10_29_040005_900.trn", StartTime = DateTime.Parse("2018-10-29 03:00:06.000") }, new BackupMetadata { @@ -103,89 +55,141 @@ private static List ListBackupsWithStripes() LastLsn = 126000000945500001, DatabaseName = "TestDb", ServerName = "ServerA", - PhysicalDeviceName = @"\\DFS\BACKUP\ServerA\testDb\FullBackup_stripe_1.full", + PhysicalDeviceName = @"\\DFS\BACKUP\ServerA\testDb\Testdb_backup_2018_10_28_000227_200.full", StartTime = DateTime.Parse("2018-10-28 00:02:28.000") }, - new BackupMetadata { - BackupType = BackupFileTools.BackupType.Diff, - DatabaseBackupLsn = 126000000943800037, - CheckpointLsn = 126000000953600034, - FirstLsn = 126000000943800037, - LastLsn = 126000000955200001, - DatabaseName = "TestDb", - ServerName = "ServerA", - PhysicalDeviceName = @"\\DFS\BACKUP\ServerA\testDb\DiffBackup_stripe_1.diff", - StartTime = DateTime.Parse("2018-10-29 00:03:39.000") - }, new BackupMetadata { BackupType = BackupFileTools.BackupType.Log, - DatabaseBackupLsn = 126000000882000037, + DatabaseBackupLsn = 126000000943800037, CheckpointLsn = 126000000953600034, - FirstLsn = 126000000955200001, - LastLsn = 126000000955500001, - DatabaseName = "TestDb", - ServerName = "ServerA", - PhysicalDeviceName = @"\\DFS\BACKUP\ServerA\testDb\LogBackup_2_stripe_1.trn", - StartTime = DateTime.Parse("2018-10-29 02:00:07.000") - }, - new BackupMetadata { - BackupType = BackupFileTools.BackupType.Full, - DatabaseBackupLsn = 126000000882000037, - CheckpointLsn = 126000000943800037, - FirstLsn = 126000000936100001, - LastLsn = 126000000945500001, + FirstLsn = 126000000955500001, + LastLsn = 126000000955800001, DatabaseName = "TestDb", - ServerName = "ServerA", - PhysicalDeviceName = @"\\DFS\BACKUP\ServerA\testDb\FullBackup_stripe_2.full", - StartTime = DateTime.Parse("2018-10-28 00:02:28.000") + ServerName = "ServerB", + PhysicalDeviceName = @"\\DFS\BACKUP\ServerB\testDb\Testdb_backup_2018_10_29_030006_660.trn", + StartTime = DateTime.Parse("2018-10-29 03:00:06.000") }, new BackupMetadata { BackupType = BackupFileTools.BackupType.Diff, DatabaseBackupLsn = 126000000943800037, CheckpointLsn = 126000000953600034, - FirstLsn = 126000000943800037, + FirstLsn = 126000000943800038, LastLsn = 126000000955200001, DatabaseName = "TestDb", ServerName = "ServerA", - PhysicalDeviceName = @"\\DFS\BACKUP\ServerA\testDb\DiffBackup_stripe_2.diff", + PhysicalDeviceName = @"\\DFS\BACKUP\ServerA\testDb\Testdb_backup_2018_10_29_000339_780.diff", StartTime = DateTime.Parse("2018-10-29 00:03:39.000") } }; } - [Fact] - public void BackupChainOrdered() + private static List GetBackupListWithoutLogs() { - var agDatabase = new Mock(); - agDatabase.Setup(agd => agd.RecentBackups()).Returns(_listBackups); - var backupChain = new BackupChain(agDatabase.Object); + var list = GetBackupList(); + list.RemoveAll(b => b.BackupType == BackupFileTools.BackupType.Log); + return list; + } + + private static List GetBackupListWithoutDiff() + { + var list = GetBackupList(); + list.RemoveAll(b => b.BackupType == BackupFileTools.BackupType.Diff); + return list; + } - var expected = _listBackups.OrderBy(bu => bu.FirstLsn); + private static List GetBackupListWithStripes() + { + var list = GetBackupList(); + var listWithStripes = CloneBackupMetaDataList(list).ToList(); + listWithStripes.ForEach(b => { + var path = b.PhysicalDeviceName.Split('.'); + b.PhysicalDeviceName = $"{path[0]}_striped.{path[1]}"; + }); + list.AddRange(listWithStripes); + return list; + } - Assert.Equal(backupChain.OrderedBackups, expected); + private static List GetBackupListWithStripesAndDuplicates() + { + var listWithStripes = GetBackupListWithStripes(); + var duplicate = CloneBackupMetaDataList(listWithStripes); + listWithStripes.AddRange(duplicate); + return listWithStripes; } - [Fact] - public void MissingLink() + + + private static void VerifyListIsAValidBackupChain(IEnumerable backupChain) { - var backups = ListBackups().Where(b => b.FirstLsn != 126000000955200001).ToList(); - var agDatabase = new Mock(); - agDatabase.Setup(agd => agd.RecentBackups()).Returns(backups); + var backupChainList = backupChain.ToList(); + + var listOfFileNames = backupChainList.Select(b => b.PhysicalDeviceName).ToList(); + Assert.True(listOfFileNames.Count == listOfFileNames.Distinct().Count()); + + var orderedBackups = CloneBackupMetaDataList(backupChainList) + .OrderBy(b => b.LastLsn) + // 'PhysicalDeviceName' ordering doesn't matter but need it here to simplify this check for LSN ordering + .ThenBy(b => b.PhysicalDeviceName).ToList(); + Assert.True(backupChainList.SequenceEqual(orderedBackups, new BackupMetadataEqualityComparer())); + + // first one has to be full + var full = backupChainList.FirstOrDefault(); + Assert.NotNull(full); + Assert.True(full.BackupType == BackupFileTools.BackupType.Full); + + // removing all the striped files of the full backup + backupChainList.RemoveAll(b => b.BackupType == BackupFileTools.BackupType.Full && + b.CheckpointLsn == full.CheckpointLsn); + + var otherFullBackups = backupChainList.RemoveAll(b => b.BackupType == BackupFileTools.BackupType.Full); + Assert.Equal(0, otherFullBackups); + + // next one can be diff or log + var currentBackup = backupChainList.FirstOrDefault(); + if(currentBackup == null) { + return; + } + + Assert.True(currentBackup.DatabaseBackupLsn == full.CheckpointLsn); + + if(currentBackup.BackupType == BackupFileTools.BackupType.Diff) { + + // removing all the striped files of the diff backup + backupChainList.RemoveAll(b => b.BackupType == BackupFileTools.BackupType.Diff && + b.FirstLsn == currentBackup.FirstLsn && b.LastLsn == currentBackup.LastLsn); + + var otherDiffBackups = backupChainList.RemoveAll(b => b.BackupType == BackupFileTools.BackupType.Diff); + Assert.Equal(0, otherDiffBackups); + } + + var prevBackup = currentBackup; + backupChainList.ForEach(b => { + Assert.True(b.BackupType == BackupFileTools.BackupType.Log && + ( + (prevBackup.FirstLsn == b.FirstLsn && prevBackup.LastLsn == b.LastLsn) || + prevBackup.LastLsn == b.FirstLsn) + ); + prevBackup = b; + }); - var chain = new BackupChain(agDatabase.Object).OrderedBackups; - Assert.NotEqual(chain.Last().LastLsn, ListBackups().Max(b => b.LastLsn)); } - [Fact] - public void BackupChainWithStripesOrdered() + public static IEnumerable PositiveTestData => new List { + new object[] { GetBackupList() }, + new object[] { GetBackupListWithStripes() }, + new object[] { GetBackupListWithStripesAndDuplicates() }, + new object[] { GetBackupListWithoutDiff() }, + new object[] { GetBackupListWithoutLogs() } + }; + + [Theory] + [MemberData(nameof(PositiveTestData))] + public void BackupChainIsCorrect(List backupList) { var agDatabase = new Mock(); - agDatabase.Setup(agd => agd.RecentBackups()).Returns(_listBackupsWithStripes); + agDatabase.Setup(agd => agd.RecentBackups()).Returns(backupList); var backupChain = new BackupChain(agDatabase.Object); - - var expected = _listBackupsWithStripes.OrderBy(bu => bu.FirstLsn); - - Assert.Equal(backupChain.OrderedBackups, expected); + VerifyListIsAValidBackupChain(backupChain.OrderedBackups); } // TODO: test skipping of logs if diff last LSN and log last LSN matches From 6fc3e19dcd81d29255db2b4fb616ac6c2a9b3f45 Mon Sep 17 00:00:00 2001 From: AdityaNPL Date: Tue, 2 Mar 2021 18:26:39 +0000 Subject: [PATCH 09/16] test(striped_backups): small rename in function --- tests/AgDatabaseMove.Unit/BackupOrder.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/AgDatabaseMove.Unit/BackupOrder.cs b/tests/AgDatabaseMove.Unit/BackupOrder.cs index 42ce15e..7dfc09b 100755 --- a/tests/AgDatabaseMove.Unit/BackupOrder.cs +++ b/tests/AgDatabaseMove.Unit/BackupOrder.cs @@ -12,10 +12,10 @@ namespace AgDatabaseMove.Unit public class BackupOrder { - private static IEnumerable CloneBackupMetaDataList(List baseList) + private static IEnumerable CloneBackupMetaDataList(List list) { var result = new List(); - baseList.ForEach(b => { + list.ForEach(b => { result.Add((BackupMetadata)b.Clone()); }); result.Reverse(); @@ -191,7 +191,7 @@ public void BackupChainIsCorrect(List backupList) var backupChain = new BackupChain(agDatabase.Object); VerifyListIsAValidBackupChain(backupChain.OrderedBackups); } - + // TODO: test skipping of logs if diff last LSN and log last LSN matches // TODO: test skipping of logs between diffs // TODO: test only keep last diff From 20e4ff49570ee9cf115b918f4a1313cb5ee6d398 Mon Sep 17 00:00:00 2001 From: AdityaNPL Date: Tue, 2 Mar 2021 18:47:51 +0000 Subject: [PATCH 10/16] test(striped_backups): few negative tests --- tests/AgDatabaseMove.Unit/BackupOrder.cs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/tests/AgDatabaseMove.Unit/BackupOrder.cs b/tests/AgDatabaseMove.Unit/BackupOrder.cs index 7dfc09b..29dc639 100755 --- a/tests/AgDatabaseMove.Unit/BackupOrder.cs +++ b/tests/AgDatabaseMove.Unit/BackupOrder.cs @@ -4,6 +4,7 @@ namespace AgDatabaseMove.Unit using System.Collections; using System.Collections.Generic; using System.Linq; + using Exceptions; using Moq; using SmoFacade; using Xunit; @@ -117,7 +118,12 @@ private static List GetBackupListWithStripesAndDuplicates() return listWithStripes; } - + private static List GetBackupListWithoutFull() + { + var list = GetBackupList(); + list.RemoveAll(b => b.BackupType == BackupFileTools.BackupType.Full); + return list; + } private static void VerifyListIsAValidBackupChain(IEnumerable backupChain) { @@ -174,6 +180,7 @@ private static void VerifyListIsAValidBackupChain(IEnumerable ba } + public static IEnumerable PositiveTestData => new List { new object[] { GetBackupList() }, new object[] { GetBackupListWithStripes() }, @@ -192,6 +199,21 @@ public void BackupChainIsCorrect(List backupList) VerifyListIsAValidBackupChain(backupChain.OrderedBackups); } + + public static IEnumerable NegativeTestData => new List { + new object[] { GetBackupListWithoutFull() }, + new object[] { new List() } + }; + + [Theory] + [MemberData(nameof(NegativeTestData))] + public void CanDetectBackupChainIsWrong(List backupList) + { + var agDatabase = new Mock(); + agDatabase.Setup(agd => agd.RecentBackups()).Returns(backupList); + Assert.Throws(() => new BackupChain(agDatabase.Object)); + } + // TODO: test skipping of logs if diff last LSN and log last LSN matches // TODO: test skipping of logs between diffs // TODO: test only keep last diff From 8abb6abb67b5a0ce5a3c69239d1dcb86df910c5d Mon Sep 17 00:00:00 2001 From: AdityaNPL Date: Tue, 2 Mar 2021 18:58:47 +0000 Subject: [PATCH 11/16] refactor(striped_backups): null/empty check for backup list --- src/BackupChain.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BackupChain.cs b/src/BackupChain.cs index 8b4158b..dcc1738 100755 --- a/src/BackupChain.cs +++ b/src/BackupChain.cs @@ -23,7 +23,7 @@ public class BackupChain : IBackupChain // This also handles any striped backups private BackupChain(IList recentBackups) { - if (recentBackups == null || !recentBackups.Any()) { + if (!(recentBackups?.Count > 0)) { throw new BackupChainException("There are no recent backups to form a chain"); } From f931d446eed364e3df3e8f152b6fcb13f5528f0b Mon Sep 17 00:00:00 2001 From: AdityaNPL Date: Wed, 3 Mar 2021 15:37:31 +0000 Subject: [PATCH 12/16] test(striped_backups): Adding back test for missing link --- tests/AgDatabaseMove.Unit/BackupOrder.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/AgDatabaseMove.Unit/BackupOrder.cs b/tests/AgDatabaseMove.Unit/BackupOrder.cs index 29dc639..24bb493 100755 --- a/tests/AgDatabaseMove.Unit/BackupOrder.cs +++ b/tests/AgDatabaseMove.Unit/BackupOrder.cs @@ -214,6 +214,18 @@ public void CanDetectBackupChainIsWrong(List backupList) Assert.Throws(() => new BackupChain(agDatabase.Object)); } + [Fact] + public void MissingLink() + { + var backups = GetBackupList().Where(b => b.FirstLsn != 126000000955200001).ToList(); + var agDatabase = new Mock(); + agDatabase.Setup(agd => agd.RecentBackups()).Returns(backups); + + var chain = new BackupChain(agDatabase.Object).OrderedBackups.ToList(); + Assert.NotEqual(chain.Last().LastLsn, GetBackupList().Max(b => b.LastLsn)); + VerifyListIsAValidBackupChain(chain); + } + // TODO: test skipping of logs if diff last LSN and log last LSN matches // TODO: test skipping of logs between diffs // TODO: test only keep last diff From c16466bd33febb9e681526fc9529db4e35d9b3c9 Mon Sep 17 00:00:00 2001 From: AdityaNPL Date: Wed, 3 Mar 2021 17:22:17 +0000 Subject: [PATCH 13/16] test(striped_backups): refactor the verify backup chain function --- tests/AgDatabaseMove.Unit/BackupOrder.cs | 87 +++++++++++------------- 1 file changed, 39 insertions(+), 48 deletions(-) diff --git a/tests/AgDatabaseMove.Unit/BackupOrder.cs b/tests/AgDatabaseMove.Unit/BackupOrder.cs index 24bb493..8add46d 100755 --- a/tests/AgDatabaseMove.Unit/BackupOrder.cs +++ b/tests/AgDatabaseMove.Unit/BackupOrder.cs @@ -125,59 +125,50 @@ private static List GetBackupListWithoutFull() return list; } - private static void VerifyListIsAValidBackupChain(IEnumerable backupChain) + private static void VerifyListIsAValidBackupChain(List backupChain) { - var backupChainList = backupChain.ToList(); - - var listOfFileNames = backupChainList.Select(b => b.PhysicalDeviceName).ToList(); - Assert.True(listOfFileNames.Count == listOfFileNames.Distinct().Count()); - - var orderedBackups = CloneBackupMetaDataList(backupChainList) - .OrderBy(b => b.LastLsn) - // 'PhysicalDeviceName' ordering doesn't matter but need it here to simplify this check for LSN ordering - .ThenBy(b => b.PhysicalDeviceName).ToList(); - Assert.True(backupChainList.SequenceEqual(orderedBackups, new BackupMetadataEqualityComparer())); - - // first one has to be full - var full = backupChainList.FirstOrDefault(); - Assert.NotNull(full); - Assert.True(full.BackupType == BackupFileTools.BackupType.Full); - - // removing all the striped files of the full backup - backupChainList.RemoveAll(b => b.BackupType == BackupFileTools.BackupType.Full && - b.CheckpointLsn == full.CheckpointLsn); - - var otherFullBackups = backupChainList.RemoveAll(b => b.BackupType == BackupFileTools.BackupType.Full); - Assert.Equal(0, otherFullBackups); - - // next one can be diff or log - var currentBackup = backupChainList.FirstOrDefault(); - if(currentBackup == null) { - return; + var listOfFileNames = backupChain.Select(b => b.PhysicalDeviceName).ToList(); + Assert.True(listOfFileNames.Count == listOfFileNames.Distinct().Count(), "There are no duplicates?"); + + var fullBackup = backupChain.First(); + Assert.NotNull(fullBackup); + Assert.True(fullBackup.BackupType == BackupFileTools.BackupType.Full, "Is the first backup a full backup?"); + backupChain.RemoveAt(0); + + BackupMetadata currentBackup; + var prevBackup = fullBackup; + + while((currentBackup = backupChain.FirstOrDefault())?.BackupType == BackupFileTools.BackupType.Full) { + Assert.True(currentBackup.CheckpointLsn == prevBackup.CheckpointLsn, + "Is it a striped file of the full backup?"); + prevBackup = currentBackup; + backupChain.RemoveAt(0); } - Assert.True(currentBackup.DatabaseBackupLsn == full.CheckpointLsn); - - if(currentBackup.BackupType == BackupFileTools.BackupType.Diff) { - - // removing all the striped files of the diff backup - backupChainList.RemoveAll(b => b.BackupType == BackupFileTools.BackupType.Diff && - b.FirstLsn == currentBackup.FirstLsn && b.LastLsn == currentBackup.LastLsn); - - var otherDiffBackups = backupChainList.RemoveAll(b => b.BackupType == BackupFileTools.BackupType.Diff); - Assert.Equal(0, otherDiffBackups); + prevBackup = currentBackup; + while((currentBackup = backupChain.FirstOrDefault())?.BackupType == BackupFileTools.BackupType.Diff) { + Assert.True(currentBackup.DatabaseBackupLsn == fullBackup.CheckpointLsn, + "Is the diff linked to the full backup?"); + Assert.True(currentBackup.FirstLsn == prevBackup.FirstLsn && currentBackup.LastLsn == prevBackup.LastLsn, + "Is it a striped file of the diff backup?"); + prevBackup = currentBackup; + backupChain.RemoveAt(0); } - var prevBackup = currentBackup; - backupChainList.ForEach(b => { - Assert.True(b.BackupType == BackupFileTools.BackupType.Log && - ( - (prevBackup.FirstLsn == b.FirstLsn && prevBackup.LastLsn == b.LastLsn) || - prevBackup.LastLsn == b.FirstLsn) - ); - prevBackup = b; - }); + prevBackup = currentBackup; + while((currentBackup = backupChain.FirstOrDefault())?.BackupType == BackupFileTools.BackupType.Log) { + Assert.True(currentBackup.DatabaseBackupLsn == fullBackup.CheckpointLsn, + "Is the log linked to the full backup?"); + Assert.True( + (currentBackup.FirstLsn == prevBackup.FirstLsn && currentBackup.LastLsn == prevBackup.LastLsn) || + (prevBackup.LastLsn == currentBackup.FirstLsn), + "Is it either the striped file of the prev log backup or the next one in the chain?"); + + prevBackup = currentBackup; + backupChain.RemoveAt(0); + } + Assert.Empty(backupChain); } @@ -196,7 +187,7 @@ public void BackupChainIsCorrect(List backupList) var agDatabase = new Mock(); agDatabase.Setup(agd => agd.RecentBackups()).Returns(backupList); var backupChain = new BackupChain(agDatabase.Object); - VerifyListIsAValidBackupChain(backupChain.OrderedBackups); + VerifyListIsAValidBackupChain(backupChain.OrderedBackups.ToList()); } From 3d4855dd682d74c878af4be405da1592a3d143bf Mon Sep 17 00:00:00 2001 From: AdityaNPL Date: Mon, 8 Mar 2021 14:26:56 +0000 Subject: [PATCH 14/16] test(striped_backups): refactor `VerifyListIsAValidBackupChain()` --- tests/AgDatabaseMove.Unit/BackupOrder.cs | 63 +++++++++--------------- 1 file changed, 24 insertions(+), 39 deletions(-) diff --git a/tests/AgDatabaseMove.Unit/BackupOrder.cs b/tests/AgDatabaseMove.Unit/BackupOrder.cs index 8add46d..ef67859 100755 --- a/tests/AgDatabaseMove.Unit/BackupOrder.cs +++ b/tests/AgDatabaseMove.Unit/BackupOrder.cs @@ -74,7 +74,7 @@ private static List GetBackupList() BackupType = BackupFileTools.BackupType.Diff, DatabaseBackupLsn = 126000000943800037, CheckpointLsn = 126000000953600034, - FirstLsn = 126000000943800038, + FirstLsn = 126000000945600000, LastLsn = 126000000955200001, DatabaseName = "TestDb", ServerName = "ServerA", @@ -127,51 +127,36 @@ private static List GetBackupListWithoutFull() private static void VerifyListIsAValidBackupChain(List backupChain) { - var listOfFileNames = backupChain.Select(b => b.PhysicalDeviceName).ToList(); - Assert.True(listOfFileNames.Count == listOfFileNames.Distinct().Count(), "There are no duplicates?"); - - var fullBackup = backupChain.First(); - Assert.NotNull(fullBackup); - Assert.True(fullBackup.BackupType == BackupFileTools.BackupType.Full, "Is the first backup a full backup?"); - backupChain.RemoveAt(0); + bool foundFull, foundDiff, foundLog; + foundFull = foundDiff = foundLog = false; + BackupMetadata full = null; + BackupMetadata lastBackup = null; BackupMetadata currentBackup; - var prevBackup = fullBackup; - - while((currentBackup = backupChain.FirstOrDefault())?.BackupType == BackupFileTools.BackupType.Full) { - Assert.True(currentBackup.CheckpointLsn == prevBackup.CheckpointLsn, - "Is it a striped file of the full backup?"); - prevBackup = currentBackup; - backupChain.RemoveAt(0); - } + while((currentBackup = backupChain.FirstOrDefault()) != null) { - prevBackup = currentBackup; - while((currentBackup = backupChain.FirstOrDefault())?.BackupType == BackupFileTools.BackupType.Diff) { - Assert.True(currentBackup.DatabaseBackupLsn == fullBackup.CheckpointLsn, - "Is the diff linked to the full backup?"); - Assert.True(currentBackup.FirstLsn == prevBackup.FirstLsn && currentBackup.LastLsn == prevBackup.LastLsn, - "Is it a striped file of the diff backup?"); - prevBackup = currentBackup; - backupChain.RemoveAt(0); - } + if(currentBackup.BackupType == BackupFileTools.BackupType.Full) { + Assert.True(!foundFull && !foundDiff && !foundLog); + foundFull = true; + full = currentBackup; + } + else if(currentBackup.BackupType == BackupFileTools.BackupType.Diff) { + Assert.True(foundFull && !foundDiff && !foundLog); + Assert.Equal(currentBackup.DatabaseBackupLsn, full.CheckpointLsn); + Assert.True(currentBackup.FirstLsn >= lastBackup.LastLsn); + foundDiff = true; + } + else if(currentBackup.BackupType == BackupFileTools.BackupType.Log) { + Assert.True(foundFull); + Assert.True(currentBackup.FirstLsn >= lastBackup.LastLsn); + foundLog = true; + } - prevBackup = currentBackup; - while((currentBackup = backupChain.FirstOrDefault())?.BackupType == BackupFileTools.BackupType.Log) { - Assert.True(currentBackup.DatabaseBackupLsn == fullBackup.CheckpointLsn, - "Is the log linked to the full backup?"); - Assert.True( - (currentBackup.FirstLsn == prevBackup.FirstLsn && currentBackup.LastLsn == prevBackup.LastLsn) || - (prevBackup.LastLsn == currentBackup.FirstLsn), - "Is it either the striped file of the prev log backup or the next one in the chain?"); - - prevBackup = currentBackup; - backupChain.RemoveAt(0); + lastBackup = currentBackup; + backupChain.RemoveAll(b => b.LastLsn == currentBackup.LastLsn); } - - Assert.Empty(backupChain); } - public static IEnumerable PositiveTestData => new List { new object[] { GetBackupList() }, new object[] { GetBackupListWithStripes() }, From 2fce89c796817b9913b7a89e86860aa1363f8801 Mon Sep 17 00:00:00 2001 From: AdityaNPL Date: Mon, 8 Mar 2021 14:31:31 +0000 Subject: [PATCH 15/16] test(striped_backups): refactor duplicate file test to its own function --- tests/AgDatabaseMove.Unit/BackupOrder.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/AgDatabaseMove.Unit/BackupOrder.cs b/tests/AgDatabaseMove.Unit/BackupOrder.cs index ef67859..576b8eb 100755 --- a/tests/AgDatabaseMove.Unit/BackupOrder.cs +++ b/tests/AgDatabaseMove.Unit/BackupOrder.cs @@ -160,7 +160,6 @@ private static void VerifyListIsAValidBackupChain(List backupCha public static IEnumerable PositiveTestData => new List { new object[] { GetBackupList() }, new object[] { GetBackupListWithStripes() }, - new object[] { GetBackupListWithStripesAndDuplicates() }, new object[] { GetBackupListWithoutDiff() }, new object[] { GetBackupListWithoutLogs() } }; @@ -202,6 +201,16 @@ public void MissingLink() VerifyListIsAValidBackupChain(chain); } + [Fact] + public void DuplicateFiles() + { + var backups = GetBackupListWithStripesAndDuplicates(); + var agDatabase = new Mock(); + agDatabase.Setup(agd => agd.RecentBackups()).Returns(backups); + var chain = new BackupChain(agDatabase.Object).OrderedBackups.ToList(); + Assert.Equal(chain.Count, backups.Count/2); + } + // TODO: test skipping of logs if diff last LSN and log last LSN matches // TODO: test skipping of logs between diffs // TODO: test only keep last diff From dee29a7a952e97b06eb4df4ac81c04eb8575b7b9 Mon Sep 17 00:00:00 2001 From: AdityaNPL Date: Mon, 8 Mar 2021 18:35:11 +0000 Subject: [PATCH 16/16] test(striped_backups): PR suggestions - refactor duplicate test + null check --- src/BackupChain.cs | 2 +- tests/AgDatabaseMove.Unit/BackupOrder.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/BackupChain.cs b/src/BackupChain.cs index dcc1738..4d85f29 100755 --- a/src/BackupChain.cs +++ b/src/BackupChain.cs @@ -23,7 +23,7 @@ public class BackupChain : IBackupChain // This also handles any striped backups private BackupChain(IList recentBackups) { - if (!(recentBackups?.Count > 0)) { + if (recentBackups == null || recentBackups.Count == 0) { throw new BackupChainException("There are no recent backups to form a chain"); } diff --git a/tests/AgDatabaseMove.Unit/BackupOrder.cs b/tests/AgDatabaseMove.Unit/BackupOrder.cs index 576b8eb..9f8f860 100755 --- a/tests/AgDatabaseMove.Unit/BackupOrder.cs +++ b/tests/AgDatabaseMove.Unit/BackupOrder.cs @@ -142,7 +142,7 @@ private static void VerifyListIsAValidBackupChain(List backupCha } else if(currentBackup.BackupType == BackupFileTools.BackupType.Diff) { Assert.True(foundFull && !foundDiff && !foundLog); - Assert.Equal(currentBackup.DatabaseBackupLsn, full.CheckpointLsn); + Assert.Equal(full.CheckpointLsn, currentBackup.DatabaseBackupLsn); Assert.True(currentBackup.FirstLsn >= lastBackup.LastLsn); foundDiff = true; } @@ -208,7 +208,7 @@ public void DuplicateFiles() var agDatabase = new Mock(); agDatabase.Setup(agd => agd.RecentBackups()).Returns(backups); var chain = new BackupChain(agDatabase.Object).OrderedBackups.ToList(); - Assert.Equal(chain.Count, backups.Count/2); + Assert.Equal(backups.GroupBy(b => b.PhysicalDeviceName).Count(), chain.Count); } // TODO: test skipping of logs if diff last LSN and log last LSN matches