Skip to content

Commit 7969f52

Browse files
authored
Merge pull request #4734 from mwichmann/test/node-unc
FS unit test fixes for UNC paths
2 parents 8ddc148 + ffa010a commit 7969f52

4 files changed

Lines changed: 125 additions & 95 deletions

File tree

CHANGES.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER
113113
of a list of actions. Recognition only happens if the special
114114
action is first in the list. Initial suggestion from Julien Pommier.
115115
Fixes #4731.
116+
- Fix a test problem on Windows where UNC tests failed due to incorrect
117+
path munging if a non-default %TEMP% was defined (as in moving to
118+
a Dev Drive). Also some cleanup.
116119

117120

118121
RELEASE 4.9.1 - Thu, 27 Mar 2025 11:40:20 -0700

RELEASE.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ FIXES
7373
of a list of actions. Recognition only happens if the special
7474
action is first in the list.
7575

76+
- Fix a test problem on Windows where UNC tests failed due to incorrect path
77+
munging if a non-default %TEMP% was defined (as in moving to a Dev Drive).
78+
7679
IMPROVEMENTS
7780
------------
7881

SCons/Node/FS.py

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -145,17 +145,29 @@ def initialize_do_splitdrive() -> None:
145145
do_splitdrive = bool(os.path.splitdrive('X:/foo')[0])
146146

147147
if do_splitdrive:
148+
# Note there's a little dilemma here - we should be able to use the
149+
# Python os.path.splitdrive, which is well debugged over the years.
150+
# For normal usage it should work, but we also want to as much as
151+
# possible run the full testsuite on whichever platform, even if it's
152+
# "wrong" for some feature. POSIX splitdrive doesn't do what we want
153+
# when running the drive and UNC path tests, and the NT one isn't
154+
# designed for use on non-win32. So for now stuck on our private one.
155+
# _my_splitdrive = os.path.splitdrive
156+
148157
def _my_splitdrive(p):
149158
if p[1:2] == ':':
150159
return p[:2], p[2:]
151160
if p[0:2] == '//':
152-
# Note that we leave a leading slash in the path
153-
# because UNC paths are always absolute.
161+
# We leave a leading slash in the path because UNC paths
162+
# are always absolute.
163+
#
164+
# TODO: returning "//" for the drive part is actually
165+
# completely wrong. UNC paths must have minimum three
166+
# slashes (if using '//server/share/path' style), or five
167+
# ('//?/UNC/server/share/path)' and we should return the
168+
# part up to but not including the next slash.
154169
return '//', p[1:]
155170
return '', p
156-
# TODO: the os routine should work and be better debugged than ours,
157-
# but unit test test_unc_path fails on POSIX platforms. Resolve someday.
158-
# _my_splitdrive = os.path.splitdrive
159171

160172
# Keep some commonly used values in global variables to skip to
161173
# module look-up costs.
@@ -1011,7 +1023,7 @@ def __init__(self, name, directory, fs) -> None:
10111023
def diskcheck_match(self) -> None:
10121024
pass
10131025

