// Copyright (c) 2023 Contributors to the Eclipse Foundation
//
// See the NOTICE file(s) distributed with this work for additional
// information regarding copyright ownership.
//
// This program and the accompanying materials are made available under the
// terms of the Apache Software License 2.0 which is available at
// https://www.apache.org/licenses/LICENSE-2.0, or the MIT license
// which is available at https://opensource.org/licenses/MIT.
//
// SPDX-License-Identifier: Apache-2.0 OR MIT

use iceoryx2_bb_testing_macros::conformance_tests;

#[allow(clippy::module_inception)]
#[conformance_tests]
pub mod publisher {
    use alloc::collections::BTreeSet;
    use alloc::{format, vec};
    use core::time::Duration;
    use iceoryx2::port::update_connections::UpdateConnections;
    use iceoryx2::port::{LoanError, publisher::PublisherCreateError};
    use iceoryx2::prelude::*;
    use iceoryx2::service::Service;
    use iceoryx2::service::builder::{CustomHeaderMarker, CustomPayloadMarker};
    use iceoryx2::service::static_config::message_type_details::{TypeDetail, TypeVariant};
    use iceoryx2::testing;
    use iceoryx2::testing::generate_service_name;
    use iceoryx2_bb_concurrency::atomic::{AtomicBool, Ordering};
    use iceoryx2_bb_posix::barrier::*;
    use iceoryx2_bb_posix::clock::{Time, nanosleep};
    use iceoryx2_bb_posix::mutex::{MutexBuilder, MutexHandle};
    use iceoryx2_bb_posix::thread::thread_scope;
    use iceoryx2_bb_testing::assert_that;
    use iceoryx2_bb_testing::lifetime_tracker::LifetimeTracker;
    use iceoryx2_bb_testing::watchdog::Watchdog;
    use iceoryx2_bb_testing_macros::conformance_test;

    const TIMEOUT: Duration = Duration::from_millis(25);

    #[conformance_test]
    pub fn publisher_loan_and_send_sample_works<Sut: Service>()
    -> core::result::Result<(), alloc::boxed::Box<dyn core::error::Error>> {
        let service_name = generate_service_name();
        let config = testing::generate_isolated_config();
        let node = NodeBuilder::new().config(&config).create::<Sut>().unwrap();
        let service = node
            .service_builder(&service_name)
            .publish_subscribe::<u64>()
            .create()?;

        let sut = service.publisher_builder().max_loaned_samples(2).create()?;

        let sample = sut.loan()?;

        assert_that!(sample.send(), is_ok);

        Ok(())
    }

    #[conformance_test]
    pub fn loan_initializes_sample_with_default<Sut: Service>()
    -> core::result::Result<(), alloc::boxed::Box<dyn core::error::Error>> {
        let service_name = generate_service_name();
        let config = testing::generate_isolated_config();
        let node = NodeBuilder::new().config(&config).create::<Sut>().unwrap();
        let service = node
            .service_builder(&service_name)
            .publish_subscribe::<LifetimeTracker>()
            .create()?;

        let publisher = service.publisher_builder().create()?;

        let tracker = LifetimeTracker::start_tracking();
        let _sut = publisher.loan()?;
        assert_that!(tracker.number_of_living_instances(), eq 1);

        Ok(())
    }

    #[conformance_test]
    pub fn override_preallocated_samples_to_one_works<Sut: Service>()
    -> core::result::Result<(), alloc::boxed::Box<dyn core::error::Error>> {
        let service_name = generate_service_name();
        let config = testing::generate_isolated_config();
        let node = NodeBuilder::new().config(&config).create::<Sut>().unwrap();
        let service = node
            .service_builder(&service_name)
            .publish_subscribe::<u64>()
            .create()?;

        let sut = service
            .publisher_builder()
            .override_sample_preallocation(|_| 1)
            .max_loaned_samples(2)
            .create()?;

        let _sample = sut.loan()?;
        assert_that!(sut.loan().err(), eq Some(LoanError::OutOfMemory));

        Ok(())
    }

    #[conformance_test]
    pub fn override_preallocated_samples_to_zero_rounds_it_up_to_one<Sut: Service>()
    -> core::result::Result<(), alloc::boxed::Box<dyn core::error::Error>> {
        let service_name = generate_service_name();
        let config = testing::generate_isolated_config();
        let node = NodeBuilder::new().config(&config).create::<Sut>().unwrap();
        let service = node
            .service_builder(&service_name)
            .publish_subscribe::<u64>()
            .create()?;

        let sut = service
            .publisher_builder()
            .override_sample_preallocation(|_| 0)
            .max_loaned_samples(2)
            .create()?;

        let _sample = sut.loan()?;
        assert_that!(sut.loan().err(), eq Some(LoanError::OutOfMemory));

        Ok(())
    }

