--[[----------------------------------------------------------------------------

WFFileUtilsTest.lua

--------------------------------------------------------------------------------

ADOBE CONFIDENTIAL
------------------
Copyright 2014 Adobe Systems Incorporated
All Rights Reserved.

NOTICE: All information contained herein is, and remain the property of
Adobe Systems Incorporated and its suppliers, if any. The intellectual and
technical concepts contained herein are proprietary to Adobe Systems
Incorporated and its suppliers and may be covered by U.S. and Foreign
Patents, patents in process, and are protected by trade secret or copyright
law.  Dissemination of this information or reproduction of this material is
strictly forbidden unless prior written permission is obtained from
Adobe Systems Incorporated.

------------------------------------------------------------------------------]]

	-- WF.core.os
local KSData = require 'KSData'
local KSDate = require 'KSDate'
local KSIdGenerator = require 'KSIdGenerator'
local KSPathUtils = require 'KSPathUtils'
local WFFileUtils = require 'WFFileUtils'
local WFPlatformInfo = require 'WFPlatformInfo'

	-- WF.core.self_test
local WFRxTestCase = require 'WFRxTestCase'

	-- WF.core.utils
local KSTestUtils = require 'KSTestUtils'
local WFTestFileUtils = require 'WFTestFileUtils'

--------------------------------------------------------------------------------

local mustFail = assert( KSTestUtils.assertFails )
local assertEquals = assert( KSTestUtils.assertEquals )


--============================================================================--

local WFFileUtilsTest = {}

--------------------------------------------------------------------------------

local child = KSPathUtils.child

local testDataPath = child( KSPathUtils.getStandardFilePath( 'testdata' ), 'wffileutils' )

--------------------------------------------------------------------------------

local function createDstPath( rxTest )

	local dstPath = child( KSPathUtils.getStandardFilePath( 'carouselDocuments' ),
		'WFFileUtilsTestFolder_' .. KSIdGenerator.uuidInLowercaseHex() )

	pcall( WFFileUtils.mkdir, dstPath )

	rxTest:finally( function()
		WFFileUtils.delete( dstPath )
	end )

	return dstPath

end

--------------------------------------------------------------------------------

-- @mark basic file operations

--------------------------------------------------------------------------------

function WFFileUtilsTest.rxTestExists( rxTest )

	-- A straightforward name.
	rxTest:expectEqual(
		WFFileUtils.exists( child( testDataPath, 'file.txt' ) ),
		'file' )

	-- A name with spaces.
	rxTest:expectEqual(
		WFFileUtils.exists( child( testDataPath, 'File With Spaces.txt' ) ),
		'file' )

	-- A name with no extension.
	rxTest:expectEqual(
		WFFileUtils.exists( child( testDataPath, 'noextensionfile' ) ),
		'file' )

	-- A name with a long extension.
	rxTest:expectEqual( WFFileUtils.exists( child( testDataPath,
		'File_With_Very_Long_Extension.superlongfiletypeextension' ) ),
		'file' )

	-- A file inside another folder.
	rxTest:expectEqual( WFFileUtils.exists( child( testDataPath,
		child( 'bar', 'BarFile.txt' ) ) ),
		'file' )

	-- A folder.
	rxTest:expectEqual(
		WFFileUtils.exists( testDataPath ),
		'directory' )

	-- Something that doesn't exist.
	rxTest:expectEqual(
		WFFileUtils.exists( child( testDataPath, '404.txt' ) ),
		false )

end


--------------------------------------------------------------------------------

if WFPlatformInfo.isDesktop then

function WFFileUtilsTest.rxTestIsAbsolutePath( rxTest )

	-- A straightforward name.
	rxTest:expectEqual(
		WFFileUtils.isAbsolutePath( child( testDataPath, 'file.txt' ) ),
		true )

	-- A name with spaces.
	rxTest:expectEqual(
		WFFileUtils.isAbsolutePath( child( testDataPath, 'File With Spaces.txt' ) ),
		true )

	-- A name with no extension.
	rxTest:expectEqual(
		WFFileUtils.isAbsolutePath( child( testDataPath, 'noextensionfile' ) ),
		true )

	-- A name with a long extension.
	rxTest:expectEqual( WFFileUtils.isAbsolutePath( child( testDataPath,
		'File_With_Very_Long_Extension.superlongfiletypeextension' ) ),
		true )

	-- A file inside another folder.
	rxTest:expectEqual( WFFileUtils.isAbsolutePath( child( testDataPath,
		child( 'bar', 'BarFile.txt' ) ) ),
		true )

	-- A folder.
	rxTest:expectEqual(
		WFFileUtils.isAbsolutePath( testDataPath ),
		true )

	-- Relative path for file
	rxTest:expectEqual(
		WFFileUtils.isAbsolutePath( '404.txt' ),
		false )

	-- Relative path for file under a folder
	rxTest:expectEqual(
		WFFileUtils.isAbsolutePath( child('foo', '404.txt' ) ),
		false )

end

end


--------------------------------------------------------------------------------

function WFFileUtilsTest.rxTestCopy( rxTest )

	local dstPath = createDstPath( rxTest )

	-- Copy a file with a straightforward name.

	local copyFromPath = child( testDataPath, 'file.txt' )
	local copyToPath = child( dstPath, 'copiedFile.txt' )

	assert( WFFileUtils.exists( copyFromPath ) == 'file',
			"Source file missing. Test data corrupt?" )

	assert( not WFFileUtils.exists( copyToPath ), "File exists already" )

	assert( copyToPath == WFFileUtils.copy( copyFromPath, copyToPath ), "returns dest" )

	assert( WFFileUtils.exists( copyFromPath ) == 'file',
			"Source file missing. Move instead of copy?" )

	assert( WFFileUtils.exists( copyToPath ) == 'file', "File doesn't exist after copy" )

	-- If we copy a file on top of itself, it shouldn't change.

	local dstFile1 = WFFileUtils.copy( copyToPath, copyToPath )

	assert( dstFile1 == copyToPath, "Copying to same location changed path" )

	assert( WFFileUtils.exists( dstFile1 ) == 'file',
			"Copying to same path caused file to be erased" )

	-- Second attempt should fail because destination file now exists.

	mustFail( WFFileUtils.copy, child( testDataPath, 'file.txt' ), copyToPath )

	assert( WFFileUtils.exists( copyFromPath ) == 'file',
			"Source file missing. Move instead of copy on existing file?" )

	assert( WFFileUtils.exists( copyToPath ) == 'file', "File doesn't exist after fail" )

	-- Make sure we can copy a file with spaces in the name.

	copyFromPath = child( testDataPath, 'File With Spaces.txt' )
	copyToPath = child( dstPath, 'File With Spaces.txt' )

	assert( WFFileUtils.exists( copyFromPath ) == 'file',
			"Source file missing. Test data corrupt?" )

	assert( not WFFileUtils.exists( copyToPath ), "File exists already" )

	assert( copyToPath == WFFileUtils.copy( copyFromPath, copyToPath ), "return dest" )

	assert( WFFileUtils.exists( copyFromPath ) == 'file',
			"Source file missing. Move instead of copy?" )

	assert( WFFileUtils.exists( copyToPath ) == 'file', "File doesn't exist after copy" )

	-- Make sure we fail on non-existent source file.

	copyFromPath = child( testDataPath, '404.txt' )
	copyToPath = child( dstPath, '404.txt' )

	assert( not WFFileUtils.exists( copyFromPath ), "Huh? 404 doesn't mean file not found?" )

	assert( not WFFileUtils.exists( copyToPath ), "Destination file existed before copy" )

	mustFail( WFFileUtils.copy, copyFromPath, copyToPath )

	assert( not WFFileUtils.exists( copyFromPath ), "Created bogus source file?" )

	assert( not WFFileUtils.exists( copyToPath ), "Created bogus destination file?" )

	-- TODO [scouten 2014-04-23]: Do we also need recursive folder copy?
	-- Not currently supported across all platforms.

