@@ -1399,21 +1399,23 @@ class DefaultCacheImplOpenTest
13991399 public testing::WithParamInterface<OpenTestParameters> {};
14001400
14011401TEST_P (DefaultCacheImplOpenTest, ReadOnlyDir) {
1402- const auto setup_dir = [&](const olp::porting::optional<std::string>& cache_path) {
1403- if (cache_path) {
1404- if (olp::utils::Dir::Exists (*cache_path)) {
1405- ASSERT_TRUE (olp::utils::Dir::Remove (*cache_path));
1406- }
1407- ASSERT_TRUE (olp::utils::Dir::Create (*cache_path));
1408- ASSERT_TRUE (SetRights (*cache_path, true ));
1409- }
1410- };
1411-
1412- const auto reset_dir = [&](const olp::porting::optional<std::string>& cache_path) {
1413- if (cache_path) {
1414- ASSERT_TRUE (olp::utils::Dir::Remove (*cache_path));
1415- }
1416- };
1402+ const auto setup_dir =
1403+ [&](const olp::porting::optional<std::string>& cache_path) {
1404+ if (cache_path) {
1405+ if (olp::utils::Dir::Exists (*cache_path)) {
1406+ ASSERT_TRUE (olp::utils::Dir::Remove (*cache_path));
1407+ }
1408+ ASSERT_TRUE (olp::utils::Dir::Create (*cache_path));
1409+ ASSERT_TRUE (SetRights (*cache_path, true ));
1410+ }
1411+ };
1412+
1413+ const auto reset_dir =
1414+ [&](const olp::porting::optional<std::string>& cache_path) {
1415+ if (cache_path) {
1416+ ASSERT_TRUE (olp::utils::Dir::Remove (*cache_path));
1417+ }
1418+ };
14171419
14181420 const OpenTestParameters test_params = GetParam ();
14191421
@@ -1447,4 +1449,52 @@ std::vector<OpenTestParameters> DefaultCacheImplOpenParams() {
14471449INSTANTIATE_TEST_SUITE_P (, DefaultCacheImplOpenTest,
14481450 testing::ValuesIn (DefaultCacheImplOpenParams()));
14491451
1452+ TEST_F (DefaultCacheImplTest, ProtectedCacheIOErrorFallbackToReadOnly) {
1453+ SCOPED_TRACE (" IOError fallback to read-only for protected cache" );
1454+
1455+ // Create a writable directory with a subdirectory named "LOCK" to force
1456+ // LevelDB to fail when trying to create its lock file, triggering IOError.
1457+ // This exercises the fallback branch (lines 787-789) that retries opening
1458+ // in read-only mode after an IOError in write mode.
1459+ const std::string ioerror_path =
1460+ olp::utils::Dir::TempDirectory () + " /unittest_ioerror_fallback" ;
1461+
1462+ // Clean up any previous leftovers
1463+ if (olp::utils::Dir::Exists (ioerror_path)) {
1464+ helpers::MakeDirectoryAndContentReadonly (ioerror_path, false );
1465+ ASSERT_TRUE (olp::utils::Dir::Remove (ioerror_path));
1466+ }
1467+
1468+ ASSERT_TRUE (olp::utils::Dir::Create (ioerror_path));
1469+
1470+ // Create a subdirectory named LOCK to block LevelDB's lock file creation
1471+ ASSERT_TRUE (olp::utils::Dir::Create (ioerror_path + " /LOCK" ));
1472+
1473+ // Ensure the directory is writable so IsReadOnly() returns false,
1474+ // forcing the first open attempt in R/W mode
1475+ ASSERT_FALSE (olp::utils::Dir::IsReadOnly (ioerror_path));
1476+
1477+ cache::CacheSettings settings;
1478+ settings.disk_path_protected = ioerror_path;
1479+ DefaultCacheImplHelper cache (settings);
1480+
1481+ // Open should attempt R/W first, get IOError due to LOCK directory,
1482+ // then retry in read-only mode (lines 787-789).
1483+ auto open_result = cache.Open ();
1484+
1485+ // The fallback may or may not succeed depending on LevelDB behavior,
1486+ // but the important part is that the fallback code path executes.
1487+ // We accept Success or various failure results.
1488+ EXPECT_TRUE (
1489+ open_result == cache::DefaultCache::StorageOpenResult::Success ||
1490+ open_result ==
1491+ cache::DefaultCache::StorageOpenResult::ProtectedCacheCorrupted ||
1492+ open_result ==
1493+ cache::DefaultCache::StorageOpenResult::OpenDiskPathFailure);
1494+
1495+ // Cleanup
1496+ helpers::MakeDirectoryAndContentReadonly (ioerror_path, false );
1497+ ASSERT_TRUE (olp::utils::Dir::Remove (ioerror_path));
1498+ }
1499+
14501500} // namespace
0 commit comments