blob: d42970a785e42499a028a36926dc4b2cbd9b53d8 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 "paimon/testing/utils/blocking_file_system.h"
#include <chrono>
#include <future>
#include <memory>
#include <string>
#include <vector>
#include "paimon/common/utils/path_util.h"
#include "paimon/common/utils/scope_guard.h"
#include "paimon/testing/mock/mock_file_system.h"
#include "paimon/testing/utils/testharness.h"
namespace paimon::test {
TEST(BlockingFileSystemTest, TestOpenFiltersPathAndBlocksOnlyOnce) {
auto fs = std::make_shared<BlockingFileSystem>(std::make_shared<MockFileSystem>());
auto gate = fs->BlockNext(BlockingFileSystem::Operation::OPEN, "blocked");
std::future<Result<std::unique_ptr<InputStream>>> opening;
ScopeGuard finish([&]() {
gate->Release();
if (opening.valid()) {
opening.wait();
}
});
opening = std::async(std::launch::async, [&]() { return fs->Open(FileStatus("blocked", 0)); });
ASSERT_TRUE(gate->WaitForArrival());
ASSERT_EQ("blocked", gate->MatchedPath());
ASSERT_EQ(std::future_status::timeout, opening.wait_for(std::chrono::milliseconds(20)));
ASSERT_OK_AND_ASSIGN(std::unique_ptr<InputStream> other, fs->Open("other"));
ASSERT_OK(other->Close());
gate->Release();
ASSERT_OK_AND_ASSIGN(std::unique_ptr<InputStream> stream, opening.get());
ASSERT_OK(stream->Close());
ASSERT_OK_AND_ASSIGN(stream, fs->Open("blocked"));
ASSERT_OK(stream->Close());
}
TEST(BlockingFileSystemTest, TestReleaseInjectsOneShotErrorAndGateCanBeRearmed) {
auto fs = std::make_shared<BlockingFileSystem>(std::make_shared<MockFileSystem>());
auto first = fs->BlockNext(BlockingFileSystem::Operation::OPEN, "file");
first->Release(Status::IOError("first open failed"));
ASSERT_NOK_WITH_MSG(fs->Open("file"), "first open failed");
ASSERT_OK_AND_ASSIGN(std::unique_ptr<InputStream> stream, fs->Open("file"));
ASSERT_OK(stream->Close());
auto second = fs->BlockNext(BlockingFileSystem::Operation::OPEN, "file");
second->Release(Status::IOError("second open failed"));
ASSERT_NOK_WITH_MSG(fs->Open(FileStatus("file", 0)), "second open failed");
ASSERT_OK_AND_ASSIGN(stream, fs->Open("file"));
ASSERT_OK(stream->Close());
}
TEST(BlockingFileSystemTest, TestCreateAndCloseKeepControlsAlive) {
auto directory = UniqueTestDirectory::Create();
ASSERT_NE(nullptr, directory);
const std::string path = PathUtil::JoinPath(directory->Str(), "output");
auto fs = std::make_shared<BlockingFileSystem>(directory->GetFileSystem());
auto create_gate = fs->BlockNext(BlockingFileSystem::Operation::CREATE, /*path=*/"");
auto close_gate = fs->BlockNext(BlockingFileSystem::Operation::CLOSE, path);
std::unique_ptr<OutputStream> stream;
std::future<Result<std::unique_ptr<OutputStream>>> creating;
std::future<Status> closing;
ScopeGuard finish([&]() {
create_gate->Release();
close_gate->Release();
if (creating.valid()) {
creating.wait();
}
if (closing.valid()) {
closing.wait();
}
});
creating =
std::async(std::launch::async, [&]() { return fs->Create(path, /*overwrite=*/false); });
ASSERT_TRUE(create_gate->WaitForArrival());
ASSERT_EQ(path, create_gate->MatchedPath());
ASSERT_OK_AND_ASSIGN(bool exists, fs->Exists(path));
ASSERT_FALSE(exists);
create_gate->Release();
ASSERT_OK_AND_ASSIGN(stream, creating.get());
ASSERT_OK_AND_ASSIGN(exists, fs->Exists(path));
ASSERT_TRUE(exists);
ASSERT_OK_AND_ASSIGN(int64_t written, stream->Write("data", 4));
ASSERT_EQ(4, written);
ASSERT_OK(stream->Flush());
ASSERT_OK_AND_ASSIGN(int64_t position, stream->GetPos());
ASSERT_EQ(4, position);
closing = std::async(std::launch::async, [&]() { return stream->Close(); });
ASSERT_TRUE(close_gate->WaitForArrival());
fs.reset();
close_gate->Release(Status::IOError("injected close failure"));
ASSERT_NOK_WITH_MSG(closing.get(), "injected close failure");
std::string content;
ASSERT_OK(directory->GetFileSystem()->ReadFile(path, &content));
ASSERT_EQ("data", content);
}
TEST(BlockingFileSystemTest, TestBarrierRequiresConcurrentArrivals) {
auto fs = std::make_shared<BlockingFileSystem>(std::make_shared<MockFileSystem>());
ASSERT_NOK_WITH_MSG(fs->Barrier(BlockingFileSystem::Operation::OPEN, "", 0),
"requires positive arrivals");
ASSERT_OK_AND_ASSIGN(std::shared_ptr<BlockingFileSystem::Gate> barrier,
fs->Barrier(BlockingFileSystem::Operation::OPEN, "", 3));
std::vector<std::future<Result<std::unique_ptr<InputStream>>>> openings;
ScopeGuard finish([&]() {
barrier->Release();
for (auto& opening : openings) {
if (opening.valid()) {
opening.wait();
}
}
});
openings.push_back(std::async(std::launch::async, [&]() { return fs->Open("first"); }));
ASSERT_TRUE(barrier->WaitForArrival());
ASSERT_EQ(std::future_status::timeout, openings[0].wait_for(std::chrono::milliseconds(20)));
for (int32_t i = 0; i < 2; ++i) {
openings.push_back(std::async(std::launch::async, [&]() { return fs->Open("other"); }));
}
for (auto& opening : openings) {
ASSERT_EQ(std::future_status::ready, opening.wait_for(std::chrono::seconds(5)));
ASSERT_OK_AND_ASSIGN(std::unique_ptr<InputStream> stream, opening.get());
ASSERT_OK(stream->Close());
}
}
} // namespace paimon::test