    #[conformance_test]
    pub fn override_preallocated_samples_to_many_works<Sut: Service>()
    -> core::result::Result<(), alloc::boxed::Box<dyn core::error::Error>> {
        const MAX_NUMBER_OF_SAMPLES: usize = 10;
        let service_name = generate_service_name();
        let config = testing::generate_isolated_config();
        let node = NodeBuilder::new().config(&config).create::<Sut>().unwrap();

        for n in 1..MAX_NUMBER_OF_SAMPLES {
            let service = node
                .service_builder(&service_name)
                .publish_subscribe::<u64>()
                .create()?;

            let number_of_samples = n;
            let sut = service
                .publisher_builder()
                .override_sample_preallocation(move |_| number_of_samples)
                .max_loaned_samples(n + 1)
                .create()?;

            let mut samples = vec![];
            for _ in 0..n {
                samples.push(sut.loan()?)
            }
            assert_that!(sut.loan().err(), eq Some(LoanError::OutOfMemory));
        }

        Ok(())
    }

    #[conformance_test]
    pub fn drop_is_not_called_for_underlying_type_of_sample<Sut: Service>()
    -> core::result::Result<(), alloc::boxed::Box<dyn core::error::Error>> {
        let service_name = generate_service_name();
        let config = testing::generate_isolated_config();
        let node = NodeBuilder::new().config(&config).create::<Sut>().unwrap();
        let service = node
            .service_builder(&service_name)
            .publish_subscribe::<LifetimeTracker>()
            .create()?;

        let publisher = service.publisher_builder().create()?;

        let tracker = LifetimeTracker::start_tracking();
        let sut = publisher.loan()?;
        assert_that!(tracker.number_of_living_instances(), eq 1);
        drop(sut);
        assert_that!(tracker.number_of_living_instances(), eq 1);

        Ok(())
    }

    #[conformance_test]
    pub fn loan_uninit_does_not_initialize_sample<Sut: Service>()
    -> core::result::Result<(), alloc::boxed::Box<dyn core::error::Error>> {
        let service_name = generate_service_name();
        let config = testing::generate_isolated_config();
        let node = NodeBuilder::new().config(&config).create::<Sut>().unwrap();
        let service = node
            .service_builder(&service_name)
            .publish_subscribe::<LifetimeTracker>()
            .create()?;

        let publisher = service.publisher_builder().create()?;

        let tracker = LifetimeTracker::start_tracking();
        let _sut = publisher.loan_uninit()?;
        assert_that!(tracker.number_of_living_instances(), eq 0);

        Ok(())
    }

    #[conformance_test]
    pub fn publisher_loan_slice_initializes_sample_with_default<Sut: Service>()
    -> core::result::Result<(), alloc::boxed::Box<dyn core::error::Error>> {
        const NUMBER_OF_ELEMENTS: usize = 120;
        let service_name = generate_service_name();
        let config = testing::generate_isolated_config();
        let node = NodeBuilder::new().config(&config).create::<Sut>().unwrap();
        let service = node
            .service_builder(&service_name)
            .publish_subscribe::<[LifetimeTracker]>()
            .create()?;

        let publisher = service
            .publisher_builder()
            .initial_max_slice_len(NUMBER_OF_ELEMENTS)
            .create()?;

        let tracker = LifetimeTracker::start_tracking();
        let _sut = publisher.loan_slice(NUMBER_OF_ELEMENTS)?;
        assert_that!(tracker.number_of_living_instances(), eq NUMBER_OF_ELEMENTS);

        Ok(())
    }