end

--------------------------------------------------------------------------------

function WFFileUtilsTest.rxTestMove( rxTest )

	local dstPath = createDstPath( rxTest )

	-- First I have to get a file into a writable area.

	local srcFile = child( dstPath, 'FileToMove.txt' )

	WFFileUtils.copy( child( testDataPath, 'file.txt' ), srcFile )

	-- Now try to move it, ensuring it doesn't already exist.

	local dstFile = child( dstPath, 'MovedFile.txt' )
	assert( not WFFileUtils.move( srcFile, dstFile ), "no return condition" )
		-- Verify that we are no longer returning a boolean true on success.
		-- We're all in on Lua errors to signal failure.

	assert( not WFFileUtils.exists( srcFile ), "source file should not exist" )

	assert( WFFileUtils.exists( dstFile ) == 'file', "moved file doesn't exist" )

	-- If we move a file on top of itself, it should be a harmless no-op.

	local dstFile1 = WFFileUtils.move( dstFile, dstFile )

	assert( dstFile == dstFile1, "Moving to same location changed path" )

	assert( WFFileUtils.exists( dstFile1 ) == 'file',
			"Moving to same path caused file to be erased" )

	-- Make sure we fail on non-existent source file.

	mustFail( WFFileUtils.move, srcFile, child( dstPath, 'MovedFile2.txt' ) )

	-- Make sure we fail if destination file exists.

	local dstFile2 = child( dstPath, 'MovedFileExistsAlready.txt' )

	WFFileUtils.copy( dstFile, dstFile2 )

	mustFail( WFFileUtils.move, dstFile, dstFile2 )

	-- TODO [scouten 2014-04-23]: Do we also need recursive folder move?
	-- Not currently supported across all platforms.

end

--------------------------------------------------------------------------------

local function testFunctionThatDeletes( rxTest, funcName )

	local dstPath = createDstPath( rxTest )

	-- Create a file so that we can delete it.

	local srcFile = child( dstPath, 'FileToDelete.txt' )

	local file = io.open( srcFile, 'w' )
	file:write( "blah blah blah" )
	file:close()

	-- Delete a file.

	assert( WFFileUtils.exists( srcFile ), "Precondition: file didn't get copied" )

	assert( not WFFileUtils[ funcName ]( srcFile ), "No return condition" )
		-- Verify that we are no longer returning a boolean true on success.
		-- We're all in on Lua errors to signal failure.

	assert( not WFFileUtils.exists( srcFile ), "File still exists after delete" )

	-- Deleting a file that doesn't exist is a harmless no-op.

	assert( not WFFileUtils[ funcName ]( srcFile ), "No return condition" )
		-- Verify that we are no longer returning a boolean true on success.
		-- We're all in on Lua errors to signal failure.

	assert( not WFFileUtils.exists( srcFile ), "File exists after second delete" )

	-- Now create a folder to delete.

	local folder = child( dstPath, 'FolderToDelete' )
	WFFileUtils.mkdir( folder )

	assert( WFFileUtils.exists( folder ) == 'directory',
		"Precondition: folder didn't get created" )

	-- And delete it.

	assert( not WFFileUtils[ funcName ]( folder ), "No return condition" )
		-- Verify that we are no longer returning a boolean true on success.
		-- We're all in on Lua errors to signal failure.

	assert( not WFFileUtils.exists( folder ), "Folder still exists after delete" )

	-- Delete it again. Harmless no-op.

	assert( not WFFileUtils[ funcName ]( folder ), "No return condition" )
		-- Verify that we are no longer returning a boolean true on success.
		-- We're all in on Lua errors to signal failure.

	assert( not WFFileUtils.exists( folder ), "Folder still exists after delete" )

	-- Now create a non-empty folder.

	local folder2 = child( dstPath, 'FolderToDelete2' )
	WFFileUtils.mkdir( folder2 )

	assert( WFFileUtils.exists( folder2 ) == 'directory',
		"Precondition: folder didn't get created" )

	local srcFile2 = child( folder2, 'FileToDelete.txt' )
	WFFileUtils.copy( child( testDataPath, 'file.txt' ), srcFile2 )

	assert( WFFileUtils.exists( srcFile2 ), "Precondition: file in folder didn't get copied" )

	-- Delete the non-empty folder.

	assert( not WFFileUtils[ funcName ]( folder2 ), "No return condition" )
		-- Verify that we are no longer returning a boolean true on success.
		-- We're all in on Lua errors to signal failure.

	assert( not WFFileUtils.exists( srcFile2 ), "File in folder exists after delete" )

	assert( not WFFileUtils.exists( folder2 ), "Folder still exists after delete" )

	-- Delete non-empty folder again. Should be OK.

	assert( not WFFileUtils[ funcName ]( folder2 ), "No return condition" )
		-- Verify that we are no longer returning a boolean true on success.
		-- We're all in on Lua errors to signal failure.

	assert( not WFFileUtils.exists( srcFile2 ), "File in folder exists after delete" )

	assert( not WFFileUtils.exists( folder2 ), "Folder still exists after delete" )

end

--------------------------------------------------------------------------------

function WFFileUtilsTest.rxTestDelete( rxTest )

	testFunctionThatDeletes( rxTest, 'delete' )

end

----------------------------------------------------------------------------------

function WFFileUtilsTest.rxTestDeleteOnlyFileAndNotDirectory( rxTest )

	local dstPath = createDstPath( rxTest )

	-- Now create a folder to delete.
	local folder = child( dstPath, 'FolderToDelete' )
	WFFileUtils.mkdir( folder )

	assert( WFFileUtils.exists( folder ) == 'directory',
		"Precondition: folder didn't get created" )

	-- And delete it.
	-- Verify that WFFileUtils.deleteFile API doesn't delete directory and signals failure through lua error.

	local success, errmsg = pcall( WFFileUtils.deleteFile, folder )
	rxTest:expectEqual( false, success )
	-- The error will also contain the Lua file and line that triggered the error
	local expected = string.format( 'deleteFile: "%s" is a directory', folder )
	assert( string.find( errmsg, expected, 1, true ) > 0, "invalid error message" )

	-- Create a file so that we can delete it.
	local srcFile = child( dstPath, 'FileToDelete.txt' )

	local file = io.open( srcFile, 'w' )
	file:write( "blah blah blah" )
	file:close()

	-- Delete a file.
	assert( WFFileUtils.exists( srcFile ), "Precondition: file didn't get copied" )
	assert( not WFFileUtils.deleteFile( srcFile ), "No return condition" )

	-- Verify that we are no longer returning a boolean true on success.
	-- We're all in on Lua errors to signal failure.
	assert( not WFFileUtils.exists( srcFile ), "File still exists after delete" )

	-- Deleting a file that doesn't exist is a harmless no-op.
	assert( not WFFileUtils.deleteFile( srcFile ), "No return condition" )

end

------------------------------------------------------------------------------

