#region Disclaimer / License // Copyright (C) 2008, Kenneth Skovhede // 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; using System.Collections.Generic; using System.Text; namespace Duplicati.CommandLine { /// /// This class encapsulates a simple method for testing the correctness of /// duplicati. /// public class UnitTest { /// /// Running the unit test confirms the correctness of duplicati /// /// The folders to backup. Folder at index 0 is the base, all others are incrementals public static void RunTest(string[] folders) { Logging.Log.CurrentLog = new Logging.StreamLog("unittest.log"); Logging.Log.LogLevel = Duplicati.Logging.LogMessageType.Profiling; using(new Logging.Timer("Total unittest")) using (Core.TempFolder tf = new Duplicati.Core.TempFolder()) { Dictionary options = new Dictionary(); options["time-separator"] = "'"; Console.WriteLine("Backing up the full copy: " + folders[0]); using (new Logging.Timer("Full backup of " + folders[0])) Duplicati.Main.Interface.Backup(folders[0], "file://" + tf, options); for (int i = 1; i < folders.Length; i++) { //If the backups are too close, we can't pick the right one :( System.Threading.Thread.Sleep(1000 * 5); Console.WriteLine("Backing up the incremental copy: " + folders[i]); using (new Logging.Timer("Incremental backup of " + folders[i])) Duplicati.Main.Interface.Backup(folders[i], "file://" + tf, options); } List entries = Duplicati.Main.Interface.ParseFileList("file://" + tf, options); if (entries.Count != 1 || entries[0].Incrementals.Count != folders.Length - 1) throw new Exception("Filename parsing problem, or corrupt storage"); List t = new List(); t.Add(entries[0]); t.AddRange(entries[0].Incrementals); entries = t; for (int i = 0; i < entries.Count; i++) { using (Core.TempFolder ttf = new Duplicati.Core.TempFolder()) { Console.WriteLine("Restoring the copy: " + folders[i]); Dictionary opts = new Dictionary(); opts["restore-time"] = entries[i].Time.ToString(); opts["time-separator"] = "'"; using (new Logging.Timer("Restore of " + folders[i])) Duplicati.Main.Interface.Restore("file://" + tf, ttf, opts); Console.WriteLine("Verifying the copy: " + folders[i]); using (new Logging.Timer("Verification of " + folders[i])) VerifyDir(System.IO.Path.GetFullPath(folders[i]), ttf); } } } (Logging.Log.CurrentLog as Logging.StreamLog).Dispose(); Logging.Log.CurrentLog = null; } /// /// Verifies the existence of all files and folders, and ensures that all /// files are binary equal. /// /// One folder /// Another folder private static void VerifyDir(string f1, string f2) { f1 = Core.Utility.AppendDirSeperator(f1); f2 = Core.Utility.AppendDirSeperator(f2); List folders1 = Core.Utility.EnumerateFolders(f1); List folders2 = Core.Utility.EnumerateFolders(f2); foreach (string s in folders1) { string relpath = s.Substring(f1.Length); string target = System.IO.Path.Combine(f2, relpath); if (!folders2.Contains(target)) Console.WriteLine("Missing folder: " + relpath); else folders2.Remove(target); } foreach(string s in folders2) Console.WriteLine("Extra folder: " + s.Substring(f2.Length)); List files1 = Core.Utility.EnumerateFiles(f1); List files2 = Core.Utility.EnumerateFiles(f2); foreach (string s in files1) { string relpath = s.Substring(f1.Length); string target = System.IO.Path.Combine(f2, relpath); if (!files2.Contains(target)) Console.WriteLine("Missing file: " + relpath); else { files2.Remove(target); if (!CompareFiles(s, target, relpath)) Console.WriteLine("File differs: " + relpath); } } foreach (string s in files2) Console.WriteLine("Extra file: " + s.Substring(f2.Length)); } /// /// Compares two files by reading all bytes, and comparing one by one /// /// One file /// Another file /// File display name /// True if they are equal, false otherwise private static bool CompareFiles(string f1, string f2, string display) { using (System.IO.FileStream fs1 = System.IO.File.OpenRead(f1)) using (System.IO.FileStream fs2 = System.IO.File.OpenRead(f2)) if (fs1.Length != fs2.Length) { Console.WriteLine("Lengths differ: " + display + ", " + fs1.Length.ToString() + " vs. " + fs2.Length.ToString()); return false; } else for (long l = 0; l < fs1.Length; l++) if (fs1.ReadByte() != fs2.ReadByte()) { Console.WriteLine("Mismatch in byte " + l.ToString() + " in file " + display); return false; } return true; } } }