tests.rs 11.8 KB
Newer Older
Ryan Olson's avatar
Ryan Olson committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
// SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

//! Shared test utilities and fixtures for block pool testing.

use super::super::{
    blocks::{state::*, *},
    pools::*,
    testing::{self, TestMeta},
};

/// Re-export TestMeta as TestData for backward compatibility
pub type TestData = TestMeta;

#[cfg(test)]
#[allow(unused, dead_code)]
pub(crate) mod fixtures {
    use super::*;

    use dynamo_tokens::TokenBlock;
    use std::sync::Arc;

    // Re-export from testing module with TestData specialization
    pub use super::testing::tokens_for_id;

    pub fn create_reset_block(id: BlockId) -> Block<TestData, Reset> {
        testing::create_reset_block::<TestData>(id, 4)
    }

    pub fn create_reset_blocks(count: usize) -> Vec<Block<TestData, Reset>> {
        testing::create_reset_blocks::<TestData>(count, 4)
    }

    pub fn create_token_block(tokens: &[u32], block_size: u32) -> TokenBlock {
        testing::create_test_token_block(tokens, block_size)
    }

    pub fn create_complete_block(id: BlockId, tokens: &[u32]) -> Block<TestData, Staged> {
        testing::create_staged_block::<TestData>(id, tokens)
    }

    pub fn create_registered_block(
        id: BlockId,
        tokens: &[u32],
    ) -> (Block<TestData, Registered>, SequenceHash) {
        testing::create_registered_block::<TestData>(id, tokens)
    }

    pub fn create_test_reset_pool(count: usize) -> ResetPool<TestData> {
        testing::TestPoolSetupBuilder::default()
            .block_count(count)
            .build()
            .unwrap()
            .build_reset_pool::<TestData>()
    }

    pub fn create_test_registered_pool() -> (InactivePool<TestData>, ResetPool<TestData>) {
        testing::TestPoolSetupBuilder::default()
            .build()
            .unwrap()
            .build_pools::<TestData>()
    }

    /// Type alias for TestBlockBuilder specialized to TestData
    pub type TestBlockBuilder = testing::TestBlockBuilder<TestData>;

    /// Type alias for BlockSequenceBuilder specialized to TestData
    pub type BlockSequenceBuilder = testing::BlockSequenceBuilder<TestData>;
}

#[cfg(test)]
use fixtures::*;

#[test]
fn test_fill_iota_default_block_size() {
    let block = TestBlockBuilder::new(1).fill_iota(100).build_staged();

    assert_eq!(block.block_id(), 1);
    assert_eq!(block.block_size(), 4);
}

#[test]
fn test_fill_iota_custom_block_size() {
    let block = TestBlockBuilder::new(2)
        .with_block_size(8)
        .fill_iota(200)
        .build_staged();

    assert_eq!(block.block_id(), 2);
    assert_eq!(block.block_size(), 8);
}

#[test]
fn test_with_tokens_overrides_fill_iota() {
    let custom_tokens = vec![99, 98, 97, 96];
    let block = TestBlockBuilder::new(3)
        .fill_iota(100) // This should be overridden
        .with_tokens(custom_tokens)
        .build_staged();

    assert_eq!(block.block_id(), 3);
    assert_eq!(block.block_size(), 4);
}

#[test]
fn test_fill_iota_overrides_with_tokens() {
    let block = TestBlockBuilder::new(4)
        .with_tokens(vec![1, 2, 3, 4]) // This should be overridden
        .fill_iota(500)
        .build_staged();

    assert_eq!(block.block_id(), 4);
    assert_eq!(block.block_size(), 4);
}

#[test]
fn test_block_sequence_from_tokens() {
    let tokens = vec![100, 101, 102, 103, 104, 105, 106, 107]; // 2 blocks of size 4
    let blocks = BlockSequenceBuilder::from_tokens(tokens)
        .with_block_size(4)
        .with_salt(42)
        .build();

    assert_eq!(blocks.len(), 2);
    assert_eq!(blocks[0].0.block_id(), 0);
    assert_eq!(blocks[1].0.block_id(), 1);
    assert_eq!(blocks[0].0.block_size(), 4);
    assert_eq!(blocks[1].0.block_size(), 4);
}