function WFFileUtilsTest.rxTestDeleteOnlyDirectoryAndNotFile( rxTest )

	local dstPath = createDstPath( rxTest )

	-- Now create a folder to delete.
	local folder = child( dstPath, 'FolderToDelete' )
	WFFileUtils.mkdir( folder )

	assert( WFFileUtils.exists( folder ) == 'directory',
		"Precondition: folder didn't get created" )

	-- Create a file so that we can try to delete it using WFFileUtils.deleteDirectory and expect error
	local srcFile = child( folder, 'FileToDelete.txt' )

	local file = io.open( srcFile, 'w' )
	file:write( "blah blah blah" )
	file:close()

	-- Verify that WFFileUtils.deleteDirectory API doesn't delete file and signals failure through lua error.
	
	local success, errmsg = pcall( WFFileUtils.deleteDirectory, srcFile )
	rxTest:expectEqual( false, success )
	-- The error will also contain the Lua file and line that triggered the error
	local expected = string.format( 'deleteDirectory: "%s" is a file', srcFile )
	assert( string.find( errmsg, expected, 1, true ) > 0, "invalid error message" )

	-- Try to delete the directory with the flag optOnlyDeleteDirIfEmpty set to true
	-- As the directory is not empty expect not to delete
	local optOnlyDeleteDirIfEmpty = true

	assert( not WFFileUtils.deleteDirectory( folder, optOnlyDeleteDirIfEmpty ), "No return condition" )

	-- verfify directory not deleted
	rxTest:expectEqual( WFFileUtils.exists( folder ), 'directory' )

	optOnlyDeleteDirIfEmpty = nil

	assert( not WFFileUtils.deleteDirectory( folder, optOnlyDeleteDirIfEmpty ), "No return condition" )

	-- verfify directory deleted now
	assert( not WFFileUtils.exists( folder ), "Folder still exists after delete" )

	-- Deleting a directory that doesn't exist is a harmless no-op.
	assert( not WFFileUtils.deleteDirectory( folder ), "No return condition" )

end

--------------------------------------------------------------------------------

function WFFileUtilsTest.rxTestDeleteEmptyDirectory( rxTest )

	local dstPath = createDstPath( rxTest )

	dstPath = child( dstPath, 'FolderToDelete' )
	WFFileUtils.mkdir( dstPath )

	-- Create a file so that we can delete it.

	local srcFile = child( dstPath, 'FileToDelete.txt' )

	local file = io.open( srcFile, 'w' )
	file:write( "blah blah blah" )
	file:close()

	-- Try to delete a directory with content

	assert( WFFileUtils.exists( srcFile ), "Precondition: file didn't get copied" )

	assert( not WFFileUtils.delete( dstPath, true ), "No return condition" )

	assert(  WFFileUtils.exists( srcFile ), "File does not exists after delete" )

	assert( WFFileUtils.exists( dstPath ), "Folder should still exist after delete" )

	-- delete all content from the directory

	assert( not WFFileUtils.delete( srcFile ), "No return condition" )

	-- Now try to delete dir

	assert( not WFFileUtils.exists( srcFile ), "File still exists after delete" )

	assert( not WFFileUtils.delete( dstPath, true ), "No return condition" )

	assert( not WFFileUtils.exists( dstPath ), "Parent folder exists after delete" )

end

--------------------------------------------------------------------------------

function WFFileUtilsTest.rxTestEmpty( rxTest )

	local dstPath = createDstPath( rxTest )

	assert( WFFileUtils.exists( dstPath ), "Dir didn't get created" )
	assert( WFFileUtils.empty( dstPath ), "Dir should be empty" )

	-- Create a file

	local srcFile = child( dstPath, 'FileToDelete.txt' )

	local file = io.open( srcFile, 'w' )
	file:write( "blah blah blah" )
	file:close()

	assert( WFFileUtils.exists( dstPath ), "Dir should still exist" )
	assert( not WFFileUtils.empty( dstPath ), "Dir should not be empty" )

	assert( not WFFileUtils.delete( srcFile ), "No return condition" )

	assert( WFFileUtils.exists( dstPath ), "Dir should still exist" )
	assert( WFFileUtils.empty( dstPath ), "Dir should be empty" )

if WFPlatformInfo.isCocoa then
	-- Create a hidden file
	srcFile = child( dstPath, '.DS_STORE' )

	file = io.open( srcFile, 'w' )
	file:write( "blah blah blah" )
	file:close()

	assert( WFFileUtils.exists( dstPath ), "Dir should still exist" )
	assert( WFFileUtils.empty( dstPath ), "Dir should be empty" )

	assert( not WFFileUtils.delete( srcFile ), "No return condition" )

	assert( WFFileUtils.exists( dstPath ), "Dir should still exist" )
	assert( WFFileUtils.empty( dstPath ), "Dir should be empty" )
end

end

--------------------------------------------------------------------------------

function WFFileUtilsTest.rxTestMoveToTrash( rxTest )

	testFunctionThatDeletes( rxTest, 'moveToTrash' )

end

--------------------------------------------------------------------------------

function WFFileUtilsTest.rxTestMkdir( rxTest )

	local dstPath = createDstPath( rxTest )

	-- Create new folder should work.

	local newDirPath = child( dstPath, 'newFolder' )

	assert( not WFFileUtils.exists( newDirPath ), "Target dir existed before test" )

	assert( not WFFileUtils.mkdir( newDirPath ), "No return condition" )
		-- Verify that we are no longer returning a boolean true on success.
		-- We're all in on Lua errors to signal failure.

	assert( WFFileUtils.exists( newDirPath ) == 'directory', "Failed to create new folder" )

	-- Create a file and verify that we fail to create a folder at same path.

	local folderFailPath = child( dstPath, 'folderFail' )

	assert( not WFFileUtils.exists( folderFailPath ), "Target file existed before test" )

	local file = io.open( folderFailPath, 'w' )
	file:write( "blah blah blah" )
	file:close()

	assert( WFFileUtils.exists( folderFailPath ) == 'file', "Failed to create file" )

	mustFail( WFFileUtils.mkdir, folderFailPath )

	assert( WFFileUtils.exists( folderFailPath ) == 'file', "File erased by mkdir" )

	-- Make sure we can create intermediate directories if needed.

	local parentPath = child( dstPath, 'sudo' )
	local nestedPath1 = child( parentPath, 'make' )
	local nestedPath2 = child( nestedPath1, 'aSandwich' )

	assert( not WFFileUtils.exists( parentPath ), "Parent path existed before test" )

	assert( not WFFileUtils.mkdir( nestedPath2 ), "No return condition" )
		-- Verify that we are no longer returning a boolean true on success.
		-- We're all in on Lua errors to signal failure.

	assert( WFFileUtils.exists( parentPath ) == 'directory', "Failed to create parent folder" )

	assert( WFFileUtils.exists( nestedPath2 ) == 'directory', "Failed to create inner folder" )

	-- Make sure we can handle deeply nested directories.

	local function makeDeepFolder( parent, numLevels )
		local path = parent
		for i = 1, numLevels do
			path = child( path, 's'..tostring(i) )
		end
		return path
	end

	-- max path is 255 so subdirectories are 3 max + delimiter so 4 chars per level
	-- use 30 deep generates a string 221 chars long.
	-- Note: unit test folder dstPath is 7 levels deep and 109 characters.

	local deepFolder = makeDeepFolder( dstPath, 30 )

	-- Make sure parent path doesn't exist.
	rxTest:expectEqual( false, WFFileUtils.exists( deepFolder ) )

	-- Create the deeply nested folder mkdir returns nothing or generates an error.
	rxTest:expectEqual( nil, WFFileUtils.mkdir( deepFolder ) )

	-- Make sure the deep folder exists.
	rxTest:expectEqual( 'directory', WFFileUtils.exists( deepFolder ) )

	if WFPlatformInfo.isWindows then

		-- Make sure we can error out if trying to create a folder
		-- on an unmounted drive.
		local testFolder = "Y:\\LightroomOriginalsForWorkPC\\Lightroom CC\\4be29d1e307e41faad08b34ccded07d6\\temp"

		rxTest:expectEqual( false, WFFileUtils.exists( testFolder ) )

		rxTest:expectEqual( false, pcall( WFFileUtils.mkdir, testFolder ) )

	end

end

--------------------------------------------------------------------------------
-- NOTE [immisch 2017-11-06]: linkFile is not supported on Android or Windows

if not WFPlatformInfo.isAndroid and not WFPlatformInfo.isWindows then

