Skip to content

Commit e59b9b1

Browse files
committed
Write a whole path where there is no relative one
Reported as #16, by fnMrRice, who also found the fix. `incsync` leaves a stub that includes the real header, and works out what to name by asking for the path from the include directory to the header. `std::filesystem::relative` is specified as lexically_relative on the two weakly canonical paths, and lexically_relative answers an empty path where the root names differ. That is not an error, so nothing was raised and nothing was returned to check, and the stub came out as an include of nothing at all. Only Windows can arrive there, having a root per drive. A source tree on one and a build directory on another is an ordinary thing to want, and the whole include directory came out useless. There is no relative path to be had between two drives, so the absolute one is written instead. It goes on to have its separators turned round with everything else, so what lands is an include a compiler can follow. Only the stub is affected. Copying never asked for a relative path, and the other two places that do are built on Unix alone, where there is one root. The tests build a source tree on the drive the sandbox is on and an include directory on any other drive that will take one, and pass themselves over where there is no second drive.
1 parent 9743c82 commit e59b9b1

2 files changed

Lines changed: 88 additions & 1 deletion

File tree

src/corecmd/commands/incsync.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,14 @@ int cmd_incsync(const cli::ParseResult &result) {
121121
fs::copy(path, targetPath, fs::copy_options::overwrite_existing);
122122
} else {
123123
// Make relative reference
124-
std::string rel = tstr2str(fs::relative(path, targetDir));
124+
//
125+
// `relative` answers an empty path where the two have no root in common
126+
// rather than treating it as an error, which on Windows is a source
127+
// directory on one drive and a build directory on another. Writing that
128+
// out gave `#include ""`. There is no relative path to be had in that
129+
// case, so the absolute one is written instead.
130+
const auto relPath = fs::relative(path, targetDir);
131+
std::string rel = tstr2str(relPath.empty() ? path : relPath);
125132

126133
#ifdef _WIN32
127134
// Replace separator

tests/cli/test_incsync.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,35 @@
22
either by copying them or by leaving a one-line stub that includes the real one.
33
"""
44

5+
import os
6+
import shutil
7+
import tempfile
8+
from pathlib import Path
9+
510
from testing.harness import QmTestCase
611

712

13+
def directory_on_another_drive(besides: Path):
14+
"""A temporary directory on some drive other than the one ``besides`` is on.
15+
16+
Answers None where there is no second drive to write to. Windows only,
17+
since everywhere else there is one root and so no such thing as two paths
18+
with nothing in common.
19+
"""
20+
if os.name != "nt":
21+
return None
22+
23+
here = besides.drive.upper()
24+
for letter in "ABCDEFGHIJKLMNOPQRSTUVWXYZ":
25+
if f"{letter}:" == here or not os.path.isdir(f"{letter}:\\"):
26+
continue
27+
try:
28+
return Path(tempfile.mkdtemp(prefix="qmcorecmd-test-", dir=f"{letter}:\\"))
29+
except OSError:
30+
continue # Not writable, which is ordinary for a disc or a share
31+
return None
32+
33+
834
class IncsyncTestCase(QmTestCase):
935
"""Every case here starts from the same small source tree."""
1036

@@ -108,6 +134,60 @@ def test_exclude_keeps_a_whole_subdirectory_out(self):
108134
self.assertNoFile("include/baz.h")
109135

110136

137+
class TestAcrossDrives(IncsyncTestCase):
138+
"""A source tree on one drive and an include directory on another.
139+
140+
Only Windows has two paths with no root in common. Asked for a relative path
141+
between them, the standard library answers an empty one rather than treating
142+
it as an error, so nothing was there to be caught and the stub was written
143+
out as an include of nothing at all.
144+
145+
Reported as https://github.com/stdware/qmsetup/issues/16.
146+
"""
147+
148+
def setUp(self):
149+
super().setUp()
150+
if os.name != "nt":
151+
self.skipTest("only Windows has paths with no root in common")
152+
153+
self.elsewhere = directory_on_another_drive(self.sandbox)
154+
if self.elsewhere is None:
155+
self.skipTest("no second drive here to sync to")
156+
self.addCleanup(shutil.rmtree, self.elsewhere, ignore_errors=True)
157+
158+
self.include = self.elsewhere / "include"
159+
160+
def stub(self, name: str) -> str:
161+
return (self.include / name).read_text(encoding="utf-8")
162+
163+
def test_the_headers_still_arrive(self):
164+
self.assertOk(self.run_cmd("incsync", "src", str(self.include)))
165+
self.assertTrue((self.include / "foo.h").is_file())
166+
167+
def test_the_stub_includes_something(self):
168+
self.assertOk(self.run_cmd("incsync", "src", str(self.include)))
169+
self.assertNotIn('#include ""', self.stub("foo.h"))
170+
171+
def test_and_what_it_includes_is_the_header(self):
172+
self.assertOk(self.run_cmd("incsync", "src", str(self.include)))
173+
self.assertIn("foo.h", self.stub("foo.h"))
174+
175+
def test_it_falls_back_to_the_whole_path(self):
176+
"""There is no relative form, so the absolute one is what is left."""
177+
self.assertOk(self.run_cmd("incsync", "src", str(self.include)))
178+
self.assertIn(self.sandbox.drive.lower(), self.stub("foo.h").lower())
179+
180+
def test_which_is_still_written_with_forward_slashes(self):
181+
self.assertOk(self.run_cmd("incsync", "src", str(self.include)))
182+
self.assertNotIn("\\", self.stub("foo.h"))
183+
184+
def test_copying_across_drives_was_never_the_broken_one(self):
185+
"""Only the stub goes through a relative path, so this held throughout
186+
and is here to say which half of the command the fault was in."""
187+
self.assertOk(self.run_cmd("incsync", "src", str(self.include), "-c"))
188+
self.assertIn("// foo", self.stub("foo.h"))
189+
190+
111191
class TestDryRunForceAndVerbosity(IncsyncTestCase):
112192
def test_dryrun_writes_nothing(self):
113193
r = self.run_cmd("incsync", "src", "include", "-d")

0 commit comments

Comments
 (0)