#[test]
fn test_block_sequence_individual_mode() {
    let blocks = BlockSequenceBuilder::new()
        .add_block_with(1, |b| b.fill_iota(100))
        .add_block_with(2, |b| b.fill_iota(200))
        .add_block(3)
        .build();

    assert_eq!(blocks.len(), 3);
    assert_eq!(blocks[0].0.block_id(), 1);
    assert_eq!(blocks[1].0.block_id(), 2);
    assert_eq!(blocks[2].0.block_id(), 3);
}

#[test]
#[should_panic(expected = "Token count 7 must be divisible by block size 4")]
fn test_block_sequence_invalid_token_count() {
    let tokens = vec![1, 2, 3, 4, 5, 6, 7]; // 7 tokens, not divisible by 4
    BlockSequenceBuilder::from_tokens(tokens)
        .with_block_size(4)
        .build();
}

#[test]
fn test_block_sequence_custom_block_size() {
    let tokens: Vec<u32> = (0..16).collect(); // 2 blocks of size 8
    let blocks = BlockSequenceBuilder::from_tokens(tokens)
        .with_block_size(8)
        .build();

    assert_eq!(blocks.len(), 2);
    assert_eq!(blocks[0].0.block_size(), 8);
    assert_eq!(blocks[1].0.block_size(), 8);
}

#[test]
fn test_mutable_block_complete_error_returns_block() {
    use crate::blocks::BlockError;

    let reset_pool = create_test_reset_pool(5);
    let mut mutable_blocks = reset_pool.allocate_blocks(1);
    let mutable_block = mutable_blocks.pop().unwrap();
    let original_block_id = mutable_block.block_id();

    // block_size is 4, but token block has 8 tokens
    let big_token_block = testing::create_test_token_block(&[1, 2, 3, 4, 5, 6, 7, 8], 8);

    let result = mutable_block.complete(&big_token_block);
    assert!(result.is_err());

    match result {
        Err(BlockError::BlockSizeMismatch {
            expected,
            actual,
            block: recovered_block,
        }) => {
            assert_eq!(expected, 4);
            assert_eq!(actual, 8);
            // Block is recoverable from the error
            assert_eq!(recovered_block.block_id(), original_block_id);
        }
        _ => panic!("Expected BlockSizeMismatch error"),
    }
}

#[test]
fn test_mutable_block_stage_and_debug() {
    let reset_pool = create_test_reset_pool(5);
    let mut mutable_blocks = reset_pool.allocate_blocks(1);
    let mutable_block = mutable_blocks.pop().unwrap();

    // Exercise Debug for MutableBlock
    let debug_str = format!("{:?}", mutable_block);
    assert!(debug_str.contains("MutableBlock"));

    // Exercise the `stage` method (bypass block_size check)
    let seq_hash = crate::KvbmSequenceHashProvider::kvbm_sequence_hash(
        &testing::create_test_token_block(&[10, 11, 12, 13], 4),
    );
    let complete_block = mutable_block
        .stage(seq_hash, 4)
        .expect("block size should match");
    assert_eq!(complete_block.sequence_hash(), seq_hash);
}

#[test]
fn test_complete_block_reset() {
    let reset_pool = create_test_reset_pool(5);
    let mut mutable_blocks = reset_pool.allocate_blocks(1);
    let mutable_block = mutable_blocks.pop().unwrap();
    let original_block_id = mutable_block.block_id();

    let token_block = create_token_block(&[10, 11, 12, 13], 4);
    let complete_block = mutable_block
        .complete(&token_block)
        .expect("Should complete");

    assert_eq!(complete_block.block_id(), original_block_id);

    // Reset the complete block back to a mutable block
    let reset_mutable = complete_block.reset();
    assert_eq!(reset_mutable.block_id(), original_block_id);
}

