Skip to content

Fix Storage build #1762

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions cmake/external/leveldb.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,22 @@ if(TARGET leveldb)
return()
endif()

set(current_patch_file "") # Explicitly initialize for this scope
if (DESKTOP AND MSVC)
set(patch_file
${CMAKE_CURRENT_LIST_DIR}/../../scripts/git/patches/leveldb/0001-leveldb-1.23-windows-paths.patch)
set(current_patch_file
${CMAKE_CURRENT_LIST_DIR}/../../scripts/git/patches/leveldb/0001-leveldb-1.23-windows-paths.patch)
endif()

# This version must be kept in sync with the version in firestore.patch.txt.
# If this version ever changes then make sure to update the version in
# firestore.patch.txt accordingly.
set(version 1.23)

set(final_patch_command "")
if(current_patch_file)
set(final_patch_command git apply ${current_patch_file} && git gc --aggressive)
endif()

ExternalProject_Add(
leveldb

Expand All @@ -42,5 +48,5 @@ ExternalProject_Add(
INSTALL_COMMAND ""
TEST_COMMAND ""
HTTP_HEADER "${EXTERNAL_PROJECT_HTTP_HEADER}"
PATCH_COMMAND git apply ${patch_file} && git gc --aggressive
PATCH_COMMAND ${final_patch_command}
)
467 changes: 467 additions & 0 deletions scripts/print_workflow_run_errors.py

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions storage/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ set(common_SRCS
src/common/common.cc
src/common/controller.cc
src/common/listener.cc
src/common/list_result.cc
src/common/metadata.cc
src/common/storage.cc
src/common/storage_reference.cc
Expand All @@ -36,6 +37,7 @@ binary_to_array("storage_resources"
set(android_SRCS
${storage_resources_source}
src/android/controller_android.cc
src/android/list_result_android.cc
src/android/metadata_android.cc
src/android/storage_android.cc
src/android/storage_reference_android.cc)
Expand All @@ -44,6 +46,7 @@ set(android_SRCS
set(ios_SRCS
src/ios/controller_ios.mm
src/ios/listener_ios.mm
src/ios/list_result_ios.mm
src/ios/metadata_ios.mm
src/ios/storage_ios.mm
src/ios/storage_reference_ios.mm
Expand All @@ -54,6 +57,7 @@ set(desktop_SRCS
src/desktop/controller_desktop.cc
src/desktop/curl_requests.cc
src/desktop/listener_desktop.cc
src/desktop/list_result_desktop.cc
src/desktop/metadata_desktop.cc
src/desktop/rest_operation.cc
src/desktop/storage_desktop.cc
Expand Down
284 changes: 283 additions & 1 deletion storage/integration_test/src/integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include <cstring>
#include <ctime>
#include <thread> // NOLINT
#include <vector> // For std::vector in list tests

#include "app_framework.h" // NOLINT
#include "firebase/app.h"
Expand Down Expand Up @@ -80,6 +81,8 @@ using app_framework::PathForResource;
using app_framework::ProcessEvents;
using firebase_test_framework::FirebaseTest;
using testing::ElementsAreArray;
using testing::IsEmpty;
using testing::UnorderedElementsAreArray;

class FirebaseStorageTest : public FirebaseTest {
public:
Expand All @@ -96,7 +99,6 @@ class FirebaseStorageTest : public FirebaseTest {
// Called after each test.
void TearDown() override;

// File references that we need to delete on test exit.
protected:
// Initialize Firebase App and Firebase Auth.
static void InitializeAppAndAuth();
Expand All @@ -118,6 +120,17 @@ class FirebaseStorageTest : public FirebaseTest {
// Create a unique working folder and return a reference to it.
firebase::storage::StorageReference CreateFolder();

// Uploads a string as a file to the given StorageReference.
void UploadStringAsFile(firebase::storage::StorageReference& ref,
const std::string& content,
const char* content_type = nullptr);

// Verifies the contents of a ListResult.
void VerifyListResultContains(
const firebase::storage::ListResult& list_result,
const std::vector<std::string>& expected_item_names,
const std::vector<std::string>& expected_prefix_names);

static firebase::App* shared_app_;
static firebase::auth::Auth* shared_auth_;

Expand Down Expand Up @@ -212,6 +225,7 @@ void FirebaseStorageTest::TerminateAppAndAuth() {
void FirebaseStorageTest::SetUp() {
FirebaseTest::SetUp();
InitializeStorage();
// list_test_root_ removed from SetUp
}

void FirebaseStorageTest::TearDown() {
Expand Down Expand Up @@ -313,6 +327,65 @@ void FirebaseStorageTest::SignOut() {
EXPECT_FALSE(shared_auth_->current_user().is_valid());
}

void FirebaseStorageTest::UploadStringAsFile(
firebase::storage::StorageReference& ref, const std::string& content,
const char* content_type) {
LogDebug("Uploading string content to: gs://%s%s", ref.bucket().c_str(),
ref.full_path().c_str());
firebase::storage::Metadata metadata;
if (content_type) {
metadata.set_content_type(content_type);
}
firebase::Future<firebase::storage::Metadata> future =
RunWithRetry<firebase::storage::Metadata>([&]() {
return ref.PutBytes(content.c_str(), content.length(), metadata);
});
WaitForCompletion(future, "UploadStringAsFile");
ASSERT_EQ(future.error(), firebase::storage::kErrorNone)
<< "Failed to upload to " << ref.full_path() << ": "
<< future.error_message();
ASSERT_NE(future.result(), nullptr);
// On some platforms (iOS), size_bytes might not be immediately available or
// might be 0 if the upload was very fast and metadata propagation is slow.
// For small files, this is less critical than the content being there.
// For larger files in other tests, size_bytes is asserted.
// ASSERT_EQ(future.result()->size_bytes(), content.length());
cleanup_files_.push_back(ref);
}

void FirebaseStorageTest::VerifyListResultContains(
const firebase::storage::ListResult& list_result,
const std::vector<std::string>& expected_item_names,
const std::vector<std::string>& expected_prefix_names) {
ASSERT_TRUE(list_result.is_valid());

std::vector<std::string> actual_item_names;
for (const auto& item_ref : list_result.items()) {
actual_item_names.push_back(item_ref.name());
}
std::sort(actual_item_names.begin(), actual_item_names.end());
std::vector<std::string> sorted_expected_item_names = expected_item_names;
std::sort(sorted_expected_item_names.begin(),
sorted_expected_item_names.end());

EXPECT_THAT(actual_item_names,
::testing::ContainerEq(sorted_expected_item_names))
<< "Item names do not match expected.";

std::vector<std::string> actual_prefix_names;
for (const auto& prefix_ref : list_result.prefixes()) {
actual_prefix_names.push_back(prefix_ref.name());
}
std::sort(actual_prefix_names.begin(), actual_prefix_names.end());
std::vector<std::string> sorted_expected_prefix_names = expected_prefix_names;
std::sort(sorted_expected_prefix_names.begin(),
sorted_expected_prefix_names.end());

EXPECT_THAT(actual_prefix_names,
::testing::ContainerEq(sorted_expected_prefix_names))
<< "Prefix names do not match expected.";
}

firebase::storage::StorageReference FirebaseStorageTest::CreateFolder() {
// Generate a folder for the test data based on the time in milliseconds.
int64_t time_in_microseconds = GetCurrentTimeInMicroseconds();
Expand Down Expand Up @@ -1622,4 +1695,213 @@ TEST_F(FirebaseStorageTest, TestInvalidatingReferencesWhenDeletingApp) {
InitializeAppAndAuth();
}

TEST_F(FirebaseStorageTest, ListAllBasic) {
// SKIP_TEST_ON_ANDROID_EMULATOR; // Removed
SignIn();
firebase::storage::StorageReference test_root =
CreateFolder().Child("list_all_basic_root");
ASSERT_TRUE(test_root.is_valid())
<< "Test root for ListAllBasic is not valid.";

UploadStringAsFile(test_root.Child("file_a.txt"), "content_a");
UploadStringAsFile(test_root.Child("file_b.txt"), "content_b");
UploadStringAsFile(test_root.Child("prefix1/file_c.txt"),
"content_c_in_prefix1");
UploadStringAsFile(test_root.Child("prefix2/file_e.txt"),
"content_e_in_prefix2");

LogDebug("Calling ListAll() on gs://%s%s", test_root.bucket().c_str(),
test_root.full_path().c_str());
firebase::Future<firebase::storage::ListResult> future = test_root.ListAll();
WaitForCompletion(future, "ListAllBasic");

ASSERT_EQ(future.error(), firebase::storage::kErrorNone)
<< future.error_message();
ASSERT_NE(future.result(), nullptr);
const firebase::storage::ListResult* result = future.result();

VerifyListResultContains(*result, {"file_a.txt", "file_b.txt"},
{"prefix1/", "prefix2/"});
EXPECT_TRUE(result->page_token().empty())
<< "Page token should be empty for ListAll.";
}

TEST_F(FirebaseStorageTest, ListPaginated) {
// SKIP_TEST_ON_ANDROID_EMULATOR; // Removed
SignIn();
firebase::storage::StorageReference test_root =
CreateFolder().Child("list_paginated_root");
ASSERT_TRUE(test_root.is_valid())
<< "Test root for ListPaginated is not valid.";

// Expected total entries: file_aa.txt, file_bb.txt, file_ee.txt, prefix_x/,
// prefix_y/ (5 entries)
UploadStringAsFile(test_root.Child("file_aa.txt"), "content_aa");
UploadStringAsFile(test_root.Child("prefix_x/file_cc.txt"),
"content_cc_in_prefix_x");
UploadStringAsFile(test_root.Child("file_bb.txt"), "content_bb");
UploadStringAsFile(test_root.Child("prefix_y/file_dd.txt"),
"content_dd_in_prefix_y");
UploadStringAsFile(test_root.Child("file_ee.txt"), "content_ee");

std::vector<std::string> all_item_names_collected;
std::vector<std::string> all_prefix_names_collected;
std::string page_token = "";
const int page_size = 2;
int page_count = 0;
const int max_pages = 5; // Safety break for loop

LogDebug("Starting paginated List() on gs://%s%s with page_size %d",
test_root.bucket().c_str(), test_root.full_path().c_str(),
page_size);

do {
page_count++;
LogDebug("Fetching page %d, token: '%s'", page_count, page_token.c_str());
firebase::Future<firebase::storage::ListResult> future =
page_token.empty() ? test_root.List(page_size)
: test_root.List(page_size, page_token.c_str());
WaitForCompletion(future,
"ListPaginated - Page " + std::to_string(page_count));

ASSERT_EQ(future.error(), firebase::storage::kErrorNone)
<< future.error_message();
ASSERT_NE(future.result(), nullptr);
const firebase::storage::ListResult* result = future.result();
ASSERT_TRUE(result->is_valid());

LogDebug("Page %d items: %zu, prefixes: %zu", page_count,
result->items().size(), result->prefixes().size());
for (const auto& item : result->items()) {
all_item_names_collected.push_back(item.name());
LogDebug(" Item: %s", item.name().c_str());
}
for (const auto& prefix : result->prefixes()) {
all_prefix_names_collected.push_back(prefix.name());
LogDebug(" Prefix: %s", prefix.name().c_str());
}

page_token = result->page_token();

size_t entries_on_page = result->items().size() + result->prefixes().size();

if (!page_token.empty()) {
EXPECT_EQ(entries_on_page, page_size)
<< "A non-last page should have full page_size entries.";
} else {
// This is the last page
size_t total_entries = 5;
size_t expected_entries_on_last_page = total_entries % page_size;
if (expected_entries_on_last_page == 0 &&
total_entries > 0) { // if total is a multiple of page_size
expected_entries_on_last_page = page_size;
}
EXPECT_EQ(entries_on_page, expected_entries_on_last_page);
}
} while (!page_token.empty() && page_count < max_pages);

EXPECT_LT(page_count, max_pages)
<< "Exceeded max_pages, possible infinite loop.";
EXPECT_EQ(page_count, (5 + page_size - 1) / page_size)
<< "Unexpected number of pages.";

std::vector<std::string> expected_final_items = {"file_aa.txt", "file_bb.txt",
"file_ee.txt"};
std::vector<std::string> expected_final_prefixes = {"prefix_x/", "prefix_y/"};

// VerifyListResultContains needs a ListResult object. We can't directly use
// it with collected names. Instead, we sort and compare the collected names.
std::sort(all_item_names_collected.begin(), all_item_names_collected.end());
std::sort(all_prefix_names_collected.begin(),
all_prefix_names_collected.end());
std::sort(expected_final_items.begin(), expected_final_items.end());
std::sort(expected_final_prefixes.begin(), expected_final_prefixes.end());

EXPECT_THAT(all_item_names_collected,
::testing::ContainerEq(expected_final_items));
EXPECT_THAT(all_prefix_names_collected,
::testing::ContainerEq(expected_final_prefixes));
}

TEST_F(FirebaseStorageTest, ListEmpty) {
// SKIP_TEST_ON_ANDROID_EMULATOR; // No skip needed as it's a lightweight
// test.
SignIn();
firebase::storage::StorageReference test_root =
CreateFolder().Child("list_empty_root");
ASSERT_TRUE(test_root.is_valid()) << "Test root for ListEmpty is not valid.";

// Do not upload anything to test_root.

LogDebug("Calling ListAll() on empty folder: gs://%s%s",
test_root.bucket().c_str(), test_root.full_path().c_str());
firebase::Future<firebase::storage::ListResult> future = test_root.ListAll();
WaitForCompletion(future, "ListEmpty");

ASSERT_EQ(future.error(), firebase::storage::kErrorNone)
<< future.error_message();
ASSERT_NE(future.result(), nullptr);
const firebase::storage::ListResult* result = future.result();

VerifyListResultContains(*result, {}, {});
EXPECT_TRUE(result->page_token().empty());
}

TEST_F(FirebaseStorageTest, ListWithMaxResultsGreaterThanActual) {
// SKIP_TEST_ON_ANDROID_EMULATOR; // No skip needed.
SignIn();
firebase::storage::StorageReference test_root =
CreateFolder().Child("list_max_greater_root");
ASSERT_TRUE(test_root.is_valid())
<< "Test root for ListWithMaxResultsGreaterThanActual is not valid.";

UploadStringAsFile(test_root.Child("only_file.txt"), "content_only");
UploadStringAsFile(test_root.Child("only_prefix/another.txt"),
"content_another_in_prefix");

LogDebug("Calling List(10) on gs://%s%s", test_root.bucket().c_str(),
test_root.full_path().c_str());
firebase::Future<firebase::storage::ListResult> future =
test_root.List(10); // Max results (10) > actual (1 file + 1 prefix = 2)
WaitForCompletion(future, "ListWithMaxResultsGreaterThanActual");

ASSERT_EQ(future.error(), firebase::storage::kErrorNone)
<< future.error_message();
ASSERT_NE(future.result(), nullptr);
const firebase::storage::ListResult* result = future.result();

VerifyListResultContains(*result, {"only_file.txt"}, {"only_prefix/"});
EXPECT_TRUE(result->page_token().empty());
}

TEST_F(FirebaseStorageTest, ListNonExistentPath) {
// SKIP_TEST_ON_ANDROID_EMULATOR; // No skip needed.
SignIn();
firebase::storage::StorageReference test_root =
CreateFolder().Child("list_non_existent_parent_root");
ASSERT_TRUE(test_root.is_valid())
<< "Test root for ListNonExistentPath is not valid.";

firebase::storage::StorageReference non_existent_ref =
test_root.Child("this_folder_truly_does_not_exist");
// No cleanup needed as nothing is created.

LogDebug("Calling ListAll() on non-existent path: gs://%s%s",
non_existent_ref.bucket().c_str(),
non_existent_ref.full_path().c_str());
firebase::Future<firebase::storage::ListResult> future =
non_existent_ref.ListAll();
WaitForCompletion(future, "ListNonExistentPath");

// Listing a non-existent path should not be an error, it's just an empty
// list.
ASSERT_EQ(future.error(), firebase::storage::kErrorNone)
<< future.error_message();
ASSERT_NE(future.result(), nullptr);
const firebase::storage::ListResult* result = future.result();

VerifyListResultContains(*result, {}, {});
EXPECT_TRUE(result->page_token().empty());
}

} // namespace firebase_testapp_automated
Loading
Loading