218 lines
11 KiB
Python
218 lines
11 KiB
Python
import contextlib
|
|
import io
|
|
import json
|
|
import tempfile
|
|
import sys
|
|
import unittest
|
|
from pathlib import Path, PureWindowsPath
|
|
from unittest.mock import patch
|
|
from urllib.error import URLError
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
import nid_names
|
|
|
|
|
|
class DatabaseCacheTests(unittest.TestCase):
|
|
def test_interrupted_download_does_not_publish_a_partial_cache(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
cache = Path(directory) / "cache" / "aerolib.csv"
|
|
|
|
def interrupted(url, filename):
|
|
Path(filename).write_text("nid1 scePartial\n", encoding="utf-8")
|
|
raise URLError("connection interrupted")
|
|
|
|
with patch.object(nid_names.urllib.request, "urlretrieve", side_effect=interrupted):
|
|
with self.assertRaises(URLError):
|
|
nid_names.load_db(cache)
|
|
self.assertFalse(cache.exists())
|
|
self.assertEqual(list(cache.parent.iterdir()), [])
|
|
|
|
def test_retry_after_interruption_downloads_the_complete_database(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
cache = Path(directory) / "aerolib.csv"
|
|
|
|
def interrupted(url, filename):
|
|
Path(filename).write_text("nid1 scePartial\n", encoding="utf-8")
|
|
raise URLError("connection interrupted")
|
|
|
|
def complete(url, filename):
|
|
Path(filename).write_text("nid1 sceComplete\nnid2 sceOther\n", encoding="utf-8")
|
|
|
|
with patch.object(nid_names.urllib.request, "urlretrieve", side_effect=interrupted):
|
|
with self.assertRaises(URLError):
|
|
nid_names.load_db(cache)
|
|
with patch.object(nid_names.urllib.request, "urlretrieve", side_effect=complete) as download:
|
|
self.assertEqual(nid_names.load_db(cache), {"nid1": "sceComplete", "nid2": "sceOther"})
|
|
download.assert_called_once()
|
|
self.assertEqual(list(cache.parent.iterdir()), [cache])
|
|
|
|
def test_cache_is_published_only_after_download_completes(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
cache = Path(directory) / "aerolib.csv"
|
|
|
|
def complete(url, filename):
|
|
self.assertEqual(url, nid_names.DB_URL)
|
|
self.assertNotEqual(Path(filename), cache)
|
|
self.assertEqual(Path(filename).parent, cache.parent)
|
|
self.assertFalse(cache.exists())
|
|
Path(filename).write_text("nid1 sceComplete\n", encoding="utf-8")
|
|
|
|
with patch.object(nid_names.urllib.request, "urlretrieve", side_effect=complete):
|
|
self.assertEqual(nid_names.load_db(cache), {"nid1": "sceComplete"})
|
|
self.assertEqual(cache.read_text(encoding="utf-8"), "nid1 sceComplete\n")
|
|
self.assertEqual(list(cache.parent.iterdir()), [cache])
|
|
|
|
def test_existing_cache_is_used_without_downloading(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
cache = Path(directory) / "aerolib.csv"
|
|
cache.write_text("nid1 sceExisting\n", encoding="utf-8")
|
|
with patch.object(nid_names.urllib.request, "urlretrieve") as download:
|
|
self.assertEqual(nid_names.load_db(cache), {"nid1": "sceExisting"})
|
|
download.assert_not_called()
|
|
|
|
def test_utf8_bom_does_not_change_the_first_nid(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
cache = Path(directory) / "aerolib.csv"
|
|
nid = nid_names.compute_nid("sceExample")
|
|
cache.write_text(f"{nid} sceExample\n", encoding="utf-8-sig")
|
|
self.assertEqual(nid_names.load_db(cache), {nid: "sceExample"})
|
|
|
|
def test_comment_rows_do_not_become_database_entries(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
cache = Path(directory) / "aerolib.csv"
|
|
nid = nid_names.compute_nid("sceExample")
|
|
cache.write_text(f"# Database names\n{nid} sceExample\n", encoding="utf-8")
|
|
self.assertEqual(nid_names.load_db(cache), {nid: "sceExample"})
|
|
|
|
def test_annotations_do_not_change_hash_inputs(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
cache = Path(directory) / "aerolib.csv"
|
|
nid = nid_names.compute_nid("sceExample")
|
|
cache.write_text(f"{nid}#A#B sceExample#verified\n", encoding="utf-8")
|
|
self.assertEqual(nid_names.load_db(cache), {nid: "sceExample"})
|
|
|
|
class NidArgumentTests(unittest.TestCase):
|
|
options = ["-h", "--help", "--db", "--nid", "--json"]
|
|
|
|
def normalize(self, arguments):
|
|
return nid_names.nid_arguments(arguments, self.options)
|
|
|
|
def test_nids_starting_with_a_dash_are_accepted(self):
|
|
self.assertEqual(
|
|
self.normalize(["--nid", "-nvxBWa0iDs", "ZjtRqSMJwdw", "--ll-AgiK2M", "--json"]),
|
|
["--nid=-nvxBWa0iDs", "--nid=ZjtRqSMJwdw", "--nid=--ll-AgiK2M", "--json"])
|
|
|
|
def test_nid_with_value_after_a_bare_nid_is_kept(self):
|
|
self.assertEqual(self.normalize(["--nid", "A", "--nid=B"]), ["--nid=A", "--nid=B"])
|
|
|
|
def test_abbreviated_options_end_the_nids(self):
|
|
self.assertEqual(self.normalize(["--nid", "A", "--j"]), ["--nid=A", "--j"])
|
|
self.assertEqual(self.normalize(["--nid", "A", "--d=x.csv"]), ["--nid=A", "--d=x.csv"])
|
|
self.assertEqual(self.normalize(["--ni", "-nvxBWa0iDs"]), ["--nid=-nvxBWa0iDs"])
|
|
|
|
def test_end_of_options_marker_is_kept(self):
|
|
self.assertEqual(self.normalize(["--nid", "A", "--", "B"]), ["--nid=A", "--", "B"])
|
|
|
|
def test_main_resolves_every_nid_given_after_nid(self):
|
|
database = {"-nvxBWa0iDs": "gethostname", "ZjtRqSMJwdw": "sinh"}
|
|
arguments = ["nid_names.py", "--json", "--nid", "-nvxBWa0iDs", "ZjtRqSMJwdw"]
|
|
with (patch.object(sys, "argv", arguments),
|
|
patch.object(nid_names, "load_db", return_value=database),
|
|
patch.object(nid_names, "collect_real_names", return_value=set()),
|
|
patch("builtins.print") as printed):
|
|
self.assertEqual(nid_names.main(), 0)
|
|
rows = json.loads(printed.call_args.args[0])["rows"]
|
|
self.assertEqual({row["nid"]: row["suggestion"] for row in rows}, database)
|
|
|
|
|
|
class SourceCollectionTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.directory = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self.directory.cleanup)
|
|
self.root = Path(self.directory.name)
|
|
self.prx = self.root / "core" / "libs" / "prx"
|
|
self.module = self.prx / "libSceExample"
|
|
self.module.mkdir(parents=True)
|
|
self.root_patch = patch.object(nid_names, "ROOT", self.root)
|
|
self.prx_patch = patch.object(nid_names, "PRX", self.prx)
|
|
self.root_patch.start()
|
|
self.prx_patch.start()
|
|
self.addCleanup(self.root_patch.stop)
|
|
self.addCleanup(self.prx_patch.stop)
|
|
|
|
def write_source(self, text, relative="Export.cpp"):
|
|
path = self.module / relative
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(text, encoding="utf-8")
|
|
return path
|
|
|
|
def test_comments_and_literals_do_not_create_export_records(self):
|
|
self.write_source('''// APS5_EXPORT("AAAAAAAAAAA", exampleUnknown00);
|
|
/* int APS5_VABI sceCommented() { return 0; } */
|
|
auto example = R"text(APS5_EXPORT("BBBBBBBBBBB", exampleUnknown01);
|
|
int APS5_VABI sceQuoted() { return 0; })text";
|
|
APS5_EXPORT("CCCCCCCCCCC", exampleUnknown02);
|
|
int APS5_VABI sceReal() { return 0; }
|
|
''')
|
|
self.assertEqual(nid_names.collect_unknowns(), {"CCCCCCCCCCC": "core/libs/prx/libSceExample/Export.cpp:exampleUnknown02"})
|
|
self.assertEqual(nid_names.collect_real_names(), {"sceReal"})
|
|
|
|
def test_formatted_alias_macro_is_detected(self):
|
|
self.write_source('APS5_EXPORT (\n "AAAAAAAAAAA" ,\n exampleUnknown00\n );\n')
|
|
self.assertEqual(nid_names.collect_unknowns(), {"AAAAAAAAAAA": "core/libs/prx/libSceExample/Export.cpp:exampleUnknown00"})
|
|
|
|
def test_known_aliases_are_not_unknown_exports(self):
|
|
self.write_source('APS5_EXPORT("AAAAAAAAAAA", sceKnownCall);\n'
|
|
'APS5_EXPORT("BBBBBBBBBBB", exampleUnknown00);\n')
|
|
self.assertEqual(nid_names.collect_unknowns(), {"BBBBBBBBBBB": "core/libs/prx/libSceExample/Export.cpp:exampleUnknown00"})
|
|
|
|
def test_windows_source_paths_use_portable_separators(self):
|
|
self.write_source('APS5_EXPORT("AAAAAAAAAAA", exampleUnknown00);\n')
|
|
relative = PureWindowsPath("core/libs/prx/libSceExample/Export.cpp")
|
|
with patch.object(Path, "relative_to", return_value=relative):
|
|
self.assertEqual(nid_names.collect_unknowns(), {"AAAAAAAAAAA": "core/libs/prx/libSceExample/Export.cpp:exampleUnknown00"})
|
|
|
|
def test_test_fixtures_are_not_runtime_implementations(self):
|
|
self.write_source('APS5_EXPORT("AAAAAAAAAAA", exampleUnknown00);\nint APS5_VABI sceFixture() { return 0; }\n', "tests/Fixture.cpp")
|
|
self.assertEqual(nid_names.collect_unknowns(), {})
|
|
self.assertEqual(nid_names.collect_real_names(), set())
|
|
|
|
def test_unreadable_sources_fail_instead_of_returning_incomplete_names(self):
|
|
self.write_source('int APS5_VABI sceReal() { return 0; }\n')
|
|
with patch.object(Path, "read_text", side_effect=OSError("read denied")):
|
|
with self.assertRaisesRegex(OSError, "read denied"):
|
|
nid_names.collect_unknowns()
|
|
with self.assertRaisesRegex(OSError, "read denied"):
|
|
nid_names.collect_real_names()
|
|
|
|
def test_explicit_empty_nid_filter_does_not_scan_the_tree(self):
|
|
cache = self.root / "aerolib.csv"
|
|
cache.write_text("AAAAAAAAAAA example\n", encoding="utf-8")
|
|
output = io.StringIO()
|
|
with patch.object(sys, "argv", ["nid_names.py", "--db", str(cache), "--nid", "--json"]), patch.object(nid_names, "collect_unknowns", side_effect=AssertionError("unexpected tree scan")), contextlib.redirect_stdout(output):
|
|
self.assertEqual(nid_names.main(), 0)
|
|
self.assertEqual(json.loads(output.getvalue())["rows"], [])
|
|
|
|
def test_empty_abbreviated_filter_skips_all_source_scans(self):
|
|
cache = self.root / "aerolib.csv"
|
|
cache.write_text("AAAAAAAAAAA example\n", encoding="utf-8")
|
|
arguments = ["nid_names.py", "--db", str(cache), "--ni", "--json"]
|
|
with patch.object(sys, "argv", arguments), patch.object(nid_names, "collect_unknowns", side_effect=AssertionError("unexpected aliases scan")), patch.object(nid_names, "collect_real_names", side_effect=AssertionError("unexpected definitions scan")), contextlib.redirect_stdout(io.StringIO()):
|
|
self.assertEqual(nid_names.main(), 0)
|
|
|
|
def test_literals_and_digit_separators_keep_following_definitions(self):
|
|
self.write_source('''auto count = 1'000;
|
|
auto quote = '\\'';
|
|
auto string = "\\\" APS5_VABI sceQuoted() { }";
|
|
int APS5_VABI sceReal() { return 0; }
|
|
''')
|
|
self.assertEqual(nid_names.collect_real_names(), {"sceReal"})
|
|
|
|
def test_spliced_line_comment_and_macro_tokens(self):
|
|
self.write_source('// continued \\\nAPS5_EXPORT("AAAAAAAAAAA", exampleUnknown00);\nAPS5_EX\\\nPORT("BBBBBBBBBBB", exampleUnknown01);\n')
|
|
self.assertEqual(nid_names.collect_unknowns(), {"BBBBBBBBBBB": "core/libs/prx/libSceExample/Export.cpp:exampleUnknown01"})
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|