fix(tools): importers clear the read-only LFS bit before saving; docs refresh
0.1.3 packaging and setup fixes from what 0.1.x actually exposed. - New Tools/ue_common.py: make_writable() clears the read-only bit an LFS .umap checks out with (save_map() otherwise returns False silently and the staged actor is lost) and require_saved() makes that failure visible. ue_hero_facade_import and ue_import_north_buildings now call it; the map builders already did. Verified from a forced 0o444 map: chmod logged, save_map True, exit 0. - Docs/build.md: pipeline table now lists the seven site FBXs plus frontage, north, hero and map steps and the shared FBX preset; packaging archive corrected to Builds/GigLineQA; packaged-log location and -qaselftest usage documented; new 'Traps that cost real time in 0.1.x' table (read-only save, hero --out is the FBX, MPFB add-on path, shot actor quitting under QA, null-RHI trace trap). - Docs/setup.md: fresh-checkout build marked verified 2026-11-08 and the blocker closed; new optional-keys section for OPENROUTER_API_KEY and MAPBOX_ACCESS_TOKEN (gitignored .env, not needed by build/tests/release). - Docs/Architecture 07 + architecture.md: build-output path corrected, marked outside the repository. - 5 new tests for ue_common (54 total).
This commit is contained in:
1 parent
ba68dcfdbc
commit
1b722048ce
16 files changed
+305
-178
No files matched your search
@@ -0,0 +1,70 @@
|
||||
"""Tests for Tools/ue_common.py — the shared Unreal commandlet helpers.
|
||||
|
||||
Pure stdlib: these helpers exist to turn two silent failures into loud ones, so
|
||||
they are tested without starting Unreal.
|
||||
"""
|
||||
|
||||
import os
|
||||
import stat
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||
sys.path.insert(0, os.path.join(ROOT, "Tools"))
|
||||
|
||||
import ue_common # noqa: E402
|
||||
|
||||
|
||||
class MakeWritableTest(unittest.TestCase):
|
||||
def test_read_only_file_is_made_writable(self):
|
||||
# Exactly how an LFS-tracked .umap checks out: 0o444.
|
||||
with tempfile.NamedTemporaryFile(delete=False) as handle:
|
||||
path = handle.name
|
||||
try:
|
||||
os.chmod(path, 0o444)
|
||||
self.assertFalse(os.access(path, os.W_OK))
|
||||
note = ue_common.make_writable(path)
|
||||
self.assertIsNotNone(note)
|
||||
self.assertIn("chmod u+w", note)
|
||||
self.assertTrue(os.access(path, os.W_OK))
|
||||
finally:
|
||||
os.chmod(path, 0o644)
|
||||
os.unlink(path)
|
||||
|
||||
def test_writable_file_reports_nothing(self):
|
||||
with tempfile.NamedTemporaryFile(delete=False) as handle:
|
||||
path = handle.name
|
||||
try:
|
||||
os.chmod(path, 0o644)
|
||||
self.assertIsNone(ue_common.make_writable(path))
|
||||
finally:
|
||||
os.unlink(path)
|
||||
|
||||
def test_missing_file_raises(self):
|
||||
# A typo must not look like a permissions problem.
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
with self.assertRaises(FileNotFoundError):
|
||||
ue_common.make_writable(os.path.join(tmp, "absent.umap"))
|
||||
|
||||
def test_read_bits_are_preserved(self):
|
||||
with tempfile.NamedTemporaryFile(delete=False) as handle:
|
||||
path = handle.name
|
||||
try:
|
||||
os.chmod(path, 0o400)
|
||||
ue_common.make_writable(path)
|
||||
mode = os.stat(path).st_mode
|
||||
self.assertTrue(mode & stat.S_IRUSR)
|
||||
finally:
|
||||
os.chmod(path, 0o644)
|
||||
os.unlink(path)
|
||||
|
||||
|
||||
class RequireSavedTest(unittest.TestCase):
|
||||
def test_passes_the_save_result_through(self):
|
||||
self.assertTrue(ue_common.require_saved(True, "/Game/X"))
|
||||
self.assertFalse(ue_common.require_saved(False, "/Game/X"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user