1014-
def disambiguate(self, must_exist=None):
1026+
def disambiguate(self, must_exist=False):
10151027
"""
10161028
"""
10171029
if self.isfile():
@@ -1069,7 +1081,7 @@ def get_text_contents(self) -> str:
10691081
system, we check to see into what sort of subclass we should
10701082
morph this Entry."""
10711083
try:
1072-
self = self.disambiguate(must_exist=1)
1084+
self = self.disambiguate(must_exist=True)
10731085
except SCons.Errors.UserError:
10741086
# There was nothing on disk with which to disambiguate
10751087
# this entry. Leave it as an Entry, but return a null

SCons/Node/FSTests.py

Lines changed: 100 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,7 +1053,7 @@ def test_runTest(self):
10531053

10541054
seps = [os.sep]
10551055
if os.sep != '/':
1056-
seps = seps + ['/']
1056+
seps.append('/')
10571057

10581058
drive, path = os.path.splitdrive(os.getcwd())
10591059

@@ -1089,21 +1089,16 @@ def strip_slash(p, drive=drive):
10891089
else:
10901090
dir_up_path = dir.up().get_internal_path()
10911091

1092-
assert dir.name == name, \
1093-
"dir.name %s != expected name %s" % \
1094-
(dir.name, name)
1095-
assert dir.get_internal_path() == path, \
1096-
"dir.path %s != expected path %s" % \
1097-
(dir.get_internal_path(), path)
1098-
assert str(dir) == path, \
1099-
"str(dir) %s != expected path %s" % \
1100-
(str(dir), path)
1101-
assert dir.get_abspath() == abspath, \
1102-
"dir.abspath %s != expected absolute path %s" % \
1103-
(dir.get_abspath(), abspath)
1104-
assert dir_up_path == up_path, \
1105-
"dir.up().path %s != expected parent path %s" % \
1106-
(dir_up_path, up_path)
1092+
with self.subTest():
1093+
self.assertEqual(dir.name, name)
1094+
with self.subTest():
1095+
self.assertEqual(dir.get_internal_path(), path)
1096+
with self.subTest():
1097+
self.assertEqual(str(dir), path)
1098+
with self.subTest():
1099+
self.assertEqual(dir.get_abspath(), abspath)
1100+
with self.subTest():
1101+
self.assertEqual(dir_up_path, up_path)
11071102

11081103
for sep in seps:
11091104

@@ -1583,44 +1578,40 @@ def test_remove_fail(self) -> None:
15831578

15841579
def test_drive_letters(self) -> None:
15851580
"""Test drive-letter look-ups"""
1586-
15871581
test = self.test
1588-
15891582
test.subdir('sub', ['sub', 'dir'])
15901583

1584+
seps = [os.sep]
1585+
if os.sep != '/':
1586+
seps.append('/')
1587+
15911588
def drive_workpath(dirs, test=test):
15921589
x = test.workpath(*dirs)
15931590
drive, path = os.path.splitdrive(x)
15941591
return 'X:' + path
15951592

15961593
wp = drive_workpath([''])
1597-
1598-
if wp[-1] in (os.sep, '/'):
1594+
if wp[-1] in seps:
15991595
tmp = os.path.split(wp[:-1])[0]
16001596
else:
16011597
tmp = os.path.split(wp)[0]
1602-
16031598
parent_tmp = os.path.split(tmp)[0]
16041599
if parent_tmp == 'X:':
16051600
parent_tmp = 'X:' + os.sep
1606-
16071601
tmp_foo = os.path.join(tmp, 'foo')
1608-
16091602
foo = drive_workpath(['foo'])
16101603
foo_bar = drive_workpath(['foo', 'bar'])
1611-
sub = drive_workpath(['sub', ''])
1612-
sub_dir = drive_workpath(['sub', 'dir', ''])
1613-
sub_dir_foo = drive_workpath(['sub', 'dir', 'foo', ''])
1614-
sub_dir_foo_bar = drive_workpath(['sub', 'dir', 'foo', 'bar', ''])
1615-
sub_foo = drive_workpath(['sub', 'foo', ''])
1604+
# The following are not used, but kept for reference.
1605+
# sub = drive_workpath(['sub', ''])
1606+
# sub_dir = drive_workpath(['sub', 'dir', ''])
1607+
# sub_dir_foo = drive_workpath(['sub', 'dir', 'foo', ''])
1608+
# sub_dir_foo_bar = drive_workpath(['sub', 'dir', 'foo', 'bar', ''])
1609+
# sub_foo = drive_workpath(['sub', 'foo', ''])
16161610

16171611
fs = SCons.Node.FS.FS()
16181612

1619-
seps = [os.sep]
1620-
if os.sep != '/':
1621-
seps = seps + ['/']
1622-
16231613
def _do_Dir_test(lpath, path_, up_path_, sep, fileSys=fs) -> None:
1614+
"""Test the Dir constructor with a given path and expected values."""
16241615
dir = fileSys.Dir(lpath.replace('/', sep))
16251616

16261617
if os.sep != '/':
@@ -1636,18 +1627,14 @@ def strip_slash(p):
16361627
up_path = strip_slash(up_path_)
16371628
name = path.split(os.sep)[-1]
16381629

1639-
assert dir.name == name, \
1640-
"dir.name %s != expected name %s" % \
1641-
(dir.name, name)
1642-
assert dir.get_internal_path() == path, \
1643-
"dir.path %s != expected path %s" % \
1644-
(dir.get_internal_path(), path)
1645-
assert str(dir) == path, \
1646-
"str(dir) %s != expected path %s" % \
1647-
(str(dir), path)
1648-
assert dir.up().get_internal_path() == up_path, \
1649-
"dir.up().path %s != expected parent path %s" % \
1650-
(dir.up().get_internal_path(), up_path)
1630+
with self.subTest():
1631+
self.assertEqual(dir.name, name)
1632+
with self.subTest():
1633+
self.assertEqual(dir.get_internal_path(), path)
1634+
with self.subTest():
1635+
self.assertEqual(str(dir), path)
1636+
with self.subTest():
1637+
self.assertEqual(dir.up().get_internal_path(), up_path)
16511638

16521639
save_os_path = os.path
16531640
save_os_sep = os.sep
@@ -1658,6 +1645,7 @@ def strip_slash(p):
16581645
SCons.Node.FS.initialize_do_splitdrive()
16591646

16601647
for sep in seps:
1648+
16611649
def Dir_test(lpath, path_, up_path_, sep=sep, func=_do_Dir_test):
16621650
return func(lpath, path_, up_path_, sep)
16631651

@@ -1684,59 +1672,73 @@ def Dir_test(lpath, path_, up_path_, sep=sep, func=_do_Dir_test):
16841672

16851673
def test_unc_path(self) -> None:
16861674
"""Test UNC path look-ups"""
1687-
16881675
test = self.test
1689-
16901676
test.subdir('sub', ['sub', 'dir'])
16911677

1692-
def strip_slash(p):
1693-
if p[-1] == os.sep and len(p) > 3:
1694-
p = p[:-1]
1695-
return p
1678+
seps = [os.sep]
1679+
if os.sep != '/':
1680+
seps.append('/')
16961681

16971682
def unc_workpath(dirs, test=test):
1683+
"""Return a UNC-style path for the given directory.
1684+
1685+
*dirs* is a list of directory names, which will be joined
1686+
together to derive the base of the path - any "drive letter"
1687+
bits split off. Makes no effort to create an actual *valid*
1688+
UNC path - does not add a server or share name, so it will not
1689+
actually work in real life, just for string testing.
1690+
1691+
So that tests will work on non-Windows systems, we use the ntpath
1692+
module to perform the splitdrive, as that's otherwise a no-op.
1693+
"""
16981694
import ntpath
1695+
1696+
# This duplicates the definition in _do_Dir_test below.
1697+
# It depends on the monkeypatching of os.sep so we just have two.
1698+
def strip_slash(p):
1699+
if p[-1] in seps and len(p) > 3:
1700+
p = p[:-1]
1701+
return p
1702+
16991703
x = test.workpath(*dirs)
17001704
drive, path = ntpath.splitdrive(x)
1701-
try:
1702-
unc, path = ntpath.splitunc(path)
1703-
except AttributeError:
1704-
# could be python 3.7 or newer, make sure splitdrive can do UNC
1705-
assert ntpath.splitdrive(r'\\split\drive\test')[0] == r'\\split\drive'
17061705
path = strip_slash(path)
17071706
return '//' + path[1:]
17081707

17091708
wp = unc_workpath([''])
1710-
1711-
if wp[-1] in (os.sep, '/'):
1712-
tmp = os.path.split(wp[:-1])[0]
1713-
else:
1714-
tmp = os.path.split(wp)[0]
1715-
1716-
parent_tmp = os.path.split(tmp)[0]
1717-
1709+
tmp = os.path.split(wp)[0]
1710+
# parent_tmp = os.path.split(tmp)[0]
17181711
tmp_foo = os.path.join(tmp, 'foo')
1719-
17201712
foo = unc_workpath(['foo'])
17211713
foo_bar = unc_workpath(['foo', 'bar'])
1722-
sub = unc_workpath(['sub', ''])
1723-
sub_dir = unc_workpath(['sub', 'dir', ''])
1724-
sub_dir_foo = unc_workpath(['sub', 'dir', 'foo', ''])
1725-
sub_dir_foo_bar = unc_workpath(['sub', 'dir', 'foo', 'bar', ''])
1726-
sub_foo = unc_workpath(['sub', 'foo', ''])
1714+
# The following are not used, but kept for reference.
1715+
# sub = unc_workpath(['sub', ''])
1716+
# sub_dir = unc_workpath(['sub', 'dir', ''])
1717+
# sub_dir_foo = unc_workpath(['sub', 'dir', 'foo', ''])
1718+
# sub_dir_foo_bar = unc_workpath(['sub', 'dir', 'foo', 'bar', ''])
1719+
# sub_foo = unc_workpath(['sub', 'foo', ''])
17271720

17281721
fs = SCons.Node.FS.FS()
17291722

1730-
seps = [os.sep]
1731-
if os.sep != '/':
1732-
seps = seps + ['/']
1733-
1734-
def _do_Dir_test(lpath, path, up_path, sep, fileSys=fs) -> None:
1723+
def _do_Dir_test(lpath, path_, up_path_, sep, fileSys=fs) -> None:
1724+
"""Test the Dir constructor with a given path and expected values."""
1725+
# before = time.perf_counter()
17351726
dir = fileSys.Dir(lpath.replace('/', sep))
1727+
# after = time.perf_counter()
1728+
# print(f"TIME: dir creation for {lpath!r} (as {dir.path!r}) took {after - before:.6f} seconds")
17361729

1730+
# convert to os-native paths
17371731
if os.sep != '/':
1738-
path = path.replace('/', os.sep)
1739-
up_path = up_path.replace('/', os.sep)
1732+
path_ = path_.replace('/', os.sep)
1733+
up_path_ = up_path_.replace('/', os.sep)
1734+
1735+
def strip_slash(p):
1736+
if p[-1] in os.sep and len(p) > 3:
1737+
p = p[:-1]
1738+
return p
1739+
1740+
path = strip_slash(path_)
1741+
up_path = strip_slash(up_path_)
17401742

17411743
if path == os.sep + os.sep:
17421744
name = os.sep + os.sep
@@ -1748,23 +1750,23 @@ def _do_Dir_test(lpath, path, up_path, sep, fileSys=fs) -> None:
17481750
else:
17491751
dir_up_path = dir.up().get_internal_path()
17501752

1751-
assert dir.name == name, \
1752-
"dir.name %s != expected name %s" % \
1753-
(dir.name, name)
1754-
assert dir.get_internal_path() == path, \
1755-
"dir.path %s != expected path %s" % \
1756-
(dir.get_internal_path(), path)
1757-
assert str(dir) == path, \
1758-
"str(dir) %s != expected path %s" % \
1759-
(str(dir), path)
1760-
assert dir_up_path == up_path, \
1761-
"dir.up().path %s != expected parent path %s" % \
1762-
(dir.up().get_internal_path(), up_path)
1753+
with self.subTest():
1754+
self.assertEqual(dir.name, name)
1755+
with self.subTest():
1756+
self.assertEqual(dir.get_internal_path(), path)
1757+
with self.subTest():
1758+
self.assertEqual(str(dir), path)
1759+
with self.subTest():
1760+
self.assertEqual(dir_up_path, up_path)
17631761

17641762
save_os_path = os.path
17651763
save_os_sep = os.sep
17661764
try:
17671765
import ntpath
1766+
1767+
# monkeypatch some things to look like Windows and force the
1768+
# splitdrive stuff in the Node package to re-init; we'll put
1769+
# it back later if we happened to be on a non-Windows platform.
17681770
os.path = ntpath
17691771
os.sep = '\\'
17701772
SCons.Node.FS.initialize_do_splitdrive()
@@ -1773,6 +1775,16 @@ def _do_Dir_test(lpath, path, up_path, sep, fileSys=fs) -> None:
17731775
def Dir_test(lpath, path_, up_path_, sep=sep, func=_do_Dir_test):
17741776
return func(lpath, path_, up_path_, sep)
17751777

1778+
# Note that if any of the UNC paths eventually makes it to
1779+
# Windows, and looks syntactically valid (\\server\share), the
1780+
# mkdir takes a *long* time to fail with "network path not
1781+
# found". If it's not valid syntax, it fails immediately.
1782+
# Thus, some of these tests can be very slow on the first pass.
1783+
# Second pass they all "hit" on the Node lookup cache,
1784+
# so there's no further slowdown. Tried doing this passing
1785+
# create=False to the Dir() function, but that causes a hard
1786+
# fail if the dir doesn't already exist, so not right either.
1787+
17761788
Dir_test('//foo', '//foo', '//')
17771789
Dir_test('//foo/bar', '//foo/bar', '//foo')
17781790
Dir_test('//', '//', '//')

0 commit comments

Comments
 (0)