function WFFileUtilsTest.rxTestLinkFile( rxTest )

	local dstPath = createDstPath( rxTest )

	local srcFile = child( dstPath, 'uniqueFileTest.txt' )

	local file1 = WFFileUtils.ensureFileNameIsUnique( srcFile )

	-- Create that file.

	local file = io.open( file1, 'w' )
	file:write( "blah blah blah" )
	file:close()

	local file2 = WFFileUtils.ensureFileNameIsUnique( file1 )

	WFFileUtils.linkFile( file1, file2 )

	assert( WFFileUtils.exists( file2 ) )

	WFFileUtils.delete( file1 )
	assert( not WFFileUtils.exists( file1 ) )

	assert( WFFileUtils.exists( file2 ) )

	-- Read from linked file to see whether the content is still there

	file = io.open( file2, 'r' )
	local fileContent = file:read( '*a' )
	file:close()

	assert( fileContent == 'blah blah blah' )

	WFFileUtils.delete( file2 )
	assert( not WFFileUtils.exists( file2 ) )

end

end
--------------------------------------------------------------------------------

-- @mark unique file name operations

--------------------------------------------------------------------------------

function WFFileUtilsTest.rxTestEnsureFileNameIsUnique( rxTest )

	local dstPath = createDstPath( rxTest )

	-- Ask for a unique file name within our test folder.

	local srcFile = child( dstPath, 'uniqueFileTest.txt' )

	assert( not WFFileUtils.exists( srcFile ), "File existed at start of test" )

	local file1 = WFFileUtils.ensureFileNameIsUnique( srcFile )

	-- Since file didn't exist, we should get same path back.

	assert( srcFile == file1, "Didn't get same path back" )

	-- Create that file.

	local file = io.open( file1, 'w' )
	file:write( "blah blah blah" )
	file:close()

	-- Ask for another unique file name in same folder.

	local file2 = WFFileUtils.ensureFileNameIsUnique( srcFile )

	-- Make sure we got a valid response.

	assert( type( file2 ) == 'string', "Didn't get a string path back" )

	assert( file1 ~= file2, "Returned same path a second time" )

	assert( KSPathUtils.parent( file2 ) == dstPath, "File wasn't in correct folder" )

	assert( not WFFileUtils.exists( file2 ), "File already exists at this path" )

	-- Verify that we don't error out when parent dir doesn't exist.

	local bogusFile = WFFileUtils.ensureFileNameIsUnique( dstPath .. '-BOGUS' )
	assert( bogusFile == dstPath .. '-BOGUS' )

end

--------------------------------------------------------------------------------

function WFFileUtilsTest.rxTestCopyToUniquelyNamedFile( rxTest )

	local dstPath = createDstPath( rxTest )

	-- First time we copy to dest folder, name shouldn't change.

	local intendedFilePath = child( dstPath, 'file.txt' )

	assert( not WFFileUtils.exists( intendedFilePath ), "File already exists at destination" )

	local dstFile = WFFileUtils.copyToUniquelyNamedFile( child( testDataPath, 'file.txt' ),
														 intendedFilePath )

	assert( dstFile == intendedFilePath, "Renamed file when we shouldn't have" )

	assert( WFFileUtils.exists( intendedFilePath ) == 'file',
			"Didn't create copy of file as expected" )

	-- If we copy a file on top of itself, it shouldn't change.

	dstFile = WFFileUtils.copyToUniquelyNamedFile( intendedFilePath, intendedFilePath )

	assert( dstFile == intendedFilePath, "Copying to same location changed path" )

	assert( WFFileUtils.exists( intendedFilePath ) == 'file',
			"Copying to same path caused file to be erased" )
			
	-- ... unless we force the file to be duplicated
	
	dstFile = WFFileUtils.copyToUniquelyNamedFile( intendedFilePath, intendedFilePath, false, true )
	
	local intendedFilePath2 = child( dstPath, 'file-2.txt' )
	
	assert( dstFile == intendedFilePath2, "Did not duplicate the file" )

	assert( WFFileUtils.exists( intendedFilePath2 ) == 'file',
			"Copying to same path caused file to be erased" )

	-- Make same copy again. This time, the file name should change since there's
	-- already a file at that location.

	local dstFile2 = WFFileUtils.copyToUniquelyNamedFile( child( testDataPath, 'file.txt' ),
														  intendedFilePath )

	assert( dstFile2 ~= intendedFilePath, "Didn't choose a new name" )

	assert( KSPathUtils.parent( dstFile2 ) == dstPath, "Didn't create copy in same folder" )

	assert( WFFileUtils.exists( dstFile2 ) == 'file',
			"Didn't create copy of file as expected" )

	-- Make a third copy. This time, name shouldn't match eithe rof the first two names.

	local dstFile3 = WFFileUtils.copyToUniquelyNamedFile( child( testDataPath, 'file.txt' ),
														  intendedFilePath )

	assert( dstFile3 ~= intendedFilePath, "Didn't choose a new name" )
	assert( dstFile3 ~= dstFile2, "Didn't choose a new name" )

	assert( KSPathUtils.parent( dstFile3 ) == dstPath, "Didn't create copy in same folder" )

	assert( WFFileUtils.exists( dstFile3 ) == 'file',
			"Didn't create copy of file as expected" )

end

--------------------------------------------------------------------------------

function WFFileUtilsTest.rxTestCopyToUniquelyNamedFileWhenSourceIsFolder( rxTest )

	rxTest:setTimeLimit( 10 )

	local dstFolder = createDstPath( rxTest )

	local src = testDataPath
	local dst = child( dstFolder, 'newFolder' )

	local success, result = pcall( WFFileUtils.copyToUniquelyNamedFile, src, dst )

	if WFPlatformInfo.isWindows or WFPlatformInfo.isAndroid then

		assert( not success, "folder copy is not supported"  )

	else

		assert( success, "Failed to copy folder" )

		assert( WFFileUtils.exists( result ) == 'directory', "Didn't create copy of directory" )

		local fileInDst = child( result, 'file.txt' )
		assert( WFFileUtils.exists( fileInDst ) == 'file', "Didn't copy contents of directory" )

	end

end

--------------------------------------------------------------------------------

local function makeFilePathWithLongestLegalName( inFolder )

	local testFilePath
	local leafNameLength = 257
		-- Start with a length that is too long for any operating system.

	repeat

		leafNameLength = leafNameLength - 1

		if leafNameLength == 0 then
			error( "unable to create test file" )
		end

		local testFileName = string.rep( 'a', leafNameLength ) .. '.jpg'
		testFilePath = child( inFolder, testFileName )

		local testFile = io.open( testFilePath, 'w' )
		if testFile then
			testFile:close()
			WFFileUtils.delete( testFilePath )
		end

	until testFile

	return testFilePath

end

--------------------------------------------------------------------------------

function WFFileUtilsTest.rxTestCopyToUniquelyNamedFileSendsErrorOnFailure( rxTest )

	rxTest:setTimeLimit( 10 )

	local dstFolder = createDstPath( rxTest )

	local src = child( testDataPath, 'file.txt' )
	local dst = makeFilePathWithLongestLegalName( dstFolder )

	-- First copy to dest folder.

	assert( WFFileUtils.copyToUniquelyNamedFile( src, dst ) == dst )

	-- Second copy should fail as it's too long to be made unique.

	assert( WFFileUtils.exists( dst ) == 'file', "Didn't create copy of file" )

	local success, _ = pcall( WFFileUtils.copyToUniquelyNamedFile, src, dst )

	assert( not success, "shouldn't be able to make a new copy because file name is too long" )

end

--------------------------------------------------------------------------------

