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
// Copyright (c) Zefchain Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use rand::{Rng, SeedableRng};

// The following seed is chosen to have equal numbers of 1s and 0s, as advised by
// https://docs.rs/rand/latest/rand/rngs/struct.SmallRng.html
// Specifically, it's "01" × 32 in binary
const RNG_SEED: u64 = 6148914691236517205;

/// A deterministic RNG.
pub type DeterministicRng = rand::rngs::SmallRng;

/// A RNG that is non-deterministic if the platform supports it.
pub struct NonDeterministicRng(
    #[cfg(target_arch = "wasm32")] std::sync::MutexGuard<'static, DeterministicRng>,
    #[cfg(not(target_arch = "wasm32"))] rand::rngs::ThreadRng,
);

impl NonDeterministicRng {
    /// Access the internal RNG.
    pub fn rng_mut(&mut self) -> &mut impl Rng {
        #[cfg(target_arch = "wasm32")]
        {
            &mut *self.0
        }
        #[cfg(not(target_arch = "wasm32"))]
        {
            &mut self.0
        }
    }
}

/// Returns a deterministic RNG for testing.
pub fn make_deterministic_rng() -> DeterministicRng {
    rand::rngs::SmallRng::seed_from_u64(RNG_SEED)
}

/// Returns a non-deterministic RNG where supported.
pub fn make_nondeterministic_rng() -> NonDeterministicRng {
    #[cfg(target_arch = "wasm32")]
    {
        use std::sync::{Mutex, OnceLock};

        use rand::rngs::SmallRng;

        static RNG: OnceLock<Mutex<SmallRng>> = OnceLock::new();
        NonDeterministicRng(
            RNG.get_or_init(|| Mutex::new(make_deterministic_rng()))
                .lock()
                .expect("failed to lock RNG mutex"),
        )
    }

    #[cfg(not(target_arch = "wasm32"))]
    {
        NonDeterministicRng(rand::thread_rng())
    }
}

/// Get a random alphanumeric string that can be used for all tests.
pub fn generate_random_alphanumeric_string(length: usize, charset: &[u8]) -> String {
    (0..length)
        .map(|_| {
            let random_index = make_nondeterministic_rng()
                .rng_mut()
                .gen_range(0..charset.len());
            charset[random_index] as char
        })
        .collect()
}

/// Returns a unique namespace for testing.
pub fn generate_test_namespace() -> String {
    // Define the characters that are allowed in the alphanumeric string
    let charset: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyz";
    let entry = generate_random_alphanumeric_string(20, charset);
    let namespace = format!("table_{}", entry);
    tracing::warn!("Generating namespace={}", namespace);
    namespace
}