    #[conformance_test]
    pub fn slice_sample_does_not_call_drop_for_underlying_value<Sut: Service>()
    -> core::result::Result<(), alloc::boxed::Box<dyn core::error::Error>> {
        const NUMBER_OF_ELEMENTS: usize = 120;
        let service_name = generate_service_name();
        let config = testing::generate_isolated_config();
        let node = NodeBuilder::new().config(&config).create::<Sut>().unwrap();
        let service = node
            .service_builder(&service_name)
            .publish_subscribe::<[LifetimeTracker]>()
            .create()?;

        let publisher = service
            .publisher_builder()
            .initial_max_slice_len(NUMBER_OF_ELEMENTS)
            .create()?;

        let tracker = LifetimeTracker::start_tracking();
        let sut = publisher.loan_slice(NUMBER_OF_ELEMENTS)?;
        assert_that!(tracker.number_of_living_instances(), eq NUMBER_OF_ELEMENTS);
        drop(sut);
        assert_that!(tracker.number_of_living_instances(), eq NUMBER_OF_ELEMENTS);

        Ok(())
    }

    #[conformance_test]
    pub fn publisher_loan_slice_up_to_max_elements_works<Sut: Service>()
    -> core::result::Result<(), alloc::boxed::Box<dyn core::error::Error>> {
        const NUMBER_OF_ELEMENTS: usize = 125;
        let service_name = generate_service_name();
        let config = testing::generate_isolated_config();
        let node = NodeBuilder::new().config(&config).create::<Sut>().unwrap();
        let service = node
            .service_builder(&service_name)
            .publish_subscribe::<[u64]>()
            .create()?;

        let publisher = service
            .publisher_builder()
            .initial_max_slice_len(NUMBER_OF_ELEMENTS)
            .create()?;

        for i in 0..NUMBER_OF_ELEMENTS {
            let sut = publisher.loan_slice(i)?;
            assert_that!(sut.payload().len(), eq i);
        }

        Ok(())
    }

    #[conformance_test]
    pub fn publisher_loan_slice_more_than_max_elements_fails<Sut: Service>()
    -> core::result::Result<(), alloc::boxed::Box<dyn core::error::Error>> {
        const NUMBER_OF_ELEMENTS: usize = 125;
        let service_name = generate_service_name();
        let config = testing::generate_isolated_config();
        let node = NodeBuilder::new().config(&config).create::<Sut>().unwrap();
        let service = node
            .service_builder(&service_name)
            .publish_subscribe::<[u64]>()
            .create()?;

        let publisher = service
            .publisher_builder()
            .initial_max_slice_len(NUMBER_OF_ELEMENTS)
            .create()?;

        let sut = publisher.loan_slice(NUMBER_OF_ELEMENTS + 1);
        assert_that!(sut, is_err);
        assert_that!(sut.err().unwrap(), eq LoanError::ExceedsMaxLoanSize);

        Ok(())
    }

    #[conformance_test]
    pub fn publisher_loan_unit_and_send_sample_works<Sut: Service>()
    -> core::result::Result<(), alloc::boxed::Box<dyn core::error::Error>> {
        let service_name = generate_service_name();
        let config = testing::generate_isolated_config();
        let node = NodeBuilder::new().config(&config).create::<Sut>().unwrap();
        let service = node
            .service_builder(&service_name)
            .publish_subscribe::<u64>()
            .create()?;

        let sut = service.publisher_builder().max_loaned_samples(2).create()?;

        let sample = sut.loan_uninit()?.write_payload(42);

        assert_that!(sample.send(), is_ok);

        Ok(())
    }

    #[conformance_test]
    pub fn publisher_can_borrow_multiple_sample_at_once<Sut: Service>()
    -> core::result::Result<(), alloc::boxed::Box<dyn core::error::Error>> {
        let service_name = generate_service_name();
        let config = testing::generate_isolated_config();
        let node = NodeBuilder::new().config(&config).create::<Sut>().unwrap();
        let service = node
            .service_builder(&service_name)
            .publish_subscribe::<u64>()
            .create()?;

        let sut = service.publisher_builder().max_loaned_samples(4).create()?;

        let sample1 = sut.loan_uninit()?.write_payload(1);
        let sample2 = sut.loan_uninit()?.write_payload(2);
        let sample3 = sut.loan_uninit()?.write_payload(3);

        let subscriber = service.subscriber_builder().create()?;

        assert_that!(sut.send_copy(4), is_ok);
        assert_that!(sample3.send(), is_ok);
        drop(sample2);
        drop(sample1);

        let r = subscriber.receive()?;
        assert_that!(r, is_some);
        assert_that!( *r.unwrap(), eq 4);
        let r = subscriber.receive()?;
        assert_that!(r, is_some);
        assert_that!( *r.unwrap(), eq 3);

        Ok(())
    }