function WFFileUtilsTest.rxTestMoveToUniquelyNamedFile( rxTest )

	local dstPath = createDstPath( rxTest )

	-- First copy file into a writable area so subsequent moves don't mess up
	-- our original data.

	local srcFile = WFFileUtils.copyToUniquelyNamedFile( child( testDataPath, 'file.txt' ),
														 child( dstPath, 'FileToMove.txt' ) )

	-- Now try to move it, ensuring it doesn't already exist.

	local dstFile = WFFileUtils.moveToUniquelyNamedFile( srcFile, child( dstPath, 'MovedFileUnique.txt' ) )

	assert( not WFFileUtils.exists( srcFile ), "Source file should not exist" )
	assert( dstFile == child( dstPath, 'MovedFileUnique.txt' ), "Moved filename is incorrect" )
	assert( WFFileUtils.exists( dstFile ), "Moved file doesn't exist" )

	-- If we move a file on top of itself, it should be a harmless no-op.

	local dstFile1 = WFFileUtils.moveToUniquelyNamedFile( dstFile, dstFile )

	assert( dstFile == dstFile1, "Moving to same location changed path" )

	assert( WFFileUtils.exists( dstFile1 ) == 'file',
			"Moving to same path caused file to be erased" )

	-- Try the same move again. This time it should fail because source doesn't exist.

	mustFail( WFFileUtils.moveToUniquelyNamedFile, srcFile, child( dstPath, 'MovedFileUnique.txt' ) )

	-- Create a temporary file to induce a collision.

	local existingFile = child( dstPath, 'DontOverwriteMe.txt' )

	local file = io.open( existingFile, 'w' )
	file:write( "blah blah blah" )
	file:close()

	-- Move the file to collide with that temporary file. We should rename to avoid
	-- that collision.

	local dstFile2 = WFFileUtils.moveToUniquelyNamedFile( dstFile, existingFile )

	assert( dstFile ~= dstFile2, "File name should have changed" )
	assert( dstFile ~= existingFile, "Should have avoided existing file" )

	assert( WFFileUtils.exists( existingFile ) == 'file', "Existing file still exists" )
	assert( WFFileUtils.exists( dstFile2 ) == 'file', "Moved file exists at reported new location" )
	assert( KSPathUtils.parent( dstFile2 ) == dstPath, "Moved file is in wrong folder" )
	assert( not WFFileUtils.exists( dstFile ), "Moved file still exists at original location" )

end

--------------------------------------------------------------------------------

-- @mark file attributes

--------------------------------------------------------------------------------

function WFFileUtilsTest.rxTestFileAttributes( rxTest )

	local expectedValueTypes = {
		fileSize = 'number',
		fileCreationDate = 'number',
		fileCreationDateTimezoneOffset = 'number',
		fileModificationDate = 'number',
		fileModificationDateTimezoneOffset = 'number',
	}

	local attribs = WFFileUtils.fileAttributes( child( testDataPath, 'file.txt' ) )

	assert( type( attribs ) == 'table', "Result should be a table" )

	for k, expectedType in pairs( expectedValueTypes ) do

		if type( attribs[ k ] ) ~= expectedType then
			error( "File attribute " .. k .. " is incorrect type "
				   .. type( attribs[ k ] ) )
		end

	end

	assert( attribs.fileSize == 5, string.format( "fileSize is wrong: expected %s, received %s", tostring( 5 ), tostring( attribs.fileSize ) ) )

	-- We claim that the function returns an empty table if no such file.
	-- Verify that claim.

	local noSuchFile = child( testDataPath, '404.txt' )

	assert( not WFFileUtils.exists( noSuchFile ), "I thought 404 meant not found!" )

	local nsfAttribs = WFFileUtils.fileAttributes( noSuchFile )

	assert( type( nsfAttribs ) == 'table', "Result should be a table" )
	assert( not next( nsfAttribs ), "Table should be empty for no such file" )

end

--------------------------------------------------------------------------------

function WFFileUtilsTest.rxTestSetFileAttributes( rxTest )

	local testFile = KSPathUtils.child( KSPathUtils.getStandardFilePath( 'temp' ),
										'testSetFileAttributes.txt' )

	testFile = WFFileUtils.ensureFileNameIsUnique( testFile )

	local file = io.open( testFile, 'w' )
	file:write( 'WFFileUtilsTest.testSetFileAttributes\n' )
	file:close()

	rxTest:finally( function()
		WFFileUtils.delete( testFile )
	end )

	local bDate = '2003-03-08T00:30:00'
	local wDate = '2005-05-27T13:30:00'

	assert( not WFFileUtils.setFileAttributes( testFile,
		{
			fileCreationDate = bDate,
			fileModificationDate = wDate,
		} ), "no return condition" )
		-- Verify that we are no longer returning a boolean true on success.
		-- We're all in on Lua errors to signal failure.

	local fileAttribs = WFFileUtils.fileAttributes( testFile )

	assert( type( fileAttribs.fileModificationDate ) == 'number' )
	assert( type( fileAttribs.fileModificationDateTimezoneOffset ) == 'number' )

	local modDate = KSDate.formatDateTimeAsUTC( fileAttribs.fileModificationDate
										+ fileAttribs.fileModificationDateTimezoneOffset )

	assert( modDate == wDate, modDate .. " != " .. wDate )

	if not WFPlatformInfo.isAndroid then
		-- Android does not support creation date.

		assert( type( fileAttribs.fileCreationDate ) == 'number' )
		assert( type( fileAttribs.fileCreationDateTimezoneOffset ) == 'number' )

		local createDate = KSDate.formatDateTimeAsUTC( fileAttribs.fileCreationDate
											+ fileAttribs.fileCreationDateTimezoneOffset )

		assert( createDate == bDate, createDate .. " != " .. bDate )

	end

end

--------------------------------------------------------------------------------

function WFFileUtilsTest.rxTestFileSystemAttributes( rxTest )

	local expectedValueTypes = {
		fileSystemSize = 'number',
		fileSystemFreeSize = 'number',
		fileSystemNumber = 'number',
	}

	-- Directory Test

	local attribs = WFFileUtils.fileSystemAttributes( testDataPath )

	assert( type( attribs ) == 'table', "Result should be a table" )

	for k, expectedType in pairs( expectedValueTypes ) do

		if type( attribs[ k ] ) ~= expectedType then
			error( "File attribute " .. k .. " is incorrect type "
				   .. type( attribs[ k ] ) )
		end

	end

	-- File Test

	local actualFileOnDisk = child( testDataPath, 'file.txt' )

	assert( WFFileUtils.exists( actualFileOnDisk ) == 'file',
			"Source file missing. Test data corrupt?" )

	attribs = WFFileUtils.fileSystemAttributes( actualFileOnDisk )

	assert( type( attribs ) == 'table', "Result should be a table" )

	for k, expectedType in pairs( expectedValueTypes ) do

		if type( attribs[ k ] ) ~= expectedType then
			error( "File attribute " .. k .. " is incorrect type "
				   .. type( attribs[ k ] ) )
		end

	end

end

--------------------------------------------------------------------------------

function WFFileUtilsTest.rxTestFreeSpaceAtPath( rxTest )

	local actualFileOnDisk = child( testDataPath, 'file.txt' )

	assert( WFFileUtils.exists( actualFileOnDisk ) == 'file',
			"Source file missing. Test data corrupt?" )

	local freeSpace, totalSpace = WFFileUtils.freeSpaceAtPath( actualFileOnDisk )

	assert( type( freeSpace ) == 'number', "Free space should be a number" )
	assert( freeSpace > 0, "Free space should be >0" )

	assert( type( totalSpace ) == 'number', "Total space should be a number" )
	assert( totalSpace > 0, "Total space should be >0" )

end

--------------------------------------------------------------------------------

function WFFileUtilsTest.rxTestSetDoNotBackUpFlag( rxTest )

	-- Can't really verify that this did anything. We can verify that it doesn't
	-- crash and doesn't return any value.

	local result = WFFileUtils.setDoNotBackUpFlag( child( testDataPath, 'file.txt' ) )

	assert( result == nil, "Should not return a value" )

end

--------------------------------------------------------------------------------

-- @mark Alias resolution

--------------------------------------------------------------------------------
if WFPlatformInfo.isMac then

