Includes earlier uncommitted work: Tomie and Unity adapters, benchmark, structured output, call metrics. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
96 lines
5.2 KiB
Python
96 lines
5.2 KiB
Python
import unittest
|
|
import tempfile
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
import zlib
|
|
|
|
from local_translator.tomie_full import chunks, split_text, text_slots
|
|
from local_translator.tomie_extra import dictionary_segments, lines_with_spans, replace_records
|
|
|
|
|
|
class FullTranslationTests(unittest.TestCase):
|
|
def test_split_preserves_source_and_syntax(self):
|
|
text = ('A sentence. ' * 80) + '{' + 'variable' * 160 + '}' + '\n<color=yellow>Yes</color> ' * 30
|
|
pieces = split_text(text, 200)
|
|
self.assertEqual(''.join(pieces), text)
|
|
self.assertTrue(any('{' + 'variable' * 160 + '}' in piece for piece in pieces))
|
|
for piece in pieces:
|
|
self.assertEqual(piece.count('<color=yellow>') + piece.count('</color>'), piece.count('<'))
|
|
|
|
def test_only_visible_parameters_are_extracted(self):
|
|
tree = {'references': {'records': [
|
|
{'type': {'class': 'PrintText'}, 'data': {'Text': {'value': 'Hello', 'hasValue': 1,
|
|
'dynamicValue': {'ValueText': ''}}, 'AuthorId': {'value': 'Tomie'}}},
|
|
{'type': {'class': 'AddChoice'}, 'data': {'ChoiceSummary': {'value': '', 'hasValue': 0,
|
|
'dynamicValue': {'ValueText': 'Pay {price}'}}}},
|
|
{'type': {'class': 'SetCustomVariable'}, 'data': {'Expression': 'score="Hello"'}}
|
|
]}}
|
|
self.assertEqual(list(text_slots(tree)), [(0, 'Text', ('value',), 'Hello'),
|
|
(1, 'ChoiceSummary', ('dynamicValue', 'ValueText'), 'Pay {price}')])
|
|
|
|
def test_parts_cover_each_item_once(self):
|
|
items = list(range(1001))
|
|
parts = chunks(items, 400)
|
|
self.assertEqual([len(p) for p in parts], [400, 400, 201])
|
|
self.assertEqual([item for part in parts for item in part], items)
|
|
|
|
def test_dictionary_duplicate_keys_and_delimiters_are_preserved(self):
|
|
source = 'Tip1: Hello | category | World\\nAgain\r\nTip1: Duplicate\r\n'
|
|
slots = list(dictionary_segments(source))
|
|
self.assertEqual([s[2] for s in slots], [' Hello ', ' category ', ' World\\nAgain', ' Duplicate'])
|
|
records = [{'kind': 'dictionary', 'start': a, 'end': b, 'text': t, 'parts': [str(i)]}
|
|
for i, (a, b, t) in enumerate(slots)]
|
|
values = {str(i): s[2] for i, s in enumerate(slots)}
|
|
translated = {'0': 'Привет', '1': 'категория', '2': 'Мир\\nСнова', '3': 'Повтор'}
|
|
result = replace_records(source, records, translated, values)
|
|
self.assertEqual(result, 'Tip1: Привет | категория | Мир\\nСнова\r\nTip1: Повтор\r\n')
|
|
|
|
def test_synonym_keys_and_comma_counts_are_preserved(self):
|
|
source = '{KEY}=hello,world\r\n'
|
|
slots = list(lines_with_spans(source, True))
|
|
records = [{'kind': 'text', 'synonym': True, 'start': a, 'end': b, 'text': t, 'parts': [str(i)]}
|
|
for i, (a, b, t) in enumerate(slots)]
|
|
values = {str(i): s[2] for i, s in enumerate(slots)}
|
|
self.assertEqual(replace_records(source, records, {'0': 'привет', '1': 'мир'}, values), '{KEY}=привет,мир\r\n')
|
|
with self.assertRaises(ValueError):
|
|
replace_records(source, records, {'0': 'привет, ага', '1': 'мир'}, values)
|
|
|
|
def test_streamed_bundle_is_readable_by_native_and_streamed_readers(self):
|
|
try:
|
|
import UnityPy
|
|
except ImportError:
|
|
self.skipTest('Install .[unity] for asset tests')
|
|
from UnityPy.streams import EndianBinaryReader
|
|
from local_translator.unity_stream import load, resource_hashes, save
|
|
raw = bytes(range(256)) * 2000
|
|
reader = EndianBinaryReader(raw)
|
|
reader.flags = 0
|
|
bundle = SimpleNamespace(signature='UnityFS', version=7, version_player='5.x.x',
|
|
version_engine='2019.4.22f1', parse_version=lambda: (2019,4,22,1), files={'test.resS':reader})
|
|
with tempfile.TemporaryDirectory() as temp:
|
|
path = Path(temp)/'test.bundle'
|
|
crc = save(bundle, path)
|
|
self.assertEqual(crc,zlib.crc32(raw))
|
|
standard = next(iter(UnityPy.load(path.read_bytes()).files.values()))
|
|
streamed = next(iter(load(path).files.values()))
|
|
self.assertEqual(resource_hashes(standard), resource_hashes(streamed))
|
|
self.assertEqual(streamed.stream_crc, crc)
|
|
self.assertEqual(standard.files['test.resS'].bytes,raw)
|
|
|
|
def test_catalog_can_be_repatched_using_reserved_numeric_padding(self):
|
|
import base64
|
|
import json
|
|
import struct
|
|
from local_translator.tomie import patch_catalog
|
|
original_hash, first_hash, second_hash = 'a'*32, 'b'*32, 'c'*32
|
|
options = json.dumps({'m_Hash':original_hash,'m_Crc':2659166264,'m_BundleSize':37229646},separators=(',',':')).encode('utf-16-le')
|
|
catalog = {'m_ExtraDataString':base64.b64encode(struct.pack('<I',len(options))+options).decode()}
|
|
first,_,_=patch_catalog(catalog,original_hash,12345,37229646,first_hash)
|
|
second,_,after=patch_catalog(first,first_hash,3456789012,37229646,second_hash)
|
|
self.assertEqual(after['m_Crc'],3456789012)
|
|
self.assertEqual(len(base64.b64decode(catalog['m_ExtraDataString'])),len(base64.b64decode(second['m_ExtraDataString'])))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|