@@ -1580,6 +1580,33 @@ def test_patch_rpaths_existing_msan(self):
15801580 rpaths = build_manager .get_rpaths (os .environ ['APP_PATH' ])
15811581 self .assertListEqual (['/msan/lib' , '/msan/usr/lib' ], rpaths )
15821582
1583+ def test_patch_rpaths_schema_v1 (self ):
1584+ """Tests that RPATH is not patched when schema version is > 0."""
1585+
1586+ def mock_unpack_build_schema_v1 (actual_self ,
1587+ base_build_dir ,
1588+ build_dir ,
1589+ url ,
1590+ http_build_url = None ):
1591+ self .mock_unpack_build ('rpath_new' , actual_self , base_build_dir ,
1592+ build_dir , url , http_build_url )
1593+ schema_version_path = os .path .join (build_dir ,
1594+ build_manager .SCHEMA_VERSION_FILE_NAME )
1595+ utils .write_data_to_file ('1' , schema_version_path )
1596+ return True
1597+
1598+ self .mock ._unpack_build .side_effect = mock_unpack_build_schema_v1
1599+
1600+ build = build_manager .RegularBuild (self .base_build_dir , 1337 ,
1601+ 'chained-origins' )
1602+ self .assertTrue (build .setup ())
1603+ self .assertEqual (
1604+ os .path .join (self .base_build_dir , 'revisions' , 'app' ),
1605+ os .environ ['APP_PATH' ])
1606+
1607+ rpaths = build_manager .get_rpaths (os .environ ['APP_PATH' ])
1608+ self .assertListEqual ([], rpaths )
1609+
15831610
15841611class SortBuildUrlsByRevisionTest (unittest .TestCase ):
15851612 """Test _sort_build_urls_by_revision."""
@@ -1969,3 +1996,184 @@ def test_is_discovery_non_fuzzing(self):
19691996 self .mock .is_engine_fuzzer_job .return_value = False
19701997 build = build_manager .Build ('/base' , 1 , build_prefix = '' , fuzz_target = None )
19711998 self .assertFalse (build .is_discovery )
1999+
2000+
2001+ class GetSchemaVersionTest (fake_filesystem_unittest .TestCase ):
2002+ """Tests _get_schema_version resolution."""
2003+
2004+ def setUp (self ):
2005+ fake_filesystem_unittest .TestCase .setUp (self )
2006+ self .setUpPyfakefs ()
2007+ self .base_build_dir = '/base_build_dir'
2008+ self .fs .create_dir (self .base_build_dir )
2009+
2010+ def test_regular_build (self ):
2011+ """Tests reading schema version for regular build."""
2012+ build = build_manager .RegularBuild (self .base_build_dir , 1337 ,
2013+ 'gs://dummy/url.zip' )
2014+ self .fs .create_file (
2015+ os .path .join (build .build_dir , '.schema_version' ), contents = '1' )
2016+ self .assertEqual (build ._get_schema_version (), 1 )
2017+
2018+ def test_symbolized_build (self ):
2019+ """Tests reading schema version for symbolized build."""
2020+ build = build_manager .SymbolizedBuild (self .base_build_dir , 1337 ,
2021+ 'gs://dummy/release.zip' ,
2022+ 'gs://dummy/debug.zip' )
2023+ self .fs .create_file (
2024+ os .path .join (build .build_dir , '.schema_version' ), contents = '2' )
2025+ self .assertEqual (build ._get_schema_version (), 2 )
2026+
2027+ def test_missing_file (self ):
2028+ """Tests that 0 is returned if file is missing."""
2029+ build = build_manager .RegularBuild (self .base_build_dir , 1337 ,
2030+ 'gs://dummy/url.zip' )
2031+ self .assertEqual (build ._get_schema_version (), 0 )
2032+
2033+ def test_get_schema_version_caching (self ):
2034+ """Tests that _get_schema_version caches results."""
2035+ build = build_manager .RegularBuild (self .base_build_dir , 1337 ,
2036+ 'gs://dummy/url.zip' )
2037+ self .fs .create_file (
2038+ os .path .join (build .build_dir , '.schema_version' ), contents = '1' )
2039+
2040+ with mock .patch (
2041+ 'clusterfuzz._internal.base.utils.read_data_from_file' ,
2042+ return_value = '1' ) as mock_read_call :
2043+ # First call should trigger read.
2044+ v1 = build ._get_schema_version ()
2045+
2046+ self .assertEqual (v1 , 1 )
2047+
2048+ # Second call should use cache.
2049+ v2 = build ._get_schema_version ()
2050+
2051+ self .assertEqual (v2 , 1 )
2052+ self .assertEqual (mock_read_call .call_count , 1 )
2053+
2054+ # Clear call history
2055+ mock_read_call .reset_mock ()
2056+
2057+ # Call again, should still be cached.
2058+ v3 = build ._get_schema_version ()
2059+
2060+ self .assertEqual (v3 , 1 )
2061+ self .assertEqual (mock_read_call .call_count , 0 )
2062+
2063+
2064+ class UnpackBuildSchemaVersionTest (fake_filesystem_unittest .TestCase ):
2065+ """Tests schema version handling during _unpack_build."""
2066+
2067+ def setUp (self ):
2068+ test_utils .set_up_pyfakefs (self )
2069+ test_helpers .patch (self , [
2070+ 'clusterfuzz._internal.build_management.build_manager._make_space' ,
2071+ 'clusterfuzz._internal.system.shell.clear_temp_directory' ,
2072+ 'clusterfuzz._internal.build_management.build_manager.Build._open_build_archive' ,
2073+ ])
2074+ self .mock ._make_space .return_value = True
2075+
2076+ test_helpers .patch_environ (self )
2077+ os .environ ['BUILDS_DIR' ] = '/builds'
2078+ os .environ ['FAIL_RETRIES' ] = '1'
2079+ os .environ ['APP_NAME' ] = FAKE_APP_NAME
2080+ os .environ ['JOB_NAME' ] = 'job'
2081+
2082+ self .base_build_dir = '/base_build_dir'
2083+ self .fs .create_dir (self .base_build_dir )
2084+
2085+ self .build = build_manager .RegularBuild (self .base_build_dir , 1337 ,
2086+ 'gs://dummy/url.zip' )
2087+ # build_dir for RegularBuild is base_build_dir/revisions
2088+ self .fs .create_dir (self .build .build_dir )
2089+
2090+ def mock_archive (self , schema_version ):
2091+ archive_mock = mock .MagicMock ()
2092+ archive_mock .unpacked_size .return_value = 100
2093+ archive_mock .archive_schema_version .return_value = schema_version
2094+ archive_mock .list_fuzz_targets .return_value = []
2095+ return archive_mock
2096+
2097+ def test_first_unpack_writes_version (self ):
2098+ """Tests that the first unpack writes the schema version."""
2099+ archive = self .mock_archive (1 )
2100+ self .mock ._open_build_archive .return_value .__enter__ .return_value = archive
2101+ schema_file = os .path .join (self .build .build_dir , '.schema_version' )
2102+
2103+ result = self .build ._unpack_build (self .base_build_dir , self .build .build_dir ,
2104+ 'gs://dummy/url.zip' )
2105+
2106+ self .assertTrue (result )
2107+ self .assertTrue (os .path .exists (schema_file ))
2108+ self .assertEqual (utils .read_data_from_file (schema_file ), 1 )
2109+ self .assertEqual (self .build ._schema_version , 1 )
2110+
2111+ def test_second_unpack_same_version (self ):
2112+ """Tests that subsequent unpack with same version does not overwrite and doesn't log error."""
2113+ schema_file = os .path .join (self .build .build_dir , '.schema_version' )
2114+ self .fs .create_file (schema_file , contents = '1' )
2115+ self .build ._schema_version = 1
2116+
2117+ archive = self .mock_archive (1 )
2118+ self .mock ._open_build_archive .return_value .__enter__ .return_value = archive
2119+
2120+ with mock .patch .object (self .build , '_write_schema_version' ) as mock_write :
2121+ with mock .patch (
2122+ 'clusterfuzz._internal.metrics.logs.error' ) as mock_log_error :
2123+ result = self .build ._unpack_build (
2124+ self .base_build_dir , self .build .build_dir , 'gs://dummy/url.zip' )
2125+
2126+ self .assertTrue (result )
2127+ mock_write .assert_not_called ()
2128+ mock_log_error .assert_not_called ()
2129+
2130+ def test_second_unpack_different_version_logs_error (self ):
2131+ """Tests that subsequent unpack with different version logs error and does not overwrite."""
2132+ schema_file = os .path .join (self .build .build_dir , '.schema_version' )
2133+ self .fs .create_file (schema_file , contents = '1' )
2134+ self .build ._schema_version = 1
2135+
2136+ archive = self .mock_archive (2 ) # Second archive has version 2
2137+ self .mock ._open_build_archive .return_value .__enter__ .return_value = archive
2138+
2139+ with mock .patch .object (self .build , '_write_schema_version' ) as mock_write :
2140+ with mock .patch (
2141+ 'clusterfuzz._internal.metrics.logs.error' ) as mock_log_error :
2142+ result = self .build ._unpack_build (
2143+ self .base_build_dir , self .build .build_dir , 'gs://dummy/url.zip' )
2144+
2145+ self .assertTrue (result )
2146+ mock_write .assert_not_called ()
2147+ mock_log_error .assert_called_once ()
2148+ error_msg = mock_log_error .call_args [0 ][0 ]
2149+ self .assertIn ('Schema version mismatch' , error_msg )
2150+ self .assertIn ('1' , error_msg ) # Existing
2151+ self .assertIn ('2' , error_msg ) # New
2152+
2153+ def test_unpack_reads_from_disk_if_not_in_memory (self ):
2154+ """Tests that unpack reads from disk if _schema_version is not in memory."""
2155+ build = build_manager .SymbolizedBuild (self .base_build_dir , 1337 ,
2156+ 'gs://dummy/release.zip' ,
2157+ 'gs://dummy/debug.zip' )
2158+ self .fs .create_dir (build .build_dir )
2159+ self .fs .create_dir (build .release_build_dir )
2160+ self .fs .create_dir (build .debug_build_dir )
2161+
2162+ schema_file = os .path .join (build .build_dir , '.schema_version' )
2163+ self .fs .create_file (schema_file , contents = '1' )
2164+ build ._schema_version = None # Not in memory
2165+
2166+ archive = self .mock_archive (1 )
2167+ self .mock ._open_build_archive .return_value .__enter__ .return_value = archive
2168+
2169+ with mock .patch .object (build , '_write_schema_version' ) as mock_write :
2170+ with mock .patch (
2171+ 'clusterfuzz._internal.metrics.logs.error' ) as mock_log_error :
2172+ # Unpack debug build (simulating it running after release build, but with clean memory)
2173+ result = build ._unpack_build (self .base_build_dir , build .debug_build_dir ,
2174+ 'gs://dummy/debug.zip' )
2175+
2176+ self .assertTrue (result )
2177+ mock_write .assert_not_called ()
2178+ self .assertEqual (build ._schema_version , 1 ) # Should be synced to memory
2179+ mock_log_error .assert_not_called ()
0 commit comments