diff --git a/base/cvd/cuttlefish/host/libs/web/BUILD.bazel b/base/cvd/cuttlefish/host/libs/web/BUILD.bazel index d438832cc13..66495e9879b 100644 --- a/base/cvd/cuttlefish/host/libs/web/BUILD.bazel +++ b/base/cvd/cuttlefish/host/libs/web/BUILD.bazel @@ -98,6 +98,19 @@ cf_cc_test( ], ) +cf_cc_test( + name = "build_api_test", + srcs = ["build_api_test.cpp"], + deps = [ + "//cuttlefish/host/libs/web:android_build", + "//cuttlefish/host/libs/web:android_build_string", + "//cuttlefish/host/libs/web:build_api", + "//cuttlefish/host/libs/zip/libzip_cc:seekable_source", + "//cuttlefish/result", + "//cuttlefish/result:result_matchers", + ], +) + cf_cc_library( name = "android_build_url", srcs = ["android_build_url.cpp"], @@ -112,6 +125,7 @@ cf_cc_library( cf_cc_library( name = "build_api", + srcs = ["build_api.cpp"], hdrs = ["build_api.h"], deps = [ "//cuttlefish/host/libs/web:android_build", diff --git a/base/cvd/cuttlefish/host/libs/web/android_build_api.cpp b/base/cvd/cuttlefish/host/libs/web/android_build_api.cpp index abfdf575d16..15043d53075 100644 --- a/base/cvd/cuttlefish/host/libs/web/android_build_api.cpp +++ b/base/cvd/cuttlefish/host/libs/web/android_build_api.cpp @@ -148,17 +148,6 @@ Result AndroidBuildApi::DownloadFile( return DownloadTargetFile(build, target_directory, artifact_name); } -Result AndroidBuildApi::DownloadFileWithBackup( - const Build& build, const std::string& target_directory, - const std::string& artifact_name, const std::string& backup_artifact_name) { - std::unordered_set artifacts = - CF_EXPECT(Artifacts(build, {artifact_name, backup_artifact_name})); - std::string selected_artifact = artifact_name; - if (!Contains(artifacts, artifact_name)) { - selected_artifact = backup_artifact_name; - } - return DownloadTargetFile(build, target_directory, selected_artifact); -} Result AndroidBuildApi::FileReader( const Build& build, const std::string& artifact_name) { diff --git a/base/cvd/cuttlefish/host/libs/web/android_build_api.h b/base/cvd/cuttlefish/host/libs/web/android_build_api.h index 4944065117a..aabcfaf9fc1 100644 --- a/base/cvd/cuttlefish/host/libs/web/android_build_api.h +++ b/base/cvd/cuttlefish/host/libs/web/android_build_api.h @@ -52,10 +52,6 @@ class AndroidBuildApi : public BuildApi { const std::string& target_directory, const std::string& artifact_name) override; - Result DownloadFileWithBackup( - const Build& build, const std::string& target_directory, - const std::string& artifact_name, - const std::string& backup_artifact_name) override; Result FileReader( const Build&, const std::string& artifact_name) override; diff --git a/base/cvd/cuttlefish/host/libs/web/build_api.cpp b/base/cvd/cuttlefish/host/libs/web/build_api.cpp new file mode 100644 index 00000000000..f3402d1433e --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/web/build_api.cpp @@ -0,0 +1,38 @@ +// +// Copyright (C) 2026 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cuttlefish/host/libs/web/build_api.h" + +#include + +#include "cuttlefish/host/libs/web/android_build.h" +#include "cuttlefish/result/result.h" + +namespace cuttlefish { + +Result DownloadFileWithBackup( + BuildApi& build_api, const Build& build, + const std::string& target_directory, const std::string& artifact_name, + const std::string& backup_artifact_name) { + Result result = + build_api.DownloadFile(build, target_directory, artifact_name); + if (result.ok()) { + return result; + } + return CF_EXPECT( + build_api.DownloadFile(build, target_directory, backup_artifact_name)); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/libs/web/build_api.h b/base/cvd/cuttlefish/host/libs/web/build_api.h index 32c7735435f..82b6cfeb81d 100644 --- a/base/cvd/cuttlefish/host/libs/web/build_api.h +++ b/base/cvd/cuttlefish/host/libs/web/build_api.h @@ -33,13 +33,13 @@ class BuildApi { const Build& build, const std::string& target_directory, const std::string& artifact_name) = 0; - virtual Result DownloadFileWithBackup( - const Build& build, const std::string& target_directory, - const std::string& artifact_name, - const std::string& backup_artifact_name) = 0; - virtual Result FileReader( const Build&, const std::string& artifact_name) = 0; }; +Result DownloadFileWithBackup( + BuildApi& build_api, const Build& build, + const std::string& target_directory, const std::string& artifact_name, + const std::string& backup_artifact_name); + } // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/libs/web/build_api_test.cpp b/base/cvd/cuttlefish/host/libs/web/build_api_test.cpp new file mode 100644 index 00000000000..52bc9c8d1f8 --- /dev/null +++ b/base/cvd/cuttlefish/host/libs/web/build_api_test.cpp @@ -0,0 +1,102 @@ +// +// Copyright (C) 2026 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "cuttlefish/host/libs/web/build_api.h" + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "cuttlefish/host/libs/web/android_build.h" +#include "cuttlefish/host/libs/web/android_build_string.h" +#include "cuttlefish/host/libs/zip/libzip_cc/seekable_source.h" +#include "cuttlefish/result/result.h" +#include "cuttlefish/result/result_matchers.h" + +namespace cuttlefish { +namespace { + +using ::testing::_; +using ::testing::Return; + +class MockBuildApi : public BuildApi { + public: + MOCK_METHOD(Result, GetBuild, (const BuildString&), (override)); + MOCK_METHOD(Result, DownloadFile, + (const Build&, const std::string&, const std::string&), + (override)); + MOCK_METHOD(Result, FileReader, + (const Build&, const std::string&), (override)); +}; + +TEST(BuildApiTest, DownloadFileWithBackupSuccessFirst) { + MockBuildApi mock_api; + Build build = DeviceBuild{.id = "123", .target = "test"}; + std::string target_dir = "/tmp"; + std::string artifact_name = "primary.zip"; + std::string backup_artifact_name = "backup.zip"; + std::string expected_path = "/tmp/primary.zip"; + + EXPECT_CALL(mock_api, DownloadFile(_, target_dir, artifact_name)) + .WillOnce(Return(expected_path)); + EXPECT_CALL(mock_api, DownloadFile(_, target_dir, backup_artifact_name)) + .Times(0); + + Result result = DownloadFileWithBackup( + mock_api, build, target_dir, artifact_name, backup_artifact_name); + + ASSERT_THAT(result, IsOkAndValue(expected_path)); +} + +TEST(BuildApiTest, DownloadFileWithBackupFallback) { + MockBuildApi mock_api; + Build build = DeviceBuild{.id = "123", .target = "test"}; + std::string target_dir = "/tmp"; + std::string artifact_name = "primary.zip"; + std::string backup_artifact_name = "backup.zip"; + std::string expected_path = "/tmp/backup.zip"; + + EXPECT_CALL(mock_api, DownloadFile(_, target_dir, artifact_name)) + .WillOnce(Return(CF_ERR("File not found"))); + EXPECT_CALL(mock_api, DownloadFile(_, target_dir, backup_artifact_name)) + .WillOnce(Return(expected_path)); + + Result result = DownloadFileWithBackup( + mock_api, build, target_dir, artifact_name, backup_artifact_name); + + ASSERT_THAT(result, IsOkAndValue(expected_path)); +} + +TEST(BuildApiTest, DownloadFileWithBackupBothFail) { + MockBuildApi mock_api; + Build build = DeviceBuild{.id = "123", .target = "test"}; + std::string target_dir = "/tmp"; + std::string artifact_name = "primary.zip"; + std::string backup_artifact_name = "backup.zip"; + + EXPECT_CALL(mock_api, DownloadFile(_, target_dir, artifact_name)) + .WillOnce(Return(CF_ERR("Primary file not found"))); + EXPECT_CALL(mock_api, DownloadFile(_, target_dir, backup_artifact_name)) + .WillOnce(Return(CF_ERR("Backup file not found"))); + + Result result = DownloadFileWithBackup( + mock_api, build, target_dir, artifact_name, backup_artifact_name); + + ASSERT_THAT(result, IsError()); +} + +} // namespace +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/libs/web/caching_build_api.cpp b/base/cvd/cuttlefish/host/libs/web/caching_build_api.cpp index f84e98ebc66..215cc062f34 100644 --- a/base/cvd/cuttlefish/host/libs/web/caching_build_api.cpp +++ b/base/cvd/cuttlefish/host/libs/web/caching_build_api.cpp @@ -22,7 +22,6 @@ #include #include #include "absl/log/log.h" -#include "absl/strings/match.h" #include "cuttlefish/common/libs/utils/files.h" #include "cuttlefish/host/libs/web/android_build.h" @@ -119,30 +118,6 @@ Result CachingBuildApi::DownloadFile( kOverwriteExistingFile)); } -Result CachingBuildApi::DownloadFileWithBackup( - const Build& build, const std::string& target_directory, - const std::string& artifact_name, const std::string& backup_artifact_name) { - const auto paths = - CF_EXPECT(ConstructCachePaths(cache_base_path_, build, target_directory, - artifact_name, backup_artifact_name)); - if (IsInCache(paths.cache_artifact)) { - return CF_EXPECT(CreateHardLink(paths.cache_artifact, paths.target_artifact, - kOverwriteExistingFile)); - } - if (IsInCache(paths.cache_backup_artifact)) { - return CF_EXPECT(CreateHardLink(paths.cache_backup_artifact, - paths.target_backup_artifact, - kOverwriteExistingFile)); - } - const auto artifact_filepath = CF_EXPECT(build_api_.DownloadFileWithBackup( - build, paths.build_cache, artifact_name, backup_artifact_name)); - if (absl::EndsWith(artifact_filepath, artifact_name)) { - return CF_EXPECT(CreateHardLink(paths.cache_artifact, paths.target_artifact, - kOverwriteExistingFile)); - } - return CF_EXPECT(CreateHardLink(paths.cache_backup_artifact, - paths.target_backup_artifact)); -} Result CachingBuildApi::FileReader( const Build& build, const std::string& artifact) { diff --git a/base/cvd/cuttlefish/host/libs/web/caching_build_api.h b/base/cvd/cuttlefish/host/libs/web/caching_build_api.h index a9c3a01e23d..ff39eae35c7 100644 --- a/base/cvd/cuttlefish/host/libs/web/caching_build_api.h +++ b/base/cvd/cuttlefish/host/libs/web/caching_build_api.h @@ -35,10 +35,6 @@ class CachingBuildApi : public BuildApi { Result DownloadFile(const Build& build, const std::string& target_directory, const std::string& artifact_name) override; - Result DownloadFileWithBackup( - const Build& build, const std::string& target_directory, - const std::string& artifact_name, - const std::string& backup_artifact_name) override; Result FileReader(const Build&, const std::string& artifact) override;