# This file tests the filesystem and vfs internals.
#
# This file contains a collection of tests for one or more of the Tcl
# built-in commands.  Sourcing this file into Tcl runs the tests and
# generates output for errors.  No output means no errors were found.
#
# Copyright (c) 2002 Vincent Darley.
#
# See the file "license.terms" for information on usage and redistribution
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.

package require tcltest 2
namespace eval ::tcl::test::fileSystem {

    catch {
	namespace import ::tcltest::cleanupTests
	namespace import ::tcltest::makeDirectory
	namespace import ::tcltest::makeFile
	namespace import ::tcltest::removeDirectory
	namespace import ::tcltest::removeFile
	namespace import ::tcltest::test
    }
    
    catch {
	file delete -force link.file
	file delete -force dir.link
	file delete -force [file join dir.dir linkinside.file]
    }

makeFile "test file" gorp.file
makeDirectory dir.dir
makeDirectory [file join dir.dir dirinside.dir]
makeFile "test file in directory" [file join dir.dir inside.file]

proc testPathEqual {one two} {
    if {[string equal $one $two]} {
	return 1
    } else {
	return "not equal: $one $two"
    }
}

if {[catch {
    file link link.file gorp.file
    cd dir.dir
    file link \
      [file join linkinside.file] \
      [file join inside.file]
    cd ..
    file link dir.link dir.dir
    cd dir.dir
    file link [file join dirinside.link] \
      [file join dirinside.dir]
    cd ..
}]} {
    tcltest::testConstraint hasLinks 0
} else {
    tcltest::testConstraint hasLinks 1
}

tcltest::testConstraint testsetplatform [string equal testsetplatform [info commands testsetplatform]]
if {[tcltest::testConstraint testsetplatform]} {
    set platform [testgetplatform]
}

tcltest::testConstraint testsimplefilesystem \
  [string equal testsimplefilesystem [info commands testsimplefilesystem]]

test filesystem-1.0 {link normalisation} {hasLinks} {
   string equal [file normalize gorp.file] [file normalize link.file]
} {0}

test filesystem-1.1 {link normalisation} {hasLinks} {
   string equal [file normalize dir.dir] [file normalize dir.link]
} {0}

test filesystem-1.2 {link normalisation} {hasLinks macOrUnix} {
    testPathEqual [file normalize [file join gorp.file foo]] \
     [file normalize [file join link.file foo]]
} {1}

test filesystem-1.3 {link normalisation} {hasLinks} {
    testPathEqual [file normalize [file join dir.dir foo]] \
     [file normalize [file join dir.link foo]]
} {1}

test filesystem-1.4 {link normalisation} {hasLinks} {
    testPathEqual [file normalize [file join dir.dir inside.file]] \
     [file normalize [file join dir.link inside.file]]
} {1}

test filesystem-1.5 {link normalisation} {hasLinks} {
    testPathEqual [file normalize [file join dir.dir linkinside.file]] \
     [file normalize [file join dir.dir linkinside.file]]
} {1}

test filesystem-1.6 {link normalisation} {hasLinks} {
   string equal [file normalize [file join dir.dir linkinside.file]] \
     [file normalize [file join dir.link inside.file]]
} {0}

test filesystem-1.7 {link normalisation} {hasLinks macOrUnix} {
    testPathEqual [file normalize [file join dir.link linkinside.file foo]] \
     [file normalize [file join dir.dir inside.file foo]]
} {1}

test filesystem-1.8 {link normalisation} {hasLinks} {
   string equal [file normalize [file join dir.dir linkinside.filefoo]] \
     [file normalize [file join dir.link inside.filefoo]]
} {0}

test filesystem-1.9 {link normalisation} {macOrUnix hasLinks} {
    file delete -force dir.link
    file link dir.link [file nativename dir.dir]
    testPathEqual [file normalize [file join dir.dir linkinside.file foo]] \
      [file normalize [file join dir.link inside.file foo]]
} {1}

test filesystem-1.10 {link normalisation: double link} {macOrUnix hasLinks} {
    file link dir2.link dir.link
    testPathEqual [file normalize [file join dir.dir linkinside.file foo]] \
      [file normalize [file join dir2.link inside.file foo]]
} {1}

makeDirectory dir2.file

test filesystem-1.11 {link normalisation: double link, back in tree} {macOrUnix hasLinks} {
    file link [file join dir2.file dir2.link] [file join .. dir2.link]
    testPathEqual [file normalize [file join dir.dir linkinside.file foo]] \
      [file normalize [file join dir2.file dir2.link inside.file foo]]
} {1}

test filesystem-1.12 {file new native path} {} {
    for {set i 0} {$i < 10} {incr i} {
	foreach f [lsort [glob -nocomplain -type l *]] {
	    catch {file readlink $f}
	}
    }
    # If we reach here we've succeeded. We used to crash above.
    expr 1
} {1}

test filesystem-1.13 {file normalisation} {winOnly} {
    # This used to be broken
    file normalize C:/thislongnamedoesntexist
} {C:/thislongnamedoesntexist}

test filesystem-1.14 {file normalisation} {winOnly} {
    # This used to be broken
    file normalize c:/
} {C:/}

test filesystem-1.15 {file normalisation} {winOnly} {
    file normalize c:/../
} {C:/}

test filesystem-1.16 {file normalisation} {winOnly} {
    file normalize c:/.
} {C:/}

test filesystem-1.17 {file normalisation} {winOnly} {
    file normalize c:/..
} {C:/}

test filesystem-1.17.1 {file normalisation} {winOnly} {
    file normalize c:\\..
} {C:/}

test filesystem-1.18 {file normalisation} {winOnly} {
    file normalize c:/./
} {C:/}

test filesystem-1.19 {file normalisation} {winOnly} {
    file normalize w:/./../../..
} {w:/}

test filesystem-1.20 {file normalisation} {winOnly} {
    file normalize //name/foo/../
} {//name/foo}

test filesystem-1.21 {file normalisation} {winOnly} {
    file normalize C:///foo/./
} {C:/foo}

test filesystem-1.22 {file normalisation} {winOnly} {
    file normalize //name/foo/.
} {//name/foo}

test filesystem-1.23 {file normalisation} {winOnly} {
    file normalize c:/./foo
} {C:/foo}

test filesystem-1.24 {file normalisation} {winOnly} {
    file normalize w:/./../../../a
} {w:/a}

test filesystem-1.25 {file normalisation} {winOnly} {
    file normalize w:/./.././../../a
} {w:/a}

test filesystem-1.25.1 {file normalisation} {winOnly} {
    file normalize w:/./.././..\\..\\a\\bb
} {w:/a/bb}

test filesystem-1.26 {link normalisation: link and ..} {hasLinks} {
    file delete -force dir2.link
    set dir [file join dir2 foo bar]
    file mkdir $dir
    file link dir2.link [file join dir2 foo bar]
    set res [list [file normalize [file join dir2 foo x]] \
      [file normalize [file join dir2.link .. x]]]
    if {![string equal [lindex $res 0] [lindex $res 1]]} {
	set res "$res not equal"
    } else {
	set res "ok"
    }
} {ok}

test filesystem-1.27 {file normalisation: up and down with ..} {
    set dir [file join dir2 foo bar]
    file mkdir $dir
    set dir2 [file join dir2 .. dir2 foo .. foo bar]
    set res [list [file normalize $dir] \
      [file normalize $dir2]]
    set res2 [list [file exists $dir] [file exists $dir2]]
    if {![string equal [lindex $res 0] [lindex $res 1]]} {
	set res "exists: $res2, $res not equal"
    } else {
	set res "ok: $res2"
    }
} {ok: 1 1}

test filesystem-1.28 {link normalisation: link with .. and ..} {hasLinks} {
    file delete -force dir2.link
    set dir [file join dir2 foo bar]
    file mkdir $dir
    set to [file join dir2 .. dir2 foo .. foo bar]
    file link dir2.link $to
    set res [list [file normalize [file join dir2 foo x]] \
      [file normalize [file join dir2.link .. x]]]
    if {![string equal [lindex $res 0] [lindex $res 1]]} {
	set res "$res not equal"
    } else {
	set res "ok"
    }
} {ok}

test filesystem-1.29 {link normalisation: link with ..} {hasLinks} {
    file delete -force dir2.link
    set dir [file join dir2 foo bar]
    file mkdir $dir
    set to [file join dir2 .. dir2 foo .. foo bar]
    file link dir2.link $to
    set res [file normalize [file join dir2.link x yyy z]]
    if {[string first ".." $res] != -1} {
	set res "$res must not contain '..'"
    } else {
	set res "ok"
    }
} {ok}

test filesystem-1.29.1 {link normalisation with two consecutive links} {hasLinks} {
    testPathEqual [file normalize [file join dir.link dirinside.link abc]] \
      [file normalize [file join dir.dir dirinside.dir abc]]
} {1}

file delete -force dir2.file
file delete -force dir2.link
file delete -force link.file dir.link
file delete -force dir2
file delete -force [file join dir.dir dirinside.link]
removeFile [file join dir.dir inside.file]
removeDirectory [file join dir.dir dirinside.dir]
removeDirectory dir.dir

test filesystem-1.30 {normalisation of nonexistent user} {
    list [catch {file normalize ~noonewiththisname} err] $err
} {1 {user "noonewiththisname" doesn't exist}}

test filesystem-1.31 {link normalisation: link near filesystem root} {testsetplatform} {
    testsetplatform unix
    file normalize /foo/../bar
} {/bar}

test filesystem-1.32 {link normalisation: link near filesystem root} {testsetplatform} {
    testsetplatform unix
    file normalize /../bar
} {/bar}

test filesystem-1.33 {link normalisation: link near filesystem root} {testsetplatform} {
    testsetplatform windows
    file normalize C:/../bar
} {C:/bar}

if {[tcltest::testConstraint testsetplatform]} {
    testsetplatform $platform
}

test filesystem-2.0 {new native path} {unixOnly} {
   foreach f [lsort [glob -nocomplain /usr/bin/c*]] {
       catch {file readlink $f}
   }
   # If we reach here we've succeeded. We used to crash above.
   expr 1
} {1}

if {[catch {package require tcltest 2}]} {
    puts stderr "Skipping tests filesystem-{3,4}.*: tcltest 2 required."
} else {
    namespace import ::tcltest::testConstraint

    # Is the Tcltest package loaded?
    #   - that is, the special C-coded testing commands in tclTest.c
    #   - tests use testing commands introduced in Tcltest 8.4
    testConstraint Tcltest [expr {
        [llength [package provide Tcltest]]
        && [package vsatisfies [package provide Tcltest] 8.4]}]

# Make sure the testfilesystem hasn't been registered.
while {![catch {testfilesystem 0}]} {}

test filesystem-3.0 {Tcl_FSRegister} Tcltest {
    testfilesystem 1
} {registered}

test filesystem-3.1 {Tcl_FSUnregister} Tcltest {
    testfilesystem 0
} {unregistered}

test filesystem-3.2 {Tcl_FSUnregister} Tcltest {
    list [catch {testfilesystem 0} err] $err
} {1 failed}

test filesystem-3.3 {Tcl_FSRegister} Tcltest {
    testfilesystem 1
    testfilesystem 1
    testfilesystem 0
    testfilesystem 0
} {unregistered}

test filesystem-3.4 {Tcl_FSRegister} Tcltest {
    testfilesystem 1
    file system bar
} {reporting}

test filesystem-3.5 {Tcl_FSUnregister} Tcltest {
    testfilesystem 0
    lindex [file system bar] 0
} {native}

test filesystem-4.0 {testfilesystem} {
    -constraints Tcltest
    -match glob
    -body {
	testfilesystem 1
	set filesystemReport {}
	file exists foo
	testfilesystem 0
	set filesystemReport
    }
    -result {*{access foo}}
}

test filesystem-4.1 {testfilesystem} {
    -constraints Tcltest
    -match glob
    -body {
	testfilesystem 1
	set filesystemReport {}
	catch {file stat foo bar}
	testfilesystem 0
	set filesystemReport
    }
    -result {*{stat foo}}
}

test filesystem-4.2 {testfilesystem} {
    -constraints Tcltest
    -match glob
    -body {
	testfilesystem 1
	set filesystemReport {}
	catch {file lstat foo bar}
	testfilesystem 0
	set filesystemReport
    }
    -result {*{lstat foo}}
}

test filesystem-4.3 {testfilesystem} {
    -constraints Tcltest
    -match glob
    -body {
	testfilesystem 1
	set filesystemReport {}
	catch {glob *}
	testfilesystem 0
	set filesystemReport
    }
    -result {*{matchindirectory *}*}
}

test filesystem-5.1 {cache and ~} {
    -constraints Tcltest
    -match regexp
    -body {
	set orig $env(HOME)
	set ::env(HOME) /foo/bar/blah
	set testdir ~
	set res1 "Parent of ~ (/foo/bar/blah) is [file dirname $testdir]"
	set ::env(HOME) /a/b/c
	set res2 "Parent of ~ (/a/b/c) is [file dirname $testdir]"
	set ::env(HOME) $orig
	list $res1 $res2
    }
    -result {{Parent of ~ \(/foo/bar/blah\) is (/foo/bar|foo:bar)} {Parent of ~ \(/a/b/c\) is (/a/b|a:b)}}
}

test filesystem-6.1 {empty file name} {
    list [catch {open ""} msg] $msg
} {1 {couldn't open "": no such file or directory}}

test filesystem-6.2 {empty file name} {
    list [catch {file stat "" arr} msg] $msg
} {1 {could not read "": no such file or directory}}

test filesystem-6.3 {empty file name} {
    list [catch {file atime ""} msg] $msg
} {1 {could not read "": no such file or directory}}

test filesystem-6.4 {empty file name} {
    list [catch {file attributes ""} msg] $msg
} {1 {could not read "": no such file or directory}}

test filesystem-6.5 {empty file name} {
    list [catch {file copy "" ""} msg] $msg
} {1 {error copying "": no such file or directory}}

test filesystem-6.6 {empty file name} {
    list [catch {file delete ""} msg] $msg
} {0 {}}

test filesystem-6.7 {empty file name} {
    list [catch {file dirname ""} msg] $msg
} {0 .}

test filesystem-6.8 {empty file name} {
    list [catch {file executable ""} msg] $msg
} {0 0}

test filesystem-6.9 {empty file name} {
    list [catch {file exists ""} msg] $msg
} {0 0}

test filesystem-6.10 {empty file name} {
    list [catch {file extension ""} msg] $msg
} {0 {}}

test filesystem-6.11 {empty file name} {
    list [catch {file isdirectory ""} msg] $msg
} {0 0}

test filesystem-6.12 {empty file name} {
    list [catch {file isfile ""} msg] $msg
} {0 0}

test filesystem-6.13 {empty file name} {
    list [catch {file join ""} msg] $msg
} {0 {}}

test filesystem-6.14 {empty file name} {
    list [catch {file link ""} msg] $msg
} {1 {could not read link "": no such file or directory}}

test filesystem-6.15 {empty file name} {
    list [catch {file lstat "" arr} msg] $msg
} {1 {could not read "": no such file or directory}}

test filesystem-6.16 {empty file name} {
    list [catch {file mtime ""} msg] $msg
} {1 {could not read "": no such file or directory}}

test filesystem-6.17 {empty file name} {
    list [catch {file mtime "" 0} msg] $msg
} {1 {could not read "": no such file or directory}}

test filesystem-6.18 {empty file name} {
    list [catch {file mkdir ""} msg] $msg
} {1 {can't create directory "": no such file or directory}}

test filesystem-6.19 {empty file name} {
    list [catch {file nativename ""} msg] $msg
} {0 {}}

test filesystem-6.20 {empty file name} {
    list [catch {file normalize ""} msg] $msg
} {0 {}}

test filesystem-6.21 {empty file name} {
    list [catch {file owned ""} msg] $msg
} {0 0}

test filesystem-6.22 {empty file name} {
    list [catch {file pathtype ""} msg] $msg
} {0 relative}

test filesystem-6.23 {empty file name} {
    list [catch {file readable ""} msg] $msg
} {0 0}

test filesystem-6.24 {empty file name} {
    list [catch {file readlink ""} msg] $msg
} {1 {could not readlink "": no such file or directory}}

test filesystem-6.25 {empty file name} {
    list [catch {file rename "" ""} msg] $msg
} {1 {error renaming "": no such file or directory}}

test filesystem-6.26 {empty file name} {
    list [catch {file rootname ""} msg] $msg
} {0 {}}

test filesystem-6.27 {empty file name} {
    list [catch {file separator ""} msg] $msg
} {1 {Unrecognised path}}

test filesystem-6.28 {empty file name} {
    list [catch {file size ""} msg] $msg
} {1 {could not read "": no such file or directory}}

test filesystem-6.29 {empty file name} {
    list [catch {file split ""} msg] $msg
} {0 {}}

test filesystem-6.30 {empty file name} {
    list [catch {file system ""} msg] $msg
} {1 {Unrecognised path}}

test filesystem-6.31 {empty file name} {
    list [catch {file tail ""} msg] $msg
} {0 {}}

test filesystem-6.32 {empty file name} {
    list [catch {file type ""} msg] $msg
} {1 {could not read "": no such file or directory}}

test filesystem-6.33 {empty file name} {
    list [catch {file writable ""} msg] $msg
} {0 0}

# Make sure the testfilesystem hasn't been registered.
while {![catch {testfilesystem 0}]} {}
}

test filesystem-7.1 {load from vfs} {win testsimplefilesystem} {
    # This may cause a crash on exit
    set dir [pwd]
    cd [file dirname [info nameof]]
    set dde [lindex [glob *dde*[info sharedlib]] 0]
    testsimplefilesystem 1
    # This loads dde via a complex copy-to-temp operation
    load simplefs:/$dde dde
    testsimplefilesystem 0
    cd $dir
    set res "ok"
    # The real result of this test is what happens when Tcl exits.
} {ok}

test filesystem-7.2 {cross-filesystem copy from vfs maintains mtime} \
  {testsimplefilesystem} {
    set dir [pwd]
    cd [tcltest::temporaryDirectory]
    # We created this file several tests ago.
    set origtime [file mtime gorp.file]
    testsimplefilesystem 1
    file delete -force theCopy
    file copy simplefs:/gorp.file theCopy
    testsimplefilesystem 0
    set newtime [file mtime theCopy]
    file delete theCopy
    cd $dir
    expr {$origtime == $newtime}
} {1}

removeFile gorp.file

test filesystem-8.1 {relative path objects and caching of pwd} {
    set dir [pwd]
    cd [tcltest::temporaryDirectory]
    makeDirectory abc
    makeDirectory def
    makeFile "contents" [file join abc foo]
    cd abc
    set f "foo"
    set res {}
    lappend res [file exists $f]
    lappend res [file exists $f]
    cd ..
    cd def
    # If we haven't cleared the object's cwd cache, Tcl 
    # will think it still exists.
    lappend res [file exists $f]
    lappend res [file exists $f]
    removeFile [file join abc foo]
    removeDirectory abc
    removeDirectory def
    cd $dir
    set res
} {1 1 0 0}

test filesystem-8.2 {relative path objects and use of pwd} {
    set origdir [pwd]
    cd [tcltest::temporaryDirectory]
    set dir "abc"
    makeDirectory $dir
    makeFile "contents" [file join abc foo]
    cd $dir
    set res [file exists [lindex [glob *] 0]]
    cd ..
    removeFile [file join abc foo]
    removeDirectory abc
    cd $origdir
    set res
} {1}

test filesystem-8.3 {path objects and empty string} {
    set anchor ""
    set dst foo
    set res $dst

    set yyy [file split $anchor]
    set dst [file join  $anchor $dst]
    lappend res $dst $yyy
} {foo foo {}}

cleanupTests
}
namespace delete ::tcl::test::fileSystem
return
