blob: 9af869f56b28e6f9c7d7581668f93394548d13e2 [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 <gtest/gtest.h>
#include <pulsar/MessageId.h>
#include <pulsar/ProducerConfiguration.h>
#include <cstdint>
#include <string>
#include "lib/st/MessageIdImpl.h"
#include "lib/st/StProducerImpl.h"
namespace pulsar::st {
// Test-only access to StProducerImpl's private, broker-independent helpers. These read
// only config_ and the segment argument (never the classic client), so they can run with
// a null client and no connection.
struct StProducerTestAccess {
static pulsar::ProducerConfiguration segmentConfig(StProducerImpl& producer, const Segment& segment) {
return producer.buildSegmentConfiguration(segment);
}
static pulsar::ProducerConfiguration::ProducerAccessMode toClassic(ProducerAccessMode mode) {
return StProducerImpl::toClassicAccessMode(mode);
}
static bool segmentGone(pulsar::Result result, const std::string& message) {
return StProducerImpl::isSegmentGoneError(result, message);
}
};
namespace {
ProducerConfig baseConfig() {
ProducerConfig config;
config.topic = "topic://public/default/orders";
config.producerName = "orders-producer";
return config;
}
Segment normalSegment(std::uint64_t id) {
Segment segment;
segment.segmentId = id;
segment.segmentTopicName = "segment://public/default/orders/0000-ffff-" + std::to_string(id);
return segment;
}
Segment legacySegment(std::uint64_t id) {
Segment segment = normalSegment(id);
segment.legacyTopicName = "persistent://public/default/orders-partition-" + std::to_string(id);
return segment;
}
// Guards the ProducerConfiguration shallow-copy defect: pulsar::ProducerConfiguration's copy
// constructor shares its impl, so building a second segment's config by copy-then-mutate would
// clobber the first. Each segment must get an independent, freshly-built config.
TEST(StProducerTest, testPerSegmentConfigsAreIsolated) {
StProducerImpl producer(nullptr, baseConfig());
auto confA = StProducerTestAccess::segmentConfig(producer, normalSegment(3));
auto confB = StProducerTestAccess::segmentConfig(producer, legacySegment(7));
// If the configs shared state, building B would rename A too.
EXPECT_EQ(confA.getProducerName(), "orders-producer-seg-3");
EXPECT_EQ(confB.getProducerName(), "orders-producer-seg-7");
// The legacy managed-metadata marker belongs to the legacy segment only.
EXPECT_EQ(confB.getProperties().count("__pulsar.v5.managed"), 1u);
EXPECT_EQ(confA.getProperties().count("__pulsar.v5.managed"), 0u);
}
// The st and classic ProducerAccessMode enums do NOT share numeric values (st orders
// ExclusiveWithFencing before WaitForExclusive; classic is the reverse), so a cast would be
// wrong — the mapping must go by name.
TEST(StProducerTest, testAccessModeMappedByName) {
using Classic = pulsar::ProducerConfiguration;
EXPECT_EQ(StProducerTestAccess::toClassic(ProducerAccessMode::Shared), Classic::Shared);
EXPECT_EQ(StProducerTestAccess::toClassic(ProducerAccessMode::Exclusive), Classic::Exclusive);
EXPECT_EQ(StProducerTestAccess::toClassic(ProducerAccessMode::ExclusiveWithFencing),
Classic::ExclusiveWithFencing);
EXPECT_EQ(StProducerTestAccess::toClassic(ProducerAccessMode::WaitForExclusive),
Classic::WaitForExclusive);
}
TEST(StProducerTest, testSegmentGoneClassification) {
EXPECT_TRUE(StProducerTestAccess::segmentGone(ResultTopicTerminated, ""));
EXPECT_TRUE(StProducerTestAccess::segmentGone(ResultAlreadyClosed, ""));
// A not-ready layout is a transient routing failure, NOT a gone segment — treating it as
// gone would spin the retry loop instead of failing terminally.
EXPECT_FALSE(StProducerTestAccess::segmentGone(ResultServiceUnitNotReady, ""));
EXPECT_FALSE(StProducerTestAccess::segmentGone(ResultConnectError, ""));
}
TEST(StProducerTest, testMintedMessageIdCarriesSegmentId) {
MessageId id = MessageIdFactory::create(pulsar::MessageId::earliest(), 42);
ASSERT_TRUE(static_cast<bool>(id));
const auto& impl = MessageIdFactory::impl(id);
ASSERT_TRUE(impl);
EXPECT_EQ(impl->segmentId, 42);
EXPECT_NE(impl->segmentId, MessageIdImpl::kNoSegment);
}
} // namespace
} // namespace pulsar::st