    #[conformance_test]
    pub fn publisher_max_loaned_samples_works<Sut: Service>()
    -> core::result::Result<(), alloc::boxed::Box<dyn core::error::Error>> {
        let service_name = generate_service_name();
        let config = testing::generate_isolated_config();
        let node = NodeBuilder::new().config(&config).create::<Sut>().unwrap();
        let service = node
            .service_builder(&service_name)
            .publish_subscribe::<u64>()
            .create()?;

        let sut = service.publisher_builder().max_loaned_samples(2).create()?;

        let _sample1 = sut.loan_uninit()?;
        let _sample2 = sut.loan_uninit()?;

        let sample3 = sut.loan_uninit();
        assert_that!(sample3, is_err);
        assert_that!(sample3.err().unwrap(), eq LoanError::ExceedsMaxLoans);

        Ok(())
    }

    #[conformance_test]
    pub fn publisher_sending_sample_reduces_loan_counter<Sut: Service>()
    -> core::result::Result<(), alloc::boxed::Box<dyn core::error::Error>> {
        let service_name = generate_service_name();
        let config = testing::generate_isolated_config();
        let node = NodeBuilder::new().config(&config).create::<Sut>().unwrap();
        let service = node
            .service_builder(&service_name)
            .publish_subscribe::<u64>()
            .create()?;

        let sut = service.publisher_builder().max_loaned_samples(2).create()?;

        let _sample1 = sut.loan_uninit()?;
        let sample2 = sut.loan_uninit()?.write_payload(2);

        assert_that!(sample2.send(), is_ok);

        let _sample3 = sut.loan_uninit();
        let sample4 = sut.loan_uninit();
        assert_that!(sample4, is_err);
        assert_that!(sample4.err().unwrap(), eq LoanError::ExceedsMaxLoans);

        Ok(())
    }

    #[conformance_test]
    pub fn publisher_dropping_sample_reduces_loan_counter<Sut: Service>()
    -> core::result::Result<(), alloc::boxed::Box<dyn core::error::Error>> {
        let service_name = generate_service_name();
        let config = testing::generate_isolated_config();
        let node = NodeBuilder::new().config(&config).create::<Sut>().unwrap();
        let service = node
            .service_builder(&service_name)
            .publish_subscribe::<u64>()
            .create()?;

        let sut = service.publisher_builder().max_loaned_samples(2).create()?;

        let _sample1 = sut.loan_uninit()?;
        let sample2 = sut.loan_uninit()?;

        drop(sample2);

        let _sample3 = sut.loan_uninit();
        let sample4 = sut.loan_uninit();
        assert_that!(sample4, is_err);
        assert_that!(sample4.err().unwrap(), eq LoanError::ExceedsMaxLoans);

        Ok(())
    }

    #[derive(Debug, ZeroCopySend, Eq, PartialEq)]
    #[repr(C)]
    struct CustomUserHeader<const A: u32, const B: u64> {
        data_a: u32,
        data_b: u64,
    }

    impl<const A: u32, const B: u64> Default for CustomUserHeader<A, B> {
        fn default() -> Self {
            Self {
                data_a: A,
                data_b: B,
            }
        }
    }

    #[conformance_test]
    pub fn loaned_sample_has_default_constructed_user_header<Sut: Service>()
    -> core::result::Result<(), alloc::boxed::Box<dyn core::error::Error>> {
        type UserHeader = CustomUserHeader<89123, 98123891>;
        let service_name = generate_service_name();
        let config = testing::generate_isolated_config();
        let node = NodeBuilder::new().config(&config).create::<Sut>().unwrap();
        let service = node
            .service_builder(&service_name)
            .publish_subscribe::<u64>()
            .user_header::<UserHeader>()
            .create()?;

        let sut = service.publisher_builder().create()?;
        let sample = sut.loan()?;

        assert_that!(*sample.user_header(), eq UserHeader::default());

        Ok(())
    }

    #[conformance_test]
    pub fn uninitialized_loaned_sample_has_default_constructed_user_header<Sut: Service>()
    -> core::result::Result<(), alloc::boxed::Box<dyn core::error::Error>> {
        type UserHeader = CustomUserHeader<87718, 9851291>;
        let service_name = generate_service_name();
        let config = testing::generate_isolated_config();
        let node = NodeBuilder::new().config(&config).create::<Sut>().unwrap();
        let service = node
            .service_builder(&service_name)
            .publish_subscribe::<u64>()
            .user_header::<UserHeader>()
            .create()?;

        let sut = service.publisher_builder().create()?;
        let sample = sut.loan_uninit()?;

        assert_that!(*sample.user_header(), eq UserHeader::default());

        Ok(())
    }