function WFFileUtilsTest.rxTestResolveAliases( rxTest )

	-- If we point this at a folder that doesn't have any aliases in the path,
	-- it should just return the same string. If we point it at an alias it
	-- should return the path of the original folder.

	local dstPath = createDstPath( rxTest )

	-- first test the degenerate case (calling for a path with no alias)
	local result = WFFileUtils.resolveAliasesForPath( dstPath )
	assert( result == dstPath, "Returned something other than original path\n('"..result.."' ~=\n '"..dstPath.."')" )

	-- now create an alias and test that

	local aliasPath = createDstPath( rxTest )

	rxTest:finally( function()
		WFFileUtils.delete( aliasPath )
		-- in some failing circumstances, what happens is that the alias actually gets created as "file.txt alias 2"
		-- and this doesn't automatically get cleaned up and so we need to make sure it does here
		for i = 1, 10, 1 do
			WFFileUtils.delete( string.format( "%s %s", child( aliasPath, 'myAlias' ), i ) )
		end
	end )

	local destFolder = dstPath
	local parentFolder = aliasPath
	local name = 'myAlias'

	WFFileUtils._DEBUG_createAliasForPath( destFolder, parentFolder, name )

	local result2 = WFFileUtils.resolveAliasesForPath( child( aliasPath, 'myAlias' ) )

	assert( result2 == dstPath, "Returned something other than original path\n('"..result2.."' ~=\n '"..dstPath.."')" )

end

end

--------------------------------------------------------------------------------

function WFFileUtilsTest.rxTestCalculatePathSize( rxTest )

	local dstPath = createDstPath( rxTest )

	local pathSize = WFFileUtils.calculatePathSize( dstPath )
	assertEquals( rxTest, "Expected size of empty directory to be 0" )( 0 ) ( pathSize )

	-- add a file to the dstPath
	WFFileUtils.copyToUniquelyNamedFile( child( testDataPath, 'file.txt' ),
											child( dstPath, 'file.txt' ) )

	-- re calculate directory size
	pathSize = WFFileUtils.calculatePathSize( dstPath )
	assertEquals( rxTest, "Expected size of directory to be 5" )( 5 ) ( pathSize )

	-- add a sub directory to dstPath
	local subDir = child( dstPath, 'subDir' )
	WFFileUtils.mkdir( subDir )

	-- add a file to sub directory
	-- add a file to the dstPath
	local srcFile2 = WFFileUtils.copyToUniquelyNamedFile( child( testDataPath, 'file.txt' ),
														  child( subDir, 'file.txt' ) )

	-- re calculate directory size
	pathSize = WFFileUtils.calculatePathSize( dstPath )
	assertEquals( rxTest, "Expected size of directory to be 10" )( 10 ) ( pathSize )

	-- calculate directory size of non-existent dir ( 0 )
	local nonexistentDir = child( dstPath, 'dontcreate' )

	pathSize = WFFileUtils.calculatePathSize( nonexistentDir )
	assertEquals( rxTest, "Expected size of non-existent directory to be 0" )( 0 ) ( pathSize )

	-- calculate directory size of file instead of dir (file size)
	pathSize = WFFileUtils.calculatePathSize( srcFile2 )
	assertEquals( rxTest, "Expected size of file to be 5" )( 5 ) ( pathSize )

end

--------------------------------------------------------------------------------

-- @mark volume operations

--------------------------------------------------------------------------------

function WFFileUtilsTest.rxTestVolumeForPath( rxTest )

	local actualVolume = WFFileUtils.volumeForPath( testDataPath )

	local expectedVolume

	if WFPlatformInfo.isMac then
		expectedVolume = "/"
		rxTest:expectEqual( expectedVolume, actualVolume )
	elseif WFPlatformInfo.isWindows then
		--expectedVolume = "C:" -- Not all build machines use `C`
		rxTest:expectEqual( string.len( actualVolume ), 2 )
		rxTest:expectEqual( string.sub( actualVolume, -1 ), ':' )
	end

end

--------------------------------------------------------------------------------

function WFFileUtilsTest.rxTestIsPathOnOfflineVolume( rxTest )

	rxTest:expectEqual( false, WFFileUtils.isPathOnOfflineVolume( testDataPath ) )

	rxTest:expectEqual( false, WFFileUtils.isPathOnOfflineVolume() )

	if WFPlatformInfo.isMac then
		rxTest:expectEqual( true, WFFileUtils.isPathOnOfflineVolume( '/Volumes/foo' ) )
	elseif WFPlatformInfo.isWindows then
		rxTest:expectEqual( true, WFFileUtils.isPathOnOfflineVolume( 'Q:' ) )
	end

end

--------------------------------------------------------------------------------

function WFFileUtilsTest.rxTestRemoveVolumeFromPath( rxTest )

	local path = '/Volumes/Foo/a/b/c.d'
	local newPath = WFFileUtils.removeVolumeFromPath( path )
	rxTest:expectEqual( '/a/b/c.d', newPath )

	path = '/foo/a/b/c.d'
	newPath = WFFileUtils.removeVolumeFromPath( path )
	rxTest:expectEqual( 'foo/a/b/c.d', newPath )

	path = 'c:\\foo\\a\\b\\c.d'
	newPath = WFFileUtils.removeVolumeFromPath( path )
	rxTest:expectEqual( '\\foo\\a\\b\\c.d', newPath )

end

--------------------------------------------------------------------------------

function WFFileUtilsTest.rxTestVolumeGuidsBasic( rxTest )

	local volumeGuid = WFFileUtils.getVolumeGuidForPath( testDataPath )
	local volumeMountPoint = WFFileUtils.getVolumeFromGuid( volumeGuid )

	rxTest:expectEqual( WFFileUtils.volumeForPath( testDataPath ):lower(), volumeMountPoint:lower() )

	local testPath = WFFileUtils.removeVolumeFromPath( testDataPath )

	local builtPath = WFFileUtils.createFullPathFromRelativePathAndGuid( testPath, volumeGuid )

	rxTest:expectEqual( testDataPath:lower(), builtPath:lower() )

	local volumeName = WFFileUtils.getVolumeNameFromGuid( volumeGuid )

	rxTest:expectEqual( type( volumeName ), "string" )

	-- Can't actually test the volume name here, since it's device dependent.

	--[[local thumbGuid = WFFileUtils.getVolumeGuidForPath( '/Volumes/FOO' )
	local thumbMain = WFFileUtils.getVolumeFromGuid( thumbGuid )
	rxTest:expectEqual( '/Volumes/FOO', thumbMain )]]

end

--------------------------------------------------------------------------------

--[[

-- SPAM [jandrew 2018-01-16]: Enable this test for testing NAS functionality.

function WFFileUtilsTest.rxTestVolumeGuidsNAS( rxTest )

	local nasPath = 'Z:\\Foo'

	local volumeGuid = WFFileUtils.getVolumeGuidForPath( nasPath )

	rxTest:expectNotEqual( false, not not volumeGuid )

	local volumeName = WFFileUtils.getVolumeNameFromGuid( volumeGuid )

	rxTest:expectEqual( type( volumeName ), "string" )

	local volumeMountPoint = WFFileUtils.getVolumeFromGuid( volumeGuid )

	rxTest:expectNotEqual( false, not not volumeMountPoint )

	local volumeForNasPath = WFFileUtils.volumeForPath( nasPath )

	rxTest:expectEqual( volumeForNasPath:lower(), volumeMountPoint:lower() )

	local testPath = WFFileUtils.removeVolumeFromPath( nasPath )

	local builtPath = WFFileUtils.createFullPathFromRelativePathAndGuid( testPath, volumeGuid )

	rxTest:expectEqual( nasPath:lower(), builtPath:lower() )

end
]]

--------------------------------------------------------------------------------