#[test]
fn test_immutable_block_downgrade_and_upgrade() {
    let manager = testing::create_test_manager::<TestData>(10);

    let token_block = testing::create_iota_token_block(100, 4);
    let seq_hash = crate::KvbmSequenceHashProvider::kvbm_sequence_hash(&token_block);

    let mutable_blocks = manager.allocate_blocks(1).expect("Should allocate");
    let complete_block = mutable_blocks
        .into_iter()
        .next()
        .unwrap()
        .complete(&token_block)
        .expect("Should complete");

    let immutable_blocks = manager.register_blocks(vec![complete_block]);
    let immutable_block = immutable_blocks.into_iter().next().unwrap();

    // Check accessors
    assert_eq!(immutable_block.sequence_hash(), seq_hash);
    let _block_id = immutable_block.block_id();
    let _handle = immutable_block.registration_handle();
    assert!(immutable_block.use_count() >= 1);

    // Downgrade to WeakBlock
    let weak_block = immutable_block.downgrade();
    assert_eq!(weak_block.sequence_hash(), seq_hash);

    // Upgrade while original is alive — should succeed via direct Weak path
    let upgraded = weak_block
        .upgrade()
        .expect("Should upgrade while original alive");
    assert_eq!(upgraded.sequence_hash(), seq_hash);
    assert_eq!(upgraded.block_id(), immutable_block.block_id());
}

#[test]
fn test_weak_block_upgrade_via_upgrade_fn() {
    let manager = testing::create_test_manager::<TestData>(10);

    let token_block = testing::create_iota_token_block(200, 4);
    let seq_hash = crate::KvbmSequenceHashProvider::kvbm_sequence_hash(&token_block);

    // Create weak block, then drop the original
    let weak_block = {
        let mutable_blocks = manager.allocate_blocks(1).expect("Should allocate");
        let complete_block = mutable_blocks
            .into_iter()
            .next()
            .unwrap()
            .complete(&token_block)
            .expect("Should complete");
        let immutable_blocks = manager.register_blocks(vec![complete_block]);
        let immutable_block = immutable_blocks.into_iter().next().unwrap();
        immutable_block.downgrade()
    }; // original dropped — block returns to inactive pool

    // Upgrade should succeed via the upgrade_fn path (finds block in inactive pool)
    let upgraded = weak_block
        .upgrade()
        .expect("upgrade should succeed via upgrade_fn");
    assert_eq!(upgraded.sequence_hash(), seq_hash);
}

#[test]
fn test_immutable_and_weak_block_debug() {
    let manager = testing::create_test_manager::<TestData>(10);

    let token_block = testing::create_iota_token_block(300, 4);
    let _seq_hash = crate::KvbmSequenceHashProvider::kvbm_sequence_hash(&token_block);

    let mutable_blocks = manager.allocate_blocks(1).expect("Should allocate");
    let complete_block = mutable_blocks
        .into_iter()
        .next()
        .unwrap()
        .complete(&token_block)
        .expect("Should complete");

    let immutable_blocks = manager.register_blocks(vec![complete_block]);
    let immutable_block = immutable_blocks.into_iter().next().unwrap();

    // Exercise Debug for ImmutableBlock
    let debug_str = format!("{:?}", immutable_block);
    assert!(debug_str.contains("ImmutableBlock"));

    // Exercise Debug for WeakBlock
    let weak_block = immutable_block.downgrade();
    let weak_debug_str = format!("{:?}", weak_block);
    assert!(weak_debug_str.contains("WeakBlock"));
}

#[test]
fn test_weak_block_upgrade_fails_when_evicted() {
    let manager = testing::create_test_manager::<TestData>(10);

    let token_block = testing::create_test_token_block(&[999, 998, 997, 996], 4);

    let weak_block = {
        let mutable_blocks = manager.allocate_blocks(1).expect("Should allocate");
        let complete_block = mutable_blocks
            .into_iter()
            .next()
            .unwrap()
            .complete(&token_block)
            .expect("Should complete");
        let immutable_blocks = manager.register_blocks(vec![complete_block]);
        let immutable_block = immutable_blocks.into_iter().next().unwrap();
        immutable_block.downgrade()
    };

    // Fill up the pool with other blocks to force eviction of original
    for i in 0..10 {
        let tokens = vec![1000 + i, 1001 + i, 1002 + i, 1003 + i];
        let tb = testing::create_test_token_block(&tokens, 4);
        let mutable_blocks = manager.allocate_blocks(1).expect("Should allocate");
        let complete_block = mutable_blocks
            .into_iter()
            .next()
            .unwrap()
            .complete(&tb)
            .expect("Should complete");
        let _immutable = manager.register_blocks(vec![complete_block]);
    }

    // Upgrade should fail since original was evicted
    let result = weak_block.upgrade();
    assert!(result.is_none(), "Upgrade should fail after eviction");
}