    #[conformance_test]
    pub fn loaned_slice_sample_has_default_constructed_user_header<Sut: Service>()
    -> core::result::Result<(), alloc::boxed::Box<dyn core::error::Error>> {
        type UserHeader = CustomUserHeader<3387718, 985129331>;
        let service_name = generate_service_name();
        let config = testing::generate_isolated_config();
        let node = NodeBuilder::new().config(&config).create::<Sut>().unwrap();
        let service = node
            .service_builder(&service_name)
            .publish_subscribe::<[u64]>()
            .user_header::<UserHeader>()
            .create()?;

        let sut = service.publisher_builder().create()?;
        let sample = sut.loan_slice(1)?;

        assert_that!(*sample.user_header(), eq UserHeader::default());

        Ok(())
    }

    #[conformance_test]
    pub fn uninitialized_loaned_slice_sample_has_default_constructed_user_header<Sut: Service>()
    -> core::result::Result<(), alloc::boxed::Box<dyn core::error::Error>> {
        type UserHeader = CustomUserHeader<312312, 92221>;
        let service_name = generate_service_name();
        let config = testing::generate_isolated_config();
        let node = NodeBuilder::new().config(&config).create::<Sut>().unwrap();
        let service = node
            .service_builder(&service_name)
            .publish_subscribe::<[u64]>()
            .user_header::<UserHeader>()
            .create()?;

        let sut = service.publisher_builder().create()?;
        let sample = sut.loan_slice_uninit(1)?;

        assert_that!(*sample.user_header(), eq UserHeader::default());

        Ok(())
    }

    #[conformance_test]
    pub fn backpressure_strategy_blocks_when_subscriber_buffer_is_full<Sut: Service>()
    -> core::result::Result<(), alloc::boxed::Box<dyn core::error::Error>> {
        let _watchdog = Watchdog::new();
        let service_name = generate_service_name();
        let config = testing::generate_isolated_config();
        let handle = MutexHandle::new();
        let node = MutexBuilder::new()
            .create(
                NodeBuilder::new().config(&config).create::<Sut>().unwrap(),
                &handle,
            )
            .unwrap();
        let service = node
            .lock()
            .unwrap()
            .service_builder(&service_name)
            .publish_subscribe::<u64>()
            .subscriber_max_buffer_size(1)
            .enable_safe_overflow(false)
            .create()?;

        let sut = service
            .publisher_builder()
            .backpressure_strategy(BackpressureStrategy::RetryUntilDelivered)
            .create()?;
        assert_that!(sut.backpressure_strategy(), eq BackpressureStrategy::RetryUntilDelivered);

        let handle = BarrierHandle::new();
        let barrier = BarrierBuilder::new(2).create(&handle).unwrap();

        thread_scope(|s| {
            s.thread_builder().spawn(|| {
                let service = node
                    .lock()
                    .unwrap()
                    .service_builder(&service_name)
                    .publish_subscribe::<u64>()
                    .subscriber_max_buffer_size(1)
                    .open()
                    .unwrap();

                let subscriber = service.subscriber_builder().create().unwrap();
                let receive_sample = || loop {
                    if let Some(sample) = subscriber.receive().unwrap() {
                        return sample;
                    }
                };

                barrier.wait();
                nanosleep(TIMEOUT).unwrap();
                let sample_1 = receive_sample();
                nanosleep(TIMEOUT).unwrap();
                let sample_2 = receive_sample();

                assert_that!(*sample_1, eq 8192);
                assert_that!(*sample_2, eq 2);
            })?;

            barrier.wait();
            let now = Time::now().unwrap();
            sut.send_copy(8192).unwrap();
            sut.send_copy(2).unwrap();
            assert_that!(now.elapsed().unwrap(), time_at_least TIMEOUT);

            Ok(())
        })
        .unwrap();

        Ok(())
    }

