mirror of
https://github.com/nim-lang/Nim.git
synced 2026-08-01 04:59:05 +00:00
This PR implements `expandSymlink` on Windows with POSIX readlink semantics: it expands exactly one hop and returns the stored link target without resolving the full chain. The main design question was whether Windows symlink expansion should be built on path-finalization APIs such as `GetFinalPathNameByHandleW`, or on direct reparse-point inspection. Current `expandSymlink` is a single-hop "what target is stored in this link object?" operation and most of other ways to resolve symlinks on Windows actually try to answer the "final true file location" question in various slightly-incompatible ways. The full final-path resolution on Windows is substantially more complex than readlink and is planned as a follow-up. ## Implementation choice Implements Windows `expandSymlink` by: - opening the path with `FILE_FLAG_OPEN_REPARSE_POINT` - calling `DeviceIoControl(FSCTL_GET_REPARSE_POINT)` - parsing the reparse payload for `IO_REPARSE_TAG_SYMLINK` and `IO_REPARSE_TAG_MOUNT_POINT` - decoding the UTF-16 slice referenced by the payload - returning the stored target This is the right primitive for the API: - does not depend on whole-path finalization - works for both symlinks and junctions - matches the existing Linux behaviour `widestrs` changes allow using WideCString views without temporary allocations. Windows prohibits symlink creation without admin rights, so, unfortunately, the tests are conditionally skipped by default. Manually running `testament` in an admin console is required. ## Behaviour: - One hop only - Relative symlink targets are returned unchanged - Absolute Windows targets are converted from stored NT-style prefixes to usable Win32 forms when applicable - Non-links, malformed payloads, and unsupported reparse tags raise `OSError` ## Future work Path canonicalization, i.e. "final true file location". Which is, BTW, different from `absolutePath`, which works on paths only and doesn't hit the underlying FS. So this needs to be an API extension. I'd like to follow-up with this when I sort through the docs, for now you can resolve symlinks in a loop. --------- Co-authored-by: Andreas Rumpf <rumpf_a@web.de>
913 lines
34 KiB
Nim
913 lines
34 KiB
Nim
discard """
|
|
output: '''
|
|
All:
|
|
__really_obscure_dir_name/are.x
|
|
__really_obscure_dir_name/created
|
|
__really_obscure_dir_name/dirs
|
|
__really_obscure_dir_name/files.q
|
|
__really_obscure_dir_name/some
|
|
__really_obscure_dir_name/test
|
|
__really_obscure_dir_name/testing.r
|
|
__really_obscure_dir_name/these.txt
|
|
Files:
|
|
__really_obscure_dir_name/are.x
|
|
__really_obscure_dir_name/files.q
|
|
__really_obscure_dir_name/testing.r
|
|
__really_obscure_dir_name/these.txt
|
|
Dirs:
|
|
__really_obscure_dir_name/created
|
|
__really_obscure_dir_name/dirs
|
|
__really_obscure_dir_name/some
|
|
__really_obscure_dir_name/test
|
|
Raises
|
|
Raises
|
|
'''
|
|
matrix: "--mm:refc; --mm:orc"
|
|
joinable: false
|
|
"""
|
|
# test os path creation, iteration, and deletion
|
|
|
|
from stdtest/specialpaths import buildDir
|
|
import std/[syncio, assertions, osproc, os, strutils, pathnorm]
|
|
|
|
import std/paths except getCurrentDir
|
|
import std/[files, dirs]
|
|
|
|
block fileOperations:
|
|
let files = @["these.txt", "are.x", "testing.r", "files.q"]
|
|
let dirs = @["some", "created", "test", "dirs"]
|
|
|
|
let dname = "__really_obscure_dir_name"
|
|
|
|
createDir(dname)
|
|
doAssert dirExists(dname)
|
|
|
|
block: # copyFile, copyFileToDir
|
|
doAssertRaises(OSError): copyFile(dname/"nonexistent.txt", dname/"nonexistent.txt")
|
|
let fname = "D20201009T112235"
|
|
let fname2 = "D20201009T112235.2"
|
|
let str = "foo1\0foo2\nfoo3\0"
|
|
let file = dname/fname
|
|
let file2 = dname/fname2
|
|
writeFile(file, str)
|
|
doAssert readFile(file) == str
|
|
let sub = "sub"
|
|
doAssertRaises(OSError): copyFile(file, dname/sub/fname2)
|
|
doAssertRaises(OSError): copyFileToDir(file, dname/sub)
|
|
doAssertRaises(ValueError): copyFileToDir(file, "")
|
|
copyFile(Path file, Path file2)
|
|
doAssert fileExists(file2)
|
|
doAssert readFile(file2) == str
|
|
createDir(dname/sub)
|
|
copyFileToDir(Path file, Path dname/sub)
|
|
doAssert fileExists(dname/sub/fname)
|
|
removeDir(dname/sub)
|
|
doAssert not dirExists(dname/sub)
|
|
removeFile(file)
|
|
removeFile(file2)
|
|
|
|
# Test creating files and dirs
|
|
for dir in dirs:
|
|
createDir(dname/dir)
|
|
doAssert dirExists(dname/dir)
|
|
|
|
for file in files:
|
|
let fh = open(dname/file, fmReadWrite)
|
|
fh.close()
|
|
doAssert fileExists(dname/file)
|
|
|
|
echo "All:"
|
|
|
|
template norm(x): untyped =
|
|
(when defined(windows): x.replace('\\', '/') else: x)
|
|
|
|
for path in walkPattern(dname/"*"):
|
|
echo path.norm
|
|
|
|
echo "Files:"
|
|
|
|
for path in walkFiles(dname/"*"):
|
|
echo path.norm
|
|
|
|
echo "Dirs:"
|
|
|
|
for path in walkDirs(dname/"*"):
|
|
echo path.norm
|
|
|
|
# Test removal of files dirs
|
|
for dir in dirs:
|
|
removeDir(dname/dir)
|
|
doAssert: not dirExists(dname/dir)
|
|
|
|
for file in files:
|
|
removeFile(dname/file)
|
|
doAssert: not fileExists(dname/file)
|
|
|
|
removeDir(dname)
|
|
doAssert: not dirExists(dname)
|
|
|
|
# createDir should create recursive directories
|
|
createDir(dirs[0] / dirs[1])
|
|
doAssert dirExists(dirs[0] / dirs[1]) # true
|
|
removeDir(dirs[0])
|
|
|
|
# createDir should properly handle trailing separator
|
|
createDir(dname / "")
|
|
doAssert dirExists(dname) # true
|
|
removeDir(dname)
|
|
|
|
# createDir should raise IOError if the path exists
|
|
# and is not a directory
|
|
open(dname, fmWrite).close
|
|
try:
|
|
createDir(dname)
|
|
except IOError:
|
|
echo "Raises"
|
|
removeFile(dname)
|
|
|
|
# removeFile should not remove directory
|
|
createDir(dname)
|
|
try:
|
|
removeFile(dname)
|
|
except OSError:
|
|
echo "Raises"
|
|
removeDir(dname)
|
|
|
|
# test copyDir:
|
|
createDir(Path "a/b")
|
|
open("a/b/file.txt", fmWrite).close
|
|
createDir("a/b/c")
|
|
open("a/b/c/fileC.txt", fmWrite).close
|
|
|
|
createDir(Path"a/b")
|
|
copyDir(Path "a", Path "../dest/a")
|
|
removeDir("a")
|
|
|
|
doAssert dirExists("../dest/a/b")
|
|
doAssert fileExists("../dest/a/b/file.txt")
|
|
|
|
doAssert fileExists("../dest/a/b/c/fileC.txt")
|
|
removeDir("../dest")
|
|
|
|
# test copyDir:
|
|
# if separator at the end of a path
|
|
createDir("a/b")
|
|
open("a/file.txt", fmWrite).close
|
|
|
|
copyDir("a/", "../dest/a/")
|
|
removeDir("a")
|
|
|
|
doAssert dirExists("../dest/a/b")
|
|
doAssert fileExists("../dest/a/file.txt")
|
|
removeDir("../dest")
|
|
|
|
# createDir should not fail if `dir` is empty
|
|
createDir("")
|
|
|
|
|
|
when defined(linux): # bug #24174
|
|
createDir("a/b")
|
|
open("a/file.txt", fmWrite).close
|
|
|
|
if not fileExists("a/fifoFile"):
|
|
doAssert execCmd("mkfifo -m 600 a/fifoFile") == 0
|
|
|
|
copyDir("a/", "../dest/a/", skipSpecial = true)
|
|
copyDirWithPermissions(Path "a/", Path "../dest2/a/", skipSpecial = true)
|
|
removeDir("a")
|
|
|
|
# Symlink handling in `copyFile`, `copyFileWithPermissions`, `copyFileToDir`,
|
|
# `copyDir`, `copyDirWithPermissions`, `moveFile`, and `moveDir`.
|
|
block:
|
|
const symlinkCopiesAreHandled = not defined(windows)
|
|
const dname = buildDir/"D20210116T140629"
|
|
const subDir = dname/"sub"
|
|
const subDir2 = dname/"sub2"
|
|
const brokenSymlinkName = "D20210101T191320_BROKEN_SYMLINK"
|
|
const brokenSymlink = dname/brokenSymlinkName
|
|
const brokenSymlinkSrc = "D20210101T191320_nonexistent"
|
|
const brokenSymlinkCopy = brokenSymlink & "_COPY"
|
|
const brokenSymlinkInSubDir = subDir/brokenSymlinkName
|
|
const brokenSymlinkInSubDir2 = subDir2/brokenSymlinkName
|
|
const symlinkProbeTarget = dname/"symlink_probe_target"
|
|
const symlinkProbeLink = dname/"symlink_probe_link"
|
|
|
|
proc removePathIfExists(path: string) =
|
|
if fileExists(path):
|
|
removeFile(path)
|
|
elif dirExists(path):
|
|
removeDir(path)
|
|
|
|
proc canCreateSymlinks(): bool =
|
|
# We need this check for Windows if we want to permit the block to run
|
|
# when we have admin privileges
|
|
try:
|
|
removePathIfExists(dname)
|
|
createDir(dname)
|
|
writeFile(symlinkProbeTarget, "")
|
|
createSymlink(symlinkProbeTarget, symlinkProbeLink)
|
|
result = true
|
|
except OSError:
|
|
result = false
|
|
finally:
|
|
removePathIfExists(symlinkProbeLink)
|
|
removePathIfExists(symlinkProbeTarget)
|
|
removePathIfExists(dname)
|
|
|
|
proc doAssertExpandedSymlink(path, expected: string) =
|
|
let actual = expandSymlink(path)
|
|
doAssert actual == expected,
|
|
"expandSymlink(" & path & ") returned " & actual &
|
|
" instead of " & expected
|
|
|
|
removePathIfExists(dname)
|
|
let symlinksAreAvailable = not defined(windows) or canCreateSymlinks()
|
|
if symlinksAreAvailable:
|
|
defer:
|
|
removePathIfExists(dname)
|
|
|
|
createDir(subDir)
|
|
createSymlink(brokenSymlinkSrc, brokenSymlink)
|
|
doAssertExpandedSymlink(brokenSymlink, brokenSymlinkSrc)
|
|
doAssertRaises(OSError):
|
|
discard expandSymlink(dname)
|
|
|
|
# Test copyFile
|
|
when symlinkCopiesAreHandled:
|
|
doAssertRaises(OSError):
|
|
copyFile(brokenSymlink, brokenSymlinkCopy)
|
|
doAssertRaises(OSError):
|
|
copyFile(brokenSymlink, brokenSymlinkCopy, {cfSymlinkFollow})
|
|
copyFile(brokenSymlink, brokenSymlinkCopy, {cfSymlinkIgnore})
|
|
doAssert not fileExists(brokenSymlinkCopy)
|
|
copyFile(brokenSymlink, brokenSymlinkCopy, {cfSymlinkAsIs})
|
|
when symlinkCopiesAreHandled:
|
|
doAssertExpandedSymlink(brokenSymlinkCopy, brokenSymlinkSrc)
|
|
removeFile(brokenSymlinkCopy)
|
|
else:
|
|
doAssert not fileExists(brokenSymlinkCopy)
|
|
doAssertRaises(AssertionDefect):
|
|
copyFile(brokenSymlink, brokenSymlinkCopy,
|
|
{cfSymlinkAsIs, cfSymlinkFollow})
|
|
|
|
# Test copyFileWithPermissions
|
|
when symlinkCopiesAreHandled:
|
|
doAssertRaises(OSError):
|
|
copyFileWithPermissions(brokenSymlink, brokenSymlinkCopy)
|
|
doAssertRaises(OSError):
|
|
copyFileWithPermissions(brokenSymlink, brokenSymlinkCopy,
|
|
options = {cfSymlinkFollow})
|
|
copyFileWithPermissions(brokenSymlink, brokenSymlinkCopy,
|
|
options = {cfSymlinkIgnore})
|
|
doAssert not fileExists(brokenSymlinkCopy)
|
|
copyFileWithPermissions(brokenSymlink, brokenSymlinkCopy,
|
|
options = {cfSymlinkAsIs})
|
|
when symlinkCopiesAreHandled:
|
|
doAssertExpandedSymlink(brokenSymlinkCopy, brokenSymlinkSrc)
|
|
removeFile(brokenSymlinkCopy)
|
|
else:
|
|
doAssert not fileExists(brokenSymlinkCopy)
|
|
doAssertRaises(AssertionDefect):
|
|
copyFileWithPermissions(brokenSymlink, brokenSymlinkCopy,
|
|
options = {cfSymlinkAsIs, cfSymlinkFollow})
|
|
|
|
# Test copyFileToDir
|
|
when symlinkCopiesAreHandled:
|
|
doAssertRaises(OSError):
|
|
copyFileToDir(brokenSymlink, subDir)
|
|
doAssertRaises(OSError):
|
|
copyFileToDir(brokenSymlink, subDir, {cfSymlinkFollow})
|
|
copyFileToDir(brokenSymlink, subDir, {cfSymlinkIgnore})
|
|
doAssert not fileExists(brokenSymlinkInSubDir)
|
|
copyFileToDir(brokenSymlink, subDir, {cfSymlinkAsIs})
|
|
when symlinkCopiesAreHandled:
|
|
doAssertExpandedSymlink(brokenSymlinkInSubDir, brokenSymlinkSrc)
|
|
removeFile(brokenSymlinkInSubDir)
|
|
else:
|
|
doAssert not fileExists(brokenSymlinkInSubDir)
|
|
|
|
createSymlink(brokenSymlinkSrc, brokenSymlinkInSubDir)
|
|
|
|
# Test copyDir
|
|
copyDir(subDir, subDir2)
|
|
when symlinkCopiesAreHandled:
|
|
doAssertExpandedSymlink(brokenSymlinkInSubDir2, brokenSymlinkSrc)
|
|
else:
|
|
doAssert not fileExists(brokenSymlinkInSubDir2)
|
|
removeDir(subDir2)
|
|
|
|
# Test copyDirWithPermissions
|
|
copyDirWithPermissions(subDir, subDir2)
|
|
when symlinkCopiesAreHandled:
|
|
doAssertExpandedSymlink(brokenSymlinkInSubDir2, brokenSymlinkSrc)
|
|
else:
|
|
doAssert not fileExists(brokenSymlinkInSubDir2)
|
|
removeDir(subDir2)
|
|
|
|
# Test moveFile
|
|
moveFile(brokenSymlink, brokenSymlinkCopy)
|
|
doAssertExpandedSymlink(brokenSymlinkCopy, brokenSymlinkSrc)
|
|
removeFile(brokenSymlinkCopy)
|
|
|
|
# Test moveDir
|
|
moveDir(subDir, subDir2)
|
|
doAssertExpandedSymlink(brokenSymlinkInSubDir2, brokenSymlinkSrc)
|
|
else:
|
|
discard "Skipping symlink tests: symlink creation is not permitted in this environment"
|
|
|
|
block: # moveFile
|
|
let tempDir = getTempDir() / "D20210609T151608"
|
|
createDir(tempDir)
|
|
defer: removeDir(tempDir)
|
|
|
|
writeFile(tempDir / "a.txt", "")
|
|
moveFile(tempDir / "a.txt", tempDir / "b.txt")
|
|
doAssert not fileExists(tempDir / "a.txt")
|
|
doAssert fileExists(tempDir / "b.txt")
|
|
removeFile(tempDir / "b.txt")
|
|
|
|
createDir(tempDir / "moveFile_test")
|
|
writeFile(tempDir / "moveFile_test/a.txt", "")
|
|
moveFile(tempDir / "moveFile_test/a.txt", tempDir / "moveFile_test/b.txt")
|
|
doAssert not fileExists(tempDir / "moveFile_test/a.txt")
|
|
doAssert fileExists(tempDir / "moveFile_test/b.txt")
|
|
removeDir(tempDir / "moveFile_test")
|
|
|
|
createDir(tempDir / "moveFile_test")
|
|
writeFile(tempDir / "a.txt", "")
|
|
moveFile(tempDir / "a.txt", tempDir / "moveFile_test/b.txt")
|
|
doAssert not fileExists(tempDir / "a.txt")
|
|
doAssert fileExists(tempDir / "moveFile_test/b.txt")
|
|
removeDir(tempDir / "moveFile_test")
|
|
|
|
block: # moveDir
|
|
let tempDir = getTempDir() / "D20210609T161443"
|
|
createDir(tempDir)
|
|
defer: removeDir(tempDir)
|
|
|
|
createDir(tempDir / "moveDir_test")
|
|
moveDir(tempDir / "moveDir_test/", tempDir / "moveDir_test_dest")
|
|
doAssert not dirExists(tempDir / "moveDir_test")
|
|
doAssert dirExists(tempDir / "moveDir_test_dest")
|
|
removeDir(tempDir / "moveDir_test_dest")
|
|
|
|
createDir(tempDir / "moveDir_test")
|
|
writeFile(tempDir / "moveDir_test/a.txt", "")
|
|
moveDir(tempDir / "moveDir_test", tempDir / "moveDir_test_dest")
|
|
doAssert not dirExists(tempDir / "moveDir_test")
|
|
doAssert not fileExists(tempDir / "moveDir_test/a.txt")
|
|
doAssert dirExists(tempDir / "moveDir_test_dest")
|
|
doAssert fileExists(tempDir / "moveDir_test_dest/a.txt")
|
|
removeDir(tempDir / "moveDir_test_dest")
|
|
|
|
import times
|
|
block modificationTime:
|
|
# Test get/set modification times
|
|
# Should support at least microsecond resolution
|
|
let tm = fromUnix(0) + 100.microseconds
|
|
writeFile("a", "")
|
|
setLastModificationTime("a", tm)
|
|
|
|
when defined(macosx):
|
|
doAssert true
|
|
else:
|
|
doAssert getLastModificationTime("a") == tm
|
|
removeFile("a")
|
|
|
|
block walkDirRec:
|
|
createDir("walkdir_test/a/b")
|
|
open("walkdir_test/a/b/file_1", fmWrite).close()
|
|
open("walkdir_test/a/file_2", fmWrite).close()
|
|
|
|
for p in walkDirRec("walkdir_test"):
|
|
doAssert p.fileExists
|
|
doAssert p.startsWith("walkdir_test")
|
|
|
|
var s: seq[string]
|
|
for p in walkDirRec("walkdir_test", {pcFile}, {pcDir}, relative = true):
|
|
s.add(p)
|
|
|
|
doAssert s.len == 2
|
|
doAssert "a" / "b" / "file_1" in s
|
|
doAssert "a" / "file_2" in s
|
|
|
|
removeDir("walkdir_test")
|
|
|
|
import std/sequtils
|
|
|
|
block: # walkDir
|
|
doAssertRaises(OSError):
|
|
for a in walkDir("nonexistent", checkDir = true): discard
|
|
doAssertRaises(OSError):
|
|
for p in walkDirRec("nonexistent", checkDir = true): discard
|
|
|
|
when not defined(windows):
|
|
block walkDirRelative:
|
|
createDir("walkdir_test")
|
|
createSymlink(".", "walkdir_test/c")
|
|
for k, p in walkDir("walkdir_test", true):
|
|
doAssert k == pcLinkToDir
|
|
removeDir("walkdir_test")
|
|
|
|
when defined(posix):
|
|
block walkDirSpecial:
|
|
createDir("walkdir_test")
|
|
doAssert execShellCmd("mkfifo walkdir_test/fifo") == 0
|
|
createSymlink("fifo", "walkdir_test/fifo_link")
|
|
let withSpecialFiles = toSeq(walkDir("walkdir_test", relative = true))
|
|
doAssert (withSpecialFiles.len == 2 and
|
|
(pcFile, "fifo") in withSpecialFiles and
|
|
(pcLinkToFile, "fifo_link") in withSpecialFiles)
|
|
# now Unix special files are excluded from walkdir output:
|
|
let skipSpecialFiles = toSeq(walkDir("walkdir_test", relative = true,
|
|
skipSpecial = true))
|
|
doAssert skipSpecialFiles.len == 0
|
|
removeDir("walkdir_test")
|
|
|
|
block normalizedPath:
|
|
doAssert normalizedPath("") == ""
|
|
block relative:
|
|
doAssert normalizedPath(".") == "."
|
|
doAssert normalizedPath("foo/..") == "."
|
|
doAssert normalizedPath("foo//../bar/.") == "bar"
|
|
doAssert normalizedPath("..") == ".."
|
|
doAssert normalizedPath("../") == ".."
|
|
doAssert normalizedPath("../..") == unixToNativePath"../.."
|
|
doAssert normalizedPath("../a/..") == ".."
|
|
doAssert normalizedPath("../a/../") == ".."
|
|
doAssert normalizedPath("./") == "."
|
|
|
|
block absolute:
|
|
doAssert normalizedPath("/") == unixToNativePath"/"
|
|
doAssert normalizedPath("/.") == unixToNativePath"/"
|
|
doAssert normalizedPath("/..") == unixToNativePath"/.."
|
|
doAssert normalizedPath("/../") == unixToNativePath"/.."
|
|
doAssert normalizedPath("/../..") == unixToNativePath"/../.."
|
|
doAssert normalizedPath("/../../") == unixToNativePath"/../.."
|
|
doAssert normalizedPath("/../../../") == unixToNativePath"/../../.."
|
|
doAssert normalizedPath("/a/b/../../foo") == unixToNativePath"/foo"
|
|
doAssert normalizedPath("/a/b/../../../foo") == unixToNativePath"/../foo"
|
|
doAssert normalizedPath("/./") == unixToNativePath"/"
|
|
doAssert normalizedPath("//") == unixToNativePath"/"
|
|
doAssert normalizedPath("///") == unixToNativePath"/"
|
|
doAssert normalizedPath("/a//b") == unixToNativePath"/a/b"
|
|
doAssert normalizedPath("/a///b") == unixToNativePath"/a/b"
|
|
doAssert normalizedPath("/a/b/c/..") == unixToNativePath"/a/b"
|
|
doAssert normalizedPath("/a/b/c/../") == unixToNativePath"/a/b"
|
|
|
|
block isHidden:
|
|
when defined(posix):
|
|
doAssert ".foo.txt".isHidden
|
|
doAssert "bar/.foo.ext".isHidden
|
|
doAssert not "bar".isHidden
|
|
doAssert not "foo/".isHidden
|
|
doAssert ".foo/.".isHidden
|
|
# Corner cases: `isHidden` is not yet `..` aware
|
|
doAssert not ".foo/..".isHidden
|
|
|
|
block absolutePath:
|
|
doAssertRaises(ValueError): discard absolutePath("a", "b")
|
|
doAssert absolutePath("a") == getCurrentDir() / "a"
|
|
doAssert absolutePath("a", "/b") == "/b" / "a"
|
|
when defined(posix):
|
|
doAssert absolutePath("a", "/b/") == "/b" / "a"
|
|
doAssert absolutePath("a", "/b/c") == "/b/c" / "a"
|
|
doAssert absolutePath("/a", "b/") == "/a"
|
|
|
|
block splitFile:
|
|
doAssert splitFile("") == ("", "", "")
|
|
doAssert splitFile("abc/") == ("abc", "", "")
|
|
doAssert splitFile("/") == ("/", "", "")
|
|
doAssert splitFile("./abc") == (".", "abc", "")
|
|
doAssert splitFile(".txt") == ("", ".txt", "")
|
|
doAssert splitFile("abc/.txt") == ("abc", ".txt", "")
|
|
doAssert splitFile("abc") == ("", "abc", "")
|
|
doAssert splitFile("abc.txt") == ("", "abc", ".txt")
|
|
doAssert splitFile("/abc.txt") == ("/", "abc", ".txt")
|
|
doAssert splitFile("/foo/abc.txt") == ("/foo", "abc", ".txt")
|
|
doAssert splitFile("/foo/abc.txt.gz") == ("/foo", "abc.txt", ".gz")
|
|
doAssert splitFile(".") == ("", ".", "")
|
|
doAssert splitFile("abc/.") == ("abc", ".", "")
|
|
doAssert splitFile("..") == ("", "..", "")
|
|
doAssert splitFile("a/..") == ("a", "..", "")
|
|
doAssert splitFile("/foo/abc....txt") == ("/foo", "abc...", ".txt")
|
|
|
|
# execShellCmd is tested in tosproc
|
|
|
|
block ospaths:
|
|
doAssert unixToNativePath("") == ""
|
|
doAssert unixToNativePath(".") == $CurDir
|
|
doAssert unixToNativePath("..") == $ParDir
|
|
doAssert isAbsolute(unixToNativePath("/"))
|
|
doAssert isAbsolute(unixToNativePath("/", "a"))
|
|
doAssert isAbsolute(unixToNativePath("/a"))
|
|
doAssert isAbsolute(unixToNativePath("/a", "a"))
|
|
doAssert isAbsolute(unixToNativePath("/a/b"))
|
|
doAssert isAbsolute(unixToNativePath("/a/b", "a"))
|
|
doAssert unixToNativePath("a/b") == joinPath("a", "b")
|
|
|
|
when defined(macos):
|
|
doAssert unixToNativePath("./") == ":"
|
|
doAssert unixToNativePath("./abc") == ":abc"
|
|
doAssert unixToNativePath("../abc") == "::abc"
|
|
doAssert unixToNativePath("../../abc") == ":::abc"
|
|
doAssert unixToNativePath("/abc", "a") == "abc"
|
|
doAssert unixToNativePath("/abc/def", "a") == "abc:def"
|
|
elif doslikeFileSystem:
|
|
doAssert unixToNativePath("./") == ".\\"
|
|
doAssert unixToNativePath("./abc") == ".\\abc"
|
|
doAssert unixToNativePath("../abc") == "..\\abc"
|
|
doAssert unixToNativePath("../../abc") == "..\\..\\abc"
|
|
doAssert unixToNativePath("/abc", "a") == "a:\\abc"
|
|
doAssert unixToNativePath("/abc/def", "a") == "a:\\abc\\def"
|
|
else:
|
|
#Tests for unix
|
|
doAssert unixToNativePath("./") == "./"
|
|
doAssert unixToNativePath("./abc") == "./abc"
|
|
doAssert unixToNativePath("../abc") == "../abc"
|
|
doAssert unixToNativePath("../../abc") == "../../abc"
|
|
doAssert unixToNativePath("/abc", "a") == "/abc"
|
|
doAssert unixToNativePath("/abc/def", "a") == "/abc/def"
|
|
|
|
block extractFilenameTest:
|
|
doAssert extractFilename("") == ""
|
|
when defined(posix):
|
|
doAssert extractFilename("foo/bar") == "bar"
|
|
doAssert extractFilename("foo/bar.txt") == "bar.txt"
|
|
doAssert extractFilename("foo/") == ""
|
|
doAssert extractFilename("/") == ""
|
|
when doslikeFileSystem:
|
|
doAssert extractFilename(r"foo\bar") == "bar"
|
|
doAssert extractFilename(r"foo\bar.txt") == "bar.txt"
|
|
doAssert extractFilename(r"foo\") == ""
|
|
doAssert extractFilename(r"C:\") == ""
|
|
|
|
block lastPathPartTest:
|
|
doAssert lastPathPart("") == ""
|
|
when defined(posix):
|
|
doAssert lastPathPart("foo/bar.txt") == "bar.txt"
|
|
doAssert lastPathPart("foo/") == "foo"
|
|
doAssert lastPathPart("/") == ""
|
|
when doslikeFileSystem:
|
|
doAssert lastPathPart(r"foo\bar.txt") == "bar.txt"
|
|
doAssert lastPathPart(r"foo\") == "foo"
|
|
|
|
template canon(x): untyped = normalizePath(x, '/')
|
|
doAssert canon"/foo/../bar" == "/bar"
|
|
doAssert canon"foo/../bar" == "bar"
|
|
|
|
doAssert canon"/f/../bar///" == "/bar"
|
|
doAssert canon"f/..////bar" == "bar"
|
|
|
|
doAssert canon"../bar" == "../bar"
|
|
doAssert canon"/../bar" == "/../bar"
|
|
|
|
doAssert canon("foo/../../bar/") == "../bar"
|
|
doAssert canon("./bla/blob/") == "bla/blob"
|
|
doAssert canon(".hiddenFile") == ".hiddenFile"
|
|
doAssert canon("./bla/../../blob/./zoo.nim") == "../blob/zoo.nim"
|
|
|
|
doAssert canon("C:/file/to/this/long") == "C:/file/to/this/long"
|
|
doAssert canon("") == ""
|
|
doAssert canon("foobar") == "foobar"
|
|
doAssert canon("f/////////") == "f"
|
|
|
|
doAssert relativePath("/foo/bar//baz.nim", "/foo", '/') == "bar/baz.nim"
|
|
doAssert normalizePath("./foo//bar/../baz", '/') == "foo/baz"
|
|
|
|
doAssert relativePath("/Users/me/bar/z.nim", "/Users/other/bad", '/') == "../../me/bar/z.nim"
|
|
|
|
doAssert relativePath("/Users/me/bar/z.nim", "/Users/other", '/') == "../me/bar/z.nim"
|
|
|
|
# `//` is a UNC path, `/` is the current working directory's drive, so can't
|
|
# run this test on Windows.
|
|
when not doslikeFileSystem:
|
|
doAssert relativePath("/Users///me/bar//z.nim", "//Users/", '/') == "me/bar/z.nim"
|
|
doAssert relativePath("/Users/me/bar/z.nim", "/Users/me", '/') == "bar/z.nim"
|
|
doAssert relativePath("", "/users/moo", '/') == ""
|
|
doAssert relativePath("foo", "", '/') == "foo"
|
|
doAssert relativePath("/foo", "/Foo", '/') == (when FileSystemCaseSensitive: "../foo" else: ".")
|
|
doAssert relativePath("/Foo", "/foo", '/') == (when FileSystemCaseSensitive: "../Foo" else: ".")
|
|
doAssert relativePath("/foo", "/fOO", '/') == (when FileSystemCaseSensitive: "../foo" else: ".")
|
|
doAssert relativePath("/foO", "/foo", '/') == (when FileSystemCaseSensitive: "../foO" else: ".")
|
|
|
|
doAssert relativePath("foo", ".", '/') == "foo"
|
|
doAssert relativePath(".", ".", '/') == "."
|
|
doAssert relativePath("..", ".", '/') == ".."
|
|
|
|
doAssert relativePath("foo", "foo") == "."
|
|
doAssert relativePath("", "foo") == ""
|
|
doAssert relativePath("././/foo", "foo//./") == "."
|
|
|
|
doAssert relativePath(getCurrentDir() / "bar", "foo") == "../bar".unixToNativePath
|
|
doAssert relativePath("bar", getCurrentDir() / "foo") == "../bar".unixToNativePath
|
|
|
|
when doslikeFileSystem:
|
|
doAssert relativePath(r"c:\foo.nim", r"C:\") == r"foo.nim"
|
|
doAssert relativePath(r"c:\foo\bar\baz.nim", r"c:\foo") == r"bar\baz.nim"
|
|
doAssert relativePath(r"c:\foo\bar\baz.nim", r"d:\foo") == r"c:\foo\bar\baz.nim"
|
|
doAssert relativePath(r"\foo\baz.nim", r"\foo") == r"baz.nim"
|
|
doAssert relativePath(r"\foo\bar\baz.nim", r"\bar") == r"..\foo\bar\baz.nim"
|
|
doAssert relativePath(r"\\foo\bar\baz.nim", r"\\foo\bar") == r"baz.nim"
|
|
doAssert relativePath(r"\\foo\bar\baz.nim", r"\\foO\bar") == r"baz.nim"
|
|
doAssert relativePath(r"\\foo\bar\baz.nim", r"\\bar\bar") == r"\\foo\bar\baz.nim"
|
|
doAssert relativePath(r"\\foo\bar\baz.nim", r"\\foo\car") == r"\\foo\bar\baz.nim"
|
|
doAssert relativePath(r"\\foo\bar\baz.nim", r"\\goo\bar") == r"\\foo\bar\baz.nim"
|
|
doAssert relativePath(r"\\foo\bar\baz.nim", r"c:\") == r"\\foo\bar\baz.nim"
|
|
doAssert relativePath(r"\\foo\bar\baz.nim", r"\foo") == r"\\foo\bar\baz.nim"
|
|
doAssert relativePath(r"c:\foo.nim", r"\foo") == r"c:\foo.nim"
|
|
|
|
doAssert joinPath("usr", "") == unixToNativePath"usr"
|
|
doAssert joinPath("", "lib") == "lib"
|
|
doAssert joinPath("", "/lib") == unixToNativePath"/lib"
|
|
doAssert joinPath("usr/", "/lib") == unixToNativePath"usr/lib"
|
|
doAssert joinPath("", "") == unixToNativePath"" # issue #13455
|
|
doAssert joinPath("", "/") == unixToNativePath"/"
|
|
doAssert joinPath("/", "/") == unixToNativePath"/"
|
|
doAssert joinPath("/", "") == unixToNativePath"/"
|
|
doAssert joinPath("/" / "") == unixToNativePath"/" # weird test case...
|
|
doAssert joinPath("/", "/a/b/c") == unixToNativePath"/a/b/c"
|
|
doAssert joinPath("foo/", "") == unixToNativePath"foo/"
|
|
doAssert joinPath("foo/", "abc") == unixToNativePath"foo/abc"
|
|
doAssert joinPath("foo//./", "abc/.//") == unixToNativePath"foo/abc/"
|
|
doAssert joinPath("foo", "abc") == unixToNativePath"foo/abc"
|
|
doAssert joinPath("", "abc") == unixToNativePath"abc"
|
|
|
|
doAssert joinPath("zook/.", "abc") == unixToNativePath"zook/abc"
|
|
|
|
# controversial: inconsistent with `joinPath("zook/.","abc")`
|
|
# on linux, `./foo` and `foo` are treated a bit differently for executables
|
|
# but not `./foo/bar` and `foo/bar`
|
|
doAssert joinPath(".", "/lib") == unixToNativePath"./lib"
|
|
doAssert joinPath(".", "abc") == unixToNativePath"./abc"
|
|
|
|
# cases related to issue #13455
|
|
doAssert joinPath("foo", "", "") == "foo"
|
|
doAssert joinPath("foo", "") == "foo"
|
|
doAssert joinPath("foo/", "") == unixToNativePath"foo/"
|
|
doAssert joinPath("foo/", ".") == "foo"
|
|
doAssert joinPath("foo", "./") == unixToNativePath"foo/"
|
|
doAssert joinPath("foo", "", "bar/") == unixToNativePath"foo/bar/"
|
|
|
|
# issue #13579
|
|
doAssert joinPath("/foo", "../a") == unixToNativePath"/a"
|
|
doAssert joinPath("/foo/", "../a") == unixToNativePath"/a"
|
|
doAssert joinPath("/foo/.", "../a") == unixToNativePath"/a"
|
|
doAssert joinPath("/foo/.b", "../a") == unixToNativePath"/foo/a"
|
|
doAssert joinPath("/foo///", "..//a/") == unixToNativePath"/a/"
|
|
doAssert joinPath("foo/", "../a") == unixToNativePath"a"
|
|
|
|
when doslikeFileSystem:
|
|
doAssert joinPath("C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\Common7\\Tools\\", "..\\..\\VC\\vcvarsall.bat") == r"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat"
|
|
doAssert joinPath("C:\\foo", "..\\a") == r"C:\a"
|
|
doAssert joinPath("C:\\foo\\", "..\\a") == r"C:\a"
|
|
|
|
block getTempDir:
|
|
block TMPDIR:
|
|
# TMPDIR env var is not used if either of these are defined.
|
|
when not (defined(tempDir) or defined(windows) or defined(android)):
|
|
if existsEnv("TMPDIR"):
|
|
let origTmpDir = getEnv("TMPDIR")
|
|
putEnv("TMPDIR", "/mytmp")
|
|
doAssert getTempDir() == "/mytmp/"
|
|
delEnv("TMPDIR")
|
|
doAssert getTempDir() == "/tmp/"
|
|
putEnv("TMPDIR", origTmpDir)
|
|
else:
|
|
doAssert getTempDir() == "/tmp/"
|
|
|
|
block: # getCacheDir
|
|
doAssert getCacheDir().dirExists
|
|
|
|
block isRelativeTo:
|
|
doAssert isRelativeTo("/foo", "/")
|
|
doAssert isRelativeTo("/foo/bar", "/foo")
|
|
doAssert isRelativeTo("foo/bar", "foo")
|
|
doAssert isRelativeTo("/foo/bar.nim", "/foo/bar.nim")
|
|
doAssert isRelativeTo("./foo/", "foo")
|
|
doAssert isRelativeTo("foo", "./foo/")
|
|
doAssert isRelativeTo(".", ".")
|
|
doAssert isRelativeTo("foo/bar", ".")
|
|
doAssert not isRelativeTo("foo/bar.nims", "foo/bar.nim")
|
|
doAssert not isRelativeTo("/foo2", "/foo")
|
|
|
|
block: # quoteShellWindows
|
|
doAssert quoteShellWindows("aaa") == "aaa"
|
|
doAssert quoteShellWindows("aaa\"") == "aaa\\\""
|
|
doAssert quoteShellWindows("") == "\"\""
|
|
|
|
block: # quoteShellCommand
|
|
when defined(windows):
|
|
doAssert quoteShellCommand(["a b c", "d", "e"]) == """"a b c" d e"""
|
|
doAssert quoteShellCommand(["""ab"c""", r"\", "d"]) == """ab\"c \ d"""
|
|
doAssert quoteShellCommand(["""ab"c""", """ \""", "d"]) == """ab\"c " \\" d"""
|
|
doAssert quoteShellCommand(["""a\\\b""", """de fg""", "h"]) == """a\\\b "de fg" h"""
|
|
doAssert quoteShellCommand(["""a\"b""", "c", "d"]) == """a\\\"b c d"""
|
|
doAssert quoteShellCommand(["""a\\b c""", "d", "e"]) == """"a\\b c" d e"""
|
|
doAssert quoteShellCommand(["""a\\b\ c""", "d", "e"]) == """"a\\b\ c" d e"""
|
|
doAssert quoteShellCommand(["ab", ""]) == """ab """""
|
|
|
|
block: # quoteShellPosix
|
|
doAssert quoteShellPosix("aaa") == "aaa"
|
|
doAssert quoteShellPosix("aaa a") == "'aaa a'"
|
|
doAssert quoteShellPosix("") == "''"
|
|
doAssert quoteShellPosix("a'a") == "'a'\"'\"'a'"
|
|
|
|
block: # quoteShell
|
|
when defined(posix):
|
|
doAssert quoteShell("") == "''"
|
|
|
|
block: # normalizePathEnd
|
|
# handle edge cases correctly: shouldn't affect whether path is
|
|
# absolute/relative
|
|
doAssert "".normalizePathEnd(true) == ""
|
|
doAssert "".normalizePathEnd(false) == ""
|
|
doAssert "/".normalizePathEnd(true) == $DirSep
|
|
doAssert "/".normalizePathEnd(false) == $DirSep
|
|
|
|
when defined(posix):
|
|
doAssert "//".normalizePathEnd(false) == "/"
|
|
doAssert "foo.bar//".normalizePathEnd == "foo.bar"
|
|
doAssert "bar//".normalizePathEnd(trailingSep = true) == "bar/"
|
|
when defined(windows):
|
|
doAssert r"C:\foo\\".normalizePathEnd == r"C:\foo"
|
|
doAssert r"C:\foo".normalizePathEnd(trailingSep = true) == r"C:\foo\"
|
|
# this one is controversial: we could argue for returning `D:\` instead,
|
|
# but this is simplest.
|
|
doAssert r"D:\".normalizePathEnd == r"D:"
|
|
doAssert r"E:/".normalizePathEnd(trailingSep = true) == r"E:\"
|
|
doAssert "/".normalizePathEnd == r"\"
|
|
|
|
|
|
import sugar
|
|
|
|
block: # normalizeExe
|
|
doAssert "".dup(normalizeExe) == ""
|
|
when defined(posix):
|
|
doAssert "foo".dup(normalizeExe) == "./foo"
|
|
doAssert "foo/../bar".dup(normalizeExe) == "foo/../bar"
|
|
when defined(windows):
|
|
doAssert "foo".dup(normalizeExe) == "foo"
|
|
|
|
block: # isAdmin
|
|
let isAzure = existsEnv("TF_BUILD") # xxx factor with testament.specs.isAzure
|
|
# In Azure on Windows tests run as an admin user
|
|
if isAzure and defined(windows): doAssert isAdmin()
|
|
# In Azure on POSIX tests run as a normal user
|
|
if isAzure and defined(posix): doAssert not isAdmin()
|
|
|
|
|
|
import sugar
|
|
|
|
block: # normalizeExe
|
|
doAssert "".dup(normalizeExe) == ""
|
|
when defined(posix):
|
|
doAssert "foo".dup(normalizeExe) == "./foo"
|
|
doAssert "foo/../bar".dup(normalizeExe) == "foo/../bar"
|
|
when defined(windows):
|
|
doAssert "foo".dup(normalizeExe) == "foo"
|
|
|
|
block: # isAdmin
|
|
let isAzure = existsEnv("TF_BUILD") # xxx factor with testament.specs.isAzure
|
|
# In Azure on Windows tests run as an admin user
|
|
if isAzure and defined(windows): doAssert isAdmin()
|
|
# In Azure on POSIX tests run as a normal user
|
|
if isAzure and defined(posix): doAssert not isAdmin()
|
|
|
|
when doslikeFileSystem:
|
|
import std/private/ntpath
|
|
|
|
block: # Bug #19103 UNC paths
|
|
|
|
# Easiest way of generating a valid, readable and writable UNC path
|
|
let tempDir = r"\\?\" & getTempDir()
|
|
doAssert dirExists tempDir
|
|
createDir tempDir / "test"
|
|
removeDir tempDir / "test"
|
|
createDir tempDir / "recursive" / "test"
|
|
removeDir tempDir / "recursive" / "test"
|
|
|
|
let tempDir2 = getTempDir()
|
|
let (drive, pathNoDrive) = splitDrive(tempDir2)
|
|
setCurrentDir drive
|
|
doAssert cmpIgnoreCase(getCurrentDir().splitDrive.drive, drive) == 0
|
|
|
|
# Test `\Users` path syntax on Windows by stripping away drive. `\`
|
|
# resolves to the drive in current working directory. This drive will be
|
|
# the same as `tempDir2` because of the `setCurrentDir` above.
|
|
doAssert pathNoDrive[0] == '\\'
|
|
createDir pathNoDrive / "test"
|
|
doAssert dirExists pathNoDrive / "test"
|
|
removeDir pathNoDrive / "test"
|
|
|
|
doAssert splitPath("//?/c:") == ("//?/c:", "")
|
|
|
|
doAssert relativePath("//?/c:///Users//me", "//?/c:", '/') == "Users/me"
|
|
|
|
doAssert parentDir(r"\\?\c:") == r""
|
|
doAssert parentDir(r"//?/c:/Users") == r"\\?\c:"
|
|
doAssert parentDir(r"\\localhost\c$") == r""
|
|
doAssert parentDir(r"\Users") == r"\"
|
|
|
|
doAssert tailDir("//?/c:") == ""
|
|
doAssert tailDir("//?/c:/Users") == "Users"
|
|
doAssert tailDir(r"\\localhost\c$\Windows\System32") == r"Windows\System32"
|
|
|
|
doAssert isRootDir("//?/c:")
|
|
doAssert isRootDir("//?/UNC/localhost/c$")
|
|
doAssert not isRootDir(r"\\?\c:\Users")
|
|
|
|
doAssert parentDirs(r"C:\Users", fromRoot = true).toSeq == @[r"C:\", r"C:\Users"]
|
|
doAssert parentDirs(r"C:\Users", fromRoot = false).toSeq == @[r"C:\Users", r"C:"]
|
|
doAssert parentDirs(r"\\?\c:\Users", fromRoot = true).toSeq ==
|
|
@[r"\\?\c:\", r"\\?\c:\Users"]
|
|
doAssert parentDirs(r"\\?\c:\Users", fromRoot = false).toSeq ==
|
|
@[r"\\?\c:\Users", r"\\?\c:"]
|
|
doAssert parentDirs(r"//localhost/c$/Users", fromRoot = true).toSeq ==
|
|
@[r"//localhost/c$/", r"//localhost/c$/Users"]
|
|
doAssert parentDirs(r"//?/UNC/localhost/c$/Users", fromRoot = false).toSeq ==
|
|
@[r"//?/UNC/localhost/c$/Users", r"\\?\UNC\localhost\c$"]
|
|
doAssert parentDirs(r"\Users", fromRoot = true).toSeq == @[r"\", r"\Users"]
|
|
doAssert parentDirs(r"\Users", fromRoot = false).toSeq == @[r"\Users", r"\"]
|
|
|
|
doAssert r"//?/c:" /../ "d/e" == r"\\?\c:\d\e"
|
|
doAssert r"//?/c:/Users" /../ "d/e" == r"\\?\c:\d\e"
|
|
doAssert r"\\localhost\c$" /../ "d/e" == r"\\localhost\c$\d\e"
|
|
|
|
doAssert splitFile("//?/c:") == ("//?/c:", "", "")
|
|
doAssert splitFile("//?/c:/Users") == ("//?/c:", "Users", "")
|
|
doAssert splitFile(r"\\localhost\c$\test.txt") == (r"\\localhost\c$", "test", ".txt")
|
|
|
|
else:
|
|
block: # parentDirs
|
|
doAssert parentDirs("/home", fromRoot=true).toSeq == @["/", "/home"]
|
|
doAssert parentDirs("/home", fromRoot=false).toSeq == @["/home", "/"]
|
|
doAssert parentDirs("home", fromRoot=true).toSeq == @["home"]
|
|
doAssert parentDirs("home", fromRoot=false).toSeq == @["home"]
|
|
|
|
doAssert parentDirs("/home/user", fromRoot=true).toSeq == @["/", "/home/", "/home/user"]
|
|
doAssert parentDirs("/home/user", fromRoot=false).toSeq == @["/home/user", "/home", "/"]
|
|
doAssert parentDirs("home/user", fromRoot=true).toSeq == @["home/", "home/user"]
|
|
doAssert parentDirs("home/user", fromRoot=false).toSeq == @["home/user", "home"]
|
|
|
|
|
|
# https://github.com/nim-lang/Nim/pull/19643#issuecomment-1235102314
|
|
block: # isValidFilename
|
|
# Negative Tests.
|
|
doAssert not isValidFilename("abcd", maxLen = 2)
|
|
doAssert not isValidFilename("0123456789", maxLen = 8)
|
|
doAssert not isValidFilename("con")
|
|
doAssert not isValidFilename("aux")
|
|
doAssert not isValidFilename("prn")
|
|
doAssert not isValidFilename("OwO|UwU")
|
|
doAssert not isValidFilename(" foo")
|
|
doAssert not isValidFilename("foo ")
|
|
doAssert not isValidFilename("foo.")
|
|
doAssert not isValidFilename("con.txt")
|
|
doAssert not isValidFilename("aux.bat")
|
|
doAssert not isValidFilename("prn.exe")
|
|
doAssert not isValidFilename("nim>.nim")
|
|
doAssert not isValidFilename(" foo.log")
|
|
# Positive Tests.
|
|
doAssert isValidFilename("abcd", maxLen = 42.Positive)
|
|
doAssert isValidFilename("c0n")
|
|
doAssert isValidFilename("foo.aux")
|
|
doAssert isValidFilename("bar.prn")
|
|
doAssert isValidFilename("OwO_UwU")
|
|
doAssert isValidFilename("cron")
|
|
doAssert isValidFilename("ux.bat")
|
|
doAssert isValidFilename("nim.nim")
|
|
doAssert isValidFilename("foo.log")
|
|
|
|
block: # searchExtPos
|
|
doAssert "foo.nim".searchExtPos == 3
|
|
doAssert "/foo.nim".searchExtPos == 4
|
|
doAssert "".searchExtPos == -1
|
|
doAssert "/".searchExtPos == -1
|
|
doAssert "a.b/foo".searchExtPos == -1
|
|
doAssert ".".searchExtPos == -1
|
|
doAssert "foo.".searchExtPos == 3
|
|
doAssert "foo..".searchExtPos == 4
|
|
doAssert "..".searchExtPos == -1
|
|
doAssert "...".searchExtPos == -1
|
|
doAssert "./".searchExtPos == -1
|
|
doAssert "../".searchExtPos == -1
|
|
doAssert "/.".searchExtPos == -1
|
|
doAssert "/..".searchExtPos == -1
|
|
doAssert ".b".searchExtPos == -1
|
|
doAssert "..b".searchExtPos == -1
|
|
doAssert "/.b".searchExtPos == -1
|
|
doAssert "a/.b".searchExtPos == -1
|
|
doAssert ".a.b".searchExtPos == 2
|
|
doAssert "a/.b.c".searchExtPos == 4
|
|
doAssert "a/..b".searchExtPos == -1
|
|
doAssert "a/b..c".searchExtPos == 4
|
|
|
|
when doslikeFileSystem:
|
|
doAssert "c:a.b".searchExtPos == 3
|
|
doAssert "c:.a".searchExtPos == -1
|
|
doAssert r"c:\.a".searchExtPos == -1
|
|
doAssert "c:..a".searchExtPos == -1
|
|
doAssert r"c:\..a".searchExtPos == -1
|
|
doAssert "c:.a.b".searchExtPos == 4
|