xenadmin/XenAdminTests/ArchiveTests/ArchiveIteratorTests.cs
Danilo Del Busso 86fa2f6abf
CA-375900: Prepend \\?\ to file paths when creating streams for archive generation (#3184)
* CA-375900: Prepend `//?/` to file paths when creating streams for archive generation
The string works to enable creation of files with paths larger than 260 characters.
* CA-375900: Add directory support and rename utility method
* Fix whitespace in `ArchiveWriterTest`
* CA-375900: Explicitly enumerate files and directories in `ArchiveWriter`
`Directory.GetFiles` and `Directory.GetDirectories` do not enumerate if paths are longer than 260, even when prepended with `//?/`.
* CA-375900: Add long path tests to `ArchiveWriter`
* CA-375900: Add long path tests to `ArchiveIterator`
* CA-375900: Ensure files are added to folders in archive
* Use a recursive method to add directories and files to archive in `ArchiveIterator`
Also improves progress reporting by basing it on directory count
* Fix typos
* Expand `ArchiveWriterTests` to cover all combinations of directory and path lengths
* Ensure that directories used in recursive `Directory.Delete` calls are using long path format
If files in the directory exceed the 260 character limit, the calls will fail
* Expand `ArchiveIteratorTests` to cover all combinations of directory and path lengths
* Ensure relative path name removes `rootPath`
* Fix typo
* Do not use long paths when importing OVFs
The import uses `DiscUtils` which cannot handle paths prepended with `//?/`
* Remove use of `ToLongWindowsPath` within appliance export
This partially reverts commit 819425855c56c14b937849714b359003465bd2f4.
* Refactoring and some corrections.

Signed-off-by: Danilo Del Busso <danilo.delbusso@cloud.com>
Signed-off-by: Konstantina Chremmou <Konstantina.Chremmou@cloud.com>
Co-authored-by: Konstantina Chremmou <Konstantina.Chremmou@cloud.com>
2023-09-22 15:41:54 +01:00

248 lines
9.2 KiB
C#

/* Copyright (c) Cloud Software Group, Inc.
*
* Redistribution and use in source and binary forms,
* with or without modification, are permitted provided
* that the following conditions are met:
*
* * Redistributions of source code must retain the above
* copyright notice, this list of conditions and the
* following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
using System;
using System.IO;
using System.Text;
using NUnit.Framework;
using XenCenterLib;
using XenCenterLib.Archive;
namespace XenAdminTests.ArchiveTests
{
[TestFixture, Category(TestCategories.Unit)]
public class ArchiveIteratorTests
{
#region ArchiveIterator Fake
private class ArchiveIteratorFake : ArchiveIterator
{
private Stream extractedFile;
private bool disposed;
public int NumberOfCallsLeftReturn { private get; set; }
public Stream ExtractedFileReturn
{
set
{
extractedFile = value;
disposed = false;
}
}
public string CurrentFileNameReturn { get; set; }
public long CurrentFileSizeReturn { private get; set; }
public DateTime ModTimeReturn { private get; set; }
public bool IsDirectoryReturn { private get; set; }
public ArchiveIteratorFake()
{
Reset();
}
public void Reset()
{
CurrentFileNameReturn = "TestFileName.fake";
CurrentFileSizeReturn = 100;
ExtractedFileReturn = new MemoryStream(Encoding.ASCII.GetBytes("This is a test"));
IsDirectoryReturn = false;
ModTimeReturn = new DateTime(2011, 4, 1, 11, 04, 01);
NumberOfCallsLeftReturn = 1;
}
public override bool HasNext()
{
if (NumberOfCallsLeftReturn < 1)
return false;
NumberOfCallsLeftReturn--;
return true;
}
public override void ExtractCurrentFile(Stream extractedFileContents, Action cancellingDelegate)
{
byte[] buffer = new byte[2 * 1024 * 1024];
int read;
while ((read = extractedFile.Read(buffer, 0, buffer.Length)) > 0)
{
extractedFileContents.Write(buffer, 0, read);
}
extractedFile.Position = 0;
}
public override string CurrentFileName()
{
if (string.IsNullOrEmpty(CurrentFileNameReturn))
return CurrentFileNameReturn;
return CurrentFileNameReturn + NumberOfCallsLeftReturn;
}
public override long CurrentFileSize()
{
return CurrentFileSizeReturn;
}
public override DateTime CurrentFileModificationTime()
{
return ModTimeReturn;
}
public override bool IsDirectory()
{
return IsDirectoryReturn;
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposed && disposing)
extractedFile?.Dispose();
}
}
#endregion
private ArchiveIteratorFake fakeIterator;
private string tempPath;
[OneTimeSetUp]
public void Setup()
{
fakeIterator = new ArchiveIteratorFake();
}
[OneTimeTearDown]
public void TearDown()
{
fakeIterator.Dispose();
}
[SetUp]
public void TestSetup()
{
tempPath = null;
fakeIterator.Reset();
}
[TearDown]
public void TestTearDown()
{
if (Directory.Exists(tempPath))
Directory.Delete(tempPath, true);
}
[Test]
public void TestExtractToNullDestinationPath()
{
Assert.Throws(typeof(ArgumentNullException), () => fakeIterator.ExtractAllContents(null));
}
[Test]
public void TestExtractNullFile()
{
fakeIterator.CurrentFileNameReturn = null;
Assert.Throws(typeof(NullReferenceException), () => fakeIterator.ExtractAllContents(Path.GetTempPath()));
}
[TestCase(true, true)]
[TestCase(true, false)]
[TestCase(false, true)]
[TestCase(false, false)]
public void TestExtractFile(bool longDirectoryPath, bool longFilePath)
{
tempPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
var dirCharNumber = (longDirectoryPath ? 248 : 247) - tempPath.Length - 2;
//2 was removed for the combining slash between tempPath and dir, and the combining slash between dir and filename
var dir = new string('A', dirCharNumber);
var fileCharNumber = (longFilePath ? 260 : 259) - Path.Combine(tempPath, dir).Length - 2;
//2 was removed for the combining slash between the full dir path and filename, and the NumberOfCallsLeftReturn
var fileName = new string('B', fileCharNumber);
const int numberOfFiles = 3;
fakeIterator.NumberOfCallsLeftReturn = numberOfFiles;
fakeIterator.CurrentFileNameReturn = Path.Combine(dir, fileName);
fakeIterator.ExtractAllContents(tempPath);
for (var i = 0; i < 3; i++)
{
string targetFile = Path.Combine(tempPath, fakeIterator.CurrentFileNameReturn + i);
if (longDirectoryPath || longFilePath)
targetFile = StringUtility.ToLongWindowsPathUnchecked(targetFile);
Assert.IsTrue(File.Exists(targetFile), "File should exist");
Assert.IsNotEmpty(File.ReadAllBytes(targetFile), "File should not be empty");
Assert.IsFalse((File.GetAttributes(targetFile) & FileAttributes.Directory) == FileAttributes.Directory,
"It should not have directory attributes");
}
//Check recursively that there are only the correct number of files
var actualFileNumber = Directory.GetFiles(tempPath, "*.*", SearchOption.AllDirectories).Length;
Assert.AreEqual(numberOfFiles, actualFileNumber, $"There should be {numberOfFiles}");
if (longDirectoryPath || longFilePath)
tempPath = StringUtility.ToLongWindowsPathUnchecked(tempPath);
}
[TestCase(true)]
[TestCase(false)]
public void TestExtractDirectory(bool longDirectoryPath)
{
tempPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
var dirCharNumber = (longDirectoryPath ? 248 : 247) - tempPath.Length - 2;
//2 was removed for the combining slash between tempPath and dir, and the NumberOfCallsLeftReturn
var dir = new string('A', dirCharNumber);
fakeIterator.IsDirectoryReturn = true;
fakeIterator.CurrentFileNameReturn = dir;
fakeIterator.ExtractAllContents(tempPath);
string targetPath = Path.Combine(tempPath, fakeIterator.CurrentFileName());
if (longDirectoryPath)
targetPath = StringUtility.ToLongWindowsPathUnchecked(targetPath);
Assert.IsFalse(File.Exists(targetPath), "Files should not exist");
Assert.IsEmpty(Directory.GetFiles(tempPath), "Directory should not have files");
Assert.IsTrue(Directory.Exists(targetPath), "Directory should exist");
Assert.IsTrue((File.GetAttributes(targetPath) & FileAttributes.Directory) == FileAttributes.Directory,
"It should have directory attributes");
if (longDirectoryPath)
tempPath = StringUtility.ToLongWindowsPathUnchecked(tempPath);
}
}
}