xref: /openbmc/openbmc/poky/bitbake/lib/prserv/tests.py (revision 8460358c3d24c71d9d38fd126c745854a6301564)
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