    #[conformance_test]
    pub fn backpressure_strategy_block_unblock_when_subscriber_disconnects<Sut: Service>()
    -> core::result::Result<(), alloc::boxed::Box<dyn core::error::Error>> {
        let _watchdog = Watchdog::new();
        let service_name = generate_service_name();
        let config = testing::generate_isolated_config();
        let handle = MutexHandle::new();
        let node = MutexBuilder::new()
            .create(
                NodeBuilder::new().config(&config).create::<Sut>().unwrap(),
                &handle,
            )
            .unwrap();
        let service = node
            .lock()
            .unwrap()
            .service_builder(&service_name)
            .publish_subscribe::<u64>()
            .subscriber_max_buffer_size(1)
            .enable_safe_overflow(false)
            .create()?;
        let subscriber = service.subscriber_builder().create().unwrap();

        let init_handle = BarrierHandle::new();
        let init_barrier = BarrierBuilder::new(2).create(&init_handle).unwrap();
        let start_handle = BarrierHandle::new();
        let start_barrier = BarrierBuilder::new(2).create(&start_handle).unwrap();
        let has_finished_send_thread = AtomicBool::new(false);

        thread_scope(|s| {
            s.thread_builder().spawn(|| {
                let service = node
                    .lock()
                    .unwrap()
                    .service_builder(&service_name)
                    .publish_subscribe::<u64>()
                    .open()
                    .unwrap();

                let sut = service
                    .publisher_builder()
                    .backpressure_strategy(BackpressureStrategy::RetryUntilDelivered)
                    .create()
                    .unwrap();

                init_barrier.wait();

                start_barrier.wait();

                sut.send_copy(123).unwrap();
                sut.send_copy(456).unwrap();
                has_finished_send_thread.store(true, Ordering::Relaxed);
            })?;

            init_barrier.wait();
            subscriber.update_connections().unwrap();

            start_barrier.wait();
            nanosleep(TIMEOUT * 10).unwrap();
            assert_that!(has_finished_send_thread.load(Ordering::Relaxed), eq false);
            drop(subscriber);

            Ok(())
        })
        .unwrap();

        Ok(())
    }

    #[conformance_test]
    pub fn create_error_display_works<S: Service>() {
        assert_that!(
            format!("{}", PublisherCreateError::ExceedsMaxSupportedPublishers), eq "PublisherCreateError::ExceedsMaxSupportedPublishers");
        assert_that!(
            format!("{}", PublisherCreateError::UnableToCreateDataSegment), eq "PublisherCreateError::UnableToCreateDataSegment");
    }

    #[conformance_test]
    pub fn loan_error_display_works<S: Service>() {
        assert_that!(
            format!("{}", LoanError::OutOfMemory), eq "LoanError::OutOfMemory");
        assert_that!(
            format!("{}", LoanError::ExceedsMaxLoans), eq "LoanError::ExceedsMaxLoans");
        assert_that!(
            format!("{}", LoanError::ExceedsMaxLoanSize), eq "LoanError::ExceedsMaxLoanSize");
        assert_that!(
            format!("{}", LoanError::InternalFailure), eq "LoanError::InternalFailure");
    }

    #[conformance_test]
    pub fn id_is_unique<Sut: Service>() {
        let service_name = generate_service_name();
        let config = testing::generate_isolated_config();
        let node = NodeBuilder::new().config(&config).create::<Sut>().unwrap();
        const MAX_PUBLISHERS: usize = 8;

        let sut = node
            .service_builder(&service_name)
            .publish_subscribe::<u64>()
            .max_publishers(MAX_PUBLISHERS)
            .create()
            .unwrap();

        let mut publishers = vec![];
        let mut publisher_id_set = BTreeSet::new();

        for _ in 0..MAX_PUBLISHERS {
            let publisher = sut.publisher_builder().create().unwrap();
            assert_that!(publisher_id_set.insert(publisher.id()), eq true);
            publishers.push(publisher);
        }
    }

    #[conformance_test]
    pub fn publisher_with_custom_payload_details_adjusts_slice_len<Sut: Service>()
    -> core::result::Result<(), alloc::boxed::Box<dyn core::error::Error>> {
        const TYPE_SIZE_OVERRIDE: usize = 128;
        let service_name = generate_service_name();
        let config = testing::generate_isolated_config();
        let node = NodeBuilder::new().config(&config).create::<Sut>().unwrap();
        let mut type_detail = TypeDetail::new::<u8>(TypeVariant::FixedSize);
        testing::type_detail_set_size(&mut type_detail, TYPE_SIZE_OVERRIDE);

        let service = unsafe {
            node.service_builder(&service_name)
                .publish_subscribe::<[CustomPayloadMarker]>()
                .user_header::<CustomHeaderMarker>()
                .__internal_set_payload_type_details(&type_detail)
                .create()?
        };

        let sut = service.publisher_builder().create()?;

        let sample = unsafe { sut.loan_custom_payload(1)? };

        assert_that!(sample.payload(), len TYPE_SIZE_OVERRIDE);

        Ok(())
    }