--[[
-- SPAM [kcarlson 2021-05-07]: Update and enable all these tests when making
-- changes to the volume related functions.
-- Make sure to test drives without a persistnant volume guid (NTFSForMac is
-- an example).  Also test with network drive offline and online.

local function getVolumeGuidFuncResults( guidOrMountPoint, relativePath, pathToTest )

	local pathOnOfflineVolume = WFFileUtils.isPathOnOfflineVolume( pathToTest )
	local volumeGuidForPath = WFFileUtils.getVolumeGuidForPath( pathToTest )
	local volumeForPath = WFFileUtils.volumeForPath( pathToTest )
	local volumeRemovedFromPath = WFFileUtils.removeVolumeFromPath( pathToTest )

	local volumeGuidOnline = WFFileUtils.isVolumeGuidOnline( guidOrMountPoint )
	local volumeName = WFFileUtils.getVolumeNameFromGuid( guidOrMountPoint )
	local volumeMountPoint = WFFileUtils.getVolumeFromGuid( guidOrMountPoint )
	local pathFromRelativePathAndGuid = WFFileUtils.createFullPathFromRelativePathAndGuid( relativePath, guidOrMountPoint )

	return {
		volumeGuidForPath = volumeGuidForPath,
		volumeGuidOnline = volumeGuidOnline,
		volumeName = volumeName,
		volumeMountPoint = volumeMountPoint,
		volumeRemovedFromPath = volumeRemovedFromPath,
		volumeForPath = volumeForPath,
		pathOnOfflineVolume = pathOnOfflineVolume,
		pathFromRelativePathAndGuid = pathFromRelativePathAndGuid,
	}

end

--------------------------------------------------------------------------------

-- Obtain guid from terminal via 'diskutil info /' and set of value of 'APFS Volume Group:'
local rootVolumeGuid = '83702090-B2B3-4D4E-A521-DDA85C985E9D'

function WFFileUtilsTest.rxTestVolumeGuidsMac_RootVolume( rxTest )

	rxTest:expectEqual( {
		volumeGuidForPath = rootVolumeGuid,
		volumeName = 'Macintosh HD',
		volumeMountPoint = '/',
		volumeGuidOnline = true,
		pathOnOfflineVolume = false,
		volumeForPath = '/',
		volumeRemovedFromPath = 'test',
		pathFromRelativePathAndGuid = '/test',
	}, getVolumeGuidFuncResults( rootVolumeGuid, 'test', '/test' ) )

end

--------------------------------------------------------------------------------

function WFFileUtilsTest.rxTestVolumeGuidsMac_RootVolumeByName( rxTest )

	rxTest:expectEqual( {
		volumeGuidForPath = rootVolumeGuid,
		volumeName = 'Macintosh HD',
		volumeMountPoint = '/',
		volumeGuidOnline = true,
		pathOnOfflineVolume = false,
		volumeForPath = '/Volumes/Macintosh HD',
		volumeRemovedFromPath = '/test',
		pathFromRelativePathAndGuid = '/test',
	}, getVolumeGuidFuncResults( rootVolumeGuid, 'test', '/Volumes/Macintosh HD/test' ) )

end

--------------------------------------------------------------------------------

function WFFileUtilsTest.rxTestVolumeGuidsMac_NTFS_Volume( rxTest )

	-- This test validates we work correctly with databases that now have the
	-- volumeGuid saved as /Volumes/BOOTCAMP instead the invalid guid.

	-- Enable this test after installing Paragon Software's NTFS for Mac
	-- software, and then mounting your BOOTCAMP drive.
	-- Note this software is provided by Western Digitial for drives formatted with NTFS.
	rxTest:expectEqual( {
		volumeGuidForPath = '/Volumes/BOOTCAMP',
		volumeName = '/Volumes/BOOTCAMP',
		volumeMountPoint = '/Volumes/BOOTCAMP',
		volumeGuidOnline = true,
		pathOnOfflineVolume = false,
		volumeRemovedFromPath = '/test',
		volumeForPath = '/Volumes/BOOTCAMP',
		pathFromRelativePathAndGuid = '/Volumes/BOOTCAMP/test',
	}, getVolumeGuidFuncResults( '/Volumes/BOOTCAMP', 'test', '/Volumes/BOOTCAMP/test' ) )

end

--------------------------------------------------------------------------------

function WFFileUtilsTest.rxTestVolumeGuidsMac_NTFS_DeviceNode( rxTest )

	-- This test validates we work correctly with databases that have unfortunately
	-- saved original file paths with volumeGuid = /dev/disk***

	-- Enable this test after installing Paragon Software's NTFS for Mac
	-- software, and then mounting your BOOTCAMP drive.
	-- Note this software is provided by Western Digitial for drives formatted with NTFS.

	-- Obtain device node for bootcamp from terminal via 'diskutil info BOOTCAMP' and set to value of 'Device Node:'
	local deviceNodeForBootcamp = '/dev/disk0s4'

	-- Demonstrates mounting of 4.2 and earlier drives works properly with a device node.
	rxTest:expectEqual( {
		volumeGuidForPath = '/Volumes/BOOTCAMP',
		volumeName = '/Volumes/BOOTCAMP',
		volumeMountPoint = '/Volumes/BOOTCAMP',
		volumeForPath = '/Volumes/BOOTCAMP',
		volumeGuidOnline = true,
		pathOnOfflineVolume = false,
		volumeRemovedFromPath = '/test',
		pathFromRelativePathAndGuid = '/Volumes/BOOTCAMP/test',
	}, getVolumeGuidFuncResults( deviceNodeForBootcamp, 'test', '/Volumes/BOOTCAMP/test' ) )

end

--------------------------------------------------------------------------------

function WFFileUtilsTest.rxTestVolumeGuidsMac_OnlineNetworkVolume( rxTest )

	-- test SJStore
	rxTest:expectEqual( {
		volumeGuidForPath = '//kcarlson@sjshare.corp.adobe.com/users_k',
		volumeName = '/Volumes/users_k',
		volumeMountPoint = '/Volumes/users_k',
		volumeGuidOnline = true,
		pathOnOfflineVolume = false,
		volumeForPath = '/Volumes/users_k',
		volumeRemovedFromPath = '/test',
		pathFromRelativePathAndGuid = '/Volumes/users_k/test',
	}, getVolumeGuidFuncResults( '//kcarlson@sjshare.corp.adobe.com/users_k', 'test', '/Volumes/users_k/test' ) )

end

--------------------------------------------------------------------------------

function WFFileUtilsTest.rxTestVolumeGuidsMac_OfflineNetworkVolume( rxTest )

	-- test SJStore unmounted
	rxTest:expectEqual( {
		volumeGuidForPath = false, -- '//kcarlson@sjshare.corp.adobe.com/users_k',
		volumeName = nil, -- '/Volumes/users_k',
		volumeMountPoint = nil, -- '/Volumes/users_k',
		volumeGuidOnline = false,
		pathOnOfflineVolume = true,
		volumeForPath = '/Volumes/users_k',
		volumeRemovedFromPath = '/test',
		pathFromRelativePathAndGuid = nil, -- '/Volumes/users_k/test',
	}, getVolumeGuidFuncResults( '//kcarlson@sjshare.corp.adobe.com/users_k', 'test', '/Volumes/users_k/test' ) )

end
]]

--------------------------------------------------------------------------------

function WFFileUtilsTest.rxTestIsMountedDMG( rxTest )

	local shouldBeFalse = WFFileUtils.isMountedDMG( testDataPath )

	rxTest:expectEqual( shouldBeFalse, false )

	-- Can't actually test external volumes or mounted volumes, since we can't recreate
	-- such states on the test machines.

	--[[shouldBeFalse = WFFileUtils.isMountedDMG( '/Volumes/FOO' )

	rxTest:expectEqual( shouldBeFalse, false )

	local shouldBeTrue = WFFileUtils.isMountedDMG( '/Volumes/Install DB4S Nightly build' )

	rxTest:expectEqual( shouldBeTrue, true )]]

end

--------------------------------------------------------------------------------

