mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
Test that an update leaves a player's nx2navi.ini alone, and keep its bytes when the older installer sets the language.
A player wrote that the settings file is overwritten on updates and that a line has to be added again each time. Nothing an update of the launcher's install does writes the file: the updater works in the launcher's own folders, and the build only reads it (the bind_ lines for the button prompts, shader_carry for the shaders step). The tests now hold that with the player's file as reported: settings the menu wrote, three lines that are no settings of the game, a comment, CRLF line ends and no line end after the last line. It comes through the launcher's part of an update (staging, the switch, the cleanup; BackgroundUpdateTest), through the rebuild under a newer kit, a rebuild with nothing to do and a build from the game files again (MakeGameTest, with the check, the ExeFS, RomFS, shaders and prompts steps real and the lift and compile as stand-ins), and through the launcher's own settings switch, which adds its one line (PlayerIniTest). One writer did write the whole file again: installer/ini.py, the older installer's last step at every install and update. It kept every line, but turned CRLF into LF, added a line end after the last line and dropped a byte order mark, and a file saved as UTF-16 stopped the step with an error. It now changes the lang line only and keeps every other byte; a UTF-16 file is read as that and written as UTF-8, as the game's menu writes it. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,8 @@
|
|||||||
A new file starts from nx2navi.ini.example, whose lines are all commented defaults. The language goes into `lang`
|
A new file starts from nx2navi.ini.example, whose lines are all commented defaults. The language goes into `lang`
|
||||||
(a Switch language code, runtime/ov_system.c): an active `lang = ...` line is rewritten in place, else the commented
|
(a Switch language code, runtime/ov_system.c): an active `lang = ...` line is rewritten in place, else the commented
|
||||||
`# lang = ...` line of the example is activated, else the line is added to [language] (created at the end if
|
`# lang = ...` line of the example is activated, else the line is added to [language] (created at the end if
|
||||||
missing). Other lines, comments and line order stay as they are.
|
missing). Other lines, comments and line order stay as they are, byte for byte: the file's byte order mark, its line
|
||||||
|
ends and a last line without one are kept, so a player's own lines come through an install or an update unchanged.
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
@@ -21,6 +22,7 @@ NEED_SHARED_FONTS = ('ko', 'zh-Hans', 'zh-Hant')
|
|||||||
ACTIVE = re.compile(r'^(\s*)(?:nx2navi_)?lang(\s*=\s*)(.*?)(\s*)$', re.IGNORECASE)
|
ACTIVE = re.compile(r'^(\s*)(?:nx2navi_)?lang(\s*=\s*)(.*?)(\s*)$', re.IGNORECASE)
|
||||||
COMMENTED = re.compile(r'^\s*[#;]\s*(?:nx2navi_)?lang\s*=.*$', re.IGNORECASE)
|
COMMENTED = re.compile(r'^\s*[#;]\s*(?:nx2navi_)?lang\s*=.*$', re.IGNORECASE)
|
||||||
SECTION = re.compile(r'^\s*\[(.*)\]\s*$')
|
SECTION = re.compile(r'^\s*\[(.*)\]\s*$')
|
||||||
|
BOM = ''
|
||||||
|
|
||||||
|
|
||||||
def canonical_language(lang):
|
def canonical_language(lang):
|
||||||
@@ -29,27 +31,43 @@ def canonical_language(lang):
|
|||||||
|
|
||||||
|
|
||||||
def set_language(text, lang):
|
def set_language(text, lang):
|
||||||
"""text with `lang = <lang>` (an alias written as its canonical code); see the module docstring."""
|
"""text with `lang = <lang>` (an alias written as its canonical code); see the module docstring. Only that line
|
||||||
|
changes: a byte order mark, each line's own ending (LF or CRLF) and a last line without one stay as they are, and
|
||||||
|
a line that is added ends as the file's first line does."""
|
||||||
lang = canonical_language(lang)
|
lang = canonical_language(lang)
|
||||||
lines = text.splitlines()
|
bom = BOM if text.startswith(BOM) else ''
|
||||||
|
lines = text[len(bom):].splitlines(keepends=True)
|
||||||
|
nl = '\r\n' if lines and lines[0].endswith('\r\n') else '\n'
|
||||||
|
|
||||||
|
def split(line):
|
||||||
|
body = line.rstrip('\r\n')
|
||||||
|
return body, line[len(body):]
|
||||||
|
|
||||||
for i, line in enumerate(lines):
|
for i, line in enumerate(lines):
|
||||||
m = ACTIVE.match(line)
|
body, end = split(line)
|
||||||
|
m = ACTIVE.match(body)
|
||||||
if m:
|
if m:
|
||||||
lines[i] = f'{m.group(1)}lang{m.group(2)}{lang}'
|
lines[i] = f'{m.group(1)}lang{m.group(2)}{lang}{end}'
|
||||||
return '\n'.join(lines) + '\n'
|
return bom + ''.join(lines)
|
||||||
for i, line in enumerate(lines):
|
for i, line in enumerate(lines):
|
||||||
if COMMENTED.match(line):
|
body, end = split(line)
|
||||||
lines[i] = f'lang = {lang}'
|
if COMMENTED.match(body):
|
||||||
return '\n'.join(lines) + '\n'
|
lines[i] = f'lang = {lang}{end}'
|
||||||
|
return bom + ''.join(lines)
|
||||||
for i, line in enumerate(lines):
|
for i, line in enumerate(lines):
|
||||||
m = SECTION.match(line)
|
body, end = split(line)
|
||||||
|
m = SECTION.match(body)
|
||||||
if m and m.group(1).strip().lower() == 'language':
|
if m and m.group(1).strip().lower() == 'language':
|
||||||
lines.insert(i + 1, f'lang = {lang}')
|
if not end: # the header is the file's last line, without an ending
|
||||||
return '\n'.join(lines) + '\n'
|
lines[i] = body + nl
|
||||||
|
lines.insert(i + 1, f'lang = {lang}{nl}')
|
||||||
|
return bom + ''.join(lines)
|
||||||
|
if lines and not split(lines[-1])[1]:
|
||||||
|
lines[-1] += nl
|
||||||
if lines and lines[-1].strip():
|
if lines and lines[-1].strip():
|
||||||
lines.append('')
|
lines.append(nl)
|
||||||
lines += ['[language]', f'lang = {lang}']
|
lines += [f'[language]{nl}', f'lang = {lang}{nl}']
|
||||||
return '\n'.join(lines) + '\n'
|
return bom + ''.join(lines)
|
||||||
|
|
||||||
|
|
||||||
def get_language(text):
|
def get_language(text):
|
||||||
@@ -60,19 +78,33 @@ def get_language(text):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def read(path):
|
||||||
|
"""The file's text with its byte order mark and its own line ends. Bytes that are not UTF-8 come back as they are
|
||||||
|
(surrogate escapes), so a line in another encoding survives a rewrite. A file saved as UTF-16 (an editor's
|
||||||
|
"Unicode", which the game reads too: runtime/cfg.c utf16_text) is decoded as that and written back as UTF-8, as
|
||||||
|
the game's own menu writes it."""
|
||||||
|
raw = Path(path).read_bytes()
|
||||||
|
if raw[:2] in (b'\xff\xfe', b'\xfe\xff'):
|
||||||
|
return raw.decode('utf-16', errors='replace')
|
||||||
|
return raw.decode('utf-8', errors='surrogateescape')
|
||||||
|
|
||||||
|
|
||||||
def write(dest, lang, example=None):
|
def write(dest, lang, example=None):
|
||||||
"""Write dest/nx2navi.ini with lang; an existing file keeps its other lines. Returns the path."""
|
"""Write dest/nx2navi.ini with lang; an existing file keeps every other line byte for byte (set_language).
|
||||||
|
Returns the path."""
|
||||||
path = Path(dest) / 'nx2navi.ini'
|
path = Path(dest) / 'nx2navi.ini'
|
||||||
|
old = None
|
||||||
if path.is_file():
|
if path.is_file():
|
||||||
text = path.read_text(encoding='utf-8-sig')
|
text = old = read(path)
|
||||||
elif example and Path(example).is_file():
|
elif example and Path(example).is_file():
|
||||||
text = Path(example).read_text(encoding='utf-8')
|
text = Path(example).read_text(encoding='utf-8')
|
||||||
else:
|
else:
|
||||||
text = ''
|
text = ''
|
||||||
new = set_language(text, lang)
|
new = set_language(text, lang)
|
||||||
if path.is_file() and new == text:
|
if new == old:
|
||||||
return path
|
return path
|
||||||
tmp = path.with_name('nx2navi.ini.tmp')
|
tmp = path.with_name('nx2navi.ini.tmp')
|
||||||
tmp.write_text(new, encoding='utf-8', newline='\n')
|
with open(tmp, 'w', encoding='utf-8', errors='surrogateescape', newline='') as f:
|
||||||
|
f.write(new)
|
||||||
os.replace(tmp, path)
|
os.replace(tmp, path)
|
||||||
return path
|
return path
|
||||||
|
|||||||
@@ -262,6 +262,50 @@ class VersionTest(unittest.TestCase):
|
|||||||
versions.parse('banana')
|
versions.parse('banana')
|
||||||
|
|
||||||
|
|
||||||
|
# A player's settings file as a report showed it (an AMD card, 0.7.1): what the game's menu wrote, three lines added by
|
||||||
|
# hand that are no settings of the game (async_compute, vram_safe_mode, exception_handler: the player took the first for
|
||||||
|
# the fix of a crash and asked for it as a default, because "the configuration file gets overwritten on updates"), and a
|
||||||
|
# comment. Saved as an editor on Windows saves it: CRLF line ends, no line end after the last line. Nothing in an
|
||||||
|
# update may change a byte of it (PlayerIniTest, BackgroundUpdateTest and MakeGameTest each run their part of one).
|
||||||
|
PLAYER_INI = (
|
||||||
|
b'# my settings\r\n'
|
||||||
|
b'[display]\r\n'
|
||||||
|
b'async_compute = 1\r\n'
|
||||||
|
b'fullscreen = 1\r\n'
|
||||||
|
b'window_size = 1920x1080\r\n'
|
||||||
|
b'gpu_index = 0\r\n'
|
||||||
|
b'perf_overlay = fps\r\n'
|
||||||
|
b'vk_present = mailbox\r\n'
|
||||||
|
b'upscaler = bilinear\r\n'
|
||||||
|
b'dlaa = 1\r\n'
|
||||||
|
b'framegen = off\r\n'
|
||||||
|
b'taa = fsr3\r\n'
|
||||||
|
b'hdr = 0\r\n'
|
||||||
|
b'aspect = fit\r\n'
|
||||||
|
b'game_res = 1800\r\n'
|
||||||
|
b'sharpness = 80\r\n'
|
||||||
|
b'aniso = 2\r\n'
|
||||||
|
b'assume_focus = 1\r\n'
|
||||||
|
b'texture_detail = auto\r\n'
|
||||||
|
b'\r\n'
|
||||||
|
b'[input]\r\n'
|
||||||
|
b'bind_a = key:default, pad:south\r\n'
|
||||||
|
b'bind_b = key:default, pad:east\r\n'
|
||||||
|
b'bind_x = key:default, pad:north\r\n'
|
||||||
|
b'bind_y = key:default, pad:west\r\n'
|
||||||
|
b'\r\n'
|
||||||
|
b'[language]\r\n'
|
||||||
|
b'operation_mode = docked\r\n'
|
||||||
|
b'\r\n'
|
||||||
|
b'[performance]\r\n'
|
||||||
|
b'wb_lazy = 0\r\n'
|
||||||
|
b'vram_safe_mode = 1\r\n'
|
||||||
|
b'exception_handler = 1\r\n'
|
||||||
|
b'\r\n'
|
||||||
|
b'[frame rate]\r\n'
|
||||||
|
b'fps_limit = 120')
|
||||||
|
|
||||||
|
|
||||||
class GameIniTest(Tmp):
|
class GameIniTest(Tmp):
|
||||||
"""The game settings the launcher switches (launcher/gameini.py), read as runtime/cfg.c reads nx2navi.ini."""
|
"""The game settings the launcher switches (launcher/gameini.py), read as runtime/cfg.c reads nx2navi.ini."""
|
||||||
|
|
||||||
@@ -305,6 +349,45 @@ class GameIniTest(Tmp):
|
|||||||
gameini.put(self.tmp, 'discord', 'discord_rpc', '1')
|
gameini.put(self.tmp, 'discord', 'discord_rpc', '1')
|
||||||
self.assertTrue((self.tmp / 'nx2navi.ini').is_dir())
|
self.assertTrue((self.tmp / 'nx2navi.ini').is_dir())
|
||||||
|
|
||||||
|
class PlayerIniTest(Tmp):
|
||||||
|
"""The settings file of a player (PLAYER_INI) under the two writers outside the game: the launcher's Settings
|
||||||
|
(launcher/gameini.py) and the older installer's last step (installer/ini.py). Each changes the one line it is
|
||||||
|
for; the lines that are no settings of the game, the comment and every other byte stay."""
|
||||||
|
|
||||||
|
def test_the_launchers_setting_adds_one_line(self):
|
||||||
|
ini = self.tmp / 'nx2navi.ini'
|
||||||
|
ini.write_bytes(PLAYER_INI)
|
||||||
|
self.assertEqual(gameini.get(self.tmp, 'wb_lazy'), '0')
|
||||||
|
self.assertEqual(gameini.get(self.tmp, 'async_compute'), '1') # read like any line; the game ignores it
|
||||||
|
gameini.put(self.tmp, 'discord', 'discord_rpc', '0')
|
||||||
|
self.assertEqual(ini.read_bytes(), PLAYER_INI + b'\r\n\r\n[discord]\r\ndiscord_rpc = 0\r\n')
|
||||||
|
gameini.put(self.tmp, 'discord', 'discord_rpc', '1')
|
||||||
|
self.assertEqual(ini.read_bytes(), PLAYER_INI + b'\r\n\r\n[discord]\r\ndiscord_rpc = 1\r\n')
|
||||||
|
|
||||||
|
def test_the_older_installer_changes_the_language_line_only(self):
|
||||||
|
"""installer/ini.py sets the language, in the older installer's last step at every install and update: the
|
||||||
|
one line goes under [language] with the file's own line ends, and every other byte stays (it used to write
|
||||||
|
the whole file again with LF line ends, a line end after the last line and no byte order mark). A second run
|
||||||
|
leaves the file alone; a changed language changes that line; a byte order mark and a line that is not UTF-8
|
||||||
|
stay too."""
|
||||||
|
from installer import ini as inst_ini
|
||||||
|
path = self.tmp / 'nx2navi.ini'
|
||||||
|
path.write_bytes(PLAYER_INI)
|
||||||
|
inst_ini.write(self.tmp, 'en-US')
|
||||||
|
want = PLAYER_INI.replace(b'[language]\r\n', b'[language]\r\nlang = en-US\r\n')
|
||||||
|
self.assertEqual(path.read_bytes(), want)
|
||||||
|
self.assertEqual(want.count(b'lang = en-US'), 1)
|
||||||
|
stamp = path.stat().st_mtime_ns
|
||||||
|
inst_ini.write(self.tmp, 'en-US')
|
||||||
|
self.assertEqual((path.read_bytes(), path.stat().st_mtime_ns), (want, stamp))
|
||||||
|
inst_ini.write(self.tmp, 'de')
|
||||||
|
self.assertEqual(path.read_bytes(), want.replace(b'lang = en-US', b'lang = de'))
|
||||||
|
marked = b'\xef\xbb\xbf# caf\xe9\r\nlang = ja\r\nasync_compute = 1'
|
||||||
|
path.write_bytes(marked)
|
||||||
|
inst_ini.write(self.tmp, 'fr')
|
||||||
|
self.assertEqual(path.read_bytes(), marked.replace(b'lang = ja', b'lang = fr'))
|
||||||
|
self.assertFalse((self.tmp / 'nx2navi.ini.tmp').exists())
|
||||||
|
|
||||||
|
|
||||||
class RebuildArgsTest(Tmp):
|
class RebuildArgsTest(Tmp):
|
||||||
"""Rebuild the Game compiles the game's code for this processor's level (--retune); an update keeps the level its
|
"""Rebuild the Game compiles the game's code for this processor's level (--retune); an update keeps the level its
|
||||||
@@ -898,6 +981,28 @@ class BackgroundUpdateTest(Tmp):
|
|||||||
self.assertEqual(bg.install(self.game, rel), '')
|
self.assertEqual(bg.install(self.game, rel), '')
|
||||||
self.assertEqual(len(self.restarts), 1)
|
self.assertEqual(len(self.restarts), 1)
|
||||||
|
|
||||||
|
def test_an_update_leaves_the_games_settings_file(self):
|
||||||
|
"""The launcher's part of an update (the release staged beside the running version, the switch to it, the
|
||||||
|
cleanup after the rebuild) changes nothing in the game folder, also in its default place inside the
|
||||||
|
launcher's folder: the player's nx2navi.ini is the same bytes afterwards, and an uninstall that keeps the game
|
||||||
|
does not name it."""
|
||||||
|
game = self.layout.root / 'game'
|
||||||
|
(game / 'profile').mkdir(parents=True)
|
||||||
|
ini = game / 'nx2navi.ini'
|
||||||
|
ini.write_bytes(PLAYER_INI)
|
||||||
|
before = sorted(p.relative_to(game).as_posix() for p in game.rglob('*'))
|
||||||
|
bg = self.background(self.context())
|
||||||
|
rel = self.publish('0.5.0', runtime='py-2')
|
||||||
|
self.assertEqual(self.stage(bg, rel), 'staged')
|
||||||
|
self.assertEqual(ini.read_bytes(), PLAYER_INI)
|
||||||
|
self.assertEqual(bg.install(str(game), rel, why='the launcher started'), '0.5.0')
|
||||||
|
self.assertEqual(self.state(), ('0.5.0', '0.4.0', [['--continue-update', '0.4.0']]))
|
||||||
|
updater.prune(self.layout) # what ContinueUpdateJob ends with
|
||||||
|
self.assertEqual(ini.read_bytes(), PLAYER_INI)
|
||||||
|
self.assertEqual(sorted(p.relative_to(game).as_posix() for p in game.rglob('*')), before)
|
||||||
|
gone, _ = winsys.uninstall_plan(self.layout)
|
||||||
|
self.assertFalse([p for p in gone if p == game or p in ini.parents])
|
||||||
|
|
||||||
def test_no_install_while_the_game_runs(self):
|
def test_no_install_while_the_game_runs(self):
|
||||||
"""A copy of the game started elsewhere, the copy the launcher started, and a download under way each hold
|
"""A copy of the game started elsewhere, the copy the launcher started, and a download under way each hold
|
||||||
the install back, also one the player asked for."""
|
the install back, also one the player asked for."""
|
||||||
@@ -1635,6 +1740,75 @@ class MakeGameTest(Tmp):
|
|||||||
self.assertEqual(build(), ([], [{'ev': 'step', 'step': 'prompts', 'state': 'skip'}], 4))
|
self.assertEqual(build(), ([], [{'ev': 'step', 'step': 'prompts', 'state': 'skip'}], 4))
|
||||||
self.assertTrue((self.out / 'romfs-patches-xbox' / 'manifest.tsv').is_file())
|
self.assertTrue((self.out / 'romfs-patches-xbox' / 'manifest.tsv').is_file())
|
||||||
|
|
||||||
|
def test_an_update_keeps_the_settings_file(self):
|
||||||
|
"""The rebuild an update ends with (make_game.py --rebuild under a newer kit: the lift, the compile, the
|
||||||
|
shaders step and the button prompts all run again) leaves the game folder's nx2navi.ini as the player has it,
|
||||||
|
byte for byte. The build reads the file (the bind_ lines for the prompts, shader_carry for the shaders step)
|
||||||
|
and never writes it; so does a second rebuild that finds nothing to do, and one from the game files again.
|
||||||
|
The lift and the compile are stand-ins here (the real ones need the game and clang); the check, the ExeFS and
|
||||||
|
RomFS steps, the shaders step and the prompts step are the real ones."""
|
||||||
|
from unittest import mock
|
||||||
|
kit = self.tmp / 'kit'
|
||||||
|
for d in ('tools', 'bin', 'recomp'):
|
||||||
|
(kit / d).mkdir(parents=True)
|
||||||
|
(kit / 'VERSION').write_text('0.7.0\n')
|
||||||
|
(kit / 'recomp' / 'gen.py').write_text('# the lifter of 0.7.0\n')
|
||||||
|
(kit / 'bin' / self.mg.XLATE_NAME).write_bytes(b'translator')
|
||||||
|
(kit / 'bin' / 'nx2navi_xlate.id').write_text('xlate 1\n')
|
||||||
|
(kit / 'tools' / 'prepare_shaders.py').write_text('print("prepare-shaders: progress build 1 1", flush=True)\n')
|
||||||
|
for name in ('prompt_art.py', 'button_icons.py'):
|
||||||
|
shutil.copy2(ROOT / 'tools' / name, kit / 'tools' / name)
|
||||||
|
(kit / 'tools' / 'pad_prompts.py').write_text(FAKE_PAD_PROMPTS)
|
||||||
|
art = kit / 'prompt-art' / 'XGamepad' / 'Default'
|
||||||
|
art.mkdir(parents=True)
|
||||||
|
(art / 'T_X_A_White.png').write_bytes(b'picture one')
|
||||||
|
ran = []
|
||||||
|
|
||||||
|
def fake(name):
|
||||||
|
def step(*args, **kw):
|
||||||
|
ran.append(name)
|
||||||
|
return step
|
||||||
|
|
||||||
|
def build(*source):
|
||||||
|
"""One run of make_game.py; the steps that ran, in order."""
|
||||||
|
buf = io.StringIO()
|
||||||
|
argv = ['make_game.py', *source, '--out', str(self.out), '--msys', str(self.msys), '--events']
|
||||||
|
with mock.patch.object(sys, 'argv', argv), contextlib.redirect_stdout(buf), \
|
||||||
|
contextlib.redirect_stderr(io.StringIO()):
|
||||||
|
self.mg.main()
|
||||||
|
events = [json.loads(ln[2:]) for ln in buf.getvalue().splitlines() if ln.startswith('@@')]
|
||||||
|
return [e['step'] for e in events if e['ev'] == 'step' and e['state'] == 'start']
|
||||||
|
|
||||||
|
sys.path.insert(0, str(self.layout.pydeps)) # the fake zbic
|
||||||
|
self.addCleanup(sys.path.remove, str(self.layout.pydeps))
|
||||||
|
ini = self.out / 'nx2navi.ini'
|
||||||
|
with mock.patch.multiple(self.mg, KIT=kit, PROMPT_PACKAGES={}, EVENTS=False, step_lift=fake('lift'),
|
||||||
|
step_compile=fake('compile'), step_scan=fake('scan')):
|
||||||
|
self.assertEqual(build('--nup', str(self.nup)),
|
||||||
|
['check', 'exefs', 'romfs', 'lift', 'compile', 'shaders', 'prompts', 'scan'])
|
||||||
|
self.assertFalse(ini.exists()) # the build makes no settings file: the game's menu does
|
||||||
|
ini.write_bytes(PLAYER_INI)
|
||||||
|
names = sorted(p.name for p in self.out.iterdir())
|
||||||
|
# the update: a kit with another lifter and another version
|
||||||
|
(kit / 'VERSION').write_text('0.7.1\n')
|
||||||
|
(kit / 'recomp' / 'gen.py').write_text('# the lifter of 0.7.1\n')
|
||||||
|
ran.clear()
|
||||||
|
steps = build('--rebuild')
|
||||||
|
self.assertEqual(steps, ['check', 'lift', 'compile', 'shaders', 'prompts']) # the bind_ lines are new to it
|
||||||
|
self.assertEqual(ran, ['lift', 'compile'])
|
||||||
|
self.assertEqual(ini.read_bytes(), PLAYER_INI)
|
||||||
|
self.assertEqual(sorted(p.name for p in self.out.iterdir()), names) # no copy, no backup, no temporary file
|
||||||
|
self.assertEqual(json.loads((self.out / 'build-cache' / 'state.json').read_text())['kit'], '0.7.1')
|
||||||
|
self.assertEqual(build('--rebuild'), ['check']) # nothing to do
|
||||||
|
self.assertEqual(ini.read_bytes(), PLAYER_INI)
|
||||||
|
# from the game files again into the same folder (Rebuild the Game without a build to start from)
|
||||||
|
st = json.loads((self.out / 'build-cache' / 'state.json').read_text())
|
||||||
|
st['steps'] = {}
|
||||||
|
(self.out / 'build-cache' / 'state.json').write_text(json.dumps(st))
|
||||||
|
self.assertEqual(build('--nup', str(self.nup)),
|
||||||
|
['check', 'exefs', 'romfs', 'lift', 'compile', 'shaders', 'prompts', 'scan'])
|
||||||
|
self.assertEqual(ini.read_bytes(), PLAYER_INI)
|
||||||
|
|
||||||
@unittest.skipIf(os.name == 'nt', 'uses a POSIX script as the compiler')
|
@unittest.skipIf(os.name == 'nt', 'uses a POSIX script as the compiler')
|
||||||
def test_largest_source_compiles_first(self):
|
def test_largest_source_compiles_first(self):
|
||||||
src, obj = self.tmp / 'src', self.tmp / 'obj'
|
src, obj = self.tmp / 'src', self.tmp / 'obj'
|
||||||
|
|||||||
Reference in New Issue
Block a user