    #[conformance_test]
    #[should_panic]
    #[cfg(debug_assertions)]
    pub fn publisher_with_custom_payload_details_panics_when_calling_loan_slice_uninit<
        Sut: Service,
    >() {
        const TYPE_SIZE_OVERRIDE: usize = 128;
        let service_name = generate_service_name();
        let config = testing::generate_isolated_config();
        let node = NodeBuilder::new().config(&config).create::<Sut>().unwrap();
        let mut type_detail = TypeDetail::new::<u8>(TypeVariant::FixedSize);
        testing::type_detail_set_size(&mut type_detail, TYPE_SIZE_OVERRIDE);

        let service = unsafe {
            node.service_builder(&service_name)
                .publish_subscribe::<[CustomPayloadMarker]>()
                .__internal_set_payload_type_details(&type_detail)
                .create()
                .unwrap()
        };

        let sut = service.publisher_builder().create().unwrap();

        // panics here
        let _sample = sut.loan_slice_uninit(1);
    }

    #[conformance_test]
    #[should_panic]
    #[cfg(debug_assertions)]
    pub fn custom_fixed_size_payload_panics_when_loaning_more_than_one_element<Sut: Service>() {
        set_log_level(LogLevel::Error);
        let service_name = generate_service_name();
        let config = testing::generate_isolated_config();
        let node = NodeBuilder::new().config(&config).create::<Sut>().unwrap();
        let type_details = TypeDetail::new::<u8>(TypeVariant::FixedSize);

        let service = unsafe {
            node.service_builder(&service_name)
                .publish_subscribe::<[CustomPayloadMarker]>()
                .user_header::<CustomHeaderMarker>()
                .__internal_set_payload_type_details(&type_details)
                .create()
                .unwrap()
        };

        let sut = service.publisher_builder().create().unwrap();

        let _sample = unsafe { sut.loan_custom_payload(2) };
    }

    #[conformance_test]
    pub fn reclaims_all_samples_when_subscriber_is_disconnected_and_never_received_them<
        Sut: Service,
    >() -> core::result::Result<(), alloc::boxed::Box<dyn core::error::Error>> {
        const MAX_SUBSCRIBERS: usize = 4;
        const ITERATIONS: usize = 20;
        const SUBSCRIBER_BUFFER_SIZE: usize = 7;
        let service_name = generate_service_name();
        let config = testing::generate_isolated_config();
        let node = NodeBuilder::new().config(&config).create::<Sut>().unwrap();
        let service = node
            .service_builder(&service_name)
            .publish_subscribe::<u64>()
            .max_subscribers(MAX_SUBSCRIBERS)
            .subscriber_max_buffer_size(SUBSCRIBER_BUFFER_SIZE)
            .create()?;

        let sut = service.publisher_builder().max_loaned_samples(2).create()?;

        for n in 0..MAX_SUBSCRIBERS {
            for _ in 0..ITERATIONS {
                let mut subscribers = vec![];

                for _ in 0..n {
                    subscribers.push(service.subscriber_builder().create()?);
                }

                for _ in 0..SUBSCRIBER_BUFFER_SIZE {
                    sut.send_copy(1293)?;
                }

                // disconnect all subscribers
                drop(subscribers);
            }
        }

        let sample = sut.loan()?;

        assert_that!(sample.send(), is_ok);

        Ok(())
    }

    #[conformance_test]
    pub fn publisher_allocation_strategy_default_is_taken_from_config<Sut: Service>()
    -> core::result::Result<(), alloc::boxed::Box<dyn core::error::Error>> {
        let service_name = generate_service_name();
        let mut config = testing::generate_isolated_config();
        config
            .defaults
            .publish_subscribe
            .publisher_allocation_strategy = AllocationStrategy::PowerOfTwo;

        let node = NodeBuilder::new().config(&config).create::<Sut>().unwrap();
        let service = node
            .service_builder(&service_name)
            .publish_subscribe::<[u64]>()
            .create()?;

        let publisher = service
            .publisher_builder()
            .initial_max_slice_len(4)
            .create()?;

        let sample = publisher.loan_slice(5)?;
        assert_that!(sample.payload().len(), eq 5);

        Ok(())
    }
}
