Message ID | 20240429201418.657042-6-michael.opdenacker@bootlin.com |
---|---|
State | New |
Headers | show |
Series | prserv: add support for an "upstream" server | expand |
On Mon, Apr 29, 2024 at 2:14 PM <michael.opdenacker@bootlin.com> wrote: > > From: Michael Opdenacker <michael.opdenacker@bootlin.com> > > Run them with "bitbake-selftest prserv.tests" > > Signed-off-by: Michael Opdenacker <michael.opdenacker@bootlin.com> > Cc: Joshua Watt <JPEWhacker@gmail.com> > Cc: Tim Orling <ticotimo@gmail.com> > Cc: Thomas Petazzoni <thomas.petazzoni@bootlin.com> > --- > bin/bitbake-selftest | 2 + > lib/prserv/tests.py | 416 +++++++++++++++++++++++++++++++++++++++++++ > 2 files changed, 418 insertions(+) > create mode 100644 bitbake/lib/prserv/tests.py > > diff --git a/bin/bitbake-selftest b/bin/bitbake-selftest > index f25f23b1ae..ce901232fe 100755 > --- a/bin/bitbake-selftest > +++ b/bin/bitbake-selftest > @@ -15,6 +15,7 @@ import unittest > try: > import bb > import hashserv > + import prserv > import layerindexlib > except RuntimeError as exc: > sys.exit(str(exc)) > @@ -33,6 +34,7 @@ tests = ["bb.tests.codeparser", > "bb.tests.utils", > "bb.tests.compression", > "hashserv.tests", > + "prserv.tests", > "layerindexlib.tests.layerindexobj", > "layerindexlib.tests.restapi", > "layerindexlib.tests.cooker"] > diff --git a/lib/prserv/tests.py b/lib/prserv/tests.py > new file mode 100644 > index 0000000000..051a03c876 > --- /dev/null > +++ b/lib/prserv/tests.py > @@ -0,0 +1,416 @@ > +#! /usr/bin/env python3 > +# > +# Copyright (C) 2024 BitBake Contributors > +# > +# SPDX-License-Identifier: GPL-2.0-only > +# > + > +from . import create_server, create_client, increase_revision, revision_greater, revision_smaller, _revision_greater_or_equal > +import prserv.db as db > +from bb.asyncrpc import InvokeError > +import logging > +import os > +import sys > +import tempfile > +import unittest > +import socket > +import subprocess > +from pathlib import Path > + > +THIS_DIR = Path(__file__).parent > +BIN_DIR = THIS_DIR.parent.parent / "bin" > + > +version = "dummy-1.0-r0" > +pkgarch = "core2-64" > +other_arch = "aarch64" > + > +checksumX = "51bf8189dbe9ea81fa6dd89608bf19380c437a9cf12f6c6239887801ba4ab4f0" > +checksum0 = "51bf8189dbe9ea81fa6dd89608bf19380c437a9cf12f6c6239887801ba4ab4a0" > +checksum1 = "51bf8189dbe9ea81fa6dd89608bf19380c437a9cf12f6c6239887801ba4ab4a1" > +checksum2 = "51bf8189dbe9ea81fa6dd89608bf19380c437a9cf12f6c6239887801ba4ab4a2" > +checksum3 = "51bf8189dbe9ea81fa6dd89608bf19380c437a9cf12f6c6239887801ba4ab4a3" > +checksum4 = "51bf8189dbe9ea81fa6dd89608bf19380c437a9cf12f6c6239887801ba4ab4a4" > +checksum5 = "51bf8189dbe9ea81fa6dd89608bf19380c437a9cf12f6c6239887801ba4ab4a5" > +checksum6 = "51bf8189dbe9ea81fa6dd89608bf19380c437a9cf12f6c6239887801ba4ab4a6" > +checksum7 = "51bf8189dbe9ea81fa6dd89608bf19380c437a9cf12f6c6239887801ba4ab4a7" > +checksum8 = "51bf8189dbe9ea81fa6dd89608bf19380c437a9cf12f6c6239887801ba4ab4a8" > +checksum9 = "51bf8189dbe9ea81fa6dd89608bf19380c437a9cf12f6c6239887801ba4ab4a9" > +checksum10 = "51bf8189dbe9ea81fa6dd89608bf19380c437a9cf12f6c6239887801ba4ab4aa" > + > +def server_prefunc(server, name): > + logging.basicConfig(level=logging.DEBUG, filename='prserv-%s.log' % name, filemode='w', > + format='%(levelname)s %(filename)s:%(lineno)d %(message)s') > + server.logger.debug("Running server %s" % name) > + sys.stdout = open('prserv-stdout-%s.log' % name, 'w') > + sys.stderr = sys.stdout > + > +class PRTestSetup(object): > + > + def start_server(self, name, dbfile, upstream=None, read_only=False, prefunc=server_prefunc): > + > + def cleanup_server(server): > + if server.process.exitcode is not None: > + return > + server.process.terminate() > + server.process.join() > + > + server = create_server(socket.gethostbyname("localhost") + ":0", > + dbfile, > + upstream=upstream, > + read_only=read_only) > + > + server.serve_as_process(prefunc=prefunc, args=(name,)) > + self.addCleanup(cleanup_server, server) > + > + return server > + > + def start_client(self, server_address): > + def cleanup_client(client): > + client.close() > + > + client = create_client(server_address) > + self.addCleanup(cleanup_client, client) > + > + return client > + > +class FunctionTests(unittest.TestCase): > + > + def test_0a_increase_revision(self): > + self.assertEqual(increase_revision("1"), "2") > + self.assertEqual(increase_revision("1.0"), "1.1") > + self.assertEqual(increase_revision("1.1.1"), "1.1.2") > + self.assertEqual(increase_revision("1.1.1.3"), "1.1.1.4") > + self.assertRaises(ValueError, increase_revision, "1.a") > + self.assertRaises(ValueError, increase_revision, "1.") > + self.assertRaises(ValueError, increase_revision, "") > + > + def test_0b_revision_greater_or_equal(self): > + self.assertTrue(_revision_greater_or_equal("2", "2")) > + self.assertTrue(_revision_greater_or_equal("2", "1")) > + self.assertTrue(_revision_greater_or_equal("10", "2")) > + self.assertTrue(_revision_greater_or_equal("1.10", "1.2")) > + self.assertFalse(_revision_greater_or_equal("1.2", "1.10")) > + self.assertTrue(_revision_greater_or_equal("1.10", "1")) > + self.assertTrue(_revision_greater_or_equal("1.10.1", "1.10")) > + self.assertFalse(_revision_greater_or_equal("1.10.1", "1.10.2")) > + self.assertTrue(_revision_greater_or_equal("1.10.1", "1.10.1")) > + self.assertTrue(_revision_greater_or_equal("1.10.1", "1")) > + self.assertTrue(revision_greater("1.20", "1.3")) > + self.assertTrue(revision_smaller("1.3", "1.20")) > + > + # DB tests > + > + def test_0b_db(self): > + dbfile = "testtable.sqlite3" > + if os.path.exists(dbfile): > + os.remove(dbfile) > + > + self.db = db.PRData(dbfile) > + self.table = self.db["PRMAIN"] > + > + self.table.store_value(version, pkgarch, checksum0, "0") > + self.table.store_value(version, pkgarch, checksum1, "1") > + # "No history" mode supports multiple PRs for the same checksum > + self.table.store_value(version, pkgarch, checksum0, "2") > + self.table.store_value(version, pkgarch, checksum2, "1.0") > + > + self.assertTrue(self.table.test_package(version, pkgarch)) > + self.assertFalse(self.table.test_package(version, other_arch)) > + > + self.assertTrue(self.table.test_value(version, pkgarch, "0")) > + self.assertTrue(self.table.test_value(version, pkgarch, "1")) > + self.assertTrue(self.table.test_value(version, pkgarch, "2")) > + > + self.assertEqual(self.table.find_package_max_value(version, pkgarch), "2") > + > + self.assertEqual(self.table.find_min_value(version, pkgarch, checksum0), "0") > + self.assertEqual(self.table.find_max_value(version, pkgarch, checksum0), "2") > + > + # Test history modes > + self.assertEqual(self.table.find_value(version, pkgarch, checksum0, True), "0") > + self.assertEqual(self.table.find_value(version, pkgarch, checksum0, False), "2") > + > + self.assertEqual(self.table.find_new_subvalue(version, pkgarch, "3"), "3.0") > + self.assertEqual(self.table.find_new_subvalue(version, pkgarch, "1"), "1.1") > + > + # Revision comparison tests > + self.table.store_value(version, pkgarch, checksum1, "1.3") > + self.table.store_value(version, pkgarch, checksum1, "1.20") > + self.assertEqual(self.table.find_min_value(version, pkgarch, checksum1), "1") > + self.assertEqual(self.table.find_max_value(version, pkgarch, checksum1), "1.20") > + > +class PRBasicTests(PRTestSetup, unittest.TestCase): > + > + def setUp(self): > + dbfile = "prtest-basic.sqlite3" It's generally considered bad form to dump test files in the users CWD and also blindly os.remove() things there :) In the hash server tests, we create a temporary directory and clean it up when the test ends by doing this in setUp(): self.temp_dir = tempfile.TemporaryDirectory(prefix='bb-hashserv') self.addCleanup(self.temp_dir.cleanup) Then use self.temp_dir as the base whenever you need a path, e.g: dbfile = os.path.join(self.temp_dir, "prtest-basic.sqlite3") > + if os.path.exists(dbfile): > + os.remove(dbfile) > + > + self.server1 = self.start_server("basic", dbfile) > + self.client1 = self.start_client(self.server1.address) > + > + def test_1a_basic(self): > + > + # Checks on non existing configuration > + > + result = self.client1.test_pr(version, pkgarch, checksum0) > + self.assertIsNone(result, "test_pr should return 'None' for a non existing PR") > + > + result = self.client1.test_package(version, pkgarch) > + self.assertFalse(result, "test_package should return 'False' for a non existing PR") > + > + result = self.client1.max_package_pr(version, pkgarch) > + self.assertIsNone(result, "max_package_pr should return 'None' for a non existing PR") > + > + # Add a first configuration > + > + result = self.client1.getPR(version, pkgarch, checksum0) > + self.assertEqual(result, "0", "getPR: initial PR of a package should be '0'") > + > + result = self.client1.test_pr(version, pkgarch, checksum0) > + self.assertEqual(result, "0", "test_pr should return '0' here, matching the result of getPR") > + > + result = self.client1.test_package(version, pkgarch) > + self.assertTrue(result, "test_package should return 'True' for an existing PR") > + > + result = self.client1.max_package_pr(version, pkgarch) > + self.assertEqual(result, "0", "max_package_pr should return '0' in the current test series") > + > + # Check that the same request gets the same value > + > + result = self.client1.getPR(version, pkgarch, checksum0) > + self.assertEqual(result, "0", "getPR: asking for the same PR a second time in a row should return the same value.") > + > + # Add new configurations > + > + result = self.client1.getPR(version, pkgarch, checksum1) > + self.assertEqual(result, "1", "getPR: second PR of a package should be '1'") > + > + result = self.client1.test_pr(version, pkgarch, checksum1) > + self.assertEqual(result, "1", "test_pr should return '1' here, matching the result of getPR") > + > + result = self.client1.max_package_pr(version, pkgarch) > + self.assertEqual(result, "1", "max_package_pr should return '1' in the current test series") > + > + result = self.client1.getPR(version, pkgarch, checksum2) > + self.assertEqual(result, "2", "getPR: second PR of a package should be '2'") > + > + result = self.client1.test_pr(version, pkgarch, checksum2) > + self.assertEqual(result, "2", "test_pr should return '2' here, matching the result of getPR") > + > + result = self.client1.max_package_pr(version, pkgarch) > + self.assertEqual(result, "2", "max_package_pr should return '2' in the current test series") > + > + result = self.client1.getPR(version, pkgarch, checksum3) > + self.assertEqual(result, "3", "getPR: second PR of a package should be '3'") > + > + result = self.client1.test_pr(version, pkgarch, checksum3) > + self.assertEqual(result, "3", "test_pr should return '3' here, matching the result of getPR") > + > + result = self.client1.max_package_pr(version, pkgarch) > + self.assertEqual(result, "3", "max_package_pr should return '3' in the current test series") > + > + # Ask again for the first configuration > + > + result = self.client1.getPR(version, pkgarch, checksum0) > + self.assertEqual(result, "4", "getPR: should return '4' in this configuration") > + > + # Ask again with explicit "no history" mode > + > + result = self.client1.getPR(version, pkgarch, checksum0, False) > + self.assertEqual(result, "4", "getPR: should return '4' in this configuration") > + > + # Ask again with explicit "history" mode. This should return the first recorded PR for checksum0 > + > + result = self.client1.getPR(version, pkgarch, checksum0, True) > + self.assertEqual(result, "0", "getPR: should return '0' in this configuration") > + > + # Check again that another pkgarg resets the counters > + > + result = self.client1.test_pr(version, other_arch, checksum0) > + self.assertIsNone(result, "test_pr should return 'None' for a non existing PR") > + > + result = self.client1.test_package(version, other_arch) > + self.assertFalse(result, "test_package should return 'False' for a non existing PR") > + > + result = self.client1.max_package_pr(version, other_arch) > + self.assertIsNone(result, "max_package_pr should return 'None' for a non existing PR") > + > + # Now add the configuration > + > + result = self.client1.getPR(version, other_arch, checksum0) > + self.assertEqual(result, "0", "getPR: initial PR of a package should be '0'") > + > + result = self.client1.test_pr(version, other_arch, checksum0) > + self.assertEqual(result, "0", "test_pr should return '0' here, matching the result of getPR") > + > + result = self.client1.test_package(version, other_arch) > + self.assertTrue(result, "test_package should return 'True' for an existing PR") > + > + result = self.client1.max_package_pr(version, other_arch) > + self.assertEqual(result, "0", "max_package_pr should return '0' in the current test series") > + > + def test_1b_is_readonly(self): > + result = self.client1.is_readonly() > + self.assertFalse(result, "Server should not be described as 'read-only'") > + > +class PRReadOnlyTests(PRTestSetup, unittest.TestCase): > + > + def setUp(self): > + # Here we're using the database test table, as for reasons not understood yet, > + # the other tables we created from the PR server in prior tests, have empty contents! > + dbfile = "testtable.sqlite3" > + self.server1 = self.start_server("basic-readonly", dbfile, read_only=True) > + self.client1 = self.start_client(self.server1.address) > + > + def test_2a_read_only_server(self): > + > + result = self.client1.is_readonly() > + self.assertTrue(result, "Database should be described as 'read-only'") > + > + # Checks on non existing configuration > + self.assertIsNone(self.client1.test_pr(version, pkgarch, checksumX)) > + self.assertFalse(self.client1.test_package("unknown", pkgarch)) > + > + # Look up an existing configuration > + self.assertEqual(self.client1.getPR(version, pkgarch, checksum0), "2") # "no history" mode > + self.assertEqual(self.client1.getPR(version, pkgarch, checksum0, True), "0") # "history" mode > + self.assertEqual(self.client1.getPR(version, pkgarch, checksum2), "3") > + self.assertEqual(self.client1.getPR(version, pkgarch, checksum2, True), "1.0") > + self.assertEqual(self.client1.max_package_pr(version, pkgarch), "2") > + > + # Try to insert a new value. The value should be correct given the read-only dababase, but won't be saved > + self.assertEqual(self.client1.getPR(version, pkgarch, checksum3), "3") > + # Same of another value > + self.assertEqual(self.client1.getPR(version, pkgarch, checksum3), "3") > + > +class PRUpstreamTests(PRTestSetup, unittest.TestCase): > + > + def setUp(self): > + > + dbfile2 = "prtest-upstream2.sqlite3" > + if os.path.exists(dbfile2): > + os.remove(dbfile2) > + > + self.server2 = self.start_server("upstream2", dbfile2) > + self.client2 = self.start_client(self.server2.address) > + > + dbfile1 = "prtest-upstream1.sqlite3" > + if os.path.exists(dbfile1): > + os.remove(dbfile1) > + > + self.server1 = self.start_server("upstream1", dbfile1, upstream=self.server2.address) > + self.client1 = self.start_client(self.server1.address) > + > + dbfile0 = "prtest-local.sqlite3" > + if os.path.exists(dbfile0): > + os.remove(dbfile0) > + > + self.server0 = self.start_server("local", dbfile0, upstream=self.server1.address) > + self.client0 = self.start_client(self.server0.address) > + self.shared_db = dbfile0 > + > + def test_3a_upstream(self): > + > + # For identical checksums, all servers should return the same PR > + > + result = self.client2.getPR(version, pkgarch, checksum0) > + self.assertEqual(result, "0", "getPR: initial PR of a package should be '0'") > + > + result = self.client1.getPR(version, pkgarch, checksum0) > + self.assertEqual(result, "0", "getPR: initial PR of a package should be '0' (same as upstream)") > + > + result = self.client0.getPR(version, pkgarch, checksum0) > + self.assertEqual(result, "0", "getPR: initial PR of a package should be '0' (same as upstream)") > + > + # Now introduce new checksums on server1 for, same version > + > + result = self.client1.getPR(version, pkgarch, checksum1) > + self.assertEqual(result, "0.0", "getPR: first PR of a package which has a different checksum upstream should be '0.0'") > + > + result = self.client1.getPR(version, pkgarch, checksum2) > + self.assertEqual(result, "0.1", "getPR: second PR of a package that has a different checksum upstream should be '0.1'") > + > + # Now introduce checksums on server0 for, same version > + > + result = self.client1.getPR(version, pkgarch, checksum1) > + self.assertEqual(result, "0.2", "getPR: can't decrease for known PR") > + > + result = self.client1.getPR(version, pkgarch, checksum2) > + self.assertEqual(result, "0.3") > + > + result = self.client1.max_package_pr(version, pkgarch) > + self.assertEqual(result, "0.3") > + > + result = self.client0.getPR(version, pkgarch, checksum3) > + self.assertEqual(result, "0.3.0", "getPR: first PR of a package that doesn't exist upstream should be '0.3.0'") > + > + result = self.client0.getPR(version, pkgarch, checksum4) > + self.assertEqual(result, "0.3.1", "getPR: second PR of a package that doesn't exist upstream should be '0.3.1'") > + > + result = self.client0.getPR(version, pkgarch, checksum3) > + self.assertEqual(result, "0.3.2") > + > + # More upstream updates > + # Here, we assume no communication between server2 and server0. server2 only impacts server0 > + # after impacting server1 > + > + self.assertEqual(self.client2.getPR(version, pkgarch, checksum5), "1") > + self.assertEqual(self.client1.getPR(version, pkgarch, checksum6), "1.0") > + self.assertEqual(self.client1.getPR(version, pkgarch, checksum7), "1.1") > + self.assertEqual(self.client0.getPR(version, pkgarch, checksum8), "1.1.0") > + self.assertEqual(self.client0.getPR(version, pkgarch, checksum9), "1.1.1") > + > + # "history" mode tests > + > + self.assertEqual(self.client2.getPR(version, pkgarch, checksum0, True), "0") > + self.assertEqual(self.client1.getPR(version, pkgarch, checksum2, True), "0.1") > + self.assertEqual(self.client0.getPR(version, pkgarch, checksum3, True), "0.3.0") > + > + # More "no history" mode tests > + > + self.assertEqual(self.client2.getPR(version, pkgarch, checksum0), "2") > + self.assertEqual(self.client1.getPR(version, pkgarch, checksum0), "2") # Same as upstream > + self.assertEqual(self.client0.getPR(version, pkgarch, checksum0), "2") # Same as upstream > + self.assertEqual(self.client1.getPR(version, pkgarch, checksum7), "3") # This could be surprising, but since the previous revision was "2", increasing it yields "3". > + # We don't know how many upstream servers we have > + def test_3b_readonly(self): > + > + # Start read-only server > + self.server_ro = self.start_server("local-ro", self.shared_db, upstream=self.server1.address, read_only=True) > + self.client_ro = self.start_client(self.server_ro.address) > + > + self.assertTrue(self.client_ro.is_readonly(), "Database should be described as 'read-only'") > + > + # Checks on non existing configurations > + self.assertIsNone(self.client_ro.test_pr(version, pkgarch, checksumX)) > + self.assertFalse(self.client_ro.test_package("unknown", pkgarch)) > + > + # Look up existing configurations > + self.assertEqual(self.client_ro.getPR(version, pkgarch, checksum0), "2") # "no history" mode > + #self.assertEqual(self.client_ro.getPR(version, pkgarch, checksum0, True), "0") # "history" mode > + #self.assertEqual(self.client_ro.getPR(version, pkgarch, checksum3), "0.3.0") > + #self.assertEqual(self.client_ro.getPR(version, pkgarch, checksum3, True), "0.3.0") > + self.assertEqual(self.client_ro.max_package_pr(version, pkgarch), "3") > + > + # Try to insert a new value. The value should be correct given the read-only dababase, but won't be saved > + self.assertEqual(self.client_ro.getPR(version, pkgarch, checksum7), "3") > + # Same of another value which only exists in the upstream upstream > + # It's as it's a completely new one. We may want the upstream server to ask its upstream server for this value though. > + self.assertEqual(self.client_ro.getPR(version, pkgarch, checksum8), "4") > + # Try to insert a completely new value. It should also return "4" as the previous value wasn't saved by the read-only server. > + self.assertEqual(self.client_ro.getPR(version, pkgarch, checksum10), "4") > + > +class ScriptTests(unittest.TestCase): > + > + def test_5a_start_bitbake_prserv(self): > + try: > + subprocess.check_call([BIN_DIR / "bitbake-prserv", "--start", "-f", "testtable.sqlite3"]) > + except subprocess.CalledProcessError as e: > + self.fail("Failed to start bitbake-prserv: %s" % e.returncode) > + > + def test_5b_stop_bitbake_prserv(self): > + try: > + subprocess.check_call([BIN_DIR / "bitbake-prserv", "--stop"]) > + except subprocess.CalledProcessError as e: > + self.fail("Failed to stop bitbake-prserv: %s" % e.returncode) > -- > 2.34.1 >
Hi Joshua Many thanks for the review! On 4/29/24 at 23:08, Joshua Watt wrote: >> + >> +class PRBasicTests(PRTestSetup, unittest.TestCase): >> + >> + def setUp(self): >> + dbfile = "prtest-basic.sqlite3" > It's generally considered bad form to dump test files in the users CWD > and also blindly os.remove() things there :) > > In the hash server tests, we create a temporary directory and clean it > up when the test ends by doing this in setUp(): > > self.temp_dir = tempfile.TemporaryDirectory(prefix='bb-hashserv') > self.addCleanup(self.temp_dir.cleanup) > > Then use self.temp_dir as the base whenever you need a path, e.g: > > dbfile = os.path.join(self.temp_dir, "prtest-basic.sqlite3") Done! Good catch. I remember now keeping the database files when I was implementing the tests so that I could inspect the databases manually. At least with this approach, I don't have to remove the database file if it exists! This almost makes the code smaller :) This will be included in my next branch update. Thanks! Michael.
diff --git a/bin/bitbake-selftest b/bin/bitbake-selftest index f25f23b1ae..ce901232fe 100755 --- a/bin/bitbake-selftest +++ b/bin/bitbake-selftest @@ -15,6 +15,7 @@ import unittest try: import bb import hashserv + import prserv import layerindexlib except RuntimeError as exc: sys.exit(str(exc)) @@ -33,6 +34,7 @@ tests = ["bb.tests.codeparser", "bb.tests.utils", "bb.tests.compression", "hashserv.tests", + "prserv.tests", "layerindexlib.tests.layerindexobj", "layerindexlib.tests.restapi", "layerindexlib.tests.cooker"] diff --git a/lib/prserv/tests.py b/lib/prserv/tests.py new file mode 100644 index 0000000000..051a03c876 --- /dev/null +++ b/lib/prserv/tests.py @@ -0,0 +1,416 @@ +#! /usr/bin/env python3 +# +# Copyright (C) 2024 BitBake Contributors +# +# SPDX-License-Identifier: GPL-2.0-only +# + +from . import create_server, create_client, increase_revision, revision_greater, revision_smaller, _revision_greater_or_equal +import prserv.db as db +from bb.asyncrpc import InvokeError +import logging +import os +import sys +import tempfile +import unittest +import socket +import subprocess +from pathlib import Path + +THIS_DIR = Path(__file__).parent +BIN_DIR = THIS_DIR.parent.parent / "bin" + +version = "dummy-1.0-r0" +pkgarch = "core2-64" +other_arch = "aarch64" + +checksumX = "51bf8189dbe9ea81fa6dd89608bf19380c437a9cf12f6c6239887801ba4ab4f0" +checksum0 = "51bf8189dbe9ea81fa6dd89608bf19380c437a9cf12f6c6239887801ba4ab4a0" +checksum1 = "51bf8189dbe9ea81fa6dd89608bf19380c437a9cf12f6c6239887801ba4ab4a1" +checksum2 = "51bf8189dbe9ea81fa6dd89608bf19380c437a9cf12f6c6239887801ba4ab4a2" +checksum3 = "51bf8189dbe9ea81fa6dd89608bf19380c437a9cf12f6c6239887801ba4ab4a3" +checksum4 = "51bf8189dbe9ea81fa6dd89608bf19380c437a9cf12f6c6239887801ba4ab4a4" +checksum5 = "51bf8189dbe9ea81fa6dd89608bf19380c437a9cf12f6c6239887801ba4ab4a5" +checksum6 = "51bf8189dbe9ea81fa6dd89608bf19380c437a9cf12f6c6239887801ba4ab4a6" +checksum7 = "51bf8189dbe9ea81fa6dd89608bf19380c437a9cf12f6c6239887801ba4ab4a7" +checksum8 = "51bf8189dbe9ea81fa6dd89608bf19380c437a9cf12f6c6239887801ba4ab4a8" +checksum9 = "51bf8189dbe9ea81fa6dd89608bf19380c437a9cf12f6c6239887801ba4ab4a9" +checksum10 = "51bf8189dbe9ea81fa6dd89608bf19380c437a9cf12f6c6239887801ba4ab4aa" + +def server_prefunc(server, name): + logging.basicConfig(level=logging.DEBUG, filename='prserv-%s.log' % name, filemode='w', + format='%(levelname)s %(filename)s:%(lineno)d %(message)s') + server.logger.debug("Running server %s" % name) + sys.stdout = open('prserv-stdout-%s.log' % name, 'w') + sys.stderr = sys.stdout + +class PRTestSetup(object): + + def start_server(self, name, dbfile, upstream=None, read_only=False, prefunc=server_prefunc): + + def cleanup_server(server): + if server.process.exitcode is not None: + return + server.process.terminate() + server.process.join() + + server = create_server(socket.gethostbyname("localhost") + ":0", + dbfile, + upstream=upstream, + read_only=read_only) + + server.serve_as_process(prefunc=prefunc, args=(name,)) + self.addCleanup(cleanup_server, server) + + return server + + def start_client(self, server_address): + def cleanup_client(client): + client.close() + + client = create_client(server_address) + self.addCleanup(cleanup_client, client) + + return client + +class FunctionTests(unittest.TestCase): + + def test_0a_increase_revision(self): + self.assertEqual(increase_revision("1"), "2") + self.assertEqual(increase_revision("1.0"), "1.1") + self.assertEqual(increase_revision("1.1.1"), "1.1.2") + self.assertEqual(increase_revision("1.1.1.3"), "1.1.1.4") + self.assertRaises(ValueError, increase_revision, "1.a") + self.assertRaises(ValueError, increase_revision, "1.") + self.assertRaises(ValueError, increase_revision, "") + + def test_0b_revision_greater_or_equal(self): + self.assertTrue(_revision_greater_or_equal("2", "2")) + self.assertTrue(_revision_greater_or_equal("2", "1")) + self.assertTrue(_revision_greater_or_equal("10", "2")) + self.assertTrue(_revision_greater_or_equal("1.10", "1.2")) + self.assertFalse(_revision_greater_or_equal("1.2", "1.10")) + self.assertTrue(_revision_greater_or_equal("1.10", "1")) + self.assertTrue(_revision_greater_or_equal("1.10.1", "1.10")) + self.assertFalse(_revision_greater_or_equal("1.10.1", "1.10.2")) + self.assertTrue(_revision_greater_or_equal("1.10.1", "1.10.1")) + self.assertTrue(_revision_greater_or_equal("1.10.1", "1")) + self.assertTrue(revision_greater("1.20", "1.3")) + self.assertTrue(revision_smaller("1.3", "1.20")) + + # DB tests + + def test_0b_db(self): + dbfile = "testtable.sqlite3" + if os.path.exists(dbfile): + os.remove(dbfile) + + self.db = db.PRData(dbfile) + self.table = self.db["PRMAIN"] + + self.table.store_value(version, pkgarch, checksum0, "0") + self.table.store_value(version, pkgarch, checksum1, "1") + # "No history" mode supports multiple PRs for the same checksum + self.table.store_value(version, pkgarch, checksum0, "2") + self.table.store_value(version, pkgarch, checksum2, "1.0") + + self.assertTrue(self.table.test_package(version, pkgarch)) + self.assertFalse(self.table.test_package(version, other_arch)) + + self.assertTrue(self.table.test_value(version, pkgarch, "0")) + self.assertTrue(self.table.test_value(version, pkgarch, "1")) + self.assertTrue(self.table.test_value(version, pkgarch, "2")) + + self.assertEqual(self.table.find_package_max_value(version, pkgarch), "2") + + self.assertEqual(self.table.find_min_value(version, pkgarch, checksum0), "0") + self.assertEqual(self.table.find_max_value(version, pkgarch, checksum0), "2") + + # Test history modes + self.assertEqual(self.table.find_value(version, pkgarch, checksum0, True), "0") + self.assertEqual(self.table.find_value(version, pkgarch, checksum0, False), "2") + + self.assertEqual(self.table.find_new_subvalue(version, pkgarch, "3"), "3.0") + self.assertEqual(self.table.find_new_subvalue(version, pkgarch, "1"), "1.1") + + # Revision comparison tests + self.table.store_value(version, pkgarch, checksum1, "1.3") + self.table.store_value(version, pkgarch, checksum1, "1.20") + self.assertEqual(self.table.find_min_value(version, pkgarch, checksum1), "1") + self.assertEqual(self.table.find_max_value(version, pkgarch, checksum1), "1.20") + +class PRBasicTests(PRTestSetup, unittest.TestCase): + + def setUp(self): + dbfile = "prtest-basic.sqlite3" + if os.path.exists(dbfile): + os.remove(dbfile) + + self.server1 = self.start_server("basic", dbfile) + self.client1 = self.start_client(self.server1.address) + + def test_1a_basic(self): + + # Checks on non existing configuration + + result = self.client1.test_pr(version, pkgarch, checksum0) + self.assertIsNone(result, "test_pr should return 'None' for a non existing PR") + + result = self.client1.test_package(version, pkgarch) + self.assertFalse(result, "test_package should return 'False' for a non existing PR") + + result = self.client1.max_package_pr(version, pkgarch) + self.assertIsNone(result, "max_package_pr should return 'None' for a non existing PR") + + # Add a first configuration + + result = self.client1.getPR(version, pkgarch, checksum0) + self.assertEqual(result, "0", "getPR: initial PR of a package should be '0'") + + result = self.client1.test_pr(version, pkgarch, checksum0) + self.assertEqual(result, "0", "test_pr should return '0' here, matching the result of getPR") + + result = self.client1.test_package(version, pkgarch) + self.assertTrue(result, "test_package should return 'True' for an existing PR") + + result = self.client1.max_package_pr(version, pkgarch) + self.assertEqual(result, "0", "max_package_pr should return '0' in the current test series") + + # Check that the same request gets the same value + + result = self.client1.getPR(version, pkgarch, checksum0) + self.assertEqual(result, "0", "getPR: asking for the same PR a second time in a row should return the same value.") + + # Add new configurations + + result = self.client1.getPR(version, pkgarch, checksum1) + self.assertEqual(result, "1", "getPR: second PR of a package should be '1'") + + result = self.client1.test_pr(version, pkgarch, checksum1) + self.assertEqual(result, "1", "test_pr should return '1' here, matching the result of getPR") + + result = self.client1.max_package_pr(version, pkgarch) + self.assertEqual(result, "1", "max_package_pr should return '1' in the current test series") + + result = self.client1.getPR(version, pkgarch, checksum2) + self.assertEqual(result, "2", "getPR: second PR of a package should be '2'") + + result = self.client1.test_pr(version, pkgarch, checksum2) + self.assertEqual(result, "2", "test_pr should return '2' here, matching the result of getPR") + + result = self.client1.max_package_pr(version, pkgarch) + self.assertEqual(result, "2", "max_package_pr should return '2' in the current test series") + + result = self.client1.getPR(version, pkgarch, checksum3) + self.assertEqual(result, "3", "getPR: second PR of a package should be '3'") + + result = self.client1.test_pr(version, pkgarch, checksum3) + self.assertEqual(result, "3", "test_pr should return '3' here, matching the result of getPR") + + result = self.client1.max_package_pr(version, pkgarch) + self.assertEqual(result, "3", "max_package_pr should return '3' in the current test series") + + # Ask again for the first configuration + + result = self.client1.getPR(version, pkgarch, checksum0) + self.assertEqual(result, "4", "getPR: should return '4' in this configuration") + + # Ask again with explicit "no history" mode + + result = self.client1.getPR(version, pkgarch, checksum0, False) + self.assertEqual(result, "4", "getPR: should return '4' in this configuration") + + # Ask again with explicit "history" mode. This should return the first recorded PR for checksum0 + + result = self.client1.getPR(version, pkgarch, checksum0, True) + self.assertEqual(result, "0", "getPR: should return '0' in this configuration") + + # Check again that another pkgarg resets the counters + + result = self.client1.test_pr(version, other_arch, checksum0) + self.assertIsNone(result, "test_pr should return 'None' for a non existing PR") + + result = self.client1.test_package(version, other_arch) + self.assertFalse(result, "test_package should return 'False' for a non existing PR") + + result = self.client1.max_package_pr(version, other_arch) + self.assertIsNone(result, "max_package_pr should return 'None' for a non existing PR") + + # Now add the configuration + + result = self.client1.getPR(version, other_arch, checksum0) + self.assertEqual(result, "0", "getPR: initial PR of a package should be '0'") + + result = self.client1.test_pr(version, other_arch, checksum0) + self.assertEqual(result, "0", "test_pr should return '0' here, matching the result of getPR") + + result = self.client1.test_package(version, other_arch) + self.assertTrue(result, "test_package should return 'True' for an existing PR") + + result = self.client1.max_package_pr(version, other_arch) + self.assertEqual(result, "0", "max_package_pr should return '0' in the current test series") + + def test_1b_is_readonly(self): + result = self.client1.is_readonly() + self.assertFalse(result, "Server should not be described as 'read-only'") + +class PRReadOnlyTests(PRTestSetup, unittest.TestCase): + + def setUp(self): + # Here we're using the database test table, as for reasons not understood yet, + # the other tables we created from the PR server in prior tests, have empty contents! + dbfile = "testtable.sqlite3" + self.server1 = self.start_server("basic-readonly", dbfile, read_only=True) + self.client1 = self.start_client(self.server1.address) + + def test_2a_read_only_server(self): + + result = self.client1.is_readonly() + self.assertTrue(result, "Database should be described as 'read-only'") + + # Checks on non existing configuration + self.assertIsNone(self.client1.test_pr(version, pkgarch, checksumX)) + self.assertFalse(self.client1.test_package("unknown", pkgarch)) + + # Look up an existing configuration + self.assertEqual(self.client1.getPR(version, pkgarch, checksum0), "2") # "no history" mode + self.assertEqual(self.client1.getPR(version, pkgarch, checksum0, True), "0") # "history" mode + self.assertEqual(self.client1.getPR(version, pkgarch, checksum2), "3") + self.assertEqual(self.client1.getPR(version, pkgarch, checksum2, True), "1.0") + self.assertEqual(self.client1.max_package_pr(version, pkgarch), "2") + + # Try to insert a new value. The value should be correct given the read-only dababase, but won't be saved + self.assertEqual(self.client1.getPR(version, pkgarch, checksum3), "3") + # Same of another value + self.assertEqual(self.client1.getPR(version, pkgarch, checksum3), "3") + +class PRUpstreamTests(PRTestSetup, unittest.TestCase): + + def setUp(self): + + dbfile2 = "prtest-upstream2.sqlite3" + if os.path.exists(dbfile2): + os.remove(dbfile2) + + self.server2 = self.start_server("upstream2", dbfile2) + self.client2 = self.start_client(self.server2.address) + + dbfile1 = "prtest-upstream1.sqlite3" + if os.path.exists(dbfile1): + os.remove(dbfile1) + + self.server1 = self.start_server("upstream1", dbfile1, upstream=self.server2.address) + self.client1 = self.start_client(self.server1.address) + + dbfile0 = "prtest-local.sqlite3" + if os.path.exists(dbfile0): + os.remove(dbfile0) + + self.server0 = self.start_server("local", dbfile0, upstream=self.server1.address) + self.client0 = self.start_client(self.server0.address) + self.shared_db = dbfile0 + + def test_3a_upstream(self): + + # For identical checksums, all servers should return the same PR + + result = self.client2.getPR(version, pkgarch, checksum0) + self.assertEqual(result, "0", "getPR: initial PR of a package should be '0'") + + result = self.client1.getPR(version, pkgarch, checksum0) + self.assertEqual(result, "0", "getPR: initial PR of a package should be '0' (same as upstream)") + + result = self.client0.getPR(version, pkgarch, checksum0) + self.assertEqual(result, "0", "getPR: initial PR of a package should be '0' (same as upstream)") + + # Now introduce new checksums on server1 for, same version + + result = self.client1.getPR(version, pkgarch, checksum1) + self.assertEqual(result, "0.0", "getPR: first PR of a package which has a different checksum upstream should be '0.0'") + + result = self.client1.getPR(version, pkgarch, checksum2) + self.assertEqual(result, "0.1", "getPR: second PR of a package that has a different checksum upstream should be '0.1'") + + # Now introduce checksums on server0 for, same version + + result = self.client1.getPR(version, pkgarch, checksum1) + self.assertEqual(result, "0.2", "getPR: can't decrease for known PR") + + result = self.client1.getPR(version, pkgarch, checksum2) + self.assertEqual(result, "0.3") + + result = self.client1.max_package_pr(version, pkgarch) + self.assertEqual(result, "0.3") + + result = self.client0.getPR(version, pkgarch, checksum3) + self.assertEqual(result, "0.3.0", "getPR: first PR of a package that doesn't exist upstream should be '0.3.0'") + + result = self.client0.getPR(version, pkgarch, checksum4) + self.assertEqual(result, "0.3.1", "getPR: second PR of a package that doesn't exist upstream should be '0.3.1'") + + result = self.client0.getPR(version, pkgarch, checksum3) + self.assertEqual(result, "0.3.2") + + # More upstream updates + # Here, we assume no communication between server2 and server0. server2 only impacts server0 + # after impacting server1 + + self.assertEqual(self.client2.getPR(version, pkgarch, checksum5), "1") + self.assertEqual(self.client1.getPR(version, pkgarch, checksum6), "1.0") + self.assertEqual(self.client1.getPR(version, pkgarch, checksum7), "1.1") + self.assertEqual(self.client0.getPR(version, pkgarch, checksum8), "1.1.0") + self.assertEqual(self.client0.getPR(version, pkgarch, checksum9), "1.1.1") + + # "history" mode tests + + self.assertEqual(self.client2.getPR(version, pkgarch, checksum0, True), "0") + self.assertEqual(self.client1.getPR(version, pkgarch, checksum2, True), "0.1") + self.assertEqual(self.client0.getPR(version, pkgarch, checksum3, True), "0.3.0") + + # More "no history" mode tests + + self.assertEqual(self.client2.getPR(version, pkgarch, checksum0), "2") + self.assertEqual(self.client1.getPR(version, pkgarch, checksum0), "2") # Same as upstream + self.assertEqual(self.client0.getPR(version, pkgarch, checksum0), "2") # Same as upstream + self.assertEqual(self.client1.getPR(version, pkgarch, checksum7), "3") # This could be surprising, but since the previous revision was "2", increasing it yields "3". + # We don't know how many upstream servers we have + def test_3b_readonly(self): + + # Start read-only server + self.server_ro = self.start_server("local-ro", self.shared_db, upstream=self.server1.address, read_only=True) + self.client_ro = self.start_client(self.server_ro.address) + + self.assertTrue(self.client_ro.is_readonly(), "Database should be described as 'read-only'") + + # Checks on non existing configurations + self.assertIsNone(self.client_ro.test_pr(version, pkgarch, checksumX)) + self.assertFalse(self.client_ro.test_package("unknown", pkgarch)) + + # Look up existing configurations + self.assertEqual(self.client_ro.getPR(version, pkgarch, checksum0), "2") # "no history" mode + #self.assertEqual(self.client_ro.getPR(version, pkgarch, checksum0, True), "0") # "history" mode + #self.assertEqual(self.client_ro.getPR(version, pkgarch, checksum3), "0.3.0") + #self.assertEqual(self.client_ro.getPR(version, pkgarch, checksum3, True), "0.3.0") + self.assertEqual(self.client_ro.max_package_pr(version, pkgarch), "3") + + # Try to insert a new value. The value should be correct given the read-only dababase, but won't be saved + self.assertEqual(self.client_ro.getPR(version, pkgarch, checksum7), "3") + # Same of another value which only exists in the upstream upstream + # It's as it's a completely new one. We may want the upstream server to ask its upstream server for this value though. + self.assertEqual(self.client_ro.getPR(version, pkgarch, checksum8), "4") + # Try to insert a completely new value. It should also return "4" as the previous value wasn't saved by the read-only server. + self.assertEqual(self.client_ro.getPR(version, pkgarch, checksum10), "4") + +class ScriptTests(unittest.TestCase): + + def test_5a_start_bitbake_prserv(self): + try: + subprocess.check_call([BIN_DIR / "bitbake-prserv", "--start", "-f", "testtable.sqlite3"]) + except subprocess.CalledProcessError as e: + self.fail("Failed to start bitbake-prserv: %s" % e.returncode) + + def test_5b_stop_bitbake_prserv(self): + try: + subprocess.check_call([BIN_DIR / "bitbake-prserv", "--stop"]) + except subprocess.CalledProcessError as e: + self.fail("Failed to stop bitbake-prserv: %s" % e.returncode)