1#! /usr/bin/env python3 2# 3# Copyright (C) 2024 BitBake Contributors 4# 5# SPDX-License-Identifier: GPL-2.0-only 6# 7 8from . import create_server, create_client, increase_revision, revision_greater, revision_smaller, _revision_greater_or_equal 9import prserv.db as db 10from bb.asyncrpc import InvokeError 11import logging 12import os 13import sys 14import tempfile 15import unittest 16import socket 17import subprocess 18from pathlib import Path 19 20THIS_DIR = Path(__file__).parent 21BIN_DIR = THIS_DIR.parent.parent / "bin" 22 23version = "dummy-1.0-r0" 24pkgarch = "core2-64" 25other_arch = "aarch64" 26 27checksumX = "51bf8189dbe9ea81fa6dd89608bf19380c437a9cf12f6c6239887801ba4ab4f0" 28checksum0 = "51bf8189dbe9ea81fa6dd89608bf19380c437a9cf12f6c6239887801ba4ab4a0" 29checksum1 = "51bf8189dbe9ea81fa6dd89608bf19380c437a9cf12f6c6239887801ba4ab4a1" 30checksum2 = "51bf8189dbe9ea81fa6dd89608bf19380c437a9cf12f6c6239887801ba4ab4a2" 31checksum3 = "51bf8189dbe9ea81fa6dd89608bf19380c437a9cf12f6c6239887801ba4ab4a3" 32checksum4 = "51bf8189dbe9ea81fa6dd89608bf19380c437a9cf12f6c6239887801ba4ab4a4" 33checksum5 = "51bf8189dbe9ea81fa6dd89608bf19380c437a9cf12f6c6239887801ba4ab4a5" 34checksum6 = "51bf8189dbe9ea81fa6dd89608bf19380c437a9cf12f6c6239887801ba4ab4a6" 35checksum7 = "51bf8189dbe9ea81fa6dd89608bf19380c437a9cf12f6c6239887801ba4ab4a7" 36checksum8 = "51bf8189dbe9ea81fa6dd89608bf19380c437a9cf12f6c6239887801ba4ab4a8" 37checksum9 = "51bf8189dbe9ea81fa6dd89608bf19380c437a9cf12f6c6239887801ba4ab4a9" 38checksum10 = "51bf8189dbe9ea81fa6dd89608bf19380c437a9cf12f6c6239887801ba4ab4aa" 39 40def server_prefunc(server, name): 41 logging.basicConfig(level=logging.DEBUG, filename='prserv-%s.log' % name, filemode='w', 42 format='%(levelname)s %(filename)s:%(lineno)d %(message)s') 43 server.logger.debug("Running server %s" % name) 44 sys.stdout = open('prserv-stdout-%s.log' % name, 'w') 45 sys.stderr = sys.stdout 46 47class PRTestSetup(object): 48 49 def start_server(self, name, dbfile, upstream=None, read_only=False, prefunc=server_prefunc): 50 51 def cleanup_server(server): 52 if server.process.exitcode is not None: 53 return 54 server.process.terminate() 55 server.process.join() 56 57 server = create_server(socket.gethostbyname("localhost") + ":0", 58 dbfile, 59 upstream=upstream, 60 read_only=read_only) 61 62 server.serve_as_process(prefunc=prefunc, args=(name,)) 63 self.addCleanup(cleanup_server, server) 64 65 return server 66 67 def start_client(self, server_address): 68 def cleanup_client(client): 69 client.close() 70 71 client = create_client(server_address) 72 self.addCleanup(cleanup_client, client) 73 74 return client 75 76class FunctionTests(unittest.TestCase): 77 78 def setUp(self): 79 self.temp_dir = tempfile.TemporaryDirectory(prefix='bb-prserv') 80 self.addCleanup(self.temp_dir.cleanup) 81 82 def test_increase_revision(self): 83 self.assertEqual(increase_revision("1"), "2") 84 self.assertEqual(increase_revision("1.0"), "1.1") 85 self.assertEqual(increase_revision("1.1.1"), "1.1.2") 86 self.assertEqual(increase_revision("1.1.1.3"), "1.1.1.4") 87 self.assertEqual(increase_revision("9"), "10") 88 self.assertEqual(increase_revision("1.9"), "1.10") 89 self.assertRaises(ValueError, increase_revision, "1.a") 90 self.assertRaises(ValueError, increase_revision, "1.") 91 self.assertRaises(ValueError, increase_revision, "") 92 93 def test_revision_greater_or_equal(self): 94 self.assertTrue(_revision_greater_or_equal("2", "2")) 95 self.assertTrue(_revision_greater_or_equal("2", "1")) 96 self.assertTrue(_revision_greater_or_equal("10", "2")) 97 self.assertTrue(_revision_greater_or_equal("1.10", "1.2")) 98 self.assertFalse(_revision_greater_or_equal("1.2", "1.10")) 99 self.assertTrue(_revision_greater_or_equal("1.10", "1")) 100 self.assertTrue(_revision_greater_or_equal("1.10.1", "1.10")) 101 self.assertFalse(_revision_greater_or_equal("1.10.1", "1.10.2")) 102 self.assertTrue(_revision_greater_or_equal("1.10.1", "1.10.1")) 103 self.assertTrue(_revision_greater_or_equal("1.10.1", "1")) 104 self.assertTrue(revision_greater("1.20", "1.3")) 105 self.assertTrue(revision_smaller("1.3", "1.20")) 106 107 # DB tests 108 109 def test_db(self): 110 dbfile = os.path.join(self.temp_dir.name, "testtable.sqlite3") 111 112 self.db = db.PRData(dbfile) 113 self.table = self.db["PRMAIN"] 114 115 self.table.store_value(version, pkgarch, checksum0, "0") 116 self.table.store_value(version, pkgarch, checksum1, "1") 117 # "No history" mode supports multiple PRs for the same checksum 118 self.table.store_value(version, pkgarch, checksum0, "2") 119 self.table.store_value(version, pkgarch, checksum2, "1.0") 120 121 self.assertTrue(self.table.test_package(version, pkgarch)) 122 self.assertFalse(self.table.test_package(version, other_arch)) 123 124 self.assertTrue(self.table.test_value(version, pkgarch, "0")) 125 self.assertTrue(self.table.test_value(version, pkgarch, "1")) 126 self.assertTrue(self.table.test_value(version, pkgarch, "2")) 127 128 self.assertEqual(self.table.find_package_max_value(version, pkgarch), "2") 129 130 self.assertEqual(self.table.find_min_value(version, pkgarch, checksum0), "0") 131 self.assertEqual(self.table.find_max_value(version, pkgarch, checksum0), "2") 132 133 # Test history modes 134 self.assertEqual(self.table.find_value(version, pkgarch, checksum0, True), "0") 135 self.assertEqual(self.table.find_value(version, pkgarch, checksum0, False), "2") 136 137 self.assertEqual(self.table.find_new_subvalue(version, pkgarch, "3"), "3.0") 138 self.assertEqual(self.table.find_new_subvalue(version, pkgarch, "1"), "1.1") 139 140 # Revision comparison tests 141 self.table.store_value(version, pkgarch, checksum1, "1.3") 142 self.table.store_value(version, pkgarch, checksum1, "1.20") 143 self.assertEqual(self.table.find_min_value(version, pkgarch, checksum1), "1") 144 self.assertEqual(self.table.find_max_value(version, pkgarch, checksum1), "1.20") 145 146class PRBasicTests(PRTestSetup, unittest.TestCase): 147 148 def setUp(self): 149 self.temp_dir = tempfile.TemporaryDirectory(prefix='bb-prserv') 150 self.addCleanup(self.temp_dir.cleanup) 151 152 dbfile = os.path.join(self.temp_dir.name, "prtest-basic.sqlite3") 153 154 self.server1 = self.start_server("basic", dbfile) 155 self.client1 = self.start_client(self.server1.address) 156 157 def test_basic(self): 158 159 # Checks on non existing configuration 160 161 result = self.client1.test_pr(version, pkgarch, checksum0) 162 self.assertIsNone(result, "test_pr should return 'None' for a non existing PR") 163 164 result = self.client1.test_package(version, pkgarch) 165 self.assertFalse(result, "test_package should return 'False' for a non existing PR") 166 167 result = self.client1.max_package_pr(version, pkgarch) 168 self.assertIsNone(result, "max_package_pr should return 'None' for a non existing PR") 169 170 # Add a first configuration 171 172 result = self.client1.getPR(version, pkgarch, checksum0) 173 self.assertEqual(result, "0", "getPR: initial PR of a package should be '0'") 174 175 result = self.client1.test_pr(version, pkgarch, checksum0) 176 self.assertEqual(result, "0", "test_pr should return '0' here, matching the result of getPR") 177 178 result = self.client1.test_package(version, pkgarch) 179 self.assertTrue(result, "test_package should return 'True' for an existing PR") 180 181 result = self.client1.max_package_pr(version, pkgarch) 182 self.assertEqual(result, "0", "max_package_pr should return '0' in the current test series") 183 184 # Check that the same request gets the same value 185 186 result = self.client1.getPR(version, pkgarch, checksum0) 187 self.assertEqual(result, "0", "getPR: asking for the same PR a second time in a row should return the same value.") 188 189 # Add new configurations 190 191 result = self.client1.getPR(version, pkgarch, checksum1) 192 self.assertEqual(result, "1", "getPR: second PR of a package should be '1'") 193 194 result = self.client1.test_pr(version, pkgarch, checksum1) 195 self.assertEqual(result, "1", "test_pr should return '1' here, matching the result of getPR") 196 197 result = self.client1.max_package_pr(version, pkgarch) 198 self.assertEqual(result, "1", "max_package_pr should return '1' in the current test series") 199 200 result = self.client1.getPR(version, pkgarch, checksum2) 201 self.assertEqual(result, "2", "getPR: second PR of a package should be '2'") 202 203 result = self.client1.test_pr(version, pkgarch, checksum2) 204 self.assertEqual(result, "2", "test_pr should return '2' here, matching the result of getPR") 205 206 result = self.client1.max_package_pr(version, pkgarch) 207 self.assertEqual(result, "2", "max_package_pr should return '2' in the current test series") 208 209 result = self.client1.getPR(version, pkgarch, checksum3) 210 self.assertEqual(result, "3", "getPR: second PR of a package should be '3'") 211 212 result = self.client1.test_pr(version, pkgarch, checksum3) 213 self.assertEqual(result, "3", "test_pr should return '3' here, matching the result of getPR") 214 215 result = self.client1.max_package_pr(version, pkgarch) 216 self.assertEqual(result, "3", "max_package_pr should return '3' in the current test series") 217 218 # Ask again for the first configuration 219 220 result = self.client1.getPR(version, pkgarch, checksum0) 221 self.assertEqual(result, "4", "getPR: should return '4' in this configuration") 222 223 # Ask again with explicit "no history" mode 224 225 result = self.client1.getPR(version, pkgarch, checksum0, False) 226 self.assertEqual(result, "4", "getPR: should return '4' in this configuration") 227 228 # Ask again with explicit "history" mode. This should return the first recorded PR for checksum0 229 230 result = self.client1.getPR(version, pkgarch, checksum0, True) 231 self.assertEqual(result, "0", "getPR: should return '0' in this configuration") 232 233 # Check again that another pkgarg resets the counters 234 235 result = self.client1.test_pr(version, other_arch, checksum0) 236 self.assertIsNone(result, "test_pr should return 'None' for a non existing PR") 237 238 result = self.client1.test_package(version, other_arch) 239 self.assertFalse(result, "test_package should return 'False' for a non existing PR") 240 241 result = self.client1.max_package_pr(version, other_arch) 242 self.assertIsNone(result, "max_package_pr should return 'None' for a non existing PR") 243 244 # Now add the configuration 245 246 result = self.client1.getPR(version, other_arch, checksum0) 247 self.assertEqual(result, "0", "getPR: initial PR of a package should be '0'") 248 249 result = self.client1.test_pr(version, other_arch, checksum0) 250 self.assertEqual(result, "0", "test_pr should return '0' here, matching the result of getPR") 251 252 result = self.client1.test_package(version, other_arch) 253 self.assertTrue(result, "test_package should return 'True' for an existing PR") 254 255 result = self.client1.max_package_pr(version, other_arch) 256 self.assertEqual(result, "0", "max_package_pr should return '0' in the current test series") 257 258 result = self.client1.is_readonly() 259 self.assertFalse(result, "Server should not be described as 'read-only'") 260 261class PRUpstreamTests(PRTestSetup, unittest.TestCase): 262 263 def setUp(self): 264 265 self.temp_dir = tempfile.TemporaryDirectory(prefix='bb-prserv') 266 self.addCleanup(self.temp_dir.cleanup) 267 268 dbfile2 = os.path.join(self.temp_dir.name, "prtest-upstream2.sqlite3") 269 self.server2 = self.start_server("upstream2", dbfile2) 270 self.client2 = self.start_client(self.server2.address) 271 272 dbfile1 = os.path.join(self.temp_dir.name, "prtest-upstream1.sqlite3") 273 self.server1 = self.start_server("upstream1", dbfile1, upstream=self.server2.address) 274 self.client1 = self.start_client(self.server1.address) 275 276 dbfile0 = os.path.join(self.temp_dir.name, "prtest-local.sqlite3") 277 self.server0 = self.start_server("local", dbfile0, upstream=self.server1.address) 278 self.client0 = self.start_client(self.server0.address) 279 self.shared_db = dbfile0 280 281 def test_upstream_and_readonly(self): 282 283 # For identical checksums, all servers should return the same PR 284 285 result = self.client2.getPR(version, pkgarch, checksum0) 286 self.assertEqual(result, "0", "getPR: initial PR of a package should be '0'") 287 288 result = self.client1.getPR(version, pkgarch, checksum0) 289 self.assertEqual(result, "0", "getPR: initial PR of a package should be '0' (same as upstream)") 290 291 result = self.client0.getPR(version, pkgarch, checksum0) 292 self.assertEqual(result, "0", "getPR: initial PR of a package should be '0' (same as upstream)") 293 294 # Now introduce new checksums on server1 for, same version 295 296 result = self.client1.getPR(version, pkgarch, checksum1) 297 self.assertEqual(result, "0.0", "getPR: first PR of a package which has a different checksum upstream should be '0.0'") 298 299 result = self.client1.getPR(version, pkgarch, checksum2) 300 self.assertEqual(result, "0.1", "getPR: second PR of a package that has a different checksum upstream should be '0.1'") 301 302 # Now introduce checksums on server0 for, same version 303 304 result = self.client1.getPR(version, pkgarch, checksum1) 305 self.assertEqual(result, "0.2", "getPR: can't decrease for known PR") 306 307 result = self.client1.getPR(version, pkgarch, checksum2) 308 self.assertEqual(result, "0.3") 309 310 result = self.client1.max_package_pr(version, pkgarch) 311 self.assertEqual(result, "0.3") 312 313 result = self.client0.getPR(version, pkgarch, checksum3) 314 self.assertEqual(result, "0.3.0", "getPR: first PR of a package that doesn't exist upstream should be '0.3.0'") 315 316 result = self.client0.getPR(version, pkgarch, checksum4) 317 self.assertEqual(result, "0.3.1", "getPR: second PR of a package that doesn't exist upstream should be '0.3.1'") 318 319 result = self.client0.getPR(version, pkgarch, checksum3) 320 self.assertEqual(result, "0.3.2") 321 322 # More upstream updates 323 # Here, we assume no communication between server2 and server0. server2 only impacts server0 324 # after impacting server1 325 326 self.assertEqual(self.client2.getPR(version, pkgarch, checksum5), "1") 327 self.assertEqual(self.client1.getPR(version, pkgarch, checksum6), "1.0") 328 self.assertEqual(self.client1.getPR(version, pkgarch, checksum7), "1.1") 329 self.assertEqual(self.client0.getPR(version, pkgarch, checksum8), "1.1.0") 330 self.assertEqual(self.client0.getPR(version, pkgarch, checksum9), "1.1.1") 331 332 # "history" mode tests 333 334 self.assertEqual(self.client2.getPR(version, pkgarch, checksum0, True), "0") 335 self.assertEqual(self.client1.getPR(version, pkgarch, checksum2, True), "0.1") 336 self.assertEqual(self.client0.getPR(version, pkgarch, checksum3, True), "0.3.0") 337 338 # More "no history" mode tests 339 340 self.assertEqual(self.client2.getPR(version, pkgarch, checksum0), "2") 341 self.assertEqual(self.client1.getPR(version, pkgarch, checksum0), "2") # Same as upstream 342 self.assertEqual(self.client0.getPR(version, pkgarch, checksum0), "2") # Same as upstream 343 self.assertEqual(self.client1.getPR(version, pkgarch, checksum7), "3") # This could be surprising, but since the previous revision was "2", increasing it yields "3". 344 # We don't know how many upstream servers we have 345 # Start read-only server with server1 as upstream 346 self.server_ro = self.start_server("local-ro", self.shared_db, upstream=self.server1.address, read_only=True) 347 self.client_ro = self.start_client(self.server_ro.address) 348 349 self.assertTrue(self.client_ro.is_readonly(), "Database should be described as 'read-only'") 350 351 # Checks on non existing configurations 352 self.assertIsNone(self.client_ro.test_pr(version, pkgarch, checksumX)) 353 self.assertFalse(self.client_ro.test_package("unknown", pkgarch)) 354 355 # Look up existing configurations 356 self.assertEqual(self.client_ro.getPR(version, pkgarch, checksum0), "3") # "no history" mode 357 self.assertEqual(self.client_ro.getPR(version, pkgarch, checksum0, True), "0") # "history" mode 358 self.assertEqual(self.client_ro.getPR(version, pkgarch, checksum3), "3") 359 self.assertEqual(self.client_ro.getPR(version, pkgarch, checksum3, True), "0.3.0") 360 self.assertEqual(self.client_ro.max_package_pr(version, pkgarch), "2") # normal as "3" was never saved 361 362 # Try to insert a new value. Here this one is know upstream. 363 self.assertEqual(self.client_ro.getPR(version, pkgarch, checksum7), "3") 364 # Try to insert a completely new value. As the max upstream value is already "3", it should be "3.0" 365 self.assertEqual(self.client_ro.getPR(version, pkgarch, checksum10), "3.0") 366 # Same with another value which only exists in the upstream upstream server 367 # This time, as the upstream server doesn't know it, it will ask its upstream server. So that's a known one. 368 self.assertEqual(self.client_ro.getPR(version, pkgarch, checksum9), "3") 369 370class ScriptTests(unittest.TestCase): 371 372 def setUp(self): 373 374 self.temp_dir = tempfile.TemporaryDirectory(prefix='bb-prserv') 375 self.addCleanup(self.temp_dir.cleanup) 376 self.dbfile = os.path.join(self.temp_dir.name, "prtest.sqlite3") 377 378 def test_1_start_bitbake_prserv(self): 379 try: 380 subprocess.check_call([BIN_DIR / "bitbake-prserv", "--start", "-f", self.dbfile]) 381 except subprocess.CalledProcessError as e: 382 self.fail("Failed to start bitbake-prserv: %s" % e.returncode) 383 384 def test_2_stop_bitbake_prserv(self): 385 try: 386 subprocess.check_call([BIN_DIR / "bitbake-prserv", "--stop"]) 387 except subprocess.CalledProcessError as e: 388 self.fail("Failed to stop bitbake-prserv: %s" % e.returncode) 389