Mercurial > hg
view tests/test-admin-commands.py @ 51181:dcaa2df1f688
changelog: never inline changelog
The test suite mostly use small repositories, that implies that most changelog in the
tests are inlined. As a result, non-inlined changelog are quite poorly tested.
Since non-inline changelog are most common case for serious repositories, this
lack of testing is a significant problem that results in high profile issue like
the one recently fixed by 66417f55ea33 and 849745d7da89.
Inlining the changelog does not bring much to the table, the number of total
file saved is negligible, and the changelog will be read by most operation
anyway.
So this changeset is make it so we never inline the changelog, and de-inline the
one that are still inlined whenever we touch them.
By doing that, we remove the "dual code path" situation for writing new entry to
the changelog and move to a "single code path" situation. Having a single
code path simplify the code and make sure it is covered by test (if test cover
that situation obviously)
This impact all tests that care about the number of file and the exchange size,
but there is nothing too complicated in them just a lot of churn.
The churn is made "worse" by the fact rust will use the persistent nodemap on
any changelog now. Which is overall a win as it means testing the persistent
nodemap more and having less special cases.
In short, having inline changelog is mostly useless and an endless source of
pain. We get rid of it.
author | Pierre-Yves David <pierre-yves.david@octobus.net> |
---|---|
date | Mon, 11 Dec 2023 22:27:59 +0100 |
parents | 752c5a5b73c6 |
children |
line wrap: on
line source
# Test admin commands import functools import unittest from mercurial.i18n import _ from mercurial import error, ui as uimod from mercurial import registrar from mercurial.admin import verify class TestAdminVerifyFindChecks(unittest.TestCase): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.ui = uimod.ui.load() self.repo = b"fake-repo" def cleanup_table(self): self.table = {} self.alias_table = {} self.pyramid = {} self.addCleanup(cleanup_table, self) def setUp(self): self.table = {} self.alias_table = {} self.pyramid = {} check = registrar.verify_check(self.table, self.alias_table) # mock some fake check method for tests purpose @check( b"test.dummy", alias=b"dummy", options=[], ) def check_dummy(ui, repo, **options): return options @check( b"test.fake", alias=b"fake", options=[ (b'a', False, _(b'a boolean value (default: False)')), (b'b', True, _(b'a boolean value (default: True)')), (b'c', [], _(b'a list')), ], ) def check_fake(ui, repo, **options): return options # alias in the middle of a hierarchy check( b"test.noop", alias=b"noop", options=[], )(verify.noop_func) @check( b"test.noop.deeper", alias=b"deeper", options=[ (b'y', True, _(b'a boolean value (default: True)')), (b'z', [], _(b'a list')), ], ) def check_noop_deeper(ui, repo, **options): return options # args wrapper utilities def find_checks(self, name): return verify.find_checks( name=name, table=self.table, alias_table=self.alias_table, full_pyramid=self.pyramid, ) def pass_options(self, checks, options): return verify.pass_options( self.ui, checks, options, table=self.table, alias_table=self.alias_table, full_pyramid=self.pyramid, ) def get_checks(self, names, options): return verify.get_checks( self.repo, self.ui, names=names, options=options, table=self.table, alias_table=self.alias_table, full_pyramid=self.pyramid, ) # tests find_checks def test_find_checks_empty_name(self): with self.assertRaises(error.InputError): self.find_checks(name=b"") def test_find_checks_wrong_name(self): with self.assertRaises(error.InputError): self.find_checks(name=b"unknown") def test_find_checks_dummy(self): name = b"test.dummy" found = self.find_checks(name=name) self.assertEqual(len(found), 1) self.assertIn(name, found) meth = found[name] self.assertTrue(callable(meth)) self.assertEqual(len(meth.options), 0) def test_find_checks_fake(self): name = b"test.fake" found = self.find_checks(name=name) self.assertEqual(len(found), 1) self.assertIn(name, found) meth = found[name] self.assertTrue(callable(meth)) self.assertEqual(len(meth.options), 3) def test_find_checks_noop(self): name = b"test.noop.deeper" found = self.find_checks(name=name) self.assertEqual(len(found), 1) self.assertIn(name, found) meth = found[name] self.assertTrue(callable(meth)) self.assertEqual(len(meth.options), 2) def test_find_checks_from_aliases(self): found = self.find_checks(name=b"dummy") self.assertEqual(len(found), 1) self.assertIn(b"test.dummy", found) found = self.find_checks(name=b"fake") self.assertEqual(len(found), 1) self.assertIn(b"test.fake", found) found = self.find_checks(name=b"deeper") self.assertEqual(len(found), 1) self.assertIn(b"test.noop.deeper", found) def test_find_checks_from_root(self): found = self.find_checks(name=b"test") self.assertEqual(len(found), 3) self.assertIn(b"test.dummy", found) self.assertIn(b"test.fake", found) self.assertIn(b"test.noop.deeper", found) def test_find_checks_from_intermediate(self): found = self.find_checks(name=b"test.noop") self.assertEqual(len(found), 1) self.assertIn(b"test.noop.deeper", found) def test_find_checks_from_parent_dot_name(self): found = self.find_checks(name=b"noop.deeper") self.assertEqual(len(found), 1) self.assertIn(b"test.noop.deeper", found) # tests pass_options def test_pass_options_no_checks_no_options(self): checks = {} options = [] with self.assertRaises(error.Error): self.pass_options(checks=checks, options=options) def test_pass_options_fake_empty_options(self): checks = self.find_checks(name=b"test.fake") funcs = { n: functools.partial(f, self.ui, self.repo) for n, f in checks.items() } options = [] # should end with default options expected_options = {"a": False, "b": True, "c": []} func = self.pass_options(checks=funcs, options=options) self.assertDictEqual(func[b"test.fake"].keywords, expected_options) def test_pass_options_fake_non_existing_options(self): checks = self.find_checks(name=b"test.fake") funcs = { n: functools.partial(f, self.ui, self.repo) for n, f in checks.items() } with self.assertRaises(error.InputError): options = [b"test.fake:boom=yes"] self.pass_options(checks=funcs, options=options) def test_pass_options_fake_unrelated_options(self): checks = self.find_checks(name=b"test.fake") funcs = { n: functools.partial(f, self.ui, self.repo) for n, f in checks.items() } options = [b"test.noop.deeper:y=yes"] with self.assertRaises(error.InputError): self.pass_options(checks=funcs, options=options) def test_pass_options_fake_set_option(self): checks = self.find_checks(name=b"test.fake") funcs = { n: functools.partial(f, self.ui, self.repo) for n, f in checks.items() } options = [b"test.fake:a=yes"] expected_options = {"a": True, "b": True, "c": []} func = self.pass_options(checks=funcs, options=options) self.assertDictEqual(func[b"test.fake"].keywords, expected_options) def test_pass_options_fake_set_option_with_alias(self): checks = self.find_checks(name=b"test.fake") funcs = { n: functools.partial(f, self.ui, self.repo) for n, f in checks.items() } options = [b"fake:a=yes"] expected_options = {"a": True, "b": True, "c": []} func = self.pass_options(checks=funcs, options=options) self.assertDictEqual(func[b"test.fake"].keywords, expected_options) def test_pass_options_fake_set_all_option(self): checks = self.find_checks(name=b"test.fake") funcs = { n: functools.partial(f, self.ui, self.repo) for n, f in checks.items() } options = [b"test.fake:a=yes", b"test.fake:b=no", b"test.fake:c=0,1,2"] expected_options = {"a": True, "b": False, "c": [b"0", b"1", b"2"]} func = self.pass_options(checks=funcs, options=options) self.assertDictEqual(func[b"test.fake"].keywords, expected_options) def test_pass_options_fake_set_all_option_plus_unexisting(self): checks = self.find_checks(name=b"test.fake") funcs = { n: functools.partial(f, self.ui, self.repo) for n, f in checks.items() } options = [ b"test.fake:a=yes", b"test.fake:b=no", b"test.fake:c=0,1,2", b"test.fake:d=0", ] with self.assertRaises(error.InputError): self.pass_options(checks=funcs, options=options) def test_pass_options_fake_duplicate_option(self): checks = self.find_checks(name=b"test.fake") funcs = { n: functools.partial(f, self.ui, self.repo) for n, f in checks.items() } options = [ b"test.fake:a=yes", b"test.fake:a=no", ] with self.assertRaises(error.InputError): self.pass_options(checks=funcs, options=options) def test_pass_options_fake_set_malformed_option(self): checks = self.find_checks(name=b"test.fake") funcs = { n: functools.partial(f, self.ui, self.repo) for n, f in checks.items() } options = [ b"test.fake:ayes", b"test.fake:b==no", b"test.fake=", b"test.fake:", b"test.fa=ke:d=0", b"test.fa=ke:d=0", ] for opt in options: with self.assertRaises(error.InputError): self.pass_options(checks=funcs, options=[opt]) def test_pass_options_types(self): checks = self.find_checks(name=b"test.fake") funcs = { n: functools.partial(f, self.ui, self.repo) for n, f in checks.items() } # boolean, yes/no options = [b"test.fake:a=yes", b"test.fake:b=no"] expected_options = {"a": True, "b": False, "c": []} func = self.pass_options(checks=funcs, options=options) self.assertDictEqual(func[b"test.fake"].keywords, expected_options) # boolean, 0/1 options = [b"test.fake:a=1", b"test.fake:b=0"] expected_options = {"a": True, "b": False, "c": []} func = self.pass_options(checks=funcs, options=options) self.assertDictEqual(func[b"test.fake"].keywords, expected_options) # boolean, true/false options = [b"test.fake:a=true", b"test.fake:b=false"] expected_options = {"a": True, "b": False, "c": []} func = self.pass_options(checks=funcs, options=options) self.assertDictEqual(func[b"test.fake"].keywords, expected_options) # boolean, wrong type options = [b"test.fake:a=si"] with self.assertRaises(error.InputError): self.pass_options(checks=funcs, options=options) # lists options = [b"test.fake:c=0,1,2"] expected_options = {"a": False, "b": True, "c": [b"0", b"1", b"2"]} func = self.pass_options(checks=funcs, options=options) self.assertDictEqual(func[b"test.fake"].keywords, expected_options) options = [b"test.fake:c=x,y,z"] expected_options = {"a": False, "b": True, "c": [b"x", b"y", b"z"]} func = self.pass_options(checks=funcs, options=options) self.assertDictEqual(func[b"test.fake"].keywords, expected_options) # tests get_checks def test_get_checks_fake(self): funcs = self.get_checks( names=[b"test.fake"], options=[b"test.fake:a=yes"] ) options = funcs.get(b"test.fake").keywords expected_options = {"a": True, "b": True, "c": []} self.assertDictEqual(options, expected_options) def test_get_checks_multiple_mixed_with_defaults(self): funcs = self.get_checks( names=[b"test.fake", b"test.noop.deeper", b"test.dummy"], options=[ b"test.noop.deeper:y=no", b"test.noop.deeper:z=-1,0,1", ], ) options = funcs.get(b"test.fake").keywords expected_options = {"a": False, "b": True, "c": []} self.assertDictEqual(options, expected_options) options = funcs.get(b"test.noop.deeper").keywords expected_options = {"y": False, "z": [b"-1", b"0", b"1"]} self.assertDictEqual(options, expected_options) options = funcs.get(b"test.dummy").keywords expected_options = {} self.assertDictEqual(options, expected_options) def test_broken_pyramid(self): """Check that we detect pyramids that can't resolve""" table = {} alias_table = {} pyramid = {} check = registrar.verify_check(table, alias_table) # Create two checks that clash @check(b"test.wrong.intermediate") def check_dummy(ui, repo, **options): return options @check(b"test.wrong.intermediate.thing") def check_fake(ui, repo, **options): return options with self.assertRaises(error.ProgrammingError) as e: verify.get_checks( self.repo, self.ui, names=[b"test.wrong.intermediate"], options=[], table=table, alias_table=alias_table, full_pyramid=pyramid, ) assert "`verify.noop_func`" in str(e.exception), str(e.exception) if __name__ == '__main__': import silenttestrunner silenttestrunner.main(__name__)