function WFFileUtilsTest.rxTestIsVolumeReadOnly( rxTest )

	local volumeGuid = WFFileUtils.getVolumeGuidForPath( testDataPath )

	local shouldBeFalse = WFFileUtils.isVolumeReadOnly( volumeGuid )

	rxTest:expectEqual( shouldBeFalse, false )

	-- Can't actually test external volumes or mounted volumes, since we can't recreate
	-- such states on the test machines.

	--[[local readOnlyVolumeGuid = WFFileUtils.getVolumeGuidForPath( '/Volumes/KINGSTON' )

	rxTest:expectEqual( WFFileUtils.isVolumeReadOnly( readOnlyVolumeGuid ), true )]]

end

--------------------------------------------------------------------------------

function WFFileUtilsTest.rxTestIsCDROM( rxTest )

	local shouldBeFalse = WFFileUtils.isCDROM( testDataPath )

	rxTest:expectEqual( shouldBeFalse, false )

	-- Can't actually test external volumes or CDROM drives, since we can't recreate
	-- such states on the test machines.

	--[[shouldBeFalse = WFFileUtils.isMountedDMG( 'E:\Nimbus' )

	rxTest:expectEqual( shouldBeFalse, false )

	local shouldBeTrue = WFFileUtils.isMountedDMG( 'CDROM' )

	rxTest:expectEqual( shouldBeTrue, true )]]

end

--------------------------------------------------------------------------------

function WFFileUtilsTest.rxTestIsVolumeGuidOnline( rxTest )

	local volumeGuid = WFFileUtils.getVolumeGuidForPath( testDataPath )

	rxTest:expectEqual( true, WFFileUtils.isVolumeGuidOnline( volumeGuid ) )

	rxTest:expectEqual( false, WFFileUtils.isVolumeGuidOnline( 'foo' ) )

end

--------------------------------------------------------------------------------

if WFPlatformInfo.isDesktop then
	-- these tests only work on desktop oses

	function WFFileUtilsTest.rxTestSaveToFile( rxTest )

		local dstPath = createDstPath( rxTest )
		local saveToFilePath = child( dstPath, 'saveToFile.txt' )
		local testData = "this is a test"

		WFFileUtils.saveToFile( saveToFilePath, testData )

		rxTest:finally( function()
			WFFileUtils.delete( saveToFilePath )
		end )

		local file = io.open( saveToFilePath, 'r' )
		local fileContent = file:read( '*a' )
		file:close()

		rxTest:expectEqual( testData, fileContent )

	end

	--------------------------------------------------------------------------------

	function WFFileUtilsTest.rxTestGetFileContents( rxTest )

		local dstPath = createDstPath( rxTest )
		local getFileContentsPath = child( dstPath, 'getFileContents.txt' )
		local testData = "this is a test"

		local file = io.open( getFileContentsPath, 'w' )
		file:write( testData )
		file:close()

		rxTest:expectEqual( testData, WFFileUtils.getFileContents( getFileContentsPath ) )

	end

end

--------------------------------------------------------------------------------

if WFPlatformInfo.isAndroid then
	-- these tests only work on Android

	function WFFileUtilsTest.rxTestCopyToUniquelyNamedFileUsingFileDescriptor( rxTest )

		local dstPath = createDstPath( rxTest )
		local srcPath = child( testDataPath, 'file.txt' )
		local srcFD = WFFileUtils.getReadOnlyFileDescriptor( srcPath )

		-- First time we copy to dest folder, name shouldn't change.

		local intendedFilePath = child( dstPath, 'file.txt' )

		assert( not WFFileUtils.exists( intendedFilePath ), "File already exists at destination" )

		local dstFile = WFFileUtils.copyToUniquelyNamedFileUsingFileDescriptor( srcFD,
															 intendedFilePath )

		assert( dstFile == intendedFilePath, "Renamed file when we shouldn't have" )

		assert( WFFileUtils.exists( intendedFilePath ) == 'file',
				"Didn't create copy of file as expected" )


		-- Make same copy again. This time, the file name should change since there's
		-- already a file at that location.

		srcFD = WFFileUtils.getReadOnlyFileDescriptor( srcPath )
		local dstFile2 = WFFileUtils.copyToUniquelyNamedFileUsingFileDescriptor( srcFD,
															  intendedFilePath )

		assert( dstFile2 ~= intendedFilePath, "Didn't choose a new name" )

		assert( KSPathUtils.parent( dstFile2 ) == dstPath, "Didn't create copy in same folder" )

		assert( WFFileUtils.exists( dstFile2 ) == 'file',
				"Didn't create copy of file as expected" )

		end

end

--------------------------------------------------------------------------------

-- @mark Monitor File System Changes

--------------------------------------------------------------------------------

function WFFileUtilsTest.rxTestGetDirectoryUpdatesSince( rxTest )

	-- This test verifies that getDirectoryUpdatesSince can detect updates that
	-- occured to files within a directory since some given point in time.

	rxTest:setTimeLimit( 10 )

	-- Set up some directories for this test

	local testDirectory = WFTestFileUtils.createTestFolder( rxTest )

	local nestedDirectory = KSPathUtils.child( testDirectory, KSIdGenerator.uuidInLowercaseHex() )

	WFFileUtils.mkdir( nestedDirectory )

	local doubleNestedDirectory = KSPathUtils.child( nestedDirectory, KSIdGenerator.uuidInLowercaseHex() )

	WFFileUtils.mkdir( doubleNestedDirectory )

	-- Set up a helper function for creating test files.

	local fileData = KSData.make( 'foo' )

	local function createPathAndSaveFile( optParent )

		local path = KSPathUtils.child( optParent or testDirectory, KSIdGenerator.uuidInLowercaseHex() )

		KSData.saveToFile( fileData, path )

		return path

	end

	-- First create a few files to detect updates/deletions

	local updatedPath = createPathAndSaveFile()

	local deletedPath = createPathAndSaveFile()

	-- Create some static files that will not be updated.

	createPathAndSaveFile()
	createPathAndSaveFile()
	createPathAndSaveFile()

	createPathAndSaveFile( nestedDirectory )
	createPathAndSaveFile( nestedDirectory )
	createPathAndSaveFile( nestedDirectory )
	
	createPathAndSaveFile( doubleNestedDirectory )
	createPathAndSaveFile( doubleNestedDirectory )
	createPathAndSaveFile( doubleNestedDirectory )

	-- Get the current time

	rxTest:sleep( 2 )
		-- Wait a few seconds to increase test stability/reliablity.

	local startTime = KSDate.timeInIso8601Format()

	rxTest:sleep( 2 )
		-- Wait a few more seconds to increase test stability/reliablity.

	-- Create a file AFTER the start time to verify we detect file creation.

	local createdPath = createPathAndSaveFile()

	-- Create a file AFTER the start time within a nested directory to verify recursion support.

	local nestedCreatedPath = createPathAndSaveFile( nestedDirectory )

	-- Create a file AFTER the start time within a nested directory to verify deeper recursion support.

	local doubleNestedCreatedPath = createPathAndSaveFile( doubleNestedDirectory )

	-- Modify the file located at updatedPath to verify we detect updates.

	KSData.saveToFile( KSData.make( 'bar' ), updatedPath )

	-- Delete the file located at deletedPath. We aren't going to see this deletion,
	-- and we're not supposed to.

	WFFileUtils.delete( deletedPath )

	-- Get all updated files since startTime

	local actual = WFFileUtils.getDirectoryUpdatesSince( testDirectory, startTime )

	-- Make sure we receive the update and creation events, and that we
	-- do not see the static files that have not changed.

	local expected = {
		createdPath,
		nestedCreatedPath,
		doubleNestedCreatedPath,
		updatedPath
	}

	table.sort( expected )
	table.sort( actual )

	rxTest:expectEqual( expected, actual )

end

--------------------------------------------------------------------------------

return WFRxTestCase.wrapSuite( 'WFFileUtilsTest', WFFileUtilsTest )
